mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 02:36:19 +02:00
Edit Shortcuts UI (dev/keyboardManager) (#1647)
* Added EditShortcuts Window and added Detecting shortcuts functionality * Fixed build error * Changed detection to take place only when window is in focus * Added solution folder * Added a common project and refactored shared variables to an object with wrapper functions * Added dynamic addition of shortcuts * Moved all shared variables in detection to state variable with wrapper functions * Added code to re-load saved shortcuts in the UI * Added comments * Fixed argument modifiers in Helpers * Updated arg modifiers in all functions * Removed unused headers and added precompiled headers
This commit is contained in:
committed by
Udit Singh
parent
fc7e7074ce
commit
b713083574
32
src/modules/keyboardmanager/dll/PowerKeys.rc
Normal file
32
src/modules/keyboardmanager/dll/PowerKeys.rc
Normal file
@@ -0,0 +1,32 @@
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 0,1,0,0
|
||||
PRODUCTVERSION 0,1,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x40004L
|
||||
FILETYPE 0x2L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Company Name"
|
||||
VALUE "FileDescription", "$projectname$ Module"
|
||||
VALUE "FileVersion", "0.1.0.0"
|
||||
VALUE "InternalName", "$projectname$"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2019 Company Name"
|
||||
VALUE "OriginalFilename", "$projectname$.dll"
|
||||
VALUE "ProductName", "$projectname$"
|
||||
VALUE "ProductVersion", "0.1.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200
|
||||
END
|
||||
END
|
||||
138
src/modules/keyboardmanager/dll/PowerKeys.vcxproj
Normal file
138
src/modules/keyboardmanager/dll/PowerKeys.vcxproj
Normal file
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{89f34af7-1c34-4a72-aa6e-534bcf972bd9}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>PowerKeys</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>PowerKeys</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
<Link>
|
||||
<AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|x64'">shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(SolutionDir)src\common\common.vcxproj">
|
||||
<Project>{74485049-c722-400f-abe5-86ac52d929b3}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\common\KeyboardManagerCommon.vcxproj">
|
||||
<Project>{8affa899-0b73-49ec-8c50-0fadda57b2fc}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\ui\PowerKeysUI.vcxproj">
|
||||
<Project>{eaf23649-ef6e-478b-980e-81fad96cca2a}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="PowerKeys.rc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
621
src/modules/keyboardmanager/dll/dllmain.cpp
Normal file
621
src/modules/keyboardmanager/dll/dllmain.cpp
Normal file
@@ -0,0 +1,621 @@
|
||||
#include "pch.h"
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include <interface/lowlevel_keyboard_event_data.h>
|
||||
#include <interface/win_hook_event_data.h>
|
||||
#include <common/settings_objects.h>
|
||||
#include "trace.h"
|
||||
#include <keyboardmanager/ui/MainWindow.h>
|
||||
#include <keyboardmanager/common/KeyboardManagerState.h>
|
||||
|
||||
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<DWORD> 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<DWORD>({ VK_LMENU, 0x44 })] = std::make_pair(std::vector<WORD>({ VK_LCONTROL, 0x56 }), false);
|
||||
//keyboardManagerState.osLevelShortcutReMap[std::vector<DWORD>({ VK_LMENU, 0x45 })] = std::make_pair(std::vector<WORD>({ VK_LCONTROL, 0x58 }), false);
|
||||
//keyboardManagerState.osLevelShortcutReMap[std::vector<DWORD>({ VK_LWIN, 0x46 })] = std::make_pair(std::vector<WORD>({ VK_LWIN, 0x53 }), false);
|
||||
//keyboardManagerState.osLevelShortcutReMap[std::vector<DWORD>({ VK_LWIN, 0x41 })] = std::make_pair(std::vector<WORD>({ VK_LCONTROL, 0x58 }), false);
|
||||
//keyboardManagerState.osLevelShortcutReMap[std::vector<DWORD>({ VK_LCONTROL, 0x58 })] = std::make_pair(std::vector<WORD>({ VK_LWIN, 0x41 }), false);
|
||||
|
||||
//keyboardManagerState.osLevelShortcutReMap[std::vector<DWORD>({ VK_LWIN, 0x41 })] = std::make_pair(std::vector<WORD>({ VK_LCONTROL, 0x58 }), false);
|
||||
//keyboardManagerState.osLevelShortcutReMap[std::vector<DWORD>({ VK_LCONTROL, 0x58 })] = std::make_pair(std::vector<WORD>({ VK_LMENU, 0x44 }), false);
|
||||
//keyboardManagerState.osLevelShortcutReMap[std::vector<DWORD>({ VK_LCONTROL, 0x56 })] = std::make_pair(std::vector<WORD>({ VK_LWIN, 0x41 }), false);
|
||||
|
||||
////App-specific shortcut remappings
|
||||
//keyboardManagerState.appSpecificShortcutReMap[L"msedge.exe"][std::vector<DWORD>({ VK_LCONTROL, 0x43 })] = std::make_pair(std::vector<WORD>({ VK_LCONTROL, 0x56 }), false); // Ctrl+C to Ctrl+V
|
||||
//keyboardManagerState.appSpecificShortcutReMap[L"msedge.exe"][std::vector<DWORD>({ VK_LMENU, 0x44 })] = std::make_pair(std::vector<WORD>({ VK_LCONTROL, 0x46 }), false); // Alt+D to Ctrl+F
|
||||
//keyboardManagerState.appSpecificShortcutReMap[L"OUTLOOK.EXE"][std::vector<DWORD>({ VK_LCONTROL, 0x46 })] = std::make_pair(std::vector<WORD>({ VK_LCONTROL, 0x45 }), false); // Ctrl+F to Ctrl+E
|
||||
//keyboardManagerState.appSpecificShortcutReMap[L"MicrosoftEdge.exe"][std::vector<DWORD>({ VK_LCONTROL, 0x58 })] = std::make_pair(std::vector<WORD>({ VK_LCONTROL, 0x56 }), false); // Ctrl+X to Ctrl+V
|
||||
//keyboardManagerState.appSpecificShortcutReMap[L"Calculator.exe"][std::vector<DWORD>({ VK_LCONTROL, 0x47 })] = std::make_pair(std::vector<WORD>({ 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<HINSTANCE>(&__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<HINSTANCE>(&__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<KBDLLHOOKSTRUCT*>(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::vector<DWORD>, std::pair<std::vector<WORD>, 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();
|
||||
}
|
||||
2
src/modules/keyboardmanager/dll/pch.cpp
Normal file
2
src/modules/keyboardmanager/dll/pch.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#include "pch.h"
|
||||
#pragma comment(lib, "windowsapp")
|
||||
8
src/modules/keyboardmanager/dll/pch.h
Normal file
8
src/modules/keyboardmanager/dll/pch.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <common/common.h>
|
||||
#include <ProjectTelemetry.h>
|
||||
#include <shlwapi.h>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
0
src/modules/keyboardmanager/dll/resource.h
Normal file
0
src/modules/keyboardmanager/dll/resource.h
Normal file
29
src/modules/keyboardmanager/dll/trace.cpp
Normal file
29
src/modules/keyboardmanager/dll/trace.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "pch.h"
|
||||
#include "trace.h"
|
||||
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hProvider,
|
||||
"Microsoft.PowerToys",
|
||||
// {38e8889b-9731-53f5-e901-e8a7c1753074}
|
||||
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
|
||||
TraceLoggingOptionProjectTelemetry());
|
||||
|
||||
void Trace::RegisterProvider()
|
||||
{
|
||||
TraceLoggingRegister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::UnregisterProvider()
|
||||
{
|
||||
TraceLoggingUnregister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::MyEvent()
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hProvider,
|
||||
"PowerToyName_MyEvent",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
9
src/modules/keyboardmanager/dll/trace.h
Normal file
9
src/modules/keyboardmanager/dll/trace.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
class Trace
|
||||
{
|
||||
public:
|
||||
static void RegisterProvider();
|
||||
static void UnregisterProvider();
|
||||
static void MyEvent();
|
||||
};
|
||||
Reference in New Issue
Block a user