Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DATA_AND_PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@
| Event Name | Description |
| --- | --- |
| Microsoft.PowerToys.GrabAndMove_EnableGrabAndMove | Triggered when Grab And Move is enabled or disabled. |
| Microsoft.PowerToys.GrabAndMove_ShortcutUse | Logs an attempt to move or resize a window via the Alt+Drag shortcut, including whether it succeeded, the action type (move or resize), and the reason (e.g., started, blocked by game mode). |
| Microsoft.PowerToys.GrabAndMove_ShortcutUse | Logs an attempt to move, resize, or maximize/restore a window via Grab And Move mouse shortcuts, including whether it succeeded, the action type (move, resize, or maximize), and the reason (e.g., started, toggled, blocked by game mode, or not maximizable). |

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error

maximizable is not a recognized word
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

### Hosts File Editor

Expand Down
77 changes: 74 additions & 3 deletions src/modules/GrabAndMove/GrabAndMove/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@
static bool g_showGeometry = false; // true if we want to draw the X, Y, W and H on the overlay on move and resize
static bool g_doNotActivateOnGameMode = true; // true if GrabAndMove is suppressed when Windows Game Mode is active

static bool g_useAltResize = true; // This can be toggled from the settings. If false, Alt + right click does nothing.
static bool g_useAltResize = true; // This can be toggled from the settings. If false, Alt + right click does nothing.
static bool g_useMiddleClickMaximize = true; // If true, modifier + middle click toggles maximize/restore.
static bool g_middleClickConsumed = false; // Swallow the matching middle-button release after a handled press.

// Count of non-modifier keys currently held. Used to suppress GrabAndMove when the
// modifier key is pressed while another key is already down (e.g. Q held, then modifier pressed).
Expand Down Expand Up @@ -225,11 +227,23 @@
{
Move,
Resize,
Maximize,
};

static void TraceShortcutUse(bool successful, GrabAndMoveShortcutAction action, const wchar_t* reason) noexcept
{
const wchar_t* actionName = action == GrabAndMoveShortcutAction::Move ? L"move" : L"resize";
const wchar_t* actionName = L"move";
switch (action)
{
case GrabAndMoveShortcutAction::Resize:
actionName = L"resize";
break;
case GrabAndMoveShortcutAction::Maximize:
actionName = L"maximize";
break;
default:
break;
}

TraceLoggingWrite(
g_hProvider,
Expand All @@ -241,6 +255,22 @@
TraceLoggingWideString(reason, "Reason"));
}

static bool IsWindowMaximizable(HWND hwnd)
{
return (GetWindowLongPtrW(hwnd, GWL_STYLE) & WS_MAXIMIZEBOX) != 0;
}

static bool ToggleWindowMaximized(HWND hwnd)
{
if (!hwnd || !IsWindowMaximizable(hwnd))
{
return false;
}

ShowWindow(hwnd, IsZoomed(hwnd) ? SW_RESTORE : SW_MAXIMIZE);
return true;
}

// ---------------------------------------------------------------------------
// Settings file helpers
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -270,6 +300,11 @@
g_useAltResize = *v;
}

if (auto v = values.get_bool_value(L"useMiddleClickMaximize"))
{
g_useMiddleClickMaximize = *v;
}

if (auto v = values.get_int_value(L"modifierKey"))
{
g_modifierKey = (*v == 1) ? GrabAndMoveModifier::Win : GrabAndMoveModifier::Alt;
Expand Down Expand Up @@ -1011,16 +1046,52 @@
goto forward;

// Recovery path: if a non-modifier click occurs while stale drag/resize state exists, clear it.
if ((wParam == WM_LBUTTONDOWN || wParam == WM_RBUTTONDOWN) && !IsActivationModifierPressed())
if ((wParam == WM_LBUTTONDOWN || wParam == WM_RBUTTONDOWN || wParam == WM_MBUTTONDOWN) && !IsActivationModifierPressed())
{
EndInteraction(true, true);
}

if (wParam == WM_MBUTTONUP && g_middleClickConsumed)
{
g_middleClickConsumed = false;
return 1;
}

if (!g_dragging && !g_resizing && g_hOverlay && IsWindowVisible(g_hOverlay))
{
HideOverlay();
}

// ----- Alt + Middle Button Down: toggle maximize/restore -----
if (wParam == WM_MBUTTONDOWN && g_useMiddleClickMaximize && IsActivationModifierPressed())
{
if (IsSuppressedByGameMode())
{
TraceShortcutUse(false, GrabAndMoveShortcutAction::Maximize, L"game_mode");
goto forward;
}

POINT pt = ms->pt;
HWND hwnd = ResolveTargetWindow(pt);
if (hwnd)
{
if (IsExcluded(hwnd))
{
goto forward;
}

if (ToggleWindowMaximized(hwnd))
{
g_dragConsumedAlt = true;
g_middleClickConsumed = true;
TraceShortcutUse(true, GrabAndMoveShortcutAction::Maximize, L"toggled");
return 1;
}

TraceShortcutUse(false, GrabAndMoveShortcutAction::Maximize, L"not_maximizable");

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error

maximizable is not a recognized word
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}
}

// ----- Alt + Left Button Down: start drag -----
if (wParam == WM_LBUTTONDOWN && IsActivationModifierPressed())
{
Expand Down
4 changes: 4 additions & 0 deletions src/settings-ui/Settings.UI.Library/GrabAndMoveProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public GrabAndMoveProperties()
DoNotActivateOnGameMode = new BoolProperty(true);
ShowGeometry = new BoolProperty(false);
UseAltResize = new BoolProperty(true);
UseMiddleClickMaximize = new BoolProperty(true);
ExcludedApps = new StringProperty();
ModifierKey = new IntProperty(0); // 0 = Alt, 1 = Win
}
Expand All @@ -30,6 +31,9 @@ public GrabAndMoveProperties()
[JsonPropertyName("useAltResize")]
public BoolProperty UseAltResize { get; set; }

[JsonPropertyName("useMiddleClickMaximize")]
public BoolProperty UseMiddleClickMaximize { get; set; }

[JsonPropertyName("doNotActivateOnGameMode")]
public BoolProperty DoNotActivateOnGameMode { get; set; }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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.Text.Json;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ViewModelTests
{
[TestClass]
public class GrabAndMove
{
[TestMethod]
public void MiddleClickMaximizeIsEnabledByDefault()
{
var settings = new GrabAndMoveSettings();

Assert.IsTrue(settings.Properties.UseMiddleClickMaximize.Value);
}

[TestMethod]
public void UpdatingMiddleClickMaximizeNotifiesRunner()
{
var moduleSettings = new GrabAndMoveSettings();
string serializedSettings = string.Empty;

using var viewModel = new GrabAndMoveViewModel(
SettingsRepository<GeneralSettings>.GetInstance(ISettingsUtilsMocks.GetStubSettingsUtils<GeneralSettings>().Object),
moduleSettings,
msg =>
{
serializedSettings = msg;
return 0;
});

viewModel.UseMiddleClickMaximize = false;

var outgoingSettings = JsonSerializer.Deserialize<SndModuleSettings<SndGrabAndMoveSettings>>(serializedSettings);
Assert.IsFalse(outgoingSettings!.PowertoysSetting.Settings.Properties.UseMiddleClickMaximize.Value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
<tkcontrols:SettingsCard x:Uid="GrabAndMove_ShowGeometry" HeaderIcon="{ui:FontIcon Glyph=&#xE809;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowGeometry, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="GrabAndMove_UseMiddleClickMaximize" HeaderIcon="{ui:FontIcon Glyph=&#xE740;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.UseMiddleClickMaximize, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</controls:SettingsGroup>

<controls:SettingsGroup x:Uid="ExcludedApps" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
Expand Down
9 changes: 8 additions & 1 deletion src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -6162,6 +6162,13 @@ Text uses the current drawing color.</value>
<data name="GrabAndMove_UseAltResize.Description" xml:space="preserve">
<value>Hold the modifier key and drag with the right mouse button from the nearest edge or corner to resize the window</value>
</data>
<data name="GrabAndMove_UseMiddleClickMaximize.Header" xml:space="preserve">
<value>Maximize or restore windows with the modifier key + middle-click</value>
<comment>Middle-click refers to clicking the mouse wheel button.</comment>
</data>
<data name="GrabAndMove_UseMiddleClickMaximize.Description" xml:space="preserve">
<value>Hold the modifier key and middle-click inside a window to toggle between maximized and restored</value>
</data>
<data name="GrabAndMove_ExcludeApps.Header" xml:space="preserve">
<value>Excluded apps</value>
</data>
Expand All @@ -6172,4 +6179,4 @@ Text uses the current drawing color.</value>
<value>Example: outlook</value>
<comment>{Locked="outlook"}</comment>
</data>
</root>
</root>
15 changes: 15 additions & 0 deletions src/settings-ui/Settings.UI/ViewModels/GrabAndMoveViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,21 @@ public bool UseAltResize
}
}

public bool UseMiddleClickMaximize
{
get => _moduleSettings.Properties.UseMiddleClickMaximize.Value;

set
{
if (_moduleSettings.Properties.UseMiddleClickMaximize.Value != value)
{
_moduleSettings.Properties.UseMiddleClickMaximize.Value = value;
NotifyModuleSettingsChanged();
OnPropertyChanged(nameof(UseMiddleClickMaximize));
}
}
}

public bool DoNotActivateOnGameMode
{
get => _moduleSettings.Properties.DoNotActivateOnGameMode.Value;
Expand Down