From 2cd16837419d8f82612881c7c718658a0005e003 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 16 Mar 2025 22:24:06 +0800 Subject: [PATCH 01/12] Improve code quality --- Flow.Launcher.Infrastructure/MonitorInfo.cs | 123 ++++++++ .../NativeMethods.txt | 29 +- Flow.Launcher.Infrastructure/Win32Helper.cs | 262 ++++++++++++++++-- Flow.Launcher/Flow.Launcher.csproj | 4 - Flow.Launcher/Helper/DWMDropShadow.cs | 66 ----- .../Helper/WallpaperPathRetrieval.cs | 18 +- Flow.Launcher/Helper/WindowsInteropHelper.cs | 211 -------------- Flow.Launcher/MainWindow.xaml.cs | 44 ++- Flow.Launcher/Msg.xaml.cs | 21 +- Flow.Launcher/NativeMethods.txt | 20 -- Flow.Launcher/SettingWindow.xaml.cs | 10 +- Flow.Launcher/ViewModel/MainViewModel.cs | 4 +- 12 files changed, 434 insertions(+), 378 deletions(-) create mode 100644 Flow.Launcher.Infrastructure/MonitorInfo.cs delete mode 100644 Flow.Launcher/Helper/DWMDropShadow.cs delete mode 100644 Flow.Launcher/Helper/WindowsInteropHelper.cs delete mode 100644 Flow.Launcher/NativeMethods.txt 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 From a98a0335b3c49f0875f7e68972329418afb26edb Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 16 Mar 2025 22:37:22 +0800 Subject: [PATCH 02/12] Improve code quality --- Flow.Launcher.Infrastructure/Win32Helper.cs | 7 ++++-- Flow.Launcher/MainWindow.xaml.cs | 27 +++++++++------------ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 9cc4be0c5..6e429cddd 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -313,10 +313,13 @@ namespace Flow.Launcher.Infrastructure #region Window Handle - internal static HWND GetWindowHandle(Window window) + internal static HWND GetWindowHandle(Window window, bool ensure = false) { var windowHelper = new WindowInteropHelper(window); - windowHelper.EnsureHandle(); + if (ensure) + { + windowHelper.EnsureHandle(); + } return new(windowHelper.Handle); } diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 1e8ba1369..f81f03f42 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -5,7 +5,6 @@ using System.Windows; using System.Windows.Input; using System.Windows.Media.Animation; using System.Windows.Controls; -using System.Windows.Forms; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure.UserSettings; @@ -63,13 +62,6 @@ namespace Flow.Launcher InitSoundEffects(); DataObject.AddPastingHandler(QueryTextBox, OnPaste); - - Loaded += (_, _) => - { - var handle = new WindowInteropHelper(this).Handle; - var win = HwndSource.FromHwnd(handle); - win.AddHook(WndProc); - }; } private int _initialWidth; @@ -143,13 +135,13 @@ namespace Flow.Launcher private void OnPaste(object sender, DataObjectPastingEventArgs e) { - var isText = e.SourceDataObject.GetDataPresent(System.Windows.DataFormats.UnicodeText, true); + var isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true); if (isText) { - var text = e.SourceDataObject.GetData(System.Windows.DataFormats.UnicodeText) as string; + var text = e.SourceDataObject.GetData(DataFormats.UnicodeText) as string; text = text.Replace(Environment.NewLine, " "); DataObject data = new DataObject(); - data.SetData(System.Windows.DataFormats.UnicodeText, text); + data.SetData(DataFormats.UnicodeText, text); e.DataObject = data; } } @@ -166,6 +158,9 @@ namespace Flow.Launcher private void OnSourceInitialized(object sender, EventArgs e) { + var handle = Win32Helper.GetWindowHandle(this, true); + var win = HwndSource.FromHwnd(handle); + win.AddHook(WndProc); Win32Helper.HideFromAltTab(this); Win32Helper.DisableControlBox(this); } @@ -392,10 +387,10 @@ namespace Flow.Launcher { switch (e.Button) { - case MouseButtons.Left: + case System.Windows.Forms.MouseButtons.Left: _viewModel.ToggleFlowLauncher(); break; - case MouseButtons.Right: + case System.Windows.Forms.MouseButtons.Right: contextMenu.IsOpen = true; // Get context menu handle and bring it to the foreground @@ -713,7 +708,7 @@ namespace Flow.Launcher { _isClockPanelAnimating = true; - System.Windows.Application.Current.Dispatcher.Invoke(() => + Application.Current.Dispatcher.Invoke(() => { ClockPanel.Visibility = Visibility.Visible; // ✅ Visibility를 먼저 Visible로 설정 @@ -969,7 +964,7 @@ namespace Flow.Launcher } } - private void MainPreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e) + private void MainPreviewMouseMove(object sender, MouseEventArgs e) { if (isArrowKeyPressed) { @@ -1005,7 +1000,7 @@ namespace Flow.Launcher { if (_viewModel.QueryText != QueryTextBox.Text) { - BindingExpression be = QueryTextBox.GetBindingExpression(System.Windows.Controls.TextBox.TextProperty); + BindingExpression be = QueryTextBox.GetBindingExpression(TextBox.TextProperty); be.UpdateSource(); } } From 7d62dedece537152dbb76035aeba99f42001ef2d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 16 Mar 2025 23:10:12 +0800 Subject: [PATCH 03/12] Improve code quality --- Flow.Launcher/MainWindow.xaml | 6 +- Flow.Launcher/MainWindow.xaml.cs | 1062 +++++++++++++++--------------- 2 files changed, 535 insertions(+), 533 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 93d79d767..5b63303ac 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -20,13 +20,13 @@ Closing="OnClosing" Deactivated="OnDeactivated" Icon="Images/app.png" - Initialized="OnInitialized" Left="{Binding Settings.WindowLeft, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Loaded="OnLoaded" LocationChanged="OnLocationChanged" Opacity="{Binding MainWindowOpacity, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" PreviewKeyDown="OnKeyDown" PreviewKeyUp="OnKeyUp" + PreviewMouseMove="OnPreviewMouseMove" ResizeMode="CanResize" ShowInTaskbar="False" SizeToContent="Height" @@ -240,14 +240,14 @@ FontSize="{Binding QueryBoxFontSize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" InputMethod.PreferredImeConversionMode="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEConversionModeConverter}}" InputMethod.PreferredImeState="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEStateConverter}}" - PreviewDragOver="OnPreviewDragOver" + PreviewDragOver="QueryTextBox_OnPreviewDragOver" PreviewKeyUp="QueryTextBox_KeyUp" Style="{DynamicResource QueryBoxStyle}" Text="{Binding QueryText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Visibility="Visible" WindowChrome.IsHitTestVisibleInChrome="True"> - + diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index f81f03f42..d0375e8f6 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -45,13 +45,19 @@ namespace Flow.Launcher private MediaPlayer animationSoundWMP; private SoundPlayer animationSoundWPF; + private int _initialWidth; + private int _initialHeight; + // Window Animations private Storyboard clocksb; private Storyboard iconsb; private Storyboard windowsb; + private bool _isClockPanelAnimating = false; // 애니메이션 실행 중인지 여부 #endregion + #region Constructor + public MainWindow(Settings settings, MainViewModel mainVM) { DataContext = mainVM; @@ -61,100 +67,14 @@ namespace Flow.Launcher InitializeComponent(); InitSoundEffects(); - DataObject.AddPastingHandler(QueryTextBox, OnPaste); + DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste); } - private int _initialWidth; - private int _initialHeight; + #endregion - private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) - { - if (Win32Helper.WM_ENTERSIZEMOVE(msg)) - { - _initialWidth = (int)Width; - _initialHeight = (int)Height; - handled = true; - } - else if (Win32Helper.WM_EXITSIZEMOVE(msg)) - { - if (_initialHeight != (int)Height) - { - OnResizeEnd(); - } + #region Window Event - if (_initialWidth != (int)Width) - { - FlowMainWindow.SizeToContent = SizeToContent.Height; - } - - handled = true; - } - - return IntPtr.Zero; - } - - private void OnResizeEnd() - { - int shadowMargin = 0; - if (_settings.UseDropShadowEffect) - { - shadowMargin = 32; - } - - if (!_settings.KeepMaxResults) - { - var itemCount = (Height - (_settings.WindowHeightSize + 14) - shadowMargin) / _settings.ItemHeightSize; - - if (itemCount < 2) - { - _settings.MaxResultsToShow = 2; - } - else - { - _settings.MaxResultsToShow = Convert.ToInt32(Math.Truncate(itemCount)); - } - } - - FlowMainWindow.SizeToContent = SizeToContent.Height; - _viewModel.MainWindowWidth = Width; - } - - private void OnCopy(object sender, ExecutedRoutedEventArgs e) - { - var result = _viewModel.Results.SelectedItem?.Result; - if (QueryTextBox.SelectionLength == 0 && result != null) - { - string copyText = result.CopyText; - App.API.CopyToClipboard(copyText, directCopy: true); - } - else if (!string.IsNullOrEmpty(QueryTextBox.Text)) - { - App.API.CopyToClipboard(QueryTextBox.SelectedText, showDefaultNotification: false); - } - } - - private void OnPaste(object sender, DataObjectPastingEventArgs e) - { - var isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true); - if (isText) - { - var text = e.SourceDataObject.GetData(DataFormats.UnicodeText) as string; - text = text.Replace(Environment.NewLine, " "); - DataObject data = new DataObject(); - data.SetData(DataFormats.UnicodeText, text); - e.DataObject = data; - } - } - - private async void OnClosing(object sender, CancelEventArgs e) - { - _notifyIcon.Visible = false; - App.API.SaveAppAllSettings(); - e.Cancel = true; - await PluginManager.DisposePluginsAsync(); - Notification.Uninstall(); - Environment.Exit(0); - } +#pragma warning disable VSTHRD100 private void OnSourceInitialized(object sender, EventArgs e) { @@ -165,26 +85,52 @@ namespace Flow.Launcher Win32Helper.DisableControlBox(this); } - private void OnInitialized(object sender, EventArgs e) - { - } - private async void OnLoaded(object sender, RoutedEventArgs _) { - // MouseEventHandler - PreviewMouseMove += MainPreviewMouseMove; - CheckFirstLaunch(); - HideStartup(); + // Check first launch + if (_settings.FirstLaunch) + { + _settings.FirstLaunch = false; + App.API.SaveAppAllSettings(); + var WelcomeWindow = new WelcomeWindow(); + WelcomeWindow.Show(); + } + + // Hide window if need + if (_settings.HideOnStartup) + { + _viewModel.Hide(); + } + else + { + _viewModel.Show(); + } + // Show notify icon when flowlauncher is hidden InitializeNotifyIcon(); - InitializeColorScheme(); + + // Initialize color scheme + if (_settings.ColorScheme == Constant.Light) + { + ModernWpf.ThemeManager.Current.ApplicationTheme = ModernWpf.ApplicationTheme.Light; + } + else if (_settings.ColorScheme == Constant.Dark) + { + ModernWpf.ThemeManager.Current.ApplicationTheme = ModernWpf.ApplicationTheme.Dark; + } + + // Initialize position InitProgressbarAnimation(); - // Initialize call twice to work around multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910 - InitializePosition(); - InitializePosition(); + + // Force update position + UpdatePosition(true); + // Refresh frame await Ioc.Default.GetRequiredService().RefreshFrameAsync(); - PreviewReset(); + + // Reset preview + _viewModel.ResetPreview(); + // Since the default main window visibility is visible, so we need set focus during startup QueryTextBox.Focus(); @@ -193,37 +139,39 @@ namespace Flow.Launcher switch (e.PropertyName) { case nameof(MainViewModel.MainWindowVisibilityStatus): - { - Dispatcher.Invoke(() => { - if (_viewModel.MainWindowVisibilityStatus) + Dispatcher.Invoke(() => { - if (_settings.UseSound) + if (_viewModel.MainWindowVisibilityStatus) { - SoundPlay(); - } + if (_settings.UseSound) + { + SoundPlay(); + } - UpdatePosition(); - PreviewReset(); - Activate(); - QueryTextBox.Focus(); - _settings.ActivateTimes++; - if (!_viewModel.LastQuerySelected) - { - QueryTextBox.SelectAll(); - _viewModel.LastQuerySelected = true; - } + UpdatePosition(false); + _viewModel.ResetPreview(); + Activate(); + QueryTextBox.Focus(); + _settings.ActivateTimes++; + if (!_viewModel.LastQuerySelected) + { + QueryTextBox.SelectAll(); + _viewModel.LastQuerySelected = true; + } - if (_settings.UseAnimation) - WindowAnimator(); - } - }); - break; - } + if (_settings.UseAnimation) + WindowAnimation(); + } + }); + break; + } case nameof(MainViewModel.QueryTextCursorMovedToEnd): if (_viewModel.QueryTextCursorMovedToEnd) { - MoveQueryTextToEnd(); + // QueryTextBox seems to be update with a DispatcherPriority as low as ContextIdle. + // To ensure QueryTextBox is up to date with QueryText from the View, we need to Dispatch with such a priority + Dispatcher.Invoke(() => QueryTextBox.CaretIndex = QueryTextBox.Text.Length); _viewModel.QueryTextCursorMovedToEnd = false; } @@ -271,6 +219,356 @@ namespace Flow.Launcher .AddValueChanged(History, (s, e) => UpdateClockPanelVisibility()); } + private async void OnClosing(object sender, CancelEventArgs e) + { + _notifyIcon.Visible = false; + App.API.SaveAppAllSettings(); + e.Cancel = true; + await PluginManager.DisposePluginsAsync(); + Notification.Uninstall(); + Environment.Exit(0); + } + + private void OnLocationChanged(object sender, EventArgs e) + { + if (_animating) + return; + if (_settings.SearchWindowScreen == SearchWindowScreens.RememberLastLaunchLocation) + { + _settings.WindowLeft = Left; + _settings.WindowTop = Top; + } + } + + private async void OnDeactivated(object sender, EventArgs e) + { + _settings.WindowLeft = Left; + _settings.WindowTop = Top; + //This condition stops extra hide call when animator is on, + // which causes the toggling to occasional hide instead of show. + if (_viewModel.MainWindowVisibilityStatus) + { + // Need time to initialize the main query window animation. + // This also stops the mainwindow from flickering occasionally after Settings window is opened + // and always after Settings window is closed. + if (_settings.UseAnimation) + await Task.Delay(100); + + if (_settings.HideWhenDeactivated && !_viewModel.ExternalPreviewVisible) + { + _viewModel.Hide(); + } + } + } + + private void OnKeyDown(object sender, KeyEventArgs e) + { + var specialKeyState = GlobalHotkey.CheckModifiers(); + switch (e.Key) + { + case Key.Down: + isArrowKeyPressed = true; + _viewModel.SelectNextItemCommand.Execute(null); + e.Handled = true; + break; + case Key.Up: + isArrowKeyPressed = true; + _viewModel.SelectPrevItemCommand.Execute(null); + e.Handled = true; + break; + case Key.PageDown: + _viewModel.SelectNextPageCommand.Execute(null); + e.Handled = true; + break; + case Key.PageUp: + _viewModel.SelectPrevPageCommand.Execute(null); + e.Handled = true; + break; + case Key.Right: + if (_viewModel.SelectedIsFromQueryResults() + && QueryTextBox.CaretIndex == QueryTextBox.Text.Length + && !string.IsNullOrEmpty(QueryTextBox.Text)) + { + _viewModel.LoadContextMenuCommand.Execute(null); + e.Handled = true; + } + + break; + case Key.Left: + if (!_viewModel.SelectedIsFromQueryResults() && QueryTextBox.CaretIndex == 0) + { + _viewModel.EscCommand.Execute(null); + e.Handled = true; + } + + break; + case Key.Back: + if (specialKeyState.CtrlPressed) + { + if (_viewModel.SelectedIsFromQueryResults() + && QueryTextBox.Text.Length > 0 + && QueryTextBox.CaretIndex == QueryTextBox.Text.Length) + { + var queryWithoutActionKeyword = + QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins)?.Search; + + if (FilesFolders.IsLocationPathString(queryWithoutActionKeyword)) + { + _viewModel.BackspaceCommand.Execute(null); + e.Handled = true; + } + } + } + + break; + default: + break; + } + } + + private void OnKeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.Up || e.Key == Key.Down) + { + isArrowKeyPressed = false; + } + } + + private void OnPreviewMouseMove(object sender, MouseEventArgs e) + { + if (isArrowKeyPressed) + { + e.Handled = true; // Ignore Mouse Hover when press Arrowkeys + } + } + +#pragma warning restore VSTHRD100 + + #endregion + + #region Window Boarder Event + + private void OnMouseDown(object sender, MouseButtonEventArgs e) + { + if (e.ChangedButton == MouseButton.Left) DragMove(); + } + + #endregion + + #region Window Context Menu Event + +#pragma warning disable VSTHRD100 + + private async void OnContextMenusForSettingsClick(object sender, RoutedEventArgs e) + { + _viewModel.Hide(); + + if (_settings.UseAnimation) + await Task.Delay(100); + + App.API.OpenSettingDialog(); + } + +#pragma warning restore VSTHRD100 + + #endregion + + #region Window WndProc + + private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + if (Win32Helper.WM_ENTERSIZEMOVE(msg)) + { + _initialWidth = (int)Width; + _initialHeight = (int)Height; + handled = true; + } + else if (Win32Helper.WM_EXITSIZEMOVE(msg)) + { + if (_initialHeight != (int)Height) + { + var shadowMargin = 0; + if (_settings.UseDropShadowEffect) + { + shadowMargin = 32; + } + + if (!_settings.KeepMaxResults) + { + var itemCount = (Height - (_settings.WindowHeightSize + 14) - shadowMargin) / _settings.ItemHeightSize; + + if (itemCount < 2) + { + _settings.MaxResultsToShow = 2; + } + else + { + _settings.MaxResultsToShow = Convert.ToInt32(Math.Truncate(itemCount)); + } + } + + SizeToContent = SizeToContent.Height; + _viewModel.MainWindowWidth = Width; + } + + if (_initialWidth != (int)Width) + { + SizeToContent = SizeToContent.Height; + } + + handled = true; + } + + return IntPtr.Zero; + } + + #endregion + + #region Window Sound Effects + + private void InitSoundEffects() + { + if (_settings.WMPInstalled) + { + animationSoundWMP = new MediaPlayer(); + animationSoundWMP.Open(new Uri(AppContext.BaseDirectory + "Resources\\open.wav")); + } + else + { + animationSoundWPF = new SoundPlayer(AppContext.BaseDirectory + "Resources\\open.wav"); + } + } + + private void SoundPlay() + { + if (_settings.WMPInstalled) + { + animationSoundWMP.Position = TimeSpan.Zero; + animationSoundWMP.Volume = _settings.SoundVolume / 100.0; + animationSoundWMP.Play(); + } + else + { + animationSoundWPF.Play(); + } + } + + #endregion + + #region Window Notify Icon + + private void InitializeNotifyIcon() + { + _notifyIcon = new NotifyIcon + { + Text = Constant.FlowLauncherFullName, + Icon = Constant.Version == "1.0.0" ? Properties.Resources.dev : Properties.Resources.app, + Visible = !_settings.HideNotifyIcon + }; + var openIcon = new FontIcon { Glyph = "\ue71e" }; + var open = new MenuItem + { + Header = App.API.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")", + Icon = openIcon + }; + var gamemodeIcon = new FontIcon { Glyph = "\ue7fc" }; + var gamemode = new MenuItem + { + Header = App.API.GetTranslation("GameMode"), + Icon = gamemodeIcon + }; + var positionresetIcon = new FontIcon { Glyph = "\ue73f" }; + var positionreset = new MenuItem + { + Header = App.API.GetTranslation("PositionReset"), + Icon = positionresetIcon + }; + var settingsIcon = new FontIcon { Glyph = "\ue713" }; + var settings = new MenuItem + { + Header = App.API.GetTranslation("iconTraySettings"), + Icon = settingsIcon + }; + var exitIcon = new FontIcon { Glyph = "\ue7e8" }; + var exit = new MenuItem + { + Header = App.API.GetTranslation("iconTrayExit"), + Icon = exitIcon + }; + + open.Click += (o, e) => _viewModel.ToggleFlowLauncher(); + gamemode.Click += (o, e) => _viewModel.ToggleGameMode(); + positionreset.Click += (o, e) => _ = PositionResetAsync(); + settings.Click += (o, e) => App.API.OpenSettingDialog(); + exit.Click += (o, e) => Close(); + + gamemode.ToolTip = App.API.GetTranslation("GameModeToolTip"); + positionreset.ToolTip = App.API.GetTranslation("PositionResetToolTip"); + + contextMenu.Items.Add(open); + contextMenu.Items.Add(gamemode); + contextMenu.Items.Add(positionreset); + contextMenu.Items.Add(settings); + contextMenu.Items.Add(exit); + + _notifyIcon.MouseClick += (o, e) => + { + switch (e.Button) + { + case System.Windows.Forms.MouseButtons.Left: + _viewModel.ToggleFlowLauncher(); + break; + case System.Windows.Forms.MouseButtons.Right: + + contextMenu.IsOpen = true; + // Get context menu handle and bring it to the foreground + if (PresentationSource.FromVisual(contextMenu) is HwndSource hwndSource) + { + Win32Helper.SetForegroundWindow(hwndSource.Handle); + } + + contextMenu.Focus(); + break; + } + }; + } + + private void UpdateNotifyIconText() + { + var menu = contextMenu; + ((MenuItem)menu.Items[0]).Header = App.API.GetTranslation("iconTrayOpen") + + " (" + _settings.Hotkey + ")"; + ((MenuItem)menu.Items[1]).Header = App.API.GetTranslation("GameMode"); + ((MenuItem)menu.Items[2]).Header = App.API.GetTranslation("PositionReset"); + ((MenuItem)menu.Items[3]).Header = App.API.GetTranslation("iconTraySettings"); + ((MenuItem)menu.Items[4]).Header = App.API.GetTranslation("iconTrayExit"); + } + + #endregion + + #region Window Position + + private void UpdatePosition(bool force) + { + if (_animating && !force) + { + return; + } + + // Initialize call twice to work around multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910 + InitializePosition(); + InitializePosition(); + } + + private async Task PositionResetAsync() + { + _viewModel.Show(); + await Task.Delay(300); // If don't give a time, Positioning will be weird. + var screen = SelectedScreen(); + Left = HorizonCenter(screen); + Top = VerticalCenter(screen); + } + private void InitializePosition() { // Initialize call twice to work around multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910 @@ -317,119 +615,70 @@ namespace Flow.Launcher } } - private void UpdateNotifyIconText() + private Screen SelectedScreen() { - var menu = contextMenu; - ((MenuItem)menu.Items[0]).Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + - " (" + _settings.Hotkey + ")"; - ((MenuItem)menu.Items[1]).Header = InternationalizationManager.Instance.GetTranslation("GameMode"); - ((MenuItem)menu.Items[2]).Header = InternationalizationManager.Instance.GetTranslation("PositionReset"); - ((MenuItem)menu.Items[3]).Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings"); - ((MenuItem)menu.Items[4]).Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit"); - } - - private void InitializeNotifyIcon() - { - _notifyIcon = new NotifyIcon + Screen screen; + switch (_settings.SearchWindowScreen) { - Text = Constant.FlowLauncherFullName, - Icon = Constant.Version == "1.0.0" ? Properties.Resources.dev : Properties.Resources.app, - Visible = !_settings.HideNotifyIcon - }; - var openIcon = new FontIcon { Glyph = "\ue71e" }; - var open = new MenuItem - { - Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" + - _settings.Hotkey + ")", - Icon = openIcon - }; - var gamemodeIcon = new FontIcon { Glyph = "\ue7fc" }; - var gamemode = new MenuItem - { - Header = InternationalizationManager.Instance.GetTranslation("GameMode"), - Icon = gamemodeIcon - }; - var positionresetIcon = new FontIcon { Glyph = "\ue73f" }; - var positionreset = new MenuItem - { - Header = InternationalizationManager.Instance.GetTranslation("PositionReset"), - Icon = positionresetIcon - }; - var settingsIcon = new FontIcon { Glyph = "\ue713" }; - var settings = new MenuItem - { - Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings"), - Icon = settingsIcon - }; - var exitIcon = new FontIcon { Glyph = "\ue7e8" }; - var exit = new MenuItem - { - Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit"), - Icon = exitIcon - }; - - open.Click += (o, e) => _viewModel.ToggleFlowLauncher(); - gamemode.Click += (o, e) => _viewModel.ToggleGameMode(); - positionreset.Click += (o, e) => _ = PositionResetAsync(); - settings.Click += (o, e) => App.API.OpenSettingDialog(); - exit.Click += (o, e) => Close(); - - gamemode.ToolTip = InternationalizationManager.Instance.GetTranslation("GameModeToolTip"); - positionreset.ToolTip = InternationalizationManager.Instance.GetTranslation("PositionResetToolTip"); - - contextMenu.Items.Add(open); - contextMenu.Items.Add(gamemode); - contextMenu.Items.Add(positionreset); - contextMenu.Items.Add(settings); - contextMenu.Items.Add(exit); - - _notifyIcon.MouseClick += (o, e) => - { - switch (e.Button) - { - case System.Windows.Forms.MouseButtons.Left: - _viewModel.ToggleFlowLauncher(); - break; - case System.Windows.Forms.MouseButtons.Right: - - contextMenu.IsOpen = true; - // Get context menu handle and bring it to the foreground - if (PresentationSource.FromVisual(contextMenu) is HwndSource hwndSource) - { - Win32Helper.SetForegroundWindow(hwndSource.Handle); - } - - contextMenu.Focus(); - break; - } - }; - } - - private void CheckFirstLaunch() - { - if (_settings.FirstLaunch) - { - _settings.FirstLaunch = false; - App.API.SaveAppAllSettings(); - OpenWelcomeWindow(); + case SearchWindowScreens.Cursor: + screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); + break; + case SearchWindowScreens.Primary: + screen = Screen.PrimaryScreen; + break; + case SearchWindowScreens.Focus: + var foregroundWindowHandle = Win32Helper.GetForegroundWindow(); + screen = Screen.FromHandle(foregroundWindowHandle); + break; + case SearchWindowScreens.Custom: + if (_settings.CustomScreenNumber <= Screen.AllScreens.Length) + screen = Screen.AllScreens[_settings.CustomScreenNumber - 1]; + else + screen = Screen.AllScreens[0]; + break; + default: + screen = Screen.AllScreens[0]; + break; } + + return screen ?? Screen.AllScreens[0]; } - private static void OpenWelcomeWindow() + private double HorizonCenter(Screen screen) { - var WelcomeWindow = new WelcomeWindow(); - WelcomeWindow.Show(); + 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; } - private async Task PositionResetAsync() + private double VerticalCenter(Screen screen) { - _viewModel.Show(); - await Task.Delay(300); // If don't give a time, Positioning will be weird. - var screen = SelectedScreen(); - Left = HorizonCenter(screen); - Top = VerticalCenter(screen); + 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; } + private double HorizonRight(Screen screen) + { + 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; + } + + private double HorizonLeft(Screen screen) + { + var dip1 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); + var left = dip1.X + 10; + return left; + } + + #endregion + + #region Window Animation + private void InitProgressbarAnimation() { var progressBarStoryBoard = new Storyboard(); @@ -477,27 +726,14 @@ namespace Flow.Launcher _viewModel.ProgressBarVisibility = Visibility.Hidden; } - public void ResetAnimation() - { - // 애니메이션 중지 - clocksb?.Stop(ClockPanel); - iconsb?.Stop(SearchIcon); - windowsb?.Stop(FlowMainWindow); - - // UI 요소 상태 초기화 - //ClockPanel.Margin = new Thickness(0, 0, ClockPanel.Margin.Right, 0); - ClockPanel.Opacity = 0; - SearchIcon.Opacity = 0; - } - - public void WindowAnimator() + private void WindowAnimation() { if (_animating) return; isArrowKeyPressed = true; _animating = true; - UpdatePosition(); + UpdatePosition(false); windowsb = new Storyboard(); clocksb = new Storyboard(); @@ -604,43 +840,9 @@ namespace Flow.Launcher } iconsb.Begin(SearchIcon); - windowsb.Begin(FlowMainWindow); + windowsb.Begin(this); } - private static double GetOpacityFromStyle(Style style, double defaultOpacity = 1.0) - { - if (style == null) - return defaultOpacity; - - foreach (Setter setter in style.Setters.Cast()) - { - if (setter.Property == OpacityProperty) - { - return setter.Value is double opacity ? opacity : defaultOpacity; - } - } - - return defaultOpacity; - } - - private static Thickness GetThicknessFromStyle(Style style, Thickness defaultThickness) - { - if (style == null) - return defaultThickness; - - foreach (Setter setter in style.Setters.Cast()) - { - if (setter.Property == MarginProperty) - { - return setter.Value is Thickness thickness ? thickness : defaultThickness; - } - } - - return defaultThickness; - } - - private bool _isClockPanelAnimating = false; // 애니메이션 실행 중인지 여부 - private void UpdateClockPanelVisibility() { if (QueryTextBox == null || ContextMenu == null || History == null || ClockPanel == null) @@ -726,273 +928,66 @@ namespace Flow.Launcher } } - private void InitSoundEffects() + private static double GetOpacityFromStyle(Style style, double defaultOpacity = 1.0) { - if (_settings.WMPInstalled) + if (style == null) + return defaultOpacity; + + foreach (Setter setter in style.Setters.Cast()) { - animationSoundWMP = new MediaPlayer(); - animationSoundWMP.Open(new Uri(AppDomain.CurrentDomain.BaseDirectory + "Resources\\open.wav")); - } - else - { - animationSoundWPF = new SoundPlayer(AppDomain.CurrentDomain.BaseDirectory + "Resources\\open.wav"); - } - } - - private void SoundPlay() - { - if (_settings.WMPInstalled) - { - animationSoundWMP.Position = TimeSpan.Zero; - animationSoundWMP.Volume = _settings.SoundVolume / 100.0; - animationSoundWMP.Play(); - } - else - { - animationSoundWPF.Play(); - } - } - - private void OnMouseDown(object sender, MouseButtonEventArgs e) - { - if (e.ChangedButton == MouseButton.Left) DragMove(); - } - - private void OnPreviewDragOver(object sender, DragEventArgs e) - { - e.Handled = true; - } - - private async void OnContextMenusForSettingsClick(object sender, RoutedEventArgs e) - { - _viewModel.Hide(); - - if (_settings.UseAnimation) - await Task.Delay(100); - - App.API.OpenSettingDialog(); - } - - private async void OnDeactivated(object sender, EventArgs e) - { - _settings.WindowLeft = Left; - _settings.WindowTop = Top; - //This condition stops extra hide call when animator is on, - // which causes the toggling to occasional hide instead of show. - if (_viewModel.MainWindowVisibilityStatus) - { - // Need time to initialize the main query window animation. - // This also stops the mainwindow from flickering occasionally after Settings window is opened - // and always after Settings window is closed. - if (_settings.UseAnimation) - await Task.Delay(100); - - if (_settings.HideWhenDeactivated && !_viewModel.ExternalPreviewVisible) + if (setter.Property == OpacityProperty) { - _viewModel.Hide(); + return setter.Value is double opacity ? opacity : defaultOpacity; } } + + return defaultOpacity; } - private void UpdatePosition() + private static Thickness GetThicknessFromStyle(Style style, Thickness defaultThickness) { - if (_animating) - return; + if (style == null) + return defaultThickness; - // Initialize call twice to work around multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910 - InitializePosition(); - InitializePosition(); - } - - private void OnLocationChanged(object sender, EventArgs e) - { - if (_animating) - return; - if (_settings.SearchWindowScreen == SearchWindowScreens.RememberLastLaunchLocation) + foreach (Setter setter in style.Setters.Cast()) { - _settings.WindowLeft = Left; - _settings.WindowTop = Top; + if (setter.Property == MarginProperty) + { + return setter.Value is Thickness thickness ? thickness : defaultThickness; + } + } + + return defaultThickness; + } + + #endregion + + #region QueryTextBox Event + + private void QueryTextBox_OnCopy(object sender, ExecutedRoutedEventArgs e) + { + var result = _viewModel.Results.SelectedItem?.Result; + if (QueryTextBox.SelectionLength == 0 && result != null) + { + string copyText = result.CopyText; + App.API.CopyToClipboard(copyText, directCopy: true); + } + else if (!string.IsNullOrEmpty(QueryTextBox.Text)) + { + App.API.CopyToClipboard(QueryTextBox.SelectedText, showDefaultNotification: false); } } - public void HideStartup() + private void QueryTextBox_OnPaste(object sender, DataObjectPastingEventArgs e) { - if (_settings.HideOnStartup) + var isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true); + if (isText) { - _viewModel.Hide(); - } - else - { - _viewModel.Show(); - } - } - - public Screen SelectedScreen() - { - Screen screen; - switch (_settings.SearchWindowScreen) - { - case SearchWindowScreens.Cursor: - screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); - break; - case SearchWindowScreens.Primary: - screen = Screen.PrimaryScreen; - break; - case SearchWindowScreens.Focus: - var foregroundWindowHandle = Win32Helper.GetForegroundWindow(); - screen = Screen.FromHandle(foregroundWindowHandle); - break; - case SearchWindowScreens.Custom: - if (_settings.CustomScreenNumber <= Screen.AllScreens.Length) - screen = Screen.AllScreens[_settings.CustomScreenNumber - 1]; - else - screen = Screen.AllScreens[0]; - break; - default: - screen = Screen.AllScreens[0]; - break; - } - - return screen ?? Screen.AllScreens[0]; - } - - public double HorizonCenter(Screen screen) - { - 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 = 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 = 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 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); - var left = dip1.X + 10; - return left; - } - - /// - /// Register up and down key - /// todo: Put this in xaml? - /// - private void OnKeyDown(object sender, KeyEventArgs e) - { - var specialKeyState = GlobalHotkey.CheckModifiers(); - switch (e.Key) - { - case Key.Down: - isArrowKeyPressed = true; - _viewModel.SelectNextItemCommand.Execute(null); - e.Handled = true; - break; - case Key.Up: - isArrowKeyPressed = true; - _viewModel.SelectPrevItemCommand.Execute(null); - e.Handled = true; - break; - case Key.PageDown: - _viewModel.SelectNextPageCommand.Execute(null); - e.Handled = true; - break; - case Key.PageUp: - _viewModel.SelectPrevPageCommand.Execute(null); - e.Handled = true; - break; - case Key.Right: - if (_viewModel.SelectedIsFromQueryResults() - && QueryTextBox.CaretIndex == QueryTextBox.Text.Length - && !string.IsNullOrEmpty(QueryTextBox.Text)) - { - _viewModel.LoadContextMenuCommand.Execute(null); - e.Handled = true; - } - - break; - case Key.Left: - if (!_viewModel.SelectedIsFromQueryResults() && QueryTextBox.CaretIndex == 0) - { - _viewModel.EscCommand.Execute(null); - e.Handled = true; - } - - break; - case Key.Back: - if (specialKeyState.CtrlPressed) - { - if (_viewModel.SelectedIsFromQueryResults() - && QueryTextBox.Text.Length > 0 - && QueryTextBox.CaretIndex == QueryTextBox.Text.Length) - { - var queryWithoutActionKeyword = - QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins)?.Search; - - if (FilesFolders.IsLocationPathString(queryWithoutActionKeyword)) - { - _viewModel.BackspaceCommand.Execute(null); - e.Handled = true; - } - } - } - - break; - default: - break; - } - } - - private void OnKeyUp(object sender, KeyEventArgs e) - { - if (e.Key == Key.Up || e.Key == Key.Down) - { - isArrowKeyPressed = false; - } - } - - private void MainPreviewMouseMove(object sender, MouseEventArgs e) - { - if (isArrowKeyPressed) - { - e.Handled = true; // Ignore Mouse Hover when press Arrowkeys - } - } - - public void PreviewReset() - { - _viewModel.ResetPreview(); - } - - private void MoveQueryTextToEnd() - { - // QueryTextBox seems to be update with a DispatcherPriority as low as ContextIdle. - // To ensure QueryTextBox is up to date with QueryText from the View, we need to Dispatch with such a priority - Dispatcher.Invoke(() => QueryTextBox.CaretIndex = QueryTextBox.Text.Length); - } - - public void InitializeColorScheme() - { - if (_settings.ColorScheme == Constant.Light) - { - ModernWpf.ThemeManager.Current.ApplicationTheme = ModernWpf.ApplicationTheme.Light; - } - else if (_settings.ColorScheme == Constant.Dark) - { - ModernWpf.ThemeManager.Current.ApplicationTheme = ModernWpf.ApplicationTheme.Dark; + var text = e.SourceDataObject.GetData(DataFormats.UnicodeText) as string; + text = text.Replace(Environment.NewLine, " "); + DataObject data = new DataObject(); + data.SetData(DataFormats.UnicodeText, text); + e.DataObject = data; } } @@ -1004,5 +999,12 @@ namespace Flow.Launcher be.UpdateSource(); } } + + private void QueryTextBox_OnPreviewDragOver(object sender, DragEventArgs e) + { + e.Handled = true; + } + + #endregion } } From 49f1d7979843845d22ad90ad45bcce568b699f30 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 16 Mar 2025 23:18:50 +0800 Subject: [PATCH 04/12] Fix SetWindowLong issue --- .../NativeMethods.txt | 1 - .../PInvokeExtensions.cs | 25 ++++++++++++++++++ Flow.Launcher.Infrastructure/Win32Helper.cs | 26 +++++++++---------- Flow.Launcher/MainWindow.xaml.cs | 4 +-- 4 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 Flow.Launcher.Infrastructure/PInvokeExtensions.cs diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index 5d72239f3..f080f24de 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -28,7 +28,6 @@ SystemParametersInfo SetForegroundWindow GetWindowLong -SetWindowLong GetForegroundWindow GetDesktopWindow GetShellWindow diff --git a/Flow.Launcher.Infrastructure/PInvokeExtensions.cs b/Flow.Launcher.Infrastructure/PInvokeExtensions.cs new file mode 100644 index 000000000..1a72ab7a6 --- /dev/null +++ b/Flow.Launcher.Infrastructure/PInvokeExtensions.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace Windows.Win32; + +// Edited from: https://github.com/files-community/Files +internal static partial class PInvoke +{ + [DllImport("User32", EntryPoint = "SetWindowLongW", ExactSpelling = true)] + static extern int _SetWindowLong(HWND hWnd, int nIndex, int dwNewLong); + + [DllImport("User32", EntryPoint = "SetWindowLongPtrW", ExactSpelling = true)] + static extern nint _SetWindowLongPtr(HWND hWnd, int nIndex, nint dwNewLong); + + // NOTE: + // CsWin32 doesn't generate SetWindowLong on other than x86 and vice versa. + // For more info, visit https://github.com/microsoft/CsWin32/issues/882 + public static unsafe nint SetWindowLongPtr(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, nint dwNewLong) + { + return sizeof(nint) is 4 + ? _SetWindowLong(hWnd, (int)nIndex, (int)dwNewLong) + : _SetWindowLongPtr(hWnd, (int)nIndex, dwNewLong); + } +} diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 6e429cddd..cdb384ab7 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -132,12 +132,12 @@ namespace Flow.Launcher.Infrastructure { var hwnd = GetWindowHandle(window); - var exStyle = GetCurrentWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); + 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; - SetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle); + SetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle); } /// @@ -148,12 +148,12 @@ namespace Flow.Launcher.Infrastructure { var hwnd = GetWindowHandle(window); - var exStyle = GetCurrentWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); + 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; - SetWindowLong(GetWindowHandle(window), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle); + SetWindowStyle(GetWindowHandle(window), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle); } /// @@ -164,14 +164,14 @@ namespace Flow.Launcher.Infrastructure { var hwnd = GetWindowHandle(window); - var style = GetCurrentWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE); + var style = GetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE); style &= ~(int)WINDOW_STYLE.WS_SYSMENU; - SetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, style); + SetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, style); } - private static int GetCurrentWindowStyle(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex) + private static int GetWindowStyle(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex) { var style = PInvoke.GetWindowLong(hWnd, nIndex); if (style == 0 && Marshal.GetLastPInvokeError() != 0) @@ -181,11 +181,11 @@ namespace Flow.Launcher.Infrastructure return style; } - private static int SetWindowLong(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, int dwNewLong) + 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.SetWindowLong(hWnd, nIndex, dwNewLong); + var result = PInvoke.SetWindowLongPtr(hWnd, nIndex, dwNewLong); if (result == 0 && Marshal.GetLastPInvokeError() != 0) { throw new Win32Exception(Marshal.GetLastPInvokeError()); @@ -299,14 +299,14 @@ namespace Flow.Launcher.Infrastructure #region WndProc - public static bool WM_ENTERSIZEMOVE(int msg) + public static bool WM_ENTERSIZEMOVE(uint msg) { - return msg == (int)PInvoke.WM_ENTERSIZEMOVE; + return msg == PInvoke.WM_ENTERSIZEMOVE; } - public static bool WM_EXITSIZEMOVE(int msg) + public static bool WM_EXITSIZEMOVE(uint msg) { - return msg == (int)PInvoke.WM_EXITSIZEMOVE; + return msg == PInvoke.WM_EXITSIZEMOVE; } #endregion diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index d0375e8f6..9038762f2 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -377,13 +377,13 @@ namespace Flow.Launcher private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { - if (Win32Helper.WM_ENTERSIZEMOVE(msg)) + if (Win32Helper.WM_ENTERSIZEMOVE((uint)msg)) { _initialWidth = (int)Width; _initialHeight = (int)Height; handled = true; } - else if (Win32Helper.WM_EXITSIZEMOVE(msg)) + else if (Win32Helper.WM_EXITSIZEMOVE((uint)msg)) { if (_initialHeight != (int)Height) { From bf5591c9a84c6cf58f0b5be162729626c8681e23 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 16 Mar 2025 23:19:35 +0800 Subject: [PATCH 05/12] Fix position set issue --- Flow.Launcher/MainWindow.xaml.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 9038762f2..aa77d64f3 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -65,7 +65,8 @@ namespace Flow.Launcher _settings = settings; InitializeComponent(); - + UpdatePosition(true); + InitSoundEffects(); DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste); } @@ -97,6 +98,7 @@ namespace Flow.Launcher } // Hide window if need + UpdatePosition(true); if (_settings.HideOnStartup) { _viewModel.Hide(); From 0bcc187b5defe11d79054bcb64816d5054f6b4b7 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 16 Mar 2025 23:55:15 +0800 Subject: [PATCH 06/12] Keep user settings when changing theme --- Flow.Launcher.Core/Resource/Theme.cs | 137 +++++++++++------- .../UserSettings/Settings.cs | 1 - Flow.Launcher/App.xaml.cs | 3 +- Flow.Launcher/MainWindow.xaml.cs | 13 +- .../ViewModels/SettingsPaneThemeViewModel.cs | 16 +- .../Flow.Launcher.Plugin.Sys/ThemeSelector.cs | 15 -- 6 files changed, 94 insertions(+), 91 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 4169f3534..17fb698b4 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -416,21 +416,24 @@ namespace Flow.Launcher.Core.Resource /// public async Task RefreshFrameAsync() { - await Application.Current.Dispatcher.InvokeAsync(async () => + await Application.Current.Dispatcher.InvokeAsync(() => { + // Get the actual backdrop type and drop shadow effect settings + var (backdropType, useDropShadowEffect) = GetActualValue(); + // Remove OS minimizing/maximizing animation // Methods.SetWindowAttribute(new WindowInteropHelper(mainWindow).Handle, DWMWINDOWATTRIBUTE.DWMWA_TRANSITIONS_FORCEDISABLED, 3); - + // The timing of adding the shadow effect should vary depending on whether the theme is transparent. if (BlurEnabled) { - AutoDropShadow(); + AutoDropShadow(useDropShadowEffect); } - await SetBlurForWindowAsync(); + SetBlurForWindow(backdropType); if (!BlurEnabled) { - AutoDropShadow(); + AutoDropShadow(useDropShadowEffect); } }, DispatcherPriority.Normal); } @@ -440,61 +443,87 @@ namespace Flow.Launcher.Core.Resource /// public async Task SetBlurForWindowAsync() { - await Application.Current.Dispatcher.InvokeAsync(async () => + await Application.Current.Dispatcher.InvokeAsync(() => { - var dict = GetThemeResourceDictionary(_settings.Theme); - if (dict == null) - return; + // Get the actual backdrop type and drop shadow effect settings + var (backdropType, _) = GetActualValue(); - var windowBorderStyle = dict.Contains("WindowBorderStyle") ? dict["WindowBorderStyle"] as Style : null; - if (windowBorderStyle == null) - return; - - Window mainWindow = Application.Current.MainWindow; - if (mainWindow == null) - return; - - // Check if the theme supports blur - bool hasBlur = dict.Contains("ThemeBlurEnabled") && dict["ThemeBlurEnabled"] is bool b && b; - if (!hasBlur) - { - _settings.BackdropType = BackdropTypes.None; - } - - if (BlurEnabled && hasBlur && Win32Helper.IsBackdropSupported()) - { - // If the BackdropType is Mica or MicaAlt, set the windowborderstyle's background to transparent - if (_settings.BackdropType == BackdropTypes.Mica || _settings.BackdropType == BackdropTypes.MicaAlt) - { - windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType().FirstOrDefault(x => x.Property.Name == "Background")); - windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)))); - } - else if (_settings.BackdropType == BackdropTypes.Acrylic) - { - windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType().FirstOrDefault(x => x.Property.Name == "Background")); - windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Colors.Transparent))); - } - - // Apply the blur effect - Win32Helper.DWMSetBackdropForWindow(mainWindow, _settings.BackdropType); - ColorizeWindow(); - } - else - { - // Apply default style when Blur is disabled - Win32Helper.DWMSetBackdropForWindow(mainWindow, BackdropTypes.None); - ColorizeWindow(); - } - - UpdateResourceDictionary(dict); + SetBlurForWindow(backdropType); }, DispatcherPriority.Normal); } - private void AutoDropShadow() + /// + /// Gets the actual backdrop type and drop shadow effect settings based on the current theme status. + /// + public (BackdropTypes BackdropType, bool UseDropShadowEffect) GetActualValue() + { + var backdropType = _settings.BackdropType; + var useDropShadowEffect = _settings.UseDropShadowEffect; + + // When changed non-blur theme, change to backdrop to none + if (!BlurEnabled) + { + backdropType = BackdropTypes.None; + } + + // Dropshadow on and control disabled.(user can't change dropshadow with blur theme) + if (BlurEnabled) + { + useDropShadowEffect = true; + } + + return (backdropType, useDropShadowEffect); + } + + private void SetBlurForWindow(BackdropTypes backdropType) + { + var dict = GetThemeResourceDictionary(_settings.Theme); + if (dict == null) + return; + + var windowBorderStyle = dict.Contains("WindowBorderStyle") ? dict["WindowBorderStyle"] as Style : null; + if (windowBorderStyle == null) + return; + + Window mainWindow = Application.Current.MainWindow; + if (mainWindow == null) + return; + + // Check if the theme supports blur + bool hasBlur = dict.Contains("ThemeBlurEnabled") && dict["ThemeBlurEnabled"] is bool b && b; + if (BlurEnabled && hasBlur && Win32Helper.IsBackdropSupported()) + { + // If the BackdropType is Mica or MicaAlt, set the windowborderstyle's background to transparent + if (backdropType == BackdropTypes.Mica || backdropType == BackdropTypes.MicaAlt) + { + windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType().FirstOrDefault(x => x.Property.Name == "Background")); + windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)))); + } + else if (backdropType == BackdropTypes.Acrylic) + { + windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType().FirstOrDefault(x => x.Property.Name == "Background")); + windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Colors.Transparent))); + } + + // Apply the blur effect + Win32Helper.DWMSetBackdropForWindow(mainWindow, backdropType); + ColorizeWindow(backdropType); + } + else + { + // Apply default style when Blur is disabled + Win32Helper.DWMSetBackdropForWindow(mainWindow, BackdropTypes.None); + ColorizeWindow(backdropType); + } + + UpdateResourceDictionary(dict); + } + + private void AutoDropShadow(bool useDropShadowEffect) { SetWindowCornerPreference("Default"); RemoveDropShadowEffectFromCurrentTheme(); - if (_settings.UseDropShadowEffect) + if (useDropShadowEffect) { if (BlurEnabled && Win32Helper.IsBackdropSupported()) { @@ -605,7 +634,7 @@ namespace Flow.Launcher.Core.Resource Application.Current.Resources["PreviewWindowBorderStyle"] = previewStyle; } - private void ColorizeWindow() + private void ColorizeWindow(BackdropTypes backdropType) { var dict = GetThemeResourceDictionary(_settings.Theme); if (dict == null) return; @@ -688,7 +717,7 @@ namespace Flow.Launcher.Core.Resource else { // Only set the background to transparent if the theme supports blur - if (_settings.BackdropType == BackdropTypes.Mica || _settings.BackdropType == BackdropTypes.MicaAlt) + if (backdropType == BackdropTypes.Mica || backdropType == BackdropTypes.MicaAlt) { mainWindow.Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)); } diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 8277e8e0b..63debfb47 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -76,7 +76,6 @@ namespace Flow.Launcher.Infrastructure.UserSettings } } public bool UseDropShadowEffect { get; set; } = true; - public BackdropTypes BackdropType{ get; set; } = BackdropTypes.None; /* Appearance Settings. It should be separated from the setting later.*/ diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 550b1bdae..7d094a0af 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -137,8 +137,7 @@ namespace Flow.Launcher await PluginManager.InitializePluginsAsync(); await imageLoadertask; - var mainVM = Ioc.Default.GetRequiredService(); - var window = new MainWindow(_settings, mainVM); + var window = new MainWindow(); Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}"); diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index aa77d64f3..3bc9be83f 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -36,6 +36,7 @@ namespace Flow.Launcher #region Private Fields private readonly Settings _settings; + private readonly Theme _theme; private NotifyIcon _notifyIcon; private readonly ContextMenu contextMenu = new(); private readonly MainViewModel _viewModel; @@ -58,11 +59,12 @@ namespace Flow.Launcher #region Constructor - public MainWindow(Settings settings, MainViewModel mainVM) + public MainWindow() { - DataContext = mainVM; - _viewModel = mainVM; - _settings = settings; + _settings = Ioc.Default.GetRequiredService(); + _theme = Ioc.Default.GetRequiredService(); + _viewModel = Ioc.Default.GetRequiredService(); + DataContext = _viewModel; InitializeComponent(); UpdatePosition(true); @@ -390,7 +392,8 @@ namespace Flow.Launcher if (_initialHeight != (int)Height) { var shadowMargin = 0; - if (_settings.UseDropShadowEffect) + var (_, useDropShadowEffect) = _theme.GetActualValue(); + if (useDropShadowEffect) { shadowMargin = 32; } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index 1036d321c..f8c0fa642 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -35,18 +35,6 @@ public partial class SettingsPaneThemeViewModel : BaseModel _selectedTheme = value; ThemeManager.Instance.ChangeTheme(value.FileNameWithoutExtension); - // when changed non-blur theme, change to backdrop to none - if (!ThemeManager.Instance.BlurEnabled) - { - Settings.BackdropType = BackdropTypes.None; - } - - // dropshadow on and control disabled.(user can't change dropshadow with blur theme) - if (ThemeManager.Instance.BlurEnabled) - { - Settings.UseDropShadowEffect = true; - } - // Update UI state OnPropertyChanged(nameof(BackdropType)); OnPropertyChanged(nameof(IsBackdropEnabled)); @@ -235,8 +223,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel public BackdropTypes BackdropType { get => Enum.IsDefined(typeof(BackdropTypes), Settings.BackdropType) - ? Settings.BackdropType - : BackdropTypes.None; + ? Settings.BackdropType + : BackdropTypes.None; set { if (!Enum.IsDefined(typeof(BackdropTypes), value)) diff --git a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs index 913c313de..b467aefee 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs @@ -2,7 +2,6 @@ using System.Linq; using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.Resource; -using FLSettings = Flow.Launcher.Infrastructure.UserSettings.Settings; namespace Flow.Launcher.Plugin.Sys { @@ -10,7 +9,6 @@ namespace Flow.Launcher.Plugin.Sys { public const string Keyword = "fltheme"; - private readonly FLSettings _settings; private readonly Theme _theme; private readonly PluginInitContext _context; @@ -27,18 +25,6 @@ namespace Flow.Launcher.Plugin.Sys _selectedTheme = value; _theme.ChangeTheme(value.FileNameWithoutExtension); - // when changed non-blur theme, change to backdrop to none - if (!_theme.BlurEnabled) - { - _settings.BackdropType = 0; // Change to 0 instead of BackdropTypes.None - } - - // dropshadow on and control disabled.(user can't change dropshadow with blur theme) - if (_theme.BlurEnabled) - { - _settings.UseDropShadowEffect = true; - } - _ = _theme.RefreshFrameAsync(); } } @@ -51,7 +37,6 @@ namespace Flow.Launcher.Plugin.Sys { _context = context; _theme = Ioc.Default.GetRequiredService(); - _settings = Ioc.Default.GetRequiredService(); } public List Query(Query query) From 2b6e1bf1c73d321909937e3cd31dd974654ffcae Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 17 Mar 2025 09:35:42 +0800 Subject: [PATCH 07/12] Improve code quality --- Flow.Launcher/App.xaml | 2 +- Flow.Launcher/App.xaml.cs | 21 +++++++++++---------- Flow.Launcher/MainWindow.xaml.cs | 8 ++++---- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher/App.xaml b/Flow.Launcher/App.xaml index 17c0ae0d5..565bbe3c7 100644 --- a/Flow.Launcher/App.xaml +++ b/Flow.Launcher/App.xaml @@ -4,7 +4,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="http://schemas.modernwpf.com/2019" ShutdownMode="OnMainWindowClose" - Startup="OnStartupAsync"> + Startup="OnStartup"> diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 7d094a0af..23c77618f 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -101,15 +101,15 @@ namespace Flow.Launcher { if (SingleInstance.InitializeAsFirstInstance(Unique)) { - using (var application = new App()) - { - application.InitializeComponent(); - application.Run(); - } + using var application = new App(); + application.InitializeComponent(); + application.Run(); } } - private async void OnStartupAsync(object sender, StartupEventArgs e) +#pragma warning disable VSTHRD100 // Avoid async void methods + + private async void OnStartup(object sender, StartupEventArgs e) { await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () => { @@ -128,7 +128,7 @@ namespace Flow.Launcher AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings); // TODO: Clean InternationalizationManager.Instance and InternationalizationManager.Instance.GetTranslation in future - InternationalizationManager.Instance.ChangeLanguage(_settings.Language); + Ioc.Default.GetRequiredService().ChangeLanguage(_settings.Language); PluginManager.LoadPlugins(_settings.PluginSettings); @@ -148,7 +148,7 @@ namespace Flow.Launcher // main windows needs initialized before theme change because of blur settings // TODO: Clean ThemeManager.Instance in future - ThemeManager.Instance.ChangeTheme(_settings.Theme); + Ioc.Default.GetRequiredService().ChangeTheme(); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); @@ -163,6 +163,8 @@ namespace Flow.Launcher }); } +#pragma warning restore VSTHRD100 // Avoid async void methods + private void AutoStartup() { // we try to enable auto-startup on first launch, or reenable if it was removed @@ -185,8 +187,7 @@ namespace Flow.Launcher // but if it fails (permissions, etc) then don't keep retrying // this also gives the user a visual indication in the Settings widget _settings.StartFlowLauncherOnSystemStartup = false; - Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"), - e.Message); + API.ShowMsg(API.GetTranslation("setAutoStartFailed"), e.Message); } } } diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 3bc9be83f..3fd13afdf 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -77,7 +77,7 @@ namespace Flow.Launcher #region Window Event -#pragma warning disable VSTHRD100 +#pragma warning disable VSTHRD100 // Avoid async void methods private void OnSourceInitialized(object sender, EventArgs e) { @@ -346,7 +346,7 @@ namespace Flow.Launcher } } -#pragma warning restore VSTHRD100 +#pragma warning restore VSTHRD100 // Avoid async void methods #endregion @@ -361,7 +361,7 @@ namespace Flow.Launcher #region Window Context Menu Event -#pragma warning disable VSTHRD100 +#pragma warning disable VSTHRD100 // Avoid async void methods private async void OnContextMenusForSettingsClick(object sender, RoutedEventArgs e) { @@ -373,7 +373,7 @@ namespace Flow.Launcher App.API.OpenSettingDialog(); } -#pragma warning restore VSTHRD100 +#pragma warning restore VSTHRD100 // Avoid async void methods #endregion From da30e2e5958fecc9ae64edb1e7241f22117525d2 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 17 Mar 2025 09:37:34 +0800 Subject: [PATCH 08/12] Improve code quality --- Flow.Launcher.Core/Resource/Theme.cs | 52 +++++++------ .../ViewModels/SettingsPaneThemeViewModel.cs | 78 +++++++++---------- .../Flow.Launcher.Plugin.Sys/ThemeSelector.cs | 2 +- 3 files changed, 66 insertions(+), 66 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 17fb698b4..2027f2f32 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -24,8 +24,6 @@ namespace Flow.Launcher.Core.Resource { #region Properties & Fields - public string CurrentTheme => _settings.Theme; - public bool BlurEnabled { get; set; } private const string ThemeMetadataNamePrefix = "Name:"; @@ -78,6 +76,11 @@ namespace Flow.Launcher.Core.Resource #region Theme Resources + public string GetCurrentTheme() + { + return _settings.Theme; + } + private void MakeSureThemeDirectoriesExist() { foreach (var dir in _themeDirectories.Where(dir => !Directory.Exists(dir))) @@ -183,7 +186,7 @@ namespace Flow.Launcher.Core.Resource private ResourceDictionary GetCurrentResourceDictionary() { - return GetResourceDictionary(_settings.Theme); + return GetResourceDictionary(GetCurrentTheme()); } private ThemeData GetThemeDataFromPath(string path) @@ -253,9 +256,10 @@ namespace Flow.Launcher.Core.Resource return themes.OrderBy(o => o.Name).ToList(); } - public bool ChangeTheme(string theme) + public bool ChangeTheme(string theme = null) { - const string defaultTheme = Constant.DefaultTheme; + if (string.IsNullOrEmpty(theme)) + theme = GetCurrentTheme(); string path = GetThemePath(theme); try @@ -270,7 +274,7 @@ namespace Flow.Launcher.Core.Resource _settings.Theme = theme; //always allow re-loading default theme, in case of failure of switching to a new theme from default theme - if (_oldTheme != theme || theme == defaultTheme) + if (_oldTheme != theme || theme == Constant.DefaultTheme) { _oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath); } @@ -284,20 +288,20 @@ namespace Flow.Launcher.Core.Resource catch (DirectoryNotFoundException) { Log.Error($"|Theme.ChangeTheme|Theme <{theme}> path can't be found"); - if (theme != defaultTheme) + if (theme != Constant.DefaultTheme) { - _api.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_path_not_exists"), theme)); - ChangeTheme(defaultTheme); + _api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_path_not_exists"), theme)); + ChangeTheme(Constant.DefaultTheme); } return false; } catch (XamlParseException) { Log.Error($"|Theme.ChangeTheme|Theme <{theme}> fail to parse"); - if (theme != defaultTheme) + if (theme != Constant.DefaultTheme) { - _api.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_parse_error"), theme)); - ChangeTheme(defaultTheme); + _api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_parse_error"), theme)); + ChangeTheme(Constant.DefaultTheme); } return false; } @@ -429,7 +433,7 @@ namespace Flow.Launcher.Core.Resource { AutoDropShadow(useDropShadowEffect); } - SetBlurForWindow(backdropType); + SetBlurForWindow(GetCurrentTheme(), backdropType); if (!BlurEnabled) { @@ -448,7 +452,7 @@ namespace Flow.Launcher.Core.Resource // Get the actual backdrop type and drop shadow effect settings var (backdropType, _) = GetActualValue(); - SetBlurForWindow(backdropType); + SetBlurForWindow(GetCurrentTheme(), backdropType); }, DispatcherPriority.Normal); } @@ -475,9 +479,9 @@ namespace Flow.Launcher.Core.Resource return (backdropType, useDropShadowEffect); } - private void SetBlurForWindow(BackdropTypes backdropType) + private void SetBlurForWindow(string theme, BackdropTypes backdropType) { - var dict = GetThemeResourceDictionary(_settings.Theme); + var dict = GetThemeResourceDictionary(theme); if (dict == null) return; @@ -507,13 +511,13 @@ namespace Flow.Launcher.Core.Resource // Apply the blur effect Win32Helper.DWMSetBackdropForWindow(mainWindow, backdropType); - ColorizeWindow(backdropType); + ColorizeWindow(theme, backdropType); } else { // Apply default style when Blur is disabled Win32Helper.DWMSetBackdropForWindow(mainWindow, BackdropTypes.None); - ColorizeWindow(backdropType); + ColorizeWindow(theme, backdropType); } UpdateResourceDictionary(dict); @@ -559,9 +563,9 @@ namespace Flow.Launcher.Core.Resource // Get Background Color from WindowBorderStyle when there not color for BG. // for theme has not "LightBG" or "DarkBG" case. - private Color GetWindowBorderStyleBackground() + private Color GetWindowBorderStyleBackground(string theme) { - var Resources = GetThemeResourceDictionary(_settings.Theme); + var Resources = GetThemeResourceDictionary(theme); var windowBorderStyle = (Style)Resources["WindowBorderStyle"]; var backgroundSetter = windowBorderStyle.Setters @@ -634,9 +638,9 @@ namespace Flow.Launcher.Core.Resource Application.Current.Resources["PreviewWindowBorderStyle"] = previewStyle; } - private void ColorizeWindow(BackdropTypes backdropType) + private void ColorizeWindow(string theme, BackdropTypes backdropType) { - var dict = GetThemeResourceDictionary(_settings.Theme); + var dict = GetThemeResourceDictionary(theme); if (dict == null) return; var mainWindow = Application.Current.MainWindow; @@ -687,11 +691,11 @@ namespace Flow.Launcher.Core.Resource // Retrieve LightBG value (fallback to WindowBorderStyle background color if not found) try { - LightBG = dict.Contains("LightBG") ? (Color)dict["LightBG"] : GetWindowBorderStyleBackground(); + LightBG = dict.Contains("LightBG") ? (Color)dict["LightBG"] : GetWindowBorderStyleBackground(theme); } catch (Exception) { - LightBG = GetWindowBorderStyleBackground(); + LightBG = GetWindowBorderStyleBackground(theme); } // Retrieve DarkBG value (fallback to LightBG if not found) diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index f8c0fa642..61d365b64 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Windows.Media; +using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Resource; using Flow.Launcher.Helper; @@ -13,7 +14,6 @@ using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.ViewModel; using ModernWpf; -using ThemeManager = Flow.Launcher.Core.Resource.ThemeManager; using ThemeManagerForColorSchemeSwitch = ModernWpf.ThemeManager; namespace Flow.Launcher.SettingPages.ViewModels; @@ -22,18 +22,22 @@ public partial class SettingsPaneThemeViewModel : BaseModel { private const string DefaultFont = "Segoe UI"; public Settings Settings { get; } + private readonly Theme _theme = Ioc.Default.GetRequiredService(); public static string LinkHowToCreateTheme => @"https://flowlauncher.com/docs/#/how-to-create-a-theme"; public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438"; + private List _themes; + public List Themes => _themes ??= _theme.LoadAvailableThemes(); + private Theme.ThemeData _selectedTheme; public Theme.ThemeData SelectedTheme { - get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == Settings.Theme); + get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == _theme.GetCurrentTheme()); set { _selectedTheme = value; - ThemeManager.Instance.ChangeTheme(value.FileNameWithoutExtension); + _theme.ChangeTheme(value.FileNameWithoutExtension); // Update UI state OnPropertyChanged(nameof(BackdropType)); @@ -41,7 +45,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel OnPropertyChanged(nameof(IsDropShadowEnabled)); OnPropertyChanged(nameof(DropShadowEffect)); - _ = ThemeManager.Instance.RefreshFrameAsync(); + _ = _theme.RefreshFrameAsync(); } } @@ -54,14 +58,14 @@ public partial class SettingsPaneThemeViewModel : BaseModel } } - public bool IsDropShadowEnabled => !ThemeManager.Instance.BlurEnabled; + public bool IsDropShadowEnabled => !_theme.BlurEnabled; public bool DropShadowEffect { get => Settings.UseDropShadowEffect; set { - if (ThemeManager.Instance.BlurEnabled) + if (_theme.BlurEnabled) { // Always DropShadowEffect = true with blur theme Settings.UseDropShadowEffect = true; @@ -71,11 +75,11 @@ public partial class SettingsPaneThemeViewModel : BaseModel // User can change shadow with non-blur theme. if (value) { - ThemeManager.Instance.AddDropShadowEffectToCurrentTheme(); + _theme.AddDropShadowEffectToCurrentTheme(); } else { - ThemeManager.Instance.RemoveDropShadowEffectFromCurrentTheme(); + _theme.RemoveDropShadowEffectFromCurrentTheme(); } Settings.UseDropShadowEffect = value; @@ -113,9 +117,6 @@ public partial class SettingsPaneThemeViewModel : BaseModel set => Settings.ResultSubItemFontSize = value; } - private List _themes; - public List Themes => _themes ??= ThemeManager.Instance.LoadAvailableThemes(); - public class ColorSchemeData : DropdownDataGeneric { } public List ColorSchemes { get; } = DropdownDataGeneric.GetValues("ColorScheme"); @@ -132,7 +133,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel _ => ThemeManagerForColorSchemeSwitch.Current.ApplicationTheme }; Settings.ColorScheme = value; - _ = ThemeManager.Instance.RefreshFrameAsync(); + _ = _theme.RefreshFrameAsync(); } } @@ -209,13 +210,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel public class AnimationSpeedData : DropdownDataGeneric { } public List AnimationSpeeds { get; } = DropdownDataGeneric.GetValues("AnimationSpeed"); - public class BackdropTypeData : DropdownDataGeneric - { - public void ApplyBackdrop() - { - _ = ThemeManager.Instance.SetBlurForWindowAsync(); - } - } + public class BackdropTypeData : DropdownDataGeneric { } public List BackdropTypesList { get; } = DropdownDataGeneric.GetValues("BackdropTypes"); @@ -233,8 +228,9 @@ public partial class SettingsPaneThemeViewModel : BaseModel } Settings.BackdropType = value; - var backdropData = BackdropTypesList.FirstOrDefault(b => b.Value == value); - backdropData?.ApplyBackdrop(); + + _ = _theme.SetBlurForWindowAsync(); + OnPropertyChanged(nameof(IsDropShadowEnabled)); } } @@ -284,37 +280,37 @@ public partial class SettingsPaneThemeViewModel : BaseModel { var results = new List { - new Result + new() { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleExplorer"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleExplorer"), + Title = App.API.GetTranslation("SampleTitleExplorer"), + SubTitle = App.API.GetTranslation("SampleSubTitleExplorer"), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png" ) }, - new Result + new() { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleWebSearch"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleWebSearch"), + Title = App.API.GetTranslation("SampleTitleWebSearch"), + SubTitle = App.API.GetTranslation("SampleSubTitleWebSearch"), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png" ) }, - new Result + new() { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProgram"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProgram"), + Title = App.API.GetTranslation("SampleTitleProgram"), + SubTitle = App.API.GetTranslation("SampleSubTitleProgram"), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png" ) }, - new Result + new() { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProcessKiller"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProcessKiller"), + Title = App.API.GetTranslation("SampleTitleProcessKiller"), + SubTitle = App.API.GetTranslation("SampleSubTitleProcessKiller"), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png" @@ -346,7 +342,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel set { Settings.QueryBoxFont = value.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); + _theme.ChangeTheme(); } } @@ -368,7 +364,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel Settings.QueryBoxFontStretch = value.Stretch.ToString(); Settings.QueryBoxFontWeight = value.Weight.ToString(); Settings.QueryBoxFontStyle = value.Style.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); + _theme.ChangeTheme(); } } @@ -390,7 +386,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel set { Settings.ResultFont = value.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); + _theme.ChangeTheme(); } } @@ -412,7 +408,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel Settings.ResultFontStretch = value.Stretch.ToString(); Settings.ResultFontWeight = value.Weight.ToString(); Settings.ResultFontStyle = value.Style.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); + _theme.ChangeTheme(); } } @@ -420,9 +416,9 @@ public partial class SettingsPaneThemeViewModel : BaseModel { get { - if (Fonts.SystemFontFamilies.Count(o => + if (Fonts.SystemFontFamilies.Any(o => o.FamilyNames.Values != null && - o.FamilyNames.Values.Contains(Settings.ResultSubFont)) > 0) + o.FamilyNames.Values.Contains(Settings.ResultSubFont))) { var font = new FontFamily(Settings.ResultSubFont); return font; @@ -436,7 +432,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel set { Settings.ResultSubFont = value.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); + _theme.ChangeTheme(); } } @@ -457,7 +453,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel Settings.ResultSubFontStretch = value.Stretch.ToString(); Settings.ResultSubFontWeight = value.Weight.ToString(); Settings.ResultSubFontStyle = value.Style.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); + _theme.ChangeTheme(); } } diff --git a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs index b467aefee..4467b94fb 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs @@ -19,7 +19,7 @@ namespace Flow.Launcher.Plugin.Sys private Theme.ThemeData _selectedTheme; public Theme.ThemeData SelectedTheme { - get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == _theme.CurrentTheme); + get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == _theme.GetCurrentTheme()); set { _selectedTheme = value; From a2f70f412a79e50f1b1b959de093618ec8e55d14 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 17 Mar 2025 09:41:22 +0800 Subject: [PATCH 09/12] Organize usings --- Flow.Launcher/MainWindow.xaml.cs | 38 ++++++++++++++------------------ 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 3fd13afdf..253144457 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -1,33 +1,29 @@ using System; using System.ComponentModel; +using System.Linq; +using System.Media; using System.Threading.Tasks; using System.Windows; -using System.Windows.Input; -using System.Windows.Media.Animation; using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Shapes; +using System.Windows.Threading; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Resource; -using Flow.Launcher.Infrastructure.UserSettings; -using Flow.Launcher.ViewModel; -using Screen = System.Windows.Forms.Screen; -using DragEventArgs = System.Windows.DragEventArgs; -using KeyEventArgs = System.Windows.Input.KeyEventArgs; -using NotifyIcon = System.Windows.Forms.NotifyIcon; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Hotkey; +using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.SharedCommands; -using System.Windows.Threading; -using System.Windows.Data; +using Flow.Launcher.ViewModel; using ModernWpf.Controls; -using Key = System.Windows.Input.Key; -using System.Media; -using DataObject = System.Windows.DataObject; -using System.Windows.Media; -using System.Windows.Interop; -using Window = System.Windows.Window; -using System.Linq; -using System.Windows.Shapes; -using CommunityToolkit.Mvvm.DependencyInjection; +using MouseButtons = System.Windows.Forms.MouseButtons; +using NotifyIcon = System.Windows.Forms.NotifyIcon; +using Screen = System.Windows.Forms.Screen; namespace Flow.Launcher { @@ -520,10 +516,10 @@ namespace Flow.Launcher { switch (e.Button) { - case System.Windows.Forms.MouseButtons.Left: + case MouseButtons.Left: _viewModel.ToggleFlowLauncher(); break; - case System.Windows.Forms.MouseButtons.Right: + case MouseButtons.Right: contextMenu.IsOpen = true; // Get context menu handle and bring it to the foreground From 1e6bbdd1b7e3f2a47e628ce9b10e64b36fc1ae13 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 17 Mar 2025 09:44:02 +0800 Subject: [PATCH 10/12] Organize usings --- Flow.Launcher/ViewModel/MainViewModel.cs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 7d719a442..f7c683bc6 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1,31 +1,30 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Windows.Input; using System.Linq; +using System.Text; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using System.Windows; +using System.Windows.Media; +using System.Windows.Threading; +using CommunityToolkit.Mvvm.DependencyInjection; +using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Hotkey; +using Flow.Launcher.Infrastructure.Image; +using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Storage; -using Flow.Launcher.Infrastructure.Logger; using Microsoft.VisualStudio.Threading; -using System.Text; -using System.Threading.Channels; -using ISavable = Flow.Launcher.Plugin.ISavable; -using CommunityToolkit.Mvvm.Input; -using System.Globalization; -using System.Windows.Input; -using System.ComponentModel; -using Flow.Launcher.Infrastructure.Image; -using System.Windows.Media; -using CommunityToolkit.Mvvm.DependencyInjection; -using System.Windows.Threading; namespace Flow.Launcher.ViewModel { From 698217f25d14a6ced6faaad23cb874d30c4b85f4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 17 Mar 2025 10:07:40 +0800 Subject: [PATCH 11/12] Improve code quality --- Flow.Launcher/PublicAPIInstance.cs | 31 ++++++------ Flow.Launcher/ViewModel/MainViewModel.cs | 62 ++++++++++++------------ 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index ac22170ae..d9f935833 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -1,33 +1,32 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; using System.Linq; using System.Net; +using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using System.Windows; +using CommunityToolkit.Mvvm.DependencyInjection; using Squirrel; +using Flow.Launcher.Core; using Flow.Launcher.Core.Plugin; -using Flow.Launcher.Core.Resource; using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; +using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Image; -using Flow.Launcher.Plugin; -using Flow.Launcher.ViewModel; -using Flow.Launcher.Plugin.SharedModels; -using Flow.Launcher.Plugin.SharedCommands; -using System.Threading; -using System.IO; -using Flow.Launcher.Infrastructure.Http; -using JetBrains.Annotations; -using System.Runtime.CompilerServices; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Storage; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Collections.Specialized; -using CommunityToolkit.Mvvm.DependencyInjection; -using Flow.Launcher.Core; using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedModels; +using Flow.Launcher.Plugin.SharedCommands; +using Flow.Launcher.ViewModel; +using JetBrains.Annotations; namespace Flow.Launcher { @@ -153,7 +152,7 @@ namespace Flow.Launcher public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed; - public string GetTranslation(string key) => InternationalizationManager.Instance.GetTranslation(key); + public string GetTranslation(string key) => App.API.GetTranslation(key); public List GetAllPlugins() => PluginManager.AllPlugins.ToList(); diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index f7c683bc6..59ca356a7 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -14,7 +14,6 @@ using System.Windows.Threading; using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Plugin; -using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Image; @@ -47,8 +46,6 @@ namespace Flow.Launcher.ViewModel private CancellationTokenSource _updateSource; private CancellationToken _updateToken; - private readonly Internationalization _translator = InternationalizationManager.Instance; - private ChannelWriter _resultsUpdateChannelWriter; private Task _resultsViewUpdateTask; @@ -180,9 +177,9 @@ namespace Flow.Launcher.ViewModel var resultUpdateChannel = Channel.CreateUnbounded(); _resultsUpdateChannelWriter = resultUpdateChannel.Writer; _resultsViewUpdateTask = - Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); + Task.Run(UpdateActionAsync).ContinueWith(continueAction, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); - async Task updateAction() + async Task UpdateActionAsync() { var queue = new Dictionary(); var channelReader = resultUpdateChannel.Reader; @@ -249,8 +246,8 @@ namespace Flow.Launcher.ViewModel Hide(); await PluginManager.ReloadDataAsync().ConfigureAwait(false); - Notification.Show(InternationalizationManager.Instance.GetTranslation("success"), - InternationalizationManager.Instance.GetTranslation("completedSuccessfully")); + Notification.Show(App.API.GetTranslation("success"), + App.API.GetTranslation("completedSuccessfully")); } [RelayCommand] @@ -272,14 +269,14 @@ namespace Flow.Launcher.ViewModel { if (SelectedIsFromQueryResults()) { - QueryResults(isReQuery: true); + _ = QueryResultsAsync(isReQuery: true); } } public void ReQuery(bool reselect) { BackToQueryResults(); - QueryResults(isReQuery: true, reSelect: reselect); + _ = QueryResultsAsync(isReQuery: true, reSelect: reselect); } [RelayCommand] @@ -387,11 +384,11 @@ namespace Flow.Launcher.ViewModel } var hideWindow = await result.ExecuteAsync(new ActionContext - { - // not null means pressing modifier key + number, should ignore the modifier key - SpecialKeyState = index is not null ? SpecialKeyState.Default : GlobalHotkey.CheckModifiers() - }) - .ConfigureAwait(false); + { + // not null means pressing modifier key + number, should ignore the modifier key + SpecialKeyState = index is not null ? SpecialKeyState.Default : GlobalHotkey.CheckModifiers() + }) + .ConfigureAwait(false); if (SelectedIsFromQueryResults()) { @@ -455,7 +452,6 @@ namespace Flow.Launcher.ViewModel SelectedResults.SelectLastResult(); } - [RelayCommand] private void SelectPrevPage() { @@ -482,7 +478,6 @@ namespace Flow.Launcher.ViewModel { SelectedResults.SelectPrevResult(); } - } [RelayCommand] @@ -559,7 +554,6 @@ namespace Flow.Launcher.ViewModel public bool GameModeStatus { get; set; } = false; private string _queryText; - public string QueryText { get => _queryText; @@ -808,8 +802,8 @@ namespace Flow.Launcher.ViewModel throw new NotImplementedException("ResultAreaColumn should match ResultAreaColumnPreviewShown/ResultAreaColumnPreviewHidden value"); #else Log.Error("MainViewModel", "ResultAreaColumnPreviewHidden/ResultAreaColumnPreviewShown int value not implemented", "InternalPreviewVisible"); -#endif return false; +#endif } } @@ -897,7 +891,7 @@ namespace Flow.Launcher.ViewModel ExternalPreviewVisible = false; } - private void SwitchExternalPreview(string path, bool sendFailToast = true) + private static void SwitchExternalPreview(string path, bool sendFailToast = true) { _ = PluginManager.SwitchExternalPreviewAsync(path,sendFailToast).ConfigureAwait(false); } @@ -979,7 +973,7 @@ namespace Flow.Launcher.ViewModel { if (SelectedIsFromQueryResults()) { - QueryResults(isReQuery); + _ = QueryResultsAsync(isReQuery); } else if (ContextMenuSelected()) { @@ -1042,8 +1036,8 @@ namespace Flow.Launcher.ViewModel var results = new List(); foreach (var h in _history.Items) { - var title = _translator.GetTranslation("executeQuery"); - var time = _translator.GetTranslation("lastExecuteTime"); + var title = App.API.GetTranslation("executeQuery"); + var time = App.API.GetTranslation("lastExecuteTime"); var result = new Result { Title = string.Format(title, h.Query), @@ -1077,7 +1071,7 @@ namespace Flow.Launcher.ViewModel private readonly IReadOnlyList _emptyResult = new List(); - private async void QueryResults(bool isReQuery = false, bool reSelect = true) + private async Task QueryResultsAsync(bool isReQuery = false, bool reSelect = true) { _updateSource?.Cancel(); @@ -1155,7 +1149,7 @@ namespace Flow.Launcher.ViewModel var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch { - false => QueryTask(plugin, reSelect), + false => QueryTaskAsync(plugin, reSelect), true => Task.CompletedTask }).ToArray(); @@ -1183,7 +1177,7 @@ namespace Flow.Launcher.ViewModel } // Local function - async Task QueryTask(PluginPair plugin, bool reSelect = true) + async Task QueryTaskAsync(PluginPair plugin, bool reSelect = true) { // Since it is wrapped within a ThreadPool Thread, the synchronous context is null // Task.Yield will force it to run in ThreadPool @@ -1282,13 +1276,13 @@ namespace Flow.Launcher.ViewModel { menu = new Result { - Title = InternationalizationManager.Instance.GetTranslation("cancelTopMostInThisQuery"), + Title = App.API.GetTranslation("cancelTopMostInThisQuery"), IcoPath = "Images\\down.png", PluginDirectory = Constant.ProgramDirectory, Action = _ => { _topMostRecord.Remove(result); - App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success")); + App.API.ShowMsg(App.API.GetTranslation("success")); App.API.ReQuery(); return false; } @@ -1298,14 +1292,14 @@ namespace Flow.Launcher.ViewModel { menu = new Result { - Title = InternationalizationManager.Instance.GetTranslation("setAsTopMostInThisQuery"), + Title = App.API.GetTranslation("setAsTopMostInThisQuery"), IcoPath = "Images\\up.png", Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xeac2"), PluginDirectory = Constant.ProgramDirectory, Action = _ => { _topMostRecord.AddOrUpdate(result); - App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success")); + App.API.ShowMsg(App.API.GetTranslation("success")); App.API.ReQuery(); return false; } @@ -1315,10 +1309,10 @@ namespace Flow.Launcher.ViewModel return menu; } - private Result ContextMenuPluginInfo(string id) + private static Result ContextMenuPluginInfo(string id) { var metadata = PluginManager.GetPluginForId(id).Metadata; - var translator = InternationalizationManager.Instance; + var translator = App.API; var author = translator.GetTranslation("author"); var website = translator.GetTranslation("website"); @@ -1400,12 +1394,16 @@ namespace Flow.Launcher.ViewModel }); } +#pragma warning disable VSTHRD100 // Avoid async void methods + public async void Hide() { lastHistoryIndex = 1; if (ExternalPreviewVisible) + { CloseExternalPreview(); + } if (!SelectedIsFromQueryResults()) { @@ -1476,6 +1474,8 @@ namespace Flow.Launcher.ViewModel VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = false }); } +#pragma warning restore VSTHRD100 // Avoid async void methods + /// /// Checks if Flow Launcher should ignore any hotkeys /// From 4d080a90c651568851a62613d91f51181f9ed1f1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 17 Mar 2025 10:25:02 +0800 Subject: [PATCH 12/12] Fix build issue --- Flow.Launcher/PublicAPIInstance.cs | 11 +++++++---- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index d9f935833..f367ac921 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -27,19 +27,22 @@ using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.ViewModel; using JetBrains.Annotations; +using Flow.Launcher.Core.Resource; namespace Flow.Launcher { public class PublicAPIInstance : IPublicAPI { private readonly Settings _settings; + private readonly Internationalization _translater; private readonly MainViewModel _mainVM; #region Constructor - public PublicAPIInstance(Settings settings, MainViewModel mainVM) + public PublicAPIInstance(Settings settings, Internationalization translater, MainViewModel mainVM) { _settings = settings; + _translater = translater; _mainVM = mainVM; GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback; WebRequest.RegisterPrefix("data", new DataWebRequestFactory()); @@ -152,17 +155,17 @@ namespace Flow.Launcher public void StopLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Collapsed; - public string GetTranslation(string key) => App.API.GetTranslation(key); + public string GetTranslation(string key) => _translater.GetTranslation(key); public List GetAllPlugins() => PluginManager.AllPlugins.ToList(); public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare); - public Task HttpGetStringAsync(string url, CancellationToken token = default) => Http.GetAsync(url); + public Task HttpGetStringAsync(string url, CancellationToken token = default) => Http.GetAsync(url, token); public Task HttpGetStreamAsync(string url, CancellationToken token = default) => - Http.GetStreamAsync(url); + Http.GetStreamAsync(url, token); public Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action reportProgress = null, CancellationToken token = default) => Http.DownloadAsync(url, filePath, reportProgress, token); diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 59ca356a7..6cd4f7f96 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -208,7 +208,7 @@ namespace Flow.Launcher.ViewModel #else Log.Error($"Error happen in task dealing with viewupdate for results. {t.Exception}"); _resultsViewUpdateTask = - Task.Run(updateAction).ContinueWith(continueAction, TaskContinuationOptions.OnlyOnFaulted); + Task.Run(UpdateActionAsync).ContinueWith(continueAction, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); #endif } }