Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
################################################################################
################################################################################
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
################################################################################

Expand All @@ -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
15 changes: 15 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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."
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
38 changes: 34 additions & 4 deletions Sources/OpenKey/engine/SmartSwitchKey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,32 @@ void getSmartSwitchKeySaveData(vector<Byte>& outData) {
}
}

static bool isDefaultEnglishApp(const string& bundleId) {
static const vector<string> 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;
}
Expand All @@ -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;
}
Binary file removed Sources/OpenKey/macOS/.DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions Sources/OpenKey/macOS/ModernKey/AppDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

@interface AppDelegate : NSObject <NSApplicationDelegate>

-(void)setInputMethod:(int)targetLanguage willNotify:(BOOL)willNotify;
-(void)onImputMethodChanged:(BOOL)willNotify;
-(void)onInputMethodSelected;

Expand Down
76 changes: 56 additions & 20 deletions Sources/OpenKey/macOS/ModernKey/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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]) {
Expand Down Expand Up @@ -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"];
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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];
Expand All @@ -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];
Expand All @@ -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];
Expand Down Expand Up @@ -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();
}
}
}

Expand Down
Loading