2020-03-06 07:52:59 +08:00
# include "pch.h"
# include <interface/powertoy_module_interface.h>
# include <common/settings_objects.h>
2020-04-22 01:25:14 +08:00
# include <common/shared_constants.h>
2020-08-25 06:10:50 +08:00
# include "Generated Files/resource.h"
2020-04-15 00:24:11 +08:00
# include <keyboardmanager/ui/EditKeyboardWindow.h>
# include <keyboardmanager/ui/EditShortcutsWindow.h>
2020-03-24 01:44:02 +08:00
# include <keyboardmanager/common/KeyboardManagerState.h>
2020-04-09 00:11:58 +08:00
# include <keyboardmanager/common/Shortcut.h>
# include <keyboardmanager/common/RemapShortcut.h>
2020-04-20 23:22:36 +08:00
# include <keyboardmanager/common/KeyboardManagerConstants.h>
2020-05-06 03:30:50 +08:00
# include <common/settings_helpers.h>
2020-06-18 19:27:20 +08:00
# include <common/debug_control.h>
2020-05-06 03:30:50 +08:00
# include <keyboardmanager/common/trace.h>
2020-07-24 07:43:49 +08:00
# include <keyboardmanager/common/Helpers.h>
2020-05-29 05:47:32 +08:00
# include "KeyboardEventHandlers.h"
2020-06-12 04:07:46 +08:00
# include "Input.h"
2020-03-06 07:52:59 +08:00
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 ;
}
// Implement the PowerToy Module Interface and all the required methods.
2020-03-31 02:05:29 +08:00
class KeyboardManager : public PowertoyModuleIface
2020-03-06 07:52:59 +08:00
{
private :
// The PowerToy state.
bool m_enabled = false ;
2020-03-31 02:05:29 +08:00
// The PowerToy name that will be shown in the settings.
const std : : wstring app_name = GET_RESOURCE_STRING ( IDS_KEYBOARDMANAGER ) ;
2020-03-06 07:52:59 +08:00
// Low level hook handles
static HHOOK hook_handle ;
2020-03-24 01:44:02 +08:00
// Required for Unhook in old versions of Windows
static HHOOK hook_handle_copy ;
2020-03-31 02:05:29 +08:00
// Static pointer to the current keyboardmanager 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 KeyboardManager * keyboardmanager_object_ptr ;
2020-03-06 07:52:59 +08:00
2020-03-24 01:44:02 +08:00
// Variable which stores all the state information to be shared between the UI and back-end
KeyboardManagerState keyboardManagerState ;
2020-06-12 04:07:46 +08:00
// Object of class which implements InputInterface. Required for calling library functions while enabling testing
Input inputHandler ;
2020-03-06 07:52:59 +08:00
public :
// Constructor
2020-03-31 02:05:29 +08:00
KeyboardManager ( )
2020-03-06 07:52:59 +08:00
{
2020-04-20 23:22:36 +08:00
// Load the initial configuration.
load_config ( ) ;
2020-03-24 01:44:02 +08:00
// Set the static pointer to the newest object of the class
2020-03-31 02:05:29 +08:00
keyboardmanager_object_ptr = this ;
2020-03-06 07:52:59 +08:00
} ;
2020-04-20 23:22:36 +08:00
// Load config from the saved settings.
2020-04-23 23:37:52 +08:00
void load_config ( )
2020-04-20 23:22:36 +08:00
{
try
{
PowerToysSettings : : PowerToyValues settings =
PowerToysSettings : : PowerToyValues : : load_from_settings_file ( get_name ( ) ) ;
auto current_config = settings . get_string_value ( KeyboardManagerConstants : : ActiveConfigurationSettingName ) ;
if ( current_config )
{
keyboardManagerState . SetCurrentConfigName ( * current_config ) ;
// Read the config file and load the remaps.
auto configFile = json : : from_file ( PTSettingsHelper : : get_module_save_folder_location ( KeyboardManagerConstants : : ModuleName ) + L " \\ " + * current_config + L " .json " ) ;
if ( configFile )
{
auto jsonData = * configFile ;
2020-07-11 08:07:28 +08:00
// Load single key remaps
try
2020-04-20 23:22:36 +08:00
{
2020-07-11 08:07:28 +08:00
auto remapKeysData = jsonData . GetNamedObject ( KeyboardManagerConstants : : RemapKeysSettingName ) ;
keyboardManagerState . ClearSingleKeyRemaps ( ) ;
if ( remapKeysData )
2020-04-20 23:22:36 +08:00
{
2020-07-11 08:07:28 +08:00
auto inProcessRemapKeys = remapKeysData . GetNamedArray ( KeyboardManagerConstants : : InProcessRemapKeysSettingName ) ;
for ( const auto & it : inProcessRemapKeys )
2020-04-20 23:22:36 +08:00
{
2020-07-11 08:07:28 +08:00
try
{
auto originalKey = it . GetObjectW ( ) . GetNamedString ( KeyboardManagerConstants : : OriginalKeysSettingName ) ;
auto newRemapKey = it . GetObjectW ( ) . GetNamedString ( KeyboardManagerConstants : : NewRemapKeysSettingName ) ;
2020-07-24 07:43:49 +08:00
// If remapped to a shortcut
if ( std : : wstring ( newRemapKey ) . find ( L " ; " ) ! = std : : string : : npos )
{
keyboardManagerState . AddSingleKeyRemap ( std : : stoul ( originalKey . c_str ( ) ) , Shortcut ( newRemapKey . c_str ( ) ) ) ;
}
// If remapped to a key
else
{
keyboardManagerState . AddSingleKeyRemap ( std : : stoul ( originalKey . c_str ( ) ) , std : : stoul ( newRemapKey . c_str ( ) ) ) ;
}
2020-07-11 08:07:28 +08:00
}
catch ( . . . )
{
// Improper Key Data JSON. Try the next remap.
}
2020-04-20 23:22:36 +08:00
}
}
}
2020-07-11 08:07:28 +08:00
catch ( . . . )
{
// Improper JSON format for single key remaps. Skip to next remap type
}
2020-04-20 23:22:36 +08:00
2020-07-11 08:07:28 +08:00
// Load shortcut remaps
try
2020-04-20 23:22:36 +08:00
{
2020-07-11 08:07:28 +08:00
auto remapShortcutsData = jsonData . GetNamedObject ( KeyboardManagerConstants : : RemapShortcutsSettingName ) ;
keyboardManagerState . ClearOSLevelShortcuts ( ) ;
keyboardManagerState . ClearAppSpecificShortcuts ( ) ;
if ( remapShortcutsData )
2020-04-20 23:22:36 +08:00
{
2020-07-11 08:07:28 +08:00
// Load os level shortcut remaps
try
{
auto globalRemapShortcuts = remapShortcutsData . GetNamedArray ( KeyboardManagerConstants : : GlobalRemapShortcutsSettingName ) ;
for ( const auto & it : globalRemapShortcuts )
{
try
{
auto originalKeys = it . GetObjectW ( ) . GetNamedString ( KeyboardManagerConstants : : OriginalKeysSettingName ) ;
auto newRemapKeys = it . GetObjectW ( ) . GetNamedString ( KeyboardManagerConstants : : NewRemapKeysSettingName ) ;
2020-07-24 07:43:49 +08:00
// If remapped to a shortcut
if ( std : : wstring ( newRemapKeys ) . find ( L " ; " ) ! = std : : string : : npos )
{
keyboardManagerState . AddOSLevelShortcut ( Shortcut ( originalKeys . c_str ( ) ) , Shortcut ( newRemapKeys . c_str ( ) ) ) ;
}
// If remapped to a key
else
{
keyboardManagerState . AddOSLevelShortcut ( Shortcut ( originalKeys . c_str ( ) ) , std : : stoul ( newRemapKeys . c_str ( ) ) ) ;
}
2020-07-11 08:07:28 +08:00
}
catch ( . . . )
{
// Improper Key Data JSON. Try the next shortcut.
}
}
}
catch ( . . . )
{
// Improper JSON format for os level shortcut remaps. Skip to next remap type
}
// Load app specific shortcut remaps
2020-04-20 23:22:36 +08:00
try
{
2020-07-11 08:07:28 +08:00
auto appSpecificRemapShortcuts = remapShortcutsData . GetNamedArray ( KeyboardManagerConstants : : AppSpecificRemapShortcutsSettingName ) ;
for ( const auto & it : appSpecificRemapShortcuts )
{
try
{
auto originalKeys = it . GetObjectW ( ) . GetNamedString ( KeyboardManagerConstants : : OriginalKeysSettingName ) ;
auto newRemapKeys = it . GetObjectW ( ) . GetNamedString ( KeyboardManagerConstants : : NewRemapKeysSettingName ) ;
auto targetApp = it . GetObjectW ( ) . GetNamedString ( KeyboardManagerConstants : : TargetAppSettingName ) ;
2020-07-24 07:43:49 +08:00
// If remapped to a shortcut
if ( std : : wstring ( newRemapKeys ) . find ( L " ; " ) ! = std : : string : : npos )
{
keyboardManagerState . AddAppSpecificShortcut ( targetApp . c_str ( ) , Shortcut ( originalKeys . c_str ( ) ) , Shortcut ( newRemapKeys . c_str ( ) ) ) ;
}
// If remapped to a key
else
{
keyboardManagerState . AddAppSpecificShortcut ( targetApp . c_str ( ) , Shortcut ( originalKeys . c_str ( ) ) , std : : stoul ( newRemapKeys . c_str ( ) ) ) ;
}
2020-07-11 08:07:28 +08:00
}
catch ( . . . )
{
// Improper Key Data JSON. Try the next shortcut.
}
}
2020-04-20 23:22:36 +08:00
}
catch ( . . . )
{
2020-07-11 08:07:28 +08:00
// Improper JSON format for os level shortcut remaps. Skip to next remap type
2020-04-20 23:22:36 +08:00
}
}
}
2020-07-11 08:07:28 +08:00
catch ( . . . )
{
// Improper JSON format for shortcut remaps. Skip to next remap type
}
2020-04-20 23:22:36 +08:00
}
}
}
catch ( . . . )
{
// Unable to load inital config.
}
}
2020-03-06 07:52:59 +08:00
// 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
{
2020-03-31 02:05:29 +08:00
return app_name . c_str ( ) ;
2020-03-06 07:52:59 +08:00
}
// 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 ( ) ) ;
2020-03-31 02:05:29 +08:00
settings . set_description ( IDS_SETTINGS_DESCRIPTION ) ;
2020-06-12 01:16:39 +08:00
settings . set_overview_link ( L " https://aka.ms/PowerToysOverview_KeyboardManager " ) ;
2020-03-06 07:52:59 +08:00
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 ) ;
2020-04-15 00:24:11 +08:00
HINSTANCE hInstance = reinterpret_cast < HINSTANCE > ( & __ImageBase ) ;
2020-04-17 06:17:57 +08:00
if ( action_object . get_name ( ) = = L " RemapKeyboard " )
2020-04-15 00:24:11 +08:00
{
2020-05-02 08:34:42 +08:00
if ( ! CheckEditKeyboardWindowActive ( ) & & ! CheckEditShortcutsWindowActive ( ) )
2020-04-15 00:24:11 +08:00
{
std : : thread ( createEditKeyboardWindow , hInstance , std : : ref ( keyboardManagerState ) ) . detach ( ) ;
}
}
else if ( action_object . get_name ( ) = = L " EditShortcut " )
{
2020-05-02 08:34:42 +08:00
if ( ! CheckEditKeyboardWindowActive ( ) & & ! CheckEditShortcutsWindowActive ( ) )
2020-04-15 00:24:11 +08:00
{
std : : thread ( createEditShortcutsWindow , hInstance , std : : ref ( keyboardManagerState ) ) . detach ( ) ;
}
}
2020-03-06 07:52:59 +08:00
}
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 ;
2020-05-06 03:30:50 +08:00
// Log telemetry
Trace : : EnableKeyboardManager ( true ) ;
// Start keyboard hook
2020-03-06 07:52:59 +08:00
start_lowlevel_keyboard_hook ( ) ;
}
// Disable the powertoy
virtual void disable ( )
{
m_enabled = false ;
2020-05-06 03:30:50 +08:00
// Log telemetry
Trace : : EnableKeyboardManager ( false ) ;
2020-05-13 06:58:11 +08:00
// Close active windows
CloseActiveEditKeyboardWindow ( ) ;
CloseActiveEditShortcutsWindow ( ) ;
2020-05-06 03:30:50 +08:00
// Stop keyboard hook
2020-03-06 07:52:59 +08:00
stop_lowlevel_keyboard_hook ( ) ;
}
// Returns if the powertoys is enabled
virtual bool is_enabled ( ) override
{
return m_enabled ;
}
// 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 ;
2020-03-31 02:05:29 +08:00
if ( keyboardmanager_object_ptr - > HandleKeyboardHookEvent ( & event ) = = 1 )
2020-03-06 07:52:59 +08:00
{
2020-06-06 03:54:52 +08:00
// Reset Num Lock whenever a NumLock key down event is suppressed since Num Lock key state change occurs before it is intercepted by low level hooks
if ( event . lParam - > vkCode = = VK_NUMLOCK & & ( event . wParam = = WM_KEYDOWN | | event . wParam = = WM_SYSKEYDOWN ) & & event . lParam - > dwExtraInfo ! = KeyboardManagerConstants : : KEYBOARDMANAGER_SUPPRESS_FLAG )
{
2020-06-12 04:07:46 +08:00
KeyboardEventHandlers : : SetNumLockToPreviousState ( keyboardmanager_object_ptr - > inputHandler ) ;
2020-06-06 03:54:52 +08:00
}
2020-03-06 07:52:59 +08:00
return 1 ;
}
}
return CallNextHookEx ( hook_handle_copy , nCode , wParam , lParam ) ;
}
void start_lowlevel_keyboard_hook ( )
{
2020-06-19 18:27:29 +08:00
# if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED)
2020-03-06 07:52:59 +08:00
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 )
{
2020-09-10 05:27:40 +08:00
DWORD errorCode = GetLastError ( ) ;
show_last_error_message ( L " SetWindowsHookEx " , errorCode , L " PowerToys - Keyboard Manager " ) ;
auto errorMessage = get_last_error_message ( errorCode ) ;
Trace : : Error ( errorCode , errorMessage . has_value ( ) ? errorMessage . value ( ) : L " " , L " start_lowlevel_keyboard_hook.SetWindowsHookEx " ) ;
2020-03-06 07:52:59 +08:00
}
}
}
2020-03-24 01:44:02 +08:00
// Function to terminate the low level hook
2020-03-06 07:52:59 +08:00
void stop_lowlevel_keyboard_hook ( )
{
if ( hook_handle )
{
UnhookWindowsHookEx ( hook_handle ) ;
hook_handle = nullptr ;
}
}
2020-03-24 01:44:02 +08:00
// Function called by the hook procedure to handle the events. This is the starting point function for remapping
intptr_t HandleKeyboardHookEvent ( LowlevelKeyboardEvent * data ) noexcept
2020-03-06 07:52:59 +08:00
{
2020-10-09 02:28:24 +08:00
// If remappings are disabled (due to the remap tables getting updated) skip the rest of the hook
if ( ! keyboardManagerState . AreRemappingsEnabled ( ) )
{
return 0 ;
}
2020-06-06 03:54:52 +08:00
// If key has suppress flag, then suppress it
if ( data - > lParam - > dwExtraInfo = = KeyboardManagerConstants : : KEYBOARDMANAGER_SUPPRESS_FLAG )
{
return 1 ;
}
2020-03-24 01:44:02 +08:00
// If the Detect Key Window is currently activated, then suppress the keyboard event
2020-05-05 06:49:37 +08:00
KeyboardManagerHelper : : KeyboardHookDecision singleKeyRemapUIDetected = keyboardManagerState . DetectSingleRemapKeyUIBackend ( data ) ;
if ( singleKeyRemapUIDetected = = KeyboardManagerHelper : : KeyboardHookDecision : : Suppress )
2020-03-06 07:52:59 +08:00
{
2020-03-24 01:44:02 +08:00
return 1 ;
2020-03-06 07:52:59 +08:00
}
2020-05-05 06:49:37 +08:00
else if ( singleKeyRemapUIDetected = = KeyboardManagerHelper : : KeyboardHookDecision : : SkipHook )
{
return 0 ;
}
2020-03-06 07:52:59 +08:00
2020-07-24 07:43:49 +08:00
// If the Detect Shortcut Window from Remap Keys is currently activated, then suppress the keyboard event
KeyboardManagerHelper : : KeyboardHookDecision remapKeyShortcutUIDetected = keyboardManagerState . DetectShortcutUIBackend ( data , true ) ;
if ( remapKeyShortcutUIDetected = = KeyboardManagerHelper : : KeyboardHookDecision : : Suppress )
{
return 1 ;
}
else if ( remapKeyShortcutUIDetected = = KeyboardManagerHelper : : KeyboardHookDecision : : SkipHook )
{
return 0 ;
}
2020-03-24 01:44:02 +08:00
// Remap a key
2020-06-12 04:07:46 +08:00
intptr_t SingleKeyRemapResult = KeyboardEventHandlers : : HandleSingleKeyRemapEvent ( inputHandler , data , keyboardManagerState ) ;
2020-03-24 01:44:02 +08:00
// 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.
2020-03-06 07:52:59 +08:00
if ( SingleKeyRemapResult = = 1 )
{
return 1 ;
}
2020-03-24 01:44:02 +08:00
// If the Detect Shortcut Window is currently activated, then suppress the keyboard event
2020-07-24 07:43:49 +08:00
KeyboardManagerHelper : : KeyboardHookDecision shortcutUIDetected = keyboardManagerState . DetectShortcutUIBackend ( data , false ) ;
2020-05-05 06:49:37 +08:00
if ( shortcutUIDetected = = KeyboardManagerHelper : : KeyboardHookDecision : : Suppress )
2020-03-24 01:44:02 +08:00
{
return 1 ;
}
2020-05-05 06:49:37 +08:00
else if ( shortcutUIDetected = = KeyboardManagerHelper : : KeyboardHookDecision : : SkipHook )
{
return 0 ;
}
2020-03-24 01:44:02 +08:00
2020-10-09 02:28:24 +08:00
/* This feature has not been enabled (code from proof of concept stage)
*
2020-05-05 06:49:37 +08:00
//// Remap a key to behave like a modifier instead of a toggle
2020-06-12 04:07:46 +08:00
//intptr_t SingleKeyToggleToModResult = KeyboardEventHandlers::HandleSingleKeyToggleToModEvent(inputHandler, data, keyboardManagerState);
2020-10-09 02:28:24 +08:00
*/
2020-03-24 01:44:02 +08:00
2020-07-07 07:45:53 +08:00
// Handle an app-specific shortcut remapping
intptr_t AppSpecificShortcutRemapResult = KeyboardEventHandlers : : HandleAppSpecificShortcutRemapEvent ( inputHandler , data , keyboardManagerState ) ;
2020-03-24 01:44:02 +08:00
2020-07-07 07:45:53 +08:00
// If an app-specific shortcut is remapped then the os-level shortcut remapping should be suppressed.
if ( AppSpecificShortcutRemapResult = = 1 )
2020-03-06 07:52:59 +08:00
{
return 1 ;
}
2020-07-07 07:45:53 +08:00
// Handle an os-level shortcut remapping
return KeyboardEventHandlers : : HandleOSLevelShortcutRemapEvent ( inputHandler , data , keyboardManagerState ) ;
2020-03-06 07:52:59 +08:00
}
} ;
2020-03-31 02:05:29 +08:00
HHOOK KeyboardManager : : hook_handle = nullptr ;
HHOOK KeyboardManager : : hook_handle_copy = nullptr ;
KeyboardManager * KeyboardManager : : keyboardmanager_object_ptr = nullptr ;
2020-03-06 07:52:59 +08:00
extern " C " __declspec ( dllexport ) PowertoyModuleIface * __cdecl powertoy_create ( )
{
2020-03-31 02:05:29 +08:00
return new KeyboardManager ( ) ;
2020-03-06 07:52:59 +08:00
}