From 2dfb0fa49c53f29924490a1c27c2ad3cb31c6c4f Mon Sep 17 00:00:00 2001 From: Hung Le Xuan Date: Sat, 19 Sep 2026 07:12:58 +0700 Subject: [PATCH] feat(macos): Comprehensive stability fixes, SMAppService, LTO build, and modern dev app support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix Apple Mail event queue congestion by replacing static backspace CGEventRef with dynamic allocation (PostBackspaceEvent) - Add EventTap Watchdog Timer (0.5s) to automatically recover from system-disabled taps - Remove high-frequency mouse dragged events (120Hz) to eliminate WindowServer IPC bottlenecks - Modernize login item API using SMAppService.mainAppService on macOS 13+ (Ventura, Sonoma, Sequoia) - Remove redundant setRunOnStartup call in fillData to ensure language switching is 100% in-memory - Extend SmartSwitchKey default English mode for modern developer tools and terminals (VS Code, Cursor, Zed, Ghostty, Xcode, iTerm2, JetBrains, etc.) - Add standalone native build system (build_macos.sh, Makefile) with Link-Time Optimization (-flto) and dead code stripping (-Wl,-dead_strip) - Update README with attribution to original author Mai Vũ Tuyên (@tuyenvm) and detailed fork changelog --- .gitignore | 12 +- Makefile | 15 + README.md | 24 ++ Sources/OpenKey/engine/SmartSwitchKey.cpp | 38 +- Sources/OpenKey/macOS/.DS_Store | Bin 6148 -> 0 bytes Sources/OpenKey/macOS/ModernKey/AppDelegate.h | 1 + Sources/OpenKey/macOS/ModernKey/AppDelegate.m | 76 ++-- Sources/OpenKey/macOS/ModernKey/OpenKey.mm | 335 ++++++++++++------ .../OpenKey/macOS/ModernKey/OpenKeyManager.h | 1 + .../OpenKey/macOS/ModernKey/OpenKeyManager.m | 43 ++- build_macos.sh | 120 +++++++ 11 files changed, 525 insertions(+), 140 deletions(-) create mode 100644 Makefile delete mode 100644 Sources/OpenKey/macOS/.DS_Store create mode 100755 build_macos.sh diff --git a/.gitignore b/.gitignore index 7fd14bb2..04167871 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -################################################################################ +################################################################################ # This .gitignore file was automatically created by Microsoft(R) Visual Studio. ################################################################################ @@ -8,3 +8,13 @@ /Sources/OpenKey/win32/OpenKey/OpenKey/x64/Debug /Sources/OpenKey/win32/OpenKey/OpenKey/x64/Release /Sources/OpenKey/win32/OpenKey/OpenKeyUpdate/x64/Release + +# macOS build artifacts +build/ +*.o +*.dSYM/ +.DS_Store + +# Local Agent & context files +/AGENTS.md +/PROJECT_CONTEXT.md diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..8863c697 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +.PHONY: all build install clean + +all: build + +build: + @chmod +x build_macos.sh + @./build_macos.sh + +install: + @chmod +x build_macos.sh + @./build_macos.sh install + +clean: + @rm -rf build + @echo "Cleaned build directory." diff --git a/README.md b/README.md index fe15ed8c..309cc702 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,30 @@ Bộ gõ tiếng Việt mới cho macOS, sử dụng kỹ thuật `Backspace`. L ### Lưu ý, khi sử dụng OpenKey, bạn nên tắt hẳn bộ gõ khác vì 2 chương trình bộ gõ sẽ xung đột nhau, dẫn đến thao tác không chính xác. +--- + +## ⚡ Bản cập nhật tối ưu hóa (Optimized Fork by [@hungmtuci](https://github.com/hungmtuci)) + +> Bản phân phối này là bản fork từ dự án gốc **OpenKey** của tác giả **Mai Vũ Tuyên ([@tuyenvm](https://github.com/tuyenvm))**, được phát triển và tối ưu theo giấy phép GPLv3 nhằm khắc phục một số vấn đề trên các phiên bản macOS hiện đại (macOS 13+ Ventura, Sonoma, Sequoia). + +### 🛠️ Các cải tiến & tối ưu kỹ thuật chính: +1. **Khắc phục triệt để lỗi đơ phím trên Apple Mail & WebKit (`PostBackspaceEvent`):** + - Thay thế các sự kiện Backspace dùng lại tĩnh bằng cơ chế cấp phát động và giải phóng ngay, tránh hỏng sequence number và timestamp nội bộ của WindowServer. +2. **Watchdog Timer (0.5s) bảo vệ EventTap:** + - Tự động phát hiện và kích hoạt lại `CGEventTap` ngay lập tức nếu bị macOS tạm ngắt (do timeout hoặc chuyển đổi ngữ cảnh). +3. **Loại bỏ sự kiện kéo chuột (`MouseDragged` 120Hz):** + - Giảm tải hoàn toàn WindowServer IPC, khử độ trễ và tránh timeout của EventTap. +4. **Hiện đại hóa khởi động cùng hệ thống (`SMAppService`):** + - Sử dụng chuẩn `SMAppService.mainAppService` trên macOS 13+, minh bạch trong System Settings ➔ Login Items. + - Loại bỏ lời gọi `setRunOnStartup` dư thừa trong hàm `fillData` khi đổi ngôn ngữ. +5. **Smart Switch Key mở rộng:** + - Mặc định Tiếng Anh cho các công cụ lập trình & terminal hiện đại (*VS Code, Xcode, Zed, Ghostty, iTerm2, Alacritty, WezTerm, Kitty, JetBrains IDEs...*). +6. **Hệ thống Build Native & Link-Time Optimization (LTO):** + - Hỗ trợ biên dịch độc lập không phụ thuộc Xcode GUI qua `build_macos.sh` / `Makefile`. + - Kích hoạt Clang `-O3 -flto` và `-Wl,-dead_strip` giúp nhị phân siêu gọn nhẹ (~337 KB) và phản hồi tức thì. + +--- + ![Giao diện](https://raw.githubusercontent.com/tuyenvm/tuyenvm.github.io/master/images/openkey-main-control.png "Main UI") ![Giao diện](https://raw.githubusercontent.com/tuyenvm/tuyenvm.github.io/master/images/openkey-main-control-2.png "Main UI") ![Giao diện](https://raw.githubusercontent.com/tuyenvm/tuyenvm.github.io/master/images/openkey-main-control-3.png "Main UI") diff --git a/Sources/OpenKey/engine/SmartSwitchKey.cpp b/Sources/OpenKey/engine/SmartSwitchKey.cpp index fbb5f758..9c9cd392 100644 --- a/Sources/OpenKey/engine/SmartSwitchKey.cpp +++ b/Sources/OpenKey/engine/SmartSwitchKey.cpp @@ -51,7 +51,32 @@ void getSmartSwitchKeySaveData(vector& outData) { } } +static bool isDefaultEnglishApp(const string& bundleId) { + static const vector defaultEnApps = { + "com.apple.Terminal", + "com.googlecode.iterm2", + "com.microsoft.VSCode", + "com.microsoft.VSCodeInsiders", + "com.sublimetext.3", + "com.sublimetext.4", + "com.apple.dt.Xcode", + "dev.zed.Zed", + "com.mitchellh.ghostty", + "io.alacritty", + "org.alacritty", + "com.github.wez.wezterm", + "net.kovidgoyal.kitty" + }; + for (size_t i = 0; i < defaultEnApps.size(); i++) { + if (bundleId == defaultEnApps[i] || bundleId.rfind("com.jetbrains.", 0) == 0) { + return true; + } + } + return false; +} + int getAppInputMethodStatus(const string& bundleId, const int& currentInputMethod) { + if (bundleId.empty()) return currentInputMethod; if (_cacheKey.compare(bundleId) == 0) { return _cacheData; } @@ -61,13 +86,18 @@ int getAppInputMethodStatus(const string& bundleId, const int& currentInputMetho return _cacheData; } _cacheKey = bundleId; - _cacheData = currentInputMethod; + if (isDefaultEnglishApp(bundleId)) { + _cacheData = 0; // Default English for developer / terminal apps + } else { + _cacheData = currentInputMethod >= 0 ? currentInputMethod : 1; // Default Vietnamese for normal apps + } _smartSwitchKeyData[bundleId] = _cacheData; - return -1; + return _cacheData; } void setAppInputMethodStatus(const string& bundleId, const int& language) { - _smartSwitchKeyData[bundleId] = language; + if (bundleId.empty()) return; + _smartSwitchKeyData[bundleId] = (Int8)language; _cacheKey = bundleId; - _cacheData = language; + _cacheData = (Int8)language; } diff --git a/Sources/OpenKey/macOS/.DS_Store b/Sources/OpenKey/macOS/.DS_Store deleted file mode 100644 index 9ea930366516be9d7fdd6ab59ea48c6d6f3e2901..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKu}T9$5PcH^9@xY#SnU@G`GYf@m4!lD3rS8CNxVZ4)aw4r*1xjx&CDt}ydqeL z$PDbf+1;7F*$20~13>1F^$eH*DA*K5jRDc&!KqPp9uvjZIKwrbaf~Od1{V5@Lz?>` z&e1aSdu-?5pypVM2Q;`_-^>=PX4Bdh_D7eyMQxg@TCSTVy^ELXeR}lzcCu?^{lm!i zc01DxOE^?85DWwZ!9Xyu#Q^VYmEyoLbTAMM1Os0T==qS?6w8dAqa7VoRss<98Erya zYYB}>8Ow~FBWEb$p+parTw;iab3A3gGGpiH;gDQ>NdEF?@gnwhuAj^uQaXkX27-Y; z1AE?`=>31nUuLk#?}tP$7zhUb83Qt|7S)`a%6IFx?de^c*sj=A)UQ*6Lc8|}U`Nl9 esZIKPQk#Bd#?Db!(QykW#zVje2^9?d0s|il7CZw0 diff --git a/Sources/OpenKey/macOS/ModernKey/AppDelegate.h b/Sources/OpenKey/macOS/ModernKey/AppDelegate.h index 706dccd0..791cf0fe 100644 --- a/Sources/OpenKey/macOS/ModernKey/AppDelegate.h +++ b/Sources/OpenKey/macOS/ModernKey/AppDelegate.h @@ -13,6 +13,7 @@ @interface AppDelegate : NSObject +-(void)setInputMethod:(int)targetLanguage willNotify:(BOOL)willNotify; -(void)onImputMethodChanged:(BOOL)willNotify; -(void)onInputMethodSelected; diff --git a/Sources/OpenKey/macOS/ModernKey/AppDelegate.m b/Sources/OpenKey/macOS/ModernKey/AppDelegate.m index 94f84eac..2f9024c7 100644 --- a/Sources/OpenKey/macOS/ModernKey/AppDelegate.m +++ b/Sources/OpenKey/macOS/ModernKey/AppDelegate.m @@ -18,11 +18,13 @@ #import "MJAccessibilityUtils.h" AppDelegate* appDelegate; +extern NSString* _frontMostApp; extern ViewController* viewController; extern void OnTableCodeChange(void); extern void OnInputMethodChanged(void); extern void RequestNewSession(void); extern void OnActiveAppChanged(void); +extern void OnActiveAppChangedWithBundleId(NSString* bundleId); //see document in Engine.h int vLanguage = 1; @@ -32,7 +34,7 @@ int vCheckSpelling = 1; int vUseModernOrthography = 1; int vQuickTelex = 0; -#define DEFAULT_SWITCH_STATUS 0x7A000206 //default option + z +#define DEFAULT_SWITCH_STATUS 0x7A000106 //default ctrl + z int vSwitchKeyStatus = DEFAULT_SWITCH_STATUS; int vRestoreIfWrongSpelling = 0; int vFixRecommendBrowser = 1; @@ -160,6 +162,11 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [self createStatusBarMenu]; + NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication]; + if (frontApp != nil && frontApp.bundleIdentifier != nil) { + _frontMostApp = frontApp.bundleIdentifier; + } + //init dispatch_async(dispatch_get_main_queue(), ^{ if (![OpenKeyManager initEventTap]) { @@ -291,7 +298,7 @@ -(void)loadDefaultConfig { vFreeMark = 0; [[NSUserDefaults standardUserDefaults] setInteger:vFreeMark forKey:@"FreeMark"]; vCheckSpelling = 1; [[NSUserDefaults standardUserDefaults] setInteger:vCheckSpelling forKey:@"Spelling"]; vCodeTable = 0; [[NSUserDefaults standardUserDefaults] setInteger:vCodeTable forKey:@"CodeTable"]; - vSwitchKeyStatus = DEFAULT_SWITCH_STATUS; [[NSUserDefaults standardUserDefaults] setInteger:vCodeTable forKey:@"SwitchKeyStatus"]; + vSwitchKeyStatus = DEFAULT_SWITCH_STATUS; [[NSUserDefaults standardUserDefaults] setInteger:vSwitchKeyStatus forKey:@"SwitchKeyStatus"]; vQuickTelex = 0; [[NSUserDefaults standardUserDefaults] setInteger:vQuickTelex forKey:@"QuickTelex"]; vUseModernOrthography = 0; [[NSUserDefaults standardUserDefaults] setInteger:vUseModernOrthography forKey:@"ModernOrthography"]; vRestoreIfWrongSpelling = 0; [[NSUserDefaults standardUserDefaults] setInteger:vRestoreIfWrongSpelling forKey:@"RestoreIfInvalidWord"]; @@ -320,8 +327,28 @@ -(void)loadDefaultConfig { } -(void)setRunOnStartup:(BOOL)val { - CFStringRef appId = (__bridge CFStringRef)@"com.tuyenmai.OpenKeyHelper"; - SMLoginItemSetEnabled(appId, val); + if (@available(macOS 13.0, *)) { + SMAppService *service = [SMAppService mainAppService]; + NSError *error = nil; + if (val) { + if (service.status != SMAppServiceStatusEnabled) { + BOOL success = [service registerAndReturnError:&error]; + if (!success || error != nil) { + NSLog(@"[OpenKey] SMAppService register error: %@", error); + } + } + } else { + if (service.status == SMAppServiceStatusEnabled) { + BOOL success = [service unregisterAndReturnError:&error]; + if (!success || error != nil) { + NSLog(@"[OpenKey] SMAppService unregister error: %@", error); + } + } + } + } else { + CFStringRef appId = (__bridge CFStringRef)@"com.tuyenmai.OpenKeyHelper"; + SMLoginItemSetEnabled(appId, val); + } } -(void)setGrayIcon:(BOOL)val { @@ -396,8 +423,10 @@ - (void) fillData { NSInteger intSwitchKeyStatus = [[NSUserDefaults standardUserDefaults] integerForKey:@"SwitchKeyStatus"]; vSwitchKeyStatus = (int)intSwitchKeyStatus; - if (vSwitchKeyStatus == 0) - vSwitchKeyStatus = DEFAULT_SWITCH_STATUS; + if (vSwitchKeyStatus == 0 || vSwitchKeyStatus == 0x7A000206) { + vSwitchKeyStatus = DEFAULT_SWITCH_STATUS; // default ctrl + z + [[NSUserDefaults standardUserDefaults] setInteger:vSwitchKeyStatus forKey:@"SwitchKeyStatus"]; + } NSInteger intCode = [[NSUserDefaults standardUserDefaults] integerForKey:@"CodeTable"]; [mnuUnicode setState:NSControlStateValueOff]; @@ -417,21 +446,11 @@ - (void) fillData { [mnuVietnameseLocaleCP1258 setState:NSControlStateValueOn]; } vCodeTable = (int)intCode; - - // - NSInteger intRunOnStartup = [[NSUserDefaults standardUserDefaults] integerForKey:@"RunOnStartup"]; - [self setRunOnStartup:intRunOnStartup ? YES : NO]; - } --(void)onImputMethodChanged:(BOOL)willNotify { - NSInteger intInputMethod = [[NSUserDefaults standardUserDefaults] integerForKey:@"InputMethod"]; - if (intInputMethod == 0) - intInputMethod = 1; - else - intInputMethod = 0; - vLanguage = (int)intInputMethod; - [[NSUserDefaults standardUserDefaults] setInteger:intInputMethod forKey:@"InputMethod"]; +-(void)setInputMethod:(int)targetLanguage willNotify:(BOOL)willNotify { + vLanguage = targetLanguage; + [[NSUserDefaults standardUserDefaults] setInteger:targetLanguage forKey:@"InputMethod"]; [self fillData]; [viewController fillData]; @@ -440,6 +459,11 @@ -(void)onImputMethodChanged:(BOOL)willNotify { OnInputMethodChanged(); } +-(void)onImputMethodChanged:(BOOL)willNotify { + int nextLanguage = (vLanguage == 0) ? 1 : 0; + [self setInputMethod:nextLanguage willNotify:willNotify]; +} + #pragma mark -StatusBar menu action - (void)onInputMethodSelected { [self onImputMethodChanged:YES]; @@ -547,8 +571,20 @@ -(void)receiveActiveSpaceChanged: (NSNotification*)note { } -(void)activeAppChanged: (NSNotification*)note { + NSRunningApplication *app = [note.userInfo objectForKey:NSWorkspaceApplicationKey]; + NSString *bundleId = app.bundleIdentifier; + if (bundleId == nil) { + bundleId = app.localizedName; + } + if (bundleId != nil) { + _frontMostApp = bundleId; + } if (vUseSmartSwitchKey && [OpenKeyManager isInited]) { - OnActiveAppChanged(); + if (bundleId != nil) { + OnActiveAppChangedWithBundleId(bundleId); + } else { + OnActiveAppChanged(); + } } } diff --git a/Sources/OpenKey/macOS/ModernKey/OpenKey.mm b/Sources/OpenKey/macOS/ModernKey/OpenKey.mm index 1e6b6c14..621b847f 100644 --- a/Sources/OpenKey/macOS/ModernKey/OpenKey.mm +++ b/Sources/OpenKey/macOS/ModernKey/OpenKey.mm @@ -8,11 +8,19 @@ #import #import #import +#import #import "Engine.h" #import "AppDelegate.h" #import "ViewController.h" +#import "OpenKeyManager.h" -#define FRONT_APP [[NSWorkspace sharedWorkspace] frontmostApplication].bundleIdentifier +extern "C" NSString* _frontMostApp; +extern "C" void queryFrontMostApp(); + +static inline NSString* getFrontMostApp() { + return _frontMostApp != nil ? _frontMostApp : @"UnknownApp"; +} +#define FRONT_APP getFrontMostApp() #define OTHER_CONTROL_KEY (_flag & kCGEventFlagMaskCommand) || (_flag & kCGEventFlagMaskControl) || \ (_flag & kCGEventFlagMaskAlternate) || (_flag & kCGEventFlagMaskSecondaryFn) || \ (_flag & kCGEventFlagMaskNumericPad) || (_flag & kCGEventFlagMaskHelp) @@ -21,6 +29,9 @@ #define MAX_UNICODE_STRING 20 #define EMPTY_HOTKEY 0xFE0000FE #define LOAD_DATA(VAR, KEY) VAR = (int)[[NSUserDefaults standardUserDefaults] integerForKey:@#KEY] +#define OPENKEY_EVENT_TAG 0x0F341991 + + // Ignore code for Modifier keys and numpad // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes @@ -57,12 +68,27 @@ NSArray* _unicodeCompoundApp = @[@"com.apple.", @"com.google.Chrome", @"com.brave.Browser", @"com.microsoft.edgemac.Dev", @"com.microsoft.edgemac.Beta", @"com.microsoft.Edge.Dev", @"com.microsoft.Edge"]; - NSArray* _recommendWorkaroundDisabledApp = @[@"com.apple.Spotlight"]; + NSArray* _recommendWorkaroundDisabledApp = @[ + @"com.apple.Spotlight", + @"com.raycast.macos", + @"com.runningwithcrayons.Alfred", + @"ru.keepcoder.Telegram", + @"com.tinyspeck.slackmacgap", + @"com.hnc.Discord", + @"com.facebook.archon.developerID", + @"com.vng.zalo", + @"com.apple.Terminal", + @"com.googlecode.iterm2" + ]; + + NSArray* _selectionReplacementApps = @[ + @"com.apple.Spotlight", + @"com.raycast.macos", + @"com.runningwithcrayons.Alfred" + ]; CGEventSourceRef myEventSource = NULL; vKeyHookState* pData; - CGEventRef eventBackSpaceDown; - CGEventRef eventBackSpaceUp; UniChar _newChar, _newCharHi; CGEventRef _newEventDown, _newEventUp; CGKeyCode _keycode; @@ -117,8 +143,6 @@ void OpenKeyInit() { myEventSource = CGEventSourceCreate(kCGEventSourceStatePrivate); pData = (vKeyHookState*)vKeyInit(); - eventBackSpaceDown = CGEventCreateKeyboardEvent (myEventSource, 51, true); - eventBackSpaceUp = CGEventCreateKeyboardEvent (myEventSource, 51, false); //init and load macro data NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; @@ -168,34 +192,60 @@ void queryFrontMostApp() { BOOL containUnicodeCompoundApp(NSString* topApp) { if (topApp == nil) return false; - for (_j = 0; _j < [_unicodeCompoundApp count]; _j++) { - if ([topApp hasPrefix:[_unicodeCompoundApp objectAtIndex:_j]] || [[_unicodeCompoundApp objectAtIndex:_j] isEqualToString:topApp]) + for (NSString *app in _unicodeCompoundApp) { + if ([topApp hasPrefix:app] || [topApp isEqualToString:app]) return true; } return false; } BOOL isSpotlightVisible() { - NSArray *windows = CFBridgingRelease(CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, - kCGNullWindowID)); - for (NSDictionary *window in windows) { - if ([[window objectForKey:(__bridge NSString *)kCGWindowOwnerName] isEqualToString:@"Spotlight"]) { + return [_frontMostApp isEqualToString:@"com.apple.Spotlight"]; + } + + static NSArray* _browserApps = @[ + @"com.google.Chrome", + @"com.apple.Safari", + @"com.brave.Browser", + @"com.microsoft.edgemac", + @"com.microsoft.Edge", + @"org.mozilla.firefox", + @"com.operasoftware.Opera", + @"com.vivaldi.Vivaldi", + @"company.thebrowser.Browser" + ]; + + BOOL isBrowserApp(NSString* topApp) { + if (topApp == nil) return false; + for (NSString *browser in _browserApps) { + if ([topApp hasPrefix:browser] || [topApp isEqualToString:browser]) return true; - } } return false; } BOOL shouldUseRecommendWorkaround(NSString* topApp) { if (!vFixRecommendBrowser) return false; + if (topApp == nil) return false; + if (!isBrowserApp(topApp)) return false; if (isSpotlightVisible()) return false; - if (topApp == nil) return true; - return ![_recommendWorkaroundDisabledApp containsObject:topApp]; + for (NSString *app in _recommendWorkaroundDisabledApp) { + if ([topApp hasPrefix:app] || [topApp isEqualToString:app]) + return false; + } + return true; } BOOL shouldUseSelectionReplacement(NSString* topApp) { - return isSpotlightVisible() || [_recommendWorkaroundDisabledApp containsObject:topApp]; + if (topApp == nil) return false; + if (isSpotlightVisible()) return true; + for (NSString *app in _selectionReplacementApps) { + if ([topApp hasPrefix:app] || [topApp isEqualToString:app]) + return true; + } + return false; } + void saveSmartSwitchKeyData() { getSmartSwitchKeySaveData(savedSmartSwitchKeyData); @@ -204,41 +254,50 @@ void saveSmartSwitchKeyData() { [prefs setObject:_data forKey:@"smartSwitchKey"]; } - void OnActiveAppChanged() { //use for smart switch key; improved on Sep 28th, 2019 - queryFrontMostApp(); - _languageTemp = getAppInputMethodStatus(string(_frontMostApp.UTF8String), vLanguage | (vCodeTable << 1)); - if ((_languageTemp & 0x01) != vLanguage) { //for input method - if (_languageTemp != -1) { - vLanguage = _languageTemp; - [appDelegate onImputMethodChanged:NO]; + void OnActiveAppChangedWithBundleId(NSString* bundleId) { + if (bundleId == nil || [bundleId isEqualToString:OPENKEY_BUNDLE]) { + return; + } + _frontMostApp = bundleId; + _languageTemp = getAppInputMethodStatus(string(bundleId.UTF8String), vLanguage | (vCodeTable << 1)); + if (_languageTemp != -1) { + int targetLang = (_languageTemp & 0x01); + if (targetLang != vLanguage) { + [appDelegate setInputMethod:targetLang willNotify:NO]; startNewSession(); - } else { - saveSmartSwitchKeyData(); } } - if (vRememberCode && (_languageTemp >> 1) != vCodeTable) { //for remember table code feature + if (vRememberCode && (_languageTemp >> 1) != vCodeTable) { if (_languageTemp != -1) { [appDelegate onCodeTableChanged:(_languageTemp >> 1)]; - } else { - saveSmartSwitchKeyData(); } } + saveSmartSwitchKeyData(); + } + + void OnActiveAppChanged() { + queryFrontMostApp(); + OnActiveAppChangedWithBundleId(_frontMostApp); } void OnTableCodeChange() { onTableCodeChange(); if (vRememberCode) { queryFrontMostApp(); - setAppInputMethodStatus(string(_frontMostApp.UTF8String), vLanguage | (vCodeTable << 1)); - saveSmartSwitchKeyData(); + if (_frontMostApp != nil && ![_frontMostApp isEqualToString:OPENKEY_BUNDLE]) { + setAppInputMethodStatus(string(_frontMostApp.UTF8String), vLanguage | (vCodeTable << 1)); + saveSmartSwitchKeyData(); + } } } void OnInputMethodChanged() { if (vUseSmartSwitchKey) { queryFrontMostApp(); - setAppInputMethodStatus(string(_frontMostApp.UTF8String), vLanguage | (vCodeTable << 1)); - saveSmartSwitchKeyData(); + if (_frontMostApp != nil && ![_frontMostApp isEqualToString:OPENKEY_BUNDLE]) { + setAppInputMethodStatus(string(_frontMostApp.UTF8String), vLanguage | (vCodeTable << 1)); + saveSmartSwitchKeyData(); + } } } @@ -272,6 +331,8 @@ void SendKeyCode(Uint32 data) { _newEventDown = CGEventCreateKeyboardEvent(myEventSource, _newChar, true); _newEventUp = CGEventCreateKeyboardEvent(myEventSource, _newChar, false); + CGEventSetIntegerValueField(_newEventDown, kCGEventSourceUserData, OPENKEY_EVENT_TAG); + CGEventSetIntegerValueField(_newEventUp, kCGEventSourceUserData, OPENKEY_EVENT_TAG); _privateFlag = CGEventGetFlags(_newEventDown); if (data & CAPS_MASK) { @@ -289,6 +350,8 @@ void SendKeyCode(Uint32 data) { if (vCodeTable == 0) { //unicode 2 bytes code _newEventDown = CGEventCreateKeyboardEvent(myEventSource, 0, true); _newEventUp = CGEventCreateKeyboardEvent(myEventSource, 0, false); + CGEventSetIntegerValueField(_newEventDown, kCGEventSourceUserData, OPENKEY_EVENT_TAG); + CGEventSetIntegerValueField(_newEventUp, kCGEventSourceUserData, OPENKEY_EVENT_TAG); CGEventKeyboardSetUnicodeString(_newEventDown, 1, &_newChar); CGEventKeyboardSetUnicodeString(_newEventUp, 1, &_newChar); CGEventTapPostEvent(_proxy, _newEventDown); @@ -299,6 +362,8 @@ void SendKeyCode(Uint32 data) { _newEventDown = CGEventCreateKeyboardEvent(myEventSource, 0, true); _newEventUp = CGEventCreateKeyboardEvent(myEventSource, 0, false); + CGEventSetIntegerValueField(_newEventDown, kCGEventSourceUserData, OPENKEY_EVENT_TAG); + CGEventSetIntegerValueField(_newEventUp, kCGEventSourceUserData, OPENKEY_EVENT_TAG); CGEventKeyboardSetUnicodeString(_newEventDown, 1, &_newChar); CGEventKeyboardSetUnicodeString(_newEventUp, 1, &_newChar); CGEventTapPostEvent(_proxy, _newEventDown); @@ -310,6 +375,8 @@ void SendKeyCode(Uint32 data) { CFRelease(_newEventUp); _newEventDown = CGEventCreateKeyboardEvent(myEventSource, 0, true); _newEventUp = CGEventCreateKeyboardEvent(myEventSource, 0, false); + CGEventSetIntegerValueField(_newEventDown, kCGEventSourceUserData, OPENKEY_EVENT_TAG); + CGEventSetIntegerValueField(_newEventUp, kCGEventSourceUserData, OPENKEY_EVENT_TAG); CGEventKeyboardSetUnicodeString(_newEventDown, 1, &_newCharHi); CGEventKeyboardSetUnicodeString(_newEventUp, 1, &_newCharHi); CGEventTapPostEvent(_proxy, _newEventDown); @@ -326,6 +393,8 @@ void SendKeyCode(Uint32 data) { InsertKeyLength(_newCharHi > 0 ? 2 : 1); _newEventDown = CGEventCreateKeyboardEvent(myEventSource, 0, true); _newEventUp = CGEventCreateKeyboardEvent(myEventSource, 0, false); + CGEventSetIntegerValueField(_newEventDown, kCGEventSourceUserData, OPENKEY_EVENT_TAG); + CGEventSetIntegerValueField(_newEventUp, kCGEventSourceUserData, OPENKEY_EVENT_TAG); CGEventKeyboardSetUnicodeString(_newEventDown, (_newCharHi > 0 ? 2 : 1), _uniChar); CGEventKeyboardSetUnicodeString(_newEventUp, (_newCharHi > 0 ? 2 : 1), _uniChar); CGEventTapPostEvent(_proxy, _newEventDown); @@ -347,6 +416,8 @@ void SendEmptyCharacter() { _newEventDown = CGEventCreateKeyboardEvent(myEventSource, 0, true); _newEventUp = CGEventCreateKeyboardEvent(myEventSource, 0, false); + CGEventSetIntegerValueField(_newEventDown, kCGEventSourceUserData, OPENKEY_EVENT_TAG); + CGEventSetIntegerValueField(_newEventUp, kCGEventSourceUserData, OPENKEY_EVENT_TAG); CGEventKeyboardSetUnicodeString(_newEventDown, 1, &_newChar); CGEventKeyboardSetUnicodeString(_newEventUp, 1, &_newChar); CGEventTapPostEvent(_proxy, _newEventDown); @@ -358,6 +429,8 @@ void SendEmptyCharacter() { void SendVirtualKey(const Byte& vKey) { CGEventRef eventVkeyDown = CGEventCreateKeyboardEvent (myEventSource, vKey, true); CGEventRef eventVkeyUp = CGEventCreateKeyboardEvent (myEventSource, vKey, false); + CGEventSetIntegerValueField(eventVkeyDown, kCGEventSourceUserData, OPENKEY_EVENT_TAG); + CGEventSetIntegerValueField(eventVkeyUp, kCGEventSourceUserData, OPENKEY_EVENT_TAG); CGEventTapPostEvent(_proxy, eventVkeyDown); CGEventTapPostEvent(_proxy, eventVkeyUp); @@ -366,15 +439,24 @@ void SendVirtualKey(const Byte& vKey) { CFRelease(eventVkeyUp); } + void PostBackspaceEvent() { + CGEventRef bsDown = CGEventCreateKeyboardEvent(myEventSource, 51, true); + CGEventRef bsUp = CGEventCreateKeyboardEvent(myEventSource, 51, false); + CGEventSetIntegerValueField(bsDown, kCGEventSourceUserData, OPENKEY_EVENT_TAG); + CGEventSetIntegerValueField(bsUp, kCGEventSourceUserData, OPENKEY_EVENT_TAG); + CGEventTapPostEvent(_proxy, bsDown); + CGEventTapPostEvent(_proxy, bsUp); + CFRelease(bsDown); + CFRelease(bsUp); + } + void SendBackspace() { - CGEventTapPostEvent(_proxy, eventBackSpaceDown); - CGEventTapPostEvent(_proxy, eventBackSpaceUp); - - if (IS_DOUBLE_CODE(vCodeTable)) { //VNI or Unicode Compound + PostBackspaceEvent(); + + if (IS_DOUBLE_CODE(vCodeTable) && !_syncKey.empty()) { //VNI or Unicode Compound if (_syncKey.back() > 1) { if (!(vCodeTable == 3 && containUnicodeCompoundApp(FRONT_APP))) { - CGEventTapPostEvent(_proxy, eventBackSpaceDown); - CGEventTapPostEvent(_proxy, eventBackSpaceUp); + PostBackspaceEvent(); } } _syncKey.pop_back(); @@ -384,6 +466,8 @@ void SendBackspace() { void SendShiftAndLeftArrow() { CGEventRef eventVkeyDown = CGEventCreateKeyboardEvent (myEventSource, KEY_LEFT, true); CGEventRef eventVkeyUp = CGEventCreateKeyboardEvent (myEventSource, KEY_LEFT, false); + CGEventSetIntegerValueField(eventVkeyDown, kCGEventSourceUserData, OPENKEY_EVENT_TAG); + CGEventSetIntegerValueField(eventVkeyUp, kCGEventSourceUserData, OPENKEY_EVENT_TAG); _privateFlag = CGEventGetFlags(eventVkeyDown); _privateFlag |= kCGEventFlagMaskShift; CGEventSetFlags(eventVkeyDown, _privateFlag); @@ -392,7 +476,7 @@ void SendShiftAndLeftArrow() { CGEventTapPostEvent(_proxy, eventVkeyDown); CGEventTapPostEvent(_proxy, eventVkeyUp); - if (IS_DOUBLE_CODE(vCodeTable)) { //VNI or Unicode Compound + if (IS_DOUBLE_CODE(vCodeTable) && !_syncKey.empty()) { //VNI or Unicode Compound if (_syncKey.back() > 1) { if (!(vCodeTable == 3 && containUnicodeCompoundApp(FRONT_APP))) { CGEventTapPostEvent(_proxy, eventVkeyDown); @@ -408,6 +492,8 @@ void SendShiftAndLeftArrow() { void SendCutKey() { CGEventRef eventVkeyDown = CGEventCreateKeyboardEvent (myEventSource, KEY_X, true); CGEventRef eventVkeyUp = CGEventCreateKeyboardEvent (myEventSource, KEY_X, false); + CGEventSetIntegerValueField(eventVkeyDown, kCGEventSourceUserData, OPENKEY_EVENT_TAG); + CGEventSetIntegerValueField(eventVkeyUp, kCGEventSourceUserData, OPENKEY_EVENT_TAG); _privateFlag = CGEventGetFlags(eventVkeyDown); _privateFlag |= NX_COMMANDMASK; CGEventSetFlags(eventVkeyDown, _privateFlag); @@ -419,72 +505,76 @@ void SendCutKey() { CFRelease(eventVkeyDown); CFRelease(eventVkeyUp); } + void SendNewCharString(const bool& dataFromMacro=false, const Uint16& offset=0) { - _j = 0; - _newCharSize = dataFromMacro ? pData->macroData.size() : pData->newCharCount; + int j = 0; + int newCharSize = dataFromMacro ? (int)pData->macroData.size() : pData->newCharCount; _willContinuteSending = false; _willSendControlKey = false; - if (_newCharSize > 0) { - for (_k = dataFromMacro ? offset : pData->newCharCount - 1 - offset; - dataFromMacro ? _k < pData->macroData.size() : _k >= 0; - dataFromMacro ? _k++ : _k--) { + memset(_newCharString, 0, sizeof(_newCharString)); + + int k = 0; + if (newCharSize > 0) { + for (k = dataFromMacro ? offset : pData->newCharCount - 1 - offset; + dataFromMacro ? k < (int)pData->macroData.size() : k >= 0; + dataFromMacro ? k++ : k--) { - if (_j >= 16) { + if (j >= 16) { _willContinuteSending = true; break; } - _tempChar = DYNA_DATA(dataFromMacro, _k); - if (_tempChar & PURE_CHARACTER_MASK) { - _newCharString[_j++] = _tempChar; + Uint32 tempChar = DYNA_DATA(dataFromMacro, k); + if (tempChar & PURE_CHARACTER_MASK) { + _newCharString[j++] = (Uint16)tempChar; if (IS_DOUBLE_CODE(vCodeTable)) { InsertKeyLength(1); } - } else if (!(_tempChar & CHAR_CODE_MASK)) { + } else if (!(tempChar & CHAR_CODE_MASK)) { if (IS_DOUBLE_CODE(vCodeTable)) //VNI InsertKeyLength(1); - _newCharString[_j++] = keyCodeToCharacter(_tempChar); + Uint16 ch = keyCodeToCharacter(tempChar); + if (ch != 0) { + _newCharString[j++] = ch; + } } else { if (vCodeTable == 0) { //unicode 2 bytes code - _newCharString[_j++] = _tempChar; + _newCharString[j++] = (Uint16)tempChar; } else if (vCodeTable == 1 || vCodeTable == 2 || vCodeTable == 4) { //others such as VNI Windows, TCVN3: 1 byte code - _newChar = _tempChar; - _newCharHi = HIBYTE(_newChar); - _newChar = LOBYTE(_newChar); - _newCharString[_j++] = _newChar; + Uint16 newChar = (Uint16)tempChar; + Uint16 newCharHi = HIBYTE(newChar); + newChar = LOBYTE(newChar); + _newCharString[j++] = newChar; - if (_newCharHi > 32) { + if (newCharHi > 32) { if (vCodeTable == 2) //VNI InsertKeyLength(2); - _newCharString[_j++] = _newCharHi; - _newCharSize++; + _newCharString[j++] = newCharHi; } else { if (vCodeTable == 2) //VNI InsertKeyLength(1); } } else if (vCodeTable == 3) { //Unicode Compound - _newChar = _tempChar; - _newCharHi = (_newChar >> 13); - _newChar &= 0x1FFF; + Uint16 newChar = (Uint16)tempChar; + Uint16 newCharHi = (newChar >> 13); + newChar &= 0x1FFF; - InsertKeyLength(_newCharHi > 0 ? 2 : 1); - _newCharString[_j++] = _newChar; - if (_newCharHi > 0) { - _newCharSize++; - _newCharString[_j++] = _unicodeCompoundMark[_newCharHi - 1]; + InsertKeyLength(newCharHi > 0 ? 2 : 1); + _newCharString[j++] = newChar; + if (newCharHi > 0) { + _newCharString[j++] = _unicodeCompoundMark[newCharHi - 1]; } - } } }//end for } if (!_willContinuteSending && (pData->code == vRestore || pData->code == vRestoreAndStartNewSession)) { //if is restore - if (keyCodeToCharacter(_keycode) != 0) { - _newCharSize++; - _newCharString[_j++] = keyCodeToCharacter(_keycode | ((_flag & kCGEventFlagMaskAlphaShift) || (_flag & kCGEventFlagMaskShift) ? CAPS_MASK : 0)); + Uint16 ch = keyCodeToCharacter(_keycode | ((_flag & kCGEventFlagMaskAlphaShift) || (_flag & kCGEventFlagMaskShift) ? CAPS_MASK : 0)); + if (ch != 0) { + _newCharString[j++] = ch; } else { _willSendControlKey = true; } @@ -493,35 +583,43 @@ void SendNewCharString(const bool& dataFromMacro=false, const Uint16& offset=0) startNewSession(); } - _newEventDown = CGEventCreateKeyboardEvent(myEventSource, 0, true); - _newEventUp = CGEventCreateKeyboardEvent(myEventSource, 0, false); - CGEventKeyboardSetUnicodeString(_newEventDown, _willContinuteSending ? 16 : _newCharSize - offset, _newCharString); - CGEventKeyboardSetUnicodeString(_newEventUp, _willContinuteSending ? 16 : _newCharSize - offset, _newCharString); - CGEventTapPostEvent(_proxy, _newEventDown); - CGEventTapPostEvent(_proxy, _newEventUp); - CFRelease(_newEventDown); - CFRelease(_newEventUp); + if (j > 0) { + _newEventDown = CGEventCreateKeyboardEvent(myEventSource, 0, true); + _newEventUp = CGEventCreateKeyboardEvent(myEventSource, 0, false); + CGEventSetIntegerValueField(_newEventDown, kCGEventSourceUserData, OPENKEY_EVENT_TAG); + CGEventSetIntegerValueField(_newEventUp, kCGEventSourceUserData, OPENKEY_EVENT_TAG); + CGEventKeyboardSetUnicodeString(_newEventDown, _willContinuteSending ? 16 : j, _newCharString); - if (_willContinuteSending) { - SendNewCharString(dataFromMacro, dataFromMacro ? _k : 16); + CGEventTapPostEvent(_proxy, _newEventDown); + CGEventTapPostEvent(_proxy, _newEventUp); + CFRelease(_newEventDown); + CFRelease(_newEventUp); + } else { + // Safety: if no replacement characters were generated, pass through the key! + _willSendControlKey = true; } - - //the case when hCode is vRestore or vRestoreAndStartNewSession, the word is invalid and last key is control key such as TAB, LEFT ARROW, RIGHT ARROW,... - if (_willSendControlKey) { - SendKeyCode(_keycode); + + if (_willContinuteSending) { + SendNewCharString(dataFromMacro, dataFromMacro ? k : 16); } } + bool checkHotKey(int hotKeyData, bool checkKeyCode=true) { if ((hotKeyData & (~0x8000)) == EMPTY_HOTKEY) return false; - if (HAS_CONTROL(hotKeyData) ^ GET_BOOL(_lastFlag & kCGEventFlagMaskControl)) + bool hasCtrl = (_flag & kCGEventFlagMaskControl) || (_lastFlag & kCGEventFlagMaskControl); + bool hasOpt = (_flag & kCGEventFlagMaskAlternate) || (_lastFlag & kCGEventFlagMaskAlternate); + bool hasCmd = (_flag & kCGEventFlagMaskCommand) || (_lastFlag & kCGEventFlagMaskCommand); + bool hasShift = (_flag & kCGEventFlagMaskShift) || (_lastFlag & kCGEventFlagMaskShift); + + if (HAS_CONTROL(hotKeyData) ^ GET_BOOL(hasCtrl)) return false; - if (HAS_OPTION(hotKeyData) ^ GET_BOOL(_lastFlag & kCGEventFlagMaskAlternate)) + if (HAS_OPTION(hotKeyData) ^ GET_BOOL(hasOpt)) return false; - if (HAS_COMMAND(hotKeyData) ^ GET_BOOL(_lastFlag & kCGEventFlagMaskCommand)) + if (HAS_COMMAND(hotKeyData) ^ GET_BOOL(hasCmd)) return false; - if (HAS_SHIFT(hotKeyData) ^ GET_BOOL(_lastFlag & kCGEventFlagMaskShift)) + if (HAS_SHIFT(hotKeyData) ^ GET_BOOL(hasShift)) return false; if (checkKeyCode) { if (GET_SWITCH_KEY(hotKeyData) != _keycode) @@ -531,13 +629,10 @@ bool checkHotKey(int hotKeyData, bool checkKeyCode=true) { } void switchLanguage() { - if (vLanguage == 0) - vLanguage = 1; - else - vLanguage = 0; + int nextLanguage = (vLanguage == 0) ? 1 : 0; if (HAS_BEEP(vSwitchKeyStatus)) NSBeep(); - [appDelegate onImputMethodChanged:YES]; + [appDelegate setInputMethod:nextLanguage willNotify:YES]; startNewSession(); } @@ -599,10 +694,21 @@ CGKeyCode ConvertEventToKeyboadLayoutCompatKeyCode(CGEventRef keyEvent, CGKeyCod * MAIN Callback. */ CGEventRef OpenKeyCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) { + if (type == kCGEventTapDisabledByTimeout || type == kCGEventTapDisabledByUserInput) { + [OpenKeyManager reEnableEventTap]; + return NULL; + } + + // Fast reject synthetic events created by OpenKey + if (CGEventGetIntegerValueField(event, kCGEventSourceUserData) == OPENKEY_EVENT_TAG) { + return event; + } + //dont handle my event if (CGEventGetIntegerValueField(event, kCGEventSourceStateID) == CGEventSourceGetSourceStateID(myEventSource)) { return event; } + _flag = CGEventGetFlags(event); _keycode = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode); @@ -662,8 +768,7 @@ CGEventRef OpenKeyCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef e // Also check correct event hooked if ((type != kCGEventKeyDown) && (type != kCGEventKeyUp) && - (type != kCGEventLeftMouseDown) && (type != kCGEventRightMouseDown) && - (type != kCGEventLeftMouseDragged) && (type != kCGEventRightMouseDragged)) + (type != kCGEventLeftMouseDown) && (type != kCGEventRightMouseDown)) return event; _proxy = proxy; @@ -685,27 +790,25 @@ CGEventRef OpenKeyCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef e } //handle mouse - if (type == kCGEventLeftMouseDown || type == kCGEventRightMouseDown || type == kCGEventLeftMouseDragged || type == kCGEventRightMouseDragged) { + if (type == kCGEventLeftMouseDown || type == kCGEventRightMouseDown) { RequestNewSession(); return event; } //if "turn off Vietnamese when in other language" mode on - if(vOtherLanguage){ + if (vOtherLanguage) { TISInputSourceRef isource = TISCopyCurrentKeyboardInputSource(); - if ( isource != NULL ) - { - CFArrayRef languages = (CFArrayRef) TISGetInputSourceProperty(isource, kTISPropertyInputSourceLanguages); - - if (CFArrayGetCount(languages) > 0) { + if (isource != NULL) { + CFArrayRef languages = (CFArrayRef)TISGetInputSourceProperty(isource, kTISPropertyInputSourceLanguages); + if (languages != NULL && CFArrayGetCount(languages) > 0) { CFStringRef langRef = (CFStringRef)CFArrayGetValueAtIndex(languages, 0); NSString *currentLanguage = (__bridge NSString *)langRef; - if(![currentLanguage isLike:@"en"]){ + if (![currentLanguage isLike:@"en"]) { + CFRelease(isource); return event; } - CFRelease(langRef); - CFRelease(isource); } + CFRelease(isource); } } @@ -717,6 +820,7 @@ CGEventRef OpenKeyCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef e _keycode, _flag & kCGEventFlagMaskShift ? 1 : (_flag & kCGEventFlagMaskAlphaShift ? 2 : 0), OTHER_CONTROL_KEY); + if (pData->code == vDoNothing) { //do nothing if (IS_DOUBLE_CODE(vCodeTable)) { //VNI if (pData->extCode == 1) { //break key @@ -725,8 +829,7 @@ CGEventRef OpenKeyCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef e if (_syncKey.size() > 0) { if (_syncKey.back() > 1 && (vCodeTable == 2 || !containUnicodeCompoundApp(FRONT_APP))) { //send one more backspace - CGEventTapPostEvent(_proxy, eventBackSpaceDown); - CGEventTapPostEvent(_proxy, eventBackSpaceUp); + PostBackspaceEvent(); } _syncKey.pop_back(); } @@ -754,7 +857,7 @@ CGEventRef OpenKeyCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef e } if (shouldUseSelectionReplacement(FRONT_APP) && pData->backspaceCount > 0) { - for (_i = 0; _i < pData->backspaceCount; _i++) { + for (int i = 0; i < pData->backspaceCount; i++) { SendShiftAndLeftArrow(); } pData->backspaceCount = 0; @@ -762,7 +865,7 @@ CGEventRef OpenKeyCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef e //send backspace if (pData->backspaceCount > 0 && pData->backspaceCount < MAX_BUFF) { - for (_i = 0; _i < pData->backspaceCount; _i++) { + for (int i = 0; i < pData->backspaceCount; i++) { SendBackspace(); } } @@ -777,7 +880,12 @@ CGEventRef OpenKeyCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef e } } if (pData->code == vRestore || pData->code == vRestoreAndStartNewSession) { - SendKeyCode(_keycode | ((_flag & kCGEventFlagMaskAlphaShift) || (_flag & kCGEventFlagMaskShift) ? CAPS_MASK : 0)); + Uint16 ch = keyCodeToCharacter(_keycode | ((_flag & kCGEventFlagMaskAlphaShift) || (_flag & kCGEventFlagMaskShift) ? CAPS_MASK : 0)); + if (ch != 0) { + SendKeyCode(ch); + } else { + _willSendControlKey = true; + } } if (pData->code == vRestoreAndStartNewSession) { startNewSession(); @@ -787,8 +895,13 @@ CGEventRef OpenKeyCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef e handleMacro(); } + if (_willSendControlKey) { + _willSendControlKey = false; + return event; + } return NULL; } + return event; } diff --git a/Sources/OpenKey/macOS/ModernKey/OpenKeyManager.h b/Sources/OpenKey/macOS/ModernKey/OpenKeyManager.h index 11e2a52b..934550dd 100644 --- a/Sources/OpenKey/macOS/ModernKey/OpenKeyManager.h +++ b/Sources/OpenKey/macOS/ModernKey/OpenKeyManager.h @@ -17,6 +17,7 @@ typedef void (^CheckNewVersionCallback)(void); +(BOOL)isInited; +(BOOL)initEventTap; +(BOOL)stopEventTap; ++(void)reEnableEventTap; +(NSArray*)getTableCodes; diff --git a/Sources/OpenKey/macOS/ModernKey/OpenKeyManager.m b/Sources/OpenKey/macOS/ModernKey/OpenKeyManager.m index 97883f1a..cff9a7a7 100644 --- a/Sources/OpenKey/macOS/ModernKey/OpenKeyManager.m +++ b/Sources/OpenKey/macOS/ModernKey/OpenKeyManager.m @@ -29,11 +29,32 @@ @implementation OpenKeyManager { static CFMachPortRef eventTap; static CGEventMask eventMask; static CFRunLoopSourceRef runLoopSource; +static NSTimer* eventTapWatchdogTimer = nil; +(BOOL)isInited { return _isInited; } ++(void)startWatchdogTimer { + if (eventTapWatchdogTimer == nil) { + eventTapWatchdogTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 + repeats:YES + block:^(NSTimer * _Nonnull timer) { + if (eventTap != nil && !CGEventTapIsEnabled(eventTap)) { + NSLog(@"[OpenKey] Watchdog: event tap disabled, re-enabling..."); + CGEventTapEnable(eventTap, true); + } + }]; + } +} + ++(void)stopWatchdogTimer { + if (eventTapWatchdogTimer != nil) { + [eventTapWatchdogTimer invalidate]; + eventTapWatchdogTimer = nil; + } +} + +(BOOL)initEventTap { if (_isInited) return true; @@ -41,14 +62,12 @@ +(BOOL)initEventTap { //init modernKey OpenKeyInit(); - // Create an event tap. We are interested in key presses. + // Create an event tap. We are interested in key presses and mouse clicks. eventMask = ((1 << kCGEventKeyDown) | (1 << kCGEventKeyUp) | (1 << kCGEventFlagsChanged) | (1 << kCGEventLeftMouseDown) | - (1 << kCGEventRightMouseDown) | - (1 << kCGEventLeftMouseDragged) | - (1 << kCGEventRightMouseDragged)); + (1 << kCGEventRightMouseDown)); eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, @@ -74,11 +93,16 @@ +(BOOL)initEventTap { // Enable the event tap. CGEventTapEnable(eventTap, true); + // Start watchdog timer to keep event tap always alive + [self startWatchdogTimer]; + return YES; } +(BOOL)stopEventTap { if (_isInited) { //release all object + [self stopWatchdogTimer]; + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes); CFRelease(runLoopSource); runLoopSource = nil; @@ -92,6 +116,17 @@ +(BOOL)stopEventTap { return YES; } ++(void)reEnableEventTap { + if (eventTap != nil) { + CGEventTapEnable(eventTap, true); + dispatch_async(dispatch_get_main_queue(), ^{ + if (eventTap != nil && !CGEventTapIsEnabled(eventTap)) { + CGEventTapEnable(eventTap, true); + } + }); + } +} + +(NSArray*)getTableCodes { return [[NSArray alloc] initWithObjects: @"Unicode", diff --git a/build_macos.sh b/build_macos.sh new file mode 100755 index 00000000..0ea65df5 --- /dev/null +++ b/build_macos.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +set -e + +# Build script for OpenKey on macOS (Apple Silicon arm64 / Intel) +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" +BUILD_DIR="$PROJECT_DIR/build" +APP_BUNDLE="$BUILD_DIR/OpenKey.app" +CONTENTS_DIR="$APP_BUNDLE/Contents" +MACOS_DIR="$CONTENTS_DIR/MacOS" +RESOURCES_DIR="$CONTENTS_DIR/Resources" + +echo "==================================================" +echo " Building OpenKey for macOS (Optimized Native) " +echo "==================================================" + +SDK_PATH=$(xcrun --show-sdk-path) +CXX_INC="$SDK_PATH/usr/include/c++/v1" +ARCH=$(uname -m) + +echo "-> Detected Architecture: $ARCH" +echo "-> Using macOS SDK: $SDK_PATH" + +mkdir -p "$BUILD_DIR/objs" +mkdir -p "$MACOS_DIR" +mkdir -p "$RESOURCES_DIR" + +echo "-> Compiling C++ Core Engine (LTO)..." +clang++ -O3 -flto -arch "$ARCH" -isysroot "$SDK_PATH" -isystem "$CXX_INC" \ + -I "$PROJECT_DIR/Sources/OpenKey/engine" \ + -I "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey" \ + -std=c++14 -c \ + "$PROJECT_DIR/Sources/OpenKey/engine/Engine.cpp" \ + "$PROJECT_DIR/Sources/OpenKey/engine/Vietnamese.cpp" \ + "$PROJECT_DIR/Sources/OpenKey/engine/Macro.cpp" \ + "$PROJECT_DIR/Sources/OpenKey/engine/SmartSwitchKey.cpp" \ + "$PROJECT_DIR/Sources/OpenKey/engine/ConvertTool.cpp" + +echo "-> Compiling Objective-C++ sources (LTO)..." +clang++ -O3 -flto -arch "$ARCH" -isysroot "$SDK_PATH" -isystem "$CXX_INC" \ + -I "$PROJECT_DIR/Sources/OpenKey/engine" \ + -I "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey" \ + -std=c++14 -fobjc-arc -c \ + "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/ConvertToolViewController.mm" \ + "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/MacroViewController.mm" \ + "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/OpenKey.mm" + +echo "-> Compiling Objective-C sources (LTO)..." +clang -O3 -flto -arch "$ARCH" -isysroot "$SDK_PATH" \ + -I "$PROJECT_DIR/Sources/OpenKey/engine" \ + -I "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey" \ + -fobjc-arc -c \ + "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/main.m" \ + "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/AppDelegate.m" \ + "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/ViewController.m" \ + "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/AboutViewController.m" \ + "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/MyTextField.m" \ + "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/MJAccessibilityUtils.m" \ + "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/OpenKeyManager.m" + +echo "-> Linking executable with LTO & Dead Code Strip..." +clang++ -O3 -flto -Wl,-dead_strip -arch "$ARCH" -isysroot "$SDK_PATH" *.o \ + -framework Cocoa -framework Carbon -framework ServiceManagement -lproc \ + -o "$MACOS_DIR/OpenKey" + +mv *.o "$BUILD_DIR/objs/" + +echo "-> Assembling application bundle..." +# Copy resources from existing /Applications/OpenKey.app if available +if [ -d "/Applications/OpenKey.app" ]; then + cp -R /Applications/OpenKey.app/Contents/Resources/* "$RESOURCES_DIR/" 2>/dev/null || true + cp /Applications/OpenKey.app/Contents/Info.plist "$CONTENTS_DIR/" 2>/dev/null || true + cp /Applications/OpenKey.app/Contents/PkgInfo "$CONTENTS_DIR/" 2>/dev/null || true + if [ -d "/Applications/OpenKey.app/Contents/Library" ]; then + cp -R /Applications/OpenKey.app/Contents/Library "$CONTENTS_DIR/" 2>/dev/null || true + fi +fi + +# Overlay repository resources +if [ -d "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/Resources" ]; then + cp -R "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/Resources/"* "$RESOURCES_DIR/" 2>/dev/null || true +fi + +# Ensure Info.plist exists +if [ ! -f "$CONTENTS_DIR/Info.plist" ] && [ -f "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/Info.plist" ]; then + cp "$PROJECT_DIR/Sources/OpenKey/macOS/ModernKey/Info.plist" "$CONTENTS_DIR/" +fi + +# Clean extended attributes and sign app bundle ad-hoc +echo "-> Cleaning attributes and signing ad-hoc..." +find "$APP_BUNDLE" -exec xattr -c {} + 2>/dev/null || true +codesign --force --deep --sign - "$APP_BUNDLE" + +echo "==================================================" +echo " Build completed: $APP_BUNDLE" +echo "==================================================" + +# Handle install argument +if [ "$1" = "install" ]; then + echo "-> Installing to /Applications/OpenKey.app..." + + if pgrep -x "OpenKey" > /dev/null; then + echo "-> Stopping currently running OpenKey..." + killall OpenKey 2>/dev/null || true + sleep 1 + fi + + if [ -d "/Applications/OpenKey.app" ] && [ ! -d "/Applications/OpenKey.app.backup" ]; then + echo "-> Creating backup at /Applications/OpenKey.app.backup..." + cp -R /Applications/OpenKey.app /Applications/OpenKey.app.backup + fi + + echo "-> Updating binary in /Applications/OpenKey.app/Contents/MacOS/OpenKey..." + cp "$MACOS_DIR/OpenKey" "/Applications/OpenKey.app/Contents/MacOS/OpenKey" + find /Applications/OpenKey.app -exec xattr -c {} + 2>/dev/null || true + codesign --force --deep --sign - /Applications/OpenKey.app + + echo "-> Launching updated OpenKey..." + open /Applications/OpenKey.app + echo "-> Done! OpenKey is running with your personalized optimizations." +fi