Files
PowerToys/tools/module_loader/src/HotkeyManager.cpp
Mike Hall 452e0dcf51 Module Loader tool for rapid testing of modules (#43813)
## Summary of the Pull Request
ModuleLoader tool, a stand-alone Win32 executable for testing of
PowerToy modules without needing branch builds.

sample output from running the tool is below:

.\ModuleLoader.exe .\powertoys.cursorwrap.dll
PowerToys Module Loader v1.0
=============================

Loading module: .\powertoys.cursorwrap.dll
Detected module name: cursorwrap

Loading settings...
Trying settings path:
C:\Users\mikehall\AppData\Local\Microsoft\PowerToys\cursorwrap\settings.json
Settings file loaded (315 characters)
Settings loaded successfully.

Loading module DLL...
Module instance created successfully
Module DLL loaded successfully.
Module key: CursorWrap
Module name: CursorWrap

Applying settings to module...
Settings applied.

Registering module hotkeys...
Module reports 1 legacy hotkey(s)
  Registering hotkey 0: Win+Alt+U - OK
Hotkeys registered: 1

Enabling module...
Module enabled.

=============================
Module is now running!
=============================

Module Status:
  - Name: CursorWrap
  - Key: CursorWrap
  - Enabled: Yes
  - Hotkeys: 1 registered

Registered Hotkeys:
  Win+Alt+U

Press Ctrl+C to exit.
You can press the module's hotkey to toggle its functionality.

Note that this doesn't integrate with Powertoys settings UI - this is
purely to test Powertoys module functionality.

## PR Checklist

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

## Detailed Description of the Pull Request / Additional comments
See details above.

## Validation Steps Performed
ModuleLoader tested on Windows 11, Surface Laptop 7 Pro.
2025-11-26 22:08:34 +08:00

280 lines
7.8 KiB
C++

// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#include "HotkeyManager.h"
#include <iostream>
#include <sstream>
HotkeyManager::HotkeyManager()
: m_nextHotkeyId(1) // Start from 1
, m_hotkeyExRegistered(false)
, m_hotkeyExId(0)
{
}
HotkeyManager::~HotkeyManager()
{
UnregisterAll();
}
UINT HotkeyManager::ConvertModifiers(bool win, bool ctrl, bool alt, bool shift) const
{
UINT modifiers = MOD_NOREPEAT; // Prevent repeat events
if (win) modifiers |= MOD_WIN;
if (ctrl) modifiers |= MOD_CONTROL;
if (alt) modifiers |= MOD_ALT;
if (shift) modifiers |= MOD_SHIFT;
return modifiers;
}
bool HotkeyManager::RegisterModuleHotkeys(ModuleLoader& moduleLoader)
{
if (!moduleLoader.IsLoaded())
{
std::wcerr << L"Error: Module not loaded\n";
return false;
}
bool anyRegistered = false;
// First, try the newer GetHotkeyEx() API
auto hotkeyEx = moduleLoader.GetHotkeyEx();
if (hotkeyEx.has_value())
{
std::wcout << L"Module has HotkeyEx activation hotkey\n";
UINT modifiers = hotkeyEx->modifiersMask | MOD_NOREPEAT;
UINT vkCode = hotkeyEx->vkCode;
if (vkCode != 0)
{
int hotkeyId = m_nextHotkeyId++;
std::wcout << L" Registering HotkeyEx: ";
std::wcout << ModifiersToString(modifiers) << L"+" << VKeyToString(vkCode);
if (RegisterHotKey(nullptr, hotkeyId, modifiers, vkCode))
{
m_hotkeyExRegistered = true;
m_hotkeyExId = hotkeyId;
std::wcout << L" - OK (Activation/Toggle)\n";
anyRegistered = true;
}
else
{
DWORD error = GetLastError();
std::wcout << L" - FAILED (Error: " << error << L")\n";
if (error == ERROR_HOTKEY_ALREADY_REGISTERED)
{
std::wcout << L" (Hotkey is already registered by another application)\n";
}
}
}
}
// Also check the legacy get_hotkeys() API
size_t hotkeyCount = moduleLoader.GetHotkeys(nullptr, 0);
if (hotkeyCount > 0)
{
std::wcout << L"Module reports " << hotkeyCount << L" legacy hotkey(s)\n";
// Allocate buffer and get the hotkeys
std::vector<PowertoyModuleIface::Hotkey> hotkeys(hotkeyCount);
size_t actualCount = moduleLoader.GetHotkeys(hotkeys.data(), hotkeyCount);
// Register each hotkey
for (size_t i = 0; i < actualCount; i++)
{
const auto& hotkey = hotkeys[i];
UINT modifiers = ConvertModifiers(hotkey.win, hotkey.ctrl, hotkey.alt, hotkey.shift);
UINT vkCode = hotkey.key;
if (vkCode == 0)
{
std::wcout << L" Skipping hotkey " << i << L" (no key code)\n";
continue;
}
int hotkeyId = m_nextHotkeyId++;
std::wcout << L" Registering hotkey " << i << L": ";
std::wcout << ModifiersToString(modifiers) << L"+" << VKeyToString(vkCode);
if (RegisterHotKey(nullptr, hotkeyId, modifiers, vkCode))
{
HotkeyInfo info;
info.id = hotkeyId;
info.moduleHotkeyId = i;
info.modifiers = modifiers;
info.vkCode = vkCode;
info.description = ModifiersToString(modifiers) + L"+" + VKeyToString(vkCode);
m_registeredHotkeys.push_back(info);
std::wcout << L" - OK\n";
anyRegistered = true;
}
else
{
DWORD error = GetLastError();
std::wcout << L" - FAILED (Error: " << error << L")\n";
if (error == ERROR_HOTKEY_ALREADY_REGISTERED)
{
std::wcout << L" (Hotkey is already registered by another application)\n";
}
}
}
}
if (!anyRegistered && hotkeyCount == 0 && !hotkeyEx.has_value())
{
std::wcout << L"Module has no hotkeys\n";
}
return anyRegistered;
}
void HotkeyManager::UnregisterAll()
{
for (const auto& hotkey : m_registeredHotkeys)
{
UnregisterHotKey(nullptr, hotkey.id);
}
m_registeredHotkeys.clear();
if (m_hotkeyExRegistered)
{
UnregisterHotKey(nullptr, m_hotkeyExId);
m_hotkeyExRegistered = false;
m_hotkeyExId = 0;
}
}
bool HotkeyManager::HandleHotkey(int hotkeyId, ModuleLoader& moduleLoader)
{
// Check if it's the HotkeyEx activation hotkey
if (m_hotkeyExRegistered && hotkeyId == m_hotkeyExId)
{
std::wcout << L"\nActivation hotkey triggered (HotkeyEx)\n";
moduleLoader.OnHotkeyEx();
std::wcout << L"Module toggled via activation hotkey\n";
std::wcout << L"Module enabled: " << (moduleLoader.IsEnabled() ? L"Yes" : L"No") << L"\n\n";
return true;
}
// Check legacy hotkeys
for (const auto& hotkey : m_registeredHotkeys)
{
if (hotkey.id == hotkeyId)
{
std::wcout << L"\nHotkey triggered: " << hotkey.description << L"\n";
bool result = moduleLoader.OnHotkey(hotkey.moduleHotkeyId);
std::wcout << L"Module handled hotkey: " << (result ? L"Swallowed" : L"Not swallowed") << L"\n";
std::wcout << L"Module enabled: " << (moduleLoader.IsEnabled() ? L"Yes" : L"No") << L"\n\n";
return true;
}
}
return false;
}
void HotkeyManager::PrintHotkeys() const
{
for (const auto& hotkey : m_registeredHotkeys)
{
std::wcout << L" " << hotkey.description << L"\n";
}
}
std::wstring HotkeyManager::ModifiersToString(UINT modifiers) const
{
std::wstringstream ss;
bool first = true;
if (modifiers & MOD_WIN)
{
if (!first) ss << L"+";
ss << L"Win";
first = false;
}
if (modifiers & MOD_CONTROL)
{
if (!first) ss << L"+";
ss << L"Ctrl";
first = false;
}
if (modifiers & MOD_ALT)
{
if (!first) ss << L"+";
ss << L"Alt";
first = false;
}
if (modifiers & MOD_SHIFT)
{
if (!first) ss << L"+";
ss << L"Shift";
first = false;
}
return ss.str();
}
std::wstring HotkeyManager::VKeyToString(UINT vkCode) const
{
// Handle special keys
switch (vkCode)
{
case VK_SPACE: return L"Space";
case VK_RETURN: return L"Enter";
case VK_ESCAPE: return L"Esc";
case VK_TAB: return L"Tab";
case VK_BACK: return L"Backspace";
case VK_DELETE: return L"Del";
case VK_INSERT: return L"Ins";
case VK_HOME: return L"Home";
case VK_END: return L"End";
case VK_PRIOR: return L"PgUp";
case VK_NEXT: return L"PgDn";
case VK_LEFT: return L"Left";
case VK_RIGHT: return L"Right";
case VK_UP: return L"Up";
case VK_DOWN: return L"Down";
case VK_F1: return L"F1";
case VK_F2: return L"F2";
case VK_F3: return L"F3";
case VK_F4: return L"F4";
case VK_F5: return L"F5";
case VK_F6: return L"F6";
case VK_F7: return L"F7";
case VK_F8: return L"F8";
case VK_F9: return L"F9";
case VK_F10: return L"F10";
case VK_F11: return L"F11";
case VK_F12: return L"F12";
}
// For alphanumeric keys, use MapVirtualKey
wchar_t keyName[256];
UINT scanCode = MapVirtualKeyW(vkCode, MAPVK_VK_TO_VSC);
if (GetKeyNameTextW(scanCode << 16, keyName, 256) > 0)
{
return keyName;
}
// Fallback to hex code
std::wstringstream ss;
ss << L"0x" << std::hex << vkCode;
return ss.str();
}