diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml index 294c06fc1..11a921955 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yaml +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -16,6 +16,8 @@ body: I have checked that this issue has not already been reported. - label: > I am using the latest version of Flow Launcher. + - label: > + I am using the prerelease version of Flow Launcher. - type: textarea attributes: diff --git a/.github/update_release_pr.py b/.github/update_release_pr.py new file mode 100644 index 000000000..ccea511b3 --- /dev/null +++ b/.github/update_release_pr.py @@ -0,0 +1,242 @@ +from os import getenv + +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. + + Args: + token (str): GitHub token. + owner (str): The owner of the repository. + repo (str): The name of the repository. + label (str): The label name. Filter is not applied when empty string. + state (str): State of PR, e.g. open, closed, all + + Returns: + list: A list of dictionaries, where each dictionary represents a pull request. + Returns an empty list if no PRs are found or an error occurs. + """ + headers = { + "Authorization": f"token {token}", + "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. + prs_url = f"https://api.github.com/repos/{owner}/{repo}/issues" + params = { + "state": state, + "milestone": milestone_id, + "labels": label, + "per_page": 100, + } + + all_prs = [] + page = 1 + while True: + try: + params["page"] = page + response = requests.get(prs_url, headers=headers, params=params) + response.raise_for_status() # Raise an exception for HTTP errors + prs = response.json() + + if not prs: + break # No more PRs to fetch + + # Check for pr key since we are using issues endpoint instead. + all_prs.extend([item for item in prs if "pull_request" in item]) + page += 1 + + except requests.exceptions.RequestException as e: + print(f"Error fetching pull requests: {e}") + exit(1) + + return all_prs + + +def get_prs(pull_request_items: list[dict], label: str = "", state: str = "all") -> list[dict]: + """ + Returns a list of pull requests after applying the label and state filters. + + 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 + + Returns: + list: A list of dictionaries, where each dictionary represents a pull request. + Returns an empty list if no PRs are found. + """ + 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 + + print(f"Found {count} PRs with {label if label else 'no filter on'} label and state as {state}") + + return pr_list + +def get_prs_assignees(pull_request_items: list[dict], label: str = "", state: str = "all") -> list[str]: + """ + Returns a list of pull request assignees after applying the label and state filters, 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 + + Returns: + list: A list of strs, where each string is an assignee name. List is not distinct, so can contain + duplicate names. + Returns an empty list if none are found. + """ + 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" ] + + print(f"Found {len(assignee_list)} assignees with {label if label else 'no filter on'} label and state as {state}") + + 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 + '- PR title 1 #3651 + - PR title 2 #3652 + - PR title 3 #3653 + ' + + Args: + pull_request_items (list[dict]): List of PR items. + + Returns: + str: a string of PR titles and numbers + """ + description_content = "" + for pr in pull_request_items: + description_content += f"- {pr['title']} #{pr['number']}\n" + + return description_content + + +def update_pull_request_description(token: str, owner: str, repo: str, pr_number: int, new_description: str) -> None: + """ + Updates the description (body) of a GitHub Pull Request. + + Args: + token (str): Token. + owner (str): The owner of the repository. + repo (str): The name of the repository. + pr_number (int): The number of the pull request to update. + new_description (str): The new content for the PR's description. + + Returns: + dict or None: The updated PR object (as a dictionary) if successful, + None otherwise. + """ + headers = { + "Authorization": f"token {token}", + "Accept": "application/vnd.github.v3+json", + "Content-Type": "application/json", + } + + url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}" + + payload = {"body": new_description} + + print(f"Attempting to update PR #{pr_number} in {owner}/{repo}...") + print(f"URL: {url}") + + try: + response = None + response = requests.patch(url, headers=headers, json=payload) + response.raise_for_status() + + print(f"Successfully updated PR #{pr_number}.") + + except requests.exceptions.RequestException as e: + print(f"Error updating pull request #{pr_number}: {e}") + if response is not None: + print(f"Response status code: {response.status_code}") + print(f"Response text: {response.text}") + exit(1) + + +if __name__ == "__main__": + github_token = getenv("GITHUB_TOKEN") + + if not github_token: + print("Error: GITHUB_TOKEN environment variable not set.") + exit(1) + + repository_owner = "flow-launcher" + repository_name = "flow.launcher" + state = "all" + + print(f"Fetching {state} PRs for {repository_owner}/{repository_name} ...") + + pull_requests = get_github_prs(github_token, repository_owner, repository_name) + + if not pull_requests: + print("No matching pull requests found") + exit(1) + + print(f"\nFound total of {len(pull_requests)} pull requests") + + release_pr = get_prs(pull_requests, "release", "open") + + if len(release_pr) != 1: + print(f"Unable to find the exact release PR. Returned result: {release_pr}") + exit(1) + + 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") + + 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.sort(key=str.lower) + + description_content += f"### Authors:\n{', '.join(assignees)}" + + update_pull_request_description( + github_token, repository_owner, repository_name, release_pr[0]["number"], description_content + ) + + print(f"PR content updated to:\n{description_content}") diff --git a/.github/workflows/default_plugins.yml b/.github/workflows/default_plugins.yml index 85acafae1..ec8dfcd4e 100644 --- a/.github/workflows/default_plugins.yml +++ b/.github/workflows/default_plugins.yml @@ -3,11 +3,10 @@ name: Publish Default Plugins on: push: branches: ['master'] - paths: ['Plugins/**'] workflow_dispatch: jobs: - build: + publish: runs-on: windows-latest steps: @@ -17,39 +16,24 @@ jobs: with: dotnet-version: 7.0.x - - name: Determine New Plugin Updates - uses: dorny/paths-filter@v3 - id: changes - with: - filters: | - browserbookmark: - - 'Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json' - calculator: - - 'Plugins/Flow.Launcher.Plugin.Calculator/plugin.json' - explorer: - - 'Plugins/Flow.Launcher.Plugin.Explorer/plugin.json' - pluginindicator: - - 'Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json' - pluginsmanager: - - 'Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json' - processkiller: - - 'Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json' - program: - - 'Plugins/Flow.Launcher.Plugin.Program/plugin.json' - shell: - - 'Plugins/Flow.Launcher.Plugin.Shell/plugin.json' - sys: - - 'Plugins/Flow.Launcher.Plugin.Sys/plugin.json' - url: - - 'Plugins/Flow.Launcher.Plugin.Url/plugin.json' - websearch: - - 'Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json' - windowssettings: - - 'Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json' - base: 'master' + - name: Update Plugins To Production Version + run: | + $version = "1.0.0" + Get-Content appveyor.yml | ForEach-Object { + if ($_ -match "version:\s*'(\d+\.\d+\.\d+)\.") { + $version = $matches[1] + } + } + + $jsonFiles = Get-ChildItem -Path ".\Plugins\*\plugin.json" + foreach ($file in $jsonFiles) { + $plugin_old_ver = Get-Content $file.FullName -Raw | ConvertFrom-Json + (Get-Content $file) -replace '"Version"\s*:\s*".*?"', "`"Version`": `"$version`"" | Set-Content $file + $plugin_new_ver = Get-Content $file.FullName -Raw | ConvertFrom-Json + Write-Host "Updated" $plugin_old_ver.Name "version from" $plugin_old_ver.Version "to" $plugin_new_ver.Version + } - name: Get BrowserBookmark Version - if: steps.changes.outputs.browserbookmark == 'true' id: updated-version-browserbookmark uses: notiz-dev/github-action-json-property@release with: @@ -57,14 +41,12 @@ jobs: prop_path: 'Version' - name: Build BrowserBookmark - if: steps.changes.outputs.browserbookmark == 'true' run: | dotnet publish 'Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.BrowserBookmark" 7z a -tzip "Flow.Launcher.Plugin.BrowserBookmark.zip" "./Flow.Launcher.Plugin.BrowserBookmark/*" rm -r "Flow.Launcher.Plugin.BrowserBookmark" - name: Publish BrowserBookmark - if: steps.changes.outputs.browserbookmark == 'true' uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.BrowserBookmark" @@ -76,7 +58,6 @@ jobs: - name: Get Calculator Version - if: steps.changes.outputs.calculator == 'true' id: updated-version-calculator uses: notiz-dev/github-action-json-property@release with: @@ -84,14 +65,12 @@ jobs: prop_path: 'Version' - name: Build Calculator - if: steps.changes.outputs.calculator == 'true' run: | dotnet publish 'Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.Calculator" 7z a -tzip "Flow.Launcher.Plugin.Calculator.zip" "./Flow.Launcher.Plugin.Calculator/*" rm -r "Flow.Launcher.Plugin.Calculator" - name: Publish Calculator - if: steps.changes.outputs.calculator == 'true' uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.Calculator" @@ -103,7 +82,6 @@ jobs: - name: Get Explorer Version - if: steps.changes.outputs.explorer == 'true' id: updated-version-explorer uses: notiz-dev/github-action-json-property@release with: @@ -111,14 +89,12 @@ jobs: prop_path: 'Version' - name: Build Explorer - if: steps.changes.outputs.explorer == 'true' run: | dotnet publish 'Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.Explorer" 7z a -tzip "Flow.Launcher.Plugin.Explorer.zip" "./Flow.Launcher.Plugin.Explorer/*" rm -r "Flow.Launcher.Plugin.Explorer" - name: Publish Explorer - if: steps.changes.outputs.explorer == 'true' uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.Explorer" @@ -130,7 +106,6 @@ jobs: - name: Get PluginIndicator Version - if: steps.changes.outputs.pluginindicator == 'true' id: updated-version-pluginindicator uses: notiz-dev/github-action-json-property@release with: @@ -138,14 +113,12 @@ jobs: prop_path: 'Version' - name: Build PluginIndicator - if: steps.changes.outputs.pluginindicator == 'true' run: | dotnet publish 'Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.PluginIndicator" 7z a -tzip "Flow.Launcher.Plugin.PluginIndicator.zip" "./Flow.Launcher.Plugin.PluginIndicator/*" rm -r "Flow.Launcher.Plugin.PluginIndicator" - name: Publish PluginIndicator - if: steps.changes.outputs.pluginindicator == 'true' uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.PluginIndicator" @@ -157,7 +130,6 @@ jobs: - name: Get PluginsManager Version - if: steps.changes.outputs.pluginsmanager == 'true' id: updated-version-pluginsmanager uses: notiz-dev/github-action-json-property@release with: @@ -165,14 +137,12 @@ jobs: prop_path: 'Version' - name: Build PluginsManager - if: steps.changes.outputs.pluginsmanager == 'true' run: | dotnet publish 'Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.PluginsManager" 7z a -tzip "Flow.Launcher.Plugin.PluginsManager.zip" "./Flow.Launcher.Plugin.PluginsManager/*" rm -r "Flow.Launcher.Plugin.PluginsManager" - name: Publish PluginsManager - if: steps.changes.outputs.pluginsmanager == 'true' uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.PluginsManager" @@ -184,7 +154,6 @@ jobs: - name: Get ProcessKiller Version - if: steps.changes.outputs.processkiller == 'true' id: updated-version-processkiller uses: notiz-dev/github-action-json-property@release with: @@ -192,14 +161,12 @@ jobs: prop_path: 'Version' - name: Build ProcessKiller - if: steps.changes.outputs.processkiller == 'true' run: | dotnet publish 'Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.ProcessKiller" 7z a -tzip "Flow.Launcher.Plugin.ProcessKiller.zip" "./Flow.Launcher.Plugin.ProcessKiller/*" rm -r "Flow.Launcher.Plugin.ProcessKiller" - name: Publish ProcessKiller - if: steps.changes.outputs.processkiller == 'true' uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller" @@ -211,7 +178,6 @@ jobs: - name: Get Program Version - if: steps.changes.outputs.program == 'true' id: updated-version-program uses: notiz-dev/github-action-json-property@release with: @@ -219,14 +185,12 @@ jobs: prop_path: 'Version' - name: Build Program - if: steps.changes.outputs.program == 'true' run: | dotnet publish 'Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj' --framework net7.0-windows10.0.19041.0 -c Release -o "Flow.Launcher.Plugin.Program" 7z a -tzip "Flow.Launcher.Plugin.Program.zip" "./Flow.Launcher.Plugin.Program/*" rm -r "Flow.Launcher.Plugin.Program" - name: Publish Program - if: steps.changes.outputs.program == 'true' uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.Program" @@ -238,7 +202,6 @@ jobs: - name: Get Shell Version - if: steps.changes.outputs.shell == 'true' id: updated-version-shell uses: notiz-dev/github-action-json-property@release with: @@ -246,14 +209,12 @@ jobs: prop_path: 'Version' - name: Build Shell - if: steps.changes.outputs.shell == 'true' run: | dotnet publish 'Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.Shell" 7z a -tzip "Flow.Launcher.Plugin.Shell.zip" "./Flow.Launcher.Plugin.Shell/*" rm -r "Flow.Launcher.Plugin.Shell" - name: Publish Shell - if: steps.changes.outputs.shell == 'true' uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.Shell" @@ -265,7 +226,6 @@ jobs: - name: Get Sys Version - if: steps.changes.outputs.sys == 'true' id: updated-version-sys uses: notiz-dev/github-action-json-property@release with: @@ -273,14 +233,12 @@ jobs: prop_path: 'Version' - name: Build Sys - if: steps.changes.outputs.sys == 'true' run: | dotnet publish 'Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.Sys" 7z a -tzip "Flow.Launcher.Plugin.Sys.zip" "./Flow.Launcher.Plugin.Sys/*" rm -r "Flow.Launcher.Plugin.Sys" - name: Publish Sys - if: steps.changes.outputs.sys == 'true' uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.Sys" @@ -292,7 +250,6 @@ jobs: - name: Get Url Version - if: steps.changes.outputs.url == 'true' id: updated-version-url uses: notiz-dev/github-action-json-property@release with: @@ -300,14 +257,12 @@ jobs: prop_path: 'Version' - name: Build Url - if: steps.changes.outputs.url == 'true' run: | dotnet publish 'Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.Url" 7z a -tzip "Flow.Launcher.Plugin.Url.zip" "./Flow.Launcher.Plugin.Url/*" rm -r "Flow.Launcher.Plugin.Url" - name: Publish Url - if: steps.changes.outputs.url == 'true' uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.Url" @@ -319,7 +274,6 @@ jobs: - name: Get WebSearch Version - if: steps.changes.outputs.websearch == 'true' id: updated-version-websearch uses: notiz-dev/github-action-json-property@release with: @@ -327,14 +281,12 @@ jobs: prop_path: 'Version' - name: Build WebSearch - if: steps.changes.outputs.websearch == 'true' run: | dotnet publish 'Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.WebSearch" 7z a -tzip "Flow.Launcher.Plugin.WebSearch.zip" "./Flow.Launcher.Plugin.WebSearch/*" rm -r "Flow.Launcher.Plugin.WebSearch" - name: Publish WebSearch - if: steps.changes.outputs.websearch == 'true' uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.WebSearch" @@ -346,7 +298,6 @@ jobs: - name: Get WindowsSettings Version - if: steps.changes.outputs.windowssettings == 'true' id: updated-version-windowssettings uses: notiz-dev/github-action-json-property@release with: @@ -354,14 +305,12 @@ jobs: prop_path: 'Version' - name: Build WindowsSettings - if: steps.changes.outputs.windowssettings == 'true' run: | dotnet publish 'Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj' --framework net7.0-windows -c Release -o "Flow.Launcher.Plugin.WindowsSettings" 7z a -tzip "Flow.Launcher.Plugin.WindowsSettings.zip" "./Flow.Launcher.Plugin.WindowsSettings/*" rm -r "Flow.Launcher.Plugin.WindowsSettings" - name: Publish WindowsSettings - if: steps.changes.outputs.windowssettings == 'true' uses: softprops/action-gh-release@v2 with: repository: "Flow-Launcher/Flow.Launcher.Plugin.WindowsSettings" diff --git a/.github/workflows/release_pr.yml b/.github/workflows/release_pr.yml new file mode 100644 index 000000000..451bf386c --- /dev/null +++ b/.github/workflows/release_pr.yml @@ -0,0 +1,25 @@ +name: Update release PR + +on: + pull_request: + types: [opened, reopened, synchronize] + branches: + - master + workflow_dispatch: + +jobs: + update-pr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Run release PR update + env: + GITHUB_TOKEN: ${{ secrets.PR_TOKEN }} + run: | + pip install requests -q + python3 ./.github/update_release_pr.py diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index aae8dd764..9b525f331 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -187,11 +187,21 @@ namespace Flow.Launcher.Core.Plugin { if (AllowedLanguage.IsDotNet(metadata.Language)) { + if (string.IsNullOrEmpty(metadata.AssemblyName)) + { + API.LogWarn(ClassName, $"AssemblyName is empty for plugin with metadata: {metadata.Name}"); + continue; // Skip if AssemblyName is not set, which can happen for erroneous plugins + } metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.AssemblyName); metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.AssemblyName); } else { + if (string.IsNullOrEmpty(metadata.Name)) + { + API.LogWarn(ClassName, $"Name is empty for plugin with metadata: {metadata.Name}"); + continue; // Skip if Name is not set, which can happen for erroneous plugins + } metadata.PluginSettingsDirectoryPath = Path.Combine(DataLocation.PluginSettingsDirectory, metadata.Name); metadata.PluginCacheDirectoryPath = Path.Combine(DataLocation.PluginCacheDirectory, metadata.Name); } diff --git a/Flow.Launcher.Core/Plugin/QueryBuilder.cs b/Flow.Launcher.Core/Plugin/QueryBuilder.cs index fae821736..25a32a728 100644 --- a/Flow.Launcher.Core/Plugin/QueryBuilder.cs +++ b/Flow.Launcher.Core/Plugin/QueryBuilder.cs @@ -16,7 +16,8 @@ namespace Flow.Launcher.Core.Plugin Search = string.Empty, RawQuery = string.Empty, SearchTerms = Array.Empty(), - ActionKeyword = string.Empty + ActionKeyword = string.Empty, + IsHomeQuery = true }; } @@ -53,7 +54,8 @@ namespace Flow.Launcher.Core.Plugin Search = search, RawQuery = rawQuery, SearchTerms = searchTerms, - ActionKeyword = actionKeyword + ActionKeyword = actionKeyword, + IsHomeQuery = false }; } } diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 059359694..a6e8dc6bf 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -671,7 +671,15 @@ namespace Flow.Launcher.Core.Resource windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType().FirstOrDefault(x => x.Property.Name == "Background")); windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Colors.Transparent))); } - + + // For themes with blur enabled, the window border is rendered by the system, so it's treated as a simple rectangle regardless of thickness. + //(This is to avoid issues when the window is forcibly changed to a rectangular shape during snap scenarios.) + var cornerRadiusSetter = windowBorderStyle.Setters.OfType().FirstOrDefault(x => x.Property == Border.CornerRadiusProperty); + if (cornerRadiusSetter != null) + cornerRadiusSetter.Value = new CornerRadius(0); + else + windowBorderStyle.Setters.Add(new Setter(Border.CornerRadiusProperty, new CornerRadius(0))); + // Apply the blur effect Win32Helper.DWMSetBackdropForWindow(mainWindow, backdropType); ColorizeWindow(theme, backdropType); diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index 12edf34a4..a29f8accf 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -182,7 +182,6 @@ namespace Flow.Launcher.Infrastructure.Http public static Task GetStreamAsync([NotNull] string url, CancellationToken token = default) => GetStreamAsync(new Uri(url), token); - /// /// Send a GET request to the specified Uri with an HTTP completion option and a cancellation token as an asynchronous operation. /// @@ -212,7 +211,14 @@ namespace Flow.Launcher.Infrastructure.Http /// public static async Task SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead, CancellationToken token = default) { - return await client.SendAsync(request, completionOption, token); + try + { + return await client.SendAsync(request, completionOption, token); + } + catch (System.Exception) + { + return new HttpResponseMessage(HttpStatusCode.InternalServerError); + } } } } diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index 2591506c8..edc71feef 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -22,7 +22,7 @@ SystemParametersInfo SetForegroundWindow -GetWindowLong +WINDOW_LONG_PTR_INDEX GetForegroundWindow GetDesktopWindow GetShellWindow @@ -42,6 +42,11 @@ MONITORINFOEXW WM_ENTERSIZEMOVE WM_EXITSIZEMOVE +WM_NCLBUTTONDBLCLK +WM_SYSCOMMAND + +SC_MAXIMIZE +SC_MINIMIZE OleInitialize OleUninitialize diff --git a/Flow.Launcher.Infrastructure/PInvokeExtensions.cs b/Flow.Launcher.Infrastructure/PInvokeExtensions.cs index 1a72ab7a6..18b992043 100644 --- a/Flow.Launcher.Infrastructure/PInvokeExtensions.cs +++ b/Flow.Launcher.Infrastructure/PInvokeExtensions.cs @@ -4,14 +4,16 @@ using Windows.Win32.UI.WindowsAndMessaging; namespace Windows.Win32; -// Edited from: https://github.com/files-community/Files internal static partial class PInvoke { + // SetWindowLong + // Edited from: https://github.com/files-community/Files + [DllImport("User32", EntryPoint = "SetWindowLongW", ExactSpelling = true)] - static extern int _SetWindowLong(HWND hWnd, int nIndex, int dwNewLong); + private 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); + private static extern nint _SetWindowLongPtr(HWND hWnd, int nIndex, nint dwNewLong); // NOTE: // CsWin32 doesn't generate SetWindowLong on other than x86 and vice versa. @@ -22,4 +24,22 @@ internal static partial class PInvoke ? _SetWindowLong(hWnd, (int)nIndex, (int)dwNewLong) : _SetWindowLongPtr(hWnd, (int)nIndex, dwNewLong); } + + // GetWindowLong + + [DllImport("User32", EntryPoint = "GetWindowLongW", ExactSpelling = true)] + private static extern int _GetWindowLong(HWND hWnd, int nIndex); + + [DllImport("User32", EntryPoint = "GetWindowLongPtrW", ExactSpelling = true)] + private static extern nint _GetWindowLongPtr(HWND hWnd, int nIndex); + + // NOTE: + // CsWin32 doesn't generate GetWindowLong on other than x86 and vice versa. + // For more info, visit https://github.com/microsoft/CsWin32/issues/882 + public static unsafe nint GetWindowLongPtr(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex) + { + return sizeof(nint) is 4 + ? _GetWindowLong(hWnd, (int)nIndex) + : _GetWindowLongPtr(hWnd, (int)nIndex); + } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index f00f42ac0..892045994 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -50,6 +50,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings public string SelectPrevPageHotkey { get; set; } = $"PageDown"; public string OpenContextMenuHotkey { get; set; } = $"Ctrl+O"; public string SettingWindowHotkey { get; set; } = $"Ctrl+I"; + public string OpenHistoryHotkey { get; set; } = $"Ctrl+H"; public string CycleHistoryUpHotkey { get; set; } = $"{KeyConstant.Alt} + Up"; public string CycleHistoryDownHotkey { get; set; } = $"{KeyConstant.Alt} + Down"; @@ -426,6 +427,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings list.Add(new(SelectPrevItemHotkey2, "SelectPrevItemHotkey", () => SelectPrevItemHotkey2 = "")); if (!string.IsNullOrEmpty(SettingWindowHotkey)) list.Add(new(SettingWindowHotkey, "SettingWindowHotkey", () => SettingWindowHotkey = "")); + if (!string.IsNullOrEmpty(OpenHistoryHotkey)) + list.Add(new(OpenHistoryHotkey, "OpenHistoryHotkey", () => OpenHistoryHotkey = "")); if (!string.IsNullOrEmpty(OpenContextMenuHotkey)) list.Add(new(OpenContextMenuHotkey, "OpenContextMenuHotkey", () => OpenContextMenuHotkey = "")); if (!string.IsNullOrEmpty(SelectNextPageHotkey)) @@ -461,7 +464,6 @@ namespace Flow.Launcher.Infrastructure.UserSettings new("Alt+Home", "HotkeySelectFirstResult"), new("Alt+End", "HotkeySelectLastResult"), new("Ctrl+R", "HotkeyRequery"), - new("Ctrl+H", "ToggleHistoryHotkey"), new("Ctrl+OemCloseBrackets", "QuickWidthHotkey"), new("Ctrl+OemOpenBrackets", "QuickWidthHotkey"), new("Ctrl+OemPlus", "QuickHeightHotkey"), diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 96d8e925b..86e7b7c97 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -194,9 +194,9 @@ namespace Flow.Launcher.Infrastructure SetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, style); } - private static int GetWindowStyle(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex) + private static nint GetWindowStyle(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex) { - var style = PInvoke.GetWindowLong(hWnd, nIndex); + var style = PInvoke.GetWindowLongPtr(hWnd, nIndex); if (style == 0 && Marshal.GetLastPInvokeError() != 0) { throw new Win32Exception(Marshal.GetLastPInvokeError()); @@ -204,7 +204,7 @@ namespace Flow.Launcher.Infrastructure return style; } - private static nint SetWindowStyle(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, int dwNewLong) + private static nint SetWindowStyle(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, nint dwNewLong) { PInvoke.SetLastError(WIN32_ERROR.NO_ERROR); // Clear any existing error @@ -324,6 +324,11 @@ namespace Flow.Launcher.Infrastructure public const int WM_ENTERSIZEMOVE = (int)PInvoke.WM_ENTERSIZEMOVE; public const int WM_EXITSIZEMOVE = (int)PInvoke.WM_EXITSIZEMOVE; + public const int WM_NCLBUTTONDBLCLK = (int)PInvoke.WM_NCLBUTTONDBLCLK; + public const int WM_SYSCOMMAND = (int)PInvoke.WM_SYSCOMMAND; + + public const int SC_MAXIMIZE = (int)PInvoke.SC_MAXIMIZE; + public const int SC_MINIMIZE = (int)PInvoke.SC_MINIMIZE; #endregion diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index ce3b7ab1a..4a26cec95 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -14,10 +14,10 @@ - 4.4.0 - 4.4.0 - 4.4.0 - 4.4.0 + 4.5.0 + 4.5.0 + 4.5.0 + 4.5.0 Flow.Launcher.Plugin Flow-Launcher MIT diff --git a/Flow.Launcher.Plugin/Query.cs b/Flow.Launcher.Plugin/Query.cs index c3eede4c6..f50614699 100644 --- a/Flow.Launcher.Plugin/Query.cs +++ b/Flow.Launcher.Plugin/Query.cs @@ -21,6 +21,11 @@ namespace Flow.Launcher.Plugin /// public bool IsReQuery { get; internal set; } = false; + /// + /// Determines whether the query is a home query. + /// + public bool IsHomeQuery { get; internal init; } = false; + /// /// Search part of a query. /// This will not include action keyword if exclusive plugin gets it, otherwise it should be same as RawQuery. diff --git a/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs b/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs index 752c85933..ed3e91daf 100644 --- a/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs +++ b/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs @@ -1,8 +1,9 @@ -using Microsoft.Win32; -using System; +using System; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; +using Microsoft.Win32; namespace Flow.Launcher.Plugin.SharedCommands { @@ -13,7 +14,7 @@ namespace Flow.Launcher.Plugin.SharedCommands { private static string GetDefaultBrowserPath() { - string name = string.Empty; + var name = string.Empty; try { using var regDefault = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice", false); @@ -23,8 +24,7 @@ namespace Flow.Launcher.Plugin.SharedCommands name = regKey.GetValue(null).ToString().ToLower().Replace("\"", ""); if (!name.EndsWith("exe")) - name = name.Substring(0, name.LastIndexOf(".exe") + 4); - + name = name[..(name.LastIndexOf(".exe") + 4)]; } catch { @@ -65,12 +65,21 @@ namespace Flow.Launcher.Plugin.SharedCommands { Process.Start(psi)?.Dispose(); } - catch (System.ComponentModel.Win32Exception) + // This error may be thrown if browser path is incorrect + catch (Win32Exception) { - Process.Start(new ProcessStartInfo + try { - FileName = url, UseShellExecute = true - }); + Process.Start(new ProcessStartInfo + { + FileName = url, + UseShellExecute = true + }); + } + catch + { + throw; // Re-throw the exception if we cannot open the URL in the default browser + } } } @@ -100,12 +109,20 @@ namespace Flow.Launcher.Plugin.SharedCommands Process.Start(psi)?.Dispose(); } // This error may be thrown if browser path is incorrect - catch (System.ComponentModel.Win32Exception) + catch (Win32Exception) { - Process.Start(new ProcessStartInfo + try { - FileName = url, UseShellExecute = true - }); + Process.Start(new ProcessStartInfo + { + FileName = url, + UseShellExecute = true + }); + } + catch + { + throw; // Re-throw the exception if we cannot open the URL in the default browser + } } } } diff --git a/Flow.Launcher/Helper/ErrorReporting.cs b/Flow.Launcher/Helper/ErrorReporting.cs index aa810ba65..e201284cb 100644 --- a/Flow.Launcher/Helper/ErrorReporting.cs +++ b/Flow.Launcher/Helper/ErrorReporting.cs @@ -1,20 +1,21 @@ using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; -using System.Windows; using System.Windows.Threading; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Exception; +using Flow.Launcher.Infrastructure.Logger; using NLog; namespace Flow.Launcher.Helper; public static class ErrorReporting { - private static void Report(Exception e, [CallerMemberName] string methodName = "UnHandledException") + private static void Report(Exception e, bool silent = false, [CallerMemberName] string methodName = "UnHandledException") { var logger = LogManager.GetLogger(methodName); logger.Fatal(ExceptionFormatter.FormatExcpetion(e)); + if (silent) return; var reportWindow = new ReportWindow(e); reportWindow.Show(); } @@ -35,8 +36,9 @@ public static class ErrorReporting public static void TaskSchedulerUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) { - // handle unobserved task exceptions on UI thread - Application.Current.Dispatcher.Invoke(() => Report(e.Exception)); + // log exception but do not handle unobserved task exceptions on UI thread + //Application.Current.Dispatcher.Invoke(() => Report(e.Exception, true)); + Log.Exception(nameof(ErrorReporting), "Unobserved task exception occurred.", e.Exception); // prevent application exit, so the user can copy the prompted error info e.SetObserved(); } diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 262727127..e8961058c 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -100,6 +100,7 @@ namespace Flow.Launcher PreviewHotkey, OpenContextMenuHotkey, SettingWindowHotkey, + OpenHistoryHotkey, CycleHistoryUpHotkey, CycleHistoryDownHotkey, SelectPrevPageHotkey, @@ -130,6 +131,7 @@ namespace Flow.Launcher HotkeyType.PreviewHotkey => _settings.PreviewHotkey, HotkeyType.OpenContextMenuHotkey => _settings.OpenContextMenuHotkey, HotkeyType.SettingWindowHotkey => _settings.SettingWindowHotkey, + HotkeyType.OpenHistoryHotkey => _settings.OpenHistoryHotkey, HotkeyType.CycleHistoryUpHotkey => _settings.CycleHistoryUpHotkey, HotkeyType.CycleHistoryDownHotkey => _settings.CycleHistoryDownHotkey, HotkeyType.SelectPrevPageHotkey => _settings.SelectPrevPageHotkey, @@ -166,6 +168,9 @@ namespace Flow.Launcher case HotkeyType.SettingWindowHotkey: _settings.SettingWindowHotkey = value; break; + case HotkeyType.OpenHistoryHotkey: + _settings.OpenHistoryHotkey = value; + break; case HotkeyType.CycleHistoryUpHotkey: _settings.CycleHistoryUpHotkey = value; break; diff --git a/Flow.Launcher/Languages/ar.xaml b/Flow.Launcher/Languages/ar.xaml index 42cfdf3eb..88e8fbc61 100644 --- a/Flow.Launcher/Languages/ar.xaml +++ b/Flow.Launcher/Languages/ar.xaml @@ -364,6 +364,7 @@ موقع بيانات المستخدم يتم حفظ إعدادات المستخدم والإضافات المثبتة في مجلد بيانات المستخدم. قد يختلف هذا الموقع اعتمادًا على ما إذا كان في وضع النقل أم لا. فتح المجلد + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/cs.xaml b/Flow.Launcher/Languages/cs.xaml index bfcd92360..552f1a36d 100644 --- a/Flow.Launcher/Languages/cs.xaml +++ b/Flow.Launcher/Languages/cs.xaml @@ -364,6 +364,7 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/da.xaml b/Flow.Launcher/Languages/da.xaml index 9a4cbc003..7fd6d478a 100644 --- a/Flow.Launcher/Languages/da.xaml +++ b/Flow.Launcher/Languages/da.xaml @@ -364,6 +364,7 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/de.xaml b/Flow.Launcher/Languages/de.xaml index 88c5b84d4..3c650920f 100644 --- a/Flow.Launcher/Languages/de.xaml +++ b/Flow.Launcher/Languages/de.xaml @@ -42,8 +42,8 @@ Spielmodus Aussetzen der Verwendung von Hotkeys. Position zurücksetzen - Reset search window position - Type here to search + Position des Suchfensters zurücksetzen + Zum Suchen hier tippen Einstellungen @@ -126,12 +126,12 @@ - Open Language and Region System Settings + Sprach- und Regionen-Systemeinstellungen öffnen Opens the Korean IME setting location. Go to Korean > Language Options > Keyboard - Microsoft IME > Compatibility Öffnen - Use Previous Korean IME + Vorherige koreanische IME verwenden You can change the Previous Korean IME settings directly from here - Home Page + Homepage Show home page results when query text is empty. Show History Results in Home Page Maximum History Results Shown in Home Page @@ -154,11 +154,11 @@ Aktions-Schlüsselwörter ändern Plugin search delay time Change Plugin Search Delay Time - Advanced Settings: + Erweiterte Einstellungen Aktiviert Priorität Search Delay - Home Page + Homepage Aktuelle Priorität Neue Priorität Priorität @@ -246,13 +246,13 @@ Acrylic Mica Mica Alt - This theme supports two (light/dark) modes. + Dieses Theme unterstützt zwei Modi (hell/dunkel). Dieses Theme unterstützt Unschärfe und transparenten Hintergrund. - Show placeholder + Platzhalter zeigen Display placeholder when query is empty - Placeholder text + Platzhaltertext Change placeholder text. Input empty will use: {0} - Fixed Window Size + Festgelegte Fenstergröße The window size is not adjustable by dragging. @@ -313,7 +313,7 @@ Segoe Fluent-Icons verwenden Segoe Fluent-Icons für Abfrageergebnisse verwenden, wo unterstützt Taste drücken - Show Result Badges + Ergebnis-Badges zeigen For supported plugins, badges are displayed to help distinguish them more easily. Show Result Badges for Global Query Only @@ -356,14 +356,15 @@ Ordner »Logs« Logs löschen Sind Sie sicher, dass Sie alle Logs löschen wollen? - Cache Folder - Clear Caches + Cache-Ordner + Cache leeren Are you sure you want to delete all caches? Failed to clear part of folders and files. Please see log file for more information Assistent Speicherort für Benutzerdaten Benutzereinstellungen und installierte Plug-ins werden im Ordner für Benutzerdaten gespeichert. Dieser Speicherort kann variieren, je nachdem, ob sich das Programm im portablen Modus befindet oder nicht. Ordner öffnen + Advanced Log-Ebene Debug Info @@ -371,7 +372,7 @@ Dateimanager auswählen - Learn more + Mehr erfahren Bitte geben Sie den Dateiort des von Ihnen verwendeten Dateimanagers an und fügen Sie bei Bedarf Argumente hinzu. Das „%d“ repräsentiert den dafür zu öffnenden Verzeichnispfad, der vom Feld Arg for Folder und für Befehle zum Öffnen bestimmter Verzeichnisse verwendet wird. Das „%f“ repräsentiert den dafür zu öffnenden Dateipfad, der vom Feld Arg for File und für Befehle zum Öffnen bestimmter Dateien verwendet wird. Zum Beispiel, wenn der Dateimanager einen Befehl wie „totalcmd.exe /A c:\windows“ verwendet, um das Verzeichnis c:\windows zu öffnen, lautet der Dateimanager-Pfad „totalcmd.exe“ und der Arg for Folder „/A %d“. Bestimmte Dateimanager wie QTTabBar kann nur die Angabe eines Pfades erfordern, in diesem Fall verwenden Sie „%d“ als den Dateimanager-Pfad und lassen den Rest der Felder blank. Dateimanager @@ -416,7 +417,7 @@ Input the search delay time in ms you like to use for the plugin. Input empty if you don't want to specify any, and the plugin will use default search delay time. - Home Page + Homepage Enable the plugin home page state if you like to show the plugin results when query is empty. diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index f233a30d5..c9fc36892 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -359,6 +359,7 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Advanced Log Level Debug Info @@ -472,6 +473,7 @@ Error An error occurred while opening the folder. {0} + An error occurred while opening the URL in the browser. Please check your Default Web Browser configuration in the General section of the settings window Please wait... diff --git a/Flow.Launcher/Languages/es-419.xaml b/Flow.Launcher/Languages/es-419.xaml index b73a1ef12..5bd77a8dd 100644 --- a/Flow.Launcher/Languages/es-419.xaml +++ b/Flow.Launcher/Languages/es-419.xaml @@ -364,6 +364,7 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/es.xaml b/Flow.Launcher/Languages/es.xaml index 2b6074f06..7324fc01b 100644 --- a/Flow.Launcher/Languages/es.xaml +++ b/Flow.Launcher/Languages/es.xaml @@ -364,6 +364,7 @@ Ubicación de datos del usuario La configuración del usuario y los complementos instalados se guardan en la carpeta de datos del usuario. Esta ubicación puede variar dependiendo de si está en modo portable o no. Abrir carpeta + Advanced Nivel de registro Depurar Información @@ -371,7 +372,7 @@ Seleccionar administrador de archivos - Learn more + Más información Especifique la ubicación del archivo del administrador de archivos que está utilizando y añada los argumentos necesarios. El argumento "%d" representa la ruta del directorio a abrir, utilizada por el campo Argumentos de la carpeta y por comandos que abren directorios específicos. El "%f" representa la ruta del archivo a abrir, utilizada por el campo Argumentos del archivo y por comandos que abren archivos específicos. Por ejemplo, si el administrador de archivos utiliza un comando como "totalcmd.exe /A c:\windows" para abrir el directorio c:\windows, la ruta del administrador de archivos será totalcmd.exe, y los Argumentos de la carpeta serán /A "%d". Ciertos administradores de archivos como QTTabBar pueden requerir solo la ruta, en este caso utilice "%d" como la ruta del administrador de archivos y deje el resto de los campos en blanco. Administrador de archivos @@ -379,8 +380,8 @@ Ruta del administrador de archivos Argumentos de la carpeta Argumentos del archivo - The file manager '{0}' could not be located at '{1}'. Would you like to continue? - File Manager Path Error + El administrador de archivos '{0}' no pudo ser localizado en '{1}'. ¿Desea continuar? + Error de ruta del administrador de archivos Navegador web predeterminado @@ -473,12 +474,12 @@ Si añade un prefijo "@" al introducir un acceso directo, éste coinci 2. Copiar el siguiente mensaje de excepción - File Manager Error + Error del administrador de archivos - The specified file manager could not be found. Please check the Custom File Manager setting under Settings > General. + No se ha encontrado el administrador de archivos especificado. Compruebe la configuración del Administrador de archivos personalizado en Configuración > General. Error - An error occurred while opening the folder. {0} + Se ha producido un error al abrir la carpeta. {0} Por favor espere... diff --git a/Flow.Launcher/Languages/fr.xaml b/Flow.Launcher/Languages/fr.xaml index cd6d8c01f..95ad58d6e 100644 --- a/Flow.Launcher/Languages/fr.xaml +++ b/Flow.Launcher/Languages/fr.xaml @@ -363,6 +363,7 @@ Emplacement des données utilisateur Les paramètres utilisateur et les plugins installés sont enregistrés dans le dossier des données utilisateur. Cet emplacement peut varier selon que vous soyez en mode portable ou non. Ouvrir le dossier + Avancé Niveau de journalisation Débogage Info @@ -370,7 +371,7 @@ Sélectionner le gestionnaire de fichiers - Learn more + En savoir plus Veuillez spécifier l'emplacement du fichier de l'explorateur de fichiers que vous utilisez et ajouter des arguments si nécessaire. Le "%d" représente le chemin du répertoire à ouvrir, utilisé par le champ Arg for Folder et pour les commandes ouvrant des répertoires spécifiques. Le "%f" représente le chemin du fichier à ouvrir, utilisé par le champ Arg for File et pour les commandes ouvrant des fichiers spécifiques. Par exemple, si l'explorateur de fichiers utilise une commande telle que "totalcmd.exe /A c:\windows" pour ouvrir le répertoire c:\windows, le chemin de l'explorateur de fichiers sera totalcmd.exe et l'argument Arg For Folder sera /A "%d"". Certains explorateurs de fichiers comme QTTabBar peuvent simplement nécessiter qu'un chemin soit fourni, dans ce cas, utilisez "%d" comme chemin de l'explorateur de fichiers et laissez le reste des fichiers vides. Gestionnaire de fichiers @@ -378,8 +379,8 @@ Chemin du gestionnaire de fichiers Arguments pour le répertoire Arguments pour le fichier - The file manager '{0}' could not be located at '{1}'. Would you like to continue? - File Manager Path Error + Le gestionnaire de fichiers '{0}' n'a pas pu être situé à '{1}'. Souhaitez-vous continuer ? + Erreur de chemin du gestionnaire de fichiers Navigateur web par défaut @@ -472,12 +473,12 @@ Si vous ajoutez un préfixe "@" lors de la saisie d'un raccourci, celu 2. Copiez le message d’exception ci-dessous - File Manager Error + Erreur du gestionnaire de fichiers - The specified file manager could not be found. Please check the Custom File Manager setting under Settings > General. + Le gestionnaire de fichiers spécifié n'a pas été trouvé. Veuillez vérifier le paramètre Gestionnaire de fichiers personnalisé dans Paramètres > Général. Erreur - An error occurred while opening the folder. {0} + Une erreur s'est produite lors de l'ouverture du dossier. {0} Veuillez patienter... diff --git a/Flow.Launcher/Languages/he.xaml b/Flow.Launcher/Languages/he.xaml index b98c2ec73..d1688b647 100644 --- a/Flow.Launcher/Languages/he.xaml +++ b/Flow.Launcher/Languages/he.xaml @@ -67,7 +67,7 @@ מרכז עליון שמאל עליון ימין עליון - Custom Position + מיקום מותאם אישית שפה סגנון שאילתה אחרונה הצג/הסתר תוצאות קודמות כאשר Flow Launcher מופעל מחדש. @@ -102,7 +102,7 @@ נמוך Regular חפש באמצעות Pinyin - Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. + מאפשר חיפוש באמצעות Pinyin, מערכת הכתיבה הסטנדרטית לתרגום סינית. הצג תמיד תצוגה מקדימה פתח תמיד את לוח התצוגה המקדימה כאשר Flow מופעל. הקש על {0} כדי להחליף את התצוגה המקדימה. לא ניתן להחיל אפקט צל כאשר העיצוב הנוכחי מוגדר לאפקט טשטוש @@ -134,7 +134,7 @@ Show home page results when query text is empty. Show History Results in Home Page Maximum History Results Shown in Home Page - This can only be edited if plugin supports Home feature and Home Page is enabled. + ניתן לערוך זאת רק אם התוסף תומך בתכונת הבית ודף הבית מופעל. חפש תוסף @@ -151,7 +151,7 @@ מילת מפתח נוכחית לפעולה מילת מפתח חדשה לפעולה שנה מילות מפתח לפעולה - Plugin search delay time + זמן השהייה של חיפוש תוסף שנה את זמן השהיית חיפוש של תוסף הגדרות מתקדמות: מופעל @@ -313,7 +313,7 @@ השתמש ב-Segoe Fluent Icons לתוצאות חיפוש כאשר נתמך הקש על מקש הצג תגי תוצאות - For supported plugins, badges are displayed to help distinguish them more easily. + עבור תוספים נתמכים, מוצגים תגים כדי לעזור להבחין ביניהם ביתר קלות. Show Result Badges for Global Query Only @@ -363,6 +363,7 @@ מיקום נתוני משתמש הגדרות המשתמש והתוספים המותקנים נשמרים בתיקיית נתוני המשתמש. מיקום זה עשוי להשתנות אם התוכנה במצב נייד. פתח תיקיה + Advanced רמת יומן ניפוי שגיאות מידע @@ -370,7 +371,7 @@ בחר מנהל קבצים - Learn more + למד עוד אנא ציין את מיקום הקובץ של מנהל הקבצים שבו אתה משתמש והוסף ארגומנטים כנדרש. "%d" מייצג את נתיב התיקייה שיש לפתוח, ומשמש בשדה ארגומנט לתיקייה ובפקודות לפתיחת תיקיות מסוימות. "%f" מייצג את נתיב הקובץ שיש לפתוח, ומשמש בשדה ארגומנט לקובץ ובפקודות לפתיחת קבצים מסוימים. לדוגמה, אם מנהל הקבצים משתמש בפקודה כגון "totalcmd.exe /A c:\windows" כדי לפתוח את התיקייה c:\windows, נתיב מנהל הקבצים יהיה totalcmd.exe, והארגומנט לתיקייה יהיה /A "%d". מנהלי קבצים מסוימים, כגון QTTabBar, עשויים לדרוש רק ציון נתיב, במקרה כזה השתמש ב-"%d" כנתיב מנהל הקבצים והשאר את שאר השדות ריקים. מנהל קבצים @@ -378,8 +379,8 @@ נתיב מנהל קבצים ארגומנט לתיקייה ארגומנט לקובץ - The file manager '{0}' could not be located at '{1}'. Would you like to continue? - File Manager Path Error + לא ניתן היה לאתר את מנהל הקבצים '{0}' ב-'{1}'. האם ברצונך להמשיך? + שגיאת נתיב למנהל הקבצים דפדפן ברירת מחדל @@ -416,7 +417,7 @@ דף הבית - Enable the plugin home page state if you like to show the plugin results when query is empty. + הפעל את מצב דף הבית של התוסף אם ברצונך להציג את תוצאות התוסף כאשר השאילתה ריקה. מקש קיצור לשאילתה מותאמת אישית @@ -472,12 +473,12 @@ 2. העתק את הודעת החריגה למטה - File Manager Error + שגיאת מנהל הקבצים - The specified file manager could not be found. Please check the Custom File Manager setting under Settings > General. + לא ניתן היה למצוא את מנהל הקבצים שצוין. אנא בדוק את ההגדרה של מנהל קבצים מותאם אישית תחת הגדרות > כללי. שגיאה - An error occurred while opening the folder. {0} + אירעה שגיאה בעת פתיחת התיקייה. {0} אנא המתן... diff --git a/Flow.Launcher/Languages/it.xaml b/Flow.Launcher/Languages/it.xaml index 7ea797fa9..64d9f5abe 100644 --- a/Flow.Launcher/Languages/it.xaml +++ b/Flow.Launcher/Languages/it.xaml @@ -364,6 +364,7 @@ Posizione Dati Utente Le impostazioni dell'utente e i plugin installati sono salvati nella cartella dati utente. Questa posizione può variare se è in modalità portable o no. Apri Cartella + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/ja.xaml b/Flow.Launcher/Languages/ja.xaml index 33673d60f..c2e64ae26 100644 --- a/Flow.Launcher/Languages/ja.xaml +++ b/Flow.Launcher/Languages/ja.xaml @@ -364,6 +364,7 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/ko.xaml b/Flow.Launcher/Languages/ko.xaml index 9e8f9a73b..b78c86a82 100644 --- a/Flow.Launcher/Languages/ko.xaml +++ b/Flow.Launcher/Languages/ko.xaml @@ -122,10 +122,10 @@ 열기 이전 버전의 Microsoft IME 사용 이전 버전의 IME를 사용하도록 시스템 설정을 변경합니다 - Home Page - Show home page results when query text is empty. - Show History Results in Home Page - Maximum History Results Shown in Home Page + 홈페이지 + 쿼리 입력창이 비어있을때, 홈페이지의 결과를 표시합니다. + 히스토리를 홈페이지에 표시 + 홈페이지에 표시할 최대 히스토리 수 This can only be edited if plugin supports Home feature and Home Page is enabled. @@ -149,7 +149,7 @@ 중요 검색 지연 - Home Page + 홈페이지 현재 중요도: 새 중요도: 중요도 @@ -347,7 +347,7 @@ 로그 폴더 로그 삭제 정말 모든 로그를 삭제하시겠습니까? - Cache Folder + 캐시 폴더 캐시 지우기 모든 캐시를 삭제하시겠습니까? Failed to clear part of folders and files. Please see log file for more information @@ -355,14 +355,15 @@ 사용자 데이터 위치 사용자 설정과 설치된 플러그인은 사용자 데이터 폴더에 저장됩니다. 이 위치는 휴대용 모드 활성화 여부에 따라 달라질 수 있습니다. 폴더 열기 + Advanced 로그 레벨 Debug Info - Setting Window Font + 설정창 글꼴 파일관리자 선택 - Learn more + 더 알아보기 사용 중인 파일 관리자의 파일 위치를 지정하고, 필요한 경우 인수를 추가하세요. "%d"는 열고자 하는 디렉터리 경로를 나타내며, 폴더용 인수 필드 및 특정 디렉터리를 여는 명령어에서 사용됩니다. "%f"는 열고자 하는 파일 경로를 나타내며, 파일용 인수 필드 및 특정 파일을 여는 명령어에서 사용됩니다. 예를 들어, 파일 관리자가 totalcmd.exe /A c:\windows와 같은 명령어로 c:\windows 디렉터리를 연다면, 파일 관리자 경로는 totalcmd.exe가 되고, 폴더용 인수는 /A "%d"가 됩니다. QTTabBar와 같은 일부 파일 관리자는 경로만 전달하면 되는 경우가 있으므로, 이 경우에는 파일 관리자 경로에 "%d"를 입력하고 나머지 필드는 비워두세요. 파일관리자 @@ -407,7 +408,7 @@ 플러그인에서 사용할 검색 지연 시간(ms)을 입력하세요. 지정하지 않으려면 비워두세요. 기본 검색 지연 시간이 사용됩니다. - Home Page + 홈페이지 Enable the plugin home page state if you like to show the plugin results when query is empty. diff --git a/Flow.Launcher/Languages/nb.xaml b/Flow.Launcher/Languages/nb.xaml index 8d5ac7a94..9e62ec2cc 100644 --- a/Flow.Launcher/Languages/nb.xaml +++ b/Flow.Launcher/Languages/nb.xaml @@ -364,6 +364,7 @@ Plassering av brukerdata Brukerinnstillinger og installerte programtillegg lagres i brukerens datamappe. Denne plasseringen kan variere avhengig av om den er i portabel modus eller ikke. Åpne mappe + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/nl.xaml b/Flow.Launcher/Languages/nl.xaml index 0f6ad436d..31e6ea768 100644 --- a/Flow.Launcher/Languages/nl.xaml +++ b/Flow.Launcher/Languages/nl.xaml @@ -364,6 +364,7 @@ Gegevenslocatie van gebruiker Gebruikersinstellingen en geïnstalleerde plug-ins worden opgeslagen in de gebruikersgegevensmap. Deze locatie kan variëren afhankelijk van of het in draagbare modus is of niet. Map openen + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/pl.xaml b/Flow.Launcher/Languages/pl.xaml index 1397afa25..f32a38585 100644 --- a/Flow.Launcher/Languages/pl.xaml +++ b/Flow.Launcher/Languages/pl.xaml @@ -364,6 +364,7 @@ Kliknij "nie", jeśli jest już zainstalowany. Zostaniesz wtedy popros Lokalizacja danych użytkownika Ustawienia użytkownika i zainstalowane wtyczki są zapisywane w folderze danych użytkownika. Ta lokalizacja może się różnić w zależności od tego, czy aplikacja jest w trybie przenośnym, czy nie. Otwórz folder + Advanced Poziom logowania Debug Info diff --git a/Flow.Launcher/Languages/pt-br.xaml b/Flow.Launcher/Languages/pt-br.xaml index 232134290..bcbfa74af 100644 --- a/Flow.Launcher/Languages/pt-br.xaml +++ b/Flow.Launcher/Languages/pt-br.xaml @@ -364,6 +364,7 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/pt-pt.xaml b/Flow.Launcher/Languages/pt-pt.xaml index f98fdf64b..4eca2c5c6 100644 --- a/Flow.Launcher/Languages/pt-pt.xaml +++ b/Flow.Launcher/Languages/pt-pt.xaml @@ -362,6 +362,7 @@ Localização dos dados do utilizador As definições e os plugins instalados são guardados na pasta de dados do utilizador. A localização pode variar, tendo em conta se a aplicação está instalada ou no modo portátil Abrir pasta + Avançado Nível de registo Depuração Informação diff --git a/Flow.Launcher/Languages/ru.xaml b/Flow.Launcher/Languages/ru.xaml index 2a9b5c26b..3a63a4ea4 100644 --- a/Flow.Launcher/Languages/ru.xaml +++ b/Flow.Launcher/Languages/ru.xaml @@ -364,6 +364,7 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/sk.xaml b/Flow.Launcher/Languages/sk.xaml index 934b0747a..3c47d61fd 100644 --- a/Flow.Launcher/Languages/sk.xaml +++ b/Flow.Launcher/Languages/sk.xaml @@ -364,6 +364,7 @@ Cesta k používateľskému priečinku Nastavenia používateľa a nainštalované pluginy sa ukladajú do používateľského priečinka. Toto umiestnenie sa môže líšiť v závislosti od toho, či je v prenosnom režime alebo nie. Otvoriť priečinok + Rozšírené Úroveň logovania Debug Info diff --git a/Flow.Launcher/Languages/sr.xaml b/Flow.Launcher/Languages/sr.xaml index 0d2c04513..43f316a38 100644 --- a/Flow.Launcher/Languages/sr.xaml +++ b/Flow.Launcher/Languages/sr.xaml @@ -364,6 +364,7 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/tr.xaml b/Flow.Launcher/Languages/tr.xaml index ab9aa31e6..2f4e4d302 100644 --- a/Flow.Launcher/Languages/tr.xaml +++ b/Flow.Launcher/Languages/tr.xaml @@ -364,6 +364,7 @@ Kullanıcı Verisi Dizini Kullanıcı ayarları ve yüklü eklentiler bu klasörde saklanır. Klasörün konumu taşınabilir moda bağlı olarak değişebilir. Klasörü Aç + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/uk-UA.xaml b/Flow.Launcher/Languages/uk-UA.xaml index f7cc7b0ed..a7dfcda55 100644 --- a/Flow.Launcher/Languages/uk-UA.xaml +++ b/Flow.Launcher/Languages/uk-UA.xaml @@ -364,6 +364,7 @@ Розташування даних користувача Налаштування користувача та встановлені плагіни зберігаються у теці даних користувача. Це місце може змінюватися залежно від того, чи перебуває програма в портативному режимі, чи ні. Відкрити теку + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/vi.xaml b/Flow.Launcher/Languages/vi.xaml index 95e43e297..b77c6d4e8 100644 --- a/Flow.Launcher/Languages/vi.xaml +++ b/Flow.Launcher/Languages/vi.xaml @@ -42,7 +42,7 @@ Chế độ trò chơi Tạm dừng sử dụng phím nóng. Đặt lại vị trí - Reset search window position + Cài lại vị trí cửa sổ tìm kiếm Type here to search @@ -366,6 +366,7 @@ Vị trí dữ liệu người dùng Thiết đặt người dùng và plugin đã cài đặt sẽ được lưu trong thư mục dữ liệu người dùng. Vị trí này có thể thay đổi tùy thuộc vào việc nó có ở chế độ di động hay không. Mở thư mục + Advanced Log Level Debug Info diff --git a/Flow.Launcher/Languages/zh-cn.xaml b/Flow.Launcher/Languages/zh-cn.xaml index f6576070f..95a97d5ed 100644 --- a/Flow.Launcher/Languages/zh-cn.xaml +++ b/Flow.Launcher/Languages/zh-cn.xaml @@ -8,9 +8,9 @@ 请选择 {0} 可执行文件 - Your selected {0} executable is invalid. + 您选择的 {0} 可执行文件无效。 {2}{2} - Click yes if you would like select the {0} executable agian. Click no if you would like to download {1} + 如果您希望重新选择 {0} 可执行文件,请点击“是”。如果需要下载 {1},请点击“否” 无法设置 {0} 可执行路径,请尝试从 Flow 的设置中设置(向下滚动到底部)。 无法初始化插件 @@ -18,7 +18,7 @@ 无法注册热键“{0}”。该热键可能正在被其他程序使用。更改为不同的热键,或退出其他程序。 - Failed to unregister hotkey "{0}". Please try again or see log for details + 未能取消注册快捷键"{0}"。请重试或查看日志以获取详细信息 Flow Launcher 启动命令 {0} 失败 无效的 Flow Launcher 插件文件格式 @@ -42,8 +42,8 @@ 游戏模式 暂停使用热键。 重置位置 - Reset search window position - Type here to search + 重置搜索窗口位置 + 在此处输入以搜索 设置 @@ -51,12 +51,12 @@ 便携模式 将所有设置和用户数据存储在一个文件夹中 (可用于可移除驱动器或云服务)。 开机自启 - Use logon task instead of startup entry for faster startup experience - After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler + 使用登录任务代替启动条目来更快地启动体验 + 卸载后,您需要通过任务计划程序手动移除此任务 (Flow.Launcher Startup) 设置开机自启时出错 失去焦点时自动隐藏 Flow Launcher 不显示新版本提示 - Search Window Location + 搜索窗口位置 记住上次的位置 鼠标光标所在显示器 聚焦窗口所在显示器 @@ -74,8 +74,8 @@ 保留上次搜索关键字 选择上次搜索关键字 清空上次搜索关键字 - Preserve Last Action Keyword - Select Last Action Keyword + 保留最后操作关键词 + 选择最后一个操作关键词 最大结果显示个数 您也可以通过使用 CTRL+ "+" 和 CTRL+ "-" 来快速调整它。 全屏模式下忽略热键 @@ -106,36 +106,36 @@ 始终打开预览 Flow 启动时总是打开预览面板。按 {0} 以切换预览。 当前主题已启用模糊效果,不允许启用阴影效果 - Search Delay - Adds a short delay while typing to reduce UI flicker and result load. Recommended if your typing speed is average. - Enter the wait time (in ms) until input is considered complete. This can only be edited if Search Delay is enabled. - Default Search Delay Time - Wait time before showing results after typing stops. Higher values wait longer. (ms) - Information for Korean IME user + 延迟搜索 + 在输入时添加一个短时间延迟以减少UI闪烁和加载结果的负载。建议您的输入速度是平均的。 + 输入等待时间(毫秒),直到输入被认为完成。这只能在启用搜索延迟时进行编辑。 + 默认搜索延迟时间 + 在输入停止后显示结果之前等待时间。更高的数值等待更长时间(毫秒) + 韩文输入法用户信息 - The Korean input method used in Windows 11 may cause some issues in Flow Launcher. + Windows 11中使用的韩国输入法可能会在Flow Launcher中引起一些问题。 - If you experience any problems, you may need to enable "Use previous version of Korean IME". + 如果您遇到任何问题,您可能需要启用"使用上一个版本的韩语IME"。 - Open Setting in Windows 11 and go to: + Windows 11中的打开设置,转到: - Time & Language > Language & Region > Korean > Language Options > Keyboard - Microsoft IME > Compatibility, + 时间和语言> 语言和区域 > 韩国语言选项 > 键盘-微软IME > 兼容性 - and enable "Use previous version of Microsoft IME". + 并启用"使用之前版本的 Microsoft IME"。 - Open Language and Region System Settings - Opens the Korean IME setting location. Go to Korean > Language Options > Keyboard - Microsoft IME > Compatibility + 打开语言和区域系统设置 + 打开韩语输入法设置位置。转到韩语> 语言选项 > 键盘-微软输入法 > 兼容性 打开 - Use Previous Korean IME - You can change the Previous Korean IME settings directly from here - Home Page - Show home page results when query text is empty. - Show History Results in Home Page - Maximum History Results Shown in Home Page - This can only be edited if plugin supports Home feature and Home Page is enabled. + 使用以前的韩语输入法 + 您可以直接从这里更改前韩语输入法设置 + 首页 + 当查询文本为空时显示主页结果。 + 在主页中显示历史记录 + 在主页显示的最大历史结果数 + 这只能在插件支持主页功能和主页启用时进行编辑。 搜索插件 @@ -152,13 +152,13 @@ 当前触发关键字 新触发关键字 更改触发关键字 - Plugin search delay time - Change Plugin Search Delay Time - Advanced Settings: + 插件搜索延迟时间 + 更改插件搜索延迟时间 + 高级设置: 启用 优先级 - Search Delay - Home Page + 延迟搜索 + 首页 当前优先级 新优先级 优先级 @@ -170,10 +170,10 @@ 版本 官方网站 卸载 - Fail to remove plugin settings - Plugins: {0} - Fail to remove plugin settings files, please remove them manually - Fail to remove plugin cache - Plugins: {0} - Fail to remove plugin cache files, please remove them manually + 移除插件设置失败 + 插件:{0} - 移除插件设置文件失败,请手动删除 + 移除插件缓存失败 + 插件:{0} - 移除插件设置文件失败,请手动删除 插件商店 @@ -210,9 +210,9 @@ 结果标题字体 结果字幕字体 重置 - Reset to the recommended font and size settings. - Import Theme Size - If a size value intended by the theme designer is available, it will be retrieved and applied. + 重置为推荐字体和大小设置。 + 导入主题尺寸 + 如果主题设计器想要的大小值可用,它将被检索和应用。 自定义 窗口模式 透明度 @@ -239,21 +239,21 @@ 自定义 时钟 日期 - Backdrop Type - The backdrop effect is not applied in the preview. - Backdrop supported starting from Windows 11 build 22000 and above + 返回类型 + 预览中没有应用背景效果。 + 自 Windows 11 Build 22000 起支持背景效果 - Acrylic - Mica - Mica Alt - This theme supports two (light/dark) modes. + 亚克力 + 云母 + 云母平替 + 该主题支持两种(浅色/深色)模式。 该主题支持模糊透明背景。 - Show placeholder - Display placeholder when query is empty - Placeholder text - Change placeholder text. Input empty will use: {0} - Fixed Window Size - The window size is not adjustable by dragging. + 显示占位符 + 当查询为空时显示占位符 + 占位符文本 + 更改占位符文本。输入空将使用:{0} + 固定窗口大小 + 窗口高度不能通过拖动来调整。 热键 @@ -313,9 +313,9 @@ 使用 Segoe Fluent 图标 在支持时在选项中显示 Segoe Fluent 图标 按下按键 - Show Result Badges - For supported plugins, badges are displayed to help distinguish them more easily. - Show Result Badges for Global Query Only + 显示结果徽章 + 对于支持的插件,将显示徽章以帮助更容易区分它们。 + 仅在全局查询下显示结果徽章 HTTP 代理 @@ -356,22 +356,23 @@ 日志目录 清除日志 你确定要删除所有的日志吗? - Cache Folder - Clear Caches - Are you sure you want to delete all caches? - Failed to clear part of folders and files. Please see log file for more information + 缓存文件夹 + 清除缓存 + 您确定要删除所有缓存吗? + 无法清除部分文件夹和文件。请查看日志文件以获取更多信息 向导 用户数据位置 用户设置和已安装的插件保存在用户数据文件夹中。此位置可能因是否处于便携模式而异。 打开文件夹 - Log Level - Debug - Info - Setting Window Font + 高级 + 日志等级 + 调试 + 信息 + 设置窗口字体 默认文件管理器 - Learn more + 了解更多 请指定您使用的文件管理器的文件位置并根据需要添加参数。“%d”表示要打开的目录路径,由文件夹字段的参数和打开特定目录的命令使用。“%f”表示要打开的文件路径,由文件字段的参数和打开特定文件的命令使用。 例如,如果文件管理器使用诸如“totalcmd.exe /A c:\windows”之类的命令来打开 c:\windows 目录,则文件管理器路径将为 totalcmd.exe,文件夹参数将为 /A "%d"。某些文件管理器(如 QTTabBar)可能只需要提供路径,在本例中,使用“%d”作为文件管理器路径,其余字段留空。 文件管理器 @@ -379,8 +380,8 @@ 文件管理器路径 文件夹路径参数 选中文件路径参数 - The file manager '{0}' could not be located at '{1}'. Would you like to continue? - File Manager Path Error + 文件管理器 '{0}' 不能在 '{1}'中定位。您想要继续吗? + 文件管理器路径错误 默认浏览器 @@ -388,7 +389,7 @@ 浏览器 浏览器名称 浏览器路径 - 新窗户 + 新窗口 新标签 隐身模式 @@ -405,19 +406,19 @@ 找不到指定的插件 新触发关键字不能为空 此触发关键字已经被指派给其他插件了,请换一个关键字 - This new Action Keyword is the same as old, please choose a different one + 此触发关键字已经被指派给其他插件了,请换一个关键字 成功 成功完成 - Failed to copy - Enter the action keywords you like to use to start the plugin and use whitespace to divide them. Use * if you don't want to specify any, and the plugin will be triggered without any action keywords. + 复制失败 + 请输入您希望用来启动插件的动作关键字,并使用空格进行分隔。若不想指定任何关键字,可直接输入*,此时插件将在未输入动作关键字的情况下被触发 - Search Delay Time Setting - Input the search delay time in ms you like to use for the plugin. Input empty if you don't want to specify any, and the plugin will use default search delay time. + 搜索延迟时间设置 + 请输入您希望插件使用的搜索延迟时间(单位:毫秒)。若不想指定,可留空,此时插件将采用默认的搜索延迟时间。 - Home Page - Enable the plugin home page state if you like to show the plugin results when query is empty. + 首页 + 如果您想在查询为空时显示插件的结果,请启用插件主页状态。 自定义查询热键 @@ -468,17 +469,17 @@ 发送成功 发送失败 Flow Launcher 出错啦 - Please open new issue in - 1. Upload log file: {0} - 2. Copy below exception message + 请打开新的问题在 + 1. 上传日志文件:{0} + 2. 复制下面的异常消息 - File Manager Error + 文件管理器错误 - The specified file manager could not be found. Please check the Custom File Manager setting under Settings > General. + 找不到指定的文件管理器。请在“设置 > 通用”下检查自定义文件管理器设置。 错误 - An error occurred while opening the folder. {0} + 打开文件夹时发生错误。{0} 请稍等... diff --git a/Flow.Launcher/Languages/zh-tw.xaml b/Flow.Launcher/Languages/zh-tw.xaml index 42810f590..60531c6aa 100644 --- a/Flow.Launcher/Languages/zh-tw.xaml +++ b/Flow.Launcher/Languages/zh-tw.xaml @@ -364,6 +364,7 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Advanced Log Level Debug Info diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 31bc2ba50..9ff38a564 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -64,10 +64,6 @@ Key="R" Command="{Binding ReQueryCommand}" Modifiers="Ctrl" /> - + - + @@ -373,7 +373,7 @@ - + @@ -419,7 +419,7 @@ diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index bb29d78e5..a77d6471c 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -101,7 +101,7 @@ namespace Flow.Launcher private void ThemeManager_ActualApplicationThemeChanged(ModernWpf.ThemeManager sender, object args) { - _theme.RefreshFrameAsync(); + _ = _theme.RefreshFrameAsync(); } private void OnSourceInitialized(object sender, EventArgs e) @@ -295,14 +295,14 @@ namespace Flow.Launcher // QueryTextBox.Text change detection (modified to only work when character count is 1 or higher) QueryTextBox.TextChanged += (s, e) => UpdateClockPanelVisibility(); - // Detecting ContextMenu.Visibility changes + // Detecting ResultContextMenu.Visibility changes DependencyPropertyDescriptor - .FromProperty(VisibilityProperty, typeof(ContextMenu)) - .AddValueChanged(ContextMenu, (s, e) => UpdateClockPanelVisibility()); + .FromProperty(VisibilityProperty, typeof(ResultListBox)) + .AddValueChanged(ResultContextMenu, (s, e) => UpdateClockPanelVisibility()); // Detect History.Visibility changes DependencyPropertyDescriptor - .FromProperty(VisibilityProperty, typeof(StackPanel)) + .FromProperty(VisibilityProperty, typeof(ResultListBox)) .AddValueChanged(History, (s, e) => UpdateClockPanelVisibility()); // Initialize query state @@ -465,7 +465,55 @@ namespace Flow.Launcher private void OnMouseDown(object sender, MouseButtonEventArgs e) { - if (e.ChangedButton == MouseButton.Left) DragMove(); + // When the window is maximized via Snap, + // dragging attempts will first switch the window from Maximized to Normal state, + // and adjust the drag position accordingly. + if (e.ChangedButton == MouseButton.Left) + { + try + { + if (WindowState == WindowState.Maximized) + { + // Calculate ratio based on maximized window dimensions + double maxWidth = ActualWidth; + double maxHeight = ActualHeight; + var mousePos = e.GetPosition(this); + double xRatio = mousePos.X / maxWidth; + double yRatio = mousePos.Y / maxHeight; + + // Current monitor information + var screen = Screen.FromHandle(new WindowInteropHelper(this).Handle); + var workingArea = screen.WorkingArea; + var screenLeftTop = Win32Helper.TransformPixelsToDIP(this, workingArea.X, workingArea.Y); + + // Switch to Normal state + WindowState = WindowState.Normal; + + Application.Current?.Dispatcher.Invoke(new Action(() => + { + double normalWidth = Width; + double normalHeight = Height; + + // Apply ratio based on the difference between maximized and normal window sizes + Left = screenLeftTop.X + (maxWidth - normalWidth) * xRatio; + Top = screenLeftTop.Y + (maxHeight - normalHeight) * yRatio; + + if (Mouse.LeftButton == MouseButtonState.Pressed) + { + DragMove(); + } + }), DispatcherPriority.ApplicationIdle); + } + else + { + DragMove(); + } + } + catch (InvalidOperationException) + { + // Ignored - can occur if drag operation is already in progress + } + } } #endregion @@ -490,56 +538,76 @@ namespace Flow.Launcher #region Window WndProc - private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { - if (msg == Win32Helper.WM_ENTERSIZEMOVE) + switch (msg) { - _initialWidth = (int)Width; - _initialHeight = (int)Height; - - handled = true; - } - else if (msg == Win32Helper.WM_EXITSIZEMOVE) - { - if (_initialHeight != (int)Height) - { - if (!_settings.KeepMaxResults) + case Win32Helper.WM_ENTERSIZEMOVE: + _initialWidth = (int)Width; + _initialHeight = (int)Height; + handled = true; + break; + case Win32Helper.WM_EXITSIZEMOVE: + //Prevent updating the number of results when the window height is below the height of a single result item. + //This situation occurs not only when the user manually resizes the window, but also when the window is released from a side snap, as the OS automatically adjusts the window height. + //(Without this check, releasing from a snap can cause the window height to hit the minimum, resulting in only 2 results being shown.) + if (_initialHeight != (int)Height && Height > (_settings.WindowHeightSize + _settings.ItemHeightSize)) { - // Get shadow margin - var shadowMargin = 0; - var (_, useDropShadowEffect) = _theme.GetActualValue(); - if (useDropShadowEffect) + if (!_settings.KeepMaxResults) { - shadowMargin = 32; + // Get shadow margin + var shadowMargin = 0; + var (_, useDropShadowEffect) = _theme.GetActualValue(); + if (useDropShadowEffect) + { + shadowMargin = 32; + } + + // Calculate max results to show + var itemCount = (Height - (_settings.WindowHeightSize + 14) - shadowMargin) / _settings.ItemHeightSize; + if (itemCount < 2) + { + _settings.MaxResultsToShow = 2; + } + else + { + _settings.MaxResultsToShow = Convert.ToInt32(Math.Truncate(itemCount)); + } } - // Calculate max results to show - 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; + } + else + { + // Update height when exiting maximized snap state. + SizeToContent = SizeToContent.Height; } - SizeToContent = SizeToContent.Height; - } - - if (_initialWidth != (int)Width) - { - if (!_settings.KeepMaxResults) + if (_initialWidth != (int)Width) { - // Update width - _viewModel.MainWindowWidth = Width; + if (!_settings.KeepMaxResults) + { + // Update width + _viewModel.MainWindowWidth = Width; + } + + SizeToContent = SizeToContent.Height; } + handled = true; + break; + case Win32Helper.WM_NCLBUTTONDBLCLK: // Block the double click in frame SizeToContent = SizeToContent.Height; - } - - handled = true; + handled = true; + break; + case Win32Helper.WM_SYSCOMMAND: // Block Maximize/Minimize by Win+Up and Win+Down Arrow + var command = wParam.ToInt32() & 0xFFF0; + if (command == Win32Helper.SC_MAXIMIZE || command == Win32Helper.SC_MINIMIZE) + { + SizeToContent = SizeToContent.Height; + handled = true; + } + break; } return IntPtr.Zero; @@ -1016,7 +1084,7 @@ namespace Flow.Launcher private void UpdateClockPanelVisibility() { - if (QueryTextBox == null || ContextMenu == null || History == null || ClockPanel == null) + if (QueryTextBox == null || ResultContextMenu == null || History == null || ClockPanel == null) { return; } @@ -1031,20 +1099,20 @@ namespace Flow.Launcher }; var animationDuration = TimeSpan.FromMilliseconds(animationLength * 2 / 3); - // ✅ Conditions for showing ClockPanel (No query input & ContextMenu, History are closed) + // ✅ Conditions for showing ClockPanel (No query input / ResultContextMenu & History are closed) var shouldShowClock = QueryTextBox.Text.Length == 0 && - ContextMenu.Visibility != Visibility.Visible && + ResultContextMenu.Visibility != Visibility.Visible && History.Visibility != Visibility.Visible; - // ✅ 1. When ContextMenu opens, immediately set Visibility.Hidden (force hide without animation) - if (ContextMenu.Visibility == Visibility.Visible) + // ✅ 1. When ResultContextMenu opens, immediately set Visibility.Hidden (force hide without animation) + if (ResultContextMenu.Visibility == Visibility.Visible) { _viewModel.ClockPanelVisibility = Visibility.Hidden; _viewModel.ClockPanelOpacity = 0.0; // Set to 0 in case Opacity animation affects it return; } - // ✅ 2. When ContextMenu is closed, keep it Hidden if there's text in the query (remember previous state) + // ✅ 2. When ResultContextMenu is closed, keep it Hidden if there's text in the query (remember previous state) else if (QueryTextBox.Text.Length > 0) { _viewModel.ClockPanelVisibility = Visibility.Hidden; diff --git a/Flow.Launcher/MessageBoxEx.xaml.cs b/Flow.Launcher/MessageBoxEx.xaml.cs index c084b5149..7296ff4ca 100644 --- a/Flow.Launcher/MessageBoxEx.xaml.cs +++ b/Flow.Launcher/MessageBoxEx.xaml.cs @@ -160,6 +160,7 @@ namespace Flow.Launcher private void KeyEsc_OnPress(object sender, ExecutedRoutedEventArgs e) { if (_button == MessageBoxButton.YesNo) + // Follow System.Windows.MessageBox behavior return; else if (_button == MessageBoxButton.OK) _result = MessageBoxResult.OK; @@ -188,6 +189,7 @@ namespace Flow.Launcher private void Button_Cancel(object sender, RoutedEventArgs e) { if (_button == MessageBoxButton.YesNo) + // Follow System.Windows.MessageBox behavior return; else if (_button == MessageBoxButton.OK) _result = MessageBoxResult.OK; diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index c06c56039..80d5de53d 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -251,7 +251,7 @@ namespace Flow.Launcher 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); + CancellationToken token = default) => Http.DownloadAsync(url, filePath, reportProgress, token); public void AddActionKeyword(string pluginId, string newActionKeyword) => PluginManager.AddActionKeyword(pluginId, newActionKeyword); @@ -399,13 +399,27 @@ namespace Flow.Launcher var path = browserInfo.Path == "*" ? "" : browserInfo.Path; - if (browserInfo.OpenInTab) + try { - uri.AbsoluteUri.OpenInBrowserTab(path, inPrivate ?? browserInfo.EnablePrivate, browserInfo.PrivateArg); + if (browserInfo.OpenInTab) + { + uri.AbsoluteUri.OpenInBrowserTab(path, inPrivate ?? browserInfo.EnablePrivate, browserInfo.PrivateArg); + } + else + { + uri.AbsoluteUri.OpenInBrowserWindow(path, inPrivate ?? browserInfo.EnablePrivate, browserInfo.PrivateArg); + } } - else + catch (Exception e) { - uri.AbsoluteUri.OpenInBrowserWindow(path, inPrivate ?? browserInfo.EnablePrivate, browserInfo.PrivateArg); + var tabOrWindow = browserInfo.OpenInTab ? "tab" : "window"; + LogException(ClassName, $"Failed to open URL in browser {tabOrWindow}: {path}, {inPrivate ?? browserInfo.EnablePrivate}, {browserInfo.PrivateArg}", e); + ShowMsgBox( + GetTranslation("browserOpenError"), + GetTranslation("errorTitle"), + MessageBoxButton.OK, + MessageBoxImage.Error + ); } } else diff --git a/Flow.Launcher/Resources/CustomControlTemplate.xaml b/Flow.Launcher/Resources/CustomControlTemplate.xaml index 08b239c41..cdaadd45a 100644 --- a/Flow.Launcher/Resources/CustomControlTemplate.xaml +++ b/Flow.Launcher/Resources/CustomControlTemplate.xaml @@ -2407,6 +2407,79 @@ + + + + + - @@ -163,653 +78,761 @@ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - + + + + + + + + + + + + + + + + + + + + - + - - - -