From 549a8d5bd95369ebacd54766a2678ee7b8ce07a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Sun, 9 Aug 2026 13:19:41 -0500 Subject: [PATCH 1/9] Add deferred work area parameter to UpdatePosition --- src/ManagedShell.AppBar/AppBarWindow.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/ManagedShell.AppBar/AppBarWindow.cs b/src/ManagedShell.AppBar/AppBarWindow.cs index b3f9e78a..d4d99000 100644 --- a/src/ManagedShell.AppBar/AppBarWindow.cs +++ b/src/ManagedShell.AppBar/AppBarWindow.cs @@ -713,7 +713,7 @@ protected internal NativeMethods.Rect GetDesiredRect() return rect; } - protected internal bool SetWindowPosition(NativeMethods.Rect newRect) + protected internal bool SetWindowPosition(NativeMethods.Rect newRect, bool deferWorkArea = false) { var currentRect = WindowRect; if (newRect.Top == currentRect.Top && @@ -735,7 +735,7 @@ protected internal bool SetWindowPosition(NativeMethods.Rect newRect) NativeMethods.SetWindowPos(Handle, IntPtr.Zero, newRect.Left, newRect.Top, newRect.Width, newRect.Height, swp); IsMoving = false; - if (EnvironmentHelper.IsAppRunningAsShell) + if (EnvironmentHelper.IsAppRunningAsShell && !deferWorkArea) { _appBarManager.SetWorkArea(Screen); } @@ -766,14 +766,24 @@ protected virtual void SetScreenProperties(ScreenSetupReason reason) } public virtual bool UpdatePosition() + { + return UpdatePosition(false); + } + + public virtual bool UpdatePosition(bool deferWorkArea) { // Let Explorer AppBar figure out our position if we are an AppBar, otherwise set our desired rect if (AppBarMode == AppBarMode.Normal && !EnvironmentHelper.IsAppRunningAsShell) { + if (deferWorkArea) + { + return SetWindowPosition(GetDesiredRect(), true); + } + return _appBarManager.ABSetPos(this); } - return SetWindowPosition(GetDesiredRect()); + return SetWindowPosition(GetDesiredRect(), deferWorkArea); } #endregion From 7c7a89db59bc5ed9088976e54933166277b01e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Sun, 9 Aug 2026 13:20:23 -0500 Subject: [PATCH 2/9] Fix POINT struct for x64 --- src/ManagedShell.Interop/NativeMethods.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/ManagedShell.Interop/NativeMethods.cs b/src/ManagedShell.Interop/NativeMethods.cs index 43d12352..bf750da5 100644 --- a/src/ManagedShell.Interop/NativeMethods.cs +++ b/src/ManagedShell.Interop/NativeMethods.cs @@ -53,16 +53,23 @@ public ShortRect(short left, short top, short right, short bottom) public int Height => Bottom - Top; } + [StructLayout(LayoutKind.Sequential)] public struct POINT { - public POINT(long x, long y) + public int x; + public int y; + + public POINT(int x, int y) { this.x = x; this.y = y; } - - public long x; - public long y; + + public POINT(long x, long y) + { + this.x = (int)x; + this.y = (int)y; + } } // lo = x; hi = y From 9cff5b668c0a27f58ea8632e7f42cac324fe381a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Sun, 9 Aug 2026 23:48:44 -0500 Subject: [PATCH 3/9] Respect deferred app bar work area updates Skip app bar reposition and broadcast handling while `DeferWorkArea` is enabled. This prevents `AppBarWindow` from reacting to position-change notifications or unexpected moves during deferred work area updates. --- src/ManagedShell.AppBar/AppBarWindow.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ManagedShell.AppBar/AppBarWindow.cs b/src/ManagedShell.AppBar/AppBarWindow.cs index d4d99000..386e0eee 100644 --- a/src/ManagedShell.AppBar/AppBarWindow.cs +++ b/src/ManagedShell.AppBar/AppBarWindow.cs @@ -465,7 +465,10 @@ protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lPa switch ((NativeMethods.AppBarNotifications)wParam.ToInt32()) { case NativeMethods.AppBarNotifications.PosChanged: - _appBarManager.ABSetPos(this); + if (!DeferWorkArea) + { + _appBarManager.ABSetPos(this); + } break; case NativeMethods.AppBarNotifications.WindowArrange: @@ -538,14 +541,14 @@ protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lPa changed = true; } - if (changed && AppBarMode == AppBarMode.Normal && !EnvironmentHelper.IsAppRunningAsShell && !AllowClose) + if (changed && AppBarMode == AppBarMode.Normal && !EnvironmentHelper.IsAppRunningAsShell && !AllowClose && !DeferWorkArea) { // Tell other AppBars we changed _appBarManager.AppBarWindowPosChanged(this); } // Determine if we are intentionally moving - if (changed && !IsMoving && (wndPos.flags & NativeMethods.SetWindowPosFlags.SWP_NOMOVE) == 0) + if (changed && !IsMoving && !DeferWorkArea && (wndPos.flags & NativeMethods.SetWindowPosFlags.SWP_NOMOVE) == 0) { // Someone else moved us! Let's restore state. ShellLogger.Debug($"AppBarWindow: Repositioning due to unexpected move to {wndPos.x},{wndPos.y}"); @@ -713,6 +716,8 @@ protected internal NativeMethods.Rect GetDesiredRect() return rect; } + public bool DeferWorkArea { get; set; } + protected internal bool SetWindowPosition(NativeMethods.Rect newRect, bool deferWorkArea = false) { var currentRect = WindowRect; From c3871a40e2146d7d6c30ab5b50567f9658380977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Mon, 10 Aug 2026 00:10:30 -0500 Subject: [PATCH 4/9] Add user32 system menu interop Expose the user32 constants and P/Invokes needed to work with window system menus, including cursor position, menu enabling, and popup tracking. --- .../NativeMethods.User32.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/ManagedShell.Interop/NativeMethods.User32.cs b/src/ManagedShell.Interop/NativeMethods.User32.cs index 1d2d8bff..50a18d0e 100644 --- a/src/ManagedShell.Interop/NativeMethods.User32.cs +++ b/src/ManagedShell.Interop/NativeMethods.User32.cs @@ -232,11 +232,37 @@ public enum WindowLongFlags : int public const int HSHELL_HIGHBIT = 0x8000; public const int SC_MINIMIZE = 0xF020; + public const int SC_MAXIMIZE = 0xF030; public const int SC_MOVE = 0xF010; public const int SC_RESTORE = 0xF120; public const int SC_SIZE = 0xF000; public const int SC_CLOSE = 0xF060; + public const uint MF_BYCOMMAND = 0x00000000; + public const uint MF_GRAYED = 0x00000001; + public const uint MF_ENABLED = 0x00000000; + + public const uint TPM_LEFTBUTTON = 0x0000; + public const uint TPM_RIGHTBUTTON = 0x0002; + public const uint TPM_LEFTALIGN = 0x0000; + public const uint TPM_RIGHTALIGN = 0x0008; + public const uint TPM_TOPALIGN = 0x0000; + public const uint TPM_BOTTOMALIGN = 0x0020; + public const uint TPM_VERTICAL = 0x0040; + public const uint TPM_RETURNCMD = 0x0100; + + [DllImport(User32_DllName, SetLastError = true)] + public static extern bool GetCursorPos(out POINT lpPoint); + + [DllImport(User32_DllName, SetLastError = true)] + public static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert); + + [DllImport(User32_DllName, SetLastError = true)] + public static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable); + + [DllImport(User32_DllName, SetLastError = true)] + public static extern int TrackPopupMenuEx(IntPtr hMenu, uint uFlags, int x, int y, IntPtr hWnd, IntPtr lpTPMParams); + [StructLayout(LayoutKind.Sequential)] public struct COPYDATASTRUCT { From 12720b86d1f67e7d0c631093ca5603f51dc75dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Tue, 11 Aug 2026 12:50:14 -0500 Subject: [PATCH 5/9] Consolidate TrackPopupMenuEx in User32 --- .../NativeMethods.User32.cs | 39 ++++++++++++++----- src/ManagedShell.ShellFolders/Enums/TPM.cs | 29 -------------- src/ManagedShell.ShellFolders/Interop.cs | 13 ------- .../ShellFolderContextMenu.cs | 4 +- .../ShellItemContextMenu.cs | 4 +- 5 files changed, 33 insertions(+), 56 deletions(-) delete mode 100644 src/ManagedShell.ShellFolders/Enums/TPM.cs diff --git a/src/ManagedShell.Interop/NativeMethods.User32.cs b/src/ManagedShell.Interop/NativeMethods.User32.cs index 50a18d0e..b5bc5f6a 100644 --- a/src/ManagedShell.Interop/NativeMethods.User32.cs +++ b/src/ManagedShell.Interop/NativeMethods.User32.cs @@ -242,14 +242,31 @@ public enum WindowLongFlags : int public const uint MF_GRAYED = 0x00000001; public const uint MF_ENABLED = 0x00000000; - public const uint TPM_LEFTBUTTON = 0x0000; - public const uint TPM_RIGHTBUTTON = 0x0002; - public const uint TPM_LEFTALIGN = 0x0000; - public const uint TPM_RIGHTALIGN = 0x0008; - public const uint TPM_TOPALIGN = 0x0000; - public const uint TPM_BOTTOMALIGN = 0x0020; - public const uint TPM_VERTICAL = 0x0040; - public const uint TPM_RETURNCMD = 0x0100; + // Specifies how TrackPopupMenuEx positions the shortcut menu horizontally + [Flags] + public enum TPM : uint + { + LEFTBUTTON = 0x0000, + RIGHTBUTTON = 0x0002, + LEFTALIGN = 0x0000, + CENTERALIGN = 0x0004, + RIGHTALIGN = 0x0008, + TOPALIGN = 0x0000, + VCENTERALIGN = 0x0010, + BOTTOMALIGN = 0x0020, + HORIZONTAL = 0x0000, + VERTICAL = 0x0040, + NONOTIFY = 0x0080, + RETURNCMD = 0x0100, + RECURSE = 0x0001, + HORPOSANIMATION = 0x0400, + HORNEGANIMATION = 0x0800, + VERPOSANIMATION = 0x1000, + VERNEGANIMATION = 0x2000, + NOANIMATION = 0x4000, + LAYOUTRTL = 0x8000, + WORKAREA = 0x10000 + } [DllImport(User32_DllName, SetLastError = true)] public static extern bool GetCursorPos(out POINT lpPoint); @@ -260,8 +277,10 @@ public enum WindowLongFlags : int [DllImport(User32_DllName, SetLastError = true)] public static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable); - [DllImport(User32_DllName, SetLastError = true)] - public static extern int TrackPopupMenuEx(IntPtr hMenu, uint uFlags, int x, int y, IntPtr hWnd, IntPtr lpTPMParams); + // Displays a shortcut menu at the specified location and + // tracks the selection of items on the shortcut menu + [DllImport(User32_DllName, ExactSpelling = true, SetLastError = true)] + public static extern uint TrackPopupMenuEx(IntPtr hMenu, TPM uFlags, int x, int y, IntPtr hWnd, IntPtr lpTPMParams); [StructLayout(LayoutKind.Sequential)] public struct COPYDATASTRUCT diff --git a/src/ManagedShell.ShellFolders/Enums/TPM.cs b/src/ManagedShell.ShellFolders/Enums/TPM.cs deleted file mode 100644 index 31295583..00000000 --- a/src/ManagedShell.ShellFolders/Enums/TPM.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace ManagedShell.ShellFolders.Enums -{ - // Specifies how TrackPopupMenuEx positions the shortcut menu horizontally - [Flags] - public enum TPM : uint - { - LEFTBUTTON = 0x0000, - RIGHTBUTTON = 0x0002, - LEFTALIGN = 0x0000, - CENTERALIGN = 0x0004, - RIGHTALIGN = 0x0008, - TOPALIGN = 0x0000, - VCENTERALIGN = 0x0010, - BOTTOMALIGN = 0x0020, - HORIZONTAL = 0x0000, - VERTICAL = 0x0040, - NONOTIFY = 0x0080, - RETURNCMD = 0x0100, - RECURSE = 0x0001, - HORPOSANIMATION = 0x0400, - HORNEGANIMATION = 0x0800, - VERPOSANIMATION = 0x1000, - VERNEGANIMATION = 0x2000, - NOANIMATION = 0x4000, - LAYOUTRTL = 0x8000 - } -} diff --git a/src/ManagedShell.ShellFolders/Interop.cs b/src/ManagedShell.ShellFolders/Interop.cs index 0d8a4242..0b2658c1 100644 --- a/src/ManagedShell.ShellFolders/Interop.cs +++ b/src/ManagedShell.ShellFolders/Interop.cs @@ -119,19 +119,6 @@ public static extern int CoCreateInstance( ref Guid riid, out IntPtr ppv); - // Displays a shortcut menu at the specified location and - // tracks the selection of items on the shortcut menu - [DllImport("user32.dll", - ExactSpelling = true, - CharSet = CharSet.Auto)] - public static extern uint TrackPopupMenuEx( - IntPtr hmenu, - TPM flags, - int x, - int y, - IntPtr hwnd, - IntPtr lptpm); - // Creates a popup-menu. The menu is initially empty, but it can be filled with // menu items by using the InsertMenuItem, AppendMenu, and InsertMenu functions [DllImport("user32", diff --git a/src/ManagedShell.ShellFolders/ShellFolderContextMenu.cs b/src/ManagedShell.ShellFolders/ShellFolderContextMenu.cs index c3551800..8df4d410 100644 --- a/src/ManagedShell.ShellFolders/ShellFolderContextMenu.cs +++ b/src/ManagedShell.ShellFolders/ShellFolderContextMenu.cs @@ -86,9 +86,9 @@ private void ShowMenu(ShellFolder folder, IntPtr contextMenu) NativeMethods.AllowDarkModeForWindow(Handle, true); } - uint selected = Interop.TrackPopupMenuEx( + uint selected = NativeMethods.TrackPopupMenuEx( contextMenu, - TPM.RETURNCMD, + NativeMethods.TPM.RETURNCMD, x, y, Handle, diff --git a/src/ManagedShell.ShellFolders/ShellItemContextMenu.cs b/src/ManagedShell.ShellFolders/ShellItemContextMenu.cs index e98c4045..7673461b 100644 --- a/src/ManagedShell.ShellFolders/ShellItemContextMenu.cs +++ b/src/ManagedShell.ShellFolders/ShellItemContextMenu.cs @@ -171,9 +171,9 @@ private void ShowMenu(ShellItem[] files, bool allFolders) NativeMethods.AllowDarkModeForWindow(Handle, true); } - uint selected = Interop.TrackPopupMenuEx( + uint selected = NativeMethods.TrackPopupMenuEx( nativeMenuPtr, - TPM.RETURNCMD, + NativeMethods.TPM.RETURNCMD, x, y, Handle, From 8095652a39a0de84221e08905d3598d8dbcad41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Wed, 12 Aug 2026 10:14:58 -0500 Subject: [PATCH 6/9] Use DeferWorkArea in position updates Remove the extra `deferWorkArea` parameters from window positioning methods and rely on the `DeferWorkArea` property instead. This keeps work area updates tied to the app bar's current state and simplifies the positioning flow. --- src/ManagedShell.AppBar/AppBarWindow.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/ManagedShell.AppBar/AppBarWindow.cs b/src/ManagedShell.AppBar/AppBarWindow.cs index 386e0eee..414f2ece 100644 --- a/src/ManagedShell.AppBar/AppBarWindow.cs +++ b/src/ManagedShell.AppBar/AppBarWindow.cs @@ -718,7 +718,7 @@ protected internal NativeMethods.Rect GetDesiredRect() public bool DeferWorkArea { get; set; } - protected internal bool SetWindowPosition(NativeMethods.Rect newRect, bool deferWorkArea = false) + protected internal bool SetWindowPosition(NativeMethods.Rect newRect) { var currentRect = WindowRect; if (newRect.Top == currentRect.Top && @@ -740,7 +740,7 @@ protected internal bool SetWindowPosition(NativeMethods.Rect newRect, bool defer NativeMethods.SetWindowPos(Handle, IntPtr.Zero, newRect.Left, newRect.Top, newRect.Width, newRect.Height, swp); IsMoving = false; - if (EnvironmentHelper.IsAppRunningAsShell && !deferWorkArea) + if (EnvironmentHelper.IsAppRunningAsShell && !DeferWorkArea) { _appBarManager.SetWorkArea(Screen); } @@ -771,24 +771,19 @@ protected virtual void SetScreenProperties(ScreenSetupReason reason) } public virtual bool UpdatePosition() - { - return UpdatePosition(false); - } - - public virtual bool UpdatePosition(bool deferWorkArea) { // Let Explorer AppBar figure out our position if we are an AppBar, otherwise set our desired rect if (AppBarMode == AppBarMode.Normal && !EnvironmentHelper.IsAppRunningAsShell) { - if (deferWorkArea) + if (DeferWorkArea) { - return SetWindowPosition(GetDesiredRect(), true); + return SetWindowPosition(GetDesiredRect()); } return _appBarManager.ABSetPos(this); } - return SetWindowPosition(GetDesiredRect(), deferWorkArea); + return SetWindowPosition(GetDesiredRect()); } #endregion From 570f18ec24f3e250bb1e4b8d49666e2470ce97fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Wed, 12 Aug 2026 12:48:30 -0500 Subject: [PATCH 7/9] Fix fallback to XP balloon sound for toast notification When `Notification.Default` is set to `(None)` (such as in legacy or custom sound schemes), `PlaySound` with `SND_ALIAS` returned `true` without playing an audible sound, preventing the fallback to the XP notification sound. Using the registry-checking `PlaySystemSound` overload ensures empty sound assignments return `false` and trigger the fallback. --- src/ManagedShell.Common/Helpers/SoundHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ManagedShell.Common/Helpers/SoundHelper.cs b/src/ManagedShell.Common/Helpers/SoundHelper.cs index 382a7c34..cbe45c03 100644 --- a/src/ManagedShell.Common/Helpers/SoundHelper.cs +++ b/src/ManagedShell.Common/Helpers/SoundHelper.cs @@ -157,7 +157,7 @@ public static void PlayNotificationSound() if (EnvironmentHelper.IsWindows8OrBetter) { // Toast notification sound. - if (!PlaySystemSound("Notification.Default")) + if (!PlaySystemSound(".Default", "Notification.Default")) PlayXPNotificationSound(); } else From 753181ef330eac0c4d63a081231f328b948d6f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Thu, 13 Aug 2026 18:47:01 -0500 Subject: [PATCH 8/9] Add region APIs to Win32 interop Adds missing P/Invoke declarations for `CreateRectRgn` (Gdi32) and `SetWindowRgn` (User32). This enables creating and applying custom window regions from managed code, supporting non-rectangular window shaping/clipping. --- src/ManagedShell.Interop/NativeMethods.Gdi32.cs | 3 +++ src/ManagedShell.Interop/NativeMethods.User32.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/ManagedShell.Interop/NativeMethods.Gdi32.cs b/src/ManagedShell.Interop/NativeMethods.Gdi32.cs index ff64fc2a..9cc1995f 100644 --- a/src/ManagedShell.Interop/NativeMethods.Gdi32.cs +++ b/src/ManagedShell.Interop/NativeMethods.Gdi32.cs @@ -9,5 +9,8 @@ public partial class NativeMethods [DllImport(Gdi32_DllName)] public static extern bool DeleteObject(IntPtr hObject); + + [DllImport(Gdi32_DllName)] + public static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); } } diff --git a/src/ManagedShell.Interop/NativeMethods.User32.cs b/src/ManagedShell.Interop/NativeMethods.User32.cs index b5bc5f6a..c88927da 100644 --- a/src/ManagedShell.Interop/NativeMethods.User32.cs +++ b/src/ManagedShell.Interop/NativeMethods.User32.cs @@ -36,6 +36,9 @@ public partial class NativeMethods [DllImport(User32_DllName)] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); + [DllImport(User32_DllName)] + public static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw); + public enum WindowZOrder { HWND_TOP = 0, From 7f164c4dd4a847fe75479e396a38802968a64be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaro=20Mart=C3=ADnez?= Date: Thu, 13 Aug 2026 18:51:37 -0500 Subject: [PATCH 9/9] Simplify UpdatePosition conditional check Combine nested if conditions in AppBarWindow.UpdatePosition per review suggestion from @dremin. --- src/ManagedShell.AppBar/AppBarWindow.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/ManagedShell.AppBar/AppBarWindow.cs b/src/ManagedShell.AppBar/AppBarWindow.cs index 414f2ece..daaab462 100644 --- a/src/ManagedShell.AppBar/AppBarWindow.cs +++ b/src/ManagedShell.AppBar/AppBarWindow.cs @@ -773,13 +773,8 @@ protected virtual void SetScreenProperties(ScreenSetupReason reason) public virtual bool UpdatePosition() { // Let Explorer AppBar figure out our position if we are an AppBar, otherwise set our desired rect - if (AppBarMode == AppBarMode.Normal && !EnvironmentHelper.IsAppRunningAsShell) + if (AppBarMode == AppBarMode.Normal && !EnvironmentHelper.IsAppRunningAsShell && !DeferWorkArea) { - if (DeferWorkArea) - { - return SetWindowPosition(GetDesiredRect()); - } - return _appBarManager.ABSetPos(this); }