mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
321 lines
11 KiB
C#
321 lines
11 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.Runtime.InteropServices;
|
|
using System.Windows;
|
|
using System.Windows.Interop;
|
|
using System.Windows.Media;
|
|
using Windows.Win32;
|
|
using Windows.Win32.Foundation;
|
|
using Windows.Win32.Graphics.Dwm;
|
|
using Windows.Win32.UI.WindowsAndMessaging;
|
|
using Flow.Launcher.Infrastructure.UserSettings;
|
|
|
|
namespace Flow.Launcher.Infrastructure
|
|
{
|
|
public static class Win32Helper
|
|
{
|
|
#region Blur Handling
|
|
|
|
public static bool IsBackdropSupported()
|
|
{
|
|
// Mica and Acrylic only supported Windows 11 22000+
|
|
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
|
|
Environment.OSVersion.Version.Build >= 22000;
|
|
}
|
|
|
|
public static unsafe bool DWMSetCloakForWindow(Window window, bool cloak)
|
|
{
|
|
var cloaked = cloak ? 1 : 0;
|
|
|
|
return PInvoke.DwmSetWindowAttribute(
|
|
GetWindowHandle(window),
|
|
DWMWINDOWATTRIBUTE.DWMWA_CLOAK,
|
|
&cloaked,
|
|
(uint)Marshal.SizeOf<int>()).Succeeded;
|
|
}
|
|
|
|
public static unsafe bool DWMSetBackdropForWindow(Window window, BackdropTypes backdrop)
|
|
{
|
|
var backdropType = backdrop switch
|
|
{
|
|
BackdropTypes.Acrylic => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_TRANSIENTWINDOW,
|
|
BackdropTypes.Mica => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_MAINWINDOW,
|
|
BackdropTypes.MicaAlt => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_TABBEDWINDOW,
|
|
_ => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_AUTO
|
|
};
|
|
|
|
return PInvoke.DwmSetWindowAttribute(
|
|
GetWindowHandle(window),
|
|
DWMWINDOWATTRIBUTE.DWMWA_SYSTEMBACKDROP_TYPE,
|
|
&backdropType,
|
|
(uint)Marshal.SizeOf<int>()).Succeeded;
|
|
}
|
|
|
|
public static unsafe bool DWMSetDarkModeForWindow(Window window, bool useDarkMode)
|
|
{
|
|
var darkMode = useDarkMode ? 1 : 0;
|
|
|
|
return PInvoke.DwmSetWindowAttribute(
|
|
GetWindowHandle(window),
|
|
DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE,
|
|
&darkMode,
|
|
(uint)Marshal.SizeOf<int>()).Succeeded;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="window"></param>
|
|
/// <param name="cornerType">DoNotRound, Round, RoundSmall, Default</param>
|
|
/// <returns></returns>
|
|
public static unsafe bool DWMSetCornerPreferenceForWindow(Window window, string cornerType)
|
|
{
|
|
var preference = cornerType switch
|
|
{
|
|
"DoNotRound" => DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_DONOTROUND,
|
|
"Round" => DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND,
|
|
"RoundSmall" => DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUNDSMALL,
|
|
"Default" => DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_DEFAULT,
|
|
_ => throw new InvalidOperationException("Invalid corner type")
|
|
};
|
|
|
|
return PInvoke.DwmSetWindowAttribute(
|
|
GetWindowHandle(window),
|
|
DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
|
|
&preference,
|
|
(uint)Marshal.SizeOf<int>()).Succeeded;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Wallpaper
|
|
|
|
public static unsafe string GetWallpaperPath()
|
|
{
|
|
var wallpaperPtr = stackalloc char[(int)PInvoke.MAX_PATH];
|
|
PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETDESKWALLPAPER, PInvoke.MAX_PATH,
|
|
wallpaperPtr,
|
|
0);
|
|
var wallpaper = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(wallpaperPtr);
|
|
|
|
return wallpaper.ToString();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Window Foreground
|
|
|
|
public static nint GetForegroundWindow()
|
|
{
|
|
return PInvoke.GetForegroundWindow().Value;
|
|
}
|
|
|
|
public static bool SetForegroundWindow(Window window)
|
|
{
|
|
return PInvoke.SetForegroundWindow(GetWindowHandle(window));
|
|
}
|
|
|
|
public static bool SetForegroundWindow(nint handle)
|
|
{
|
|
return PInvoke.SetForegroundWindow(new(handle));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Task Switching
|
|
|
|
/// <summary>
|
|
/// Hide windows in the Alt+Tab window list
|
|
/// </summary>
|
|
/// <param name="window">To hide a window</param>
|
|
public static void HideFromAltTab(Window window)
|
|
{
|
|
var hwnd = GetWindowHandle(window);
|
|
|
|
var exStyle = GetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
|
|
|
|
// Add TOOLWINDOW style, remove APPWINDOW style
|
|
var newExStyle = ((uint)exStyle | (uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) & ~(uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW;
|
|
|
|
SetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restore window display in the Alt+Tab window list.
|
|
/// </summary>
|
|
/// <param name="window">To restore the displayed window</param>
|
|
public static void ShowInAltTab(Window window)
|
|
{
|
|
var hwnd = GetWindowHandle(window);
|
|
|
|
var exStyle = GetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
|
|
|
|
// Remove the TOOLWINDOW style and add the APPWINDOW style.
|
|
var newExStyle = ((uint)exStyle & ~(uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) | (uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW;
|
|
|
|
SetWindowStyle(GetWindowHandle(window), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disable windows toolbar's control box
|
|
/// This will also disable system menu with Alt+Space hotkey
|
|
/// </summary>
|
|
public static void DisableControlBox(Window window)
|
|
{
|
|
var hwnd = GetWindowHandle(window);
|
|
|
|
var style = GetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE);
|
|
|
|
style &= ~(int)WINDOW_STYLE.WS_SYSMENU;
|
|
|
|
SetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, style);
|
|
}
|
|
|
|
private static int GetWindowStyle(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex)
|
|
{
|
|
var style = PInvoke.GetWindowLong(hWnd, nIndex);
|
|
if (style == 0 && Marshal.GetLastPInvokeError() != 0)
|
|
{
|
|
throw new Win32Exception(Marshal.GetLastPInvokeError());
|
|
}
|
|
return style;
|
|
}
|
|
|
|
private static nint SetWindowStyle(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, int dwNewLong)
|
|
{
|
|
PInvoke.SetLastError(WIN32_ERROR.NO_ERROR); // Clear any existing error
|
|
|
|
var result = PInvoke.SetWindowLongPtr(hWnd, nIndex, dwNewLong);
|
|
if (result == 0 && Marshal.GetLastPInvokeError() != 0)
|
|
{
|
|
throw new Win32Exception(Marshal.GetLastPInvokeError());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Window Fullscreen
|
|
|
|
private const string WINDOW_CLASS_CONSOLE = "ConsoleWindowClass";
|
|
private const string WINDOW_CLASS_WINTAB = "Flip3D";
|
|
private const string WINDOW_CLASS_PROGMAN = "Progman";
|
|
private const string WINDOW_CLASS_WORKERW = "WorkerW";
|
|
|
|
private static HWND _hwnd_shell;
|
|
private static HWND HWND_SHELL =>
|
|
_hwnd_shell != HWND.Null ? _hwnd_shell : _hwnd_shell = PInvoke.GetShellWindow();
|
|
|
|
private static HWND _hwnd_desktop;
|
|
private static HWND HWND_DESKTOP =>
|
|
_hwnd_desktop != HWND.Null ? _hwnd_desktop : _hwnd_desktop = PInvoke.GetDesktopWindow();
|
|
|
|
public static unsafe bool IsForegroundWindowFullscreen()
|
|
{
|
|
// Get current active window
|
|
var hWnd = PInvoke.GetForegroundWindow();
|
|
if (hWnd.Equals(HWND.Null))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If current active window is desktop or shell, exit early
|
|
if (hWnd.Equals(HWND_DESKTOP) || hWnd.Equals(HWND_SHELL))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
string windowClass;
|
|
const int capacity = 256;
|
|
Span<char> buffer = stackalloc char[capacity];
|
|
int validLength;
|
|
fixed (char* pBuffer = buffer)
|
|
{
|
|
validLength = PInvoke.GetClassName(hWnd, pBuffer, capacity);
|
|
}
|
|
|
|
windowClass = buffer[..validLength].ToString();
|
|
|
|
// For Win+Tab (Flip3D)
|
|
if (windowClass == WINDOW_CLASS_WINTAB)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
PInvoke.GetWindowRect(hWnd, out var appBounds);
|
|
|
|
// For console (ConsoleWindowClass), we have to check for negative dimensions
|
|
if (windowClass == WINDOW_CLASS_CONSOLE)
|
|
{
|
|
return appBounds.top < 0 && appBounds.bottom < 0;
|
|
}
|
|
|
|
// For desktop (Progman or WorkerW, depends on the system), we have to check
|
|
if (windowClass is WINDOW_CLASS_PROGMAN or WINDOW_CLASS_WORKERW)
|
|
{
|
|
var hWndDesktop = PInvoke.FindWindowEx(hWnd, HWND.Null, "SHELLDLL_DefView", null);
|
|
hWndDesktop = PInvoke.FindWindowEx(hWndDesktop, HWND.Null, "SysListView32", "FolderView");
|
|
if (hWndDesktop.Value != IntPtr.Zero)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
var monitorInfo = MonitorInfo.GetNearestDisplayMonitor(hWnd);
|
|
return (appBounds.bottom - appBounds.top) == monitorInfo.RectMonitor.Height &&
|
|
(appBounds.right - appBounds.left) == monitorInfo.RectMonitor.Width;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Pixel to DIP
|
|
|
|
/// <summary>
|
|
/// Transforms pixels to Device Independent Pixels used by WPF
|
|
/// </summary>
|
|
/// <param name="visual">current window, required to get presentation source</param>
|
|
/// <param name="unitX">horizontal position in pixels</param>
|
|
/// <param name="unitY">vertical position in pixels</param>
|
|
/// <returns>point containing device independent pixels</returns>
|
|
public static Point TransformPixelsToDIP(Visual visual, double unitX, double unitY)
|
|
{
|
|
Matrix matrix;
|
|
var source = PresentationSource.FromVisual(visual);
|
|
if (source is not null)
|
|
{
|
|
matrix = source.CompositionTarget.TransformFromDevice;
|
|
}
|
|
else
|
|
{
|
|
using var src = new HwndSource(new HwndSourceParameters());
|
|
matrix = src.CompositionTarget.TransformFromDevice;
|
|
}
|
|
|
|
return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region WndProc
|
|
|
|
public const int WM_ENTERSIZEMOVE = (int)PInvoke.WM_ENTERSIZEMOVE;
|
|
public const int WM_EXITSIZEMOVE = (int)PInvoke.WM_EXITSIZEMOVE;
|
|
|
|
#endregion
|
|
|
|
#region Window Handle
|
|
|
|
internal static HWND GetWindowHandle(Window window, bool ensure = false)
|
|
{
|
|
var windowHelper = new WindowInteropHelper(window);
|
|
if (ensure)
|
|
{
|
|
windowHelper.EnsureHandle();
|
|
}
|
|
return new(windowHelper.Handle);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|