Skip to content

Commit 1025e8f

Browse files
committed
Fix ghost keystrokes after app exits
Instead of using a null EventTap to do a pre-flight check for permissions, using a dedicated EventTap for KeyDown events serves the same purpose and fixes the issue. Also convert KCEventTap to ARC. [fixes #72] [fixes #311]
1 parent cf95a5c commit 1025e8f

2 files changed

Lines changed: 74 additions & 79 deletions

File tree

keycastr/KCEventTap.m

Lines changed: 72 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) 2009 Stephen Deken
2-
// Copyright (c) 2014-2023 Andrew Kitchen
2+
// Copyright (c) 2014-2024 Andrew Kitchen
33
//
44
// All rights reserved.
55
//
@@ -26,16 +26,20 @@
2626
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
2727
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2828

29+
#if !__has_feature(objc_arc)
30+
#error "ARC is required for this file -- enable with -fobjc-arc"
31+
#endif
2932

3033
#import "KCEventTap.h"
3134
#import "KCKeystroke.h"
3235
#import "KCKeycastrEvent.h"
3336
#import "KCMouseEvent.h"
3437

3538
@interface KCEventTap () {
36-
CFMachPortRef eventTap;
37-
CFRunLoopRef eventTapRunLoop;
38-
CFRunLoopSourceRef eventTapEventSource;
39+
CFMachPortRef keyEventTap;
40+
CFMachPortRef mouseAndFlagsEventTap;
41+
CFRunLoopSourceRef keyEventTapSource;
42+
CFRunLoopSourceRef mouseAndFlagsEventTapSource;
3943
}
4044

4145
- (void)_noteMouseEvent:(CGEventRef)eventRef;
@@ -44,22 +48,33 @@ - (void)_noteFlagsChanged:(CGEventRef)event;
4448

4549
@end
4650

47-
CGEventRef nullEventTapCallback(
51+
CGEventRef keyEventTapCallback(
4852
CGEventTapProxy proxy,
4953
CGEventType type,
5054
CGEventRef event,
51-
void *vp)
55+
void *context)
5256
{
53-
return NULL;
57+
KCEventTap *eventTap = (__bridge KCEventTap *)context;
58+
switch (type)
59+
{
60+
case kCGEventKeyDown:
61+
[eventTap _noteKeyEvent:event];
62+
break;
63+
case kCGEventKeyUp:
64+
break;
65+
default:
66+
break;
67+
}
68+
return event;
5469
}
5570

56-
CGEventRef eventTapCallback(
71+
CGEventRef mouseAndFlagsEventTapCallback(
5772
CGEventTapProxy proxy,
5873
CGEventType type,
5974
CGEventRef event,
60-
void *vp)
75+
void *context)
6176
{
62-
KCEventTap* keyTap = (KCEventTap*)vp;
77+
KCEventTap *eventTap = (__bridge KCEventTap *)context;
6378
switch (type)
6479
{
6580
case kCGEventLeftMouseDown:
@@ -71,24 +86,19 @@ CGEventRef eventTapCallback(
7186
case kCGEventOtherMouseDown:
7287
case kCGEventOtherMouseUp:
7388
case kCGEventOtherMouseDragged:
74-
[keyTap _noteMouseEvent:event];
75-
break;
76-
case kCGEventKeyDown:
77-
[keyTap _noteKeyEvent:event];
89+
[eventTap _noteMouseEvent:event];
7890
break;
7991
case kCGEventFlagsChanged:
80-
[keyTap _noteFlagsChanged:event];
92+
[eventTap _noteFlagsChanged:event];
8193
break;
8294
default:
8395
break;
8496
}
85-
return NULL;
97+
return event;
8698
}
8799

88100
@implementation KCEventTap
89101

90-
@synthesize delegate = _delegate;
91-
92102
-(id) init
93103
{
94104
if (!(self = [super init]))
@@ -101,8 +111,6 @@ - (void)dealloc {
101111
if (_tapInstalled) {
102112
[self removeTap];
103113
}
104-
105-
[super dealloc];
106114
}
107115

108116
-(NSError*) constructErrorWithDescription:(NSString*)description {
@@ -121,69 +129,52 @@ -(BOOL) installTapWithError:(NSError **)error {
121129
// We have to try to tap the keydown event independently because CGEventTapCreate will succeed if it can
122130
// install the event tap for the flags changed event, which apparently doesn't require universal access
123131
// to be enabled. Thus, the call would succeed but KeyCastr would be, um, useless.
124-
CFMachPortRef tapKeyDown = CGEventTapCreate(
125-
kCGSessionEventTap,
126-
kCGHeadInsertEventTap,
127-
kCGEventTapOptionListenOnly,
128-
CGEventMaskBit(kCGEventKeyDown),
129-
nullEventTapCallback,
130-
self
131-
);
132-
133-
if (tapKeyDown == NULL) {
134-
if (error != NULL) {
135-
*error = [self constructErrorWithDescription:@"Could not create keyDown event tap!"];
136-
}
137-
return NO;
138-
}
139-
CFRelease( tapKeyDown );
140-
141-
eventTap = CGEventTapCreate(
142-
kCGSessionEventTap,
143-
kCGHeadInsertEventTap,
144-
kCGEventTapOptionListenOnly,
145-
CGEventMaskBit(kCGEventLeftMouseDown)
146-
| CGEventMaskBit(kCGEventLeftMouseUp)
147-
| CGEventMaskBit(kCGEventRightMouseDown)
148-
| CGEventMaskBit(kCGEventRightMouseUp)
149-
| CGEventMaskBit(kCGEventLeftMouseDragged)
150-
| CGEventMaskBit(kCGEventRightMouseDragged)
151-
| CGEventMaskBit(kCGEventKeyDown)
152-
| CGEventMaskBit(kCGEventFlagsChanged)
153-
| CGEventMaskBit(kCGEventOtherMouseDown)
154-
| CGEventMaskBit(kCGEventOtherMouseUp)
155-
| CGEventMaskBit(kCGEventOtherMouseDragged),
156-
eventTapCallback,
157-
self
158-
);
159-
160-
if (eventTap == NULL) {
132+
keyEventTap = CGEventTapCreate(kCGSessionEventTap,
133+
kCGHeadInsertEventTap,
134+
kCGEventTapOptionListenOnly,
135+
CGEventMaskBit(kCGEventKeyDown)
136+
| CGEventMaskBit(kCGEventKeyUp),
137+
keyEventTapCallback,
138+
(__bridge void *)self
139+
);
140+
141+
if (keyEventTap == NULL) {
161142
if (error != NULL) {
162-
*error = [self constructErrorWithDescription:@"Could not create keyDown|flagsChanged event tap!"];
143+
*error = [self constructErrorWithDescription:@"Could not create key event tap! Permissions needed..."];
163144
}
164145
return NO;
165146
}
166147

167-
eventTapEventSource = CFMachPortCreateRunLoopSource(NULL, eventTap, 0);
168-
if (eventTapEventSource == NULL) {
169-
CFRelease(eventTap);
170-
if (error != NULL) {
171-
*error = [self constructErrorWithDescription:@"Could not create run loop source!"];
172-
}
173-
return NO;
174-
}
148+
mouseAndFlagsEventTap = CGEventTapCreate(kCGSessionEventTap,
149+
kCGHeadInsertEventTap,
150+
kCGEventTapOptionListenOnly,
151+
CGEventMaskBit(kCGEventLeftMouseDown)
152+
| CGEventMaskBit(kCGEventLeftMouseUp)
153+
| CGEventMaskBit(kCGEventRightMouseDown)
154+
| CGEventMaskBit(kCGEventRightMouseUp)
155+
| CGEventMaskBit(kCGEventLeftMouseDragged)
156+
| CGEventMaskBit(kCGEventRightMouseDragged)
157+
| CGEventMaskBit(kCGEventFlagsChanged)
158+
| CGEventMaskBit(kCGEventOtherMouseDown)
159+
| CGEventMaskBit(kCGEventOtherMouseUp)
160+
| CGEventMaskBit(kCGEventOtherMouseDragged),
161+
mouseAndFlagsEventTapCallback,
162+
(__bridge void *)self
163+
);
175164

176-
eventTapRunLoop = CFRunLoopGetCurrent();
177-
if (eventTapRunLoop == NULL) {
178-
CFRelease(eventTapEventSource);
179-
CFRelease(eventTap);
165+
if (mouseAndFlagsEventTap == NULL) {
180166
if (error != NULL) {
181-
*error = [self constructErrorWithDescription:@"Could not get current run loop!"];
167+
*error = [self constructErrorWithDescription:@"Could not create mouse and modifiers event tap!"];
182168
}
183169
return NO;
184170
}
185171

186-
CFRunLoopAddSource(eventTapRunLoop, eventTapEventSource, kCFRunLoopDefaultMode);
172+
keyEventTapSource = CFMachPortCreateRunLoopSource(NULL, keyEventTap, 0);
173+
mouseAndFlagsEventTapSource = CFMachPortCreateRunLoopSource(NULL, mouseAndFlagsEventTap, 0);
174+
175+
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
176+
CFRunLoopAddSource(runLoop, keyEventTapSource, kCFRunLoopDefaultMode);
177+
CFRunLoopAddSource(runLoop, mouseAndFlagsEventTapSource, kCFRunLoopDefaultMode);
187178

188179
_tapInstalled = YES;
189180

@@ -194,10 +185,15 @@ -(void) removeTap {
194185
if (!_tapInstalled) {
195186
return;
196187
}
197-
198-
CFRunLoopRemoveSource(eventTapRunLoop, eventTapEventSource, kCFRunLoopDefaultMode);
199-
CFRelease(eventTapEventSource);
200-
CFRelease(eventTap);
188+
189+
CFRunLoopSourceInvalidate(keyEventTapSource);
190+
CFRunLoopSourceInvalidate(mouseAndFlagsEventTapSource);
191+
192+
CFRelease(keyEventTapSource);
193+
CFRelease(mouseAndFlagsEventTapSource);
194+
195+
CFRelease(keyEventTap);
196+
CFRelease(mouseAndFlagsEventTap);
201197

202198
_tapInstalled = NO;
203199
}

keycastr/KeyCastr.xcodeproj/project.pbxproj

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
3D3F52A70F30BF1E001C7272 /* KeyCastr.icns in Resources */ = {isa = PBXBuildFile; fileRef = 3D3F52A60F30BF1E001C7272 /* KeyCastr.icns */; };
3838
3D3F52BA0F30C68B001C7272 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D3F52B90F30C68B001C7272 /* Quartz.framework */; };
3939
3D3F52EB0F30CB13001C7272 /* KCPrefsWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D3F52EA0F30CB13001C7272 /* KCPrefsWindowController.m */; };
40-
3D3F540C0F30E8E7001C7272 /* KCEventTap.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D3F540B0F30E8E7001C7272 /* KCEventTap.m */; };
40+
3D3F540C0F30E8E7001C7272 /* KCEventTap.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D3F540B0F30E8E7001C7272 /* KCEventTap.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; };
4141
3D3F548A0F30F37E001C7272 /* GeneralIcon.tif in Resources */ = {isa = PBXBuildFile; fileRef = 3D3F54870F30F37E001C7272 /* GeneralIcon.tif */; };
4242
3D3F548B0F30F37E001C7272 /* DisplayIcon.tif in Resources */ = {isa = PBXBuildFile; fileRef = 3D3F54880F30F37E001C7272 /* DisplayIcon.tif */; };
4343
3D3F548E0F30F3C7001C7272 /* UpdateIcon.tif in Resources */ = {isa = PBXBuildFile; fileRef = 3D3F548D0F30F3C7001C7272 /* UpdateIcon.tif */; };
@@ -796,7 +796,6 @@
796796
ProvisioningStyle = Automatic;
797797
};
798798
8D1107260486CEB800E47090 = {
799-
DevelopmentTeam = 68TUCLNAPS;
800799
ProvisioningStyle = Automatic;
801800
};
802801
AFC172182377CFF500292155 = {
@@ -1521,7 +1520,7 @@
15211520
PRODUCT_BUNDLE_IDENTIFIER = io.github.keycastr;
15221521
PRODUCT_NAME = KeyCastr;
15231522
PROVISIONING_PROFILE_SPECIFIER = "KeyCastr Developer ID";
1524-
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "KeyCastr Developer ID";
1523+
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "KeyCastr Developer ID Distribution";
15251524
WRAPPER_EXTENSION = app;
15261525
};
15271526
name = Release;

0 commit comments

Comments
 (0)