Cmdpal extension: Powertoys extension for cmdpal (#44006)

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## 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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Installer built, and every command works as expected,
Now use sparse app deployment, so we don't need an extra msix

---------

Co-authored-by: kaitao-ms <kaitao1105@gmail.com>
This commit is contained in:
Kai Tao
2025-12-23 21:07:44 +08:00
committed by GitHub
parent 534c411fd8
commit d87dde132d
206 changed files with 8800 additions and 691 deletions

View File

@@ -6,6 +6,7 @@
#include "../../../common/utils/resources.h"
#include "../../../common/logger/logger.h"
#include "../../../common/utils/logger_helper.h"
#include "../../../common/interop/shared_constants.h"
#include <atomic>
#include <thread>
#include <vector>
@@ -108,6 +109,12 @@ private:
// Hotkey
Hotkey m_activationHotkey{};
// Event-driven trigger support (for CmdPal/automation)
HANDLE m_triggerEventHandle = nullptr;
HANDLE m_terminateEventHandle = nullptr;
std::thread m_eventThread;
std::atomic_bool m_listening{ false };
public:
// Constructor
CursorWrap()
@@ -121,7 +128,8 @@ public:
// Destroy the powertoy and free memory
virtual void destroy() override
{
StopMouseHook();
// Ensure hooks/threads/handles are torn down before deletion
disable();
g_cursorWrapInstance = nullptr; // Clear global instance pointer
delete this;
}
@@ -195,11 +203,54 @@ public:
{
m_enabled = true;
Trace::EnableCursorWrap(true);
// Always start the mouse hook when the module is enabled
// This ensures cursor wrapping is active immediately after enabling
StartMouseHook();
Logger::info("CursorWrap enabled - mouse hook started");
// Start listening for external trigger event so we can invoke the same logic as the activation hotkey.
m_triggerEventHandle = CreateEventW(nullptr, false, false, CommonSharedConstants::CURSOR_WRAP_TRIGGER_EVENT);
m_terminateEventHandle = CreateEventW(nullptr, false, false, nullptr);
if (m_triggerEventHandle && m_terminateEventHandle)
{
m_listening = true;
m_eventThread = std::thread([this]() {
HANDLE handles[2] = { m_triggerEventHandle, m_terminateEventHandle };
// WH_MOUSE_LL callbacks are delivered to the thread that installed the hook.
// Ensure this thread has a message queue and pumps messages while the hook is active.
MSG msg;
PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
StartMouseHook();
Logger::info("CursorWrap enabled - mouse hook started");
while (m_listening)
{
auto res = MsgWaitForMultipleObjects(2, handles, false, INFINITE, QS_ALLINPUT);
if (!m_listening)
{
break;
}
if (res == WAIT_OBJECT_0)
{
ToggleMouseHook();
}
else if (res == WAIT_OBJECT_0 + 1)
{
break;
}
else
{
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
StopMouseHook();
Logger::info("CursorWrap event listener stopped");
});
}
}
// Disable the powertoy
@@ -207,8 +258,26 @@ public:
{
m_enabled = false;
Trace::EnableCursorWrap(false);
StopMouseHook();
Logger::info("CursorWrap disabled - mouse hook stopped");
m_listening = false;
if (m_terminateEventHandle)
{
SetEvent(m_terminateEventHandle);
}
if (m_eventThread.joinable())
{
m_eventThread.join();
}
if (m_triggerEventHandle)
{
CloseHandle(m_triggerEventHandle);
m_triggerEventHandle = nullptr;
}
if (m_terminateEventHandle)
{
CloseHandle(m_terminateEventHandle);
m_terminateEventHandle = nullptr;
}
}
// Returns if the powertoys is enabled
@@ -240,7 +309,19 @@ public:
return false;
}
// Toggle cursor wrapping
// Toggle on the thread that owns the WH_MOUSE_LL hook (the event listener thread).
if (m_triggerEventHandle)
{
return SetEvent(m_triggerEventHandle);
}
return false;
}
private:
void ToggleMouseHook()
{
// Toggle cursor wrapping.
if (m_hookActive)
{
StopMouseHook();
@@ -253,11 +334,8 @@ public:
RunComprehensiveTests();
#endif
}
return true;
}
private:
// Load the settings file.
void init_settings()
{

View File

@@ -8,6 +8,8 @@
#include <common/utils/logger_helper.h>
#include <common/utils/color.h>
#include <common/utils/string_utils.h>
#include <common/utils/EventWaiter.h>
#include <common/interop/shared_constants.h>
namespace
{
@@ -69,6 +71,9 @@ private:
// Find My Mouse specific settings
FindMyMouseSettings m_findMyMouseSettings;
// Event-driven trigger support
EventWaiter m_triggerEventWaiter;
// Load initial settings from the persisted values.
void init_settings();
@@ -86,6 +91,8 @@ public:
// Destroy the powertoy and free memory
virtual void destroy() override
{
// Ensure threads/handles are cleaned up before destruction
disable();
delete this;
}
@@ -150,6 +157,11 @@ public:
m_enabled = true;
Trace::EnableFindMyMouse(true);
std::thread([=]() { FindMyMouseMain(m_hModule, m_findMyMouseSettings); }).detach();
// Start listening for external trigger event so we can invoke the same logic as the hotkey.
m_triggerEventWaiter.start(CommonSharedConstants::FIND_MY_MOUSE_TRIGGER_EVENT, [this](DWORD) {
OnHotkeyEx();
});
}
// Disable the powertoy
@@ -158,6 +170,8 @@ public:
m_enabled = false;
Trace::EnableFindMyMouse(false);
FindMyMouseDisable();
m_triggerEventWaiter.stop();
}
// Returns if the powertoys is enabled
@@ -216,7 +230,7 @@ inline static uint8_t LegacyOpacityToAlpha(int overlayOpacityPercent)
overlayOpacityPercent = 100;
}
// Round to nearest integer (0<EFBFBD>255)
// Round to nearest integer (0255)
return static_cast<uint8_t>((overlayOpacityPercent * 255 + 50) / 100);
}
@@ -532,4 +546,4 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new FindMyMouse();
}
}

View File

@@ -4,6 +4,8 @@
#include "trace.h"
#include "MouseHighlighter.h"
#include "common/utils/color.h"
#include <common/utils/EventWaiter.h>
#include <common/interop/shared_constants.h>
namespace
{
@@ -61,6 +63,9 @@ private:
// Mouse Highlighter specific settings
MouseHighlighterSettings m_highlightSettings;
// Event-driven trigger support
EventWaiter m_triggerEventWaiter;
public:
// Constructor
MouseHighlighter()
@@ -72,6 +77,8 @@ public:
// Destroy the powertoy and free memory
virtual void destroy() override
{
// Tear down threads/handles before deletion to avoid abort() on joinable threads during shutdown
disable();
delete this;
}
@@ -132,6 +139,11 @@ public:
m_enabled = true;
Trace::EnableMouseHighlighter(true);
std::thread([=]() { MouseHighlighterMain(m_hModule, m_highlightSettings); }).detach();
// Start listening for external trigger event so we can invoke the same logic as the hotkey.
m_triggerEventWaiter.start(CommonSharedConstants::MOUSE_HIGHLIGHTER_TRIGGER_EVENT, [this](DWORD) {
OnHotkeyEx();
});
}
// Disable the powertoy
@@ -140,6 +152,8 @@ public:
m_enabled = false;
Trace::EnableMouseHighlighter(false);
MouseHighlighterDisable();
m_triggerEventWaiter.stop();
}
// Returns if the powertoys is enabled

View File

@@ -4,7 +4,8 @@
#include "trace.h"
#include "InclusiveCrosshairs.h"
#include "common/utils/color.h"
#include <atomic>
#include <common/utils/EventWaiter.h>
#include <common/interop/shared_constants.h>
#include <thread>
#include <chrono>
#include <memory>
@@ -124,6 +125,9 @@ private:
// Mouse Pointer Crosshairs specific settings
InclusiveCrosshairsSettings m_inclusiveCrosshairsSettings;
// Event-driven trigger support
EventWaiter m_triggerEventWaiter;
public:
// Constructor
MousePointerCrosshairs()
@@ -137,11 +141,9 @@ public:
// Destroy the powertoy and free memory
virtual void destroy() override
{
UninstallKeyboardHook();
StopXTimer();
StopYTimer();
// Ensure all background threads/handles are torn down before destruction to avoid std::terminate/abort on joinable threads
disable();
g_instance.store(nullptr, std::memory_order_release);
// Release shared state so worker threads (if any) exit when weak_ptr lock fails
m_state.reset();
delete this;
}
@@ -203,6 +205,11 @@ public:
m_enabled = true;
Trace::EnableMousePointerCrosshairs(true);
std::thread([=]() { InclusiveCrosshairsMain(m_hModule, m_inclusiveCrosshairsSettings); }).detach();
// Start listening for external trigger event so we can invoke the same logic as the activation hotkey.
m_triggerEventWaiter.start(CommonSharedConstants::MOUSE_CROSSHAIRS_TRIGGER_EVENT, [this](DWORD) {
on_hotkey(0); // activation hotkey
});
}
// Disable the powertoy
@@ -215,6 +222,8 @@ public:
StopYTimer();
m_glideState = 0;
InclusiveCrosshairsDisable();
m_triggerEventWaiter.stop();
}
// Returns if the powertoys is enabled
@@ -901,4 +910,4 @@ private:
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new MousePointerCrosshairs();
}
}