Cursor wrap command

This commit is contained in:
vanzue
2025-12-03 11:06:07 +08:00
parent 22ec01c548
commit 666a0e062f
7 changed files with 106 additions and 1 deletions

View File

@@ -99,6 +99,10 @@ namespace winrt::PowerToys::Interop::implementation
{ {
return CommonSharedConstants::MOUSE_CROSSHAIRS_TRIGGER_EVENT; return CommonSharedConstants::MOUSE_CROSSHAIRS_TRIGGER_EVENT;
} }
hstring Constants::CursorWrapTriggerEvent()
{
return CommonSharedConstants::CURSOR_WRAP_TRIGGER_EVENT;
}
hstring Constants::LightSwitchToggleEvent() hstring Constants::LightSwitchToggleEvent()
{ {
return CommonSharedConstants::LIGHTSWITCH_TOGGLE_EVENT; return CommonSharedConstants::LIGHTSWITCH_TOGGLE_EVENT;

View File

@@ -29,6 +29,7 @@ namespace winrt::PowerToys::Interop::implementation
static hstring FindMyMouseTriggerEvent(); static hstring FindMyMouseTriggerEvent();
static hstring MouseHighlighterTriggerEvent(); static hstring MouseHighlighterTriggerEvent();
static hstring MouseCrosshairsTriggerEvent(); static hstring MouseCrosshairsTriggerEvent();
static hstring CursorWrapTriggerEvent();
static hstring LightSwitchToggleEvent(); static hstring LightSwitchToggleEvent();
static hstring ZoomItZoomEvent(); static hstring ZoomItZoomEvent();
static hstring ZoomItDrawEvent(); static hstring ZoomItDrawEvent();

View File

@@ -25,6 +25,7 @@ namespace PowerToys
static String FindMyMouseTriggerEvent(); static String FindMyMouseTriggerEvent();
static String MouseHighlighterTriggerEvent(); static String MouseHighlighterTriggerEvent();
static String MouseCrosshairsTriggerEvent(); static String MouseCrosshairsTriggerEvent();
static String CursorWrapTriggerEvent();
static String LightSwitchToggleEvent(); static String LightSwitchToggleEvent();
static String ZoomItZoomEvent(); static String ZoomItZoomEvent();
static String ZoomItDrawEvent(); static String ZoomItDrawEvent();

View File

@@ -89,6 +89,7 @@ namespace CommonSharedConstants
const wchar_t FIND_MY_MOUSE_TRIGGER_EVENT[] = L"Local\\FindMyMouseTriggerEvent-5a9dc5f4-1c74-4f2f-a66f-1b9b6a2f9b23"; const wchar_t FIND_MY_MOUSE_TRIGGER_EVENT[] = L"Local\\FindMyMouseTriggerEvent-5a9dc5f4-1c74-4f2f-a66f-1b9b6a2f9b23";
const wchar_t MOUSE_HIGHLIGHTER_TRIGGER_EVENT[] = L"Local\\MouseHighlighterTriggerEvent-1e3c9c3d-3fdf-4f9a-9a52-31c9b3c3a8f4"; const wchar_t MOUSE_HIGHLIGHTER_TRIGGER_EVENT[] = L"Local\\MouseHighlighterTriggerEvent-1e3c9c3d-3fdf-4f9a-9a52-31c9b3c3a8f4";
const wchar_t MOUSE_CROSSHAIRS_TRIGGER_EVENT[] = L"Local\\MouseCrosshairsTriggerEvent-0d4c7f92-0a5c-4f5c-b64b-8a2a2f7e0b21"; const wchar_t MOUSE_CROSSHAIRS_TRIGGER_EVENT[] = L"Local\\MouseCrosshairsTriggerEvent-0d4c7f92-0a5c-4f5c-b64b-8a2a2f7e0b21";
const wchar_t CURSOR_WRAP_TRIGGER_EVENT[] = L"Local\\CursorWrapTriggerEvent-1f8452b5-4e6e-45b3-8b09-13f14a5900c9";
// Path to the event used by RegistryPreview // Path to the event used by RegistryPreview
const wchar_t REGISTRY_PREVIEW_TRIGGER_EVENT[] = L"Local\\RegistryPreviewEvent-4C559468-F75A-4E7F-BC4F-9C9688316687"; const wchar_t REGISTRY_PREVIEW_TRIGGER_EVENT[] = L"Local\\RegistryPreviewEvent-4C559468-F75A-4E7F-BC4F-9C9688316687";

View File

@@ -6,6 +6,7 @@
#include "../../../common/utils/resources.h" #include "../../../common/utils/resources.h"
#include "../../../common/logger/logger.h" #include "../../../common/logger/logger.h"
#include "../../../common/utils/logger_helper.h" #include "../../../common/utils/logger_helper.h"
#include "../../../common/interop/shared_constants.h"
#include <atomic> #include <atomic>
#include <thread> #include <thread>
#include <vector> #include <vector>
@@ -108,6 +109,12 @@ private:
// Hotkey // Hotkey
Hotkey m_activationHotkey{}; 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: public:
// Constructor // Constructor
CursorWrap() CursorWrap()
@@ -121,7 +128,8 @@ public:
// Destroy the powertoy and free memory // Destroy the powertoy and free memory
virtual void destroy() override virtual void destroy() override
{ {
StopMouseHook(); // Ensure hooks/threads/handles are torn down before deletion
disable();
g_cursorWrapInstance = nullptr; // Clear global instance pointer g_cursorWrapInstance = nullptr; // Clear global instance pointer
delete this; delete this;
} }
@@ -200,6 +208,34 @@ public:
// This ensures cursor wrapping is active immediately after enabling // This ensures cursor wrapping is active immediately after enabling
StartMouseHook(); StartMouseHook();
Logger::info("CursorWrap enabled - mouse hook started"); 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 };
while (m_listening)
{
auto res = WaitForMultipleObjects(2, handles, false, INFINITE);
if (!m_listening)
{
break;
}
if (res == WAIT_OBJECT_0)
{
on_hotkey(0);
}
else
{
break;
}
}
});
}
} }
// Disable the powertoy // Disable the powertoy
@@ -209,6 +245,26 @@ public:
Trace::EnableCursorWrap(false); Trace::EnableCursorWrap(false);
StopMouseHook(); StopMouseHook();
Logger::info("CursorWrap disabled - mouse hook stopped"); 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 // Returns if the powertoys is enabled

View File

@@ -0,0 +1,35 @@
// 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.
using System;
using System.Threading;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
namespace PowerToysExtension.Commands;
/// <summary>
/// Toggles Cursor Wrap via the shared trigger event.
/// </summary>
internal sealed partial class ToggleCursorWrapCommand : InvokableCommand
{
public ToggleCursorWrapCommand()
{
Name = "Toggle Cursor Wrap";
}
public override CommandResult Invoke()
{
try
{
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CursorWrapTriggerEvent());
evt.Set();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast($"Failed to toggle Cursor Wrap: {ex.Message}");
}
}
}

View File

@@ -38,6 +38,13 @@ internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
Icon = icon, Icon = icon,
}; };
yield return new ListItem(new ToggleCursorWrapCommand())
{
Title = "Toggle Cursor Wrap",
Subtitle = "Wrap the cursor across monitor edges",
Icon = icon,
};
yield return new ListItem(new ShowMouseJumpPreviewCommand()) yield return new ListItem(new ShowMouseJumpPreviewCommand())
{ {
Title = "Show Mouse Jump Preview", Title = "Show Mouse Jump Preview",