diff --git a/binding.gyp b/binding.gyp index 212fb36..c78ee13 100644 --- a/binding.gyp +++ b/binding.gyp @@ -11,7 +11,8 @@ "src/HotkeysMac.mm", "src/AccessibilityMac.mm", "src/AddonMac.mm", - "src/main.cpp" + "src/main.cpp", + "src/KeyMapping.cpp" ], 'configurations': { 'Debug': { @@ -42,7 +43,8 @@ "sources": [ "src/main.cpp", "src/Hotkeys.cpp", - "src/HotkeyManager.cpp" + "src/HotkeyManager.cpp", + "src/KeyMapping.cpp" ] }], ["OS=='linux'", { diff --git a/index.js b/index.js index 226ab29..f2b1e91 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ class ShortcutHelper { constructor() { + console.log('\r\n(DHK) init ShortcutHelper'); const binary = require('@mapbox/node-pre-gyp'); const path = require('path'); const binding_path = binary.find(path.resolve(path.join(__dirname, './package.json'))); @@ -89,6 +90,14 @@ class ShortcutHelper { setHotkeysEnabled(enable) { this.impl.setHotkeysEnabled(enable); } + + convertHotkeysCodes(keyCodes, keysAreVKC) { + return this.impl.convertHotkeysCodes(keyCodes, keysAreVKC); + } + + checkHotkeyConflicts(excludeId, keyCodes) { + return this.impl.checkHotkeyConflicts(excludeId, keyCodes); + } } var shortcutHelper = new ShortcutHelper(); diff --git a/package-lock.json b/package-lock.json index 2b77700..5a568ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,22 @@ { "name": "desktop-hotkeys", - "version": "1.5.13", + "version": "1.9.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "desktop-hotkeys", - "version": "1.5.13", + "version": "1.9.2", "hasInstallScript": true, "license": "ISC", "dependencies": { "@mapbox/node-pre-gyp": "1.0.10", - "node-addon-api": "5.0.0", + "node-addon-api": "6.0.0", "node-gyp": "9.3.1" }, "devDependencies": { - "aws-sdk": "2.1291.0", - "np": "7.6.3" + "aws-sdk": "2.1353.0", + "np": "7.7.0" } }, "node_modules/@babel/code-frame": { @@ -446,9 +446,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1291.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1291.0.tgz", - "integrity": "sha512-iM82Md2Wb29MZ72BpNF4YeIHtkJTyw+JMGXJG48Dwois2Rq7rLcriX6NN/4Fx4b5EEtUkwAO/wJc+NTHI7QeJw==", + "version": "2.1353.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1353.0.tgz", + "integrity": "sha512-IEzWRxeecNYwUEvHVvYlE9Q85VF1uytzL0SeZp67xl9BQHAhwPVMZyTqqxUq3z4A/8iDUteZe/o0DIJhttoeTg==", "dev": true, "dependencies": { "buffer": "4.9.2", @@ -3451,9 +3451,9 @@ } }, "node_modules/node-addon-api": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", - "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.0.0.tgz", + "integrity": "sha512-GyHvgPvUXBvAkXa0YvYnhilSB1A+FRYMpIVggKzPZqdaZfevZOuzfWzyvgzOwRLHBeo/MMswmJFsrNF4Nw1pmA==" }, "node_modules/node-fetch": { "version": "2.6.7", @@ -3649,9 +3649,9 @@ } }, "node_modules/np": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/np/-/np-7.6.3.tgz", - "integrity": "sha512-GTFNvIhu/cZqzUhil/AlISCiipYeAUVx0JtyjtmumFJvWogSewUSAshFi5MSMc6BOj9C98s8NAFZiqlHb2wQPQ==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/np/-/np-7.7.0.tgz", + "integrity": "sha512-G4HfO6JUl7iKOX1qfYHM/kG5ApqqZ4ma8YjtVAJoyS5VdKkGE/OdSG3cOE9Lwr71klNz9n6KIZmPRnh0L7qM1Q==", "dev": true, "dependencies": { "@samverschueren/stream-to-observable": "^0.3.1", @@ -6230,9 +6230,9 @@ "dev": true }, "aws-sdk": { - "version": "2.1291.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1291.0.tgz", - "integrity": "sha512-iM82Md2Wb29MZ72BpNF4YeIHtkJTyw+JMGXJG48Dwois2Rq7rLcriX6NN/4Fx4b5EEtUkwAO/wJc+NTHI7QeJw==", + "version": "2.1353.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1353.0.tgz", + "integrity": "sha512-IEzWRxeecNYwUEvHVvYlE9Q85VF1uytzL0SeZp67xl9BQHAhwPVMZyTqqxUq3z4A/8iDUteZe/o0DIJhttoeTg==", "dev": true, "requires": { "buffer": "4.9.2", @@ -8469,9 +8469,9 @@ } }, "node-addon-api": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz", - "integrity": "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.0.0.tgz", + "integrity": "sha512-GyHvgPvUXBvAkXa0YvYnhilSB1A+FRYMpIVggKzPZqdaZfevZOuzfWzyvgzOwRLHBeo/MMswmJFsrNF4Nw1pmA==" }, "node-fetch": { "version": "2.6.7", @@ -8609,9 +8609,9 @@ "dev": true }, "np": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/np/-/np-7.6.3.tgz", - "integrity": "sha512-GTFNvIhu/cZqzUhil/AlISCiipYeAUVx0JtyjtmumFJvWogSewUSAshFi5MSMc6BOj9C98s8NAFZiqlHb2wQPQ==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/np/-/np-7.7.0.tgz", + "integrity": "sha512-G4HfO6JUl7iKOX1qfYHM/kG5ApqqZ4ma8YjtVAJoyS5VdKkGE/OdSG3cOE9Lwr71klNz9n6KIZmPRnh0L7qM1Q==", "dev": true, "requires": { "@samverschueren/stream-to-observable": "^0.3.1", diff --git a/package.json b/package.json index cc5d38a..c7cd052 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "desktop-hotkeys", - "version": "1.5.13", + "version": "1.9.2", "description": "This package provides press/release callbacks for system-wide hotkeys on Windows and Mac", "main": "index.js", "gypfile": true, @@ -8,8 +8,9 @@ "install": "node-pre-gyp install --fallback-to-build", "make": "node-pre-gyp rebuild --build-from-source package publish", "make32": "node-pre-gyp rebuild --build-from-source --verbose --target_arch=ia32 package publish", - "make64": "node-pre-gyp rebuild --build-from-source --verbose --target_arch=x64 package publish", + "make64": "node-pre-gyp rebuild --build-from-source --verbose --target_arch=x64 package unpublish publish", "makeM1": "node-pre-gyp rebuild --build-from-source --verbose --target_arch=arm64 package publish", + "makeM11": "node-pre-gyp rebuild --build-from-source --verbose --target_arch=arm64", "maked": "node-pre-gyp rebuild --build-from-source --debug --verbose", "test": "echo \"Test not applicable\"" }, @@ -40,8 +41,8 @@ }, "homepage": "https://github.com/zelloptt/desktop-hotkeys#readme", "devDependencies": { - "aws-sdk": "2.1291.0", - "np": "7.6.3" + "aws-sdk": "2.1353.0", + "np": "7.7.0" }, "np": { "yarn": false, @@ -49,7 +50,7 @@ }, "dependencies": { "@mapbox/node-pre-gyp": "1.0.10", - "node-addon-api": "5.0.0", + "node-addon-api": "6.0.0", "node-gyp": "9.3.1" } } diff --git a/sample/package-lock.json b/sample/package-lock.json index ecc1abc..02ab3e1 100644 --- a/sample/package-lock.json +++ b/sample/package-lock.json @@ -13,18 +13,18 @@ } }, "..": { - "version": "1.5.10", + "version": "2.0.0", "integrity": "sha512-7AnRiFnNhbgTe4pWHR3Nmp3/LL/fRoamhRBkQ8Yt//mgkVH3OJArqEhnxqSeQfIbUe5sQ55xwv6af413kBtLzA==", "hasInstallScript": true, "license": "ISC", "dependencies": { - "@mapbox/node-pre-gyp": "1.0.9", - "node-addon-api": "5.0.0", - "node-gyp": "9.0.0" + "@mapbox/node-pre-gyp": "1.0.10", + "node-addon-api": "6.0.0", + "node-gyp": "9.3.1" }, "devDependencies": { - "aws-sdk": "2.1143.0", - "np": "7.6.1" + "aws-sdk": "2.1291.0", + "np": "7.6.3" } }, "../node_modules/abbrev": { @@ -920,11 +920,11 @@ "desktop-hotkeys": { "version": "file:..", "requires": { - "@mapbox/node-pre-gyp": "1.0.9", - "aws-sdk": "2.1143.0", - "node-addon-api": "5.0.0", - "node-gyp": "9.0.0", - "np": "7.6.1" + "@mapbox/node-pre-gyp": "1.0.10", + "aws-sdk": "2.1291.0", + "node-addon-api": "6.0.0", + "node-gyp": "9.3.1", + "np": "7.6.3" }, "dependencies": { "abbrev": { diff --git a/sample/sample.js b/sample/sample.js index d2eccc7..d928bf4 100644 --- a/sample/sample.js +++ b/sample/sample.js @@ -16,7 +16,7 @@ function fnReleased2() { console.log('Hotkey#2 released'); } -console.log("desktop-hotkeys module started: " + dh.start(true)); +console.log('desktop-hotkeys module started: ' + dh.start(true)); function logCb(text) { console.log('(PTT) Callback: ' + text); @@ -31,11 +31,11 @@ const F6 = F1 + 5; const F7 = F6 + 1; const useVKC = isWindows; try { - // dh.setLoggerCb(logCb); + dh.setLoggerCb(logCb); dh.setupAccessibilityCallback(true, (granted) => { // it has been reported that actual permission status might be applied // only after a short period of time - log.info('(PTT) Accessibility permission has changed to ' + granted); + console.log('(PTT) Accessibility permission has changed to ' + granted); }); const hk1 = dh.registerShortcut([CTRL, ALT, F1], fnPressed, fnReleased, useVKC); @@ -56,8 +56,16 @@ try { } catch (ex) { console.log('exception ' + ex); } + +const keyCodes = [17, 18, 112]; +console.log('Test codes are: ' + keyCodes.toString()); +const macCodes = dh.convertHotkeysCodes(keyCodes, true); +console.log('Mac codes are: ' + macCodes.toString()); +const winCodes = dh.convertHotkeysCodes(macCodes, false); +console.log('Mac codes are: ' + winCodes.toString()); + console.log('waiting for hotkeys...'); -setTimeout(() => { +/*setTimeout(() => { console.log("Disable hotkeys."); dh.setHotkeysEnabled(false); setTimeout(() => { @@ -65,4 +73,4 @@ setTimeout(() => { dh.setHotkeysEnabled(true); }, "10000"); -}, "10000"); +}, "10000");*/ diff --git a/src/AddonMac.mm b/src/AddonMac.mm index 28bc185..1e38f12 100644 --- a/src/AddonMac.mm +++ b/src/AddonMac.mm @@ -13,6 +13,8 @@ exports.Set("registerShortcut", Napi::Function::New(env, HotKeys::registerShortcut)); exports.Set("unregisterShortcut", Napi::Function::New(env, HotKeys::unregisterShortcut)); exports.Set("unregisterAllShortcuts", Napi::Function::New(env, HotKeys::unregisterAllShortcuts)); + exports.Set("convertHotkeysCodes", Napi::Function::New(env, HotKeys::convertHotkeysCodes)); + exports.Set("checkHotkeyConflicts", Napi::Function::New(env, HotKeys::checkHotkeyConflicts)); exports.Set("macCheckAccessibilityGranted", Napi::Function::New(env, HotKeys::macCheckAccessibilityGranted)); exports.Set("macShowAccessibilitySettings", Napi::Function::New(env, HotKeys::macShowAccessibilitySettings)); exports.Set("macSubscribeAccessibilityUpdates", Napi::Function::New(env, HotKeys::macSubscribeAccessibilityUpdates)); diff --git a/src/HotkeyManager.cpp b/src/HotkeyManager.cpp index 263ab2c..4db1a91 100644 --- a/src/HotkeyManager.cpp +++ b/src/HotkeyManager.cpp @@ -68,13 +68,27 @@ bool HotKeyManager::Valid() const return _uThreadId != 0 && _hWnd != NULL; } -void HotKeyManager::NotifyHotKeyEvent(unsigned uCode, bool bPressed) +bool HotKeyManager::NotifyHotKeyEvent(unsigned uCode, bool bPressed) { + auto callback = []( Napi::Env env, Napi::Function jsCallback, int* value ) { + // Transform native data into JS data, passing it to the provided + // `jsCallback` -- the TSFN's JavaScript function. + jsCallback.Call( {Napi::Number::New( env, *value )} ); + delete value; + }; + + if (this->_DisabledState) { + return false; + } TCONT::iterator cit = _hotkeys.find(uCode); if (cit != _hotkeys.end()) { - Napi::ThreadSafeFunction& tsfn = bPressed ? cit->second.first : cit->second.second; - tsfn.NonBlockingCall(); + const Napi::ThreadSafeFunction& tsfn = bPressed ? cit->second.first : cit->second.second; + int* pCode = new int; + *pCode = uCode; + tsfn.NonBlockingCall(pCode, callback); + return true; } + return false; } void HotKeyManager::UpdateCallbacks(unsigned uCode, bool bSetInUse) @@ -91,17 +105,59 @@ void HotKeyManager::UpdateCallbacks(unsigned uCode, bool bSetInUse) } } +std::string HotKeyManager::GenerateAtomName(WPARAM wKeys) +{ + std::string sAtomName("DesktopHotkey#"); + char buf[32] = {0}; + sAtomName.append(itoa(wKeys, buf, 16)); + return sAtomName; +} + +DWORD HotKeyManager::checkShortcut(DWORD dwExcludeShortcutId, WORD wKeyCode, WORD wMod, bool fullCheck) +{ + if (!Valid()) { + return 0; // unable to verify! + } + WPARAM wParam = MAKEWPARAM(wKeyCode, wMod); + // check if hotkey already registered + for (std::map::const_iterator it = _hotkeyIds.begin(); it != _hotkeyIds.end(); it ++) { + if (it->second == wParam) { + return it->first == dwExcludeShortcutId ? 0 : it->first; + } + } + if (!fullCheck) { + return 0; + } + // existing hotkey not found, try to register new hotkey in order to check for other conflicts + std::string sName = HotKeyManager::GenerateAtomName(wParam); + ATOM atm = GlobalFindAtomA(sName.c_str()); + if (atm != 0) { + return 0xFFFFFFFF; // conflict! + } + atm = GlobalAddAtomA(sName.c_str()); + if (atm == 0) { + return 0xFFFFFFFF; // unable to generate unique key id + } + bool hotKeyRegistered = 0 != SendMessage(_hWnd, WM_REGISTER_HOTKEY, wParam, atm); + GlobalDeleteAtom(atm); + if (!hotKeyRegistered) { + return 0xFFFFFFFF; // unable to register such a hotkey + } + SendMessage(_hWnd, WM_UNREGISTER_HOTKEY, atm, 0); + return 0; +} + DWORD HotKeyManager::registerShortcut(WORD wKeyCode, WORD wMod, const Napi::ThreadSafeFunction& tsfPress, const Napi::ThreadSafeFunction& tsfRelease) { DWORD dwId = 0; if (!Valid()) { return 0; } + if (0 != this->checkShortcut(0, wKeyCode, wMod, false)) { + return 0; + } WPARAM wParam = MAKEWPARAM(wKeyCode, wMod); - std::string s("DesktopHotkey#"); - char buf[32] = {0}; - s.append(itoa(wParam, buf, 16)); - ATOM atm = GlobalAddAtomA(s.c_str()); + ATOM atm = GlobalAddAtomA(HotKeyManager::GenerateAtomName(wParam).c_str()); if (atm) { _hotkeys[atm] = std::make_pair(tsfPress, tsfRelease); if (0 != SendMessage(_hWnd, WM_REGISTER_HOTKEY, wParam, atm)) { @@ -198,9 +254,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) unsigned uKeyCode = wParam; pushedKey = HIWORD(lParam); pushedCode = wParam; - g_pHotKeyManager->NotifyHotKeyEvent(wParam, true); - timerId = SetTimer(hWnd, wParam, 100, NULL); - log("(DHK): new evt%d activated\r\n", timerId); + if (g_pHotKeyManager->NotifyHotKeyEvent(wParam, true)) { + timerId = SetTimer(hWnd, wParam, 100, NULL); + log("(DHK): new evt%d activated\r\n", timerId); + } break; } case WM_TIMER: diff --git a/src/HotkeyManager.h b/src/HotkeyManager.h index 298a0c4..61a13f4 100644 --- a/src/HotkeyManager.h +++ b/src/HotkeyManager.h @@ -19,10 +19,12 @@ class HotKeyManager HotKeyManager(); ~HotKeyManager(); bool Valid() const; - void NotifyHotKeyEvent(unsigned uCode, bool bPressed); + bool NotifyHotKeyEvent(unsigned uCode, bool bPressed); void UpdateCallbacks(unsigned uCode, bool bSetInUse); + DWORD checkShortcut(DWORD dwExcludeShortcutId, WORD wKeyCode, WORD wMod, bool fullCheck); DWORD registerShortcut(WORD wKeyCode, WORD wMod, const Napi::ThreadSafeFunction& tsfPress, const Napi::ThreadSafeFunction& tsfRelease); DWORD unregisterShortcut(DWORD dwId); DWORD unregisterAllShortcuts(); void DisableAllShortcuts(bool bDisable); + static std::string GenerateAtomName(WPARAM wKeys); }; diff --git a/src/Hotkeys.cpp b/src/Hotkeys.cpp index 5d97d86..8b988cd 100644 --- a/src/Hotkeys.cpp +++ b/src/Hotkeys.cpp @@ -9,6 +9,52 @@ extern bool g_bVerboseMode; bool log(const char* format, ...); +bool combineKeyCodes(const Napi::Array& arrKeys, bool keysAreVirtualCodes, WORD& wKeyCode, WORD& wMod) +{ + wKeyCode = wMod = 0; + for (size_t idx = 0; idx < arrKeys.Length(); ++idx) { + Napi::Value v = arrKeys[idx]; + DWORD dwCode = v.As().Uint32Value(); + DWORD dw = keysAreVirtualCodes ? dwCode : MapVirtualKey(dwCode, MAPVK_VSC_TO_VK); + switch (dw) { + case 0: + { + char szErrBuf[64]; + if (keysAreVirtualCodes) { + sprintf(szErrBuf, "invalid arguments: virtual key code cannot be 0"); + } else { + sprintf(szErrBuf, "Can't convert scancode %d(%X) to VKCode", dwCode, dwCode); + } + log(szErrBuf); + return false; + } + break; + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + wMod = wMod | MOD_CONTROL; + break; + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + wMod = wMod | MOD_SHIFT; + break; + case VK_MENU: + case VK_LMENU: + case VK_RMENU: + wMod = wMod | MOD_ALT; + break; + case VK_LWIN: + case VK_RWIN: + wMod = wMod | MOD_WIN; + break; + default: + wKeyCode = static_cast(dw); + } + } + return true; +} + Napi::Number HotKeys::start(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); @@ -209,6 +255,76 @@ Napi::Number HotKeys::setHotkeysEnabled(const Napi::CallbackInfo& info) return Napi::Number::New(env, 0); } +Napi::Number HotKeys::checkHotkeyConflicts(const Napi::CallbackInfo& info) +{ + Napi::Env env = info.Env(); + unsigned argCount = info.Length(); + while (argCount > 0) { + if (info[argCount - 1].IsEmpty() || info[argCount - 1].IsUndefined() || info[argCount - 1].IsNull()) { + argCount = argCount - 1; + } else { + break; + } + } + unsigned uExcludeHotkeyId = 0; + unsigned retValue = 0; + Napi::Array arrKeys; + if (argCount >= 1 && info[0].IsNumber()) { + uExcludeHotkeyId = info[0].As().Uint32Value(); + } + if (argCount >= 2 && info[1].IsArray()) { + arrKeys = info[1].As(); + } else { + log("(DHK): invalid checkHotkeyConflicts arguments: expected number + array"); + Napi::TypeError::New(env, "invalid checkHotkeyConflicts arguments: expected number + array").ThrowAsJavaScriptException(); + return Napi::Number::New(env, 0); + } + if (arrKeys.Length() == 0) { + log("(DHK): checkHotkeyConflicts received empty array of keyCodes; early return"); + return Napi::Number::New(env, 0); + } + WORD wKeyCode = 0, wMod = 0; + bool keysAreVirtualCodes = true; + if (argCount > 2 && info[2].IsBoolean()) { + keysAreVirtualCodes = info[2].As(); + } + combineKeyCodes(arrKeys, keysAreVirtualCodes, wKeyCode, wMod); + if (g_pHotKeyManager && g_pHotKeyManager->Valid()) { + retValue = g_pHotKeyManager->checkShortcut(uExcludeHotkeyId, wKeyCode, wMod, true); + } + return Napi::Number::New(env, retValue); +} + +unsigned keycode_convert(unsigned code, bool toWinVK); + +Napi::Array HotKeys::convertHotkeysCodes(const Napi::CallbackInfo& info) +{ + Napi::Env env = info.Env(); + unsigned argCount = info.Length(); + Napi::Array arrKeys; + bool keysAreVirtualCodes = true; + if (argCount > 0 && info[0].IsArray()) {// not used in mac: info[1].IsBoolean()) { + arrKeys = info[0].As(); + if (argCount > 1 && info[1].IsBoolean()) { + keysAreVirtualCodes = info[1].As(); + } + } else { + log("(DHK): invalid convertHotkeysCodes arguments: expected array and boolean"); + Napi::TypeError::New(env, "invalid convertHotkeysCodes arguments: expected array and boolean").ThrowAsJavaScriptException(); + return Napi::Array::New(env, 0); + } + if (arrKeys.Length() == 0) { + log("(DHK): convertHotkeysCodes received empty array of keyCodes; early return"); + return Napi::Array::New(env, 0); + } + Napi::Array arr = Napi::Array::New(env, arrKeys.Length()); + for (size_t idx = 0; idx < arrKeys.Length(); ++idx) { + Napi::Value key = arrKeys[idx]; + arr[idx] = keycode_convert(key.As().Uint32Value(), !keysAreVirtualCodes); + } + return arr; +} + Napi::Object InitAll(Napi::Env env, Napi::Object exports) { exports.Set("start", Napi::Function::New(env, HotKeys::start)); @@ -223,5 +339,7 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports) exports.Set("macShowAccessibilitySettings", Napi::Function::New(env, HotKeys::macShowAccessibilitySettings)); exports.Set("macSubscribeAccessibilityUpdates", Napi::Function::New(env, HotKeys::macSubscribeAccessibilityUpdates)); exports.Set("macUnsubscribeAccessibilityUpdates", Napi::Function::New(env, HotKeys::macUnsubscribeAccessibilityUpdates)); + exports.Set("convertHotkeysCodes", Napi::Function::New(env, HotKeys::convertHotkeysCodes)); + exports.Set("checkHotkeyConflicts", Napi::Function::New(env, HotKeys::checkHotkeyConflicts)); return exports; } diff --git a/src/Hotkeys.h b/src/Hotkeys.h index f233367..444a17d 100644 --- a/src/Hotkeys.h +++ b/src/Hotkeys.h @@ -17,6 +17,8 @@ namespace HotKeys Napi::Number macSubscribeAccessibilityUpdates(const Napi::CallbackInfo& info); Napi::Number macUnsubscribeAccessibilityUpdates(const Napi::CallbackInfo& info); Napi::Number setHotkeysEnabled(const Napi::CallbackInfo& info); + Napi::Array convertHotkeysCodes(const Napi::CallbackInfo& info); + Napi::Number checkHotkeyConflicts(const Napi::CallbackInfo& info); } Napi::Object InitAll(Napi::Env env, Napi::Object exports); diff --git a/src/HotkeysLinuxEmptyImpl.cpp b/src/HotkeysLinuxEmptyImpl.cpp index 80326e5..fe2cda3 100644 --- a/src/HotkeysLinuxEmptyImpl.cpp +++ b/src/HotkeysLinuxEmptyImpl.cpp @@ -68,6 +68,16 @@ Napi::Boolean HotKeys::macCheckAccessibilityGranted(const Napi::CallbackInfo& in return emptyImpl(info, true); } +Napi::Array HotKeys::convertHotkeysCodes(const Napi::CallbackInfo& info) +{ + return return emptyImpl(env, 0); +} + +Napi::Number checkHotkeyConflicts(const Napi::CallbackInfo& info); +{ + return emptyImpl(info, 0); +} + Napi::Object InitAll(Napi::Env env, Napi::Object exports) { exports.Set("start", Napi::Function::New(env, HotKeys::start)); @@ -79,5 +89,7 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports) exports.Set("macShowAccessibilitySettings", Napi::Function::New(env, HotKeys::macShowAccessibilitySettings)); exports.Set("macSubscribeAccessibilityUpdates", Napi::Function::New(env, HotKeys::macSubscribeAccessibilityUpdates)); exports.Set("macUnsubscribeAccessibilityUpdates", Napi::Function::New(env, HotKeys::macUnsubscribeAccessibilityUpdates)); + exports.Set("convertHotkeysCodes", Napi::Function::New(env, HotKeys::convertHotkeysCodes)); + exports.Set("checkHotkeyConflicts", Napi::Function::New(env, HotKeys::checkHotkeyConflicts)); return exports; } diff --git a/src/HotkeysMac.mm b/src/HotkeysMac.mm index 72d62f9..c28a589 100644 --- a/src/HotkeysMac.mm +++ b/src/HotkeysMac.mm @@ -5,6 +5,7 @@ #import #include "../libuiohook/include/uiohook.h" +#include "../libuiohook/src/darwin/input_helper.h" #include #include #include @@ -36,13 +37,14 @@ Napi::ThreadSafeFunction _pressedCb; Napi::ThreadSafeFunction _releasedCb; std::map _keyPressedState; + const unsigned _id; unsigned _keysPressed; const unsigned _keysCount; public: - HotKey(Napi::ThreadSafeFunction pressedCb, Napi::ThreadSafeFunction releasedCb, unsigned* keyCodes, unsigned keyCount) : - _pressedCb(pressedCb), _releasedCb(releasedCb), _keysPressed(0), _keysCount(keyCount) + HotKey(Napi::ThreadSafeFunction pressedCb, Napi::ThreadSafeFunction releasedCb, const std::vector& keyCodes, unsigned id) : + _pressedCb(pressedCb), _releasedCb(releasedCb), _id(id), _keysPressed(0), _keysCount(keyCodes.size()) { - for (size_t idx = 0; idx < keyCount; ++idx) { + for (size_t idx = 0; idx < _keysCount; ++idx) { _keyPressedState[keyCodes[idx]] = false; } } @@ -53,26 +55,46 @@ void onKeyEvent(unsigned keyCode, bool pressed) if (cit != _keyPressedState.end()) { if (cit->second != pressed) { cit->second = pressed; + int* pid = new int; + *pid = static_cast(_id); + auto callback = []( Napi::Env env, Napi::Function jsCallback, int* pHotkeyId ) { + jsCallback.Call( {Napi::Number::New(env, *pHotkeyId)} ); + delete pHotkeyId; + }; + if (!pressed && _keysPressed == _keysCount) { - _releasedCb.NonBlockingCall(); + _releasedCb.NonBlockingCall(pid, callback); } _keysPressed += (pressed ? 1 : -1); if (pressed && _keysPressed == _keysCount) { - _pressedCb.NonBlockingCall(); + _pressedCb.NonBlockingCall(pid, callback); } } } } - bool hasConflict(unsigned* keyCodes, unsigned keyCount) { + unsigned checkConflictLevel(const std::vector& keyCodes) { std::map::iterator ce = _keyPressedState.end(); + const size_t keyCount = keyCodes.size(); + size_t equalKeysCount = 0; for (size_t idx = 0; idx < keyCount; ++idx) { - if (ce == _keyPressedState.find(keyCodes[idx])) { - return false; + if (ce != _keyPressedState.find(keyCodes[idx])) { + ++equalKeysCount; } } - return true; + if (equalKeysCount == keyCount) { + // all new keys already in use here + return keyCount == _keyPressedState.size() ? 2 : 1; + } else if (equalKeysCount == _keyPressedState.size()) { + // all my key will be used as a new hotkey + return 1; + } + return 0; } + + unsigned getId() const { + return _id; + } }; class HotKeyStore @@ -85,15 +107,13 @@ bool hasConflict(unsigned* keyCodes, unsigned keyCount) { { } - unsigned create(const Napi::ThreadSafeFunction& pressed, const Napi::ThreadSafeFunction& released, unsigned* keyCodes, unsigned keyCount) + unsigned create(const Napi::ThreadSafeFunction& pressed, const Napi::ThreadSafeFunction& released, const std::vector& keyCodes) { - for (TCONT::const_iterator cit = _hotkeys.begin(); cit != _hotkeys.end(); ++cit) { - if (cit->second->hasConflict(keyCodes, keyCount)) { - return 0; - } + if (checkConflicts(0, keyCodes) > 0) { + return 0; } unsigned id = ++nextHotkeyId; - _hotkeys[id] = std::unique_ptr(new HotKey(pressed, released, keyCodes, keyCount)); + _hotkeys[id] = std::unique_ptr(new HotKey(pressed, released, keyCodes, id)); return id; } @@ -122,6 +142,22 @@ void changeState(bool disabled) { _disabled = disabled; } + + unsigned checkConflicts(unsigned uIdToSkip, const std::vector& keyCodes) const { + unsigned uMaxConflictLevel = 0; + unsigned uConfictHotkeyId = 0; + for (TCONT::const_iterator cit = _hotkeys.begin(); cit != _hotkeys.end(); ++cit) { + if (cit->first == uIdToSkip) { + continue; + } + unsigned uConflictLevel = cit->second->checkConflictLevel(keyCodes); + if (uConflictLevel > uMaxConflictLevel) { + uMaxConflictLevel = uConflictLevel; + uConfictHotkeyId = cit->first; + } + } + return uConfictHotkeyId; + } } hotKeyStore; class PressedKeysCollection @@ -664,7 +700,6 @@ int runStartStopThread(bool startHook) Napi::Array arrKeys; Napi::Function fnPressed; Napi::Function fnReleased; - bool keysAreVirtualCodes = false; unsigned argCount = info.Length(); while (argCount > 0) { if (info[argCount - 1].IsEmpty() || info[argCount - 1].IsUndefined() || info[argCount - 1].IsNull()) { @@ -678,9 +713,6 @@ int runStartStopThread(bool startHook) arrKeys = info[0].As(); fnPressed = info[1].As(); fnReleased = info[2].As(); - if (argCount >= 4 && info[3].IsBoolean()) { - keysAreVirtualCodes = info[3].As(); - } } else if (argCount == 2 && info[0].IsArray() && info[1].IsFunction()) { arrKeys = info[0].As(); fnPressed = info[1].As(); @@ -689,7 +721,7 @@ int runStartStopThread(bool startHook) Napi::TypeError::New(env, "invalid registerShortcut arguments: Array/Function/Function or Array/Function expected").ThrowAsJavaScriptException(); return Napi::Number::New(env, 0); } - unsigned* keyCodes = new unsigned[arrKeys.Length()]; + std::vector keyCodes(arrKeys.Length()); for (size_t idx = 0; idx < arrKeys.Length(); ++idx) { Napi::Value key = arrKeys[idx]; keyCodes[idx] = key.As().Uint32Value(); @@ -707,8 +739,7 @@ int runStartStopThread(bool startHook) "desktop-hotkeys released cb ", 0, 1), - keyCodes, - arrKeys.Length() + keyCodes ); if (keyId == 0) { logger_proc(LOG_LEVEL_ERROR, "(DHK): failed to create the new hotkey"); @@ -767,4 +798,62 @@ int runStartStopThread(bool startHook) hotKeyStore.changeState(!bEnable); } return Napi::Number::New(env, 0); -} \ No newline at end of file +} + +unsigned keycode_convert(unsigned code, bool toWinVK); + +Napi::Array HotKeys::convertHotkeysCodes(const Napi::CallbackInfo& info) +{ + Napi::Env env = info.Env(); + unsigned argCount = info.Length(); + Napi::Array arrKeys; + bool keysAreVirtualCodes = false; + if (argCount > 0 && info[0].IsArray()) {// not used in mac: info[1].IsBoolean()) { + arrKeys = info[0].As(); + if (argCount > 1 && info[1].IsBoolean()) { + keysAreVirtualCodes = info[1].As(); + } + } else { + logger_proc(LOG_LEVEL_ERROR, "(DHK): invalid convertHotkeysCodes arguments: expected array and boolean"); + Napi::TypeError::New(env, "invalid convertHotkeysCodes arguments: expected array and boolean").ThrowAsJavaScriptException(); + return Napi::Array::New(env, 0); + } + if (arrKeys.Length() == 0) { + logger_proc(LOG_LEVEL_WARN, "(DHK): convertHotkeysCodes received empty array of keyCodes; early return"); + return Napi::Array::New(env, 0); + } + Napi::Array arr = Napi::Array::New(env, arrKeys.Length()); + for (size_t idx = 0; idx < arrKeys.Length(); ++idx) { + Napi::Value key = arrKeys[idx]; + arr[idx] = keycode_convert(key.As().Uint32Value(), !keysAreVirtualCodes); + } + return arr; +} + +Napi::Number HotKeys::checkHotkeyConflicts(const Napi::CallbackInfo& info) +{ + Napi::Env env = info.Env(); + unsigned argCount = info.Length(); + unsigned uExcludeHotkeyId = 0; + Napi::Array arrKeys; + if (argCount >= 1 && info[0].IsNumber()) { + uExcludeHotkeyId = info[0].As().Uint32Value(); + } + if (argCount >= 2 && info[1].IsArray()) { + arrKeys = info[1].As(); + } else { + logger_proc(LOG_LEVEL_ERROR, "(DHK): invalid checkHotkeyConflicts arguments: expected number + array"); + Napi::TypeError::New(env, "invalid checkHotkeyConflicts arguments: expected number + array").ThrowAsJavaScriptException(); + return Napi::Number::New(env, 0); + } + if (arrKeys.Length() == 0) { + logger_proc(LOG_LEVEL_WARN, "(DHK): checkHotkeyConflicts received empty array of keyCodes; early return"); + return Napi::Number::New(env, 0); + } + std::vector keyCodes(arrKeys.Length()); + for (size_t idx = 0; idx < arrKeys.Length(); ++idx) { + Napi::Value key = arrKeys[idx]; + keyCodes[idx] = key.As().Uint32Value(); + } + return Napi::Number::New(env, hotKeyStore.checkConflicts(uExcludeHotkeyId, keyCodes)); +} diff --git a/src/HotkeysMacEmptyImpl.mm b/src/HotkeysMacEmptyImpl.mm index 0995d8e..479bc6a 100644 --- a/src/HotkeysMacEmptyImpl.mm +++ b/src/HotkeysMacEmptyImpl.mm @@ -78,3 +78,12 @@ T emptyImpl(const Napi::CallbackInfo& info, TPrim defValue) return emptyImpl(info, true); } +Napi::Array HotKeys::convertHotkeysCodes(const Napi::CallbackInfo& info) +{ + return return emptyImpl(env, 0); +} + +Napi::Number checkHotkeyConflicts(const Napi::CallbackInfo& info); +{ + return emptyImpl(info, 0); +} \ No newline at end of file diff --git a/src/KeyMapping.cpp b/src/KeyMapping.cpp new file mode 100644 index 0000000..b462594 --- /dev/null +++ b/src/KeyMapping.cpp @@ -0,0 +1,205 @@ +#include "windows_vkc.h" +#include "../libuiohook/include/uiohook.h" +static const uint16_t table[][2] = { +{VK_LBUTTON, MOUSE_BUTTON1}, // Left mouse button +{VK_RBUTTON, MOUSE_BUTTON2}, // Right mouse button +{VK_CANCEL, 0}, // Control-break processing +{VK_MBUTTON, MOUSE_BUTTON3}, // Middle mouse button (three-button mouse) +{VK_XBUTTON1, MOUSE_BUTTON4}, // X1 mouse button +{VK_XBUTTON2, MOUSE_BUTTON5}, // X2 mouse button +{VK_BACK, VC_BACKSPACE}, // BACKSPACE key +{VK_TAB, VC_TAB}, // TAB key +{VK_CLEAR, 0}, // CLEAR key +{VK_RETURN, VC_ENTER}, // ENTER key + +{VK_SHIFT, VC_SHIFT_L}, // SHIFT key +{VK_CONTROL, VC_CONTROL_L}, // CTRL key +{VK_MENU, VC_ALT_L}, // ALT key +{VK_PAUSE, VC_PAUSE}, // PAUSE key +{VK_CAPITAL, VC_CAPS_LOCK}, // CAPS LOCK key +{VK_KANA, VC_KATAKANA}, // IME Kana mode +//{VK_IME_ON, 0}, // 0x16 IME On +{VK_JUNJA, 0}, // 0x17 IME Junja mode +{VK_FINAL, 0}, // 0x18 IME final mode +{VK_HANJA, 0}, // 0x19 IME Hanja mode +{VK_KANJI, VC_KANJI}, // 0x19 IME Kanji mode +//{VK_IME_OFF, 0}, // 0x1A IME Off +{VK_ESCAPE, VC_ESCAPE}, // 0x1B ESC key +{VK_CONVERT, 0}, // 0x1C IME convert +{VK_NONCONVERT, 0}, // 0x1D IME nonconvert +{VK_ACCEPT, 0}, // 0x1E IME accept +{VK_MODECHANGE, 0}, // 0x1F IME mode change request +{VK_SPACE, VC_SPACE}, // 0x20 SPACEBAR +{VK_PRIOR, VC_PAGE_UP}, // 0x21 PAGE UP key +{VK_NEXT, VC_PAGE_DOWN}, // 0x22 PAGE DOWN key +{VK_END, VC_END}, // 0x23 END key +{VK_HOME, VC_HOME}, // 0x24 HOME key +{VK_LEFT, VC_LEFT}, // 0x25 LEFT ARROW key +{VK_UP, VC_UP}, // 0x26 UP ARROW key +{VK_RIGHT, VC_RIGHT}, // 0x27 RIGHT ARROW key +{VK_DOWN, VC_DOWN}, // 0x28 DOWN ARROW key +{VK_SELECT, 0}, // 0x29 SELECT key +{VK_PRINT, 0}, // 0x2A PRINT key +{VK_EXECUTE, 0}, // 0x2B EXECUTE key +{VK_SNAPSHOT, VC_PRINTSCREEN}, // 0x2C PRINT SCREEN key +{VK_INSERT, VC_INSERT}, // 0x2D INS key +{VK_DELETE, VC_DELETE}, // 0x2E DEL key +{VK_HELP, 0}, // 0x2F HELP key +{0x30, VC_0}, // 0 key +{0x31, VC_1}, // 1 key +{0x32, VC_2}, // 2 key +{0x33, VC_3}, //3 key +{0x34, VC_4}, //4 key +{0x35, VC_5}, // 5 key +{0x36, VC_6}, // 6 key +{0x37, VC_7}, // 7 key +{0x38, VC_8}, // 8 key +{0x39, VC_9}, // 9 key +//- 0x3A-40 Undefined +{0x41, VC_A }, //A key +{0x42, VC_B }, //key +{0x43, VC_C }, //key +{0x44, VC_D }, // key +{0x45, VC_E }, // key +{0x46, VC_F }, // key +{0x47, VC_G }, // key +{0x48, VC_H }, // key +{0x49, VC_I }, // key +{0x4A, VC_J }, // key +{0x4B, VC_K }, // key +{0x4C, VC_L }, // key +{0x4D, VC_M }, // key +{0x4E, VC_N }, // key +{0x4F, VC_O }, // key +{0x50, VC_P }, // key +{0x51, VC_Q }, // key +{0x52, VC_R }, // key +{0x53, VC_S }, // key +{0x54, VC_T }, // key +{0x55, VC_U }, // key +{0x56, VC_V }, // key +{0x57, VC_W }, // key +{0x58, VC_X }, // key +{0x59, VC_Y }, // key +{0x5A, VC_Z}, // key +{VK_LWIN, VC_META_L}, // 0x5B Left Windows key (Natural keyboard) +{VK_RWIN, VC_META_R}, // 0x5C Right Windows key (Natural keyboard) +{VK_APPS, VC_CONTEXT_MENU}, // 0x5D Applications key (Natural keyboard) +//- 0x5E Reserved +{VK_SLEEP, VC_SLEEP}, // 0x5F Computer Sleep key +{VK_NUMPAD0, VC_KP_0}, // 0x60 Numeric keypad 0 key +{VK_NUMPAD1, VC_KP_1}, // 0x61 Numeric keypad 1 key +{VK_NUMPAD2, VC_KP_2}, // 0x62 Numeric keypad 2 key +{VK_NUMPAD3, VC_KP_3}, // 0x63 Numeric keypad 3 key +{VK_NUMPAD4, VC_KP_4}, // 0x64 Numeric keypad 4 key +{VK_NUMPAD5, VC_KP_5}, // 0x65 Numeric keypad 5 key +{VK_NUMPAD6, VC_KP_6}, // 0x66 Numeric keypad 6 key +{VK_NUMPAD7, VC_KP_7}, // 0x67 Numeric keypad 7 key +{VK_NUMPAD8, VC_KP_8}, // 0x68 Numeric keypad 8 key +{VK_NUMPAD9, VC_KP_9}, // 0x69 Numeric keypad 9 key +{VK_MULTIPLY, VC_KP_MULTIPLY}, // 0x6A Multiply key +{VK_ADD, VC_KP_ADD}, // 0x6B Add key +{VK_SEPARATOR, 0}, // 0x6C Separator key +{VK_SUBTRACT, VC_KP_SUBTRACT}, // 0x6D Subtract key +{VK_DECIMAL, VC_KP_SEPARATOR}, // 0x6E Decimal key +{VK_DIVIDE, VC_KP_DIVIDE}, // 0x6F Divide key +{VK_F1, VC_F1}, //0x70 F1 key +{VK_F2, VC_F2}, // 0x71 F2 key +{VK_F3, VC_F3}, // 0x72 F3 key +{VK_F4, VC_F4}, // 0x73 F4 key +{VK_F5, VC_F5}, // 0x74 F5 key +{VK_F6, VC_F6}, // 0x75 F6 key +{VK_F7, VC_F7}, // 0x76 F7 key +{VK_F8, VC_F8}, // 0x77 F8 key +{VK_F9, VC_F9}, // 0x78 F9 key +{VK_F10, VC_F10}, // 0x79 F10 key +{VK_F11, VC_F11}, // 0x7A F11 key +{VK_F12, VC_F12}, // 0x7B F12 key +{VK_F13, VC_F13}, // 0x7C F13 key +{VK_F14, VC_F14}, // 0x7D F14 key +{VK_F15, VC_F15}, // 0x7E F15 key +{VK_F16, VC_F16}, // 0x7F F16 key +{VK_F17, VC_F17}, // 0x80 F17 key +{VK_F18, VC_F18}, // 0x81 F18 key +{VK_F19, VC_F19}, // 0x82 F19 key +{VK_F20, VC_F20}, // 0x83 F20 key +{VK_F21, VC_F21}, // 0x84 F21 key +{VK_F22, VC_F22}, // 0x85 F22 key +{VK_F23, VC_F23}, // 0x86 F23 key +{VK_F24, VC_F24}, // 0x87 F24 key +//- 0x88-8F Unassigned +{VK_NUMLOCK, VC_NUM_LOCK}, // 0x90 NUM LOCK key +{VK_SCROLL, VC_SCROLL_LOCK}, // 0x91 SCROLL LOCK key +//0x92-96 OEM specific +//- 0x97-9F Unassigned +{VK_LSHIFT, VC_SHIFT_L}, // 0xA0 Left SHIFT key +{VK_RSHIFT, VC_SHIFT_R}, // 0xA1 Right SHIFT key +{VK_LCONTROL, VC_CONTROL_L}, // 0xA2 Left CONTROL key +{VK_RCONTROL, VC_CONTROL_L}, // 0xA3 Right CONTROL key +{VK_LMENU, VC_ALT_L}, // 0xA4 Left ALT key +{VK_RMENU, VC_ALT_R}, // 0xA5 Right ALT key +{VK_BROWSER_BACK, VC_BROWSER_BACK}, // 0xA6 Browser Back key +{VK_BROWSER_FORWARD, VC_BROWSER_FORWARD}, // 0xA7 Browser Forward key +{VK_BROWSER_REFRESH, VC_BROWSER_REFRESH}, // 0xA8 Browser Refresh key +{VK_BROWSER_STOP, VC_BROWSER_STOP}, // 0xA9 Browser Stop key +{VK_BROWSER_SEARCH, VC_BROWSER_SEARCH}, // 0xAA Browser Search key +{VK_BROWSER_FAVORITES, VC_BROWSER_FAVORITES}, // 0xAB Browser Favorites key +{VK_BROWSER_HOME, VC_BROWSER_HOME}, // 0xAC Browser Start and Home key +{VK_VOLUME_MUTE, VC_VOLUME_MUTE}, // 0xAD Volume Mute key +{VK_VOLUME_DOWN, VC_VOLUME_DOWN}, // 0xAE Volume Down key +{VK_VOLUME_UP, VC_VOLUME_UP}, // 0xAF Volume Up key +{VK_MEDIA_NEXT_TRACK, VC_MEDIA_NEXT}, // 0xB0 Next Track key +{VK_MEDIA_PREV_TRACK, VC_MEDIA_PREVIOUS}, // 0xB1 Previous Track key +{VK_MEDIA_STOP, VC_MEDIA_STOP}, // 0xB2 Stop Media key +{VK_MEDIA_PLAY_PAUSE, VC_MEDIA_PLAY}, // 0xB3 Play/Pause Media key +{VK_LAUNCH_MAIL, 0}, // 0xB4 Start Mail key +{VK_LAUNCH_MEDIA_SELECT, VC_MEDIA_SELECT}, // 0xB5 Select Media key +{VK_LAUNCH_APP1, VC_APP_MAIL}, // 0xB6 Start Application 1 key +{VK_LAUNCH_APP2, VC_APP_CALCULATOR}, // 0xB7 Start Application 2 key +//- 0xB8-B9 Reserved +{VK_OEM_1, VC_SEMICOLON}, // 0xBA Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key +{VK_OEM_PLUS, VC_EQUALS}, // 0xBB For any country/region, the '+' key +{VK_OEM_COMMA, VC_COMMA}, // 0xBC For any country/region, the ',' key +{VK_OEM_MINUS, VC_MINUS}, // 0xBD For any country/region, the '-' key +{VK_OEM_PERIOD, VC_PERIOD}, // 0xBE For any country/region, the '.' key +{VK_OEM_2, VC_SLASH}, // 0xBF Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key +{VK_OEM_3, VC_BACKQUOTE}, // 0xC0 Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key +//- 0xC1-D7 Reserved +//- 0xD8-DA Unassigned +{VK_OEM_4, VC_OPEN_BRACKET}, // 0xDB Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key +{VK_OEM_5, VC_BACK_SLASH}, // 0xDC Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\|' key +{VK_OEM_6, VC_CLOSE_BRACKET}, // 0xDD Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key +{VK_OEM_7, VC_QUOTE}, // 0xDE Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key +{VK_OEM_8, VC_YEN}, // 0xDF Used for miscellaneous characters; it can vary by keyboard. +//- 0xE0 Reserved +//0xE1 OEM specific +{VK_OEM_102, VC_LESSER_GREATER}, // 0xE2 The <> keys on the US standard keyboard, or the \\| key on the non-US 102-key keyboard +//0xE3-E4 OEM specific +{VK_PROCESSKEY, VC_APP_PICTURES}, // 0xE5 IME PROCESS key +{0xE6, VC_APP_MUSIC}, // OEM specific +{VK_PACKET, 0}, // 0xE7 Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more information, see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP +//- 0xE8 Unassigned +//0xE9-F5 OEM specific +{VK_ATTN, 0}, // 0xF6 Attn key +{VK_CRSEL, 0}, // 0xF7 CrSel key +{VK_EXSEL, 0}, // 0xF8 ExSel key +{VK_EREOF, 0}, // 0xF9 Erase EOF key +{VK_PLAY, 0}, // 0xFA Play key +{VK_ZOOM, 0}, // 0xFB Zoom key +{VK_NONAME, 0}, // 0xFC Reserved +{VK_PA1, 0}, // 0xFD PA1 key +{VK_OEM_CLEAR, VC_CLEAR} // 0xFE Clear key +}; + +static const uint16_t totalKeyCount = sizeof(table) / sizeof(table[0]); + +unsigned keycode_convert(unsigned code, bool toWinVK) { + for (uint16_t idx = 0; idx " or "\|" on RT 102-key kbd. +#define VK_ICO_HELP 0xE3 // Help key on ICO +#define VK_ICO_00 0xE4 // 00 key on ICO + +#define VK_PROCESSKEY 0xE5 + +#define VK_ICO_CLEAR 0xE6 + + +#define VK_PACKET 0xE7 + +/* + * 0xE8 : unassigned + */ + +/* + * Nokia/Ericsson definitions + */ +#define VK_OEM_RESET 0xE9 +#define VK_OEM_JUMP 0xEA +#define VK_OEM_PA1 0xEB +#define VK_OEM_PA2 0xEC +#define VK_OEM_PA3 0xED +#define VK_OEM_WSCTRL 0xEE +#define VK_OEM_CUSEL 0xEF +#define VK_OEM_ATTN 0xF0 +#define VK_OEM_FINISH 0xF1 +#define VK_OEM_COPY 0xF2 +#define VK_OEM_AUTO 0xF3 +#define VK_OEM_ENLW 0xF4 +#define VK_OEM_BACKTAB 0xF5 + +#define VK_ATTN 0xF6 +#define VK_CRSEL 0xF7 +#define VK_EXSEL 0xF8 +#define VK_EREOF 0xF9 +#define VK_PLAY 0xFA +#define VK_ZOOM 0xFB +#define VK_NONAME 0xFC +#define VK_PA1 0xFD +#define VK_OEM_CLEAR 0xFE