From bfeb8fc5cdbbaa5289474ad773b90ee5fe3ee120 Mon Sep 17 00:00:00 2001 From: Nhat Hoang Date: Mon, 15 Jun 2026 20:08:53 +0700 Subject: [PATCH] fork as nkey for personnal use --- .gitignore | 7 + Sources/OpenKey/engine/Engine.cpp | 236 +- Sources/OpenKey/engine/Engine.h | 9 + Sources/OpenKey/engine/EnglishDetect.cpp | 93 + Sources/OpenKey/engine/EnglishDetect.h | 58 + Sources/OpenKey/macOS/.DS_Store | Bin 6148 -> 0 bytes Sources/OpenKey/macOS/Makefile | 69 + Sources/OpenKey/macOS/ModernKey/AppDelegate.h | 2 +- Sources/OpenKey/macOS/ModernKey/AppDelegate.m | 26 +- .../ModernKey/Base.lproj/Main.storyboard | 22 +- Sources/OpenKey/macOS/ModernKey/Info.plist | 2 +- .../macOS/ModernKey/MacroViewController.mm | 4 +- Sources/OpenKey/macOS/ModernKey/OpenKey.mm | 51 +- .../OpenKey/macOS/ModernKey/OpenKeyManager.m | 29 +- .../OpenKey/macOS/ModernKey/ViewController.m | 18 +- .../OpenKey/macOS/ModernKey/english_words.dat | 9868 +++++ .../OpenKey/macOS/ModernKey/viet_telex.dat | 29644 ++++++++++++++++ .../macOS/OpenKey.xcodeproj/project.pbxproj | 46 +- .../UserInterfaceState.xcuserstate | Bin 0 -> 16733 bytes .../xcshareddata/xcschemes/OpenKey.xcscheme | 16 +- 20 files changed, 40131 insertions(+), 69 deletions(-) create mode 100644 Sources/OpenKey/engine/EnglishDetect.cpp create mode 100644 Sources/OpenKey/engine/EnglishDetect.h delete mode 100644 Sources/OpenKey/macOS/.DS_Store create mode 100644 Sources/OpenKey/macOS/Makefile create mode 100644 Sources/OpenKey/macOS/ModernKey/english_words.dat create mode 100644 Sources/OpenKey/macOS/ModernKey/viet_telex.dat create mode 100644 Sources/OpenKey/macOS/OpenKey.xcodeproj/project.xcworkspace/xcuserdata/nhhoang.xcuserdatad/UserInterfaceState.xcuserstate diff --git a/.gitignore b/.gitignore index 7fd14bb2..5af71166 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,10 @@ /Sources/OpenKey/win32/OpenKey/OpenKey/x64/Debug /Sources/OpenKey/win32/OpenKey/OpenKey/x64/Release /Sources/OpenKey/win32/OpenKey/OpenKeyUpdate/x64/Release + +# macOS build output (xcodebuild -derivedDataPath via Makefile) +/Sources/OpenKey/macOS/build/ + +# macOS Finder metadata +.DS_Store +*/.DS_Store diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 2a895f47..f94f2eee 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -10,6 +10,7 @@ #include "Engine.h" #include #include +#include #include "Macro.h" static vector _charKeyCode = { @@ -93,6 +94,12 @@ static Uint32 KeyStates[MAX_BUFF]; static Byte _stateIndex = 0; static bool tempDisableKey = false; +//Set when a standalone vowel is toggled back to a literal letter this word +//("ww" -> "w"). Tells the word-break English restore to leave the word alone, so +//the escape isn't reverted to the raw English keystrokes (e.g. "ww " stays "w "). +//(The tone-removal escape "iss" -> "is" deliberately does NOT set this: there the +//word-break restore is wanted, so real English words like "miss" come out whole.) +static bool _engTelexEscape = false; static int capsElem; static int key; static int markElem; @@ -135,6 +142,8 @@ string wideStringToUtf8(const wstring& str) { void* vKeyInit() { _index = 0; _stateIndex = 0; + tempDisableKey = false; + _engTelexEscape = false; _useSpellCheckingBefore = vCheckSpelling; _typingStatesData.clear(); _typingStates.clear(); @@ -459,6 +468,7 @@ void startNewSession() { hBPC = 0; hNCC = 0; tempDisableKey = false; + _engTelexEscape = false; _stateIndex = 0; _hasHandledMacro = false; _hasHandleQuickConsonant = false; @@ -777,7 +787,7 @@ void insertMark(const Uint32& markMask, const bool& canModifyFlag) { kk = _index - 1 - VSI; //if duplicate same mark -> restore if (TypingWord[VWSM] & markMask) { - + TypingWord[VWSM] &= ~MARK_MASK; if (canModifyFlag) hCode = vRestore; @@ -949,6 +959,7 @@ void insertW(const Uint16& data, const bool& isCaps) { hCode = vWillProcess; if (CHR(ii) == KEY_U){ TypingWord[ii] = KEY_W | ((TypingWord[ii] & CAPS_MASK) ? CAPS_MASK : 0); + _engTelexEscape = true; } else if (CHR(ii) == KEY_O) { hCode = vRestore; TypingWord[ii] = KEY_O | ((TypingWord[ii] & CAPS_MASK) ? CAPS_MASK : 0); @@ -1311,6 +1322,215 @@ void vEnglishMode(const vKeyEventState& state, const Uint16& data, const bool& i } } +//Auto English detection: map a keycode (caps already stripped by Uint16 cast) to +//its lowercase ascii letter, or 0 for any non a-z key. +static char engKeyToChar(const Uint16& keyCode) { + switch (keyCode) { + case KEY_A: return 'a'; case KEY_B: return 'b'; case KEY_C: return 'c'; + case KEY_D: return 'd'; case KEY_E: return 'e'; case KEY_F: return 'f'; + case KEY_G: return 'g'; case KEY_H: return 'h'; case KEY_I: return 'i'; + case KEY_J: return 'j'; case KEY_K: return 'k'; case KEY_L: return 'l'; + case KEY_M: return 'm'; case KEY_N: return 'n'; case KEY_O: return 'o'; + case KEY_P: return 'p'; case KEY_Q: return 'q'; case KEY_R: return 'r'; + case KEY_S: return 's'; case KEY_T: return 't'; case KEY_U: return 'u'; + case KEY_V: return 'v'; case KEY_W: return 'w'; case KEY_X: return 'x'; + case KEY_Y: return 'y'; case KEY_Z: return 'z'; + default: return 0; + } +} + +static string _engRawWord; +//Auto English detection only runs in a Telex-style input method with the +//dictionary loaded and the option on. +static bool engDetectEnabled() { + return vAutoDetectEnglish && isEnglishDictReady() && + (vInputType == vTelex || vInputType == vSimpleTelex1 || vInputType == vSimpleTelex2); +} + +//Rebuild _engRawWord from the raw keystroke history (KeyStates). Returns false +//if the word is empty, too long, or contains a non-letter key. +static bool buildEngRawFromStates() { + if (_stateIndex < 2 || _stateIndex > MAX_BUFF) + return false; + _engRawWord.clear(); + for (i = 0; i < _stateIndex; i++) { + char c = engKeyToChar((Uint16)KeyStates[i]); + if (c == 0) + return false; + _engRawWord.push_back(c); + } + return true; +} + +//Decide whether the word currently being typed is English, so the engine inserts +//the raw key instead of applying a Vietnamese diacritic. This is only consulted +//when a transform key would otherwise fire (see the gate below), so it stays off +//the hot path for ordinary keys. +static bool rawDdReorderIsViet(); +static bool shouldTreatAsEnglish() { + if (!engDetectEnabled() || !buildEngRawFromStates()) + return false; + + //Vietnamese-first for the đ-trigger placed after the vowel ("dod" = đo): its + //canonical spelling ("ddo") is Vietnamese even though the raw keys look English + //("dod"/"dodge"). restoreEnglishAtBreak() still reverts genuine English at the break. + if (rawDdReorderIsViet()) + return false; + + //Complete English word: suppress unless the keystrokes are also a valid + //Vietnamese word, OR could still grow into one. The prefix check matters for + //transform digraphs that are themselves short English words but the start of + //many Vietnamese words (e.g. "dd" -> đ, the prefix of đi/đường/...): without + //it, English-detection would wrongly block every đ-word. + if (isEnglishWord(_engRawWord)) + return !isVietByTelex(_engRawWord) && !isVietByTelexPrefix(_engRawWord); + + //Prefix of an English word (e.g. "goo" of "google"): stricter, also bail out + //if the keystrokes could still grow into a Vietnamese word. + if (isEnglishPrefix(_engRawWord)) + return !isVietByTelex(_engRawWord) && !isVietByTelexPrefix(_engRawWord); + + return false; +} + +//Typing the modifier key again to turn a standalone vowel back into its literal +//letter ("ww" -> "w", undoing the lone-w -> ư) is a deliberate Telex escape. It must +//run even though "ww" is an English-dictionary word, so the user can type a literal w. +static bool isStandaloneToggle(const Uint16& data) { + return data == KEY_W && _index > 0 && + CHR(_index - 1) == KEY_U && (TypingWord[_index - 1] & TONEW_MASK); +} + +//Telex lets the tone key sit anywhere after the vowel: "ít" can be typed +//i-t-s OR i-s-t. The Vietnamese dictionary only stores the canonical tone-last +//spelling ("its"), so a tone-first raw string ("ist") slips past the viet +//guards in restoreEnglishAtBreak — and since it happens to be an English word +//("ist"), the valid Vietnamese word would be wrongly restored. Rebuild the +//canonical spelling by moving the applied tone key to the end. +// +//We only override the English restore when that canonical spelling is ITSELF +//both a Vietnamese word and an English word (e.g. "its" = ít): typing it in +//canonical order already resolves to Vietnamese (the "favor Vietnamese" rule), +//so the tone-first variant must too. This deliberately leaves genuine English +//like "test" alone — its canonical form "tets" is not an English word, so +//"test" stays a distinct English token (the Vietnamese "tét" is typed "tets"). +//Only fires when a tone mark was actually applied (toneless English like +//"google" is untouched). +static bool rawToneReorderIsViet() { + Uint32 markMask = 0; + for (i = 0; i < _index; i++) { + if (TypingWord[i] & MARK_MASK) { markMask = TypingWord[i] & MARK_MASK; break; } + } + if (!markMask) + return false; + Uint16 toneKey = markMask == MARK1_MASK ? KEY_S : markMask == MARK2_MASK ? KEY_F : + markMask == MARK3_MASK ? KEY_R : markMask == MARK4_MASK ? KEY_X : + markMask == MARK5_MASK ? KEY_J : 0; + char toneChar = toneKey ? engKeyToChar(toneKey) : 0; + if (!toneChar) + return false; + string w = _engRawWord; + size_t pos = w.rfind(toneChar); + if (pos == string::npos || pos == w.size() - 1) //missing, or already tone-last + return false; + w.erase(pos, 1); + w.push_back(toneChar); + return isEnglishWord(w) && (isVietByTelex(w) || isVietByTelexPrefix(w)); +} + +//Telex lets the đ-trigger 'd' sit after the vowel/coda: "đo" can be typed +//d-d-o (canonical) OR d-o-d. The Vietnamese dictionary only stores the canonical +//leading-"dd" spelling ("ddo"), so a trigger-last raw ("dod") looks English +//("dod"/"dodge") and gets suppressed — while "dond"/"dongd" transform only because +//they happen not to be English. In Vietnamese mode we favor Vietnamese: rebuild the +//canonical spelling (drop the trailing trigger 'd', prepend a 'd') and check the +//dict. Naturally scoped to words that start AND end with 'd', so English like +//"add"/"dad"/"dodge" (no trailing trigger) is untouched. Mirrors rawToneReorderIsViet(). +static bool rawDdReorderIsViet() { + string w = _engRawWord; + if (w.size() < 2 || w[0] != 'd' || w.back() != 'd') + return false; + w.pop_back(); + w.insert(w.begin(), 'd'); // "dod" -> "ddo" + return isVietByTelex(w) || isVietByTelexPrefix(w); +} + +//At a word boundary, if the whole typed word turned out to be English but a +//diacritic was applied mid-word (the ambiguous-prefix case the keystroke-time +//check intentionally leaves to Vietnamese, e.g. "google", "message"), restore +//the raw keystrokes so the final word is clean. Returns true if it restored. +static bool restoreEnglishAtBreak(const int& handleCode) { + if (!engDetectEnabled() || _index == 0 || _engTelexEscape || !buildEngRawFromStates()) + return false; + //Don't restore if the keystrokes spell a Vietnamese word, or are still a + //valid Vietnamese prefix (e.g. "dd" -> đ): otherwise a complete-English-word + //digraph like "dd" would be reverted at the break, undoing the diacritic. + //rawToneReorderIsViet() covers tone-first spellings ("ist" of "ít"). + if (!isEnglishWord(_engRawWord) || isVietByTelex(_engRawWord) || isVietByTelexPrefix(_engRawWord) + || rawToneReorderIsViet() || rawDdReorderIsViet()) + return false; + + //Only act if the current on-screen word actually differs from the raw keys. + bool differs = (_index != _stateIndex); + for (i = 0; !differs && i < _index; i++) { + if (TypingWord[i] != KeyStates[i]) + differs = true; + } + if (!differs) + return false; + + hCode = handleCode; + hBPC = _index; + hNCC = _stateIndex; + for (i = 0; i < _stateIndex; i++) { + TypingWord[i] = KeyStates[i]; + hData[_stateIndex - 1 - i] = TypingWord[i]; + } + _index = _stateIndex; + return true; +} + +//At a word boundary, drop a doubled-tone-key mark so an ambiguous short word the +//user "escaped" comes out as the literal English letters: "is" -> í (prefer +//Vietnamese), but a 2nd "s" ("iss") -> "is". Only fires when the word is neither a +//real English word (those are handled by restoreEnglishAtBreak, e.g. "miss", +//"issue") nor valid Vietnamese, and the mark's tone key was actually pressed twice +//(so single-tone Vietnamese like "í"/"á" is never touched). Returns true if it acted. +static bool dropDoubledToneAtBreak(const int& handleCode) { + if (!engDetectEnabled() || _index == 0 || !buildEngRawFromStates()) + return false; + if (isEnglishWord(_engRawWord) || isVietByTelex(_engRawWord) || isVietByTelexPrefix(_engRawWord)) + return false; + + Uint32 markMask = 0; + for (i = 0; i < _index; i++) { + if (TypingWord[i] & MARK_MASK) { markMask = TypingWord[i] & MARK_MASK; break; } + } + if (!markMask) + return false; + Uint16 toneKey = markMask == MARK1_MASK ? KEY_S : markMask == MARK2_MASK ? KEY_F : + markMask == MARK3_MASK ? KEY_R : markMask == MARK4_MASK ? KEY_X : + markMask == MARK5_MASK ? KEY_J : 0; + if (!toneKey) + return false; + + int toneKeyCount = 0; + for (i = 0; i < _stateIndex; i++) + if ((KeyStates[i] & CHAR_MASK) == toneKey) + toneKeyCount++; + if (toneKeyCount < 2) + return false; + + hCode = handleCode; + hBPC = _index; + for (i = 0; i < _index; i++) + TypingWord[i] &= ~MARK_MASK; + hNCC = _index; + for (i = 0; i < _index; i++) + hData[_index - 1 - i] = GET(TypingWord[i]); + return true; +} + void vKeyHandleEvent(const vKeyEvent& event, const vKeyEventState& state, const Uint16& data, @@ -1339,8 +1559,15 @@ void vKeyHandleEvent(const vKeyEvent& event, if (tempDisableKey && !checkRestoreIfWrongSpelling(vRestoreAndStartNewSession)) { hCode = vDoNothing; } + } else if (!_hasHandledMacro && (restoreEnglishAtBreak(vRestoreAndStartNewSession) + || dropDoubledToneAtBreak(vRestoreAndStartNewSession))) { + //We are already in the word-break / number / control branch, so the + //word is ending no matter which key did it — space, ".", "!", numpad + //".", a Cmd-combo, etc. restoreEnglishAtBreak self-gates (it only acts + //when an English word had a mid-word diacritic), so fire it for all of + //them rather than only the break-code set (e.g. "wow!"/numpad "." too). } - + _isCharKeyCode = state == KeyDown && std::find(_charKeyCode.begin(), _charKeyCode.end(), data) != _charKeyCode.end(); if (!_isCharKeyCode) { //clear all line cache _specialChar.clear(); @@ -1397,6 +1624,9 @@ void vKeyHandleEvent(const vKeyEvent& event, hCode = vDoNothing; } _spaceCount++; + } else if (!_hasHandledMacro && (restoreEnglishAtBreak(vRestore) + || dropDoubledToneAtBreak(vRestore))) { //English word restore, or drop a doubled-tone escape ("iss" -> "is") + _spaceCount++; } else { //do nothing with SPACE KEY hCode = vDoNothing; _spaceCount++; @@ -1483,7 +1713,7 @@ void vKeyHandleEvent(const vKeyEvent& event, insertState(data, _isCaps); //save state - if (!IS_SPECIALKEY(data) || tempDisableKey) { //do nothing + if (!IS_SPECIALKEY(data) || tempDisableKey || (shouldTreatAsEnglish() && !isStandaloneToggle(data))) { //do nothing if (vQuickTelex && IS_QUICK_TELEX_KEY(data)) { handleQuickTelex(data, _isCaps); return; diff --git a/Sources/OpenKey/engine/Engine.h b/Sources/OpenKey/engine/Engine.h index f288f8c5..cce9db83 100644 --- a/Sources/OpenKey/engine/Engine.h +++ b/Sources/OpenKey/engine/Engine.h @@ -17,6 +17,7 @@ #include "Macro.h" #include "SmartSwitchKey.h" #include "ConvertTool.h" +#include "EnglishDetect.h" #define IS_DEBUG 1 @@ -187,6 +188,14 @@ extern int vOtherLanguage; */ extern int vTempOffOpenKey; +/** + * 0: No; 1: Yes + * Auto-detect English words while typing in Vietnamese (Telex) and skip + * diacritics for them (e.g. "project", "guns", "code"). Needs the English + * dictionary loaded via initEnglishDict(). Telex only. + */ +extern int vAutoDetectEnglish; + /** * Call this function first to receive data pointer */ diff --git a/Sources/OpenKey/engine/EnglishDetect.cpp b/Sources/OpenKey/engine/EnglishDetect.cpp new file mode 100644 index 00000000..c8e73995 --- /dev/null +++ b/Sources/OpenKey/engine/EnglishDetect.cpp @@ -0,0 +1,93 @@ +// +// EnglishDetect.cpp +// OpenKey +// + +#include "EnglishDetect.h" + +#include +#include + +using namespace std; + +static vector _engDict; // sorted, unique +static vector _vietDict; // sorted, unique (Telex spellings) + +// Parse a raw byte buffer into a sorted/unique lowercase word list. +// Tokens are split on any non a-z/A-Z byte; uppercase is folded to lowercase. +static void buildDict(const Byte* pData, const int& size, vector& out) { + out.clear(); + if (pData == NULL || size <= 0) + return; + string cur; + cur.reserve(32); + for (int i = 0; i <= size; i++) { + char c = (i < size) ? (char)pData[i] : '\n'; // force flush of last token + if (c >= 'A' && c <= 'Z') + c = (char)(c - 'A' + 'a'); + if (c >= 'a' && c <= 'z') { + cur.push_back(c); + } else { + if (!cur.empty()) { + out.push_back(cur); + cur.clear(); + } + } + } + sort(out.begin(), out.end()); + out.erase(unique(out.begin(), out.end()), out.end()); +} + +void initEnglishDict(const Byte* pData, const int& size) { + buildDict(pData, size, _engDict); +} + +void initVietByTelexDict(const Byte* pData, const int& size) { + buildDict(pData, size, _vietDict); +} + +bool isEnglishDictReady(void) { + return !_engDict.empty(); +} + +static bool dictContains(const vector& dict, const string& w) { + return binary_search(dict.begin(), dict.end(), w); +} + +// True if some entry has `w` as a strict prefix (longer than w). +static bool dictHasLongerPrefix(const vector& dict, const string& w) { + if (w.empty()) + return false; + vector::const_iterator it = lower_bound(dict.begin(), dict.end(), w); + for (; it != dict.end(); ++it) { + if (it->size() < w.size() || it->compare(0, w.size(), w) != 0) + break; // no longer entries start with w + if (it->size() > w.size()) + return true; + } + return false; +} + +// True if some entry has `w` as a prefix (including an exact match). +static bool dictHasPrefix(const vector& dict, const string& w) { + if (w.empty()) + return false; + vector::const_iterator it = lower_bound(dict.begin(), dict.end(), w); + return it != dict.end() && it->size() >= w.size() && it->compare(0, w.size(), w) == 0; +} + +bool isEnglishWord(const string& word) { + return dictContains(_engDict, word); +} + +bool isEnglishPrefix(const string& word) { + return dictHasLongerPrefix(_engDict, word); +} + +bool isVietByTelex(const string& word) { + return dictContains(_vietDict, word); +} + +bool isVietByTelexPrefix(const string& word) { + return dictHasPrefix(_vietDict, word); +} diff --git a/Sources/OpenKey/engine/EnglishDetect.h b/Sources/OpenKey/engine/EnglishDetect.h new file mode 100644 index 00000000..e21076d0 --- /dev/null +++ b/Sources/OpenKey/engine/EnglishDetect.h @@ -0,0 +1,58 @@ +// +// EnglishDetect.h +// OpenKey +// +// Lightweight English-vs-Vietnamese word detection used to skip diacritics +// when an English word is being typed in Vietnamese (Telex) mode. +// +// Both dictionaries are keyed by the *raw keystroke string* (lowercase a-z): +// - English dictionary: common English words. +// - Vietnamese dictionary: the Telex spelling of valid Vietnamese words +// (e.g. "cu3"-style is NOT used; Telex "cur" -> "củ"). +// This lets the engine compare what was typed against both sets directly, +// without having to materialise the toned output. +// + +#ifndef EnglishDetect_h +#define EnglishDetect_h + +#include +#include "DataType.h" + +/** + * Load the English word list from a memory buffer (tokens separated by any + * non-letter byte, e.g. newlines). Safe to call again to replace the data. + */ +void initEnglishDict(const Byte* pData, const int& size); + +/** + * Load the Vietnamese word list (Telex spellings) from a memory buffer. + */ +void initVietByTelexDict(const Byte* pData, const int& size); + +/** + * True once the English dictionary has at least one entry. + */ +bool isEnglishDictReady(void); + +/** + * Exact match: `word` is a complete English word. + */ +bool isEnglishWord(const std::string& word); + +/** + * `word` is a strict prefix of some longer English word (word itself excluded). + */ +bool isEnglishPrefix(const std::string& word); + +/** + * Exact match: `word` (raw Telex keystrokes) spells a valid Vietnamese word. + */ +bool isVietByTelex(const std::string& word); + +/** + * `word` is a prefix of some Vietnamese Telex spelling (word itself included). + */ +bool isVietByTelexPrefix(const std::string& word); + +#endif /* EnglishDetect_h */ 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/Makefile b/Sources/OpenKey/macOS/Makefile new file mode 100644 index 00000000..8bf305db --- /dev/null +++ b/Sources/OpenKey/macOS/Makefile @@ -0,0 +1,69 @@ +# Makefile for building and installing NKey (OpenKey macOS fork) +# +# make # build Release (arm64, Apple Silicon) +# make install # build, sign, then copy to /Applications (quits a running copy first) +# make run # install, then launch +# make clean # remove build output +# make uninstall # quit and remove from /Applications +# +# Signing strategy: xcodebuild produces an ad-hoc build (reliable, no provisioning +# hassle), then we RE-SIGN the .app with a stable Apple Development certificate via +# codesign. A stable signature is what lets macOS keep the Accessibility grant across +# rebuilds. Override the identity with `make install SIGN_IDENTITY="..."`, or pass +# SIGN_IDENTITY= (empty) to stay ad-hoc (macOS will then re-prompt every rebuild). + +APP := NKey +PROJECT := OpenKey.xcodeproj +SCHEME := OpenKey +CONFIG := Release +ARCH := arm64 +DERIVED := build +BUILT_APP := $(DERIVED)/Build/Products/$(CONFIG)/$(APP).app +INSTALL_DIR := /Applications +INSTALLED_APP := $(INSTALL_DIR)/$(APP).app + +# Stable code-signing identity (keychain). Empty => leave the ad-hoc signature. +SIGN_IDENTITY ?= Apple Development: Duy Nhat Hoang (QCK566ZXTF) + +.PHONY: all build sign install run clean uninstall + +all: build + +build: + xcodebuild -project $(PROJECT) -scheme $(SCHEME) -configuration $(CONFIG) \ + -derivedDataPath $(DERIVED) \ + -arch $(ARCH) ONLY_ACTIVE_ARCH=NO \ + CODE_SIGN_IDENTITY="-" CODE_SIGN_STYLE=Manual DEVELOPMENT_TEAM="" \ + build + @echo "Built: $(BUILT_APP)" + +# Re-sign with the stable identity so the Accessibility grant survives rebuilds. +sign: build +ifneq ($(strip $(SIGN_IDENTITY)),) + codesign --force --deep -s "$(SIGN_IDENTITY)" "$(BUILT_APP)" + @echo "Signed with: $(SIGN_IDENTITY)" +else + @echo "SIGN_IDENTITY empty -> keeping ad-hoc signature" +endif + +install: sign + @echo "Quitting any running $(APP)..." + -osascript -e 'quit app "$(APP)"' >/dev/null 2>&1 || true + -pkill -x $(APP) >/dev/null 2>&1 || true + rm -rf "$(INSTALLED_APP)" + cp -R "$(BUILT_APP)" "$(INSTALLED_APP)" + -xattr -dr com.apple.quarantine "$(INSTALLED_APP)" + @echo "Installed -> $(INSTALLED_APP)" + @echo "Grant access: System Settings > Privacy & Security > Accessibility > $(APP)" + +run: install + open "$(INSTALLED_APP)" + +clean: + rm -rf $(DERIVED) + +uninstall: + -osascript -e 'quit app "$(APP)"' >/dev/null 2>&1 || true + -pkill -x $(APP) >/dev/null 2>&1 || true + rm -rf "$(INSTALLED_APP)" + @echo "Removed $(INSTALLED_APP)" diff --git a/Sources/OpenKey/macOS/ModernKey/AppDelegate.h b/Sources/OpenKey/macOS/ModernKey/AppDelegate.h index 706dccd0..e043218d 100644 --- a/Sources/OpenKey/macOS/ModernKey/AppDelegate.h +++ b/Sources/OpenKey/macOS/ModernKey/AppDelegate.h @@ -9,7 +9,7 @@ #import #import "ViewController.h" -#define OPENKEY_BUNDLE @"com.tuyenmai.openkey" +#define OPENKEY_BUNDLE @"com.nkey.app" @interface AppDelegate : NSObject diff --git a/Sources/OpenKey/macOS/ModernKey/AppDelegate.m b/Sources/OpenKey/macOS/ModernKey/AppDelegate.m index 011708e2..17cd4a0e 100644 --- a/Sources/OpenKey/macOS/ModernKey/AppDelegate.m +++ b/Sources/OpenKey/macOS/ModernKey/AppDelegate.m @@ -47,6 +47,7 @@ int vRememberCode = 1; //new on version 2.0 int vOtherLanguage = 1; //new on version 2.0 int vTempOffOpenKey = 0; //new on version 2.0 +int vAutoDetectEnglish = 1; //auto-detect English words in Telex (ported from 84Key) int vShowIconOnDock = 0; //new on version 2.0 @@ -87,11 +88,12 @@ @implementation AppDelegate { NSMenuItem* mnuVietnameseLocaleCP1258; NSMenuItem* mnuQuickConvert; + NSMenuItem* mnuAutoDetectEnglish; } -(void)askPermission { NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText: [NSString stringWithFormat:@"OpenKey cần bạn cấp quyền để có thể hoạt động!"]]; + [alert setMessageText: [NSString stringWithFormat:@"NKey cần bạn cấp quyền để có thể hoạt động!"]]; [alert setInformativeText:@"Vui lòng chạy lại ứng dụng sau khi cấp quyền."]; [alert addButtonWithTitle:@"Không"]; @@ -159,10 +161,8 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { } [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"NonFirstTime"]; - //check update if enable - NSInteger dontCheckUpdate = [[NSUserDefaults standardUserDefaults] integerForKey:@"DontCheckUpdate"]; - if (!dontCheckUpdate) - [OpenKeyManager checkNewVersion:nil callbackFunc:nil]; + //auto update disabled for NKey: the update endpoint points to the upstream + //OpenKey project, so checking would offer to "update" to an OpenKey release. //correct run on startup NSInteger val = [[NSUserDefaults standardUserDefaults] integerForKey:@"RunOnStartup"]; @@ -209,7 +209,13 @@ -(void) createStatusBarMenu { mnuQuickConvert = [theMenu addItemWithTitle:@"Chuyển mã nhanh" action:@selector(onQuickConvert) keyEquivalent:@""]; [theMenu addItem:[NSMenuItem separatorItem]]; - + + mnuAutoDetectEnglish = [theMenu addItemWithTitle:@"Tự động nhận diện tiếng Anh" + action:@selector(onAutoDetectEnglishSelected) + keyEquivalent:@""]; + + [theMenu addItem:[NSMenuItem separatorItem]]; + [theMenu addItemWithTitle:@"Bảng điều khiển..." action:@selector(onControlPanelSelected) keyEquivalent:@""]; [theMenu addItemWithTitle:@"Gõ tắt..." action:@selector(onMacroSelected) keyEquivalent:@""]; [theMenu addItemWithTitle:@"Giới thiệu" action:@selector(onAboutSelected) keyEquivalent:@""]; @@ -288,6 +294,7 @@ -(void)loadDefaultConfig { vRememberCode = 1;[[NSUserDefaults standardUserDefaults] setInteger:vRememberCode forKey:@"vRememberCode"]; vOtherLanguage = 1;[[NSUserDefaults standardUserDefaults] setInteger:vOtherLanguage forKey:@"vOtherLanguage"]; vTempOffOpenKey = 0;[[NSUserDefaults standardUserDefaults] setInteger:vTempOffOpenKey forKey:@"vTempOffOpenKey"]; + vAutoDetectEnglish = 1;[[NSUserDefaults standardUserDefaults] setInteger:vAutoDetectEnglish forKey:@"vAutoDetectEnglish"]; vShowIconOnDock = 0;[[NSUserDefaults standardUserDefaults] setInteger:vShowIconOnDock forKey:@"vShowIconOnDock"]; vFixChromiumBrowser = 0;[[NSUserDefaults standardUserDefaults] setInteger:vFixChromiumBrowser forKey:@"vFixChromiumBrowser"]; vPerformLayoutCompat = 0;[[NSUserDefaults standardUserDefaults] setInteger:vPerformLayoutCompat forKey:@"vPerformLayoutCompat"]; @@ -402,6 +409,13 @@ - (void) fillData { NSInteger intRunOnStartup = [[NSUserDefaults standardUserDefaults] integerForKey:@"RunOnStartup"]; [self setRunOnStartup:intRunOnStartup ? YES : NO]; + [mnuAutoDetectEnglish setState:(vAutoDetectEnglish ? NSControlStateValueOn : NSControlStateValueOff)]; +} + +- (void)onAutoDetectEnglishSelected { + vAutoDetectEnglish = vAutoDetectEnglish ? 0 : 1; + [[NSUserDefaults standardUserDefaults] setInteger:vAutoDetectEnglish forKey:@"vAutoDetectEnglish"]; + [self fillData]; } -(void)onImputMethodChanged:(BOOL)willNotify { diff --git a/Sources/OpenKey/macOS/ModernKey/Base.lproj/Main.storyboard b/Sources/OpenKey/macOS/ModernKey/Base.lproj/Main.storyboard index 1ad0aedf..a2aac8b4 100644 --- a/Sources/OpenKey/macOS/ModernKey/Base.lproj/Main.storyboard +++ b/Sources/OpenKey/macOS/ModernKey/Base.lproj/Main.storyboard @@ -16,11 +16,11 @@ - + - + - + @@ -34,7 +34,7 @@ - + @@ -52,7 +52,7 @@ - + @@ -665,7 +665,7 @@ - + @@ -731,7 +731,7 @@ - + @@ -864,7 +864,7 @@ - + @@ -1255,7 +1255,7 @@