From 9e63a77cdc62a2595e1f4b91c449ac2db7b673da Mon Sep 17 00:00:00 2001
From: _sam <3804518+aahung@users.noreply.github.com>
Date: Mon, 19 Nov 2018 16:37:25 -0800
Subject: [PATCH] Add a workaround for #1, improved on #19
---
Unshaky/Info.plist | 4 +-
Unshaky/Preference.storyboard | 57 +++++++++++-------
Unshaky/PreferenceViewController.swift | 5 +-
Unshaky/ShakyPressPreventer.h | 3 +-
Unshaky/ShakyPressPreventer.m | 69 ++++++++++++++++++----
UnshakyTests/ShakyPressPreventerSpec.swift | 50 ++++++++++++++--
6 files changed, 145 insertions(+), 43 deletions(-)
diff --git a/Unshaky/Info.plist b/Unshaky/Info.plist
index 0b94d23..b510e87 100644
--- a/Unshaky/Info.plist
+++ b/Unshaky/Info.plist
@@ -17,9 +17,9 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 0.2.6
+ 0.3
CFBundleVersion
- 14
+ 17
LSMinimumSystemVersion
$(MACOSX_DEPLOYMENT_TARGET)
LSUIElement
diff --git a/Unshaky/Preference.storyboard b/Unshaky/Preference.storyboard
index 621e2fd..acfa683 100644
--- a/Unshaky/Preference.storyboard
+++ b/Unshaky/Preference.storyboard
@@ -1,8 +1,8 @@
-
+
-
+
@@ -34,20 +34,20 @@
-
+
-
+
-
+
-
+
-
+
@@ -93,7 +93,7 @@
-
+
@@ -107,11 +107,11 @@
-
+
-
+
@@ -143,7 +143,7 @@
-
+
@@ -151,15 +151,15 @@
-
+
-
+
-
+
@@ -167,7 +167,7 @@
-
+
@@ -176,7 +176,7 @@
+
-
+
-
+
+
+
@@ -264,6 +277,7 @@
+
@@ -271,6 +285,7 @@
+
@@ -289,7 +304,7 @@
-
+
diff --git a/Unshaky/PreferenceViewController.swift b/Unshaky/PreferenceViewController.swift
index 0dfa365..87a7ea6 100644
--- a/Unshaky/PreferenceViewController.swift
+++ b/Unshaky/PreferenceViewController.swift
@@ -68,7 +68,10 @@ class PreferenceViewController: NSViewController,
ShakyPressPreventer.sharedInstance()?.loadIgnoreExternalKeyboard()
}
-
+ @IBAction func workaroundForCmdSpaceToggled(_ sender: Any) {
+ ShakyPressPreventer.sharedInstance()?.loadWorkaroundForCmdSpace()
+ }
+
// this list credits to the answer at https://stackoverflow.com/a/36901239/2361752
let keyCodeToString = [29: " 0",
18: " 1",
diff --git a/Unshaky/ShakyPressPreventer.h b/Unshaky/ShakyPressPreventer.h
index 9bb1787..728370a 100644
--- a/Unshaky/ShakyPressPreventer.h
+++ b/Unshaky/ShakyPressPreventer.h
@@ -23,7 +23,8 @@ typedef void (^Handler)(void);
- (void)shakyPressDismissed:(Handler)handler;
- (void)loadKeyDelays;
- (void)loadIgnoreExternalKeyboard;
+- (void)loadWorkaroundForCmdSpace;
// This initWithKeyDelays:ignoreExternalKeyboard: is used for testing purpose
-- (instancetype)initWithKeyDelays:(int*)keyDelays_ ignoreExternalKeyboard:(BOOL)ignoreExternalKeyboard_;
+- (instancetype)initWithKeyDelays:(int*)keyDelays_ ignoreExternalKeyboard:(BOOL)ignoreExternalKeyboard_ workaroundForCmdSpace:(BOOL)workaroundForCmdSpace_;
@end
diff --git a/Unshaky/ShakyPressPreventer.m b/Unshaky/ShakyPressPreventer.m
index 77fcec2..593dd1c 100644
--- a/Unshaky/ShakyPressPreventer.m
+++ b/Unshaky/ShakyPressPreventer.m
@@ -12,6 +12,11 @@
@implementation ShakyPressPreventer {
NSTimeInterval lastPressedTimestamps[128];
CGEventType lastPressedEventTypes[128];
+
+ CGEventFlags lastEventFlagsAboutModifierKeysForSpace;
+ BOOL cmdSpaceAllowance;
+ BOOL workaroundForCmdSpace;
+
BOOL dismissNextEvent[128];
int keyDelays[128];
BOOL ignoreExternalKeyboard;
@@ -32,6 +37,7 @@ - (instancetype)init {
if (self = [super init]) {
[self loadKeyDelays];
[self loadIgnoreExternalKeyboard];
+ [self loadWorkaroundForCmdSpace];
for (int i = 0; i < 128; ++i) {
lastPressedTimestamps[i] = 0.0;
lastPressedEventTypes[i] = 0;
@@ -42,9 +48,10 @@ - (instancetype)init {
}
// This initWithKeyDelays:ignoreExternalKeyboard: is used for testing purpose
-- (instancetype)initWithKeyDelays:(int*)keyDelays_ ignoreExternalKeyboard:(BOOL)ignoreExternalKeyboard_ {
+- (instancetype)initWithKeyDelays:(int*)keyDelays_ ignoreExternalKeyboard:(BOOL)ignoreExternalKeyboard_ workaroundForCmdSpace:(BOOL)workaroundForCmdSpace_ {
if (self = [super init]) {
ignoreExternalKeyboard = ignoreExternalKeyboard_;
+ workaroundForCmdSpace = workaroundForCmdSpace_;
for (int i = 0; i < 128; ++i) {
keyDelays[i] = keyDelays_[i];
lastPressedTimestamps[i] = 0.0;
@@ -70,6 +77,11 @@ - (void)loadIgnoreExternalKeyboard {
ignoreExternalKeyboard = [defaults boolForKey:@"ignoreExternalKeyboard"]; // default No
}
+- (void)loadWorkaroundForCmdSpace {
+ NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
+ workaroundForCmdSpace = [defaults boolForKey:@"workaroundForCmdSpace"]; // default No
+}
+
- (CGEventRef)filterShakyPressEvent:(CGEventRef)event {
// keyboard type, dismiss if it is not built-in keyboard
if (ignoreExternalKeyboard) {
@@ -84,8 +96,33 @@ - (CGEventRef)filterShakyPressEvent:(CGEventRef)event {
if (keyDelays[keyCode] == 0) return event;
CGEventType eventType = CGEventGetType(event);
-
+ CGEventFlags eventFlagsAboutModifierKeys = (kCGEventFlagMaskShift | kCGEventFlagMaskControl |
+ kCGEventFlagMaskAlternate | kCGEventFlagMaskCommand |
+ kCGEventFlagMaskSecondaryFn) & CGEventGetFlags(event);
+ double currentTimestamp = [[NSDate date] timeIntervalSince1970];
+
if (lastPressedTimestamps[keyCode] != 0.0) {
+ /** @ghost711: CMD+Space was pressed, which causes a duplicate pair of down/up
+ keyEvents to occur 1-5 msecs after the "real" pair of events.
+ - If the CMD key is released first, it will look like:
+ CMD+Space Down
+ Space Up
+ CMD+Space Down
+ CMD+Space Up
+ - Whereas if the space bar is released first, it will be:
+ CMD+Space Down
+ CMD+Space Up
+ CMD+Space Down
+ CMD+Space Up
+ - The issue only appears to happen with CMD+Space,
+ not CMD+, or +Space.*/
+ // So here we allow one double-press to slip away
+
+ // reset allowance to 1
+ if (keyCode == 49 && eventFlagsAboutModifierKeys && 1000 * (currentTimestamp - lastPressedTimestamps[keyCode]) >= keyDelays[keyCode]) {
+ cmdSpaceAllowance = YES;
+ }
+
if (dismissNextEvent[keyCode]) {
// dismiss the corresponding keyup event
NSLog(@"DISMISSING KEYUP:%d", keyCode);
@@ -96,20 +133,28 @@ - (CGEventRef)filterShakyPressEvent:(CGEventRef)event {
if (eventType == kCGEventKeyDown
&& lastPressedEventTypes[keyCode] == kCGEventKeyUp
&& 1000 * ([[NSDate date] timeIntervalSince1970] - lastPressedTimestamps[keyCode]) < keyDelays[keyCode]) {
- // dismiss the keydown event if it follows keyup event too soon
- NSLog(@"DISMISSING KEYDOWN:%d", keyCode);
- if (_debugTextView != nil) [self appendToDebugTextView:[NSString stringWithFormat:@"%f\t Key(%d)\t Event(%d) DISMISSED\n", [[NSDate date] timeIntervalSince1970], keyCode, eventType]];
-
- if (shakyPressDismissedHandler != nil) {
- shakyPressDismissedHandler();
+
+ // let it slip away if allowance is 1 for CMD+SPACE
+ if (keyCode == 49 && lastEventFlagsAboutModifierKeysForSpace &&
+ eventFlagsAboutModifierKeys && workaroundForCmdSpace && cmdSpaceAllowance) {
+ cmdSpaceAllowance = NO;
+ } else {
+ // dismiss the keydown event if it follows keyup event too soon
+ NSLog(@"DISMISSING KEYDOWN:%d", keyCode);
+ if (_debugTextView != nil) [self appendToDebugTextView:[NSString stringWithFormat:@"%f\t Key(%d)\t Event(%d) DISMISSED\n", [[NSDate date] timeIntervalSince1970], keyCode, eventType]];
+
+ if (shakyPressDismissedHandler != nil) {
+ shakyPressDismissedHandler();
+ }
+ dismissNextEvent[keyCode] = YES;
+ return nil;
}
- dismissNextEvent[keyCode] = YES;
- return nil;
}
- }
+ } else if (keyCode == 49 && eventFlagsAboutModifierKeys) cmdSpaceAllowance = YES;
- lastPressedTimestamps[keyCode] = [[NSDate date] timeIntervalSince1970];
+ lastPressedTimestamps[keyCode] = currentTimestamp;
lastPressedEventTypes[keyCode] = eventType;
+ if (keyCode == 49) lastEventFlagsAboutModifierKeysForSpace = eventFlagsAboutModifierKeys;
if (_debugTextView != nil) [self appendToDebugTextView:[NSString stringWithFormat:@"%f\t Key(%d)\t Event(%d)\n", [[NSDate date] timeIntervalSince1970], keyCode, eventType]];
return event;
diff --git a/UnshakyTests/ShakyPressPreventerSpec.swift b/UnshakyTests/ShakyPressPreventerSpec.swift
index a82abbd..2c01bd9 100644
--- a/UnshakyTests/ShakyPressPreventerSpec.swift
+++ b/UnshakyTests/ShakyPressPreventerSpec.swift
@@ -138,7 +138,7 @@ class ShakyPressPreventerSpec: QuickSpec {
it(keyName) {
let keyDelays = UnsafeMutablePointer.allocate(capacity: 128)
keyDelays[_keyCode] = 400 // 400ms
- let preventer = ShakyPressPreventer(keyDelays: keyDelays, ignoreExternalKeyboard: false)!
+ let preventer = ShakyPressPreventer(keyDelays: keyDelays, ignoreExternalKeyboard: false, workaroundForCmdSpace: false)!
expect{preventer.filterShakyPress(CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: true)) != nil}.to(beTrue())
usleep(20000) // sleep for 20ms
expect{preventer.filterShakyPress(CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: false)) != nil}.to(beTrue())
@@ -156,7 +156,7 @@ class ShakyPressPreventerSpec: QuickSpec {
it(keyName) {
let keyDelays = UnsafeMutablePointer.allocate(capacity: 128)
keyDelays[_keyCode] = 400 // 400ms
- let preventer = ShakyPressPreventer(keyDelays: keyDelays, ignoreExternalKeyboard: false)!
+ let preventer = ShakyPressPreventer(keyDelays: keyDelays, ignoreExternalKeyboard: false, workaroundForCmdSpace: false)!
expect{preventer.filterShakyPress(CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: true)) != nil}.to(beTrue())
usleep(20000) // sleep for 20ms
expect{preventer.filterShakyPress(CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: false)) != nil}.to(beTrue())
@@ -168,7 +168,7 @@ class ShakyPressPreventerSpec: QuickSpec {
}
}
- describe("Should be able to prevent double press beyond m sec for configured selective normal keys with holding CMD") {
+ describe("Should be able to prevent double press within m sec for configured selective normal keys with holding CMD") {
let keyCodeToStringNormalSelective = [
49: "Space",
36: "Return"
@@ -186,13 +186,51 @@ class ShakyPressPreventerSpec: QuickSpec {
keyDownEvent?.flags = CGEventFlags.maskCommand
let keyUpEvent = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: false)
keyUpEvent?.flags = CGEventFlags.maskCommand
+
+ let preventer = ShakyPressPreventer(keyDelays: keyDelays, ignoreExternalKeyboard: false, workaroundForCmdSpace: false)!
+ expect{preventer.filterShakyPress(cmdKeyDownEvent) != nil}.to(beTrue())
+ usleep(20000) // sleep for 20ms
+ expect{preventer.filterShakyPress(keyDownEvent) != nil}.to(beTrue())
+ usleep(20000) // sleep for 20ms
+ expect{preventer.filterShakyPress(keyUpEvent) != nil}.to(beTrue())
+ usleep(41000) // sleep for 41ms, within 400ms
+ expect{preventer.filterShakyPress(keyDownEvent) == nil}.to(beTrue())
+ usleep(20000) // sleep for 20ms
+ expect{preventer.filterShakyPress(keyUpEvent) == nil}.to(beTrue())
+ usleep(20000) // sleep for 20ms
+ expect{preventer.filterShakyPress(cmdKeyUpEvent) != nil}.to(beTrue())
+ }
+ }
+ }
- let preventer = ShakyPressPreventer(keyDelays: keyDelays, ignoreExternalKeyboard: false)!
+ describe("Should be able to prevent 2nd double press within m sec for configured space key with holding CMD") {
+ let keyCodeToStringNormalSelective = [
+ 49: "Space"
+ ]
+ for (_keyCode, keyName) in keyCodeToStringNormalSelective {
+ let keyCode = CGKeyCode(_keyCode)
+ it(keyName) {
+ let keyDelays = UnsafeMutablePointer.allocate(capacity: 128)
+ keyDelays[_keyCode] = 400 // 400ms
+
+ let cmdKeyDownEvent = CGEvent(keyboardEventSource: nil, virtualKey: 55, keyDown: true)
+ let cmdKeyUpEvent = CGEvent(keyboardEventSource: nil, virtualKey: 55, keyDown: false)
+
+ let keyDownEvent = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: true)
+ keyDownEvent?.flags = CGEventFlags.maskCommand
+ let keyUpEvent = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: false)
+ keyUpEvent?.flags = CGEventFlags.maskCommand
+
+ let preventer = ShakyPressPreventer(keyDelays: keyDelays, ignoreExternalKeyboard: false, workaroundForCmdSpace: true)!
expect{preventer.filterShakyPress(cmdKeyDownEvent) != nil}.to(beTrue())
usleep(20000) // sleep for 20ms
expect{preventer.filterShakyPress(keyDownEvent) != nil}.to(beTrue())
usleep(20000) // sleep for 20ms
expect{preventer.filterShakyPress(keyUpEvent) != nil}.to(beTrue())
+ usleep(41000) // sleep for 41ms, within 400ms, but within allowance
+ expect{preventer.filterShakyPress(keyDownEvent) != nil}.to(beTrue())
+ usleep(20000) // sleep for 20ms
+ expect{preventer.filterShakyPress(keyUpEvent) != nil}.to(beTrue())
usleep(41000) // sleep for 41ms, within 400ms
expect{preventer.filterShakyPress(keyDownEvent) == nil}.to(beTrue())
usleep(20000) // sleep for 20ms
@@ -212,7 +250,7 @@ class ShakyPressPreventerSpec: QuickSpec {
keyDelays[i] = 0
}
keyDelays[_keyCode + 1] = 400 // 400ms
- let preventer = ShakyPressPreventer(keyDelays: keyDelays, ignoreExternalKeyboard: false)!
+ let preventer = ShakyPressPreventer(keyDelays: keyDelays, ignoreExternalKeyboard: false, workaroundForCmdSpace: false)!
expect{preventer.filterShakyPress(CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: true)) != nil}.to(beTrue())
usleep(20000) // sleep for 20ms
expect{preventer.filterShakyPress(CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: false)) != nil}.to(beTrue())
@@ -234,7 +272,7 @@ class ShakyPressPreventerSpec: QuickSpec {
it(keyName) {
let keyDelays = UnsafeMutablePointer.allocate(capacity: 128)
keyDelays[_keyCode] = 400 // 400ms
- let preventer = ShakyPressPreventer(keyDelays: keyDelays, ignoreExternalKeyboard: false)!
+ let preventer = ShakyPressPreventer(keyDelays: keyDelays, ignoreExternalKeyboard: false, workaroundForCmdSpace: false)!
expect{preventer.filterShakyPress(CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: true)) != nil}.to(beTrue())
usleep(20000) // sleep for 20ms
expect{preventer.filterShakyPress(CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: false)) != nil}.to(beTrue())