diff --git a/Flow.Launcher.Infrastructure/MonitorInfo.cs b/Flow.Launcher.Infrastructure/MonitorInfo.cs new file mode 100644 index 000000000..3221708c1 --- /dev/null +++ b/Flow.Launcher.Infrastructure/MonitorInfo.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using System; +using System.Runtime.InteropServices; +using System.Windows; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Graphics.Gdi; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace Flow.Launcher.Infrastructure; + +/// +/// Contains full information about a display monitor. +/// Codes are edited from: . +/// +internal class MonitorInfo +{ + /// + /// Gets the display monitors (including invisible pseudo-monitors associated with the mirroring drivers). + /// + /// A list of display monitors + public static unsafe IList GetDisplayMonitors() + { + var monitorCount = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CMONITORS); + var list = new List(monitorCount); + var callback = new MONITORENUMPROC((HMONITOR monitor, HDC deviceContext, RECT* rect, LPARAM data) => + { + list.Add(new MonitorInfo(monitor, rect)); + return true; + }); + var dwData = new LPARAM(); + var hdc = new HDC(); + bool ok = PInvoke.EnumDisplayMonitors(hdc, (RECT?)null, callback, dwData); + if (!ok) + { + Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error()); + } + return list; + } + + /// + /// Gets the display monitor that is nearest to a given window. + /// + /// Window handle + /// The display monitor that is nearest to a given window, or null if no monitor is found. + public static unsafe MonitorInfo GetNearestDisplayMonitor(HWND hwnd) + { + var nearestMonitor = PInvoke.MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST); + MonitorInfo nearestMonitorInfo = null; + var callback = new MONITORENUMPROC((HMONITOR monitor, HDC deviceContext, RECT* rect, LPARAM data) => + { + if (monitor == nearestMonitor) + { + nearestMonitorInfo = new MonitorInfo(monitor, rect); + return false; + } + return true; + }); + var dwData = new LPARAM(); + var hdc = new HDC(); + bool ok = PInvoke.EnumDisplayMonitors(hdc, (RECT?)null, callback, dwData); + if (!ok) + { + Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error()); + } + return nearestMonitorInfo; + } + + private readonly HMONITOR _monitor; + + internal unsafe MonitorInfo(HMONITOR monitor, RECT* rect) + { + RectMonitor = + new Rect(new Point(rect->left, rect->top), + new Point(rect->right, rect->bottom)); + _monitor = monitor; + var info = new MONITORINFOEXW() { monitorInfo = new MONITORINFO() { cbSize = (uint)sizeof(MONITORINFOEXW) } }; + GetMonitorInfo(monitor, ref info); + RectWork = + new Rect(new Point(info.monitorInfo.rcWork.left, info.monitorInfo.rcWork.top), + new Point(info.monitorInfo.rcWork.right, info.monitorInfo.rcWork.bottom)); + Name = new string(info.szDevice.AsSpan()).Replace("\0", "").Trim(); + } + + /// + /// Gets the name of the display. + /// + public string Name { get; } + + /// + /// Gets the display monitor rectangle, expressed in virtual-screen coordinates. + /// + /// + /// If the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values. + /// + public Rect RectMonitor { get; } + + /// + /// Gets the work area rectangle of the display monitor, expressed in virtual-screen coordinates. + /// + /// + /// If the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values. + /// + public Rect RectWork { get; } + + /// + /// Gets if the monitor is the the primary display monitor. + /// + public bool IsPrimary => _monitor == PInvoke.MonitorFromWindow(new(IntPtr.Zero), MONITOR_FROM_FLAGS.MONITOR_DEFAULTTOPRIMARY); + + /// + public override string ToString() => $"{Name} {RectMonitor.Width}x{RectMonitor.Height}"; + + private static unsafe bool GetMonitorInfo(HMONITOR hMonitor, ref MONITORINFOEXW lpmi) + { + fixed (MONITORINFOEXW* lpmiLocal = &lpmi) + { + var lpmiBase = (MONITORINFO*)lpmiLocal; + var __result = PInvoke.GetMonitorInfo(hMonitor, lpmiBase); + return __result; + } + } +} diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index e7256e8c4..5d72239f3 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -20,4 +20,31 @@ EnumWindows DwmSetWindowAttribute DWM_SYSTEMBACKDROP_TYPE -DWM_WINDOW_CORNER_PREFERENCE \ No newline at end of file +DWM_WINDOW_CORNER_PREFERENCE + +MAX_PATH +SystemParametersInfo + +SetForegroundWindow + +GetWindowLong +SetWindowLong +GetForegroundWindow +GetDesktopWindow +GetShellWindow +GetWindowRect +GetClassName +FindWindowEx +WINDOW_STYLE + +SetLastError +WINDOW_EX_STYLE + +GetSystemMetrics +EnumDisplayMonitors +MonitorFromWindow +GetMonitorInfo +MONITORINFOEXW + +WM_ENTERSIZEMOVE +WM_EXITSIZEMOVE \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 08452b721..9cc4be0c5 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -1,9 +1,13 @@ using System; +using System.ComponentModel; using System.Runtime.InteropServices; -using System.Windows.Interop; 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 @@ -14,20 +18,17 @@ namespace Flow.Launcher.Infrastructure public static bool IsBackdropSupported() { - // Windows 11 (22000) 이상에서만 Mica 및 Acrylic 효과 지원 + // 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 windowHelper = new WindowInteropHelper(window); - windowHelper.EnsureHandle(); - var cloaked = cloak ? 1 : 0; return PInvoke.DwmSetWindowAttribute( - new(windowHelper.Handle), + GetWindowHandle(window), DWMWINDOWATTRIBUTE.DWMWA_CLOAK, &cloaked, (uint)Marshal.SizeOf()).Succeeded; @@ -35,9 +36,6 @@ namespace Flow.Launcher.Infrastructure public static unsafe bool DWMSetBackdropForWindow(Window window, BackdropTypes backdrop) { - var windowHelper = new WindowInteropHelper(window); - windowHelper.EnsureHandle(); - var backdropType = backdrop switch { BackdropTypes.Acrylic => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_TRANSIENTWINDOW, @@ -47,7 +45,7 @@ namespace Flow.Launcher.Infrastructure }; return PInvoke.DwmSetWindowAttribute( - new(windowHelper.Handle), + GetWindowHandle(window), DWMWINDOWATTRIBUTE.DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, (uint)Marshal.SizeOf()).Succeeded; @@ -55,13 +53,10 @@ namespace Flow.Launcher.Infrastructure public static unsafe bool DWMSetDarkModeForWindow(Window window, bool useDarkMode) { - var windowHelper = new WindowInteropHelper(window); - windowHelper.EnsureHandle(); - var darkMode = useDarkMode ? 1 : 0; return PInvoke.DwmSetWindowAttribute( - new(windowHelper.Handle), + GetWindowHandle(window), DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, &darkMode, (uint)Marshal.SizeOf()).Succeeded; @@ -75,9 +70,6 @@ namespace Flow.Launcher.Infrastructure /// public static unsafe bool DWMSetCornerPreferenceForWindow(Window window, string cornerType) { - var windowHelper = new WindowInteropHelper(window); - windowHelper.EnsureHandle(); - var preference = cornerType switch { "DoNotRound" => DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_DONOTROUND, @@ -88,12 +80,246 @@ namespace Flow.Launcher.Infrastructure }; return PInvoke.DwmSetWindowAttribute( - new(windowHelper.Handle), + GetWindowHandle(window), DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, &preference, (uint)Marshal.SizeOf()).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 + + /// + /// Hide windows in the Alt+Tab window list + /// + /// To hide a window + public static void HideFromAltTab(Window window) + { + var hwnd = GetWindowHandle(window); + + var exStyle = GetCurrentWindowStyle(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; + + SetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle); + } + + /// + /// Restore window display in the Alt+Tab window list. + /// + /// To restore the displayed window + public static void ShowInAltTab(Window window) + { + var hwnd = GetWindowHandle(window); + + var exStyle = GetCurrentWindowStyle(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; + + SetWindowLong(GetWindowHandle(window), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle); + } + + /// + /// Disable windows toolbar's control box + /// This will also disable system menu with Alt+Space hotkey + /// + public static void DisableControlBox(Window window) + { + var hwnd = GetWindowHandle(window); + + var style = GetCurrentWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE); + + style &= ~(int)WINDOW_STYLE.WS_SYSMENU; + + SetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, style); + } + + private static int GetCurrentWindowStyle(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 int SetWindowLong(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, int dwNewLong) + { + PInvoke.SetLastError(WIN32_ERROR.NO_ERROR); // Clear any existing error + + var result = PInvoke.SetWindowLong(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 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 + + /// + /// Transforms pixels to Device Independent Pixels used by WPF + /// + /// current window, required to get presentation source + /// horizontal position in pixels + /// vertical position in pixels + /// point containing device independent pixels + 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 static bool WM_ENTERSIZEMOVE(int msg) + { + return msg == (int)PInvoke.WM_ENTERSIZEMOVE; + } + + public static bool WM_EXITSIZEMOVE(int msg) + { + return msg == (int)PInvoke.WM_EXITSIZEMOVE; + } + + #endregion + + #region Window Handle + + internal static HWND GetWindowHandle(Window window) + { + var windowHelper = new WindowInteropHelper(window); + windowHelper.EnsureHandle(); + return new(windowHelper.Handle); + } + + #endregion } } diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 1e305d3d9..91cab9fa0 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -94,10 +94,6 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/Flow.Launcher/Helper/DWMDropShadow.cs b/Flow.Launcher/Helper/DWMDropShadow.cs deleted file mode 100644 index 58817d70e..000000000 --- a/Flow.Launcher/Helper/DWMDropShadow.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Interop; -using Windows.Win32; -using Windows.Win32.Foundation; -using Windows.Win32.Graphics.Dwm; -using Windows.Win32.UI.Controls; - -namespace Flow.Launcher.Helper; - -public class DwmDropShadow -{ - - /// - /// Drops a standard shadow to a WPF Window, even if the window isborderless. Only works with DWM (Vista and Seven). - /// This method is much more efficient than setting AllowsTransparency to true and using the DropShadow effect, - /// as AllowsTransparency involves a huge permormance issue (hardware acceleration is turned off for all the window). - /// - /// Window to which the shadow will be applied - public static void DropShadowToWindow(Window window) - { - if (!DropShadow(window)) - { - window.SourceInitialized += window_SourceInitialized; - } - } - - private static void window_SourceInitialized(object sender, EventArgs e) //fixed typo - { - Window window = (Window)sender; - - DropShadow(window); - - window.SourceInitialized -= window_SourceInitialized; - } - - /// - /// The actual method that makes API calls to drop the shadow to the window - /// - /// Window to which the shadow will be applied - /// True if the method succeeded, false if not - private static unsafe bool DropShadow(Window window) - { - try - { - WindowInteropHelper helper = new WindowInteropHelper(window); - int val = 2; - var ret1 = PInvoke.DwmSetWindowAttribute(new (helper.Handle), DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY, &val, 4); - - if (ret1 == HRESULT.S_OK) - { - var m = new MARGINS { cyBottomHeight = 0, cxLeftWidth = 0, cxRightWidth = 0, cyTopHeight = 0 }; - var ret2 = PInvoke.DwmExtendFrameIntoClientArea(new(helper.Handle), &m); - return ret2 == HRESULT.S_OK; - } - - return false; - } - catch (Exception) - { - // Probably dwmapi.dll not found (incompatible OS) - return false; - } - } - -} diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs index a3bd83a97..151ce97dd 100644 --- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs +++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs @@ -2,19 +2,16 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; +using Flow.Launcher.Infrastructure; using Microsoft.Win32; -using Windows.Win32; -using Windows.Win32.UI.WindowsAndMessaging; namespace Flow.Launcher.Helper; public static class WallpaperPathRetrieval { - private static readonly int MAX_PATH = 260; private static readonly int MAX_CACHE_SIZE = 3; private static readonly Dictionary<(string, DateTime), ImageBrush> wallpaperCache = new(); @@ -29,7 +26,7 @@ public static class WallpaperPathRetrieval try { - var wallpaperPath = GetWallpaperPath(); + var wallpaperPath = Win32Helper.GetWallpaperPath(); if (wallpaperPath is not null && File.Exists(wallpaperPath)) { // Since the wallpaper file name can be the same (TranscodedWallpaper), @@ -78,17 +75,6 @@ public static class WallpaperPathRetrieval } } - private static unsafe string GetWallpaperPath() - { - var wallpaperPtr = stackalloc char[MAX_PATH]; - PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETDESKWALLPAPER, (uint)MAX_PATH, - wallpaperPtr, - 0); - var wallpaper = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(wallpaperPtr); - - return wallpaper.ToString(); - } - private static Color GetWallpaperColor() { RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", true); diff --git a/Flow.Launcher/Helper/WindowsInteropHelper.cs b/Flow.Launcher/Helper/WindowsInteropHelper.cs deleted file mode 100644 index 3e57948a5..000000000 --- a/Flow.Launcher/Helper/WindowsInteropHelper.cs +++ /dev/null @@ -1,211 +0,0 @@ -using System; -using System.ComponentModel; -using System.Drawing; -using System.Runtime.InteropServices; -using System.Windows; -using System.Windows.Forms; -using System.Windows.Interop; -using System.Windows.Media; -using Windows.Win32; -using Windows.Win32.Foundation; -using Windows.Win32.UI.WindowsAndMessaging; -using Point = System.Windows.Point; - -namespace Flow.Launcher.Helper; - -public class WindowsInteropHelper -{ - private static HWND _hwnd_shell; - private static HWND _hwnd_desktop; - - //Accessors for shell and desktop handlers - //Will set the variables once and then will return them - private static HWND HWND_SHELL - { - get - { - return _hwnd_shell != HWND.Null ? _hwnd_shell : _hwnd_shell = PInvoke.GetShellWindow(); - } - } - - private static HWND HWND_DESKTOP - { - get - { - return _hwnd_desktop != HWND.Null ? _hwnd_desktop : _hwnd_desktop = PInvoke.GetDesktopWindow(); - } - } - - const string WINDOW_CLASS_CONSOLE = "ConsoleWindowClass"; - const string WINDOW_CLASS_WINTAB = "Flip3D"; - const string WINDOW_CLASS_PROGMAN = "Progman"; - const string WINDOW_CLASS_WORKERW = "WorkerW"; - - public unsafe static bool IsWindowFullscreen() - { - //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 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; - } - } - - Rectangle screenBounds = Screen.FromHandle(hWnd).Bounds; - return (appBounds.bottom - appBounds.top) == screenBounds.Height && - (appBounds.right - appBounds.left) == screenBounds.Width; - } - - /// - /// disable windows toolbar's control box - /// this will also disable system menu with Alt+Space hotkey - /// - public static void DisableControlBox(Window win) - { - var hwnd = new HWND(new WindowInteropHelper(win).Handle); - - var style = PInvoke.GetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE); - - if (style == 0) - { - throw new Win32Exception(Marshal.GetLastPInvokeError()); - } - - style &= ~(int)WINDOW_STYLE.WS_SYSMENU; - - var previousStyle = PInvoke.SetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, - style); - - if (previousStyle == 0) - { - throw new Win32Exception(Marshal.GetLastPInvokeError()); - } - } - - /// - /// Transforms pixels to Device Independent Pixels used by WPF - /// - /// current window, required to get presentation source - /// horizontal position in pixels - /// vertical position in pixels - /// point containing device independent pixels - 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)); - } - - #region Alt Tab - - private static int SetWindowLong(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, int dwNewLong) - { - PInvoke.SetLastError(WIN32_ERROR.NO_ERROR); // Clear any existing error - - var result = PInvoke.SetWindowLong(hWnd, nIndex, dwNewLong); - if (result == 0 && Marshal.GetLastPInvokeError() != 0) - { - throw new Win32Exception(Marshal.GetLastPInvokeError()); - } - - return result; - } - - /// - /// Hide windows in the Alt+Tab window list - /// - /// To hide a window - public static void HideFromAltTab(Window window) - { - var exStyle = GetCurrentWindowStyle(window); - - // Add TOOLWINDOW style, remove APPWINDOW style - var newExStyle = ((uint)exStyle | (uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) & ~(uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW; - - SetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle); - } - - /// - /// Restore window display in the Alt+Tab window list. - /// - /// To restore the displayed window - public static void ShowInAltTab(Window window) - { - var exStyle = GetCurrentWindowStyle(window); - - // 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; - - SetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle); - } - - /// - /// To obtain the current overridden style of a window. - /// - /// To obtain the style dialog window - /// current extension style value - private static int GetCurrentWindowStyle(Window window) - { - var style = PInvoke.GetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); - if (style == 0 && Marshal.GetLastPInvokeError() != 0) - { - throw new Win32Exception(Marshal.GetLastPInvokeError()); - } - return style; - } - - #endregion -} diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 5aba73aa3..1e8ba1369 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -8,7 +8,6 @@ using System.Windows.Controls; using System.Windows.Forms; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Resource; -using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.ViewModel; using Screen = System.Windows.Forms.Screen; @@ -26,10 +25,10 @@ using System.Media; using DataObject = System.Windows.DataObject; using System.Windows.Media; using System.Windows.Interop; -using Windows.Win32; using Window = System.Windows.Window; using System.Linq; using System.Windows.Shapes; +using CommunityToolkit.Mvvm.DependencyInjection; namespace Flow.Launcher { @@ -78,14 +77,13 @@ namespace Flow.Launcher private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { - if (msg == PInvoke.WM_ENTERSIZEMOVE) + if (Win32Helper.WM_ENTERSIZEMOVE(msg)) { _initialWidth = (int)Width; _initialHeight = (int)Height; handled = true; } - - if (msg == PInvoke.WM_EXITSIZEMOVE) + else if (Win32Helper.WM_EXITSIZEMOVE(msg)) { if (_initialHeight != (int)Height) { @@ -168,7 +166,8 @@ namespace Flow.Launcher private void OnSourceInitialized(object sender, EventArgs e) { - WindowsInteropHelper.HideFromAltTab(this); + Win32Helper.HideFromAltTab(this); + Win32Helper.DisableControlBox(this); } private void OnInitialized(object sender, EventArgs e) @@ -184,13 +183,12 @@ namespace Flow.Launcher // Show notify icon when flowlauncher is hidden InitializeNotifyIcon(); InitializeColorScheme(); - WindowsInteropHelper.DisableControlBox(this); InitProgressbarAnimation(); // Initialize call twice to work around multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910 InitializePosition(); InitializePosition(); // Refresh frame - await ThemeManager.Instance.RefreshFrameAsync(); + await Ioc.Default.GetRequiredService().RefreshFrameAsync(); PreviewReset(); // Since the default main window visibility is visible, so we need set focus during startup QueryTextBox.Focus(); @@ -314,9 +312,9 @@ namespace Flow.Launcher Top = 10; break; case SearchWindowAligns.Custom: - Left = WindowsInteropHelper.TransformPixelsToDIP(this, + Left = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.X + _settings.CustomWindowLeft, 0).X; - Top = WindowsInteropHelper.TransformPixelsToDIP(this, 0, + Top = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y + _settings.CustomWindowTop).Y; break; } @@ -377,7 +375,7 @@ namespace Flow.Launcher open.Click += (o, e) => _viewModel.ToggleFlowLauncher(); gamemode.Click += (o, e) => _viewModel.ToggleGameMode(); - positionreset.Click += (o, e) => PositionReset(); + positionreset.Click += (o, e) => _ = PositionResetAsync(); settings.Click += (o, e) => App.API.OpenSettingDialog(); exit.Click += (o, e) => Close(); @@ -403,7 +401,7 @@ namespace Flow.Launcher // Get context menu handle and bring it to the foreground if (PresentationSource.FromVisual(contextMenu) is HwndSource hwndSource) { - PInvoke.SetForegroundWindow(new(hwndSource.Handle)); + Win32Helper.SetForegroundWindow(hwndSource.Handle); } contextMenu.Focus(); @@ -422,13 +420,13 @@ namespace Flow.Launcher } } - private void OpenWelcomeWindow() + private static void OpenWelcomeWindow() { var WelcomeWindow = new WelcomeWindow(); WelcomeWindow.Show(); } - private async void PositionReset() + private async Task PositionResetAsync() { _viewModel.Show(); await Task.Delay(300); // If don't give a time, Positioning will be weird. @@ -846,7 +844,7 @@ namespace Flow.Launcher screen = Screen.PrimaryScreen; break; case SearchWindowScreens.Focus: - var foregroundWindowHandle = PInvoke.GetForegroundWindow().Value; + var foregroundWindowHandle = Win32Helper.GetForegroundWindow(); screen = Screen.FromHandle(foregroundWindowHandle); break; case SearchWindowScreens.Custom: @@ -865,38 +863,38 @@ namespace Flow.Launcher public double HorizonCenter(Screen screen) { - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); + var dip1 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); + var dip2 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); var left = (dip2.X - ActualWidth) / 2 + dip1.X; return left; } public double VerticalCenter(Screen screen) { - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); + var dip1 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); + var dip2 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); var top = (dip2.Y - QueryTextBox.ActualHeight) / 4 + dip1.Y; return top; } public double HorizonRight(Screen screen) { - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); + var dip1 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); + var dip2 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); var left = (dip1.X + dip2.X - ActualWidth) - 10; return left; } public double HorizonLeft(Screen screen) { - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); + var dip1 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); var left = dip1.X + 10; return left; } /// /// Register up and down key - /// todo: any way to put this in xaml ? + /// todo: Put this in xaml? /// private void OnKeyDown(object sender, KeyEventArgs e) { diff --git a/Flow.Launcher/Msg.xaml.cs b/Flow.Launcher/Msg.xaml.cs index 0bb02bbc5..94184ff63 100644 --- a/Flow.Launcher/Msg.xaml.cs +++ b/Flow.Launcher/Msg.xaml.cs @@ -1,10 +1,9 @@ -using System; +using System; using System.IO; using System.Windows; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Media.Animation; -using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Image; @@ -12,14 +11,14 @@ namespace Flow.Launcher { public partial class Msg : Window { - Storyboard fadeOutStoryboard = new Storyboard(); + private readonly Storyboard fadeOutStoryboard = new(); private bool closing; public Msg() { InitializeComponent(); var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); - var dipWorkingArea = WindowsInteropHelper.TransformPixelsToDIP(this, + var dipWorkingArea = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.Width, screen.WorkingArea.Height); Left = dipWorkingArea.X - Width; @@ -82,13 +81,13 @@ namespace Flow.Launcher Show(); await Dispatcher.InvokeAsync(async () => - { - if (!closing) - { - closing = true; - await Dispatcher.InvokeAsync(fadeOutStoryboard.Begin); - } - }); + { + if (!closing) + { + closing = true; + await Dispatcher.InvokeAsync(fadeOutStoryboard.Begin); + } + }); } } } diff --git a/Flow.Launcher/NativeMethods.txt b/Flow.Launcher/NativeMethods.txt deleted file mode 100644 index 88eeeca6e..000000000 --- a/Flow.Launcher/NativeMethods.txt +++ /dev/null @@ -1,20 +0,0 @@ -DwmSetWindowAttribute -DwmExtendFrameIntoClientArea -SystemParametersInfo -SetForegroundWindow - -GetWindowLong -SetWindowLong -GetForegroundWindow -GetDesktopWindow -GetShellWindow -GetWindowRect -GetClassName -FindWindowEx -WINDOW_STYLE - -WM_ENTERSIZEMOVE -WM_EXITSIZEMOVE - -SetLastError -WINDOW_EX_STYLE \ No newline at end of file diff --git a/Flow.Launcher/SettingWindow.xaml.cs b/Flow.Launcher/SettingWindow.xaml.cs index 30b51f992..d7f8489af 100644 --- a/Flow.Launcher/SettingWindow.xaml.cs +++ b/Flow.Launcher/SettingWindow.xaml.cs @@ -4,7 +4,7 @@ using System.Windows.Forms; using System.Windows.Input; using System.Windows.Interop; using CommunityToolkit.Mvvm.DependencyInjection; -using Flow.Launcher.Helper; +using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.SettingPages.Views; @@ -143,8 +143,8 @@ public partial class SettingWindow private double WindowLeft() { var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); + var dip1 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); + var dip2 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); var left = (dip2.X - ActualWidth) / 2 + dip1.X; return left; } @@ -152,8 +152,8 @@ public partial class SettingWindow private double WindowTop() { var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); + var dip1 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); + var dip2 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); var top = (dip2.Y - ActualHeight) / 2 + dip1.Y - 20; return top; } diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 476479405..7d719a442 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using System.Windows; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Resource; -using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Storage; @@ -27,7 +26,6 @@ using Flow.Launcher.Infrastructure.Image; using System.Windows.Media; using CommunityToolkit.Mvvm.DependencyInjection; using System.Windows.Threading; -using System.Windows.Interop; namespace Flow.Launcher.ViewModel { @@ -1484,7 +1482,7 @@ namespace Flow.Launcher.ViewModel /// public bool ShouldIgnoreHotkeys() { - return Settings.IgnoreHotkeysOnFullscreen && WindowsInteropHelper.IsWindowFullscreen() || GameModeStatus; + return Settings.IgnoreHotkeysOnFullscreen && Win32Helper.IsForegroundWindowFullscreen() || GameModeStatus; } #endregion