-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathdebounce.m
152 lines (126 loc) · 3.98 KB
/
debounce.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// compile and run from the command-line with:
// clang -fobjc-arc -framework Cocoa ./debounce.m -o debounce
// sudo ./debounce
/*
* 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
EventTap _eventTap;
CFRunLoopSourceRef _runLoopSource;
long long lastKeytime;
UInt16 lastKeycode;
}
@end
CGEventRef _tapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, KeyChanger* listener);
@implementation KeyChanger
- (BOOL)tapEvents
{
if (!_eventTap) {
NSLog(@"Initializing an event tap.");
_eventTap = CGEventTapCreate(kCGSessionEventTap,
kCGTailAppendEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(kCGEventKeyDown),
(CGEventTapCallBack)_tapCallback,
(__bridge void *)(self));
if (!_eventTap) {
NSLog(@"Unable to create event tap. Must run as root or add Accessibility privileges to this app.");
return NO;
}
}
CGEventTapEnable(_eventTap, TRUE);
return [self isTapActive];
}
- (BOOL)isTapActive
{
return CGEventTapIsEnabled(_eventTap);
}
- (void)listen
{
if (!_runLoopSource) {
if (_eventTap) { // Don't use [self tapActive]
_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{
NSLog(@"No Event tap in place! You will need to call listen after tapEvents to get events.");
}
}
}
- (CGEventRef)processEvent:(CGEventRef)cgEvent
{
NSEvent* event = [NSEvent eventWithCGEvent:cgEvent];
long long currentKeytime = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
UInt16 currentKeycode = [event keyCode];
BOOL debounce = NO;
long long keyboard_id = CGEventGetIntegerValueField(cgEvent, kCGKeyboardEventKeyboardType);
if (keyboard_id != SYNTHETIC_KB_ID &&
currentKeycode == lastKeycode &&
![event isARepeat] &&
(currentKeytime - lastKeytime) < DEBOUNCE_DELAY) {
NSLog(@"BOUNCE detected!!! Character: %@",
event.characters);
NSLog(@"Time between keys: %lldms (limit <%dms)",
(currentKeytime - lastKeytime),
DEBOUNCE_DELAY);
// Cancel keypress event
debounce = YES;
}
if(debounce) {
return NULL;
}
lastKeytime = currentKeytime;
lastKeycode = currentKeycode;
return cgEvent;
}
- (void)dealloc
{
if (_runLoopSource){
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes);
CFRelease(_runLoopSource);
}
if (_eventTap){
// Kill the event tap
CGEventTapEnable(_eventTap, FALSE);
CFRelease(_eventTap);
}
}
@end
CGEventRef _tapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, KeyChanger* listener) {
// Do not make the NSEvent here.
// NSEvent will throw an exception if we try to make an event from the tap timeout type
@autoreleasepool {
if(type == kCGEventTapDisabledByTimeout) {
NSLog(@"Event tap has timed out, re-enabling tap.");
[listener tapEvents];
return nil;
}
if (type != kCGEventTapDisabledByUserInput) {
return [listener processEvent:event];
}
}
return event;
}
int main(int argc, const char * argv[])
{
@autoreleasepool {
KeyChanger* keyChanger = [KeyChanger new];
[keyChanger tapEvents];
[keyChanger listen]; // This is a blocking call.
}
return 0;
}