#include "pch.h" #include #include #include #include #include "trace.h" #include #include extern "C" IMAGE_DOS_HEADER __ImageBase; BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: Trace::RegisterProvider(); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: Trace::UnregisterProvider(); break; } return TRUE; } // The PowerToy name that will be shown in the settings. const static wchar_t* MODULE_NAME = L"PowerKeys"; // Add a description that will we shown in the module settings page. const static wchar_t* MODULE_DESC = L"Customize your experience by remapping keys or creating new shortcuts!"; // Implement the PowerToy Module Interface and all the required methods. class PowerKeys : public PowertoyModuleIface { private: // The PowerToy state. bool m_enabled = false; // Flags used for distinguishing key events sent by PowerKeys static const ULONG_PTR POWERKEYS_INJECTED_FLAG = 0x1; static const ULONG_PTR POWERKEYS_SINGLEKEY_FLAG = 0x11; static const ULONG_PTR POWERKEYS_SHORTCUT_FLAG = 0x101; // Dummy key event used in between key up and down events to prevent certain global events from happening static const DWORD DUMMY_KEY = 0xFF; // Low level hook handles static HHOOK hook_handle; // Required for Unhook in old versions of Windows static HHOOK hook_handle_copy; // Static pointer to the current powerkeys object required for accessing the HandleKeyboardHookEvent function in the hook procedure (Only global or static variables can be accessed in a hook procedure CALLBACK) static PowerKeys* powerkeys_object_ptr; // Variable which stores all the state information to be shared between the UI and back-end KeyboardManagerState keyboardManagerState; // Vector to store the detected shortcut in the detect shortcut UI. Acts as a shortcut buffer while detecting the shortcuts in the UI. std::vector detectedShortcutKeys; public: // Constructor PowerKeys() { init_map(); // Set the static pointer to the newest object of the class powerkeys_object_ptr = this; }; // This function is used to add the hardcoded mappings void init_map() { //// If mapped to 0x0 then key is disabled. //keyboardManagerState.singleKeyReMap[0x41] = 0x42; //keyboardManagerState.singleKeyReMap[0x42] = 0x43; //keyboardManagerState.singleKeyReMap[0x43] = 0x41; //keyboardManagerState.singleKeyReMap[VK_LWIN] = VK_LCONTROL; //keyboardManagerState.singleKeyReMap[VK_LCONTROL] = VK_LWIN; //keyboardManagerState.singleKeyReMap[VK_CAPITAL] = 0x0; //keyboardManagerState.singleKeyReMap[VK_LSHIFT] = VK_CAPITAL; //keyboardManagerState.singleKeyToggleToMod[VK_CAPITAL] = false; //// OS-level shortcut remappings //keyboardManagerState.osLevelShortcutReMap[std::vector({ VK_LMENU, 0x44 })] = std::make_pair(std::vector({ VK_LCONTROL, 0x56 }), false); //keyboardManagerState.osLevelShortcutReMap[std::vector({ VK_LMENU, 0x45 })] = std::make_pair(std::vector({ VK_LCONTROL, 0x58 }), false); //keyboardManagerState.osLevelShortcutReMap[std::vector({ VK_LWIN, 0x46 })] = std::make_pair(std::vector({ VK_LWIN, 0x53 }), false); //keyboardManagerState.osLevelShortcutReMap[std::vector({ VK_LWIN, 0x41 })] = std::make_pair(std::vector({ VK_LCONTROL, 0x58 }), false); //keyboardManagerState.osLevelShortcutReMap[std::vector({ VK_LCONTROL, 0x58 })] = std::make_pair(std::vector({ VK_LWIN, 0x41 }), false); //keyboardManagerState.osLevelShortcutReMap[std::vector({ VK_LWIN, 0x41 })] = std::make_pair(std::vector({ VK_LCONTROL, 0x58 }), false); //keyboardManagerState.osLevelShortcutReMap[std::vector({ VK_LCONTROL, 0x58 })] = std::make_pair(std::vector({ VK_LMENU, 0x44 }), false); //keyboardManagerState.osLevelShortcutReMap[std::vector({ VK_LCONTROL, 0x56 })] = std::make_pair(std::vector({ VK_LWIN, 0x41 }), false); ////App-specific shortcut remappings //keyboardManagerState.appSpecificShortcutReMap[L"msedge.exe"][std::vector({ VK_LCONTROL, 0x43 })] = std::make_pair(std::vector({ VK_LCONTROL, 0x56 }), false); // Ctrl+C to Ctrl+V //keyboardManagerState.appSpecificShortcutReMap[L"msedge.exe"][std::vector({ VK_LMENU, 0x44 })] = std::make_pair(std::vector({ VK_LCONTROL, 0x46 }), false); // Alt+D to Ctrl+F //keyboardManagerState.appSpecificShortcutReMap[L"OUTLOOK.EXE"][std::vector({ VK_LCONTROL, 0x46 })] = std::make_pair(std::vector({ VK_LCONTROL, 0x45 }), false); // Ctrl+F to Ctrl+E //keyboardManagerState.appSpecificShortcutReMap[L"MicrosoftEdge.exe"][std::vector({ VK_LCONTROL, 0x58 })] = std::make_pair(std::vector({ VK_LCONTROL, 0x56 }), false); // Ctrl+X to Ctrl+V //keyboardManagerState.appSpecificShortcutReMap[L"Calculator.exe"][std::vector({ VK_LCONTROL, 0x47 })] = std::make_pair(std::vector({ VK_LSHIFT, 0x32 }), false); // Ctrl+G to Shift+2 } // Destroy the powertoy and free memory virtual void destroy() override { stop_lowlevel_keyboard_hook(); delete this; } // Return the display name of the powertoy, this will be cached by the runner virtual const wchar_t* get_name() override { return MODULE_NAME; } // Return array of the names of all events that this powertoy listens for, with // nullptr as the last element of the array. Nullptr can also be retured for empty // list. virtual const wchar_t** get_events() override { static const wchar_t* events[] = { ll_keyboard, nullptr }; return events; } // Return JSON with the configuration options. virtual bool get_config(wchar_t* buffer, int* buffer_size) override { HINSTANCE hinstance = reinterpret_cast(&__ImageBase); // Create a Settings object. PowerToysSettings::Settings settings(hinstance, get_name()); settings.set_description(MODULE_DESC); return settings.serialize_to_buffer(buffer, buffer_size); } // Signal from the Settings editor to call a custom action. // This can be used to spawn more complex editors. virtual void call_custom_action(const wchar_t* action) override { static UINT custom_action_num_calls = 0; try { // Parse the action values, including name. PowerToysSettings::CustomActionObject action_object = PowerToysSettings::CustomActionObject::from_json_string(action); //if (action_object.get_name() == L"custom_action_id") { // // Execute your custom action //} } catch (std::exception&) { // Improper JSON. } } // Called by the runner to pass the updated settings values as a serialized JSON. virtual void set_config(const wchar_t* config) override { try { // Parse the input JSON string. PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::from_json_string(config); // If you don't need to do any custom processing of the settings, proceed // to persists the values calling: values.save_to_settings_file(); } catch (std::exception&) { // Improper JSON. } } // Enable the powertoy virtual void enable() { m_enabled = true; HINSTANCE hInstance = reinterpret_cast(&__ImageBase); std::thread(createMainWindow, hInstance, std::ref(keyboardManagerState)).detach(); start_lowlevel_keyboard_hook(); } // Disable the powertoy virtual void disable() { m_enabled = false; stop_lowlevel_keyboard_hook(); } // Returns if the powertoys is enabled virtual bool is_enabled() override { return m_enabled; } // Handle incoming event, data is event-specific virtual intptr_t signal_event(const wchar_t* name, intptr_t data) override { return 0; } virtual void register_system_menu_helper(PowertoySystemMenuIface* helper) override {} virtual void signal_system_menu_action(const wchar_t* name) override {} // Hook procedure definition static LRESULT CALLBACK hook_proc(int nCode, WPARAM wParam, LPARAM lParam) { LowlevelKeyboardEvent event; if (nCode == HC_ACTION) { event.lParam = reinterpret_cast(lParam); event.wParam = wParam; if (powerkeys_object_ptr->HandleKeyboardHookEvent(&event) == 1) { return 1; } } return CallNextHookEx(hook_handle_copy, nCode, wParam, lParam); } // Prevent system-wide input lagging while paused in the debugger //#define DISABLE_LOWLEVEL_KBHOOK_WHEN_DEBUGGED void start_lowlevel_keyboard_hook() { #if defined(_DEBUG) && defined(DISABLE_LOWLEVEL_KBHOOK_WHEN_DEBUGGED) if (IsDebuggerPresent()) { return; } #endif if (!hook_handle) { hook_handle = SetWindowsHookEx(WH_KEYBOARD_LL, hook_proc, GetModuleHandle(NULL), NULL); hook_handle_copy = hook_handle; if (!hook_handle) { throw std::runtime_error("Cannot install keyboard listener"); } } } // Function to terminate the low level hook void stop_lowlevel_keyboard_hook() { if (hook_handle) { UnhookWindowsHookEx(hook_handle); hook_handle = nullptr; } } // Function called by the hook procedure to handle the events. This is the starting point function for remapping intptr_t HandleKeyboardHookEvent(LowlevelKeyboardEvent* data) noexcept { // If the Detect Key Window is currently activated, then suppress the keyboard event if (keyboardManagerState.DetectKeyUIBackend(data)) { return 1; } // Remap a key intptr_t SingleKeyRemapResult = HandleSingleKeyRemapEvent(data); // Single key remaps have priority. If a key is remapped, only the remapped version should be visible to the shortcuts and hence the event should be suppressed here. if (SingleKeyRemapResult == 1) { return 1; } // If the Detect Shortcut Window is currently activated, then suppress the keyboard event if (keyboardManagerState.DetectShortcutUIBackend(data)) { return 1; } // Remap a key to behave like a modifier instead of a toggle intptr_t SingleKeyToggleToModResult = HandleSingleKeyToggleToModEvent(data); // Handle an app-specific shortcut remapping intptr_t AppSpecificShortcutRemapResult = HandleAppSpecificShortcutRemapEvent(data); // If an app-specific shortcut is remapped then the os-level shortcut remapping should be suppressed. if (AppSpecificShortcutRemapResult == 1) { return 1; } // Handle an os-level shortcut remapping intptr_t OSLevelShortcutRemapResult = HandleOSLevelShortcutRemapEvent(data); // If any of the supported types of remappings took place, then suppress the key event if ((SingleKeyRemapResult + SingleKeyToggleToModResult + OSLevelShortcutRemapResult + AppSpecificShortcutRemapResult) > 0) { return 1; } else { return 0; } } // Function to a handle a single key remap intptr_t HandleSingleKeyRemapEvent(LowlevelKeyboardEvent* data) noexcept { // Check if the key event was generated by KeyboardManager to avoid remapping events generated by us. if (!(data->lParam->dwExtraInfo & POWERKEYS_INJECTED_FLAG)) { auto it = keyboardManagerState.singleKeyReMap.find(data->lParam->vkCode); if (it != keyboardManagerState.singleKeyReMap.end()) { // If mapped to 0x0 then the key is disabled if (it->second == 0x0) { return 1; } int key_count = 1; LPINPUT keyEventList = new INPUT[size_t(key_count)](); memset(keyEventList, 0, sizeof(keyEventList)); keyEventList[0].type = INPUT_KEYBOARD; keyEventList[0].ki.wVk = it->second; keyEventList[0].ki.dwFlags = 0; keyEventList[0].ki.dwExtraInfo = POWERKEYS_SINGLEKEY_FLAG; if (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP) { keyEventList[0].ki.dwFlags = KEYEVENTF_KEYUP; } UINT res = SendInput(key_count, keyEventList, sizeof(INPUT)); delete[] keyEventList; return 1; } } return 0; } // Function to a change a key's behaviour from toggle to modifier intptr_t HandleSingleKeyToggleToModEvent(LowlevelKeyboardEvent* data) noexcept { // Check if the key event was generated by KeyboardManager to avoid remapping events generated by us. if (!(data->lParam->dwExtraInfo & POWERKEYS_INJECTED_FLAG)) { auto it = keyboardManagerState.singleKeyToggleToMod.find(data->lParam->vkCode); if (it != keyboardManagerState.singleKeyToggleToMod.end()) { // To avoid long presses (which leads to continuous keydown messages) from toggling the key on and off if (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN) { if (it->second == false) { keyboardManagerState.singleKeyToggleToMod[data->lParam->vkCode] = true; } else { return 1; } } int key_count = 2; LPINPUT keyEventList = new INPUT[size_t(key_count)](); memset(keyEventList, 0, sizeof(keyEventList)); keyEventList[0].type = INPUT_KEYBOARD; keyEventList[0].ki.wVk = (WORD)data->lParam->vkCode; keyEventList[0].ki.dwFlags = 0; keyEventList[0].ki.dwExtraInfo = POWERKEYS_SINGLEKEY_FLAG; keyEventList[1].type = INPUT_KEYBOARD; keyEventList[1].ki.wVk = (WORD)data->lParam->vkCode; keyEventList[1].ki.dwFlags = KEYEVENTF_KEYUP; keyEventList[1].ki.dwExtraInfo = POWERKEYS_SINGLEKEY_FLAG; UINT res = SendInput(key_count, keyEventList, sizeof(INPUT)); delete[] keyEventList; // Reset the long press flag when the key has been lifted. if (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP) { keyboardManagerState.singleKeyToggleToMod[data->lParam->vkCode] = false; } return 1; } } return 0; } // Function to a handle a shortcut remap intptr_t HandleShortcutRemapEvent(LowlevelKeyboardEvent* data, std::map, std::pair, bool>>& reMap) noexcept { for (auto& it : reMap) { DWORD src_1 = it.first[0]; DWORD src_2 = it.first[1]; WORD dest_1 = it.second.first[0]; WORD dest_2 = it.second.first[1]; // If the shortcut has been pressed down if ((GetAsyncKeyState(src_1) & 0x8000) && !it.second.second) { if (data->lParam->vkCode == src_2 && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)) { int key_count = 4; LPINPUT keyEventList = new INPUT[size_t(key_count)](); memset(keyEventList, 0, sizeof(keyEventList)); if (src_1 == dest_1) { key_count = 1; keyEventList[0].type = INPUT_KEYBOARD; keyEventList[0].ki.wVk = dest_2; keyEventList[0].ki.dwFlags = 0; keyEventList[0].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; } else { keyEventList[0].type = INPUT_KEYBOARD; keyEventList[0].ki.wVk = (WORD)DUMMY_KEY; keyEventList[0].ki.dwFlags = KEYEVENTF_KEYUP; keyEventList[0].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; keyEventList[1].type = INPUT_KEYBOARD; keyEventList[1].ki.wVk = (WORD)src_1; keyEventList[1].ki.dwFlags = KEYEVENTF_KEYUP; keyEventList[1].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; keyEventList[2].type = INPUT_KEYBOARD; keyEventList[2].ki.wVk = dest_1; keyEventList[2].ki.dwFlags = 0; keyEventList[2].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; keyEventList[3].type = INPUT_KEYBOARD; keyEventList[3].ki.wVk = dest_2; keyEventList[3].ki.dwFlags = 0; keyEventList[3].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; } it.second.second = true; UINT res = SendInput(key_count, keyEventList, sizeof(INPUT)); delete[] keyEventList; return 1; } } // The shortcut has already been pressed down at least once, i.e. the shortcut has been invoked else if (it.second.second) { // If the modifier key of the original shortcut is released before the normal key if (data->lParam->vkCode == src_1 && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)) { int key_count = 2; LPINPUT keyEventList = new INPUT[size_t(key_count)](); memset(keyEventList, 0, sizeof(keyEventList)); keyEventList[0].type = INPUT_KEYBOARD; keyEventList[0].ki.wVk = dest_2; keyEventList[0].ki.dwFlags = KEYEVENTF_KEYUP; keyEventList[0].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; keyEventList[1].type = INPUT_KEYBOARD; keyEventList[1].ki.wVk = dest_1; keyEventList[1].ki.dwFlags = KEYEVENTF_KEYUP; keyEventList[1].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; it.second.second = false; UINT res = SendInput(key_count, keyEventList, sizeof(INPUT)); delete[] keyEventList; return 1; } // The system will see dest_1 as being held down because of the shortcut remap if (GetAsyncKeyState(dest_1) & 0x8000) { // If the original shortcut is still held down the keyboard will see the original normal key along with the new modifier (keys held down send repeated keydown messages) if (data->lParam->vkCode == src_2 && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)) { int key_count = 1; LPINPUT keyEventList = new INPUT[size_t(key_count)](); memset(keyEventList, 0, sizeof(keyEventList)); keyEventList[0].type = INPUT_KEYBOARD; keyEventList[0].ki.wVk = dest_2; keyEventList[0].ki.dwFlags = 0; keyEventList[0].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; it.second.second = true; UINT res = SendInput(key_count, keyEventList, sizeof(INPUT)); delete[] keyEventList; return 1; } // If the normal key is released from the original shortcut then revert the keyboard state to just the original modifier being held down if (data->lParam->vkCode == src_2 && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)) { int key_count = 4; LPINPUT keyEventList = new INPUT[size_t(key_count)](); memset(keyEventList, 0, sizeof(keyEventList)); if (src_1 == dest_1) { key_count = 1; keyEventList[0].type = INPUT_KEYBOARD; keyEventList[0].ki.wVk = dest_2; keyEventList[0].ki.dwFlags = KEYEVENTF_KEYUP; keyEventList[0].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; } else { keyEventList[0].type = INPUT_KEYBOARD; keyEventList[0].ki.wVk = dest_2; keyEventList[0].ki.dwFlags = KEYEVENTF_KEYUP; keyEventList[0].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; keyEventList[1].type = INPUT_KEYBOARD; keyEventList[1].ki.wVk = dest_1; keyEventList[1].ki.dwFlags = KEYEVENTF_KEYUP; keyEventList[1].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; keyEventList[2].type = INPUT_KEYBOARD; keyEventList[2].ki.wVk = (WORD)src_1; keyEventList[2].ki.dwFlags = 0; keyEventList[2].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; keyEventList[3].type = INPUT_KEYBOARD; keyEventList[3].ki.wVk = (WORD)DUMMY_KEY; keyEventList[3].ki.dwFlags = KEYEVENTF_KEYUP; keyEventList[3].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; } it.second.second = false; UINT res = SendInput(key_count, keyEventList, sizeof(INPUT)); delete[] keyEventList; return 1; } } } } return 0; } // Function to a handle an os-level shortcut remap intptr_t HandleOSLevelShortcutRemapEvent(LowlevelKeyboardEvent* data) noexcept { // Check if the key event was generated by KeyboardManager to avoid remapping events generated by us. if (data->lParam->dwExtraInfo != POWERKEYS_SHORTCUT_FLAG) { return HandleShortcutRemapEvent(data, keyboardManagerState.osLevelShortcutReMap); } return 0; } // Function to return the window in focus HWND GetFocusWindowHandle() { // Using GetGUIThreadInfo for getting the process of the window in focus. GetForegroundWindow has issues with UWP apps as it returns the Application Frame Host as its linked process GUITHREADINFO guiThreadInfo; guiThreadInfo.cbSize = sizeof(GUITHREADINFO); GetGUIThreadInfo(0, &guiThreadInfo); // If no window in focus, use the active window if (guiThreadInfo.hwndFocus == nullptr) { return guiThreadInfo.hwndActive; } return guiThreadInfo.hwndFocus; } // Function to return the executable name of the application in focus std::wstring GetCurrentApplication(bool keepPath) { HWND current_window_handle = GetFocusWindowHandle(); DWORD process_id; DWORD nSize = MAX_PATH; WCHAR buffer[MAX_PATH] = { 0 }; // Get process ID of the focus window DWORD thread_id = GetWindowThreadProcessId(current_window_handle, &process_id); HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, process_id); // Get full path of the executable bool res = QueryFullProcessImageName(hProc, 0, buffer, &nSize); std::wstring process_name; CloseHandle(hProc); process_name = buffer; if (res) { PathStripPath(buffer); if (!keepPath) { process_name = buffer; } } return process_name; } // Function to a handle an app-specific shortcut remap intptr_t HandleAppSpecificShortcutRemapEvent(LowlevelKeyboardEvent* data) noexcept { // Check if the key event was generated by KeyboardManager to avoid remapping events generated by us. if (data->lParam->dwExtraInfo != POWERKEYS_SHORTCUT_FLAG) { std::wstring process_name = GetCurrentApplication(false); if (process_name.empty()) { return 0; } auto it = keyboardManagerState.appSpecificShortcutReMap.find(process_name); if (it != keyboardManagerState.appSpecificShortcutReMap.end()) { return HandleShortcutRemapEvent(data, keyboardManagerState.appSpecificShortcutReMap[process_name]); } } return 0; } }; HHOOK PowerKeys::hook_handle = nullptr; HHOOK PowerKeys::hook_handle_copy = nullptr; PowerKeys* PowerKeys::powerkeys_object_ptr = nullptr; extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() { return new PowerKeys(); }