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/dotnet.yml b/.github/workflows/dotnet.yml
new file mode 100644
index 000000000..7498262de
--- /dev/null
+++ b/.github/workflows/dotnet.yml
@@ -0,0 +1,91 @@
+# This workflow will build a .NET project
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
+
+name: Build
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - dev
+ - master
+ pull_request:
+
+jobs:
+ build:
+
+ runs-on: windows-latest
+ env:
+ FlowVersion: 1.19.5
+ NUGET_CERT_REVOCATION_MODE: offline
+ BUILD_NUMBER: ${{ github.run_number }}
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set Flow.Launcher.csproj version
+ id: update
+ uses: vers-one/dotnet-project-version-updater@v1.7
+ with:
+ file: |
+ "**/SolutionAssemblyInfo.cs"
+ version: ${{ env.FlowVersion }}.${{ env.BUILD_NUMBER }}
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 7.0.x
+# cache: true
+# cache-dependency-path: |
+# Flow.Launcher/packages.lock.json
+# Flow.Launcher.Core/packages.lock.json
+# Flow.Launcher.Infrastructure/packages.lock.json
+# Flow.Launcher.Plugin/packages.lock.json
+ - name: Install vpk
+ run: dotnet tool install -g vpk
+ - name: Restore dependencies
+ run: nuget restore
+ - name: Build
+ run: dotnet build --no-restore -c Release
+ - name: Initialize Service
+ run: |
+ sc config WSearch start= auto # Starts Windows Search service- Needed for running ExplorerTest
+ net start WSearch
+ - name: Test
+ run: dotnet test --no-build --verbosity normal -c Release
+ - name: Perform post_build tasks
+ shell: powershell
+ run: .\Scripts\post_build.ps1
+ - name: Upload Plugin Nupkg
+ uses: actions/upload-artifact@v4
+ with:
+ name: Plugin nupkg
+ path: |
+ Output\Release\Flow.Launcher.Plugin.*.nupkg
+ compression-level: 0
+ - name: Upload Setup
+ uses: actions/upload-artifact@v4
+ with:
+ name: Flow Installer
+ path: |
+ Output\Packages\Flow-Launcher-*.exe
+ compression-level: 0
+ - name: Upload Portable Version
+ uses: actions/upload-artifact@v4
+ with:
+ name: Portable Version
+ path: |
+ Output\Packages\Flow-Launcher-Portable.zip
+ compression-level: 0
+ - name: Upload Full Nupkg
+ uses: actions/upload-artifact@v4
+ with:
+ name: Full nupkg
+ path: |
+ Output\Packages\FlowLauncher-*-full.nupkg
+
+ compression-level: 0
+ - name: Upload Release Information
+ uses: actions/upload-artifact@v4
+ with:
+ name: RELEASES
+ path: |
+ Output\Packages\RELEASES
+ compression-level: 0
diff --git a/.github/workflows/release_deploy.yml b/.github/workflows/release_deploy.yml
new file mode 100644
index 000000000..9e082b95f
--- /dev/null
+++ b/.github/workflows/release_deploy.yml
@@ -0,0 +1,34 @@
+---
+
+name: New Release Deployments
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+
+jobs:
+ deploy-website:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Trigger dispatch event for deploying website
+ run: |
+ http_status=$(curl -L -f -s -o /dev/null -w "%{http_code}" \
+ -X POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer ${{ secrets.DEPLOY_FLOW_WEBSITE }}" \
+ https://api.github.com/repos/Flow-Launcher/flow-launcher.github.io/dispatches \
+ -d '{"event_type":"deploy"}')
+ if [ "$http_status" -ne 204 ]; then echo "Error: Deploy website failed, HTTP status code is $http_status"; exit 1; fi
+
+ publish-chocolatey:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Trigger dispatch event for publishing to Chocolatey
+ run: |
+ http_status=$(curl -L -f -s -o /dev/null -w "%{http_code}" \
+ -X POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer ${{ secrets.Publish_Chocolatey }}" \
+ https://api.github.com/repos/Flow-Launcher/chocolatey-package/dispatches \
+ -d '{"event_type":"publish"}')
+ if [ "$http_status" -ne 204 ]; then echo "Error: Publish Chocolatey package failed, HTTP status code is $http_status"; exit 1; fi
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/.github/workflows/spelling.yml b/.github/workflows/spelling.yml
index 7aaa9296a..47bd66107 100644
--- a/.github/workflows/spelling.yml
+++ b/.github/workflows/spelling.yml
@@ -41,9 +41,8 @@ on:
# tags-ignore:
# - "**"
pull_request_target:
- branches:
- - '**'
- # - '!l10n_dev'
+ branches-ignore:
+ - master
tags-ignore:
- "**"
types:
diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs
index 2b570d2c0..7f02cef09 100644
--- a/Flow.Launcher.Core/Configuration/Portable.cs
+++ b/Flow.Launcher.Core/Configuration/Portable.cs
@@ -1,21 +1,22 @@
-using Microsoft.Win32;
-using Squirrel;
-using System;
+using System;
using System.IO;
+using System.Linq;
using System.Reflection;
using System.Windows;
-using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Infrastructure.Logger;
-using Flow.Launcher.Infrastructure.UserSettings;
-using Flow.Launcher.Plugin.SharedCommands;
-using System.Linq;
using CommunityToolkit.Mvvm.DependencyInjection;
+using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
+using Flow.Launcher.Plugin.SharedCommands;
+using Microsoft.Win32;
+using Squirrel;
namespace Flow.Launcher.Core.Configuration
{
public class Portable : IPortable
{
+ private static readonly string ClassName = nameof(Portable);
+
private readonly IPublicAPI API = Ioc.Default.GetRequiredService();
///
@@ -51,7 +52,7 @@ namespace Flow.Launcher.Core.Configuration
}
catch (Exception e)
{
- Log.Exception("|Portable.DisablePortableMode|Error occurred while disabling portable mode", e);
+ API.LogException(ClassName, "Error occurred while disabling portable mode", e);
}
}
@@ -75,7 +76,7 @@ namespace Flow.Launcher.Core.Configuration
}
catch (Exception e)
{
- Log.Exception("|Portable.EnablePortableMode|Error occurred while enabling portable mode", e);
+ API.LogException(ClassName, "Error occurred while enabling portable mode", e);
}
}
diff --git a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs
index e9713564e..6f3b23e11 100644
--- a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs
@@ -1,25 +1,32 @@
-using Flow.Launcher.Infrastructure.Http;
-using Flow.Launcher.Infrastructure.Logger;
-using Flow.Launcher.Plugin;
-using System;
+using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
+using System.Net.Sockets;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.DependencyInjection;
+using Flow.Launcher.Infrastructure.Http;
+using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.ExternalPlugins
{
public record CommunityPluginSource(string ManifestFileUrl)
{
+ private static readonly string ClassName = nameof(CommunityPluginSource);
+
+ // We should not initialize API in static constructor because it will create another API instance
+ private static IPublicAPI api = null;
+ private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService();
+
private string latestEtag = "";
private List plugins = new();
- private static JsonSerializerOptions PluginStoreItemSerializationOption = new JsonSerializerOptions()
+ private static readonly JsonSerializerOptions PluginStoreItemSerializationOption = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
};
@@ -34,35 +41,49 @@ namespace Flow.Launcher.Core.ExternalPlugins
///
public async Task> FetchAsync(CancellationToken token)
{
- Log.Info(nameof(CommunityPluginSource), $"Loading plugins from {ManifestFileUrl}");
+ API.LogInfo(ClassName, $"Loading plugins from {ManifestFileUrl}");
var request = new HttpRequestMessage(HttpMethod.Get, ManifestFileUrl);
request.Headers.Add("If-None-Match", latestEtag);
- using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token)
+ try
+ {
+ using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token)
.ConfigureAwait(false);
- if (response.StatusCode == HttpStatusCode.OK)
- {
- this.plugins = await response.Content
- .ReadFromJsonAsync>(PluginStoreItemSerializationOption, cancellationToken: token)
- .ConfigureAwait(false);
- this.latestEtag = response.Headers.ETag?.Tag;
+ if (response.StatusCode == HttpStatusCode.OK)
+ {
+ plugins = await response.Content
+ .ReadFromJsonAsync>(PluginStoreItemSerializationOption, cancellationToken: token)
+ .ConfigureAwait(false);
+ latestEtag = response.Headers.ETag?.Tag;
- Log.Info(nameof(CommunityPluginSource), $"Loaded {this.plugins.Count} plugins from {ManifestFileUrl}");
- return this.plugins;
+ API.LogInfo(ClassName, $"Loaded {plugins.Count} plugins from {ManifestFileUrl}");
+ return plugins;
+ }
+ else if (response.StatusCode == HttpStatusCode.NotModified)
+ {
+ API.LogInfo(ClassName, $"Resource {ManifestFileUrl} has not been modified.");
+ return plugins;
+ }
+ else
+ {
+ API.LogWarn(ClassName, $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
+ return null;
+ }
}
- else if (response.StatusCode == HttpStatusCode.NotModified)
+ catch (Exception e)
{
- Log.Info(nameof(CommunityPluginSource), $"Resource {ManifestFileUrl} has not been modified.");
- return this.plugins;
- }
- else
- {
- Log.Warn(nameof(CommunityPluginSource),
- $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
- throw new Exception($"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
+ if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)
+ {
+ API.LogException(ClassName, $"Check your connection and proxy settings to {ManifestFileUrl}.", e);
+ }
+ else
+ {
+ API.LogException(ClassName, "Error Occurred", e);
+ }
+ return null;
}
}
}
diff --git a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginStore.cs b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginStore.cs
index 1f23c2f66..bdc1ad3dd 100644
--- a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginStore.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginStore.cs
@@ -40,10 +40,14 @@ namespace Flow.Launcher.Core.ExternalPlugins
var completedTask = await Task.WhenAny(tasks);
if (completedTask.IsCompletedSuccessfully)
{
- // one of the requests completed successfully; keep its results
- // and cancel the remaining http requests.
- pluginResults = await completedTask;
- cts.Cancel();
+ var result = await completedTask;
+ if (result != null)
+ {
+ // one of the requests completed successfully; keep its results
+ // and cancel the remaining http requests.
+ pluginResults = result;
+ cts.Cancel();
+ }
}
tasks.Remove(completedTask);
}
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs
index bbb6cf638..14796a87a 100644
--- a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs
@@ -5,7 +5,6 @@ using System.Linq;
using System.Windows;
using System.Windows.Forms;
using CommunityToolkit.Mvvm.DependencyInjection;
-using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
@@ -14,6 +13,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
public abstract class AbstractPluginEnvironment
{
+ private static readonly string ClassName = nameof(AbstractPluginEnvironment);
+
protected readonly IPublicAPI API = Ioc.Default.GetRequiredService();
internal abstract string Language { get; }
@@ -120,7 +121,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
else
{
API.ShowMsgBox(string.Format(API.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
- Log.Error("PluginsLoader",
+ API.LogError(ClassName,
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
$"{Language}Environment");
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptEnvironment.cs
index b67059b1b..62d2d3e91 100644
--- a/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptEnvironment.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptEnvironment.cs
@@ -4,7 +4,6 @@ using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
-
internal class JavaScriptEnvironment : TypeScriptEnvironment
{
internal override string Language => AllowedLanguage.JavaScript;
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptV2Environment.cs
index 6c8c5aa57..726bc4cd4 100644
--- a/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptV2Environment.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptV2Environment.cs
@@ -4,7 +4,6 @@ using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
-
internal class JavaScriptV2Environment : TypeScriptV2Environment
{
internal override string Language => AllowedLanguage.JavaScriptV2;
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs
index fab5738de..455ee096d 100644
--- a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs
@@ -5,6 +5,7 @@ using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
+using Microsoft.VisualStudio.Threading;
namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
@@ -30,13 +31,15 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal PythonEnvironment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
+ private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());
+
internal override void InstallEnvironment()
{
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
// Python 3.11.4 is no longer Windows 7 compatible. If user is on Win 7 and
// uses Python plugin they need to custom install and use v3.8.9
- DroplexPackage.Drop(App.python_3_11_4_embeddable, InstallPath).Wait();
+ JTF.Run(() => DroplexPackage.Drop(App.python_3_11_4_embeddable, InstallPath));
PluginsSettingsFilePath = ExecutablePath;
}
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs
index 8a4f527ba..12965286f 100644
--- a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs
@@ -5,6 +5,7 @@ using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
+using Microsoft.VisualStudio.Threading;
namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
@@ -27,11 +28,13 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal TypeScriptEnvironment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
+ private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());
+
internal override void InstallEnvironment()
{
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
- DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
+ JTF.Run(() => DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath));
PluginsSettingsFilePath = ExecutablePath;
}
diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs
index 61fd28376..6960b79c9 100644
--- a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptV2Environment.cs
@@ -5,6 +5,7 @@ using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
+using Microsoft.VisualStudio.Threading;
namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
@@ -27,11 +28,13 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal TypeScriptV2Environment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
+ private JoinableTaskFactory JTF { get; } = new JoinableTaskFactory(new JoinableTaskContext());
+
internal override void InstallEnvironment()
{
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
- DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
+ JTF.Run(() => DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath));
PluginsSettingsFilePath = ExecutablePath;
}
diff --git a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs
index 44d3ef0ff..7ca91eaec 100644
--- a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs
+++ b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs
@@ -9,6 +9,8 @@ namespace Flow.Launcher.Core.ExternalPlugins
{
public static class PluginsManifest
{
+ private static readonly string ClassName = nameof(PluginsManifest);
+
private static readonly CommunityPluginStore mainPluginStore =
new("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/plugin_api_v2/plugins.json",
"https://fastly.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json",
@@ -44,7 +46,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
}
catch (Exception e)
{
- Ioc.Default.GetRequiredService().LogException(nameof(PluginsManifest), "Http request failed", e);
+ Ioc.Default.GetRequiredService().LogException(ClassName, "Http request failed", e);
}
finally
{
diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
index e0a217251..435d97ab7 100644
--- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
+++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs
@@ -12,7 +12,7 @@ using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin
{
- public class JsonRPCPluginSettings
+ public class JsonRPCPluginSettings : ISavable
{
public required JsonRpcConfigurationModel? Configuration { get; init; }
@@ -113,7 +113,7 @@ namespace Flow.Launcher.Core.Plugin
// If can parse the default value to bool, use it, otherwise use false
: value is string stringValue && bool.TryParse(stringValue, out var boolValueFromString)
&& boolValueFromString;
- checkBox.Dispatcher.Invoke(() =>checkBox.IsChecked = isChecked);
+ checkBox.Dispatcher.Invoke(() => checkBox.IsChecked = isChecked);
break;
}
}
@@ -154,8 +154,7 @@ namespace Flow.Launcher.Core.Plugin
public Control CreateSettingPanel()
{
- // No need to check if NeedCreateSettingPanel is true because CreateSettingPanel will only be called if it's true
- // if (!NeedCreateSettingPanel()) return null;
+ if (!NeedCreateSettingPanel()) return null!;
// Create main grid with two columns (Column 1: Auto, Column 2: *)
var mainPanel = new Grid { Margin = SettingPanelMargin, VerticalAlignment = VerticalAlignment.Center };
diff --git a/Flow.Launcher.Core/Plugin/PluginConfig.cs b/Flow.Launcher.Core/Plugin/PluginConfig.cs
index 163f97046..f7457b4e1 100644
--- a/Flow.Launcher.Core/Plugin/PluginConfig.cs
+++ b/Flow.Launcher.Core/Plugin/PluginConfig.cs
@@ -3,14 +3,20 @@ using System.Collections.Generic;
using System.Linq;
using System.IO;
using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin;
using System.Text.Json;
+using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Core.Plugin
{
internal abstract class PluginConfig
{
+ private static readonly string ClassName = nameof(PluginConfig);
+
+ // We should not initialize API in static constructor because it will create another API instance
+ private static IPublicAPI api = null;
+ private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService();
+
///
/// Parse plugin metadata in the given directories
///
@@ -32,7 +38,7 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
- Log.Exception($"|PluginConfig.ParsePLuginConfigs|Can't delete <{directory}>", e);
+ API.LogException(ClassName, $"Can't delete <{directory}>", e);
}
}
else
@@ -49,11 +55,11 @@ namespace Flow.Launcher.Core.Plugin
duplicateList
.ForEach(
- x => Log.Warn("PluginConfig",
- string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " +
- "not loaded due to version not the highest of the duplicates",
- x.Name, x.ID, x.Version),
- "GetUniqueLatestPluginMetadata"));
+ x => API.LogWarn(ClassName,
+ string.Format("Duplicate plugin name: {0}, id: {1}, version: {2} " +
+ "not loaded due to version not the highest of the duplicates",
+ x.Name, x.ID, x.Version),
+ "GetUniqueLatestPluginMetadata"));
return uniqueList;
}
@@ -101,7 +107,7 @@ namespace Flow.Launcher.Core.Plugin
string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName);
if (!File.Exists(configPath))
{
- Log.Error($"|PluginConfig.GetPluginMetadata|Didn't find config file <{configPath}>");
+ API.LogError(ClassName, $"Didn't find config file <{configPath}>");
return null;
}
@@ -117,19 +123,19 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
- Log.Exception($"|PluginConfig.GetPluginMetadata|invalid json for config <{configPath}>", e);
+ API.LogException(ClassName, $"Invalid json for config <{configPath}>", e);
return null;
}
if (!AllowedLanguage.IsAllowed(metadata.Language))
{
- Log.Error($"|PluginConfig.GetPluginMetadata|Invalid language <{metadata.Language}> for config <{configPath}>");
+ API.LogError(ClassName, $"Invalid language <{metadata.Language}> for config <{configPath}>");
return null;
}
if (!File.Exists(metadata.ExecuteFilePath))
{
- Log.Error($"|PluginConfig.GetPluginMetadata|execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}");
+ API.LogError(ClassName, $"Execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}");
return null;
}
diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs
index 29d91dc8d..9b525f331 100644
--- a/Flow.Launcher.Core/Plugin/PluginManager.cs
+++ b/Flow.Launcher.Core/Plugin/PluginManager.cs
@@ -9,7 +9,6 @@ using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
@@ -26,6 +25,7 @@ namespace Flow.Launcher.Core.Plugin
private static readonly string ClassName = nameof(PluginManager);
private static IEnumerable _contextMenuPlugins;
+ private static IEnumerable _homePlugins;
public static List AllPlugins { get; private set; }
public static readonly HashSet GlobalPlugins = new();
@@ -37,7 +37,7 @@ namespace Flow.Launcher.Core.Plugin
private static PluginsSettings Settings;
private static List _metadatas;
- private static List _modifiedPlugins = new();
+ private static readonly List _modifiedPlugins = new();
///
/// Directories that will hold Flow Launcher plugin directory
@@ -61,10 +61,17 @@ namespace Flow.Launcher.Core.Plugin
///
public static void Save()
{
- foreach (var plugin in AllPlugins)
+ foreach (var pluginPair in AllPlugins)
{
- var savable = plugin.Plugin as ISavable;
- savable?.Save();
+ var savable = pluginPair.Plugin as ISavable;
+ try
+ {
+ savable?.Save();
+ }
+ catch (Exception e)
+ {
+ API.LogException(ClassName, $"Failed to save plugin {pluginPair.Metadata.Name}", e);
+ }
}
API.SavePluginSettings();
@@ -81,14 +88,21 @@ namespace Flow.Launcher.Core.Plugin
private static async Task DisposePluginAsync(PluginPair pluginPair)
{
- switch (pluginPair.Plugin)
+ try
{
- case IDisposable disposable:
- disposable.Dispose();
- break;
- case IAsyncDisposable asyncDisposable:
- await asyncDisposable.DisposeAsync();
- break;
+ switch (pluginPair.Plugin)
+ {
+ case IDisposable disposable:
+ disposable.Dispose();
+ break;
+ case IAsyncDisposable asyncDisposable:
+ await asyncDisposable.DisposeAsync();
+ break;
+ }
+ }
+ catch (Exception e)
+ {
+ API.LogException(ClassName, $"Failed to dispose plugin {pluginPair.Metadata.Name}", e);
}
}
@@ -173,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);
}
@@ -200,20 +224,33 @@ namespace Flow.Launcher.Core.Plugin
() => pair.Plugin.InitAsync(new PluginInitContext(pair.Metadata, API)));
pair.Metadata.InitTime += milliseconds;
- Log.Info(
- $"|PluginManager.InitializePlugins|Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>");
+ API.LogInfo(ClassName,
+ $"Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>");
}
catch (Exception e)
{
- Log.Exception(ClassName, $"Fail to Init plugin: {pair.Metadata.Name}", e);
- pair.Metadata.Disabled = true;
- failedPlugins.Enqueue(pair);
+ API.LogException(ClassName, $"Fail to Init plugin: {pair.Metadata.Name}", e);
+ if (pair.Metadata.Disabled && pair.Metadata.HomeDisabled)
+ {
+ // If this plugin is already disabled, do not show error message again
+ // Or else it will be shown every time
+ API.LogDebug(ClassName, $"Skipped init for <{pair.Metadata.Name}> due to error");
+ }
+ else
+ {
+ pair.Metadata.Disabled = true;
+ pair.Metadata.HomeDisabled = true;
+ failedPlugins.Enqueue(pair);
+ API.LogDebug(ClassName, $"Disable plugin <{pair.Metadata.Name}> because init failed");
+ }
}
}));
await Task.WhenAll(InitTasks);
_contextMenuPlugins = GetPluginsForInterface();
+ _homePlugins = GetPluginsForInterface();
+
foreach (var plugin in AllPlugins)
{
// set distinct on each plugin's action keywords helps only firing global(*) and action keywords once where a plugin
@@ -261,6 +298,11 @@ namespace Flow.Launcher.Core.Plugin
};
}
+ public static ICollection ValidPluginsForHomeQuery()
+ {
+ return _homePlugins.ToList();
+ }
+
public static async Task> QueryForPluginAsync(PluginPair pair, Query query, CancellationToken token)
{
var results = new List();
@@ -292,7 +334,7 @@ namespace Flow.Launcher.Core.Plugin
{
Title = $"{metadata.Name}: Failed to respond!",
SubTitle = "Select this result for more info",
- IcoPath = Flow.Launcher.Infrastructure.Constant.ErrorIcon,
+ IcoPath = Constant.ErrorIcon,
PluginDirectory = metadata.PluginDirectory,
ActionKeywordAssigned = query.ActionKeyword,
PluginID = metadata.ID,
@@ -305,6 +347,36 @@ namespace Flow.Launcher.Core.Plugin
return results;
}
+ public static async Task> QueryHomeForPluginAsync(PluginPair pair, Query query, CancellationToken token)
+ {
+ var results = new List();
+ var metadata = pair.Metadata;
+
+ try
+ {
+ var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}",
+ async () => results = await ((IAsyncHomeQuery)pair.Plugin).HomeQueryAsync(token).ConfigureAwait(false));
+
+ token.ThrowIfCancellationRequested();
+ if (results == null)
+ return null;
+ UpdatePluginMetadata(results, metadata, query);
+
+ token.ThrowIfCancellationRequested();
+ }
+ catch (OperationCanceledException)
+ {
+ // null will be fine since the results will only be added into queue if the token hasn't been cancelled
+ return null;
+ }
+ catch (Exception e)
+ {
+ API.LogException(ClassName, $"Failed to query home for plugin: {metadata.Name}", e);
+ return null;
+ }
+ return results;
+ }
+
public static void UpdatePluginMetadata(IReadOnlyList results, PluginMetadata metadata, Query query)
{
foreach (var r in results)
@@ -356,8 +428,8 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
- Log.Exception(
- $"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{pluginPair.Metadata.Name}>",
+ API.LogException(ClassName,
+ $"Can't load context menus for plugin <{pluginPair.Metadata.Name}>",
e);
}
}
@@ -365,12 +437,17 @@ namespace Flow.Launcher.Core.Plugin
return results;
}
+ public static bool IsHomePlugin(string id)
+ {
+ return _homePlugins.Any(p => p.Metadata.ID == id);
+ }
+
public static bool ActionKeywordRegistered(string actionKeyword)
{
// this method is only checking for action keywords (defined as not '*') registration
// hence the actionKeyword != Query.GlobalPluginWildcardSign logic
- return actionKeyword != Query.GlobalPluginWildcardSign
- && NonGlobalPlugins.ContainsKey(actionKeyword);
+ return actionKeyword != Query.GlobalPluginWildcardSign
+ && NonGlobalPlugins.ContainsKey(actionKeyword);
}
///
@@ -549,7 +626,7 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
- Log.Exception($"|PluginManager.InstallPlugin|Failed to delete temp folder {tempFolderPluginPath}", e);
+ API.LogException(ClassName, $"Failed to delete temp folder {tempFolderPluginPath}", e);
}
if (checkModified)
@@ -594,7 +671,7 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
- Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin settings folder for {plugin.Name}", e);
+ API.LogException(ClassName, $"Failed to delete plugin settings folder for {plugin.Name}", e);
API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
}
@@ -610,7 +687,7 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
- Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin cache folder for {plugin.Name}", e);
+ API.LogException(ClassName, $"Failed to delete plugin cache folder for {plugin.Name}", e);
API.ShowMsg(API.GetTranslation("failedToRemovePluginCacheTitle"),
string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name));
}
diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
index 1010d9f08..256c36065 100644
--- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs
+++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs
@@ -89,19 +89,19 @@ namespace Flow.Launcher.Core.Plugin
#else
catch (Exception e) when (assembly == null)
{
- Log.Exception($"|PluginsLoader.DotNetPlugins|Couldn't load assembly for the plugin: {metadata.Name}", e);
+ Log.Exception(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e);
}
catch (InvalidOperationException e)
{
- Log.Exception($"|PluginsLoader.DotNetPlugins|Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
+ Log.Exception(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
}
catch (ReflectionTypeLoadException e)
{
- Log.Exception($"|PluginsLoader.DotNetPlugins|The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
+ Log.Exception(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
}
catch (Exception e)
{
- Log.Exception($"|PluginsLoader.DotNetPlugins|The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
+ Log.Exception(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
}
#endif
diff --git a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
index bae263157..7a6bf07e2 100644
--- a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
+++ b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs
@@ -1,21 +1,19 @@
-#nullable enable
-
-using System;
-using System.Collections.Generic;
+using System;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin;
using Meziantou.Framework.Win32;
-using Microsoft.VisualBasic.ApplicationServices;
using Nerdbank.Streams;
+#nullable enable
+
namespace Flow.Launcher.Core.Plugin
{
internal abstract class ProcessStreamPluginV2 : JsonRPCPluginV2
{
- private static JobObject _jobObject = new JobObject();
+ private static readonly JobObject _jobObject = new();
static ProcessStreamPluginV2()
{
@@ -66,11 +64,10 @@ namespace Flow.Launcher.Core.Plugin
ClientPipe = new DuplexPipe(reader, writer);
}
-
public override async Task ReloadDataAsync()
{
var oldProcess = ClientProcess;
- ClientProcess = Process.Start(StartInfo);
+ ClientProcess = Process.Start(StartInfo)!;
ArgumentNullException.ThrowIfNull(ClientProcess);
SetupPipe(ClientProcess);
await base.ReloadDataAsync();
@@ -79,7 +76,6 @@ namespace Flow.Launcher.Core.Plugin
oldProcess.Dispose();
}
-
public override async ValueTask DisposeAsync()
{
await base.DisposeAsync();
diff --git a/Flow.Launcher.Core/Plugin/QueryBuilder.cs b/Flow.Launcher.Core/Plugin/QueryBuilder.cs
index 3dc7877ac..25a32a728 100644
--- a/Flow.Launcher.Core/Plugin/QueryBuilder.cs
+++ b/Flow.Launcher.Core/Plugin/QueryBuilder.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using Flow.Launcher.Plugin;
@@ -8,10 +8,24 @@ namespace Flow.Launcher.Core.Plugin
{
public static Query Build(string text, Dictionary nonGlobalPlugins)
{
+ // home query
+ if (string.IsNullOrEmpty(text))
+ {
+ return new Query()
+ {
+ Search = string.Empty,
+ RawQuery = string.Empty,
+ SearchTerms = Array.Empty(),
+ ActionKeyword = string.Empty,
+ IsHomeQuery = true
+ };
+ }
+
// replace multiple white spaces with one white space
var terms = text.Split(Query.TermSeparator, StringSplitOptions.RemoveEmptyEntries);
if (terms.Length == 0)
- { // nothing was typed
+ {
+ // nothing was typed
return null;
}
@@ -21,25 +35,28 @@ namespace Flow.Launcher.Core.Plugin
string[] searchTerms;
if (nonGlobalPlugins.TryGetValue(possibleActionKeyword, out var pluginPair) && !pluginPair.Metadata.Disabled)
- { // use non global plugin for query
+ {
+ // use non global plugin for query
actionKeyword = possibleActionKeyword;
search = terms.Length > 1 ? rawQuery[(actionKeyword.Length + 1)..].TrimStart() : string.Empty;
searchTerms = terms[1..];
}
else
- { // non action keyword
+ {
+ // non action keyword
actionKeyword = string.Empty;
search = rawQuery.TrimStart();
searchTerms = terms;
}
- return new Query ()
+ return new Query()
{
Search = search,
RawQuery = rawQuery,
SearchTerms = searchTerms,
- ActionKeyword = actionKeyword
+ ActionKeyword = actionKeyword,
+ IsHomeQuery = false
};
}
}
-}
\ No newline at end of file
+}
diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs
index ffa17ab4d..b32b09e8f 100644
--- a/Flow.Launcher.Core/Resource/Internationalization.cs
+++ b/Flow.Launcher.Core/Resource/Internationalization.cs
@@ -6,7 +6,6 @@ using System.Reflection;
using System.Windows;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using System.Globalization;
@@ -17,13 +16,19 @@ namespace Flow.Launcher.Core.Resource
{
public class Internationalization
{
+ private static readonly string ClassName = nameof(Internationalization);
+
+ // We should not initialize API in static constructor because it will create another API instance
+ private static IPublicAPI api = null;
+ private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService();
+
private const string Folder = "Languages";
private const string DefaultLanguageCode = "en";
private const string DefaultFile = "en.xaml";
private const string Extension = ".xaml";
private readonly Settings _settings;
- private readonly List _languageDirectories = new List();
- private readonly List _oldResources = new List();
+ private readonly List _languageDirectories = new();
+ private readonly List _oldResources = new();
private readonly string SystemLanguageCode;
public Internationalization(Settings settings)
@@ -80,7 +85,7 @@ namespace Flow.Launcher.Core.Resource
}
else
{
- Log.Error($"|Internationalization.AddPluginLanguageDirectories|Can't find plugin path <{location}> for <{plugin.Metadata.Name}>");
+ API.LogError(ClassName, $"Can't find plugin path <{location}> for <{plugin.Metadata.Name}>");
}
}
@@ -144,13 +149,13 @@ namespace Flow.Launcher.Core.Resource
_settings.Language = isSystem ? Constant.SystemLanguageCode : language.LanguageCode;
}
- private Language GetLanguageByLanguageCode(string languageCode)
+ private static Language GetLanguageByLanguageCode(string languageCode)
{
var lowercase = languageCode.ToLower();
var language = AvailableLanguages.GetAvailableLanguages().FirstOrDefault(o => o.LanguageCode.ToLower() == lowercase);
if (language == null)
{
- Log.Error($"|Internationalization.GetLanguageByLanguageCode|Language code can't be found <{languageCode}>");
+ API.LogError(ClassName, $"Language code can't be found <{languageCode}>");
return AvailableLanguages.English;
}
else
@@ -239,7 +244,7 @@ namespace Flow.Launcher.Core.Resource
return list;
}
- public string GetTranslation(string key)
+ public static string GetTranslation(string key)
{
var translation = Application.Current.TryFindResource(key);
if (translation is string)
@@ -248,7 +253,7 @@ namespace Flow.Launcher.Core.Resource
}
else
{
- Log.Error($"|Internationalization.GetTranslation|No Translation for key {key}");
+ API.LogError(ClassName, $"No Translation for key {key}");
return $"No Translation for key {key}";
}
}
@@ -257,8 +262,7 @@ namespace Flow.Launcher.Core.Resource
{
foreach (var p in PluginManager.GetPluginsForInterface())
{
- var pluginI18N = p.Plugin as IPluginI18n;
- if (pluginI18N == null) return;
+ if (p.Plugin is not IPluginI18n pluginI18N) return;
try
{
p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle();
@@ -267,31 +271,31 @@ namespace Flow.Launcher.Core.Resource
}
catch (Exception e)
{
- Log.Exception($"|Internationalization.UpdatePluginMetadataTranslations|Failed for <{p.Metadata.Name}>", e);
+ API.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e);
}
}
}
- public string LanguageFile(string folder, string language)
+ private static string LanguageFile(string folder, string language)
{
if (Directory.Exists(folder))
{
- string path = Path.Combine(folder, language);
+ var path = Path.Combine(folder, language);
if (File.Exists(path))
{
return path;
}
else
{
- Log.Error($"|Internationalization.LanguageFile|Language path can't be found <{path}>");
- string english = Path.Combine(folder, DefaultFile);
+ API.LogError(ClassName, $"Language path can't be found <{path}>");
+ var english = Path.Combine(folder, DefaultFile);
if (File.Exists(english))
{
return english;
}
else
{
- Log.Error($"|Internationalization.LanguageFile|Default English Language path can't be found <{path}>");
+ API.LogError(ClassName, $"Default English Language path can't be found <{path}>");
return string.Empty;
}
}
diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs
index 59e76e2d2..a6e8dc6bf 100644
--- a/Flow.Launcher.Core/Resource/Theme.cs
+++ b/Flow.Launcher.Core/Resource/Theme.cs
@@ -13,7 +13,6 @@ using System.Windows.Media.Effects;
using System.Windows.Shell;
using System.Windows.Threading;
using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
@@ -25,6 +24,8 @@ namespace Flow.Launcher.Core.Resource
{
#region Properties & Fields
+ private readonly string ClassName = nameof(Theme);
+
public bool BlurEnabled { get; private set; }
private const string ThemeMetadataNamePrefix = "Name:";
@@ -73,9 +74,9 @@ namespace Flow.Launcher.Core.Resource
}
else
{
- Log.Error("Current theme resource not found. Initializing with default theme.");
+ _api.LogError(ClassName, "Current theme resource not found. Initializing with default theme.");
_oldTheme = Constant.DefaultTheme;
- };
+ }
}
#endregion
@@ -92,7 +93,7 @@ namespace Flow.Launcher.Core.Resource
}
catch (Exception e)
{
- Log.Exception($"|Theme.MakesureThemeDirectoriesExist|Exception when create directory <{dir}>", e);
+ _api.LogException(ClassName, $"Exception when create directory <{dir}>", e);
}
}
}
@@ -125,7 +126,7 @@ namespace Flow.Launcher.Core.Resource
// Load a ResourceDictionary for the specified theme.
var themeName = _settings.Theme;
var dict = GetThemeResourceDictionary(themeName);
-
+
// Apply font settings to the theme resource.
ApplyFontSettings(dict);
UpdateResourceDictionary(dict);
@@ -135,7 +136,7 @@ namespace Flow.Launcher.Core.Resource
}
catch (Exception e)
{
- Log.Exception("Error occurred while updating theme fonts", e);
+ _api.LogException(ClassName, "Error occurred while updating theme fonts", e);
}
}
@@ -151,11 +152,11 @@ namespace Flow.Launcher.Core.Resource
var fontStyle = FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.QueryBoxFontStyle);
var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.QueryBoxFontWeight);
var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.QueryBoxFontStretch);
-
+
SetFontProperties(queryBoxStyle, fontFamily, fontStyle, fontWeight, fontStretch, true);
SetFontProperties(querySuggestionBoxStyle, fontFamily, fontStyle, fontWeight, fontStretch, false);
}
-
+
if (dict["ItemTitleStyle"] is Style resultItemStyle &&
dict["ItemTitleSelectedStyle"] is Style resultItemSelectedStyle &&
dict["ItemHotkeyStyle"] is Style resultHotkeyItemStyle &&
@@ -171,7 +172,7 @@ namespace Flow.Launcher.Core.Resource
SetFontProperties(resultHotkeyItemStyle, fontFamily, fontStyle, fontWeight, fontStretch, false);
SetFontProperties(resultHotkeyItemSelectedStyle, fontFamily, fontStyle, fontWeight, fontStretch, false);
}
-
+
if (dict["ItemSubTitleStyle"] is Style resultSubItemStyle &&
dict["ItemSubTitleSelectedStyle"] is Style resultSubItemSelectedStyle)
{
@@ -196,7 +197,7 @@ namespace Flow.Launcher.Core.Resource
// First, find the setters to remove and store them in a list
var settersToRemove = style.Setters
.OfType()
- .Where(setter =>
+ .Where(setter =>
setter.Property == Control.FontFamilyProperty ||
setter.Property == Control.FontStyleProperty ||
setter.Property == Control.FontWeightProperty ||
@@ -226,18 +227,18 @@ namespace Flow.Launcher.Core.Resource
{
var settersToRemove = style.Setters
.OfType()
- .Where(setter =>
+ .Where(setter =>
setter.Property == TextBlock.FontFamilyProperty ||
setter.Property == TextBlock.FontStyleProperty ||
setter.Property == TextBlock.FontWeightProperty ||
setter.Property == TextBlock.FontStretchProperty)
.ToList();
-
+
foreach (var setter in settersToRemove)
{
style.Setters.Remove(setter);
}
-
+
style.Setters.Add(new Setter(TextBlock.FontFamilyProperty, fontFamily));
style.Setters.Add(new Setter(TextBlock.FontStyleProperty, fontStyle));
style.Setters.Add(new Setter(TextBlock.FontWeightProperty, fontWeight));
@@ -324,7 +325,7 @@ namespace Flow.Launcher.Core.Resource
return dict;
}
- private ResourceDictionary GetCurrentResourceDictionary()
+ public ResourceDictionary GetCurrentResourceDictionary()
{
return GetResourceDictionary(_settings.Theme);
}
@@ -387,7 +388,7 @@ namespace Flow.Launcher.Core.Resource
var matchingTheme = themes.FirstOrDefault(t => t.FileNameWithoutExtension == _settings.Theme);
if (matchingTheme == null)
{
- Log.Warn($"No matching theme found for '{_settings.Theme}'. Falling back to the first available theme.");
+ _api.LogWarn(ClassName, $"No matching theme found for '{_settings.Theme}'. Falling back to the first available theme.");
}
return matchingTheme ?? themes.FirstOrDefault();
}
@@ -420,7 +421,7 @@ namespace Flow.Launcher.Core.Resource
// Retrieve theme resource – always use the resource with font settings applied.
var resourceDict = GetResourceDictionary(theme);
-
+
UpdateResourceDictionary(resourceDict);
_settings.Theme = theme;
@@ -440,7 +441,7 @@ namespace Flow.Launcher.Core.Resource
}
catch (DirectoryNotFoundException)
{
- Log.Error($"|Theme.ChangeTheme|Theme <{theme}> path can't be found");
+ _api.LogError(ClassName, $"Theme <{theme}> path can't be found");
if (theme != Constant.DefaultTheme)
{
_api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_path_not_exists"), theme));
@@ -450,7 +451,7 @@ namespace Flow.Launcher.Core.Resource
}
catch (XamlParseException)
{
- Log.Error($"|Theme.ChangeTheme|Theme <{theme}> fail to parse");
+ _api.LogError(ClassName, $"Theme <{theme}> fail to parse");
if (theme != Constant.DefaultTheme)
{
_api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_parse_error"), theme));
@@ -670,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);
@@ -771,22 +780,18 @@ namespace Flow.Launcher.Core.Resource
{
if (bgColor == null) return;
- // Copy the existing WindowBorderStyle
+ // Create a new Style for the preview
var previewStyle = new Style(typeof(Border));
- if (Application.Current.Resources.Contains("WindowBorderStyle"))
+
+ // Get the original WindowBorderStyle
+ if (Application.Current.Resources.Contains("WindowBorderStyle") &&
+ Application.Current.Resources["WindowBorderStyle"] is Style originalStyle)
{
- if (Application.Current.Resources["WindowBorderStyle"] is Style originalStyle)
- {
- foreach (var setter in originalStyle.Setters.OfType())
- {
- previewStyle.Setters.Add(new Setter(setter.Property, setter.Value));
- }
- }
+ // Copy the original style, including the base style if it exists
+ CopyStyle(originalStyle, previewStyle);
}
// Apply background color (remove transparency in color)
- // WPF does not allow the use of an acrylic brush within the window's internal area,
- // so transparency effects are not applied to the preview.
Color backgroundColor = Color.FromRgb(bgColor.Value.R, bgColor.Value.G, bgColor.Value.B);
previewStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(backgroundColor)));
@@ -797,9 +802,26 @@ namespace Flow.Launcher.Core.Resource
previewStyle.Setters.Add(new Setter(Border.CornerRadiusProperty, new CornerRadius(5)));
previewStyle.Setters.Add(new Setter(Border.BorderThicknessProperty, new Thickness(1)));
}
+
+ // Set the new style to the resource
Application.Current.Resources["PreviewWindowBorderStyle"] = previewStyle;
}
+ private void CopyStyle(Style originalStyle, Style targetStyle)
+ {
+ // If the style is based on another style, copy the base style first
+ if (originalStyle.BasedOn != null)
+ {
+ CopyStyle(originalStyle.BasedOn, targetStyle);
+ }
+
+ // Copy the setters from the original style
+ foreach (var setter in originalStyle.Setters.OfType())
+ {
+ targetStyle.Setters.Add(new Setter(setter.Property, setter.Value));
+ }
+ }
+
private void ColorizeWindow(string theme, BackdropTypes backdropType)
{
var dict = GetThemeResourceDictionary(theme);
diff --git a/Flow.Launcher.Core/Resource/TranslationConverter.cs b/Flow.Launcher.Core/Resource/TranslationConverter.cs
deleted file mode 100644
index eb0032758..000000000
--- a/Flow.Launcher.Core/Resource/TranslationConverter.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-using System.Globalization;
-using System.Windows.Data;
-using CommunityToolkit.Mvvm.DependencyInjection;
-using Flow.Launcher.Plugin;
-
-namespace Flow.Launcher.Core.Resource
-{
- public class TranslationConverter : IValueConverter
- {
- // We should not initialize API in static constructor because it will create another API instance
- private static IPublicAPI api = null;
- private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService();
-
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- {
- var key = value.ToString();
- if (string.IsNullOrEmpty(key)) return key;
- return API.GetTranslation(key);
- }
-
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
- throw new InvalidOperationException();
- }
-}
diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs
index 83d4fd9e0..bc3655f69 100644
--- a/Flow.Launcher.Core/Updater.cs
+++ b/Flow.Launcher.Core/Updater.cs
@@ -12,7 +12,6 @@ using System.Windows;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http;
-using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using JetBrains.Annotations;
@@ -24,6 +23,8 @@ namespace Flow.Launcher.Core
{
public string GitHubRepository { get; init; }
+ private static readonly string ClassName = nameof(Updater);
+
private readonly IPublicAPI _api;
public Updater(IPublicAPI publicAPI, string gitHubRepository)
@@ -51,7 +52,7 @@ namespace Flow.Launcher.Core
var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
var currentVersion = Version.Parse(Constant.Version);
- Log.Info($"|Updater.UpdateApp|Future Release <{Formatted(newUpdateInfo.FutureReleaseEntry)}>");
+ _api.LogInfo(ClassName, $"Future Release <{Formatted(newUpdateInfo.FutureReleaseEntry)}>");
if (newReleaseVersion <= currentVersion)
{
@@ -84,7 +85,7 @@ namespace Flow.Launcher.Core
var newVersionTips = NewVersionTips(newReleaseVersion.ToString());
- Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
+ _api.LogInfo(ClassName, $"Update success:{newVersionTips}");
if (_api.ShowMsgBox(newVersionTips, _api.GetTranslation("update_flowlauncher_new_update"), MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
@@ -93,10 +94,14 @@ namespace Flow.Launcher.Core
}
catch (Exception e)
{
- if ((e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException))
- Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
+ if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)
+ {
+ _api.LogException(ClassName, $"Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
+ }
else
- Log.Exception($"|Updater.UpdateApp|Error Occurred", e);
+ {
+ _api.LogException(ClassName, $"Error Occurred", e);
+ }
if (!silentUpdate)
_api.ShowMsg(_api.GetTranslation("update_flowlauncher_fail"),
diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
index f02b2297f..31547200b 100644
--- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
+++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
@@ -66,7 +66,9 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+ all
+
diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs
index 030aff7cf..22eb065f5 100644
--- a/Flow.Launcher.Infrastructure/Http/Http.cs
+++ b/Flow.Launcher.Infrastructure/Http/Http.cs
@@ -1,30 +1,31 @@
-using System.IO;
+using System;
+using System.IO;
using System.Net;
using System.Net.Http;
+using System.Threading;
using System.Threading.Tasks;
-using JetBrains.Annotations;
+using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
-using System;
-using System.Threading;
using Flow.Launcher.Plugin;
-using CommunityToolkit.Mvvm.DependencyInjection;
+using JetBrains.Annotations;
namespace Flow.Launcher.Infrastructure.Http
{
public static class Http
{
+ private static readonly string ClassName = nameof(Http);
+
private const string UserAgent = @"Mozilla/5.0 (Trident/7.0; rv:11.0) like Gecko";
- private static HttpClient client = new HttpClient();
+ private static readonly HttpClient client = new();
static Http()
{
// need to be added so it would work on a win10 machine
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls
- | SecurityProtocolType.Tls11
- | SecurityProtocolType.Tls12;
+ | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
client.DefaultRequestHeaders.Add("User-Agent", UserAgent);
HttpClient.DefaultProxy = WebProxy;
@@ -34,7 +35,7 @@ namespace Flow.Launcher.Infrastructure.Http
public static HttpProxy Proxy
{
- private get { return proxy; }
+ private get => proxy;
set
{
proxy = value;
@@ -72,13 +73,13 @@ namespace Flow.Launcher.Infrastructure.Http
ProxyProperty.Port => (new Uri($"http://{Proxy.Server}:{Proxy.Port}"), WebProxy.Credentials),
ProxyProperty.UserName => (WebProxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)),
ProxyProperty.Password => (WebProxy.Address, new NetworkCredential(Proxy.UserName, Proxy.Password)),
- _ => throw new ArgumentOutOfRangeException()
+ _ => throw new ArgumentOutOfRangeException(null)
};
}
catch (UriFormatException e)
{
Ioc.Default.GetRequiredService().ShowMsg("Please try again", "Unable to parse Http Proxy");
- Log.Exception("Flow.Launcher.Infrastructure.Http", "Unable to parse Uri", e);
+ Log.Exception(ClassName, "Unable to parse Uri", e);
}
}
@@ -134,7 +135,7 @@ namespace Flow.Launcher.Infrastructure.Http
}
catch (HttpRequestException e)
{
- Log.Exception("Infrastructure.Http", "Http Request Error", e, "DownloadAsync");
+ Log.Exception(ClassName, "Http Request Error", e, "DownloadAsync");
throw;
}
}
@@ -147,7 +148,7 @@ namespace Flow.Launcher.Infrastructure.Http
/// The Http result as string. Null if cancellation requested
public static Task GetAsync([NotNull] string url, CancellationToken token = default)
{
- Log.Debug($"|Http.Get|Url <{url}>");
+ Log.Debug(ClassName, $"Url <{url}>");
return GetAsync(new Uri(url), token);
}
@@ -159,7 +160,7 @@ namespace Flow.Launcher.Infrastructure.Http
/// The Http result as string. Null if cancellation requested
public static async Task GetAsync([NotNull] Uri url, CancellationToken token = default)
{
- Log.Debug($"|Http.Get|Url <{url}>");
+ Log.Debug(ClassName, $"Url <{url}>");
using var response = await client.GetAsync(url, token);
var content = await response.Content.ReadAsStringAsync(token);
if (response.StatusCode != HttpStatusCode.OK)
@@ -181,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.
///
@@ -191,7 +191,7 @@ namespace Flow.Launcher.Infrastructure.Http
public static async Task GetStreamAsync([NotNull] Uri url,
CancellationToken token = default)
{
- Log.Debug($"|Http.Get|Url <{url}>");
+ Log.Debug(ClassName, $"Url <{url}>");
return await client.GetStreamAsync(url, token);
}
@@ -202,7 +202,7 @@ namespace Flow.Launcher.Infrastructure.Http
public static async Task GetResponseAsync([NotNull] Uri url, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead,
CancellationToken token = default)
{
- Log.Debug($"|Http.Get|Url <{url}>");
+ Log.Debug(ClassName, $"Url <{url}>");
return await client.GetAsync(url, completionOption, token);
}
@@ -211,7 +211,27 @@ 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);
+ }
+ }
+
+ public static async Task GetStringAsync(string url, CancellationToken token = default)
+ {
+ try
+ {
+ Log.Debug(ClassName, $"Url <{url}>");
+ return await client.GetStringAsync(url, token);
+ }
+ catch (System.Exception e)
+ {
+ return string.Empty;
+ }
}
}
}
diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
index 9e31d2b4e..86df01a30 100644
--- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
+++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs
@@ -7,9 +7,8 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
-using CommunityToolkit.Mvvm.DependencyInjection;
+using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
-using Flow.Launcher.Plugin;
using SharpVectors.Converters;
using SharpVectors.Renderers.Wpf;
@@ -17,10 +16,6 @@ namespace Flow.Launcher.Infrastructure.Image
{
public static class ImageLoader
{
- // We should not initialize API in static constructor because it will create another API instance
- private static IPublicAPI api = null;
- private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService();
-
private static readonly string ClassName = nameof(ImageLoader);
private static readonly ImageCache ImageCache = new();
@@ -58,14 +53,14 @@ namespace Flow.Launcher.Infrastructure.Image
_ = Task.Run(async () =>
{
- await API.StopwatchLogInfoAsync(ClassName, "Preload images cost", async () =>
+ await Stopwatch.InfoAsync(ClassName, "Preload images cost", async () =>
{
foreach (var (path, isFullImage) in usage)
{
await LoadAsync(path, isFullImage);
}
});
- API.LogInfo(ClassName, $"Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
+ Log.Info(ClassName, $"Number of preload images is <{ImageCache.CacheSize()}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}");
});
}
@@ -81,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Image
}
catch (System.Exception e)
{
- API.LogException(ClassName, "Failed to save image cache to file", e);
+ Log.Exception(ClassName, "Failed to save image cache to file", e);
}
finally
{
@@ -176,8 +171,8 @@ namespace Flow.Launcher.Infrastructure.Image
}
catch (System.Exception e2)
{
- API.LogException(ClassName, $"|ImageLoader.Load|Failed to get thumbnail for {path} on first try", e);
- API.LogException(ClassName, $"|ImageLoader.Load|Failed to get thumbnail for {path} on second try", e2);
+ Log.Exception(ClassName, $"Failed to get thumbnail for {path} on first try", e);
+ Log.Exception(ClassName, $"Failed to get thumbnail for {path} on second try", e2);
ImageSource image = ImageCache[Constant.MissingImgIcon, false];
ImageCache[path, false] = image;
@@ -243,7 +238,7 @@ namespace Flow.Launcher.Infrastructure.Image
{
image = Image;
type = ImageType.Error;
- API.LogException(ClassName, $"Failed to load image file from path {path}: {ex.Message}", ex);
+ Log.Exception(ClassName, $"Failed to load image file from path {path}: {ex.Message}", ex);
}
}
else
@@ -267,7 +262,7 @@ namespace Flow.Launcher.Infrastructure.Image
{
image = Image;
type = ImageType.Error;
- API.LogException(ClassName, $"Failed to load SVG image from path {path}: {ex.Message}", ex);
+ Log.Exception(ClassName, $"Failed to load SVG image from path {path}: {ex.Message}", ex);
}
}
else
diff --git a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs
index b98ea50fe..4ce0df026 100644
--- a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs
+++ b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs
@@ -12,7 +12,7 @@ using Windows.Win32.Graphics.Gdi;
namespace Flow.Launcher.Infrastructure.Image
{
///
- /// Subclass of
+ /// Subclass of
///
[Flags]
public enum ThumbnailOptions
@@ -31,7 +31,9 @@ namespace Flow.Launcher.Infrastructure.Image
private static readonly Guid GUID_IShellItem = typeof(IShellItem).GUID;
- private static readonly HRESULT S_ExtractionFailed = (HRESULT)0x8004B200;
+ private static readonly HRESULT S_EXTRACTIONFAILED = (HRESULT)0x8004B200;
+
+ private static readonly HRESULT S_PATHNOTFOUND = (HRESULT)0x8004B205;
public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
{
@@ -79,9 +81,10 @@ namespace Flow.Launcher.Infrastructure.Image
{
imageFactory.GetImage(size, (SIIGBF)options, &hBitmap);
}
- catch (COMException ex) when (ex.HResult == S_ExtractionFailed && options == ThumbnailOptions.ThumbnailOnly)
+ catch (COMException ex) when (options == ThumbnailOptions.ThumbnailOnly &&
+ (ex.HResult == S_PATHNOTFOUND || ex.HResult == S_EXTRACTIONFAILED))
{
- // Fallback to IconOnly if ThumbnailOnly fails
+ // Fallback to IconOnly if extraction fails or files cannot be found
imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap);
}
catch (FileNotFoundException) when (options == ThumbnailOptions.ThumbnailOnly)
@@ -89,6 +92,11 @@ namespace Flow.Launcher.Infrastructure.Image
// Fallback to IconOnly if files cannot be found
imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap);
}
+ catch (System.Exception ex)
+ {
+ // Handle other exceptions
+ throw new InvalidOperationException("Failed to get thumbnail", ex);
+ }
}
finally
{
diff --git a/Flow.Launcher.Infrastructure/Logger/Log.cs b/Flow.Launcher.Infrastructure/Logger/Log.cs
index 807d631c7..09eb98f46 100644
--- a/Flow.Launcher.Infrastructure/Logger/Log.cs
+++ b/Flow.Launcher.Infrastructure/Logger/Log.cs
@@ -94,13 +94,6 @@ namespace Flow.Launcher.Infrastructure.Logger
logger.Fatal(message);
}
- private static bool FormatValid(string message)
- {
- var parts = message.Split('|');
- var valid = parts.Length == 3 && !string.IsNullOrWhiteSpace(parts[1]) && !string.IsNullOrWhiteSpace(parts[2]);
- return valid;
- }
-
public static void Exception(string className, string message, System.Exception exception, [CallerMemberName] string methodName = "")
{
exception = exception.Demystify();
@@ -135,57 +128,14 @@ namespace Flow.Launcher.Infrastructure.Logger
return className;
}
+#if !DEBUG
private static void ExceptionInternal(string classAndMethod, string message, System.Exception e)
{
var logger = LogManager.GetLogger(classAndMethod);
logger.Error(e, message);
}
-
- private static void LogInternal(string message, LogLevel level)
- {
- if (FormatValid(message))
- {
- var parts = message.Split('|');
- var prefix = parts[1];
- var unprefixed = parts[2];
- var logger = LogManager.GetLogger(prefix);
- logger.Log(level, unprefixed);
- }
- else
- {
- LogFaultyFormat(message);
- }
- }
-
- /// Example: "|ClassName.MethodName|Message"
- /// Example: "|ClassName.MethodName|Message"
- /// Exception
- public static void Exception(string message, System.Exception e)
- {
- e = e.Demystify();
-#if DEBUG
- ExceptionDispatchInfo.Capture(e).Throw();
-#else
- if (FormatValid(message))
- {
- var parts = message.Split('|');
- var prefix = parts[1];
- var unprefixed = parts[2];
- ExceptionInternal(prefix, unprefixed, e);
- }
- else
- {
- LogFaultyFormat(message);
- }
#endif
- }
-
- /// Example: "|ClassName.MethodName|Message"
- public static void Error(string message)
- {
- LogInternal(message, LogLevel.Error);
- }
public static void Error(string className, string message, [CallerMemberName] string methodName = "")
{
@@ -206,33 +156,15 @@ namespace Flow.Launcher.Infrastructure.Logger
LogInternal(LogLevel.Debug, className, message, methodName);
}
- /// Example: "|ClassName.MethodName|Message""
- public static void Debug(string message)
- {
- LogInternal(message, LogLevel.Debug);
- }
-
public static void Info(string className, string message, [CallerMemberName] string methodName = "")
{
LogInternal(LogLevel.Info, className, message, methodName);
}
- /// Example: "|ClassName.MethodName|Message"
- public static void Info(string message)
- {
- LogInternal(message, LogLevel.Info);
- }
-
public static void Warn(string className, string message, [CallerMemberName] string methodName = "")
{
LogInternal(LogLevel.Warn, className, message, methodName);
}
-
- /// Example: "|ClassName.MethodName|Message"
- public static void Warn(string message)
- {
- LogInternal(message, LogLevel.Warn);
- }
}
public enum LOGLEVEL
diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt
index 18b206022..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,14 @@ MONITORINFOEXW
WM_ENTERSIZEMOVE
WM_EXITSIZEMOVE
+WM_NCLBUTTONDBLCLK
+WM_SYSCOMMAND
+
+SC_MAXIMIZE
+SC_MINIMIZE
+
+OleInitialize
+OleUninitialize
GetKeyboardLayout
GetWindowThreadProcessId
@@ -53,4 +61,8 @@ INPUTLANGCHANGE_FORWARD
LOCALE_TRANSIENT_KEYBOARD1
LOCALE_TRANSIENT_KEYBOARD2
LOCALE_TRANSIENT_KEYBOARD3
-LOCALE_TRANSIENT_KEYBOARD4
\ No newline at end of file
+LOCALE_TRANSIENT_KEYBOARD4
+
+SHParseDisplayName
+SHOpenFolderAndSelectItems
+CoTaskMemFree
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/Stopwatch.cs b/Flow.Launcher.Infrastructure/Stopwatch.cs
index 784d323fe..870e0fe26 100644
--- a/Flow.Launcher.Infrastructure/Stopwatch.cs
+++ b/Flow.Launcher.Infrastructure/Stopwatch.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
@@ -9,54 +10,50 @@ namespace Flow.Launcher.Infrastructure
///
/// This stopwatch will appear only in Debug mode
///
- public static long Debug(string message, Action action)
+ public static long Debug(string className, string message, Action action, [CallerMemberName] string methodName = "")
{
var stopWatch = new System.Diagnostics.Stopwatch();
stopWatch.Start();
action();
stopWatch.Stop();
var milliseconds = stopWatch.ElapsedMilliseconds;
- string info = $"{message} <{milliseconds}ms>";
- Log.Debug(info);
+ Log.Debug(className, $"{message} <{milliseconds}ms>", methodName);
return milliseconds;
}
///
/// This stopwatch will appear only in Debug mode
///
- public static async Task DebugAsync(string message, Func action)
+ public static async Task DebugAsync(string className, string message, Func action, [CallerMemberName] string methodName = "")
{
var stopWatch = new System.Diagnostics.Stopwatch();
stopWatch.Start();
await action();
stopWatch.Stop();
var milliseconds = stopWatch.ElapsedMilliseconds;
- string info = $"{message} <{milliseconds}ms>";
- Log.Debug(info);
+ Log.Debug(className, $"{message} <{milliseconds}ms>", methodName);
return milliseconds;
}
- public static long Normal(string message, Action action)
+ public static long Info(string className, string message, Action action, [CallerMemberName] string methodName = "")
{
var stopWatch = new System.Diagnostics.Stopwatch();
stopWatch.Start();
action();
stopWatch.Stop();
var milliseconds = stopWatch.ElapsedMilliseconds;
- string info = $"{message} <{milliseconds}ms>";
- Log.Info(info);
+ Log.Info(className, $"{message} <{milliseconds}ms>", methodName);
return milliseconds;
}
- public static async Task NormalAsync(string message, Func action)
+ public static async Task InfoAsync(string className, string message, Func action, [CallerMemberName] string methodName = "")
{
var stopWatch = new System.Diagnostics.Stopwatch();
stopWatch.Start();
await action();
stopWatch.Stop();
var milliseconds = stopWatch.ElapsedMilliseconds;
- string info = $"{message} <{milliseconds}ms>";
- Log.Info(info);
+ Log.Info(className, $"{message} <{milliseconds}ms>", methodName);
return milliseconds;
}
}
diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
index 43bb8dade..48e6b5523 100644
--- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
@@ -19,6 +20,8 @@ namespace Flow.Launcher.Infrastructure.Storage
///
public class BinaryStorage : ISavable
{
+ private static readonly string ClassName = "BinaryStorage";
+
protected T? Data;
public const string FileSuffix = ".cache";
@@ -40,6 +43,16 @@ namespace Flow.Launcher.Infrastructure.Storage
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
}
+ // Let the old Program plugin get this constructor
+ [Obsolete("This constructor is obsolete. Use BinaryStorage(string filename) instead.")]
+ public BinaryStorage(string filename, string directoryPath = null!)
+ {
+ DirectoryPath = directoryPath ?? DataLocation.CacheDirectory;
+ FilesFolders.ValidateDirectory(DirectoryPath);
+
+ FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
+ }
+
public async ValueTask TryLoadAsync(T defaultData)
{
if (Data != null) return Data;
@@ -48,7 +61,7 @@ namespace Flow.Launcher.Infrastructure.Storage
{
if (new FileInfo(FilePath).Length == 0)
{
- Log.Error($"|BinaryStorage.TryLoad|Zero length cache file <{FilePath}>");
+ Log.Error(ClassName, $"Zero length cache file <{FilePath}>");
Data = defaultData;
await SaveAsync();
}
@@ -58,7 +71,7 @@ namespace Flow.Launcher.Infrastructure.Storage
}
else
{
- Log.Info("|BinaryStorage.TryLoad|Cache file not exist, load default data");
+ Log.Info(ClassName, "Cache file not exist, load default data");
Data = defaultData;
await SaveAsync();
}
@@ -82,8 +95,10 @@ namespace Flow.Launcher.Infrastructure.Storage
public void Save()
{
- var serialized = MemoryPackSerializer.Serialize(Data);
+ // User may delete the directory, so we need to check it
+ FilesFolders.ValidateDirectory(DirectoryPath);
+ var serialized = MemoryPackSerializer.Serialize(Data);
File.WriteAllBytes(FilePath, serialized);
}
@@ -103,6 +118,9 @@ namespace Flow.Launcher.Infrastructure.Storage
// so we need to pass it to SaveAsync
public async ValueTask SaveAsync(T data)
{
+ // User may delete the directory, so we need to check it
+ FilesFolders.ValidateDirectory(DirectoryPath);
+
await using var stream = new FileStream(FilePath, FileMode.Create);
await MemoryPackSerializer.SerializeAsync(stream, data);
}
diff --git a/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs
index 8b4062b6b..857490bad 100644
--- a/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/FlowLauncherJsonStorage.cs
@@ -1,27 +1,24 @@
using System.IO;
using System.Threading.Tasks;
-using CommunityToolkit.Mvvm.DependencyInjection;
+using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Infrastructure.Storage
{
- public class FlowLauncherJsonStorage : JsonStorage where T : new()
+ // Expose ISaveable interface in derived class to make sure we are calling the new version of Save method
+ public class FlowLauncherJsonStorage : JsonStorage, ISavable where T : new()
{
private static readonly string ClassName = "FlowLauncherJsonStorage";
- // We should not initialize API in static constructor because it will create another API instance
- private static IPublicAPI api = null;
- private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService();
-
public FlowLauncherJsonStorage()
{
- var directoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
- FilesFolders.ValidateDirectory(directoryPath);
+ DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName);
+ FilesFolders.ValidateDirectory(DirectoryPath);
var filename = typeof(T).Name;
- FilePath = Path.Combine(directoryPath, $"{filename}{FileSuffix}");
+ FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
}
public new void Save()
@@ -32,7 +29,7 @@ namespace Flow.Launcher.Infrastructure.Storage
}
catch (System.Exception e)
{
- API.LogException(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
+ Log.Exception(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
}
}
@@ -44,7 +41,7 @@ namespace Flow.Launcher.Infrastructure.Storage
}
catch (System.Exception e)
{
- API.LogException(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
+ Log.Exception(ClassName, $"Failed to save FL settings to path: {FilePath}", e);
}
}
}
diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
index cdf3ae909..c7eba05fd 100644
--- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs
@@ -16,6 +16,8 @@ namespace Flow.Launcher.Infrastructure.Storage
///
public class JsonStorage : ISavable where T : new()
{
+ private static readonly string ClassName = "JsonStorage";
+
protected T? Data;
// need a new directory name
@@ -43,6 +45,22 @@ namespace Flow.Launcher.Infrastructure.Storage
FilesFolders.ValidateDirectory(DirectoryPath);
}
+ public bool Exists()
+ {
+ return File.Exists(FilePath);
+ }
+
+ public void Delete()
+ {
+ foreach (var path in new[] { FilePath, BackupFilePath, TempFilePath })
+ {
+ if (File.Exists(path))
+ {
+ File.Delete(path);
+ }
+ }
+ }
+
public async Task LoadAsync()
{
if (Data != null)
@@ -104,7 +122,7 @@ namespace Flow.Launcher.Infrastructure.Storage
private void RestoreBackup()
{
- Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully");
+ Log.Info(ClassName, $"Failed to load settings.json, {BackupFilePath} restored successfully");
if (File.Exists(FilePath))
File.Replace(BackupFilePath, FilePath, null);
@@ -183,7 +201,10 @@ namespace Flow.Launcher.Infrastructure.Storage
public void Save()
{
- string serialized = JsonSerializer.Serialize(Data,
+ // User may delete the directory, so we need to check it
+ FilesFolders.ValidateDirectory(DirectoryPath);
+
+ var serialized = JsonSerializer.Serialize(Data,
new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(TempFilePath, serialized);
@@ -193,6 +214,9 @@ namespace Flow.Launcher.Infrastructure.Storage
public async Task SaveAsync()
{
+ // User may delete the directory, so we need to check it
+ FilesFolders.ValidateDirectory(DirectoryPath);
+
await using var tempOutput = File.OpenWrite(TempFilePath);
await JsonSerializer.SerializeAsync(tempOutput, Data,
new JsonSerializerOptions { WriteIndented = true });
diff --git a/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs
index d18060e3d..0e0906e73 100644
--- a/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/PluginBinaryStorage.cs
@@ -1,19 +1,16 @@
using System.IO;
using System.Threading.Tasks;
-using CommunityToolkit.Mvvm.DependencyInjection;
+using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Infrastructure.Storage
{
- public class PluginBinaryStorage : BinaryStorage where T : new()
+ // Expose ISaveable interface in derived class to make sure we are calling the new version of Save method
+ public class PluginBinaryStorage : BinaryStorage, ISavable where T : new()
{
private static readonly string ClassName = "PluginBinaryStorage";
- // We should not initialize API in static constructor because it will create another API instance
- private static IPublicAPI api = null;
- private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService();
-
public PluginBinaryStorage(string cacheName, string cacheDirectory)
{
DirectoryPath = cacheDirectory;
@@ -30,7 +27,7 @@ namespace Flow.Launcher.Infrastructure.Storage
}
catch (System.Exception e)
{
- API.LogException(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
+ Log.Exception(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
}
}
@@ -42,7 +39,7 @@ namespace Flow.Launcher.Infrastructure.Storage
}
catch (System.Exception e)
{
- API.LogException(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
+ Log.Exception(ClassName, $"Failed to save plugin caches to path: {FilePath}", e);
}
}
}
diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs
index e8cbd70fb..d59083071 100644
--- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs
+++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs
@@ -1,23 +1,20 @@
using System.IO;
using System.Threading.Tasks;
-using CommunityToolkit.Mvvm.DependencyInjection;
+using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Infrastructure.Storage
{
- public class PluginJsonStorage : JsonStorage where T : new()
+ // Expose ISaveable interface in derived class to make sure we are calling the new version of Save method
+ public class PluginJsonStorage : JsonStorage, ISavable where T : new()
{
// Use assembly name to check which plugin is using this storage
public readonly string AssemblyName;
private static readonly string ClassName = "PluginJsonStorage";
- // We should not initialize API in static constructor because it will create another API instance
- private static IPublicAPI api = null;
- private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService();
-
public PluginJsonStorage()
{
// C# related, add python related below
@@ -42,7 +39,7 @@ namespace Flow.Launcher.Infrastructure.Storage
}
catch (System.Exception e)
{
- API.LogException(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
+ Log.Exception(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
}
}
@@ -54,7 +51,7 @@ namespace Flow.Launcher.Infrastructure.Storage
}
catch (System.Exception e)
{
- API.LogException(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
+ Log.Exception(ClassName, $"Failed to save plugin settings to path: {FilePath}", e);
}
}
}
diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs
index 71020369a..2603d4675 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/CustomShortcutModel.cs
@@ -1,15 +1,17 @@
using System;
using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.DependencyInjection;
+using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings
{
+ #region Base
+
public abstract class ShortcutBaseModel
{
public string Key { get; set; }
- [JsonIgnore]
- public Func Expand { get; set; } = () => { return ""; };
-
public override bool Equals(object obj)
{
return obj is ShortcutBaseModel other &&
@@ -22,16 +24,14 @@ namespace Flow.Launcher.Infrastructure.UserSettings
}
}
- public class CustomShortcutModel : ShortcutBaseModel
+ public class BaseCustomShortcutModel : ShortcutBaseModel
{
public string Value { get; set; }
- [JsonConstructorAttribute]
- public CustomShortcutModel(string key, string value)
+ public BaseCustomShortcutModel(string key, string value)
{
Key = key;
Value = value;
- Expand = () => { return Value; };
}
public void Deconstruct(out string key, out string value)
@@ -40,26 +40,75 @@ namespace Flow.Launcher.Infrastructure.UserSettings
value = Value;
}
- public static implicit operator (string Key, string Value)(CustomShortcutModel shortcut)
+ public static implicit operator (string Key, string Value)(BaseCustomShortcutModel shortcut)
{
return (shortcut.Key, shortcut.Value);
}
- public static implicit operator CustomShortcutModel((string Key, string Value) shortcut)
+ public static implicit operator BaseCustomShortcutModel((string Key, string Value) shortcut)
{
- return new CustomShortcutModel(shortcut.Key, shortcut.Value);
+ return new BaseCustomShortcutModel(shortcut.Key, shortcut.Value);
}
}
- public class BuiltinShortcutModel : ShortcutBaseModel
+ public class BaseBuiltinShortcutModel : ShortcutBaseModel
{
public string Description { get; set; }
- public BuiltinShortcutModel(string key, string description, Func expand)
+ public string LocalizedDescription => API.GetTranslation(Description);
+
+ // We should not initialize API in static constructor because it will create another API instance
+ private static IPublicAPI api = null;
+ private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService();
+
+ public BaseBuiltinShortcutModel(string key, string description)
{
Key = key;
Description = description;
- Expand = expand ?? (() => { return ""; });
}
}
+
+ #endregion
+
+ #region Custom Shortcut
+
+ public class CustomShortcutModel : BaseCustomShortcutModel
+ {
+ [JsonIgnore]
+ public Func Expand { get; set; } = () => { return string.Empty; };
+
+ [JsonConstructor]
+ public CustomShortcutModel(string key, string value) : base(key, value)
+ {
+ Expand = () => { return Value; };
+ }
+ }
+
+ #endregion
+
+ #region Builtin Shortcut
+
+ public class BuiltinShortcutModel : BaseBuiltinShortcutModel
+ {
+ [JsonIgnore]
+ public Func Expand { get; set; } = () => { return string.Empty; };
+
+ public BuiltinShortcutModel(string key, string description, Func expand) : base(key, description)
+ {
+ Expand = expand ?? (() => { return string.Empty; });
+ }
+ }
+
+ public class AsyncBuiltinShortcutModel : BaseBuiltinShortcutModel
+ {
+ [JsonIgnore]
+ public Func> ExpandAsync { get; set; } = () => { return Task.FromResult(string.Empty); };
+
+ public AsyncBuiltinShortcutModel(string key, string description, Func> expandAsync) : base(key, description)
+ {
+ ExpandAsync = expandAsync ?? (() => { return Task.FromResult(string.Empty); });
+ }
+ }
+
+ #endregion
}
diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs
index 7fb9b895a..920abc284 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs
@@ -67,6 +67,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
metadata.Disabled = settings.Disabled;
metadata.Priority = settings.Priority;
metadata.SearchDelayTime = settings.SearchDelayTime;
+ metadata.HomeDisabled = settings.HomeDisabled;
}
else
{
@@ -79,6 +80,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
DefaultActionKeywords = metadata.ActionKeywords, // metadata provides default values
ActionKeywords = metadata.ActionKeywords, // use default value
Disabled = metadata.Disabled,
+ HomeDisabled = metadata.HomeDisabled,
Priority = metadata.Priority,
DefaultSearchDelayTime = metadata.SearchDelayTime, // metadata provides default values
SearchDelayTime = metadata.SearchDelayTime, // use default value
@@ -128,5 +130,6 @@ namespace Flow.Launcher.Infrastructure.UserSettings
/// Used only to save the state of the plugin in settings
///
public bool Disabled { get; set; }
+ public bool HomeDisabled { get; set; }
}
}
diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
index 243c549c5..85fb0dd03 100644
--- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
+++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs
@@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.Drawing;
using System.Text.Json.Serialization;
using System.Windows;
+using System.Windows.Media;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Infrastructure.Logger;
@@ -33,8 +33,6 @@ namespace Flow.Launcher.Infrastructure.UserSettings
_storage.Save();
}
- private string language = Constant.SystemLanguageCode;
- private string _theme = Constant.DefaultTheme;
public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}";
public string OpenResultModifiers { get; set; } = KeyConstant.Alt;
public string ColorScheme { get; set; } = "System";
@@ -51,18 +49,21 @@ 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";
+ private string _language = Constant.SystemLanguageCode;
public string Language
{
- get => language;
+ get => _language;
set
{
- language = value;
+ _language = value;
OnPropertyChanged();
}
}
+ private string _theme = Constant.DefaultTheme;
public string Theme
{
get => _theme;
@@ -78,22 +79,23 @@ namespace Flow.Launcher.Infrastructure.UserSettings
}
public bool UseDropShadowEffect { get; set; } = true;
public BackdropTypes BackdropType{ get; set; } = BackdropTypes.None;
+ public string ReleaseNotesVersion { get; set; } = string.Empty;
/* Appearance Settings. It should be separated from the setting later.*/
public double WindowHeightSize { get; set; } = 42;
public double ItemHeightSize { get; set; } = 58;
- public double QueryBoxFontSize { get; set; } = 20;
+ public double QueryBoxFontSize { get; set; } = 16;
public double ResultItemFontSize { get; set; } = 16;
public double ResultSubItemFontSize { get; set; } = 13;
- public string QueryBoxFont { get; set; } = FontFamily.GenericSansSerif.Name;
+ public string QueryBoxFont { get; set; } = Win32Helper.GetSystemDefaultFont();
public string QueryBoxFontStyle { get; set; }
public string QueryBoxFontWeight { get; set; }
public string QueryBoxFontStretch { get; set; }
- public string ResultFont { get; set; } = FontFamily.GenericSansSerif.Name;
+ public string ResultFont { get; set; } = Win32Helper.GetSystemDefaultFont();
public string ResultFontStyle { get; set; }
public string ResultFontWeight { get; set; }
public string ResultFontStretch { get; set; }
- public string ResultSubFont { get; set; } = FontFamily.GenericSansSerif.Name;
+ public string ResultSubFont { get; set; } = Win32Helper.GetSystemDefaultFont();
public string ResultSubFontStyle { get; set; }
public string ResultSubFontWeight { get; set; }
public string ResultSubFontStretch { get; set; }
@@ -101,6 +103,24 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public bool UseAnimation { get; set; } = true;
public bool UseSound { get; set; } = true;
public double SoundVolume { get; set; } = 50;
+ public bool ShowBadges { get; set; } = false;
+ public bool ShowBadgesGlobalOnly { get; set; } = false;
+
+ private string _settingWindowFont { get; set; } = Win32Helper.GetSystemDefaultFont(false);
+ public string SettingWindowFont
+ {
+ get => _settingWindowFont;
+ set
+ {
+ if (_settingWindowFont != value)
+ {
+ _settingWindowFont = value;
+ OnPropertyChanged();
+ Application.Current.Resources["SettingWindowFont"] = new FontFamily(value);
+ Application.Current.Resources["ContentControlThemeFontFamily"] = new FontFamily(value);
+ }
+ }
+ }
public bool UseClock { get; set; } = true;
public bool UseDate { get; set; } = false;
@@ -114,7 +134,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public double? SettingWindowLeft { get; set; } = null;
public WindowState SettingWindowState { get; set; } = WindowState.Normal;
- bool _showPlaceholder { get; set; } = false;
+ private bool _showPlaceholder { get; set; } = true;
public bool ShowPlaceholder
{
get => _showPlaceholder;
@@ -127,7 +147,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
}
}
}
- string _placeholderText { get; set; } = string.Empty;
+ private string _placeholderText { get; set; } = string.Empty;
public string PlaceholderText
{
get => _placeholderText;
@@ -141,6 +161,36 @@ namespace Flow.Launcher.Infrastructure.UserSettings
}
}
+ private bool _showHomePage { get; set; } = true;
+ public bool ShowHomePage
+ {
+ get => _showHomePage;
+ set
+ {
+ if (_showHomePage != value)
+ {
+ _showHomePage = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ private bool _showHistoryResultsForHomePage = false;
+ public bool ShowHistoryResultsForHomePage
+ {
+ get => _showHistoryResultsForHomePage;
+ set
+ {
+ if (_showHistoryResultsForHomePage != value)
+ {
+ _showHistoryResultsForHomePage = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public int MaxHistoryResultsToShowForHomePage { get; set; } = 5;
+
public int CustomExplorerIndex { get; set; } = 0;
[JsonIgnore]
@@ -178,8 +228,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
new()
{
Name = "Files",
- Path = "Files",
- DirectoryArgument = "-select \"%d\"",
+ Path = "Files-Stable",
+ DirectoryArgument = "\"%d\"",
FileArgument = "-select \"%f\""
}
};
@@ -260,6 +310,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public double WindowLeft { get; set; }
public double WindowTop { get; set; }
+ public double PreviousScreenWidth { get; set; }
+ public double PreviousScreenHeight { get; set; }
+ public double PreviousDpiX { get; set; }
+ public double PreviousDpiY { get; set; }
///
/// Custom left position on selected monitor
@@ -297,9 +351,9 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public ObservableCollection CustomShortcuts { get; set; } = new ObservableCollection();
[JsonIgnore]
- public ObservableCollection BuiltinShortcuts { get; set; } = new()
+ public ObservableCollection BuiltinShortcuts { get; set; } = new()
{
- new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText),
+ new AsyncBuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", () => Win32Helper.StartSTATaskAsync(Clipboard.GetText)),
new BuiltinShortcutModel("{active_explorer_path}", "shortcut_active_explorer_path", FileExplorerHelper.GetActiveExplorerPath)
};
@@ -309,7 +363,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public bool StartFlowLauncherOnSystemStartup { get; set; } = false;
public bool UseLogonTaskForStartup { get; set; } = false;
public bool HideOnStartup { get; set; } = true;
- bool _hideNotifyIcon { get; set; }
+ private bool _hideNotifyIcon;
public bool HideNotifyIcon
{
get => _hideNotifyIcon;
@@ -358,29 +412,31 @@ namespace Flow.Launcher.Infrastructure.UserSettings
var list = FixedHotkeys();
// Customizeable hotkeys
- if(!string.IsNullOrEmpty(Hotkey))
+ if (!string.IsNullOrEmpty(Hotkey))
list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = ""));
- if(!string.IsNullOrEmpty(PreviewHotkey))
+ if (!string.IsNullOrEmpty(PreviewHotkey))
list.Add(new(PreviewHotkey, "previewHotkey", () => PreviewHotkey = ""));
- if(!string.IsNullOrEmpty(AutoCompleteHotkey))
+ if (!string.IsNullOrEmpty(AutoCompleteHotkey))
list.Add(new(AutoCompleteHotkey, "autoCompleteHotkey", () => AutoCompleteHotkey = ""));
- if(!string.IsNullOrEmpty(AutoCompleteHotkey2))
+ if (!string.IsNullOrEmpty(AutoCompleteHotkey2))
list.Add(new(AutoCompleteHotkey2, "autoCompleteHotkey", () => AutoCompleteHotkey2 = ""));
- if(!string.IsNullOrEmpty(SelectNextItemHotkey))
+ if (!string.IsNullOrEmpty(SelectNextItemHotkey))
list.Add(new(SelectNextItemHotkey, "SelectNextItemHotkey", () => SelectNextItemHotkey = ""));
- if(!string.IsNullOrEmpty(SelectNextItemHotkey2))
+ if (!string.IsNullOrEmpty(SelectNextItemHotkey2))
list.Add(new(SelectNextItemHotkey2, "SelectNextItemHotkey", () => SelectNextItemHotkey2 = ""));
- if(!string.IsNullOrEmpty(SelectPrevItemHotkey))
+ if (!string.IsNullOrEmpty(SelectPrevItemHotkey))
list.Add(new(SelectPrevItemHotkey, "SelectPrevItemHotkey", () => SelectPrevItemHotkey = ""));
- if(!string.IsNullOrEmpty(SelectPrevItemHotkey2))
+ if (!string.IsNullOrEmpty(SelectPrevItemHotkey2))
list.Add(new(SelectPrevItemHotkey2, "SelectPrevItemHotkey", () => SelectPrevItemHotkey2 = ""));
- if(!string.IsNullOrEmpty(SettingWindowHotkey))
+ if (!string.IsNullOrEmpty(SettingWindowHotkey))
list.Add(new(SettingWindowHotkey, "SettingWindowHotkey", () => SettingWindowHotkey = ""));
- if(!string.IsNullOrEmpty(OpenContextMenuHotkey))
+ if (!string.IsNullOrEmpty(OpenHistoryHotkey))
+ list.Add(new(OpenHistoryHotkey, "OpenHistoryHotkey", () => OpenHistoryHotkey = ""));
+ if (!string.IsNullOrEmpty(OpenContextMenuHotkey))
list.Add(new(OpenContextMenuHotkey, "OpenContextMenuHotkey", () => OpenContextMenuHotkey = ""));
- if(!string.IsNullOrEmpty(SelectNextPageHotkey))
+ if (!string.IsNullOrEmpty(SelectNextPageHotkey))
list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = ""));
- if(!string.IsNullOrEmpty(SelectPrevPageHotkey))
+ if (!string.IsNullOrEmpty(SelectPrevPageHotkey))
list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = ""));
if (!string.IsNullOrEmpty(CycleHistoryUpHotkey))
list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = ""));
@@ -411,7 +467,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 f9c548de8..86e7b7c97 100644
--- a/Flow.Launcher.Infrastructure/Win32Helper.cs
+++ b/Flow.Launcher.Infrastructure/Win32Helper.cs
@@ -1,9 +1,16 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
+using System.Diagnostics;
using System.Globalization;
+using System.IO;
+using System.Linq;
using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
+using System.Windows.Markup;
using System.Windows.Media;
using Flow.Launcher.Infrastructure.UserSettings;
using Microsoft.Win32;
@@ -11,8 +18,10 @@ using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
using Windows.Win32.UI.Input.KeyboardAndMouse;
+using Windows.Win32.UI.Shell.Common;
using Windows.Win32.UI.WindowsAndMessaging;
using Point = System.Windows.Point;
+using SystemFonts = System.Windows.SystemFonts;
namespace Flow.Launcher.Infrastructure
{
@@ -185,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());
@@ -195,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
@@ -315,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
@@ -332,6 +346,78 @@ namespace Flow.Launcher.Infrastructure
#endregion
+ #region STA Thread
+
+ /*
+ Inspired by https://github.com/files-community/Files code on STA Thread handling.
+ */
+
+ public static Task StartSTATaskAsync(Action action)
+ {
+ var taskCompletionSource = new TaskCompletionSource();
+ Thread thread = new(() =>
+ {
+ PInvoke.OleInitialize();
+
+ try
+ {
+ action();
+ taskCompletionSource.SetResult();
+ }
+ catch (System.Exception ex)
+ {
+ taskCompletionSource.SetException(ex);
+ }
+ finally
+ {
+ PInvoke.OleUninitialize();
+ }
+ })
+ {
+ IsBackground = true,
+ Priority = ThreadPriority.Normal
+ };
+
+ thread.SetApartmentState(ApartmentState.STA);
+ thread.Start();
+
+ return taskCompletionSource.Task;
+ }
+
+ public static Task StartSTATaskAsync(Func func)
+ {
+ var taskCompletionSource = new TaskCompletionSource();
+
+ Thread thread = new(() =>
+ {
+ PInvoke.OleInitialize();
+
+ try
+ {
+ taskCompletionSource.SetResult(func());
+ }
+ catch (System.Exception ex)
+ {
+ taskCompletionSource.SetException(ex);
+ }
+ finally
+ {
+ PInvoke.OleUninitialize();
+ }
+ })
+ {
+ IsBackground = true,
+ Priority = ThreadPriority.Normal
+ };
+
+ thread.SetApartmentState(ApartmentState.STA);
+ thread.Start();
+
+ return taskCompletionSource.Task;
+ }
+
+ #endregion
+
#region Keyboard Layout
private const string UserProfileRegistryPath = @"Control Panel\International\User Profile";
@@ -364,20 +450,10 @@ namespace Flow.Launcher.Infrastructure
// No installed English layout found
if (enHKL == HKL.Null) return;
- // When application is exiting, the Application.Current will be null
- if (Application.Current == null) return;
-
- // Get the FL main window
- var hwnd = GetWindowHandle(Application.Current.MainWindow, true);
+ // Get the foreground window
+ var hwnd = PInvoke.GetForegroundWindow();
if (hwnd == HWND.Null) return;
- // Check if the FL main window is the current foreground window
- if (!IsForegroundWindow(hwnd))
- {
- var result = PInvoke.SetForegroundWindow(hwnd);
- if (!result) throw new Win32Exception(Marshal.GetLastWin32Error());
- }
-
// Get the current foreground window thread ID
var threadId = PInvoke.GetWindowThreadProcessId(hwnd);
if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error());
@@ -517,5 +593,203 @@ namespace Flow.Launcher.Infrastructure
}
#endregion
+
+ #region Korean IME
+
+ public static bool IsWindows11()
+ {
+ return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
+ Environment.OSVersion.Version.Build >= 22000;
+ }
+
+ public static bool IsKoreanIMEExist()
+ {
+ return GetLegacyKoreanIMERegistryValue() != null;
+ }
+
+ public static bool IsLegacyKoreanIMEEnabled()
+ {
+ object value = GetLegacyKoreanIMERegistryValue();
+
+ if (value is int intValue)
+ {
+ return intValue == 1;
+ }
+ else if (value != null && int.TryParse(value.ToString(), out int parsedValue))
+ {
+ return parsedValue == 1;
+ }
+
+ return false;
+ }
+
+ public static bool SetLegacyKoreanIMEEnabled(bool enable)
+ {
+ const string subKeyPath = @"Software\Microsoft\input\tsf\tsf3override\{A028AE76-01B1-46C2-99C4-ACD9858AE02F}";
+ const string valueName = "NoTsf3Override5";
+
+ try
+ {
+ using RegistryKey key = Registry.CurrentUser.CreateSubKey(subKeyPath);
+ if (key != null)
+ {
+ int value = enable ? 1 : 0;
+ key.SetValue(valueName, value, RegistryValueKind.DWord);
+ return true;
+ }
+ }
+ catch (System.Exception)
+ {
+ // Ignored
+ }
+
+ return false;
+ }
+
+ public static object GetLegacyKoreanIMERegistryValue()
+ {
+ const string subKeyPath = @"Software\Microsoft\input\tsf\tsf3override\{A028AE76-01B1-46C2-99C4-ACD9858AE02F}";
+ const string valueName = "NoTsf3Override5";
+
+ try
+ {
+ using RegistryKey key = Registry.CurrentUser.OpenSubKey(subKeyPath);
+ if (key != null)
+ {
+ return key.GetValue(valueName);
+ }
+ }
+ catch (System.Exception)
+ {
+ // Ignored
+ }
+
+ return null;
+ }
+
+ public static void OpenImeSettings()
+ {
+ try
+ {
+ Process.Start(new ProcessStartInfo("ms-settings:regionlanguage") { UseShellExecute = true });
+ }
+ catch (System.Exception)
+ {
+ // Ignored
+ }
+ }
+
+ #endregion
+
+ #region System Font
+
+ private static readonly Dictionary _languageToNotoSans = new()
+ {
+ { "ko", "Noto Sans KR" },
+ { "ja", "Noto Sans JP" },
+ { "zh-CN", "Noto Sans SC" },
+ { "zh-SG", "Noto Sans SC" },
+ { "zh-Hans", "Noto Sans SC" },
+ { "zh-TW", "Noto Sans TC" },
+ { "zh-HK", "Noto Sans TC" },
+ { "zh-MO", "Noto Sans TC" },
+ { "zh-Hant", "Noto Sans TC" },
+ { "th", "Noto Sans Thai" },
+ { "ar", "Noto Sans Arabic" },
+ { "he", "Noto Sans Hebrew" },
+ { "hi", "Noto Sans Devanagari" },
+ { "bn", "Noto Sans Bengali" },
+ { "ta", "Noto Sans Tamil" },
+ { "el", "Noto Sans Greek" },
+ { "ru", "Noto Sans" },
+ { "en", "Noto Sans" },
+ { "fr", "Noto Sans" },
+ { "de", "Noto Sans" },
+ { "es", "Noto Sans" },
+ { "pt", "Noto Sans" }
+ };
+
+ ///
+ /// Gets the system default font.
+ ///
+ ///
+ /// If true, it will try to find the Noto font for the current culture.
+ ///
+ ///
+ /// The name of the system default font.
+ ///
+ public static string GetSystemDefaultFont(bool useNoto = true)
+ {
+ try
+ {
+ if (useNoto)
+ {
+ var culture = CultureInfo.CurrentCulture;
+ var language = culture.Name; // e.g., "zh-TW"
+ var langPrefix = language.Split('-')[0]; // e.g., "zh"
+
+ // First, try to find by full name, and if not found, fallback to prefix
+ if (TryGetNotoFont(language, out var notoFont) || TryGetNotoFont(langPrefix, out notoFont))
+ {
+ // If the font is installed, return it
+ if (Fonts.SystemFontFamilies.Any(f => f.Source.Equals(notoFont)))
+ {
+ return notoFont;
+ }
+ }
+ }
+
+ // If Noto font is not found, fallback to the system default font
+ var font = SystemFonts.MessageFontFamily;
+ if (font.FamilyNames.TryGetValue(XmlLanguage.GetLanguage("en-US"), out var englishName))
+ {
+ return englishName;
+ }
+
+ return font.Source ?? "Segoe UI";
+ }
+ catch
+ {
+ return "Segoe UI";
+ }
+ }
+
+ private static bool TryGetNotoFont(string langKey, out string notoFont)
+ {
+ return _languageToNotoSans.TryGetValue(langKey, out notoFont);
+ }
+
+ #endregion
+
+ #region Explorer
+
+ // https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shopenfolderandselectitems
+
+ public static unsafe void OpenFolderAndSelectFile(string filePath)
+ {
+ ITEMIDLIST* pidlFolder = null;
+ ITEMIDLIST* pidlFile = null;
+
+ var folderPath = Path.GetDirectoryName(filePath);
+
+ try
+ {
+ var hrFolder = PInvoke.SHParseDisplayName(folderPath, null, out pidlFolder, 0, null);
+ if (hrFolder.Failed) throw new COMException("Failed to parse folder path", hrFolder);
+
+ var hrFile = PInvoke.SHParseDisplayName(filePath, null, out pidlFile, 0, null);
+ if (hrFile.Failed) throw new COMException("Failed to parse file path", hrFile);
+
+ var hrSelect = PInvoke.SHOpenFolderAndSelectItems(pidlFolder, 1, &pidlFile, 0);
+ if (hrSelect.Failed) throw new COMException("Failed to open folder and select item", hrSelect);
+ }
+ finally
+ {
+ if (pidlFile != null) PInvoke.CoTaskMemFree(pidlFile);
+ if (pidlFolder != null) PInvoke.CoTaskMemFree(pidlFolder);
+ }
+ }
+
+ #endregion
}
}
diff --git a/Flow.Launcher.Plugin/AllowedLanguage.cs b/Flow.Launcher.Plugin/AllowedLanguage.cs
index 619a94deb..0d22756a7 100644
--- a/Flow.Launcher.Plugin/AllowedLanguage.cs
+++ b/Flow.Launcher.Plugin/AllowedLanguage.cs
@@ -65,7 +65,42 @@ namespace Flow.Launcher.Plugin
public static bool IsDotNet(string language)
{
return language.Equals(CSharp, StringComparison.OrdinalIgnoreCase)
- || language.Equals(FSharp, StringComparison.OrdinalIgnoreCase);
+ || language.Equals(FSharp, StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ /// Determines if this language is a Python language
+ ///
+ ///
+ ///
+ public static bool IsPython(string language)
+ {
+ return language.Equals(Python, StringComparison.OrdinalIgnoreCase)
+ || language.Equals(PythonV2, StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ /// Determines if this language is a Node.js language
+ ///
+ ///
+ ///
+ public static bool IsNodeJs(string language)
+ {
+ return language.Equals(TypeScript, StringComparison.OrdinalIgnoreCase)
+ || language.Equals(TypeScriptV2, StringComparison.OrdinalIgnoreCase)
+ || language.Equals(JavaScript, StringComparison.OrdinalIgnoreCase)
+ || language.Equals(JavaScriptV2, StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ /// Determines if this language is a executable language
+ ///
+ ///
+ ///
+ public static bool IsExecutable(string language)
+ {
+ return language.Equals(Executable, StringComparison.OrdinalIgnoreCase)
+ || language.Equals(ExecutableV2, StringComparison.OrdinalIgnoreCase);
}
///
@@ -76,15 +111,9 @@ namespace Flow.Launcher.Plugin
public static bool IsAllowed(string language)
{
return IsDotNet(language)
- || language.Equals(Python, StringComparison.OrdinalIgnoreCase)
- || language.Equals(PythonV2, StringComparison.OrdinalIgnoreCase)
- || language.Equals(Executable, StringComparison.OrdinalIgnoreCase)
- || language.Equals(TypeScript, StringComparison.OrdinalIgnoreCase)
- || language.Equals(JavaScript, StringComparison.OrdinalIgnoreCase)
- || language.Equals(ExecutableV2, StringComparison.OrdinalIgnoreCase)
- || language.Equals(TypeScriptV2, StringComparison.OrdinalIgnoreCase)
- || language.Equals(JavaScriptV2, StringComparison.OrdinalIgnoreCase);
- ;
+ || IsPython(language)
+ || IsNodeJs(language)
+ || IsExecutable(language);
}
}
}
diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj
index 1472813b8..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.0Flow.Launcher.PluginFlow-LauncherMIT
@@ -76,7 +76,9 @@
allruntime; build; native; contentfiles; analyzers; buildtransitive
-
+
+ all
+
diff --git a/Flow.Launcher.Plugin/Interfaces/IAsyncHomeQuery.cs b/Flow.Launcher.Plugin/Interfaces/IAsyncHomeQuery.cs
new file mode 100644
index 000000000..78d6454ae
--- /dev/null
+++ b/Flow.Launcher.Plugin/Interfaces/IAsyncHomeQuery.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Flow.Launcher.Plugin
+{
+ ///
+ /// Asynchronous Query Model for Flow Launcher When Query Text is Empty
+ ///
+ public interface IAsyncHomeQuery : IFeatures
+ {
+ ///
+ /// Asynchronous Querying When Query Text is Empty
+ ///
+ ///
+ /// If the Querying method requires high IO transmission
+ /// or performing CPU intense jobs (performing better with cancellation), please use this IAsyncHomeQuery interface
+ ///
+ /// Cancel when querying job is obsolete
+ ///
+ Task> HomeQueryAsync(CancellationToken token);
+ }
+}
diff --git a/Flow.Launcher.Plugin/Interfaces/IHomeQuery.cs b/Flow.Launcher.Plugin/Interfaces/IHomeQuery.cs
new file mode 100644
index 000000000..81186fca2
--- /dev/null
+++ b/Flow.Launcher.Plugin/Interfaces/IHomeQuery.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Flow.Launcher.Plugin
+{
+ ///
+ /// Synchronous Query Model for Flow Launcher When Query Text is Empty
+ ///
+ /// If the Querying method requires high IO transmission
+ /// or performing CPU intense jobs (performing better with cancellation), please try the IAsyncHomeQuery interface
+ ///
+ ///
+ public interface IHomeQuery : IAsyncHomeQuery
+ {
+ ///
+ /// Querying When Query Text is Empty
+ ///
+ /// This method will be called within a Task.Run,
+ /// so please avoid synchronously wait for long.
+ ///
+ ///
+ ///
+ List HomeQuery();
+
+ Task> IAsyncHomeQuery.HomeQueryAsync(CancellationToken token) => Task.Run(HomeQuery);
+ }
+}
diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
index a73ead814..76c7a4911 100644
--- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
+++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
@@ -84,10 +84,24 @@ namespace Flow.Launcher.Plugin
/// Optional message subtitle
void ShowMsgError(string title, string subTitle = "");
+ ///
+ /// Show the error message using Flow's standard error icon.
+ ///
+ /// Message title
+ /// Message button content
+ /// Message button action
+ /// Optional message subtitle
+ void ShowMsgErrorWithButton(string title, string buttonText, Action buttonAction, string subTitle = "");
+
///
/// Show the MainWindow when hiding
///
void ShowMainWindow();
+
+ ///
+ /// Focus the query text box in the main window
+ ///
+ void FocusQueryTextBox();
///
/// Hide MainWindow
@@ -122,6 +136,27 @@ namespace Flow.Launcher.Plugin
/// when true will use main windows as the owner
void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true);
+ ///
+ /// Show message box with button
+ ///
+ /// Message title
+ /// Message button content
+ /// Message button action
+ /// Message subtitle
+ /// Message icon path (relative path to your plugin folder)
+ void ShowMsgWithButton(string title, string buttonText, Action buttonAction, string subTitle = "", string iconPath = "");
+
+ ///
+ /// Show message box with button
+ ///
+ /// Message title
+ /// Message button content
+ /// Message button action
+ /// Message subtitle
+ /// Message icon path (relative path to your plugin folder)
+ /// when true will use main windows as the owner
+ void ShowMsgWithButton(string title, string buttonText, Action buttonAction, string subTitle, string iconPath, bool useMainWindowAsOwner = true);
+
///
/// Open setting dialog
///
@@ -223,11 +258,15 @@ namespace Flow.Launcher.Plugin
Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action reportProgress = null, CancellationToken token = default);
///
- /// Add ActionKeyword and update action keyword metadata for specific plugin
+ /// Add ActionKeyword and update action keyword metadata for specific plugin.
/// Before adding, please check if action keyword is already assigned by
///
/// ID for plugin that needs to add action keyword
/// The actionkeyword that is supposed to be added
+ ///
+ /// If new action keyword contains any whitespace, FL will still add it but it will not work for users.
+ /// So plugin should check the whitespace before calling this function.
+ ///
void AddActionKeyword(string pluginId, string newActionKeyword);
///
@@ -280,9 +319,10 @@ namespace Flow.Launcher.Plugin
T LoadSettingJsonStorage() where T : new();
///
- /// Save JsonStorage for current plugin's setting. This is the method used to save settings to json in Flow.Launcher
+ /// Save JsonStorage for current plugin's setting. This is the method used to save settings to json in Flow.
/// This method will save the original instance loaded with LoadJsonStorage.
- /// This API call is for manually Save. Flow will automatically save all setting type that has called LoadSettingJsonStorage or SaveSettingJsonStorage previously.
+ /// This API call is for manually Save.
+ /// Flow will automatically save all setting type that has called or previously.
///
/// Type for Serialization
///
@@ -424,9 +464,10 @@ namespace Flow.Launcher.Plugin
Task LoadCacheBinaryStorageAsync(string cacheName, string cacheDirectory, T defaultData) where T : new();
///
- /// Save BinaryStorage for current plugin's cache. This is the method used to save cache to binary in Flow.Launcher
+ /// Save BinaryStorage for current plugin's cache. This is the method used to save cache to binary in Flow.
/// This method will save the original instance loaded with LoadCacheBinaryStorageAsync.
- /// This API call is for manually Save. Flow will automatically save all cache type that has called LoadCacheBinaryStorageAsync or SaveCacheBinaryStorageAsync previously.
+ /// This API call is for manually Save.
+ /// Flow will automatically save all cache type that has called or previously.
///
/// Type for Serialization
/// Cache file name
@@ -465,8 +506,11 @@ namespace Flow.Launcher.Plugin
public Task UpdatePluginManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default);
///
- /// Get the plugin manifest
+ /// Get the plugin manifest.
///
+ ///
+ /// If Flow cannot get manifest data, this could be null
+ ///
///
public IReadOnlyList GetPluginManifest();
diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs
index da10bc6a5..09803cbd7 100644
--- a/Flow.Launcher.Plugin/PluginMetadata.cs
+++ b/Flow.Launcher.Plugin/PluginMetadata.cs
@@ -50,6 +50,11 @@ namespace Flow.Launcher.Plugin
///
public bool Disabled { get; set; }
+ ///
+ /// Whether plugin is disabled in home query.
+ ///
+ public bool HomeDisabled { get; set; }
+
///
/// Plugin execute file path.
///
diff --git a/Flow.Launcher.Plugin/Query.cs b/Flow.Launcher.Plugin/Query.cs
index 913dc31ae..f50614699 100644
--- a/Flow.Launcher.Plugin/Query.cs
+++ b/Flow.Launcher.Plugin/Query.cs
@@ -8,7 +8,8 @@ namespace Flow.Launcher.Plugin
public class Query
{
///
- /// Raw query, this includes action keyword if it has
+ /// Raw query, this includes action keyword if it has.
+ /// It has handled buildin custom query shortkeys and build-in shortcuts, and it trims the whitespace.
/// We didn't recommend use this property directly. You should always use Search property.
///
public string RawQuery { get; internal init; }
@@ -20,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.
@@ -63,10 +69,10 @@ namespace Flow.Launcher.Plugin
///
[JsonIgnore]
public string FirstSearch => SplitSearch(0);
-
+
[JsonIgnore]
private string _secondToEndSearch;
-
+
///
/// strings from second search (including) to last search
///
diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs
index 910485438..f0fcd48ff 100644
--- a/Flow.Launcher.Plugin/Result.cs
+++ b/Flow.Launcher.Plugin/Result.cs
@@ -12,12 +12,19 @@ namespace Flow.Launcher.Plugin
///
public class Result
{
+ ///
+ /// Maximum score. This can be useful when set one result to the top by default. This is the score for the results set to the topmost by users.
+ ///
+ public const int MaxScore = int.MaxValue;
+
private string _pluginDirectory;
private string _icoPath;
private string _copyText = string.Empty;
+ private string _badgeIcoPath;
+
///
/// The title of the result. This is always required.
///
@@ -60,7 +67,7 @@ namespace Flow.Launcher.Plugin
/// GlyphInfo is prioritized if not null
public string IcoPath
{
- get { return _icoPath; }
+ get => _icoPath;
set
{
// As a standard this property will handle prepping and converting to absolute local path for icon image processing
@@ -80,6 +87,33 @@ namespace Flow.Launcher.Plugin
}
}
+ ///
+ /// The image to be displayed for the badge of the result.
+ ///
+ /// Can be a local file path or a URL.
+ /// If null or empty, will use plugin icon
+ public string BadgeIcoPath
+ {
+ get => _badgeIcoPath;
+ set
+ {
+ // As a standard this property will handle prepping and converting to absolute local path for icon image processing
+ if (!string.IsNullOrEmpty(value)
+ && !string.IsNullOrEmpty(PluginDirectory)
+ && !Path.IsPathRooted(value)
+ && !value.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
+ && !value.StartsWith("https://", StringComparison.OrdinalIgnoreCase)
+ && !value.StartsWith("data:image", StringComparison.OrdinalIgnoreCase))
+ {
+ _badgeIcoPath = Path.Combine(PluginDirectory, value);
+ }
+ else
+ {
+ _badgeIcoPath = value;
+ }
+ }
+ }
+
///
/// Determines if Icon has a border radius
///
@@ -94,14 +128,18 @@ namespace Flow.Launcher.Plugin
///
/// Delegate to load an icon for this result.
///
- public IconDelegate Icon;
+ public IconDelegate Icon = null;
+
+ ///
+ /// Delegate to load an icon for the badge of this result.
+ ///
+ public IconDelegate BadgeIcon = null;
///
/// Information for Glyph Icon (Prioritized than IcoPath/Icon if user enable Glyph Icons)
///
public GlyphInfo Glyph { get; init; }
-
///
/// An action to take in the form of a function call when the result has been selected.
///
@@ -143,59 +181,19 @@ namespace Flow.Launcher.Plugin
///
public string PluginDirectory
{
- get { return _pluginDirectory; }
+ get => _pluginDirectory;
set
{
_pluginDirectory = value;
// When the Result object is returned from the query call, PluginDirectory is not provided until
// UpdatePluginMetadata call is made at PluginManager.cs L196. Once the PluginDirectory becomes available
- // we need to update (only if not Uri path) the IcoPath with the full absolute path so the image can be loaded.
+ // we need to update (only if not Uri path) the IcoPath and BadgeIcoPath with the full absolute path so the image can be loaded.
IcoPath = _icoPath;
+ BadgeIcoPath = _badgeIcoPath;
}
}
- ///
- public override string ToString()
- {
- return Title + SubTitle + Score;
- }
-
- ///
- /// Clones the current result
- ///
- public Result Clone()
- {
- return new Result
- {
- Title = Title,
- SubTitle = SubTitle,
- ActionKeywordAssigned = ActionKeywordAssigned,
- CopyText = CopyText,
- AutoCompleteText = AutoCompleteText,
- IcoPath = IcoPath,
- RoundedIcon = RoundedIcon,
- Icon = Icon,
- Glyph = Glyph,
- Action = Action,
- AsyncAction = AsyncAction,
- Score = Score,
- TitleHighlightData = TitleHighlightData,
- OriginQuery = OriginQuery,
- PluginDirectory = PluginDirectory,
- ContextData = ContextData,
- PluginID = PluginID,
- TitleToolTip = TitleToolTip,
- SubTitleToolTip = SubTitleToolTip,
- PreviewPanel = PreviewPanel,
- ProgressBar = ProgressBar,
- ProgressBarColor = ProgressBarColor,
- Preview = Preview,
- AddSelectedCount = AddSelectedCount,
- RecordKey = RecordKey
- };
- }
-
///
/// Additional data associated with this result
///
@@ -224,16 +222,6 @@ namespace Flow.Launcher.Plugin
///
public Lazy PreviewPanel { get; set; }
- ///
- /// Run this result, asynchronously
- ///
- ///
- ///
- public ValueTask ExecuteAsync(ActionContext context)
- {
- return AsyncAction?.Invoke(context) ?? ValueTask.FromResult(Action?.Invoke(context) ?? false);
- }
-
///
/// Progress bar display. Providing an int value between 0-100 will trigger the progress bar to be displayed on the result
///
@@ -255,11 +243,6 @@ namespace Flow.Launcher.Plugin
///
public bool AddSelectedCount { get; set; } = true;
- ///
- /// Maximum score. This can be useful when set one result to the top by default. This is the score for the results set to the topmost by users.
- ///
- public const int MaxScore = int.MaxValue;
-
///
/// The key to identify the record. This is used when FL checks whether the result is the topmost record. Or FL calculates the hashcode of the result for user selected records.
/// This can be useful when your plugin will change the Title or SubTitle of the result dynamically.
@@ -268,6 +251,66 @@ namespace Flow.Launcher.Plugin
///
public string RecordKey { get; set; } = null;
+ ///
+ /// Determines if the badge icon should be shown.
+ /// If users want to show the result badges and here you set this to true, the results will show the badge icon.
+ ///
+ public bool ShowBadge { get; set; } = false;
+
+ ///
+ /// Run this result, asynchronously
+ ///
+ ///
+ ///
+ public ValueTask ExecuteAsync(ActionContext context)
+ {
+ return AsyncAction?.Invoke(context) ?? ValueTask.FromResult(Action?.Invoke(context) ?? false);
+ }
+
+ ///
+ public override string ToString()
+ {
+ return Title + SubTitle + Score;
+ }
+
+ ///
+ /// Clones the current result
+ ///
+ public Result Clone()
+ {
+ return new Result
+ {
+ Title = Title,
+ SubTitle = SubTitle,
+ ActionKeywordAssigned = ActionKeywordAssigned,
+ CopyText = CopyText,
+ AutoCompleteText = AutoCompleteText,
+ IcoPath = IcoPath,
+ BadgeIcoPath = BadgeIcoPath,
+ RoundedIcon = RoundedIcon,
+ Icon = Icon,
+ BadgeIcon = BadgeIcon,
+ Glyph = Glyph,
+ Action = Action,
+ AsyncAction = AsyncAction,
+ Score = Score,
+ TitleHighlightData = TitleHighlightData,
+ OriginQuery = OriginQuery,
+ PluginDirectory = PluginDirectory,
+ ContextData = ContextData,
+ PluginID = PluginID,
+ TitleToolTip = TitleToolTip,
+ SubTitleToolTip = SubTitleToolTip,
+ PreviewPanel = PreviewPanel,
+ ProgressBar = ProgressBar,
+ ProgressBarColor = ProgressBarColor,
+ Preview = Preview,
+ AddSelectedCount = AddSelectedCount,
+ RecordKey = RecordKey,
+ ShowBadge = ShowBadge,
+ };
+ }
+
///
/// Info of the preview section of a
///
diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
index 1de5841a5..6c506cfc0 100644
--- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
+++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
@@ -264,12 +264,12 @@ namespace Flow.Launcher.Plugin.SharedCommands
var index = path.LastIndexOf('\\');
if (index > 0 && index < (path.Length - 1))
{
- string previousDirectoryPath = path.Substring(0, index + 1);
- return locationExists(previousDirectoryPath) ? previousDirectoryPath : "";
+ string previousDirectoryPath = path[..(index + 1)];
+ return locationExists(previousDirectoryPath) ? previousDirectoryPath : string.Empty;
}
else
{
- return "";
+ return string.Empty;
}
}
@@ -285,7 +285,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
// not full path, get previous level directory string
var indexOfSeparator = path.LastIndexOf('\\');
- return path.Substring(0, indexOfSeparator + 1);
+ return path[..(indexOfSeparator + 1)];
}
return path;
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.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs
index 420da266d..9ec952155 100644
--- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs
+++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs
@@ -39,8 +39,8 @@ namespace Flow.Launcher.Test.Plugins
}
[SupportedOSPlatform("windows7.0")]
- [TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY System.FileName")]
- [TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY System.FileName")]
+ [TestCase("C:\\", $"SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY {QueryConstructor.OrderIdentifier}")]
+ [TestCase("C:\\SomeFolder\\", $"SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY {QueryConstructor.OrderIdentifier}")]
public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString)
{
// Given
@@ -59,7 +59,7 @@ namespace Flow.Launcher.Test.Plugins
[TestCase("C:\\SomeFolder", "flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType" +
" FROM SystemIndex WHERE directory='file:C:\\SomeFolder'" +
" AND (System.FileName LIKE 'flow.launcher.sln%' OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"'))" +
- " ORDER BY System.FileName")]
+ $" ORDER BY {QueryConstructor.OrderIdentifier}")]
public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString(
string folderPath, string userSearchString, string expectedString)
{
@@ -87,8 +87,8 @@ namespace Flow.Launcher.Test.Plugins
[SupportedOSPlatform("windows7.0")]
[TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" " +
"FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
- "OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY System.FileName")]
- [TestCase("", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" FROM \"SystemIndex\" WHERE WorkId IS NOT NULL AND scope='file:' ORDER BY System.FileName")]
+ $"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY {QueryConstructor.OrderIdentifier}")]
+ [TestCase("", $"SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" FROM \"SystemIndex\" WHERE WorkId IS NOT NULL AND scope='file:' ORDER BY {QueryConstructor.OrderIdentifier}")]
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString(
string userSearchString, string expectedString)
{
@@ -107,7 +107,6 @@ namespace Flow.Launcher.Test.Plugins
ClassicAssert.AreEqual(expectedString, resultString);
}
-
[SupportedOSPlatform("windows7.0")]
[TestCase(@"some words", @"FREETEXT('some words')")]
public void GivenWindowsIndexSearch_WhenQueryWhereRestrictionsIsForFileContentSearch_ThenShouldReturnFreeTextString(
@@ -127,7 +126,7 @@ namespace Flow.Launcher.Test.Plugins
[SupportedOSPlatform("windows7.0")]
[TestCase("some words", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
- "FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY System.FileName")]
+ $"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY {QueryConstructor.OrderIdentifier}")]
public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString(
string userSearchString, string expectedString)
{
diff --git a/Flow.Launcher/ActionKeywords.xaml b/Flow.Launcher/ActionKeywords.xaml
index 740b0d402..887b13126 100644
--- a/Flow.Launcher/ActionKeywords.xaml
+++ b/Flow.Launcher/ActionKeywords.xaml
@@ -53,11 +53,11 @@
-
-
+
+
-
+
-
+
@@ -112,20 +112,20 @@
Grid.Row="1"
Background="{DynamicResource PopupButtonAreaBGColor}"
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
- BorderThickness="0,1,0,0">
+ BorderThickness="0 1 0 0">
-
-
+
+
-
+
@@ -124,14 +124,14 @@
LastChildFill="True">
@@ -142,21 +142,21 @@
+ BorderThickness="0 1 0 0">
+
+
+
+ Segoe UI
+
+
+
+
+
+ {DynamicResource SettingWindowFont}
+
+
-
+
+ Text="{DynamicResource Welcome_Page4_Title}" TextWrapping="WrapWithOverflow"/>
diff --git a/Flow.Launcher/Resources/Pages/WelcomePage4.xaml.cs b/Flow.Launcher/Resources/Pages/WelcomePage4.xaml.cs
index 4c83f3a83..63c9b9a7a 100644
--- a/Flow.Launcher/Resources/Pages/WelcomePage4.xaml.cs
+++ b/Flow.Launcher/Resources/Pages/WelcomePage4.xaml.cs
@@ -1,21 +1,26 @@
-using CommunityToolkit.Mvvm.DependencyInjection;
+using System.Windows.Navigation;
+using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.ViewModel;
-using System.Windows.Navigation;
namespace Flow.Launcher.Resources.Pages
{
public partial class WelcomePage4
{
+ public Settings Settings { get; } = Ioc.Default.GetRequiredService();
+ private readonly WelcomeViewModel _viewModel = Ioc.Default.GetRequiredService();
+
protected override void OnNavigatedTo(NavigationEventArgs e)
{
- Settings = Ioc.Default.GetRequiredService();
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page number
- Ioc.Default.GetRequiredService().PageNum = 4;
- InitializeComponent();
- }
+ _viewModel.PageNum = 4;
- public Settings Settings { get; set; }
+ if (!IsInitialized)
+ {
+ InitializeComponent();
+ }
+ base.OnNavigatedTo(e);
+ }
}
}
diff --git a/Flow.Launcher/Resources/Pages/WelcomePage5.xaml b/Flow.Launcher/Resources/Pages/WelcomePage5.xaml
index 3df4b506e..997f724b9 100644
--- a/Flow.Launcher/Resources/Pages/WelcomePage5.xaml
+++ b/Flow.Launcher/Resources/Pages/WelcomePage5.xaml
@@ -53,7 +53,7 @@
-
+
@@ -79,18 +79,18 @@
-
+
+ Text="{DynamicResource Welcome_Page5_Title}" TextWrapping="WrapWithOverflow"/>
-
+
-
+ ();
+ private readonly WelcomeViewModel _viewModel = Ioc.Default.GetRequiredService();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
- Settings = Ioc.Default.GetRequiredService();
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page number
- Ioc.Default.GetRequiredService().PageNum = 5;
- InitializeComponent();
+ _viewModel.PageNum = 5;
+
+ if (!IsInitialized)
+ {
+ InitializeComponent();
+ }
+ base.OnNavigatedTo(e);
}
private void OnAutoStartupChecked(object sender, RoutedEventArgs e)
{
- SetStartup();
- }
- private void OnAutoStartupUncheck(object sender, RoutedEventArgs e)
- {
- RemoveStartup();
+ ChangeAutoStartup(true);
}
- private void RemoveStartup()
+ private void OnAutoStartupUncheck(object sender, RoutedEventArgs e)
{
- using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
- key?.DeleteValue(Constant.FlowLauncher, false);
- Settings.StartFlowLauncherOnSystemStartup = false;
+ ChangeAutoStartup(false);
}
- private void SetStartup()
+
+ private void ChangeAutoStartup(bool value)
{
- using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true);
- key?.SetValue(Constant.FlowLauncher, Constant.ExecutablePath);
- Settings.StartFlowLauncherOnSystemStartup = true;
+ Settings.StartFlowLauncherOnSystemStartup = value;
+ try
+ {
+ if (value)
+ {
+ if (Settings.UseLogonTaskForStartup)
+ {
+ AutoStartup.ChangeToViaLogonTask();
+ }
+ else
+ {
+ AutoStartup.ChangeToViaRegistry();
+ }
+ }
+ else
+ {
+ AutoStartup.DisableViaLogonTaskAndRegistry();
+ }
+ }
+ catch (Exception e)
+ {
+ App.API.ShowMsg(App.API.GetTranslation("setAutoStartFailed"), e.Message);
+ }
}
private void OnHideOnStartupChecked(object sender, RoutedEventArgs e)
{
Settings.HideOnStartup = true;
}
+
private void OnHideOnStartupUnchecked(object sender, RoutedEventArgs e)
{
Settings.HideOnStartup = false;
@@ -59,6 +78,5 @@ namespace Flow.Launcher.Resources.Pages
var window = Window.GetWindow(this);
window.Close();
}
-
}
}
diff --git a/Flow.Launcher/Resources/SettingWindowStyle.xaml b/Flow.Launcher/Resources/SettingWindowStyle.xaml
index fc9246aa3..3ebd22c74 100644
--- a/Flow.Launcher/Resources/SettingWindowStyle.xaml
+++ b/Flow.Launcher/Resources/SettingWindowStyle.xaml
@@ -6,7 +6,6 @@
- F1 M512,512z M0,0z M448,256C448,150,362,64,256,64L256,448C362,448,448,362,448,256z M0,256A256,256,0,1,1,512,256A256,256,0,1,1,0,256z
diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml
index 4c3bd1d12..8cb15400f 100644
--- a/Flow.Launcher/ResultListBox.xaml
+++ b/Flow.Launcher/ResultListBox.xaml
@@ -32,6 +32,8 @@
+
+
@@ -90,60 +92,64 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+
@@ -129,7 +130,7 @@
-
-
+
+
@@ -239,18 +238,18 @@
Grid.Row="1"
Background="{DynamicResource PopupButtonAreaBGColor}"
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
- BorderThickness="0,1,0,0">
+ BorderThickness="0 1 0 0">
selectedCustomBrowserIndex; set
- {
- selectedCustomBrowserIndex = value;
- PropertyChanged?.Invoke(this, new(nameof(CustomBrowser)));
- }
- }
- public ObservableCollection CustomBrowsers { get; set; }
-
- public CustomBrowserViewModel CustomBrowser => CustomBrowsers[SelectedCustomBrowserIndex];
- public SelectBrowserWindow(Settings settings)
- {
- Settings = settings;
- CustomBrowsers = new ObservableCollection(Settings.CustomBrowserList.Select(x => x.Copy()));
- SelectedCustomBrowserIndex = Settings.CustomBrowserIndex;
+ _viewModel = Ioc.Default.GetRequiredService();
+ DataContext = _viewModel;
InitializeComponent();
}
@@ -42,34 +23,20 @@ namespace Flow.Launcher
private void btnDone_Click(object sender, RoutedEventArgs e)
{
- Settings.CustomBrowserList = CustomBrowsers.ToList();
- Settings.CustomBrowserIndex = SelectedCustomBrowserIndex;
- Close();
- }
-
- private void btnAdd_Click(object sender, RoutedEventArgs e)
- {
- CustomBrowsers.Add(new()
+ if (_viewModel.SaveSettings())
{
- Name = "New Profile"
- });
- SelectedCustomBrowserIndex = CustomBrowsers.Count - 1;
- }
-
- private void btnDelete_Click(object sender, RoutedEventArgs e)
- {
- CustomBrowsers.RemoveAt(SelectedCustomBrowserIndex--);
+ Close();
+ }
}
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
{
- Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
- Nullable result = dlg.ShowDialog();
+ var selectedFilePath = _viewModel.SelectFile();
- if (result == true)
+ if (!string.IsNullOrEmpty(selectedFilePath))
{
- TextBox path = (TextBox)(((FrameworkElement)sender).Parent as FrameworkElement).FindName("PathTextBox");
- path.Text = dlg.FileName;
+ var path = (TextBox)(((FrameworkElement)sender).Parent as FrameworkElement).FindName("PathTextBox");
+ path.Text = selectedFilePath;
path.Focus();
((Button)sender).Focus();
}
diff --git a/Flow.Launcher/SelectFileManagerWindow.xaml b/Flow.Launcher/SelectFileManagerWindow.xaml
index 0287af9b0..b3b219d1c 100644
--- a/Flow.Launcher/SelectFileManagerWindow.xaml
+++ b/Flow.Launcher/SelectFileManagerWindow.xaml
@@ -6,10 +6,11 @@
xmlns:local="clr-namespace:Flow.Launcher"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.modernwpf.com/2019"
+ xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
Title="{DynamicResource fileManagerWindow}"
Width="600"
+ d:DataContext="{d:DesignInstance vm:SelectFileManagerViewModel}"
Background="{DynamicResource PopuBGColor}"
- DataContext="{Binding RelativeSource={RelativeSource Self}}"
Foreground="{DynamicResource PopupTextColor}"
ResizeMode="NoResize"
SizeToContent="Height"
@@ -73,9 +74,17 @@
+
+
+
+
+
-
-
+
+
@@ -111,7 +120,7 @@
+ Fill="{StaticResource SeparatorForeground}" />
selectedCustomExplorerIndex; set
- {
- selectedCustomExplorerIndex = value;
- PropertyChanged?.Invoke(this, new(nameof(CustomExplorer)));
- }
- }
- public ObservableCollection CustomExplorers { get; set; }
-
- public CustomExplorerViewModel CustomExplorer => CustomExplorers[SelectedCustomExplorerIndex];
- public SelectFileManagerWindow(Settings settings)
- {
- Settings = settings;
- CustomExplorers = new ObservableCollection(Settings.CustomExplorerList.Select(x => x.Copy()));
- SelectedCustomExplorerIndex = Settings.CustomExplorerIndex;
+ _viewModel = Ioc.Default.GetRequiredService();
+ DataContext = _viewModel;
InitializeComponent();
}
@@ -43,34 +24,26 @@ namespace Flow.Launcher
private void btnDone_Click(object sender, RoutedEventArgs e)
{
- Settings.CustomExplorerList = CustomExplorers.ToList();
- Settings.CustomExplorerIndex = SelectedCustomExplorerIndex;
- Close();
- }
-
- private void btnAdd_Click(object sender, RoutedEventArgs e)
- {
- CustomExplorers.Add(new()
+ if (_viewModel.SaveSettings())
{
- Name = "New Profile"
- });
- SelectedCustomExplorerIndex = CustomExplorers.Count - 1;
+ Close();
+ }
}
- private void btnDelete_Click(object sender, RoutedEventArgs e)
+ private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
- CustomExplorers.RemoveAt(SelectedCustomExplorerIndex--);
+ _viewModel.OpenUrl(e.Uri.AbsoluteUri);
+ e.Handled = true;
}
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
{
- Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
- Nullable result = dlg.ShowDialog();
+ var selectedFilePath = _viewModel.SelectFile();
- if (result == true)
+ if (!string.IsNullOrEmpty(selectedFilePath))
{
- TextBox path = (TextBox)(((FrameworkElement)sender).Parent as FrameworkElement).FindName("PathTextBox");
- path.Text = dlg.FileName;
+ var path = (TextBox)(((FrameworkElement)sender).Parent as FrameworkElement).FindName("PathTextBox");
+ path.Text = selectedFilePath;
path.Focus();
((Button)sender).Focus();
}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
index 6cfb98306..1efc89972 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
@@ -144,6 +144,12 @@ public partial class SettingsPaneAboutViewModel : BaseModel
App.API.OpenDirectory(parentFolderPath);
}
+ [RelayCommand]
+ private void OpenCacheFolder()
+ {
+ App.API.OpenDirectory(DataLocation.CacheDirectory);
+ }
+
[RelayCommand]
private void OpenLogsFolder()
{
@@ -180,7 +186,8 @@ public partial class SettingsPaneAboutViewModel : BaseModel
{
try
{
- dir.Delete(true);
+ // Log folders are the last level of folders
+ dir.Delete(recursive: false);
}
catch (Exception e)
{
@@ -208,6 +215,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
{
var success = true;
var cacheDirectory = GetCacheDir();
+ var pluginCacheDirectory = GetPluginCacheDir();
var cacheFiles = GetCacheFiles();
cacheFiles.ForEach(f =>
@@ -223,13 +231,15 @@ public partial class SettingsPaneAboutViewModel : BaseModel
}
});
- cacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
+ // Firstly, delete plugin cache directories
+ pluginCacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
.ToList()
.ForEach(dir =>
{
try
{
- dir.Delete(true);
+ // Plugin may create directories in its cache directory
+ dir.Delete(recursive: true);
}
catch (Exception e)
{
@@ -238,6 +248,18 @@ public partial class SettingsPaneAboutViewModel : BaseModel
}
});
+ // Then, delete plugin directory
+ var dir = GetPluginCacheDir();
+ try
+ {
+ dir.Delete(recursive: false);
+ }
+ catch (Exception e)
+ {
+ App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
+ success = false;
+ }
+
OnPropertyChanged(nameof(CacheFolderSize));
return success;
@@ -248,6 +270,11 @@ public partial class SettingsPaneAboutViewModel : BaseModel
return new DirectoryInfo(DataLocation.CacheDirectory);
}
+ private static DirectoryInfo GetPluginCacheDir()
+ {
+ return new DirectoryInfo(DataLocation.PluginCacheDirectory);
+ }
+
private static List GetCacheFiles()
{
return GetCacheDir().EnumerateFiles("*", SearchOption.AllDirectories).ToList();
@@ -268,4 +295,30 @@ public partial class SettingsPaneAboutViewModel : BaseModel
return "0 B";
}
+
+ public string SettingWindowFont
+ {
+ get => _settings.SettingWindowFont;
+ set
+ {
+ if (_settings.SettingWindowFont != value)
+ {
+ _settings.SettingWindowFont = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ [RelayCommand]
+ private void ResetSettingWindowFont()
+ {
+ SettingWindowFont = Win32Helper.GetSystemDefaultFont(false);
+ }
+
+ [RelayCommand]
+ private void OpenReleaseNotes()
+ {
+ var releaseNotesWindow = new ReleaseNotesWindow();
+ releaseNotesWindow.Show();
+ }
}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
index 069a7cb24..bec59a2b1 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
@@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Windows.Forms;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core;
using Flow.Launcher.Core.Configuration;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
+using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
@@ -15,11 +17,12 @@ namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPaneGeneralViewModel : BaseModel
{
public Settings Settings { get; }
+
private readonly Updater _updater;
- private readonly IPortable _portable;
+ private readonly Portable _portable;
private readonly Internationalization _translater;
- public SettingsPaneGeneralViewModel(Settings settings, Updater updater, IPortable portable, Internationalization translater)
+ public SettingsPaneGeneralViewModel(Settings settings, Updater updater, Portable portable, Internationalization translater)
{
Settings = settings;
_updater = updater;
@@ -46,11 +49,11 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
{
if (UseLogonTaskForStartup)
{
- AutoStartup.EnableViaLogonTask();
+ AutoStartup.ChangeToViaLogonTask();
}
else
{
- AutoStartup.EnableViaRegistry();
+ AutoStartup.ChangeToViaRegistry();
}
}
else
@@ -76,7 +79,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
{
try
{
- if (UseLogonTaskForStartup)
+ if (value)
{
AutoStartup.ChangeToViaLogonTask();
}
@@ -151,11 +154,22 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
{
Settings.SearchDelayTime = value;
OnPropertyChanged();
- OnPropertyChanged(nameof(SearchDelayTimeDisplay));
}
}
}
- public string SearchDelayTimeDisplay => $"{SearchDelayTimeValue}ms";
+
+ public int MaxHistoryResultsToShowValue
+ {
+ get => Settings.MaxHistoryResultsToShowForHomePage;
+ set
+ {
+ if (Settings.MaxHistoryResultsToShowForHomePage != value)
+ {
+ Settings.MaxHistoryResultsToShowForHomePage = value;
+ OnPropertyChanged();
+ }
+ }
+ }
private void UpdateEnumDropdownLocalizations()
{
@@ -163,6 +177,8 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
DropdownDataGeneric.UpdateLabels(SearchWindowAligns);
DropdownDataGeneric.UpdateLabels(SearchPrecisionScores);
DropdownDataGeneric.UpdateLabels(LastQueryModes);
+ // Since we are using Binding instead of DynamicResource, we need to manually trigger the update
+ OnPropertyChanged(nameof(AlwaysPreviewToolTip));
}
public string Language
@@ -179,6 +195,70 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
}
}
+ #region Korean IME
+
+ // The new Korean IME used in Windows 11 has compatibility issues with WPF. This issue is difficult to resolve within
+ // WPF itself, but it can be avoided by having the user switch to the legacy IME at the system level. Therefore,
+ // we provide guidance and a direct button for users to make this change themselves. If the relevant registry key does
+ // not exist (i.e., the Korean IME is not installed), this setting will not be shown at all.
+
+ public bool LegacyKoreanIMEEnabled
+ {
+ get => Win32Helper.IsLegacyKoreanIMEEnabled();
+ set
+ {
+ if (Win32Helper.SetLegacyKoreanIMEEnabled(value))
+ {
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(KoreanIMERegistryValueIsZero));
+ }
+ else
+ {
+ //Since this is rarely seen text, language support is not provided.
+ App.API.ShowMsg("Failed to change Korean IME setting", "Please check your system registry access or contact support.");
+ }
+ }
+ }
+
+ public bool KoreanIMERegistryKeyExists
+ {
+ get
+ {
+ var registryKeyExists = Win32Helper.IsKoreanIMEExist();
+ var koreanLanguageInstalled = InputLanguage.InstalledInputLanguages.Cast().Any(lang => lang.Culture.Name.StartsWith("ko"));
+ var isWindows11 = Win32Helper.IsWindows11();
+
+ // Return true if Windows 11 with Korean IME installed, or if the registry key exists
+ return (isWindows11 && koreanLanguageInstalled) || registryKeyExists;
+ }
+ }
+
+ public bool KoreanIMERegistryValueIsZero
+ {
+ get
+ {
+ var value = Win32Helper.GetLegacyKoreanIMERegistryValue();
+ if (value is int intValue)
+ {
+ return intValue == 0;
+ }
+ else if (value != null && int.TryParse(value.ToString(), out var parsedValue))
+ {
+ return parsedValue == 0;
+ }
+
+ return false;
+ }
+ }
+
+ [RelayCommand]
+ private void OpenImeSettings()
+ {
+ Win32Helper.OpenImeSettings();
+ }
+
+ #endregion
+
public bool ShouldUsePinyin
{
get => Settings.ShouldUsePinyin;
@@ -257,14 +337,14 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
[RelayCommand]
private void SelectFileManager()
{
- var fileManagerChangeWindow = new SelectFileManagerWindow(Settings);
+ var fileManagerChangeWindow = new SelectFileManagerWindow();
fileManagerChangeWindow.ShowDialog();
}
[RelayCommand]
private void SelectBrowser()
{
- var browserWindow = new SelectBrowserWindow(Settings);
+ var browserWindow = new SelectBrowserWindow();
browserWindow.ShowDialog();
}
}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
index fd2c8e09f..07df0682d 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
@@ -2,7 +2,6 @@
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
-using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
@@ -10,10 +9,78 @@ namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPanePluginStoreViewModel : BaseModel
{
- public string FilterText { get; set; } = string.Empty;
+ private string filterText = string.Empty;
+ public string FilterText
+ {
+ get => filterText;
+ set
+ {
+ if (filterText != value)
+ {
+ filterText = value;
+ OnPropertyChanged();
+ }
+ }
+ }
- public IList ExternalPlugins =>
- App.API.GetPluginManifest()?.Select(p => new PluginStoreItemViewModel(p))
+ private bool showDotNet = true;
+ public bool ShowDotNet
+ {
+ get => showDotNet;
+ set
+ {
+ if (showDotNet != value)
+ {
+ showDotNet = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ private bool showPython = true;
+ public bool ShowPython
+ {
+ get => showPython;
+ set
+ {
+ if (showPython != value)
+ {
+ showPython = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ private bool showNodeJs = true;
+ public bool ShowNodeJs
+ {
+ get => showNodeJs;
+ set
+ {
+ if (showNodeJs != value)
+ {
+ showNodeJs = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ private bool showExecutable = true;
+ public bool ShowExecutable
+ {
+ get => showExecutable;
+ set
+ {
+ if (showExecutable != value)
+ {
+ showExecutable = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public IList ExternalPlugins => App.API.GetPluginManifest()?
+ .Select(p => new PluginStoreItemViewModel(p))
.OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease)
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated)
.ThenByDescending(p => p.Category == PluginStoreItemViewModel.None)
@@ -31,8 +98,29 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
public bool SatisfiesFilter(PluginStoreItemViewModel plugin)
{
+ // Check plugin language
+ var pluginShown = false;
+ if (AllowedLanguage.IsDotNet(plugin.Language))
+ {
+ pluginShown = ShowDotNet;
+ }
+ else if (AllowedLanguage.IsPython(plugin.Language))
+ {
+ pluginShown = ShowPython;
+ }
+ else if (AllowedLanguage.IsNodeJs(plugin.Language))
+ {
+ pluginShown = ShowNodeJs;
+ }
+ else if (AllowedLanguage.IsExecutable(plugin.Language))
+ {
+ pluginShown = ShowExecutable;
+ }
+ if (!pluginShown) return false;
+
+ // Check plugin name & description
return string.IsNullOrEmpty(FilterText) ||
- App.API.FuzzySearch(FilterText, plugin.Name).IsSearchPrecisionScoreMet() ||
- App.API.FuzzySearch(FilterText, plugin.Description).IsSearchPrecisionScoreMet();
+ App.API.FuzzySearch(FilterText, plugin.Name).IsSearchPrecisionScoreMet() ||
+ App.API.FuzzySearch(FilterText, plugin.Description).IsSearchPrecisionScoreMet();
}
}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs
index b89e970e9..3e1294bc2 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs
@@ -5,7 +5,6 @@ using System.Windows.Controls;
using System.Windows;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Plugin;
-using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel;
@@ -81,18 +80,42 @@ public partial class SettingsPanePluginsViewModel : BaseModel
}
}
+ private bool _isHomeOnOffSelected;
+ public bool IsHomeOnOffSelected
+ {
+ get => _isHomeOnOffSelected;
+ set
+ {
+ if (_isHomeOnOffSelected != value)
+ {
+ _isHomeOnOffSelected = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
public SettingsPanePluginsViewModel(Settings settings)
{
_settings = settings;
UpdateEnumDropdownLocalizations();
}
- public string FilterText { get; set; } = string.Empty;
+ private string filterText = string.Empty;
+ public string FilterText
+ {
+ get => filterText;
+ set
+ {
+ if (filterText != value)
+ {
+ filterText = value;
+ OnPropertyChanged();
+ }
+ }
+ }
- public PluginViewModel? SelectedPlugin { get; set; }
-
- private IEnumerable? _pluginViewModels;
- private IEnumerable PluginViewModels => _pluginViewModels ??= PluginManager.AllPlugins
+ private IList? _pluginViewModels;
+ public IList PluginViewModels => _pluginViewModels ??= PluginManager.AllPlugins
.OrderBy(plugin => plugin.Metadata.Disabled)
.ThenBy(plugin => plugin.Metadata.Name)
.Select(plugin => new PluginViewModel
@@ -103,19 +126,19 @@ public partial class SettingsPanePluginsViewModel : BaseModel
.Where(plugin => plugin.PluginSettingsObject != null)
.ToList();
- public List FilteredPluginViewModels => PluginViewModels
- .Where(v =>
- string.IsNullOrEmpty(FilterText) ||
- App.API.FuzzySearch(FilterText, v.PluginPair.Metadata.Name).IsSearchPrecisionScoreMet() ||
- App.API.FuzzySearch(FilterText, v.PluginPair.Metadata.Description).IsSearchPrecisionScoreMet()
- )
- .ToList();
+ public bool SatisfiesFilter(PluginViewModel plugin)
+ {
+ return string.IsNullOrEmpty(FilterText) ||
+ App.API.FuzzySearch(FilterText, plugin.PluginPair.Metadata.Name).IsSearchPrecisionScoreMet() ||
+ App.API.FuzzySearch(FilterText, plugin.PluginPair.Metadata.Description).IsSearchPrecisionScoreMet();
+ }
[RelayCommand]
- private async Task OpenHelperAsync()
+ private async Task OpenHelperAsync(Button button)
{
var helpDialog = new ContentDialog()
{
+ Owner = Window.GetWindow(button),
Content = new StackPanel
{
Children =
@@ -143,10 +166,21 @@ public partial class SettingsPanePluginsViewModel : BaseModel
{
Text = (string)Application.Current.Resources["searchDelayTimeTips"],
TextWrapping = TextWrapping.Wrap
+ },
+ new TextBlock
+ {
+ Text = (string)Application.Current.Resources["homeTitle"],
+ FontSize = 18,
+ Margin = new Thickness(0, 24, 0, 10),
+ TextWrapping = TextWrapping.Wrap
+ },
+ new TextBlock
+ {
+ Text = (string)Application.Current.Resources["homeTips"],
+ TextWrapping = TextWrapping.Wrap
}
}
},
-
PrimaryButtonText = (string)Application.Current.Resources["commonOK"],
CornerRadius = new CornerRadius(8),
Style = (Style)Application.Current.Resources["ContentDialog"]
@@ -168,16 +202,25 @@ public partial class SettingsPanePluginsViewModel : BaseModel
IsOnOffSelected = false;
IsPrioritySelected = true;
IsSearchDelaySelected = false;
+ IsHomeOnOffSelected = false;
break;
case DisplayMode.SearchDelay:
IsOnOffSelected = false;
IsPrioritySelected = false;
IsSearchDelaySelected = true;
+ IsHomeOnOffSelected = false;
+ break;
+ case DisplayMode.HomeOnOff:
+ IsOnOffSelected = false;
+ IsPrioritySelected = false;
+ IsSearchDelaySelected = false;
+ IsHomeOnOffSelected = true;
break;
default:
IsOnOffSelected = true;
IsPrioritySelected = false;
IsSearchDelaySelected = false;
+ IsHomeOnOffSelected = false;
break;
}
}
@@ -187,5 +230,6 @@ public enum DisplayMode
{
OnOff,
Priority,
- SearchDelay
+ SearchDelay,
+ HomeOnOff
}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneProxyViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneProxyViewModel.cs
index 38a6ef31e..6cddee8d8 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneProxyViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneProxyViewModel.cs
@@ -1,4 +1,6 @@
using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core;
using Flow.Launcher.Infrastructure.UserSettings;
@@ -8,49 +10,43 @@ namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPaneProxyViewModel : BaseModel
{
- private readonly Updater _updater;
public Settings Settings { get; }
+ private readonly Updater _updater;
+
public SettingsPaneProxyViewModel(Settings settings, Updater updater)
{
- _updater = updater;
Settings = settings;
+ _updater = updater;
}
[RelayCommand]
- private void OnTestProxyClicked()
+ private async Task OnTestProxyClickedAsync()
{
- var message = TestProxy();
+ var message = await TestProxyAsync();
App.API.ShowMsgBox(App.API.GetTranslation(message));
}
- private string TestProxy()
+ private async Task TestProxyAsync()
{
if (string.IsNullOrEmpty(Settings.Proxy.Server)) return "serverCantBeEmpty";
if (Settings.Proxy.Port <= 0) return "portCantBeEmpty";
- HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_updater.GitHubRepository);
+ var handler = new HttpClientHandler
+ {
+ Proxy = new WebProxy(Settings.Proxy.Server, Settings.Proxy.Port)
+ };
- if (string.IsNullOrEmpty(Settings.Proxy.UserName) || string.IsNullOrEmpty(Settings.Proxy.Password))
+ if (!string.IsNullOrEmpty(Settings.Proxy.UserName) && !string.IsNullOrEmpty(Settings.Proxy.Password))
{
- request.Proxy = new WebProxy(Settings.Proxy.Server, Settings.Proxy.Port);
- }
- else
- {
- request.Proxy = new WebProxy(Settings.Proxy.Server, Settings.Proxy.Port)
- {
- Credentials = new NetworkCredential(Settings.Proxy.UserName, Settings.Proxy.Password)
- };
+ handler.Proxy.Credentials = new NetworkCredential(Settings.Proxy.UserName, Settings.Proxy.Password);
}
+ using var client = new HttpClient(handler);
try
{
- var response = (HttpWebResponse)request.GetResponse();
- return response.StatusCode switch
- {
- HttpStatusCode.OK => "proxyIsCorrect",
- _ => "proxyConnectFailed"
- };
+ var response = await client.GetAsync(_updater.GitHubRepository);
+ return response.IsSuccessStatusCode ? "proxyIsCorrect" : "proxyConnectFailed";
}
catch
{
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs
index f78704ef2..79465cd71 100644
--- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs
@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Windows;
+using System.Windows.Controls;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows.Media;
-using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
@@ -21,10 +21,12 @@ namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPaneThemeViewModel : BaseModel
{
- private const string DefaultFont = "Segoe UI";
- public string BackdropSubText => !Win32Helper.IsBackdropSupported() ? App.API.GetTranslation("BackdropTypeDisabledToolTip") : "";
public Settings Settings { get; }
- private readonly Theme _theme = Ioc.Default.GetRequiredService();
+
+ private readonly Theme _theme;
+
+ private readonly string DefaultFont = Win32Helper.GetSystemDefaultFont();
+ public string BackdropSubText => !Win32Helper.IsBackdropSupported() ? App.API.GetTranslation("BackdropTypeDisabledToolTip") : "";
public static string LinkHowToCreateTheme => @"https://www.flowlauncher.com/theme-builder/";
public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438";
@@ -288,59 +290,14 @@ public partial class SettingsPaneThemeViewModel : BaseModel
set => Settings.UseDate = value;
}
+ public FontFamily ClockPanelFont { get; }
+
public Brush PreviewBackground
{
get => WallpaperPathRetrieval.GetWallpaperBrush();
}
- public ResultsViewModel PreviewResults
- {
- get
- {
- var results = new List
- {
- new()
- {
- Title = App.API.GetTranslation("SampleTitleExplorer"),
- SubTitle = App.API.GetTranslation("SampleSubTitleExplorer"),
- IcoPath = Path.Combine(
- Constant.ProgramDirectory,
- @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png"
- )
- },
- new()
- {
- Title = App.API.GetTranslation("SampleTitleWebSearch"),
- SubTitle = App.API.GetTranslation("SampleSubTitleWebSearch"),
- IcoPath = Path.Combine(
- Constant.ProgramDirectory,
- @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png"
- )
- },
- new()
- {
- Title = App.API.GetTranslation("SampleTitleProgram"),
- SubTitle = App.API.GetTranslation("SampleSubTitleProgram"),
- IcoPath = Path.Combine(
- Constant.ProgramDirectory,
- @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png"
- )
- },
- new()
- {
- Title = App.API.GetTranslation("SampleTitleProcessKiller"),
- SubTitle = App.API.GetTranslation("SampleSubTitleProcessKiller"),
- IcoPath = Path.Combine(
- Constant.ProgramDirectory,
- @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png"
- )
- }
- };
- var vm = new ResultsViewModel(Settings);
- vm.AddResults(results, "PREVIEW");
- return vm;
- }
- }
+ public ResultsViewModel PreviewResults { get; }
public FontFamily SelectedQueryBoxFont
{
@@ -478,9 +435,54 @@ public partial class SettingsPaneThemeViewModel : BaseModel
public string ThemeImage => Constant.QueryTextBoxIconImagePath;
- public SettingsPaneThemeViewModel(Settings settings)
+ public SettingsPaneThemeViewModel(Settings settings, Theme theme)
{
Settings = settings;
+ _theme = theme;
+ ClockPanelFont = new FontFamily(DefaultFont);
+ var results = new List
+ {
+ new()
+ {
+ Title = App.API.GetTranslation("SampleTitleExplorer"),
+ SubTitle = App.API.GetTranslation("SampleSubTitleExplorer"),
+ IcoPath = Path.Combine(
+ Constant.ProgramDirectory,
+ @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png"
+ )
+ },
+ new()
+ {
+ Title = App.API.GetTranslation("SampleTitleWebSearch"),
+ SubTitle = App.API.GetTranslation("SampleSubTitleWebSearch"),
+ IcoPath = Path.Combine(
+ Constant.ProgramDirectory,
+ @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png"
+ )
+ },
+ new()
+ {
+ Title = App.API.GetTranslation("SampleTitleProgram"),
+ SubTitle = App.API.GetTranslation("SampleSubTitleProgram"),
+ IcoPath = Path.Combine(
+ Constant.ProgramDirectory,
+ @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png"
+ )
+ },
+ new()
+ {
+ Title = App.API.GetTranslation("SampleTitleProcessKiller"),
+ SubTitle = App.API.GetTranslation("SampleSubTitleProcessKiller"),
+ IcoPath = Path.Combine(
+ Constant.ProgramDirectory,
+ @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png"
+ )
+ }
+ };
+ // Set main view model to null because the results are for preview only
+ var vm = new ResultsViewModel(Settings, null);
+ vm.AddResults(results, "PREVIEW");
+ PreviewResults = vm;
}
[RelayCommand]
@@ -494,7 +496,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
{
SelectedQueryBoxFont = new FontFamily(DefaultFont);
SelectedQueryBoxFontFaces = new FamilyTypeface { Stretch = FontStretches.Normal, Weight = FontWeights.Normal, Style = FontStyles.Normal };
- QueryBoxFontSize = 20;
+ QueryBoxFontSize = 16;
SelectedResultFont = new FontFamily(DefaultFont);
SelectedResultFontFaces = new FamilyTypeface { Stretch = FontStretches.Normal, Weight = FontWeights.Normal, Style = FontStyles.Normal };
@@ -507,4 +509,56 @@ public partial class SettingsPaneThemeViewModel : BaseModel
WindowHeightSize = 42;
ItemHeightSize = 58;
}
+
+ [RelayCommand]
+ private void Import()
+ {
+ var resourceDictionary = _theme.GetCurrentResourceDictionary();
+
+ if (resourceDictionary["QueryBoxStyle"] is Style queryBoxStyle)
+ {
+ var fontSizeSetter = queryBoxStyle.Setters
+ .OfType()
+ .FirstOrDefault(setter => setter.Property == TextBox.FontSizeProperty);
+ if (fontSizeSetter?.Value is double fontSize)
+ {
+ QueryBoxFontSize = fontSize;
+ }
+
+ var heightSetter = queryBoxStyle.Setters
+ .OfType()
+ .FirstOrDefault(setter => setter.Property == FrameworkElement.HeightProperty);
+ if (heightSetter?.Value is double height)
+ {
+ WindowHeightSize = height;
+ }
+ }
+
+ if (resourceDictionary["ResultItemHeight"] is double resultItemHeight)
+ {
+ ItemHeightSize = resultItemHeight;
+ }
+
+ if (resourceDictionary["ItemTitleStyle"] is Style itemTitleStyle)
+ {
+ var fontSizeSetter = itemTitleStyle.Setters
+ .OfType()
+ .FirstOrDefault(setter => setter.Property == TextBlock.FontSizeProperty);
+ if (fontSizeSetter?.Value is double fontSize)
+ {
+ ResultItemFontSize = fontSize;
+ }
+ }
+
+ if (resourceDictionary["ItemSubTitleStyle"] is Style itemSubTitleStyle)
+ {
+ var fontSizeSetter = itemSubTitleStyle.Setters
+ .OfType()
+ .FirstOrDefault(setter => setter.Property == TextBlock.FontSizeProperty);
+ if (fontSizeSetter?.Value is double fontSize)
+ {
+ ResultSubItemFontSize = fontSize;
+ }
+ }
+ }
}
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml
index f7deee7cf..5710138c0 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml
@@ -12,6 +12,11 @@
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
+
+
+
+
+
-
+
@@ -98,10 +103,8 @@
Margin="0 0 12 0"
Command="{Binding AskClearLogFolderConfirmationCommand}"
Content="{Binding LogFolderSize, Mode=OneWay}" />
-
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ ();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
+ // Sometimes the navigation is not triggered by button click,
+ // so we need to reset the page type
+ _settingViewModel.PageType = typeof(SettingsPaneAbout);
+
+ // If the navigation is not triggered by button click, view model will be null again
+ if (_viewModel == null)
+ {
+ _viewModel = Ioc.Default.GetRequiredService();
+ DataContext = _viewModel;
+ }
if (!IsInitialized)
{
- var settings = Ioc.Default.GetRequiredService();
- var updater = Ioc.Default.GetRequiredService();
- _viewModel = new SettingsPaneAboutViewModel(settings, updater);
- DataContext = _viewModel;
InitializeComponent();
}
base.OnNavigatedTo(e);
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
index 657e97f30..c3901a746 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
@@ -3,6 +3,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
+ xmlns:converters="clr-namespace:Flow.Launcher.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ext="clr-namespace:Flow.Launcher.Resources.MarkupExtensions"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -14,6 +15,9 @@
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -312,6 +347,34 @@
SelectedValue="{Binding Language}"
SelectedValuePath="LanguageCode" />
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml.cs
index 569d489d2..753cb7b0e 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml.cs
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml.cs
@@ -1,27 +1,29 @@
using System.Windows.Navigation;
using CommunityToolkit.Mvvm.DependencyInjection;
-using Flow.Launcher.Core;
-using Flow.Launcher.Core.Configuration;
-using Flow.Launcher.Core.Resource;
-using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.SettingPages.ViewModels;
+using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneGeneral
{
private SettingsPaneGeneralViewModel _viewModel = null!;
+ private readonly SettingWindowViewModel _settingViewModel = Ioc.Default.GetRequiredService();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
+ // Sometimes the navigation is not triggered by button click,
+ // so we need to reset the page type
+ _settingViewModel.PageType = typeof(SettingsPaneGeneral);
+
+ // If the navigation is not triggered by button click, view model will be null again
+ if (_viewModel == null)
+ {
+ _viewModel = Ioc.Default.GetRequiredService();
+ DataContext = _viewModel;
+ }
if (!IsInitialized)
{
- var settings = Ioc.Default.GetRequiredService();
- var updater = Ioc.Default.GetRequiredService();
- var portable = Ioc.Default.GetRequiredService();
- var translater = Ioc.Default.GetRequiredService();
- _viewModel = new SettingsPaneGeneralViewModel(settings, updater, portable, translater);
- DataContext = _viewModel;
InitializeComponent();
}
base.OnNavigatedTo(e);
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml
index b1d72ede5..89eb2dccd 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml
@@ -89,7 +89,10 @@
Title="{DynamicResource ToggleHistoryHotkey}"
Icon=""
Type="Inside">
-
+
-
+
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs
index eb100da0c..202869bc5 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs
@@ -1,21 +1,29 @@
using System.Windows.Navigation;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.SettingPages.ViewModels;
-using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneHotkey
{
private SettingsPaneHotkeyViewModel _viewModel = null!;
+ private readonly SettingWindowViewModel _settingViewModel = Ioc.Default.GetRequiredService();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
+ // Sometimes the navigation is not triggered by button click,
+ // so we need to reset the page type
+ _settingViewModel.PageType = typeof(SettingsPaneHotkey);
+
+ // If the navigation is not triggered by button click, view model will be null again
+ if (_viewModel == null)
+ {
+ _viewModel = Ioc.Default.GetRequiredService();
+ DataContext = _viewModel;
+ }
if (!IsInitialized)
{
- var settings = Ioc.Default.GetRequiredService();
- _viewModel = new SettingsPaneHotkeyViewModel(settings);
- DataContext = _viewModel;
InitializeComponent();
}
base.OnNavigatedTo(e);
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml
index 89d25377b..9312b0c2d 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml
@@ -27,8 +27,8 @@
-
-
+
+
@@ -37,9 +37,9 @@
+ Padding="5 18 0 0">
+ Margin="5 24 0 0">
-
-
-
-
-
-
+ Orientation="Horizontal">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs
index 3bd24bc13..c0a77957a 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs
+++ b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs
@@ -5,21 +5,28 @@ using System.Windows.Navigation;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.SettingPages.ViewModels;
using Flow.Launcher.ViewModel;
-using Flow.Launcher.Infrastructure.UserSettings;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPanePluginStore
{
private SettingsPanePluginStoreViewModel _viewModel = null!;
+ private readonly SettingWindowViewModel _settingViewModel = Ioc.Default.GetRequiredService();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
+ // Sometimes the navigation is not triggered by button click,
+ // so we need to reset the page type
+ _settingViewModel.PageType = typeof(SettingsPanePluginStore);
+
+ // If the navigation is not triggered by button click, view model will be null again
+ if (_viewModel == null)
+ {
+ _viewModel = Ioc.Default.GetRequiredService();
+ DataContext = _viewModel;
+ }
if (!IsInitialized)
{
- var settings = Ioc.Default.GetRequiredService();
- _viewModel = new SettingsPanePluginStoreViewModel();
- DataContext = _viewModel;
InitializeComponent();
}
_viewModel.PropertyChanged += ViewModel_PropertyChanged;
@@ -28,9 +35,15 @@ public partial class SettingsPanePluginStore
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
- if (e.PropertyName == nameof(SettingsPanePluginStoreViewModel.FilterText))
+ switch (e.PropertyName)
{
- ((CollectionViewSource)FindResource("PluginStoreCollectionView")).View.Refresh();
+ case nameof(SettingsPanePluginStoreViewModel.FilterText):
+ case nameof(SettingsPanePluginStoreViewModel.ShowDotNet):
+ case nameof(SettingsPanePluginStoreViewModel.ShowPython):
+ case nameof(SettingsPanePluginStoreViewModel.ShowNodeJs):
+ case nameof(SettingsPanePluginStoreViewModel.ShowExecutable):
+ ((CollectionViewSource)FindResource("PluginStoreCollectionView")).View.Refresh();
+ break;
}
}
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml
index f9f708314..52d77f914 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml
@@ -15,6 +15,12 @@
FocusManager.FocusedElement="{Binding ElementName=PluginFilterTextbox}"
KeyDown="SettingsPanePlugins_OnKeyDown"
mc:Ignorable="d">
+
+
+
@@ -57,9 +63,10 @@
Height="34"
Margin="0 0 20 0"
Command="{Binding OpenHelperCommand}"
- Content=""
- FontFamily="{DynamicResource SymbolThemeFontFamily}"
- FontSize="14" />
+ CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
+ FontSize="14">
+
+
();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
+ // Sometimes the navigation is not triggered by button click,
+ // so we need to reset the page type
+ _settingViewModel.PageType = typeof(SettingsPanePlugins);
+
+ // If the navigation is not triggered by button click, view model will be null again
+ if (_viewModel == null)
+ {
+ _viewModel = Ioc.Default.GetRequiredService();
+ DataContext = _viewModel;
+ }
if (!IsInitialized)
{
- var settings = Ioc.Default.GetRequiredService();
- _viewModel = new SettingsPanePluginsViewModel(settings);
- DataContext = _viewModel;
InitializeComponent();
}
+ _viewModel.PropertyChanged += ViewModel_PropertyChanged;
base.OnNavigatedTo(e);
}
+ private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(SettingsPanePluginsViewModel.FilterText))
+ {
+ ((CollectionViewSource)FindResource("PluginCollectionView")).View.Refresh();
+ }
+ }
+
+ protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
+ {
+ _viewModel.PropertyChanged -= ViewModel_PropertyChanged;
+ base.OnNavigatingFrom(e);
+ }
+
private void SettingsPanePlugins_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key is not Key.F || Keyboard.Modifiers is not ModifierKeys.Control) return;
PluginFilterTextbox.Focus();
}
+
+ private void PluginCollectionView_OnFilter(object sender, FilterEventArgs e)
+ {
+ if (e.Item is not PluginViewModel plugin)
+ {
+ e.Accepted = false;
+ return;
+ }
+
+ e.Accepted = _viewModel.SatisfiesFilter(plugin);
+ }
}
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml
index 768abbf97..f429a6e29 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml
@@ -2,11 +2,11 @@
x:Class="Flow.Launcher.SettingPages.Views.SettingsPaneProxy"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ui="http://schemas.modernwpf.com/2019"
- xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
xmlns:viewModels="clr-namespace:Flow.Launcher.SettingPages.ViewModels"
Title="Proxy"
d:DataContext="{d:DesignInstance viewModels:SettingsPaneProxyViewModel}"
@@ -18,8 +18,8 @@
@@ -71,9 +71,9 @@
+ IsEnabled="{Binding Settings.Proxy.Enabled}" />
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml.cs
index 26350b8bb..3e617229d 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml.cs
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml.cs
@@ -1,26 +1,31 @@
using System.Windows.Navigation;
using CommunityToolkit.Mvvm.DependencyInjection;
-using Flow.Launcher.Core;
using Flow.Launcher.SettingPages.ViewModels;
-using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneProxy
{
private SettingsPaneProxyViewModel _viewModel = null!;
+ private readonly SettingWindowViewModel _settingViewModel = Ioc.Default.GetRequiredService();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
+ // Sometimes the navigation is not triggered by button click,
+ // so we need to reset the page type
+ _settingViewModel.PageType = typeof(SettingsPaneProxy);
+
+ // If the navigation is not triggered by button click, view model will be null again
+ if (_viewModel == null)
+ {
+ _viewModel = Ioc.Default.GetRequiredService();
+ DataContext = _viewModel;
+ }
if (!IsInitialized)
{
- var settings = Ioc.Default.GetRequiredService();
- var updater = Ioc.Default.GetRequiredService();
- _viewModel = new SettingsPaneProxyViewModel(settings, updater);
- DataContext = _viewModel;
InitializeComponent();
}
-
base.OnNavigatedTo(e);
}
}
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml
index 49306cd2d..6e16012ac 100644
--- a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml
@@ -256,10 +256,17 @@
BorderThickness="1"
Style="{StaticResource SettingSeparatorStyle}" />
+
+ Content="{DynamicResource resetCustomize}"
+ ToolTip="{DynamicResource resetCustomizeToolTip}" />
@@ -305,7 +312,7 @@
@@ -324,20 +331,22 @@
IsReadOnly="True"
Style="{DynamicResource QueryBoxStyle}"
Text="{DynamicResource hiThere}" />
-
+
@@ -365,11 +374,22 @@
IsHitTestVisible="False"
Visibility="Visible" />
-
+
+
+
+
@@ -698,11 +718,10 @@
-
+
+
+
+
+
+
+
+
+
+
+
();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
+ // Sometimes the navigation is not triggered by button click,
+ // so we need to reset the page type
+ _settingViewModel.PageType = typeof(SettingsPaneTheme);
+
+ // If the navigation is not triggered by button click, view model will be null again
+ if (_viewModel == null)
+ {
+ _viewModel = Ioc.Default.GetRequiredService();
+ DataContext = _viewModel;
+ }
if (!IsInitialized)
{
- var settings = Ioc.Default.GetRequiredService();
- _viewModel = new SettingsPaneThemeViewModel(settings);
- DataContext = _viewModel;
InitializeComponent();
}
-
base.OnNavigatedTo(e);
}
}
diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml
index 6d629de63..da128266c 100644
--- a/Flow.Launcher/SettingWindow.xaml
+++ b/Flow.Launcher/SettingWindow.xaml
@@ -13,15 +13,17 @@
MinHeight="600"
d:DataContext="{d:DesignInstance vm:SettingWindowViewModel}"
Closed="OnClosed"
+ FontFamily="{DynamicResource SettingWindowFont}"
Icon="Images\app.ico"
Left="{Binding SettingWindowLeft, Mode=TwoWay}"
Loaded="OnLoaded"
+ LocationChanged="Window_LocationChanged"
MouseDown="window_MouseDown"
ResizeMode="CanResize"
SnapsToDevicePixels="True"
- UseLayoutRounding="True"
StateChanged="Window_StateChanged"
Top="{Binding SettingWindowTop, Mode=TwoWay}"
+ UseLayoutRounding="True"
WindowStartupLocation="Manual"
mc:Ignorable="d">
diff --git a/Flow.Launcher/SettingWindow.xaml.cs b/Flow.Launcher/SettingWindow.xaml.cs
index 28140f024..c1c0f96a7 100644
--- a/Flow.Launcher/SettingWindow.xaml.cs
+++ b/Flow.Launcher/SettingWindow.xaml.cs
@@ -1,55 +1,91 @@
using System;
+using System.ComponentModel;
using System.Windows;
-using System.Windows.Forms;
+using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
-using Flow.Launcher.Plugin;
using Flow.Launcher.SettingPages.Views;
using Flow.Launcher.ViewModel;
using ModernWpf.Controls;
-using TextBox = System.Windows.Controls.TextBox;
+using Screen = System.Windows.Forms.Screen;
namespace Flow.Launcher;
public partial class SettingWindow
{
- private readonly IPublicAPI _api;
+ #region Private Fields
+
private readonly Settings _settings;
private readonly SettingWindowViewModel _viewModel;
+ #endregion
+
+ #region Constructor
+
public SettingWindow()
{
- var viewModel = Ioc.Default.GetRequiredService();
_settings = Ioc.Default.GetRequiredService();
- DataContext = viewModel;
- _viewModel = viewModel;
- _api = Ioc.Default.GetRequiredService();
- InitializePosition();
+ _viewModel = Ioc.Default.GetRequiredService();
+ DataContext = _viewModel;
+ // Since WindowStartupLocation is set to Manual, initialize the window position before calling InitializeComponent
+ UpdatePositionAndState();
InitializeComponent();
}
+ #endregion
+
+ #region Window Events
+
private void OnLoaded(object sender, RoutedEventArgs e)
{
RefreshMaximizeRestoreButton();
+
// Fix (workaround) for the window freezes after lock screen (Win+L) or sleep
// https://stackoverflow.com/questions/4951058/software-rendering-mode-wpf
HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
HwndTarget hwndTarget = hwndSource.CompositionTarget;
hwndTarget.RenderMode = RenderMode.SoftwareOnly; // Must use software only render mode here
- InitializePosition();
+ UpdatePositionAndState();
+
+ _viewModel.PropertyChanged += ViewModel_PropertyChanged;
+ }
+
+ // Sometimes the navigation is not triggered by button click,
+ // so we need to update the selected item here
+ private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ switch (e.PropertyName)
+ {
+ case nameof(SettingWindowViewModel.PageType):
+ var selectedIndex = _viewModel.PageType.Name switch
+ {
+ nameof(SettingsPaneGeneral) => 0,
+ nameof(SettingsPanePlugins) => 1,
+ nameof(SettingsPanePluginStore) => 2,
+ nameof(SettingsPaneTheme) => 3,
+ nameof(SettingsPaneHotkey) => 4,
+ nameof(SettingsPaneProxy) => 5,
+ nameof(SettingsPaneAbout) => 6,
+ _ => 0
+ };
+ NavView.SelectedItem = NavView.MenuItems[selectedIndex];
+ break;
+ }
}
private void OnClosed(object sender, EventArgs e)
{
- _settings.SettingWindowState = WindowState;
- _settings.SettingWindowTop = Top;
- _settings.SettingWindowLeft = Left;
- _viewModel.Save();
- _api.SavePluginSettings();
+ _viewModel.PropertyChanged -= ViewModel_PropertyChanged;
+
+ // If app is exiting, settings save is not needed because main window closing event will handle this
+ if (App.LoadingOrExiting) return;
+ // Save settings when window is closed
+ _settings.Save();
+ App.API.SavePluginSettings();
}
private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e)
@@ -59,15 +95,32 @@ public partial class SettingWindow
private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */
{
- if (Keyboard.FocusedElement is not TextBox textBox)
- {
- return;
- }
+ if (Keyboard.FocusedElement is not TextBox textBox) return;
var tRequest = new TraversalRequest(FocusNavigationDirection.Next);
textBox.MoveFocus(tRequest);
}
- /* Custom TitleBar */
+ private void Window_StateChanged(object sender, EventArgs e)
+ {
+ RefreshMaximizeRestoreButton();
+ if (IsLoaded)
+ {
+ _settings.SettingWindowState = WindowState;
+ }
+ }
+
+ private void Window_LocationChanged(object sender, EventArgs e)
+ {
+ if (IsLoaded)
+ {
+ _settings.SettingWindowTop = Top;
+ _settings.SettingWindowLeft = Left;
+ }
+ }
+
+ #endregion
+
+ #region Window Custom TitleBar
private void OnMinimizeButtonClick(object sender, RoutedEventArgs e)
{
@@ -102,29 +155,51 @@ public partial class SettingWindow
}
}
- private void Window_StateChanged(object sender, EventArgs e)
- {
- RefreshMaximizeRestoreButton();
- }
+ #endregion
- public void InitializePosition()
+ #region Window Position
+
+ public void UpdatePositionAndState()
{
var previousTop = _settings.SettingWindowTop;
var previousLeft = _settings.SettingWindowLeft;
if (previousTop == null || previousLeft == null || !IsPositionValid(previousTop.Value, previousLeft.Value))
{
- Top = WindowTop();
- Left = WindowLeft();
+ SetWindowPosition(WindowTop(), WindowLeft());
}
else
{
- Top = previousTop.Value;
- Left = previousLeft.Value;
+ var left = _settings.SettingWindowLeft.Value;
+ var top = _settings.SettingWindowTop.Value;
+ AdjustWindowPosition(ref top, ref left);
+ SetWindowPosition(top, left);
}
+
WindowState = _settings.SettingWindowState;
}
+ private void SetWindowPosition(double top, double left)
+ {
+ // Ensure window does not exceed screen boundaries
+ top = Math.Max(top, SystemParameters.VirtualScreenTop);
+ left = Math.Max(left, SystemParameters.VirtualScreenLeft);
+ top = Math.Min(top, SystemParameters.VirtualScreenHeight - ActualHeight);
+ left = Math.Min(left, SystemParameters.VirtualScreenWidth - ActualWidth);
+
+ Top = top;
+ Left = left;
+ }
+
+ private void AdjustWindowPosition(ref double top, ref double left)
+ {
+ // Adjust window position if it exceeds screen boundaries
+ top = Math.Max(top, SystemParameters.VirtualScreenTop);
+ left = Math.Max(left, SystemParameters.VirtualScreenLeft);
+ top = Math.Min(top, SystemParameters.VirtualScreenHeight - ActualHeight);
+ left = Math.Min(left, SystemParameters.VirtualScreenWidth - ActualWidth);
+ }
+
private static bool IsPositionValid(double top, double left)
{
foreach (var screen in Screen.AllScreens)
@@ -158,10 +233,15 @@ public partial class SettingWindow
return top;
}
+ #endregion
+
+ #region Navigation View Events
+
private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
if (args.IsSettingsSelected)
{
+ _viewModel.SetPageType(typeof(SettingsPaneGeneral));
ContentFrame.Navigate(typeof(SettingsPaneGeneral));
}
else
@@ -184,7 +264,11 @@ public partial class SettingWindow
nameof(About) => typeof(SettingsPaneAbout),
_ => typeof(SettingsPaneGeneral)
};
- ContentFrame.Navigate(pageType);
+ // Only navigate if the page type changes to fix navigation forward/back issue
+ if (_viewModel.SetPageType(pageType))
+ {
+ ContentFrame.Navigate(pageType);
+ }
}
}
@@ -202,6 +286,9 @@ public partial class SettingWindow
private void ContentFrame_Loaded(object sender, RoutedEventArgs e)
{
- NavView.SelectedItem ??= NavView.MenuItems[0]; /* Set First Page */
+ _viewModel.SetPageType(null); // Set page type to null so that NavigationView_SelectionChanged can navigate the frame
+ NavView.SelectedItem = NavView.MenuItems[0]; /* Set First Page */
}
+
+ #endregion
}
diff --git a/Flow.Launcher/Storage/TopMostRecord.cs b/Flow.Launcher/Storage/TopMostRecord.cs
index 7f35904a5..7fc9dcaaa 100644
--- a/Flow.Launcher/Storage/TopMostRecord.cs
+++ b/Flow.Launcher/Storage/TopMostRecord.cs
@@ -1,14 +1,153 @@
-using System.Collections.Concurrent;
+using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
using System.Text.Json.Serialization;
+using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Storage
{
- public class TopMostRecord
+ public class FlowLauncherJsonStorageTopMostRecord
+ {
+ private readonly FlowLauncherJsonStorage _topMostRecordStorage;
+ private readonly MultipleTopMostRecord _topMostRecord;
+
+ public FlowLauncherJsonStorageTopMostRecord()
+ {
+#pragma warning disable CS0618 // Type or member is obsolete
+ // Get old data & new data
+ var topMostRecordStorage = new FlowLauncherJsonStorage();
+#pragma warning restore CS0618 // Type or member is obsolete
+ _topMostRecordStorage = new FlowLauncherJsonStorage();
+
+ // Check if data exist
+ var oldDataExist = topMostRecordStorage.Exists();
+ var newDataExist = _topMostRecordStorage.Exists();
+
+ // If new data exist, it means we have already migrated the old data
+ // So we can safely delete the old data and load the new data
+ if (newDataExist)
+ {
+ try
+ {
+ topMostRecordStorage.Delete();
+ }
+ catch
+ {
+ // Ignored - Flow will delete the old data during next startup
+ }
+ _topMostRecord = _topMostRecordStorage.Load();
+ }
+ // If new data does not exist and old data exist, we need to migrate the old data to the new data
+ else if (oldDataExist)
+ {
+ // Migrate old data to new data
+ _topMostRecord = _topMostRecordStorage.Load();
+ var oldTopMostRecord = topMostRecordStorage.Load();
+ if (oldTopMostRecord == null || oldTopMostRecord.records.IsEmpty) return;
+ foreach (var record in oldTopMostRecord.records)
+ {
+ var newValue = new ConcurrentQueue();
+ newValue.Enqueue(record.Value);
+ _topMostRecord.records.AddOrUpdate(record.Key, newValue, (key, oldValue) =>
+ {
+ oldValue.Enqueue(record.Value);
+ return oldValue;
+ });
+ }
+
+ // Delete old data and save the new data
+ try
+ {
+ topMostRecordStorage.Delete();
+ }
+ catch
+ {
+ // Ignored - Flow will delete the old data during next startup
+ }
+ Save();
+ }
+ // If both data do not exist, we just need to create a new data
+ else
+ {
+ _topMostRecord = _topMostRecordStorage.Load();
+ }
+ }
+
+ public void Save()
+ {
+ _topMostRecordStorage.Save();
+ }
+
+ public bool IsTopMost(Result result)
+ {
+ return _topMostRecord.IsTopMost(result);
+ }
+
+ public int GetTopMostIndex(Result result)
+ {
+ return _topMostRecord.GetTopMostIndex(result);
+ }
+
+ public void Remove(Result result)
+ {
+ _topMostRecord.Remove(result);
+ }
+
+ public void AddOrUpdate(Result result)
+ {
+ _topMostRecord.AddOrUpdate(result);
+ }
+ }
+
+ ///
+ /// Old data structure to support only one top most record for the same query
+ ///
+ [Obsolete("Use MultipleTopMostRecord instead. This class will be removed in future versions.")]
+ internal class TopMostRecord
{
[JsonInclude]
- public ConcurrentDictionary records { get; private set; } = new ConcurrentDictionary();
+ public ConcurrentDictionary records { get; private set; } = new();
+
+ internal bool IsTopMost(Result result)
+ {
+ if (records.IsEmpty || !records.TryGetValue(result.OriginQuery.RawQuery, out var value))
+ {
+ return false;
+ }
+
+ // since this dictionary should be very small (or empty) going over it should be pretty fast.
+ return value.Equals(result);
+ }
+
+ internal void Remove(Result result)
+ {
+ records.Remove(result.OriginQuery.RawQuery, out _);
+ }
+
+ internal void AddOrUpdate(Result result)
+ {
+ var record = new Record
+ {
+ PluginID = result.PluginID,
+ Title = result.Title,
+ SubTitle = result.SubTitle,
+ RecordKey = result.RecordKey
+ };
+ records.AddOrUpdate(result.OriginQuery.RawQuery, record, (key, oldValue) => record);
+ }
+ }
+
+ ///
+ /// New data structure to support multiple top most records for the same query
+ ///
+ internal class MultipleTopMostRecord
+ {
+ [JsonInclude]
+ [JsonConverter(typeof(ConcurrentDictionaryConcurrentQueueConverter))]
+ public ConcurrentDictionary> records { get; private set; } = new();
internal bool IsTopMost(Result result)
{
@@ -21,19 +160,57 @@ namespace Flow.Launcher.Storage
}
// since this dictionary should be very small (or empty) going over it should be pretty fast.
- return value.Equals(result);
+ return value.Any(record => record.Equals(result));
+ }
+
+ internal int GetTopMostIndex(Result result)
+ {
+ // origin query is null when user select the context menu item directly of one item from query list
+ // in this case, we do not need to check if the result is top most
+ if (records.IsEmpty || result.OriginQuery == null ||
+ !records.TryGetValue(result.OriginQuery.RawQuery, out var value))
+ {
+ return -1;
+ }
+
+ // since this dictionary should be very small (or empty) going over it should be pretty fast.
+ // since the latter items should be more recent, we should return the smaller index for score to subtract
+ // which can make them more topmost
+ // A, B, C => 2, 1, 0 => (max - 2), (max - 1), (max - 0)
+ var index = 0;
+ foreach (var record in value)
+ {
+ if (record.Equals(result))
+ {
+ return value.Count - 1 - index;
+ }
+ index++;
+ }
+ return -1;
}
internal void Remove(Result result)
{
// origin query is null when user select the context menu item directly of one item from query list
// in this case, we do not need to remove the record
- if (result.OriginQuery == null)
+ if (result.OriginQuery == null ||
+ !records.TryGetValue(result.OriginQuery.RawQuery, out var value))
{
return;
}
- records.Remove(result.OriginQuery.RawQuery, out _);
+ // remove the record from the queue
+ var queue = new ConcurrentQueue(value.Where(r => !r.Equals(result)));
+ if (queue.IsEmpty)
+ {
+ // if the queue is empty, remove the queue from the dictionary
+ records.TryRemove(result.OriginQuery.RawQuery, out _);
+ }
+ else
+ {
+ // change the queue in the dictionary
+ records[result.OriginQuery.RawQuery] = queue;
+ }
}
internal void AddOrUpdate(Result result)
@@ -52,16 +229,56 @@ namespace Flow.Launcher.Storage
SubTitle = result.SubTitle,
RecordKey = result.RecordKey
};
- records.AddOrUpdate(result.OriginQuery.RawQuery, record, (key, oldValue) => record);
+ if (!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
+ {
+ // create a new queue if it does not exist
+ value = new ConcurrentQueue();
+ value.Enqueue(record);
+ records.TryAdd(result.OriginQuery.RawQuery, value);
+ }
+ else
+ {
+ // add or update the record in the queue
+ var queue = new ConcurrentQueue(value.Where(r => !r.Equals(result))); // make sure we don't have duplicates
+ queue.Enqueue(record);
+ records[result.OriginQuery.RawQuery] = queue;
+ }
}
}
- public class Record
+ ///
+ /// Because ConcurrentQueue does not support serialization, we need to convert it to a List
+ ///
+ internal class ConcurrentDictionaryConcurrentQueueConverter : JsonConverter>>
{
- public string Title { get; set; }
- public string SubTitle { get; set; }
- public string PluginID { get; set; }
- public string RecordKey { get; set; }
+ public override ConcurrentDictionary> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var dictionary = JsonSerializer.Deserialize>>(ref reader, options);
+ var concurrentDictionary = new ConcurrentDictionary>();
+ foreach (var kvp in dictionary)
+ {
+ concurrentDictionary.TryAdd(kvp.Key, new ConcurrentQueue(kvp.Value));
+ }
+ return concurrentDictionary;
+ }
+
+ public override void Write(Utf8JsonWriter writer, ConcurrentDictionary> value, JsonSerializerOptions options)
+ {
+ var dict = new Dictionary>();
+ foreach (var kvp in value)
+ {
+ dict.Add(kvp.Key, kvp.Value.ToList());
+ }
+ JsonSerializer.Serialize(writer, dict, options);
+ }
+ }
+
+ internal class Record
+ {
+ public string Title { get; init; }
+ public string SubTitle { get; init; }
+ public string PluginID { get; init; }
+ public string RecordKey { get; init; }
public bool Equals(Result r)
{
diff --git a/Flow.Launcher/Themes/Base.xaml b/Flow.Launcher/Themes/Base.xaml
index 35d1f9a41..a5ded7e59 100644
--- a/Flow.Launcher/Themes/Base.xaml
+++ b/Flow.Launcher/Themes/Base.xaml
@@ -108,6 +108,10 @@
+
@@ -180,9 +184,13 @@
@@ -68,6 +68,7 @@
x:Key="ItemTitleStyle"
BasedOn="{StaticResource BaseItemTitleStyle}"
TargetType="{x:Type TextBlock}">
+
@@ -75,6 +76,7 @@
x:Key="ItemSubTitleStyle"
BasedOn="{StaticResource BaseItemSubTitleStyle}"
TargetType="{x:Type TextBlock}">
+
@@ -84,7 +86,7 @@
TargetType="{x:Type Rectangle}">
-
+ #d6d4d7
@@ -113,7 +117,7 @@
+
+
+
64 0 4 0
@@ -172,7 +192,7 @@
BasedOn="{StaticResource ClockPanel}"
TargetType="{x:Type StackPanel}">
-
+
@@ -206,7 +226,7 @@
x:Key="PreviewBorderStyle"
BasedOn="{StaticResource BasePreviewBorderStyle}"
TargetType="{x:Type Border}">
-
+
+
-
+
@@ -59,7 +64,7 @@
-
+
@@ -79,7 +84,9 @@
+ TargetType="{x:Type Line}">
+
+
-
+
@@ -188,6 +195,7 @@
510 0 10 00 10 0 10
+ 58
-
@@ -109,7 +23,7 @@
+ Text="{Binding LocalizedDescription, Mode=OneTime}">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ VerticalAlignment="Center"
+ Text="{Binding FileEditorPath}"
+ TextWrapping="NoWrap" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ DockPanel.Dock="Right">
+
+
+
+
+
+
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ DockPanel.Dock="Right"
+ IsEnabled="{Binding ShowPreviewPanelDateTimeChoices}"
+ Visibility="{Binding PreviewPanelDateTimeChoicesVisibility}">
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs
index b7f5efc3c..4256c2ae0 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs
@@ -1,46 +1,45 @@
-using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
-using Flow.Launcher.Plugin.Explorer.ViewModels;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
+using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
+using Flow.Launcher.Plugin.Explorer.ViewModels;
using DataFormats = System.Windows.DataFormats;
using DragDropEffects = System.Windows.DragDropEffects;
using DragEventArgs = System.Windows.DragEventArgs;
namespace Flow.Launcher.Plugin.Explorer.Views
{
- ///
- /// Interaction logic for ExplorerSettings.xaml
- ///
public partial class ExplorerSettings
{
- private readonly SettingsViewModel viewModel;
-
- private List actionKeywordsListView;
-
+ private readonly SettingsViewModel _viewModel;
+ private readonly List _expanders;
public ExplorerSettings(SettingsViewModel viewModel)
{
+ _viewModel = viewModel;
DataContext = viewModel;
InitializeComponent();
- this.viewModel = viewModel;
-
DataContext = viewModel;
ActionKeywordModel.Init(viewModel.Settings);
- lbxAccessLinks.Items.SortDescriptions.Add(new SortDescription("Path", ListSortDirection.Ascending));
-
- lbxExcludedPaths.Items.SortDescriptions.Add(new SortDescription("Path", ListSortDirection.Ascending));
+ _expanders = new List
+ {
+ GeneralSettingsExpander,
+ ContextMenuExpander,
+ PreviewPanelExpander,
+ EverythingExpander,
+ ActionKeywordsExpander,
+ QuickAccessExpander,
+ ExcludedPathsExpander
+ };
}
-
-
private void AccessLinkDragDrop(string containerName, DragEventArgs e)
{
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
@@ -57,7 +56,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
{
Path = s
};
- viewModel.AppendLink(containerName, newFolderLink);
+ _viewModel.AppendLink(containerName, newFolderLink);
}
}
}
@@ -82,8 +81,8 @@ namespace Flow.Launcher.Plugin.Explorer.Views
{
if (tbFastSortWarning is not null)
{
- tbFastSortWarning.Visibility = viewModel.FastSortWarningVisibility;
- tbFastSortWarning.Text = viewModel.SortOptionWarningMessage;
+ tbFastSortWarning.Visibility = _viewModel.FastSortWarningVisibility;
+ tbFastSortWarning.Text = _viewModel.SortOptionWarningMessage;
}
}
private void LbxAccessLinks_OnDrop(object sender, DragEventArgs e)
@@ -99,5 +98,49 @@ namespace Flow.Launcher.Plugin.Explorer.Views
{
e.Handled = e.Text.ToCharArray().Any(c => !char.IsDigit(c));
}
+
+ private void Expander_Expanded(object sender, RoutedEventArgs e)
+ {
+ if (sender is Expander expandedExpander)
+ {
+ // Ensure _expanders is not null and contains items
+ if (_expanders == null || !_expanders.Any()) return;
+
+ foreach (var expander in _expanders)
+ {
+ if (expander != null && expander != expandedExpander && expander.IsExpanded)
+ {
+ expander.IsExpanded = false;
+ }
+ }
+ }
+ }
+
+ private void lbxAccessLinks_Loaded(object sender, RoutedEventArgs e)
+ {
+ lbxAccessLinks.Items.SortDescriptions.Add(new SortDescription("Path", ListSortDirection.Ascending));
+ }
+
+ private void lbxExcludedPaths_Loaded(object sender, RoutedEventArgs e)
+ {
+ lbxExcludedPaths.Items.SortDescriptions.Add(new SortDescription("Path", ListSortDirection.Ascending));
+ }
+
+ private void lbxAccessLinks_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ if (sender is not ListView listView) return;
+ if (listView.View is not GridView gView) return;
+
+ var workingWidth =
+ listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
+
+ if (workingWidth <= 0) return;
+
+ var col1 = 0.4;
+ var col2 = 0.6;
+
+ gView.Columns[0].Width = workingWidth * col1;
+ gView.Columns[1].Width = workingWidth * col2;
+ }
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml
index 22aa8f597..e200a187f 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml
@@ -117,7 +117,7 @@
HorizontalAlignment="Right"
VerticalAlignment="Top"
Style="{DynamicResource PreviewItemSubTitleStyle}"
- Text="{Binding FileSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"
+ Text="{Binding FileSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Mode=OneWay}"
TextWrapping="Wrap"
Visibility="{Binding FileSizeVisibility, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs
index aaf1efdc1..5714b0d0f 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs
@@ -1,7 +1,10 @@
-using System.ComponentModel;
+using System;
+using System.ComponentModel;
using System.Globalization;
using System.IO;
+using System.Linq;
using System.Runtime.CompilerServices;
+using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
@@ -15,8 +18,10 @@ namespace Flow.Launcher.Plugin.Explorer.Views;
public partial class PreviewPanel : UserControl, INotifyPropertyChanged
{
+ private static readonly string ClassName = nameof(PreviewPanel);
+
private string FilePath { get; }
- public string FileSize { get; } = "";
+ public string FileSize { get; private set; } = Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
public string CreatedAt { get; } = "";
public string LastModifiedAt { get; } = "";
private ImageSource _previewImage = new BitmapImage();
@@ -49,7 +54,7 @@ public partial class PreviewPanel : UserControl, INotifyPropertyChanged
? Visibility.Visible
: Visibility.Collapsed;
- public PreviewPanel(Settings settings, string filePath)
+ public PreviewPanel(Settings settings, string filePath, ResultType type)
{
InitializeComponent();
@@ -59,28 +64,32 @@ public partial class PreviewPanel : UserControl, INotifyPropertyChanged
if (Settings.ShowFileSizeInPreviewPanel)
{
- var fileSize = new FileInfo(filePath).Length;
- FileSize = ResultManager.ToReadableSize(fileSize, 2);
+ if (type == ResultType.File)
+ {
+ FileSize = GetFileSize(filePath);
+ }
+ else
+ {
+ _ = Task.Run(() =>
+ {
+ FileSize = GetFolderSize(filePath);
+ OnPropertyChanged(nameof(FileSize));
+ }).ConfigureAwait(false);
+ }
}
if (Settings.ShowCreatedDateInPreviewPanel)
{
- CreatedAt = File
- .GetCreationTime(filePath)
- .ToString(
- $"{Settings.PreviewPanelDateFormat} {Settings.PreviewPanelTimeFormat}",
- CultureInfo.CurrentCulture
- );
+ CreatedAt = type == ResultType.File ?
+ GetFileCreatedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel) :
+ GetFolderCreatedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
}
if (Settings.ShowModifiedDateInPreviewPanel)
{
- LastModifiedAt = File
- .GetLastWriteTime(filePath)
- .ToString(
- $"{Settings.PreviewPanelDateFormat} {Settings.PreviewPanelTimeFormat}",
- CultureInfo.CurrentCulture
- );
+ LastModifiedAt = type == ResultType.File ?
+ GetFileLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel) :
+ GetFolderLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
}
_ = LoadImageAsync();
@@ -91,6 +100,234 @@ public partial class PreviewPanel : UserControl, INotifyPropertyChanged
PreviewImage = await Main.Context.API.LoadImageAsync(FilePath, true).ConfigureAwait(false);
}
+ public static string GetFileSize(string filePath)
+ {
+ try
+ {
+ var fileInfo = new FileInfo(filePath);
+ return ResultManager.ToReadableSize(fileInfo.Length, 2);
+ }
+ catch (FileNotFoundException)
+ {
+ Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.LogException(ClassName, $"Failed to get file size for {filePath}", e);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+
+ public static string GetFileCreatedAt(string filePath, string previewPanelDateFormat, string previewPanelTimeFormat, bool showFileAgeInPreviewPanel)
+ {
+ try
+ {
+ var createdDate = File.GetCreationTime(filePath);
+ var formattedDate = createdDate.ToString(
+ $"{previewPanelDateFormat} {previewPanelTimeFormat}",
+ CultureInfo.CurrentCulture
+ );
+
+ var result = formattedDate;
+ if (showFileAgeInPreviewPanel) result = $"{GetFileAge(createdDate)} - {formattedDate}";
+ return result;
+ }
+ catch (FileNotFoundException)
+ {
+ Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.LogException(ClassName, $"Failed to get file created date for {filePath}", e);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+
+ public static string GetFileLastModifiedAt(string filePath, string previewPanelDateFormat, string previewPanelTimeFormat, bool showFileAgeInPreviewPanel)
+ {
+ try
+ {
+ var lastModifiedDate = File.GetLastWriteTime(filePath);
+ var formattedDate = lastModifiedDate.ToString(
+ $"{previewPanelDateFormat} {previewPanelTimeFormat}",
+ CultureInfo.CurrentCulture
+ );
+
+ var result = formattedDate;
+ if (showFileAgeInPreviewPanel) result = $"{GetFileAge(lastModifiedDate)} - {formattedDate}";
+ return result;
+ }
+ catch (FileNotFoundException)
+ {
+ Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.LogException(ClassName, $"Failed to get file modified date for {filePath}", e);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+
+ public static string GetFolderSize(string folderPath)
+ {
+ using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
+
+ try
+ {
+ // Use parallel enumeration for better performance
+ var directoryInfo = new DirectoryInfo(folderPath);
+ long size = directoryInfo.EnumerateFiles("*", SearchOption.AllDirectories)
+ .AsParallel()
+ .WithCancellation(timeoutCts.Token)
+ .Sum(file => file.Length);
+
+ return ResultManager.ToReadableSize(size, 2);
+ }
+ catch (FileNotFoundException)
+ {
+ Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (OperationCanceledException)
+ {
+ Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ // For parallel operations, AggregateException may be thrown if any of the tasks fail
+ catch (AggregateException ae)
+ {
+ switch (ae.InnerException)
+ {
+ case FileNotFoundException:
+ Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ case UnauthorizedAccessException:
+ Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ case OperationCanceledException:
+ Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ default:
+ Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", ae);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", e);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+
+ public static string GetFolderCreatedAt(string folderPath, string previewPanelDateFormat, string previewPanelTimeFormat, bool showFileAgeInPreviewPanel)
+ {
+ try
+ {
+ var createdDate = Directory.GetCreationTime(folderPath);
+ var formattedDate = createdDate.ToString(
+ $"{previewPanelDateFormat} {previewPanelTimeFormat}",
+ CultureInfo.CurrentCulture
+ );
+
+ var result = formattedDate;
+ if (showFileAgeInPreviewPanel) result = $"{GetFileAge(createdDate)} - {formattedDate}";
+ return result;
+ }
+ catch (FileNotFoundException)
+ {
+ Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.LogException(ClassName, $"Failed to get folder created date for {folderPath}", e);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+
+ public static string GetFolderLastModifiedAt(string folderPath, string previewPanelDateFormat, string previewPanelTimeFormat, bool showFileAgeInPreviewPanel)
+ {
+ try
+ {
+ var lastModifiedDate = Directory.GetLastWriteTime(folderPath);
+ var formattedDate = lastModifiedDate.ToString(
+ $"{previewPanelDateFormat} {previewPanelTimeFormat}",
+ CultureInfo.CurrentCulture
+ );
+
+ var result = formattedDate;
+ if (showFileAgeInPreviewPanel) result = $"{GetFileAge(lastModifiedDate)} - {formattedDate}";
+ return result;
+ }
+ catch (FileNotFoundException)
+ {
+ Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (UnauthorizedAccessException)
+ {
+ Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ catch (Exception e)
+ {
+ Main.Context.API.LogException(ClassName, $"Failed to get folder modified date for {folderPath}", e);
+ return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
+ }
+ }
+
+ private static string GetFileAge(DateTime fileDateTime)
+ {
+ var now = DateTime.Now;
+ var difference = now - fileDateTime;
+
+ if (difference.TotalDays < 1)
+ return Main.Context.API.GetTranslation("Today");
+ if (difference.TotalDays < 30)
+ return string.Format(Main.Context.API.GetTranslation("DaysAgo"), (int)difference.TotalDays);
+
+ var monthsDiff = (now.Year - fileDateTime.Year) * 12 + now.Month - fileDateTime.Month;
+ if (monthsDiff == 1)
+ return Main.Context.API.GetTranslation("OneMonthAgo");
+ if (monthsDiff < 12)
+ return string.Format(Main.Context.API.GetTranslation("MonthsAgo"), monthsDiff);
+
+ var yearsDiff = now.Year - fileDateTime.Year;
+ if (now.Month < fileDateTime.Month || (now.Month == fileDateTime.Month && now.Day < fileDateTime.Day))
+ yearsDiff--;
+
+ return yearsDiff == 1 ? Main.Context.API.GetTranslation("OneYearAgo") :
+ string.Format(Main.Context.API.GetTranslation("YearsAgo"), yearsDiff);
+ }
+
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml
new file mode 100644
index 000000000..e9ba53618
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs
new file mode 100644
index 000000000..36a00e9e5
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Windows;
+using System.Windows.Forms;
+using Flow.Launcher.Plugin.Explorer.Helper;
+using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
+
+namespace Flow.Launcher.Plugin.Explorer.Views;
+
+public partial class QuickAccessLinkSettings : INotifyPropertyChanged
+{
+ private string _selectedPath;
+ public string SelectedPath
+ {
+ get => _selectedPath;
+ set
+ {
+ if (_selectedPath != value)
+ {
+ _selectedPath = value;
+ OnPropertyChanged();
+ if (string.IsNullOrEmpty(_selectedName))
+ {
+ SelectedName = _selectedPath.GetPathName();
+ }
+ }
+ }
+ }
+
+ private string _selectedName;
+ public string SelectedName
+ {
+ get
+ {
+ return string.IsNullOrEmpty(_selectedName) ? _selectedPath.GetPathName() : _selectedName;
+ }
+ set
+ {
+ if (_selectedName != value)
+ {
+ _selectedName = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ private bool IsEdit { get; }
+ private AccessLink SelectedAccessLink { get; }
+
+ public ObservableCollection QuickAccessLinks { get; }
+
+ public QuickAccessLinkSettings(ObservableCollection quickAccessLinks)
+ {
+ IsEdit = false;
+ QuickAccessLinks = quickAccessLinks;
+ InitializeComponent();
+ }
+
+ public QuickAccessLinkSettings(ObservableCollection quickAccessLinks, AccessLink selectedAccessLink)
+ {
+ IsEdit = true;
+ _selectedName = selectedAccessLink.Name;
+ _selectedPath = selectedAccessLink.Path;
+ SelectedAccessLink = selectedAccessLink;
+ QuickAccessLinks = quickAccessLinks;
+ InitializeComponent();
+ }
+
+ private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
+ {
+ DialogResult = false;
+ Close();
+ }
+
+ private void OnDoneButtonClick(object sender, RoutedEventArgs e)
+ {
+ // Validate the input before proceeding
+ if (string.IsNullOrEmpty(SelectedName) || string.IsNullOrEmpty(SelectedPath))
+ {
+ var warning = Main.Context.API.GetTranslation("plugin_explorer_quick_access_link_no_folder_selected");
+ Main.Context.API.ShowMsgBox(warning);
+ return;
+ }
+
+ // Check if the path already exists in the quick access links
+ if (QuickAccessLinks.Any(x =>
+ x.Path.Equals(SelectedPath, StringComparison.OrdinalIgnoreCase) &&
+ x.Name.Equals(SelectedName, StringComparison.OrdinalIgnoreCase)))
+ {
+ var warning = Main.Context.API.GetTranslation("plugin_explorer_quick_access_link_path_already_exists");
+ Main.Context.API.ShowMsgBox(warning);
+ return;
+ }
+
+ // If editing, update the existing link
+ if (IsEdit)
+ {
+ if (SelectedAccessLink == null) return;
+
+ var index = QuickAccessLinks.IndexOf(SelectedAccessLink);
+ if (index >= 0)
+ {
+ var updatedLink = new AccessLink
+ {
+ Name = SelectedName,
+ Type = SelectedAccessLink.Type,
+ Path = SelectedPath
+ };
+ QuickAccessLinks[index] = updatedLink;
+ }
+ DialogResult = true;
+ Close();
+ }
+ // Otherwise, add a new one
+ else
+ {
+ var newAccessLink = new AccessLink
+ {
+ Name = SelectedName,
+ Path = SelectedPath
+ };
+ QuickAccessLinks.Add(newAccessLink);
+ DialogResult = true;
+ Close();
+ }
+ }
+
+ private void SelectPath_OnClick(object commandParameter, RoutedEventArgs e)
+ {
+ var folderBrowserDialog = new FolderBrowserDialog();
+
+ if (folderBrowserDialog.ShowDialog() != System.Windows.Forms.DialogResult.OK)
+ return;
+
+ SelectedPath = folderBrowserDialog.SelectedPath;
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json
index d2440ab61..5eea2646e 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json
@@ -11,7 +11,7 @@
"Name": "Explorer",
"Description": "Find and manage files and folders via Windows Search or Everything",
"Author": "Jeremy Wu",
- "Version": "3.2.4",
+ "Version": "1.0.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",
diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/ko.xaml
index cc78c9a9a..7af9e9306 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/ko.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/ko.xaml
@@ -1,9 +1,9 @@
- Activate {0} plugin action keyword
+ {0} 플러그인의 액션 키워드
- 플러그인 인디케이터
+ 플러그인 키워드 힌트사용중인 플러그인들의 전체 액션 키워드 목록을 보여줍니다
diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs
index 05e8d960f..48717816b 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs
@@ -3,21 +3,33 @@ using System.Linq;
namespace Flow.Launcher.Plugin.PluginIndicator
{
- public class Main : IPlugin, IPluginI18n
+ public class Main : IPlugin, IPluginI18n, IHomeQuery
{
internal PluginInitContext Context { get; private set; }
public List Query(Query query)
+ {
+ return QueryResults(query);
+ }
+
+ public List HomeQuery()
+ {
+ return QueryResults();
+ }
+
+ private List QueryResults(Query query = null)
{
var nonGlobalPlugins = GetNonGlobalPlugins();
+ var querySearch = query?.Search ?? string.Empty;
+
var results =
from keyword in nonGlobalPlugins.Keys
let plugin = nonGlobalPlugins[keyword].Metadata
- let keywordSearchResult = Context.API.FuzzySearch(query.Search, keyword)
- let searchResult = keywordSearchResult.IsSearchPrecisionScoreMet() ? keywordSearchResult : Context.API.FuzzySearch(query.Search, plugin.Name)
+ let keywordSearchResult = Context.API.FuzzySearch(querySearch, keyword)
+ let searchResult = keywordSearchResult.IsSearchPrecisionScoreMet() ? keywordSearchResult : Context.API.FuzzySearch(querySearch, plugin.Name)
let score = searchResult.Score
where (searchResult.IsSearchPrecisionScoreMet()
- || string.IsNullOrEmpty(query.Search)) // To list all available action keywords
+ || string.IsNullOrEmpty(querySearch)) // To list all available action keywords
&& !plugin.Disabled
select new Result
{
diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json
index 2b4870792..7e6a2e613 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/plugin.json
@@ -4,7 +4,7 @@
"Name": "Plugin Indicator",
"Description": "Provides plugin action keyword suggestions",
"Author": "qianlifeng",
- "Version": "3.0.7",
+ "Version": "1.0.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.PluginIndicator.dll",
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-cn.xaml
index 9542bd474..1a4199965 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-cn.xaml
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-cn.xaml
@@ -13,8 +13,8 @@
正在安装插件下载与安装 {0}插件卸载
- Keep plugin settings
- Do you want to keep the settings of the plugin for the next usage?
+ 保留插件设置
+ 你想要保留插件设置以便下一次的使用吗?插件安装成功。正在重新启动 Flow Launcher,请稍候...安装失败:无法从新插件中找到plugin.json元数据文件错误:具有相同或更高版本的 {0} 的插件已经存在。
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
index a16778ff4..25182f6d3 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs
@@ -316,61 +316,75 @@ namespace Flow.Launcher.Plugin.PluginsManager
var downloadToFilePath = Path.Combine(Path.GetTempPath(),
$"{x.Name}-{x.NewVersion}.zip");
- _ = Task.Run(async delegate
+ _ = Task.Run(async () =>
{
- using var cts = new CancellationTokenSource();
+ try
+ {
+ using var cts = new CancellationTokenSource();
- if (!x.PluginNewUserPlugin.IsFromLocalInstallPath)
- {
- await DownloadFileAsync(
- $"{Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin")} {x.PluginNewUserPlugin.Name}",
- x.PluginNewUserPlugin.UrlDownload, downloadToFilePath, cts);
- }
- else
- {
- downloadToFilePath = x.PluginNewUserPlugin.LocalInstallPath;
- }
-
- // check if user cancelled download before installing plugin
- if (cts.IsCancellationRequested)
- {
- return;
- }
- else
- {
- await Context.API.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin,
- downloadToFilePath);
-
- if (Settings.AutoRestartAfterChanging)
+ if (!x.PluginNewUserPlugin.IsFromLocalInstallPath)
{
- Context.API.ShowMsg(
- Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- string.Format(
- Context.API.GetTranslation(
- "plugin_pluginsmanager_update_success_restart"),
- x.Name));
- Context.API.RestartApp();
+ await DownloadFileAsync(
+ $"{Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin")} {x.PluginNewUserPlugin.Name}",
+ x.PluginNewUserPlugin.UrlDownload, downloadToFilePath, cts);
}
else
{
- Context.API.ShowMsg(
- Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
- string.Format(
- Context.API.GetTranslation(
- "plugin_pluginsmanager_update_success_no_restart"),
- x.Name));
+ downloadToFilePath = x.PluginNewUserPlugin.LocalInstallPath;
+ }
+
+ // check if user cancelled download before installing plugin
+ if (cts.IsCancellationRequested)
+ {
+ return;
+ }
+ else
+ {
+ await Context.API.UpdatePluginAsync(x.PluginExistingMetadata, x.PluginNewUserPlugin,
+ downloadToFilePath);
+
+ if (Settings.AutoRestartAfterChanging)
+ {
+ Context.API.ShowMsg(
+ Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ string.Format(
+ Context.API.GetTranslation(
+ "plugin_pluginsmanager_update_success_restart"),
+ x.Name));
+ Context.API.RestartApp();
+ }
+ else
+ {
+ Context.API.ShowMsg(
+ Context.API.GetTranslation("plugin_pluginsmanager_update_title"),
+ string.Format(
+ Context.API.GetTranslation(
+ "plugin_pluginsmanager_update_success_no_restart"),
+ x.Name));
+ }
}
}
- }).ContinueWith(t =>
- {
- Context.API.LogException(ClassName, $"Update failed for {x.Name}",
- t.Exception.InnerException);
- Context.API.ShowMsg(
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
- string.Format(
- Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
- x.Name));
- }, token, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
+ catch (HttpRequestException e)
+ {
+ // show error message
+ Context.API.ShowMsgError(
+ string.Format(Context.API.GetTranslation("plugin_pluginsmanager_downloading_plugin"), x.Name),
+ Context.API.GetTranslation("plugin_pluginsmanager_download_error"));
+ Context.API.LogException(ClassName, "An error occurred while downloading plugin", e);
+ return;
+ }
+ catch (Exception e)
+ {
+ // show error message
+ Context.API.LogException(ClassName, $"Update failed for {x.Name}", e);
+ Context.API.ShowMsgError(
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
+ string.Format(
+ Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
+ x.Name));
+ return;
+ }
+ });
return true;
},
@@ -436,7 +450,7 @@ namespace Flow.Launcher.Plugin.PluginsManager
catch (Exception ex)
{
Context.API.LogException(ClassName, $"Update failed for {plugin.Name}", ex.InnerException);
- Context.API.ShowMsg(
+ Context.API.ShowMsgError(
Context.API.GetTranslation("plugin_pluginsmanager_install_error_title"),
string.Format(
Context.API.GetTranslation("plugin_pluginsmanager_install_error_subtitle"),
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
index 811bec50c..f23ff71f0 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Settings.cs
@@ -10,6 +10,6 @@
public bool WarnFromUnknownSource { get; set; } = true;
- public bool AutoRestartAfterChanging { get; set; } = true;
+ public bool AutoRestartAfterChanging { get; set; } = false;
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json
index df5a2c784..327011ac3 100644
--- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json
@@ -6,7 +6,7 @@
"Name": "Plugins Manager",
"Description": "Management of installing, uninstalling or updating Flow Launcher plugins",
"Author": "Jeremy Wu",
- "Version": "3.2.4",
+ "Version": "1.0.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll",
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ar.xaml
index 04619239d..006dd93d6 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ar.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ar.xaml
@@ -8,6 +8,7 @@
قتل عمليات {0}قتل جميع الأمثلة
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/cs.xaml
index d621b63c4..d53616cc0 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/cs.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/cs.xaml
@@ -8,6 +8,7 @@
ukončit {0} procesůukončit všechny instance
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/da.xaml
index 8563f49a0..0a7176d2c 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/da.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/da.xaml
@@ -8,6 +8,7 @@
kill {0} processeskill all instances
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/de.xaml
index 766d170a4..d2492fab9 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/de.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/de.xaml
@@ -8,6 +8,7 @@
{0} Prozesse beendenAlle Instanzen beenden
- Put processes with visible windows on the top
+ Titel für Prozesse mit sichtbaren Fenstern zeigen
+ Prozesse mit sichtbaren Fenstern ganz oben setzen
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/en.xaml
index ea6e54fef..ddabd31f8 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/en.xaml
@@ -10,6 +10,7 @@
kill {0} processeskill all instances
+ Show title for processes with visible windowsPut processes with visible windows on the top
\ No newline at end of file
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/es-419.xaml
index 9b4a20a69..50799dca2 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/es-419.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/es-419.xaml
@@ -8,6 +8,7 @@
terminar {0} procesostermina todas las instancias
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/es.xaml
index ab788075e..6ab3f9797 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/es.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/es.xaml
@@ -8,6 +8,7 @@
finalizar {0} procesosfinalizar todas las instancias
+ Mostrar el título de los procesos con ventanas visiblesColocar procesos con ventanas visibles en la parte superior
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/fr.xaml
index 7064de1fe..ae3b6bab2 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/fr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/fr.xaml
@@ -8,6 +8,7 @@
Tuer {0} processusTuer toutes les instances
+ Afficher le titre des processus avec des fenêtres visiblesPlacer les processus dont les fenêtres sont visibles en haut de la page
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/he.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/he.xaml
index 8567b5412..15d14a42c 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/he.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/he.xaml
@@ -8,6 +8,7 @@
סגור {0} תהליכיםסגור את כל המופעים
+ הצג כותרת עבור תהליכים בעלי חלונות גלוייםהצב תהליכים עם חלונות גלויים בחלק העליון
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/it.xaml
index 1333fe573..5bd4a9eac 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/it.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/it.xaml
@@ -8,6 +8,7 @@
termina {0} processitermina tutte le istanze
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ja.xaml
index 8563f49a0..0a7176d2c 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ja.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ja.xaml
@@ -8,6 +8,7 @@
kill {0} processeskill all instances
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ko.xaml
index c3d1f6d08..09679a58b 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ko.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ko.xaml
@@ -8,6 +8,7 @@
{0} 프로세스 종료모든 인스턴스 종료
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/nb.xaml
index 1210818bf..37413385d 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/nb.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/nb.xaml
@@ -8,6 +8,7 @@
terminer {0} processesterminer alle forekomstene
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/nl.xaml
index cfcd71c37..ea9f9a591 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/nl.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/nl.xaml
@@ -8,6 +8,7 @@
kill {0} processeskill all instances
+ Show title for processes with visible windowsZet processen met zichtbare vensters bovenaan
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/pl.xaml
index 650f14657..7e59db5ec 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/pl.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/pl.xaml
@@ -8,6 +8,7 @@
zamknij {0} procesówzamknij wszystkie instancje
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/pt-br.xaml
index 8563f49a0..0a7176d2c 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/pt-br.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/pt-br.xaml
@@ -8,6 +8,7 @@
kill {0} processeskill all instances
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/pt-pt.xaml
index 944a883c9..b50a31744 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/pt-pt.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/pt-pt.xaml
@@ -8,6 +8,7 @@
terminar {0} processosterminar todas as instâncias
+ Mostrar título dos processos com janelas visíveisColocar processos com janelas visíveis por cima
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ru.xaml
index 0e2bef052..d030a778e 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ru.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/ru.xaml
@@ -8,6 +8,7 @@
удалить {0} процессовудалить все экземпляры
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/sk.xaml
index 2b06d3bbe..6c85b9476 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/sk.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/sk.xaml
@@ -8,6 +8,7 @@
ukončiť {0} procesovukončiť všetky inštancie
+ Zobraziť nadpis pre procesy s viditeľnými oknamiZobraziť procesy s viditeľným oknom navrchu
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/sr.xaml
index 8563f49a0..0a7176d2c 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/sr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/sr.xaml
@@ -8,6 +8,7 @@
kill {0} processeskill all instances
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/tr.xaml
index 8563f49a0..0a7176d2c 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/tr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/tr.xaml
@@ -8,6 +8,7 @@
kill {0} processeskill all instances
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/uk-UA.xaml
index 02dab46ba..56004028b 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/uk-UA.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/uk-UA.xaml
@@ -8,6 +8,7 @@
вбити {0} процесіввбити всі екземпляри
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/vi.xaml
index 89fd67635..5ce54a0dc 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/vi.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/vi.xaml
@@ -8,6 +8,7 @@
Buộc tắt các tiến trình {0}Tắt tất cả phên bản
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/zh-cn.xaml
index 15e717c79..b99cbea28 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/zh-cn.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/zh-cn.xaml
@@ -8,6 +8,7 @@
杀死 {0} 进程杀死所有实例
- Put processes with visible windows on the top
+ 显示带有可见窗口的进程标题
+ 在顶部放置带有可见窗口的进程
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/zh-tw.xaml
index 8563f49a0..0a7176d2c 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/zh-tw.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/zh-tw.xaml
@@ -8,6 +8,7 @@
kill {0} processeskill all instances
+ Show title for processes with visible windowsPut processes with visible windows on the top
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs
index 4f5d1becd..8f5ba4bd2 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Main.cs
@@ -81,7 +81,10 @@ namespace Flow.Launcher.Plugin.ProcessKiller
// Filter processes based on search term
var searchTerm = query.Search;
var processlist = new List();
- var processWindowTitle = ProcessHelper.GetProcessesWithNonEmptyWindowTitle();
+ var processWindowTitle =
+ Settings.ShowWindowTitle || Settings.PutVisibleWindowProcessesTop ?
+ ProcessHelper.GetProcessesWithNonEmptyWindowTitle() :
+ new Dictionary();
if (string.IsNullOrWhiteSpace(searchTerm))
{
foreach (var p in allPocessList)
@@ -91,12 +94,22 @@ namespace Flow.Launcher.Plugin.ProcessKiller
if (processWindowTitle.TryGetValue(p.Id, out var windowTitle))
{
// Add score to prioritize processes with visible windows
- // And use window title for those processes
- processlist.Add(new ProcessResult(p, Settings.PutVisibleWindowProcessesTop ? 200 : 0, windowTitle, null, progressNameIdTitle));
+ // Use window title for those processes if enabled
+ processlist.Add(new ProcessResult(
+ p,
+ Settings.PutVisibleWindowProcessesTop ? 200 : 0,
+ Settings.ShowWindowTitle ? windowTitle : progressNameIdTitle,
+ null,
+ progressNameIdTitle));
}
else
{
- processlist.Add(new ProcessResult(p, 0, progressNameIdTitle, null, progressNameIdTitle));
+ processlist.Add(new ProcessResult(
+ p,
+ 0,
+ progressNameIdTitle,
+ null,
+ progressNameIdTitle));
}
}
}
@@ -115,13 +128,17 @@ namespace Flow.Launcher.Plugin.ProcessKiller
if (score > 0)
{
// Add score to prioritize processes with visible windows
- // And use window title for those processes
+ // Use window title for those processes
if (Settings.PutVisibleWindowProcessesTop)
{
score += 200;
}
- processlist.Add(new ProcessResult(p, score, windowTitle,
- score == windowTitleMatch.Score ? windowTitleMatch : null, progressNameIdTitle));
+ processlist.Add(new ProcessResult(
+ p,
+ score,
+ Settings.ShowWindowTitle ? windowTitle : progressNameIdTitle,
+ score == windowTitleMatch.Score ? windowTitleMatch : null,
+ progressNameIdTitle));
}
}
else
@@ -130,7 +147,12 @@ namespace Flow.Launcher.Plugin.ProcessKiller
var score = processNameIdMatch.Score;
if (score > 0)
{
- processlist.Add(new ProcessResult(p, score, progressNameIdTitle, processNameIdMatch, progressNameIdTitle));
+ processlist.Add(new ProcessResult(
+ p,
+ score,
+ progressNameIdTitle,
+ processNameIdMatch,
+ progressNameIdTitle));
}
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs
index 4c07341ec..386782905 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs
@@ -1,9 +1,11 @@
-using Microsoft.Win32.SafeHandles;
-using System;
+using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Win32.SafeHandles;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Threading;
@@ -12,6 +14,8 @@ namespace Flow.Launcher.Plugin.ProcessKiller
{
internal class ProcessHelper
{
+ private static readonly string ClassName = nameof(ProcessHelper);
+
private readonly HashSet _systemProcessList = new()
{
"conhost",
@@ -70,8 +74,21 @@ namespace Flow.Launcher.Plugin.ProcessKiller
///
public static unsafe Dictionary GetProcessesWithNonEmptyWindowTitle()
{
- var processDict = new Dictionary();
+ // Collect all window handles
+ var windowHandles = new List();
PInvoke.EnumWindows((hWnd, _) =>
+ {
+ if (PInvoke.IsWindowVisible(hWnd))
+ {
+ windowHandles.Add(hWnd);
+ }
+ return true;
+ }, IntPtr.Zero);
+
+ // Concurrently process each window handle
+ var processDict = new ConcurrentDictionary();
+ var processedProcessIds = new ConcurrentDictionary();
+ Parallel.ForEach(windowHandles, hWnd =>
{
var windowTitle = GetWindowTitle(hWnd);
if (!string.IsNullOrWhiteSpace(windowTitle) && PInvoke.IsWindowVisible(hWnd))
@@ -80,20 +97,26 @@ namespace Flow.Launcher.Plugin.ProcessKiller
var result = PInvoke.GetWindowThreadProcessId(hWnd, &processId);
if (result == 0u || processId == 0u)
{
- return false;
+ return;
}
- var process = Process.GetProcessById((int)processId);
- if (!processDict.ContainsKey((int)processId))
+ // Ensure each process ID is processed only once
+ if (processedProcessIds.TryAdd((int)processId, 0))
{
- processDict.Add((int)processId, windowTitle);
+ try
+ {
+ var process = Process.GetProcessById((int)processId);
+ processDict.TryAdd((int)processId, windowTitle);
+ }
+ catch
+ {
+ // Handle exceptions (e.g., process exited)
+ }
}
}
+ });
- return true;
- }, IntPtr.Zero);
-
- return processDict;
+ return new Dictionary(processDict);
}
private static unsafe string GetWindowTitle(HWND hwnd)
@@ -131,7 +154,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller
}
catch (Exception e)
{
- context.API.LogException($"{nameof(ProcessHelper)}", $"Failed to kill process {p.ProcessName}", e);
+ context.API.LogException(ClassName, $"Failed to kill process {p.ProcessName}", e);
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Settings.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Settings.cs
index 916bc6a39..57cd2ab86 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Settings.cs
@@ -2,6 +2,8 @@
{
public class Settings
{
+ public bool ShowWindowTitle { get; set; } = true;
+
public bool PutVisibleWindowProcessesTop { get; set; } = false;
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ViewModels/SettingsViewModel.cs
index bacf1ba08..0728d9c0f 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ViewModels/SettingsViewModel.cs
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ViewModels/SettingsViewModel.cs
@@ -9,6 +9,12 @@
Settings = settings;
}
+ public bool ShowWindowTitle
+ {
+ get => Settings.ShowWindowTitle;
+ set => Settings.ShowWindowTitle = value;
+ }
+
public bool PutVisibleWindowProcessesTop
{
get => Settings.PutVisibleWindowProcessesTop;
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml
index d15d6c3e0..b969be4e8 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Views/SettingsControl.xaml
@@ -12,10 +12,16 @@
+
+
diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json b/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json
index 956c4b4e1..0379194c4 100644
--- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/plugin.json
@@ -4,7 +4,7 @@
"Name":"Process Killer",
"Description":"Kill running processes from Flow",
"Author":"Flow-Launcher",
- "Version":"3.0.8",
+ "Version": "1.0.0",
"Language":"csharp",
"Website":"https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller",
"IcoPath":"Images\\app.png",
diff --git a/Plugins/Flow.Launcher.Plugin.Program/AddProgramSource.xaml b/Plugins/Flow.Launcher.Plugin.Program/AddProgramSource.xaml
index 9848f51cf..26efc6813 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/AddProgramSource.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/AddProgramSource.xaml
@@ -5,21 +5,21 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Flow.Launcher.Plugin.Program.ViewModels"
- mc:Ignorable="d"
Title="{DynamicResource flowlauncher_plugin_program_directory}"
- d:DataContext="{d:DesignInstance vm:AddProgramSourceViewModel}"
Width="Auto"
Height="276"
+ d:DataContext="{d:DesignInstance vm:AddProgramSourceViewModel}"
Background="{DynamicResource PopuBGColor}"
Foreground="{DynamicResource PopupTextColor}"
ResizeMode="NoResize"
SizeToContent="Width"
- WindowStartupLocation="CenterScreen">
+ WindowStartupLocation="CenterScreen"
+ mc:Ignorable="d">
-
+
@@ -59,11 +59,11 @@
-
-
+
+
-
+
@@ -104,16 +104,16 @@
HorizontalAlignment="Stretch"
Click="BrowseButton_Click"
Content="{DynamicResource flowlauncher_plugin_program_browse}"
- Visibility="{Binding IsCustomSource, Converter={StaticResource BooleanToVisibilityConverter}}"
- DockPanel.Dock="Right" />
+ DockPanel.Dock="Right"
+ Visibility="{Binding IsCustomSource, Converter={StaticResource BooleanToVisibilityConverter}}" />
+ VerticalAlignment="Center"
+ IsReadOnly="{Binding IsNotCustomSource}"
+ Text="{Binding Location, Mode=TwoWay}" />
+ Margin="10 0"
+ VerticalAlignment="Center"
+ IsChecked="{Binding Enabled, Mode=TwoWay}" />
+ BorderThickness="0 1 0 0">
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/ar.xaml
index 69ca16b69..c65fa196b 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/ar.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/ar.xaml
@@ -46,6 +46,8 @@
الرجاء تحديد مصدر برنامجهل أنت متأكد أنك تريد حذف مصادر البرامج المحددة؟
+ Please select program sources that are not added by you
+ Please select program sources that are added by youمصدر برنامج آخر بنفس الموقع موجود بالفعل.مصدر البرنامج
@@ -74,7 +76,7 @@
التشغيل كمستخدم مختلفالتشغيل كمسؤولفتح المجلد المحتوي
- تعطيل عرض هذا البرنامج
+ Hideفتح المجلد الهدفالبرنامج
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/cs.xaml
index 1c70c6b6f..b694c5bdd 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/cs.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/cs.xaml
@@ -46,6 +46,8 @@
Prosím vyberte zdroj programuJste si jisti, že chcete odstranit vybrané zdroje programů?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youJiž existuje jiný zdroj programu se stejným umístěním.Zdroj programu
@@ -74,7 +76,7 @@
Spustit jako jiný uživatelSpustit jako správceOtevřít umístění složky
- Zakázat zobrazování tohoto programu
+ HideOpen target folderProgram
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/da.xaml
index 0f39f5d56..5958eba32 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/da.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/da.xaml
@@ -46,6 +46,8 @@
Please select a program sourceAre you sure you want to delete the selected program sources?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youAnother program source with the same location already exists.Program Source
@@ -74,7 +76,7 @@
Run As Different UserRun As AdministratorOpen containing folder
- Disable this program from displaying
+ HideOpen target folderProgram
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/de.xaml
index 14e228b2a..7919ae7cb 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/de.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/de.xaml
@@ -46,6 +46,8 @@
Bitte wählen Sie eine Programmquelle ausSind Sie sicher, dass Sie die ausgewählten Programmquellen löschen wollen?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youEine andere Programmquelle mit dem gleichen Ort ist bereits vorhanden.Programmquelle
@@ -74,7 +76,7 @@
Als anderer Benutzer ausführenAls Administrator ausführenEnthaltenden Ordner öffnen
- Dieses Programm von der Anzeige deaktivieren
+ HideZielordner öffnenProgramm
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml
index 790c9d2c6..e551a7dcb 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml
@@ -48,6 +48,8 @@
Please select a program sourceAre you sure you want to delete the selected program sources?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youAnother program source with the same location already exists.Program Source
@@ -76,7 +78,7 @@
Run As Different UserRun As AdministratorOpen containing folder
- Disable this program from displaying
+ HideOpen target folderProgram
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/es-419.xaml
index b928bd1ce..9818e4226 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/es-419.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/es-419.xaml
@@ -46,6 +46,8 @@
Please select a program sourceAre you sure you want to delete the selected program sources?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youAnother program source with the same location already exists.Program Source
@@ -74,7 +76,7 @@
Run As Different UserRun As AdministratorOpen containing folder
- Disable this program from displaying
+ HideOpen target folderProgram
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/es.xaml
index 57b5c94b7..25b1a23bf 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/es.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/es.xaml
@@ -46,6 +46,8 @@
Por favor, seleccione la ruta del programa¿Está seguro de que desea eliminar las fuentes del programa seleccionadas?
+ Por favor, seleccione las fuentes del programa que no han sido añadidas por usted
+ Por favor, seleccione las fuentes del programa que han sido añadidas por ustedYa existe otra fuente de programa con la misma ubicación.Fuente de Programa
@@ -74,7 +76,7 @@
Ejecutar como usuario diferenteEjecutar como administradorAbrir carpeta contenedora
- Desactivar la visualización de este programa
+ OcultarAbrir carpeta de destinoPrograma
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/fr.xaml
index 7cccd5a42..e509a875f 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/fr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/fr.xaml
@@ -46,6 +46,8 @@
Veuillez sélectionner une source de programmeÊtes-vous sûr de vouloir supprimer les sources de programmes sélectionnées ?
+ Veuillez sélectionner les sources de programmes qui n'ont pas été ajoutées par vous-même
+ Veuillez sélectionner les sources de programmes qui ont été ajoutées par vous-mêmeIl existe déjà une autre source de programme ayant le même emplacement.Source du programme
@@ -74,7 +76,7 @@
Exécuter en tant qu'utilisateur différentExécuter en tant qu'administrateurOuvrir l'emplacement du fichier
- Masquer ce programme des résultats
+ MasquerOuvrir le répertoire cibleProgrammes
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/he.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/he.xaml
index af272fb46..83f034646 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/he.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/he.xaml
@@ -46,6 +46,8 @@
אנא בחר מקור תוכנההאם אתה בטוח שברצונך למחוק את מקורות התוכניות שנבחרו?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youמקור תוכנה נוסף עם אותו מיקום כבר קיים.מקור תוכנה
@@ -74,7 +76,7 @@
הפעל כמשתמש אחרהפעל כמנהלפתח תיקייה מכילה
- השבת הצגת תוכנה זו
+ הסתרפתח תיקיית יעדתוכנה
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/it.xaml
index cf5de9bab..bb97a0085 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/it.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/it.xaml
@@ -46,6 +46,8 @@
Seleziona la sorgente del programmaSei sicuro di voler cancellare le sorgenti dei programmi selezionate?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youAnother program source with the same location already exists.Program Source
@@ -74,7 +76,7 @@
Esegui Come Utente DifferenteEsegui Come AmministratoreApri percorso file
- Disabilita questo programma dalla visualizzazione
+ HideOpen target folderProgramma
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/ja.xaml
index 1f1dd4d37..4a1d815ec 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/ja.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/ja.xaml
@@ -4,12 +4,12 @@
Reset Default削除
- 編
- 追
+ 編集
+ 追加Name有効Enabled
- Disable
+ 無効StatusEnabledDisabled
@@ -28,11 +28,11 @@
When enabled, Flow will load programs from the registryPATHWhen enabled, Flow will load programs from the PATH environment variable
- Hide app path
- For executable files such as UWP or lnk, hide the file path from being visible
+ アプリのパスを非表示
+ UWPやlnkなどの実行可能ファイルについて、サブタイトル領域にファイルパスが表示されないようにします。Hide uninstallersHides programs with common uninstaller names, such as unins000.exe
- Search in Program Description
+ プログラムの説明で検索Flow will search program's descriptionHide duplicated appsHide duplicated Win32 programs that are already in the UWP list
@@ -46,6 +46,8 @@
Please select a program sourceAre your sure to delete {0}?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youAnother program source with the same location already exists.Program Source
@@ -71,14 +73,14 @@
Insert protocols of .url files you want to index. Protocols should be separated by ';', and should end with "://". (ex>ftp://;mailto://)
- Run As Different User
- Run As Administrator
+ 別のユーザーとして実行
+ 管理者として実行Open containing folder
- Disable this program from displaying
+ HideOpen target folder
- Program
- Search programs in Flow Launcher
+ プログラム
+ Flow Launcherでプログラムを検索Invalid Path
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/ko.xaml
index affa7567f..f2ac75762 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/ko.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/ko.xaml
@@ -34,7 +34,7 @@
Unins000처럼 일반적으로 사용되는 설치 삭제(Uninstaller) 프로그램의 이름을 숨깁니다.프로그램 설명 검색Flow will search program's description
- Hide duplicated apps
+ 중복된 앱 숨기기Hide duplicated Win32 programs that are already in the UWP list확장자최대 깊이
@@ -46,6 +46,8 @@
프로그램 검색 출처를 선택하세요선택하신 프로그램 출처를 삭제하시겠습니까?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youAnother program source with the same location already exists.Program Source
@@ -74,8 +76,8 @@
다른 유저 권한으로 실행관리자 권한으로 실행포함된 폴더 열기
- 이 프로그램 표시 비활성화
- Open target folder
+ Hide
+ 대상 폴더 열기프로그램Flow Launcher에서 프로그램을 검색합니다
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/nb.xaml
index 3fa53aba8..25018d752 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/nb.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/nb.xaml
@@ -46,6 +46,8 @@
Vennligst velg en programkildeEr du sikker på at du vil slette de valgte programkildene?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youDet finnes allerede en annen programkilde med samme plassering.Programkilde
@@ -74,7 +76,7 @@
Kjør som en annen brukerKjør som administratorÅpne inneholdende mappe
- Deaktiver visningen av dette programmet
+ HideÅpne målmappeProgram
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/nl.xaml
index 8a86dc511..eb8a6f1a6 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/nl.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/nl.xaml
@@ -46,6 +46,8 @@
Please select a program sourceAre you sure you want to delete the selected program sources?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youAnother program source with the same location already exists.Program Source
@@ -74,7 +76,7 @@
Run As Different UserRun As AdministratorOpen containing folder
- Disable this program from displaying
+ HideOpen target folderProgram
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/pl.xaml
index 8a163de7a..4d1483eee 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/pl.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/pl.xaml
@@ -46,6 +46,8 @@
Musisz wybrać katalog programuCzy na pewno chcesz usunąć wybrane źródła programów?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youInne źródło programu z tą samą lokalizacją już istnieje.Źródło programu
@@ -74,7 +76,7 @@
Uruchom jako inny użytkownikUruchom jako administratorOtwórz folder nadrzędny
- Wyłącz wyświetlanie tego programu
+ HideOtwórz folder docelowyProgramy
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-br.xaml
index bab077683..978c2833d 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-br.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-br.xaml
@@ -46,6 +46,8 @@
Please select a program sourceAre you sure you want to delete the selected program sources?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youAnother program source with the same location already exists.Program Source
@@ -74,7 +76,7 @@
Run As Different UserRun As AdministratorOpen containing folder
- Disable this program from displaying
+ HideOpen target folderProgram
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-pt.xaml
index c7b394593..cc3e280b2 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-pt.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-pt.xaml
@@ -46,6 +46,8 @@
Por favor selecione uma origem de programasTem a certeza de que deseja remover as origens selecionadas?
+ Selecione as origens de programas NÃO adicionadas por si
+ Selecione as origens de programas adicionadas por siJá existe uma origem de programas com esta localização.Origem de programas
@@ -74,7 +76,7 @@
Executar com outro utilizadorExecutar como administradorAbrir pasta de destino
- Desativar exibição deste programa
+ OcultarAbrir pasta de destinoProgramas
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/ru.xaml
index 8cb62137e..7624d6517 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/ru.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/ru.xaml
@@ -46,6 +46,8 @@
Пожалуйста, выберите источник программыВы уверены, что хотите удалить выбранные источники программ?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youДругой источник программы с таким же расположением уже существует.Источник программы
@@ -74,7 +76,7 @@
Запустить от имени другого пользователяЗапустить от имени администратораОткрыть содержащую папку
- Отключить отображение этой программы
+ HideOpen target folderПрограмма
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml
index ee0705b96..b30b56a5b 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml
@@ -46,6 +46,8 @@
Prosím, zadajte zdroj programuNaozaj chcete odstrániť vybrané zdroje programov?
+ Vyberte zdroje programov, ktoré ste nepridali vy
+ Vyberte zdroje programov, ktoré ste pridali vyIný zdroj programu s rovnakým umiestnením už existuje.Zdroj programu
@@ -74,7 +76,7 @@
Spustiť ako iný používateľSpustiť ako správcaOtvoriť umiestnenie priečinka
- Zakázať zobrazovanie tohto programu
+ SkryťOtvoriť cieľový priečinokProgram
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/sr.xaml
index a69d7e96b..a79655a7b 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/sr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/sr.xaml
@@ -46,6 +46,8 @@
Please select a program sourceAre you sure you want to delete the selected program sources?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youAnother program source with the same location already exists.Program Source
@@ -74,7 +76,7 @@
Run As Different UserRun As AdministratorOpen containing folder
- Disable this program from displaying
+ HideOpen target folderProgram
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/tr.xaml
index d46de0592..126613065 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/tr.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/tr.xaml
@@ -46,6 +46,8 @@
İşlem yapmak istediğiniz klasörü seçin.Are you sure you want to delete the selected program sources?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youAnother program source with the same location already exists.Program Source
@@ -74,7 +76,7 @@
Run As Different UserYönetici Olarak ÇalıştırOpen containing folder
- Disable this program from displaying
+ HideOpen target folderProgram
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/uk-UA.xaml
index 8e8d55f47..290954d5f 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/uk-UA.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/uk-UA.xaml
@@ -46,6 +46,8 @@
Будь ласка, виберіть джерело програмиВи впевнені, що хочете видалити вибрані джерела програм?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youІнше програмне джерело з тим самим розташуванням вже існує.Вихідний код програми
@@ -74,7 +76,7 @@
Запустити від імені іншого користувачаЗапустити від імені адміністратораВідкрити папку
- Вимкнути відображення цієї програми
+ HideВідкрити цільову папкуПрограма
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/vi.xaml
index 21a3981c5..0dede0f8b 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/vi.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/vi.xaml
@@ -46,6 +46,8 @@
Hãy chọn một nguồn dữ liệuBạn có chắc chắn là muốn xóa các đặt hàng đã chọn?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youĐã tồn tại một nguồn chương trình khác có cùng vị trí.Nguồn chương trình
@@ -74,7 +76,7 @@
Xóa lựa chọn đã chọnChạy với quyền quản trịMở thư mục chứa
- Vô hiệu hóa chương trình này hiển thị
+ HideMở thư mục đíchChương trình
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-cn.xaml
index 6d5c0079f..9e34b316b 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-cn.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-cn.xaml
@@ -34,8 +34,8 @@
隐藏具有常见卸载程序名称的程序,例如 unins000.exe启用程序描述Flow 将搜索程序描述
- Hide duplicated apps
- Hide duplicated Win32 programs that are already in the UWP list
+ 隐藏重复的应用
+ 隐藏已经在UWP列表中重复的Win32程序后缀最大深度
@@ -46,6 +46,8 @@
请先选择一项您确定要删除选定的程序源吗?
+ 请选择没有被您添加的程序源
+ 请选择由您添加的程序源相同位置存在另一个程序源。程序源
@@ -74,7 +76,7 @@
以其他用户身份运行以管理员身份运行打开文件所在文件夹
- 禁止显示该程序
+ 隐藏打开目标文件夹程序
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-tw.xaml
index fd0bd427a..a8ff4ecbc 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-tw.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-tw.xaml
@@ -46,6 +46,8 @@
請先選擇一項Are you sure you want to delete the selected program sources?
+ Please select program sources that are not added by you
+ Please select program sources that are added by youAnother program source with the same location already exists.Program Source
@@ -74,7 +76,7 @@
Run As Different User以系統管理員身分執行開啟檔案位置
- Disable this program from displaying
+ HideOpen target folder程式
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
index 16b6bcab2..d28845994 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs
@@ -109,7 +109,6 @@ namespace Flow.Launcher.Plugin.Program
}
catch (OperationCanceledException)
{
- Context.API.LogDebug(ClassName, "Query operation cancelled");
return emptyResults;
}
finally
diff --git a/Plugins/Flow.Launcher.Plugin.Program/ProgramSuffixes.xaml b/Plugins/Flow.Launcher.Plugin.Program/ProgramSuffixes.xaml
index 71bc12a86..53baa79a3 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/ProgramSuffixes.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/ProgramSuffixes.xaml
@@ -46,8 +46,8 @@
-
-
+
+
@@ -65,8 +65,8 @@
-
-
+
+
@@ -120,20 +120,20 @@
-
+
-
+
@@ -151,82 +151,82 @@
TextWrapping="Wrap" />
-
+
-
+
appref-ms
exe
lnk
+ BorderThickness="1 0 0 0">
@@ -237,27 +237,27 @@
Grid.Row="1"
Background="{DynamicResource PopupButtonAreaBGColor}"
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
- BorderThickness="0,1,0,0">
+ BorderThickness="0 1 0 0">
buffer = stackalloc char[MAX_PATH];
@@ -79,6 +80,6 @@ namespace Flow.Launcher.Plugin.Program.Programs
Marshal.ReleaseComObject(link);
return target;
- }
+ }
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml
index 5c0ba8d0b..3bf1c6ad1 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml
@@ -203,6 +203,12 @@
Margin="{StaticResource SettingPanelItemLeftTopBottomMargin}"
Click="btnEditProgramSource_OnClick"
Content="{DynamicResource flowlauncher_plugin_program_edit}" />
+ 0
@@ -141,11 +142,13 @@ namespace Flow.Launcher.Plugin.Program.Views
{
btnProgramSourceStatus.Visibility = Visibility.Visible;
btnEditProgramSource.Visibility = Visibility.Visible;
+ btnDeleteProgramSource.Visibility = Visibility.Visible;
}
programSourceView.Items.Refresh();
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "")]
private async void ReIndexing()
{
ViewRefresh();
@@ -183,6 +186,7 @@ namespace Flow.Launcher.Plugin.Program.Views
EditProgramSource(selectedProgramSource);
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "")]
private async void EditProgramSource(ProgramSource selectedProgramSource)
{
if (selectedProgramSource == null)
@@ -268,8 +272,8 @@ namespace Flow.Launcher.Plugin.Program.Views
if (directoriesToAdd.Count > 0)
{
- directoriesToAdd.ForEach(x => _settings.ProgramSources.Add(x));
- directoriesToAdd.ForEach(x => ProgramSettingDisplayList.Add(x));
+ directoriesToAdd.ForEach(_settings.ProgramSources.Add);
+ directoriesToAdd.ForEach(ProgramSettingDisplayList.Add);
ViewRefresh();
ReIndexing();
@@ -277,6 +281,7 @@ namespace Flow.Launcher.Plugin.Program.Views
}
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "")]
private async void btnLoadAllProgramSource_OnClick(object sender, RoutedEventArgs e)
{
await ProgramSettingDisplay.DisplayAllProgramsAsync();
@@ -284,6 +289,7 @@ namespace Flow.Launcher.Plugin.Program.Views
ViewRefresh();
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "")]
private async void btnProgramSourceStatus_OnClick(object sender, RoutedEventArgs e)
{
var selectedItems = programSourceView
@@ -292,24 +298,11 @@ namespace Flow.Launcher.Plugin.Program.Views
if (selectedItems.Count == 0)
{
- string msg = context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source");
- context.API.ShowMsgBox(msg);
+ context.API.ShowMsgBox(context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source"));
return;
}
- if (IsAllItemsUserAdded(selectedItems))
- {
- var msg = string.Format(
- context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"));
-
- if (context.API.ShowMsgBox(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
- {
- return;
- }
-
- DeleteProgramSources(selectedItems);
- }
- else if (HasMoreOrEqualEnabledItems(selectedItems))
+ if (HasMoreOrEqualEnabledItems(selectedItems))
{
await ProgramSettingDisplay.SetProgramSourcesStatusAsync(selectedItems, false);
@@ -337,10 +330,9 @@ namespace Flow.Launcher.Plugin.Program.Views
private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)
{
- var headerClicked = e.OriginalSource as GridViewColumnHeader;
ListSortDirection direction;
- if (headerClicked != null)
+ if (e.OriginalSource is GridViewColumnHeader headerClicked)
{
if (headerClicked.Role != GridViewColumnHeaderRole.Padding)
{
@@ -376,7 +368,7 @@ namespace Flow.Launcher.Plugin.Program.Views
var dataView = CollectionViewSource.GetDefaultView(programSourceView.ItemsSource);
dataView.SortDescriptions.Clear();
- SortDescription sd = new SortDescription(sortBy, direction);
+ var sd = new SortDescription(sortBy, direction);
dataView.SortDescriptions.Add(sd);
dataView.Refresh();
}
@@ -393,11 +385,7 @@ namespace Flow.Launcher.Plugin.Program.Views
.SelectedItems.Cast()
.ToList();
- if (IsAllItemsUserAdded(selectedItems))
- {
- btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_delete");
- }
- else if (HasMoreOrEqualEnabledItems(selectedItems))
+ if (HasMoreOrEqualEnabledItems(selectedItems))
{
btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_disable");
}
@@ -416,6 +404,41 @@ namespace Flow.Launcher.Plugin.Program.Views
}
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "")]
+ private async void btnDeleteProgramSource_OnClick(object sender, RoutedEventArgs e)
+ {
+ var selectedItems = programSourceView
+ .SelectedItems.Cast()
+ .ToList();
+
+ if (selectedItems.Count == 0)
+ {
+ context.API.ShowMsgBox(context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source"));
+ return;
+ }
+
+ if (!IsAllItemsUserAdded(selectedItems))
+ {
+ context.API.ShowMsgBox(context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source_select_user_added"));
+ return;
+ }
+
+ if (context.API.ShowMsgBox(context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"),
+ string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
+ {
+ return;
+ }
+
+ DeleteProgramSources(selectedItems);
+
+ if (await selectedItems.IsReindexRequiredAsync())
+ ReIndexing();
+
+ programSourceView.SelectedItems.Clear();
+
+ ViewRefresh();
+ }
+
private bool IsAllItemsUserAdded(List items)
{
return items.All(x => _settings.ProgramSources.Any(y => y.UniqueIdentifier == x.UniqueIdentifier));
@@ -423,11 +446,14 @@ namespace Flow.Launcher.Plugin.Program.Views
private void ListView_SizeChanged(object sender, SizeChangedEventArgs e)
{
- ListView listView = sender as ListView;
- GridView gView = listView.View as GridView;
+ var listView = sender as ListView;
+ var gView = listView.View as GridView;
var workingWidth =
listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
+
+ if (workingWidth <= 0) return;
+
var col1 = 0.25;
var col2 = 0.15;
var col3 = 0.60;
diff --git a/Plugins/Flow.Launcher.Plugin.Program/plugin.json b/Plugins/Flow.Launcher.Plugin.Program/plugin.json
index 5a95e75f4..0316a2397 100644
--- a/Plugins/Flow.Launcher.Plugin.Program/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.Program/plugin.json
@@ -4,7 +4,7 @@
"Name": "Program",
"Description": "Search programs in Flow.Launcher",
"Author": "qianlifeng",
- "Version": "3.3.4",
+ "Version": "1.0.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Program.dll",
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml
index 90eb49317..781227267 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ja.xaml
@@ -12,7 +12,7 @@
Allows to execute system commands from Flow Launcherthis command has been executed {0} timesexecute command through command shell
- Run As Administrator
+ 管理者として実行Copy the commandOnly show number of most used commands:
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml
index 67594ae8d..b661e836b 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ko.xaml
@@ -9,7 +9,7 @@
Use Windows Terminal다른 유저 권한으로 실행쉘
- Allows to execute system commands from Flow Launcher
+ Flow Launcher를 통해 시스템 명령어를 실행할 수 있습니다이 명령은 {0}회 실행되었습니다.쉘을 통해 명령 실행관리자 권한으로 실행
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml
index 7e7c2837f..0362bf6fc 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml
@@ -6,7 +6,7 @@
按下任意键以关闭此窗口...执行后不关闭命令窗口始终以管理员身份运行
- Use Windows Terminal
+ 使用 Windows 终端以其他用户身份运行命令行允许从 Flow Launcher 中执行系统命令
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
index 0d395c053..d0add9f31 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs
@@ -201,94 +201,101 @@ namespace Flow.Launcher.Plugin.Shell
switch (_settings.Shell)
{
case Shell.Cmd:
- {
- if (_settings.UseWindowsTerminal)
{
- info.FileName = "wt.exe";
- info.ArgumentList.Add("cmd");
- }
- else
- {
- info.FileName = "cmd.exe";
- }
+ if (_settings.UseWindowsTerminal)
+ {
+ info.FileName = "wt.exe";
+ info.ArgumentList.Add("cmd");
+ }
+ else
+ {
+ 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" : "")}");
- break;
- }
+ 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" : "")}");
+ break;
+ }
case Shell.Powershell:
- {
- if (_settings.UseWindowsTerminal)
{
- info.FileName = "wt.exe";
- info.ArgumentList.Add("powershell");
+ // 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";
+ info.ArgumentList.Add("powershell");
+ }
+ else
+ {
+ info.FileName = "powershell.exe";
+ }
+ if (_settings.LeaveShellOpen)
+ {
+ info.ArgumentList.Add("-NoExit");
+ info.ArgumentList.Add(command);
+ }
+ 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" : "")}");
+ }
+ break;
}
- else
- {
- info.FileName = "powershell.exe";
- }
- if (_settings.LeaveShellOpen)
- {
- info.ArgumentList.Add("-NoExit");
- info.ArgumentList.Add(command);
- }
- else
- {
- info.ArgumentList.Add("-Command");
- info.ArgumentList.Add($"{command}\\; {(_settings.CloseShellAfterPress ? $"Write-Host '{Context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'\\; [System.Console]::ReadKey()\\; exit" : "")}");
- }
- break;
- }
case Shell.Pwsh:
- {
- if (_settings.UseWindowsTerminal)
{
- info.FileName = "wt.exe";
- info.ArgumentList.Add("pwsh");
+ // 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";
+ info.ArgumentList.Add("pwsh");
+ }
+ else
+ {
+ info.FileName = "pwsh.exe";
+ }
+ if (_settings.LeaveShellOpen)
+ {
+ 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" : "")}");
+ break;
}
- else
- {
- info.FileName = "pwsh.exe";
- }
- if (_settings.LeaveShellOpen)
- {
- info.ArgumentList.Add("-NoExit");
- }
- info.ArgumentList.Add("-Command");
- info.ArgumentList.Add($"{command}\\; {(_settings.CloseShellAfterPress ? $"Write-Host '{Context.API.GetTranslation("flowlauncher_plugin_cmd_press_any_key_to_close")}'\\; [System.Console]::ReadKey()\\; exit" : "")}");
- break;
- }
case Shell.RunCommand:
- {
- var parts = command.Split(new[]
{
- ' '
- }, 2);
- if (parts.Length == 2)
- {
- var filename = parts[0];
- if (ExistInPath(filename))
+ var parts = command.Split(new[]
{
- var arguments = parts[1];
- info.FileName = filename;
- info.ArgumentList.Add(arguments);
+ ' '
+ }, 2);
+ if (parts.Length == 2)
+ {
+ var filename = parts[0];
+ if (ExistInPath(filename))
+ {
+ var arguments = parts[1];
+ info.FileName = filename;
+ info.ArgumentList.Add(arguments);
+ }
+ else
+ {
+ info.FileName = command;
+ }
}
else
{
info.FileName = command;
}
- }
- else
- {
- info.FileName = command;
+
+ info.UseShellExecute = true;
+
+ break;
}
- info.UseShellExecute = true;
-
- break;
- }
default:
throw new NotImplementedException();
}
@@ -378,11 +385,17 @@ namespace Flow.Launcher.Plugin.Shell
private void OnWinRPressed()
{
+ Context.API.ShowMainWindow();
// show the main window and set focus to the query box
- _ = Task.Run(() =>
+ _ = Task.Run(async () =>
{
- Context.API.ShowMainWindow();
Context.API.ChangeQuery($"{Context.CurrentPluginMetadata.ActionKeywords[0]}{Plugin.Query.TermSeparator}");
+
+ // Win+R is a system-reserved shortcut, and though the plugin intercepts the keyboard event and
+ // shows the main window, Windows continues to process the Win key and briefly reclaims focus.
+ // So we need to wait until the keyboard event processing is completed and then set focus
+ await Task.Delay(50);
+ Context.API.FocusQueryTextBox();
});
}
diff --git a/Plugins/Flow.Launcher.Plugin.Shell/plugin.json b/Plugins/Flow.Launcher.Plugin.Shell/plugin.json
index 681e8f751..36f9b8e00 100644
--- a/Plugins/Flow.Launcher.Plugin.Shell/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.Shell/plugin.json
@@ -4,7 +4,7 @@
"Name": "Shell",
"Description": "Provide executing commands from Flow Launcher",
"Author": "qianlifeng",
- "Version": "3.2.5",
+ "Version": "1.0.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Shell.dll",
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/he.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/he.xaml
index 86688a130..b02a89a0a 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/he.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/he.xaml
@@ -26,7 +26,7 @@
מדריך Flow Launcherתיקיית הנתונים של Flow Launcherמצב משחק
- Set the Flow Launcher Theme
+ הגדר את ערכת הנושא של Flow Launcherערו
@@ -51,7 +51,7 @@
עיין במדריך של Flow Launcher לקבלת מידע נוסף וטיפיםפתח את מיקום תיקיית ההגדרות של Flow Launcherהפעל/כבה מצב משחק
- Quickly change the Flow Launcher theme
+ שנה במהירות את ערכת הנושא של Flow Launcherהצליח
@@ -62,14 +62,14 @@
האם אתה בטוח שברצונך להפעיל מחדש את המחשב עם אפשרויות אתחול מתקדמות?האם אתה בטוח שברצונך להתנתק?
- Command Keyword Setting
- Custom Command Keyword
- Enter a keyword to search for command: {0}. This keyword is used to match your query.
- Command Keyword
+ הגדרת מילת מפתח לפקודה
+ מילת מפתח מותאמת לפקודה
+ הזן מילת מפתח כדי לחפש את הפקודה: {0}. מילת מפתח זו משמשת להתאמה לשאילתה שלך.
+ מילת מפתח לפקודהאפסאישוביטול
- Please enter a non-empty command keyword
+ אנא הזן מילת מפתח תקינה לפקודהפקודות מערכתמספק פקודות הקשורות למערכת, כגון כיבוי, נעילה, הגדרות ועוד.
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml
index bc7dff59f..27fee87be 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml
@@ -6,20 +6,20 @@
説明コマンド
- Shutdown
- Restart
+ シャットダウン
+ 再起動Restart With Advanced Boot OptionsLog Off/Sign OutLockSleepHibernateIndex Option
- Empty Recycle Bin
- Open Recycle Bin
- 終
- Save Settings
+ ごみ箱を空にする
+ ごみ箱を開く
+ 終了
+ 設定を保存Flow Launcherを再起動する
- 設
+ 設定プラグインデータのリロードCheck For UpdateOpen Log Location
@@ -28,7 +28,7 @@
Toggle Game ModeSet the Flow Launcher Theme
- 編
+ 編集コンピュータをシャットダウンする
@@ -41,7 +41,7 @@
このアプリの設定スリープゴミ箱を空にする
- Open recycle bin
+ ごみ箱を開くIndexing OptionsHibernate computerSave all Flow Launcher settings
@@ -58,17 +58,17 @@
All Flow Launcher settings savedReloaded all applicable plugin dataAre you sure you want to shut the computer down?
- Are you sure you want to restart the computer?
- Are you sure you want to restart the computer with Advanced Boot Options?
- Are you sure you want to log off?
+ 本当にコンピューターを再起動しますか?
+ 高度な起動オプションでコンピューターを再起動しますか?
+ 本当にログオフしますか?Command Keyword SettingCustom Command KeywordEnter a keyword to search for command: {0}. This keyword is used to match your query.Command KeywordReset
- Confirm
-
+ 確認
+ キャンセルPlease enter a non-empty command keywordシステムコマンド
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml
index f2049038f..873e71d44 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml
@@ -6,21 +6,21 @@
설명명령어
- Shutdown
- Restart
+ 시스템 종료
+ 다시 시작Restart With Advanced Boot Options
- Log Off/Sign Out
- Lock
- Sleep
+ 로그아웃
+ 컴퓨터 잠금
+ 절전Hibernate
- Index Option
- Empty Recycle Bin
- Open Recycle Bin
+ 색인 옵션
+ 휴지통 비우기
+ 휴지통 열기종료
- Save Settings
+ 설정 저장Flow Launcher 재시작설정
- 플러그인 데이터 새로고
+ 플러그인 데이터 새로고침Check For UpdateOpen Log LocationFlow Launcher Tips
@@ -60,13 +60,13 @@
시스템을 종료하시겠습니까?시스템을 재시작 하시겠습니까?고급 부팅 옵션으로 시스템을 다시 시작하시겠습니까?
- Are you sure you want to log off?
+ 정말 로그아웃 하시겠습니까?Command Keyword SettingCustom Command KeywordEnter a keyword to search for command: {0}. This keyword is used to match your query.Command Keyword
- Reset
+ 초기화확인취소Please enter a non-empty command keyword
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml
index 745129130..d59b77145 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml
@@ -26,7 +26,7 @@
Flow Launcher 提示Flow Launcher 用户数据文件夹切换游戏模式
- Set the Flow Launcher Theme
+ 设置Flow Launcher的主题编辑
@@ -51,7 +51,7 @@
访问 Flow Launcher 的文档以获取更多帮助以及使用技巧打开Flow Launcher 设置文件夹切换游戏模式
- Quickly change the Flow Launcher theme
+ 快速更改Flow Launcher的主题成功
@@ -62,14 +62,14 @@
您确定要以高级启动选项重启吗?您确定要注销吗?
- Command Keyword Setting
- Custom Command Keyword
- Enter a keyword to search for command: {0}. This keyword is used to match your query.
- Command Keyword
+ 命令关键词设置
+ 自定义命令关键词
+ 输入一个关键词来搜索命令:{0}。此关键词将被用于匹配您的查询输入。
+ 命令关键词重置确认取消
- Please enter a non-empty command keyword
+ 请输入一个非空的命令关键字系统命令提供操作系统相关的命令,如关机、锁定、设置等。
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
index 043eb7a19..39bf49654 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs
@@ -362,6 +362,7 @@ namespace Flow.Launcher.Plugin.Sys
Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xe89f"),
Action = c =>
{
+ _context.API.HideMainWindow();
Application.Current.MainWindow.Close();
return true;
}
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs
index 0a38fda04..1a8621eeb 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.Sys/SysSettings.xaml.cs
@@ -21,16 +21,15 @@ namespace Flow.Launcher.Plugin.Sys
ListView listView = sender as ListView;
GridView gView = listView.View as GridView;
- var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
+ var workingWidth =
+ listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
+
+ if (workingWidth <= 0) return;
+
var col1 = 0.2;
var col2 = 0.6;
var col3 = 0.2;
- if (workingWidth <= 0)
- {
- return;
- }
-
gView.Columns[0].Width = workingWidth * col1;
gView.Columns[1].Width = workingWidth * col2;
gView.Columns[2].Width = workingWidth * col3;
diff --git a/Plugins/Flow.Launcher.Plugin.Sys/plugin.json b/Plugins/Flow.Launcher.Plugin.Sys/plugin.json
index 90ca264cc..68ce6feb1 100644
--- a/Plugins/Flow.Launcher.Plugin.Sys/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.Sys/plugin.json
@@ -4,7 +4,7 @@
"Name": "System Commands",
"Description": "Provide System related commands. e.g. shutdown,lock, setting etc.",
"Author": "qianlifeng",
- "Version": "3.1.7",
+ "Version": "1.0.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Sys.dll",
diff --git a/Plugins/Flow.Launcher.Plugin.Url/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Url/Languages/zh-cn.xaml
index 7d548e51a..183d81bf1 100644
--- a/Plugins/Flow.Launcher.Plugin.Url/Languages/zh-cn.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Url/Languages/zh-cn.xaml
@@ -2,7 +2,7 @@
在以下位置打开
- 新窗户
+ 新窗口新标签打开链接:{0}
diff --git a/Plugins/Flow.Launcher.Plugin.Url/plugin.json b/Plugins/Flow.Launcher.Plugin.Url/plugin.json
index 73d9bff30..9f5576ba9 100644
--- a/Plugins/Flow.Launcher.Plugin.Url/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.Url/plugin.json
@@ -4,7 +4,7 @@
"Name": "URL",
"Description": "Open the typed URL from Flow Launcher",
"Author": "qianlifeng",
- "Version": "3.0.8",
+ "Version": "1.0.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Url.dll",
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ja.xaml
index 9ae628853..4112f41ff 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ja.xaml
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ja.xaml
@@ -34,7 +34,7 @@
タイトル
- Status
+ 状態アイコンを選択アイコンキャンセル
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ko.xaml
index 3ac9f6a6c..c8f069ca7 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ko.xaml
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ko.xaml
@@ -21,20 +21,18 @@
자동완성 데이터 출처:웹 검색을 선택하세요Are you sure you want to delete {0}?
- If you want to add a search for a particular website to Flow, first enter a dummy text string in the search bar of that website, and launch the search. Now copy the contents of the browser's address bar, and paste it in the URL field below. Replace your test string with {q}. For example, if you search for casino on Netflix, its address bar reads
+ 특정 웹사이트의 검색 기능을 Flow에 추가하고 싶다면, 먼저 해당 웹사이트의 검색창에 임의의 텍스트를 입력하고 검색을 실행하세요. 그런 다음 브라우저의 주소 표시줄에 표시된 내용을 복사하여 아래의 URL 필드에 붙여넣습니다. 이때, 검색에 사용한 테스트 문자열을 {q}로 바꿔주세요. 예를 들어, Netflix에서 casino를 검색하면 주소 표시줄에는 다음과 같이 표시됩니다.https://www.netflix.com/search?q=Casino
- Now copy this entire string and paste it in the URL field below.
- Then replace casino with {q}.
- Thus, the generic formula for a search on Netflix is https://www.netflix.com/search?q={q}
+ 이제 이 전체 문자열을 복사해서 아래의 URL 필드에 붙여넣으세요. 그런 다음 casino를 **{q}**로 바꿔주세요. 따라서 Netflix에서의 일반적인 검색 형식은 다음과 같습니다: https://www.netflix.com/search?q={q}
- Copy URL
- Copy search URL to clipboard
+ URL 복사
+ 검색 주소를 클립보드에 복사이름
- Status
+ 상태아이콘 선택아이콘취소
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/zh-cn.xaml
index 375a741d0..7b8a72d6b 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/zh-cn.xaml
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/zh-cn.xaml
@@ -17,7 +17,7 @@
触发关键字打开链接搜索
- Use Search Query Autocomplete
+ 使用搜索查询自动补全自动补全数据:请选择一项您确定要删除 {0} 吗?
@@ -29,8 +29,8 @@
那么 Netflix 搜索的表达式就是 https://www.netflix.com/search?q={q}
- Copy URL
- Copy search URL to clipboard
+ 复制链接
+ 复制搜索网址到剪贴板标题
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml
index 3df50b3ed..746c9cf84 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml
@@ -56,13 +56,13 @@
-
+
-
+
@@ -139,13 +139,13 @@
Name="imgPreviewIcon"
Width="24"
Height="24"
- Margin="14,0,0,0"
+ Margin="14 0 0 0"
VerticalAlignment="Center" />
@@ -196,16 +198,16 @@
Grid.Row="1"
Background="{DynamicResource PopupButtonAreaBGColor}"
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
- BorderThickness="0,1,0,0">
+ BorderThickness="0 1 0 0">
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml.cs
index 58577dbc1..acc2c1e5c 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml.cs
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SearchSourceSetting.xaml.cs
@@ -1,5 +1,7 @@
using System.Collections.Generic;
+using System.Linq;
using System.Windows;
+using System.Windows.Input;
using Microsoft.Win32;
namespace Flow.Launcher.Plugin.WebSearch
@@ -28,6 +30,7 @@ namespace Flow.Launcher.Plugin.WebSearch
Initialize(sources, context, Action.Add);
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "")]
private async void Initialize(IList sources, PluginInitContext context, Action action)
{
InitializeComponent();
@@ -124,6 +127,7 @@ namespace Flow.Launcher.Plugin.WebSearch
}
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "")]
private async void OnSelectIconClick(object sender, RoutedEventArgs e)
{
const string filter = "Image files (*.jpg, *.jpeg, *.gif, *.png, *.bmp) |*.jpg; *.jpeg; *.gif; *.png; *.bmp";
@@ -143,6 +147,30 @@ namespace Flow.Launcher.Plugin.WebSearch
}
}
}
+
+ //Block Space Input
+ private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Space)
+ {
+ e.Handled = true;
+ }
+ }
+ private void TextBox_Pasting(object sender, DataObjectPastingEventArgs e)
+ {
+ if (e.DataObject.GetDataPresent(DataFormats.Text))
+ {
+ string text = e.DataObject.GetData(DataFormats.Text) as string;
+ if (!string.IsNullOrEmpty(text) && text.Any(char.IsWhiteSpace))
+ {
+ e.CancelCommand();
+ }
+ }
+ else
+ {
+ e.CancelCommand();
+ }
+ }
}
public enum Action
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs
index 65be06b53..681c8b649 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Baidu.cs
@@ -11,6 +11,8 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
{
public class Baidu : SuggestionSource
{
+ private static readonly string ClassName = nameof(Baidu);
+
private readonly Regex _reg = new Regex("window.baidu.sug\\((.*)\\)");
public override async Task> SuggestionsAsync(string query, CancellationToken token)
@@ -24,7 +26,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
}
catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException})
{
- Main._context.API.LogException(nameof(Baidu), "Can't get suggestion from Baidu", e);
+ Main._context.API.LogException(ClassName, "Can't get suggestion from Baidu", e);
return null;
}
@@ -39,7 +41,7 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
}
catch (JsonException e)
{
- Main._context.API.LogException(nameof(Baidu), "Can't parse suggestions", e);
+ Main._context.API.LogException(ClassName, "Can't parse suggestions", e);
return new List();
}
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs
index 9efc36263..ccfa5dcc8 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Bing.cs
@@ -10,6 +10,8 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
{
public class Bing : SuggestionSource
{
+ private static readonly string ClassName = nameof(Bing);
+
public override async Task> SuggestionsAsync(string query, CancellationToken token)
{
try
@@ -33,12 +35,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
}
catch (Exception e) when (e is HttpRequestException or { InnerException: TimeoutException })
{
- Main._context.API.LogException(nameof(Bing), "Can't get suggestion from Bing", e);
+ Main._context.API.LogException(ClassName, "Can't get suggestion from Bing", e);
return null;
}
catch (JsonException e)
{
- Main._context.API.LogException(nameof(Bing), "Can't parse suggestions", e);
+ Main._context.API.LogException(ClassName, "Can't parse suggestions", e);
return new List();
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/DuckDuckGo.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/DuckDuckGo.cs
index 1d248caf3..0627f7220 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/DuckDuckGo.cs
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/DuckDuckGo.cs
@@ -10,6 +10,8 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
{
public class DuckDuckGo : SuggestionSource
{
+ private static readonly string ClassName = nameof(DuckDuckGo);
+
public override async Task> SuggestionsAsync(string query, CancellationToken token)
{
// When the search query is empty, DuckDuckGo returns `[]`. When it's not empty, it returns data
@@ -34,12 +36,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
}
catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException})
{
- Main._context.API.LogException(nameof(DuckDuckGo), "Can't get suggestion from DuckDuckGo", e);
+ Main._context.API.LogException(ClassName, "Can't get suggestion from DuckDuckGo", e);
return null;
}
catch (JsonException e)
{
- Main._context.API.LogException(nameof(DuckDuckGo), "Can't parse suggestions", e);
+ Main._context.API.LogException(ClassName, "Can't parse suggestions", e);
return new List();
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs
index ad8fb508f..f28212524 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/SuggestionSources/Google.cs
@@ -10,6 +10,8 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
{
public class Google : SuggestionSource
{
+ private static readonly string ClassName = nameof(Google);
+
public override async Task> SuggestionsAsync(string query, CancellationToken token)
{
try
@@ -27,12 +29,12 @@ namespace Flow.Launcher.Plugin.WebSearch.SuggestionSources
}
catch (Exception e) when (e is HttpRequestException or {InnerException: TimeoutException})
{
- Main._context.API.LogException(nameof(Google), "Can't get suggestion from Google", e);
+ Main._context.API.LogException(ClassName, "Can't get suggestion from Google", e);
return null;
}
catch (JsonException e)
{
- Main._context.API.LogException(nameof(Google), "Can't parse suggestions", e);
+ Main._context.API.LogException(ClassName, "Can't parse suggestions", e);
return new List();
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json b/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json
index c8b6310a7..b4153feb1 100644
--- a/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.WebSearch/plugin.json
@@ -27,7 +27,7 @@
"Name": "Web Searches",
"Description": "Provide the web search ability",
"Author": "qianlifeng",
- "Version": "3.1.4",
+ "Version": "1.0.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.WebSearch.dll",
diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Log.cs b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Log.cs
index 257b0fa8b..c72230f2b 100644
--- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Log.cs
+++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Log.cs
@@ -11,10 +11,12 @@ namespace Flow.Launcher.Plugin.WindowsSettings
{
_api = api;
}
+
public static void Exception(string message, Exception exception, Type type, [CallerMemberName] string methodName = "")
{
_api?.LogException(type.FullName, message, exception, methodName);
}
+
public static void Warn(string message, Type type, [CallerMemberName] string methodName = "")
{
_api?.LogWarn(type.FullName, message, methodName);
diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.de-DE.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.de-DE.resx
index 5f8d9a6df..d9de28e4b 100644
--- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.de-DE.resx
+++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.de-DE.resx
@@ -1752,7 +1752,7 @@
Einen Dateityp immer in einem spezifischen Programm öffnen lassen
- Change the Narrator’s voice
+ Stimme ändernTastaturprobleme finden und beheben
@@ -1761,7 +1761,7 @@
Screenreader verwenden
- Show which workgroup this computer is on
+ Arbeitsgruppe auf diesem Computer AnzeigenMausrad-Einstellungen ändern
@@ -1773,7 +1773,7 @@
Probleme finden und beheben
- Change settings for content received using Tap and send
+ Einstellung für empfangene Inhalte von Tippen und SendenChange default settings for media or devices
@@ -2176,7 +2176,7 @@
Change advanced colour management settings for displays, scanners and printers
- Let Windows suggest Ease of Access settings
+ Lasse Windows Vereinfachte Zugriffseinstellungen vorschlagenClear disk space by deleting unnecessary files
@@ -2191,16 +2191,16 @@
Record steps to reproduce a problem
- Adjust the appearance and performance of Windows
+ Aussehen und Leistung von Windows anpassen
- Settings for Microsoft IME (Japanese)
+ Einstellungen für Microsoft IME (Japanisch)
- Invite someone to connect to your PC and help you, or offer to help someone else
+ Lade jemanden ein, sich mit deinem PC zu verbinden und dir zu helfen oder anderen zu helfen
- Run programs made for previous versions of Windows
+ Programme für frühere Versionen von Windows ausführenChoose the order of how your screen rotates
diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.he-IL.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.he-IL.resx
index faa8c2dcc..5bf27c746 100644
--- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.he-IL.resx
+++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.he-IL.resx
@@ -416,7 +416,7 @@
Area Privacy
- Cangjie IME
+ קלט CangjieArea TimeAndLanguage
@@ -547,7 +547,7 @@
Area Control Panel (legacy settings)
- deuteranopia
+ עיוורון צבעים – אדום־ירוקMedical: Mean you don't can see red colors
@@ -697,7 +697,7 @@
Area Control Panel (legacy settings)
- Game DVR
+ מקליט משחקיםArea Gaming
@@ -721,7 +721,7 @@
Area Control Panel (legacy settings)
- Glance
+ הצצהArea Personalization, Deprecated in Windows 10, version 1809 and later
@@ -1251,7 +1251,7 @@
Area System
- protanopia
+ עיוורון צבעים – אדוםMedical: Mean you don't can see green colors
@@ -1297,7 +1297,7 @@
Mean the weakness you can't differ between red and green colors
- Red week
+ שבוע אדוםMean you don't can see red colors
@@ -1332,7 +1332,7 @@
Area Control Panel (legacy settings)
- schedtasks
+ משימות מתוזמנותFile name, Should not translated
@@ -1544,7 +1544,7 @@
שקיפות
- tritanopia
+ עיוורון צבעים – כחולMedical: Mean you don't can see yellow and blue colors
diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.ja-JP.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.ja-JP.resx
index 2e2de0681..91be8a392 100644
--- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.ja-JP.resx
+++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.ja-JP.resx
@@ -456,7 +456,7 @@
Area Personalization
-
+ The command to direct start a setting
@@ -1117,7 +1117,7 @@
Area Control Panel (legacy settings)
-
+ password.cpl
@@ -1572,7 +1572,7 @@
Area Control Panel (legacy settings)
-
+ Means The "Windows Version"
diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.ko-KR.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.ko-KR.resx
index ab69a2a31..71f1a327a 100644
--- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.ko-KR.resx
+++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.ko-KR.resx
@@ -468,7 +468,7 @@
Area Privacy
- Control Panel
+ 제어판Type of the setting is a "(legacy) Control Panel setting"
@@ -1524,7 +1524,7 @@
File name, Should not translated
- timedate.cpl
+ File name, Should not translated
@@ -1740,34 +1740,34 @@
Change device installation settings
- Turn off background images
+ 배경 이미지 제거Navigation properties
- Media streaming options
+ 미디어 스트리밍 옵션
- Make a file type always open in a specific program
+ 파일 형식을 항상 특정 프로그램에서 열도록 설정
- Change the Narrator’s voice
+ 내레이터 목소리 변경Find and fix keyboard problems
- Use screen reader
+ 내레이터 켜기Show which workgroup this computer is on
- Change mouse wheel settings
+ 마우스 휠 설정 변경
- Manage computer certificates
+ 컴퓨터 인증서 관리Find and fix problems
@@ -1776,94 +1776,94 @@
Change settings for content received using Tap and send
- Change default settings for media or devices
+ 미디어 또는 장치에 대한 기본 설정 변경Print the speech reference card
- Calibrate display colour
+ 디스플레이 색 보정
- Manage file encryption certificates
+ 파일 암호화 인증서 관리
- View recent messages about your computer
+ 최근 메세지 검토 및 문제 해결
- Give other users access to this computer
+ 다른 사용자에게 컴퓨터 액세스 권한 부여
- Show hidden files and folders
+ 숨김 파일 및 폴더 표시
- Change Windows To Go start-up options
+ Windows To Go 시작 옵션 변경
- See which processes start up automatically when you start Windows
+ Windows 시작 시 자동으로 실행되는 프로세스 확인Tell if an RSS feed is available on a website
- Add clocks for different time zones
+ 다양한 시간대의 시계 추가
- Add a Bluetooth device
+ Bluetooth 장치 추가
- Customise the mouse buttons
+ 마우스 버튼 사용자 설정
- Set tablet buttons to perform certain tasks
+ 태블릿 버튼을 특정 작업에 맞게 설정
- View installed fonts
+ 설치된 글꼴 보기
- Change the way currency is displayed
+ 날짜 및 시간 표시 방식 변경
- Edit group policy
+ 로컬 그룹 정책 편집기Manage browser add-ons
- Check processor speed
+ 프로세서 속도 확인
- Check firewall status
+ 방화벽 상태 확인Send or receive a file
- Add or remove user accounts
+ 사용자 계정 추가 또는 삭제
- Edit the system environment variables
+ 시스템 환경 변수 편집
- Manage BitLocker
+ BitLocker 관리
- Auto-hide the taskbar
+ 작업 표시줄 자동 숨기기
- Change sound card settings
+ 사운드 카드 설정 변경Make changes to accounts
- Edit local users and groups
+ 로컬 사용자 및 그룹 편집
- View network computers and devices
+ 네트워크 컴퓨터 및 장치 보기
- Install a program from the network
+ 네트워크에서 프로그램 설치View scanners and cameras
@@ -1872,7 +1872,7 @@
Microsoft IME Register Word (Japanese)
- Restore your files with File History
+ 파일 기록으로 파일 복원Turn On-Screen keyboard on or off
@@ -1884,22 +1884,22 @@
Find and fix audio recording problems
- Create a recovery drive
+ 복구 드라이브 만들기Microsoft New Phonetic Settings
- Generate a system health report
+ 시스템 건강 보고서 생성Fix problems with your computer
- Back up and Restore (Windows 7)
+ 파일 백업 또는 복원 (Windows 7)
- Preview, delete, show or hide fonts
+ 글꼴 미리 보기, 삭제, 표시 또는 숨기기Microsoft Quick Settings
@@ -1908,7 +1908,7 @@
View reliability history
- Access RemoteApp and desktops
+ RemoteApp 및 데스크톱 액세스Set up ODBC data sources
@@ -1929,19 +1929,19 @@
Change what closing the lid does
- Turn off unnecessary animations
+ 불필요한 애니메이션 끄기
- Create a restore point
+ 복원 지점 만들기
- Turn off automatic window arrangement
+ 자동 창 배열 끄기
- Troubleshooting History
+ 문제 해결 기록
- Diagnose your computer's memory problems
+ 컴퓨터의 메모리 문제 진단View recommended actions to keep Windows running smoothly
@@ -1992,13 +1992,13 @@
Change the order of Windows SideShow gadgets
- Check keyboard status
+ 키보드 상태 확인
- Control the computer without the mouse or keyboard
+ 마우스 또는 키보드가 없는 컴퓨터 사용
- Change or remove a program
+ 프로그램 변경 또는 제거Change multi-touch gesture settings
@@ -2046,7 +2046,7 @@
How to change your Windows password
- Make it easier to see the mouse pointer
+ 마우스 포인터를 더 쉽게 보이게 설정Set up iSCSI initiator
@@ -2076,7 +2076,7 @@
Find and fix audio playback problems
- Change the mouse pointer display or speed
+ 마우스 포인터 표시 또는 속도 변경Back up your recovery key
@@ -2143,10 +2143,10 @@
Turn Windows features on or off
- Show which operating system your computer is running
+ 내 컴퓨터가 실행 중인 운영 체제를 표시
- View local services
+ 로컬 서비스 보기Manage Work Folders
@@ -2164,28 +2164,28 @@
Change default printer
- Edit environment variables for your account
+ 내 계정의 환경 변수 수정Optimise visual display
- Change mouse click settings
+ 마우스 클릭 설정 변경
- Change advanced colour management settings for displays, scanners and printers
+ 디스플레이, 스캐너 및 프린터의 고급 색 관리 설정 변경Let Windows suggest Ease of Access settings
- Clear disk space by deleting unnecessary files
+ 불필요한 파일을 삭제하여 디스크 공간 확보
- View devices and printers
+ 장치 및 프린터 보기
- Private Character Editor
+ 개인 문자 편집기Record steps to reproduce a problem
@@ -2248,7 +2248,7 @@
Turn flicks on or off
- Add a language
+ 언어 추가View network status and tasks
diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json b/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json
index 413a555d3..64743b3d8 100644
--- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json
+++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json
@@ -4,7 +4,7 @@
"Description": "Search settings inside Control Panel and Settings App",
"Name": "Windows Settings",
"Author": "TobiasSekan",
- "Version": "4.0.12",
+ "Version": "1.0.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.WindowsSettings.dll",
diff --git a/README.md b/README.md
index cbb553bd1..4cd8e059e 100644
--- a/README.md
+++ b/README.md
@@ -351,6 +351,7 @@ Or download the [early access version](https://github.com/Flow-Launcher/Prerelea
+
@@ -374,6 +375,10 @@ Yes please, let us know in the [Q&A](https://github.com/Flow-Launcher/Flow.Launc
## Development
+### Localization
+
+Our project localization is based on [Crowdin](https://crowdin.com). If you would like to change them, please go to https://crowdin.com/project/flow-launcher.
+
### New changes
All changes to flow are captured via pull requests. Some new changes will have been merged but still pending release, this means whilst a change may not exist in the current release, it may very well have been accepted and merged into the dev branch and available as a pre-release download. It is therefore a good idea that before you start to make changes, search through the open and closed pull requests to make sure the change you intend to make is not already done.
diff --git a/Scripts/post_build.ps1 b/Scripts/post_build.ps1
index 1757ed99e..e54852d32 100644
--- a/Scripts/post_build.ps1
+++ b/Scripts/post_build.ps1
@@ -1,5 +1,5 @@
param(
- [string]$config = "Release",
+ [string]$config = "Release",
[string]$solution = (Join-Path $PSScriptRoot ".." -Resolve)
)
Write-Host "Config: $config"
@@ -40,11 +40,11 @@ function Delete-Unused ($path, $config) {
$target = "$path\Output\$config"
$included = Get-ChildItem $target -Filter "*.dll"
foreach ($i in $included){
- $deleteList = Get-ChildItem $target\Plugins -Include $i -Recurse | Where { $_.VersionInfo.FileVersion -eq $i.VersionInfo.FileVersion -And $_.Name -eq "$i" }
+ $deleteList = Get-ChildItem $target\Plugins -Include $i -Recurse | Where { $_.VersionInfo.FileVersion -eq $i.VersionInfo.FileVersion -And $_.Name -eq "$i" }
$deleteList | ForEach-Object{ Write-Host Deleting duplicated $_.Name with version $_.VersionInfo.FileVersion at location $_.Directory.FullName }
$deleteList | Remove-Item
}
- Remove-Item -Path $target -Include "*.xml" -Recurse
+ Remove-Item -Path $target -Include "*.xml" -Recurse
}
function Remove-CreateDumpExe ($path, $config) {
@@ -87,7 +87,7 @@ function Pack-Squirrel-Installer ($path, $version, $output) {
Squirrel --releasify $nupkg --releaseDir $temp --setupIcon $icon --no-msi | Write-Output
Move-Item $temp\* $output -Force
Remove-Item $temp
-
+
$file = "$output\Flow-Launcher-Setup.exe"
Write-Host "Filename: $file"
@@ -107,7 +107,7 @@ function Publish-Self-Contained ($p) {
}
function Publish-Portable ($outputLocation, $version) {
-
+
& $outputLocation\Flow-Launcher-Setup.exe --silent | Out-Null
mkdir "$env:LocalAppData\FlowLauncher\app-$version\UserData"
Compress-Archive -Path $env:LocalAppData\FlowLauncher -DestinationPath $outputLocation\Flow-Launcher-Portable.zip
@@ -119,7 +119,7 @@ function Main {
Copy-Resources $p
if ($config -eq "Release"){
-
+
Delete-Unused $p $config
Publish-Self-Contained $p
@@ -134,4 +134,4 @@ function Main {
}
}
-Main
+Main
\ No newline at end of file
diff --git a/appveyor.yml b/appveyor.yml
index af5aaefdc..fa0b5956b 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: '1.19.5.{build}'
+version: '1.20.0.{build}'
init:
- ps: |
@@ -26,7 +26,16 @@ image: Visual Studio 2022
platform: Any CPU
configuration: Release
before_build:
-- ps: nuget restore
+- ps: |
+ nuget restore
+
+ $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`": `"$env:flowVersion`"" | 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
+ }
build:
project: Flow.Launcher.sln
verbosity: minimal