From 54e70702432b63fd7fabaa272f174fe8eb6197a0 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 18:09:13 +0700 Subject: [PATCH 01/60] ponyman: create build.cmd script --- build.cmd | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 build.cmd diff --git a/build.cmd b/build.cmd new file mode 100644 index 00000000..f9a8b557 --- /dev/null +++ b/build.cmd @@ -0,0 +1,37 @@ +@echo off +setlocal enabledelayedexpansion + +:: Find VS Installation Path +set "VS_PATH=" +for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath`) do ( + set "VS_PATH=%%i" +) + +if not defined VS_PATH ( + echo Visual Studio installation not found! + exit /b 1 +) + +:: Locate MSBuild +set "MSBUILD_PATH=!VS_PATH!\MSBuild\Current\Bin\MSBuild.exe" +if not exist "!MSBUILD_PATH!" ( + echo MSBuild not found at !MSBUILD_PATH! + exit /b 1 +) + +:: Run MSBuild +echo Building OpenKey... +"!MSBUILD_PATH!" "Sources\OpenKey\win32\OpenKey\OpenKey.sln" /p:Configuration=Release /p:Platform=x64 /p:PlatformToolset=v145 /t:Restore,Build + +if %ERRORLEVEL% neq 0 ( + echo Build failed! + exit /b %ERRORLEVEL% +) + +echo Build succeeded! +:: Automatically open the output folder +start "" "Sources\OpenKey\win32\OpenKey\x64\Release" +:: Automatically run the compiled exe +start "" "Sources\OpenKey\win32\OpenKey\x64\Release\OpenKey64.exe" + +exit /b 0 From 1c1aacb982b2508cee2f4ee1d5aabb00a9f63bc1 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 18:15:42 +0700 Subject: [PATCH 02/60] ponyman: change GetTempPath2 to GetTempPath for compatibility --- Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp index 13d6c005..a18f67e1 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp @@ -307,7 +307,7 @@ wstring OpenKeyHelper::getVersionString() { wstring OpenKeyHelper::getContentOfUrl(LPCTSTR url){ WCHAR path[MAX_PATH]; - GetTempPath2(MAX_PATH, path); + GetTempPath(MAX_PATH, path); wsprintf(path, TEXT("%s\\_OpenKey.tempf"), path); HRESULT res = URLDownloadToFile(NULL, url, path, 0, NULL); From 0b5da4a343fc754285b7524856f7f16103268ad4 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 18:34:24 +0700 Subject: [PATCH 03/60] ponyman: add Tu Binh Tran Don Gian input method --- Sources/OpenKey/engine/DataType.h | 9 +++-- Sources/OpenKey/engine/Engine.cpp | 35 ++++++++++++------- .../OpenKey/OpenKey/MainControlDialog.cpp | 11 ++++-- .../win32/OpenKey/OpenKey/OpenKeyManager.cpp | 3 +- .../OpenKey/OpenKey/SystemTrayHelper.cpp | 9 ++++- 5 files changed, 48 insertions(+), 19 deletions(-) diff --git a/Sources/OpenKey/engine/DataType.h b/Sources/OpenKey/engine/DataType.h index 1393e09f..54f1d2a5 100644 --- a/Sources/OpenKey/engine/DataType.h +++ b/Sources/OpenKey/engine/DataType.h @@ -34,7 +34,8 @@ enum vKeyInputType { vTelex = 0, vVNI, vSimpleTelex1, - vSimpleTelex2 + vSimpleTelex2, + vTuBinhTranDonGian }; typedef unsigned char Byte; @@ -143,7 +144,11 @@ struct vKeyHookState { keyCode == KEY_D || keyCode == KEY_F || keyCode == KEY_J || keyCode == KEY_Z || keyCode == KEY_X || keyCode == KEY_W \ : (vInputType == vSimpleTelex2 ? \ keyCode == KEY_W || keyCode == KEY_E || keyCode == KEY_R || keyCode == KEY_O || keyCode == KEY_A || keyCode == KEY_S || \ - keyCode == KEY_D || keyCode == KEY_F || keyCode == KEY_J || keyCode == KEY_Z || keyCode == KEY_X || keyCode == KEY_W : false)))) + keyCode == KEY_D || keyCode == KEY_F || keyCode == KEY_J || keyCode == KEY_Z || keyCode == KEY_X || keyCode == KEY_W \ + : (vInputType == vTuBinhTranDonGian ? \ + keyCode == KEY_1 || keyCode == KEY_2 || keyCode == KEY_3 || keyCode == KEY_4 || keyCode == KEY_5 || \ + keyCode == KEY_6 || keyCode == KEY_7 || keyCode == KEY_8 || keyCode == KEY_9 || keyCode == KEY_0 || \ + keyCode == KEY_Z || keyCode == KEY_D || keyCode == KEY_LEFT_BRACKET || keyCode == KEY_RIGHT_BRACKET : false))))) //is VNI or Unicode compound... #define IS_DOUBLE_CODE(code) (code == 2 || code == 3) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index de90833b..b2470f5c 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -35,23 +35,27 @@ static Uint16 ProcessingChar[][11] = { {KEY_S, KEY_F, KEY_R, KEY_X, KEY_J, KEY_A, KEY_O, KEY_E, KEY_W, KEY_D, KEY_Z}, //Telex {KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0}, //VNI {KEY_S, KEY_F, KEY_R, KEY_X, KEY_J, KEY_A, KEY_O, KEY_E, KEY_W, KEY_D, KEY_Z}, //Simple Telex 1 - {KEY_S, KEY_F, KEY_R, KEY_X, KEY_J, KEY_A, KEY_O, KEY_E, KEY_W, KEY_D, KEY_Z} //Simple Telex 2 + {KEY_S, KEY_F, KEY_R, KEY_X, KEY_J, KEY_A, KEY_O, KEY_E, KEY_W, KEY_D, KEY_Z}, //Simple Telex 2 + {KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_8, KEY_7, KEY_9, KEY_D, KEY_0} //Tu Binh Tran Don Gian }; -#define IS_KEY_Z(key) (ProcessingChar[vInputType][10] == key) +#define IS_KEY_Z(key) (ProcessingChar[vInputType][10] == key || (vInputType == vTuBinhTranDonGian && key == KEY_Z)) #define IS_KEY_D(key) (ProcessingChar[vInputType][9] == key) -#define IS_KEY_W(key) ((vInputType != vVNI) ? ProcessingChar[vInputType][8] == key : \ - (vInputType == vVNI ? (ProcessingChar[vInputType][8] == key || ProcessingChar[vInputType][7] == key) : false)) -#define IS_KEY_DOUBLE(key) ((vInputType != vVNI) ? (ProcessingChar[vInputType][5] == key || ProcessingChar[vInputType][6] == key || ProcessingChar[vInputType][7] == key) :\ - (vInputType == vVNI ? ProcessingChar[vInputType][6] == key : false)) +#define IS_KEY_W(key) (vInputType == vTuBinhTranDonGian ? (key == KEY_9) : \ + ((vInputType != vVNI) ? ProcessingChar[vInputType][8] == key : \ + (vInputType == vVNI ? (ProcessingChar[vInputType][8] == key || ProcessingChar[vInputType][7] == key) : false))) +#define IS_KEY_DOUBLE(key) (vInputType == vTuBinhTranDonGian ? (key == KEY_6 || key == KEY_7 || key == KEY_8) : \ + ((vInputType != vVNI) ? (ProcessingChar[vInputType][5] == key || ProcessingChar[vInputType][6] == key || ProcessingChar[vInputType][7] == key) : \ + (vInputType == vVNI ? ProcessingChar[vInputType][6] == key : false))) #define IS_KEY_S(key) (ProcessingChar[vInputType][0] == key) #define IS_KEY_F(key) (ProcessingChar[vInputType][1] == key) #define IS_KEY_R(key) (ProcessingChar[vInputType][2] == key) #define IS_KEY_X(key) (ProcessingChar[vInputType][3] == key) #define IS_KEY_J(key) (ProcessingChar[vInputType][4] == key) -#define IS_MARK_KEY(keyCode) (((vInputType != vVNI) && (keyCode == KEY_S || keyCode == KEY_F || keyCode == KEY_R || keyCode == KEY_J || keyCode == KEY_X)) || \ - (vInputType == vVNI && (keyCode == KEY_1 || keyCode == KEY_2 || keyCode == KEY_3 || keyCode == KEY_5 || keyCode == KEY_4))) +#define IS_MARK_KEY(keyCode) ((vInputType == vTuBinhTranDonGian || vInputType == vVNI) ? \ + (keyCode == KEY_1 || keyCode == KEY_2 || keyCode == KEY_3 || keyCode == KEY_4 || keyCode == KEY_5) : \ + (keyCode == KEY_S || keyCode == KEY_F || keyCode == KEY_R || keyCode == KEY_J || keyCode == KEY_X)) #define IS_BRACKET_KEY(key) (key == KEY_LEFT_BRACKET || key == KEY_RIGHT_BRACKET) #define VSI vowelStartIndex @@ -1063,12 +1067,12 @@ void handleMainKey(const Uint16& data, const bool& isCaps) { } if (data == KEY_LEFT_BRACKET) { //standalone key [ - checkForStandaloneChar(data, isCaps, KEY_O); + checkForStandaloneChar(data, isCaps, (vInputType == vTuBinhTranDonGian) ? KEY_U : KEY_O); return; } - if (data == KEY_RIGHT_BRACKET) { //standalone key } - checkForStandaloneChar(data, isCaps, KEY_U); + if (data == KEY_RIGHT_BRACKET) { //standalone key ] + checkForStandaloneChar(data, isCaps, (vInputType == vTuBinhTranDonGian) ? KEY_O : KEY_U); return; } @@ -1151,7 +1155,14 @@ void handleMainKey(const Uint16& data, const bool& isCaps) { } } - keyForAEO = ((vInputType != vVNI) ? data : ((data == KEY_7 || data == KEY_8 ? KEY_W : (data == KEY_6 ? TypingWord[VEI] : data)))); + keyForAEO = (vInputType == vTuBinhTranDonGian) ? + (data == KEY_6 ? KEY_A : + (data == KEY_7 ? KEY_E : + (data == KEY_8 ? KEY_O : + (data == KEY_9 ? KEY_W : data)))) : + ((vInputType != vVNI) ? data : + ((data == KEY_7 || data == KEY_8 ? KEY_W : + (data == KEY_6 ? TypingWord[VEI] : data)))); vector>& charset = _vowel[keyForAEO]; isCorect = false; isChanged = false; diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MainControlDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MainControlDialog.cpp index eab2e7c2..3b181dbb 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MainControlDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MainControlDialog.cpp @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- OpenKey - The Cross platform Open source Vietnamese Keyboard application. Copyright (C) 2019 Mai Vu Tuyen @@ -324,7 +324,9 @@ INT_PTR MainControlDialog::tabPageEventProc(HWND hDlg, UINT uMsg, WPARAM wParam, } void MainControlDialog::fillData() { - SendMessage(comboBoxInputType, CB_SETCURSEL, vInputType, 0); + int sel = vInputType; + if (sel == vTuBinhTranDonGian) sel = 3; + SendMessage(comboBoxInputType, CB_SETCURSEL, sel, 0); SendMessage(comboBoxTableCode, CB_SETCURSEL, vCodeTable, 0); SendMessage(checkCtrl, BM_SETCHECK, HAS_CONTROL(vSwitchKeyStatus) ? 1 : 0, 0); @@ -387,7 +389,10 @@ void MainControlDialog::setSwitchKey(const unsigned short& code) { void MainControlDialog::onComboBoxSelected(const HWND& hCombobox, const int& comboboxId) { if (hCombobox == comboBoxInputType) { - APP_SET_DATA(vInputType, (int)SendMessage(hCombobox, CB_GETCURSEL, 0, 0)); + int sel = (int)SendMessage(hCombobox, CB_GETCURSEL, 0, 0); + int type = sel; + if (sel == 3) type = vTuBinhTranDonGian; + APP_SET_DATA(vInputType, type); } else if (hCombobox == comboBoxTableCode) { APP_SET_DATA(vCodeTable, (int)SendMessage(hCombobox, CB_GETCURSEL, 0, 0)); diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyManager.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyManager.cpp index ea1181b4..9b5d58e7 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyManager.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyManager.cpp @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- OpenKey - The Cross platform Open source Vietnamese Keyboard application. Copyright (C) 2019 Mai Vu Tuyen @@ -18,6 +18,7 @@ static vector _inputType = { _T("Telex"), _T("VNI"), _T("Simple Telex"), + _T("Tư Bình Trần đơn giản"), }; static vector _tableCode = { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/SystemTrayHelper.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/SystemTrayHelper.cpp index 55d786e9..08a0e03c 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/SystemTrayHelper.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/SystemTrayHelper.cpp @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- OpenKey - The Cross platform Open source Vietnamese Keyboard application. Copyright (C) 2019 Mai Vu Tuyen @@ -25,6 +25,7 @@ redistribute your new version, it MUST be open source. #define POPUP_TELEX 910 #define POPUP_VNI 911 #define POPUP_SIMPLE_TELEX 912 +#define POPUP_TU_BINH_TRAN_DON_GIAN 913 #define POPUP_UNICODE 930 #define POPUP_TCVN3 931 @@ -60,6 +61,7 @@ map menuData = { {POPUP_TELEX, _T("Kiểu gõ Telex")}, {POPUP_VNI, _T("Kiểu gõ VNI")}, {POPUP_SIMPLE_TELEX, _T("Kiểu gõ Simple Telex")}, + {POPUP_TU_BINH_TRAN_DON_GIAN, _T("Kiểu gõ Tư Bình Trần đơn giản")}, {POPUP_UNICODE, _T("Unicode dựng sẵn")}, {POPUP_TCVN3, _T("TCVN3 (ABC)")}, {POPUP_VNI_WINDOWS, _T("VNI Windows")}, @@ -134,6 +136,9 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case POPUP_SIMPLE_TELEX: AppDelegate::getInstance()->onInputType(2); break; + case POPUP_TU_BINH_TRAN_DON_GIAN: + AppDelegate::getInstance()->onInputType(vTuBinhTranDonGian); + break; case POPUP_UNICODE: AppDelegate::getInstance()->onTableCode(0); break; @@ -216,6 +221,7 @@ void SystemTrayHelper::createPopupMenu() { AppendMenu(popupMenu, MF_CHECKED, POPUP_TELEX, menuData[POPUP_TELEX]); AppendMenu(popupMenu, MF_CHECKED, POPUP_VNI, menuData[POPUP_VNI]); AppendMenu(popupMenu, MF_CHECKED, POPUP_SIMPLE_TELEX, menuData[POPUP_SIMPLE_TELEX]); + AppendMenu(popupMenu, MF_CHECKED, POPUP_TU_BINH_TRAN_DON_GIAN, menuData[POPUP_TU_BINH_TRAN_DON_GIAN]); //AppendMenu(popupMenu, MF_POPUP, (UINT_PTR)menuInputType, _T("Kiểu gõ")); AppendMenu(popupMenu, MF_SEPARATOR, 0, 0); @@ -263,6 +269,7 @@ void SystemTrayHelper::updateData() { MODIFY_MENU(popupMenu, POPUP_TELEX, vInputType == 0); MODIFY_MENU(popupMenu, POPUP_VNI, vInputType == 1); MODIFY_MENU(popupMenu, POPUP_SIMPLE_TELEX, vInputType == 2); + MODIFY_MENU(popupMenu, POPUP_TU_BINH_TRAN_DON_GIAN, vInputType == vTuBinhTranDonGian); MODIFY_MENU(popupMenu, POPUP_UNICODE, vCodeTable == 0); MODIFY_MENU(popupMenu, POPUP_TCVN3, vCodeTable == 1); MODIFY_MENU(popupMenu, POPUP_VNI_WINDOWS, vCodeTable == 2); From a1935462297307548f3d962a84e88e4ac85c853d Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 19:05:14 +0700 Subject: [PATCH 04/60] ponyman: support custom input method dialog and Tu Binh Tran standalone keys --- Sources/OpenKey/engine/DataType.h | 8 +- Sources/OpenKey/engine/Engine.cpp | 135 ++++++- Sources/OpenKey/engine/Engine.h | 1 + Sources/OpenKey/engine/Vietnamese.h | 1 + .../win32/OpenKey/OpenKey/AppDelegate.cpp | 14 +- .../win32/OpenKey/OpenKey/AppDelegate.h | 4 +- .../OpenKey/CustomInputMethodDialog.cpp | 358 ++++++++++++++++++ .../OpenKey/OpenKey/CustomInputMethodDialog.h | 36 ++ .../OpenKey/OpenKey/MainControlDialog.cpp | 8 + .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 8 + .../OpenKey/win32/OpenKey/OpenKey/OpenKey.rc | 25 +- .../win32/OpenKey/OpenKey/OpenKey.vcxproj | 6 + .../win32/OpenKey/OpenKey/OpenKeyManager.cpp | 1 + .../OpenKey/OpenKey/SystemTrayHelper.cpp | 8 + .../OpenKey/win32/OpenKey/OpenKey/resource.h | 11 + .../OpenKeyUpdate/OpenKeyUpdate.vcxproj | 4 + 16 files changed, 622 insertions(+), 6 deletions(-) create mode 100644 Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.cpp create mode 100644 Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.h diff --git a/Sources/OpenKey/engine/DataType.h b/Sources/OpenKey/engine/DataType.h index 54f1d2a5..9b71534e 100644 --- a/Sources/OpenKey/engine/DataType.h +++ b/Sources/OpenKey/engine/DataType.h @@ -35,7 +35,8 @@ enum vKeyInputType { vVNI, vSimpleTelex1, vSimpleTelex2, - vTuBinhTranDonGian + vTuBinhTranDonGian, + vTuDinhNghia }; typedef unsigned char Byte; @@ -81,6 +82,11 @@ struct vKeyHookState { vector macroData; //used for macro function; it is keycode data }; +struct CustomRule { + Uint32 key; + int action; +}; + #ifdef LINUX #include "platforms/linux.h" #elif _WIN32 diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index b2470f5c..497a634f 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -72,6 +72,7 @@ static Uint16 ProcessingChar[][11] = { //Data to sendback to main program vKeyHookState HookState; +vector customRules; //private data /** @@ -1043,6 +1044,25 @@ void checkForStandaloneChar(const Uint16& data, const bool& isCaps, const Uint32 insertKey(data, isCaps); } +void checkForStandaloneCharTBT(const Uint16& data, const bool& isCaps, const Uint32& keyWillReverse, const Uint32& maskToAdd) { + if (_index > 0 && CHR(_index - 1) == keyWillReverse && (TypingWord[_index - 1] & maskToAdd) && (TypingWord[_index - 1] & STANDALONE_MASK)) { + hCode = vWillProcess; + hBPC = 1; + hNCC = 1; + TypingWord[_index - 1] = data | (isCaps ? CAPS_MASK : 0); + hData[0] = GET(TypingWord[_index - 1]); + return; + } + hCode = vWillProcess; + hBPC = 0; + hNCC = 1; + hExt = 4; + TypingWord[_index] = (keyWillReverse | maskToAdd | STANDALONE_MASK | (isCaps ? CAPS_MASK : 0)); + hData[0] = GET(TypingWord[_index]); + _index++; + saveWord(); +} + void upperCaseFirstCharacter() { if (!(TypingWord[0] & CAPS_MASK)) { hCode = vWillProcess; @@ -1066,6 +1086,107 @@ void handleMainKey(const Uint16& data, const bool& isCaps) { return; } + // Tư Bình Trần và Tự Định Nghĩa standalone keys + if (vInputType == vTuBinhTranDonGian) { + if (data == KEY_6) { + checkForStandaloneCharTBT(data, isCaps, KEY_A, TONE_MASK); + return; + } + if (data == KEY_7) { + checkForStandaloneCharTBT(data, isCaps, KEY_E, TONE_MASK); + return; + } + if (data == KEY_8) { + checkForStandaloneCharTBT(data, isCaps, KEY_O, TONE_MASK); + return; + } + if (data == KEY_9) { + checkForStandaloneCharTBT(data, isCaps, KEY_A, TONEW_MASK); + return; + } + } + + if (vInputType == vTuDinhNghia) { + Uint32 lookupKey = data | (isCaps ? CAPS_MASK : 0); + for (const auto& rule : customRules) { + if (rule.key == lookupKey) { + int action = rule.action; + if (action == 0) { + removeMark(); + if (!isChanged) insertKey(data, isCaps); + return; + } + if (action >= 1 && action <= 5) { + Uint32 masks[] = { MARK1_MASK, MARK2_MASK, MARK3_MASK, MARK4_MASK, MARK5_MASK }; + insertMark(masks[action - 1]); + return; + } + if (action == 6) { + findAndCalculateVowel(); + if (vowelCount > 0) { + Uint32 lastVowel = CHR(VEI); + if (lastVowel == KEY_A || lastVowel == KEY_E || lastVowel == KEY_O) { + insertAOE(lastVowel, isCaps); + } + } + return; + } + if (action == 7) { insertAOE(KEY_A, isCaps); return; } + if (action == 8) { insertAOE(KEY_E, isCaps); return; } + if (action == 9) { insertAOE(KEY_O, isCaps); return; } + if (action == 10) { insertW(KEY_W, isCaps); return; } + if (action == 11) { insertW(KEY_W, isCaps); return; } + if (action == 12) { + findAndCalculateVowel(); + if (vowelCount > 0 && CHR(VEI) == KEY_U) { + insertW(KEY_U, isCaps); + } + return; + } + if (action == 13) { + findAndCalculateVowel(); + if (vowelCount > 0 && CHR(VEI) == KEY_O) { + insertW(KEY_O, isCaps); + } + return; + } + if (action == 14) { + findAndCalculateVowel(); + if (vowelCount > 0 && CHR(VEI) == KEY_A) { + insertW(KEY_A, isCaps); + } + return; + } + if (action == 15) { + insertD(data, isCaps); + return; + } + if (action == 16 || action == 17) { + insertW(KEY_W, isCaps); + return; + } + if (action == 18) { + insertKey(data, isCaps); + return; + } + if (action == 19) { checkForStandaloneCharTBT(data, false, KEY_A, TONEW_MASK); return; } + if (action == 20) { checkForStandaloneCharTBT(data, true, KEY_A, TONEW_MASK | CAPS_MASK); return; } + if (action == 21) { checkForStandaloneCharTBT(data, false, KEY_A, TONE_MASK); return; } + if (action == 22) { checkForStandaloneCharTBT(data, true, KEY_A, TONE_MASK | CAPS_MASK); return; } + if (action == 23) { checkForStandaloneCharTBT(data, false, KEY_D, TONE_MASK); return; } + if (action == 24) { checkForStandaloneCharTBT(data, true, KEY_D, TONE_MASK | CAPS_MASK); return; } + if (action == 25) { checkForStandaloneCharTBT(data, false, KEY_E, TONE_MASK); return; } + if (action == 26) { checkForStandaloneCharTBT(data, true, KEY_E, TONE_MASK | CAPS_MASK); return; } + if (action == 27) { checkForStandaloneCharTBT(data, false, KEY_O, TONE_MASK); return; } + if (action == 28) { checkForStandaloneCharTBT(data, true, KEY_O, TONE_MASK | CAPS_MASK); return; } + if (action == 29) { checkForStandaloneCharTBT(data, false, KEY_O, TONEW_MASK); return; } + if (action == 30) { checkForStandaloneCharTBT(data, true, KEY_O, TONEW_MASK | CAPS_MASK); return; } + if (action == 31) { checkForStandaloneCharTBT(data, false, KEY_U, TONEW_MASK); return; } + if (action == 32) { checkForStandaloneCharTBT(data, true, KEY_U, TONEW_MASK | CAPS_MASK); return; } + } + } + } + if (data == KEY_LEFT_BRACKET) { //standalone key [ checkForStandaloneChar(data, isCaps, (vInputType == vTuBinhTranDonGian) ? KEY_U : KEY_O); return; @@ -1549,7 +1670,19 @@ void vKeyHandleEvent(const vKeyEvent& event, } //case [ ] - if (IS_BRACKET_KEY(data) && (( IS_BRACKET_KEY((Uint16)hData[0])) || vInputType == vSimpleTelex1 || vInputType == vSimpleTelex2)) { + bool isCustomStandalone = false; + if (vInputType == vTuDinhNghia) { + Uint32 lookupKey = data | (_isCaps ? CAPS_MASK : 0); + for (const auto& r : customRules) { + if (r.key == lookupKey && r.action >= 19) { + isCustomStandalone = true; + break; + } + } + } + bool isTBTStandalone = (vInputType == vTuBinhTranDonGian) && (IS_BRACKET_KEY(data) || data == KEY_6 || data == KEY_7 || data == KEY_8 || data == KEY_9); + if ((IS_BRACKET_KEY(data) && (( IS_BRACKET_KEY((Uint16)hData[0])) || vInputType == vSimpleTelex1 || vInputType == vSimpleTelex2)) || + ((isTBTStandalone || isCustomStandalone) && ((Uint16)hData[0] == data))) { if (_index - (hCode == vWillProcess ? hBPC : 0) > 0) { _index--; saveWord(); diff --git a/Sources/OpenKey/engine/Engine.h b/Sources/OpenKey/engine/Engine.h index f288f8c5..9f455a79 100644 --- a/Sources/OpenKey/engine/Engine.h +++ b/Sources/OpenKey/engine/Engine.h @@ -52,6 +52,7 @@ extern int vLanguage; * 1: VNI */ extern int vInputType; +extern vector customRules; /** * 0: No diff --git a/Sources/OpenKey/engine/Vietnamese.h b/Sources/OpenKey/engine/Vietnamese.h index a997544d..7b5fa649 100644 --- a/Sources/OpenKey/engine/Vietnamese.h +++ b/Sources/OpenKey/engine/Vietnamese.h @@ -32,6 +32,7 @@ extern map> _quickTelex; extern map> _quickStartConsonant; extern map> _quickEndConsonant; extern map _characterMap; +extern map _keyCodeToChar; extern Uint16 keyCodeToCharacter(const Uint32& keyCode); #endif /* Vietnamese_h */ diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/AppDelegate.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/AppDelegate.cpp index 140d6e4a..3640528f 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/AppDelegate.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/AppDelegate.cpp @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- OpenKey - The Cross platform Open source Vietnamese Keyboard application. Copyright (C) 2019 Mai Vu Tuyen @@ -158,6 +158,9 @@ void AppDelegate::closeDialog(BaseDialog * dialog) { } else if (convertDialog == dialog) { delete convertDialog; convertDialog = NULL; + } else if (customInputMethodDialog == dialog) { + delete customInputMethodDialog; + customInputMethodDialog = NULL; } } @@ -257,6 +260,15 @@ void AppDelegate::onConvertTool() { } } +void AppDelegate::onCustomInputMethodDialog() { + if (customInputMethodDialog == NULL) { + customInputMethodDialog = new CustomInputMethodDialog(hInstance, IDD_DIALOG_CUSTOM_INPUT); + customInputMethodDialog->show(); + } else { + customInputMethodDialog->bringOnTop(); + } +} + void AppDelegate::onQuickConvert() { if (OpenKeyHelper::quickConvert()) { //alert when complete diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/AppDelegate.h b/Sources/OpenKey/win32/OpenKey/OpenKey/AppDelegate.h index dd5ffdaa..7ddc6ede 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/AppDelegate.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/AppDelegate.h @@ -17,12 +17,13 @@ redistribute your new version, it MUST be open source. #include "AboutDialog.h" #include "ConvertToolDialog.h" #include "MacroDialog.h" +#include "CustomInputMethodDialog.h" class BaseDialog; class AppDelegate { private: HINSTANCE hInstance; - BaseDialog* mainDialog = NULL, *aboutDialog = NULL, *macroDialog = NULL, *convertDialog = NULL; + BaseDialog* mainDialog = NULL, *aboutDialog = NULL, *macroDialog = NULL, *convertDialog = NULL, *customInputMethodDialog = NULL; private: bool isDialogMsg(MSG &msg) const; void checkUpdate(); @@ -43,6 +44,7 @@ class AppDelegate { void onMacroTable(); void onConvertTool(); + void onCustomInputMethodDialog(); void onQuickConvert(); void onInputType(const int& type); diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.cpp new file mode 100644 index 00000000..1f91378a --- /dev/null +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.cpp @@ -0,0 +1,358 @@ +/*---------------------------------------------------------- +OpenKey - The Cross platform Open source Vietnamese Keyboard application. + +Copyright (C) 2019 Mai Vu Tuyen +Contact: maivutuyen.91@gmail.com +Github: https://github.com/tuyenvm/OpenKey +Fanpage: https://www.facebook.com/OpenKeyVN + +This file is belong to the OpenKey project, Win32 version +which is released under GPL license. +You can fork, modify, improve this program. If you +redistribute your new version, it MUST be open source. +-----------------------------------------------------------*/ +#include "CustomInputMethodDialog.h" +#include "stdafx.h" +#include "AppDelegate.h" +#include "resource.h" +#include "OpenKeyHelper.h" +#include "../../../engine/Engine.h" +#include "../../../engine/DataType.h" +#include "../../../engine/Vietnamese.h" +#include + +static const TCHAR* customActions[] = { + _T("Xoá dấu"), + _T("Dấu Sắc '"), + _T("Dấu Huyền `"), + _T("Dấu Hỏi ?"), + _T("Dấu Ngã ~"), + _T("Dấu Nặng ."), + _T("Dấu mũ chung cho a, e, o thành â, ê, ô"), + _T("Dấu mũ cho a thành â"), + _T("Dấu mũ cho e thành ê"), + _T("Dấu mũ cho o thành ô"), + _T("Dấu móc cho a, u, o thành ă, ư, ơ"), + _T("Dấu móc cho uo thành ươ"), + _T("Dấu móc cho u thành ư"), + _T("Dấu móc cho o thành ơ"), + _T("Dấu móc cho a thành ă"), + _T("Dấu gạch d thành đ"), + _T("Dấu móc cho a, u, o thành ă, ư, ơ hoặc là chữ ư"), + _T("Dấu móc cho a, u, o thành ă, ư, ơ hoặc là chữ ư trừ chữ bắt đầu của từ"), + _T("Thoát bỏ dấu"), + _T("Chữ ă"), + _T("Chữ Ă"), + _T("Chữ â"), + _T("Chữ Â"), + _T("Chữ đ"), + _T("Chữ Đ"), + _T("Chữ ê"), + _T("Chữ Ê"), + _T("Chữ ô"), + _T("Chữ Ô"), + _T("Chữ ơ"), + _T("Chữ Ơ"), + _T("Chữ ư"), + _T("Chữ Ư") +}; + +static const TCHAR* presetNames[] = { + _T("Telex"), + _T("VNI"), + _T("Simple Telex"), + _T("Tư Bình Trần đơn giản") +}; + +static Uint32 getKeyCodeFromChar(wchar_t ch) { + if (ch >= L'a' && ch <= L'z') { + return (ch - L'a' + 0x41); + } + if (ch >= L'A' && ch <= L'Z') { + return (ch - L'A' + 0x41) | CAPS_MASK; + } + if (_characterMap.find(ch) != _characterMap.end()) { + return _characterMap[ch]; + } + return 0; +} + +static wchar_t getCharFromKeyCode(Uint32 key) { + Uint32 baseKey = key & CHAR_MASK; + bool isCaps = (key & CAPS_MASK) != 0; + if (baseKey >= 0x41 && baseKey <= 0x5A) { + return isCaps ? (wchar_t)baseKey : (wchar_t)(baseKey + 32); + } + if (_keyCodeToChar.find(key) != _keyCodeToChar.end()) { + return _keyCodeToChar[key]; + } + if (_keyCodeToChar.find(baseKey) != _keyCodeToChar.end()) { + return _keyCodeToChar[baseKey]; + } + return L'?'; +} + +CustomInputMethodDialog::CustomInputMethodDialog(const HINSTANCE & hInstance, const int & resourceId) + : BaseDialog(hInstance, resourceId) { +} + +CustomInputMethodDialog::~CustomInputMethodDialog() { +} + +INT_PTR CustomInputMethodDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_INITDIALOG: + this->hDlg = hDlg; + initDialog(); + return TRUE; + case WM_COMMAND: { + int wmId = LOWORD(wParam); + switch (wmId) { + case IDCANCEL: + AppDelegate::getInstance()->closeDialog(this); + break; + case IDOK: + saveRules(); + AppDelegate::getInstance()->closeDialog(this); + break; + case IDC_BTN_LOAD_PRESET: + onLoadPreset(); + break; + case IDC_BTN_ADD_RULE: + onAddRule(); + break; + case IDC_BTN_REPLACE_RULE: + onReplaceRule(); + break; + case IDC_BTN_DELETE_RULE: + onDeleteRule(); + break; + } + break; + } + case WM_NOTIFY: { + LPNMHDR pnmh = (LPNMHDR)lParam; + if (pnmh->hwndFrom == listRules && pnmh->code == LVN_ITEMCHANGED) { + LPNMLISTVIEW pnmv = (LPNMLISTVIEW)lParam; + if ((pnmv->uChanged & LVIF_STATE) && (pnmv->uNewState & LVIS_SELECTED)) { + int index = pnmv->iItem; + if (index >= 0 && index < (int)customRules.size()) { + wchar_t keyStr[2] = { getCharFromKeyCode(customRules[index].key), 0 }; + SetWindowText(editKey, keyStr); + SendMessage(comboAction, CB_SETCURSEL, customRules[index].action, 0); + } + } + } + break; + } + } + return FALSE; +} + +void CustomInputMethodDialog::initDialog() { + SET_DIALOG_ICON(IDI_APP_ICON); + + comboPresets = GetDlgItem(hDlg, IDC_COMBO_PRESETS); + btnLoadPreset = GetDlgItem(hDlg, IDC_BTN_LOAD_PRESET); + editKey = GetDlgItem(hDlg, IDC_EDIT_CUSTOM_KEY); + comboAction = GetDlgItem(hDlg, IDC_COMBO_CUSTOM_ACTION); + btnAdd = GetDlgItem(hDlg, IDC_BTN_ADD_RULE); + btnReplace = GetDlgItem(hDlg, IDC_BTN_REPLACE_RULE); + btnDelete = GetDlgItem(hDlg, IDC_BTN_DELETE_RULE); + listRules = GetDlgItem(hDlg, IDC_LIST_RULES); + + // Max 1 character in key editbox + SendMessage(editKey, EM_SETLIMITTEXT, 1, 0); + + // Load presets combobox + for (int i = 0; i < 4; i++) { + SendMessage(comboPresets, CB_ADDSTRING, 0, (LPARAM)presetNames[i]); + } + SendMessage(comboPresets, CB_SETCURSEL, 0, 0); + + // Load actions combobox + for (int i = 0; i < 33; i++) { + SendMessage(comboAction, CB_ADDSTRING, 0, (LPARAM)customActions[i]); + } + SendMessage(comboAction, CB_SETCURSEL, 0, 0); + + // Config listview columns + SendMessage(listRules, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT); + + LVCOLUMN LvCol; + memset(&LvCol, 0, sizeof(LvCol)); + LvCol.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; + + LvCol.pszText = (LPWSTR)L"Phím"; + LvCol.cx = 60; + SendMessage(listRules, LVM_INSERTCOLUMN, 0, (LPARAM)&LvCol); + + LvCol.pszText = (LPWSTR)L"Dùng cho"; + LvCol.cx = 280; + SendMessage(listRules, LVM_INSERTCOLUMN, 1, (LPARAM)&LvCol); + + fillData(); +} + +void CustomInputMethodDialog::fillData() { + loadRulesList(); +} + +void CustomInputMethodDialog::loadRulesList() { + SendMessage(listRules, LVM_DELETEALLITEMS, 0, 0); + for (int i = 0; i < (int)customRules.size(); i++) { + wchar_t keyStr[2] = { getCharFromKeyCode(customRules[i].key), 0 }; + + LV_ITEM item; + memset(&item, 0, sizeof(item)); + item.mask = LVIF_TEXT; + item.iItem = i; + item.iSubItem = 0; + item.pszText = keyStr; + SendMessage(listRules, LVM_INSERTITEM, 0, (LPARAM)&item); + + item.iSubItem = 1; + item.pszText = (LPWSTR)customActions[customRules[i].action]; + SendMessage(listRules, LVM_SETITEM, 0, (LPARAM)&item); + } +} + +void CustomInputMethodDialog::saveRules() { + OpenKeyHelper::setRegBinary(_T("customRules"), (BYTE*)customRules.data(), (int)(customRules.size() * sizeof(CustomRule))); +} + +void CustomInputMethodDialog::onAddRule() { + TCHAR keyBuffer[2] = { 0 }; + GetWindowText(editKey, keyBuffer, 2); + if (keyBuffer[0] == 0) { + MessageBox(hDlg, _T("Hãy nhập phím!"), _T("OpenKey"), MB_OK); + return; + } + + Uint32 keyCode = getKeyCodeFromChar(keyBuffer[0]); + if (keyCode == 0) { + MessageBox(hDlg, _T("Phím không hợp lệ!"), _T("OpenKey"), MB_OK); + return; + } + + // Check if already exists + for (const auto& r : customRules) { + if (r.key == keyCode) { + MessageBox(hDlg, _T("Phím đã tồn tại trong quy tắc!"), _T("OpenKey"), MB_OK); + return; + } + } + + int action = (int)SendMessage(comboAction, CB_GETCURSEL, 0, 0); + CustomRule newRule = { keyCode, action }; + customRules.push_back(newRule); + loadRulesList(); +} + +void CustomInputMethodDialog::onReplaceRule() { + TCHAR keyBuffer[2] = { 0 }; + GetWindowText(editKey, keyBuffer, 2); + if (keyBuffer[0] == 0) { + MessageBox(hDlg, _T("Hãy nhập phím!"), _T("OpenKey"), MB_OK); + return; + } + + Uint32 keyCode = getKeyCodeFromChar(keyBuffer[0]); + if (keyCode == 0) { + MessageBox(hDlg, _T("Phím không hợp lệ!"), _T("OpenKey"), MB_OK); + return; + } + + int action = (int)SendMessage(comboAction, CB_GETCURSEL, 0, 0); + + for (auto& r : customRules) { + if (r.key == keyCode) { + r.action = action; + loadRulesList(); + return; + } + } + + MessageBox(hDlg, _T("Không tìm thấy phím để thay thế!"), _T("OpenKey"), MB_OK); +} + +void CustomInputMethodDialog::onDeleteRule() { + int sel = (int)SendMessage(listRules, LVM_GETNEXTITEM, -1, LVNI_SELECTED); + if (sel >= 0 && sel < (int)customRules.size()) { + customRules.erase(customRules.begin() + sel); + loadRulesList(); + SetWindowText(editKey, _T("")); + } +} + +void CustomInputMethodDialog::onLoadPreset() { + int sel = (int)SendMessage(comboPresets, CB_GETCURSEL, 0, 0); + customRules.clear(); + + // Presets config + if (sel == 0) { // Telex + customRules.push_back({ getKeyCodeFromChar(L's'), 1 }); + customRules.push_back({ getKeyCodeFromChar(L'f'), 2 }); + customRules.push_back({ getKeyCodeFromChar(L'r'), 3 }); + customRules.push_back({ getKeyCodeFromChar(L'x'), 4 }); + customRules.push_back({ getKeyCodeFromChar(L'j'), 5 }); + customRules.push_back({ getKeyCodeFromChar(L'a'), 7 }); + customRules.push_back({ getKeyCodeFromChar(L'e'), 8 }); + customRules.push_back({ getKeyCodeFromChar(L'o'), 9 }); + customRules.push_back({ getKeyCodeFromChar(L'w'), 16 }); + customRules.push_back({ getKeyCodeFromChar(L'd'), 15 }); + customRules.push_back({ getKeyCodeFromChar(L'z'), 0 }); + customRules.push_back({ getKeyCodeFromChar(L'['), 31 }); + customRules.push_back({ getKeyCodeFromChar(L']'), 29 }); + customRules.push_back({ getKeyCodeFromChar(L'{'), 32 }); + customRules.push_back({ getKeyCodeFromChar(L'}'), 30 }); + } + else if (sel == 1) { // VNI + customRules.push_back({ getKeyCodeFromChar(L'1'), 1 }); + customRules.push_back({ getKeyCodeFromChar(L'2'), 2 }); + customRules.push_back({ getKeyCodeFromChar(L'3'), 3 }); + customRules.push_back({ getKeyCodeFromChar(L'4'), 4 }); + customRules.push_back({ getKeyCodeFromChar(L'5'), 5 }); + customRules.push_back({ getKeyCodeFromChar(L'6'), 6 }); + customRules.push_back({ getKeyCodeFromChar(L'7'), 10 }); + customRules.push_back({ getKeyCodeFromChar(L'8'), 10 }); + customRules.push_back({ getKeyCodeFromChar(L'9'), 15 }); + customRules.push_back({ getKeyCodeFromChar(L'0'), 0 }); + } + else if (sel == 2) { // Simple Telex + customRules.push_back({ getKeyCodeFromChar(L's'), 1 }); + customRules.push_back({ getKeyCodeFromChar(L'f'), 2 }); + customRules.push_back({ getKeyCodeFromChar(L'r'), 3 }); + customRules.push_back({ getKeyCodeFromChar(L'x'), 4 }); + customRules.push_back({ getKeyCodeFromChar(L'j'), 5 }); + customRules.push_back({ getKeyCodeFromChar(L'a'), 7 }); + customRules.push_back({ getKeyCodeFromChar(L'e'), 8 }); + customRules.push_back({ getKeyCodeFromChar(L'o'), 9 }); + customRules.push_back({ getKeyCodeFromChar(L'd'), 15 }); + customRules.push_back({ getKeyCodeFromChar(L'z'), 0 }); + } + else if (sel == 3) { // Tư Bình Trần đơn giản + customRules.push_back({ getKeyCodeFromChar(L'z'), 0 }); + customRules.push_back({ getKeyCodeFromChar(L'd'), 15 }); + customRules.push_back({ getKeyCodeFromChar(L'['), 31 }); + customRules.push_back({ getKeyCodeFromChar(L']'), 29 }); + customRules.push_back({ getKeyCodeFromChar(L'{'), 32 }); + customRules.push_back({ getKeyCodeFromChar(L'}'), 30 }); + customRules.push_back({ getKeyCodeFromChar(L'0'), 0 }); + customRules.push_back({ getKeyCodeFromChar(L'1'), 1 }); + customRules.push_back({ getKeyCodeFromChar(L'2'), 2 }); + customRules.push_back({ getKeyCodeFromChar(L'3'), 3 }); + customRules.push_back({ getKeyCodeFromChar(L'4'), 4 }); + customRules.push_back({ getKeyCodeFromChar(L'5'), 5 }); + customRules.push_back({ getKeyCodeFromChar(L'6'), 21 }); + customRules.push_back({ getKeyCodeFromChar(L'7'), 25 }); + customRules.push_back({ getKeyCodeFromChar(L'8'), 27 }); + customRules.push_back({ getKeyCodeFromChar(L'9'), 19 }); + customRules.push_back({ getKeyCodeFromChar(L'^'), 22 }); + customRules.push_back({ getKeyCodeFromChar(L'&'), 26 }); + customRules.push_back({ getKeyCodeFromChar(L'*'), 28 }); + customRules.push_back({ getKeyCodeFromChar(L'('), 20 }); + } + + loadRulesList(); +} diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.h b/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.h new file mode 100644 index 00000000..d013f692 --- /dev/null +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.h @@ -0,0 +1,36 @@ +/*---------------------------------------------------------- +OpenKey - The Cross platform Open source Vietnamese Keyboard application. + +Copyright (C) 2019 Mai Vu Tuyen +Contact: maivutuyen.91@gmail.com +Github: https://github.com/tuyenvm/OpenKey +Fanpage: https://www.facebook.com/OpenKeyVN + +This file is belong to the OpenKey project, Win32 version +which is released under GPL license. +You can fork, modify, improve this program. If you +redistribute your new version, it MUST be open source. +-----------------------------------------------------------*/ +#pragma once +#include "BaseDialog.h" +#include + +class CustomInputMethodDialog : public BaseDialog { +private: + HWND comboPresets, btnLoadPreset; + HWND editKey, comboAction, btnAdd, btnReplace, btnDelete, listRules; +private: + void loadRulesList(); + void saveRules(); + void onAddRule(); + void onReplaceRule(); + void onDeleteRule(); + void onLoadPreset(); +protected: + INT_PTR eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) override; + void initDialog(); +public: + CustomInputMethodDialog(const HINSTANCE & hInstance, const int & resourceId); + ~CustomInputMethodDialog(); + virtual void fillData() override; +}; diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MainControlDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MainControlDialog.cpp index 3b181dbb..aa869fcb 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MainControlDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MainControlDialog.cpp @@ -13,6 +13,7 @@ redistribute your new version, it MUST be open source. -----------------------------------------------------------*/ #include "MainControlDialog.h" #include "AppDelegate.h" +#include "../../../engine/Engine.h" #include #include @@ -236,6 +237,9 @@ INT_PTR MainControlDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM case IDC_BUTTON_MACRO_TABLE: AppDelegate::getInstance()->onMacroTable(); break; + case IDC_BTN_CUSTOM_INPUT_METHOD: + AppDelegate::getInstance()->onCustomInputMethodDialog(); + break; case IDC_BUTTON_CHECK_UPDATE: onUpdateButton(); break; @@ -326,8 +330,10 @@ INT_PTR MainControlDialog::tabPageEventProc(HWND hDlg, UINT uMsg, WPARAM wParam, void MainControlDialog::fillData() { int sel = vInputType; if (sel == vTuBinhTranDonGian) sel = 3; + else if (sel == vTuDinhNghia) sel = 4; SendMessage(comboBoxInputType, CB_SETCURSEL, sel, 0); SendMessage(comboBoxTableCode, CB_SETCURSEL, vCodeTable, 0); + EnableWindow(GetDlgItem(hDlg, IDC_BTN_CUSTOM_INPUT_METHOD), vInputType == vTuDinhNghia); SendMessage(checkCtrl, BM_SETCHECK, HAS_CONTROL(vSwitchKeyStatus) ? 1 : 0, 0); SendMessage(checkAlt, BM_SETCHECK, HAS_OPTION(vSwitchKeyStatus) ? 1 : 0, 0); @@ -392,7 +398,9 @@ void MainControlDialog::onComboBoxSelected(const HWND& hCombobox, const int& com int sel = (int)SendMessage(hCombobox, CB_GETCURSEL, 0, 0); int type = sel; if (sel == 3) type = vTuBinhTranDonGian; + else if (sel == 4) type = vTuDinhNghia; APP_SET_DATA(vInputType, type); + EnableWindow(GetDlgItem(hDlg, IDC_BTN_CUSTOM_INPUT_METHOD), vInputType == vTuDinhNghia); } else if (hCombobox == comboBoxTableCode) { APP_SET_DATA(vCodeTable, (int)SendMessage(hCombobox, CB_GETCURSEL, 0, 0)); diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index faa179cc..eccbd81d 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -162,6 +162,14 @@ void OpenKeyInit() { BYTE* data = OpenKeyHelper::getRegBinary(_T("smartSwitchKey"), smartSwitchKeySize); initSmartSwitchKey((Byte*)data, (int)smartSwitchKeySize); + //load custom rules + DWORD customRulesSize = 0; + BYTE* customRulesData = OpenKeyHelper::getRegBinary(_T("customRules"), customRulesSize); + if (customRulesData && customRulesSize > 0) { + customRules.assign((CustomRule*)customRulesData, (CustomRule*)customRulesData + (customRulesSize / sizeof(CustomRule))); + free(customRulesData); + } + //init hook HINSTANCE hInstance = GetModuleHandle(NULL); hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboardHookProcess, hInstance, 0); diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc index 25cab5ac..d82225e0 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc @@ -116,7 +116,8 @@ BEGIN PUSHBUTTON "Cài đặt gốc",ID_BTN_DEFAULT,113,234,71,20 GROUPBOX "Điều khiển",IDC_STATIC,7,7,283,73 LTEXT "Kiểu gõ:",IDC_STATIC,14,23,27,8 - COMBOBOX IDC_COMBO_INPUT_TYPE,64,20,75,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_INPUT_TYPE,64,20,73,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "...",IDC_BTN_CUSTOM_INPUT_METHOD,139,20,14,12 LTEXT "Bảng mã:",IDC_STATIC,158,23,38,8 COMBOBOX IDC_COMBO_TABLE_CODE,193,20,90,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP LTEXT "Phím chuyển:",IDC_STATIC,14,41,44,8 @@ -180,7 +181,27 @@ BEGIN COMBOBOX IDC_COMBO_TABLE_CODE_DST,171,135,95,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP LTEXT "Bảng mã nguồn",IDC_STATIC,17,125,52,8 LTEXT "Bảng mã đích",IDC_STATIC,222,125,45,8 - PUSHBUTTON "Đảo",IDC_BUTTON_CONVERT_SWITCH,124,134,36,14 +END + +IDD_DIALOG_CUSTOM_INPUT DIALOGEX 0, 0, 260, 243 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "User defined input method" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Kiểu gõ sẵn có",IDC_STATIC,7,7,246,35 + COMBOBOX IDC_COMBO_PRESETS,14,18,150,80,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Nạp kiểu gõ này",IDC_BTN_LOAD_PRESET,170,17,75,14 + GROUPBOX "Định nghĩa phím",IDC_STATIC,7,47,246,170 + LTEXT "Phím",IDC_STATIC,14,58,20,8 + EDITTEXT IDC_EDIT_CUSTOM_KEY,35,56,30,12,ES_AUTOHSCROLL + LTEXT "Dùng cho",IDC_STATIC,14,73,30,8 + COMBOBOX IDC_COMBO_CUSTOM_ACTION,50,71,195,120,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Thêm",IDC_BTN_ADD_RULE,115,87,60,14 + PUSHBUTTON "Thay thế",IDC_BTN_REPLACE_RULE,180,87,60,14 + CONTROL "",IDC_LIST_RULES,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | WS_BORDER | WS_TABSTOP,14,105,232,90 + PUSHBUTTON "Xoá",IDC_BTN_DELETE_RULE,14,200,45,14 + DEFPUSHBUTTON "Lưu",IDOK,145,222,50,16 + PUSHBUTTON "Đóng",IDCANCEL,203,222,50,16 END IDD_DIALOG_TAB_GENERAL DIALOGEX 0, 0, 353, 137 diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.vcxproj b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.vcxproj index 69b1e312..64fc67eb 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.vcxproj +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.vcxproj @@ -92,6 +92,7 @@ true WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true + /utf-8 %(AdditionalOptions) Windows @@ -107,6 +108,7 @@ true _DEBUG;_WINDOWS;%(PreprocessorDefinitions) true + /utf-8 %(AdditionalOptions) Windows @@ -124,6 +126,7 @@ WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true MultiThreaded + /utf-8 %(AdditionalOptions) Windows @@ -144,6 +147,7 @@ NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true MultiThreaded + /utf-8 %(AdditionalOptions) Windows @@ -167,6 +171,7 @@ + @@ -186,6 +191,7 @@ + diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyManager.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyManager.cpp index 9b5d58e7..8e057f1a 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyManager.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyManager.cpp @@ -19,6 +19,7 @@ static vector _inputType = { _T("VNI"), _T("Simple Telex"), _T("Tư Bình Trần đơn giản"), + _T("Tự định nghĩa"), }; static vector _tableCode = { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/SystemTrayHelper.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/SystemTrayHelper.cpp index 08a0e03c..840ce49c 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/SystemTrayHelper.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/SystemTrayHelper.cpp @@ -13,6 +13,7 @@ redistribute your new version, it MUST be open source. -----------------------------------------------------------*/ #include "SystemTrayHelper.h" #include "AppDelegate.h" +#include "../../../engine/Engine.h" #define WM_TRAYMESSAGE (WM_USER + 1) #define TRAY_ICONUID 100 @@ -26,6 +27,7 @@ redistribute your new version, it MUST be open source. #define POPUP_VNI 911 #define POPUP_SIMPLE_TELEX 912 #define POPUP_TU_BINH_TRAN_DON_GIAN 913 +#define POPUP_TU_DINH_NGHIA 914 #define POPUP_UNICODE 930 #define POPUP_TCVN3 931 @@ -62,6 +64,7 @@ map menuData = { {POPUP_VNI, _T("Kiểu gõ VNI")}, {POPUP_SIMPLE_TELEX, _T("Kiểu gõ Simple Telex")}, {POPUP_TU_BINH_TRAN_DON_GIAN, _T("Kiểu gõ Tư Bình Trần đơn giản")}, + {POPUP_TU_DINH_NGHIA, _T("Kiểu gõ Tự định nghĩa")}, {POPUP_UNICODE, _T("Unicode dựng sẵn")}, {POPUP_TCVN3, _T("TCVN3 (ABC)")}, {POPUP_VNI_WINDOWS, _T("VNI Windows")}, @@ -139,6 +142,9 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case POPUP_TU_BINH_TRAN_DON_GIAN: AppDelegate::getInstance()->onInputType(vTuBinhTranDonGian); break; + case POPUP_TU_DINH_NGHIA: + AppDelegate::getInstance()->onInputType(vTuDinhNghia); + break; case POPUP_UNICODE: AppDelegate::getInstance()->onTableCode(0); break; @@ -222,6 +228,7 @@ void SystemTrayHelper::createPopupMenu() { AppendMenu(popupMenu, MF_CHECKED, POPUP_VNI, menuData[POPUP_VNI]); AppendMenu(popupMenu, MF_CHECKED, POPUP_SIMPLE_TELEX, menuData[POPUP_SIMPLE_TELEX]); AppendMenu(popupMenu, MF_CHECKED, POPUP_TU_BINH_TRAN_DON_GIAN, menuData[POPUP_TU_BINH_TRAN_DON_GIAN]); + AppendMenu(popupMenu, MF_CHECKED, POPUP_TU_DINH_NGHIA, menuData[POPUP_TU_DINH_NGHIA]); //AppendMenu(popupMenu, MF_POPUP, (UINT_PTR)menuInputType, _T("Kiểu gõ")); AppendMenu(popupMenu, MF_SEPARATOR, 0, 0); @@ -270,6 +277,7 @@ void SystemTrayHelper::updateData() { MODIFY_MENU(popupMenu, POPUP_VNI, vInputType == 1); MODIFY_MENU(popupMenu, POPUP_SIMPLE_TELEX, vInputType == 2); MODIFY_MENU(popupMenu, POPUP_TU_BINH_TRAN_DON_GIAN, vInputType == vTuBinhTranDonGian); + MODIFY_MENU(popupMenu, POPUP_TU_DINH_NGHIA, vInputType == vTuDinhNghia); MODIFY_MENU(popupMenu, POPUP_UNICODE, vCodeTable == 0); MODIFY_MENU(popupMenu, POPUP_TCVN3, vCodeTable == 1); MODIFY_MENU(popupMenu, POPUP_VNI_WINDOWS, vCodeTable == 2); diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h index dc1f5b3e..e44f4945 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h @@ -63,6 +63,16 @@ #define IDD_DIALOG_OLD 148 #define IDD_DIALOG_TAB_INFO 149 #define IDS_STRING_OTHER_LANGUAGES 150 +#define IDD_DIALOG_CUSTOM_INPUT 151 + +#define IDC_COMBO_PRESETS 1070 +#define IDC_BTN_LOAD_PRESET 1071 +#define IDC_EDIT_CUSTOM_KEY 1072 +#define IDC_COMBO_CUSTOM_ACTION 1073 +#define IDC_BTN_ADD_RULE 1074 +#define IDC_BTN_REPLACE_RULE 1075 +#define IDC_LIST_RULES 1076 +#define IDC_BTN_DELETE_RULE 1077 #define IDC_COMBO1 1000 #define IDC_COMBO_INPUT_TYPE 1000 #define IDC_CHECK1 1001 @@ -182,6 +192,7 @@ #define IDS_STRING_START_CONSONANT 1066 #define IDS_STRING_END_CONSONANT 1067 #define IDC_CHECK_OTHER_LANGUAGES 1068 +#define IDC_BTN_CUSTOM_INPUT_METHOD 1069 #define IDC_STATIC -1 // Next default values for new objects diff --git a/Sources/OpenKey/win32/OpenKey/OpenKeyUpdate/OpenKeyUpdate.vcxproj b/Sources/OpenKey/win32/OpenKey/OpenKeyUpdate/OpenKeyUpdate.vcxproj index 4b97af97..8cc5a890 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKeyUpdate/OpenKeyUpdate.vcxproj +++ b/Sources/OpenKey/win32/OpenKey/OpenKeyUpdate/OpenKeyUpdate.vcxproj @@ -91,6 +91,7 @@ true _DEBUG;_WINDOWS;%(PreprocessorDefinitions) true + /utf-8 %(AdditionalOptions) Windows @@ -106,6 +107,7 @@ true WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true + /utf-8 %(AdditionalOptions) Windows @@ -124,6 +126,7 @@ WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true MultiThreaded + /utf-8 %(AdditionalOptions) Windows @@ -144,6 +147,7 @@ NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true MultiThreaded + /utf-8 %(AdditionalOptions) Windows From 4811bbfb6b83c04dec790009d7809e3dd1602644 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 19:45:31 +0700 Subject: [PATCH 05/60] ponyman: fix custom input method ignoring keys by registering IS_SPECIALKEY validation and expanding ProcessingChar --- Sources/OpenKey/engine/DataType.h | 3 +- Sources/OpenKey/engine/Engine.cpp | 188 +++++++++++++++++++----------- Sources/OpenKey/engine/Engine.h | 1 + 3 files changed, 126 insertions(+), 66 deletions(-) diff --git a/Sources/OpenKey/engine/DataType.h b/Sources/OpenKey/engine/DataType.h index 9b71534e..3268e7b0 100644 --- a/Sources/OpenKey/engine/DataType.h +++ b/Sources/OpenKey/engine/DataType.h @@ -138,6 +138,7 @@ struct CustomRule { //#define IS_MARK_KEY(keyCode) (keyCode == KEY_S || keyCode == KEY_F || keyCode == KEY_R || keyCode == KEY_J || keyCode == KEY_X) #define CHR(index) (Uint16)TypingWord[index] #define IS_SPECIALKEY(keyCode) \ + (vInputType == vTuDinhNghia ? isSpecialKeyCustom(keyCode) : \ (vInputType == vTelex ? \ keyCode == KEY_W || keyCode == KEY_E || keyCode == KEY_R || keyCode == KEY_O || keyCode == KEY_LEFT_BRACKET || \ keyCode == KEY_RIGHT_BRACKET || keyCode == KEY_A || keyCode == KEY_S || keyCode == KEY_D || keyCode == KEY_F || keyCode == KEY_J || \ @@ -154,7 +155,7 @@ struct CustomRule { : (vInputType == vTuBinhTranDonGian ? \ keyCode == KEY_1 || keyCode == KEY_2 || keyCode == KEY_3 || keyCode == KEY_4 || keyCode == KEY_5 || \ keyCode == KEY_6 || keyCode == KEY_7 || keyCode == KEY_8 || keyCode == KEY_9 || keyCode == KEY_0 || \ - keyCode == KEY_Z || keyCode == KEY_D || keyCode == KEY_LEFT_BRACKET || keyCode == KEY_RIGHT_BRACKET : false))))) + keyCode == KEY_Z || keyCode == KEY_D || keyCode == KEY_LEFT_BRACKET || keyCode == KEY_RIGHT_BRACKET : false)))))) //is VNI or Unicode compound... #define IS_DOUBLE_CODE(code) (code == 2 || code == 3) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 497a634f..52a668b1 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -36,7 +36,8 @@ static Uint16 ProcessingChar[][11] = { {KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0}, //VNI {KEY_S, KEY_F, KEY_R, KEY_X, KEY_J, KEY_A, KEY_O, KEY_E, KEY_W, KEY_D, KEY_Z}, //Simple Telex 1 {KEY_S, KEY_F, KEY_R, KEY_X, KEY_J, KEY_A, KEY_O, KEY_E, KEY_W, KEY_D, KEY_Z}, //Simple Telex 2 - {KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_8, KEY_7, KEY_9, KEY_D, KEY_0} //Tu Binh Tran Don Gian + {KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_8, KEY_7, KEY_9, KEY_D, KEY_0}, //Tu Binh Tran Don Gian + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} //Tu Dinh Nghia }; #define IS_KEY_Z(key) (ProcessingChar[vInputType][10] == key || (vInputType == vTuBinhTranDonGian && key == KEY_Z)) @@ -1044,6 +1045,15 @@ void checkForStandaloneChar(const Uint16& data, const bool& isCaps, const Uint32 insertKey(data, isCaps); } +bool isSpecialKeyCustom(Uint16 keyCode) { + for (const auto& rule : customRules) { + if ((rule.key & CHAR_MASK) == keyCode) { + return true; + } + } + return false; +} + void checkForStandaloneCharTBT(const Uint16& data, const bool& isCaps, const Uint32& keyWillReverse, const Uint32& maskToAdd) { if (_index > 0 && CHR(_index - 1) == keyWillReverse && (TypingWord[_index - 1] & maskToAdd) && (TypingWord[_index - 1] & STANDALONE_MASK)) { hCode = vWillProcess; @@ -1111,78 +1121,126 @@ void handleMainKey(const Uint16& data, const bool& isCaps) { for (const auto& rule : customRules) { if (rule.key == lookupKey) { int action = rule.action; - if (action == 0) { - removeMark(); - if (!isChanged) insertKey(data, isCaps); - return; - } + + auto hasChar = [](Uint16 targetKey) -> bool { + for (int ii = _index - 1; ii >= 0; ii--) { + if (CHR(ii) == targetKey) return true; + } + return false; + }; + + auto hasAnyChar = [&](const vector& targets) -> bool { + for (Uint16 t : targets) { + if (hasChar(t)) return true; + } + return false; + }; + + bool applicable = false; if (action >= 1 && action <= 5) { - Uint32 masks[] = { MARK1_MASK, MARK2_MASK, MARK3_MASK, MARK4_MASK, MARK5_MASK }; - insertMark(masks[action - 1]); - return; - } - if (action == 6) { + vowelCount = 0; findAndCalculateVowel(); - if (vowelCount > 0) { - Uint32 lastVowel = CHR(VEI); - if (lastVowel == KEY_A || lastVowel == KEY_E || lastVowel == KEY_O) { - insertAOE(lastVowel, isCaps); + applicable = (vowelCount > 0); + } else if (action == 6) { + vowelCount = 0; + findAndCalculateVowel(); + applicable = (vowelCount > 0 && (CHR(VEI) == KEY_A || CHR(VEI) == KEY_E || CHR(VEI) == KEY_O)); + } else if (action == 7) { + applicable = hasChar(KEY_A); + } else if (action == 8) { + applicable = hasChar(KEY_E); + } else if (action == 9) { + applicable = hasChar(KEY_O); + } else if (action == 10) { + applicable = hasAnyChar({KEY_A, KEY_U, KEY_O}); + } else if (action == 11) { + applicable = hasChar(KEY_U) && hasChar(KEY_O); + } else if (action == 12) { + applicable = hasChar(KEY_U); + } else if (action == 13) { + applicable = hasChar(KEY_O); + } else if (action == 14) { + applicable = hasChar(KEY_A); + } else if (action == 15) { + applicable = hasChar(KEY_D); + } else { + applicable = true; + } + + if (applicable) { + if (action == 0) { + removeMark(); + if (!isChanged) insertKey(data, isCaps); + return; + } + if (action >= 1 && action <= 5) { + Uint32 masks[] = { MARK1_MASK, MARK2_MASK, MARK3_MASK, MARK4_MASK, MARK5_MASK }; + insertMark(masks[action - 1]); + return; + } + if (action == 6) { + findAndCalculateVowel(); + if (vowelCount > 0) { + Uint32 lastVowel = CHR(VEI); + if (lastVowel == KEY_A || lastVowel == KEY_E || lastVowel == KEY_O) { + insertAOE(lastVowel, isCaps); + } } + return; } - return; - } - if (action == 7) { insertAOE(KEY_A, isCaps); return; } - if (action == 8) { insertAOE(KEY_E, isCaps); return; } - if (action == 9) { insertAOE(KEY_O, isCaps); return; } - if (action == 10) { insertW(KEY_W, isCaps); return; } - if (action == 11) { insertW(KEY_W, isCaps); return; } - if (action == 12) { - findAndCalculateVowel(); - if (vowelCount > 0 && CHR(VEI) == KEY_U) { - insertW(KEY_U, isCaps); + if (action == 7) { insertAOE(KEY_A, isCaps); return; } + if (action == 8) { insertAOE(KEY_E, isCaps); return; } + if (action == 9) { insertAOE(KEY_O, isCaps); return; } + if (action == 10) { insertW(KEY_W, isCaps); return; } + if (action == 11) { insertW(KEY_W, isCaps); return; } + if (action == 12) { + findAndCalculateVowel(); + if (vowelCount > 0 && CHR(VEI) == KEY_U) { + insertW(KEY_U, isCaps); + } + return; } - return; - } - if (action == 13) { - findAndCalculateVowel(); - if (vowelCount > 0 && CHR(VEI) == KEY_O) { - insertW(KEY_O, isCaps); + if (action == 13) { + findAndCalculateVowel(); + if (vowelCount > 0 && CHR(VEI) == KEY_O) { + insertW(KEY_O, isCaps); + } + return; } - return; - } - if (action == 14) { - findAndCalculateVowel(); - if (vowelCount > 0 && CHR(VEI) == KEY_A) { - insertW(KEY_A, isCaps); + if (action == 14) { + findAndCalculateVowel(); + if (vowelCount > 0 && CHR(VEI) == KEY_A) { + insertW(KEY_A, isCaps); + } + return; } - return; - } - if (action == 15) { - insertD(data, isCaps); - return; - } - if (action == 16 || action == 17) { - insertW(KEY_W, isCaps); - return; - } - if (action == 18) { - insertKey(data, isCaps); - return; + if (action == 15) { + insertD(data, isCaps); + return; + } + if (action == 16 || action == 17) { + insertW(KEY_W, isCaps); + return; + } + if (action == 18) { + insertKey(data, isCaps); + return; + } + if (action == 19) { checkForStandaloneCharTBT(data, false, KEY_A, TONEW_MASK); return; } + if (action == 20) { checkForStandaloneCharTBT(data, true, KEY_A, TONEW_MASK | CAPS_MASK); return; } + if (action == 21) { checkForStandaloneCharTBT(data, false, KEY_A, TONE_MASK); return; } + if (action == 22) { checkForStandaloneCharTBT(data, true, KEY_A, TONE_MASK | CAPS_MASK); return; } + if (action == 23) { checkForStandaloneCharTBT(data, false, KEY_D, TONE_MASK); return; } + if (action == 24) { checkForStandaloneCharTBT(data, true, KEY_D, TONE_MASK | CAPS_MASK); return; } + if (action == 25) { checkForStandaloneCharTBT(data, false, KEY_E, TONE_MASK); return; } + if (action == 26) { checkForStandaloneCharTBT(data, true, KEY_E, TONE_MASK | CAPS_MASK); return; } + if (action == 27) { checkForStandaloneCharTBT(data, false, KEY_O, TONE_MASK); return; } + if (action == 28) { checkForStandaloneCharTBT(data, true, KEY_O, TONE_MASK | CAPS_MASK); return; } + if (action == 29) { checkForStandaloneCharTBT(data, false, KEY_O, TONEW_MASK); return; } + if (action == 30) { checkForStandaloneCharTBT(data, true, KEY_O, TONEW_MASK | CAPS_MASK); return; } + if (action == 31) { checkForStandaloneCharTBT(data, false, KEY_U, TONEW_MASK); return; } + if (action == 32) { checkForStandaloneCharTBT(data, true, KEY_U, TONEW_MASK | CAPS_MASK); return; } } - if (action == 19) { checkForStandaloneCharTBT(data, false, KEY_A, TONEW_MASK); return; } - if (action == 20) { checkForStandaloneCharTBT(data, true, KEY_A, TONEW_MASK | CAPS_MASK); return; } - if (action == 21) { checkForStandaloneCharTBT(data, false, KEY_A, TONE_MASK); return; } - if (action == 22) { checkForStandaloneCharTBT(data, true, KEY_A, TONE_MASK | CAPS_MASK); return; } - if (action == 23) { checkForStandaloneCharTBT(data, false, KEY_D, TONE_MASK); return; } - if (action == 24) { checkForStandaloneCharTBT(data, true, KEY_D, TONE_MASK | CAPS_MASK); return; } - if (action == 25) { checkForStandaloneCharTBT(data, false, KEY_E, TONE_MASK); return; } - if (action == 26) { checkForStandaloneCharTBT(data, true, KEY_E, TONE_MASK | CAPS_MASK); return; } - if (action == 27) { checkForStandaloneCharTBT(data, false, KEY_O, TONE_MASK); return; } - if (action == 28) { checkForStandaloneCharTBT(data, true, KEY_O, TONE_MASK | CAPS_MASK); return; } - if (action == 29) { checkForStandaloneCharTBT(data, false, KEY_O, TONEW_MASK); return; } - if (action == 30) { checkForStandaloneCharTBT(data, true, KEY_O, TONEW_MASK | CAPS_MASK); return; } - if (action == 31) { checkForStandaloneCharTBT(data, false, KEY_U, TONEW_MASK); return; } - if (action == 32) { checkForStandaloneCharTBT(data, true, KEY_U, TONEW_MASK | CAPS_MASK); return; } } } } diff --git a/Sources/OpenKey/engine/Engine.h b/Sources/OpenKey/engine/Engine.h index 9f455a79..2091c915 100644 --- a/Sources/OpenKey/engine/Engine.h +++ b/Sources/OpenKey/engine/Engine.h @@ -53,6 +53,7 @@ extern int vLanguage; */ extern int vInputType; extern vector customRules; +bool isSpecialKeyCustom(Uint16 keyCode); /** * 0: No From 5b7ec40ac6a84a284f3480ed39c6b41725dd9a42 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 19:51:57 +0700 Subject: [PATCH 06/60] ponyman: fix presets showing question marks and make custom input dialog resizable --- .../OpenKey/CustomInputMethodDialog.cpp | 41 +++++++++++++++++++ .../OpenKey/win32/OpenKey/OpenKey/OpenKey.rc | 2 +- build.cmd | 1 + 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.cpp index 1f91378a..7ff052a6 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.cpp @@ -83,6 +83,11 @@ static wchar_t getCharFromKeyCode(Uint32 key) { if (baseKey >= 0x41 && baseKey <= 0x5A) { return isCaps ? (wchar_t)baseKey : (wchar_t)(baseKey + 32); } + if (_keyCodeToChar.empty()) { + for (const auto& pair : _characterMap) { + _keyCodeToChar[pair.second] = pair.first; + } + } if (_keyCodeToChar.find(key) != _keyCodeToChar.end()) { return _keyCodeToChar[key]; } @@ -105,6 +110,42 @@ INT_PTR CustomInputMethodDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, this->hDlg = hDlg; initDialog(); return TRUE; + case WM_SIZE: { + int newWidth = LOWORD(lParam); + int newHeight = HIWORD(lParam); + + HWND listRules = GetDlgItem(hDlg, IDC_LIST_RULES); + HWND btnDelete = GetDlgItem(hDlg, IDC_BTN_DELETE_RULE); + HWND btnSave = GetDlgItem(hDlg, IDOK); + HWND btnClose = GetDlgItem(hDlg, IDCANCEL); + HWND groupRules = GetDlgItem(hDlg, IDC_STATIC); // groupbox definition + + if (listRules) { + RECT rect; + GetWindowRect(listRules, &rect); + MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + MoveWindow(listRules, rect.left, rect.top, newWidth - rect.left * 2, newHeight - rect.top - 65, TRUE); + } + if (btnDelete) { + RECT rect; + GetWindowRect(btnDelete, &rect); + MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + MoveWindow(btnDelete, rect.left, newHeight - 50, rect.right - rect.left, rect.bottom - rect.top, TRUE); + } + if (btnSave) { + RECT rect; + GetWindowRect(btnSave, &rect); + MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + MoveWindow(btnSave, newWidth - 145, newHeight - 33, rect.right - rect.left, rect.bottom - rect.top, TRUE); + } + if (btnClose) { + RECT rect; + GetWindowRect(btnClose, &rect); + MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + MoveWindow(btnClose, newWidth - 75, newHeight - 33, rect.right - rect.left, rect.bottom - rect.top, TRUE); + } + break; + } case WM_COMMAND: { int wmId = LOWORD(wParam); switch (wmId) { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc index d82225e0..52b639e7 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc @@ -184,7 +184,7 @@ BEGIN END IDD_DIALOG_CUSTOM_INPUT DIALOGEX 0, 0, 260, 243 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +STYLE DS_SETFONT | WS_THICKFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX CAPTION "User defined input method" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN diff --git a/build.cmd b/build.cmd index f9a8b557..ee1a4958 100644 --- a/build.cmd +++ b/build.cmd @@ -1,6 +1,7 @@ @echo off setlocal enabledelayedexpansion +taskkill /im openkey64.exe /f :: Find VS Installation Path set "VS_PATH=" for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath`) do ( From acc6e8a92bf16545ebe73ec0d176f2fe271261e5 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 20:24:12 +0700 Subject: [PATCH 07/60] ponyman: fix custom layout resizing, implement || macro separation, unicode macro files, multi-line macro support, customizable trigger keys, and capital standalone recovery --- Sources/OpenKey/engine/Engine.cpp | 44 +++++++-- Sources/OpenKey/engine/Engine.h | 2 + Sources/OpenKey/engine/Macro.cpp | 83 ++++++++++------- .../win32/OpenKey/OpenKey/AppDelegate.cpp | 1 + .../OpenKey/CustomInputMethodDialog.cpp | 58 ++++++++---- .../win32/OpenKey/OpenKey/MacroDialog.cpp | 92 ++++++++++++++++++- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 32 ++++++- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.rc | 18 ++-- .../OpenKey/win32/OpenKey/OpenKey/resource.h | 3 + .../OpenKey/win32/OpenKey/OpenKey/stdafx.h | 1 + 10 files changed, 267 insertions(+), 67 deletions(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 52a668b1..a30ca26f 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -12,6 +12,13 @@ #include #include "Macro.h" +#ifndef VK_LSHIFT +#define VK_LSHIFT 0xA0 +#define VK_RSHIFT 0xA1 +#define VK_LCONTROL 0xA2 +#define VK_RCONTROL 0xA3 +#endif + static vector _charKeyCode = { KEY_BACKQUOTE, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUALS, KEY_LEFT_BRACKET, KEY_RIGHT_BRACKET, KEY_BACK_SLASH, @@ -1476,8 +1483,16 @@ void vEnglishMode(const vKeyEventState& state, const Uint16& data, const bool& i if (state == vKeyEventState::MouseDown || (otherControlKey && !isCaps)) { hMacroKey.clear(); _willTempOffEngine = false; - } else if (data == KEY_SPACE) { - if (!_hasHandledMacro && findMacro(hMacroKey, hMacroData)) { + } else if (data == KEY_SPACE || data == KEY_RETURN || data == KEY_ENTER || data == VK_LSHIFT || data == VK_RSHIFT || data == VK_LCONTROL || data == VK_RCONTROL) { + bool isTrigger = false; + if (data == KEY_SPACE && (vMacroTriggerMask & 0x01)) isTrigger = true; + else if ((data == KEY_RETURN || data == KEY_ENTER) && (vMacroTriggerMask & 0x02)) isTrigger = true; + else if (data == VK_LSHIFT && (vMacroTriggerMask & 0x04)) isTrigger = true; + else if (data == VK_RSHIFT && (vMacroTriggerMask & 0x08)) isTrigger = true; + else if (data == VK_LCONTROL && (vMacroTriggerMask & 0x10)) isTrigger = true; + else if (data == VK_RCONTROL && (vMacroTriggerMask & 0x20)) isTrigger = true; + + if (isTrigger && !_hasHandledMacro && findMacro(hMacroKey, hMacroData)) { hCode = vReplaceMaro; hBPC = (Byte)hMacroKey.size(); } @@ -1508,15 +1523,30 @@ void vKeyHandleEvent(const vKeyEvent& event, const bool& otherControlKey) { _isCaps = (capsStatus == 1 || //shift capsStatus == 2); //caps lock + + bool isTriggerModifier = (data == VK_LSHIFT && (vMacroTriggerMask & 0x04)) || + (data == VK_RSHIFT && (vMacroTriggerMask & 0x08)) || + (data == VK_LCONTROL && (vMacroTriggerMask & 0x10)) || + (data == VK_RCONTROL && (vMacroTriggerMask & 0x20)); + if ((IS_NUMBER_KEY(data) && capsStatus == 1) - || otherControlKey || isWordBreak(event, state, data) || (_index == 0 && IS_NUMBER_KEY(data))) { + || otherControlKey || isWordBreak(event, state, data) || (_index == 0 && IS_NUMBER_KEY(data)) || isTriggerModifier) { hCode = vDoNothing; hBPC = 0; hNCC = 0; hExt = 1; //word break //check macro feature - if (vUseMacro && isMacroBreakCode(data) && !_hasHandledMacro && findMacro(hMacroKey, hMacroData)) { + bool canTriggerMacro = false; + if (data == KEY_SPACE && (vMacroTriggerMask & 0x01)) canTriggerMacro = true; + else if ((data == KEY_RETURN || data == KEY_ENTER) && (vMacroTriggerMask & 0x02)) canTriggerMacro = true; + else if (data == VK_LSHIFT && (vMacroTriggerMask & 0x04)) canTriggerMacro = true; + else if (data == VK_RSHIFT && (vMacroTriggerMask & 0x08)) canTriggerMacro = true; + else if (data == VK_LCONTROL && (vMacroTriggerMask & 0x10)) canTriggerMacro = true; + else if (data == VK_RCONTROL && (vMacroTriggerMask & 0x20)) canTriggerMacro = true; + else if (isMacroBreakCode(data)) canTriggerMacro = true; + + if (vUseMacro && canTriggerMacro && !_hasHandledMacro && findMacro(hMacroKey, hMacroData)) { hCode = vReplaceMaro; hBPC = (Byte)hMacroKey.size(); _hasHandledMacro = true; @@ -1575,7 +1605,7 @@ void vKeyHandleEvent(const vKeyEvent& event, if (!tempDisableKey && vCheckSpelling) { checkSpelling(true); //force check spelling } - if (vUseMacro && !_hasHandledMacro && findMacro(hMacroKey, hMacroData)) { //macro + if (vUseMacro && (vMacroTriggerMask & 0x01) && !_hasHandledMacro && findMacro(hMacroKey, hMacroData)) { //macro hCode = vReplaceMaro; hBPC = (Byte)hMacroKey.size(); _spaceCount++; @@ -1739,8 +1769,8 @@ void vKeyHandleEvent(const vKeyEvent& event, } } bool isTBTStandalone = (vInputType == vTuBinhTranDonGian) && (IS_BRACKET_KEY(data) || data == KEY_6 || data == KEY_7 || data == KEY_8 || data == KEY_9); - if ((IS_BRACKET_KEY(data) && (( IS_BRACKET_KEY((Uint16)hData[0])) || vInputType == vSimpleTelex1 || vInputType == vSimpleTelex2)) || - ((isTBTStandalone || isCustomStandalone) && ((Uint16)hData[0] == data))) { + if ((IS_BRACKET_KEY(data) && (( IS_BRACKET_KEY( (((Uint16)hData[0] & ~CAPS_MASK)) )) || vInputType == vSimpleTelex1 || vInputType == vSimpleTelex2)) || + ((isTBTStandalone || isCustomStandalone) && ((((Uint16)hData[0] & ~CAPS_MASK)) == data))) { if (_index - (hCode == vWillProcess ? hBPC : 0) > 0) { _index--; saveWord(); diff --git a/Sources/OpenKey/engine/Engine.h b/Sources/OpenKey/engine/Engine.h index 2091c915..84257c11 100644 --- a/Sources/OpenKey/engine/Engine.h +++ b/Sources/OpenKey/engine/Engine.h @@ -115,6 +115,8 @@ extern int vFixRecommendBrowser; */ extern int vUseMacro; +extern int vMacroTriggerMask; + /** * Still use macro if you are in english mode */ diff --git a/Sources/OpenKey/engine/Macro.cpp b/Sources/OpenKey/engine/Macro.cpp index 66d202de..440a5c5e 100644 --- a/Sources/OpenKey/engine/Macro.cpp +++ b/Sources/OpenKey/engine/Macro.cpp @@ -2,6 +2,7 @@ // Macro.cpp // OpenKey // +#define _CRT_SECURE_NO_WARNINGS // Created by Tuyen on 8/4/19. // Copyright © 2019 Tuyen Mai. All rights reserved. // @@ -246,48 +247,68 @@ void onTableCodeChange() { } void saveToFile(const string& path) { - ofstream myfile; - myfile.open(path.c_str()); - myfile << ";Compatible OpenKey Macro Data file for UniKey*** version=1 ***\n"; - for (std::map, MacroData>::iterator it = macroMap.begin(); it != macroMap.end(); ++it) { - myfile <second.macroText << ":" << it->second.macroContent<<"\n"; + wstring wpath = utf8ToWideString(path); + FILE* f = _wfopen(wpath.c_str(), L"w, ccs=UTF-8"); + if (f) { + fwprintf(f, L";Compatible OpenKey Macro Data file for UniKey*** version=1 ***\n"); + for (std::map, MacroData>::iterator it = macroMap.begin(); it != macroMap.end(); ++it) { + wstring name = utf8ToWideString(it->second.macroText); + wstring content = utf8ToWideString(it->second.macroContent); + + // Escape newlines as \n + size_t start_pos = 0; + while ((start_pos = content.find(L"\n", start_pos)) != wstring::npos) { + content.replace(start_pos, 1, L"\\n"); + start_pos += 2; + } + // Remove \r just in case + start_pos = 0; + while ((start_pos = content.find(L"\r", start_pos)) != wstring::npos) { + content.erase(start_pos, 1); + } + + fwprintf(f, L"%s||%s\n", name.c_str(), content.c_str()); + } + fclose(f); } - myfile.close(); } void readFromFile(const string& path, const bool& append) { - ifstream myfile(path.c_str()); - string line; - int k = 0; - size_t pos = 0; - string name, content; - if (myfile.is_open()) { + wstring wpath = utf8ToWideString(path); + FILE* f = _wfopen(wpath.c_str(), L"r, ccs=UTF-8"); + if (f) { if (!append) { macroMap.clear(); } - while (getline (myfile,line) ) { + wchar_t line[8192]; + int k = 0; + while (fgetws(line, 8192, f)) { + size_t len = wcslen(line); + while (len > 0 && (line[len-1] == L'\n' || line[len-1] == L'\r')) { + line[len-1] = L'\0'; + len--; + } k++; if (k == 1) continue; - pos = line.find(":"); - if (string::npos != pos) { - name = line.substr(0, pos); - content = line.substr(pos + 1, line.length() - pos - 1); - while (name.compare("") == 0 && content.compare("") != 0) { - pos = content.find(":"); - if (string::npos != pos) { - name += ":"; - name += content.substr(0, pos); - content = content.substr(pos + 1, line.length() - pos - 1); - } else { - break; - } - } - - if (name.compare("") != 0 && !hasMacro(name)) { - addMacro(name, content); + + wstring wline(line); + size_t pos = wline.find(L"||"); + if (pos != wstring::npos) { + wstring wname = wline.substr(0, pos); + wstring wcontent = wline.substr(pos + 2); + + // Replace escaped \n with real newlines + size_t start_pos = 0; + while ((start_pos = wcontent.find(L"\\n", start_pos)) != wstring::npos) { + wcontent.replace(start_pos, 2, L"\n"); + start_pos += 1; + } + + if (!wname.empty()) { + addMacro(wideStringToUtf8(wname), wideStringToUtf8(wcontent)); } } } - myfile.close(); + fclose(f); } } diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/AppDelegate.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/AppDelegate.cpp index 3640528f..e785d75b 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/AppDelegate.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/AppDelegate.cpp @@ -31,6 +31,7 @@ int vUseMacro = 1; int vUseMacroInEnglishMode = 1; int vAutoCapsMacro = 0; int vSendKeyStepByStep = 1; +int vMacroTriggerMask = 3; int vUseSmartSwitchKey = 1; int vUpperCaseFirstChar = 0; int vTempOffSpelling = 0; diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.cpp index 7ff052a6..373bffd9 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.cpp @@ -118,31 +118,57 @@ INT_PTR CustomInputMethodDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, HWND btnDelete = GetDlgItem(hDlg, IDC_BTN_DELETE_RULE); HWND btnSave = GetDlgItem(hDlg, IDOK); HWND btnClose = GetDlgItem(hDlg, IDCANCEL); - HWND groupRules = GetDlgItem(hDlg, IDC_STATIC); // groupbox definition - + HWND grpPresets = GetDlgItem(hDlg, IDC_GRP_PRESETS); + HWND comboPresets = GetDlgItem(hDlg, IDC_COMBO_PRESETS); + HWND btnLoadPreset = GetDlgItem(hDlg, IDC_BTN_LOAD_PRESET); + HWND grpDefineKey = GetDlgItem(hDlg, IDC_GRP_DEFINE_KEY); + HWND comboAction = GetDlgItem(hDlg, IDC_COMBO_CUSTOM_ACTION); + HWND btnAddRule = GetDlgItem(hDlg, IDC_BTN_ADD_RULE); + HWND btnReplaceRule = GetDlgItem(hDlg, IDC_BTN_REPLACE_RULE); + + if (grpPresets) { + RECT rect; GetWindowRect(grpPresets, &rect); MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + MoveWindow(grpPresets, rect.left, rect.top, newWidth - rect.left * 2, rect.bottom - rect.top, TRUE); + } + if (comboPresets) { + RECT rect; GetWindowRect(comboPresets, &rect); MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + MoveWindow(comboPresets, rect.left, rect.top, newWidth - 110, rect.bottom - rect.top, TRUE); + } + if (btnLoadPreset) { + RECT rect; GetWindowRect(btnLoadPreset, &rect); MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + MoveWindow(btnLoadPreset, newWidth - 90, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); + } + if (grpDefineKey) { + RECT rect; GetWindowRect(grpDefineKey, &rect); MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + MoveWindow(grpDefineKey, rect.left, rect.top, newWidth - rect.left * 2, newHeight - rect.top - 40, TRUE); + } + if (comboAction) { + RECT rect; GetWindowRect(comboAction, &rect); MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + MoveWindow(comboAction, rect.left, rect.top, newWidth - rect.left - 15, rect.bottom - rect.top, TRUE); + } + if (btnAddRule) { + RECT rect; GetWindowRect(btnAddRule, &rect); MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + MoveWindow(btnAddRule, newWidth - 145, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); + } + if (btnReplaceRule) { + RECT rect; GetWindowRect(btnReplaceRule, &rect); MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + MoveWindow(btnReplaceRule, newWidth - 80, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); + } if (listRules) { - RECT rect; - GetWindowRect(listRules, &rect); - MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + RECT rect; GetWindowRect(listRules, &rect); MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); MoveWindow(listRules, rect.left, rect.top, newWidth - rect.left * 2, newHeight - rect.top - 65, TRUE); } if (btnDelete) { - RECT rect; - GetWindowRect(btnDelete, &rect); - MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + RECT rect; GetWindowRect(btnDelete, &rect); MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); MoveWindow(btnDelete, rect.left, newHeight - 50, rect.right - rect.left, rect.bottom - rect.top, TRUE); } if (btnSave) { - RECT rect; - GetWindowRect(btnSave, &rect); - MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); - MoveWindow(btnSave, newWidth - 145, newHeight - 33, rect.right - rect.left, rect.bottom - rect.top, TRUE); + RECT rect; GetWindowRect(btnSave, &rect); MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + MoveWindow(btnSave, newWidth - 145, newHeight - 27, rect.right - rect.left, rect.bottom - rect.top, TRUE); } if (btnClose) { - RECT rect; - GetWindowRect(btnClose, &rect); - MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); - MoveWindow(btnClose, newWidth - 75, newHeight - 33, rect.right - rect.left, rect.bottom - rect.top, TRUE); + RECT rect; GetWindowRect(btnClose, &rect); MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rect, 2); + MoveWindow(btnClose, newWidth - 75, newHeight - 27, rect.right - rect.left, rect.bottom - rect.top, TRUE); } break; } diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index 1cf89124..da526b49 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- OpenKey - The Cross platform Open source Vietnamese Keyboard application. Copyright (C) 2019 Mai Vu Tuyen @@ -33,6 +33,68 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara this->hDlg = hDlg; initDialog(); return TRUE; + case WM_MEASUREITEM: { + LPMEASUREITEMSTRUCT lpmis = (LPMEASUREITEMSTRUCT)lParam; + if (lpmis->CtlID == IDC_COMBO_MACRO_TRIGGERS) { + lpmis->itemHeight = 18; + return TRUE; + } + break; + } + case WM_DRAWITEM: { + LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam; + if (lpdis->CtlID == IDC_COMBO_MACRO_TRIGGERS && lpdis->itemID != -1) { + int bitIndex = lpdis->itemID; + bool checked = (vMacroTriggerMask & (1 << bitIndex)) != 0; + + if (lpdis->itemState & ODS_SELECTED) { + SetBkColor(lpdis->hDC, GetSysColor(COLOR_HIGHLIGHT)); + SetTextColor(lpdis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); + } else { + SetBkColor(lpdis->hDC, GetSysColor(COLOR_WINDOW)); + SetTextColor(lpdis->hDC, GetSysColor(COLOR_WINDOWTEXT)); + } + ExtTextOut(lpdis->hDC, 0, 0, ETO_OPAQUE, &lpdis->rcItem, NULL, 0, NULL); + + if (lpdis->itemState & ODS_COMBOBOXEDIT) { + wstring summary = L""; + if (vMacroTriggerMask & 0x01) summary += L"Space, "; + if (vMacroTriggerMask & 0x02) summary += L"Enter, "; + if (vMacroTriggerMask & 0x04) summary += L"LShift, "; + if (vMacroTriggerMask & 0x08) summary += L"RShift, "; + if (vMacroTriggerMask & 0x10) summary += L"LControl, "; + if (vMacroTriggerMask & 0x20) summary += L"RControl, "; + if (!summary.empty()) { + summary = summary.substr(0, summary.length() - 2); + } else { + summary = L"(Không)"; + } + RECT rcText = lpdis->rcItem; + rcText.left += 4; + DrawText(lpdis->hDC, summary.c_str(), -1, &rcText, DT_SINGLELINE | DT_VCENTER); + } else { + RECT rcCheck = lpdis->rcItem; + rcCheck.left += 4; + rcCheck.right = rcCheck.left + 14; + rcCheck.top += (rcCheck.bottom - rcCheck.top - 14) / 2; + rcCheck.bottom = rcCheck.top + 14; + DrawFrameControl(lpdis->hDC, &rcCheck, DFC_BUTTON, DFCS_BUTTONCHECK | (checked ? DFCS_CHECKED : 0)); + + TCHAR buffer[64]; + SendMessage(lpdis->hwndItem, CB_GETLBTEXT, lpdis->itemID, (LPARAM)buffer); + + RECT rcText = lpdis->rcItem; + rcText.left += 22; + DrawText(lpdis->hDC, buffer, -1, &rcText, DT_SINGLELINE | DT_VCENTER); + } + + if (lpdis->itemState & ODS_FOCUS) { + DrawFocusRect(lpdis->hDC, &lpdis->rcItem); + } + return TRUE; + } + break; + } case WM_COMMAND: { int wmId = LOWORD(wParam); switch (wmId) { @@ -52,6 +114,18 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara case IDC_BUTTON_EXPORT_MACRO: onExportMacrobutton(); break; + case IDC_COMBO_MACRO_TRIGGERS: + if (HIWORD(wParam) == CBN_SELCHANGE) { + HWND hCombo = (HWND)lParam; + int sel = (int)SendMessage(hCombo, CB_GETCURSEL, 0, 0); + if (sel != -1) { + vMacroTriggerMask ^= (1 << sel); + APP_SET_DATA(vMacroTriggerMask, vMacroTriggerMask); + InvalidateRect(hCombo, NULL, TRUE); + SendMessage(hCombo, CB_SETCURSEL, (WPARAM)-1, 0); + } + } + break; default: if (HIWORD(wParam) == EN_CHANGE) { if ((HWND)lParam == hMacroName) { @@ -96,6 +170,17 @@ void MacroDialog::initDialog() { hAddButton = GetDlgItem(hDlg, IDC_BUTTON_ADD); hAutoCaps = GetDlgItem(hDlg, IDC_CHECK_AUTO_CAPS); + HWND hComboTriggers = GetDlgItem(hDlg, IDC_COMBO_MACRO_TRIGGERS); + if (hComboTriggers) { + SendMessage(hComboTriggers, CB_ADDSTRING, 0, (LPARAM)_T("Space")); + SendMessage(hComboTriggers, CB_ADDSTRING, 0, (LPARAM)_T("Enter")); + SendMessage(hComboTriggers, CB_ADDSTRING, 0, (LPARAM)_T("Left Shift")); + SendMessage(hComboTriggers, CB_ADDSTRING, 0, (LPARAM)_T("Right Shift")); + SendMessage(hComboTriggers, CB_ADDSTRING, 0, (LPARAM)_T("Left Control")); + SendMessage(hComboTriggers, CB_ADDSTRING, 0, (LPARAM)_T("Right Control")); + SendMessage(hComboTriggers, CB_SETCURSEL, (WPARAM)-1, 0); + } + LVCOLUMN LvCol; memset(&LvCol, 0, sizeof(LvCol)); LvCol.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; @@ -128,6 +213,11 @@ void MacroDialog::saveAndReload() { getMacroSaveData(macroData); OpenKeyHelper::setRegBinary(_T("macroData"), macroData.data(), (int)macroData.size()); + // Save to openkeymacro.txt in executable folder + wstring macroPath = OpenKeyHelper::getExecutePath(); + macroPath += L"\\openkeymacro.txt"; + saveToFile(wideStringToUtf8(macroPath)); + //reload data fillData(); } diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index eccbd81d..4ae948f3 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -11,6 +11,7 @@ which is released under GPL license. You can fork, modify, improve this program. If you redistribute your new version, it MUST be open source. -----------------------------------------------------------*/ +#define _CRT_SECURE_NO_WARNINGS #include "stdafx.h" #include "AppDelegate.h" @@ -92,6 +93,7 @@ void OpenKeyInit() { APP_GET_DATA(vUseMacroInEnglishMode, 0); APP_GET_DATA(vAutoCapsMacro, 0); APP_GET_DATA(vSendKeyStepByStep, 1); + APP_GET_DATA(vMacroTriggerMask, 3); APP_GET_DATA(vUseGrayIcon, 0); APP_GET_DATA(vShowOnStartUp, 1); APP_GET_DATA(vRunWithWindows, 1); @@ -152,10 +154,19 @@ void OpenKeyInit() { if (GetKeyState(VK_CAPITAL) == 1) _flag |= MASK_CAPITAL; if (GetKeyState(VK_SCROLL) < 0) _flag |= MASK_SCROLL; - //init and load macro data - DWORD macroDataSize; - BYTE* macroData = OpenKeyHelper::getRegBinary(_T("macroData"), macroDataSize); - initMacroMap((Byte*)macroData, (int)macroDataSize); + //init and load macro data from file, fallback to registry + wstring macroPath = OpenKeyHelper::getExecutePath(); + macroPath += L"\\openkeymacro.txt"; + FILE* fMacro = _wfopen(macroPath.c_str(), L"r"); + if (fMacro) { + fclose(fMacro); + readFromFile(wideStringToUtf8(macroPath), false); + } else { + DWORD macroDataSize; + BYTE* macroData = OpenKeyHelper::getRegBinary(_T("macroData"), macroDataSize); + initMacroMap((Byte*)macroData, (int)macroDataSize); + saveToFile(wideStringToUtf8(macroPath)); + } //init and load smart switch key data DWORD smartSwitchKeySize; @@ -520,6 +531,19 @@ LRESULT CALLBACK keyboardHookProcess(int nCode, WPARAM wParam, LPARAM lParam) { //LOG(L"Key up: %d\n", keyboardData->vkCode); UnsetModifierMask((Uint16)keyboardData->vkCode); } + + bool isTriggerModifier = false; + if (vUseMacro) { + if (keyboardData->vkCode == VK_LSHIFT && (vMacroTriggerMask & 0x04)) isTriggerModifier = true; + else if (keyboardData->vkCode == VK_RSHIFT && (vMacroTriggerMask & 0x08)) isTriggerModifier = true; + else if (keyboardData->vkCode == VK_LCONTROL && (vMacroTriggerMask & 0x10)) isTriggerModifier = true; + else if (keyboardData->vkCode == VK_RCONTROL && (vMacroTriggerMask & 0x20)) isTriggerModifier = true; + } + + if (isTriggerModifier) { + _isFlagKey = false; + } + if (!_isFlagKey && wParam != WM_KEYUP && wParam != WM_SYSKEYUP) _keycode = (Uint16)keyboardData->vkCode; diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc index 52b639e7..efb073a1 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc @@ -140,17 +140,19 @@ STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTI CAPTION "Thiết lập gõ tắt - OpenKey" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - CONTROL "",IDC_LIST_MACRO_DATA,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP,7,41,401,186 + CONTROL "",IDC_LIST_MACRO_DATA,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP,7,54,401,173 LTEXT "Từ gõ tắt",IDC_STATIC,7,6,30,8 LTEXT "Nội dung đầy đủ",IDC_STATIC,90,6,53,8 EDITTEXT IDC_EDIT_MACRO_NAME,7,18,72,14,ES_AUTOHSCROLL - EDITTEXT IDC_EDIT_MACRO_CONTENT,82,18,234,14,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL - PUSHBUTTON "+ Thêm",IDC_BUTTON_ADD,320,18,43,14 - PUSHBUTTON "- Xóa",IDC_BUTTON_DELETE,365,18,43,14 + EDITTEXT IDC_EDIT_MACRO_CONTENT,82,18,234,30,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | ES_NOHIDESEL | WS_VSCROLL + PUSHBUTTON "+ Thêm",IDC_BUTTON_ADD,320,18,88,14 + PUSHBUTTON "- Xóa",IDC_BUTTON_DELETE,320,34,88,14 PUSHBUTTON "Nạp từ file...",IDC_BUTTON_IMPORT_MACRO,7,232,66,14 PUSHBUTTON "Xuất ra file...",IDC_BUTTON_EXPORT_MACRO,82,232,66,14 - CONTROL "Tự động đổi chữ hoa theo phím tắt",IDC_CHECK_AUTO_CAPS, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,167,234,221,10 + CONTROL "Tự động đổi chữ hoa",IDC_CHECK_AUTO_CAPS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,155,234,80,10 + LTEXT "Phím gõ tắt:",IDC_STATIC,242,234,40,8 + COMBOBOX IDC_COMBO_MACRO_TRIGGERS,285,232,123,80,CBS_DROPDOWNLIST | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP END IDD_DIALOG_CONVERT_TOOL DIALOGEX 0, 0, 283, 194 @@ -188,10 +190,10 @@ STYLE DS_SETFONT | WS_THICKFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTI CAPTION "User defined input method" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - GROUPBOX "Kiểu gõ sẵn có",IDC_STATIC,7,7,246,35 + GROUPBOX "Kiểu gõ sẵn có",IDC_GRP_PRESETS,7,7,246,35 COMBOBOX IDC_COMBO_PRESETS,14,18,150,80,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP PUSHBUTTON "Nạp kiểu gõ này",IDC_BTN_LOAD_PRESET,170,17,75,14 - GROUPBOX "Định nghĩa phím",IDC_STATIC,7,47,246,170 + GROUPBOX "Định nghĩa phím",IDC_GRP_DEFINE_KEY,7,47,246,170 LTEXT "Phím",IDC_STATIC,14,58,20,8 EDITTEXT IDC_EDIT_CUSTOM_KEY,35,56,30,12,ES_AUTOHSCROLL LTEXT "Dùng cho",IDC_STATIC,14,73,30,8 diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h index e44f4945..fc26d81f 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h @@ -193,6 +193,9 @@ #define IDS_STRING_END_CONSONANT 1067 #define IDC_CHECK_OTHER_LANGUAGES 1068 #define IDC_BTN_CUSTOM_INPUT_METHOD 1069 +#define IDC_GRP_DEFINE_KEY 1080 +#define IDC_GRP_PRESETS 1081 +#define IDC_COMBO_MACRO_TRIGGERS 1085 #define IDC_STATIC -1 // Next default values for new objects diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/stdafx.h b/Sources/OpenKey/win32/OpenKey/OpenKey/stdafx.h index 8960e217..a328287d 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/stdafx.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/stdafx.h @@ -73,6 +73,7 @@ extern int vUseMacro; extern int vUseMacroInEnglishMode; extern int vAutoCapsMacro; extern int vSendKeyStepByStep; +extern int vMacroTriggerMask; extern int vUseSmartSwitchKey; extern int vUpperCaseFirstChar; extern int vUseGrayIcon; From c06e78a3bcd3768154a4fbdc5532864fd02aa32a Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 20:34:48 +0700 Subject: [PATCH 08/60] ponyman: disable shift-number word break for TBT and Custom layouts to allow capital standalone inputs --- Sources/OpenKey/engine/Engine.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index a30ca26f..482ac17d 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -1529,7 +1529,11 @@ void vKeyHandleEvent(const vKeyEvent& event, (data == VK_LCONTROL && (vMacroTriggerMask & 0x10)) || (data == VK_RCONTROL && (vMacroTriggerMask & 0x20)); - if ((IS_NUMBER_KEY(data) && capsStatus == 1) + bool isTBTOrCustomNumberAction = (vInputType == vTuBinhTranDonGian || vInputType == vTuDinhNghia) && + (data == KEY_6 || data == KEY_7 || data == KEY_8 || data == KEY_9); + bool isNumberBreak = (IS_NUMBER_KEY(data) && capsStatus == 1) && !isTBTOrCustomNumberAction; + + if (isNumberBreak || otherControlKey || isWordBreak(event, state, data) || (_index == 0 && IS_NUMBER_KEY(data)) || isTriggerModifier) { hCode = vDoNothing; hBPC = 0; From 1a3bdeaae13d1a2a4f4aaafb8dba6cc76e113b4e Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 20:50:50 +0700 Subject: [PATCH 09/60] ponyman: fix standalone key start break and backspace double-tap recovery --- Sources/OpenKey/engine/Engine.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 482ac17d..8325b5be 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -1066,6 +1066,7 @@ void checkForStandaloneCharTBT(const Uint16& data, const bool& isCaps, const Uin hCode = vWillProcess; hBPC = 1; hNCC = 1; + hExt = 4; TypingWord[_index - 1] = data | (isCaps ? CAPS_MASK : 0); hData[0] = GET(TypingWord[_index - 1]); return; @@ -1532,9 +1533,10 @@ void vKeyHandleEvent(const vKeyEvent& event, bool isTBTOrCustomNumberAction = (vInputType == vTuBinhTranDonGian || vInputType == vTuDinhNghia) && (data == KEY_6 || data == KEY_7 || data == KEY_8 || data == KEY_9); bool isNumberBreak = (IS_NUMBER_KEY(data) && capsStatus == 1) && !isTBTOrCustomNumberAction; + bool isNumberAtStartBreak = (_index == 0 && IS_NUMBER_KEY(data)) && !isTBTOrCustomNumberAction; if (isNumberBreak - || otherControlKey || isWordBreak(event, state, data) || (_index == 0 && IS_NUMBER_KEY(data)) || isTriggerModifier) { + || otherControlKey || isWordBreak(event, state, data) || isNumberAtStartBreak || isTriggerModifier) { hCode = vDoNothing; hBPC = 0; hNCC = 0; From a56cf64c2c6d531fb5ea0b4f74e879d7ea3c457a Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 21:06:52 +0700 Subject: [PATCH 10/60] ponyman: support unicode emoji surrogate pairs in macro encoding and sending --- Sources/OpenKey/engine/Macro.cpp | 7 +++ .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 43 ++++++++++++++++--- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Sources/OpenKey/engine/Macro.cpp b/Sources/OpenKey/engine/Macro.cpp index 440a5c5e..fe3e0424 100644 --- a/Sources/OpenKey/engine/Macro.cpp +++ b/Sources/OpenKey/engine/Macro.cpp @@ -35,6 +35,13 @@ static void convert(const string& str, vector& outData) { int k = 0; for (int i = 0; i < data.size(); i++) { t = (Uint32)data[i]; + if (t >= 0xD800 && t <= 0xDBFF && i + 1 < data.size()) { + Uint32 low = data[i + 1]; + if (low >= 0xDC00 && low <= 0xDFFF) { + t = 0x10000 + ((t - 0xD800) << 10) + (low - 0xDC00); + i++; + } + } //find normal character fist if (_characterMap.find(t) != _characterMap.end()) { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index 4ae948f3..cd1c8760 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -332,9 +332,24 @@ static void SendNewCharString(const bool& dataFromMacro = false) { _tempChar = DYNA_DATA(dataFromMacro, _k); if (_tempChar & PURE_CHARACTER_MASK) { - _newCharString[_j++] = _tempChar; - if (IS_DOUBLE_CODE(vCodeTable)) { - InsertKeyLength(1); + Uint32 pureChar = _tempChar & ~PURE_CHARACTER_MASK; + if (pureChar <= 0xFFFF) { + _newCharString[_j++] = (Uint16)pureChar; + if (IS_DOUBLE_CODE(vCodeTable)) { + InsertKeyLength(1); + } + } else { + Uint16 high = (Uint16)(((pureChar - 0x10000) >> 10) + 0xD800); + Uint16 low = (Uint16)(((pureChar - 0x10000) & 0x3FF) + 0xDC00); + if (_newCharString.size() < _newCharSize + 1) { + _newCharString.resize(_newCharSize + 1); + } + _newCharString[_j++] = high; + _newCharString[_j++] = low; + _newCharSize++; + if (IS_DOUBLE_CODE(vCodeTable)) { + InsertKeyLength(2); + } } } else if (!(_tempChar & CHAR_CODE_MASK)) { if (IS_DOUBLE_CODE(vCodeTable)) //VNI @@ -432,16 +447,30 @@ void switchLanguage() { startNewSession(); } -static void SendPureCharacter(const Uint16& ch) { +static void SendPureCharacter(const Uint32& ch) { if (ch < 128) SendKeyCode(ch); - else { - prepareUnicodeEvent(keyEvent[0], ch, true); - prepareUnicodeEvent(keyEvent[1], ch, false); + else if (ch <= 0xFFFF) { + prepareUnicodeEvent(keyEvent[0], (Uint16)ch, true); + prepareUnicodeEvent(keyEvent[1], (Uint16)ch, false); SendInput(2, keyEvent, sizeof(INPUT)); if (IS_DOUBLE_CODE(vCodeTable)) { InsertKeyLength(1); } + } else { + Uint16 high = (Uint16)(((ch - 0x10000) >> 10) + 0xD800); + Uint16 low = (Uint16)(((ch - 0x10000) & 0x3FF) + 0xDC00); + + prepareUnicodeEvent(keyEvent[0], high, true); + prepareUnicodeEvent(keyEvent[1], high, false); + SendInput(2, keyEvent, sizeof(INPUT)); + + prepareUnicodeEvent(keyEvent[0], low, true); + prepareUnicodeEvent(keyEvent[1], low, false); + SendInput(2, keyEvent, sizeof(INPUT)); + if (IS_DOUBLE_CODE(vCodeTable)) { + InsertKeyLength(2); + } } } From 91fe7928c8200465eb02b8ad959dadd16c14cc47 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 22:04:54 +0700 Subject: [PATCH 11/60] ponyman: change macro file to openkeymacro.md and use robust UTF-8 binary stream reading/writing --- Sources/OpenKey/engine/Macro.cpp | 72 +++++++++++-------- .../win32/OpenKey/OpenKey/MacroDialog.cpp | 4 +- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 9 +-- 3 files changed, 49 insertions(+), 36 deletions(-) diff --git a/Sources/OpenKey/engine/Macro.cpp b/Sources/OpenKey/engine/Macro.cpp index fe3e0424..9c53fb17 100644 --- a/Sources/OpenKey/engine/Macro.cpp +++ b/Sources/OpenKey/engine/Macro.cpp @@ -254,68 +254,80 @@ void onTableCodeChange() { } void saveToFile(const string& path) { - wstring wpath = utf8ToWideString(path); - FILE* f = _wfopen(wpath.c_str(), L"w, ccs=UTF-8"); - if (f) { - fwprintf(f, L";Compatible OpenKey Macro Data file for UniKey*** version=1 ***\n"); + ofstream myfile(path.c_str(), ios::out | ios::binary); + if (myfile.is_open()) { + // Write UTF-8 BOM + unsigned char bom[] = {0xEF, 0xBB, 0xBF}; + myfile.write((char*)bom, 3); + + string header = ";Compatible OpenKey Macro Data file for UniKey*** version=1 ***\n"; + myfile.write(header.c_str(), header.length()); + for (std::map, MacroData>::iterator it = macroMap.begin(); it != macroMap.end(); ++it) { - wstring name = utf8ToWideString(it->second.macroText); - wstring content = utf8ToWideString(it->second.macroContent); + string name = it->second.macroText; + string content = it->second.macroContent; // Escape newlines as \n size_t start_pos = 0; - while ((start_pos = content.find(L"\n", start_pos)) != wstring::npos) { - content.replace(start_pos, 1, L"\\n"); + while ((start_pos = content.find("\n", start_pos)) != string::npos) { + content.replace(start_pos, 1, "\\n"); start_pos += 2; } // Remove \r just in case start_pos = 0; - while ((start_pos = content.find(L"\r", start_pos)) != wstring::npos) { + while ((start_pos = content.find("\r", start_pos)) != string::npos) { content.erase(start_pos, 1); } - fwprintf(f, L"%s||%s\n", name.c_str(), content.c_str()); + string line = name + "||" + content + "\n"; + myfile.write(line.c_str(), line.length()); } - fclose(f); + myfile.close(); } } void readFromFile(const string& path, const bool& append) { - wstring wpath = utf8ToWideString(path); - FILE* f = _wfopen(wpath.c_str(), L"r, ccs=UTF-8"); - if (f) { + ifstream myfile(path.c_str(), ios::in | ios::binary); + if (myfile.is_open()) { if (!append) { macroMap.clear(); } - wchar_t line[8192]; + + // Skip UTF-8 BOM if present + unsigned char bom[3] = {0}; + myfile.read((char*)bom, 3); + if (!(bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF)) { + // No BOM, seek back to beginning + myfile.seekg(0); + } + + string line; int k = 0; - while (fgetws(line, 8192, f)) { - size_t len = wcslen(line); - while (len > 0 && (line[len-1] == L'\n' || line[len-1] == L'\r')) { - line[len-1] = L'\0'; - len--; + while (getline(myfile, line)) { + // Remove trailing \r or \n + while (!line.empty() && (line.back() == '\r' || line.back() == '\n')) { + line.pop_back(); } k++; if (k == 1) continue; - wstring wline(line); - size_t pos = wline.find(L"||"); - if (pos != wstring::npos) { - wstring wname = wline.substr(0, pos); - wstring wcontent = wline.substr(pos + 2); + size_t pos = line.find("||"); + if (pos != string::npos) { + string name = line.substr(0, pos); + string content = line.substr(pos + 2); // Replace escaped \n with real newlines size_t start_pos = 0; - while ((start_pos = wcontent.find(L"\\n", start_pos)) != wstring::npos) { - wcontent.replace(start_pos, 2, L"\n"); + while ((start_pos = content.find("\\n", start_pos)) != string::npos) { + content.replace(start_pos, 2, "\n"); start_pos += 1; } - if (!wname.empty()) { - addMacro(wideStringToUtf8(wname), wideStringToUtf8(wcontent)); + if (!name.empty()) { + addMacro(name, content); } } } - fclose(f); + myfile.close(); } } diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index da526b49..2afe0211 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -213,9 +213,9 @@ void MacroDialog::saveAndReload() { getMacroSaveData(macroData); OpenKeyHelper::setRegBinary(_T("macroData"), macroData.data(), (int)macroData.size()); - // Save to openkeymacro.txt in executable folder + // Save to openkeymacro.md in executable folder wstring macroPath = OpenKeyHelper::getExecutePath(); - macroPath += L"\\openkeymacro.txt"; + macroPath += L"\\openkeymacro.md"; saveToFile(wideStringToUtf8(macroPath)); //reload data diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index cd1c8760..5702ab1d 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -14,6 +14,7 @@ redistribute your new version, it MUST be open source. #define _CRT_SECURE_NO_WARNINGS #include "stdafx.h" #include "AppDelegate.h" +#include #pragma comment(lib, "imm32") #define IMC_GETOPENSTATUS 0x0005 @@ -156,10 +157,10 @@ void OpenKeyInit() { //init and load macro data from file, fallback to registry wstring macroPath = OpenKeyHelper::getExecutePath(); - macroPath += L"\\openkeymacro.txt"; - FILE* fMacro = _wfopen(macroPath.c_str(), L"r"); - if (fMacro) { - fclose(fMacro); + macroPath += L"\\openkeymacro.md"; + ifstream testFile(wideStringToUtf8(macroPath).c_str(), ios::in | ios::binary); + if (testFile.is_open()) { + testFile.close(); readFromFile(wideStringToUtf8(macroPath), false); } else { DWORD macroDataSize; From e6d704c25ab50ba0cf009548860f39c77004c1f1 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 22:19:46 +0700 Subject: [PATCH 12/60] ponyman: change macro dialog filters and default extension to .md --- Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index 2afe0211..e2f618ad 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -293,7 +293,7 @@ void MacroDialog::onImportMacroButton() { ofn.hwndOwner = hDlg; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); - ofn.lpstrFilter = _T("Text file (*.txt)\0*.txt\0All (*.*)\0*.*\0"); + ofn.lpstrFilter = _T("Markdown file (*.md)\0*.md\0All (*.*)\0*.*\0"); ofn.nFilterIndex = 1; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = 0; @@ -321,13 +321,13 @@ void MacroDialog::onExportMacrobutton() { ofn.hwndOwner = hDlg; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); - ofn.lpstrFilter = _T("Text file (*.txt)\0*.txt\0"); + ofn.lpstrFilter = _T("Markdown file (*.md)\0*.md\0"); ofn.lpstrFileTitle = (LPTSTR)_T("lpstrFile"); ofn.nFilterIndex = 1; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = 0; ofn.lpstrInitialDir = NULL; - ofn.lpstrDefExt = (LPCWSTR)L"txt"; + ofn.lpstrDefExt = (LPCWSTR)L"md"; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; if (GetSaveFileName(&ofn) == TRUE) { wstring path = ofn.lpstrFile; From b7c3608aaa9ac0013a87192751a4b28510e3ba78 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 22:32:13 +0700 Subject: [PATCH 13/60] ponyman: fix macro pure character mask causing invalid unicode characters --- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index 5702ab1d..d082f2f7 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -321,8 +321,8 @@ static void SendEmptyCharacter() { static void SendNewCharString(const bool& dataFromMacro = false) { _j = 0; _newCharSize = dataFromMacro ? (Uint16)pData->macroData.size() : pData->newCharCount; - if (_newCharString.size() < _newCharSize) { - _newCharString.resize(_newCharSize); + if (_newCharString.size() < _newCharSize + 2) { + _newCharString.resize(_newCharSize + 2); } _willSendControlKey = false; @@ -335,6 +335,9 @@ static void SendNewCharString(const bool& dataFromMacro = false) { if (_tempChar & PURE_CHARACTER_MASK) { Uint32 pureChar = _tempChar & ~PURE_CHARACTER_MASK; if (pureChar <= 0xFFFF) { + if (_newCharString.size() < _j + 2) { + _newCharString.resize(_j + 2); + } _newCharString[_j++] = (Uint16)pureChar; if (IS_DOUBLE_CODE(vCodeTable)) { InsertKeyLength(1); @@ -342,8 +345,8 @@ static void SendNewCharString(const bool& dataFromMacro = false) { } else { Uint16 high = (Uint16)(((pureChar - 0x10000) >> 10) + 0xD800); Uint16 low = (Uint16)(((pureChar - 0x10000) & 0x3FF) + 0xDC00); - if (_newCharString.size() < _newCharSize + 1) { - _newCharString.resize(_newCharSize + 1); + if (_newCharString.size() < _j + 3) { + _newCharString.resize(_j + 3); } _newCharString[_j++] = high; _newCharString[_j++] = low; @@ -355,19 +358,31 @@ static void SendNewCharString(const bool& dataFromMacro = false) { } else if (!(_tempChar & CHAR_CODE_MASK)) { if (IS_DOUBLE_CODE(vCodeTable)) //VNI InsertKeyLength(1); + if (_newCharString.size() < _j + 2) { + _newCharString.resize(_j + 2); + } _newCharString[_j++] = keyCodeToCharacter(_tempChar); } else { _newChar = _tempChar; if (vCodeTable == 0) { //unicode 2 bytes code + if (_newCharString.size() < _j + 2) { + _newCharString.resize(_j + 2); + } _newCharString[_j++] = _newChar; } else if (vCodeTable == 1 || vCodeTable == 2 || vCodeTable == 4) { //others such as VNI Windows, TCVN3: 1 byte code _newCharHi = HIBYTE(_newChar); _newChar = LOBYTE(_newChar); + if (_newCharString.size() < _j + 2) { + _newCharString.resize(_j + 2); + } _newCharString[_j++] = _newChar; if (_newCharHi > 32) { if (vCodeTable == 2) //VNI InsertKeyLength(2); + if (_newCharString.size() < _j + 2) { + _newCharString.resize(_j + 2); + } _newCharString[_j++] = _newCharHi; _newCharSize++; } @@ -380,9 +395,15 @@ static void SendNewCharString(const bool& dataFromMacro = false) { _newChar &= 0x1FFF; InsertKeyLength(_newCharHi > 0 ? 2 : 1); + if (_newCharString.size() < _j + 2) { + _newCharString.resize(_j + 2); + } _newCharString[_j++] = _newChar; if (_newCharHi > 0) { _newCharSize++; + if (_newCharString.size() < _j + 2) { + _newCharString.resize(_j + 2); + } _newCharString[_j++] = _unicodeCompoundMark[_newCharHi - 1]; } @@ -394,6 +415,9 @@ static void SendNewCharString(const bool& dataFromMacro = false) { if (pData->code == vRestore || pData->code == vRestoreAndStartNewSession) { //if is restore if (keyCodeToCharacter(_keycode) != 0) { _newCharSize++; + if (_newCharString.size() < _j + 2) { + _newCharString.resize(_j + 2); + } _newCharString[_j++] = keyCodeToCharacter(_keycode | ((_flag & MASK_SHIFT) || (_flag & MASK_CAPITAL) ? CAPS_MASK : 0)); } else { _willSendControlKey = true; @@ -403,7 +427,12 @@ static void SendNewCharString(const bool& dataFromMacro = false) { startNewSession(); } - OpenKeyHelper::setClipboardText((LPCTSTR)_newCharString.data(), _newCharSize + 1, CF_UNICODETEXT); + if (_newCharString.size() < _j + 1) { + _newCharString.resize(_j + 1); + } + _newCharString[_j] = 0; + + OpenKeyHelper::setClipboardText((LPCTSTR)_newCharString.data(), _j + 1, CF_UNICODETEXT); //Send shift + insert SendCombineKey(KEY_LEFT_SHIFT, VK_INSERT, 0, KEYEVENTF_EXTENDEDKEY); @@ -449,18 +478,17 @@ void switchLanguage() { } static void SendPureCharacter(const Uint32& ch) { - if (ch < 128) - SendKeyCode(ch); - else if (ch <= 0xFFFF) { - prepareUnicodeEvent(keyEvent[0], (Uint16)ch, true); - prepareUnicodeEvent(keyEvent[1], (Uint16)ch, false); + Uint32 pureChar = ch & ~PURE_CHARACTER_MASK; + if (pureChar <= 0xFFFF) { + prepareUnicodeEvent(keyEvent[0], (Uint16)pureChar, true); + prepareUnicodeEvent(keyEvent[1], (Uint16)pureChar, false); SendInput(2, keyEvent, sizeof(INPUT)); if (IS_DOUBLE_CODE(vCodeTable)) { InsertKeyLength(1); } } else { - Uint16 high = (Uint16)(((ch - 0x10000) >> 10) + 0xD800); - Uint16 low = (Uint16)(((ch - 0x10000) & 0x3FF) + 0xDC00); + Uint16 high = (Uint16)(((pureChar - 0x10000) >> 10) + 0xD800); + Uint16 low = (Uint16)(((pureChar - 0x10000) & 0x3FF) + 0xDC00); prepareUnicodeEvent(keyEvent[0], high, true); prepareUnicodeEvent(keyEvent[1], high, false); From 13474fdc4af288fc1ee9954cebd7acff89e930a4 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Fri, 14 Aug 2026 22:37:35 +0700 Subject: [PATCH 14/60] ponyman: normalize macro newlines to CRLF and handle Enter key correctly in all editors --- Sources/OpenKey/engine/Macro.cpp | 55 ++++++++++++++----- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 21 ++++++- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/Sources/OpenKey/engine/Macro.cpp b/Sources/OpenKey/engine/Macro.cpp index 9c53fb17..f0bba8cf 100644 --- a/Sources/OpenKey/engine/Macro.cpp +++ b/Sources/OpenKey/engine/Macro.cpp @@ -72,6 +72,24 @@ static void convert(const string& str, vector& outData) { } } +static string normalizeNewlines(const string& str) { + string result; + result.reserve(str.size() + 16); + for (size_t i = 0; i < str.size(); i++) { + if (str[i] == '\r') { + if (i + 1 < str.size() && str[i + 1] == '\n') { + i++; + } + result += "\r\n"; + } else if (str[i] == '\n') { + result += "\r\n"; + } else { + result += str[i]; + } + } + return result; +} + /** * data structure: * byte 0 and 1: macro count @@ -107,11 +125,11 @@ void initMacroMap(const Byte* pData, const int& size) { MacroData data; data.macroText = macroText; - data.macroContent = macroContent; + data.macroContent = normalizeNewlines(macroContent); vector key; convert(macroText, key); - convert(macroContent, data.macroContentCode); + convert(data.macroContent, data.macroContentCode); macroMap[key] = data; } @@ -224,15 +242,16 @@ void getAllMacro(vector>& keys, vector& macroTexts, vecto bool addMacro(const string& macroText, const string& macroContent) { vector key; convert(macroText, key); + string normalizedContent = normalizeNewlines(macroContent); if (macroMap.find(key) == macroMap.end()) { //add new macro MacroData data; data.macroText = macroText; - data.macroContent = macroContent; - convert(macroContent, data.macroContentCode); + data.macroContent = normalizedContent; + convert(normalizedContent, data.macroContentCode); macroMap[key] = data; } else { //edit this macro - macroMap[key].macroContent = macroContent; - convert(macroContent, macroMap[key].macroContentCode); + macroMap[key].macroContent = normalizedContent; + convert(normalizedContent, macroMap[key].macroContentCode); } return true; } @@ -267,16 +286,22 @@ void saveToFile(const string& path) { string name = it->second.macroText; string content = it->second.macroContent; - // Escape newlines as \n + // Normalize CRLF or lone CR to \n first size_t start_pos = 0; - while ((start_pos = content.find("\n", start_pos)) != string::npos) { - content.replace(start_pos, 1, "\\n"); - start_pos += 2; + while ((start_pos = content.find("\r\n", start_pos)) != string::npos) { + content.replace(start_pos, 2, "\n"); + start_pos += 1; } - // Remove \r just in case start_pos = 0; while ((start_pos = content.find("\r", start_pos)) != string::npos) { - content.erase(start_pos, 1); + content.replace(start_pos, 1, "\n"); + start_pos += 1; + } + // Escape newlines as \n + start_pos = 0; + while ((start_pos = content.find("\n", start_pos)) != string::npos) { + content.replace(start_pos, 1, "\\n"); + start_pos += 2; } string line = name + "||" + content + "\n"; @@ -316,11 +341,11 @@ void readFromFile(const string& path, const bool& append) { string name = line.substr(0, pos); string content = line.substr(pos + 2); - // Replace escaped \n with real newlines + // Replace escaped \n with real CRLF size_t start_pos = 0; while ((start_pos = content.find("\\n", start_pos)) != string::npos) { - content.replace(start_pos, 2, "\n"); - start_pos += 1; + content.replace(start_pos, 2, "\r\n"); + start_pos += 2; } if (!name.empty()) { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index d082f2f7..f83f5c75 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -520,9 +520,24 @@ static void handleMacro() { if (!vSendKeyStepByStep) { SendNewCharString(true); } else { - for (int i = 0; i < pData->macroData.size(); i++) { - if (pData->macroData[i] & PURE_CHARACTER_MASK) { - SendPureCharacter(pData->macroData[i]); + for (size_t i = 0; i < pData->macroData.size(); i++) { + Uint32 ch = pData->macroData[i]; + if (ch & PURE_CHARACTER_MASK) { + Uint32 pure = ch & ~PURE_CHARACTER_MASK; + if (pure == '\r') { + if (i + 1 < pData->macroData.size() && (pData->macroData[i + 1] & ~PURE_CHARACTER_MASK) == '\n') { + i++; + } + prepareKeyEvent(keyEvent[0], VK_RETURN, true); + prepareKeyEvent(keyEvent[1], VK_RETURN, false); + SendInput(2, keyEvent, sizeof(INPUT)); + } else if (pure == '\n') { + prepareKeyEvent(keyEvent[0], VK_RETURN, true); + prepareKeyEvent(keyEvent[1], VK_RETURN, false); + SendInput(2, keyEvent, sizeof(INPUT)); + } else { + SendPureCharacter(ch); + } } else { SendKeyCode(pData->macroData[i]); } From ec93e891deda3c1435d359047b9b2d5f5dc7842c Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 07:19:18 +0700 Subject: [PATCH 15/60] ponyman: luon bung macro cung mot luc --- .agents | 1 + .vscode/c_cpp_properties.json | 18 ++++++ .vscode/launch.json | 24 ++++++++ .vscode/settings.json | 59 +++++++++++++++++++ Sources/OpenKey/macOS/ModernKey/OpenKey.mm | 12 +--- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 27 +-------- 6 files changed, 104 insertions(+), 37 deletions(-) create mode 120000 .agents create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json diff --git a/.agents b/.agents new file mode 120000 index 00000000..7a5a7084 --- /dev/null +++ b/.agents @@ -0,0 +1 @@ +R:/HDD R/ZC SYMLINK/USERS/.agents \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..cea4d3f4 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "windows-gcc-x64", + "includePath": [ + "${workspaceFolder}/**" + ], + "compilerPath": "gcc", + "cStandard": "${default}", + "cppStandard": "${default}", + "intelliSenseMode": "windows-gcc-x64", + "compilerArgs": [ + "" + ] + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..7bd41674 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "C/C++ Runner: Debug Session", + "type": "cppdbg", + "request": "launch", + "args": [], + "stopAtEntry": false, + "externalConsole": true, + "cwd": "c:/Users/Admin/source/repos/ghostminhtoan/openkey/Sources/OpenKey/win32/OpenKey/OpenKey", + "program": "c:/Users/Admin/source/repos/ghostminhtoan/openkey/Sources/OpenKey/win32/OpenKey/OpenKey/build/Debug/outDebug", + "MIMode": "gdb", + "miDebuggerPath": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..3e5eb956 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,59 @@ +{ + "C_Cpp_Runner.cCompilerPath": "gcc", + "C_Cpp_Runner.cppCompilerPath": "g++", + "C_Cpp_Runner.debuggerPath": "gdb", + "C_Cpp_Runner.cStandard": "", + "C_Cpp_Runner.cppStandard": "", + "C_Cpp_Runner.msvcBatchPath": "", + "C_Cpp_Runner.useMsvc": false, + "C_Cpp_Runner.warnings": [ + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wshadow", + "-Wformat=2", + "-Wcast-align", + "-Wconversion", + "-Wsign-conversion", + "-Wnull-dereference" + ], + "C_Cpp_Runner.msvcWarnings": [ + "/W4", + "/permissive-", + "/w14242", + "/w14287", + "/w14296", + "/w14311", + "/w14826", + "/w44062", + "/w44242", + "/w14905", + "/w14906", + "/w14263", + "/w44265", + "/w14928" + ], + "C_Cpp_Runner.enableWarnings": true, + "C_Cpp_Runner.warningsAsError": false, + "C_Cpp_Runner.compilerArgs": [], + "C_Cpp_Runner.linkerArgs": [], + "C_Cpp_Runner.includePaths": [], + "C_Cpp_Runner.includeSearch": [ + "*", + "**/*" + ], + "C_Cpp_Runner.excludeSearch": [ + "**/build", + "**/build/**", + "**/.*", + "**/.*/**", + "**/.vscode", + "**/.vscode/**" + ], + "C_Cpp_Runner.useAddressSanitizer": false, + "C_Cpp_Runner.useUndefinedSanitizer": false, + "C_Cpp_Runner.useLeakSanitizer": false, + "C_Cpp_Runner.showCompilationTime": false, + "C_Cpp_Runner.useLinkTimeOptimization": false, + "C_Cpp_Runner.msvcSecureNoWarnings": false +} \ No newline at end of file diff --git a/Sources/OpenKey/macOS/ModernKey/OpenKey.mm b/Sources/OpenKey/macOS/ModernKey/OpenKey.mm index 1e6b6c14..237f9e3d 100644 --- a/Sources/OpenKey/macOS/ModernKey/OpenKey.mm +++ b/Sources/OpenKey/macOS/ModernKey/OpenKey.mm @@ -555,17 +555,7 @@ void handleMacro() { } } //send real data - if (!vSendKeyStepByStep) { - SendNewCharString(true); - } else { - for (int i = 0; i < pData->macroData.size(); i++) { - if (pData->macroData[i] & PURE_CHARACTER_MASK) { - SendPureCharacter(pData->macroData[i]); - } else { - SendKeyCode(pData->macroData[i]); - } - } - } + SendNewCharString(true); SendKeyCode(_keycode | (_flag & kCGEventFlagMaskShift ? CAPS_MASK : 0)); } diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index f83f5c75..b5dec080 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -517,32 +517,7 @@ static void handleMacro() { } } //send real data - if (!vSendKeyStepByStep) { - SendNewCharString(true); - } else { - for (size_t i = 0; i < pData->macroData.size(); i++) { - Uint32 ch = pData->macroData[i]; - if (ch & PURE_CHARACTER_MASK) { - Uint32 pure = ch & ~PURE_CHARACTER_MASK; - if (pure == '\r') { - if (i + 1 < pData->macroData.size() && (pData->macroData[i + 1] & ~PURE_CHARACTER_MASK) == '\n') { - i++; - } - prepareKeyEvent(keyEvent[0], VK_RETURN, true); - prepareKeyEvent(keyEvent[1], VK_RETURN, false); - SendInput(2, keyEvent, sizeof(INPUT)); - } else if (pure == '\n') { - prepareKeyEvent(keyEvent[0], VK_RETURN, true); - prepareKeyEvent(keyEvent[1], VK_RETURN, false); - SendInput(2, keyEvent, sizeof(INPUT)); - } else { - SendPureCharacter(ch); - } - } else { - SendKeyCode(pData->macroData[i]); - } - } - } + SendNewCharString(true); SendKeyCode(_keycode | (_flag & MASK_SHIFT ? CAPS_MASK : 0)); } From a9e27cf7faa5ea2c7c7efa000324e3f8b29474af Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 07:24:13 +0700 Subject: [PATCH 16/60] ponyman: bung macro bang cach gui phim truc tiep khong qua clipboard --- Sources/OpenKey/macOS/ModernKey/OpenKey.mm | 8 ++++++- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 23 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Sources/OpenKey/macOS/ModernKey/OpenKey.mm b/Sources/OpenKey/macOS/ModernKey/OpenKey.mm index 237f9e3d..70d2ee48 100644 --- a/Sources/OpenKey/macOS/ModernKey/OpenKey.mm +++ b/Sources/OpenKey/macOS/ModernKey/OpenKey.mm @@ -555,7 +555,13 @@ void handleMacro() { } } //send real data - SendNewCharString(true); + for (int i = 0; i < pData->macroData.size(); i++) { + if (pData->macroData[i] & PURE_CHARACTER_MASK) { + SendPureCharacter(pData->macroData[i]); + } else { + SendKeyCode(pData->macroData[i]); + } + } SendKeyCode(_keycode | (_flag & kCGEventFlagMaskShift ? CAPS_MASK : 0)); } diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index b5dec080..54f2b0c2 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -517,7 +517,28 @@ static void handleMacro() { } } //send real data - SendNewCharString(true); + for (size_t i = 0; i < pData->macroData.size(); i++) { + Uint32 ch = pData->macroData[i]; + if (ch & PURE_CHARACTER_MASK) { + Uint32 pure = ch & ~PURE_CHARACTER_MASK; + if (pure == '\r') { + if (i + 1 < pData->macroData.size() && (pData->macroData[i + 1] & ~PURE_CHARACTER_MASK) == '\n') { + i++; + } + prepareKeyEvent(keyEvent[0], VK_RETURN, true); + prepareKeyEvent(keyEvent[1], VK_RETURN, false); + SendInput(2, keyEvent, sizeof(INPUT)); + } else if (pure == '\n') { + prepareKeyEvent(keyEvent[0], VK_RETURN, true); + prepareKeyEvent(keyEvent[1], VK_RETURN, false); + SendInput(2, keyEvent, sizeof(INPUT)); + } else { + SendPureCharacter(ch); + } + } else { + SendKeyCode(pData->macroData[i]); + } + } SendKeyCode(_keycode | (_flag & MASK_SHIFT ? CAPS_MASK : 0)); } From 22c6b2c7d86ca28612df9cb88fee9ce3031e2309 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 07:26:42 +0700 Subject: [PATCH 17/60] ponyman: luu va khoi phuc clipboard khi paste macro tren Windows --- Sources/OpenKey/macOS/ModernKey/OpenKey.mm | 8 +---- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 34 ++++++++----------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/Sources/OpenKey/macOS/ModernKey/OpenKey.mm b/Sources/OpenKey/macOS/ModernKey/OpenKey.mm index 70d2ee48..237f9e3d 100644 --- a/Sources/OpenKey/macOS/ModernKey/OpenKey.mm +++ b/Sources/OpenKey/macOS/ModernKey/OpenKey.mm @@ -555,13 +555,7 @@ void handleMacro() { } } //send real data - for (int i = 0; i < pData->macroData.size(); i++) { - if (pData->macroData[i] & PURE_CHARACTER_MASK) { - SendPureCharacter(pData->macroData[i]); - } else { - SendKeyCode(pData->macroData[i]); - } - } + SendNewCharString(true); SendKeyCode(_keycode | (_flag & kCGEventFlagMaskShift ? CAPS_MASK : 0)); } diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index 54f2b0c2..5a27abf4 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -516,29 +516,23 @@ static void handleMacro() { SendBackspace(); } } + // Backup clipboard + wstring backupText = OpenKeyHelper::getClipboardText(CF_UNICODETEXT); + //send real data - for (size_t i = 0; i < pData->macroData.size(); i++) { - Uint32 ch = pData->macroData[i]; - if (ch & PURE_CHARACTER_MASK) { - Uint32 pure = ch & ~PURE_CHARACTER_MASK; - if (pure == '\r') { - if (i + 1 < pData->macroData.size() && (pData->macroData[i + 1] & ~PURE_CHARACTER_MASK) == '\n') { - i++; - } - prepareKeyEvent(keyEvent[0], VK_RETURN, true); - prepareKeyEvent(keyEvent[1], VK_RETURN, false); - SendInput(2, keyEvent, sizeof(INPUT)); - } else if (pure == '\n') { - prepareKeyEvent(keyEvent[0], VK_RETURN, true); - prepareKeyEvent(keyEvent[1], VK_RETURN, false); - SendInput(2, keyEvent, sizeof(INPUT)); - } else { - SendPureCharacter(ch); - } - } else { - SendKeyCode(pData->macroData[i]); + SendNewCharString(true); + + // Restore clipboard + if (!backupText.empty()) { + OpenKeyHelper::setClipboardText(backupText.c_str(), (int)backupText.size() + 1, CF_UNICODETEXT); + } else { + // If clipboard was empty, clear it + if (OpenClipboard(nullptr)) { + EmptyClipboard(); + CloseClipboard(); } } + SendKeyCode(_keycode | (_flag & MASK_SHIFT ? CAPS_MASK : 0)); } From 3ca7a4ed9c9bae6464129fec3b469f570813a35b Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 07:37:44 +0700 Subject: [PATCH 18/60] ponyman: fix clipboard lock in getClipboardText --- Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp index a18f67e1..bcacaf1e 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp @@ -172,12 +172,14 @@ wstring OpenKeyHelper::getClipboardText(const int& type) { // Get handle of clipboard object for ANSI text HANDLE hData = GetClipboardData(type); if (hData == nullptr) { + CloseClipboard(); return _T(""); } // Lock the handle to get the actual text pointer wchar_t * pszText = static_cast(GlobalLock(hData)); if (pszText == nullptr) { + CloseClipboard(); return _T(""); } From 1900fd43ef39b0570d4974d05e23e273a9cda04d Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 07:39:51 +0700 Subject: [PATCH 19/60] ponyman: backup va restore clipboard khi paste macro --- Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 8 ++++++-- Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp | 2 -- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index 5a27abf4..a6f82a8c 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -516,18 +516,22 @@ static void handleMacro() { SendBackspace(); } } + // Backup clipboard wstring backupText = OpenKeyHelper::getClipboardText(CF_UNICODETEXT); //send real data SendNewCharString(true); + // Wait briefly or yield to allow paste event to be processed by target app + Sleep(50); + // Restore clipboard if (!backupText.empty()) { - OpenKeyHelper::setClipboardText(backupText.c_str(), (int)backupText.size() + 1, CF_UNICODETEXT); + OpenKeyHelper::setClipboardText(backupText.c_str(), (int)backupText.length() + 1, CF_UNICODETEXT); } else { // If clipboard was empty, clear it - if (OpenClipboard(nullptr)) { + if (OpenClipboard(0)) { EmptyClipboard(); CloseClipboard(); } diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp index bcacaf1e..a18f67e1 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp @@ -172,14 +172,12 @@ wstring OpenKeyHelper::getClipboardText(const int& type) { // Get handle of clipboard object for ANSI text HANDLE hData = GetClipboardData(type); if (hData == nullptr) { - CloseClipboard(); return _T(""); } // Lock the handle to get the actual text pointer wchar_t * pszText = static_cast(GlobalLock(hData)); if (pszText == nullptr) { - CloseClipboard(); return _T(""); } From 1d298f73eab3fbd439a3b0952f3861a23cb3402e Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 09:01:31 +0700 Subject: [PATCH 20/60] ponyman: cho phep resize cua so macro va tang size limit go tat --- .../win32/OpenKey/OpenKey/MacroDialog.cpp | 56 ++++++++++++++++++- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.rc | 2 +- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index e2f618ad..079d7e23 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -16,7 +16,7 @@ redistribute your new version, it MUST be open source. #include "AppDelegate.h" #include -#define MAX_MACRO_BUFFER 4096 +#define MAX_MACRO_BUFFER 65536 #define BTN_ADD_TEXT _T("+ Thêm") #define BTN_UPDATE_TEXT _T("+ Sửa") @@ -145,6 +145,60 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara } break; } + case WM_SIZE: { + int width = LOWORD(lParam); + int height = HIWORD(lParam); + + // Get handles of the controls + HWND hList = GetDlgItem(hDlg, IDC_LIST_MACRO_DATA); + HWND hNameLabel = GetDlgItem(hDlg, IDC_STATIC); // Careful: multiple IDC_STATIC might exist + HWND hName = GetDlgItem(hDlg, IDC_EDIT_MACRO_NAME); + HWND hContentLabel = GetDlgItem(hDlg, IDC_STATIC); // Fortunately, we can reposition edit controls directly + HWND hContent = GetDlgItem(hDlg, IDC_EDIT_MACRO_CONTENT); + HWND hAdd = GetDlgItem(hDlg, IDC_BUTTON_ADD); + HWND hDel = GetDlgItem(hDlg, IDC_BUTTON_DELETE); + HWND hImport = GetDlgItem(hDlg, IDC_BUTTON_IMPORT_MACRO); + HWND hExport = GetDlgItem(hDlg, IDC_BUTTON_EXPORT_MACRO); + HWND hCaps = GetDlgItem(hDlg, IDC_CHECK_AUTO_CAPS); + HWND hTriggerLabel = GetDlgItem(hDlg, IDC_STATIC); + HWND hTriggerCombo = GetDlgItem(hDlg, IDC_COMBO_MACRO_TRIGGERS); + + // Layout constants based on original dialog (415 x 253 DLU ~ 622 x 380 pixels) + // We will adjust controls using SetWindowPos + if (hName && hContent && hAdd && hDel && hList && hImport && hExport && hCaps && hTriggerCombo) { + // Top controls: + // Name: x=10, y=28, w=110, h=22 + SetWindowPos(hName, NULL, 10, 28, 110, 22, SWP_NOZORDER); + + // Content: x=130, y=28, w=width - 275, h=50 + int contentWidth = width - 275; + if (contentWidth < 100) contentWidth = 100; + SetWindowPos(hContent, NULL, 130, 28, contentWidth, 50, SWP_NOZORDER); + + // Add Button: x=width - 135, y=28, w=125, h=22 + SetWindowPos(hAdd, NULL, width - 135, 28, 125, 22, SWP_NOZORDER); + + // Delete Button: x=width - 135, y=54, w=125, h=22 + SetWindowPos(hDel, NULL, width - 135, 54, 125, 22, SWP_NOZORDER); + + // List Macro: x=10, y=90, w=width - 20, h=height - 135 + int listHeight = height - 135; + if (listHeight < 50) listHeight = 50; + SetWindowPos(hList, NULL, 10, 90, width - 20, listHeight, SWP_NOZORDER); + + // Bottom controls: y = height - 35 + int bottomY = height - 35; + // Import: x=10, w=100, h=22 + SetWindowPos(hImport, NULL, 10, bottomY, 100, 22, SWP_NOZORDER); + // Export: x=120, w=100, h=22 + SetWindowPos(hExport, NULL, 120, bottomY, 100, 22, SWP_NOZORDER); + // Auto Caps checkbox: x=230, w=130, h=22 + SetWindowPos(hCaps, NULL, 230, bottomY, 130, 22, SWP_NOZORDER); + // Combo triggers: x=width - 195, w=185, h=22 (drop height is handled by OS) + SetWindowPos(hTriggerCombo, NULL, width - 195, bottomY - 3, 185, 22, SWP_NOZORDER); + } + return TRUE; + } case WM_NOTIFY: { switch (((LPNMHDR)lParam)->code) { case LVN_ITEMCHANGED: { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc index efb073a1..ef1c2cec 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc @@ -136,7 +136,7 @@ BEGIN END IDD_DIALOG_MACRO DIALOGEX 0, 0, 415, 253 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +STYLE DS_SETFONT | WS_THICKFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX CAPTION "Thiết lập gõ tắt - OpenKey" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN From afbb79574369f1af3b47e7c06f678e6163e70a94 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 09:11:28 +0700 Subject: [PATCH 21/60] ponyman: fix doi vi tri trigger label khi resize va bo gioi han text --- .../OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp | 13 ++++++++++--- Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc | 2 +- Sources/OpenKey/win32/OpenKey/OpenKey/resource.h | 1 + 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index 079d7e23..d8096c90 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -151,16 +151,14 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara // Get handles of the controls HWND hList = GetDlgItem(hDlg, IDC_LIST_MACRO_DATA); - HWND hNameLabel = GetDlgItem(hDlg, IDC_STATIC); // Careful: multiple IDC_STATIC might exist HWND hName = GetDlgItem(hDlg, IDC_EDIT_MACRO_NAME); - HWND hContentLabel = GetDlgItem(hDlg, IDC_STATIC); // Fortunately, we can reposition edit controls directly HWND hContent = GetDlgItem(hDlg, IDC_EDIT_MACRO_CONTENT); HWND hAdd = GetDlgItem(hDlg, IDC_BUTTON_ADD); HWND hDel = GetDlgItem(hDlg, IDC_BUTTON_DELETE); HWND hImport = GetDlgItem(hDlg, IDC_BUTTON_IMPORT_MACRO); HWND hExport = GetDlgItem(hDlg, IDC_BUTTON_EXPORT_MACRO); HWND hCaps = GetDlgItem(hDlg, IDC_CHECK_AUTO_CAPS); - HWND hTriggerLabel = GetDlgItem(hDlg, IDC_STATIC); + HWND hTriggerLabel = GetDlgItem(hDlg, IDC_LABEL_MACRO_TRIGGERS); HWND hTriggerCombo = GetDlgItem(hDlg, IDC_COMBO_MACRO_TRIGGERS); // Layout constants based on original dialog (415 x 253 DLU ~ 622 x 380 pixels) @@ -194,6 +192,10 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara SetWindowPos(hExport, NULL, 120, bottomY, 100, 22, SWP_NOZORDER); // Auto Caps checkbox: x=230, w=130, h=22 SetWindowPos(hCaps, NULL, 230, bottomY, 130, 22, SWP_NOZORDER); + // Trigger Label: x=width - 250, w=60, h=22 + if (hTriggerLabel) { + SetWindowPos(hTriggerLabel, NULL, width - 260, bottomY + 3, 60, 22, SWP_NOZORDER); + } // Combo triggers: x=width - 195, w=185, h=22 (drop height is handled by OS) SetWindowPos(hTriggerCombo, NULL, width - 195, bottomY - 3, 185, 22, SWP_NOZORDER); } @@ -224,6 +226,11 @@ void MacroDialog::initDialog() { hAddButton = GetDlgItem(hDlg, IDC_BUTTON_ADD); hAutoCaps = GetDlgItem(hDlg, IDC_CHECK_AUTO_CAPS); + // Set EM_SETLIMITTEXT to 0 for unlimited character limit in content edit box + if (hMacroContent) { + SendMessage(hMacroContent, EM_SETLIMITTEXT, 0, 0); + } + HWND hComboTriggers = GetDlgItem(hDlg, IDC_COMBO_MACRO_TRIGGERS); if (hComboTriggers) { SendMessage(hComboTriggers, CB_ADDSTRING, 0, (LPARAM)_T("Space")); diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc index ef1c2cec..349553ff 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc @@ -151,7 +151,7 @@ BEGIN PUSHBUTTON "Xuất ra file...",IDC_BUTTON_EXPORT_MACRO,82,232,66,14 CONTROL "Tự động đổi chữ hoa",IDC_CHECK_AUTO_CAPS, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,155,234,80,10 - LTEXT "Phím gõ tắt:",IDC_STATIC,242,234,40,8 + LTEXT "Phím gõ tắt:",IDC_LABEL_MACRO_TRIGGERS,242,234,40,8 COMBOBOX IDC_COMBO_MACRO_TRIGGERS,285,232,123,80,CBS_DROPDOWNLIST | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP END diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h index fc26d81f..cad49300 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h @@ -207,5 +207,6 @@ #define _APS_NEXT_COMMAND_VALUE 32771 #define _APS_NEXT_CONTROL_VALUE 1052 #define _APS_NEXT_SYMED_VALUE 110 +#define IDC_LABEL_MACRO_TRIGGERS 1099 #endif #endif From cda2e096df04e7fbd8f72da5bc34db47ff26a507 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 09:13:29 +0700 Subject: [PATCH 22/60] ponyman: fix compile loi macro triggers label va dat vo dung vi tri --- Sources/OpenKey/win32/OpenKey/OpenKey/resource.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h index cad49300..04a92642 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h @@ -198,6 +198,8 @@ #define IDC_COMBO_MACRO_TRIGGERS 1085 #define IDC_STATIC -1 +#define IDC_LABEL_MACRO_TRIGGERS 1099 + // Next default values for new objects // #ifdef APSTUDIO_INVOKED @@ -207,6 +209,5 @@ #define _APS_NEXT_COMMAND_VALUE 32771 #define _APS_NEXT_CONTROL_VALUE 1052 #define _APS_NEXT_SYMED_VALUE 110 -#define IDC_LABEL_MACRO_TRIGGERS 1099 #endif #endif From 6639c9013b22f7dcb8f4536d2cdec13a2b5d830e Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 09:18:19 +0700 Subject: [PATCH 23/60] ponyman: xoa trung lap function handleMacro va sua logic restore clipboard --- Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index a6f82a8c..8873f61c 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -524,17 +524,19 @@ static void handleMacro() { SendNewCharString(true); // Wait briefly or yield to allow paste event to be processed by target app - Sleep(50); + Sleep(100); // Restore clipboard if (!backupText.empty()) { OpenKeyHelper::setClipboardText(backupText.c_str(), (int)backupText.length() + 1, CF_UNICODETEXT); } else { // If clipboard was empty, clear it - if (OpenClipboard(0)) { - EmptyClipboard(); - CloseClipboard(); + int retry = 5; + while (retry-- > 0 && !OpenClipboard(NULL)) { + Sleep(10); } + EmptyClipboard(); + CloseClipboard(); } SendKeyCode(_keycode | (_flag & MASK_SHIFT ? CAPS_MASK : 0)); From 4039cab21c0fb02503a605385030c76402a08469 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 09:20:07 +0700 Subject: [PATCH 24/60] ponyman: fix build sau khi unlock va rut ngan delay --- Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index 8873f61c..256fdd82 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -524,7 +524,7 @@ static void handleMacro() { SendNewCharString(true); // Wait briefly or yield to allow paste event to be processed by target app - Sleep(100); + Sleep(50); // Restore clipboard if (!backupText.empty()) { From f70844000f9d4d4388f2972836969bceed61ebfd Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 09:22:56 +0700 Subject: [PATCH 25/60] ponyman: fix double paste event va dong bo clipboard truoc khi paste --- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index 256fdd82..b61716ed 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -434,8 +434,10 @@ static void SendNewCharString(const bool& dataFromMacro = false) { OpenKeyHelper::setClipboardText((LPCTSTR)_newCharString.data(), _j + 1, CF_UNICODETEXT); - //Send shift + insert - SendCombineKey(KEY_LEFT_SHIFT, VK_INSERT, 0, KEYEVENTF_EXTENDEDKEY); + if (!dataFromMacro) { + //Send shift + insert + SendCombineKey(KEY_LEFT_SHIFT, VK_INSERT, 0, KEYEVENTF_EXTENDEDKEY); + } //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,... @@ -520,15 +522,43 @@ static void handleMacro() { // Backup clipboard wstring backupText = OpenKeyHelper::getClipboardText(CF_UNICODETEXT); - //send real data + // Send real data to clipboard SendNewCharString(true); - // Wait briefly or yield to allow paste event to be processed by target app - Sleep(50); + // Dynamic wait: Ensure the clipboard content has updated to the macro text before pasting + wstring macroString = (wchar_t*)_newCharString.data(); + for (int retry = 0; retry < 10; ++retry) { + wstring currentClip = OpenKeyHelper::getClipboardText(CF_UNICODETEXT); + if (currentClip == macroString) { + break; + } + Sleep(5); + } + + // Send shift + insert to paste + SendCombineKey(KEY_LEFT_SHIFT, VK_INSERT, 0, KEYEVENTF_EXTENDEDKEY); + + // Wait for target application to process the paste message before restoring clipboard + Sleep(60); - // Restore clipboard + // Restore clipboard with dynamic retry to handle clipboard locks if (!backupText.empty()) { - OpenKeyHelper::setClipboardText(backupText.c_str(), (int)backupText.length() + 1, CF_UNICODETEXT); + int retry = 5; + while (retry-- > 0) { + HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, (backupText.length() + 1) * sizeof(WCHAR)); + if (hMem) { + memcpy(GlobalLock(hMem), backupText.c_str(), (backupText.length() + 1) * sizeof(WCHAR)); + GlobalUnlock(hMem); + if (OpenClipboard(0)) { + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, hMem); + CloseClipboard(); + break; + } + GlobalFree(hMem); + } + Sleep(10); + } } else { // If clipboard was empty, clear it int retry = 5; From a106fbde428db9492a2106c3264cfda78c2aaed1 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 09:31:05 +0700 Subject: [PATCH 26/60] ponyman: fix cat bot ky tu khi chuyen doi giua cac tu go tat --- .../win32/OpenKey/OpenKey/MacroDialog.cpp | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index d8096c90..13492649 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -308,14 +308,19 @@ void MacroDialog::onSelectItem(const int & index) { } void MacroDialog::onAddMacroButton() { - TCHAR buffer[MAX_MACRO_BUFFER]; - GetWindowText(hMacroName, buffer, MAX_MACRO_BUFFER); - wstring name = buffer; + int nameLen = GetWindowTextLength(hMacroName); + int contentLen = GetWindowTextLength(hMacroContent); - GetWindowText(hMacroContent, buffer, MAX_MACRO_BUFFER); - wstring content = buffer; + vector nameBuf(nameLen + 1); + vector contentBuf(contentLen + 1); - if (name.compare(L"") == 0 || content.compare(L"") == 0) { + GetWindowText(hMacroName, nameBuf.data(), nameLen + 1); + wstring name = nameBuf.data(); + + GetWindowText(hMacroContent, contentBuf.data(), contentLen + 1); + wstring content = contentBuf.data(); + + if (name.empty() || content.empty()) { MessageBox(hDlg, _T("Bạn hãy nhập từ cần gõ tắt!"), _T("OpenKey"), MB_OK); return; } @@ -328,9 +333,10 @@ void MacroDialog::onAddMacroButton() { } void MacroDialog::onDeleteMacroButton() { - TCHAR buffer[MAX_MACRO_BUFFER]; - GetWindowText(hMacroName, buffer, MAX_MACRO_BUFFER); - wstring name = buffer; + int nameLen = GetWindowTextLength(hMacroName); + vector nameBuf(nameLen + 1); + GetWindowText(hMacroName, nameBuf.data(), nameLen + 1); + wstring name = nameBuf.data(); if (name.compare(L"") == 0) { MessageBox(hDlg, _T("Bạn hãy chọn từ cần xoá!"), _T("OpenKey"), MB_OK); From 39c57e2cd810fd13271cfeddaee8c5f0bc3e6583 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 09:36:20 +0700 Subject: [PATCH 27/60] ponyman: fix clip image va fix loi tu binh tran don gian --- Sources/OpenKey/engine/Engine.cpp | 1 + Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 8325b5be..a09678b2 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -1069,6 +1069,7 @@ void checkForStandaloneCharTBT(const Uint16& data, const bool& isCaps, const Uin hExt = 4; TypingWord[_index - 1] = data | (isCaps ? CAPS_MASK : 0); hData[0] = GET(TypingWord[_index - 1]); + saveWord(); return; } hCode = vWillProcess; diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp index a18f67e1..c9ddd72c 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp @@ -164,6 +164,9 @@ wstring OpenKeyHelper::getFullPath() { } wstring OpenKeyHelper::getClipboardText(const int& type) { + if (!IsClipboardFormatAvailable(type)) { + return _T(""); + } // Try opening the clipboard if (!OpenClipboard(nullptr)) { return _T(""); @@ -172,6 +175,7 @@ wstring OpenKeyHelper::getClipboardText(const int& type) { // Get handle of clipboard object for ANSI text HANDLE hData = GetClipboardData(type); if (hData == nullptr) { + CloseClipboard(); return _T(""); } From 8b0f18823ca321dbef9b6eaae58c2e80ec997a6a Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 10:23:43 +0700 Subject: [PATCH 28/60] ponyman: chan ap dung dau sau khi go so o kieu go tu binh tran --- Sources/OpenKey/engine/Engine.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index a09678b2..4057121d 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -1067,11 +1067,26 @@ void checkForStandaloneCharTBT(const Uint16& data, const bool& isCaps, const Uin hBPC = 1; hNCC = 1; hExt = 4; - TypingWord[_index - 1] = data | (isCaps ? CAPS_MASK : 0); + TypingWord[_index - 1] = data | (isCaps ? CAPS_MASK : 0); // No STANDALONE_MASK hData[0] = GET(TypingWord[_index - 1]); saveWord(); return; } + // If the previous key was a numeric key (6, 7, 8, 9), then do not trigger accent transformation, insert it directly + if (_index > 0) { + Uint16 prevChar = CHR(_index - 1); + if (prevChar == KEY_6 || prevChar == KEY_7 || prevChar == KEY_8 || prevChar == KEY_9) { + hCode = vWillProcess; + hBPC = 0; + hNCC = 1; + hExt = 3; + TypingWord[_index] = data | (isCaps ? CAPS_MASK : 0); + hData[0] = GET(TypingWord[_index]); + _index++; + saveWord(); + return; + } + } hCode = vWillProcess; hBPC = 0; hNCC = 1; From 6cf0de1518f391959500d16b6d6c9cf7e997197e Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 10:53:05 +0700 Subject: [PATCH 29/60] docs: add workflow rules --- workflow.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 workflow.md diff --git a/workflow.md b/workflow.md new file mode 100644 index 00000000..6b928ff3 --- /dev/null +++ b/workflow.md @@ -0,0 +1,55 @@ +# Workflow + +## Ngon ngu va cach lam viec + +- Luon tra loi nguoi dung bang tieng Viet. +- Truoc khi viet code, chinh sua file, hoac phan tich du an, bat buoc doc ky toan bo file `workflow.md`. +- Luon dung skill `ponyman` khi code, ke ca khi nguoi dung khong tag skill. +- Neu nguoi dung chi hoi y kien/phan tich, khong tu y sua code. +- Khi nguoi dung chap thuan sua, lam thang vao huong da chot, khong lap lai tu van dai dong. + +## Nguyen tac code + +- Uu tien YAGNI: khong them abstraction, dependency, boilerplate neu khong can. +- Dung san thu vien chuan, API native, hoac dependency da co truoc khi viet code moi. +- Sua toi thieu, dung vung bi anh huong, giu luong hien co. +- Khong refactor rong neu khong bat buoc de fix loi. +- Khong pha hanh vi cu khi sua bug: giu phim tat, cau hinh, trang thai nguoi dung, va cac luong xu ly hien tai. +- Khong chen comment thua. Chi them `ponytail:` comment khi co shortcut co chu dich va neu co tran ky thuat thi noi ro cach nang cap. +- Logic khong tam thuong can co mot check nho nhat co the chay duoc: test, assert demo, hoac lenh build bat loi. + +## Tieu chuan giao dien + +- Giu dung phong cach UI hien co cua du an. +- Khong them trang landing, hero, card trang tri, animation, hay thanh phan UI moi neu request khong can. +- Dieu khien phai ro nghia, gon, de scan; icon/button/checkbox/toggle/input dung dung vai tro. +- Khong de text tran, de, hoac che UI o desktop/mobile. +- Khong them palette mau moi neu khong can; uu tien mau va spacing san co. +- Thay doi UI phai bao toan workflow cu cua nguoi dung. + +## Quy trinh sau khi code + +- Chay build/test den khi khong con error va warning. +- Neu build phat sinh loi/warning do thay doi vua lam, phai sua tiep den sach. +- Chi stage file dung scope. +- Tu dong chay: + - `git add ` + - `git commit -m ""` + - `git push` +- Bao lai commit hash that sau khi push. +- Neu khong the build, commit, hoac push, noi ro lenh da chay va loi chan. + +## Git va an toan file + +- Khong revert thay doi cua nguoi dung neu khong duoc yeu cau ro. +- Khong dung lenh pha huy nhu `git reset --hard` hoac `git checkout --` neu nguoi dung chua yeu cau ro. +- Neu worktree co file la cua nguoi dung va khong lien quan, bo qua. +- Neu file nguoi dung chinh sua trung vung can sua, doc ky va lam cung thay doi do. + +## Bug go tieng Viet can chu y + +- Kieu go tu binh tran: `tr7n` phai ra chu `tren` co dau. +- Khi nguoi dung go so thuan nhu `667`, ket qua mong muon la `67`, khong duoc bien thanh so 6 kem chu e co dau mu. +- Khi nguoi dung go ky tu Shift nhu `Shift+998`, ket qua mong muon la `(*`, khong duoc bien thanh dau ngoac kem chu O co dau mu. +- Sau cac ky tu ky hieu nhu `^`, `&`, `*`, `(`, engine khong duoc tiep tuc ep rule tieng Viet len chuoi so/ky hieu. +- Huong fix uu tien: chan nho o tang xu ly phim truoc khi ap bien doi tieng Viet, nhan dien ngu canh so/ky hieu va tra phim goc. From 23c6eb1060574e74086e3fd04cc01a410883f131 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 10:54:15 +0700 Subject: [PATCH 30/60] chore: ignore local workflow files --- .agents | 1 - .gitignore | 6 ++++++ build.cmd | 38 ------------------------------------ workflow.md | 55 ----------------------------------------------------- 4 files changed, 6 insertions(+), 94 deletions(-) delete mode 120000 .agents delete mode 100644 build.cmd delete mode 100644 workflow.md diff --git a/.agents b/.agents deleted file mode 120000 index 7a5a7084..00000000 --- a/.agents +++ /dev/null @@ -1 +0,0 @@ -R:/HDD R/ZC SYMLINK/USERS/.agents \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7fd14bb2..e9f6341b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,9 @@ /Sources/OpenKey/win32/OpenKey/OpenKey/x64/Debug /Sources/OpenKey/win32/OpenKey/OpenKey/x64/Release /Sources/OpenKey/win32/OpenKey/OpenKeyUpdate/x64/Release + +# Local agent/workflow files +/workflow.md +/build.cmd +/.agents +/.agents/ diff --git a/build.cmd b/build.cmd deleted file mode 100644 index ee1a4958..00000000 --- a/build.cmd +++ /dev/null @@ -1,38 +0,0 @@ -@echo off -setlocal enabledelayedexpansion - -taskkill /im openkey64.exe /f -:: Find VS Installation Path -set "VS_PATH=" -for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath`) do ( - set "VS_PATH=%%i" -) - -if not defined VS_PATH ( - echo Visual Studio installation not found! - exit /b 1 -) - -:: Locate MSBuild -set "MSBUILD_PATH=!VS_PATH!\MSBuild\Current\Bin\MSBuild.exe" -if not exist "!MSBUILD_PATH!" ( - echo MSBuild not found at !MSBUILD_PATH! - exit /b 1 -) - -:: Run MSBuild -echo Building OpenKey... -"!MSBUILD_PATH!" "Sources\OpenKey\win32\OpenKey\OpenKey.sln" /p:Configuration=Release /p:Platform=x64 /p:PlatformToolset=v145 /t:Restore,Build - -if %ERRORLEVEL% neq 0 ( - echo Build failed! - exit /b %ERRORLEVEL% -) - -echo Build succeeded! -:: Automatically open the output folder -start "" "Sources\OpenKey\win32\OpenKey\x64\Release" -:: Automatically run the compiled exe -start "" "Sources\OpenKey\win32\OpenKey\x64\Release\OpenKey64.exe" - -exit /b 0 diff --git a/workflow.md b/workflow.md deleted file mode 100644 index 6b928ff3..00000000 --- a/workflow.md +++ /dev/null @@ -1,55 +0,0 @@ -# Workflow - -## Ngon ngu va cach lam viec - -- Luon tra loi nguoi dung bang tieng Viet. -- Truoc khi viet code, chinh sua file, hoac phan tich du an, bat buoc doc ky toan bo file `workflow.md`. -- Luon dung skill `ponyman` khi code, ke ca khi nguoi dung khong tag skill. -- Neu nguoi dung chi hoi y kien/phan tich, khong tu y sua code. -- Khi nguoi dung chap thuan sua, lam thang vao huong da chot, khong lap lai tu van dai dong. - -## Nguyen tac code - -- Uu tien YAGNI: khong them abstraction, dependency, boilerplate neu khong can. -- Dung san thu vien chuan, API native, hoac dependency da co truoc khi viet code moi. -- Sua toi thieu, dung vung bi anh huong, giu luong hien co. -- Khong refactor rong neu khong bat buoc de fix loi. -- Khong pha hanh vi cu khi sua bug: giu phim tat, cau hinh, trang thai nguoi dung, va cac luong xu ly hien tai. -- Khong chen comment thua. Chi them `ponytail:` comment khi co shortcut co chu dich va neu co tran ky thuat thi noi ro cach nang cap. -- Logic khong tam thuong can co mot check nho nhat co the chay duoc: test, assert demo, hoac lenh build bat loi. - -## Tieu chuan giao dien - -- Giu dung phong cach UI hien co cua du an. -- Khong them trang landing, hero, card trang tri, animation, hay thanh phan UI moi neu request khong can. -- Dieu khien phai ro nghia, gon, de scan; icon/button/checkbox/toggle/input dung dung vai tro. -- Khong de text tran, de, hoac che UI o desktop/mobile. -- Khong them palette mau moi neu khong can; uu tien mau va spacing san co. -- Thay doi UI phai bao toan workflow cu cua nguoi dung. - -## Quy trinh sau khi code - -- Chay build/test den khi khong con error va warning. -- Neu build phat sinh loi/warning do thay doi vua lam, phai sua tiep den sach. -- Chi stage file dung scope. -- Tu dong chay: - - `git add ` - - `git commit -m ""` - - `git push` -- Bao lai commit hash that sau khi push. -- Neu khong the build, commit, hoac push, noi ro lenh da chay va loi chan. - -## Git va an toan file - -- Khong revert thay doi cua nguoi dung neu khong duoc yeu cau ro. -- Khong dung lenh pha huy nhu `git reset --hard` hoac `git checkout --` neu nguoi dung chua yeu cau ro. -- Neu worktree co file la cua nguoi dung va khong lien quan, bo qua. -- Neu file nguoi dung chinh sua trung vung can sua, doc ky va lam cung thay doi do. - -## Bug go tieng Viet can chu y - -- Kieu go tu binh tran: `tr7n` phai ra chu `tren` co dau. -- Khi nguoi dung go so thuan nhu `667`, ket qua mong muon la `67`, khong duoc bien thanh so 6 kem chu e co dau mu. -- Khi nguoi dung go ky tu Shift nhu `Shift+998`, ket qua mong muon la `(*`, khong duoc bien thanh dau ngoac kem chu O co dau mu. -- Sau cac ky tu ky hieu nhu `^`, `&`, `*`, `(`, engine khong duoc tiep tuc ep rule tieng Viet len chuoi so/ky hieu. -- Huong fix uu tien: chan nho o tang xu ly phim truoc khi ap bien doi tieng Viet, nhan dien ngu canh so/ky hieu va tra phim goc. From 9a854f03b50bd6e29a25833728e272e0096a1f85 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 11:00:19 +0700 Subject: [PATCH 31/60] fix: keep TBT numbers raw after non-consonants --- Sources/OpenKey/engine/Engine.cpp | 50 +++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 4057121d..e30a8649 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -65,6 +65,8 @@ static Uint16 ProcessingChar[][11] = { (keyCode == KEY_1 || keyCode == KEY_2 || keyCode == KEY_3 || keyCode == KEY_4 || keyCode == KEY_5) : \ (keyCode == KEY_S || keyCode == KEY_F || keyCode == KEY_R || keyCode == KEY_J || keyCode == KEY_X)) #define IS_BRACKET_KEY(key) (key == KEY_LEFT_BRACKET || key == KEY_RIGHT_BRACKET) +#define IS_TBT_STANDALONE_KEY(key) (key == KEY_6 || key == KEY_7 || key == KEY_8 || key == KEY_9) +#define IS_TBT_CONSONANT_CONTEXT(key) (isLetterKey(key) && IS_CONSONANT(key)) #define VSI vowelStartIndex #define VEI vowelEndIndex @@ -175,6 +177,28 @@ bool isMacroBreakCode(const int& data) { return false; } +bool isLetterKey(const Uint16& data) { + switch (data) { + case KEY_A: case KEY_B: case KEY_C: case KEY_D: case KEY_E: + case KEY_F: case KEY_G: case KEY_H: case KEY_I: case KEY_J: + case KEY_K: case KEY_L: case KEY_M: case KEY_N: case KEY_O: + case KEY_P: case KEY_Q: case KEY_R: case KEY_S: case KEY_T: + case KEY_U: case KEY_V: case KEY_W: case KEY_X: case KEY_Y: + case KEY_Z: + return true; + default: + return false; + } +} + +bool shouldKeepTBTNumberKeyRaw(const Uint16& data) { + if (vInputType != vTuBinhTranDonGian || !IS_TBT_STANDALONE_KEY(data)) + return false; + if (_specialChar.size() > 0) + return true; + return _index > 0 && !IS_TBT_CONSONANT_CONTEXT(CHR(_index - 1)); +} + void setKeyData(const Byte& index, const Uint16& keyCode, const bool& isCaps) { if (index < 0 || index >= MAX_BUFF) return; @@ -1072,20 +1096,13 @@ void checkForStandaloneCharTBT(const Uint16& data, const bool& isCaps, const Uin saveWord(); return; } - // If the previous key was a numeric key (6, 7, 8, 9), then do not trigger accent transformation, insert it directly - if (_index > 0) { - Uint16 prevChar = CHR(_index - 1); - if (prevChar == KEY_6 || prevChar == KEY_7 || prevChar == KEY_8 || prevChar == KEY_9) { - hCode = vWillProcess; - hBPC = 0; - hNCC = 1; - hExt = 3; - TypingWord[_index] = data | (isCaps ? CAPS_MASK : 0); - hData[0] = GET(TypingWord[_index]); - _index++; - saveWord(); - return; - } + if (shouldKeepTBTNumberKeyRaw(data)) { + hCode = vDoNothing; + hBPC = 0; + hNCC = 0; + hExt = 3; + insertKey(data, isCaps); + return; } hCode = vWillProcess; hBPC = 0; @@ -1547,12 +1564,13 @@ void vKeyHandleEvent(const vKeyEvent& event, (data == VK_RCONTROL && (vMacroTriggerMask & 0x20)); bool isTBTOrCustomNumberAction = (vInputType == vTuBinhTranDonGian || vInputType == vTuDinhNghia) && - (data == KEY_6 || data == KEY_7 || data == KEY_8 || data == KEY_9); + IS_TBT_STANDALONE_KEY(data); + bool isRawTBTNumberAction = shouldKeepTBTNumberKeyRaw(data); bool isNumberBreak = (IS_NUMBER_KEY(data) && capsStatus == 1) && !isTBTOrCustomNumberAction; bool isNumberAtStartBreak = (_index == 0 && IS_NUMBER_KEY(data)) && !isTBTOrCustomNumberAction; if (isNumberBreak - || otherControlKey || isWordBreak(event, state, data) || isNumberAtStartBreak || isTriggerModifier) { + || otherControlKey || isWordBreak(event, state, data) || isNumberAtStartBreak || isTriggerModifier || isRawTBTNumberAction) { hCode = vDoNothing; hBPC = 0; hNCC = 0; From da30edcf37ae7c33ef085010577eb8b0d3ba0559 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 11:31:26 +0700 Subject: [PATCH 32/60] fix: restore TBT standalone before raw keys --- Sources/OpenKey/engine/Engine.cpp | 72 ++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index e30a8649..2bb36d26 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -65,7 +65,8 @@ static Uint16 ProcessingChar[][11] = { (keyCode == KEY_1 || keyCode == KEY_2 || keyCode == KEY_3 || keyCode == KEY_4 || keyCode == KEY_5) : \ (keyCode == KEY_S || keyCode == KEY_F || keyCode == KEY_R || keyCode == KEY_J || keyCode == KEY_X)) #define IS_BRACKET_KEY(key) (key == KEY_LEFT_BRACKET || key == KEY_RIGHT_BRACKET) -#define IS_TBT_STANDALONE_KEY(key) (key == KEY_6 || key == KEY_7 || key == KEY_8 || key == KEY_9) +#define IS_TBT_NUMBER_STANDALONE_KEY(key) (key == KEY_6 || key == KEY_7 || key == KEY_8 || key == KEY_9) +#define IS_TBT_STANDALONE_KEY(key) (IS_TBT_NUMBER_STANDALONE_KEY(key) || IS_BRACKET_KEY(key)) #define IS_TBT_CONSONANT_CONTEXT(key) (isLetterKey(key) && IS_CONSONANT(key)) #define VSI vowelStartIndex @@ -137,6 +138,8 @@ static bool _willTempOffEngine = false; //function prototype void findAndCalculateVowel(const bool& forGrammar=false); void insertMark(const Uint32& markMask, const bool& canModifyFlag=true); +void insertKey(const Uint16& keyCode, const bool& isCaps, const bool& isCheckSpelling=true); +void saveWord(); static std::wstring_convert> converter; wstring utf8ToWideString(const string& str) { @@ -191,7 +194,7 @@ bool isLetterKey(const Uint16& data) { } } -bool shouldKeepTBTNumberKeyRaw(const Uint16& data) { +bool shouldKeepTBTStandaloneKeyRaw(const Uint16& data) { if (vInputType != vTuBinhTranDonGian || !IS_TBT_STANDALONE_KEY(data)) return false; if (_specialChar.size() > 0) @@ -199,6 +202,56 @@ bool shouldKeepTBTNumberKeyRaw(const Uint16& data) { return _index > 0 && !IS_TBT_CONSONANT_CONTEXT(CHR(_index - 1)); } +bool getTBTStandaloneRawKey(const Uint32& value, Uint16& rawKey) { + if (!(value & STANDALONE_MASK)) + return false; + + Uint16 key = (Uint16)(value & CHAR_MASK); + Uint32 mark = value & (TONE_MASK | TONEW_MASK); + if (key == KEY_A && mark == TONE_MASK) { + rawKey = KEY_6; + return true; + } + if (key == KEY_E && mark == TONE_MASK) { + rawKey = KEY_7; + return true; + } + if (key == KEY_O && mark == TONE_MASK) { + rawKey = KEY_8; + return true; + } + if (key == KEY_A && mark == TONEW_MASK) { + rawKey = KEY_9; + return true; + } + if (key == KEY_U && mark == TONEW_MASK) { + rawKey = KEY_LEFT_BRACKET; + return true; + } + if (key == KEY_O && mark == TONEW_MASK) { + rawKey = KEY_RIGHT_BRACKET; + return true; + } + return false; +} + +bool restoreTBTStandaloneThenInsertRaw(const Uint16& data, const bool& isCaps) { + Uint16 previousRawKey; + if (_index == 0 || !getTBTStandaloneRawKey(TypingWord[_index - 1], previousRawKey)) + return false; + + hCode = vWillProcess; + hBPC = 1; + hNCC = 2; + hExt = 3; + TypingWord[_index - 1] = previousRawKey | (TypingWord[_index - 1] & CAPS_MASK); + hData[1] = TypingWord[_index - 1]; + insertKey(data, isCaps); + hData[0] = TypingWord[_index - 1]; + saveWord(); + return true; +} + void setKeyData(const Byte& index, const Uint16& keyCode, const bool& isCaps) { if (index < 0 || index >= MAX_BUFF) return; @@ -383,7 +436,7 @@ void checkGrammar(const int& deltaBackSpace) { } } -void insertKey(const Uint16& keyCode, const bool& isCaps, const bool& isCheckSpelling=true) { +void insertKey(const Uint16& keyCode, const bool& isCaps, const bool& isCheckSpelling) { if (_index >= MAX_BUFF) { _longWordHelper.push_back(TypingWord[0]); //save long word //left shift @@ -1086,6 +1139,10 @@ bool isSpecialKeyCustom(Uint16 keyCode) { } void checkForStandaloneCharTBT(const Uint16& data, const bool& isCaps, const Uint32& keyWillReverse, const Uint32& maskToAdd) { + if (shouldKeepTBTStandaloneKeyRaw(data) && restoreTBTStandaloneThenInsertRaw(data, isCaps)) { + return; + } + if (_index > 0 && CHR(_index - 1) == keyWillReverse && (TypingWord[_index - 1] & maskToAdd) && (TypingWord[_index - 1] & STANDALONE_MASK)) { hCode = vWillProcess; hBPC = 1; @@ -1096,7 +1153,7 @@ void checkForStandaloneCharTBT(const Uint16& data, const bool& isCaps, const Uin saveWord(); return; } - if (shouldKeepTBTNumberKeyRaw(data)) { + if (shouldKeepTBTStandaloneKeyRaw(data)) { hCode = vDoNothing; hBPC = 0; hNCC = 0; @@ -1287,11 +1344,15 @@ void handleMainKey(const Uint16& data, const bool& isCaps) { } if (data == KEY_LEFT_BRACKET) { //standalone key [ + if (shouldKeepTBTStandaloneKeyRaw(data) && restoreTBTStandaloneThenInsertRaw(data, isCaps)) + return; checkForStandaloneChar(data, isCaps, (vInputType == vTuBinhTranDonGian) ? KEY_U : KEY_O); return; } if (data == KEY_RIGHT_BRACKET) { //standalone key ] + if (shouldKeepTBTStandaloneKeyRaw(data) && restoreTBTStandaloneThenInsertRaw(data, isCaps)) + return; checkForStandaloneChar(data, isCaps, (vInputType == vTuBinhTranDonGian) ? KEY_O : KEY_U); return; } @@ -1565,12 +1626,11 @@ void vKeyHandleEvent(const vKeyEvent& event, bool isTBTOrCustomNumberAction = (vInputType == vTuBinhTranDonGian || vInputType == vTuDinhNghia) && IS_TBT_STANDALONE_KEY(data); - bool isRawTBTNumberAction = shouldKeepTBTNumberKeyRaw(data); bool isNumberBreak = (IS_NUMBER_KEY(data) && capsStatus == 1) && !isTBTOrCustomNumberAction; bool isNumberAtStartBreak = (_index == 0 && IS_NUMBER_KEY(data)) && !isTBTOrCustomNumberAction; if (isNumberBreak - || otherControlKey || isWordBreak(event, state, data) || isNumberAtStartBreak || isTriggerModifier || isRawTBTNumberAction) { + || otherControlKey || isWordBreak(event, state, data) || isNumberAtStartBreak || isTriggerModifier) { hCode = vDoNothing; hBPC = 0; hNCC = 0; From cce1d3e5de61a3cdbfb0c4d566a89dbb9908bfe5 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 11:41:59 +0700 Subject: [PATCH 33/60] fix: keep TBT raw number session --- Sources/OpenKey/engine/Engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 2bb36d26..4fb4ed09 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -1868,7 +1868,7 @@ void vKeyHandleEvent(const vKeyEvent& event, } } } - bool isTBTStandalone = (vInputType == vTuBinhTranDonGian) && (IS_BRACKET_KEY(data) || data == KEY_6 || data == KEY_7 || data == KEY_8 || data == KEY_9); + bool isTBTStandalone = (vInputType == vTuBinhTranDonGian) && IS_TBT_STANDALONE_KEY(data) && !shouldKeepTBTStandaloneKeyRaw(data); if ((IS_BRACKET_KEY(data) && (( IS_BRACKET_KEY( (((Uint16)hData[0] & ~CAPS_MASK)) )) || vInputType == vSimpleTelex1 || vInputType == vSimpleTelex2)) || ((isTBTStandalone || isCustomStandalone) && ((((Uint16)hData[0] & ~CAPS_MASK)) == data))) { if (_index - (hCode == vWillProcess ? hBPC : 0) > 0) { From 9d6c76bef9c16efeba91de2fee290523b25cf1d0 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 11:46:33 +0700 Subject: [PATCH 34/60] fix: add TBT spelling rules --- Sources/OpenKey/engine/Engine.cpp | 36 +++++++++++++++++++-------- Sources/OpenKey/engine/Vietnamese.cpp | 29 +++++++++++++++++++++ Sources/OpenKey/engine/Vietnamese.h | 1 + 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 4fb4ed09..5ddadd55 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -202,6 +202,12 @@ bool shouldKeepTBTStandaloneKeyRaw(const Uint16& data) { return _index > 0 && !IS_TBT_CONSONANT_CONTEXT(CHR(_index - 1)); } +bool isSpellingConsonant(const Uint16& data) { + if (vInputType == vTuBinhTranDonGian) + return IS_TBT_CONSONANT_CONTEXT(data); + return IS_CONSONANT(data); +} + bool getTBTStandaloneRawKey(const Uint32& value, Uint16& rawKey) { if (!(value & STANDALONE_MASK)) return false; @@ -242,9 +248,16 @@ bool restoreTBTStandaloneThenInsertRaw(const Uint16& data, const bool& isCaps) { hCode = vWillProcess; hBPC = 1; - hNCC = 2; hExt = 3; TypingWord[_index - 1] = previousRawKey | (TypingWord[_index - 1] & CAPS_MASK); + if (previousRawKey == data) { + hNCC = 1; + hData[0] = TypingWord[_index - 1]; + saveWord(); + return true; + } + + hNCC = 2; hData[1] = TypingWord[_index - 1]; insertKey(data, isCaps); hData[0] = TypingWord[_index - 1]; @@ -274,16 +287,17 @@ void checkSpelling(const bool& forceCheckVowel=false) { if (_spellingEndIndex > 0) { j = 0; + vector>& consonantTable = (vInputType == vTuBinhTranDonGian) ? _consonantTableTBT : _consonantTable; //Check first consonant - if (IS_CONSONANT(CHR(0))) { - for (i = 0; i < _consonantTable.size(); i++) { + if (isSpellingConsonant(CHR(0))) { + for (i = 0; i < consonantTable.size(); i++) { _spellingFlag = false; - if (_spellingEndIndex < _consonantTable[i].size()) + if (_spellingEndIndex < consonantTable[i].size()) _spellingFlag = true; - for (j = 0; j < _consonantTable[i].size(); j++) { + for (j = 0; j < consonantTable[i].size(); j++) { if (_spellingEndIndex > j && - (_consonantTable[i][j] & ~(vQuickStartConsonant ? END_CONSONANT_MASK : 0)) != CHR(j) && - (_consonantTable[i][j] & ~(vAllowConsonantZFWJ ? CONSONANT_ALLOW_MASK : 0)) != CHR(j)) { + (consonantTable[i][j] & ~(vQuickStartConsonant ? END_CONSONANT_MASK : 0)) != CHR(j) && + (consonantTable[i][j] & ~(vAllowConsonantZFWJ ? CONSONANT_ALLOW_MASK : 0)) != CHR(j)) { _spellingFlag = true; break; } @@ -307,11 +321,11 @@ void checkSpelling(const bool& forceCheckVowel=false) { k = k + 1; j = k; VSI = k; - } else if (_index >= 2 && CHR(0) == KEY_G && CHR(1) == KEY_I && IS_CONSONANT(CHR(2))) { + } else if (_index >= 2 && CHR(0) == KEY_G && CHR(1) == KEY_I && isSpellingConsonant(CHR(2))) { VSI = k = j = 1; //Sep 28th: fix gìn } for (l = 0; l < 3; l++) { - if (k < _spellingEndIndex && !IS_CONSONANT(CHR(k))) { + if (k < _spellingEndIndex && !isSpellingConsonant(CHR(k))) { k++; VEI = k; } @@ -329,13 +343,13 @@ void checkSpelling(const bool& forceCheckVowel=false) { break; } } - if (_spellingFlag || (k < _spellingEndIndex && !vowelSet[l][0]) || (j + ii - 1 < _spellingEndIndex && !IS_CONSONANT(CHR(j + ii - 1)))) + if (_spellingFlag || (k < _spellingEndIndex && !vowelSet[l][0]) || (j + ii - 1 < _spellingEndIndex && !isSpellingConsonant(CHR(j + ii - 1)))) continue; _spellingVowelOK = true; break; } - } else if (!IS_CONSONANT(CHR(j))) { + } else if (!isSpellingConsonant(CHR(j))) { _spellingVowelOK = true; } diff --git a/Sources/OpenKey/engine/Vietnamese.cpp b/Sources/OpenKey/engine/Vietnamese.cpp index 66136cdb..c38a8baf 100644 --- a/Sources/OpenKey/engine/Vietnamese.cpp +++ b/Sources/OpenKey/engine/Vietnamese.cpp @@ -367,6 +367,35 @@ vector> _consonantTable = { { KEY_J | END_CONSONANT_MASK}, }; +vector> _consonantTableTBT = { + { KEY_N, KEY_G, KEY_H }, + { KEY_P, KEY_H }, + { KEY_T, KEY_H }, + { KEY_T, KEY_R }, + { KEY_G, KEY_I }, + { KEY_C, KEY_H }, + { KEY_N, KEY_H }, + { KEY_N, KEY_G}, + { KEY_K, KEY_H }, + { KEY_G, KEY_H }, + { KEY_G }, + { KEY_C }, + { KEY_Q }, + { KEY_K }, + { KEY_T }, + { KEY_R }, + { KEY_H }, + { KEY_B }, + { KEY_M }, + { KEY_V }, + { KEY_N }, + { KEY_L }, + { KEY_X }, + { KEY_P }, + { KEY_S }, + { KEY_D }, +}; + vector> _endConsonantTable = { { KEY_T }, { KEY_P }, { KEY_C }, { KEY_N }, { KEY_M }, { KEY_G | END_CONSONANT_MASK }, { KEY_K | END_CONSONANT_MASK }, { KEY_H | END_CONSONANT_MASK }, diff --git a/Sources/OpenKey/engine/Vietnamese.h b/Sources/OpenKey/engine/Vietnamese.h index 7b5fa649..1b89aaaa 100644 --- a/Sources/OpenKey/engine/Vietnamese.h +++ b/Sources/OpenKey/engine/Vietnamese.h @@ -21,6 +21,7 @@ extern map>> _vowelCombine; extern map>> _vowelForMark; extern vector> _consonantD; extern vector> _consonantTable; +extern vector> _consonantTableTBT; extern vector> _endConsonantTable; extern vector _standaloneWbad; extern vector> _doubleWAllowed; From e7da9b0a2bf41058ce7e9df3b32cf5ae0cbe5ad3 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 12:04:17 +0700 Subject: [PATCH 35/60] fix: allow TBT vowel combinations --- Sources/OpenKey/engine/Engine.cpp | 61 ++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 5ddadd55..f87f6b98 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -140,6 +140,7 @@ void findAndCalculateVowel(const bool& forGrammar=false); void insertMark(const Uint32& markMask, const bool& canModifyFlag=true); void insertKey(const Uint16& keyCode, const bool& isCaps, const bool& isCheckSpelling=true); void saveWord(); +bool canApplyTBTStandaloneInVowel(const Uint16& data); static std::wstring_convert> converter; wstring utf8ToWideString(const string& str) { @@ -199,7 +200,7 @@ bool shouldKeepTBTStandaloneKeyRaw(const Uint16& data) { return false; if (_specialChar.size() > 0) return true; - return _index > 0 && !IS_TBT_CONSONANT_CONTEXT(CHR(_index - 1)); + return _index > 0 && !IS_TBT_CONSONANT_CONTEXT(CHR(_index - 1)) && !canApplyTBTStandaloneInVowel(data); } bool isSpellingConsonant(const Uint16& data) { @@ -241,6 +242,64 @@ bool getTBTStandaloneRawKey(const Uint32& value, Uint16& rawKey) { return false; } +bool getTBTStandaloneValue(const Uint16& data, Uint32& value) { + switch (data) { + case KEY_6: + value = KEY_A | TONE_MASK; + return true; + case KEY_7: + value = KEY_E | TONE_MASK; + return true; + case KEY_8: + value = KEY_O | TONE_MASK; + return true; + case KEY_9: + value = KEY_A | TONEW_MASK; + return true; + case KEY_LEFT_BRACKET: + value = KEY_U | TONEW_MASK; + return true; + case KEY_RIGHT_BRACKET: + value = KEY_O | TONEW_MASK; + return true; + default: + return false; + } +} + +bool canApplyTBTStandaloneInVowel(const Uint16& data) { + Uint32 target; + if (!getTBTStandaloneValue(data, target) || _index == 0) + return false; + + vector seq; + for (int pos = _index - 1; pos >= 0; pos--) { + Uint16 key = CHR(pos); + if (!isLetterKey(key) || isSpellingConsonant(key)) + break; + seq.insert(seq.begin(), key | (TypingWord[pos] & (TONE_MASK | TONEW_MASK))); + } + seq.push_back(target); + if (seq.size() < 2) + return false; + + vector>& vowelSet = _vowelCombine[(Uint16)(seq[0] & CHAR_MASK)]; + for (const auto& vowel : vowelSet) { + if (seq.size() > vowel.size() - 1) + continue; + bool matches = true; + for (size_t pos = 0; pos < seq.size(); pos++) { + if (vowel[pos + 1] != seq[pos]) { + matches = false; + break; + } + } + if (matches) + return true; + } + return false; +} + bool restoreTBTStandaloneThenInsertRaw(const Uint16& data, const bool& isCaps) { Uint16 previousRawKey; if (_index == 0 || !getTBTStandaloneRawKey(TypingWord[_index - 1], previousRawKey)) From d32b3ad2e0330209a92366b667b74830a6dd4f4d Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 12:10:16 +0700 Subject: [PATCH 36/60] fix: widen input type combo --- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.rc | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc index 349553ff..9847fb28 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc @@ -108,18 +108,18 @@ BEGIN PUSHBUTTON "Kiểm tra bản mới...",IDC_BUTTON_CHECK_VERSION,44,117,75,14 END -IDD_DIALOG_MAIN DIALOGEX 0, 0, 297, 261 +IDD_DIALOG_MAIN DIALOGEX 0, 0, 330, 261 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "OpenKey 1.4 - Bộ gõ Tiếng Việt" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - PUSHBUTTON "Cài đặt gốc",ID_BTN_DEFAULT,113,234,71,20 - GROUPBOX "Điều khiển",IDC_STATIC,7,7,283,73 + PUSHBUTTON "Cài đặt gốc",ID_BTN_DEFAULT,129,234,71,20 + GROUPBOX "Điều khiển",IDC_STATIC,7,7,316,73 LTEXT "Kiểu gõ:",IDC_STATIC,14,23,27,8 - COMBOBOX IDC_COMBO_INPUT_TYPE,64,20,73,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - PUSHBUTTON "...",IDC_BTN_CUSTOM_INPUT_METHOD,139,20,14,12 - LTEXT "Bảng mã:",IDC_STATIC,158,23,38,8 - COMBOBOX IDC_COMBO_TABLE_CODE,193,20,90,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_COMBO_INPUT_TYPE,64,20,108,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "...",IDC_BTN_CUSTOM_INPUT_METHOD,174,20,14,12 + LTEXT "Bảng mã:",IDC_STATIC,198,23,38,8 + COMBOBOX IDC_COMBO_TABLE_CODE,231,20,85,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP LTEXT "Phím chuyển:",IDC_STATIC,14,41,44,8 CONTROL "Ctrl",IDC_CHECK_SWITCH_KEY_CTRL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,64,40,27,10 CONTROL "Alt",IDC_CHECK_SWITCH_KEY_ALT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,97,40,25,10 @@ -130,9 +130,9 @@ BEGIN LTEXT "Chế độ gõ:",IDC_STATIC,14,60,36,8 CONTROL "Tiếng Việt",IDC_RADIO_METHOD_VIETNAMESE,"Button",BS_AUTORADIOBUTTON,64,59,47,10 CONTROL "Tiếng Anh",IDC_RADIO_METHOD_ENGLISH,"Button",BS_AUTORADIOBUTTON,158,59,48,10 - DEFPUSHBUTTON "Đóng",IDBUTTON_OK,200,234,71,20 - PUSHBUTTON "Tắt OpenKey",IDBUTTON_EXIT,26,234,71,20 - CONTROL "",IDC_TAB_CONTROL,"SysTabControl32",TCS_FIXEDWIDTH,7,89,283,140 + DEFPUSHBUTTON "Đóng",IDBUTTON_OK,216,234,71,20 + PUSHBUTTON "Tắt OpenKey",IDBUTTON_EXIT,42,234,71,20 + CONTROL "",IDC_TAB_CONTROL,"SysTabControl32",TCS_FIXEDWIDTH,7,89,316,140 END IDD_DIALOG_MACRO DIALOGEX 0, 0, 415, 253 From 755b86f96851ff3cd0a35fbbee3e5229650ec7fb Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 12:35:41 +0700 Subject: [PATCH 37/60] fix: use portable config folder --- .../win32/OpenKey/OpenKey/OpenKeyHelper.cpp | 87 ++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp index c9ddd72c..38b6bed5 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp @@ -27,6 +27,9 @@ static HKEY hKey; static LPCTSTR _runOnStartupKeyPath = _T("Software\\Microsoft\\Windows\\CurrentVersion\\Run"); static TCHAR _executePath[MAX_PATH]; static bool _hasGetPath = false; +static bool _hasCheckedPortable = false; +static bool _isPortable = false; +static TCHAR _portablePath[MAX_PATH]; static DWORD _cacheProcessId = 0, _tempProcessId = 0; static HWND _tempWnd; @@ -40,6 +43,37 @@ int CF_RTF = RegisterClipboardFormat(_T("Rich Text Format")); int CF_HTML = RegisterClipboardFormat(_T("HTML Format")); int CF_OPENKEY = RegisterClipboardFormat(_T("OpenKey Format")); +static LPCTSTR getPortablePath() { + if (!_hasCheckedPortable) { + lstrcpy(_portablePath, OpenKeyHelper::getExecutePath()); + TCHAR* slash = _tcsrchr(_portablePath, '\\'); + if (slash) { + *(slash + 1) = 0; + lstrcat(_portablePath, _T(".portable")); + DWORD attr = GetFileAttributes(_portablePath); + if (attr == INVALID_FILE_ATTRIBUTES) { + CreateDirectory(_portablePath, NULL); + attr = GetFileAttributes(_portablePath); + } + _isPortable = attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY); + } + _hasCheckedPortable = true; + } + return _portablePath; +} + +static bool isPortableMode() { + getPortablePath(); + return _isPortable; +} + +static void getPortableConfigPath(LPCTSTR key, LPCTSTR ext, LPTSTR outPath) { + lstrcpy(outPath, getPortablePath()); + lstrcat(outPath, _T("\\")); + lstrcat(outPath, key); + lstrcat(outPath, ext); +} + void OpenKeyHelper::openKey() { LONG nError = RegOpenKeyEx(HKEY_CURRENT_USER, sk, NULL, KEY_ALL_ACCESS, &hKey); if (nError == ERROR_FILE_NOT_FOUND) { @@ -51,12 +85,25 @@ void OpenKeyHelper::openKey() { } void OpenKeyHelper::setRegInt(LPCTSTR key, const int & val) { + if (isPortableMode()) { + TCHAR path[MAX_PATH]; + getPortableConfigPath(_T("settings"), _T(".ini"), path); + TCHAR value[32]; + wsprintf(value, _T("%d"), val); + WritePrivateProfileString(_T("OpenKey"), key, value, path); + return; + } openKey(); RegSetValueEx(hKey, key, 0, REG_DWORD, (LPBYTE)&val, sizeof(val)); RegCloseKey(hKey); } int OpenKeyHelper::getRegInt(LPCTSTR key, const int & defaultValue) { + if (isPortableMode()) { + TCHAR path[MAX_PATH]; + getPortableConfigPath(_T("settings"), _T(".ini"), path); + return GetPrivateProfileInt(_T("OpenKey"), key, defaultValue, path); + } openKey(); int val = defaultValue; DWORD size = sizeof(val); @@ -68,12 +115,47 @@ int OpenKeyHelper::getRegInt(LPCTSTR key, const int & defaultValue) { } void OpenKeyHelper::setRegBinary(LPCTSTR key, const BYTE * pData, const int & size) { + if (isPortableMode()) { + TCHAR path[MAX_PATH]; + getPortableConfigPath(key, _T(".bin"), path); + HANDLE file = CreateFile(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file != INVALID_HANDLE_VALUE) { + DWORD written = 0; + WriteFile(file, pData, size, &written, NULL); + CloseHandle(file); + } + return; + } openKey(); RegSetValueEx(hKey, key, 0, REG_BINARY, pData, size); RegCloseKey(hKey); } BYTE * OpenKeyHelper::getRegBinary(LPCTSTR key, DWORD& outSize) { + if (isPortableMode()) { + if (_regData) { + delete[] _regData; + _regData = NULL; + } + TCHAR path[MAX_PATH]; + getPortableConfigPath(key, _T(".bin"), path); + HANDLE file = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) { + outSize = 0; + return NULL; + } + DWORD size = GetFileSize(file, NULL); + _regData = new BYTE[size]; + DWORD read = 0; + if (!ReadFile(file, _regData, size, &read, NULL)) { + delete[] _regData; + _regData = NULL; + size = 0; + } + CloseHandle(file); + outSize = size; + return _regData; + } openKey(); if (_regData) { delete[] _regData; @@ -92,6 +174,9 @@ BYTE * OpenKeyHelper::getRegBinary(LPCTSTR key, DWORD& outSize) { } void OpenKeyHelper::registerRunOnStartup(const int& val) { + if (isPortableMode()) { + return; + } if (val) { if (vRunAsAdmin) { string path = wideStringToUtf8(getFullPath()); @@ -330,4 +415,4 @@ wstring OpenKeyHelper::getContentOfUrl(LPCTSTR url){ } return L""; -} \ No newline at end of file +} From a768a1dcc4d7b5cc8071a05fbdbb21a3bc94850d Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 13:15:18 +0700 Subject: [PATCH 38/60] fix: match custom w and TBT vowel handling --- Sources/OpenKey/engine/Engine.cpp | 38 +++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index f87f6b98..9eec3450 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -141,6 +141,9 @@ void insertMark(const Uint32& markMask, const bool& canModifyFlag=true); void insertKey(const Uint16& keyCode, const bool& isCaps, const bool& isCheckSpelling=true); void saveWord(); bool canApplyTBTStandaloneInVowel(const Uint16& data); +void reverseLastStandaloneChar(const Uint32& keyCode, const bool& isCaps); +void insertW(const Uint16& data, const bool& isCaps); +void checkForStandaloneChar(const Uint16& data, const bool& isCaps, const Uint32& keyWillReverse); static std::wstring_convert> converter; wstring utf8ToWideString(const string& str) { @@ -300,6 +303,33 @@ bool canApplyTBTStandaloneInVowel(const Uint16& data) { return false; } +bool canTBTStandaloneStartVowel(const Uint32& value) { + if (!(value & STANDALONE_MASK)) + return false; + Uint32 key = (value & CHAR_MASK) | (value & (TONE_MASK | TONEW_MASK)); + for (const auto& vowelEntry : _vowelCombine) { + for (const auto& vowel : vowelEntry.second) { + if (vowel.size() > 2 && vowel[1] == key) + return true; + } + } + return false; +} + +void insertCustomW(const Uint16& data, const bool& isCaps, const bool& allowStandaloneAtStart) { + findAndCalculateVowel(); + if (vowelCount > 0) { + insertW(KEY_W, isCaps); + } else if (_index == 0 && allowStandaloneAtStart) { + insertKey(data, isCaps, false); + reverseLastStandaloneChar(KEY_U, isCaps); + } else if (_index > 0) { + checkForStandaloneChar(data, isCaps, KEY_U); + } else { + insertKey(data, isCaps); + } +} + bool restoreTBTStandaloneThenInsertRaw(const Uint16& data, const bool& isCaps) { Uint16 previousRawKey; if (_index == 0 || !getTBTStandaloneRawKey(TypingWord[_index - 1], previousRawKey)) @@ -1362,8 +1392,8 @@ void handleMainKey(const Uint16& data, const bool& isCaps) { if (action == 7) { insertAOE(KEY_A, isCaps); return; } if (action == 8) { insertAOE(KEY_E, isCaps); return; } if (action == 9) { insertAOE(KEY_O, isCaps); return; } - if (action == 10) { insertW(KEY_W, isCaps); return; } - if (action == 11) { insertW(KEY_W, isCaps); return; } + if (action == 10) { insertCustomW(data, isCaps, false); return; } + if (action == 11) { insertCustomW(data, isCaps, false); return; } if (action == 12) { findAndCalculateVowel(); if (vowelCount > 0 && CHR(VEI) == KEY_U) { @@ -1390,7 +1420,7 @@ void handleMainKey(const Uint16& data, const bool& isCaps) { return; } if (action == 16 || action == 17) { - insertW(KEY_W, isCaps); + insertCustomW(data, isCaps, action == 16); return; } if (action == 18) { @@ -1941,7 +1971,7 @@ void vKeyHandleEvent(const vKeyEvent& event, } } } - bool isTBTStandalone = (vInputType == vTuBinhTranDonGian) && IS_TBT_STANDALONE_KEY(data) && !shouldKeepTBTStandaloneKeyRaw(data); + bool isTBTStandalone = (vInputType == vTuBinhTranDonGian) && IS_TBT_STANDALONE_KEY(data) && !shouldKeepTBTStandaloneKeyRaw(data) && !canTBTStandaloneStartVowel(TypingWord[_index - 1]); if ((IS_BRACKET_KEY(data) && (( IS_BRACKET_KEY( (((Uint16)hData[0] & ~CAPS_MASK)) )) || vInputType == vSimpleTelex1 || vInputType == vSimpleTelex2)) || ((isTBTStandalone || isCustomStandalone) && ((((Uint16)hData[0] & ~CAPS_MASK)) == data))) { if (_index - (hCode == vWillProcess ? hBPC : 0) > 0) { From 4a3237f1378906bc51e04e64299f59377a08a4f0 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 13:28:12 +0700 Subject: [PATCH 39/60] fix: handle TBT consonant clusters --- Sources/OpenKey/engine/Engine.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 9eec3450..c3db4f16 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -198,12 +198,21 @@ bool isLetterKey(const Uint16& data) { } } +bool isTBTConsonantContextAt(const int& index) { + if (index < 0) + return false; + Uint16 key = CHR(index); + if (IS_TBT_CONSONANT_CONTEXT(key)) + return true; + return index > 0 && ((key == KEY_I && CHR(index - 1) == KEY_G) || (key == KEY_U && CHR(index - 1) == KEY_Q)); +} + bool shouldKeepTBTStandaloneKeyRaw(const Uint16& data) { if (vInputType != vTuBinhTranDonGian || !IS_TBT_STANDALONE_KEY(data)) return false; if (_specialChar.size() > 0) return true; - return _index > 0 && !IS_TBT_CONSONANT_CONTEXT(CHR(_index - 1)) && !canApplyTBTStandaloneInVowel(data); + return _index > 0 && !isTBTConsonantContextAt(_index - 1) && !canApplyTBTStandaloneInVowel(data); } bool isSpellingConsonant(const Uint16& data) { From 68dabb603db30ea508d0aa14fe5e5e3008230cef Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 14:12:44 +0700 Subject: [PATCH 40/60] Support text macro blocks --- Sources/OpenKey/engine/Macro.cpp | 92 +++++++++++++------ .../win32/OpenKey/OpenKey/MacroDialog.cpp | 25 +++-- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 27 ++++-- 3 files changed, 103 insertions(+), 41 deletions(-) diff --git a/Sources/OpenKey/engine/Macro.cpp b/Sources/OpenKey/engine/Macro.cpp index f0bba8cf..07872216 100644 --- a/Sources/OpenKey/engine/Macro.cpp +++ b/Sources/OpenKey/engine/Macro.cpp @@ -279,7 +279,7 @@ void saveToFile(const string& path) { unsigned char bom[] = {0xEF, 0xBB, 0xBF}; myfile.write((char*)bom, 3); - string header = ";Compatible OpenKey Macro Data file for UniKey*** version=1 ***\n"; + string header = ";OpenKey Macro Text Data version=2\n"; myfile.write(header.c_str(), header.length()); for (std::map, MacroData>::iterator it = macroMap.begin(); it != macroMap.end(); ++it) { @@ -297,20 +297,22 @@ void saveToFile(const string& path) { content.replace(start_pos, 1, "\n"); start_pos += 1; } - // Escape newlines as \n - start_pos = 0; - while ((start_pos = content.find("\n", start_pos)) != string::npos) { - content.replace(start_pos, 1, "\\n"); - start_pos += 2; - } - string line = name + "||" + content + "\n"; + string line = "::" + name + "\n" + content + "\n::end\n\n"; myfile.write(line.c_str(), line.length()); } myfile.close(); } } +static bool readBlockMacroLine(const string& line, string& name) { + if (line.size() <= 2 || line[0] != ':' || line[1] != ':' || line == "::end") { + return false; + } + name = line.substr(2); + return !name.empty(); +} + void readFromFile(const string& path, const bool& append) { ifstream myfile(path.c_str(), ios::in | ios::binary); if (myfile.is_open()) { @@ -326,30 +328,68 @@ void readFromFile(const string& path, const bool& append) { myfile.seekg(0); } + vector lines; string line; - int k = 0; while (getline(myfile, line)) { - // Remove trailing \r or \n - while (!line.empty() && (line.back() == '\r' || line.back() == '\n')) { + while (!line.empty() && line.back() == '\r') { line.pop_back(); } - k++; - if (k == 1) continue; - - size_t pos = line.find("||"); - if (pos != string::npos) { - string name = line.substr(0, pos); - string content = line.substr(pos + 2); - - // Replace escaped \n with real CRLF - size_t start_pos = 0; - while ((start_pos = content.find("\\n", start_pos)) != string::npos) { - content.replace(start_pos, 2, "\r\n"); - start_pos += 2; + lines.push_back(line); + } + + bool hasBlockMacro = false; + for (size_t i = 0; i < lines.size(); i++) { + string blockName; + if (readBlockMacroLine(lines[i], blockName)) { + hasBlockMacro = true; + break; + } + } + + if (hasBlockMacro) { + string name; + string content; + bool inBlock = false; + for (size_t i = 0; i < lines.size(); i++) { + string blockName; + if (!inBlock && readBlockMacroLine(lines[i], blockName)) { + name = blockName; + content.clear(); + inBlock = true; + continue; } - - if (!name.empty()) { + if (inBlock && lines[i] == "::end") { addMacro(name, content); + inBlock = false; + continue; + } + if (inBlock) { + if (!content.empty()) { + content += "\n"; + } + content += lines[i]; + } + } + } else { + for (size_t i = 0; i < lines.size(); i++) { + line = lines[i]; + if (line.empty() || line[0] == ';') continue; + + size_t pos = line.find("||"); + if (pos != string::npos) { + string name = line.substr(0, pos); + string content = line.substr(pos + 2); + + // Replace escaped \n with real CRLF + size_t start_pos = 0; + while ((start_pos = content.find("\\n", start_pos)) != string::npos) { + content.replace(start_pos, 2, "\r\n"); + start_pos += 2; + } + + if (!name.empty()) { + addMacro(name, content); + } } } } diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index 13492649..97bfbc08 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -17,6 +17,16 @@ redistribute your new version, it MUST be open source. #include #define MAX_MACRO_BUFFER 65536 + +static wstring getMacroTextPath() { + wstring macroPath = OpenKeyHelper::getExecutePath(); + size_t pos = macroPath.find_last_of(L"\\/"); + if (pos != wstring::npos) { + macroPath = macroPath.substr(0, pos); + } + macroPath += L"\\openkeymacro.txt"; + return macroPath; +} #define BTN_ADD_TEXT _T("+ Thêm") #define BTN_UPDATE_TEXT _T("+ Sửa") @@ -274,10 +284,7 @@ void MacroDialog::saveAndReload() { getMacroSaveData(macroData); OpenKeyHelper::setRegBinary(_T("macroData"), macroData.data(), (int)macroData.size()); - // Save to openkeymacro.md in executable folder - wstring macroPath = OpenKeyHelper::getExecutePath(); - macroPath += L"\\openkeymacro.md"; - saveToFile(wideStringToUtf8(macroPath)); + saveToFile(wideStringToUtf8(getMacroTextPath())); //reload data fillData(); @@ -360,7 +367,7 @@ void MacroDialog::onImportMacroButton() { ofn.hwndOwner = hDlg; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); - ofn.lpstrFilter = _T("Markdown file (*.md)\0*.md\0All (*.*)\0*.*\0"); + ofn.lpstrFilter = _T("Text file (*.txt)\0*.txt\0Markdown file (*.md)\0*.md\0All (*.*)\0*.*\0"); ofn.nFilterIndex = 1; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = 0; @@ -382,19 +389,19 @@ void MacroDialog::onImportMacroButton() { void MacroDialog::onExportMacrobutton() { OPENFILENAME ofn; - TCHAR szFile[MAX_PATH] = { 'O', 'p', 'e', 'n', 'K', 'e', 'y', 'M', 'a', 'c', 'r', 'o' }; + TCHAR szFile[MAX_PATH] = { 'o', 'p', 'e', 'n', 'k', 'e', 'y', 'm', 'a', 'c', 'r', 'o', '.', 't', 'x', 't' }; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hDlg; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); - ofn.lpstrFilter = _T("Markdown file (*.md)\0*.md\0"); + ofn.lpstrFilter = _T("Text file (*.txt)\0*.txt\0"); ofn.lpstrFileTitle = (LPTSTR)_T("lpstrFile"); ofn.nFilterIndex = 1; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = 0; ofn.lpstrInitialDir = NULL; - ofn.lpstrDefExt = (LPCWSTR)L"md"; + ofn.lpstrDefExt = (LPCWSTR)L"txt"; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; if (GetSaveFileName(&ofn) == TRUE) { wstring path = ofn.lpstrFile; @@ -407,4 +414,4 @@ void MacroDialog::onCheckboxClicked(const HWND& hWnd) { int val = (int)SendMessage(hWnd, BM_GETCHECK, 0, 0); APP_SET_DATA(vAutoCapsMacro, val ? 1 : 0); } -} \ No newline at end of file +} diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index b61716ed..6d081594 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -69,6 +69,20 @@ static bool _hasJustUsedHotKey = false; static INPUT backspaceEvent[2]; static INPUT keyEvent[2]; +static wstring getExecuteFolderPath() { + wstring path = OpenKeyHelper::getExecutePath(); + size_t pos = path.find_last_of(L"\\/"); + if (pos != wstring::npos) { + path = path.substr(0, pos); + } + return path; +} + +static bool fileExists(const wstring& path) { + ifstream file(wideStringToUtf8(path).c_str(), ios::in | ios::binary); + return file.is_open(); +} + LRESULT CALLBACK keyboardHookProcess(int nCode, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK mouseHookProcess(int nCode, WPARAM wParam, LPARAM lParam); VOID CALLBACK winEventProcCallback(HWINEVENTHOOK hWinEventHook, DWORD dwEvent, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime); @@ -155,13 +169,14 @@ void OpenKeyInit() { if (GetKeyState(VK_CAPITAL) == 1) _flag |= MASK_CAPITAL; if (GetKeyState(VK_SCROLL) < 0) _flag |= MASK_SCROLL; - //init and load macro data from file, fallback to registry - wstring macroPath = OpenKeyHelper::getExecutePath(); - macroPath += L"\\openkeymacro.md"; - ifstream testFile(wideStringToUtf8(macroPath).c_str(), ios::in | ios::binary); - if (testFile.is_open()) { - testFile.close(); + //init and load macro data from text file, fallback to old text/binary data + wstring macroPath = getExecuteFolderPath() + L"\\openkeymacro.txt"; + wstring oldMacroPath = getExecuteFolderPath() + L"\\openkeymacro.md"; + if (fileExists(macroPath)) { readFromFile(wideStringToUtf8(macroPath), false); + } else if (fileExists(oldMacroPath)) { + readFromFile(wideStringToUtf8(oldMacroPath), false); + saveToFile(wideStringToUtf8(macroPath)); } else { DWORD macroDataSize; BYTE* macroData = OpenKeyHelper::getRegBinary(_T("macroData"), macroDataSize); From ea553529719ec98e0cde40c4e78d24423b8d015a Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 15:11:27 +0700 Subject: [PATCH 41/60] Fix macro import and Win key raw input --- Sources/OpenKey/engine/Macro.cpp | 5 +++++ Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Sources/OpenKey/engine/Macro.cpp b/Sources/OpenKey/engine/Macro.cpp index 07872216..d754e45d 100644 --- a/Sources/OpenKey/engine/Macro.cpp +++ b/Sources/OpenKey/engine/Macro.cpp @@ -386,6 +386,11 @@ void readFromFile(const string& path, const bool& append) { content.replace(start_pos, 2, "\r\n"); start_pos += 2; } + start_pos = 0; + while ((start_pos = content.find("\xEF\xBF\xBE", start_pos)) != string::npos) { + content.replace(start_pos, 3, "\r\n"); + start_pos += 2; + } if (!name.empty()) { addMacro(name, content); diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index 6d081594..bce64027 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -27,7 +27,7 @@ redistribute your new version, it MUST be open source. #define MASK_WIN 0x20 #define MASK_SCROLL 0x40 -#define OTHER_CONTROL_KEY (_flag & MASK_ALT) || (_flag & MASK_CONTROL) +#define OTHER_CONTROL_KEY (_flag & MASK_ALT) || (_flag & MASK_CONTROL) || (_flag & MASK_WIN) #define DYNA_DATA(macro, pos) (macro ? pData->macroData[pos] : pData->charData[pos]) #define EMPTY_HOTKEY 0xFE0000FE @@ -307,7 +307,7 @@ static void SendBackspace() { SendMessage(HWND_BROADCAST, WM_CHAR, VK_BACK, 0L); SendMessage(HWND_BROADCAST, WM_CHAR, VK_BACK, 0L); } - 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))) { SendInput(2, backspaceEvent, sizeof(INPUT)); From 2dae3618c606bfbaf7bfc879aa169cc2c67376d1 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 16:46:55 +0700 Subject: [PATCH 42/60] Fix macro conversion and punctuation triggers --- Sources/OpenKey/engine/Engine.cpp | 83 ++++++++++++++----- .../win32/OpenKey/OpenKey/MacroDialog.cpp | 38 +++++++-- .../win32/OpenKey/OpenKey/MacroDialog.h | 1 + .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 4 +- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.rc | 21 ++--- .../OpenKey/win32/OpenKey/OpenKey/resource.h | 1 + 6 files changed, 110 insertions(+), 38 deletions(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index c3db4f16..24f2c58d 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -184,6 +184,29 @@ bool isMacroBreakCode(const int& data) { return false; } +static bool canAppendCurrentKeyToMacro(const Uint16& data, const bool& isCaps) { + return data != KEY_SPACE && keyCodeToCharacter(data | (isCaps ? CAPS_MASK : 0)) != 0; +} + +static bool findMacroMatch(vector key, vector& macroData, Byte& backspaceCount) { + if (findMacro(key, macroData)) { + backspaceCount = (Byte)key.size(); + return true; + } + + vector collapsedKey; + for (size_t index = 0; index < key.size(); index++) { + if (collapsedKey.empty() || collapsedKey.back() != key[index]) { + collapsedKey.push_back(key[index]); + } + } + if (collapsedKey.size() != key.size() && findMacro(collapsedKey, macroData)) { + backspaceCount = (Byte)collapsedKey.size(); + return true; + } + return false; +} + bool isLetterKey(const Uint16& data) { switch (data) { case KEY_A: case KEY_B: case KEY_C: case KEY_D: case KEY_E: @@ -1687,9 +1710,24 @@ bool checkQuickConsonant() { void vEnglishMode(const vKeyEventState& state, const Uint16& data, const bool& isCaps, const bool& otherControlKey) { hCode = vDoNothing; + hExt = 0; if (state == vKeyEventState::MouseDown || (otherControlKey && !isCaps)) { hMacroKey.clear(); _willTempOffEngine = false; + } else if (isMacroBreakCode(data)) { + vector macroKey = hMacroKey; + if (canAppendCurrentKeyToMacro(data, isCaps)) { + macroKey.push_back(data | (isCaps ? CAPS_MASK : 0)); + } + Byte macroBackspace = 0; + if (!_hasHandledMacro && findMacroMatch(macroKey, hMacroData, macroBackspace)) { + hCode = vReplaceMaro; + hBPC = macroBackspace > 0 ? macroBackspace - 1 : 0; + hExt = 5; + _hasHandledMacro = true; + } else { + hMacroKey = macroKey; + } } else if (data == KEY_SPACE || data == KEY_RETURN || data == KEY_ENTER || data == VK_LSHIFT || data == VK_RSHIFT || data == VK_LCONTROL || data == VK_RCONTROL) { bool isTrigger = false; if (data == KEY_SPACE && (vMacroTriggerMask & 0x01)) isTrigger = true; @@ -1699,9 +1737,10 @@ void vEnglishMode(const vKeyEventState& state, const Uint16& data, const bool& i else if (data == VK_LCONTROL && (vMacroTriggerMask & 0x10)) isTrigger = true; else if (data == VK_RCONTROL && (vMacroTriggerMask & 0x20)) isTrigger = true; - if (isTrigger && !_hasHandledMacro && findMacro(hMacroKey, hMacroData)) { + Byte macroBackspace = 0; + if (isTrigger && !_hasHandledMacro && findMacroMatch(hMacroKey, hMacroData, macroBackspace)) { hCode = vReplaceMaro; - hBPC = (Byte)hMacroKey.size(); + hBPC = macroBackspace; } hMacroKey.clear(); _willTempOffEngine = false; @@ -1758,10 +1797,18 @@ void vKeyHandleEvent(const vKeyEvent& event, else if (data == VK_RCONTROL && (vMacroTriggerMask & 0x20)) canTriggerMacro = true; else if (isMacroBreakCode(data)) canTriggerMacro = true; - if (vUseMacro && canTriggerMacro && !_hasHandledMacro && findMacro(hMacroKey, hMacroData)) { - hCode = vReplaceMaro; - hBPC = (Byte)hMacroKey.size(); - _hasHandledMacro = true; + if (vUseMacro && canTriggerMacro && !_hasHandledMacro) { + vector macroKey = hMacroKey; + if (isMacroBreakCode(data) && canAppendCurrentKeyToMacro(data, _isCaps)) { + macroKey.push_back(data | (_isCaps ? CAPS_MASK : 0)); + } + Byte macroBackspace = 0; + if (findMacroMatch(macroKey, hMacroData, macroBackspace)) { + hCode = vReplaceMaro; + hBPC = macroKey.size() == hMacroKey.size() ? macroBackspace : (macroBackspace > 0 ? macroBackspace - 1 : 0); + hExt = macroKey.size() == hMacroKey.size() ? hExt : 5; + _hasHandledMacro = true; + } } else if ((vQuickStartConsonant || vQuickEndConsonant) && !tempDisableKey && isMacroBreakCode(data)) { checkQuickConsonant(); } else if (vRestoreIfWrongSpelling && isWordBreak(event, state, data)) { //restore key if wrong spelling with break-key @@ -1785,7 +1832,9 @@ void vKeyHandleEvent(const vKeyEvent& event, saveWord(); } _specialChar.push_back(data | (_isCaps ? CAPS_MASK : 0)); - hExt = 3;//normal word + if (hExt != 5) { + hExt = 3;//normal word + } } if (hCode == vDoNothing) { @@ -1798,7 +1847,7 @@ void vKeyHandleEvent(const vKeyEvent& event, //insert key for macro function if (vUseMacro) { - if (_isCharKeyCode) { + if (_isCharKeyCode && hExt != 5) { hMacroKey.push_back(data | (_isCaps ? CAPS_MASK : 0)); } else { hMacroKey.clear(); @@ -1817,9 +1866,10 @@ void vKeyHandleEvent(const vKeyEvent& event, if (!tempDisableKey && vCheckSpelling) { checkSpelling(true); //force check spelling } - if (vUseMacro && (vMacroTriggerMask & 0x01) && !_hasHandledMacro && findMacro(hMacroKey, hMacroData)) { //macro + Byte macroBackspace = 0; + if (vUseMacro && (vMacroTriggerMask & 0x01) && !_hasHandledMacro && findMacroMatch(hMacroKey, hMacroData, macroBackspace)) { //macro hCode = vReplaceMaro; - hBPC = (Byte)hMacroKey.size(); + hBPC = macroBackspace; _spaceCount++; _hasHandledMacro = true; } else if ((vQuickStartConsonant || vQuickEndConsonant) && !tempDisableKey && checkQuickConsonant()) { @@ -1948,18 +1998,7 @@ void vKeyHandleEvent(const vKeyEvent& event, //insert or replace key for macro feature if (vUseMacro) { - if (hCode == vDoNothing) { - hMacroKey.push_back(data | (_isCaps ? CAPS_MASK : 0)); - } else if (hCode == vWillProcess || hCode == vRestore) { - for (i = 0; i < hBPC; i++) { - if (hMacroKey.size() > 0) { - hMacroKey.pop_back(); - } - } - for (i = _index - hBPC; i < hNCC + (_index - hBPC); i++) { - hMacroKey.push_back(TypingWord[i]); - } - } + hMacroKey.push_back(data | (_isCaps ? CAPS_MASK : 0)); } if (vUpperCaseFirstChar) { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index 97bfbc08..61ef06a4 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -121,6 +121,9 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara case IDC_BUTTON_IMPORT_MACRO: onImportMacroButton(); break; + case IDC_BUTTON_CONVERT_EVKEY_MACRO: + onConvertEvKeyMacroButton(); + break; case IDC_BUTTON_EXPORT_MACRO: onExportMacrobutton(); break; @@ -166,6 +169,7 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara HWND hAdd = GetDlgItem(hDlg, IDC_BUTTON_ADD); HWND hDel = GetDlgItem(hDlg, IDC_BUTTON_DELETE); HWND hImport = GetDlgItem(hDlg, IDC_BUTTON_IMPORT_MACRO); + HWND hConvertEvKey = GetDlgItem(hDlg, IDC_BUTTON_CONVERT_EVKEY_MACRO); HWND hExport = GetDlgItem(hDlg, IDC_BUTTON_EXPORT_MACRO); HWND hCaps = GetDlgItem(hDlg, IDC_CHECK_AUTO_CAPS); HWND hTriggerLabel = GetDlgItem(hDlg, IDC_LABEL_MACRO_TRIGGERS); @@ -173,7 +177,7 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara // Layout constants based on original dialog (415 x 253 DLU ~ 622 x 380 pixels) // We will adjust controls using SetWindowPos - if (hName && hContent && hAdd && hDel && hList && hImport && hExport && hCaps && hTriggerCombo) { + if (hName && hContent && hAdd && hDel && hList && hImport && hConvertEvKey && hExport && hCaps && hTriggerCombo) { // Top controls: // Name: x=10, y=28, w=110, h=22 SetWindowPos(hName, NULL, 10, 28, 110, 22, SWP_NOZORDER); @@ -198,10 +202,9 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara int bottomY = height - 35; // Import: x=10, w=100, h=22 SetWindowPos(hImport, NULL, 10, bottomY, 100, 22, SWP_NOZORDER); - // Export: x=120, w=100, h=22 - SetWindowPos(hExport, NULL, 120, bottomY, 100, 22, SWP_NOZORDER); - // Auto Caps checkbox: x=230, w=130, h=22 - SetWindowPos(hCaps, NULL, 230, bottomY, 130, 22, SWP_NOZORDER); + SetWindowPos(hConvertEvKey, NULL, 120, bottomY, 115, 22, SWP_NOZORDER); + SetWindowPos(hExport, NULL, 245, bottomY, 100, 22, SWP_NOZORDER); + SetWindowPos(hCaps, NULL, 355, bottomY, 130, 22, SWP_NOZORDER); // Trigger Label: x=width - 250, w=60, h=22 if (hTriggerLabel) { SetWindowPos(hTriggerLabel, NULL, width - 260, bottomY + 3, 60, 22, SWP_NOZORDER); @@ -387,6 +390,31 @@ void MacroDialog::onImportMacroButton() { } } +void MacroDialog::onConvertEvKeyMacroButton() { + OPENFILENAME ofn; + TCHAR szFile[MAX_PATH] = { 0 }; + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = hDlg; + ofn.lpstrFile = szFile; + ofn.nMaxFile = sizeof(szFile); + ofn.lpstrFilter = _T("EVKey macro (*.txt)\0*.txt\0All (*.*)\0*.*\0"); + ofn.nFilterIndex = 1; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + + if (GetOpenFileName(&ofn) == TRUE) { + int msgboxID = MessageBox( + hDlg, + _T("Bạn có muốn giữ lại dữ liệu hiện tại không?"), + _T("Chuyển EvKey macro"), + MB_ICONEXCLAMATION | MB_YESNO + ); + wstring path = ofn.lpstrFile; + readFromFile(wideStringToUtf8(path), msgboxID == IDYES); + saveAndReload(); + } +} + void MacroDialog::onExportMacrobutton() { OPENFILENAME ofn; TCHAR szFile[MAX_PATH] = { 'o', 'p', 'e', 'n', 'k', 'e', 'y', 'm', 'a', 'c', 'r', 'o', '.', 't', 'x', 't' }; diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h index 2a9cc0d9..483f72f7 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h @@ -38,6 +38,7 @@ class MacroDialog : public BaseDialog { void onDeleteMacroButton(); void onImportMacroButton(); + void onConvertEvKeyMacroButton(); void onExportMacrobutton(); void onCheckboxClicked(const HWND& hWnd); diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index bce64027..04263fff 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -584,7 +584,9 @@ static void handleMacro() { CloseClipboard(); } - SendKeyCode(_keycode | (_flag & MASK_SHIFT ? CAPS_MASK : 0)); + if (pData->extCode != 5) { + SendKeyCode(_keycode | (_flag & MASK_SHIFT ? CAPS_MASK : 0)); + } } static bool SetModifierMask(const Uint16& vkCode) { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc index 9847fb28..e104f73f 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc @@ -135,24 +135,25 @@ BEGIN CONTROL "",IDC_TAB_CONTROL,"SysTabControl32",TCS_FIXEDWIDTH,7,89,316,140 END -IDD_DIALOG_MACRO DIALOGEX 0, 0, 415, 253 +IDD_DIALOG_MACRO DIALOGEX 0, 0, 500, 253 STYLE DS_SETFONT | WS_THICKFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX CAPTION "Thiết lập gõ tắt - OpenKey" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - CONTROL "",IDC_LIST_MACRO_DATA,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP,7,54,401,173 + CONTROL "",IDC_LIST_MACRO_DATA,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP,7,54,486,173 LTEXT "Từ gõ tắt",IDC_STATIC,7,6,30,8 LTEXT "Nội dung đầy đủ",IDC_STATIC,90,6,53,8 EDITTEXT IDC_EDIT_MACRO_NAME,7,18,72,14,ES_AUTOHSCROLL - EDITTEXT IDC_EDIT_MACRO_CONTENT,82,18,234,30,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | ES_NOHIDESEL | WS_VSCROLL - PUSHBUTTON "+ Thêm",IDC_BUTTON_ADD,320,18,88,14 - PUSHBUTTON "- Xóa",IDC_BUTTON_DELETE,320,34,88,14 + EDITTEXT IDC_EDIT_MACRO_CONTENT,82,18,318,30,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | ES_NOHIDESEL | WS_VSCROLL + PUSHBUTTON "+ Thêm",IDC_BUTTON_ADD,405,18,88,14 + PUSHBUTTON "- Xóa",IDC_BUTTON_DELETE,405,34,88,14 PUSHBUTTON "Nạp từ file...",IDC_BUTTON_IMPORT_MACRO,7,232,66,14 - PUSHBUTTON "Xuất ra file...",IDC_BUTTON_EXPORT_MACRO,82,232,66,14 + PUSHBUTTON "Convert EvKey...",IDC_BUTTON_CONVERT_EVKEY_MACRO,82,232,76,14 + PUSHBUTTON "Xuất ra file...",IDC_BUTTON_EXPORT_MACRO,166,232,66,14 CONTROL "Tự động đổi chữ hoa",IDC_CHECK_AUTO_CAPS, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,155,234,80,10 - LTEXT "Phím gõ tắt:",IDC_LABEL_MACRO_TRIGGERS,242,234,40,8 - COMBOBOX IDC_COMBO_MACRO_TRIGGERS,285,232,123,80,CBS_DROPDOWNLIST | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,245,234,80,10 + LTEXT "Phím gõ tắt:",IDC_LABEL_MACRO_TRIGGERS,330,234,40,8 + COMBOBOX IDC_COMBO_MACRO_TRIGGERS,373,232,120,80,CBS_DROPDOWNLIST | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP END IDD_DIALOG_CONVERT_TOOL DIALOGEX 0, 0, 283, 194 @@ -335,7 +336,7 @@ BEGIN IDD_DIALOG_MACRO, DIALOG BEGIN LEFTMARGIN, 7 - RIGHTMARGIN, 408 + RIGHTMARGIN, 493 TOPMARGIN, 6 BOTTOMMARGIN, 246 END diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h index 04a92642..7b610ef8 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h @@ -196,6 +196,7 @@ #define IDC_GRP_DEFINE_KEY 1080 #define IDC_GRP_PRESETS 1081 #define IDC_COMBO_MACRO_TRIGGERS 1085 +#define IDC_BUTTON_CONVERT_EVKEY_MACRO 1086 #define IDC_STATIC -1 #define IDC_LABEL_MACRO_TRIGGERS 1099 From 108784fb03233f071f0725f9eb0dfcbccb62dbdf Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 16:58:43 +0700 Subject: [PATCH 43/60] Fix UTF-16 EVKey macro import --- Sources/OpenKey/engine/Macro.cpp | 78 ++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/Sources/OpenKey/engine/Macro.cpp b/Sources/OpenKey/engine/Macro.cpp index d754e45d..ec564e87 100644 --- a/Sources/OpenKey/engine/Macro.cpp +++ b/Sources/OpenKey/engine/Macro.cpp @@ -13,6 +13,7 @@ #include #include #include +#include using namespace std; @@ -313,28 +314,85 @@ static bool readBlockMacroLine(const string& line, string& name) { return !name.empty(); } +static bool looksLikeUtf16LE(const string& data) { + if (data.size() >= 2 && (unsigned char)data[0] == 0xFF && (unsigned char)data[1] == 0xFE) { + return true; + } + size_t nulCount = 0; + size_t checkCount = data.size() < 200 ? data.size() : 200; + for (size_t i = 1; i < checkCount; i += 2) { + if (data[i] == 0) { + nulCount++; + } + } + return checkCount > 20 && nulCount > checkCount / 4; +} + +static bool looksLikeUtf16BE(const string& data) { + if (data.size() >= 2 && (unsigned char)data[0] == 0xFE && (unsigned char)data[1] == 0xFF) { + return true; + } + size_t nulCount = 0; + size_t checkCount = data.size() < 200 ? data.size() : 200; + for (size_t i = 0; i < checkCount; i += 2) { + if (data[i] == 0) { + nulCount++; + } + } + return checkCount > 20 && nulCount > checkCount / 4; +} + +static string utf16BytesToUtf8(const string& data, const bool& bigEndian) { + wstring text; + size_t start = data.size() >= 2 && + ((unsigned char)data[0] == 0xFF && (unsigned char)data[1] == 0xFE || + (unsigned char)data[0] == 0xFE && (unsigned char)data[1] == 0xFF) ? 2 : 0; + for (size_t i = start; i + 1 < data.size(); i += 2) { + unsigned char a = (unsigned char)data[i]; + unsigned char b = (unsigned char)data[i + 1]; + wchar_t ch = (wchar_t)(bigEndian ? (a << 8 | b) : (b << 8 | a)); + text += ch; + } + return wideStringToUtf8(text); +} + +static bool isEvKeyMarkerLine(const string& line) { + return line.find("<<") != string::npos && line.find("Unicode") != string::npos; +} + void readFromFile(const string& path, const bool& append) { ifstream myfile(path.c_str(), ios::in | ios::binary); if (myfile.is_open()) { if (!append) { macroMap.clear(); } - - // Skip UTF-8 BOM if present - unsigned char bom[3] = {0}; - myfile.read((char*)bom, 3); - if (!(bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF)) { - // No BOM, seek back to beginning - myfile.seekg(0); + + string fileData((istreambuf_iterator(myfile)), istreambuf_iterator()); + if (looksLikeUtf16LE(fileData)) { + fileData = utf16BytesToUtf8(fileData, false); + } else if (looksLikeUtf16BE(fileData)) { + fileData = utf16BytesToUtf8(fileData, true); + } else if (fileData.size() >= 3 && + (unsigned char)fileData[0] == 0xEF && + (unsigned char)fileData[1] == 0xBB && + (unsigned char)fileData[2] == 0xBF) { + fileData.erase(0, 3); } - + vector lines; string line; - while (getline(myfile, line)) { + for (size_t i = 0; i <= fileData.size(); i++) { + if (i < fileData.size() && fileData[i] != '\n') { + line += fileData[i]; + continue; + } while (!line.empty() && line.back() == '\r') { line.pop_back(); } - lines.push_back(line); + if (!isEvKeyMarkerLine(line)) { + lines.push_back(line); + } + line.clear(); } bool hasBlockMacro = false; From 1faae69c7127e93f766b3ffbb937b6f1c1ceeb08 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 17:19:32 +0700 Subject: [PATCH 44/60] Improve EVKey macro conversion UI --- Sources/OpenKey/engine/Macro.cpp | 21 +++++++- Sources/OpenKey/engine/Macro.h | 2 + .../win32/OpenKey/OpenKey/MacroDialog.cpp | 54 ++++++++++++++----- .../win32/OpenKey/OpenKey/MacroDialog.h | 1 + .../OpenKey/win32/OpenKey/OpenKey/OpenKey.rc | 9 ++-- .../OpenKey/win32/OpenKey/OpenKey/resource.h | 1 + 6 files changed, 70 insertions(+), 18 deletions(-) diff --git a/Sources/OpenKey/engine/Macro.cpp b/Sources/OpenKey/engine/Macro.cpp index ec564e87..5f02ffae 100644 --- a/Sources/OpenKey/engine/Macro.cpp +++ b/Sources/OpenKey/engine/Macro.cpp @@ -267,6 +267,10 @@ bool deleteMacro(const string& macroText) { return false; } +void clearMacro() { + macroMap.clear(); +} + void onTableCodeChange() { for (std::map, MacroData>::iterator it = macroMap.begin(); it != macroMap.end(); ++it) { convert(it->second.macroContent, it->second.macroContentCode); @@ -360,11 +364,16 @@ static bool isEvKeyMarkerLine(const string& line) { return line.find("<<") != string::npos && line.find("Unicode") != string::npos; } -void readFromFile(const string& path, const bool& append) { +int readFromFileCount(const string& path, const bool& append) { + int importedCount = 0; + map, MacroData> oldMacroMap; + bool clearedMacroMap = false; ifstream myfile(path.c_str(), ios::in | ios::binary); if (myfile.is_open()) { if (!append) { + oldMacroMap = macroMap; macroMap.clear(); + clearedMacroMap = true; } string fileData((istreambuf_iterator(myfile)), istreambuf_iterator()); @@ -418,6 +427,7 @@ void readFromFile(const string& path, const bool& append) { } if (inBlock && lines[i] == "::end") { addMacro(name, content); + importedCount++; inBlock = false; continue; } @@ -452,10 +462,19 @@ void readFromFile(const string& path, const bool& append) { if (!name.empty()) { addMacro(name, content); + importedCount++; } } } } myfile.close(); } + if (importedCount == 0 && clearedMacroMap) { + macroMap = oldMacroMap; + } + return importedCount; +} + +void readFromFile(const string& path, const bool& append) { + readFromFileCount(path, append); } diff --git a/Sources/OpenKey/engine/Macro.h b/Sources/OpenKey/engine/Macro.h index d5767d35..a939cc1a 100644 --- a/Sources/OpenKey/engine/Macro.h +++ b/Sources/OpenKey/engine/Macro.h @@ -56,6 +56,7 @@ bool addMacro(const string& macroText, const string& macroContent); * delete macro from memory */ bool deleteMacro(const string& macroText); +void clearMacro(); /** * When table code changed, we have to call this function to reload all macroContentCode @@ -71,5 +72,6 @@ void saveToFile(const string& path); * Load macro data from disk */ void readFromFile(const string& path, const bool& append=true); +int readFromFileCount(const string& path, const bool& append=true); #endif /* Macro_h */ diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index 61ef06a4..404bb6c0 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -118,6 +118,9 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara case IDC_BUTTON_DELETE: onDeleteMacroButton(); break; + case IDC_BUTTON_DELETE_ALL_MACRO: + onDeleteAllMacroButton(); + break; case IDC_BUTTON_IMPORT_MACRO: onImportMacroButton(); break; @@ -168,6 +171,7 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara HWND hContent = GetDlgItem(hDlg, IDC_EDIT_MACRO_CONTENT); HWND hAdd = GetDlgItem(hDlg, IDC_BUTTON_ADD); HWND hDel = GetDlgItem(hDlg, IDC_BUTTON_DELETE); + HWND hDeleteAll = GetDlgItem(hDlg, IDC_BUTTON_DELETE_ALL_MACRO); HWND hImport = GetDlgItem(hDlg, IDC_BUTTON_IMPORT_MACRO); HWND hConvertEvKey = GetDlgItem(hDlg, IDC_BUTTON_CONVERT_EVKEY_MACRO); HWND hExport = GetDlgItem(hDlg, IDC_BUTTON_EXPORT_MACRO); @@ -177,34 +181,35 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara // Layout constants based on original dialog (415 x 253 DLU ~ 622 x 380 pixels) // We will adjust controls using SetWindowPos - if (hName && hContent && hAdd && hDel && hList && hImport && hConvertEvKey && hExport && hCaps && hTriggerCombo) { + if (hName && hContent && hAdd && hDel && hDeleteAll && hList && hImport && hConvertEvKey && hExport && hCaps && hTriggerCombo) { // Top controls: // Name: x=10, y=28, w=110, h=22 SetWindowPos(hName, NULL, 10, 28, 110, 22, SWP_NOZORDER); - // Content: x=130, y=28, w=width - 275, h=50 + // Content: x=130, y=28, w=width - 275, h=80 int contentWidth = width - 275; if (contentWidth < 100) contentWidth = 100; - SetWindowPos(hContent, NULL, 130, 28, contentWidth, 50, SWP_NOZORDER); + SetWindowPos(hContent, NULL, 130, 28, contentWidth, 80, SWP_NOZORDER); // Add Button: x=width - 135, y=28, w=125, h=22 SetWindowPos(hAdd, NULL, width - 135, 28, 125, 22, SWP_NOZORDER); // Delete Button: x=width - 135, y=54, w=125, h=22 SetWindowPos(hDel, NULL, width - 135, 54, 125, 22, SWP_NOZORDER); + SetWindowPos(hDeleteAll, NULL, 10, 60, 110, 22, SWP_NOZORDER); - // List Macro: x=10, y=90, w=width - 20, h=height - 135 - int listHeight = height - 135; + // List Macro: x=10, y=120, w=width - 20, h=height - 165 + int listHeight = height - 165; if (listHeight < 50) listHeight = 50; - SetWindowPos(hList, NULL, 10, 90, width - 20, listHeight, SWP_NOZORDER); + SetWindowPos(hList, NULL, 10, 120, width - 20, listHeight, SWP_NOZORDER); // Bottom controls: y = height - 35 int bottomY = height - 35; // Import: x=10, w=100, h=22 SetWindowPos(hImport, NULL, 10, bottomY, 100, 22, SWP_NOZORDER); - SetWindowPos(hConvertEvKey, NULL, 120, bottomY, 115, 22, SWP_NOZORDER); - SetWindowPos(hExport, NULL, 245, bottomY, 100, 22, SWP_NOZORDER); - SetWindowPos(hCaps, NULL, 355, bottomY, 130, 22, SWP_NOZORDER); + SetWindowPos(hExport, NULL, 120, bottomY, 100, 22, SWP_NOZORDER); + SetWindowPos(hConvertEvKey, NULL, 230, bottomY, 125, 22, SWP_NOZORDER); + SetWindowPos(hCaps, NULL, 365, bottomY, 130, 22, SWP_NOZORDER); // Trigger Label: x=width - 250, w=60, h=22 if (hTriggerLabel) { SetWindowPos(hTriggerLabel, NULL, width - 260, bottomY + 3, 60, 22, SWP_NOZORDER); @@ -362,6 +367,23 @@ void MacroDialog::onDeleteMacroButton() { SetWindowText(hAddButton, BTN_ADD_TEXT); } +void MacroDialog::onDeleteAllMacroButton() { + int msgboxID = MessageBox( + hDlg, + _T("Bạn có chắc muốn xóa toàn bộ gõ tắt không?"), + _T("Xóa toàn bộ gõ tắt"), + MB_ICONEXCLAMATION | MB_YESNO + ); + if (msgboxID != IDYES) { + return; + } + clearMacro(); + saveAndReload(); + SetWindowText(hMacroName, _T("")); + SetWindowText(hMacroContent, _T("")); + SetWindowText(hAddButton, BTN_ADD_TEXT); +} + void MacroDialog::onImportMacroButton() { OPENFILENAME ofn; TCHAR szFile[MAX_PATH] = { 0 }; @@ -385,8 +407,11 @@ void MacroDialog::onImportMacroButton() { MB_ICONEXCLAMATION | MB_YESNO ); wstring path = ofn.lpstrFile; - readFromFile(wideStringToUtf8(path), msgboxID == IDYES); - saveAndReload(); + if (readFromFileCount(wideStringToUtf8(path), msgboxID == IDYES) > 0) { + saveAndReload(); + } else { + MessageBox(hDlg, _T("Khong tim thay macro hop le trong file."), _T("OpenKey"), MB_OK | MB_ICONWARNING); + } } } @@ -410,8 +435,11 @@ void MacroDialog::onConvertEvKeyMacroButton() { MB_ICONEXCLAMATION | MB_YESNO ); wstring path = ofn.lpstrFile; - readFromFile(wideStringToUtf8(path), msgboxID == IDYES); - saveAndReload(); + if (readFromFileCount(wideStringToUtf8(path), msgboxID == IDYES) > 0) { + saveAndReload(); + } else { + MessageBox(hDlg, _T("Khong tim thay macro EvKey hop le trong file."), _T("OpenKey"), MB_OK | MB_ICONWARNING); + } } } diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h index 483f72f7..608eb8eb 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h @@ -36,6 +36,7 @@ class MacroDialog : public BaseDialog { void onSelectItem(const int& index); void onAddMacroButton(); void onDeleteMacroButton(); + void onDeleteAllMacroButton(); void onImportMacroButton(); void onConvertEvKeyMacroButton(); diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc index e104f73f..26969ee5 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc @@ -140,16 +140,17 @@ STYLE DS_SETFONT | WS_THICKFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTI CAPTION "Thiết lập gõ tắt - OpenKey" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - CONTROL "",IDC_LIST_MACRO_DATA,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP,7,54,486,173 + CONTROL "",IDC_LIST_MACRO_DATA,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP,7,85,486,142 LTEXT "Từ gõ tắt",IDC_STATIC,7,6,30,8 LTEXT "Nội dung đầy đủ",IDC_STATIC,90,6,53,8 EDITTEXT IDC_EDIT_MACRO_NAME,7,18,72,14,ES_AUTOHSCROLL - EDITTEXT IDC_EDIT_MACRO_CONTENT,82,18,318,30,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | ES_NOHIDESEL | WS_VSCROLL + EDITTEXT IDC_EDIT_MACRO_CONTENT,82,18,318,61,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | ES_NOHIDESEL | WS_VSCROLL PUSHBUTTON "+ Thêm",IDC_BUTTON_ADD,405,18,88,14 PUSHBUTTON "- Xóa",IDC_BUTTON_DELETE,405,34,88,14 + PUSHBUTTON "Xóa toàn bộ",IDC_BUTTON_DELETE_ALL_MACRO,7,38,72,14 PUSHBUTTON "Nạp từ file...",IDC_BUTTON_IMPORT_MACRO,7,232,66,14 - PUSHBUTTON "Convert EvKey...",IDC_BUTTON_CONVERT_EVKEY_MACRO,82,232,76,14 - PUSHBUTTON "Xuất ra file...",IDC_BUTTON_EXPORT_MACRO,166,232,66,14 + PUSHBUTTON "Xuất ra file...",IDC_BUTTON_EXPORT_MACRO,82,232,66,14 + PUSHBUTTON "Convert EvKey...",IDC_BUTTON_CONVERT_EVKEY_MACRO,156,232,84,14 CONTROL "Tự động đổi chữ hoa",IDC_CHECK_AUTO_CAPS, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,245,234,80,10 LTEXT "Phím gõ tắt:",IDC_LABEL_MACRO_TRIGGERS,330,234,40,8 diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h index 7b610ef8..d68a14d6 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h @@ -197,6 +197,7 @@ #define IDC_GRP_PRESETS 1081 #define IDC_COMBO_MACRO_TRIGGERS 1085 #define IDC_BUTTON_CONVERT_EVKEY_MACRO 1086 +#define IDC_BUTTON_DELETE_ALL_MACRO 1087 #define IDC_STATIC -1 #define IDC_LABEL_MACRO_TRIGGERS 1099 From 4d624ab10203e8cac71f608c951db5dc5cbc7c38 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 17:41:38 +0700 Subject: [PATCH 45/60] Fix EVKey macro conversion parsing --- Sources/OpenKey/engine/Macro.cpp | 171 +++++++++++------- Sources/OpenKey/engine/Macro.h | 1 + .../win32/OpenKey/OpenKey/MacroDialog.cpp | 9 +- 3 files changed, 114 insertions(+), 67 deletions(-) diff --git a/Sources/OpenKey/engine/Macro.cpp b/Sources/OpenKey/engine/Macro.cpp index 5f02ffae..5d179c8e 100644 --- a/Sources/OpenKey/engine/Macro.cpp +++ b/Sources/OpenKey/engine/Macro.cpp @@ -364,52 +364,118 @@ static bool isEvKeyMarkerLine(const string& line) { return line.find("<<") != string::npos && line.find("Unicode") != string::npos; } -int readFromFileCount(const string& path, const bool& append) { +static string trimEvKeyControlChars(const string& line) { + size_t start = 0; + size_t end = line.size(); + while (start < end && ((unsigned char)line[start] <= 0x20 || (unsigned char)line[start] == 0xEF)) { + start++; + } + while (end > start && (unsigned char)line[end - 1] <= 0x20) { + end--; + } + return line.substr(start, end - start); +} + +static string readMacroTextFile(const string& path, bool& ok) { + ok = false; + ifstream myfile(path.c_str(), ios::in | ios::binary); + if (!myfile.is_open()) { + return ""; + } + string fileData((istreambuf_iterator(myfile)), istreambuf_iterator()); + myfile.close(); + ok = true; + + if (looksLikeUtf16LE(fileData)) { + return utf16BytesToUtf8(fileData, false); + } + if (looksLikeUtf16BE(fileData)) { + return utf16BytesToUtf8(fileData, true); + } + if (fileData.size() >= 3 && + (unsigned char)fileData[0] == 0xEF && + (unsigned char)fileData[1] == 0xBB && + (unsigned char)fileData[2] == 0xBF) { + fileData.erase(0, 3); + } + return fileData; +} + +static void splitMacroLines(const string& fileData, vector& lines) { + lines.clear(); + string line; + for (size_t i = 0; i <= fileData.size(); i++) { + if (i < fileData.size() && fileData[i] != '\n') { + line += fileData[i]; + continue; + } + while (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + line = trimEvKeyControlChars(line); + if (!isEvKeyMarkerLine(line)) { + lines.push_back(line); + } + line.clear(); + } +} + +static int importEvKeyLines(const vector& lines) { + int importedCount = 0; + for (size_t i = 0; i < lines.size(); i++) { + string line = lines[i]; + if (line.empty() || line[0] == ';') continue; + + size_t pos = line.find("||"); + if (pos != string::npos) { + string name = trimEvKeyControlChars(line.substr(0, pos)); + string content = line.substr(pos + 2); + + // Replace escaped \n with real CRLF + size_t start_pos = 0; + while ((start_pos = content.find("\\n", start_pos)) != string::npos) { + content.replace(start_pos, 2, "\r\n"); + start_pos += 2; + } + start_pos = 0; + while ((start_pos = content.find("\xEF\xBF\xBE", start_pos)) != string::npos) { + content.replace(start_pos, 3, "\r\n"); + start_pos += 2; + } + + if (!name.empty()) { + addMacro(name, content); + importedCount++; + } + } + } + return importedCount; +} + +static int readMacroFileCount(const string& path, const bool& append, const bool& evKeyOnly) { int importedCount = 0; map, MacroData> oldMacroMap; bool clearedMacroMap = false; - ifstream myfile(path.c_str(), ios::in | ios::binary); - if (myfile.is_open()) { + bool readOk = false; + string fileData = readMacroTextFile(path, readOk); + if (readOk) { if (!append) { oldMacroMap = macroMap; macroMap.clear(); clearedMacroMap = true; } - string fileData((istreambuf_iterator(myfile)), istreambuf_iterator()); - if (looksLikeUtf16LE(fileData)) { - fileData = utf16BytesToUtf8(fileData, false); - } else if (looksLikeUtf16BE(fileData)) { - fileData = utf16BytesToUtf8(fileData, true); - } else if (fileData.size() >= 3 && - (unsigned char)fileData[0] == 0xEF && - (unsigned char)fileData[1] == 0xBB && - (unsigned char)fileData[2] == 0xBF) { - fileData.erase(0, 3); - } - vector lines; - string line; - for (size_t i = 0; i <= fileData.size(); i++) { - if (i < fileData.size() && fileData[i] != '\n') { - line += fileData[i]; - continue; - } - while (!line.empty() && line.back() == '\r') { - line.pop_back(); - } - if (!isEvKeyMarkerLine(line)) { - lines.push_back(line); - } - line.clear(); - } + splitMacroLines(fileData, lines); bool hasBlockMacro = false; - for (size_t i = 0; i < lines.size(); i++) { - string blockName; - if (readBlockMacroLine(lines[i], blockName)) { - hasBlockMacro = true; - break; + if (!evKeyOnly) { + for (size_t i = 0; i < lines.size(); i++) { + string blockName; + if (readBlockMacroLine(lines[i], blockName)) { + hasBlockMacro = true; + break; + } } } @@ -439,35 +505,8 @@ int readFromFileCount(const string& path, const bool& append) { } } } else { - for (size_t i = 0; i < lines.size(); i++) { - line = lines[i]; - if (line.empty() || line[0] == ';') continue; - - size_t pos = line.find("||"); - if (pos != string::npos) { - string name = line.substr(0, pos); - string content = line.substr(pos + 2); - - // Replace escaped \n with real CRLF - size_t start_pos = 0; - while ((start_pos = content.find("\\n", start_pos)) != string::npos) { - content.replace(start_pos, 2, "\r\n"); - start_pos += 2; - } - start_pos = 0; - while ((start_pos = content.find("\xEF\xBF\xBE", start_pos)) != string::npos) { - content.replace(start_pos, 3, "\r\n"); - start_pos += 2; - } - - if (!name.empty()) { - addMacro(name, content); - importedCount++; - } - } - } + importedCount = importEvKeyLines(lines); } - myfile.close(); } if (importedCount == 0 && clearedMacroMap) { macroMap = oldMacroMap; @@ -475,6 +514,14 @@ int readFromFileCount(const string& path, const bool& append) { return importedCount; } +int readFromFileCount(const string& path, const bool& append) { + return readMacroFileCount(path, append, false); +} + void readFromFile(const string& path, const bool& append) { readFromFileCount(path, append); } + +int readEvKeyFromFileCount(const string& path, const bool& append) { + return readMacroFileCount(path, append, true); +} diff --git a/Sources/OpenKey/engine/Macro.h b/Sources/OpenKey/engine/Macro.h index a939cc1a..654bec83 100644 --- a/Sources/OpenKey/engine/Macro.h +++ b/Sources/OpenKey/engine/Macro.h @@ -73,5 +73,6 @@ void saveToFile(const string& path); */ void readFromFile(const string& path, const bool& append=true); int readFromFileCount(const string& path, const bool& append=true); +int readEvKeyFromFileCount(const string& path, const bool& append=true); #endif /* Macro_h */ diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index 404bb6c0..f1c35fbd 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -430,19 +430,18 @@ void MacroDialog::onConvertEvKeyMacroButton() { if (GetOpenFileName(&ofn) == TRUE) { int msgboxID = MessageBox( hDlg, - _T("Bạn có muốn giữ lại dữ liệu hiện tại không?"), - _T("Chuyển EvKey macro"), + L"B\u1EA1n c\u00F3 mu\u1ED1n gi\u1EEF l\u1EA1i d\u1EEF li\u1EC7u hi\u1EC7n t\u1EA1i kh\u00F4ng?", + L"Chuy\u1EC3n EvKey macro", MB_ICONEXCLAMATION | MB_YESNO ); wstring path = ofn.lpstrFile; - if (readFromFileCount(wideStringToUtf8(path), msgboxID == IDYES) > 0) { + if (readEvKeyFromFileCount(wideStringToUtf8(path), msgboxID == IDYES) > 0) { saveAndReload(); } else { - MessageBox(hDlg, _T("Khong tim thay macro EvKey hop le trong file."), _T("OpenKey"), MB_OK | MB_ICONWARNING); + MessageBox(hDlg, L"Kh\u00F4ng t\u00ECm th\u1EA5y macro EvKey h\u1EE3p l\u1EC7 trong file.", L"OpenKey", MB_OK | MB_ICONWARNING); } } } - void MacroDialog::onExportMacrobutton() { OPENFILENAME ofn; TCHAR szFile[MAX_PATH] = { 'o', 'p', 'e', 'n', 'k', 'e', 'y', 'm', 'a', 'c', 'r', 'o', '.', 't', 'x', 't' }; From 4f073f18acc14f4d5ec0ff98d6dbd3d268ae1148 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 17:56:28 +0700 Subject: [PATCH 46/60] Fix macro punctuation trigger timing --- Sources/OpenKey/engine/Engine.cpp | 58 ++++++------------- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 10 +--- 2 files changed, 19 insertions(+), 49 deletions(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 24f2c58d..227bab54 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -184,6 +184,16 @@ bool isMacroBreakCode(const int& data) { return false; } +static bool isMacroTriggerKey(const Uint16& data) { + if (data == KEY_SPACE && (vMacroTriggerMask & 0x01)) return true; + if ((data == KEY_RETURN || data == KEY_ENTER) && (vMacroTriggerMask & 0x02)) return true; + if (data == VK_LSHIFT && (vMacroTriggerMask & 0x04)) return true; + if (data == VK_RSHIFT && (vMacroTriggerMask & 0x08)) return true; + if (data == VK_LCONTROL && (vMacroTriggerMask & 0x10)) return true; + if (data == VK_RCONTROL && (vMacroTriggerMask & 0x20)) return true; + return false; +} + static bool canAppendCurrentKeyToMacro(const Uint16& data, const bool& isCaps) { return data != KEY_SPACE && keyCodeToCharacter(data | (isCaps ? CAPS_MASK : 0)) != 0; } @@ -1714,36 +1724,18 @@ void vEnglishMode(const vKeyEventState& state, const Uint16& data, const bool& i if (state == vKeyEventState::MouseDown || (otherControlKey && !isCaps)) { hMacroKey.clear(); _willTempOffEngine = false; - } else if (isMacroBreakCode(data)) { - vector macroKey = hMacroKey; - if (canAppendCurrentKeyToMacro(data, isCaps)) { - macroKey.push_back(data | (isCaps ? CAPS_MASK : 0)); - } - Byte macroBackspace = 0; - if (!_hasHandledMacro && findMacroMatch(macroKey, hMacroData, macroBackspace)) { - hCode = vReplaceMaro; - hBPC = macroBackspace > 0 ? macroBackspace - 1 : 0; - hExt = 5; - _hasHandledMacro = true; - } else { - hMacroKey = macroKey; - } } else if (data == KEY_SPACE || data == KEY_RETURN || data == KEY_ENTER || data == VK_LSHIFT || data == VK_RSHIFT || data == VK_LCONTROL || data == VK_RCONTROL) { - bool isTrigger = false; - if (data == KEY_SPACE && (vMacroTriggerMask & 0x01)) isTrigger = true; - else if ((data == KEY_RETURN || data == KEY_ENTER) && (vMacroTriggerMask & 0x02)) isTrigger = true; - else if (data == VK_LSHIFT && (vMacroTriggerMask & 0x04)) isTrigger = true; - else if (data == VK_RSHIFT && (vMacroTriggerMask & 0x08)) isTrigger = true; - else if (data == VK_LCONTROL && (vMacroTriggerMask & 0x10)) isTrigger = true; - else if (data == VK_RCONTROL && (vMacroTriggerMask & 0x20)) isTrigger = true; - Byte macroBackspace = 0; - if (isTrigger && !_hasHandledMacro && findMacroMatch(hMacroKey, hMacroData, macroBackspace)) { + if (isMacroTriggerKey(data) && !_hasHandledMacro && findMacroMatch(hMacroKey, hMacroData, macroBackspace)) { hCode = vReplaceMaro; hBPC = macroBackspace; } hMacroKey.clear(); _willTempOffEngine = false; + } else if (isMacroBreakCode(data)) { + if (canAppendCurrentKeyToMacro(data, isCaps)) { + hMacroKey.push_back(data | (isCaps ? CAPS_MASK : 0)); + } } else if (data == KEY_DELETE) { if (hMacroKey.size() > 0) { hMacroKey.pop_back(); @@ -1788,25 +1780,11 @@ void vKeyHandleEvent(const vKeyEvent& event, hExt = 1; //word break //check macro feature - bool canTriggerMacro = false; - if (data == KEY_SPACE && (vMacroTriggerMask & 0x01)) canTriggerMacro = true; - else if ((data == KEY_RETURN || data == KEY_ENTER) && (vMacroTriggerMask & 0x02)) canTriggerMacro = true; - else if (data == VK_LSHIFT && (vMacroTriggerMask & 0x04)) canTriggerMacro = true; - else if (data == VK_RSHIFT && (vMacroTriggerMask & 0x08)) canTriggerMacro = true; - else if (data == VK_LCONTROL && (vMacroTriggerMask & 0x10)) canTriggerMacro = true; - else if (data == VK_RCONTROL && (vMacroTriggerMask & 0x20)) canTriggerMacro = true; - else if (isMacroBreakCode(data)) canTriggerMacro = true; - - if (vUseMacro && canTriggerMacro && !_hasHandledMacro) { - vector macroKey = hMacroKey; - if (isMacroBreakCode(data) && canAppendCurrentKeyToMacro(data, _isCaps)) { - macroKey.push_back(data | (_isCaps ? CAPS_MASK : 0)); - } + if (vUseMacro && isMacroTriggerKey(data) && !_hasHandledMacro) { Byte macroBackspace = 0; - if (findMacroMatch(macroKey, hMacroData, macroBackspace)) { + if (findMacroMatch(hMacroKey, hMacroData, macroBackspace)) { hCode = vReplaceMaro; - hBPC = macroKey.size() == hMacroKey.size() ? macroBackspace : (macroBackspace > 0 ? macroBackspace - 1 : 0); - hExt = macroKey.size() == hMacroKey.size() ? hExt : 5; + hBPC = macroBackspace; _hasHandledMacro = true; } } else if ((vQuickStartConsonant || vQuickEndConsonant) && !tempDisableKey && isMacroBreakCode(data)) { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index 04263fff..b2449801 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -554,7 +554,7 @@ static void handleMacro() { SendCombineKey(KEY_LEFT_SHIFT, VK_INSERT, 0, KEYEVENTF_EXTENDEDKEY); // Wait for target application to process the paste message before restoring clipboard - Sleep(60); + Sleep(360); // Restore clipboard with dynamic retry to handle clipboard locks if (!backupText.empty()) { @@ -574,14 +574,6 @@ static void handleMacro() { } Sleep(10); } - } else { - // If clipboard was empty, clear it - int retry = 5; - while (retry-- > 0 && !OpenClipboard(NULL)) { - Sleep(10); - } - EmptyClipboard(); - CloseClipboard(); } if (pData->extCode != 5) { From 96fc0af767b6f19bc56cbb4dcba64b9c8f1670f4 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 18:08:16 +0700 Subject: [PATCH 47/60] Skip invalid macro entries on load --- Sources/OpenKey/engine/Macro.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Sources/OpenKey/engine/Macro.cpp b/Sources/OpenKey/engine/Macro.cpp index 5d179c8e..96d1eb53 100644 --- a/Sources/OpenKey/engine/Macro.cpp +++ b/Sources/OpenKey/engine/Macro.cpp @@ -242,17 +242,23 @@ void getAllMacro(vector>& keys, vector& macroTexts, vecto bool addMacro(const string& macroText, const string& macroContent) { vector key; - convert(macroText, key); string normalizedContent = normalizeNewlines(macroContent); + vector contentCode; + try { + convert(macroText, key); + convert(normalizedContent, contentCode); + } catch (...) { + return false; + } if (macroMap.find(key) == macroMap.end()) { //add new macro MacroData data; data.macroText = macroText; data.macroContent = normalizedContent; - convert(normalizedContent, data.macroContentCode); + data.macroContentCode = contentCode; macroMap[key] = data; } else { //edit this macro macroMap[key].macroContent = normalizedContent; - convert(normalizedContent, macroMap[key].macroContentCode); + macroMap[key].macroContentCode = contentCode; } return true; } @@ -443,8 +449,7 @@ static int importEvKeyLines(const vector& lines) { start_pos += 2; } - if (!name.empty()) { - addMacro(name, content); + if (!name.empty() && addMacro(name, content)) { importedCount++; } } @@ -492,8 +497,9 @@ static int readMacroFileCount(const string& path, const bool& append, const bool continue; } if (inBlock && lines[i] == "::end") { - addMacro(name, content); - importedCount++; + if (addMacro(name, content)) { + importedCount++; + } inBlock = false; continue; } From 73b82094eca8142ac448647494d6c35e2c7ce2b9 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 19:12:59 +0700 Subject: [PATCH 48/60] Consume macro trigger key --- Sources/OpenKey/engine/Engine.cpp | 4 +++- Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 227bab54..2ddcef81 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -1785,6 +1785,7 @@ void vKeyHandleEvent(const vKeyEvent& event, if (findMacroMatch(hMacroKey, hMacroData, macroBackspace)) { hCode = vReplaceMaro; hBPC = macroBackspace; + hExt = 5; _hasHandledMacro = true; } } else if ((vQuickStartConsonant || vQuickEndConsonant) && !tempDisableKey && isMacroBreakCode(data)) { @@ -1848,7 +1849,8 @@ void vKeyHandleEvent(const vKeyEvent& event, if (vUseMacro && (vMacroTriggerMask & 0x01) && !_hasHandledMacro && findMacroMatch(hMacroKey, hMacroData, macroBackspace)) { //macro hCode = vReplaceMaro; hBPC = macroBackspace; - _spaceCount++; + hExt = 5; + _spaceCount = 0; _hasHandledMacro = true; } else if ((vQuickStartConsonant || vQuickEndConsonant) && !tempDisableKey && checkQuickConsonant()) { _spaceCount++; diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index b2449801..0ce7a61d 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -711,6 +711,7 @@ LRESULT CALLBACK keyboardHookProcess(int nCode, WPARAM wParam, LPARAM lParam) { if (pData->code == vReplaceMaro) { //handle macro in english mode handleMacro(); + startNewSession(); return NULL; } } @@ -781,6 +782,7 @@ LRESULT CALLBACK keyboardHookProcess(int nCode, WPARAM wParam, LPARAM lParam) { } } else if (pData->code == vReplaceMaro) { //MACRO handleMacro(); + startNewSession(); } return -1; //consume event } From fd95b07b4a2dc66471c7fc9420dc07218c69a555 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 19:25:46 +0700 Subject: [PATCH 49/60] Reduce macro paste delay --- Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index 0ce7a61d..cbb29d5b 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -554,7 +554,7 @@ static void handleMacro() { SendCombineKey(KEY_LEFT_SHIFT, VK_INSERT, 0, KEYEVENTF_EXTENDEDKEY); // Wait for target application to process the paste message before restoring clipboard - Sleep(360); + Sleep(120); // Restore clipboard with dynamic retry to handle clipboard locks if (!backupText.empty()) { From 9bdf05ca25de984941356c8d198fc73b4ce33780 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 19:29:11 +0700 Subject: [PATCH 50/60] Add single trailing space for macro trigger --- Sources/OpenKey/engine/DataType.h | 1 + Sources/OpenKey/engine/Engine.cpp | 2 +- Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/OpenKey/engine/DataType.h b/Sources/OpenKey/engine/DataType.h index 3268e7b0..f0f59b4a 100644 --- a/Sources/OpenKey/engine/DataType.h +++ b/Sources/OpenKey/engine/DataType.h @@ -73,6 +73,7 @@ struct vKeyHookState { * 2: Delete key * 3: Normal key * 4: Should not send empty character + * 6: Macro triggered by Space, send one trailing space */ Byte extCode; diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 2ddcef81..4c54dd57 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -1849,7 +1849,7 @@ void vKeyHandleEvent(const vKeyEvent& event, if (vUseMacro && (vMacroTriggerMask & 0x01) && !_hasHandledMacro && findMacroMatch(hMacroKey, hMacroData, macroBackspace)) { //macro hCode = vReplaceMaro; hBPC = macroBackspace; - hExt = 5; + hExt = 6; _spaceCount = 0; _hasHandledMacro = true; } else if ((vQuickStartConsonant || vQuickEndConsonant) && !tempDisableKey && checkQuickConsonant()) { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index cbb29d5b..f2521cba 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -576,7 +576,9 @@ static void handleMacro() { } } - if (pData->extCode != 5) { + if (pData->extCode == 6) { + SendKeyCode(KEY_SPACE); + } else if (pData->extCode != 5) { SendKeyCode(_keycode | (_flag & MASK_SHIFT ? CAPS_MASK : 0)); } } From 6bb6df045497998c37cda8afb585b61fb4237457 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 20:26:08 +0700 Subject: [PATCH 51/60] ponyman: fix macro pasting old clipboard content by implementing verification loop and retrying write --- .../OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index f2521cba..4359a429 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -538,16 +538,22 @@ static void handleMacro() { wstring backupText = OpenKeyHelper::getClipboardText(CF_UNICODETEXT); // Send real data to clipboard - SendNewCharString(true); - - // Dynamic wait: Ensure the clipboard content has updated to the macro text before pasting - wstring macroString = (wchar_t*)_newCharString.data(); - for (int retry = 0; retry < 10; ++retry) { - wstring currentClip = OpenKeyHelper::getClipboardText(CF_UNICODETEXT); - if (currentClip == macroString) { - break; + int setRetry = 5; + while (setRetry-- > 0) { + SendNewCharString(true); + // Dynamic wait: Ensure the clipboard content has updated to the macro text before pasting + wstring macroString = (wchar_t*)_newCharString.data(); + bool match = false; + for (int retry = 0; retry < 15; ++retry) { + wstring currentClip = OpenKeyHelper::getClipboardText(CF_UNICODETEXT); + if (currentClip == macroString) { + match = true; + break; + } + Sleep(10); } - Sleep(5); + if (match) break; + Sleep(15); } // Send shift + insert to paste From 79053b12e83c3471c7402119faf622ec53891504 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 20:42:04 +0700 Subject: [PATCH 52/60] ponyman: disable clipboard set and paste in SendNewCharString during macro playback to avoid pasting old content --- Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp index 4359a429..fc4057ee 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -447,9 +447,8 @@ static void SendNewCharString(const bool& dataFromMacro = false) { } _newCharString[_j] = 0; - OpenKeyHelper::setClipboardText((LPCTSTR)_newCharString.data(), _j + 1, CF_UNICODETEXT); - if (!dataFromMacro) { + OpenKeyHelper::setClipboardText((LPCTSTR)_newCharString.data(), _j + 1, CF_UNICODETEXT); //Send shift + insert SendCombineKey(KEY_LEFT_SHIFT, VK_INSERT, 0, KEYEVENTF_EXTENDEDKEY); } From 786f91e45098342d73ed2d03962301bd7fd413d6 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 21:32:27 +0700 Subject: [PATCH 53/60] ponyman: fix macro pasting old clipboard and first typing activation --- Sources/OpenKey/engine/Engine.cpp | 1 + .../win32/OpenKey/OpenKey/OpenKeyHelper.cpp | 30 +++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 4c54dd57..58bf647d 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -698,6 +698,7 @@ void startNewSession() { _hasHandledMacro = false; _hasHandleQuickConsonant = false; _longWordHelper.clear(); + hMacroKey.clear(); } void checkCorrectVowel(vector>& charset, int& i, int& k, const Uint16& markKey) { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp index 38b6bed5..a45e69af 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp @@ -252,8 +252,17 @@ wstring OpenKeyHelper::getClipboardText(const int& type) { if (!IsClipboardFormatAvailable(type)) { return _T(""); } - // Try opening the clipboard - if (!OpenClipboard(nullptr)) { + // Try opening the clipboard with retry + int retry = 5; + bool opened = false; + while (retry-- > 0) { + if (OpenClipboard(nullptr)) { + opened = true; + break; + } + Sleep(10); + } + if (!opened) { return _T(""); } @@ -267,6 +276,7 @@ wstring OpenKeyHelper::getClipboardText(const int& type) { // Lock the handle to get the actual text pointer wchar_t * pszText = static_cast(GlobalLock(hData)); if (pszText == nullptr) { + CloseClipboard(); return _T(""); } @@ -284,12 +294,20 @@ wstring OpenKeyHelper::getClipboardText(const int& type) { void OpenKeyHelper::setClipboardText(LPCTSTR data, const int & len, const int& type) { HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, len * sizeof(WCHAR)); + if (!hMem) return; memcpy(GlobalLock(hMem), data, len * sizeof(WCHAR)); GlobalUnlock(hMem); - OpenClipboard(0); - EmptyClipboard(); - SetClipboardData(type, hMem); - CloseClipboard(); + int retry = 5; + while (retry-- > 0) { + if (OpenClipboard(0)) { + EmptyClipboard(); + SetClipboardData(type, hMem); + CloseClipboard(); + return; + } + Sleep(10); + } + GlobalFree(hMem); } bool OpenKeyHelper::quickConvert() { From 7826168f1a281a61d858d77d92607bc19ad37b0a Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sat, 15 Aug 2026 22:18:54 +0700 Subject: [PATCH 54/60] ponyman: normalize macro text to lowercase --- Sources/OpenKey/engine/Macro.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Sources/OpenKey/engine/Macro.cpp b/Sources/OpenKey/engine/Macro.cpp index 96d1eb53..9d0f1c44 100644 --- a/Sources/OpenKey/engine/Macro.cpp +++ b/Sources/OpenKey/engine/Macro.cpp @@ -17,6 +17,13 @@ using namespace std; +static string toLowercase(string str) { + for (char &c : str) { + c = tolower((unsigned char)c); + } + return str; +} + //main data map, MacroData> macroMap; @@ -124,12 +131,13 @@ void initMacroMap(const Byte* pData, const int& size) { string macroContent((char*)pData + cursor, macroContentSize); cursor += macroContentSize; + string lowerText = toLowercase(macroText); MacroData data; - data.macroText = macroText; + data.macroText = lowerText; data.macroContent = normalizeNewlines(macroContent); vector key; - convert(macroText, key); + convert(lowerText, key); convert(data.macroContent, data.macroContentCode); macroMap[key] = data; @@ -224,8 +232,9 @@ bool findMacro(vector& key, vector& macroContentCode) { } bool hasMacro(const string& macroName) { + string lowerName = toLowercase(macroName); vector key; - convert(macroName, key); + convert(lowerName, key); return (macroMap.find(key) != macroMap.end()); } @@ -241,18 +250,19 @@ void getAllMacro(vector>& keys, vector& macroTexts, vecto } bool addMacro(const string& macroText, const string& macroContent) { + string lowerText = toLowercase(macroText); vector key; string normalizedContent = normalizeNewlines(macroContent); vector contentCode; try { - convert(macroText, key); + convert(lowerText, key); convert(normalizedContent, contentCode); } catch (...) { return false; } if (macroMap.find(key) == macroMap.end()) { //add new macro MacroData data; - data.macroText = macroText; + data.macroText = lowerText; data.macroContent = normalizedContent; data.macroContentCode = contentCode; macroMap[key] = data; @@ -264,8 +274,9 @@ bool addMacro(const string& macroText, const string& macroContent) { } bool deleteMacro(const string& macroText) { + string lowerText = toLowercase(macroText); vector key; - convert(macroText, key); + convert(lowerText, key); if (macroMap.find(key) != macroMap.end()) { macroMap.erase(key); return true; From 325be4fcb1185eb74d46be5e373e0a3ce551d74c Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sun, 16 Aug 2026 07:18:36 +0700 Subject: [PATCH 55/60] ponyman: them dau tieng viet cho workflow.md --- workflow.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 workflow.md diff --git a/workflow.md b/workflow.md new file mode 100644 index 00000000..5c42a1e9 --- /dev/null +++ b/workflow.md @@ -0,0 +1,55 @@ +# Quy trình làm việc (Workflow) + +## Ngôn ngữ và cách làm việc + +- Luôn trả lời người dùng bằng tiếng Việt. +- Trước khi viết code, chỉnh sửa file, hoặc phân tích dự án, bắt buộc đọc kỹ toàn bộ file `workflow.md`. +- Luôn dùng skill `ponyman` khi code, kể cả khi người dùng không tag skill. +- Nếu người dùng chỉ hỏi ý kiến/phân tích, không tự ý sửa code. +- Khi người dùng chấp thuận sửa, làm thẳng vào hướng đã chốt, không lặp lại tư vấn dài dòng. + +## Nguyên tắc code + +- Ưu tiên YAGNI: không thêm abstraction, dependency, boilerplate nếu không cần. +- Dùng sẵn thư viện chuẩn, API native, hoặc dependency đã có trước khi viết code mới. +- Sửa tối thiểu, đúng vùng bị ảnh hưởng, giữ luồng hiện có. +- Không refactor rộng nếu không bắt buộc để fix lỗi. +- Không phá hành vi cũ khi sửa bug: giữ phím tắt, cấu hình, trạng thái người dùng, và các luồng xử lý hiện tại. +- Không chèn comment thừa. Chỉ thêm `ponytail:` comment khi có shortcut có chủ đích và nếu có nợ kỹ thuật thì nói rõ cách nâng cấp. +- Logic không tầm thường cần có một check nhỏ nhất có thể chạy được: test, assert demo, hoặc lệnh build bắt lỗi. + +## Tiêu chuẩn giao diện + +- Giữ đúng phong cách UI hiện có của dự án. +- Không thêm trang landing, hero, card trang trí, animation, hay thành phần UI mới nếu request không cần. +- Điều khiển phải rõ nghĩa, gọn, dễ scan; icon/button/checkbox/toggle/input dùng đúng vai trò. +- Không để text tràn, đè, hoặc che UI ở desktop/mobile. +- Không thêm palette màu mới nếu không cần; ưu tiên màu và spacing sẵn có. +- Thay đổi UI phải bảo toàn workflow cũ của người dùng. + +## Quy trình sau khi code + +- Chạy build/test đến khi không còn error và warning. +- Nếu build phát sinh lỗi/warning do thay đổi vừa làm, phải sửa tiếp đến sạch. +- Chỉ stage file đúng scope. +- Tự động chạy: + - `git add ` + - `git commit -m ""` + - `git push` +- Báo lại commit hash thật sau khi push. +- Nếu không thể build, commit, hoặc push, nói rõ lệnh đã chạy và lỗi chặn. + +## Git và an toàn file + +- Không revert thay đổi của người dùng nếu không được yêu cầu rõ. +- Không dùng lệnh phá hủy như `git reset --hard` hoặc `git checkout --` nếu người dùng chưa yêu cầu rõ. +- Nếu worktree có file lạ của người dùng và không liên quan, bỏ qua. +- Nếu file người dùng chỉnh sửa trùng vùng cần sửa, đọc kỹ và làm cùng thay đổi đó. + +## Bug gõ tiếng Việt cần chú ý + +- Kiểu gõ từ bình trần: `tr7n` phải ra chữ `trên` có dấu. +- Khi người dùng gõ số thuần như `667`, kết quả mong muốn là `67`, không được biến thành số 6 kèm chữ ê có dấu mũ. +- Khi người dùng gõ ký tự Shift như `Shift+998`, kết quả mong muốn là `(*`, không được biến thành dấu ngoặc kèm chữ Ô có dấu mũ. +- Sau các ký tự ký hiệu như `^`, `&`, `*`, `(`, engine không được tiếp tục ép rule tiếng Việt lên chuỗi số/ký hiệu. +- Hướng fix ưu tiên: chặn nhỏ ở tầng xử lý phím trước khi áp biến đổi tiếng Việt, nhận diện ngữ cảnh số/ký hiệu và trả phím gốc. From 2735be9c20ed6affbcd6dd0115dff7b0ec6e8fdb Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sun, 16 Aug 2026 07:28:23 +0700 Subject: [PATCH 56/60] ponyman: fix tilde macro shortcut and add 2-column sort in MacroDialog --- Sources/OpenKey/engine/platforms/win32.h | 6 +- .../win32/OpenKey/OpenKey/MacroDialog.cpp | 126 +++++++++++++----- .../win32/OpenKey/OpenKey/MacroDialog.h | 6 +- 3 files changed, 97 insertions(+), 41 deletions(-) diff --git a/Sources/OpenKey/engine/platforms/win32.h b/Sources/OpenKey/engine/platforms/win32.h index cabfabe8..9124e529 100644 --- a/Sources/OpenKey/engine/platforms/win32.h +++ b/Sources/OpenKey/engine/platforms/win32.h @@ -67,12 +67,12 @@ #define KEY_RIGHT_SHIFT 161 #define KEY_DOT VK_OEM_PERIOD -#define KEY_BACKQUOTE 220 +#define KEY_BACKQUOTE 192 #define KEY_MINUS 189 #define KEY_EQUALS 187 -#define KEY_BACK_SLASH 222 +#define KEY_BACK_SLASH 220 #define KEY_SEMICOLON 186 -#define KEY_QUOTE 192 +#define KEY_QUOTE 222 #define KEY_COMMA VK_OEM_COMMA #define KEY_SLASH 191 diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index f1c35fbd..642701b3 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -15,6 +15,7 @@ redistribute your new version, it MUST be open source. #include "stdafx.h" #include "AppDelegate.h" #include +#include #define MAX_MACRO_BUFFER 65536 @@ -165,7 +166,6 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara int width = LOWORD(lParam); int height = HIWORD(lParam); - // Get handles of the controls HWND hList = GetDlgItem(hDlg, IDC_LIST_MACRO_DATA); HWND hName = GetDlgItem(hDlg, IDC_EDIT_MACRO_NAME); HWND hContent = GetDlgItem(hDlg, IDC_EDIT_MACRO_CONTENT); @@ -179,54 +179,51 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara HWND hTriggerLabel = GetDlgItem(hDlg, IDC_LABEL_MACRO_TRIGGERS); HWND hTriggerCombo = GetDlgItem(hDlg, IDC_COMBO_MACRO_TRIGGERS); - // Layout constants based on original dialog (415 x 253 DLU ~ 622 x 380 pixels) - // We will adjust controls using SetWindowPos if (hName && hContent && hAdd && hDel && hDeleteAll && hList && hImport && hConvertEvKey && hExport && hCaps && hTriggerCombo) { - // Top controls: - // Name: x=10, y=28, w=110, h=22 SetWindowPos(hName, NULL, 10, 28, 110, 22, SWP_NOZORDER); - // Content: x=130, y=28, w=width - 275, h=80 int contentWidth = width - 275; if (contentWidth < 100) contentWidth = 100; SetWindowPos(hContent, NULL, 130, 28, contentWidth, 80, SWP_NOZORDER); - // Add Button: x=width - 135, y=28, w=125, h=22 SetWindowPos(hAdd, NULL, width - 135, 28, 125, 22, SWP_NOZORDER); - - // Delete Button: x=width - 135, y=54, w=125, h=22 SetWindowPos(hDel, NULL, width - 135, 54, 125, 22, SWP_NOZORDER); SetWindowPos(hDeleteAll, NULL, 10, 60, 110, 22, SWP_NOZORDER); - // List Macro: x=10, y=120, w=width - 20, h=height - 165 int listHeight = height - 165; if (listHeight < 50) listHeight = 50; SetWindowPos(hList, NULL, 10, 120, width - 20, listHeight, SWP_NOZORDER); - // Bottom controls: y = height - 35 int bottomY = height - 35; - // Import: x=10, w=100, h=22 SetWindowPos(hImport, NULL, 10, bottomY, 100, 22, SWP_NOZORDER); SetWindowPos(hExport, NULL, 120, bottomY, 100, 22, SWP_NOZORDER); SetWindowPos(hConvertEvKey, NULL, 230, bottomY, 125, 22, SWP_NOZORDER); SetWindowPos(hCaps, NULL, 365, bottomY, 130, 22, SWP_NOZORDER); - // Trigger Label: x=width - 250, w=60, h=22 if (hTriggerLabel) { SetWindowPos(hTriggerLabel, NULL, width - 260, bottomY + 3, 60, 22, SWP_NOZORDER); } - // Combo triggers: x=width - 195, w=185, h=22 (drop height is handled by OS) SetWindowPos(hTriggerCombo, NULL, width - 195, bottomY - 3, 185, 22, SWP_NOZORDER); } return TRUE; } case WM_NOTIFY: { - switch (((LPNMHDR)lParam)->code) { - case LVN_ITEMCHANGED: { - NMLISTVIEW* pData = (NMLISTVIEW*)lParam; - if (pData->uNewState != 0) - onSelectItem(pData->iItem); - } - break; + LPNMHDR pNmhdr = (LPNMHDR)lParam; + if (pNmhdr->idFrom == IDC_LIST_MACRO_DATA) { + if (pNmhdr->code == LVN_COLUMNCLICK) { + LPNMLISTVIEW pNmlv = (LPNMLISTVIEW)lParam; + int col = pNmlv->iSubItem; + if (col == sortColumn) { + sortAscending = !sortAscending; + } else { + sortColumn = col; + sortAscending = true; + } + fillData(); + } else if (pNmhdr->code == LVN_ITEMCHANGED) { + NMLISTVIEW* pData = (NMLISTVIEW*)lParam; + if (pData->uNewState & LVIS_SELECTED) + onSelectItem(pData->iItem); + } } break; } @@ -235,7 +232,6 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara } void MacroDialog::initDialog() { - //dialog icon SET_DIALOG_ICON(IDI_APP_ICON); listMacro = GetDlgItem(hDlg, IDC_LIST_MACRO_DATA); @@ -244,7 +240,6 @@ void MacroDialog::initDialog() { hAddButton = GetDlgItem(hDlg, IDC_BUTTON_ADD); hAutoCaps = GetDlgItem(hDlg, IDC_CHECK_AUTO_CAPS); - // Set EM_SETLIMITTEXT to 0 for unlimited character limit in content edit box if (hMacroContent) { SendMessage(hMacroContent, EM_SETLIMITTEXT, 0, 0); } @@ -263,38 +258,95 @@ void MacroDialog::initDialog() { LVCOLUMN LvCol; memset(&LvCol, 0, sizeof(LvCol)); LvCol.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; - LvCol.pszText = (LPWSTR)L"Macro Name"; + LvCol.pszText = (LPWSTR)L"Từ gõ tắt"; LvCol.cx = 165; - SendMessage(listMacro, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT); // Set style - SendMessage(listMacro, LVM_INSERTCOLUMN, 0, (LPARAM)&LvCol); //macro name column + SendMessage(listMacro, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT); + SendMessage(listMacro, LVM_INSERTCOLUMN, 0, (LPARAM)&LvCol); - LvCol.pszText = (LPWSTR)L"Macro Content"; + LvCol.pszText = (LPWSTR)L"Nội dung thay thế"; LvCol.cx = 695; - SendMessage(listMacro, LVM_INSERTCOLUMN, 1, (LPARAM)&LvCol); //macro content column + SendMessage(listMacro, LVM_INSERTCOLUMN, 1, (LPARAM)&LvCol); fillData(); } +void MacroDialog::updateColumnHeaders() { + LVCOLUMN LvCol; + memset(&LvCol, 0, sizeof(LvCol)); + LvCol.mask = LVCF_TEXT; + + wstring textHeader = L"Từ gõ tắt"; + if (sortColumn == 0) { + textHeader += sortAscending ? L" ▲" : L" ▼"; + } + LvCol.pszText = (LPWSTR)textHeader.c_str(); + SendMessage(listMacro, LVM_SETCOLUMN, 0, (LPARAM)&LvCol); + + wstring contentHeader = L"Nội dung thay thế"; + if (sortColumn == 1) { + contentHeader += sortAscending ? L" ▲" : L" ▼"; + } + LvCol.pszText = (LPWSTR)contentHeader.c_str(); + SendMessage(listMacro, LVM_SETCOLUMN, 1, (LPARAM)&LvCol); +} + +struct MacroItemSort { + string text; + string content; + wstring wideText; + wstring wideContent; +}; + void MacroDialog::fillData() { SendMessage(hAutoCaps, BM_SETCHECK, vAutoCapsMacro ? 1 : 0, 0); SendMessage(listMacro, LVM_DELETEALLITEMS, 0, 0); getAllMacro(keys, macroText, macroContent); + + vector items(macroText.size()); for (size_t i = 0; i < macroText.size(); i++) { - insertItem((int)i, (LPTSTR)utf8ToWideString(macroText[macroText.size() - 1 - i]).c_str(), - (LPTSTR)utf8ToWideString(macroContent[macroText.size() - 1 - i]).c_str()); + items[i].text = macroText[i]; + items[i].content = macroContent[i]; + items[i].wideText = utf8ToWideString(macroText[i]); + items[i].wideContent = utf8ToWideString(macroContent[i]); + } + + if (sortColumn == 0) { + std::sort(items.begin(), items.end(), [this](const MacroItemSort& a, const MacroItemSort& b) { + int cmp = _wcsicmp(a.wideText.c_str(), b.wideText.c_str()); + return sortAscending ? (cmp < 0) : (cmp > 0); + }); + } else if (sortColumn == 1) { + std::sort(items.begin(), items.end(), [this](const MacroItemSort& a, const MacroItemSort& b) { + int cmp = _wcsicmp(a.wideContent.c_str(), b.wideContent.c_str()); + return sortAscending ? (cmp < 0) : (cmp > 0); + }); } + + displayMacroText.clear(); + displayMacroContent.clear(); + displayMacroText.reserve(items.size()); + displayMacroContent.reserve(items.size()); + + for (size_t i = 0; i < items.size(); i++) { + size_t idx = (sortColumn == -1) ? (items.size() - 1 - i) : i; + displayMacroText.push_back(items[idx].text); + displayMacroContent.push_back(items[idx].content); + + insertItem((int)i, (LPTSTR)utf8ToWideString(items[idx].text).c_str(), + (LPTSTR)utf8ToWideString(items[idx].content).c_str()); + } + + updateColumnHeaders(); } void MacroDialog::saveAndReload() { - //save vector macroData; getMacroSaveData(macroData); OpenKeyHelper::setRegBinary(_T("macroData"), macroData.data(), (int)macroData.size()); saveToFile(wideStringToUtf8(getMacroTextPath())); - //reload data fillData(); } @@ -307,19 +359,19 @@ void MacroDialog::insertItem(const int& index, LPTSTR macroName, LPTSTR macroCon data.iSubItem = 0; data.pszText = macroName; - //insert macro name SendMessage(listMacro, LVM_INSERTITEM, 0, (LPARAM)&data); - //insert content data.iSubItem = 1; data.pszText = macroContent; SendMessage(listMacro, LVM_SETITEM, 0, (LPARAM)&data); } void MacroDialog::onSelectItem(const int & index) { - SetWindowText(hMacroName, utf8ToWideString(macroText[macroText.size() - 1 - index]).c_str()); - SetWindowText(hMacroContent, utf8ToWideString(macroContent[macroText.size() - 1 - index]).c_str()); - SetWindowText(hAddButton, BTN_UPDATE_TEXT); + if (index >= 0 && index < (int)displayMacroText.size()) { + SetWindowText(hMacroName, utf8ToWideString(displayMacroText[index]).c_str()); + SetWindowText(hMacroContent, utf8ToWideString(displayMacroContent[index]).c_str()); + SetWindowText(hAddButton, BTN_UPDATE_TEXT); + } } void MacroDialog::onAddMacroButton() { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h index 608eb8eb..3ef3bf09 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h @@ -22,6 +22,10 @@ class MacroDialog : public BaseDialog { vector> keys; vector macroText; vector macroContent; + vector displayMacroText; + vector displayMacroContent; + int sortColumn = -1; + bool sortAscending = true; public: MacroDialog(const HINSTANCE & hInstance, const int & resourceId); ~MacroDialog(); @@ -30,6 +34,7 @@ class MacroDialog : public BaseDialog { INT_PTR eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); void initDialog(); private: + void updateColumnHeaders(); void saveAndReload(); void insertItem(const int& index, LPTSTR macroName, LPTSTR macroContent); @@ -44,4 +49,3 @@ class MacroDialog : public BaseDialog { void onCheckboxClicked(const HWND& hWnd); }; - From 6258a29ac0c2b7c6c74e61f7e0b9f9dfa184247f Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sun, 16 Aug 2026 08:52:56 +0700 Subject: [PATCH 57/60] ponyman: add physical sort buttons for both columns in MacroDialog --- .../win32/OpenKey/OpenKey/MacroDialog.cpp | 57 ++++++++++++++++++- .../win32/OpenKey/OpenKey/MacroDialog.h | 4 ++ .../OpenKey/win32/OpenKey/OpenKey/resource.h | 2 + 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index 642701b3..73485534 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -122,6 +122,12 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara case IDC_BUTTON_DELETE_ALL_MACRO: onDeleteAllMacroButton(); break; + case IDC_BUTTON_SORT_NAME: + onSortNameButton(); + break; + case IDC_BUTTON_SORT_CONTENT: + onSortContentButton(); + break; case IDC_BUTTON_IMPORT_MACRO: onImportMacroButton(); break; @@ -188,7 +194,9 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara SetWindowPos(hAdd, NULL, width - 135, 28, 125, 22, SWP_NOZORDER); SetWindowPos(hDel, NULL, width - 135, 54, 125, 22, SWP_NOZORDER); - SetWindowPos(hDeleteAll, NULL, 10, 60, 110, 22, SWP_NOZORDER); + SetWindowPos(hDeleteAll, NULL, 10, 54, 110, 22, SWP_NOZORDER); + if (hSortName) SetWindowPos(hSortName, NULL, 10, 80, 110, 22, SWP_NOZORDER); + if (hSortContent) SetWindowPos(hSortContent, NULL, width - 135, 80, 125, 22, SWP_NOZORDER); int listHeight = height - 165; if (listHeight < 50) listHeight = 50; @@ -240,6 +248,11 @@ void MacroDialog::initDialog() { hAddButton = GetDlgItem(hDlg, IDC_BUTTON_ADD); hAutoCaps = GetDlgItem(hDlg, IDC_CHECK_AUTO_CAPS); + hSortName = CreateWindowEx(0, _T("BUTTON"), _T("Sort Từ gõ tắt ↕"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 0, 0, hDlg, (HMENU)IDC_BUTTON_SORT_NAME, hInstance, NULL); + hSortContent = CreateWindowEx(0, _T("BUTTON"), _T("Sort Nội dung ↕"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 0, 0, hDlg, (HMENU)IDC_BUTTON_SORT_CONTENT, hInstance, NULL); + if (hSortName) SendMessage(hSortName, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); + if (hSortContent) SendMessage(hSortContent, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); + if (hMacroContent) { SendMessage(hMacroContent, EM_SETLIMITTEXT, 0, 0); } @@ -291,6 +304,47 @@ void MacroDialog::updateColumnHeaders() { SendMessage(listMacro, LVM_SETCOLUMN, 1, (LPARAM)&LvCol); } +void MacroDialog::updateSortButtonTexts() { + if (hSortName) { + wstring txt = L"Sort Từ gõ tắt"; + if (sortColumn == 0) { + txt += sortAscending ? L" ▲" : L" ▼"; + } else { + txt += L" ↕"; + } + SetWindowText(hSortName, txt.c_str()); + } + if (hSortContent) { + wstring txt = L"Sort Nội dung"; + if (sortColumn == 1) { + txt += sortAscending ? L" ▲" : L" ▼"; + } else { + txt += L" ↕"; + } + SetWindowText(hSortContent, txt.c_str()); + } +} + +void MacroDialog::onSortNameButton() { + if (sortColumn == 0) { + sortAscending = !sortAscending; + } else { + sortColumn = 0; + sortAscending = true; + } + fillData(); +} + +void MacroDialog::onSortContentButton() { + if (sortColumn == 1) { + sortAscending = !sortAscending; + } else { + sortColumn = 1; + sortAscending = true; + } + fillData(); +} + struct MacroItemSort { string text; string content; @@ -338,6 +392,7 @@ void MacroDialog::fillData() { } updateColumnHeaders(); + updateSortButtonTexts(); } void MacroDialog::saveAndReload() { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h index 3ef3bf09..e0dd9789 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h @@ -19,6 +19,7 @@ class MacroDialog : public BaseDialog { HWND listMacro; HWND hMacroName, hMacroContent; HWND hAddButton, hAutoCaps; + HWND hSortName, hSortContent; vector> keys; vector macroText; vector macroContent; @@ -35,6 +36,7 @@ class MacroDialog : public BaseDialog { void initDialog(); private: void updateColumnHeaders(); + void updateSortButtonTexts(); void saveAndReload(); void insertItem(const int& index, LPTSTR macroName, LPTSTR macroContent); @@ -42,6 +44,8 @@ class MacroDialog : public BaseDialog { void onAddMacroButton(); void onDeleteMacroButton(); void onDeleteAllMacroButton(); + void onSortNameButton(); + void onSortContentButton(); void onImportMacroButton(); void onConvertEvKeyMacroButton(); diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h index d68a14d6..59c7c3b1 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/resource.h @@ -198,6 +198,8 @@ #define IDC_COMBO_MACRO_TRIGGERS 1085 #define IDC_BUTTON_CONVERT_EVKEY_MACRO 1086 #define IDC_BUTTON_DELETE_ALL_MACRO 1087 +#define IDC_BUTTON_SORT_NAME 1088 +#define IDC_BUTTON_SORT_CONTENT 1089 #define IDC_STATIC -1 #define IDC_LABEL_MACRO_TRIGGERS 1099 From cb842956601110488712fff7d66b30fc78218279 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sun, 16 Aug 2026 10:04:51 +0700 Subject: [PATCH 58/60] ponyman: increase MacroDialog initial height by 70px --- Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc index 26969ee5..0c10baa0 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc @@ -135,7 +135,7 @@ BEGIN CONTROL "",IDC_TAB_CONTROL,"SysTabControl32",TCS_FIXEDWIDTH,7,89,316,140 END -IDD_DIALOG_MACRO DIALOGEX 0, 0, 500, 253 +IDD_DIALOG_MACRO DIALOGEX 0, 0, 500, 323 STYLE DS_SETFONT | WS_THICKFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX CAPTION "Thiết lập gõ tắt - OpenKey" FONT 8, "MS Shell Dlg", 400, 0, 0x1 From 2b22d64dae93a0f73fbd4b80bacbd27fc09028d2 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sun, 16 Aug 2026 10:11:07 +0700 Subject: [PATCH 59/60] ponyman: embed sort buttons into dialog resource template and trigger WM_SIZE on init --- Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp | 10 ++++++---- Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index 73485534..48e4c327 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -248,10 +248,8 @@ void MacroDialog::initDialog() { hAddButton = GetDlgItem(hDlg, IDC_BUTTON_ADD); hAutoCaps = GetDlgItem(hDlg, IDC_CHECK_AUTO_CAPS); - hSortName = CreateWindowEx(0, _T("BUTTON"), _T("Sort Từ gõ tắt ↕"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 0, 0, hDlg, (HMENU)IDC_BUTTON_SORT_NAME, hInstance, NULL); - hSortContent = CreateWindowEx(0, _T("BUTTON"), _T("Sort Nội dung ↕"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 0, 0, hDlg, (HMENU)IDC_BUTTON_SORT_CONTENT, hInstance, NULL); - if (hSortName) SendMessage(hSortName, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); - if (hSortContent) SendMessage(hSortContent, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); + hSortName = GetDlgItem(hDlg, IDC_BUTTON_SORT_NAME); + hSortContent = GetDlgItem(hDlg, IDC_BUTTON_SORT_CONTENT); if (hMacroContent) { SendMessage(hMacroContent, EM_SETLIMITTEXT, 0, 0); @@ -282,6 +280,10 @@ void MacroDialog::initDialog() { SendMessage(listMacro, LVM_INSERTCOLUMN, 1, (LPARAM)&LvCol); fillData(); + + RECT rc; + GetClientRect(hDlg, &rc); + SendMessage(hDlg, WM_SIZE, SIZE_RESTORED, MAKELPARAM(rc.right - rc.left, rc.bottom - rc.top)); } void MacroDialog::updateColumnHeaders() { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc index 0c10baa0..82c33a33 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc @@ -135,7 +135,7 @@ BEGIN CONTROL "",IDC_TAB_CONTROL,"SysTabControl32",TCS_FIXEDWIDTH,7,89,316,140 END -IDD_DIALOG_MACRO DIALOGEX 0, 0, 500, 323 +IDD_DIALOG_MACRO DIALOGEX 0, 0, 500, 253 STYLE DS_SETFONT | WS_THICKFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX CAPTION "Thiết lập gõ tắt - OpenKey" FONT 8, "MS Shell Dlg", 400, 0, 0x1 @@ -147,7 +147,9 @@ BEGIN EDITTEXT IDC_EDIT_MACRO_CONTENT,82,18,318,61,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | ES_NOHIDESEL | WS_VSCROLL PUSHBUTTON "+ Thêm",IDC_BUTTON_ADD,405,18,88,14 PUSHBUTTON "- Xóa",IDC_BUTTON_DELETE,405,34,88,14 - PUSHBUTTON "Xóa toàn bộ",IDC_BUTTON_DELETE_ALL_MACRO,7,38,72,14 + PUSHBUTTON "Sort Nội dung ↕",IDC_BUTTON_SORT_CONTENT,405,50,88,14 + PUSHBUTTON "Xóa toàn bộ",IDC_BUTTON_DELETE_ALL_MACRO,7,34,72,14 + PUSHBUTTON "Sort Từ gõ tắt ↕",IDC_BUTTON_SORT_NAME,7,50,72,14 PUSHBUTTON "Nạp từ file...",IDC_BUTTON_IMPORT_MACRO,7,232,66,14 PUSHBUTTON "Xuất ra file...",IDC_BUTTON_EXPORT_MACRO,82,232,66,14 PUSHBUTTON "Convert EvKey...",IDC_BUTTON_CONVERT_EVKEY_MACRO,156,232,84,14 From 0f77478c8e8ebe6c4b6696965b279aad56466e67 Mon Sep 17 00:00:00 2001 From: GMTPC Date: Sun, 16 Aug 2026 10:26:24 +0700 Subject: [PATCH 60/60] ponyman: default alphabetical macro sort, auto jump on search, fix false macro matching --- Sources/OpenKey/engine/Engine.cpp | 11 ---- .../win32/OpenKey/OpenKey/MacroDialog.cpp | 53 ++++++++++++++++++- .../win32/OpenKey/OpenKey/MacroDialog.h | 2 +- 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/Sources/OpenKey/engine/Engine.cpp b/Sources/OpenKey/engine/Engine.cpp index 58bf647d..4eabb88c 100644 --- a/Sources/OpenKey/engine/Engine.cpp +++ b/Sources/OpenKey/engine/Engine.cpp @@ -203,17 +203,6 @@ static bool findMacroMatch(vector key, vector& macroData, Byte& backspaceCount = (Byte)key.size(); return true; } - - vector collapsedKey; - for (size_t index = 0; index < key.size(); index++) { - if (collapsedKey.empty() || collapsedKey.back() != key[index]) { - collapsedKey.push_back(key[index]); - } - } - if (collapsedKey.size() != key.size() && findMacro(collapsedKey, macroData)) { - backspaceCount = (Byte)collapsedKey.size(); - return true; - } return false; } diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index 48e4c327..9759e828 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp @@ -155,10 +155,59 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara TCHAR buffer[128]; GetWindowText(hMacroName, buffer, 128); wstring name = buffer; - if (hasMacro(wideStringToUtf8(name))) { + string u8Name = wideStringToUtf8(name); + + int matchIndex = -1; + if (!u8Name.empty()) { + string lowerName = u8Name; + for (char &c : lowerName) c = tolower((unsigned char)c); + + // 1. Exact match first + for (size_t i = 0; i < displayMacroText.size(); i++) { + string itemLower = displayMacroText[i]; + for (char &c : itemLower) c = tolower((unsigned char)c); + if (itemLower == lowerName) { + matchIndex = (int)i; + break; + } + } + // 2. Prefix match if no exact match + if (matchIndex == -1) { + for (size_t i = 0; i < displayMacroText.size(); i++) { + string itemLower = displayMacroText[i]; + for (char &c : itemLower) c = tolower((unsigned char)c); + if (itemLower.find(lowerName) == 0) { + matchIndex = (int)i; + break; + } + } + } + } + + if (matchIndex != -1) { + LVITEM lviClear; + memset(&lviClear, 0, sizeof(lviClear)); + lviClear.stateMask = LVIS_SELECTED | LVIS_FOCUSED; + lviClear.state = 0; + SendMessage(listMacro, LVM_SETITEMSTATE, (WPARAM)-1, (LPARAM)&lviClear); + + LVITEM lvi; + memset(&lvi, 0, sizeof(lvi)); + lvi.stateMask = LVIS_SELECTED | LVIS_FOCUSED; + lvi.state = LVIS_SELECTED | LVIS_FOCUSED; + SendMessage(listMacro, LVM_SETITEMSTATE, matchIndex, (LPARAM)&lvi); + SendMessage(listMacro, LVM_ENSUREVISIBLE, matchIndex, FALSE); + SetWindowText(hMacroContent, utf8ToWideString(displayMacroContent[matchIndex]).c_str()); SetWindowText(hAddButton, BTN_UPDATE_TEXT); } else { - SetWindowText(hAddButton, BTN_ADD_TEXT); + if (!u8Name.empty() && hasMacro(u8Name)) { + SetWindowText(hAddButton, BTN_UPDATE_TEXT); + } else { + SetWindowText(hAddButton, BTN_ADD_TEXT); + if (u8Name.empty()) { + SetWindowText(hMacroContent, _T("")); + } + } } } } else if (HIWORD(wParam) == BN_CLICKED) { diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h index e0dd9789..20889339 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h @@ -25,7 +25,7 @@ class MacroDialog : public BaseDialog { vector macroContent; vector displayMacroText; vector displayMacroContent; - int sortColumn = -1; + int sortColumn = 0; bool sortAscending = true; public: MacroDialog(const HINSTANCE & hInstance, const int & resourceId);