[KBM] Migrate Engine and Editor into separate processes (#10774)

* Move KBM engine into separate process (#10672)

* [KBM] Migrate KBM UI out of the runner (#10709)

* Clean up keyboard hook handles (#10817)

* [C++ common] Unhandled exception handler (#10821)

* [KBM] Use icon in the KeyboardManagerEditor (#10845)

* [KBM] Move resources from the Common project to the Editor. (#10844)

* KBM Editor tests (#10858)

* Rename engine executable (#10868)

* clean up (#10870)

* [KBM] Changed Editor and libraries output folders (#10871)

* [KBM] New logs structure (#10872)

* Add unhandled exception handling to the editor (#10874)

* [KBM] Trace for edit keyboard window

* Logging for XamlBridge message loop

* [KBM] Added Editor and Engine to the installer (#10876)

* Fix spelling

* Interprocess communication logs, remove unnecessary windows message logs

* [KBM] Separated telemetry for the engine and editor. (#10889)

* [KBM] Editor test project (#10891)

* Versions for the engine and the editor (#10897)

* Add the editor's and the engine's executables to signing process (#10900)

* [KBM editor] Run only one instance, exit when parent process exits (#10890)

* [KBM] Force kill editor process to avoid XAML crash (#10907)

* [KBM] Force kill editor process to avoid XAML crash

* Fix event releasing

Co-authored-by: mykhailopylyp <17161067+mykhailopylyp@users.noreply.github.com>

* Make the editor dpi aware (#10908)

* [KBM] KeyboardManagerCommon refactoring (#10909)

* Do not start the process if it is already started (#10910)

* logs

* Update src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp

* Update src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp

* [KBM] Rename InitUnhandledExceptionHandler
to make it explicit that is for x64 only.
We will fix it properly when adding support for ARM64 and add a header with
the proper conditional building.

* [KBM] rename file/class/variables using camel case

* [KBM] Rename "event_locker" -> "EventLocker"

* [KBM] rename process_waiter
Add a TODO comment

* [KBM] rename methods
Add TODO comment

* [KBM] use uppercase for function names

* [KBM] use uppercase for methos, lowercase for properties

* [KBM] rename method, make methods private, formatting

* [KBM] rename private variables

* [KBM] use uppercase for function names

* [KBM] Added support to run the editor stand-alone when built in debug mode

* Update src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp

* Check success of event creation, comment (#10947)

* [KBM] code formatting (#10951)

* [KBM] code formatting

* Update src/modules/keyboardmanager/KeyboardManagerEditorLibrary/BufferValidationHelpers.cpp

* [KBM] tracing

* [KBM] Remappings not showing fix. (#10954)

* removed mutex

* retry loop for reading

* retry on reading config once

* log error

Co-authored-by: Enrico Giordani <enricogior@users.noreply.github.com>

Co-authored-by: Enrico Giordani <enricogior@users.noreply.github.com>

Co-authored-by: Seraphima Zykova <zykovas91@gmail.com>
Co-authored-by: Enrico Giordani <enricogior@users.noreply.github.com>
Co-authored-by: Enrico Giordani <enrico.giordani@gmail.com>
This commit is contained in:
Mykhailo Pylyp
2021-04-26 22:01:38 +03:00
committed by GitHub
parent e9a0b58796
commit a8c99e9513
141 changed files with 5124 additions and 2374 deletions

View File

@@ -1,21 +1,13 @@
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/interop/shared_constants.h>
#include <common/utils/resources.h>
#include "Generated Files/resource.h"
#include <keyboardmanager/ui/EditKeyboardWindow.h>
#include <keyboardmanager/ui/EditShortcutsWindow.h>
#include <keyboardmanager/common/KeyboardManagerState.h>
#include <keyboardmanager/common/Shortcut.h>
#include <keyboardmanager/common/RemapShortcut.h>
#include <keyboardmanager/common/KeyboardManagerConstants.h>
#include <common/debug_control.h>
#include <common/utils/winapi_error.h>
#include <common/logger/logger_settings.h>
#include <keyboardmanager/common/trace.h>
#include <keyboardmanager/common/Helpers.h>
#include "KeyboardEventHandlers.h"
#include "Input.h"
#include <keyboardmanager/dll/trace.h>
#include <shellapi.h>
#include <common/utils/logger_helper.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
@@ -47,189 +39,21 @@ private:
//contains the non localized key of the powertoy
std::wstring app_key = KeyboardManagerConstants::ModuleName;
// 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 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;
// Variable which stores all the state information to be shared between the UI and back-end
KeyboardManagerState keyboardManagerState;
// Object of class which implements InputInterface. Required for calling library functions while enabling testing
Input inputHandler;
HANDLE m_hProcess = nullptr;
public:
// Constructor
KeyboardManager()
{
std::filesystem::path logFilePath(PTSettingsHelper::get_module_save_folder_location(app_key));
logFilePath.append(LogSettings::keyboardManagerLogPath);
Logger::init(LogSettings::keyboardManagerLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location());
// Load the initial configuration.
load_config();
// Set the static pointer to the newest object of the class
keyboardmanager_object_ptr = this;
LoggerHelpers::init_logger(KeyboardManagerConstants::ModuleName, L"ModuleInterface", LogSettings::keyboardManagerLoggerName);
std::filesystem::path oldLogPath(PTSettingsHelper::get_module_save_folder_location(app_key));
oldLogPath.append("Logs");
LoggerHelpers::delete_old_log_folder(oldLogPath);
};
// Load config from the saved settings.
void load_config()
{
try
{
PowerToysSettings::PowerToyValues settings =
PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
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;
// Load single key remaps
try
{
auto remapKeysData = jsonData.GetNamedObject(KeyboardManagerConstants::RemapKeysSettingName);
keyboardManagerState.ClearSingleKeyRemaps();
if (remapKeysData)
{
auto inProcessRemapKeys = remapKeysData.GetNamedArray(KeyboardManagerConstants::InProcessRemapKeysSettingName);
for (const auto& it : inProcessRemapKeys)
{
try
{
auto originalKey = it.GetObjectW().GetNamedString(KeyboardManagerConstants::OriginalKeysSettingName);
auto newRemapKey = it.GetObjectW().GetNamedString(KeyboardManagerConstants::NewRemapKeysSettingName);
// 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()));
}
}
catch (...)
{
// Improper Key Data JSON. Try the next remap.
}
}
}
}
catch (...)
{
// Improper JSON format for single key remaps. Skip to next remap type
}
// Load shortcut remaps
try
{
auto remapShortcutsData = jsonData.GetNamedObject(KeyboardManagerConstants::RemapShortcutsSettingName);
keyboardManagerState.ClearOSLevelShortcuts();
keyboardManagerState.ClearAppSpecificShortcuts();
if (remapShortcutsData)
{
// 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);
// 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()));
}
}
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
try
{
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);
// 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()));
}
}
catch (...)
{
// Improper Key Data JSON. Try the next shortcut.
}
}
}
catch (...)
{
// Improper JSON format for os level shortcut remaps. Skip to next remap type
}
}
}
catch (...)
{
// Improper JSON format for shortcut remaps. Skip to next remap type
}
}
}
}
catch (...)
{
// Unable to load inital config.
}
}
// Destroy the powertoy and free memory
virtual void destroy() override
{
stop_lowlevel_keyboard_hook();
delete this;
}
@@ -259,36 +83,8 @@ public:
}
// 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);
HINSTANCE hInstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
if (action_object.get_name() == L"RemapKeyboard")
{
if (!CheckEditKeyboardWindowActive() && !CheckEditShortcutsWindowActive())
{
std::thread(createEditKeyboardWindow, hInstance, std::ref(keyboardManagerState)).detach();
}
}
else if (action_object.get_name() == L"EditShortcut")
{
if (!CheckEditKeyboardWindowActive() && !CheckEditShortcutsWindowActive())
{
std::thread(createEditShortcutsWindow, hInstance, std::ref(keyboardManagerState)).detach();
}
}
}
catch (std::exception&)
{
// Improper JSON.
}
}
// Called by the runner to pass the updated settings values as a serialized JSON.
@@ -316,8 +112,30 @@ public:
m_enabled = true;
// Log telemetry
Trace::EnableKeyboardManager(true);
// Start keyboard hook
start_lowlevel_keyboard_hook();
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid));
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
sei.lpFile = L"modules\\KeyboardManager\\KeyboardManagerEngine\\PowerToys.KeyboardManagerEngine.exe";
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = executable_args.data();
if (ShellExecuteExW(&sei) == false)
{
Logger::error(L"Failed to start keyboard manager engine");
auto message = get_last_error_message(GetLastError());
if (message.has_value())
{
Logger::error(message.value());
}
}
else
{
m_hProcess = sei.hProcess;
SetPriorityClass(m_hProcess, REALTIME_PRIORITY_CLASS);
}
}
// Disable the powertoy
@@ -326,11 +144,12 @@ public:
m_enabled = false;
// Log telemetry
Trace::EnableKeyboardManager(false);
// Close active windows
CloseActiveEditKeyboardWindow();
CloseActiveEditShortcutsWindow();
// Stop keyboard hook
stop_lowlevel_keyboard_hook();
if (m_hProcess)
{
TerminateProcess(m_hProcess, 0);
m_hProcess = nullptr;
}
}
// Returns if the powertoys is enabled
@@ -338,142 +157,8 @@ public:
{
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;
if (keyboardmanager_object_ptr->HandleKeyboardHookEvent(&event) == 1)
{
// 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)
{
KeyboardEventHandlers::SetNumLockToPreviousState(keyboardmanager_object_ptr->inputHandler);
}
return 1;
}
}
return CallNextHookEx(hook_handle_copy, nCode, wParam, lParam);
}
void start_lowlevel_keyboard_hook()
{
#if defined(DISABLE_LOWLEVEL_HOOKS_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)
{
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");
}
}
}
// 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 remappings are disabled (due to the remap tables getting updated) skip the rest of the hook
if (!keyboardManagerState.AreRemappingsEnabled())
{
return 0;
}
// If key has suppress flag, then suppress it
if (data->lParam->dwExtraInfo == KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG)
{
return 1;
}
// If the Detect Key Window is currently activated, then suppress the keyboard event
KeyboardManagerHelper::KeyboardHookDecision singleKeyRemapUIDetected = keyboardManagerState.DetectSingleRemapKeyUIBackend(data);
if (singleKeyRemapUIDetected == KeyboardManagerHelper::KeyboardHookDecision::Suppress)
{
return 1;
}
else if (singleKeyRemapUIDetected == KeyboardManagerHelper::KeyboardHookDecision::SkipHook)
{
return 0;
}
// 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;
}
// Remap a key
intptr_t SingleKeyRemapResult = KeyboardEventHandlers::HandleSingleKeyRemapEvent(inputHandler, data, keyboardManagerState);
// 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
KeyboardManagerHelper::KeyboardHookDecision shortcutUIDetected = keyboardManagerState.DetectShortcutUIBackend(data, false);
if (shortcutUIDetected == KeyboardManagerHelper::KeyboardHookDecision::Suppress)
{
return 1;
}
else if (shortcutUIDetected == KeyboardManagerHelper::KeyboardHookDecision::SkipHook)
{
return 0;
}
/* This feature has not been enabled (code from proof of concept stage)
*
//// Remap a key to behave like a modifier instead of a toggle
//intptr_t SingleKeyToggleToModResult = KeyboardEventHandlers::HandleSingleKeyToggleToModEvent(inputHandler, data, keyboardManagerState);
*/
// Handle an app-specific shortcut remapping
intptr_t AppSpecificShortcutRemapResult = KeyboardEventHandlers::HandleAppSpecificShortcutRemapEvent(inputHandler, data, keyboardManagerState);
// 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
return KeyboardEventHandlers::HandleOSLevelShortcutRemapEvent(inputHandler, data, keyboardManagerState);
}
};
HHOOK KeyboardManager::hook_handle = nullptr;
HHOOK KeyboardManager::hook_handle_copy = nullptr;
KeyboardManager* KeyboardManager::keyboardmanager_object_ptr = nullptr;
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new KeyboardManager();