mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
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>
This commit is contained in:
@@ -163,8 +163,22 @@ void CursorWrapCore::UpdateMonitorInfo()
|
|||||||
Logger::info(L"======= UPDATE MONITOR INFO END =======");
|
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
|
// Check if wrapping should be disabled during drag
|
||||||
if (disableWrapDuringDrag && (GetAsyncKeyState(VK_LBUTTON) & 0x8000))
|
if (disableWrapDuringDrag && (GetAsyncKeyState(VK_LBUTTON) & 0x8000))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ public:
|
|||||||
|
|
||||||
// Handle mouse move with wrap mode filtering
|
// Handle mouse move with wrap mode filtering
|
||||||
// wrapMode: 0=Both, 1=VerticalOnly, 2=HorizontalOnly
|
// 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; }
|
const std::vector<MonitorInfo>& GetMonitors() const { return m_monitors; }
|
||||||
|
size_t GetMonitorCount() const { return m_monitors.size(); }
|
||||||
const MonitorTopology& GetTopology() const { return m_topology; }
|
const MonitorTopology& GetTopology() const { return m_topology; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ namespace
|
|||||||
const wchar_t JSON_KEY_AUTO_ACTIVATE[] = L"auto_activate";
|
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_DISABLE_WRAP_DURING_DRAG[] = L"disable_wrap_during_drag";
|
||||||
const wchar_t JSON_KEY_WRAP_MODE[] = L"wrap_mode";
|
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.
|
// The PowerToy name that will be shown in the settings.
|
||||||
@@ -80,6 +81,7 @@ private:
|
|||||||
bool m_enabled = false;
|
bool m_enabled = false;
|
||||||
bool m_autoActivate = false;
|
bool m_autoActivate = false;
|
||||||
bool m_disableWrapDuringDrag = true; // Default to true to prevent wrap during drag
|
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
|
int m_wrapMode = 0; // 0=Both (default), 1=VerticalOnly, 2=HorizontalOnly
|
||||||
|
|
||||||
// Mouse hook
|
// Mouse hook
|
||||||
@@ -415,6 +417,21 @@ private:
|
|||||||
{
|
{
|
||||||
Logger::warn("Failed to initialize CursorWrap wrap mode from settings. Will use default value (0=Both)");
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -646,7 +663,8 @@ private:
|
|||||||
POINT newPos = g_cursorWrapInstance->m_core.HandleMouseMove(
|
POINT newPos = g_cursorWrapInstance->m_core.HandleMouseMove(
|
||||||
currentPos,
|
currentPos,
|
||||||
g_cursorWrapInstance->m_disableWrapDuringDrag,
|
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)
|
if (newPos.x != currentPos.x || newPos.y != currentPos.y)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,12 +25,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
[JsonPropertyName("wrap_mode")]
|
[JsonPropertyName("wrap_mode")]
|
||||||
public IntProperty WrapMode { get; set; }
|
public IntProperty WrapMode { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("disable_cursor_wrap_on_single_monitor")]
|
||||||
|
public BoolProperty DisableCursorWrapOnSingleMonitor { get; set; }
|
||||||
|
|
||||||
public CursorWrapProperties()
|
public CursorWrapProperties()
|
||||||
{
|
{
|
||||||
ActivationShortcut = DefaultActivationShortcut;
|
ActivationShortcut = DefaultActivationShortcut;
|
||||||
AutoActivate = new BoolProperty(false);
|
AutoActivate = new BoolProperty(false);
|
||||||
DisableWrapDuringDrag = new BoolProperty(true);
|
DisableWrapDuringDrag = new BoolProperty(true);
|
||||||
WrapMode = new IntProperty(0); // 0=Both (default), 1=VerticalOnly, 2=HorizontalOnly
|
WrapMode = new IntProperty(0); // 0=Both (default), 1=VerticalOnly, 2=HorizontalOnly
|
||||||
|
DisableCursorWrapOnSingleMonitor = new BoolProperty(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
settingsUpgraded = true;
|
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;
|
return settingsUpgraded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,9 @@
|
|||||||
<ComboBoxItem x:Uid="MouseUtils_CursorWrap_WrapMode_HorizontalOnly" />
|
<ComboBoxItem x:Uid="MouseUtils_CursorWrap_WrapMode_HorizontalOnly" />
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
</tkcontrols:SettingsCard>
|
</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.Items>
|
||||||
</tkcontrols:SettingsExpander>
|
</tkcontrols:SettingsExpander>
|
||||||
</controls:SettingsGroup>
|
</controls:SettingsGroup>
|
||||||
|
|||||||
@@ -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">
|
<data name="MouseUtils_CursorWrap_DisableWrapDuringDrag.Content" xml:space="preserve">
|
||||||
<value>Disable wrapping while dragging</value>
|
<value>Disable wrapping while dragging</value>
|
||||||
</data>
|
</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">
|
<data name="MouseUtils_CursorWrap_AutoActivate.Header" xml:space="preserve">
|
||||||
<value>Auto-activate on startup</value>
|
<value>Auto-activate on startup</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -116,6 +116,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
// Null-safe access in case property wasn't upgraded yet - default to 0 (Both)
|
// Null-safe access in case property wasn't upgraded yet - default to 0 (Both)
|
||||||
_cursorWrapWrapMode = CursorWrapSettingsConfig.Properties.WrapMode?.Value ?? 0;
|
_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;
|
int isEnabled = 0;
|
||||||
|
|
||||||
Utilities.NativeMethods.SystemParametersInfo(Utilities.NativeMethods.SPI_GETCLIENTAREAANIMATION, 0, ref isEnabled, 0);
|
Utilities.NativeMethods.SystemParametersInfo(Utilities.NativeMethods.SPI_GETCLIENTAREAANIMATION, 0, ref isEnabled, 0);
|
||||||
@@ -1114,6 +1117,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)
|
public void NotifyCursorWrapPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
{
|
{
|
||||||
OnPropertyChanged(propertyName);
|
OnPropertyChanged(propertyName);
|
||||||
@@ -1186,5 +1217,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
private bool _cursorWrapAutoActivate;
|
private bool _cursorWrapAutoActivate;
|
||||||
private bool _cursorWrapDisableWrapDuringDrag; // Will be initialized in constructor from settings
|
private bool _cursorWrapDisableWrapDuringDrag; // Will be initialized in constructor from settings
|
||||||
private int _cursorWrapWrapMode; // 0=Both, 1=VerticalOnly, 2=HorizontalOnly
|
private int _cursorWrapWrapMode; // 0=Both, 1=VerticalOnly, 2=HorizontalOnly
|
||||||
|
private bool _cursorWrapDisableOnSingleMonitor; // Disable cursor wrap when only one monitor is connected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user