debounce-mac/debounce.m

153 lines
4.0 KiB
Mathematica
Raw Normal View History

2019-07-03 14:01:35 +03:00
// compile and run from the command-line with:
2016-10-31 11:05:48 +03:00
// clang -fobjc-arc -framework Cocoa ./debounce.m -o debounce
// sudo ./debounce
2016-10-31 13:12:39 +03:00
/*
* Keyboard debouncer, main audience is users of flaky mechanical
* keyboards. Script heavily inspired by Brad Allred's answer on
* StackOverflow:
* <http://stackoverflow.com/questions/19646108/modify-keydown-output>.
*/
#import <Foundation/Foundation.h>
#import <AppKit/NSEvent.h>
#define DEBOUNCE_DELAY 100
#define SYNTHETIC_KB_ID 666
typedef CFMachPortRef EventTap;
@interface KeyChanger : NSObject
{
@private
2016-10-31 10:56:03 +03:00
EventTap _eventTap;
CFRunLoopSourceRef _runLoopSource;
long long lastKeytime;
UInt16 lastKeycode;
}
@end
CGEventRef _tapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, KeyChanger* listener);
@implementation KeyChanger
- (BOOL)tapEvents
{
2016-10-31 10:56:03 +03:00
if (!_eventTap) {
NSLog(@"Initializing an event tap.");
_eventTap = CGEventTapCreate(kCGSessionEventTap,
kCGTailAppendEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(kCGEventKeyDown),
(CGEventTapCallBack)_tapCallback,
(__bridge void *)(self));
if (!_eventTap) {
2019-07-03 14:01:35 +03:00
NSLog(@"Unable to create event tap. Must run as root or add Accessibility privileges to this app.");
2016-10-31 10:56:03 +03:00
return NO;
}
2016-10-31 10:56:03 +03:00
}
CGEventTapEnable(_eventTap, TRUE);
2016-10-31 10:56:03 +03:00
return [self isTapActive];
}
- (BOOL)isTapActive
{
2016-10-31 10:56:03 +03:00
return CGEventTapIsEnabled(_eventTap);
}
- (void)listen
{
2016-10-31 10:56:03 +03:00
if (!_runLoopSource) {
2016-10-31 11:05:48 +03:00
if (_eventTap) { // Don't use [self tapActive]
2016-10-31 10:56:03 +03:00
_runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault,
_eventTap, 0);
// Add to the current run loop.
CFRunLoopAddSource(CFRunLoopGetCurrent(), _runLoopSource,
kCFRunLoopCommonModes);
NSLog(@"Registering event tap as run loop source.");
CFRunLoopRun();
}else{
2016-10-31 11:05:48 +03:00
NSLog(@"No Event tap in place! You will need to call listen after tapEvents to get events.");
}
2016-10-31 10:56:03 +03:00
}
}
- (CGEventRef)processEvent:(CGEventRef)cgEvent
{
2016-10-31 10:56:03 +03:00
NSEvent* event = [NSEvent eventWithCGEvent:cgEvent];
2016-10-31 10:56:03 +03:00
long long currentKeytime = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
UInt16 currentKeycode = [event keyCode];
BOOL debounce = NO;
long long keyboard_id = CGEventGetIntegerValueField(cgEvent, kCGKeyboardEventKeyboardType);
2016-10-31 10:56:03 +03:00
if (keyboard_id != SYNTHETIC_KB_ID &&
currentKeycode == lastKeycode &&
![event isARepeat] &&
(currentKeytime - lastKeytime) < DEBOUNCE_DELAY) {
2016-10-31 10:56:03 +03:00
NSLog(@"BOUNCE detected!!! Character: %@",
event.characters);
NSLog(@"Time between keys: %lldms (limit <%dms)",
(currentKeytime - lastKeytime),
DEBOUNCE_DELAY);
2016-10-31 10:56:03 +03:00
// Cancel keypress event
debounce = YES;
2016-10-31 10:56:03 +03:00
}
if(debounce) {
return NULL;
}
lastKeytime = currentKeytime;
lastKeycode = currentKeycode;
return cgEvent;
}
- (void)dealloc
{
2016-10-31 10:56:03 +03:00
if (_runLoopSource){
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes);
CFRelease(_runLoopSource);
}
if (_eventTap){
2016-10-31 11:05:48 +03:00
// Kill the event tap
2016-10-31 10:56:03 +03:00
CGEventTapEnable(_eventTap, FALSE);
CFRelease(_eventTap);
}
}
@end
CGEventRef _tapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, KeyChanger* listener) {
2016-10-31 11:05:48 +03:00
// Do not make the NSEvent here.
// NSEvent will throw an exception if we try to make an event from the tap timeout type
2016-10-31 10:56:03 +03:00
@autoreleasepool {
if(type == kCGEventTapDisabledByTimeout) {
2019-07-03 14:01:35 +03:00
NSLog(@"Event tap has timed out, re-enabling tap.");
2016-10-31 10:56:03 +03:00
[listener tapEvents];
return nil;
}
if (type != kCGEventTapDisabledByUserInput) {
return [listener processEvent:event];
}
2016-10-31 10:56:03 +03:00
}
return event;
}
int main(int argc, const char * argv[])
{
2016-10-31 10:56:03 +03:00
@autoreleasepool {
KeyChanger* keyChanger = [KeyChanger new];
[keyChanger tapEvents];
2016-10-31 11:05:48 +03:00
[keyChanger listen]; // This is a blocking call.
2016-10-31 10:56:03 +03:00
}
return 0;
}