mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-06-07 09:28:03 +08:00
[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:
parent
2159e2722e
commit
301f26aca1
2
.github/actions/spell-check/expect.txt
vendored
2
.github/actions/spell-check/expect.txt
vendored
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user