[Launcher] Use a keyboard hook in the runner to invoke the Launcher (#6660)

* Added a keyboard hook to the runner

* Update RootKeyboardHook

* Enable reading the whole JsonObject property

* Renamed RootKeyboardHook to CentralizedKeyboardHook

* Fixed build break, changed callback return type to bool

* Added Hotkey struct which somehow went missing

+ Cherry-pick fixes

* Reorganized the kb hook

* Basic version works

* Various fixes

* Finishing touches

* Fix potential threading issue

* int -> size_t

* Add default initializers to the Hotkey struct

* Added a suggested comment

* Unified a constant

* Use C# classes instead of native calls for sync

* Added a claryfing comment

* Use std::move

* Renamed a method

* Possible fix for compilation errors

* Fix a regression

* Show a message on failure

* Added DISABLE_LOWLEVEL_HOOK support

* Allow running Launcher as standalone

* Rename string constants
This commit is contained in:
Ivan Stošić
2020-09-21 12:44:16 +02:00
committed by GitHub
parent e135153c45
commit b266e336b5
20 changed files with 392 additions and 40 deletions

View File

@@ -0,0 +1,131 @@
#include "pch.h"
#include "centralized_kb_hook.h"
#include "common/common.h"
namespace CentralizedKeyboardHook
{
struct HotkeyDescriptor
{
Hotkey hotkey;
std::wstring moduleName;
std::function<bool()> action;
bool operator<(const HotkeyDescriptor& other) const
{
return hotkey < other.hotkey;
};
};
std::multiset<HotkeyDescriptor> hotkeyDescriptors;
std::mutex mutex;
HHOOK hHook{};
struct DestroyOnExit
{
~DestroyOnExit()
{
Stop();
}
} destroyOnExitObj;
LRESULT CALLBACK KeyboardHookProc(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
if (nCode < 0 || ((wParam != WM_KEYDOWN) && (wParam != WM_SYSKEYDOWN)))
{
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
const auto& keyPressInfo = *reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
Hotkey hotkey{
.win = (GetAsyncKeyState(VK_LWIN) & 0x8000) || (GetAsyncKeyState(VK_RWIN) & 0x8000),
.ctrl = static_cast<bool>(GetAsyncKeyState(VK_CONTROL) & 0x8000),
.shift = static_cast<bool>(GetAsyncKeyState(VK_SHIFT) & 0x8000),
.alt = static_cast<bool>(GetAsyncKeyState(VK_MENU) & 0x8000),
.key = static_cast<unsigned char>(keyPressInfo.vkCode)
};
std::function<bool()> action;
{
// Hold the lock for the shortest possible duration
std::unique_lock lock{ mutex };
HotkeyDescriptor dummy{ .hotkey = hotkey };
auto it = hotkeyDescriptors.find(dummy);
if (it != hotkeyDescriptors.end())
{
action = it->action;
}
}
if (action)
{
if (action())
{
// After invoking the hotkey send a dummy key to prevent Start Menu from activating
INPUT dummyEvent[1] = {};
dummyEvent[0].type = INPUT_KEYBOARD;
dummyEvent[0].ki.wVk = 0xFF;
dummyEvent[0].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, dummyEvent, sizeof(INPUT));
// Swallow the key press
return 1;
}
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
void SetHotkeyAction(const std::wstring& moduleName, const Hotkey& hotkey, std::function<bool()>&& action) noexcept
{
std::unique_lock lock{ mutex };
hotkeyDescriptors.insert({ .hotkey = hotkey, .moduleName = moduleName, .action = std::move(action) });
}
void ClearModuleHotkeys(const std::wstring& moduleName) noexcept
{
std::unique_lock lock{ mutex };
auto it = hotkeyDescriptors.begin();
while (it != hotkeyDescriptors.end())
{
if (it->moduleName == moduleName)
{
it = hotkeyDescriptors.erase(it);
}
else
{
++it;
}
}
}
void Start() noexcept
{
#if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED)
const bool hook_disabled = IsDebuggerPresent();
#else
const bool hook_disabled = false;
#endif
if (!hook_disabled)
{
if (!hHook)
{
hHook = SetWindowsHookExW(WH_KEYBOARD_LL, KeyboardHookProc, NULL, NULL);
if (!hHook)
{
DWORD errorCode = GetLastError();
show_last_error_message(L"SetWindowsHookEx", errorCode, L"centralized_kb_hook");
}
}
}
}
void Stop() noexcept
{
if (hHook && UnhookWindowsHookEx(hHook))
{
hHook = NULL;
}
}
}

View File

@@ -0,0 +1,13 @@
#include "pch.h"
#include "../modules/interface/powertoy_module_interface.h"
namespace CentralizedKeyboardHook
{
using Hotkey = PowertoyModuleIface::Hotkey;
void Start() noexcept;
void Stop() noexcept;
void SetHotkeyAction(const std::wstring& moduleName, const Hotkey& hotkey, std::function<bool()>&& action) noexcept;
void ClearModuleHotkeys(const std::wstring& moduleName) noexcept;
};

View File

@@ -27,6 +27,7 @@
#include <Psapi.h>
#include <RestartManager.h>
#include "centralized_kb_hook.h"
#if _DEBUG && _WIN64
#include "unhandled_exception_handler.h"
@@ -89,6 +90,7 @@ int runner(bool isProcessElevated)
#endif
Trace::RegisterProvider();
start_tray_icon();
CentralizedKeyboardHook::Start();
int result = -1;
try

View File

@@ -24,6 +24,7 @@
#include <tuple>
#include <unordered_set>
#include <string>
#include <set>
#include <ProjectTelemetry.h>
#include <winrt/Windows.ApplicationModel.h>

View File

@@ -1,5 +1,6 @@
#include "pch.h"
#include "powertoy_module.h"
#include "centralized_kb_hook.h"
std::map<std::wstring, PowertoyModule>& modules()
{
@@ -42,4 +43,24 @@ PowertoyModule::PowertoyModule(PowertoyModuleIface* module, HMODULE handle) :
{
throw std::runtime_error("Module not initialized");
}
update_hotkeys();
}
void PowertoyModule::update_hotkeys()
{
CentralizedKeyboardHook::ClearModuleHotkeys(module->get_name());
size_t hotkeyCount = module->get_hotkeys(nullptr, 0);
std::vector<PowertoyModuleIface::Hotkey> hotkeys(hotkeyCount);
module->get_hotkeys(hotkeys.data(), hotkeyCount);
auto modulePtr = module.get();
for (size_t i = 0; i < hotkeyCount; i++)
{
CentralizedKeyboardHook::SetHotkeyAction(module->get_name(), hotkeys[i], [modulePtr, i] {
return modulePtr->on_hotkey(i);
});
}
}

View File

@@ -40,6 +40,8 @@ public:
json::JsonObject json_config() const;
void update_hotkeys();
private:
std::unique_ptr<HMODULE, PowertoyModuleDLLDeleter> handle;
std::unique_ptr<PowertoyModuleIface, PowertoyModuleDeleter> module;

View File

@@ -120,6 +120,7 @@
<ClCompile Include="powertoy_module.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="restart_elevated.cpp" />
<ClCompile Include="centralized_kb_hook.cpp" />
<ClCompile Include="settings_window.cpp" />
<ClCompile Include="trace.cpp" />
<ClCompile Include="tray_icon.cpp" />
@@ -132,6 +133,7 @@
<ClInclude Include="auto_start_helper.h" />
<ClInclude Include="general_settings.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="centralized_kb_hook.h" />
<ClInclude Include="update_utils.h" />
<ClInclude Include="update_state.h" />
<ClInclude Include="powertoy_module.h" />

View File

@@ -36,6 +36,9 @@
<ClCompile Include="action_runner_utils.cpp">
<Filter>Utils</Filter>
</ClCompile>
<ClCompile Include="centralized_kb_hook.cpp">
<Filter>Utils</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -73,7 +76,10 @@
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="centralized_kb_hook.h">
<Filter>Utils</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>

View File

@@ -12,6 +12,7 @@
#include "common/common.h"
#include "restart_elevated.h"
#include "update_utils.h"
#include "centralized_kb_hook.h"
#include <common/json.h>
#include <common\settings_helpers.cpp>
@@ -106,9 +107,11 @@ std::optional<std::wstring> dispatch_json_action_to_module(const json::JsonObjec
void send_json_config_to_module(const std::wstring& module_key, const std::wstring& settings)
{
if (modules().find(module_key) != modules().end())
auto moduleIt = modules().find(module_key);
if (moduleIt != modules().end())
{
modules().at(module_key)->set_config(settings.c_str());
moduleIt->second->set_config(settings.c_str());
moduleIt->second.update_hotkeys();
}
}