Compare commits

..

4 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
e1986611c1 Add high contrast theme support for Monaco Editor
- Add GetMonacoTheme() to ThemeManager for detecting high contrast mode
- Update Monaco index.html to support hc-black and hc-light themes
- Update Monaco Preview Handler to use high contrast themes
- Update Peek to use high contrast themes
- Update Registry Preview to detect and use high contrast themes

Co-authored-by: yeelam-gordon <73506701+yeelam-gordon@users.noreply.github.com>
2026-02-05 15:20:31 +00:00
copilot-swe-agent[bot]
71d55ac50f Initial plan 2026-02-05 15:17:01 +00:00
Thanh Nguyen
2be4c4eb46 Fix CursorWrap "Automatically activate on utility startup" setting not persisting (#45210)
## Summary of the Pull Request

Fixes #45185 - CursorWrap "Automatically activate on utility startup"
setting cannot be disabled, and prevents spurious activation on startup.

## PR Checklist

- [x] Closes: #45185
- [x] **Communication:** Issue was reported by community; fix follows
established patterns from MousePointerCrosshairs
- [x] **Tests:** Manual validation performed by contributor (video
available)
- [x] **Localization:** No new user-facing strings added
- [ ] **Dev docs:** N/A - bug fix only
- [ ] **New binaries:** N/A - no new binaries
- [ ] **Documentation updated:** N/A - bug fix only

## Detailed Description of the Pull Request / Additional comments

### Problem

Users reported that disabling the "Automatically activate on utility
startup" setting for CursorWrap does not work - the mouse hook always
starts automatically regardless of the setting value.

### Root Causes

1. **`dllmain.cpp` `enable()` method**: `StartMouseHook()` was always
called unconditionally, ignoring `m_autoActivate`.
2. **`MouseUtilsViewModel.cs` `IsCursorWrapEnabled` setter**: enabling
CursorWrap forced `AutoActivate = true`, overriding the user's
preference.
3. **Startup edge case**: the trigger event could remain signaled from a
previous session, immediately toggling CursorWrap on startup even when
AutoActivate is off.

### Solution

1. **`src/modules/MouseUtils/CursorWrap/dllmain.cpp`**: only start the
mouse hook if `m_autoActivate` is true.
2. **`src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs`**:
remove the line that forced `AutoActivate = true` when enabling
CursorWrap.
3. **`src/modules/MouseUtils/CursorWrap/dllmain.cpp`**: reset the
trigger event on enable to avoid immediate activation on startup.

### Pattern Reference

This fix follows the same pattern used by **MousePointerCrosshairs**
module which has a similar `AutoActivate` setting that works correctly.

## Validation Steps Performed

### Build

- `tools\build\build.ps1 -Platform x64 -Configuration Debug`

### Manual validation (contributor)

#### Test Case 1: AutoActivate = false (should NOT auto-start mouse
hook)

1. Open PowerToys Settings → Mouse Utilities → Cursor Wrap
2. Enable Cursor Wrap
3. **Disable** "Automatically activate on utility startup"
4. Close PowerToys completely (right-click tray icon → Exit)
5. Restart PowerToys
6. **Expected Result**: CursorWrap module is loaded but mouse hook is
NOT active - cursor does NOT wrap at screen edges
7. Press activation hotkey (default: `Win+Alt+U`)
8. **Expected Result**: Mouse hook activates, cursor now wraps at screen
edges
9. **Actual Result**:  Works as expected

#### Test Case 2: AutoActivate = true (should auto-start mouse hook)

1. Open PowerToys Settings → Mouse Utilities → Cursor Wrap
2. Enable Cursor Wrap
3. **Enable** "Automatically activate on utility startup"
4. Close PowerToys completely
5. Restart PowerToys
6. **Expected Result**: Mouse hook is immediately active, cursor wraps
at screen edges without pressing hotkey
7. **Actual Result**:  Works as expected

#### Test Case 3: Setting persistence after restart

1. Set AutoActivate = false, restart PowerToys
2. Open Settings and verify AutoActivate is still false
3. Set AutoActivate = true, restart PowerToys
4. Open Settings and verify AutoActivate is still true
5. **Actual Result**:  Setting persists correctly

#### Test Case 4: Hotkey toggle works correctly

1. With AutoActivate = false, restart PowerToys
2. Press hotkey → cursor should start wrapping
3. Press hotkey again → cursor should stop wrapping
4. **Actual Result**:  Hotkey toggle works correctly

---

**Note**: Video demonstration available from contributor.
2026-02-05 19:58:49 +08:00
Mike Hall
731532fdd8 Add option to disable CursorWrap when on a single monitor. (#45303)
## Summary of the Pull Request
CursorWrap wraps on the outer edge of monitors, if a user is swapping
between a laptop and docked laptop with external monitors the user might
want to only enable wrapping when connected to external monitors, and
disable when only on the laptop.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #45198
- [ ] Closes: #45154
- [ ] **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
Currently CursorWrap will wrap around the horizontal/vertical edges of
monitors, if the user has more than one monitor the outer edges are used
as wrap targets, if the user only has one monitor (perhaps a laptop)
wrapping might be temporarily disabled until additional external
monitors are added (such as being plugged into a dock or using a USB-C
monitor).

The new option will disable wrapping if only a single monitor is
detected, monitor detection is dynamic.

## Validation Steps Performed
Validated on a Surface Laptop 7 Pro (Intel) with a USB-C External
Monitor.

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
2026-02-05 18:37:10 +08:00
20 changed files with 187 additions and 696 deletions

View File

@@ -6,14 +6,15 @@
// Get URL parameters:
// `code` contains the code of the file in base64 encoded
// `theme` can be "vs" for light theme or "vs-dark" for dark theme
// `theme` can be "vs" for light theme, "vs-dark" for dark theme,
// "hc-black" for dark high contrast, or "hc-light" for light high contrast
// `lang` is the language of the file
// `wrap` if the editor is wrapping or not
// `minimap` if the minimap is shown
// `contextMenu` whether to use the Monaco context menu. The built-in context menu
// doesn't work in Peek, so we set this to false and create a custom one
var theme = ("[[PT_THEME]]" == "dark") ? "vs-dark" : "vs";
var theme = "[[PT_THEME]]";
var wrap = [[PT_WRAP]];
var minimap = [[PT_MINIMAP]];
var stickyScroll = [[PT_STICKY_SCROLL]];
@@ -87,13 +88,20 @@
require(['vs/editor/editor.main'], async function () {
await registerAdditionalLanguages(monaco)
// Creates a theme to handle custom tokens
monaco.editor.defineTheme('theme', {
base: theme, // Sets the base theme to "vs" or "vs-dark" depending on the user's preference
inherit: true,
rules: customTokenThemeRules,
colors: {} // `colors` is a required attribute
});
// For high contrast themes, use Monaco's built-in themes directly
// For normal themes, create a custom theme to handle custom tokens
var editorTheme = theme;
if (theme === 'vs' || theme === 'vs-dark') {
// Creates a theme to handle custom tokens
monaco.editor.defineTheme('custom-theme', {
base: theme, // Sets the base theme to "vs" or "vs-dark" depending on the user's preference
inherit: true,
rules: customTokenThemeRules,
colors: {} // `colors` is a required attribute
});
editorTheme = 'custom-theme';
}
// For hc-black and hc-light, use the built-in Monaco themes which have proper contrast
// Creates the editor
// For all parameters: https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html
@@ -101,7 +109,7 @@
value: code, // Sets content of the editor
language: lang, // Sets language of the code
readOnly: true, // Sets to readonly
theme: 'theme', // Sets editor theme
theme: editorTheme, // Sets editor theme
minimap: { enabled: minimap }, // Controls if minimap is shown
lineNumbersMinChars: 3, // Width of the line numbers
contextmenu: contextMenu,

View File

@@ -113,6 +113,26 @@ namespace Common.UI
return ControlzEx.Theming.WindowsThemeHelper.GetWindowsBaseColor();
}
/// <summary>
/// Gets the Monaco theme name based on Windows theme and high contrast mode.
/// </summary>
/// <returns>Monaco theme name: "hc-black", "hc-light", "vs-dark", or "vs".</returns>
public static string GetMonacoTheme()
{
if (ControlzEx.Theming.WindowsThemeHelper.IsHighContrastEnabled())
{
// In high contrast mode, check if it's a dark or light high contrast theme
string baseColor = ControlzEx.Theming.WindowsThemeHelper.GetWindowsBaseColor();
return baseColor.Equals("Dark", StringComparison.OrdinalIgnoreCase) ? "hc-black" : "hc-light";
}
else
{
// Normal mode: use standard themes
string baseColor = ControlzEx.Theming.WindowsThemeHelper.GetWindowsBaseColor();
return baseColor.Equals("Dark", StringComparison.OrdinalIgnoreCase) ? "vs-dark" : "vs";
}
}
public void ChangeTheme(Theme theme, bool fromSettings = false)
{
if (fromSettings)

View File

@@ -163,8 +163,22 @@ void CursorWrapCore::UpdateMonitorInfo()
Logger::info(L"======= UPDATE MONITOR INFO END =======");
}
POINT CursorWrapCore::HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode)
POINT CursorWrapCore::HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode, bool disableOnSingleMonitor)
{
// Check if wrapping should be disabled on single monitor
if (disableOnSingleMonitor && m_monitors.size() <= 1)
{
#ifdef _DEBUG
static bool loggedOnce = false;
if (!loggedOnce)
{
OutputDebugStringW(L"[CursorWrap] Single monitor detected - cursor wrapping disabled\n");
loggedOnce = true;
}
#endif
return currentPos;
}
// Check if wrapping should be disabled during drag
if (disableWrapDuringDrag && (GetAsyncKeyState(VK_LBUTTON) & 0x8000))
{

View File

@@ -18,9 +18,11 @@ public:
// Handle mouse move with wrap mode filtering
// wrapMode: 0=Both, 1=VerticalOnly, 2=HorizontalOnly
POINT HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode);
// disableOnSingleMonitor: if true, cursor wrapping is disabled when only one monitor is connected
POINT HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode, bool disableOnSingleMonitor);
const std::vector<MonitorInfo>& GetMonitors() const { return m_monitors; }
size_t GetMonitorCount() const { return m_monitors.size(); }
const MonitorTopology& GetTopology() const { return m_topology; }
private:

View File

@@ -54,6 +54,7 @@ namespace
const wchar_t JSON_KEY_AUTO_ACTIVATE[] = L"auto_activate";
const wchar_t JSON_KEY_DISABLE_WRAP_DURING_DRAG[] = L"disable_wrap_during_drag";
const wchar_t JSON_KEY_WRAP_MODE[] = L"wrap_mode";
const wchar_t JSON_KEY_DISABLE_ON_SINGLE_MONITOR[] = L"disable_cursor_wrap_on_single_monitor";
}
// The PowerToy name that will be shown in the settings.
@@ -80,6 +81,7 @@ private:
bool m_enabled = false;
bool m_autoActivate = false;
bool m_disableWrapDuringDrag = true; // Default to true to prevent wrap during drag
bool m_disableOnSingleMonitor = false; // Default to false
int m_wrapMode = 0; // 0=Both (default), 1=VerticalOnly, 2=HorizontalOnly
// Mouse hook
@@ -196,6 +198,10 @@ public:
// 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)
{
ResetEvent(m_triggerEventHandle);
}
if (m_triggerEventHandle && m_terminateEventHandle)
{
m_listening = true;
@@ -210,8 +216,16 @@ public:
// Create message window for display change notifications
RegisterForDisplayChanges();
StartMouseHook();
Logger::info("CursorWrap enabled - mouse hook started");
// Only start the mouse hook automatically if auto-activate is enabled
if (m_autoActivate)
{
StartMouseHook();
Logger::info("CursorWrap enabled - mouse hook started (auto-activate on)");
}
else
{
Logger::info("CursorWrap enabled - waiting for activation hotkey (auto-activate off)");
}
while (m_listening)
{
@@ -415,6 +429,21 @@ private:
{
Logger::warn("Failed to initialize CursorWrap wrap mode from settings. Will use default value (0=Both)");
}
try
{
// Parse disable on single monitor
auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
if (propertiesObject.HasKey(JSON_KEY_DISABLE_ON_SINGLE_MONITOR))
{
auto disableOnSingleMonitorObject = propertiesObject.GetNamedObject(JSON_KEY_DISABLE_ON_SINGLE_MONITOR);
m_disableOnSingleMonitor = disableOnSingleMonitorObject.GetNamedBoolean(JSON_KEY_VALUE);
}
}
catch (...)
{
Logger::warn("Failed to initialize CursorWrap disable on single monitor from settings. Will use default value (false)");
}
}
else
{
@@ -646,7 +675,8 @@ private:
POINT newPos = g_cursorWrapInstance->m_core.HandleMouseMove(
currentPos,
g_cursorWrapInstance->m_disableWrapDuringDrag,
g_cursorWrapInstance->m_wrapMode);
g_cursorWrapInstance->m_wrapMode,
g_cursorWrapInstance->m_disableOnSingleMonitor);
if (newPos.x != currentPos.x || newPos.y != currentPos.y)
{

View File

@@ -16,11 +16,6 @@
#include <trace.h>
#include <WinHookEventIDs.h>
#include <dwmapi.h>
#include <ScalingUtils.h>
#include <WindowCornersUtil.h>
#include <cmath>
namespace NonLocalizable
{
@@ -36,14 +31,6 @@ bool isExcluded(HWND window)
return check_excluded_app(window, processPath, AlwaysOnTopSettings::settings().excludedApps);
}
namespace
{
bool TryGetExtendedFrameBounds(HWND window, RECT& rect) noexcept
{
return DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect)) == S_OK;
}
}
AlwaysOnTop::AlwaysOnTop(bool useLLKH, DWORD mainThreadId) :
SettingsObserver({SettingId::FrameEnabled, SettingId::Hotkey, SettingId::ExcludeApps}),
m_hinstance(reinterpret_cast<HINSTANCE>(&__ImageBase)),
@@ -61,13 +48,6 @@ AlwaysOnTop::AlwaysOnTop(bool useLLKH, DWORD mainThreadId) :
AlwaysOnTopSettings::instance().InitFileWatcher();
AlwaysOnTopSettings::instance().LoadSettings();
m_dimOverlay = std::make_unique<DimOverlay>();
if (!m_dimOverlay->Initialize(m_hinstance))
{
Logger::warn("Failed to initialize AlwaysOnTop dim overlay.");
m_dimOverlay.reset();
}
RegisterHotkey();
RegisterLLKH();
@@ -144,7 +124,6 @@ void AlwaysOnTop::SettingsUpdate(SettingId id)
iter.second = nullptr;
}
}
UpdateDimOverlay();
}
break;
case SettingId::ExcludeApps:
@@ -163,7 +142,6 @@ void AlwaysOnTop::SettingsUpdate(SettingId id)
{
m_topmostWindows.erase(window);
}
UpdateDimOverlay();
}
break;
default:
@@ -238,72 +216,17 @@ void AlwaysOnTop::ProcessCommand(HWND window)
{
soundType = Sound::Type::On;
AssignBorder(window);
MinimizeOtherWindows(window);
Trace::AlwaysOnTop::PinWindow();
}
}
UpdateDimOverlay();
if (AlwaysOnTopSettings::settings().enableSound)
{
m_sound.Play(soundType);
}
}
void AlwaysOnTop::MinimizeOtherWindows(HWND pinnedWindow)
{
struct EnumState
{
AlwaysOnTop* self{};
HWND pinned{};
};
EnumState state{ this, pinnedWindow };
EnumWindows([](HWND window, LPARAM lparam) -> BOOL {
auto* state = reinterpret_cast<EnumState*>(lparam);
if (!state || !state->self)
{
return FALSE;
}
if (window == state->pinned)
{
return TRUE;
}
if (!IsWindowVisible(window) || IsIconic(window))
{
return TRUE;
}
if (window == state->self->m_window)
{
return TRUE;
}
if (state->self->m_dimOverlay && window == state->self->m_dimOverlay->Hwnd())
{
return TRUE;
}
auto exStyle = GetWindowLongPtr(window, GWL_EXSTYLE);
if ((exStyle & WS_EX_TOOLWINDOW) == WS_EX_TOOLWINDOW)
{
return TRUE;
}
if (!state->self->m_virtualDesktopUtils.IsWindowOnCurrentDesktop(window))
{
return TRUE;
}
ShowWindow(window, SW_MINIMIZE);
return TRUE;
}, reinterpret_cast<LPARAM>(&state));
}
void AlwaysOnTop::StartTrackingTopmostWindows()
{
using result_t = std::vector<HWND>;
@@ -339,7 +262,6 @@ void AlwaysOnTop::StartTrackingTopmostWindows()
AssignBorder(window);
}
}
}
bool AlwaysOnTop::AssignBorder(HWND window)
@@ -508,18 +430,11 @@ void AlwaysOnTop::UnpinAll()
m_topmostWindows.clear();
m_windowOriginalLayeredState.clear();
UpdateDimOverlay();
}
void AlwaysOnTop::CleanUp()
{
UnpinAll();
if (m_dimOverlay)
{
m_dimOverlay->Terminate();
m_dimOverlay.reset();
}
if (m_window)
{
DestroyWindow(m_window);
@@ -577,13 +492,11 @@ bool AlwaysOnTop::IsTracked(HWND window) const noexcept
void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
{
if (!data->hwnd)
if (!AlwaysOnTopSettings::settings().enableFrame || !data->hwnd)
{
return;
}
const bool frameEnabled = AlwaysOnTopSettings::settings().enableFrame;
std::vector<HWND> toErase{};
for (const auto& [window, border] : m_topmostWindows)
{
@@ -607,29 +520,23 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
{
case EVENT_OBJECT_LOCATIONCHANGE:
{
if (frameEnabled)
auto iter = m_topmostWindows.find(data->hwnd);
if (iter != m_topmostWindows.end())
{
auto iter = m_topmostWindows.find(data->hwnd);
if (iter != m_topmostWindows.end())
const auto& border = iter->second;
if (border)
{
const auto& border = iter->second;
if (border)
{
border->UpdateBorderPosition();
}
border->UpdateBorderPosition();
}
}
}
break;
case EVENT_SYSTEM_MINIMIZESTART:
{
if (frameEnabled)
auto iter = m_topmostWindows.find(data->hwnd);
if (iter != m_topmostWindows.end())
{
auto iter = m_topmostWindows.find(data->hwnd);
if (iter != m_topmostWindows.end())
{
m_topmostWindows[data->hwnd] = nullptr;
}
m_topmostWindows[data->hwnd] = nullptr;
}
}
break;
@@ -639,26 +546,20 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
if (iter != m_topmostWindows.end())
{
// pin border again, in some cases topmost flag stops working: https://github.com/microsoft/PowerToys/issues/17332
PinTopmostWindow(data->hwnd);
if (frameEnabled)
{
AssignBorder(data->hwnd);
}
PinTopmostWindow(data->hwnd);
AssignBorder(data->hwnd);
}
}
break;
case EVENT_SYSTEM_MOVESIZEEND:
{
if (frameEnabled)
auto iter = m_topmostWindows.find(data->hwnd);
if (iter != m_topmostWindows.end())
{
auto iter = m_topmostWindows.find(data->hwnd);
if (iter != m_topmostWindows.end())
const auto& border = iter->second;
if (border)
{
const auto& border = iter->second;
if (border)
{
border->UpdateBorderPosition();
}
border->UpdateBorderPosition();
}
}
}
@@ -672,10 +573,7 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
GET_RESOURCE_STRING(IDS_SYSTEM_FOREGROUND_ELEVATED_LEARN_MORE),
GET_RESOURCE_STRING(IDS_SYSTEM_FOREGROUND_ELEVATED_DIALOG_DONT_SHOW_AGAIN));
}
if (frameEnabled)
{
RefreshBorders();
}
RefreshBorders();
}
break;
case EVENT_OBJECT_FOCUS:
@@ -695,8 +593,6 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
default:
break;
}
UpdateDimOverlay();
}
void AlwaysOnTop::RefreshBorders()
@@ -718,97 +614,6 @@ void AlwaysOnTop::RefreshBorders()
}
}
}
UpdateDimOverlay();
}
void AlwaysOnTop::UpdateDimOverlay()
{
if (!m_dimOverlay)
{
return;
}
struct PinnedEntry
{
HWND window = nullptr;
HWND border = nullptr;
};
std::vector<DimOverlayHole> holes;
std::vector<PinnedEntry> pinnedEntries;
holes.reserve(m_topmostWindows.size());
pinnedEntries.reserve(m_topmostWindows.size());
for (const auto& [window, border] : m_topmostWindows)
{
if (!IsWindow(window) || !IsPinned(window))
{
continue;
}
if (!IsWindowVisible(window) || IsIconic(window))
{
continue;
}
if (!m_virtualDesktopUtils.IsWindowOnCurrentDesktop(window))
{
continue;
}
RECT rect{};
if (!TryGetExtendedFrameBounds(window, rect))
{
if (!GetWindowRect(window, &rect))
{
continue;
}
}
int radius = WindowCornerUtils::CornersRadius(window);
if (radius > 0)
{
const float scale = ScalingUtils::ScalingFactor(window);
radius = static_cast<int>(std::lround(radius * scale));
}
else
{
radius = 0;
}
holes.push_back({ rect, radius });
pinnedEntries.push_back({ window, border ? border->Hwnd() : nullptr });
}
const bool visible = !holes.empty();
m_dimOverlay->Update(std::move(holes), visible);
if (!visible)
{
return;
}
const HWND overlayHwnd = m_dimOverlay->Hwnd();
if (!overlayHwnd)
{
return;
}
SetWindowPos(overlayHwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
for (const auto& entry : pinnedEntries)
{
if (entry.border)
{
SetWindowPos(entry.border, overlayHwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
SetWindowPos(entry.window, entry.border, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
else
{
SetWindowPos(entry.window, overlayHwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
}
}
HWND AlwaysOnTop::ResolveTransparencyTargetWindow(HWND window)
@@ -971,4 +776,4 @@ void AlwaysOnTop::RestoreWindowAlpha(HWND window)
SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
}
}
}
}

View File

@@ -1,14 +1,12 @@
#pragma once
#include <map>
#include <memory>
#include <Settings.h>
#include <SettingsObserver.h>
#include <Sound.h>
#include <VirtualDesktopUtils.h>
#include <WindowBorder.h>
#include <DimOverlay.h>
#include <common/hooks/WinHookEvent.h>
#include <common/notifications/NotificationUtil.h>
@@ -62,8 +60,6 @@ private:
COLORREF colorKey = 0;
};
std::map<HWND, WindowLayeredState> m_windowOriginalLayeredState{};
std::unique_ptr<DimOverlay> m_dimOverlay;
HANDLE m_hPinEvent;
HANDLE m_hTerminateEvent;
@@ -84,7 +80,6 @@ private:
void SubscribeToEvents();
void ProcessCommand(HWND window);
void MinimizeOtherWindows(HWND pinnedWindow);
void StartTrackingTopmostWindows();
void UnpinAll();
void CleanUp();
@@ -97,7 +92,6 @@ private:
bool UnpinTopmostWindow(HWND window) const noexcept;
bool AssignBorder(HWND window);
void RefreshBorders();
void UpdateDimOverlay();
// Transparency methods
HWND ResolveTransparencyTargetWindow(HWND window);

View File

@@ -121,7 +121,6 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="AlwaysOnTop.cpp" />
<ClCompile Include="DimOverlay.cpp" />
<ClCompile Include="FrameDrawer.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="pch.cpp">
@@ -137,7 +136,6 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="AlwaysOnTop.h" />
<ClInclude Include="DimOverlay.h" />
<ClInclude Include="FrameDrawer.h" />
<ClInclude Include="Generated Files/resource.h" />
<ClInclude Include="ModuleConstants.h" />
@@ -200,4 +198,4 @@
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>
</Project>

View File

@@ -30,9 +30,6 @@
<ClCompile Include="AlwaysOnTop.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DimOverlay.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FrameDrawer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -82,9 +79,6 @@
<ClInclude Include="AlwaysOnTop.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="DimOverlay.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FrameDrawer.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -136,4 +130,4 @@
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
</Project>
</Project>

View File

@@ -1,383 +0,0 @@
#include "pch.h"
#include "DimOverlay.h"
#include <common/utils/MsWindowsSettings.h>
#include <DispatcherQueue.h>
#include <dwmapi.h>
#include <windows.ui.composition.interop.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.UI.h>
#include <chrono>
#include <algorithm>
#include <cmath>
namespace winrt
{
using namespace winrt::Windows::System;
using namespace winrt::Windows::UI::Composition;
using namespace winrt::Windows::UI::Composition::Desktop;
}
namespace ABI
{
using namespace ABI::Windows::System;
using namespace ABI::Windows::UI::Composition::Desktop;
}
namespace
{
constexpr wchar_t OverlayWindowClassName[] = L"AlwaysOnTop_DimOverlay";
constexpr int FadeDurationMs = 200;
winrt::Windows::UI::Color GetDefaultDimColor()
{
return winrt::Windows::UI::ColorHelper::FromArgb(160, 32, 32, 32);
}
}
DimOverlay::~DimOverlay()
{
Terminate();
}
bool DimOverlay::Initialize(HINSTANCE hinstance)
{
m_hinstance = hinstance;
return CreateWindowAndVisuals();
}
void DimOverlay::Terminate()
{
if (m_destroyed)
{
return;
}
m_destroyed = true;
if (m_hwnd)
{
DestroyWindow(m_hwnd);
m_hwnd = nullptr;
}
if (m_hwndOwner)
{
DestroyWindow(m_hwndOwner);
m_hwndOwner = nullptr;
}
}
HWND DimOverlay::Hwnd() const noexcept
{
return m_hwnd;
}
void DimOverlay::Update(std::vector<DimOverlayHole> holes, bool visible)
{
if (m_destroyed || !m_dispatcherQueueController)
{
return;
}
auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue();
bool enqueueSucceeded = dispatcherQueue.TryEnqueue([this, holes = std::move(holes), visible]() {
if (m_destroyed || !m_hwnd)
{
return;
}
m_lastHoles = holes;
UpdateWindowBounds();
UpdateRegion(holes);
SetVisible(visible);
});
if (!enqueueSucceeded)
{
Logger::error("Couldn't enqueue message to update the dim overlay.");
}
}
LRESULT CALLBACK DimOverlay::WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) noexcept
{
auto thisRef = reinterpret_cast<DimOverlay*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if ((thisRef == nullptr) && (message == WM_CREATE))
{
const auto createStruct = reinterpret_cast<LPCREATESTRUCT>(lparam);
thisRef = static_cast<DimOverlay*>(createStruct->lpCreateParams);
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(thisRef));
}
return thisRef ? thisRef->MessageHandler(hwnd, message, wparam, lparam) : DefWindowProc(hwnd, message, wparam, lparam);
}
LRESULT DimOverlay::MessageHandler(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) noexcept
{
switch (message)
{
case WM_DISPLAYCHANGE:
UpdateWindowBounds();
UpdateRegion(m_lastHoles);
break;
case WM_NCHITTEST:
return HTTRANSPARENT;
case WM_ERASEBKGND:
return TRUE;
case WM_NCDESTROY:
SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
break;
default:
break;
}
return DefWindowProc(hwnd, message, wparam, lparam);
}
bool DimOverlay::CreateWindowAndVisuals()
{
WNDCLASS wc{};
if (!GetClassInfoW(m_hinstance, OverlayWindowClassName, &wc))
{
wc.lpfnWndProc = DimOverlay::WndProc;
wc.hInstance = m_hinstance;
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = static_cast<HBRUSH>(GetStockObject(NULL_BRUSH));
wc.lpszClassName = OverlayWindowClassName;
if (!RegisterClassW(&wc))
{
Logger::error("Failed to register DimOverlay window class. GetLastError={}", GetLastError());
return false;
}
}
m_hwndOwner = CreateWindow(L"static", nullptr, WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, m_hinstance, nullptr);
if (!m_hwndOwner)
{
Logger::error("Failed to create DimOverlay owner window. GetLastError={}", GetLastError());
return false;
}
const DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP |
WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_TOPMOST;
m_hwnd = CreateWindowExW(exStyle,
OverlayWindowClassName,
L"PowerToys Always On Top Dim Overlay",
WS_POPUP,
CW_USEDEFAULT,
0,
CW_USEDEFAULT,
0,
m_hwndOwner,
nullptr,
m_hinstance,
this);
if (!m_hwnd)
{
Logger::error("Failed to create DimOverlay window. GetLastError={}", GetLastError());
return false;
}
BOOL excludeFromPeek = TRUE;
DwmSetWindowAttribute(m_hwnd, DWMWA_EXCLUDED_FROM_PEEK, &excludeFromPeek, sizeof(excludeFromPeek));
UpdateWindowBounds();
if (!EnsureDispatcherQueue())
{
return false;
}
if (!EnsureCompositor())
{
return false;
}
return true;
}
bool DimOverlay::EnsureDispatcherQueue()
{
if (m_dispatcherQueueController)
{
return true;
}
DispatcherQueueOptions options = {
sizeof(DispatcherQueueOptions),
DQTYPE_THREAD_CURRENT,
DQTAT_COM_NONE,
};
ABI::IDispatcherQueueController* controller = nullptr;
const HRESULT hr = CreateDispatcherQueueController(options, &controller);
if (FAILED(hr))
{
Logger::error("Failed to create DispatcherQueueController for DimOverlay. HRESULT={:#x}", hr);
return false;
}
*winrt::put_abi(m_dispatcherQueueController) = controller;
return true;
}
bool DimOverlay::EnsureCompositor()
{
try
{
m_compositor = winrt::Compositor();
ABI::IDesktopWindowTarget* target = nullptr;
winrt::check_hresult(m_compositor.as<ABI::ICompositorDesktopInterop>()->CreateDesktopWindowTarget(m_hwnd, false, &target));
*winrt::put_abi(m_target) = target;
m_root = m_compositor.CreateContainerVisual();
m_root.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_target.Root(m_root);
m_dim = m_compositor.CreateSpriteVisual();
m_dim.RelativeSizeAdjustment({ 1.0f, 1.0f });
m_dimBrush = m_compositor.CreateColorBrush(GetDefaultDimColor());
m_dim.Brush(m_dimBrush);
m_root.Children().InsertAtTop(m_dim);
m_root.Opacity(0.0f);
m_opacityAnimation = m_compositor.CreateScalarKeyFrameAnimation();
m_opacityAnimation.Target(L"Opacity");
m_opacityAnimation.InsertExpressionKeyFrame(1.0f, L"this.FinalValue");
BOOL animationsEnabled = GetAnimationsEnabled();
m_opacityAnimation.Duration(std::chrono::milliseconds{ animationsEnabled ? FadeDurationMs : 1 });
auto implicitAnimations = m_compositor.CreateImplicitAnimationCollection();
implicitAnimations.Insert(L"Opacity", m_opacityAnimation);
m_root.ImplicitAnimations(implicitAnimations);
}
catch (const winrt::hresult_error& e)
{
Logger::error("Failed to create DimOverlay composition resources: {}", winrt::to_string(e.message()));
return false;
}
return true;
}
void DimOverlay::UpdateWindowBounds()
{
if (!m_hwnd)
{
return;
}
RECT newBounds{};
newBounds.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
newBounds.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
newBounds.right = newBounds.left + GetSystemMetrics(SM_CXVIRTUALSCREEN);
newBounds.bottom = newBounds.top + GetSystemMetrics(SM_CYVIRTUALSCREEN);
if (EqualRect(&newBounds, &m_virtualBounds))
{
return;
}
m_virtualBounds = newBounds;
const int width = m_virtualBounds.right - m_virtualBounds.left;
const int height = m_virtualBounds.bottom - m_virtualBounds.top;
SetWindowPos(m_hwnd, nullptr, m_virtualBounds.left, m_virtualBounds.top, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
}
void DimOverlay::UpdateRegion(const std::vector<DimOverlayHole>& holes)
{
if (!m_hwnd)
{
return;
}
const int width = m_virtualBounds.right - m_virtualBounds.left;
const int height = m_virtualBounds.bottom - m_virtualBounds.top;
if (width <= 0 || height <= 0)
{
return;
}
HRGN baseRegion = CreateRectRgn(0, 0, width, height);
if (!baseRegion)
{
return;
}
RECT bounds{ 0, 0, width, height };
for (const auto& hole : holes)
{
RECT local = hole.rect;
OffsetRect(&local, -m_virtualBounds.left, -m_virtualBounds.top);
if (!IntersectRect(&local, &local, &bounds))
{
continue;
}
if (local.right <= local.left || local.bottom <= local.top)
{
continue;
}
int radius = (std::max)(0, hole.radius);
const int maxRadius = (std::min)((local.right - local.left) / 2, (local.bottom - local.top) / 2);
radius = (std::min)(radius, maxRadius);
HRGN holeRegion = nullptr;
if (radius > 0)
{
const int diameter = radius * 2;
holeRegion = CreateRoundRectRgn(local.left, local.top, local.right, local.bottom, diameter, diameter);
}
else
{
holeRegion = CreateRectRgn(local.left, local.top, local.right, local.bottom);
}
if (holeRegion)
{
CombineRgn(baseRegion, baseRegion, holeRegion, RGN_DIFF);
DeleteObject(holeRegion);
}
}
if (SetWindowRgn(m_hwnd, baseRegion, TRUE) == 0)
{
DeleteObject(baseRegion);
}
}
void DimOverlay::SetVisible(bool visible)
{
if (m_visible == visible)
{
return;
}
if (!m_root || !m_compositor)
{
return;
}
m_visible = visible;
if (visible)
{
ShowWindow(m_hwnd, SW_SHOWNOACTIVATE);
}
auto batch = m_compositor.CreateScopedBatch(winrt::CompositionBatchTypes::Animation);
m_root.Opacity(visible ? 1.0f : 0.0f);
batch.Completed([hwnd = m_hwnd, visible](auto&&, auto&&) {
if (!visible)
{
ShowWindow(hwnd, SW_HIDE);
}
});
batch.End();
}

View File

@@ -1,56 +0,0 @@
#pragma once
#include <windows.h>
#include <vector>
#include <winrt/Windows.System.h>
#include <winrt/Windows.UI.Composition.h>
#include <winrt/Windows.UI.Composition.Desktop.h>
struct DimOverlayHole
{
RECT rect{};
int radius = 0;
};
class DimOverlay
{
public:
DimOverlay() = default;
~DimOverlay();
bool Initialize(HINSTANCE hinstance);
void Terminate();
void Update(std::vector<DimOverlayHole> holes, bool visible);
HWND Hwnd() const noexcept;
private:
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) noexcept;
LRESULT MessageHandler(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) noexcept;
bool CreateWindowAndVisuals();
bool EnsureDispatcherQueue();
bool EnsureCompositor();
void UpdateWindowBounds();
void UpdateRegion(const std::vector<DimOverlayHole>& holes);
void SetVisible(bool visible);
HINSTANCE m_hinstance{};
HWND m_hwndOwner{};
HWND m_hwnd{};
RECT m_virtualBounds{};
bool m_visible = false;
bool m_destroyed = false;
winrt::Windows::System::DispatcherQueueController m_dispatcherQueueController{ nullptr };
winrt::Windows::UI::Composition::Compositor m_compositor{ nullptr };
winrt::Windows::UI::Composition::Desktop::DesktopWindowTarget m_target{ nullptr };
winrt::Windows::UI::Composition::ContainerVisual m_root{ nullptr };
winrt::Windows::UI::Composition::SpriteVisual m_dim{ nullptr };
winrt::Windows::UI::Composition::CompositionColorBrush m_dimBrush{ nullptr };
winrt::Windows::UI::Composition::ScalarKeyFrameAnimation m_opacityAnimation{ nullptr };
std::vector<DimOverlayHole> m_lastHoles;
};

View File

@@ -2,8 +2,6 @@
#include <SettingsObserver.h>
#include <memory>
class FrameDrawer;
class WindowBorder : public SettingsObserver
@@ -17,7 +15,6 @@ public:
void UpdateBorderPosition() const;
void UpdateBorderProperties() const;
HWND Hwnd() const noexcept { return m_window; }
protected:
static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept

View File

@@ -75,7 +75,7 @@ namespace Peek.FilePreviewer.Previewers
}
string base64FileCode = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(fileContent));
string theme = ThemeManager.GetWindowsBaseColor().ToLowerInvariant();
string theme = ThemeManager.GetMonacoTheme();
// prepping index html to load in
string html = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.ReadIndexHtml();

View File

@@ -197,7 +197,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco
/// <returns>Theme that should be used.</returns>
public static string GetTheme()
{
return Common.UI.ThemeManager.GetWindowsBaseColor().ToLowerInvariant();
return Common.UI.ThemeManager.GetMonacoTheme();
}
}
}

View File

@@ -16,6 +16,7 @@ using Microsoft.Web.WebView2.Core;
using Windows.ApplicationModel.DataTransfer;
using Windows.System;
using Windows.UI;
using Windows.UI.ViewManagement;
namespace RegistryPreviewUILib
{
@@ -148,10 +149,35 @@ namespace RegistryPreviewUILib
private async Task SetThemeAsync()
{
var theme = Application.Current.RequestedTheme == ApplicationTheme.Light ? "vs" : "vs-dark";
var theme = GetMonacoTheme();
await Browser.CoreWebView2.ExecuteScriptAsync($"monaco.editor.setTheme('{theme}')");
}
private static string GetMonacoTheme()
{
var uiSettings = new UISettings();
var highContrast = uiSettings.HighContrast;
if (highContrast)
{
// In high contrast mode, check if it's a dark or light high contrast theme
var foreground = uiSettings.GetColorValue(UIColorType.Foreground);
var background = uiSettings.GetColorValue(UIColorType.Background);
// Determine if it's a dark theme by comparing luminance
// Dark themes have light foreground and dark background
var foregroundLuminance = (0.299 * foreground.R + 0.587 * foreground.G + 0.114 * foreground.B) / 255;
var backgroundLuminance = (0.299 * background.R + 0.587 * background.G + 0.114 * background.B) / 255;
return backgroundLuminance < foregroundLuminance ? "hc-black" : "hc-light";
}
else
{
// Normal mode: use standard themes based on app theme
return Application.Current.RequestedTheme == ApplicationTheme.Light ? "vs" : "vs-dark";
}
}
private void OnTextChangedThrottleElapsed(object sender, ElapsedEventArgs e)
{
if (_textChangedThrottled)

View File

@@ -25,12 +25,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("wrap_mode")]
public IntProperty WrapMode { get; set; }
[JsonPropertyName("disable_cursor_wrap_on_single_monitor")]
public BoolProperty DisableCursorWrapOnSingleMonitor { get; set; }
public CursorWrapProperties()
{
ActivationShortcut = DefaultActivationShortcut;
AutoActivate = new BoolProperty(false);
DisableWrapDuringDrag = new BoolProperty(true);
WrapMode = new IntProperty(0); // 0=Both (default), 1=VerticalOnly, 2=HorizontalOnly
DisableCursorWrapOnSingleMonitor = new BoolProperty(false);
}
}
}

View File

@@ -56,6 +56,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library
settingsUpgraded = true;
}
// Add DisableCursorWrapOnSingleMonitor property if it doesn't exist (for users upgrading from older versions)
if (Properties.DisableCursorWrapOnSingleMonitor == null)
{
Properties.DisableCursorWrapOnSingleMonitor = new BoolProperty(false); // Default to false
settingsUpgraded = true;
}
return settingsUpgraded;
}
}

View File

@@ -54,6 +54,9 @@
<ComboBoxItem x:Uid="MouseUtils_CursorWrap_WrapMode_HorizontalOnly" />
</ComboBox>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind ViewModel.IsCursorWrapEnabled, Mode=OneWay}">
<CheckBox x:Uid="MouseUtils_CursorWrap_DisableOnSingleMonitor" IsChecked="{x:Bind ViewModel.CursorWrapDisableOnSingleMonitor, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>

View File

@@ -2726,6 +2726,9 @@ From there, simply click on one of the supported files in the File Explorer and
<data name="MouseUtils_CursorWrap_DisableWrapDuringDrag.Content" xml:space="preserve">
<value>Disable wrapping while dragging</value>
</data>
<data name="MouseUtils_CursorWrap_DisableOnSingleMonitor.Content" xml:space="preserve">
<value>Disable wrapping when using a single monitor</value>
</data>
<data name="MouseUtils_CursorWrap_AutoActivate.Header" xml:space="preserve">
<value>Auto-activate on startup</value>
</data>

View File

@@ -116,6 +116,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// Null-safe access in case property wasn't upgraded yet - default to 0 (Both)
_cursorWrapWrapMode = CursorWrapSettingsConfig.Properties.WrapMode?.Value ?? 0;
// Null-safe access in case property wasn't upgraded yet - default to false
_cursorWrapDisableOnSingleMonitor = CursorWrapSettingsConfig.Properties.DisableCursorWrapOnSingleMonitor?.Value ?? false;
int isEnabled = 0;
Utilities.NativeMethods.SystemParametersInfo(Utilities.NativeMethods.SPI_GETCLIENTAREAANIMATION, 0, ref isEnabled, 0);
@@ -1003,13 +1006,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
GeneralSettingsConfig.Enabled.CursorWrap = value;
OnPropertyChanged(nameof(IsCursorWrapEnabled));
// Auto-enable the AutoActivate setting when CursorWrap is enabled
// This ensures cursor wrapping is active immediately after enabling
if (value && !_cursorWrapAutoActivate)
{
CursorWrapAutoActivate = true;
}
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(outgoing.ToString());
@@ -1114,6 +1110,34 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public bool CursorWrapDisableOnSingleMonitor
{
get
{
return _cursorWrapDisableOnSingleMonitor;
}
set
{
if (value != _cursorWrapDisableOnSingleMonitor)
{
_cursorWrapDisableOnSingleMonitor = value;
// Ensure the property exists before setting value
if (CursorWrapSettingsConfig.Properties.DisableCursorWrapOnSingleMonitor == null)
{
CursorWrapSettingsConfig.Properties.DisableCursorWrapOnSingleMonitor = new BoolProperty(value);
}
else
{
CursorWrapSettingsConfig.Properties.DisableCursorWrapOnSingleMonitor.Value = value;
}
NotifyCursorWrapPropertyChanged();
}
}
}
public void NotifyCursorWrapPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);
@@ -1186,5 +1210,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private bool _cursorWrapAutoActivate;
private bool _cursorWrapDisableWrapDuringDrag; // Will be initialized in constructor from settings
private int _cursorWrapWrapMode; // 0=Both, 1=VerticalOnly, 2=HorizontalOnly
private bool _cursorWrapDisableOnSingleMonitor; // Disable cursor wrap when only one monitor is connected
}
}