Helper.14393.cs
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Threading; namespace DockPSHelper { public static class AppBarHelper { const double SizeShrinkFactor = 0.7; static IntPtr MessageWindowHandle { get { return new IntPtr(-3); } } enum SystemMetric : uint { PrimaryScreenWidth = 0, PrimaryScreenHeight = 1 } [DllImport("user32.dll")] static extern int GetSystemMetrics(SystemMetric index); enum ThreadDpiAwareContext : int { Invalid = 0, Unaware = -1, SystemAware = -2, PerMonitorAware = -3, /* Fails if used before Creators Update. */ PerMonitorAwareV2 = -4 } [DllImport("user32.dll")] static extern ThreadDpiAwareContext SetThreadDpiAwarenessContext(ThreadDpiAwareContext newContext); [DllImport("user32.dll", SetLastError = true)] static extern uint RegisterWindowMessage(string messageName); [DllImport("user32.dll", SetLastError = true)] static extern bool GetWindowRect(IntPtr handle, out Rect rect); [Flags] enum SetWindowPosFlags : uint { None = 0x0000, AsyncSet = 0x4000, HideWindow = 0x0080, NoActivate = 0x0010, NoMove = 0x0002, NoSize = 0x0001, NoZOrder = 0x0004, ShowWindow = 0x0040 } static IntPtr TopMostWindowHandle { get { return new IntPtr(-1); } } static IntPtr NoTopMostWindowHandle { get { return new IntPtr(-2); } } [DllImport("user32.dll", SetLastError = true)] static extern bool SetWindowPos(IntPtr handle, IntPtr insertAfterHandle, int left, int top, int width, int height, SetWindowPosFlags flags); const int WM_CREATE = 0x0001; const int WM_DESTORY = 0x0002; const int WM_SYSCOMMAND = 0x0112; const int WM_DPICHANGED = 0x02E0; const int SC_RESTORE = 0xF120; [DllImport("user32.dll", CharSet = CharSet.Auto)] static extern IntPtr SendMessage(IntPtr handle, int message, IntPtr WParam, IntPtr LParam); [DllImport("shell32.dll", SetLastError = true)] static extern IntPtr SHAppBarMessage(AppBarMessage message, ref AppBarData appBarData); enum WindowLongIndex : int { Style = -16, ExtendedStyle = -20 } [DllImport("user32.dll", SetLastError = true)] static extern uint GetWindowLong(IntPtr handle, WindowLongIndex index); [DllImport("user32.dll", SetLastError = true)] static extern uint SetWindowLong(IntPtr handle, WindowLongIndex index, uint newLong); [Flags] enum WindowStyle : uint { None = 0, Caption = 12582912, ThickFrame = 262144, SystemMenu = 524288, Popup = 2147483648 } [Flags] enum WindowStyleExtended : uint { None = 0, WindowEdge = 256, TopMost = 8, ToolWindow = 128, AppWindow = 262144 } [StructLayout(LayoutKind.Sequential)] struct Rect { public int Left, Top, Right, Bottom; } enum AppBarMessage : uint { New = 0, Remove = 1, QueryPosition = 2, SetPosition = 3, GetState = 4, GetTaskbarPosition = 5, Activate = 6, GetAutoHideBar = 7, SetAutoHideBar = 8, WindowPositionChanged = 9, SetState = 10 } enum AppBarNotification : uint { StateChange = 0, PositionChanged = 1, FullScreenApp = 2, WindowArrange = 3 } [Flags] enum AppBarState : uint { None = 0, AutoHide = 1, AlwaysOnTop = 2 } public enum AppBarEdge : uint { Left = 0, Top = 1, Right = 2, Bottom = 3 } [StructLayout(LayoutKind.Sequential)] struct AppBarData { static uint msgId; public static uint MessageIdentifierStatic { get { return msgId; } } static AppBarData() { msgId = RegisterWindowMessage("Dock-PS AppBarMessage"); } public AppBarData(IntPtr ownerWindow) { StructSize = Marshal.SizeOf(typeof(AppBarData)); Handle = ownerWindow; MessageIdentifier = msgId; Edge = AppBarEdge.Left; Bounds = new Rect(); LParam = 0; } public readonly int StructSize; public readonly IntPtr Handle; public readonly uint MessageIdentifier; public AppBarEdge Edge; public Rect Bounds; public uint LParam; } sealed class AppBarPreference { public AppBarPreference() { width = 0.4; height = 0.4; } public AppBarEdge DockingPosition { get; set; } double width, height; public double Width { get { return width; } set { if (value <= 0) return; if (value < 0.2) value = 0.2; if (value > 0.45) value = 0.45; width = value; } } public double Height { get { return height; } set { if (value <= 0) return; if (value < 0.2) value = 0.2; if (value > 0.45) value = 0.45; height = value; } } } abstract class AppBarCommand { public AppBarCommand(IntPtr target) { TargetWindow = target; } public IntPtr TargetWindow { get; private set; } public abstract void InvokeTask(); } sealed class CreateAppBarCommand : AppBarCommand { public CreateAppBarCommand(IntPtr target, AppBarEdge dockingPosition) : base(target) { DockingPosition = dockingPosition; } AppBarEdge DockingPosition; public override void InvokeTask() { AppBarData data = new AppBarData(TargetWindow); if (SHAppBarMessage(AppBarMessage.New, ref data) == IntPtr.Zero) return; SendMessage(TargetWindow, WM_SYSCOMMAND, (IntPtr)SC_RESTORE, IntPtr.Zero); SetWindowPos(TargetWindow, TopMostWindowHandle, 0, 0, 0, 0, SetWindowPosFlags.NoMove | SetWindowPosFlags.NoSize | SetWindowPosFlags.NoActivate); managedAppBars[TargetWindow] = new AppBarPreference() { DockingPosition = DockingPosition }; commands.Enqueue(new RefreshAppBarCommand(TargetWindow, false)); commands.Enqueue(new RefreshAppBarCommand(TargetWindow, true)); } } sealed class RefreshAppBarCommand : AppBarCommand { public bool Recurs { get; private set; } public RefreshAppBarCommand(IntPtr target, bool recurs) : base(target) { Recurs = recurs; } static void RecurCommand(object param) { Thread.Sleep(700); if (!hasCleanUpStarted) commands.Enqueue((RefreshAppBarCommand)param); } public override void InvokeTask() { AppBarPreference preference; if (!managedAppBars.TryGetValue(TargetWindow, out preference)) return; uint style = GetWindowLong(TargetWindow, WindowLongIndex.Style); bool isFullScreen = ((style & (uint)WindowStyle.Popup) != 0); if (!isFullScreen) { style &= ~(uint)(WindowStyle.Caption | WindowStyle.ThickFrame | WindowStyle.SystemMenu); SetWindowLong(TargetWindow, WindowLongIndex.Style, style); style = GetWindowLong(TargetWindow, WindowLongIndex.ExtendedStyle); style |= (uint)(WindowStyleExtended.WindowEdge | WindowStyleExtended.TopMost); SetWindowLong(TargetWindow, WindowLongIndex.ExtendedStyle, style); } int scrWidth = GetSystemMetrics(SystemMetric.PrimaryScreenWidth); int scrHeight = GetSystemMetrics(SystemMetric.PrimaryScreenHeight); AppBarData data = new AppBarData(TargetWindow); data.Edge = preference.DockingPosition; data.Bounds.Left = 0; data.Bounds.Right = scrWidth; data.Bounds.Top = 0; data.Bounds.Bottom = scrHeight; SHAppBarMessage(AppBarMessage.QueryPosition, ref data); switch (data.Edge) { case AppBarEdge.Left: data.Bounds.Right = Math.Min( data.Bounds.Right, data.Bounds.Left + (int)(scrWidth * preference.Width)); break; case AppBarEdge.Right: data.Bounds.Left = Math.Max( data.Bounds.Left, data.Bounds.Right - (int)(scrWidth * preference.Width)); break; case AppBarEdge.Top: data.Bounds.Bottom = Math.Min( data.Bounds.Bottom, data.Bounds.Top + (int)(scrHeight * preference.Height)); break; case AppBarEdge.Bottom: data.Bounds.Top = Math.Max( data.Bounds.Top, data.Bounds.Bottom - (int)(scrHeight * preference.Height)); break; } SHAppBarMessage(AppBarMessage.SetPosition, ref data); preference.DockingPosition = data.Edge; if (!isFullScreen) { SetWindowPos(TargetWindow, IntPtr.Zero, data.Bounds.Left, data.Bounds.Top, data.Bounds.Right - data.Bounds.Left, data.Bounds.Bottom - data.Bounds.Top, SetWindowPosFlags.NoZOrder | SetWindowPosFlags.NoActivate); } if (!hasCleanUpStarted && Recurs) { Thread recurThread = new Thread(RecurCommand); recurThread.IsBackground = true; recurThread.Start(this); } } } sealed class RemoveAppBarCommand : AppBarCommand { public RemoveAppBarCommand(IntPtr target) : base(target) { } public override void InvokeTask() { AppBarPreference preference; bool preferenceSuccess = managedAppBars.TryRemove(TargetWindow, out preference); AppBarData data = new AppBarData(TargetWindow); SHAppBarMessage(AppBarMessage.Remove, ref data); uint style = GetWindowLong(TargetWindow, WindowLongIndex.Style); bool isFullScreen = ((style & (uint)WindowStyle.Popup) != 0); if (!isFullScreen) { style |= (uint)(WindowStyle.Caption | WindowStyle.ThickFrame | WindowStyle.SystemMenu); SetWindowLong(TargetWindow, WindowLongIndex.Style, style); } style = GetWindowLong(TargetWindow, WindowLongIndex.ExtendedStyle); style &= ~(uint)(WindowStyleExtended.WindowEdge | WindowStyleExtended.TopMost); SetWindowLong(TargetWindow, WindowLongIndex.ExtendedStyle, style); if (preferenceSuccess && !isFullScreen) { SetWindowPos(TargetWindow, NoTopMostWindowHandle, 0, 0, (int)(GetSystemMetrics(SystemMetric.PrimaryScreenWidth) * preference.Width), (int)(GetSystemMetrics(SystemMetric.PrimaryScreenHeight) * preference.Height), SetWindowPosFlags.NoMove | SetWindowPosFlags.NoActivate); } else { SetWindowPos(TargetWindow, NoTopMostWindowHandle, 0, 0, 0, 0, SetWindowPosFlags.NoMove | SetWindowPosFlags.NoSize | SetWindowPosFlags.NoActivate); } } } sealed class CommandQueue { Queue<AppBarCommand> internalQueue; AutoResetEvent incoming; public CommandQueue() { internalQueue = new Queue<AppBarCommand>(); incoming = new AutoResetEvent(false); } public void Enqueue(AppBarCommand command) { lock (internalQueue) { internalQueue.Enqueue(command); } incoming.Set(); } public void Unblock() { incoming.Set(); } public bool TryDequeue(out AppBarCommand command) { lock (internalQueue) { if (internalQueue.Count != 0) { command = internalQueue.Dequeue(); return true; } command = null; return false; } } public bool WaitDeque(int milliseconds, out AppBarCommand command) { bool success = TryDequeue(out command); if (success) return true; incoming.WaitOne(milliseconds); return TryDequeue(out command); } } static ConcurrentDictionary<IntPtr, AppBarPreference> managedAppBars; static CommandQueue commands; static volatile bool hasCleanUpStarted; static Thread worker; static AppBarHelper() { managedAppBars = new ConcurrentDictionary<IntPtr, AppBarPreference>(); commands = new CommandQueue(); hasCleanUpStarted = false; worker = new Thread(AppBarWorkerThread); worker.IsBackground = false; worker.Start(); } static void AppBarWorkerThread() { /* Console windows belong to CSRSS and are DPI aware. * As for Windows 10 Anniversary Update, powershell.exe * is not DPI-aware thus we have to set the current * thread as per-monitor DPI aware. */ SetThreadDpiAwarenessContext(ThreadDpiAwareContext.PerMonitorAware); while (!hasCleanUpStarted) { AppBarCommand command; if (commands.WaitDeque(-1, out command)) command.InvokeTask(); else Thread.Yield(); } while (true) { AppBarCommand command; if (commands.TryDequeue(out command)) command.InvokeTask(); else break; } foreach (IntPtr handle in managedAppBars.Keys.ToArray()) new RemoveAppBarCommand(handle).InvokeTask(); } /* This method is called when PowerShell is exited by typing "exit". */ public static void CleanUp() { hasCleanUpStarted = true; /* In case the queue is empty. */ commands.Unblock(); /* Gracefully shutdown. */ worker.Priority = ThreadPriority.Highest; worker.Join(); } public static void AddAppBarWindow(IntPtr target, AppBarEdge dockingPosition) { commands.Enqueue(new CreateAppBarCommand(target, dockingPosition)); } public static void MoveAppBarWindow(IntPtr target, AppBarEdge dockingPosition) { AppBarPreference preference; if (!managedAppBars.TryGetValue(target, out preference)) return; preference.DockingPosition = dockingPosition; commands.Enqueue(new RefreshAppBarCommand(target, false)); } public static void ResizeAppBarWindow(IntPtr target, double newWidth, double newHeight) { AppBarPreference preference; if (!managedAppBars.TryGetValue(target, out preference)) return; preference.Width = newWidth; preference.Height = newHeight; commands.Enqueue(new RefreshAppBarCommand(target, false)); } public static void RemoveAppBarWindow(IntPtr target) { commands.Enqueue(new RemoveAppBarCommand(target)); } } } |