2025-11-05 11:59:08 +08:00
|
|
|
// 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;
|
2025-11-19 14:40:02 +08:00
|
|
|
using System.IO;
|
|
|
|
|
using System.IO.MemoryMappedFiles;
|
2025-11-05 11:59:08 +08:00
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using System.Threading;
|
2025-11-20 15:32:32 +08:00
|
|
|
using Microsoft.PowerToys.QuickAccess.Flyout;
|
|
|
|
|
using Microsoft.PowerToys.QuickAccess.Services;
|
|
|
|
|
using Microsoft.PowerToys.QuickAccess.ViewModels;
|
2025-11-05 11:59:08 +08:00
|
|
|
using Microsoft.UI.Dispatching;
|
|
|
|
|
using Microsoft.UI.Windowing;
|
|
|
|
|
using Microsoft.UI.Xaml;
|
|
|
|
|
using Windows.Graphics;
|
|
|
|
|
using WinRT.Interop;
|
2025-11-05 17:10:27 +08:00
|
|
|
using WinUIEx;
|
2025-11-05 11:59:08 +08:00
|
|
|
|
|
|
|
|
namespace Microsoft.PowerToys.QuickAccess;
|
|
|
|
|
|
2025-11-20 15:32:32 +08:00
|
|
|
public sealed partial class MainWindow : WindowEx, IDisposable
|
2025-11-05 11:59:08 +08:00
|
|
|
{
|
|
|
|
|
private readonly QuickAccessLaunchContext _launchContext;
|
|
|
|
|
private readonly DispatcherQueue _dispatcherQueue;
|
|
|
|
|
private readonly IntPtr _hwnd;
|
|
|
|
|
private readonly AppWindow? _appWindow;
|
2025-11-20 15:32:32 +08:00
|
|
|
private readonly LauncherViewModel _launcherViewModel;
|
|
|
|
|
private readonly AllAppsViewModel _allAppsViewModel;
|
|
|
|
|
private readonly QuickAccessCoordinator _coordinator;
|
|
|
|
|
private bool _disposed;
|
2025-11-05 11:59:08 +08:00
|
|
|
private EventWaitHandle? _showEvent;
|
|
|
|
|
private EventWaitHandle? _exitEvent;
|
|
|
|
|
private ManualResetEventSlim? _listenerShutdownEvent;
|
|
|
|
|
private Thread? _showListenerThread;
|
|
|
|
|
private Thread? _exitListenerThread;
|
|
|
|
|
private bool _isWindowCloaked;
|
|
|
|
|
private bool _initialActivationHandled;
|
|
|
|
|
private bool _isPrimed;
|
2025-11-20 15:32:32 +08:00
|
|
|
|
|
|
|
|
// Prevent auto-hide until the window actually gained focus once.
|
|
|
|
|
private bool _hasSeenInteractiveActivation;
|
2025-12-02 13:33:14 +08:00
|
|
|
private bool _isVisible;
|
|
|
|
|
private IntPtr _mouseHook;
|
|
|
|
|
private LowLevelMouseProc? _mouseHookDelegate;
|
2025-11-05 11:59:08 +08:00
|
|
|
|
|
|
|
|
private const int DefaultWidth = 320;
|
|
|
|
|
private const int DefaultHeight = 480;
|
|
|
|
|
private const int DwmWaCloak = 13;
|
|
|
|
|
private const int GwlStyle = -16;
|
2025-11-05 17:34:18 +08:00
|
|
|
private const int GwlExStyle = -20;
|
2025-11-05 11:59:08 +08:00
|
|
|
private const int SwHide = 0;
|
|
|
|
|
private const int SwShow = 5;
|
|
|
|
|
private const int SwShowNoActivate = 8;
|
|
|
|
|
private const uint SwpShowWindow = 0x0040;
|
|
|
|
|
private const uint SwpNoZorder = 0x0004;
|
|
|
|
|
private const uint SwpNoSize = 0x0001;
|
|
|
|
|
private const uint SwpNoMove = 0x0002;
|
|
|
|
|
private const uint SwpNoActivate = 0x0010;
|
|
|
|
|
private const uint SwpFrameChanged = 0x0020;
|
|
|
|
|
private const long WsSysmenu = 0x00080000L;
|
|
|
|
|
private const long WsMinimizeBox = 0x00020000L;
|
|
|
|
|
private const long WsMaximizeBox = 0x00010000L;
|
2025-11-05 17:34:18 +08:00
|
|
|
private const long WsExToolWindow = 0x00000080L;
|
2025-11-19 14:40:02 +08:00
|
|
|
private const uint MonitorDefaulttonearest = 0x00000002;
|
2025-11-05 11:59:08 +08:00
|
|
|
private static readonly IntPtr HwndTopmost = new(-1);
|
|
|
|
|
private static readonly IntPtr HwndBottom = new(1);
|
|
|
|
|
|
|
|
|
|
public MainWindow(QuickAccessLaunchContext launchContext)
|
|
|
|
|
{
|
|
|
|
|
InitializeComponent();
|
|
|
|
|
_launchContext = launchContext;
|
|
|
|
|
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
|
|
|
|
_hwnd = WindowNative.GetWindowHandle(this);
|
|
|
|
|
_appWindow = InitializeAppWindow(_hwnd);
|
|
|
|
|
Title = "PowerToys Quick Access (Preview)";
|
|
|
|
|
|
2025-11-20 15:32:32 +08:00
|
|
|
_coordinator = new QuickAccessCoordinator(this, _launchContext);
|
|
|
|
|
_launcherViewModel = new LauncherViewModel(_coordinator);
|
|
|
|
|
_allAppsViewModel = new AllAppsViewModel(_coordinator);
|
|
|
|
|
ShellHost.Initialize(_coordinator, _launcherViewModel, _allAppsViewModel);
|
|
|
|
|
|
2025-11-05 11:59:08 +08:00
|
|
|
CustomizeWindowChrome();
|
2025-11-05 17:34:18 +08:00
|
|
|
HideFromTaskbar();
|
2025-11-05 11:59:08 +08:00
|
|
|
HideWindow();
|
|
|
|
|
InitializeEventListeners();
|
|
|
|
|
Closed += OnClosed;
|
|
|
|
|
Activated += OnActivated;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private AppWindow? InitializeAppWindow(IntPtr hwnd)
|
|
|
|
|
{
|
|
|
|
|
var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hwnd);
|
|
|
|
|
return AppWindow.GetFromWindowId(windowId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void HideWindow()
|
|
|
|
|
{
|
|
|
|
|
if (_hwnd != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
var cloaked = CloakWindow();
|
|
|
|
|
|
|
|
|
|
if (!ShowWindowNative(_hwnd, SwHide) && _appWindow != null)
|
|
|
|
|
{
|
|
|
|
|
_appWindow.Hide();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cloaked)
|
|
|
|
|
{
|
|
|
|
|
ShowWindowNative(_hwnd, SwShowNoActivate);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SetWindowPosNative(_hwnd, HwndBottom, 0, 0, 0, 0, SwpNoMove | SwpNoSize | SwpNoActivate);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (_appWindow != null)
|
|
|
|
|
{
|
|
|
|
|
_appWindow.Hide();
|
|
|
|
|
}
|
2025-12-02 13:33:14 +08:00
|
|
|
|
|
|
|
|
_isVisible = false;
|
|
|
|
|
RemoveGlobalMouseHook();
|
2025-12-05 08:48:48 +08:00
|
|
|
|
|
|
|
|
// Reduce memory usage when hidden
|
|
|
|
|
GC.Collect();
|
|
|
|
|
GC.WaitForPendingFinalizers();
|
|
|
|
|
GC.Collect();
|
|
|
|
|
SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
|
2025-11-05 11:59:08 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-20 15:32:32 +08:00
|
|
|
internal void RequestHide()
|
|
|
|
|
{
|
|
|
|
|
if (_dispatcherQueue.HasThreadAccess)
|
|
|
|
|
{
|
|
|
|
|
HideWindow();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_dispatcherQueue.TryEnqueue(HideWindow);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 11:59:08 +08:00
|
|
|
private void InitializeEventListeners()
|
|
|
|
|
{
|
|
|
|
|
if (!string.IsNullOrEmpty(_launchContext.ShowEventName))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_showEvent = EventWaitHandle.OpenExisting(_launchContext.ShowEventName!);
|
|
|
|
|
EnsureListenerInfrastructure();
|
|
|
|
|
StartShowListenerThread();
|
|
|
|
|
}
|
|
|
|
|
catch (WaitHandleCannotBeOpenedException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(_launchContext.ExitEventName))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_exitEvent = EventWaitHandle.OpenExisting(_launchContext.ExitEventName!);
|
|
|
|
|
EnsureListenerInfrastructure();
|
|
|
|
|
StartExitListenerThread();
|
|
|
|
|
}
|
|
|
|
|
catch (WaitHandleCannotBeOpenedException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ShowWindow()
|
|
|
|
|
{
|
|
|
|
|
if (_hwnd != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
UncloakWindow();
|
2025-11-19 14:40:02 +08:00
|
|
|
|
2025-11-05 11:59:08 +08:00
|
|
|
ShowWindowNative(_hwnd, SwShow);
|
2025-11-19 14:40:02 +08:00
|
|
|
|
|
|
|
|
var flags = SwpNoSize | SwpShowWindow;
|
|
|
|
|
var targetX = 0;
|
|
|
|
|
var targetY = 0;
|
|
|
|
|
|
2025-12-04 16:21:55 +08:00
|
|
|
var windowSize = _appWindow?.Size;
|
|
|
|
|
var windowWidth = windowSize?.Width ?? DefaultWidth;
|
|
|
|
|
var windowHeight = windowSize?.Height ?? DefaultHeight;
|
2025-11-19 14:40:02 +08:00
|
|
|
|
2025-12-04 16:21:55 +08:00
|
|
|
GetCursorPos(out var cursorPosition);
|
|
|
|
|
var monitorHandle = MonitorFromPointNative(cursorPosition, MonitorDefaulttonearest);
|
|
|
|
|
if (monitorHandle != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
var monitorInfo = new MonitorInfo { CbSize = Marshal.SizeOf<MonitorInfo>() };
|
|
|
|
|
if (GetMonitorInfoNative(monitorHandle, ref monitorInfo))
|
2025-11-19 14:40:02 +08:00
|
|
|
{
|
2025-12-04 16:21:55 +08:00
|
|
|
targetX = monitorInfo.RcWork.Right - windowWidth;
|
|
|
|
|
targetY = monitorInfo.RcWork.Bottom - windowHeight;
|
2025-11-19 14:40:02 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetWindowPosNative(_hwnd, HwndTopmost, targetX, targetY, 0, 0, flags);
|
2025-11-05 17:34:18 +08:00
|
|
|
BringToForeground(_hwnd);
|
2025-11-05 11:59:08 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-24 13:24:35 +08:00
|
|
|
_hasSeenInteractiveActivation = true;
|
|
|
|
|
_initialActivationHandled = true;
|
2025-11-05 11:59:08 +08:00
|
|
|
Activate();
|
2025-12-02 13:33:14 +08:00
|
|
|
_isVisible = true;
|
|
|
|
|
EnsureGlobalMouseHook();
|
2025-12-04 13:26:11 +08:00
|
|
|
ShellHost.RefreshIfAppsList();
|
2025-11-05 11:59:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnActivated(object sender, WindowActivatedEventArgs args)
|
|
|
|
|
{
|
|
|
|
|
if (args.WindowActivationState == WindowActivationState.Deactivated)
|
|
|
|
|
{
|
2025-11-20 15:32:32 +08:00
|
|
|
if (!_hasSeenInteractiveActivation)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 11:59:08 +08:00
|
|
|
HideWindow();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-20 15:32:32 +08:00
|
|
|
_hasSeenInteractiveActivation = true;
|
|
|
|
|
|
2025-11-05 11:59:08 +08:00
|
|
|
if (_initialActivationHandled)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_initialActivationHandled = true;
|
|
|
|
|
PrimeWindow();
|
|
|
|
|
HideWindow();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnClosed(object sender, WindowEventArgs e)
|
|
|
|
|
{
|
2025-11-20 15:32:32 +08:00
|
|
|
Dispose();
|
2025-11-05 11:59:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void PrimeWindow()
|
|
|
|
|
{
|
|
|
|
|
if (_isPrimed || _hwnd == IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_isPrimed = true;
|
|
|
|
|
|
|
|
|
|
if (_appWindow != null)
|
|
|
|
|
{
|
|
|
|
|
var currentPosition = _appWindow.Position;
|
|
|
|
|
_appWindow.MoveAndResize(new RectInt32(currentPosition.X, currentPosition.Y, DefaultWidth, DefaultHeight));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Warm up the window while cloaked so the first summon does not pay XAML initialization cost.
|
|
|
|
|
var cloaked = CloakWindow();
|
|
|
|
|
if (cloaked)
|
|
|
|
|
{
|
|
|
|
|
ShowWindowNative(_hwnd, SwShowNoActivate);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 17:34:18 +08:00
|
|
|
private void HideFromTaskbar()
|
|
|
|
|
{
|
|
|
|
|
if (_appWindow == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_appWindow.IsShownInSwitchers = false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 11:59:08 +08:00
|
|
|
private bool CloakWindow()
|
|
|
|
|
{
|
|
|
|
|
if (_hwnd == IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_isWindowCloaked)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int cloak = 1;
|
|
|
|
|
var result = DwmSetWindowAttribute(_hwnd, DwmWaCloak, ref cloak, sizeof(int));
|
|
|
|
|
if (result == 0)
|
|
|
|
|
{
|
|
|
|
|
_isWindowCloaked = true;
|
|
|
|
|
SetWindowPosNative(_hwnd, HwndBottom, 0, 0, 0, 0, SwpNoMove | SwpNoSize | SwpNoActivate);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void UncloakWindow()
|
|
|
|
|
{
|
|
|
|
|
if (_hwnd == IntPtr.Zero || !_isWindowCloaked)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int cloak = 0;
|
|
|
|
|
var result = DwmSetWindowAttribute(_hwnd, DwmWaCloak, ref cloak, sizeof(int));
|
|
|
|
|
if (result == 0)
|
|
|
|
|
{
|
|
|
|
|
_isWindowCloaked = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-20 15:32:32 +08:00
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
Dispose(true);
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
if (_disposed)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (disposing)
|
|
|
|
|
{
|
|
|
|
|
StopEventListeners();
|
|
|
|
|
|
|
|
|
|
_showEvent?.Dispose();
|
|
|
|
|
_showEvent = null;
|
|
|
|
|
|
|
|
|
|
_exitEvent?.Dispose();
|
|
|
|
|
_exitEvent = null;
|
|
|
|
|
|
|
|
|
|
if (_hwnd != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
UncloakWindow();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 13:33:14 +08:00
|
|
|
RemoveGlobalMouseHook();
|
2025-11-20 15:32:32 +08:00
|
|
|
|
|
|
|
|
_coordinator.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_disposed = true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 11:59:08 +08:00
|
|
|
[DllImport("user32.dll", EntryPoint = "ShowWindow", SetLastError = true)]
|
|
|
|
|
private static extern bool ShowWindowNative(IntPtr hWnd, int nCmdShow);
|
|
|
|
|
|
|
|
|
|
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", SetLastError = true)]
|
|
|
|
|
private static extern nint GetWindowLongPtrNative(IntPtr hWnd, int nIndex);
|
|
|
|
|
|
|
|
|
|
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", SetLastError = true)]
|
|
|
|
|
private static extern nint SetWindowLongPtrNative(IntPtr hWnd, int nIndex, nint dwNewLong);
|
|
|
|
|
|
|
|
|
|
[DllImport("user32.dll", EntryPoint = "SetWindowPos", SetLastError = true)]
|
|
|
|
|
private static extern bool SetWindowPosNative(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
|
|
|
|
|
|
|
|
|
|
[DllImport("user32.dll", EntryPoint = "SetForegroundWindow", SetLastError = true)]
|
|
|
|
|
private static extern bool SetForegroundWindowNative(IntPtr hWnd);
|
|
|
|
|
|
2025-11-05 17:34:18 +08:00
|
|
|
[DllImport("user32.dll", EntryPoint = "GetForegroundWindow", SetLastError = true)]
|
|
|
|
|
private static extern IntPtr GetForegroundWindowNative();
|
|
|
|
|
|
|
|
|
|
[DllImport("user32.dll", EntryPoint = "GetWindowThreadProcessId", SetLastError = true)]
|
|
|
|
|
private static extern uint GetWindowThreadProcessIdNative(IntPtr hWnd, IntPtr lpdwProcessId);
|
|
|
|
|
|
|
|
|
|
[DllImport("user32.dll", EntryPoint = "AttachThreadInput", SetLastError = true)]
|
|
|
|
|
private static extern bool AttachThreadInputNative(uint idAttach, uint idAttachTo, bool fAttach);
|
|
|
|
|
|
2025-11-05 11:59:08 +08:00
|
|
|
[DllImport("dwmapi.dll", EntryPoint = "DwmSetWindowAttribute", SetLastError = true)]
|
|
|
|
|
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
|
|
|
|
|
|
2025-11-19 14:40:02 +08:00
|
|
|
[DllImport("user32.dll", EntryPoint = "MonitorFromPoint", SetLastError = true)]
|
|
|
|
|
private static extern IntPtr MonitorFromPointNative(NativePoint pt, uint dwFlags);
|
|
|
|
|
|
|
|
|
|
[DllImport("user32.dll", EntryPoint = "GetMonitorInfoW", SetLastError = true)]
|
|
|
|
|
private static extern bool GetMonitorInfoNative(IntPtr hMonitor, ref MonitorInfo lpmi);
|
|
|
|
|
|
2025-12-02 13:33:14 +08:00
|
|
|
[DllImport("user32.dll", EntryPoint = "SetWindowsHookExW", SetLastError = true)]
|
|
|
|
|
private static extern IntPtr SetWindowsHookExNative(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
|
|
|
|
|
|
|
|
|
|
[DllImport("user32.dll", EntryPoint = "UnhookWindowsHookEx", SetLastError = true)]
|
|
|
|
|
private static extern bool UnhookWindowsHookExNative(IntPtr hhk);
|
|
|
|
|
|
|
|
|
|
[DllImport("user32.dll", EntryPoint = "CallNextHookEx", SetLastError = true)]
|
|
|
|
|
private static extern IntPtr CallNextHookExNative(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
|
|
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", EntryPoint = "GetModuleHandleW", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
|
|
|
private static extern IntPtr GetModuleHandleNative([MarshalAs(UnmanagedType.LPWStr)] string? lpModuleName);
|
|
|
|
|
|
|
|
|
|
[DllImport("user32.dll", EntryPoint = "GetWindowRect", SetLastError = true)]
|
|
|
|
|
private static extern bool GetWindowRectNative(IntPtr hWnd, out Rect rect);
|
|
|
|
|
|
2025-11-05 17:34:18 +08:00
|
|
|
private static void BringToForeground(IntPtr hwnd)
|
|
|
|
|
{
|
|
|
|
|
if (hwnd == IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetForegroundWindowNative(hwnd);
|
|
|
|
|
|
|
|
|
|
var foreground = GetForegroundWindowNative();
|
|
|
|
|
if (foreground == hwnd)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var windowThread = GetWindowThreadProcessIdNative(hwnd, IntPtr.Zero);
|
|
|
|
|
var foregroundThread = foreground != IntPtr.Zero ? GetWindowThreadProcessIdNative(foreground, IntPtr.Zero) : 0;
|
|
|
|
|
|
|
|
|
|
if (windowThread != 0 && foregroundThread != 0 && windowThread != foregroundThread)
|
|
|
|
|
{
|
|
|
|
|
if (AttachThreadInputNative(windowThread, foregroundThread, true))
|
|
|
|
|
{
|
|
|
|
|
SetForegroundWindowNative(hwnd);
|
|
|
|
|
AttachThreadInputNative(windowThread, foregroundThread, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SetForegroundWindowNative(hwnd);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 13:33:14 +08:00
|
|
|
private void EnsureGlobalMouseHook()
|
|
|
|
|
{
|
|
|
|
|
if (_mouseHook != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_mouseHookDelegate ??= LowLevelMouseHookCallback;
|
|
|
|
|
var moduleHandle = GetModuleHandleNative(null);
|
|
|
|
|
_mouseHook = SetWindowsHookExNative(WhMouseLl, _mouseHookDelegate, moduleHandle, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RemoveGlobalMouseHook()
|
|
|
|
|
{
|
|
|
|
|
if (_mouseHook == IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UnhookWindowsHookExNative(_mouseHook);
|
|
|
|
|
_mouseHook = IntPtr.Zero;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IntPtr LowLevelMouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
|
|
|
|
|
{
|
|
|
|
|
if (nCode >= 0 && _isVisible && lParam != IntPtr.Zero && IsMouseButtonDownMessage(wParam))
|
|
|
|
|
{
|
|
|
|
|
var data = Marshal.PtrToStructure<LowLevelMouseInput>(lParam);
|
|
|
|
|
if (!IsPointInsideWindow(data.Point))
|
|
|
|
|
{
|
|
|
|
|
_dispatcherQueue.TryEnqueue(() =>
|
|
|
|
|
{
|
|
|
|
|
if (_isVisible)
|
|
|
|
|
{
|
|
|
|
|
HideWindow();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return CallNextHookExNative(_mouseHook, nCode, wParam, lParam);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool IsMouseButtonDownMessage(IntPtr wParam)
|
|
|
|
|
{
|
|
|
|
|
var message = wParam.ToInt32();
|
|
|
|
|
return message == WmLbuttondown || message == WmRbuttondown || message == WmMbuttondown || message == WmXbuttondown;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool IsPointInsideWindow(NativePoint point)
|
|
|
|
|
{
|
|
|
|
|
if (_hwnd == IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!GetWindowRectNative(_hwnd, out var rect))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return point.X >= rect.Left && point.X <= rect.Right && point.Y >= rect.Top && point.Y <= rect.Bottom;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 11:59:08 +08:00
|
|
|
private void EnsureListenerInfrastructure()
|
|
|
|
|
{
|
|
|
|
|
_listenerShutdownEvent ??= new ManualResetEventSlim(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void StartShowListenerThread()
|
|
|
|
|
{
|
|
|
|
|
if (_showEvent == null || _listenerShutdownEvent == null || _showListenerThread != null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_showListenerThread = new Thread(ListenForShowEvents)
|
|
|
|
|
{
|
|
|
|
|
IsBackground = true,
|
|
|
|
|
Name = "QuickAccess-ShowEventListener",
|
|
|
|
|
};
|
|
|
|
|
_showListenerThread.Start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void StartExitListenerThread()
|
|
|
|
|
{
|
|
|
|
|
if (_exitEvent == null || _listenerShutdownEvent == null || _exitListenerThread != null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_exitListenerThread = new Thread(ListenForExitEvents)
|
|
|
|
|
{
|
|
|
|
|
IsBackground = true,
|
|
|
|
|
Name = "QuickAccess-ExitEventListener",
|
|
|
|
|
};
|
|
|
|
|
_exitListenerThread.Start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ListenForShowEvents()
|
|
|
|
|
{
|
|
|
|
|
if (_showEvent == null || _listenerShutdownEvent == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var handles = new WaitHandle[] { _showEvent, _listenerShutdownEvent.WaitHandle };
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
var index = WaitHandle.WaitAny(handles);
|
|
|
|
|
if (index == 0)
|
|
|
|
|
{
|
|
|
|
|
_dispatcherQueue.TryEnqueue(ShowWindow);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (ObjectDisposedException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
catch (ThreadInterruptedException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ListenForExitEvents()
|
|
|
|
|
{
|
|
|
|
|
if (_exitEvent == null || _listenerShutdownEvent == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var handles = new WaitHandle[] { _exitEvent, _listenerShutdownEvent.WaitHandle };
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
var index = WaitHandle.WaitAny(handles);
|
|
|
|
|
if (index == 0)
|
|
|
|
|
{
|
|
|
|
|
_dispatcherQueue.TryEnqueue(Close);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (index == 1)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (ObjectDisposedException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
catch (ThreadInterruptedException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void StopEventListeners()
|
|
|
|
|
{
|
|
|
|
|
if (_listenerShutdownEvent == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_listenerShutdownEvent.Set();
|
|
|
|
|
|
|
|
|
|
JoinListenerThread(ref _showListenerThread);
|
|
|
|
|
JoinListenerThread(ref _exitListenerThread);
|
|
|
|
|
|
|
|
|
|
_listenerShutdownEvent.Dispose();
|
|
|
|
|
_listenerShutdownEvent = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void JoinListenerThread(ref Thread? thread)
|
|
|
|
|
{
|
|
|
|
|
if (thread == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (!thread.Join(TimeSpan.FromMilliseconds(250)))
|
|
|
|
|
{
|
|
|
|
|
thread.Interrupt();
|
|
|
|
|
thread.Join(TimeSpan.FromMilliseconds(250));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (ThreadInterruptedException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
catch (ThreadStateException)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
thread = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CustomizeWindowChrome()
|
|
|
|
|
{
|
|
|
|
|
if (_hwnd == IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 17:34:18 +08:00
|
|
|
var windowAttributesChanged = false;
|
|
|
|
|
|
2025-11-05 11:59:08 +08:00
|
|
|
var stylePtr = GetWindowLongPtrNative(_hwnd, GwlStyle);
|
2025-11-05 17:34:18 +08:00
|
|
|
var styleError = Marshal.GetLastWin32Error();
|
|
|
|
|
if (!(stylePtr == nint.Zero && styleError != 0))
|
2025-11-05 11:59:08 +08:00
|
|
|
{
|
2025-11-05 17:34:18 +08:00
|
|
|
var styleValue = (long)stylePtr;
|
|
|
|
|
var newStyleValue = styleValue & ~(WsSysmenu | WsMinimizeBox | WsMaximizeBox);
|
2025-11-05 11:59:08 +08:00
|
|
|
|
2025-11-05 17:34:18 +08:00
|
|
|
if (newStyleValue != styleValue)
|
|
|
|
|
{
|
|
|
|
|
SetWindowLongPtrNative(_hwnd, GwlStyle, (nint)newStyleValue);
|
|
|
|
|
windowAttributesChanged = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-05 11:59:08 +08:00
|
|
|
|
2025-11-05 17:34:18 +08:00
|
|
|
var exStylePtr = GetWindowLongPtrNative(_hwnd, GwlExStyle);
|
|
|
|
|
var exStyleError = Marshal.GetLastWin32Error();
|
|
|
|
|
if (!(exStylePtr == nint.Zero && exStyleError != 0))
|
2025-11-05 11:59:08 +08:00
|
|
|
{
|
2025-11-05 17:34:18 +08:00
|
|
|
var exStyleValue = (long)exStylePtr;
|
|
|
|
|
var newExStyleValue = exStyleValue | WsExToolWindow;
|
|
|
|
|
if (newExStyleValue != exStyleValue)
|
|
|
|
|
{
|
|
|
|
|
SetWindowLongPtrNative(_hwnd, GwlExStyle, (nint)newExStyleValue);
|
|
|
|
|
windowAttributesChanged = true;
|
|
|
|
|
}
|
2025-11-05 11:59:08 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-05 17:34:18 +08:00
|
|
|
if (windowAttributesChanged)
|
|
|
|
|
{
|
|
|
|
|
// Apply the new chrome immediately so caption buttons disappear right away and the tool-window flag takes effect.
|
|
|
|
|
SetWindowPosNative(_hwnd, IntPtr.Zero, 0, 0, 0, 0, SwpNoMove | SwpNoSize | SwpNoZorder | SwpNoActivate | SwpFrameChanged);
|
|
|
|
|
}
|
2025-11-05 11:59:08 +08:00
|
|
|
}
|
2025-11-19 14:40:02 +08:00
|
|
|
|
2025-12-02 13:33:14 +08:00
|
|
|
private const int WhMouseLl = 14;
|
|
|
|
|
private const int WmLbuttondown = 0x0201;
|
|
|
|
|
private const int WmRbuttondown = 0x0204;
|
|
|
|
|
private const int WmMbuttondown = 0x0207;
|
2025-12-04 16:21:55 +08:00
|
|
|
|
|
|
|
|
[DllImport("user32.dll")]
|
|
|
|
|
private static extern bool GetCursorPos(out NativePoint lpPoint);
|
|
|
|
|
|
2025-12-05 08:48:48 +08:00
|
|
|
[DllImport("kernel32.dll")]
|
|
|
|
|
private static extern bool SetProcessWorkingSetSize(IntPtr hProcess, int dwMinimumWorkingSetSize, int dwMaximumWorkingSetSize);
|
|
|
|
|
|
2025-12-02 13:33:14 +08:00
|
|
|
private const int WmXbuttondown = 0x020B;
|
|
|
|
|
|
|
|
|
|
private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
|
|
|
|
|
|
|
|
|
|
private struct Rect
|
|
|
|
|
{
|
|
|
|
|
public int Left;
|
|
|
|
|
public int Top;
|
|
|
|
|
public int Right;
|
|
|
|
|
public int Bottom;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-19 14:40:02 +08:00
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
2025-12-02 13:33:14 +08:00
|
|
|
private struct LowLevelMouseInput
|
|
|
|
|
{
|
|
|
|
|
public NativePoint Point;
|
|
|
|
|
public int MouseData;
|
|
|
|
|
public int Flags;
|
|
|
|
|
public int Time;
|
|
|
|
|
public IntPtr DwExtraInfo;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-19 14:40:02 +08:00
|
|
|
private struct NativePoint
|
|
|
|
|
{
|
|
|
|
|
public int X;
|
|
|
|
|
public int Y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
|
private struct MonitorInfo
|
|
|
|
|
{
|
|
|
|
|
public int CbSize;
|
|
|
|
|
public Rect RcMonitor;
|
|
|
|
|
public Rect RcWork;
|
|
|
|
|
public uint DwFlags;
|
|
|
|
|
}
|
2025-11-05 11:59:08 +08:00
|
|
|
}
|