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/.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/engine/DataType.h b/Sources/OpenKey/engine/DataType.h index 1393e09f..f0f59b4a 100644 --- a/Sources/OpenKey/engine/DataType.h +++ b/Sources/OpenKey/engine/DataType.h @@ -34,7 +34,9 @@ enum vKeyInputType { vTelex = 0, vVNI, vSimpleTelex1, - vSimpleTelex2 + vSimpleTelex2, + vTuBinhTranDonGian, + vTuDinhNghia }; typedef unsigned char Byte; @@ -71,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; @@ -80,6 +83,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 @@ -131,6 +139,7 @@ struct vKeyHookState { //#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 || \ @@ -143,7 +152,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..4eabb88c 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, @@ -35,24 +42,32 @@ 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 + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} //Tu Dinh Nghia }; -#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 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 #define VEI vowelEndIndex @@ -68,6 +83,7 @@ static Uint16 ProcessingChar[][11] = { //Data to sendback to main program vKeyHookState HookState; +vector customRules; //private data /** @@ -122,6 +138,12 @@ 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(); +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) { @@ -162,6 +184,207 @@ 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; +} + +static bool findMacroMatch(vector key, vector& macroData, Byte& backspaceCount) { + if (findMacro(key, macroData)) { + backspaceCount = (Byte)key.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: + 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 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 && !isTBTConsonantContextAt(_index - 1) && !canApplyTBTStandaloneInVowel(data); +} + +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; + + 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 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 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)) + return false; + + hCode = vWillProcess; + hBPC = 1; + 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]; + saveWord(); + return true; +} + void setKeyData(const Byte& index, const Uint16& keyCode, const bool& isCaps) { if (index < 0 || index >= MAX_BUFF) return; @@ -184,16 +407,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; } @@ -217,11 +441,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; } @@ -239,13 +463,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; } @@ -346,7 +570,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 @@ -463,6 +687,7 @@ void startNewSession() { _hasHandledMacro = false; _hasHandleQuickConsonant = false; _longWordHelper.clear(); + hMacroKey.clear(); } void checkCorrectVowel(vector>& charset, int& i, int& k, const Uint16& markKey) { @@ -1039,6 +1264,48 @@ 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 (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; + hNCC = 1; + hExt = 4; + TypingWord[_index - 1] = data | (isCaps ? CAPS_MASK : 0); // No STANDALONE_MASK + hData[0] = GET(TypingWord[_index - 1]); + saveWord(); + return; + } + if (shouldKeepTBTStandaloneKeyRaw(data)) { + hCode = vDoNothing; + hBPC = 0; + hNCC = 0; + hExt = 3; + insertKey(data, isCaps); + 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; @@ -1062,13 +1329,166 @@ 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; + + 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) { + vowelCount = 0; + findAndCalculateVowel(); + 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; + } + 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) { insertCustomW(data, isCaps, false); return; } + if (action == 11) { insertCustomW(data, isCaps, false); 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) { + insertCustomW(data, isCaps, action == 16); + 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, KEY_O); + if (shouldKeepTBTStandaloneKeyRaw(data) && restoreTBTStandaloneThenInsertRaw(data, isCaps)) + return; + 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 ] + if (shouldKeepTBTStandaloneKeyRaw(data) && restoreTBTStandaloneThenInsertRaw(data, isCaps)) + return; + checkForStandaloneChar(data, isCaps, (vInputType == vTuBinhTranDonGian) ? KEY_O : KEY_U); return; } @@ -1151,7 +1571,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; @@ -1283,16 +1710,22 @@ 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 (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) { + Byte macroBackspace = 0; + if (isMacroTriggerKey(data) && !_hasHandledMacro && findMacroMatch(hMacroKey, hMacroData, macroBackspace)) { hCode = vReplaceMaro; - hBPC = (Byte)hMacroKey.size(); + 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(); @@ -1318,18 +1751,33 @@ void vKeyHandleEvent(const vKeyEvent& event, const bool& otherControlKey) { _isCaps = (capsStatus == 1 || //shift capsStatus == 2); //caps lock - if ((IS_NUMBER_KEY(data) && capsStatus == 1) - || otherControlKey || isWordBreak(event, state, data) || (_index == 0 && IS_NUMBER_KEY(data))) { + + bool isTriggerModifier = (data == VK_LSHIFT && (vMacroTriggerMask & 0x04)) || + (data == VK_RSHIFT && (vMacroTriggerMask & 0x08)) || + (data == VK_LCONTROL && (vMacroTriggerMask & 0x10)) || + (data == VK_RCONTROL && (vMacroTriggerMask & 0x20)); + + bool isTBTOrCustomNumberAction = (vInputType == vTuBinhTranDonGian || vInputType == vTuDinhNghia) && + IS_TBT_STANDALONE_KEY(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) { hCode = vDoNothing; hBPC = 0; hNCC = 0; hExt = 1; //word break //check macro feature - if (vUseMacro && isMacroBreakCode(data) && !_hasHandledMacro && findMacro(hMacroKey, hMacroData)) { - hCode = vReplaceMaro; - hBPC = (Byte)hMacroKey.size(); - _hasHandledMacro = true; + if (vUseMacro && isMacroTriggerKey(data) && !_hasHandledMacro) { + Byte macroBackspace = 0; + if (findMacroMatch(hMacroKey, hMacroData, macroBackspace)) { + hCode = vReplaceMaro; + hBPC = macroBackspace; + 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 @@ -1353,7 +1801,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) { @@ -1366,7 +1816,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(); @@ -1385,10 +1835,12 @@ void vKeyHandleEvent(const vKeyEvent& event, if (!tempDisableKey && vCheckSpelling) { checkSpelling(true); //force check spelling } - if (vUseMacro && !_hasHandledMacro && findMacro(hMacroKey, hMacroData)) { //macro + Byte macroBackspace = 0; + if (vUseMacro && (vMacroTriggerMask & 0x01) && !_hasHandledMacro && findMacroMatch(hMacroKey, hMacroData, macroBackspace)) { //macro hCode = vReplaceMaro; - hBPC = (Byte)hMacroKey.size(); - _spaceCount++; + hBPC = macroBackspace; + hExt = 6; + _spaceCount = 0; _hasHandledMacro = true; } else if ((vQuickStartConsonant || vQuickEndConsonant) && !tempDisableKey && checkQuickConsonant()) { _spaceCount++; @@ -1516,18 +1968,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) { @@ -1538,7 +1979,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_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) { _index--; saveWord(); diff --git a/Sources/OpenKey/engine/Engine.h b/Sources/OpenKey/engine/Engine.h index f288f8c5..84257c11 100644 --- a/Sources/OpenKey/engine/Engine.h +++ b/Sources/OpenKey/engine/Engine.h @@ -52,6 +52,8 @@ extern int vLanguage; * 1: VNI */ extern int vInputType; +extern vector customRules; +bool isSpecialKeyCustom(Uint16 keyCode); /** * 0: No @@ -113,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..9d0f1c44 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. // @@ -12,9 +13,17 @@ #include #include #include +#include using namespace std; +static string toLowercase(string str) { + for (char &c : str) { + c = tolower((unsigned char)c); + } + return str; +} + //main data map, MacroData> macroMap; @@ -34,6 +43,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()) { @@ -64,6 +80,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 @@ -97,13 +131,14 @@ 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.macroContent = macroContent; + data.macroText = lowerText; + data.macroContent = normalizeNewlines(macroContent); vector key; - convert(macroText, key); - convert(macroContent, data.macroContentCode); + convert(lowerText, key); + convert(data.macroContent, data.macroContentCode); macroMap[key] = data; } @@ -197,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()); } @@ -214,24 +250,33 @@ void getAllMacro(vector>& keys, vector& macroTexts, vecto } bool addMacro(const string& macroText, const string& macroContent) { + string lowerText = toLowercase(macroText); vector key; - convert(macroText, key); + string normalizedContent = normalizeNewlines(macroContent); + vector contentCode; + try { + convert(lowerText, key); + convert(normalizedContent, contentCode); + } catch (...) { + return false; + } if (macroMap.find(key) == macroMap.end()) { //add new macro MacroData data; - data.macroText = macroText; - data.macroContent = macroContent; - convert(macroContent, data.macroContentCode); + data.macroText = lowerText; + data.macroContent = normalizedContent; + data.macroContentCode = contentCode; macroMap[key] = data; } else { //edit this macro - macroMap[key].macroContent = macroContent; - convert(macroContent, macroMap[key].macroContentCode); + macroMap[key].macroContent = normalizedContent; + macroMap[key].macroContentCode = contentCode; } return true; } 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; @@ -239,6 +284,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); @@ -246,48 +295,250 @@ 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"; + 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 = ";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) { + string name = it->second.macroText; + string content = it->second.macroContent; + + // Normalize CRLF or lone CR to \n first + size_t start_pos = 0; + while ((start_pos = content.find("\r\n", start_pos)) != string::npos) { + content.replace(start_pos, 2, "\n"); + start_pos += 1; + } + start_pos = 0; + while ((start_pos = content.find("\r", start_pos)) != string::npos) { + content.replace(start_pos, 1, "\n"); + start_pos += 1; + } + + 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(); +} + +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; +} + +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; } -void readFromFile(const string& path, const bool& append) { - ifstream myfile(path.c_str()); +static void splitMacroLines(const string& fileData, vector& lines) { + lines.clear(); string line; - int k = 0; - size_t pos = 0; - string name, content; - if (myfile.is_open()) { + 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; + bool readOk = false; + string fileData = readMacroTextFile(path, readOk); + if (readOk) { if (!append) { + oldMacroMap = macroMap; macroMap.clear(); + clearedMacroMap = true; } - while (getline (myfile,line) ) { - 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); + + vector lines; + splitMacroLines(fileData, lines); + + bool hasBlockMacro = false; + if (!evKeyOnly) { + for (size_t i = 0; i < lines.size(); i++) { + string blockName; + if (readBlockMacroLine(lines[i], blockName)) { + hasBlockMacro = true; + break; } } } - myfile.close(); + + 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 (inBlock && lines[i] == "::end") { + if (addMacro(name, content)) { + importedCount++; + } + inBlock = false; + continue; + } + if (inBlock) { + if (!content.empty()) { + content += "\n"; + } + content += lines[i]; + } + } + } else { + importedCount = importEvKeyLines(lines); + } } + if (importedCount == 0 && clearedMacroMap) { + macroMap = oldMacroMap; + } + 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 d5767d35..654bec83 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,7 @@ 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); +int readEvKeyFromFileCount(const string& path, const bool& append=true); #endif /* Macro_h */ 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 a997544d..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; @@ -32,6 +33,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/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/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/AppDelegate.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/AppDelegate.cpp index 140d6e4a..e785d75b 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 @@ -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; @@ -158,6 +159,9 @@ void AppDelegate::closeDialog(BaseDialog * dialog) { } else if (convertDialog == dialog) { delete convertDialog; convertDialog = NULL; + } else if (customInputMethodDialog == dialog) { + delete customInputMethodDialog; + customInputMethodDialog = NULL; } } @@ -257,6 +261,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..373bffd9 --- /dev/null +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/CustomInputMethodDialog.cpp @@ -0,0 +1,425 @@ +/*---------------------------------------------------------- +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.empty()) { + for (const auto& pair : _characterMap) { + _keyCodeToChar[pair.second] = pair.first; + } + } + 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_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 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); + 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 - 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 - 27, rect.right - rect.left, rect.bottom - rect.top, TRUE); + } + break; + } + 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/MacroDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.cpp index 1cf89124..9759e828 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 @@ -15,8 +15,19 @@ redistribute your new version, it MUST be open source. #include "stdafx.h" #include "AppDelegate.h" #include +#include -#define MAX_MACRO_BUFFER 4096 +#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") @@ -33,6 +44,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) { @@ -46,22 +119,95 @@ 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_SORT_NAME: + onSortNameButton(); + break; + case IDC_BUTTON_SORT_CONTENT: + onSortContentButton(); + break; case IDC_BUTTON_IMPORT_MACRO: onImportMacroButton(); break; + case IDC_BUTTON_CONVERT_EVKEY_MACRO: + onConvertEvKeyMacroButton(); + break; 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) { 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) { @@ -71,14 +217,70 @@ INT_PTR MacroDialog::eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara } break; } - case WM_NOTIFY: { - switch (((LPNMHDR)lParam)->code) { - case LVN_ITEMCHANGED: { - NMLISTVIEW* pData = (NMLISTVIEW*)lParam; - if (pData->uNewState != 0) - onSelectItem(pData->iItem); + case WM_SIZE: { + int width = LOWORD(lParam); + int height = HIWORD(lParam); + + HWND hList = GetDlgItem(hDlg, IDC_LIST_MACRO_DATA); + HWND hName = GetDlgItem(hDlg, IDC_EDIT_MACRO_NAME); + 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); + HWND hCaps = GetDlgItem(hDlg, IDC_CHECK_AUTO_CAPS); + HWND hTriggerLabel = GetDlgItem(hDlg, IDC_LABEL_MACRO_TRIGGERS); + HWND hTriggerCombo = GetDlgItem(hDlg, IDC_COMBO_MACRO_TRIGGERS); + + if (hName && hContent && hAdd && hDel && hDeleteAll && hList && hImport && hConvertEvKey && hExport && hCaps && hTriggerCombo) { + SetWindowPos(hName, NULL, 10, 28, 110, 22, SWP_NOZORDER); + + int contentWidth = width - 275; + if (contentWidth < 100) contentWidth = 100; + SetWindowPos(hContent, NULL, 130, 28, contentWidth, 80, SWP_NOZORDER); + + SetWindowPos(hAdd, NULL, width - 135, 28, 125, 22, SWP_NOZORDER); + SetWindowPos(hDel, NULL, width - 135, 54, 125, 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; + SetWindowPos(hList, NULL, 10, 120, width - 20, listHeight, SWP_NOZORDER); + + int bottomY = height - 35; + 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); + if (hTriggerLabel) { + SetWindowPos(hTriggerLabel, NULL, width - 260, bottomY + 3, 60, 22, SWP_NOZORDER); + } + SetWindowPos(hTriggerCombo, NULL, width - 195, bottomY - 3, 185, 22, SWP_NOZORDER); } - break; + return TRUE; + } + case WM_NOTIFY: { + 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; } @@ -87,7 +289,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); @@ -96,39 +297,162 @@ void MacroDialog::initDialog() { hAddButton = GetDlgItem(hDlg, IDC_BUTTON_ADD); hAutoCaps = GetDlgItem(hDlg, IDC_CHECK_AUTO_CAPS); + hSortName = GetDlgItem(hDlg, IDC_BUTTON_SORT_NAME); + hSortContent = GetDlgItem(hDlg, IDC_BUTTON_SORT_CONTENT); + + 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")); + 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; - 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(); + + RECT rc; + GetClientRect(hDlg, &rc); + SendMessage(hDlg, WM_SIZE, SIZE_RESTORED, MAKELPARAM(rc.right - rc.left, rc.bottom - rc.top)); +} + +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); +} + +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; + 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(); + updateSortButtonTexts(); } void MacroDialog::saveAndReload() { - //save vector macroData; getMacroSaveData(macroData); OpenKeyHelper::setRegBinary(_T("macroData"), macroData.data(), (int)macroData.size()); - //reload data + saveToFile(wideStringToUtf8(getMacroTextPath())); + fillData(); } @@ -141,30 +465,35 @@ 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() { - TCHAR buffer[MAX_MACRO_BUFFER]; - GetWindowText(hMacroName, buffer, MAX_MACRO_BUFFER); - wstring name = buffer; + int nameLen = GetWindowTextLength(hMacroName); + int contentLen = GetWindowTextLength(hMacroContent); + + vector nameBuf(nameLen + 1); + vector contentBuf(contentLen + 1); - GetWindowText(hMacroContent, buffer, MAX_MACRO_BUFFER); - wstring content = buffer; + GetWindowText(hMacroName, nameBuf.data(), nameLen + 1); + wstring name = nameBuf.data(); - if (name.compare(L"") == 0 || content.compare(L"") == 0) { + 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; } @@ -177,9 +506,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); @@ -195,6 +525,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 }; @@ -203,7 +550,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("Text file (*.txt)\0*.txt\0Markdown file (*.md)\0*.md\0All (*.*)\0*.*\0"); ofn.nFilterIndex = 1; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = 0; @@ -218,14 +565,44 @@ 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); + } } } +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, + 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 (readEvKeyFromFileCount(wideStringToUtf8(path), msgboxID == IDYES) > 0) { + saveAndReload(); + } else { + 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' }; + 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; @@ -250,4 +627,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/MacroDialog.h b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h index 2a9cc0d9..20889339 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/MacroDialog.h @@ -19,9 +19,14 @@ class MacroDialog : public BaseDialog { HWND listMacro; HWND hMacroName, hMacroContent; HWND hAddButton, hAutoCaps; + HWND hSortName, hSortContent; vector> keys; vector macroText; vector macroContent; + vector displayMacroText; + vector displayMacroContent; + int sortColumn = 0; + bool sortAscending = true; public: MacroDialog(const HINSTANCE & hInstance, const int & resourceId); ~MacroDialog(); @@ -30,16 +35,21 @@ class MacroDialog : public BaseDialog { INT_PTR eventProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); void initDialog(); private: + void updateColumnHeaders(); + void updateSortButtonTexts(); void saveAndReload(); void insertItem(const int& index, LPTSTR macroName, LPTSTR macroContent); void onSelectItem(const int& index); void onAddMacroButton(); void onDeleteMacroButton(); + void onDeleteAllMacroButton(); + void onSortNameButton(); + void onSortContentButton(); void onImportMacroButton(); + void onConvertEvKeyMacroButton(); void onExportMacrobutton(); void onCheckboxClicked(const HWND& hWnd); }; - diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/MainControlDialog.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/MainControlDialog.cpp index eab2e7c2..aa869fcb 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 @@ -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; @@ -324,8 +328,12 @@ 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; + 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); @@ -387,7 +395,12 @@ 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; + 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..fc4057ee 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.cpp @@ -11,8 +11,10 @@ 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" +#include #pragma comment(lib, "imm32") #define IMC_GETOPENSTATUS 0x0005 @@ -25,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 @@ -67,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); @@ -92,6 +108,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,16 +169,34 @@ 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 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); + initMacroMap((Byte*)macroData, (int)macroDataSize); + saveToFile(wideStringToUtf8(macroPath)); + } //init and load smart switch key data DWORD smartSwitchKeySize; 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); @@ -272,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)); @@ -301,8 +336,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; @@ -313,26 +348,56 @@ 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) { + if (_newCharString.size() < _j + 2) { + _newCharString.resize(_j + 2); + } + _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() < _j + 3) { + _newCharString.resize(_j + 3); + } + _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 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++; } @@ -345,9 +410,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]; } @@ -359,6 +430,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; @@ -368,10 +442,16 @@ 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; - //Send shift + insert - SendCombineKey(KEY_LEFT_SHIFT, VK_INSERT, 0, KEYEVENTF_EXTENDEDKEY); + if (!dataFromMacro) { + OpenKeyHelper::setClipboardText((LPCTSTR)_newCharString.data(), _j + 1, CF_UNICODETEXT); + //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,... @@ -413,16 +493,29 @@ void switchLanguage() { startNewSession(); } -static void SendPureCharacter(const Uint16& ch) { - if (ch < 128) - SendKeyCode(ch); - else { - prepareUnicodeEvent(keyEvent[0], ch, true); - prepareUnicodeEvent(keyEvent[1], ch, false); +static void SendPureCharacter(const Uint32& ch) { + 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)(((pureChar - 0x10000) >> 10) + 0xD800); + Uint16 low = (Uint16)(((pureChar - 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); + } } } @@ -439,19 +532,60 @@ static void handleMacro() { SendBackspace(); } } - //send real data - if (!vSendKeyStepByStep) { + + // Backup clipboard + wstring backupText = OpenKeyHelper::getClipboardText(CF_UNICODETEXT); + + // Send real data to clipboard + int setRetry = 5; + while (setRetry-- > 0) { 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]); + // 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); + } + if (match) break; + Sleep(15); + } + + // 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(120); + + // Restore clipboard with dynamic retry to handle clipboard locks + if (!backupText.empty()) { + 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); } } - SendKeyCode(_keycode | (_flag & MASK_SHIFT ? CAPS_MASK : 0)); + + if (pData->extCode == 6) { + SendKeyCode(KEY_SPACE); + } else if (pData->extCode != 5) { + SendKeyCode(_keycode | (_flag & MASK_SHIFT ? CAPS_MASK : 0)); + } } static bool SetModifierMask(const Uint16& vkCode) { @@ -512,6 +646,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; @@ -571,6 +718,7 @@ LRESULT CALLBACK keyboardHookProcess(int nCode, WPARAM wParam, LPARAM lParam) { if (pData->code == vReplaceMaro) { //handle macro in english mode handleMacro(); + startNewSession(); return NULL; } } @@ -641,6 +789,7 @@ LRESULT CALLBACK keyboardHookProcess(int nCode, WPARAM wParam, LPARAM lParam) { } } else if (pData->code == vReplaceMaro) { //MACRO handleMacro(); + startNewSession(); } return -1; //consume event } diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc index 25cab5ac..82c33a33 100644 --- a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc +++ b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKey.rc @@ -108,17 +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,75,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - 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 @@ -129,27 +130,33 @@ 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 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +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,41,401,186 + 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,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,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 "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 - CONTROL "Tự động đổi chữ hoa theo phím tắt",IDC_CHECK_AUTO_CAPS, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,167,234,221,10 + 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 + 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 @@ -180,7 +187,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 | 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 + 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_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 + 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 @@ -312,7 +339,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/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/OpenKeyHelper.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyHelper.cpp index 13d6c005..a45e69af 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()); @@ -164,20 +249,34 @@ wstring OpenKeyHelper::getFullPath() { } wstring OpenKeyHelper::getClipboardText(const int& type) { - // Try opening the clipboard - if (!OpenClipboard(nullptr)) { + if (!IsClipboardFormatAvailable(type)) { + return _T(""); + } + // 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(""); } // 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(""); } @@ -195,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() { @@ -307,7 +414,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); @@ -326,4 +433,4 @@ wstring OpenKeyHelper::getContentOfUrl(LPCTSTR url){ } return L""; -} \ No newline at end of file +} diff --git a/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyManager.cpp b/Sources/OpenKey/win32/OpenKey/OpenKey/OpenKeyManager.cpp index ea1181b4..8e057f1a 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,8 @@ static vector _inputType = { _T("Telex"), _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 55d786e9..840ce49c 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 @@ -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 @@ -25,6 +26,8 @@ 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_TU_DINH_NGHIA 914 #define POPUP_UNICODE 930 #define POPUP_TCVN3 931 @@ -60,6 +63,8 @@ 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_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")}, @@ -134,6 +139,12 @@ 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_TU_DINH_NGHIA: + AppDelegate::getInstance()->onInputType(vTuDinhNghia); + break; case POPUP_UNICODE: AppDelegate::getInstance()->onTableCode(0); break; @@ -216,6 +227,8 @@ 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_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); @@ -263,6 +276,8 @@ 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_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..59c7c3b1 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,8 +192,18 @@ #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_GRP_DEFINE_KEY 1080 +#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_BUTTON_SORT_NAME 1088 +#define IDC_BUTTON_SORT_CONTENT 1089 #define IDC_STATIC -1 +#define IDC_LABEL_MACRO_TRIGGERS 1099 + // Next default values for new objects // #ifdef APSTUDIO_INVOKED 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; 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 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.