[AOT] Low level keyboard hook (#18643)

* low level keyboard hook for always on top

* fix for message loop

* fix spellcheck

* refactoring and cleanup

* spellcheck
This commit is contained in:
Davide Giacometti 2022-06-23 16:29:53 +02:00 committed by GitHub
parent 2159e2722e
commit 301f26aca1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 221 additions and 6 deletions

View File

@ -35,6 +35,7 @@ ALarger
alekhyareddy alekhyareddy
alignas alignas
ALLAPPS ALLAPPS
ALLINPUT
Alloc Alloc
ALLOWUNDO ALLOWUNDO
ALPHATYPE ALPHATYPE
@ -1096,6 +1097,7 @@ LINQTo
Linux Linux
listview listview
lld lld
LLKH
llkhf llkhf
Llvm Llvm
lmcons lmcons

View File

@ -34,6 +34,9 @@ namespace CommonSharedConstants
// Path to the event used by Awake // Path to the event used by Awake
const wchar_t AWAKE_EXIT_EVENT[] = L"Local\\PowerToysAwakeExitEvent-c0d5e305-35fc-4fb5-83ec-f6070cfaf7fe"; const wchar_t AWAKE_EXIT_EVENT[] = L"Local\\PowerToysAwakeExitEvent-c0d5e305-35fc-4fb5-83ec-f6070cfaf7fe";
// Path to the event used by AlwaysOnTop
const wchar_t ALWAYS_ON_TOP_PIN_EVENT[] = L"Local\\AlwaysOnTopPinEvent-892e0aa2-cfa8-4cc4-b196-ddeb32314ce8";
// Max DWORD for key code to disable keys. // Max DWORD for key code to disable keys.
const DWORD VK_DISABLED = 0x100; const DWORD VK_DISABLED = 0x100;

View File

@ -9,6 +9,7 @@
#include <common/utils/process_path.h> #include <common/utils/process_path.h>
#include <WinHookEventIDs.h> #include <WinHookEventIDs.h>
#include <interop/shared_constants.h>
namespace NonLocalizable namespace NonLocalizable
{ {
@ -23,9 +24,10 @@ bool isExcluded(HWND window)
return find_app_name_in_path(processPath, AlwaysOnTopSettings::settings().excludedApps); return find_app_name_in_path(processPath, AlwaysOnTopSettings::settings().excludedApps);
} }
AlwaysOnTop::AlwaysOnTop() : AlwaysOnTop::AlwaysOnTop(bool useLLKH) :
SettingsObserver({SettingId::FrameEnabled, SettingId::Hotkey, SettingId::ExcludeApps}), SettingsObserver({SettingId::FrameEnabled, SettingId::Hotkey, SettingId::ExcludeApps}),
m_hinstance(reinterpret_cast<HINSTANCE>(&__ImageBase)) m_hinstance(reinterpret_cast<HINSTANCE>(&__ImageBase)),
m_useCentralizedLLKH(useLLKH)
{ {
s_instance = this; s_instance = this;
DPIAware::EnableDPIAwarenessForThisProcess(); DPIAware::EnableDPIAwarenessForThisProcess();
@ -38,6 +40,8 @@ AlwaysOnTop::AlwaysOnTop() :
AlwaysOnTopSettings::instance().LoadSettings(); AlwaysOnTopSettings::instance().LoadSettings();
RegisterHotkey(); RegisterHotkey();
RegisterLLKH();
SubscribeToEvents(); SubscribeToEvents();
StartTrackingTopmostWindows(); StartTrackingTopmostWindows();
} }
@ -50,6 +54,13 @@ AlwaysOnTop::AlwaysOnTop() :
AlwaysOnTop::~AlwaysOnTop() AlwaysOnTop::~AlwaysOnTop()
{ {
if (m_hPinEvent)
{
SetEvent(m_hPinEvent);
m_thread.join();
CloseHandle(m_hPinEvent);
}
CleanUp(); CleanUp();
} }
@ -240,10 +251,57 @@ bool AlwaysOnTop::AssignBorder(HWND window)
void AlwaysOnTop::RegisterHotkey() const void AlwaysOnTop::RegisterHotkey() const
{ {
if (m_useCentralizedLLKH)
{
return;
}
UnregisterHotKey(m_window, static_cast<int>(HotkeyId::Pin)); UnregisterHotKey(m_window, static_cast<int>(HotkeyId::Pin));
RegisterHotKey(m_window, static_cast<int>(HotkeyId::Pin), AlwaysOnTopSettings::settings().hotkey.get_modifiers(), AlwaysOnTopSettings::settings().hotkey.get_code()); RegisterHotKey(m_window, static_cast<int>(HotkeyId::Pin), AlwaysOnTopSettings::settings().hotkey.get_modifiers(), AlwaysOnTopSettings::settings().hotkey.get_code());
} }
void AlwaysOnTop::RegisterLLKH()
{
if (!m_useCentralizedLLKH)
{
return;
}
m_hPinEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_PIN_EVENT);
if (!m_hPinEvent)
{
Logger::warn(L"Failed to create pinEvent. {}", get_last_error_or_default(GetLastError()));
return;
}
m_thread = std::thread([this]() {
MSG msg;
while (1)
{
DWORD dwEvt = MsgWaitForMultipleObjects(1, &m_hPinEvent, false, INFINITE, QS_ALLINPUT);
switch (dwEvt)
{
case WAIT_OBJECT_0:
if (HWND fw{ GetForegroundWindow() })
{
ProcessCommand(fw);
}
break;
case WAIT_OBJECT_0 + 1:
if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
break;
default:
return false;
}
}
});
}
void AlwaysOnTop::SubscribeToEvents() void AlwaysOnTop::SubscribeToEvents()
{ {
// subscribe to windows events // subscribe to windows events

View File

@ -13,7 +13,7 @@
class AlwaysOnTop : public SettingsObserver class AlwaysOnTop : public SettingsObserver
{ {
public: public:
AlwaysOnTop(); AlwaysOnTop(bool useLLKH);
~AlwaysOnTop(); ~AlwaysOnTop();
protected: protected:
@ -47,12 +47,16 @@ private:
HWND m_window{ nullptr }; HWND m_window{ nullptr };
HINSTANCE m_hinstance; HINSTANCE m_hinstance;
std::map<HWND, std::unique_ptr<WindowBorder>> m_topmostWindows{}; std::map<HWND, std::unique_ptr<WindowBorder>> m_topmostWindows{};
HANDLE m_hPinEvent;
std::thread m_thread;
const bool m_useCentralizedLLKH;
LRESULT WndProc(HWND, UINT, WPARAM, LPARAM) noexcept; LRESULT WndProc(HWND, UINT, WPARAM, LPARAM) noexcept;
void HandleWinHookEvent(WinHookEvent* data) noexcept; void HandleWinHookEvent(WinHookEvent* data) noexcept;
bool InitMainWindow(); bool InitMainWindow();
void RegisterHotkey() const; void RegisterHotkey() const;
void RegisterLLKH();
void SubscribeToEvents(); void SubscribeToEvents();
void ProcessCommand(HWND window); void ProcessCommand(HWND window);

View File

@ -52,7 +52,7 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
Trace::RegisterProvider(); Trace::RegisterProvider();
AlwaysOnTop app; AlwaysOnTop app(!pid.empty());
run_message_loop(); run_message_loop();

View File

@ -10,12 +10,26 @@
#include <AlwaysOnTop/ModuleConstants.h> #include <AlwaysOnTop/ModuleConstants.h>
#include <shellapi.h> #include <shellapi.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/interop/shared_constants.h>
namespace NonLocalizable namespace NonLocalizable
{ {
const wchar_t ModulePath[] = L"modules\\AlwaysOnTop\\PowerToys.AlwaysOnTop.exe"; const wchar_t ModulePath[] = L"modules\\AlwaysOnTop\\PowerToys.AlwaysOnTop.exe";
} }
namespace
{
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
const wchar_t JSON_KEY_WIN[] = L"win";
const wchar_t JSON_KEY_ALT[] = L"alt";
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
const wchar_t JSON_KEY_SHIFT[] = L"shift";
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_HOTKEY[] = L"hotkey";
const wchar_t JSON_KEY_VALUE[] = L"value";
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{ {
switch (ul_reason_for_call) switch (ul_reason_for_call)
@ -52,15 +66,73 @@ public:
// Return JSON with the configuration options. // Return JSON with the configuration options.
// These are the settings shown on the settings page along with their current values. // These are the settings shown on the settings page along with their current values.
virtual bool get_config(_Out_ PWSTR buffer, _Out_ int* buffer_size) override virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{ {
return false; return false;
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
// Create a Settings object.
PowerToysSettings::Settings settings(hinstance, get_name());
return settings.serialize_to_buffer(buffer, buffer_size);
} }
// Passes JSON with the configuration settings for the powertoy. // Passes JSON with the configuration settings for the powertoy.
// This is called when the user hits Save on the settings page. // This is called when the user hits Save on the settings page.
virtual void set_config(PCWSTR config) override virtual void set_config(const wchar_t* config) override
{ {
try
{
// Parse the input JSON string.
PowerToysSettings::PowerToyValues values =
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
parse_hotkey(values);
// If you don't need to do any custom processing of the settings, proceed
// to persists the values calling:
values.save_to_settings_file();
// Otherwise call a custom function to process the settings before saving them to disk:
// save_settings();
}
catch (std::exception ex)
{
// Improper JSON.
}
}
virtual bool on_hotkey(size_t hotkeyId) override
{
if (m_enabled)
{
Logger::trace(L"AlwaysOnTop hotkey pressed");
if (!is_process_running())
{
Enable();
}
SetEvent(m_hPinEvent);
return true;
}
return false;
}
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
if (m_hotkey.key)
{
if (hotkeys && buffer_size >= 1)
{
hotkeys[0] = m_hotkey;
}
return 1;
}
else
{
return 0;
}
} }
// Enable the powertoy // Enable the powertoy
@ -96,6 +168,8 @@ public:
{ {
app_name = L"AlwaysOnTop"; //TODO: localize app_name = L"AlwaysOnTop"; //TODO: localize
app_key = NonLocalizable::ModuleKey; app_key = NonLocalizable::ModuleKey;
m_hPinEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_PIN_EVENT);
init_settings();
} }
private: private:
@ -109,6 +183,7 @@ private:
unsigned long powertoys_pid = GetCurrentProcessId(); unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L""; std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid)); executable_args.append(std::to_wstring(powertoys_pid));
ResetEvent(m_hPinEvent);
SHELLEXECUTEINFOW sei{ sizeof(sei) }; SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
@ -133,6 +208,8 @@ private:
void Disable(bool const traceEvent) void Disable(bool const traceEvent)
{ {
m_enabled = false; m_enabled = false;
ResetEvent(m_hPinEvent);
// Log telemetry // Log telemetry
if (traceEvent) if (traceEvent)
{ {
@ -146,11 +223,72 @@ private:
} }
} }
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
{
auto settingsObject = settings.get_raw_json();
if (settingsObject.GetView().Size())
{
try
{
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
m_hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
m_hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
}
catch (...)
{
Logger::error("Failed to initialize AlwaysOnTop start shortcut");
}
}
else
{
Logger::info("AlwaysOnTop settings are empty");
}
if (!m_hotkey.key)
{
Logger::info("AlwaysOnTop is going to use default shortcut");
m_hotkey.win = true;
m_hotkey.alt = false;
m_hotkey.shift = false;
m_hotkey.ctrl = true;
m_hotkey.key = 'T';
}
}
bool is_process_running()
{
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
}
void init_settings()
{
try
{
// Load and parse the settings file for this PowerToy.
PowerToysSettings::PowerToyValues settings =
PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
parse_hotkey(settings);
}
catch (std::exception ex)
{
Logger::warn(L"An exception occurred while loading the settings file");
// Error while loading from the settings file. Let default values stay as they are.
}
}
std::wstring app_name; std::wstring app_name;
std::wstring app_key; //contains the non localized key of the powertoy std::wstring app_key; //contains the non localized key of the powertoy
bool m_enabled = false; bool m_enabled = false;
HANDLE m_hProcess = nullptr; HANDLE m_hProcess = nullptr;
Hotkey m_hotkey;
// Handle to event used to pin/unpin windows
HANDLE m_hPinEvent;
}; };
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()

View File

@ -3,7 +3,9 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Globalization;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Library.Helpers; using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces; using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
@ -97,6 +99,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
Settings.Properties.Hotkey.Value = _hotkey; Settings.Properties.Hotkey.Value = _hotkey;
NotifyPropertyChanged(); NotifyPropertyChanged();
// Using InvariantCulture as this is an IPC message
SendConfigMSG(
string.Format(
CultureInfo.InvariantCulture,
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
AlwaysOnTopSettings.ModuleName,
JsonSerializer.Serialize(Settings)));
} }
} }
} }