From fb1cb4fa9a535bb883571407111afcc8ef003a86 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Sun, 13 Jul 2025 19:39:55 +1000 Subject: [PATCH 01/30] version bump --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index b38e5bb1a88..2845d3dc2f1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: '1.20.1.{build}' +version: '1.20.2.{build}' init: - ps: | From b08cb2208a121d756505a5d5970447f2b46c4fd2 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sun, 13 Jul 2025 21:10:35 +1000 Subject: [PATCH 02/30] Merge pull request #3758 from Flow-Launcher/release_workflow_milestone_from_pr release_pr GitHub workflow automatically get milestone from release PR --- .github/update_release_pr.py | 102 +++++++++++++++++------------------ 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/.github/update_release_pr.py b/.github/update_release_pr.py index ccea511b31f..37d4a8683c8 100644 --- a/.github/update_release_pr.py +++ b/.github/update_release_pr.py @@ -1,11 +1,12 @@ from os import getenv +from typing import Optional import requests def get_github_prs(token: str, owner: str, repo: str, label: str = "", state: str = "all") -> list[dict]: """ - Fetches pull requests from a GitHub repository that match a given milestone and label. + Fetches pull requests from a GitHub repository that match a given label and state. Args: token (str): GitHub token. @@ -23,39 +24,10 @@ def get_github_prs(token: str, owner: str, repo: str, label: str = "", state: st "Accept": "application/vnd.github.v3+json", } - milestone_id = None - milestone_url = f"https://api.github.com/repos/{owner}/{repo}/milestones" - params = {"state": "open"} - - try: - response = requests.get(milestone_url, headers=headers, params=params) - response.raise_for_status() - milestones = response.json() - - if len(milestones) > 2: - print("More than two milestones found, unable to determine the milestone required.") - exit(1) - - # milestones.pop() - for ms in milestones: - if ms["title"] != "Future": - milestone_id = ms["number"] - print(f"Gathering PRs with milestone {ms['title']}...") - break - - if not milestone_id: - print(f"No suitable milestone found in repository '{owner}/{repo}'.") - exit(1) - - except requests.exceptions.RequestException as e: - print(f"Error fetching milestones: {e}") - exit(1) - - # This endpoint allows filtering by milestone and label. A PR in GH's perspective is a type of issue. + # This endpoint allows filtering by label(and milestone). A PR in GH's perspective is a type of issue. prs_url = f"https://api.github.com/repos/{owner}/{repo}/issues" params = { "state": state, - "milestone": milestone_id, "labels": label, "per_page": 100, } @@ -83,7 +55,9 @@ def get_github_prs(token: str, owner: str, repo: str, label: str = "", state: st return all_prs -def get_prs(pull_request_items: list[dict], label: str = "", state: str = "all") -> list[dict]: +def get_prs( + pull_request_items: list[dict], label: str = "", state: str = "all", milestone_title: Optional[str] = None +) -> list[dict]: """ Returns a list of pull requests after applying the label and state filters. @@ -91,6 +65,8 @@ def get_prs(pull_request_items: list[dict], label: str = "", state: str = "all") pull_request_items (list[dict]): List of PR items. label (str): The label name. Filter is not applied when empty string. state (str): State of PR, e.g. open, closed, all + milestone_title (Optional[str]): The milestone title to filter by. This is the milestone number you created + in GitHub, e.g. '1.20.0'. If None, no milestone filtering is applied. Returns: list: A list of dictionaries, where each dictionary represents a pull request. @@ -99,22 +75,32 @@ def get_prs(pull_request_items: list[dict], label: str = "", state: str = "all") pr_list = [] count = 0 for pr in pull_request_items: - if state in [pr["state"], "all"] and (not label or [item for item in pr["labels"] if item["name"] == label]): - pr_list.append(pr) - count += 1 + if state not in [pr["state"], "all"]: + continue + + if label and not [item for item in pr["labels"] if item["name"] == label]: + continue - print(f"Found {count} PRs with {label if label else 'no filter on'} label and state as {state}") + if milestone_title: + if pr["milestone"] is None or pr["milestone"]["title"] != milestone_title: + continue + + pr_list.append(pr) + count += 1 + + print( + f"Found {count} PRs with {label if label else 'no filter on'} label, state as {state}, and milestone {pr["milestone"] if pr["milestone"] is not None else "None"}" + ) return pr_list -def get_prs_assignees(pull_request_items: list[dict], label: str = "", state: str = "all") -> list[str]: + +def get_prs_assignees(pull_request_items: list[dict]) -> list[str]: """ - Returns a list of pull request assignees after applying the label and state filters, excludes jjw24. + Returns a list of pull request assignees, excludes jjw24. Args: - pull_request_items (list[dict]): List of PR items. - label (str): The label name. Filter is not applied when empty string. - state (str): State of PR, e.g. open, closed, all + pull_request_items (list[dict]): List of PR items to get the assignees from. Returns: list: A list of strs, where each string is an assignee name. List is not distinct, so can contain @@ -123,13 +109,13 @@ def get_prs_assignees(pull_request_items: list[dict], label: str = "", state: st """ assignee_list = [] for pr in pull_request_items: - if state in [pr["state"], "all"] and (not label or [item for item in pr["labels"] if item["name"] == label]): - [assignee_list.append(assignee["login"]) for assignee in pr["assignees"] if assignee["login"] != "jjw24" ] + [assignee_list.append(assignee["login"]) for assignee in pr["assignees"] if assignee["login"] != "jjw24"] - print(f"Found {len(assignee_list)} assignees with {label if label else 'no filter on'} label and state as {state}") + print(f"Found {len(assignee_list)} assignees") return assignee_list + def get_pr_descriptions(pull_request_items: list[dict]) -> str: """ Returns the concatenated string of pr title and number in the format of @@ -207,15 +193,16 @@ def update_pull_request_description(token: str, owner: str, repo: str, pr_number print(f"Fetching {state} PRs for {repository_owner}/{repository_name} ...") - pull_requests = get_github_prs(github_token, repository_owner, repository_name) + # First, get all PRs to find the release PR and determine the milestone + all_pull_requests = get_github_prs(github_token, repository_owner, repository_name) - if not pull_requests: - print("No matching pull requests found") + if not all_pull_requests: + print("No pull requests found") exit(1) - print(f"\nFound total of {len(pull_requests)} pull requests") + print(f"\nFound total of {len(all_pull_requests)} pull requests") - release_pr = get_prs(pull_requests, "release", "open") + release_pr = get_prs(all_pull_requests, "release", "open") if len(release_pr) != 1: print(f"Unable to find the exact release PR. Returned result: {release_pr}") @@ -223,14 +210,25 @@ def update_pull_request_description(token: str, owner: str, repo: str, pr_number print(f"Found release PR: {release_pr[0]['title']}") - enhancement_prs = get_prs(pull_requests, "enhancement", "closed") - bug_fix_prs = get_prs(pull_requests, "bug", "closed") + release_milestone_title = release_pr[0].get("milestone", {}).get("title", None) + + if not release_milestone_title: + print("Release PR does not have a milestone assigned.") + exit(1) + + print(f"Using milestone number: {release_milestone_title}") + + enhancement_prs = get_prs(all_pull_requests, "enhancement", "closed", release_milestone_title) + bug_fix_prs = get_prs(all_pull_requests, "bug", "closed", release_milestone_title) + + if len(enhancement_prs) == 0 and len(bug_fix_prs) == 0: + print(f"No PRs with {release_milestone_title} milestone were found") description_content = "# Release notes\n" description_content += f"## Features\n{get_pr_descriptions(enhancement_prs)}" if enhancement_prs else "" description_content += f"## Bug fixes\n{get_pr_descriptions(bug_fix_prs)}" if bug_fix_prs else "" - assignees = list(set(get_prs_assignees(pull_requests, "enhancement", "closed") + get_prs_assignees(pull_requests, "bug", "closed"))) + assignees = list(set(get_prs_assignees(enhancement_prs) + get_prs_assignees(bug_fix_prs))) assignees.sort(key=str.lower) description_content += f"### Authors:\n{', '.join(assignees)}" From d409e3d1afef1eded25b32b3b47d65d5938bfb8d Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Sat, 14 Jun 2025 23:26:44 +1000 Subject: [PATCH 03/30] Merge pull request #3729 from Flow-Launcher/skip_tag_build Add AppVeyor skip build on tag --- appveyor.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 2845d3dc2f1..f4ad62a4c43 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,8 @@ version: '1.20.2.{build}' +# Do not build on tags because we create a release on merge to master. Otherwise will upload artifacts twice changing the hash, as well as triggering duplicate GitHub release action & NuGet deployments. +skip_tags: true + init: - ps: | $version = new-object System.Version $env:APPVEYOR_BUILD_VERSION @@ -14,7 +17,6 @@ init: cache: - '%USERPROFILE%\.nuget\packages -> **.sln, **.csproj' # preserve nuget folder (packages) unless the solution or projects change - assembly_info: patch: true file: SolutionAssemblyInfo.cs From cbbd35769bf860042a840c52de20012b1fe22530 Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Mon, 16 Jun 2025 20:48:03 +0800 Subject: [PATCH 04/30] Merge pull request #3734 from Flow-Launcher/shell_plugin_issue Fix one more whitespace after commands --- Plugins/Flow.Launcher.Plugin.Shell/Main.cs | 36 +++++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs index d0add9f3155..a51aadec7df 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs @@ -194,10 +194,13 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin var workingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); var runAsAdministratorArg = !runAsAdministrator && !_settings.RunAsAdministrator ? "" : "runas"; - ProcessStartInfo info = new() + var info = new ProcessStartInfo() { - Verb = runAsAdministratorArg, WorkingDirectory = workingDirectory, + Verb = runAsAdministratorArg, + WorkingDirectory = workingDirectory, }; + var notifyStr = Context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close"); + var addedCharacter = _settings.UseWindowsTerminal ? "\\" : ""; switch (_settings.Shell) { case Shell.Cmd: @@ -211,8 +214,19 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin { info.FileName = "cmd.exe"; } - - info.ArgumentList.Add($"{(_settings.LeaveShellOpen ? "/k" : "/c")} {command} {(_settings.CloseShellAfterPress ? $"&& echo {Context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")} && pause > nul /c" : "")}"); + if (_settings.LeaveShellOpen) + { + info.ArgumentList.Add("/k"); + } + else + { + info.ArgumentList.Add("/c"); + } + info.ArgumentList.Add( + $"{command}" + + $"{(_settings.CloseShellAfterPress ? + $" && echo {notifyStr} && pause > nul /c" : + "")}"); break; } @@ -220,7 +234,6 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin { // Using just a ; doesn't work with wt, as it's used to create a new tab for the terminal window // \\ must be escaped for it to work properly, or breaking it into multiple arguments - var addedCharacter = _settings.UseWindowsTerminal ? "\\" : ""; if (_settings.UseWindowsTerminal) { info.FileName = "wt.exe"; @@ -238,7 +251,11 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin else { info.ArgumentList.Add("-Command"); - info.ArgumentList.Add($"{command}{addedCharacter}; {(_settings.CloseShellAfterPress ? $"Write-Host '{Context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'{addedCharacter}; [System.Console]::ReadKey(){addedCharacter}; exit" : "")}"); + info.ArgumentList.Add( + $"{command}{addedCharacter};" + + $"{(_settings.CloseShellAfterPress ? + $" Write-Host '{notifyStr}'{addedCharacter}; [System.Console]::ReadKey(){addedCharacter}; exit" : + "")}"); } break; } @@ -247,7 +264,6 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin { // Using just a ; doesn't work with wt, as it's used to create a new tab for the terminal window // \\ must be escaped for it to work properly, or breaking it into multiple arguments - var addedCharacter = _settings.UseWindowsTerminal ? "\\" : ""; if (_settings.UseWindowsTerminal) { info.FileName = "wt.exe"; @@ -262,7 +278,11 @@ private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdmin info.ArgumentList.Add("-NoExit"); } info.ArgumentList.Add("-Command"); - info.ArgumentList.Add($"{command}{addedCharacter}; {(_settings.CloseShellAfterPress ? $"Write-Host '{Context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'{addedCharacter}; [System.Console]::ReadKey(){addedCharacter}; exit" : "")}"); + info.ArgumentList.Add( + $"{command}{addedCharacter};" + + $"{(_settings.CloseShellAfterPress ? + $" Write-Host '{notifyStr}'{addedCharacter}; [System.Console]::ReadKey(){addedCharacter}; exit" : + "")}"); break; } From a4acf07c3d5cfd481041f36a099c97ff34220979 Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Mon, 16 Jun 2025 23:52:42 +1000 Subject: [PATCH 05/30] Merge pull request #3566 from Flow-Launcher/winform_language_issue Fix plugin culture info issue for those that use WinForm --- .../Resource/Internationalization.cs | 42 +++++++++---- .../UserSettings/Settings.cs | 13 +++- Flow.Launcher/App.xaml.cs | 61 ++++++++++++------- .../Helper/WindowsMediaPlayerHelper.cs | 18 +++++- 4 files changed, 94 insertions(+), 40 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index b32b09e8fc8..24edc5ed8fe 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -1,16 +1,17 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; +using System.Threading; +using System.Threading.Tasks; using System.Windows; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using System.Globalization; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.DependencyInjection; namespace Flow.Launcher.Core.Resource { @@ -29,13 +30,12 @@ public class Internationalization private readonly Settings _settings; private readonly List _languageDirectories = new(); private readonly List _oldResources = new(); - private readonly string SystemLanguageCode; + private static string SystemLanguageCode; public Internationalization(Settings settings) { _settings = settings; AddFlowLauncherLanguageDirectory(); - SystemLanguageCode = GetSystemLanguageCodeAtStartup(); } private void AddFlowLauncherLanguageDirectory() @@ -44,7 +44,7 @@ private void AddFlowLauncherLanguageDirectory() _languageDirectories.Add(directory); } - private static string GetSystemLanguageCodeAtStartup() + public static void InitSystemLanguageCode() { var availableLanguages = AvailableLanguages.GetAvailableLanguages(); @@ -65,11 +65,11 @@ private static string GetSystemLanguageCodeAtStartup() string.Equals(languageCode, threeLetterCode, StringComparison.OrdinalIgnoreCase) || string.Equals(languageCode, fullName, StringComparison.OrdinalIgnoreCase)) { - return languageCode; + SystemLanguageCode = languageCode; } } - return DefaultLanguageCode; + SystemLanguageCode = DefaultLanguageCode; } private void AddPluginLanguageDirectories() @@ -173,15 +173,33 @@ private async Task ChangeLanguageAsync(Language language) LoadLanguage(language); } - // Culture of main thread - // Use CreateSpecificCulture to preserve possible user-override settings in Windows, if Flow's language culture is the same as Windows's - CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture(language.LanguageCode); - CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture; + // Change culture info + ChangeCultureInfo(language.LanguageCode); // Raise event for plugins after culture is set await Task.Run(UpdatePluginMetadataTranslations); } + public static void ChangeCultureInfo(string languageCode) + { + // Culture of main thread + // Use CreateSpecificCulture to preserve possible user-override settings in Windows, if Flow's language culture is the same as Windows's + CultureInfo currentCulture; + try + { + currentCulture = CultureInfo.CreateSpecificCulture(languageCode); + } + catch (CultureNotFoundException) + { + currentCulture = CultureInfo.CreateSpecificCulture(SystemLanguageCode); + } + CultureInfo.CurrentCulture = currentCulture; + CultureInfo.CurrentUICulture = currentCulture; + var thread = Thread.CurrentThread; + thread.CurrentCulture = currentCulture; + thread.CurrentUICulture = currentCulture; + } + public bool PromptShouldUsePinyin(string languageCodeToSet) { var languageToSet = GetLanguageByLanguageCode(languageCodeToSet); diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 892045994bd..47dc6c3c0ed 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -25,7 +25,13 @@ public void SetStorage(FlowLauncherJsonStorage storage) public void Initialize() { + // Initialize dependency injection instances after Ioc.Default is created _stringMatcher = Ioc.Default.GetRequiredService(); + + // Initialize application resources after application is created + var settingWindowFont = new FontFamily(SettingWindowFont); + Application.Current.Resources["SettingWindowFont"] = settingWindowFont; + Application.Current.Resources["ContentControlThemeFontFamily"] = settingWindowFont; } public void Save() @@ -115,8 +121,11 @@ public string SettingWindowFont { _settingWindowFont = value; OnPropertyChanged(); - Application.Current.Resources["SettingWindowFont"] = new FontFamily(value); - Application.Current.Resources["ContentControlThemeFontFamily"] = new FontFamily(value); + if (Application.Current != null) + { + Application.Current.Resources["SettingWindowFont"] = new FontFamily(value); + Application.Current.Resources["ContentControlThemeFontFamily"] = new FontFamily(value); + } } } } diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index cedced181f2..9dad0a7eddb 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -41,9 +41,9 @@ public partial class App : IDisposable, ISingleInstanceApp private static readonly string ClassName = nameof(App); private static bool _disposed; + private static Settings _settings; private static MainWindow _mainWindow; private readonly MainViewModel _mainVM; - private readonly Settings _settings; // To prevent two disposals running at the same time. private static readonly object _disposingLock = new(); @@ -55,18 +55,7 @@ public partial class App : IDisposable, ISingleInstanceApp public App() { // Initialize settings - try - { - var storage = new FlowLauncherJsonStorage(); - _settings = storage.Load(); - _settings.SetStorage(storage); - _settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled(); - } - catch (Exception e) - { - ShowErrorMsgBoxAndFailFast("Cannot load setting storage, please check local data directory", e); - return; - } + _settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled(); // Configure the dependency injection container try @@ -123,16 +112,6 @@ public App() ShowErrorMsgBoxAndFailFast("Cannot initialize api and settings, please open new issue in Flow.Launcher", e); return; } - - // Local function - static void ShowErrorMsgBoxAndFailFast(string message, Exception e) - { - // Firstly show users the message - MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error); - - // Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info. - Environment.FailFast(message, e); - } } #endregion @@ -142,6 +121,29 @@ static void ShowErrorMsgBoxAndFailFast(string message, Exception e) [STAThread] public static void Main() { + // Initialize settings so that we can get language code + try + { + var storage = new FlowLauncherJsonStorage(); + _settings = storage.Load(); + _settings.SetStorage(storage); + } + catch (Exception e) + { + ShowErrorMsgBoxAndFailFast("Cannot load setting storage, please check local data directory", e); + return; + } + + // Initialize system language before changing culture info + Internationalization.InitSystemLanguageCode(); + + // Change culture info before application creation to localize WinForm windows + if (_settings.Language != Constant.SystemLanguageCode) + { + Internationalization.ChangeCultureInfo(_settings.Language); + } + + // Start the application as a single instance if (SingleInstance.InitializeAsFirstInstance()) { using var application = new App(); @@ -152,6 +154,19 @@ public static void Main() #endregion + #region Fail Fast + + private static void ShowErrorMsgBoxAndFailFast(string message, Exception e) + { + // Firstly show users the message + MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error); + + // Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info. + Environment.FailFast(message, e); + } + + #endregion + #region App Events #pragma warning disable VSTHRD100 // Avoid async void methods diff --git a/Flow.Launcher/Helper/WindowsMediaPlayerHelper.cs b/Flow.Launcher/Helper/WindowsMediaPlayerHelper.cs index 9d2046972eb..b05409de20c 100644 --- a/Flow.Launcher/Helper/WindowsMediaPlayerHelper.cs +++ b/Flow.Launcher/Helper/WindowsMediaPlayerHelper.cs @@ -1,11 +1,23 @@ -using Microsoft.Win32; +using System; +using Microsoft.Win32; namespace Flow.Launcher.Helper; + internal static class WindowsMediaPlayerHelper { + private static readonly string ClassName = nameof(WindowsMediaPlayerHelper); + internal static bool IsWindowsMediaPlayerInstalled() { - using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\MediaPlayer"); - return key?.GetValue("Installation Directory") != null; + try + { + using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\MediaPlayer"); + return key?.GetValue("Installation Directory") != null; + } + catch (Exception e) + { + App.API.LogException(ClassName, "Failed to check if Windows Media Player is installed", e); + return false; + } } } From bcb8c39488b7bc9ac8ec1c312937b3c7a92ff45e Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Tue, 17 Jun 2025 13:44:16 +0800 Subject: [PATCH 06/30] Merge pull request #3737 from Flow-Launcher/tab_sequence Fix tab sequence --- Flow.Launcher/CustomShortcutSetting.xaml | 26 +++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher/CustomShortcutSetting.xaml b/Flow.Launcher/CustomShortcutSetting.xaml index d8623753f63..5185354e772 100644 --- a/Flow.Launcher/CustomShortcutSetting.xaml +++ b/Flow.Launcher/CustomShortcutSetting.xaml @@ -118,24 +118,26 @@ FontSize="14" Text="{DynamicResource customShortcutExpansion}" /> - -