Compare commits

...

1370 commits
v1.20.0 ... dev

Author SHA1 Message Date
Jack Ye
e8353f7648
Merge pull request #4339 from Flow-Launcher/copilot/fix-folder-search-action
Some checks failed
Build / build (push) Has been cancelled
Fix ArgumentException when editing a disabled Explorer action keyword
2026-03-10 23:29:51 +08:00
VictoriousRaptor
8d042d2134
Fix settings window is minimized when re-opened. (#4341)
* Fix settings window is minimized when re-opened

* Don't save minimized state
2026-03-10 22:50:40 +08:00
Jack Ye
a406eb83e1
Fix ReportWindow crash when log directory is empty or missing (#4338)
* Initial plan

* Fix ReportWindow crash when log directory is empty or doesn't exist

Co-authored-by: Jack251970 <53996452+Jack251970@users.noreply.github.com>

* Improve code comments

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-10 20:29:09 +08:00
copilot-swe-agent[bot]
531b45210a Fix exception when setting Folder Search action keyword in Explorer plugin
- Fix (false, false) case in EditActionKeyword to break gracefully instead
  of throwing ArgumentException. This handles the valid scenario where the
  user changes a disabled keyword's text but keeps it disabled.
- Fix GetActiveActionKeywords to return an empty dictionary instead of null
  when actionKeywordStr is null/empty, preventing NullReferenceException.
- Fix ActionKeyword setter to only auto-enable when the value actually
  changes, preventing unintended side-effects during construction.
- Initialize ActionKeywordSetting backing fields directly in constructor to
  avoid the auto-enable triggering during initialization.
- Change TextBox binding to UpdateSourceTrigger=PropertyChanged so the
  checkbox auto-enables as the user types (before they click the checkbox),
  fixing the checkbox toggle issue caused by LostFocus timing.

Co-authored-by: Jack251970 <53996452+Jack251970@users.noreply.github.com>
2026-03-10 05:40:25 +00:00
copilot-swe-agent[bot]
319db64393 Initial plan 2026-03-10 05:19:00 +00:00
Jack Ye
24f6c90f72
Merge pull request #4333 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Svg.Skia-3.5.0
Some checks are pending
Build / build (push) Waiting to run
Bump Svg.Skia from 3.4.1 to 3.5.0
2026-03-10 11:52:19 +08:00
dependabot[bot]
3e493263c3
Bump Svg.Skia from 3.4.1 to 3.5.0
---
updated-dependencies:
- dependency-name: Svg.Skia
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 23:05:08 +00:00
Jack Ye
916b4ff091
Merge pull request #4312 from Flow-Launcher/Theme_Enhancement
Some checks are pending
Build / build (push) Waiting to run
Improve code quality
2026-03-09 15:15:41 +08:00
Jack Ye
dad8f3e695
Merge branch 'dev' into Theme_Enhancement 2026-03-09 12:05:48 +08:00
Jeremy Wu
7a46fac41a
Merge pull request #4329 from Flow-Launcher/merge_back_2_1_1
Merge v2.1.1 back to dev
2026-03-09 15:00:27 +11:00
Jeremy
79f3bfab96 merge back v2.1.1 from master to dev 2026-03-09 14:50:29 +11:00
Jeremy Wu
0d9cc7ef83
Release 2.1.1 (#4327)
Some checks failed
Publish Default Plugins / publish (push) Has been cancelled
Build / build (push) Has been cancelled
2026-03-09 11:23:36 +08:00
Jack Ye
0ddcc5c1c6
Merge pull request #4319 from Flow-Launcher/FreezeResources1
Freeze SolidColorBrush to improve performance
2026-03-09 11:11:59 +08:00
Jeremy Wu
5ef6a2b688
New Crowdin updates (#4293) 2026-03-09 04:44:49 +02:00
Jack251970
7766d7053d Rename GetFreezeSolidColorBrush to GetFrozenSolidColorBrush
Renamed the method GetFreezeSolidColorBrush to GetFrozenSolidColorBrush in ThemeHelper.cs for improved naming consistency. Updated all references in Theme.cs accordingly. No changes to functionality.
2026-03-08 23:49:09 +08:00
Jack251970
36e15d3809 Refactor: move CopyStyle to ThemeHelper class
Moved the CopyStyle method from Theme to a new static ThemeHelper class for better code organization and reusability. Updated all references in Theme to use ThemeHelper.CopyStyle. Added ThemeHelper.cs and necessary using directives.
2026-03-08 23:48:10 +08:00
Jack251970
ed14c45fd2 Refactor brush creation with ThemeHelper utility
Introduce ThemeHelper.GetFreezeSolidColorBrush to centralize and reuse SolidColorBrush creation and freezing logic. Refactor Theme.cs to use this helper, improving code clarity and maintainability. Also, use Brushes.Transparent directly where appropriate.
2026-03-08 23:46:08 +08:00
Jeremy Wu
12c4b4d711
Merge pull request #4313 from Flow-Launcher/InitProgressbarAnimation_Null
Some checks are pending
Build / build (push) Waiting to run
Move progress bar animation init to Loaded event to fix possible null exception of ProgressBar
2026-03-08 21:30:02 +11:00
Jack Ye
1bdc1164f8
Merge pull request #4322 from Flow-Launcher/FixSelectionBrush
Use DynamicResource for TextBox SelectionBrush
2026-03-08 17:56:16 +08:00
Jack251970
71944ab6cd Use DynamicResource for TextBox SelectionBrush
Changed SelectionBrush in TextBox style from StaticResource to DynamicResource for SystemAccentColorLight1Brush. This enables automatic updates to the selection color when the resource changes at runtime, improving theme responsiveness.
2026-03-04 18:42:19 +08:00
Jack Ye
984b3dabdc
Fix Explorer plugin Quick Access items missing from general search results (#4294)
Some checks failed
Build / build (push) Has been cancelled
2026-03-04 21:25:25 +11:00
Jack251970
9abf136ea2 Improve SolidColorBrush usage and property checks in Theme
Refactor background brush creation to assign, freeze, and reuse
SolidColorBrush instances for better WPF performance. Replace
string-based property checks with type-safe Control.BackgroundProperty
comparisons for improved robustness.
2026-03-04 12:48:48 +08:00
Jack251970
a2f224225e Move progress bar animation init to Loaded event
InitProgressbarAnimation is now triggered by the progress bar's Loaded event instead of during main window initialization. This ensures the animation starts only after the progress bar is fully loaded, improving reliability and preventing potential UI timing issues.
2026-03-02 18:20:09 +08:00
Jack251970
6566871b15 Refactor theme blur and dark mode handling logic
Refactored Theme.cs to improve theme blur and dark mode handling:
- Replaced static IsBlurTheme() with IsThemeBlurEnabled(dict) for per-theme blur detection.
- Updated all blur checks to use the new method.
- Improved code style, readability, and comments.
- Moved system dark mode registry check to only run when needed.
- Ensured blur and dark mode are applied based on theme and user/system preferences.
2026-03-02 18:03:24 +08:00
Jack Ye
dda900041a
Merge pull request #4299 from Flow-Launcher/IsMinimumAppVersionSatisfied
Some checks failed
Build / build (push) Has been cancelled
Add MinimumAppVersion support for plugin.json
2026-03-01 11:15:01 +08:00
Jack251970
cce80ac5aa Ensure temporary folder deletion 2026-02-27 19:14:03 +08:00
Jack251970
036a3761ea Remove log for plugin version mismatch in PluginManager
Logging when a plugin's minimum Flow Launcher version is not met
has been removed. The method now returns false without logging
any informational message.
2026-02-27 19:05:25 +08:00
Jack251970
48878d2d8c Make log message context-neutral 2026-02-27 19:04:30 +08:00
Jack Ye
dca90916fe
Fix translations
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-27 19:02:50 +08:00
Jack251970
ba1908d605 Update plugin min version warning message formatting
Improved the message shown when a plugin requires a newer Flow Launcher version by adding line breaks for clarity and updating the title wording. Adjusted code to pass Environment.NewLine and modified resource strings in en.xaml to support the new format.
2026-02-27 18:54:13 +08:00
Jack251970
590bf20204 Add null check for plugin metadata deserialization
Throw a JsonException if deserializing plugin metadata from JSON returns null. This prevents null metadata from being used and improves error handling for invalid or corrupted plugin.json files.
2026-02-27 18:53:30 +08:00
Jack251970
b02a5a265d Refactor plugin min version check and improve logging
Refactored the plugin minimum app version check to use Version.TryParse instead of try-catch with Version.Parse, preventing exceptions on invalid input. Improved error handling by logging parse failures as errors and version mismatches as info, making the logic clearer and more robust.
2026-02-27 18:48:59 +08:00
Jack251970
b1b18ee215 Improve error handling for invalid plugin.json files
Previously, installing a plugin with an invalid or corrupted plugin.json would cause an unhandled exception. Now, deserialization errors are caught, a user-friendly error message is shown, and the exception is logged. Added a new localized error message for this scenario in en.xaml.
2026-02-27 18:42:07 +08:00
Jack Ye
8e4f7258ff
Improve translations
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-27 18:36:54 +08:00
Jack251970
2e0db3b90f Add MinimumAppVersion support for plugins
Introduced MinimumAppVersion property to PluginMetadata, enabling plugins to specify required Flow Launcher version. Plugin installation now checks this requirement and prompts users if unsatisfied. Minimum app version logic moved to PluginManager and applied to manifest updates. Added localized strings for user prompts. Refactored SameOrLesserPluginVersionExists to accept PluginMetadata.
2026-02-27 18:30:29 +08:00
Jack Ye
54dc790cb6
Merge pull request #4297 from Flow-Launcher/dependabot/github_actions/actions/upload-artifact-7
Some checks failed
Build / build (push) Has been cancelled
Bump actions/upload-artifact from 6 to 7
2026-02-27 12:08:19 +08:00
dependabot[bot]
ed6fe34192
Bump actions/upload-artifact from 6 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-26 22:42:54 +00:00
Jeremy Wu
10fcbb9990
Merge pull request #4292 from Flow-Launcher/merge_2_1_0
Some checks failed
Build / build (push) Has been cancelled
Merge v2.1.0 back to dev
2026-02-25 22:54:55 +11:00
Jeremy Wu
980c1a1060
Merge branch 'dev' into merge_2_1_0 2026-02-25 22:45:03 +11:00
Jeremy Wu
cd9825380d
Release 2.1.0 | Plugin 5.2.0 (#4276)
Some checks failed
Publish Default Plugins / publish (push) Has been cancelled
Build / build (push) Has been cancelled
2026-02-24 22:52:23 +11:00
Jack Ye
3b3a59fd62
Fix process priority for logon startup task set to BelowNormal by default (#4283)
Some checks are pending
Build / build (push) Waiting to run
2026-02-24 21:17:22 +11:00
Jack Ye
73f657f84e
Merge pull request #4279 from Flow-Launcher/websearch_ui
Some checks are pending
Build / build (push) Waiting to run
Enable TwoWay binding for MaxSuggestions NumberBox
2026-02-24 14:25:10 +08:00
Jack Ye
c9baab7ac9
Merge branch 'dev' into websearch_ui 2026-02-24 00:08:25 +08:00
Jack251970
72269db8e6 Enable TwoWay binding for MaxSuggestions NumberBox
Changed NumberBox binding for Settings.MaxSuggestions from OneWay to TwoWay, allowing user input in the UI to update the underlying setting. This ensures changes made by users are saved back to the settings model.
2026-02-24 00:07:36 +08:00
Jack Ye
ffa974b4e5
Merge pull request #4277 from Flow-Launcher/websearch_ui
Some checks are pending
Build / build (push) Waiting to run
Downgrade iNKORE.UI.WPF.Modern to 0.10.1 for WebSearch Plugin
2026-02-23 17:09:43 +08:00
Jack Ye
727aac4b82
Merge pull request #4278 from Flow-Launcher/SkiaSharp_runtimes
Some checks are pending
Build / build (push) Waiting to run
Remove Skia sharp unnecessary runtimes from packages
2026-02-23 17:01:00 +08:00
Jack251970
9e66f71774 Revert version bump of SkiaSharp package 2026-02-23 16:36:08 +08:00
Jack Ye
d511872c00
Ensure runtimes folder
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-22 21:29:01 +08:00
Jack251970
c46a52d1ca Remove SkiaSharp .pdb files after build and publish
Added MSBuild targets to delete unnecessary SkiaSharp .pdb files from output and publish directories after build and publish steps, reducing artifact size.
2026-02-22 21:22:17 +08:00
Jack251970
c04da3ab83 Add RISC-V 64-bit runtime support to project config
Included linux-musl-riscv64 and linux-riscv64 in both OutputPath and PublishDir of Flow.Launcher.Plugin.BrowserBookmark.csproj to ensure runtime files for RISC-V 64-bit architectures are available during build and publish.
2026-02-22 21:13:38 +08:00
Jack251970
cb60492657 Downgrade iNKORE.UI.WPF.Modern to 0.10.1
The package reference for iNKORE.UI.WPF.Modern in Flow.Launcher.Plugin.WebSearch.csproj was changed from version 0.10.2.1 to 0.10.1.
2026-02-22 12:38:17 +08:00
Jack Ye
118d6e2a73
Fix incomplete plugin directory deletion on uninstall (#4250)
Some checks failed
Build / build (push) Has been cancelled
2026-02-21 22:34:53 +11:00
Jeremy Wu
918eb0f4a9
New Crowdin updates (#4181) 2026-02-21 12:51:21 +02:00
DB P
11e0f8c3a4
Merge pull request #4273 from onesounds/20250220_SkipConfirm
Some checks are pending
Build / build (push) Waiting to run
Add option to skip confirmation for power actions (Sys Plugin)
2026-02-21 16:01:06 +09:00
Jack251970
a99b8399fe Improve code quality 2026-02-21 14:51:20 +08:00
DB P
7e9a626891 style: fix indentation and tab spacing 2026-02-20 17:40:27 +09:00
DB P
cecd90e675 Refactor: Improve power action logic and property setter
- Add braces for better readability in shutdown/reboot logic (Main.cs).
- Prevent redundant OnPropertyChanged calls in _skipPowerActionConfirmation setter (Settings.cs).
2026-02-20 17:34:37 +09:00
DB P
2d7d025682 Add Skip logic in command 2026-02-20 17:18:21 +09:00
DB P
12fc597b56 Add Checkbox for Skip Confirm 2026-02-20 17:06:31 +09:00
Jack Ye
886cd75ad2
Merge pull request #4272 from umutKaracelebi/fix/readme-drag-drop-typo
Some checks are pending
Build / build (push) Waiting to run
Fix/readme drag drop typo
2026-02-20 12:32:40 +08:00
Jack251970
2b50c4faae README Polish: fixed Flow 2026-02-20 12:22:44 +08:00
umutKaracelebi
9c3dfcd09f README Polish: fixed grammar, consistency, and phrasing 2026-02-19 21:50:29 +03:00
umutKaracelebi
cb52fa6ab7 Fix typo: 'can be change' -> 'can be changed' in Drag & Drop section 2026-02-19 21:29:39 +03:00
Jack Ye
faeba8119a
Merge pull request #4263 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Microsoft.Data.Sqlite-10.0.3
Some checks failed
Build / build (push) Has been cancelled
Bump Microsoft.Data.Sqlite from 10.0.2 to 10.0.3
2026-02-11 10:07:49 +08:00
Jack Ye
89b338fb86
Merge pull request #4264 from Flow-Launcher/dependabot/nuget/Flow.Launcher.Plugin/Microsoft.SourceLink.GitHub-10.0.103
Bump Microsoft.SourceLink.GitHub from 10.0.102 to 10.0.103
2026-02-11 10:07:35 +08:00
dependabot[bot]
60ef8e648c
Bump Microsoft.SourceLink.GitHub from 10.0.102 to 10.0.103
---
updated-dependencies:
- dependency-name: Microsoft.SourceLink.GitHub
  dependency-version: 10.0.103
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-10 22:45:26 +00:00
dependabot[bot]
498913e71d
Bump Microsoft.Data.Sqlite from 10.0.2 to 10.0.3
---
updated-dependencies:
- dependency-name: Microsoft.Data.Sqlite
  dependency-version: 10.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-10 22:45:10 +00:00
Jack Ye
6c1f7fade8
Merge pull request #4260 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/SkiaSharp-3.119.2
Some checks are pending
Build / build (push) Waiting to run
Bump SkiaSharp from 3.119.1 to 3.119.2
2026-02-10 11:44:48 +08:00
dependabot[bot]
0f829d0b91
Bump SkiaSharp from 3.119.1 to 3.119.2
---
updated-dependencies:
- dependency-name: SkiaSharp
  dependency-version: 3.119.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-10 01:07:13 +00:00
Jack Ye
0717933459
Fix progress query management and thread safety (#4135)
Some checks failed
Build / build (push) Has been cancelled
2026-02-08 12:49:12 +11:00
Jack Ye
516059e390
Merge pull request #4252 from Flow-Launcher/dependabot/nuget/Flow.Launcher.Plugin/multi-5d8c206339
Some checks failed
Build / build (push) Has been cancelled
Bump Microsoft.Windows.CsWin32 from 0.3.205 to 0.3.269
2026-02-06 14:06:23 +08:00
Jack251970
d4f472a64e Update AdjustTokenPrivileges PInvoke usage
Refactored the AdjustTokenPrivileges call to use null for the previous state and an out variable for the return length, improving compatibility with the PInvoke signature and aligning with recommended usage.
2026-02-05 20:28:37 +08:00
dependabot[bot]
97d7eab1a7
Bump Microsoft.Windows.CsWin32 from 0.3.205 to 0.3.269
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.269
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.269
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.269
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-04 22:46:23 +00:00
Jeremy Wu
6c8add051f
Merge pull request #4057 from Flow-Launcher/last_history_show_result_icon
Some checks failed
Build / build (push) Has been cancelled
History results display actual result icon for Last Opened history style
2026-01-26 17:50:55 +11:00
Jeremy
248da93d05 fix preview when toggle history mode via Ctrl + H 2026-01-26 16:57:47 +11:00
Jack Ye
e9dcd8b7ba
Fix code comments
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-01-23 16:15:25 +08:00
Jack251970
935d41dc26 Ensure combability of glyph setter 2026-01-23 16:10:01 +08:00
Jack251970
0f0ee9ffde Revert "Try to fix combability issue"
This reverts commit b703bcdd52.
2026-01-23 16:05:03 +08:00
Jack251970
b703bcdd52 Try to fix combability issue 2026-01-23 15:51:24 +08:00
Jack251970
8e127d0339 Code cleanup 2026-01-21 18:13:27 +08:00
Jack Ye
add111fe3f
Merge pull request #4212 from Flow-Launcher/dependabot/nuget/Flow.Launcher.Plugin/Microsoft.SourceLink.GitHub-10.0.102
Bump Microsoft.SourceLink.GitHub from 8.0.0 to 10.0.102
2026-01-21 18:08:03 +08:00
Jeremy
bf44637400 revert context menu early exit when history result 2026-01-20 23:18:00 +11:00
Jeremy
657e6ae7d3 add null-safe comparison for glyph 2026-01-20 22:55:04 +11:00
Jeremy
4ddb164bbd adjusted context menu call to exit early when history result selected 2026-01-20 22:48:11 +11:00
Jeremy
5c241d7491 fix to respect max history result shown setting 2026-01-20 21:47:38 +11:00
Jeremy
90360022a4 update glyph when history result exists 2026-01-20 21:26:04 +11:00
Jack Ye
ca5f5cf0e3
Merge pull request #4206 from Flow-Launcher/copilot/fix-calculator-thousands-separator
Add setting to disable thousands separator in Calculator plugin
2026-01-17 20:17:41 +08:00
VictoriousRaptor
befcc9b37c
Update Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml 2026-01-17 19:59:47 +08:00
Jack Ye
a46350c76a
Merge pull request #4211 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Microsoft.Data.Sqlite-10.0.2
Bump Microsoft.Data.Sqlite from 10.0.1 to 10.0.2
2026-01-14 21:53:22 +08:00
Jack Ye
d72a15937d
Merge branch 'dev' into dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Microsoft.Data.Sqlite-10.0.2 2026-01-14 21:17:51 +08:00
Jack Ye
56c1b92316
Merge pull request #4210 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Svg.Skia-3.4.1
Bump Svg.Skia from 3.4.0 to 3.4.1
2026-01-14 21:17:13 +08:00
dependabot[bot]
91cd27298d
Bump Microsoft.SourceLink.GitHub from 8.0.0 to 10.0.102
---
updated-dependencies:
- dependency-name: Microsoft.SourceLink.GitHub
  dependency-version: 10.0.102
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 22:45:22 +00:00
dependabot[bot]
98bb98c265
Bump Microsoft.Data.Sqlite from 10.0.1 to 10.0.2
---
updated-dependencies:
- dependency-name: Microsoft.Data.Sqlite
  dependency-version: 10.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 22:45:07 +00:00
Jeremy
e970bb4d4e fix icon & glyph display based on selected history style results 2026-01-13 23:32:13 +11:00
dependabot[bot]
ed83da5669
Bump Svg.Skia from 3.4.0 to 3.4.1
---
updated-dependencies:
- dependency-name: Svg.Skia
  dependency-version: 3.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 03:05:02 +00:00
Jack Ye
7e7f85352b
Fix potential locale-dependent test failure
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-01-12 14:13:01 +08:00
Jack251970
013622a2a7 Restore to default in calculator tests 2026-01-12 11:37:47 +08:00
Jack251970
fb4da697a2 Improve calculator thousands separator test 2026-01-12 11:37:06 +08:00
Jack Ye
4e56cb4e74
Merge pull request #4203 from Flow-Launcher/shell_path
Fix ShellPath directory for shell path selection
2026-01-11 21:07:39 +08:00
copilot-swe-agent[bot]
649b142b46 Add UseThousandsSeparator setting to Calculator plugin
Co-authored-by: Jack251970 <53996452+Jack251970@users.noreply.github.com>
2026-01-10 06:30:57 +00:00
copilot-swe-agent[bot]
34257cad60 Initial plan 2026-01-10 06:27:26 +00:00
Jack251970
382a77d2c5 Fix ShellPath directory for shell path selection
Updated OpenShellPath to use Settings.ShellPath directory when prompting for a file path, ensuring the dialog opens in the relevant location instead of the editor path directory.
2026-01-07 13:40:13 +08:00
Jack251970
3745c44455 Revert Update the result when executing history item 2026-01-06 20:11:36 +08:00
Jack251970
9d9ab1ffd2 Do not allow context menu for history items 2026-01-06 18:35:53 +08:00
Jack251970
938a87ceca Fix preview panel blank issue 2026-01-06 18:31:15 +08:00
Jack251970
583bf74724 Update code comments 2026-01-06 15:46:08 +08:00
Jack251970
26e4529f27 Update the result when executing history item 2026-01-06 15:45:49 +08:00
Jack251970
e404d02965 Update the result when executing history item 2026-01-06 15:13:37 +08:00
Jack251970
c610100c13 Use QueryManager function 2026-01-06 15:07:00 +08:00
Jack Ye
8d4e03bdb8
Merge pull request #4202 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Svg.Skia-3.4.0
Bump Svg.Skia from 3.2.1 to 3.4.0
2026-01-06 10:40:22 +08:00
dependabot[bot]
77b631be87
Bump Svg.Skia from 3.2.1 to 3.4.0
---
updated-dependencies:
- dependency-name: Svg.Skia
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-05 22:08:44 +00:00
Jack251970
e403c41530 Change copied icon logic as origin 2026-01-05 22:55:09 +08:00
Jack251970
5b2fb1fb38 Revert "Ensure history items have valid icons in QueryHistory"
This reverts commit e6a91a9959.
2026-01-05 22:26:39 +08:00
Jack251970
cf4268d1c3 Fix preview panel blank issue 2026-01-05 21:30:06 +08:00
Jack251970
e6a91a9959 Ensure history items have valid icons in QueryHistory
Added CheckIcoPathValidity to QueryHistory to set default icons for history items missing IcoPath, addressing legacy data issues. Integrated this check into MainViewModel's RefreshLastOpenedHistoryResults to guarantee icon validity before updating absolute paths.
2026-01-05 20:17:12 +08:00
Jack251970
f22b6449d4 Fix issue when querying history items in home page 2026-01-05 19:18:47 +08:00
Jeremy
58d910dc57 fix deep copy Glyph on LastOpenHistoryResult 2026-01-05 19:37:05 +11:00
Jeremy
11645fdc9a ensure history results ordered by ExecutedDateTime 2026-01-05 19:27:11 +11:00
Jack251970
177e60739c Add code comments 2026-01-05 14:34:57 +08:00
Jack251970
5f992352ee Code cleanup 2026-01-05 13:48:58 +08:00
Jack251970
216b6f536f Code cleanup 2026-01-05 13:07:02 +08:00
Jack Ye
1e176151a0
Merge pull request #4201 from Flow-Launcher/revert-4118-UI.WPF.Modern
Revert "Upgrade iNKORE.UI.WPF.Modern and refactor scroll logic"
2026-01-05 07:38:31 +08:00
Jack Ye
604fd2be44
Merge branch 'dev' into revert-4118-UI.WPF.Modern 2026-01-04 23:59:04 +08:00
Jack Ye
27c4d35a9b
Merge pull request #4198 from nilvanlopes/feature/websearch-max-suggestions
Add max suggestions limit for web search plugin
2026-01-04 23:58:43 +08:00
Jack Ye
a9e6a68369
Revert "Upgrade iNKORE.UI.WPF.Modern and refactor scroll logic" 2026-01-04 23:47:42 +08:00
Jack251970
6c1c9fe600 Use wrap panel 2026-01-04 23:45:13 +08:00
Jack251970
af9050c34b Test no PrivateAssets 2026-01-04 23:19:41 +08:00
Jack251970
4c46e16160 Fix possible convert issue 2026-01-04 23:19:12 +08:00
Jack251970
590b4c82d2 Try NumberBox 2026-01-04 23:00:24 +08:00
Jeremy
19fa107fea minor fixes and adjustments for code quality 2026-01-04 22:05:39 +11:00
Jeremy
ea7a2d271d mark PopulateHistoryFromLegacyHistory obsolete 2026-01-04 21:37:39 +11:00
Jeremy
55673cdb4b add method comments 2026-01-04 21:16:13 +11:00
Jeremy
25037e3f5f update IcoPath if existing is different to the selected 2026-01-04 20:51:25 +11:00
Jeremy
94ebbab68a update legacy item to show Query style title 2026-01-04 19:54:16 +11:00
Jeremy
4174b5e1e4 fix title when query history style used 2026-01-04 17:41:52 +11:00
Jeremy
101e5e6ff1 fix Glyph optional null when copying 2026-01-04 16:39:33 +11:00
Nilvan Lopes
669f3ef404
Update Plugins/Flow.Launcher.Plugin.WebSearch/Languages/en.xaml
Co-authored-by: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>
2026-01-03 11:14:20 -03:00
Nilvan Lopes
4c7cbadc2f remove the language files other than the en.xaml 2026-01-02 23:07:29 -03:00
Nilvan Lopes
2176553b26 Add max suggestions setting for web search plugins
Introduced a MaxSuggestions property (min 1, max 10) to control the number of autocomplete suggestions shown in web search plugins. Updated the UI to allow user configuration and added localized labels for the new setting in all supported languages. Suggestions in results are now limited according to this setting.
2026-01-02 14:32:44 -03:00
Jeremy
024eeaf6bf changed LastOpenHistoryResult's Copy to DeepCopy to clarify intent 2026-01-02 22:30:09 +11:00
Jack Ye
83883c4b05
Merge pull request #4195 from Flow-Launcher/fix-localized-name
Fix localized name search for Program plugin
2026-01-02 19:20:56 +08:00
VictoriousRaptor
b062fc056f Catch exceptions and release com objects 2026-01-02 12:23:05 +08:00
VictoriousRaptor
3e32fca356 Fix localized name get failure 2026-01-02 01:47:44 +08:00
Jeremy
9c2f2398f1 add method comments for LastOpenedHistoryResult 2026-01-01 23:11:28 +11:00
Jeremy
6d6003fd80 switch to using TrimmedQuery 2026-01-01 22:45:52 +11:00
Jeremy
f9df44a2cd rename to IcoPathAbsolute 2026-01-01 22:32:39 +11:00
Jeremy
7c670de7c2 fix history result highlight and scroll when history mode activated 2026-01-01 18:23:12 +11:00
Jeremy
20854ba5d7 formatting 2026-01-01 18:20:29 +11:00
Jeremy
2e8bb357fc Merge branch 'dev' into last_history_show_result_icon 2026-01-01 17:01:17 +11:00
Jeremy
810725cc6b add explicit JsonInclude for PluginID internal set 2026-01-01 15:43:23 +11:00
Jeremy
8d7fa1d397 revert QueryHistoryItems property 2026-01-01 15:14:05 +11:00
Jeremy
6b6a9a9935 simplify GetHistoryItems 2026-01-01 14:30:28 +11:00
VictoriousRaptor
8e80c3bde6
Add option to show taskbar when Flow Launcher is opend (#4177)
* Add option to show taskbar when Flow Launcher is invoked

Co-authored-by: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>

* Fix: Use ABM_ACTIVATE instead of ABM_SETSTATE for temporary taskbar showing

Co-authored-by: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>

* Remove unnecessary unsafe keyword from ShowTaskbar method

Co-authored-by: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>

* Fix missing closing braces in Win32Helper.cs

Co-authored-by: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>

* Change wording from 'invoked' to 'opened' in localization strings

Co-authored-by: VictoriousRaptor <10308169+VictoriousRaptor@users.noreply.github.com>

* Show/hide tasking when showing/hiding Flow

* Remove unused APPBARDATA

* Guard HideTaskbar() with state so show/hide stay balanced

* Improve taskbar visibility management in MainViewModel

Moved taskbar show logic to after keyboard layout switch in Show().Ensures consistent taskbar state when invoking or hiding the app.

* Clean code

* Clean code

* Remove blank line

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Jack Ye <jack1160210343@gmail.com>
Co-authored-by: Jack251970 <1160210343@qq.com>
2025-12-31 18:05:53 +08:00
Jeremy
de0d022268 rename last opened class; use inheritance; initialise at startup 2025-12-31 12:14:07 +11:00
Jack Ye
8092a440f4
Merge pull request #4182 from Flow-Launcher/custom_query_hotkey
Fix an issue that custom query hotkey cannot work with context menu page
2025-12-27 23:34:04 +08:00
Jeremy
d78d313372 update to use IcoAbsoluteLocalPath 2025-12-26 21:37:30 +11:00
Jack Ye
1a7957bee6
Merge pull request #4183 from Flow-Launcher/dependabot/github_actions/toshimaru/auto-author-assign-3.0.1
Bump toshimaru/auto-author-assign from 3.0.0 to 3.0.1
2025-12-26 13:33:53 +08:00
dependabot[bot]
23caeb477c
Bump toshimaru/auto-author-assign from 3.0.0 to 3.0.1
Bumps [toshimaru/auto-author-assign](https://github.com/toshimaru/auto-author-assign) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/toshimaru/auto-author-assign/releases)
- [Changelog](https://github.com/toshimaru/auto-author-assign/blob/main/CHANGELOG.md)
- [Commits](https://github.com/toshimaru/auto-author-assign/compare/v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: toshimaru/auto-author-assign
  dependency-version: 3.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-25 22:03:58 +00:00
Jack251970
2856803371 Ensure hotkey actions return to query results page first
Added a call to App.API.BackToQueryResults() after showing the main window when a hotkey is triggered. This prevents issues that can occur if the current page is a context menu before changing the query.
2025-12-25 21:14:11 +08:00
Jeremy Wu
ffb37d3e12
New Crowdin updates (#4127) 2025-12-25 17:06:56 +08:00
Jack Ye
bb1dfcde9b
Fix hide item option not working in Program plugin (#4141) 2025-12-25 07:01:47 +02:00
Jack Ye
1594683191
Merge pull request #4178 from Flow-Launcher/dependabot/github_actions/toshimaru/auto-author-assign-3.0.0
Bump toshimaru/auto-author-assign from 2.1.2 to 3.0.0
2025-12-24 12:02:34 +08:00
dependabot[bot]
0d4775bb7d
Bump toshimaru/auto-author-assign from 2.1.2 to 3.0.0
Bumps [toshimaru/auto-author-assign](https://github.com/toshimaru/auto-author-assign) from 2.1.2 to 3.0.0.
- [Release notes](https://github.com/toshimaru/auto-author-assign/releases)
- [Changelog](https://github.com/toshimaru/auto-author-assign/blob/main/CHANGELOG.md)
- [Commits](https://github.com/toshimaru/auto-author-assign/compare/v2.1.2...v3.0.0)

---
updated-dependencies:
- dependency-name: toshimaru/auto-author-assign
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 22:04:55 +00:00
Jeremy Wu
a08a45640d
Merge pull request #4170 from Flow-Launcher/VictoriousRaptor-patch-1
Replace old clipboard plugin with a new one in README
2025-12-22 09:44:54 +11:00
VictoriousRaptor
d1518a490f
Replace old clipboard plugin with a new one in README 2025-12-22 00:02:47 +08:00
VictoriousRaptor
f95d41e093
Merge pull request #4167 from Heck-R/dev
Fix: Result.Preview.Description Does not Display in Preview Panel
2025-12-19 20:33:22 +08:00
Jack251970
a07af87cb1 Update name 2025-12-19 15:56:58 +08:00
Jack Ye
e634e2cbff
Merge branch 'dev' into dev 2025-12-19 15:51:06 +08:00
Jack Ye
c8cebc2b18
Merge pull request #4166 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Microsoft.Data.Sqlite-10.0.1
Bump Microsoft.Data.Sqlite from 10.0.0 to 10.0.1
2025-12-19 14:07:54 +08:00
Heck-R
e01869afda Fix: Result.Preview.Description is not ignored anymore 2025-12-17 23:37:43 +01:00
dependabot[bot]
b4ec480405
Bump Microsoft.Data.Sqlite from 10.0.0 to 10.0.1
---
updated-dependencies:
- dependency-name: Microsoft.Data.Sqlite
  dependency-version: 10.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-17 22:07:00 +00:00
Jack Ye
12ab11a4e4
Merge pull request #4165 from Flow-Launcher/dependabot/github_actions/toshimaru/auto-author-assign-2.1.2
Bump toshimaru/auto-author-assign from 2.1.1 to 2.1.2
2025-12-17 10:24:42 +08:00
dependabot[bot]
bb3c8fbe0c
Bump toshimaru/auto-author-assign from 2.1.1 to 2.1.2
Bumps [toshimaru/auto-author-assign](https://github.com/toshimaru/auto-author-assign) from 2.1.1 to 2.1.2.
- [Release notes](https://github.com/toshimaru/auto-author-assign/releases)
- [Changelog](https://github.com/toshimaru/auto-author-assign/blob/main/CHANGELOG.md)
- [Commits](https://github.com/toshimaru/auto-author-assign/compare/v2.1.1...v2.1.2)

---
updated-dependencies:
- dependency-name: toshimaru/auto-author-assign
  dependency-version: 2.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 22:04:28 +00:00
Jack Ye
20930617bb
Merge pull request #4118 from Flow-Launcher/UI.WPF.Modern
Upgrade iNKORE.UI.WPF.Modern and refactor scroll logic
2025-12-16 22:54:53 +08:00
Jeremy Wu
0f34829dab
Merge pull request #4160 from Flow-Launcher/add_sponsor
Add sponsor to README
2025-12-16 02:25:06 +11:00
Jack Ye
4221797569
Merge pull request #4125 from Flow-Launcher/dialog_jump_ignore_null_handler
Add null checks and improve dialog window handling
2025-12-15 19:51:32 +08:00
Jeremy Wu
437b7da0f7
formatting 2025-12-15 21:24:14 +11:00
Jeremy Wu
31f5698437
update readme 2025-12-15 21:20:35 +11:00
Jeremy Wu
666d525243
Add sponsor to README
Added sponsor to the README.
2025-12-15 20:59:18 +11:00
Diego Henrique
9066913710
[FEATURE] Folder and File Action Keywords (#4093) 2025-12-14 12:18:21 +02:00
Jack Ye
ebc1a85190
Merge pull request #4155 from Flow-Launcher/dependabot/github_actions/actions/upload-artifact-6
Bump actions/upload-artifact from 5 to 6
2025-12-13 15:39:03 +08:00
dependabot[bot]
4c44877af4
Bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-12 22:04:55 +00:00
VictoriousRaptor
c03c778efb
Merge pull request #4151 from Flow-Launcher/fix3965
Fix incorrect text highlighting when using pinyin queries
2025-12-09 08:35:53 +08:00
VictoriousRaptor
b6339d38fa
Fix compile error introduced by coderabbit 2025-12-08 13:06:14 +08:00
VictoriousRaptor
42e04d459b
Cover all positions in unit test
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-12-08 00:41:08 +08:00
VictoriousRaptor
769179d04b
Fix test case
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-07 19:28:17 +08:00
VictoriousRaptor
a7c67bbb35
Fix incorrect index marker
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-07 19:18:49 +08:00
VictoriousRaptor
b869eb8d1d Avoid adding unnecessary space and improve unit test 2025-12-07 18:50:54 +08:00
VictoriousRaptor
7d9de4b0d2 Fix index lookup for boundary 2025-12-07 18:16:09 +08:00
Jack Ye
a49eb464c8
Merge pull request #4129 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.Calculator/Mages-3.0.1
Bump Mages from 3.0.0 to 3.0.1
2025-11-26 18:33:53 +08:00
Jack Ye
48f67b1886
Improve and fix query result update logic issue & provide access to exact query typed by user (#3502) 2025-11-26 21:15:12 +11:00
dependabot[bot]
ce4a37b292
Bump Mages from 3.0.0 to 3.0.1
---
updated-dependencies:
- dependency-name: Mages
  dependency-version: 3.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-25 22:35:13 +00:00
Jack251970
5623bf253b Add null checks and improve dialog window handling
Added early null checks for `hwnd` to prevent invalid processing.
Enhanced thread safety by locking `_dialogWindow` updates.
Documented dialog window state handling with comments for clarity.
Handled scenarios for dialog window movement, resizing, hiding,
destruction, and termination. Improved robustness and maintainability.
2025-11-23 18:05:12 +08:00
Jack Ye
38c0fae064
Merge pull request #4124 from Flow-Launcher/dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 5 to 6
2025-11-21 17:43:17 +08:00
dependabot[bot]
28065f7231
Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-20 22:05:52 +00:00
Jeremy Wu
bb841fcc3b
Merge pull request #4122 from Flow-Launcher/merge_2_0_3
Merge v2.0.3 back to dev
2025-11-20 23:33:34 +11:00
Jeremy
1c29fb2db3 merge back v2.0.3 from master to dev 2025-11-20 23:12:58 +11:00
Jeremy Wu
229987ee90
Release 2.0.3 (#4121)
Some checks failed
Publish Default Plugins / publish (push) Has been cancelled
Build / build (push) Has been cancelled
2025-11-20 19:01:51 +08:00
Jeremy Wu
533a58d083
New Crowdin updates (#4051) 2025-11-20 18:26:31 +08:00
Jeremy
7958b17968 wip show result icon 2025-11-20 21:02:19 +11:00
Jack Ye
a773b51ada
Handle recoverable DWM composition exceptions gracefully (#4113) 2025-11-19 22:16:09 +02:00
Jack251970
05f15d9831 Upgrade iNKORE.UI.WPF.Modern and refactor scroll logic
Upgraded `iNKORE.UI.WPF.Modern` to version `0.10.2.1` and
replaced the custom `CustomScrollViewerEx` implementation with
the built-in `ScrollViewerEx` for simplified scrolling behavior.
Updated `Flow.Launcher.Plugin` to version `5.1.0` for compatibility.
Removed unused namespaces and adjusted styles in `Base.xaml`
to align with the new `ScrollViewerEx` usage.
2025-11-18 22:19:02 +08:00
Jeremy Wu
c6c413202c
Merge pull request #4108 from Flow-Launcher/add_sponsor
Add new sponsor to README
2025-11-14 18:11:59 +09:00
Jeremy Wu
f1b80ab90f
Add new sponsor to README 2025-11-14 17:54:45 +09:00
Jack Ye
558240279f
Merge pull request #4103 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Microsoft.Data.Sqlite-10.0.0
Bump Microsoft.Data.Sqlite from 9.0.10 to 10.0.0
2025-11-13 10:26:05 +08:00
dependabot[bot]
3492aac2c0
Bump Microsoft.Data.Sqlite from 9.0.10 to 10.0.0
---
updated-dependencies:
- dependency-name: Microsoft.Data.Sqlite
  dependency-version: 10.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-11 22:07:56 +00:00
Jack Ye
fc93d0904e
Merge pull request #4072 from Flow-Launcher/program_plugin_index_issue
Fix Possible OperationCancelException for WaitAsync Operations
2025-11-08 11:28:58 +08:00
Jack251970
2adbc334a2 Improve semaphore lock handling and code robustness
Added `lockAcquired` flags in `PluginsManifest.cs` and `Main.cs`
to ensure semaphore locks are only released if successfully
acquired, preventing potential runtime errors. Updated `finally`
blocks to conditionally release locks based on these flags.

Removed redundant cancellation check in `EverythingAPI.cs` to
simplify code, assuming cancellation is handled elsewhere. These
changes enhance reliability and maintainability of the codebase.
2025-11-07 15:30:07 +08:00
Jack251970
05c8dd2fe1 Remove unnecessary debug information 2025-11-06 20:48:41 +08:00
Jack251970
88fd1e56d0 Handle OperationCanceledException gracefully
Added a `catch` block for `OperationCanceledException` in
`PluginsManifest.cs` to ignore canceled operations. Updated
`EverythingAPI.cs` to use cancellation tokens with `_semaphore.WaitAsync`
and handle cancellations by exiting the method cleanly with `yield break`.
2025-11-06 20:47:54 +08:00
Jack251970
49f89e33b5 Make locking operations cancelable with tokens
Updated `_semaphore.WaitAsync` in `EverythingAPI.cs` to accept a `CancellationToken` and handle `OperationCanceledException` gracefully, returning `false` instead of propagating the exception.

Refactored locking mechanisms in `Main.cs` to use `CancellationToken` for `_win32sLock` and `_uwpsLock`. Added `try-catch` blocks to handle `OperationCanceledException` and ensure proper lock release. Methods now return `emptyResults` when operations are canceled.
2025-11-06 20:46:29 +08:00
Jack251970
d0a47c84b9 Clarify CancellationToken handling in comments
Updated comments to explain the rationale for not directly passing
CancellationToken to methods and instead checking
IsCancellationRequested within locks. This prevents unexpected
OperationCanceledException. Changes made in EverythingAPI.cs
(IsEverythingRunningAsync) and Main.cs (Win32 and UWP program
preparation). No functional changes to the code.
2025-11-06 15:39:19 +08:00
Jack251970
bfaff5cca5 Improve semaphore usage and logging clarity
Added comments in `EverythingAPI.cs` and `Main.cs` to explain
why `CancellationToken` is not directly passed to semaphore
locks, preventing unexpected `OperationCanceledException`.

Updated debug log messages in `Main.cs` for better clarity,
including changing "Start handling programs" to "Start querying
programs". Removed redundant log messages to improve logging
consistency.
2025-11-06 15:32:08 +08:00
Jack251970
30d7f67d42 Refactor token cancellation check for readability
Simplified the `if` statement that checks for token cancellation
by condensing it into a single line. This improves code readability
and eliminates unnecessary line breaks.
2025-11-06 15:30:32 +08:00
Jack251970
7e332fa615 Refactor caching and indexing logic
Added `ResetCache` calls after clearing `_win32s` and `_uwps` lists to ensure proper cache reset during indexing. Updated logic to return `resultList` after setting cache size and expiration for improved clarity. Removed `await Task.WhenAll` to adjust asynchronous flow in the indexing process.
2025-11-06 15:29:38 +08:00
Jack251970
3d8fd1d352 Improve cancellation, locking, and logging mechanisms
Enhanced cancellation handling by adding `token.IsCancellationRequested` checks to improve responsiveness. Refactored locking mechanisms for `_win32sLock` and `_uwpsLock` using `try-finally` blocks to ensure proper acquisition and release, improving thread safety and preventing deadlocks.

Reorganized Win32 and UWP program querying logic for better modularity and readability. Replaced shared collection access with local variables to improve clarity and maintain thread safety. Simplified empty result handling by directly returning `emptyResults` when canceled.

Removed redundant debug log statements to reduce verbosity and updated remaining logs for clarity. Suppressed unused result warnings by replacing direct calls to `EverythingApiDllImport.Everything_GetMajorVersion()` with null-coalescing assignments.
2025-11-06 15:28:13 +08:00
Jack251970
db0c86d50c Remove CancellationToken from semaphore WaitAsync calls
Removed the `CancellationToken` parameter from `WaitAsync` calls
on semaphores in `EverythingAPI.cs` and `Main.cs`. This change
eliminates cancellation support for semaphore waits, likely due
to a design decision prioritizing simplicity or avoiding issues
with cancellation handling.

In `EverythingAPI.cs`, `WaitAsync(token)` was replaced with
`WaitAsync()` in two methods. Similarly, in `Main.cs`, the
`WaitAsync` calls for `_win32sLock` and `_uwpsLock` were updated
to remove the `token` parameter.

Note: This change may impact the ability to gracefully handle
cancellation during semaphore waits.
2025-11-04 14:25:55 +08:00
Jack251970
6e17d5d756 Improve logging for Win32 program lock acquisition
Added a debug log statement to indicate when the lock for
querying Win32 programs is being acquired. This enhances
granularity in logging, making it easier to distinguish
between the acquisition of locks for Win32 and UWP programs.
Improves traceability and debugging of the program's
execution flow.
2025-11-04 12:27:59 +08:00
Jack Ye
e2ac32139c
Merge pull request #4084 from 90gq29/patch-1
Update en.xaml - Typo fix
2025-11-02 11:39:45 +08:00
90gq29
348a83a1d9
Update en.xaml
In standard title case (used for buttons and such), articles or short function words like "a" or "for" shouldn't be capitalized. 

Doesn't seem to be capitalized for stylistic reasons either as "Check for Updates" is capitalized in standard title case.
2025-11-01 15:29:06 +01:00
Jack251970
4cf942ac3a Add detailed debug logging for query execution process
Enhanced logging to provide better traceability and insights:
- Added debug logs for query reception, cache misses, and lock acquisition.
- Logged query cancellation and completion with result counts.
- Added logs for caching results, including item counts and query details.
- Improved logging for filtering and program selection processes.
- Ensured no functional changes to existing query and filtering logic.
2025-10-26 21:01:07 +08:00
Jack251970
f632a4b773 Add caching to QueryAsync and integrate cache reset logic
Introduced a MemoryCache to improve QueryAsync performance by
caching query results, reducing redundant computations. Added
a ResetCache method to reinitialize the cache when settings
are updated. Integrated cache reset calls into settings
property setters to ensure consistency.

Refactored query logic to leverage MemoryCache.GetOrCreateAsync
for streamlined caching. Removed redundant code and debug
logging for improved readability and maintainability. Ensured
thread safety with proper locking mechanisms. Simplified and
consolidated caching logic for better maintainability.
2025-10-26 20:59:29 +08:00
Jack251970
e2fa122362 Improve program indexing with logging and thread safety
Added detailed debug logging to `IndexWin32ProgramsAsync` and
`IndexUwpProgramsAsync` to track the indexing process, including
preparation, start, retrieval, caching, and completion.

Replaced direct updates to `_settings.LastIndexTime` with a
thread-safe lock to prevent race conditions.

Enhanced `IndexProgramsAsync` with a debug log to indicate the
start of indexing for better traceability.

Updated program retrieval logic to process Win32 and UWP programs
in parallel with cancellation support and applied the
`HideUninstallersFilter` for cleaner results.
2025-10-26 20:56:08 +08:00
Jack251970
6a65f8090f Enhance thread safety and refactor reindexing logic
Introduced `_lastIndexTimeLock` to ensure thread-safe access
and updates to `_settings.LastIndexTime`, preventing race
conditions. Refactored reindexing logic to use a `lock` block
for evaluating and updating the reindexing condition.

Added `emptyResults` as a static readonly placeholder list.
Improved code clarity and maintainability without altering
existing functionality.
2025-10-26 20:51:08 +08:00
Jack251970
637d926f7a Remove caching logic and initialize emptyResults list
The caching-related code, including `cacheOptions` and `cache`, has been removed from `Main.cs`, indicating that the caching mechanism is no longer in use or has been refactored elsewhere. Additionally, the `emptyResults` list is now explicitly initialized as an empty list (`[]`). No changes were made to the `Context` property or the `commonUninstallerNames` array.
2025-10-26 20:41:06 +08:00
Jack251970
7f5efc5ca1 Refactor caching and improve QueryAsync method
Removed the `ResetCache` method and its associated calls to streamline caching management and improve performance. Refactored the `QueryAsync` method for better thread safety, synchronization, and exception handling. Simplified UWP and Win32 program filtering logic and removed redundant code. Eliminated manual cache disposal and reset logic, favoring a more efficient and automated caching mechanism. These changes enhance maintainability, responsiveness, and overall plugin performance.
2025-10-26 20:40:37 +08:00
Jack Ye
1885a48241
Merge pull request #4070 from Flow-Launcher/dependabot/github_actions/actions/upload-artifact-5
Bump actions/upload-artifact from 4 to 5
2025-10-25 14:33:11 +08:00
dependabot[bot]
f0584ac853
Bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-24 22:05:42 +00:00
Jeremy
0f269455b1 remove duplicate history item sort call 2025-10-21 22:18:29 +11:00
Jeremy
25aa5bf2af show history result sorted descending by execution time 2025-10-21 21:54:38 +11:00
Jeremy
92551c58a5 show distinct records when history style is last opened 2025-10-21 21:53:32 +11:00
Diego Henrique
1bfd147421
BUG: Explorer plugin navigate path should only show results in current path (#4053) 2025-10-21 12:12:45 +11:00
Jeremy
26ad473592 change getting last history item to get the same result if exists 2025-10-19 21:46:02 +11:00
Jeremy
10d3ba268d update history result icon display logic & remove badge icon logic 2025-10-19 20:22:03 +11:00
Jeremy
7883e7a0a9 add result IcoPath & Glyph + handling show badge switching 2025-10-18 10:35:23 +11:00
Jeremy Wu
da4b961686
Merge pull request #3854 from Flow-Launcher/plugin_initialization
Asynchronous Loading & Initialization Plugin Model to Improve Window Startup Speed
2025-10-16 22:08:42 +11:00
Jack Ye
fb84cb03f3
Improve code comments
Co-authored-by: Jeremy Wu <jeremy24wu@gmail.com>
2025-10-16 18:48:17 +08:00
Jack Ye
c9e3c628e8
Improve code comments
Co-authored-by: Jeremy Wu <jeremy24wu@gmail.com>
2025-10-16 18:48:07 +08:00
Jack251970
fbeaafa8d3 Remove Score = -100 from result objects in PluginManager
Removed the `Score = -100` property from multiple result objects
in `PluginManager.cs` to simplify the code and improve clarity.
Adjusted the formatting and structure to ensure proper syntax
and maintain code consistency. This includes changes to result
objects that handle re-querying and exception handling.
2025-10-16 18:47:15 +08:00
Jack251970
68454a8a6e Change log level for plugin constructor timing message
Updated the log level for the plugin constructor cost message
from `LogInfo` to `LogDebug` to reduce verbosity in production
logs and make this information available primarily during
debugging sessions.
2025-10-16 18:43:19 +08:00
Jeremy Wu
bd880d304e
update wording 2025-10-16 20:46:35 +11:00
Jeremy Wu
386737acac
update method parameter style 2025-10-16 20:20:51 +11:00
Jack Ye
3690042d2c
Fix DialogJump UriFormatException when navigating to root directories (#4052) 2025-10-15 20:12:51 +11:00
Jack251970
c00d804073 Resolve conflicts 2025-10-15 13:07:43 +08:00
Jack Ye
e2abc1b265
Merge branch 'dev' into plugin_initialization 2025-10-15 13:06:14 +08:00
Jack Ye
3d9ef2c63f
Merge pull request #4042 from 01Dri/feature/history_mode
Add "Last opened" history mode & Fix non-result history item save issue
2025-10-15 12:47:45 +08:00
Jack251970
b13c29ab91 Record user selection after successful execution 2025-10-15 12:40:00 +08:00
Jack251970
df4f08b071 Add exception logging to ResultHelper catch block
Updated the `catch` block in the `ResultHelper` class to explicitly catch `System.Exception` and log the error using `App.API.LogException`. The log includes the class name, a failure message for querying results, and the exception details. This improves error visibility and debugging.
2025-10-15 12:35:53 +08:00
Jack Ye
83cab764a1
Fix typos
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-15 12:30:59 +08:00
Jack251970
b784a14aee Update tooltip text for historyStyleTooltip
The tooltip text for the key `historyStyleTooltip` was updated to improve capitalization and consistency. "History" and "Home Page" were capitalized to align with the formatting of other keys in the file.
2025-10-15 12:28:54 +08:00
Jack251970
629c2eb4c6 Record user-selected results for ranking
Moved `_userSelectedRecord.Add(result)` outside the `if (queryResultsSelected)` block to ensure all user-selected results are recorded, regardless of their source (query results, context menu, or history). Added a comment to clarify that only query results are added to history.
2025-10-15 12:26:21 +08:00
Jack251970
c73689fbc3 Fix spelling 2025-10-15 12:24:36 +08:00
Jack251970
1298b76b77 Track user-selected results for ranking purposes
Added logic to record user-selected results in `_userSelectedRecord`
before executing both synchronous and asynchronous actions. This
enables tracking of user interactions for result ranking or analytics.
Comments were added to clarify the purpose of the new logic.
2025-10-15 12:23:52 +08:00
Jack251970
4b6ee4ea98 Improve code quality 2025-10-15 12:23:00 +08:00
01Dri
10d353defe Returning actions 2025-10-15 00:34:38 -03:00
01Dri
9ca7c8431b Revert modifications and returning actions 2025-10-15 00:34:16 -03:00
01Dri
a8d3cdf62c SubTitle equals 2025-10-14 23:26:32 -03:00
01Dri
764c674350 code quality 2025-10-14 23:15:14 -03:00
01Dri
f31abe83b0 PopulateHistoryFromLegacyHistory with HistoryStyle 2025-10-14 23:12:25 -03:00
Jack Ye
4fe3b55720
Merge pull request #4049 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Microsoft.Data.Sqlite-9.0.10
Bump Microsoft.Data.Sqlite from 9.0.9 to 9.0.10
2025-10-15 10:08:39 +08:00
01Dri
8e96d1a461 HistoryStyle enum values 2025-10-14 22:51:28 -03:00
01Dri
9f652c33b4 History items filtered based on HistoryStyle. 2025-10-14 22:51:09 -03:00
01Dri
d5a2695766 Update the history item if it equals the last added item or already exists in the list. 2025-10-14 22:50:29 -03:00
01Dri
ced824d791 is equals 2025-10-14 22:49:03 -03:00
01Dri
0131b92c7a The HistoryStyle property is used to distinguish history items 2025-10-14 22:47:58 -03:00
dependabot[bot]
a3d1c43580
Bump Microsoft.Data.Sqlite from 9.0.9 to 9.0.10
---
updated-dependencies:
- dependency-name: Microsoft.Data.Sqlite
  dependency-version: 9.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-14 22:07:02 +00:00
Jack251970
66fe0b8245 Adjust design-time height in SettingsPaneGeneral.xaml 2025-10-14 23:23:52 +08:00
Jack Ye
adfd5425f0
Merge branch 'dev' into plugin_initialization 2025-10-14 21:53:43 +08:00
Jeremy Wu
8fde82a0eb
Merge pull request #4047 from Flow-Launcher/merge_back_2_0_2
Merge v2.0.2 back to dev
2025-10-15 00:49:27 +11:00
Jack251970
787ccad384 Refactor history handling with async ResultHelper
Replaced `HistoryHelper` with a new `ResultHelper` class to handle plugin result population asynchronously, improving performance and maintainability. Removed `ExecuteAction` and `QueryAction` properties from `LastOpenedHistoryItem` and updated `QueryHistory` and `MainViewModel` to use `LastOpenedHistoryItems` directly.

Refactored history result generation to support `AsyncAction` in `Result` objects, replacing synchronous plugin queries. Simplified legacy history migration and enhanced support for `HistoryStyle`. Improved error handling, code readability, and UI-related logic for history navigation.
2025-10-14 21:29:22 +08:00
Jack251970
693bae7631 Optimize query result selection handling
Refactored `QueryResultsSelected()` usage by introducing a local
variable `queryResultsSelected` to avoid redundant method calls,
improving efficiency and readability. Added a comment to clarify
the purpose of the variable. Updated conditional logic to use
the new variable instead of directly invoking the method.
2025-10-14 20:47:57 +08:00
Jack251970
f6d5a27e0b Refactor and enhance history management system
Refactored the `History` class to separate query and last opened history into distinct models, introducing `LastOpenedHistoryItem` and deprecating `HistoryItem`. Added a `HistoryStyle` enum and property to allow users to toggle between "Query History" and "Last Opened History." Simplified history display logic and updated the UI with a dropdown for history style selection.

Improved history item display with dynamic action population and relative timestamps. Enhanced performance by optimizing history storage with a maximum limit and streamlined logic for adding/retrieving items. Ensured backward compatibility by migrating legacy history data to the new format.

Updated localization strings, removed deprecated properties, and cleaned up redundant code. Fixed bugs related to inconsistent history actions and edge cases with legacy data.
2025-10-14 20:28:39 +08:00
Jack Ye
592440f2b1
Merge pull request #3982 from Flow-Launcher/squirrel_upgrade
Upgrade Nuget Packages
2025-10-14 19:54:21 +08:00
Jack251970
b84ca9b283 Improve code quality 2025-10-14 19:39:33 +08:00
Jeremy
7445d54d22 merge back v2.0.2 from master to dev 2025-10-14 22:35:06 +11:00
Jeremy Wu
f37d4c47cc
Release 2.0.2 | Plugin 5.1.0 (#4046) 2025-10-14 13:49:29 +03:00
Jeremy Wu
02a29c6af4
New Crowdin updates (#4002) 2025-10-14 12:14:37 +03:00
Jack251970
7fa78f0304 Code cleanup 2025-10-13 15:30:38 +08:00
Jack251970
e3527f47ea Add RecordKey for precise history matching and refactor
Added a `RecordKey` property to `HistoryItem` for unique identification of history records, enabling more accurate matching during queries and executions. Updated `HistoryHelper` methods to utilize `RecordKey` for matching, with fallback to `Title` and `SubTitle`. Enhanced `GetExecuteAction` with error handling, nullable reference types, and improved matching logic. Included `RecordKey` in `History` object creation. Enabled nullable reference types in `HistoryHelper.cs` for better code safety. Refactored code for clarity and maintainability.
2025-10-13 15:27:06 +08:00
Jack Ye
e6c4cc25e5
Merge pull request #4037 from AWAS666/httpspref
Prefer https over http setting for url
2025-10-12 13:41:29 +08:00
Jack251970
1e3306ef28 Remove unused file 2025-10-12 13:33:20 +08:00
Jack251970
6f17aa21c2 Resolve conflicts 2025-10-12 13:31:24 +08:00
Jack Ye
b8acead444
Merge branch 'dev' into httpspref 2025-10-12 13:24:11 +08:00
Jack Ye
eb261f503b
Merge pull request #3944 from Flow-Launcher/url_open_enhancement
Enhancement: Support Custom Browser Path & Open in window / tab & In private for URL Plugin
2025-10-12 12:22:36 +08:00
Diego Henrique
cc397d45a9
Merge branch 'dev' into feature/history_mode 2025-10-11 15:43:05 -03:00
Jack Ye
01bbb54054
Merge pull request #4038 from AWAS666/dragdropwebsearch
Enable reordering of websearches with drap and drop
2025-10-11 20:49:17 +08:00
01Dri
2b7c2048ef up 2025-10-11 05:26:28 -03:00
Jack Ye
b297f3d3a3
Fix ArgumentOutOfRangeException in WebSearch Plugin (#4041) 2025-10-11 18:34:05 +11:00
01Dri
a3b7c6808b up 2025-10-11 04:29:33 -03:00
01Dri
a1f82e1652 refactor: using count for better performance 2025-10-11 03:47:04 -03:00
01Dri
690d33ece3 up 2025-10-11 03:43:06 -03:00
01Dri
d7579cce9e fix erros 2025-10-11 03:23:14 -03:00
01Dri
e468c48da4 feat: fix erros 2025-10-11 03:15:10 -03:00
01Dri
e6cae1a79b feat: helper 2025-10-11 03:08:23 -03:00
01Dri
95122e91f5 feat: code quality 2025-10-11 02:56:49 -03:00
01Dri
bf2acfec38 feat: code quality 2025-10-11 02:54:29 -03:00
01Dri
9e1b8c1a72 feat: Populate new history system with legacy query history 2025-10-11 02:43:27 -03:00
AWAS666
d7dd89d719 change from remove/insert to move 2025-10-10 11:40:06 +02:00
AWAS666
20a865f012 missing nullchecks and casts 2025-10-10 10:06:54 +02:00
AWAS666
87676d2994 fix: error when dragging downwards 2025-10-10 10:06:42 +02:00
AWAS666
001dad9a79 revert manual translation 2025-10-10 10:03:16 +02:00
Jack Ye
a412c7978a
Fix format
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-10 12:23:41 +08:00
01Dri
8e8e9d35ac code quality 2025-10-10 00:08:27 -03:00
01Dri
545c4208d9 code quality 2025-10-10 00:07:24 -03:00
01Dri
50f5e850dd feat: code quality 2025-10-09 23:47:59 -03:00
AWAS666
b9a43147dc enable reordering with drap and drop on websearches 2025-10-09 12:35:04 +02:00
AWAS666
ae3559ca74 fix: explicit scheme 2025-10-09 10:48:58 +02:00
AWAS666
1a373e1c40 prefer https over http setting 2025-10-09 10:36:42 +02:00
01Dri
d122276e71 feat: clean imports 2025-10-08 22:09:09 -03:00
01Dri
156cb3055c merge 2025-10-08 22:06:39 -03:00
01Dri
6aa35d592e merge dev 2025-10-08 21:38:10 -03:00
01Dri
b290055e83 feat: up 2025-10-08 21:25:13 -03:00
01Dri
c051c5cd50 feat: new history logic in MainViewModel 2025-10-08 21:15:10 -03:00
01Dri
06711d3b8b feat: Saving actions for history 2025-10-08 21:12:36 -03:00
01Dri
4f2db28196 faet: query action save 2025-10-08 21:12:14 -03:00
01Dri
3681340587 feat: toggle history 2025-10-08 21:11:27 -03:00
01Dri
9e182a2e47 feat: base history model in MainView and refactoring code to replace action between query result or last opened 2025-10-07 00:35:34 -03:00
01Dri
e5736567f6 feat: created a base History model 2025-10-07 00:33:16 -03:00
Jack Ye
a2d12eb225
Merge branch 'dev' into plugin_initialization 2025-10-05 19:25:01 +08:00
Jack Ye
5ae159de5b
Move to iNKORE.UI.WPF.Modern UI Framework (#3593) 2025-10-05 13:44:40 +03:00
Jack Ye
8c98aedac2
Merge pull request #4025 from Flow-Launcher/hide_window_before_sleep
Save settings before shutdown/restart to prevent data loss
2025-10-04 19:52:26 +08:00
01Dri
7ba4f8de4a feat: last opened history 2025-10-04 02:19:57 -03:00
Jack251970
6f6292494c Use ContainsKey(id) for O(1) lookup instead of O(n) iteration 2025-10-03 16:13:24 +08:00
Jack251970
793476432b Show setting panel for initializing or initialization failed plugins 2025-10-03 16:06:22 +08:00
Jack251970
9d7ac851bd Show setting panel for initializing or init failed plugins 2025-10-03 16:04:26 +08:00
Jack251970
54e693d0e2 Add functions in Check Initializing & Init Failed region 2025-10-03 16:03:54 +08:00
Jack251970
969b3ccbcd Revert CancelAsync to Cancel 2025-10-03 15:58:48 +08:00
Jack251970
8a2edf274a Add AutoCompleteText property for results 2025-10-03 15:36:32 +08:00
Jack251970
171eb2dd8b Data race on _allLoadedPlugins (List) across threads 2025-10-03 15:28:11 +08:00
Jack251970
76cc22d5af Use TryRemove and discard out var to clean up action keywords 2025-10-03 15:21:11 +08:00
Jack251970
2be10eb4ca Add translation for plugin failed to respond & Improve translations for plugin stil initializing 2025-10-02 22:26:28 +08:00
Jack251970
297cb5c3ef Return results to tell users that this plugin is still initializing 2025-10-02 22:23:07 +08:00
Jack251970
9a20b0e0de Expose initialized plugins via PublicAPI 2025-10-02 21:59:05 +08:00
Jack251970
66fb1d7c60 Register plugin action keywords when plugins are loaded 2025-10-02 21:52:31 +08:00
Jack251970
5ed94c8abc Resolve conflicts 2025-10-02 20:25:45 +08:00
Jack Ye
6213193cda
Merge branch 'dev' into plugin_initialization 2025-10-02 20:22:20 +08:00
Jack Ye
5b0a30774e
Fix code comment typo
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-02 19:45:08 +08:00
Jack251970
d08ee30a7a Refactor plugin actions to simplify logic
Removed logoff operation logic and associated return statement.
Eliminated return statement after recycle bin error handling.
Removed async plugin data reload and success message logic.
Simplified theme selector query handling by removing `return false`.
These changes streamline the code and improve maintainability.
2025-10-02 19:43:31 +08:00
Jack251970
167570559f Move settings save to post-confirmation for actions
Previously, `Context.API.SaveAppAllSettings()` was called unconditionally before user confirmation for shutdown, restart, and advanced restart actions. This change ensures settings are only saved if the user confirms the action by clicking "Yes" in the confirmation dialog.

For all three functionalities:
- Moved the settings save call inside the `if (result == MessageBoxResult.Yes)` block.
- Retained the existing logic for executing the respective system commands, with checks for `EnableShutdownPrivilege()` to determine whether to use `PInvoke.ExitWindowsEx` or the `shutdown` command.

This change prevents unnecessary settings saves when the user cancels the action.
2025-10-02 19:42:24 +08:00
Jack251970
e376da4482 Save settings before shutdown/restart to prevent data loss
Added a call to `Context.API.SaveAppAllSettings()` before executing system shutdown, restart, or advanced restart operations. This ensures that any unsaved settings are persisted, reducing the risk of data loss during these actions.
2025-10-02 19:29:46 +08:00
Jack Ye
1b7a475631
Merge pull request #4024 from Flow-Launcher/power_enhancement
Use sleep mode listener to fix modern standby sleep mode issue
2025-10-02 19:19:01 +08:00
01Dri
5fcd01224c feat: text 2025-10-02 00:19:40 -03:00
01Dri
bc1f9d3734 refactor: renaming executed history to last opened history 2025-10-02 00:09:28 -03:00
01Dri
c0369e6e76 feat: radio button with history query option or history executed option 2025-10-02 00:03:48 -03:00
01Dri
3cc4f13f4d feat: New option to show executed results in settings 2025-10-01 23:05:41 -03:00
Jack251970
c27817eaf0 Fix possible null exception 2025-09-30 21:20:00 +08:00
Jack251970
f239866c68 Use sleep mode listener to fix modern standby sleep mode issue 2025-09-30 21:11:03 +08:00
Jack Ye
7f0851bcb1
Merge pull request #4023 from Flow-Launcher/shell_setting_panel_enhancement
Refactor ShellSettings with Binding & Fix IsEnabled logic
2025-09-30 20:50:16 +08:00
Jeremy Wu
652ec40d82
add removal todo comment 2025-09-30 22:28:41 +10:00
Jack251970
d56d85b702 Add lock for sound & Rename variable 2025-09-30 19:41:27 +08:00
Jack251970
9be546d6c7 Fix ShowOnlyMostUsedCMDsNumber default value 2025-09-30 09:50:29 +08:00
Jack Ye
aab213a6b9
Add default value for ShowOnlyMostUsedCMDsNumber
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-09-30 09:38:18 +08:00
Jack Ye
89505fce30
Remove unnecessary DataContext
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-30 09:37:48 +08:00
Jack251970
d106c5144b Fix IsEnabled logic 2025-09-29 23:12:02 +08:00
Jack251970
175571a130 Refactor ShellSettings with Binding logic 2025-09-29 23:00:32 +08:00
Jack251970
d363cf8137 Add property change for settings class & Add localize support for enum 2025-09-29 10:42:19 +08:00
Jack251970
ec41ec2377 Improve code quality 2025-09-28 12:04:32 +08:00
Jack251970
64e9150f11 Fix build issue 2025-09-28 12:03:49 +08:00
Jack Ye
08bf147559
Merge branch 'dev' into url_open_enhancement 2025-09-28 12:02:24 +08:00
Jack251970
ca94ef58e8 Fix build issue 2025-09-28 11:46:55 +08:00
Jack Ye
b34851bd12
Merge branch 'dev' into squirrel_upgrade 2025-09-28 11:37:18 +08:00
Jack Ye
5b6ea73513
Code cleanup & Use Flow.Launcher.Localization to improve code quality (#4009)
* Use Flow.Launcher.Localization to improve code quality

* Code cleanup

* Improve code quality

* Improve code quality

* Use internal static Context & Improve code quality

* Use Flow.Launcher.Localization to improve code quality

* Code cleanup

* Use Flow.Launcher.Localization to improve code quality

* Improve code quality

* Improve code quality

* Use Flow.Launcher.Localization to improve code quality

* Fix logic issue

* Fix the variable name typo

* Fix redundant boolean cast and ensure consistent default value handling

* Use Flow.Launcher.Localization to improve code quality

* Revert namespace styles

* Fix indent format

* Revert namespace style

* Fix indent format

* Fix namespace style

* Fix indent format

* Fix indent format
2025-09-27 19:18:33 +03:00
Jack Ye
2d00ab1d3e
Merge pull request #4011 from Flow-Launcher/flow_launcher_localization1
Use Flow.Launcher.Localization to improve code quality
2025-09-28 00:04:14 +08:00
Jack Ye
e068104253
Merge pull request #3995 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Svg.Skia-3.2.1
Bump Svg.Skia from 3.0.6 to 3.2.1
2025-09-25 13:06:11 +08:00
Jack Ye
5e2536c129
Merge branch 'dev' into dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Svg.Skia-3.2.1 2025-09-25 12:41:14 +08:00
Jack Ye
f72e4692e4
Merge pull request #4012 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/SkiaSharp-3.119.1
Bump SkiaSharp from 3.119.0 to 3.119.1
2025-09-25 12:40:28 +08:00
dependabot[bot]
a76e2fea7f
Bump SkiaSharp from 3.119.0 to 3.119.1
---
updated-dependencies:
- dependency-name: SkiaSharp
  dependency-version: 3.119.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-23 22:06:58 +00:00
Jack Ye
09425e1c43
Merge pull request #4006 from sstream17/dev
Support .url Internet Shortcut File Icons
2025-09-23 21:48:22 +08:00
Jack251970
ba7de5d33d Add documents 2025-09-23 21:39:42 +08:00
Jack251970
2770dfde56 Merge branch 'dev' of https://github.com/sstream17/Flow.Launcher into dev 2025-09-23 20:50:45 +08:00
Jack251970
9b2d36b78a Revert "Catch exception"
This reverts commit 49d5cd36df.
2025-09-23 20:48:41 +08:00
Spencer Stream
3bd6906c80 Validate iconPath exists 2025-09-23 07:19:39 -05:00
Jack251970
ac62ebadf0 Add space for code quality 2025-09-23 18:07:15 +08:00
Jack251970
54622d6759 Fix Flow.Launcher.Localization contentHash issue 2025-09-23 17:53:04 +08:00
Jack251970
0a7ed3b52f Add AbstractPluginEnvironment.API back 2025-09-23 17:52:49 +08:00
Jack251970
0e366a6269 Use PublicApi.Instance instead of private one 2025-09-23 17:40:54 +08:00
Jack251970
7350c1d4d5 Use Flow.Launcher.Localization to improve code quality 2025-09-23 17:14:30 +08:00
Jack251970
d87650de08 Use Flow.Launcher.Localization to improve code quality 2025-09-23 16:21:09 +08:00
Jack251970
4865224458 Fix format 2025-09-23 14:09:21 +08:00
Jack251970
49d5cd36df Catch exception 2025-09-23 12:24:44 +08:00
Jack Ye
130033cf4b
Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-23 12:22:46 +08:00
Spencer Stream
c293a273ca Fix nitpick on extension string comparison 2025-09-22 21:53:54 -05:00
Spencer Stream
90c73e5e3d Support .url file icons 2025-09-22 19:42:49 -05:00
Spencer Stream
fbc88bb4cd Add ini-parser package to Infrastructure project 2025-09-22 19:42:49 -05:00
Jack Ye
1e8819af0c
Merge pull request #4004 from dcog989/404-local-file-crash
Crash when opening non-existent local file
2025-09-22 21:09:29 +08:00
Jack251970
763fee0c14 Add new helper method & Improve code quality 2025-09-22 20:58:22 +08:00
Jack251970
71b8144c3c Add translations & Improve code quality 2025-09-22 20:55:36 +08:00
dcog989
bfd10f6903 Crash when opening non-existent file 2025-09-22 13:37:22 +01:00
Jack Ye
10c79da35c
Merge branch 'dev' into plugin_initialization 2025-09-21 21:37:18 +08:00
Jack Ye
d7d88a9ebb
Merge branch 'dev' into url_open_enhancement 2025-09-21 21:37:14 +08:00
Jack Ye
a32b9ca8c0
Merge branch 'dev' into squirrel_upgrade 2025-09-21 21:37:09 +08:00
Jeremy Wu
7de420e32b
Merge pull request #4001 from Flow-Launcher/merge_back_v2_0_1
Merge v2.0.1 back into dev
2025-09-21 23:32:09 +10:00
Jeremy
dc1f0f1095 merge back v2.0.1 from master into dev 2025-09-21 23:15:34 +10:00
Jack Ye
18a3ae7bff
Merge branch 'dev' into squirrel_upgrade 2025-09-21 20:28:01 +08:00
Jack Ye
35867a0a00
Merge pull request #3999 from Flow-Launcher/portable_mode_fix
Fix PortableMode Check Issue
2025-09-21 20:23:39 +08:00
Jeremy Wu
a05e09908c
Release 2.0.1 (#3998) 2025-09-21 20:23:00 +08:00
Jack251970
6fca1c919e Fix issue in PortableDataLocationInUse 2025-09-21 20:07:18 +08:00
Jeremy Wu
d7e1ad73cc
New Crowdin updates (#3948) 2025-09-21 14:36:35 +03:00
Jack251970
7e56c8a011 Revert changes 2025-09-21 16:48:00 +08:00
Jack251970
d6bc053950 Revert changes 2025-09-21 16:46:43 +08:00
Jack251970
813be4af2b Merge branch 'squirrel_upgrade' of https://github.com/Flow-Launcher/Flow.Launcher into squirrel_upgrade 2025-09-21 16:45:37 +08:00
Jack251970
df1a272090 Fix argument null exception 2025-09-21 16:45:25 +08:00
Jack Ye
f1ae421853
Update code comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-21 16:25:24 +08:00
Jack251970
99b7bdab27 Fix build issue 2025-09-21 16:21:38 +08:00
Jack251970
fbb9e09237 Improve code quality 2025-09-21 16:19:13 +08:00
Jack251970
21a6fdd100 Fix issue in PortableDataLocationInUse 2025-09-21 16:17:41 +08:00
Jack251970
2f52c1aa1b Improve code quality 2025-09-21 16:17:31 +08:00
Jack251970
56d6234f1c Initialize logger after data path initialization during portable clean up 2025-09-21 15:17:18 +08:00
Jack251970
81dcadb959 Use getter to fix data location issue 2025-09-21 14:57:50 +08:00
Jack251970
89d726de2a Use getter to fix data location issue 2025-09-21 14:03:43 +08:00
Jack Ye
9211fe476d
Merge branch 'dev' into squirrel_upgrade 2025-09-21 13:42:37 +08:00
Jack Ye
3116f3d363
Merge branch 'dev' into plugin_initialization 2025-09-21 13:22:31 +08:00
dependabot[bot]
846cc65d8e
Bump Svg.Skia from 3.0.6 to 3.2.1
---
updated-dependencies:
- dependency-name: Svg.Skia
  dependency-version: 3.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-21 04:49:51 +00:00
Jack Ye
7a7760c3dd
Merge pull request #3997 from Flow-Launcher/flow_launcher_localization
Use Flow.Launcher.Localization to improve code quality
2025-09-21 12:47:34 +08:00
Jack251970
78e5bf2a60 Use Flow.Launcher.Localization to improve code quality 2025-09-21 11:50:51 +08:00
Jack Ye
a2c11af82b
Merge pull request #3980 from Flow-Launcher/try_create_setting_panel
Catch exception when creating setting panel
2025-09-20 07:53:22 +08:00
Jack Ye
89fca85ca8
Merge pull request #3993 from Flow-Launcher/improve_log
Use Debug instead of Info
2025-09-19 13:27:40 +08:00
Jack Ye
536f1a392e
Merge pull request #3986 from Flow-Launcher/wallpaper_lock
Fix Wallpaper File Lock
2025-09-19 13:27:18 +08:00
Jack Ye
5d843ecae8
Merge pull request #3990 from Flow-Launcher/internalization_memory_leak
Remove old dictionaries references to fix possible memory leak
2025-09-19 12:41:43 +08:00
Jack251970
bbc12ec041 Revert "Disable cache feature"
This reverts commit 9a597f2b4d.
2025-09-18 21:45:24 +08:00
Jack251970
72dae631fe Use TextBox.ForegroundProperty 2025-09-18 21:19:49 +08:00
Jack251970
245c492906 Improve code quality 2025-09-18 21:13:12 +08:00
Jeremy Wu
3a8c64b02d
update wording 2025-09-18 23:08:02 +10:00
Jack251970
7a5e55e5f0 Use Debug instead of Info 2025-09-18 21:06:18 +08:00
Jack Ye
16895716e9
Merge pull request #3978 from Flow-Launcher/program_locks
Fix program lock waiting issue
2025-09-18 20:38:15 +08:00
Jack Ye
9030bffdbd
Merge pull request #3992 from Flow-Launcher/clear_cache_twice
Fix DirectoryNotFoundException when deleting cache twice
2025-09-18 19:59:34 +08:00
Jeremy Wu
4bea4101a1
add comment for cache folder size refresh event 2025-09-18 21:40:37 +10:00
Jack251970
ec182ade39 Fix property changed event 2025-09-18 19:18:11 +08:00
Jack251970
35a5e27e2d Fix DirectoryNotFoundException when deleting cache twice 2025-09-18 19:15:22 +08:00
Jack251970
9a597f2b4d Disable cache feature 2025-09-18 18:32:13 +08:00
Jack251970
fc2e3fec63 Improve ImageLoader performance 2025-09-18 18:29:45 +08:00
Jack251970
0f6245a072 Handle exceptions inside ChangeLanguageAsync to avoid unobserved task crashes 2025-09-18 18:18:02 +08:00
Jack251970
86581e6a00 Add disposable for internalization 2025-09-18 18:17:27 +08:00
Jack251970
330e6c09e7 Add language change lock 2025-09-18 18:07:18 +08:00
Jack251970
6c695f09e7 Use clear function 2025-09-18 17:55:06 +08:00
Jack251970
b05c2c1e1a Remove old dictionaries references to fix possible memory leak 2025-09-18 16:25:21 +08:00
Jack Ye
820304ce19
Merge pull request #3988 from Flow-Launcher/default_browser_explorer_translation
Add Default Browser & File Explorer & New Profile Translation & Ignore index change for -1
2025-09-18 12:58:04 +08:00
Jack251970
1017501153 Fix namespace issue 2025-09-18 12:36:40 +08:00
Jack251970
256ae5c4b0 Fill missing translation 2025-09-18 12:15:33 +08:00
Jack251970
a03f62832a Ignore index change for -1 2025-09-18 12:08:49 +08:00
Jack251970
dd07baff59 Add SelectFile helper method & Improve code quality 2025-09-18 11:58:38 +08:00
Jack251970
647c55eaf7 Add property changed check 2025-09-18 11:47:18 +08:00
Jack251970
1fea8edb74 Add translation for default items & new profile item for explorer & browser window 2025-09-18 11:46:09 +08:00
Jack Ye
2dd416028d
Merge pull request #3950 from Flow-Launcher/FileExplorerHelper_enhancement
Use DialogJump to get explorer path
2025-09-18 09:17:47 +08:00
Jack Ye
3f0f118c8b
Merge pull request #3985 from Flow-Launcher/everything_sort_option
Fix AllEverythingSortOptions issue
2025-09-18 09:07:19 +08:00
Jack Ye
e50a2772f8
Update code comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-17 19:33:46 +08:00
Jack251970
354b04bea4 Add code comments 2025-09-17 19:29:36 +08:00
Jack251970
60ec9b5c49 Use OnLoaded to ensure the wallpaper file is not locked 2025-09-17 19:24:32 +08:00
Jack251970
83f02f5c91 Use log error & Improve returned color 2025-09-17 19:16:58 +08:00
Jack251970
5c16b86edf Fix file lock during file stream 2025-09-17 19:16:09 +08:00
Jack251970
80c283a370 Improve code quality 2025-09-17 19:15:46 +08:00
Jack251970
23d0b73e20 Fix AllEverythingSortOptions issue 2025-09-17 17:30:03 +08:00
Jack Ye
8bac530504
Merge pull request #3971 from dcog989/calculator-min-fix
Backout 'smart' digit separation & Fix Mages functions for Calculator plugin
2025-09-16 21:13:40 +08:00
Jack251970
8321e400c1 Fix test setting & Add instances to private fields 2025-09-16 21:04:56 +08:00
Jack251970
552b6547db Fix unit test result issue 2025-09-16 16:40:36 +08:00
Jack251970
e10b9254ed Add workaround for log & ln function 2025-09-16 16:31:06 +08:00
Jack251970
6841ad5410 Add calculator unit testing 2025-09-16 16:08:17 +08:00
Jack251970
f7b3a7c812 Fix Squirrel.exe path 2025-09-16 12:12:35 +08:00
Jack251970
3596f59430 Upgrade squirrel package version 2025-09-16 12:03:15 +08:00
Jack Ye
43e3376d2e
Merge pull request #3979 from Flow-Launcher/json_plugin_setting
Improve JsonRPC Plugin Setting Panel
2025-09-16 08:04:57 +08:00
Jack251970
18c8a04cbc Log exception 2025-09-15 21:46:03 +08:00
Jack251970
0355993b00 Use translation 2025-09-15 21:38:53 +08:00
Jack251970
e204daa844 Catch exception when creating setting panel 2025-09-15 21:33:21 +08:00
Jack251970
55f9857dec Remove SystemParameters.VerticalScrollBarWidth 2025-09-15 18:59:01 +08:00
Jack251970
e571204a84 No need to initialize max width 2025-09-15 18:48:09 +08:00
Jack251970
988db485a2 Merge branch 'json_plugin_setting' of https://github.com/Flow-Launcher/Flow.Launcher into json_plugin_setting 2025-09-15 18:46:53 +08:00
Jack251970
f3cb85ea59 Remove unnecessary check 2025-09-15 18:46:47 +08:00
Jack Ye
5695200940
Fix typos
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-15 18:46:05 +08:00
Jack251970
44ca9c3480 Use wrap text warpping & set max width of all children 2025-09-15 18:40:16 +08:00
Jack251970
b5c860db1d Add max width constrain 2025-09-15 18:27:19 +08:00
Jack251970
ef69e329fc Fix release 2025-09-15 15:50:22 +08:00
Jack251970
9321a7df14 Fix program lock issue 2025-09-15 15:40:49 +08:00
Jack251970
684fafdfdd Improve code quality 2025-09-15 15:18:42 +08:00
Jack251970
c2e797615d Improve code quality 2025-09-15 15:18:23 +08:00
Jack251970
f9facda521 Improve code quality 2025-09-15 13:02:42 +08:00
Jack251970
1906d68541 Add ShowErrorMessage setting 2025-09-15 12:57:25 +08:00
Jack251970
cea1402dde Improve code quality 2025-09-15 12:53:17 +08:00
Jack251970
b07420a193 Use EmptyResults to improve code quality 2025-09-15 12:51:31 +08:00
dcog989
9be8b71f09 review feedback, CultureInvariant, mild refactor 2025-09-14 22:28:37 +01:00
dcog989
edc76faeb4 Review feedback, case insensitive, consistent separators 2025-09-14 21:51:47 +01:00
dcog989
e990e0ff5b Handle misplaced separators, Mages edge cases
Allow for e.g. `25,00` when `,` used as digit grouping.
Exclude Mages function from the above relaxed logic.
2025-09-14 19:39:06 +01:00
dcog989
336e51d104 IcoPath to const string 2025-09-14 17:08:37 +01:00
dcog989
c79e4831e1
Merge branch 'Flow-Launcher:dev' into calculator-min-fix 2025-09-14 14:54:28 +01:00
Jack251970
daf35a4972 Do not check bracket complete 2025-09-14 15:54:35 +08:00
Jack251970
495ace1246 Improve plugin description 2025-09-14 12:39:11 +08:00
Jack Ye
51df66e362
Merge pull request #3976 from dcog989/logging-enhanced
Enhanced logging
2025-09-14 12:34:32 +08:00
Jack251970
11f5ea5074 Improve code quality 2025-09-14 12:33:46 +08:00
Jack251970
c2fcf648e0 Improve log file format 2025-09-14 12:25:54 +08:00
Jack251970
2ae57ae34c Format & Improve string resources & Rename enums 2025-09-14 12:20:00 +08:00
Jack Ye
0e0ef0e5a2
Merge pull request #3975 from dcog989/CommunityPluginSource-logging-refined
Catch scary exception, print friendly log
2025-09-14 12:05:19 +08:00
dcog989
d4a757be49 Enhanced logging
User can now choose None, Error, Info, or Debug.
2025-09-13 19:57:54 +01:00
dcog989
08a571f076 Merge branch 'CommunityPluginSource-logging-refined' of https://github.com/dcog989/Flow.Launcher into CommunityPluginSource-logging-refined 2025-09-13 17:06:21 +01:00
dcog989
f3998a190c explicit exception logging 2025-09-13 17:04:32 +01:00
dcog989
6eeef62568
Merge branch 'dev' into CommunityPluginSource-logging-refined 2025-09-13 16:33:49 +01:00
dcog989
e63c4f3b6e Catch scary exception, print friendly log
Rather than showing the user `EXCEPTION OCCURS: System.Threading.Tasks.TaskCanceledException...`, print not scary log msg
2025-09-13 16:30:13 +01:00
dcog989
6462023d58
Merge branch 'dev' into calculator-min-fix 2025-09-13 13:17:40 +01:00
dcog989
110f571b40 Rework solution for nested Mages
Previous solution missed e.g. `pow(min(2,3), 4)`
2025-09-13 13:17:08 +01:00
Jeremy Wu
eaa90b31d8
Merge pull request #3906 from Flow-Launcher/nuget_package_upgrade
Upgrade Nuget packages & Update lock files
2025-09-13 17:36:20 +10:00
Jack251970
29b675b26a Upgrade nuget packages 2025-09-13 14:14:59 +08:00
Jack Ye
889ed58f0c
Merge pull request #3973 from dcog989/bookmark-quick-fix
Bookmark plugin - catch an exception for empty / broken favicons
2025-09-13 14:13:06 +08:00
Jack Ye
7fc30aea93
Merge branch 'dev' into nuget_package_upgrade 2025-09-13 14:06:16 +08:00
Jack Ye
45275f0a2c
Merge pull request #3964 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Microsoft.Data.Sqlite-9.0.9
Bump Microsoft.Data.Sqlite from 9.0.8 to 9.0.9
2025-09-13 14:05:36 +08:00
dcog989
15f31a1698 Bookmark plugin quick fix
Catch an exception for empty / corrupted favicons.
2025-09-12 22:34:03 +01:00
dcog989
bd186e7fe1 correct + extend description 2025-09-12 20:02:34 +01:00
dcog989
190e0e179f Fix 'German' number formatting 2025-09-12 20:01:55 +01:00
dcog989
103d3832a0 dead code, improve messages, group separator fix? 2025-09-12 19:30:07 +01:00
dcog989
e1079396c3 backout 'smart' digit grouping, mages fixes + workaround
Mages did not like the previous change to smart thousands / decimal so backed that out.

Workaround for https://github.com/FlorianRappl/Mages/issues/132
2025-09-12 19:20:19 +01:00
dependabot[bot]
ca164b37db
Bump Microsoft.Data.Sqlite from 9.0.8 to 9.0.9
---
updated-dependencies:
- dependency-name: Microsoft.Data.Sqlite
  dependency-version: 9.0.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-09 22:07:50 +00:00
Jack251970
f40255bc8a Use DialogJump to get explorer path 2025-09-06 15:52:57 +08:00
Jack251970
8e837b8e56 Avoid clobbering inherited NoWarn; append instead. 2025-09-06 13:37:27 +08:00
Jack Ye
2d8c7a39ca
Merge branch 'dev' into plugin_initialization 2025-09-06 13:31:29 +08:00
Jack Ye
8bc60e55e0
Merge branch 'dev' into nuget_package_upgrade 2025-09-06 13:31:24 +08:00
Jack Ye
45bb6e94fc
Merge branch 'dev' into url_open_enhancement 2025-09-06 13:31:19 +08:00
Jeremy Wu
e0930924b1
Merge pull request #3904 from Flow-Launcher/dev
Release 2.0.0 | Plugin 5.0.0
2025-09-06 14:09:43 +10:00
Jeremy Wu
b898c466aa
Merge pull request #3929 from Flow-Launcher/bump_versions
Bump version for 2.0.0 release + update readme
2025-09-06 13:47:59 +10:00
Jeremy Wu
c7e26d3ab7
Merge pull request #3844 from Flow-Launcher/net9_readme
Update readme for .Net 9
2025-09-06 13:47:12 +10:00
Jeremy Wu
4fe8d8c233
New Crowdin updates (#3940) 2025-09-06 06:34:23 +03:00
Jack Ye
cf8096f276
Merge branch 'dev' into plugin_initialization 2025-09-05 20:35:05 +08:00
Jack251970
b501e52f5d Upgrade nuget packages 2025-09-05 19:49:21 +08:00
Jack Ye
9b0498974c
Merge pull request #3946 from Flow-Launcher/dependabot/github_actions/actions/setup-python-6
Bump actions/setup-python from 5 to 6
2025-09-05 19:35:10 +08:00
Jack Ye
430216a345
Merge pull request #3945 from Flow-Launcher/dependabot/github_actions/actions/stale-10
Bump actions/stale from 9 to 10
2025-09-05 19:34:43 +08:00
Jack251970
3f6bebd540 Keep default value same as origin 2025-09-05 19:04:46 +08:00
Jack251970
96d7d18f6d Change string resource names & Redesign setting panel 2025-09-05 19:02:49 +08:00
Jack251970
4f49077bee Add & Use BoolToVisibilityConverter 2025-09-05 19:02:29 +08:00
Jack251970
33aef7eb54 Add convert back implementation for InverseBoolConverter 2025-09-05 19:02:11 +08:00
Jack251970
cdf3905a51 Use InverseBoolConverter 2025-09-05 18:40:50 +08:00
Jack251970
c17db8c9b4 Use using statements 2025-09-05 18:40:28 +08:00
Jack251970
34a58bc5da Add InverseBoolConverter 2025-09-05 18:39:31 +08:00
Jack251970
7ca37697d4 Use PrivateModeArgument & Implement new logic for browser openning 2025-09-05 18:39:22 +08:00
Jack251970
978b18570a Add PrivateModeArgument & Add IPropertyChanged interface for some properties 2025-09-05 18:39:02 +08:00
VictoriousRaptor
0c9dd46239
Merge pull request #3947 from PatrikHed/dev
Added ISO 8601 date to the SettingsPaneThemeViewModel
2025-09-05 18:27:09 +08:00
Patrik Hedlund
860ef6081b Added ISO 8601 date to the SettingsPaneThemeViewModel 2025-09-05 10:42:56 +02:00
Jack251970
1303ae5682 Update string resource 2025-09-05 16:07:26 +08:00
dependabot[bot]
d1c2298be9
Bump actions/setup-python from 5 to 6
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 22:05:24 +00:00
dependabot[bot]
7ffc5f7864
Bump actions/stale from 9 to 10
Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v9...v10)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: '10'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 22:05:21 +00:00
Jack251970
33a9654fe0 Add settings control 2025-09-04 22:06:02 +08:00
Jack251970
1dc1643071 Change language strings 2025-09-04 21:46:55 +08:00
Jack251970
48e845c0b1 Change language strings 2025-09-04 21:45:20 +08:00
Jeremy Wu
738a27e9b6
Merge pull request #3943 from Flow-Launcher/update_manifest_url_fetch
Handle MinimumAppVersion parse fail and reorder last manifest fetch date
2025-09-04 23:14:38 +10:00
Jeremy Wu
5d297c1f19
update to log info for plugin version requirement failure 2025-09-04 22:36:43 +10:00
Jeremy
80b0b6031e handle version parse fail and reorder last manifest fetch date 2025-09-04 21:49:16 +10:00
Jack Ye
ec7b481d27
Merge branch 'dev' into bump_versions 2025-09-04 19:09:58 +08:00
Jack Ye
7208b72561
Merge pull request #3941 from Flow-Launcher/dependabot/github_actions/actions/setup-dotnet-5
Bump actions/setup-dotnet from 4 to 5
2025-09-04 19:09:32 +08:00
Jack251970
5976ddeb72 Add more strings 2025-09-04 17:54:03 +08:00
Jack251970
2d5626b0cd Use more settings 2025-09-04 17:30:20 +08:00
Jack251970
c9ca9ad4ba Add more settings 2025-09-04 17:30:08 +08:00
Jack251970
f848d1b7b2 Improve code quality 2025-09-04 17:26:19 +08:00
Jack251970
56bdfa72d1 Improve code quality 2025-09-04 17:24:59 +08:00
dependabot[bot]
668d89018d
Bump actions/setup-dotnet from 4 to 5
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4 to 5.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 06:44:22 +00:00
Jack Ye
6d0288102d
Merge pull request #3936 from Flow-Launcher/always_preview_notification
Add maximum results shown warning when always preview is on
2025-09-04 13:32:08 +08:00
VictoriousRaptor
8ec5026e1b
Merge pull request #3933 from Flow-Launcher/point_pluginsmanifest_to_main
Switch plugins manifest file download URL to be from main branch
2025-09-04 08:47:16 +08:00
Jack251970
c8dedd50d5 Add maximum results shown warning when always preview is on 2025-09-03 21:42:47 +08:00
Jeremy
ffb7c25635 switch PluginsManifest download URL to be from main branch 2025-09-03 21:02:07 +10:00
Jack251970
96b93c904c Use new screenshot for plugin store 2025-09-03 16:24:58 +08:00
Jack251970
634a6aa3bb Add Set Flow Launcher Theme command 2025-09-03 12:13:42 +08:00
Jeremy Wu
09366f5bed
New Crowdin updates (#3931) 2025-09-03 09:32:29 +08:00
Jeremy Wu
9ee123b028
Merge pull request #3932 from Flow-Launcher/add_plugin_min_flow_version
Allow setting of minimum app version plugin requirement
2025-09-02 23:09:27 +10:00
Jeremy Wu
8392a025f3
fix typo 2025-09-02 22:46:37 +10:00
Jeremy
563ae0eeaf add manifest handling of plugin's MinimumAppVersion property 2025-09-02 22:11:08 +10:00
Jeremy Wu
04368c62e7
add roadmap link in README.md 2025-09-02 14:00:02 +10:00
Jeremy Wu
2c7ffd979e
Make image widths in features section of readme the same 2025-09-02 13:51:22 +10:00
Jeremy Wu
fba475c170
update Visual Studio version requirement in README 2025-09-02 08:36:38 +10:00
Jeremy Wu
20b4e22746
version bump Plugin project 2025-09-01 22:31:35 +10:00
Jeremy Wu
b58cf3677d
formatting 2025-09-01 21:48:25 +10:00
Jeremy Wu
90d35718b0
add Dialog Jump image to readme 2025-09-01 21:42:34 +10:00
Jeremy Wu
d4a6ff0acf
updated readme 2025-08-31 20:33:41 +10:00
Jeremy Wu
ca01d15dde
New Crowdin updates (#3824)
* New translations en.xaml (German)
[ci skip]

* New translations en.xaml (French)
[ci skip]

* New translations en.xaml (Arabic)
[ci skip]

* New translations en.xaml (Czech)
[ci skip]

* New translations en.xaml (Danish)
[ci skip]

* New translations en.xaml (Hebrew)
[ci skip]

* New translations en.xaml (Italian)
[ci skip]

* New translations en.xaml (Japanese)
[ci skip]

* New translations en.xaml (Korean)
[ci skip]

* New translations en.xaml (Dutch)
[ci skip]

* New translations en.xaml (Polish)
[ci skip]

* New translations en.xaml (Portuguese)
[ci skip]

* New translations en.xaml (Russian)
[ci skip]

* New translations en.xaml (Slovak)
[ci skip]

* New translations en.xaml (Turkish)
[ci skip]

* New translations en.xaml (Ukrainian)
[ci skip]

* New translations en.xaml (Chinese Simplified)
[ci skip]

* New translations en.xaml (Chinese Traditional)
[ci skip]

* New translations en.xaml (Vietnamese)
[ci skip]

* New translations en.xaml (Portuguese, Brazilian)
[ci skip]

* New translations en.xaml (Norwegian Bokmal)
[ci skip]

* New translations en.xaml (Serbian (Latin))
[ci skip]

* New translations en.xaml (Spanish (Modern))
[ci skip]

* New translations en.xaml (Spanish, Latin America)
[ci skip]

* New translations en.xaml (French)
[ci skip]

* New translations en.xaml (Slovak)
[ci skip]

* New translations en.xaml (Chinese Simplified)
[ci skip]

* New translations en.xaml (Ukrainian)
[ci skip]

* New translations en.xaml (French)
[ci skip]

* New translations en.xaml (Chinese Simplified)
[ci skip]

* New translations en.xaml (Chinese Simplified)
[ci skip]

* New translations en.xaml (Portuguese)
[ci skip]

* New translations en.xaml (German)
[ci skip]

* New translations en.xaml (French)
[ci skip]

* New translations en.xaml (Arabic)
[ci skip]

* New translations en.xaml (Czech)
[ci skip]

* New translations en.xaml (Danish)
[ci skip]

* New translations en.xaml (Hebrew)
[ci skip]

* New translations en.xaml (Italian)
[ci skip]

* New translations en.xaml (Japanese)
[ci skip]

* New translations en.xaml (Korean)
[ci skip]

* New translations en.xaml (Dutch)
[ci skip]

* New translations en.xaml (Polish)
[ci skip]

* New translations en.xaml (Portuguese)
[ci skip]

* New translations en.xaml (Russian)
[ci skip]

* New translations en.xaml (Slovak)
[ci skip]

* New translations en.xaml (Turkish)
[ci skip]

* New translations en.xaml (Ukrainian)
[ci skip]

* New translations en.xaml (Chinese Simplified)
[ci skip]

* New translations en.xaml (Chinese Traditional)
[ci skip]

* New translations en.xaml (Vietnamese)
[ci skip]

* New translations en.xaml (Portuguese, Brazilian)
[ci skip]

* New translations en.xaml (Norwegian Bokmal)
[ci skip]

* New translations en.xaml (Serbian (Latin))
[ci skip]

* New translations en.xaml (Spanish (Modern))
[ci skip]

* New translations en.xaml (Spanish, Latin America)
[ci skip]

* New translations en.xaml (Slovak)
[ci skip]

* New translations en.xaml (French)
[ci skip]
2025-08-31 13:05:43 +03:00
Jeremy Wu
19848aafb0
update readme for Windows 10+ only support 2025-08-31 18:28:43 +10:00
Jeremy Wu
82106ccc0f
bump plugin project version 2025-08-31 18:23:37 +10:00
Jeremy Wu
e05c94d9e4
bump version 2025-08-31 18:22:41 +10:00
Jeremy Wu
493992e25c
Merge pull request #3928 from Flow-Launcher/resolve_conflicts
Resolve conflicts
2025-08-31 18:13:09 +10:00
Jeremy Wu
e1cc50c579 Merge remote-tracking branch 'origin/master' into resolve_conflicts 2025-08-31 17:53:21 +10:00
Jack Ye
e784c8dbf5
Merge pull request #3927 from Flow-Launcher/remove_unused_runtime_files
Remove unused runtime files
2025-08-31 15:28:44 +08:00
Jack251970
9a2f813ea2 Keep x86 runtime files 2025-08-31 15:09:48 +08:00
Jack251970
3906fd46af Fix 2025-08-31 14:53:38 +08:00
Jack251970
5108114f2f Test1 2025-08-31 14:45:59 +08:00
Jack251970
2e2d7fe515 Test 2025-08-31 14:45:40 +08:00
Jeremy Wu
72b4ff31ed
Merge pull request #3898 from Flow-Launcher/storage_api_method
Load images into ImageCache & Use non-async method in ImageCache
2025-08-31 15:31:01 +10:00
Jack251970
6e873abc7b Upgrade nuget package & Fix lock file issue 2025-08-30 16:34:34 +08:00
Jack Ye
528687cf63
Merge branch 'dev' into nuget_package_upgrade 2025-08-30 16:27:15 +08:00
Jack Ye
e73023509e
Merge pull request #3901 from Flow-Launcher/ShowMsgError
Use ShowMsgError API Function & Handle E_ABORT COMException
2025-08-30 06:22:38 +01:00
Jack Ye
0f243fae7f
Merge pull request #3925 from Flow-Launcher/test_system_events
Downgrade System.Drawing.Common to 7.0.0
2025-08-30 06:14:19 +01:00
Jack251970
054135df55 Downgrade System.Drawing.Common version 2025-08-29 13:03:34 +08:00
Jack251970
afafd6dc0b Add Microsoft.Win32.SystemEvents 2025-08-29 11:44:20 +08:00
Jack251970
9ac32b0bee Use 7.0.0 SystemEvents 2025-08-29 11:40:33 +08:00
Jack Ye
54e4c1333d
Merge pull request #3921 from Flow-Launcher/auto_update
Check plugin updates only for Release
2025-08-26 04:12:18 +01:00
Jack251970
233f8cd0f3 Check plugin updates only for Release 2025-08-25 19:02:04 +08:00
Jack251970
6b60373886 Upgrade nuget packages 2025-08-25 18:50:02 +08:00
Jack Ye
d8ef49139d
Merge pull request #3899 from Flow-Launcher/QuerySuggestionText_API
Add new api Result.QuerySuggestionText
2025-08-20 09:45:19 +01:00
Jeremy Wu
a9b5b3aa38
Merge pull request #3860 from Flow-Launcher/taooceros-patch-1
Add Srpski (Cyrillic)
2025-08-20 18:31:51 +10:00
Jack Ye
2f9c101222
Merge pull request #3900 from Flow-Launcher/environment_install
Add error handler for DroplexPackage.Drop
2025-08-20 09:23:13 +01:00
Jack251970
b07763ad05 Suppress FLSG0007 2025-08-19 14:27:45 +08:00
Jack251970
922c3d5611 Upgrade nuget packages & Update lock files 2025-08-17 16:54:41 +08:00
Jack Ye
ebdea4fadf
Merge pull request #3905 from Flow-Launcher/explorer_preview_panel
Fix Explorer preview panel issue
2025-08-17 08:37:54 +01:00
Jack251970
6751942179 Use Binding instead of RelativeResource for code quality 2025-08-17 14:30:03 +08:00
Jack251970
b097aa0c29 Apply style formatter 2025-08-17 14:23:04 +08:00
Jack251970
0228e6e488 Remove unused trigger 2025-08-17 14:22:50 +08:00
Jack251970
ce8b544ff5 Display FilePath in preview panel 2025-08-17 14:20:55 +08:00
Jack251970
21299d153f Add FileName & FilePath as public properties 2025-08-17 14:18:18 +08:00
Jack251970
9d3a0f0b70 Show FileName under preview image 2025-08-17 14:14:17 +08:00
Jack251970
f1628eec66 Add MaxHeight & MaxWidth for preview image 2025-08-17 14:14:02 +08:00
Jack251970
9f8ff12155 Bind DataContext to itself 2025-08-17 14:13:32 +08:00
Jack251970
d8f8de2abe Use Српски instead of Српска 2025-08-16 11:41:25 +08:00
Jack Ye
4b41578024
Merge pull request #3902 from dcog989/Clean-orphan-files,-temp-fix
Clean orphan files, temp fix
2025-08-16 04:35:22 +01:00
dcog989
693636c942 Clean orphan files, temp fix
Clean orphan SQLite files on start-up that are being created by BrowserBookmark plugin.  This is a temporary solution to prevent users' cache accumulating very large numbers of files / MB.

Will be addressed properly with new version of plugin (complete rewrite for Flow Launcher V2).
2025-08-14 13:06:39 +01:00
Jack251970
556dce9bed Remove unnecessary Task.Run 2025-08-14 19:22:03 +08:00
Jack251970
7264f5493f Use ShowMsgError for plugin load fail message 2025-08-14 18:34:03 +08:00
Jack251970
cdd5bf1a76 Swap title/subtitle for ShowMsgError 2025-08-14 14:51:50 +08:00
Jack251970
c00d8db5ad Handle E_ABORT 2025-08-14 14:39:58 +08:00
Jack251970
4a0f126c34 Use ShowMsgError instead of ShowMsgBox 2025-08-14 14:39:41 +08:00
Jack251970
4cbc9f18c7 Add error handler for DroplexPackage.Drop 2025-08-14 14:24:54 +08:00
Jack Ye
f244812be1
Merge pull request #3891 from zoltanvi/feature/web_search_private_mode
Add private mode option to WebSearch plugin items
2025-08-14 02:17:15 +01:00
zoltanvi
db84dff4e6 Merge branch 'feature/web_search_private_mode' of https://github.com/zoltanvi/Flow.Launcher into feature/web_search_private_mode 2025-08-13 16:15:25 +02:00
zoltanvi
361c2a4a24 Revert language files other than english 2025-08-13 16:15:10 +02:00
Jack251970
bf07f2e72c Improve xml documents 2025-08-13 21:00:17 +08:00
Jack251970
c895b2acb0 Improve selectedResultPossibleSuggestion check logic 2025-08-13 20:53:53 +08:00
Jack251970
063836266d Fix Result.Clone 2025-08-13 20:44:00 +08:00
Jack251970
2a1584e2dd Add code comments 2025-08-13 20:43:51 +08:00
Jack251970
8c6f9c9875 Add new api Result.QuerySuggestionText 2025-08-13 20:26:23 +08:00
Zoltan
b612729426
Merge branch 'dev' into feature/web_search_private_mode 2025-08-13 08:34:37 +02:00
Jack Ye
cc42cd6e45
Merge pull request #3897 from Flow-Launcher/recursive_loading
Fix possible image recursive loading
2025-08-13 07:30:23 +01:00
Jack251970
3727bff865 Fix typos 2025-08-13 14:19:52 +08:00
Jack251970
e06e6e2b5a Load images into ImageCache 2025-08-13 14:01:04 +08:00
Jack251970
3afc52187f Add non-async version load in BinaryStorage & Use non-async version for ImageLoader 2025-08-13 13:58:02 +08:00
Jack251970
2aa07a25a7 Fix possible image recursive loading 2025-08-13 13:37:23 +08:00
zoltanvi
52c08c03e1 Merge branch 'feature/web_search_private_mode' of https://github.com/zoltanvi/Flow.Launcher into feature/web_search_private_mode 2025-08-12 15:06:08 +02:00
zoltanvi
99f2b313cd Fix new label in more language files 2025-08-12 15:06:00 +02:00
Zoltan
3e0539f8c6
Merge branch 'dev' into feature/web_search_private_mode 2025-08-12 12:33:51 +02:00
Jack251970
fb8daa4ed9 Potential race condition in action keyword management 2025-08-12 18:12:15 +08:00
Jack Ye
04bd9ddc2c
Verify File.Delete exception handling
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-12 18:09:59 +08:00
Jack251970
9221435bad Remove plugin from _allLoadedPlugins when one plugin is uninstalled 2025-08-12 18:07:10 +08:00
Jack251970
31c8e850cd Improve code quality 2025-08-12 18:03:23 +08:00
Jack Ye
7b08c965ec
Merge pull request #3889 from zoltanvi/bugfix/web_search
Fix property binding in WebSearch plugin
2025-08-12 10:57:20 +01:00
Jack251970
f08466245a Set GetAllInitializedPlugins to private 2025-08-12 17:53:53 +08:00
Jack251970
93af079316 Improve code quality 2025-08-12 17:49:22 +08:00
Zoltan
3c41823928
Merge branch 'dev' into feature/web_search_private_mode 2025-08-12 11:46:46 +02:00
Zoltan
5a74b8458b
Merge branch 'dev' into bugfix/web_search 2025-08-12 11:45:33 +02:00
Jack251970
cc2df8d680 Improve code quality 2025-08-12 17:31:26 +08:00
Jack Ye
defe62b54b
Merge pull request #3896 from Flow-Launcher/dependabot/github_actions/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-08-12 10:27:43 +01:00
dependabot[bot]
37c529e012
Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 06:40:12 +00:00
zoltanvi
1df6ac00b0 Fix label in language files 2025-08-12 07:28:18 +02:00
zoltanvi
0a848a32f7 Add private mode option to WebSearch items 2025-08-09 16:21:10 +02:00
zoltanvi
09d8cefcef Remove unused properties 2025-08-09 11:56:13 +02:00
zoltanvi
15b8747280 Fix IsEnabled binding for suggestion combobox 2025-08-09 11:51:12 +02:00
Jack Ye
4ae58c8890
Merge pull request #3886 from Flow-Launcher/remove_runtime_files
Remove unnecessary runtime files
2025-08-07 20:51:52 +01:00
Jack Ye
908ed0cff5
Merge pull request #3867 from Flow-Launcher/preview_panel_creation
Do not create customized preview panel when preview is off
2025-08-07 10:10:31 +01:00
Jack Ye
b0694e97aa
Merge pull request #3826 from dcog989/Fix-'100%-CPU'-BrowserBookmark-issue
Fix BrowserBookmark '100% CPU' issue
2025-08-07 02:38:21 +01:00
Jack251970
b9c18e7d4f Revert "Set RuntimeIdentifier to win-x64 for directory build properties"
This reverts commit 9f5d578aea.
2025-08-07 09:33:31 +08:00
Jack251970
9f5d578aea Set RuntimeIdentifier to win-x64 for directory build properties 2025-08-07 09:29:51 +08:00
Jack251970
2d1aa2333b Remove unnecessary runtime files 2025-08-07 09:12:07 +08:00
Jack251970
8f88998d34 Remove more unnecessary runtimes 2025-08-07 08:57:21 +08:00
dcog989
5353d8be81
Merge branch 'dev' into Fix-'100%-CPU'-BrowserBookmark-issue 2025-08-06 14:07:45 +01:00
dcog989
dcc0866de1
Update Flow.Launcher.Plugin.BrowserBookmark.csproj
bump Svg.Skia
2025-08-06 13:58:36 +01:00
dcog989
2a5ac092f0
Update Flow.Launcher.Plugin.BrowserBookmark.csproj
bump Microsoft.Data.Sqlite
2025-08-06 13:46:25 +01:00
Jack Ye
dfe96160ed
Merge pull request #3882 from Flow-Launcher/dependabot/nuget/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Microsoft.Data.Sqlite-9.0.8
Bump Microsoft.Data.Sqlite from 9.0.7 to 9.0.8
2025-08-06 12:31:22 +01:00
dcog989
522d7debe8
Merge branch 'dev' into Fix-'100%-CPU'-BrowserBookmark-issue 2025-08-06 12:26:40 +01:00
Jack Ye
3c7f85eda4
Merge pull request #3878 from Flow-Launcher/net9_fix
Set CETCompat to false
2025-08-06 10:34:54 +01:00
dependabot[bot]
d9ee6665ef
Bump Microsoft.Data.Sqlite from 9.0.7 to 9.0.8
---
updated-dependencies:
- dependency-name: Microsoft.Data.Sqlite
  dependency-version: 9.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-05 22:22:22 +00:00
Jack251970
4db825debd Set CETCompat to false 2025-08-05 15:15:11 +08:00
Kevin Zhang
7664944ff2
Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-28 16:27:47 -05:00
Jack Ye
ec2af81e62
Merge pull request #3872 from Flow-Launcher/plugin_list_mouse_hover
Clear plugin list selection to make sure all items can be mouse hovered
2025-07-28 11:53:33 +01:00
Jack251970
ee4dc394d4 Clear plugin list selection to make sure all items can be mouse hovered 2025-07-28 14:28:55 +08:00
VictoriousRaptor
9f7aca4a9b
Merge pull request #3871 from Flow-Launcher/fix-typo
Fix typo in Calculator plugin
2025-07-28 12:26:26 +08:00
Jack Ye
80c0920f10
Merge branch 'dev' into fix-typo 2025-07-28 02:56:32 +01:00
VictoriousRaptor
6a01ca403f
Merge pull request #3859 from dcog989/Calculator-accepts-flexible-separator
Smart thousands and decimals for Calculator
2025-07-27 20:18:40 +08:00
Jack Ye
7084d5a558
Merge branch 'dev' into Fix-'100%-CPU'-BrowserBookmark-issue 2025-07-27 13:04:50 +01:00
Jack251970
afc969d00f Update translations in language file 2025-07-27 20:03:33 +08:00
VictoriousRaptor
53255c3a9c Fix typo 2025-07-27 18:41:23 +08:00
VictoriousRaptor
443d4f7c02 Build regex at compile time 2025-07-27 18:39:38 +08:00
VictoriousRaptor
71e0374640 Merge dev 2025-07-27 14:45:08 +08:00
VictoriousRaptor
c42628b4fa
Merge pull request #3765 from Flow-Launcher/code_quality
Use Flow.Launcher.Localization to improve code quality
2025-07-27 00:37:14 +08:00
VictoriousRaptor
b637e0a028 Use Flow.Launcher.Localization 0.0.4 & update expect list 2025-07-27 00:17:09 +08:00
Jack251970
14af7af2d1 Remove blank lines 2025-07-25 15:32:09 +08:00
Jack251970
277767f101 Do not create customized preview panel when preview is off 2025-07-25 09:55:26 +08:00
Jack251970
14282b6734 Fix typos 2025-07-24 19:16:58 +08:00
Jack Ye
54a2817962
Merge branch 'dev' into code_quality 2025-07-24 11:55:46 +01:00
Jack Ye
97fb8d680f
Merge branch 'dev' into plugin_initialization 2025-07-24 11:54:19 +01:00
Jack Ye
65cfb29cfd
Merge pull request #3846 from Flow-Launcher/nuget_dependency
Upgrade NuGet Dependencies & Package Lock Json Files
2025-07-24 11:52:04 +01:00
Jack Ye
c6963aa1d3
Merge branch 'dev' into nuget_dependency 2025-07-24 11:30:38 +01:00
Jack251970
0564e5814b Update description 2025-07-24 16:54:19 +08:00
Jack251970
c42f84f246 Update Pinyin description 2025-07-24 16:39:58 +08:00
dcog989
f8a6b02c80 fix for GitHub build server failure
Removes external dependency for NumberGroupSeparator so it builds anywhere.
2025-07-24 02:33:59 +01:00
dcog989
9bcb1b2939 Merge branch 'Calculator-accepts-flexible-separator' of https://github.com/dcog989/Flow.Launcher into Calculator-accepts-flexible-separator 2025-07-23 17:57:12 +01:00
dcog989
76f834ff1b PR review changes 2025-07-23 17:56:58 +01:00
dcog989
e1150a4226
Merge branch 'dev' into Calculator-accepts-flexible-separator 2025-07-23 17:31:50 +01:00
dcog989
4d22e6c352
Delete Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml.cs 2025-07-23 17:26:18 +01:00
VictoriousRaptor
0b6309f175 Fix typo 2025-07-23 19:39:17 +08:00
Jack Ye
c604df96e9
Merge pull request #3862 from Flow-Launcher/dialog_jump_improvement
Fix dialog jump issue when result will not be execueted under empty query option
2025-07-23 12:32:37 +01:00
Jack251970
20f6a74fbe Fix dialog jump issue when result will not be execueted under empty query option 2025-07-23 17:33:03 +08:00
Jack251970
74306f0a76 Use PInvoke to register SystemEvents 2025-07-23 14:15:20 +08:00
Jack251970
3cd7c3ce00 Remove CursorInfo class 2025-07-23 13:52:31 +08:00
Jack251970
3cfe122c7b Use new MonitorInfo class class 2025-07-23 13:51:35 +08:00
Jack251970
4f423c47cd Allow all projects to access MonitorInfo & CursorInfo 2025-07-23 13:44:31 +08:00
Jack251970
cbbd09b8a5 Add cursor info class 2025-07-23 13:41:01 +08:00
Jack Ye
1946afbfe8
Fix typos
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-07-23 13:33:22 +08:00
Jack251970
e1b01dc407 Merge branch 'nuget_dependency' of https://github.com/Flow-Launcher/Flow.Launcher into nuget_dependency 2025-07-23 13:28:59 +08:00
Jack251970
527c27f675 Remove System.Windows.Forms.Screen reference 2025-07-23 13:28:50 +08:00
Jack251970
e63941275e Upgrade MonitorInfo class 2025-07-23 13:27:36 +08:00
Kevin Zhang
16603697a5
Add Srpski (Cyrillic) 2025-07-22 22:48:19 -05:00
dcog989
1fed54b615
Delete Flow.Launcher.Plugin/packages.lock.json 2025-07-23 04:35:18 +01:00
dcog989
cf40045acd
Delete Flow.Launcher.Core/packages.lock.json 2025-07-23 04:34:54 +01:00
dcog989
161c5462ba Merge branch 'Calculator-accepts-flexible-separator' of https://github.com/dcog989/Flow.Launcher into Calculator-accepts-flexible-separator 2025-07-23 04:27:33 +01:00
dcog989
852b2f517b fix for regression from first review, plus issues with e.g. '0,123' and ',123' 2025-07-23 04:27:19 +01:00
Jack Ye
8142ef1dee
Merge branch 'dev' into nuget_dependency 2025-07-23 10:47:19 +08:00
Jack Ye
5ff8a5b1d5
Use compiled regex
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-23 09:59:09 +08:00
Jack251970
5161bbe660 Remove unused comments & blank line 2025-07-23 09:33:14 +08:00
Jack251970
69c4d19f8b Improve code quality 2025-07-23 09:09:12 +08:00
dcog989
c83a29fb18 PR review changes 2025-07-23 01:18:26 +01:00
dcog989
113baac567 Smart thousands and decimals
## Core Logic

*   **Advanced Number Parsing:** We now process numbers with various decimal and thousand-separator formats (e.g., `1,234.56` and `1.234,56`). We distinguish between separator types based on their position and surrounding digits.
*   **Context-Aware Output Formatting:** We now mirror the output format based on the user's input. If a query includes thousand separators, the result will also have them. The decimal separator in the result will match the one used in the query.

## Code Cleanup

*   **Deleted Unused File:** `NumberTranslator.cs` was unused and therefore removed.
*   **Removed Redundant UI Code:** The `CalculatorSettings_Loaded` event handler in `CalculatorSettings.xaml.cs` (and its XAML registration) was removed. The functionality was already handled automatically by data binding.

## Maintainability

*   **Added Code Documentation:** An XML summary comment was added to the new `NormalizeNumber` method in `Main.cs` to clarify its purpose.

So, the plugin is now much more flexible, and will accept whatever format the user gives it regardless of Windows region settings.
2025-07-22 23:48:11 +01:00
dcog989
d57eca279f
Merge branch 'Flow-Launcher:dev' into Fix-'100%-CPU'-BrowserBookmark-issue 2025-07-22 20:37:24 +01:00
VictoriousRaptor
282581940a Use new Localization package 2025-07-23 00:04:52 +08:00
VictoriousRaptor
63009ba332 Use ContextMenu class instead of interface 2025-07-23 00:03:48 +08:00
VictoriousRaptor
6579a4647d Compile regex on build time 2025-07-22 23:59:33 +08:00
VictoriousRaptor
22ffc80aeb Update labels on language change and other refactor
- Bring back viewmodel construction on init
- Remove redundant init code
2025-07-22 23:48:39 +08:00
Jack Ye
1da7e1ef35
Merge pull request #3857 from Flow-Launcher/dialog_jump_glyph
Add glyph for dialog jump hotkey card item
2025-07-22 19:49:20 +08:00
Jack251970
ea7833efe4 Add glyph for dialog jump hotkey card item 2025-07-22 19:15:54 +08:00
VictoriousRaptor
08f17c4f05
Merge branch 'dev' into code_quality 2025-07-22 14:32:29 +08:00
Jack Ye
b6e2740746
Merge pull request #3851 from Flow-Launcher/url_exception
Fix uri exception in Report window
2025-07-21 23:26:02 +08:00
Jack251970
3221f930c4 Add info log message for plugin constructors 2025-07-21 21:17:30 +08:00
Jack251970
e0240784b5 Improve code quality 2025-07-21 21:11:48 +08:00
Jack Ye
2b4010720a
Merge pull request #3848 from Flow-Launcher/sys_plugin_improvement
Improve Sys plugin
2025-07-21 19:50:41 +08:00
Jack251970
0f8553b45d Refresh home page after plugins are initialized 2025-07-21 19:39:42 +08:00
Jeremy Wu
d8740768c7
wording 2025-07-21 21:33:57 +10:00
Jack251970
269d21a4c0 Change plugin modified logic 2025-07-21 18:11:16 +08:00
Jack251970
566bd04e3f Support two types in one class 2025-07-21 18:06:05 +08:00
Jack251970
bef1feea0a Fix plugin page logic 2025-07-21 18:05:22 +08:00
Jack251970
f808469285 Fix logic & Improve code quality 2025-07-21 17:59:28 +08:00
Jack251970
63f86613c3 Use internal PluginModified method instead of API.PluginModified 2025-07-21 17:55:06 +08:00
Jack251970
067a51775e Fix null exception 2025-07-21 17:54:20 +08:00
Jack251970
d9cc67ce6d Merge branch 'plugin_initialization' of https://github.com/Flow-Launcher/Flow.Launcher into plugin_initialization 2025-07-21 17:49:31 +08:00
Jack251970
59e4fb82b9 Fix logic & Add code comments 2025-07-21 17:49:24 +08:00
Jack Ye
a9a705f118
Fix logic
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-21 17:48:17 +08:00
Jack251970
6409c193c4 Add code comments 2025-07-21 17:47:19 +08:00
Jack251970
950a4a00a6 Code quality 2025-07-21 17:45:46 +08:00
Jack251970
67c940f3a8 Do not show setting panel for init failed plugins 2025-07-21 17:45:01 +08:00
Jack251970
324b3eb081 Do not call interface methods for init failed plugins 2025-07-21 17:33:47 +08:00
Jack251970
d4a1953747 Add init failed plugins 2025-07-21 17:28:20 +08:00
Jack251970
b348debc72 Code quality 2025-07-21 17:27:37 +08:00
Jack251970
fc01ddbb1c Save init failed plugins 2025-07-21 17:27:21 +08:00
Jack251970
7f797b1039 Add code comments 2025-07-21 17:22:17 +08:00
Jack251970
3bd76b2dfd Rename variable 2025-07-21 17:17:49 +08:00
Jack251970
8d03fcee2e Remove error codes 2025-07-21 17:17:11 +08:00
Jack251970
9149e3f201 Mark initializing plugins as modified 2025-07-21 17:16:36 +08:00
Jack251970
6d99416641 Search in loaded plugins 2025-07-21 17:14:03 +08:00
Jack251970
6e0f2fc4ce Return loaded plugins 2025-07-21 17:03:21 +08:00
Jack251970
fe3339f645 Get initialized plugins for plugin page 2025-07-21 17:02:19 +08:00
Jack251970
11e05f73d9 Add all loaded plugins 2025-07-21 17:00:19 +08:00
Jack251970
566572b013 Use api function & rename function 2025-07-21 16:57:35 +08:00
Jack251970
8b60d26f5e Fix build issue 2025-07-21 16:56:14 +08:00
Jack251970
50924e4189 Improve code quality 2025-07-21 16:45:34 +08:00
Jack251970
cc68183940 Change variable name 2025-07-21 16:30:39 +08:00
Jack251970
55164ef60f Improve code quality 2025-07-21 16:29:01 +08:00
Jack251970
de568140d2 Improve code quality 2025-07-21 16:27:11 +08:00
Jack251970
445a14278b Add code comments 2025-07-21 16:25:26 +08:00
Jack251970
1d3ab39dca Use () => instead of delegate 2025-07-21 16:24:23 +08:00
Jack251970
35c8e39beb Improve code comments 2025-07-21 16:23:40 +08:00
Jack251970
c3a598464f Fix code comments 2025-07-21 16:20:09 +08:00
Jack251970
0a01d85f40 Improve code quality 2025-07-21 16:16:21 +08:00
Jack251970
52bb909f0b Add plugin to all plugin list later 2025-07-21 16:00:42 +08:00
Jack251970
59a7a2c807 Improve code quality 2025-07-21 15:59:27 +08:00
Jack251970
6beeecb0f7 Use async load & initialization model 2025-07-21 14:26:24 +08:00
Jack251970
c2157e2df1 Add support for concurrent operation of explorers & dialogs adding 2025-07-21 14:03:45 +08:00
Jack251970
d4e672d630 Add support to update translation for one plugin 2025-07-21 13:55:47 +08:00
Jack251970
f5f256809c Add IResultUpdateRegister interface 2025-07-21 13:52:11 +08:00
Jack251970
aee46e8640 Initialize quick jump earlier 2025-07-21 12:10:06 +08:00
Jack251970
9ce911771e Improve code quality 2025-07-21 11:31:54 +08:00
Jack251970
af3aaa8ce7 Update labels for setting view model when culture info changes 2025-07-21 10:29:14 +08:00
Jack251970
b9e0669c46 Intialize translations in InitAsync 2025-07-21 10:27:25 +08:00
Jack Ye
71eaf2d789
Merge branch 'dev' into code_quality 2025-07-21 10:23:08 +08:00
Jeremy Wu
c02ef0e76e
Merge pull request #3850 from Flow-Launcher/fast_load_language
Initialize language before portable clean up and plugin initialization
2025-07-21 12:20:13 +10:00
VictoriousRaptor
6044fb4ce1
Merge pull request #3852 from Flow-Launcher/VictoriousRaptor-patch-1
Fix typo
2025-07-21 09:42:04 +08:00
VictoriousRaptor
df0f8e01b9
Fix typo 2025-07-21 08:58:12 +08:00
VictoriousRaptor
1e4ff43bfc Use length and count instead of Any() 2025-07-20 23:33:38 +08:00
VictoriousRaptor
ad1d42b5dd Update labels on initialization to update on language change 2025-07-20 23:23:36 +08:00
VictoriousRaptor
bea1078d20 Remove redundant function call 2025-07-20 23:20:55 +08:00
Jack251970
71c8fce4a0 Improve code quality 2025-07-20 21:01:48 +08:00
Jack251970
8e898de968 Fix build issue 2025-07-20 21:01:22 +08:00
Jack Ye
ed0524d207
Merge branch 'dev' into code_quality 2025-07-20 20:02:51 +08:00
Jack251970
55589f872a Resolve conflicts 2025-07-20 19:58:49 +08:00
Jack251970
90eec91c4e Fix ModernWpfUI version & Restore nuget 2025-07-20 19:56:54 +08:00
Jack Ye
8b3f5aa536
Merge branch 'dev' into nuget_dependency 2025-07-20 19:55:36 +08:00
Jack Ye
b639960b8c
Merge branch 'dev' into sys_plugin_improvement 2025-07-20 19:53:48 +08:00
Jack Ye
545d12c57d
Merge branch 'dev' into fast_load_language 2025-07-20 19:53:29 +08:00
Jack251970
b7096ddc32 Fix uri exception in Report window 2025-07-20 19:22:14 +08:00
Kevin Zhang
4f269d3fa9
Dialog Jump - Quickly navigate the Open/Save As dialog window (#1018) 2025-07-20 21:11:09 +10:00
Jack251970
fea899d09a No need to get lower for language code 2025-07-20 17:37:12 +08:00
Jack251970
f77f14b0ee Improve code quality 2025-07-20 17:35:07 +08:00
Jack251970
ac7da2d2d6 Do not check if the language directory contains default language file 2025-07-20 17:26:14 +08:00
Jack251970
5e8acf7d74 Use OrdinalIgnoreCase 2025-07-20 15:29:27 +08:00
Jack251970
634bdc5bd6 Do not check Flow Launcher default language file since it is binary embedded 2025-07-20 15:26:07 +08:00
Jack251970
2ee53dfbf7 Initialize language before portable clean up since it needs translations 2025-07-20 15:20:18 +08:00
Jeremy Wu
ff2d5e89f9
Merge pull request #3849 from Flow-Launcher/translations
Add Translations for All Messages and Message Boxes
2025-07-20 15:12:01 +10:00
Jack251970
d71d3a5094 Fix build issue 2025-07-20 12:57:56 +08:00
Jack Ye
e931f3ae41
Fix translations
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-07-20 12:57:04 +08:00
Jack251970
1c76114bb0 Fix translations 2025-07-20 12:53:14 +08:00
Jack Ye
d7e09abde3
Fix translations
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-20 12:52:39 +08:00
Jack251970
ba0a113cc9 Add translations & Use ShowMsgError 2025-07-20 12:28:55 +08:00
Jack251970
aed134f589 Add translations 2025-07-20 12:15:48 +08:00
Jack251970
06b3219dcb Add translations 2025-07-20 12:07:19 +08:00
Jack251970
85ffd6024b Add translations 2025-07-20 12:07:15 +08:00
Jack251970
0682e9bed1 Improve code quality for public api 2025-07-20 11:38:44 +08:00
Jack251970
b9418f1272 Add translations 2025-07-20 11:33:12 +08:00
Jack Ye
4652392c71
Update translations
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-20 11:29:15 +08:00
Jack251970
b6ecfdcf63 Remove project reference in Sys plugin to Flow.Launcher.Infrastructure 2025-07-20 09:58:30 +08:00
Jack251970
96e6882ec1 Improve string resource 2025-07-20 09:50:07 +08:00
Jack251970
365dd5ee17 Use translation for constant strings 2025-07-20 09:42:32 +08:00
Jack251970
9f63291fda Improve setting dialog command 2025-07-20 09:35:29 +08:00
Jack251970
34cba653f8 Upgrade PinYin to 3.1.0.x 2025-07-20 08:39:35 +08:00
Jack Ye
010b6dde79
Merge branch 'dev' into code_quality 2025-07-19 20:40:49 +08:00
Jack251970
13cf433889 Upgrade NLog to 6.0.1 and resolve new package NLog.OutputDebugString 2025-07-19 20:37:14 +08:00
Jack251970
7af3c5febf Upgrade NuGet dependency 2025-07-19 20:26:33 +08:00
Jack Ye
d3824f274d
Merge pull request #3332 from Flow-Launcher/dependabot/nuget/System.Data.OleDb-9.0.3
Bump System.Data.OleDb from 8.0.1 to 9.0.3
2025-07-19 20:04:28 +08:00
Jack Ye
48822fffa7
Merge branch 'dev' into dependabot/nuget/System.Data.OleDb-9.0.3 2025-07-19 19:34:19 +08:00
Jeremy Wu
ff7274fe3c
Merge pull request #3286 from Flow-Launcher/net9
update to .net 9
2025-07-19 21:32:47 +10:00
Jeremy Wu
7e68d9a49b
Merge pull request #3843 from Flow-Launcher/plugin_update_dialog
Add Plugin Update Dialog
2025-07-19 21:30:38 +10:00
Jeremy
fe4984a9ae update readme for .Net 9 2025-07-19 21:23:13 +10:00
Jeremy
0e4c3208be split readme changes out 2025-07-19 21:17:33 +10:00
Jack251970
50377388dd Remove duplicated nuget packages 2025-07-19 16:19:24 +08:00
Jeremy
56128a12a8 update readme 2025-07-19 18:08:42 +10:00
VictoriousRaptor
81ae8a4687 Only assign when value is different 2025-07-18 21:33:45 +08:00
VictoriousRaptor
2e99ff96c0
Merge branch 'dev' into code_quality 2025-07-18 21:30:25 +08:00
VictoriousRaptor
5d0bf8f882 Update spell check 2025-07-18 21:22:29 +08:00
VictoriousRaptor
b03dae638f Remove redundent save settings call 2025-07-18 21:17:48 +08:00
VictoriousRaptor
976c30e0d2 Update labels on first get 2025-07-18 21:07:53 +08:00
Jack251970
c4090bbfe1 Use ScrollViewer for max height 2025-07-18 15:29:52 +08:00
Jack251970
f562e159e8 Add update dialog 2025-07-18 15:26:07 +08:00
Jack251970
6c2f457e0a Fix update button text 2025-07-18 14:32:15 +08:00
Jack251970
5f2dd1a6a2 Change plugin update check function 2025-07-18 14:22:05 +08:00
Jack Ye
9b8c1c42c9
Merge branch 'dev' into net9 2025-07-18 09:50:02 +08:00
Jack Ye
d400cda33b
Merge pull request #3841 from Flow-Launcher/dependabot/github_actions/check-spelling/check-spelling-0.0.25
Bump check-spelling/check-spelling from 0.0.24 to 0.0.25
2025-07-18 09:49:52 +08:00
Jack251970
d59ed3248e Remove unused pubxml file 2025-07-18 09:45:16 +08:00
Jack251970
d5813ef3fc Update default plugin and dotnet workflows to use 9 2025-07-18 09:42:27 +08:00
dependabot[bot]
78fd3ca2af
Bump check-spelling/check-spelling from 0.0.24 to 0.0.25
---
updated-dependencies:
- dependency-name: check-spelling/check-spelling
  dependency-version: 0.0.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-17 22:30:22 +00:00
VictoriousRaptor
7ca0dba461 Remove unnecessary view model reference 2025-07-18 00:15:46 +08:00
VictoriousRaptor
6eb703ffff
Merge pull request #3832 from Flow-Launcher/tweak-double-pin-pane
Update wording and fix spell check
2025-07-17 20:07:47 +08:00
Jack Ye
09781b1328
Merge pull request #3838 from Flow-Launcher/message_box_ex_vertically_center
Fix message box vertically center issue when message type is not OK
2025-07-17 20:05:07 +08:00
Jack Ye
10eb5ad104
Merge pull request #3839 from Flow-Launcher/explorer_deleting
Add delete confirmation when deleting quick access links & index search excluded paths
2025-07-17 12:23:15 +08:00
Jack251970
3b3fe5201b Add delete confirmation when deleting quick access links & index search excluded paths 2025-07-17 12:05:10 +08:00
Jack251970
890b115b37 Fix message box vertically center issue when message type is not OK 2025-07-17 11:50:56 +08:00
Jack Ye
b924a79ccb
Merge pull request #2798 from Flow-Launcher/prerelease
Semantic Version
2025-07-17 08:46:20 +08:00
Hongtao Zhang
1cce95ef6a update semantic version and revert pre-release 2025-07-16 11:52:42 -07:00
Jack251970
2018956034 Fix label translation issue 2025-07-16 22:02:56 +08:00
VictoriousRaptor
c3dc8e551e
Merge branch 'dev' into code_quality 2025-07-16 21:58:02 +08:00
VictoriousRaptor
d008b391e0 Fix loc error 2025-07-16 21:31:40 +08:00
VictoriousRaptor
30f7ae0d67 Use Localization for Explorer plugin 2025-07-16 21:28:17 +08:00
VictoriousRaptor
8e942c0e21
Merge pull request #3836 from Flow-Launcher/taooceros-patch-1
Update dotnet.yml
2025-07-16 10:23:43 +08:00
VictoriousRaptor
5ed017b026
Revert line_forbidden.patterns 2025-07-16 10:02:58 +08:00
Hongtao Zhang
177da1a4d7 Merge remote-tracking branch 'refs/remotes/origin/dev' into prerelease
# Conflicts:
#	Flow.Launcher.Core/Updater.cs
2025-07-15 16:41:37 -07:00
Kevin Zhang
363c0fb2a0
Update dotnet.yml 2025-07-15 18:11:16 -05:00
Hongtao Zhang
e75edcaeb8 Merge remote-tracking branch 'origin/net9' into net9 2025-07-15 15:56:19 -07:00
Hongtao Zhang
e96a4cf59d Merge remote-tracking branch 'origin/dev' into net9
# Conflicts:
#	Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
#	Flow.Launcher/Flow.Launcher.csproj
2025-07-15 15:52:34 -07:00
VictoriousRaptor
e116668ef9 Rename file 2025-07-15 23:57:21 +08:00
VictoriousRaptor
3bf1887362 Intoduce dependency 2025-07-15 23:56:03 +08:00
Jack Ye
3e1f962acf
Merge pull request #3833 from WayneFerdon/dev
Add Empty Query Returning & Change ThemeSelector Keyword with ActionKeyword for Sys Plugin
2025-07-15 22:09:19 +08:00
Jack251970
539a852363 Improve logic 2025-07-15 21:59:40 +08:00
Jack251970
aece803905 Simplify logic 2025-07-15 21:50:41 +08:00
WayneFerdon
47089bdaab
Merge branch 'dev' into dev 2025-07-15 21:32:11 +08:00
WayneFerdon
a34b8f2630 [Plugin.Sys Enhancement] Support returning all usable commands while query is empty; ChangeQuery by ThemeSelector Action with ActionKeyword at the front as well 2025-07-15 21:22:19 +08:00
Jeremy Wu
fa6301b2de
Merge pull request #3827 from Flow-Launcher/plugin_update
Add Support for Automatically Checking Plugin Updates
2025-07-15 22:51:53 +10:00
Jeremy Wu
37d6cea2d3
fix typo 2025-07-15 22:10:07 +10:00
VictoriousRaptor
e3e8eff5c9 Fix EOF newline 2025-07-15 20:06:33 +08:00
VictoriousRaptor
07415913ed Add word 2025-07-15 20:05:03 +08:00
VictoriousRaptor
34238051cf Update spelling patterns to support optional spaces in Pinyin matching 2025-07-15 20:03:17 +08:00
Jack Ye
77a7c6ec75
Merge branch 'dev' into plugin_update 2025-07-15 19:58:49 +08:00
Jack Ye
a858aa8f55
Update auto update desc
Co-authored-by: Jeremy Wu <jeremy24wu@gmail.com>
2025-07-15 19:58:12 +08:00
VictoriousRaptor
af50c7bdc3 Add double pinyin schemas to patterns
They are not well formed English words so can be rejected by built-in checks. Use regex as a workaround.
2025-07-15 19:57:46 +08:00
VictoriousRaptor
1e84f0f604
Merge branch 'test-spellcheck-0024' into tweak-double-pin-pane 2025-07-15 19:18:23 +08:00
VictoriousRaptor
ab0e664073 Use a stable version 2025-07-15 19:16:59 +08:00
VictoriousRaptor
e08b731548
Disable line_forbidden.patterns 2025-07-15 00:10:44 +08:00
VictoriousRaptor
c15ff61f92
Use stable version 2025-07-14 22:19:34 +08:00
VictoriousRaptor
dae16b9b8d Try to fix false spell check alarms by using stable version 2025-07-14 21:55:34 +08:00
VictoriousRaptor
f3bca63232 Update wording 2025-07-14 21:48:24 +08:00
VictoriousRaptor
50e570eb36
Merge pull request #3831 from Flow-Launcher/tweak-double-pin-pane
Hide double pin card when use pinyin is false
2025-07-14 21:43:43 +08:00
VictoriousRaptor
970aa5eefe Fix typo 2025-07-14 21:24:11 +08:00
VictoriousRaptor
7cc0d232be
Merge branch 'dev' into code_quality 2025-07-14 21:23:24 +08:00
VictoriousRaptor
50be508a8b Update spell check 2025-07-14 21:22:08 +08:00
VictoriousRaptor
fd4efe009c Hide double pin card when use pinyin is false 2025-07-14 21:09:21 +08:00
Jack Ye
207355e41d
Merge pull request #3830 from Flow-Launcher/double_pin_rework
Reload on all settings change
2025-07-14 19:47:23 +08:00
Jack251970
8c56c0bddf Fix logic 2025-07-14 19:30:16 +08:00
Jack251970
6317d0eec6 Reload on all settings change 2025-07-14 19:29:12 +08:00
Jack Ye
eb81f7174f
Merge pull request #3829 from Flow-Launcher/setting_panel_design
Change double pinyin panel design
2025-07-14 17:08:46 +08:00
Jack251970
ed5749543a Replace dynamic type with a strongly-typed model 2025-07-14 16:56:12 +08:00
Jack251970
05e01ab9ac Change double pinyin panel design 2025-07-14 16:49:04 +08:00
Jack251970
44fbc6eed5 Add auto update subtitle 2025-07-14 16:35:48 +08:00
Jack251970
69d5e33150 Show message box with button instead 2025-07-14 16:31:09 +08:00
Jack251970
b5baf3ebea Rename function name 2025-07-14 16:24:46 +08:00
Jack251970
b393d36186 Add check plugin update button 2025-07-14 16:24:09 +08:00
Jack251970
6eb52b8961 Check plugin updates in the background 2025-07-14 16:18:22 +08:00
Jack251970
6ec659e3ef Update string resource 2025-07-14 16:14:13 +08:00
Jack251970
c0c7546cfc Add auto plugin update setting & UI 2025-07-14 15:48:15 +08:00
Jack251970
79f81a6274 Improve code quality 2025-07-14 15:35:23 +08:00
VictoriousRaptor
42b2fd17a9
Merge pull request #3825 from Flow-Launcher/double-pin
Improve performance and readablility of Pinyin Alphabet
2025-07-14 10:42:33 +08:00
VictoriousRaptor
bdf5ccd3a9
Add word 2025-07-14 09:28:52 +08:00
Jack251970
88a6e59f46 Fix test project field issue 2025-07-14 09:19:46 +08:00
Jack251970
d7d3549c82 Improve code quality 2025-07-14 09:17:32 +08:00
Jack251970
071b75bcb5 Improve code quality 2025-07-14 09:12:14 +08:00
Jack Ye
fc6ff5a7a0
Merge branch 'dev' into code_quality 2025-07-14 09:09:09 +08:00
Jack251970
19f37f4c3a Add code comments 2025-07-14 09:01:59 +08:00
dcog989
78b4c7db85 Fix BrowserBookmark '100% CPU' issue
# Fix BrowserBookmark plugin locking threads at 100% CPU

The following files were modified to implement this fix:

1. /Flow.Launcher.Plugin.BrowserBookmark.csproj
    - packages SkiaSharp and Svg.Skia added to output WEBP

2. /Helper/FaviconHelper.cs
    - new method, TryConvertToWebp, added to take any image data and encode to WEBP

3. /FirefoxBookmarkLoader.cs
    - LoadFaviconsFromDb to use the new helper which ensures only safe WEBP files are used by the UI, which resolves the CPU-locking. It was GZIPped SVGs that were causing the thread / CPU lock.
2025-07-14 00:43:39 +01:00
VictoriousRaptor
efcce81536
Merge branch 'dev' into double-pin 2025-07-13 23:39:44 +08:00
VictoriousRaptor
2316c36a05 Improve performance and readablility 2025-07-13 23:32:53 +08:00
Jeremy Wu
3cdf9197b9
Merge release v1.20.2 back to dev (#3823) 2025-07-13 23:11:06 +08:00
Jeremy Wu
354e5ecf99
Merge pull request #2427 from Flow-Launcher/double-pin
Double pinyin query
2025-07-14 00:35:57 +10:00
Jeremy Wu
2e0dd1cfa1
Release 1.20.2 | Plugin 4.7.0 (#3820) 2025-07-13 17:18:34 +03:00
Jeremy Wu
df9be30394
Merge pull request #3822 from Flow-Launcher/fix_milestone_string_workflow
Fix release PR milestone filter output print
2025-07-14 00:10:27 +10:00
Jeremy Wu
3b1fe2119c fix milestone filter print 2025-07-13 13:33:28 +00:00
Jeremy Wu
a6a28ce8b1
Merge pull request #3821 from Flow-Launcher/fix-uwp-package-monitoring
Fix the issue that UWP changes that can't be monitored (#2345)
2025-07-13 23:32:20 +10:00
Jack251970
44d9eb8556 Change default to false BEFORE RELEASE 2025-07-13 20:58:26 +08:00
Jack251970
43f7cecaff Improve code quality 2025-07-13 20:58:15 +08:00
VictoriousRaptor
2eeb097194
Use lazy init after version check
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-13 20:57:36 +08:00
Jeremy Wu
3159e67c9d
New Crowdin updates (#3807) 2025-07-13 15:56:34 +03:00
VictoriousRaptor
bb946b707a Fix the issue that UWP changes that can't be monitored (#2345)
相关工作项: #2337
2025-07-13 20:54:12 +08:00
VictoriousRaptor
27002c5035
Update doc 2025-07-13 19:48:49 +08:00
VictoriousRaptor
50de9ba6d6
Merge branch 'dev' into double-pin 2025-07-13 19:46:55 +08:00
VictoriousRaptor
4c560210cd More specific exception types for better error handling 2025-07-13 19:37:42 +08:00
VictoriousRaptor
5cd7ae72f4 Fix typo 2025-07-13 19:37:42 +08:00
VictoriousRaptor
3f1862775e
Fix zero boundary condition in MapToOriginalIndex
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-07-13 19:33:20 +08:00
Jeremy Wu
dd1f66ea01
Merge pull request #3758 from Flow-Launcher/release_workflow_milestone_from_pr
release_pr GitHub workflow automatically get milestone from release PR
2025-07-13 21:10:35 +10:00
VictoriousRaptor
130202108c Add UI for double pinyin options 2025-07-13 19:09:40 +08:00
VictoriousRaptor
ebcd7d5915 Fix ShouldTranslate() logic
- Check settings or it won't work  as expected
2025-07-13 19:01:59 +08:00
VictoriousRaptor
fdbb183064 Remove readonly to reload correctly 2025-07-13 19:00:31 +08:00
Jeremy Wu
a8e6ead59f fix get milestone title 2025-07-13 10:40:11 +00:00
Jack Ye
f387eba946
Merge pull request #3819 from Flow-Launcher/home_page_update
Fix Home Page Update Issue
2025-07-13 18:23:03 +08:00
Jack251970
67ec700caf Use one way binding for modifiers 2025-07-13 18:03:14 +08:00
VictoriousRaptor
d537ce22f8 Fix init issue 2025-07-13 16:08:28 +08:00
VictoriousRaptor
d9e89ad610 Refactor TranslationMapping class
- Always add index to mapping rather than only Chinese characters
- Simplify mapping algorithm
- Add unit test for TranslationMapping
2025-07-13 16:07:14 +08:00
VictoriousRaptor
100f753e9b Fix ShouldTranslate() logic 2025-07-13 16:07:14 +08:00
Jack251970
b16cd145c1 Remove unused property 2025-07-13 10:59:34 +08:00
Jack251970
ed4fdb7561 Update result modifiers when OpenResultModifiers is changed 2025-07-13 10:55:13 +08:00
Jack251970
c5373f6abf Use one way binding for showing result hotkey 2025-07-13 10:51:03 +08:00
Jack Ye
b3d2fea6d8
Merge pull request #3806 from Flow-Launcher/nuget_package_document
Generate documents for NuGet package
2025-07-12 08:04:08 +08:00
Jack Ye
b60cca42ce
Merge pull request #3816 from Flow-Launcher/clipboard_invalid
Use try-catch for query text box paste
2025-07-12 07:57:27 +08:00
Jack251970
1a8227a939 Use try-catch for query text box paste 2025-07-11 22:24:05 +08:00
Jeremy Wu
d0c733b1b3
Merge pull request #3810 from Flow-Launcher/quick_access_link_type
Fix Quick Access Link Type Fetching Issue
2025-07-10 21:51:35 +10:00
Jeremy Wu
48d08984ec
Merge pull request #3803 from Flow-Launcher/CustomQueryHotkeySetting_refactor
Refactor CustomQueryHotkeySetting control
2025-07-10 21:45:07 +10:00
Jeremy Wu
7a031e384c
Merge pull request #3811 from Flow-Launcher/refresh_home_page
Update home page when language changes
2025-07-10 21:40:27 +10:00
Jack251970
4d5a8ccb80 Fix typos 2025-07-10 13:27:04 +08:00
Jack251970
e36d5d8ce7 Fix spelling 2025-07-10 13:24:07 +08:00
Jack251970
b1e352dd54 Update home page when language changes 2025-07-09 11:12:46 +08:00
Jack251970
fe3babd4fc Log error when fail to fetch type 2025-07-08 09:50:12 +08:00
Jack251970
bad7db69dc Improve code quality 2025-07-08 09:45:54 +08:00
Jack251970
2856da83c1 Fix quick access link type fetching issue 2025-07-08 09:44:17 +08:00
Jeremy Wu
0f510b153c
Merge pull request #3572 from Flow-Launcher/plugin_store_item_vm_null
Fix separation of Plugin Store and Plugins Manager plugin
2025-07-07 22:17:02 +10:00
Jeremy
236bff1c10 fix spelling 2025-07-07 21:57:03 +10:00
Jeremy
a63c8b036b updates to method summary and correct spell check errors 2025-07-07 21:35:12 +10:00
Jack Ye
727390db4c
Merge pull request #3805 from OrigamiDev-Pete/dev
Use System.Version to compare versions
2025-07-07 14:09:57 +08:00
Jack251970
988a3bc09e Do not throw exception & Fix blank lines 2025-07-07 14:00:07 +08:00
Jack251970
e391965acb Generate documents for NuGet package 2025-07-07 13:46:44 +08:00
Peter de Vroom
770eec5296 Use System.Version to compare versions
Add error checking
2025-07-07 14:07:47 +10:00
Jack251970
eb2b8d06a1 Refactor CustomQueryHotkeySetting control 2025-07-06 21:15:00 +08:00
Jack251970
7c41a37daa Add blank lines 2025-07-06 21:07:25 +08:00
Jack251970
5c2127ec52 Add constructor for CustomPluginHotkey 2025-07-06 21:06:54 +08:00
Jack251970
edb145091d Fix build issue 2025-07-06 20:12:26 +08:00
Jack Ye
c23eb505d1
Merge branch 'dev' into plugin_store_item_vm_null 2025-07-06 20:06:28 +08:00
Jack Ye
4fcc12d717
Merge pull request #3795 from Flow-Launcher/check_plugin_modified
Check plugin modified state for more lists and improve code quality
2025-07-06 20:01:48 +08:00
Jack Ye
166591002c
Merge pull request #3802 from Flow-Launcher/action_keyword_check
Remove plugins from global & non-global plugins when they are removed from settings
2025-07-06 19:57:50 +08:00
Jack Ye
63aca6ea37
Fix possible invalid operation
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-06 16:54:04 +08:00
Jack251970
53174091d6 Use remove where for code quality 2025-07-06 16:52:26 +08:00
Jack251970
052bbb9d75 Fix format 2025-07-06 16:49:17 +08:00
Jack251970
b85b2ec30a Remove plugins from global & non-global plugins when they are removed from settings 2025-07-06 16:45:44 +08:00
Jack251970
c33f72db7e FIx format 2025-07-06 16:36:56 +08:00
Jeremy Wu
1e9eaf5db8
Merge pull request #3798 from Flow-Launcher/win32_dark_mode
Enable dark mode if the system is in dark mode for Win32 windows
2025-07-06 17:49:51 +10:00
Jeremy Wu
a6c038ff5a
Merge branch 'dev' into check_plugin_modified 2025-07-06 16:55:08 +10:00
Jack251970
c71da5413a Support for color scheme change 2025-07-06 09:08:20 +08:00
Jack251970
15d979bc2c Enable Win32 dark mode if the system is in dark mode before creating all windows 2025-07-05 23:05:49 +08:00
Jack Ye
a0dff3fbc1
Merge branch 'dev' into plugin_store_item_vm_null 2025-07-05 17:19:54 +08:00
Jack Ye
8e6eaf728d
Merge pull request #3796 from Flow-Launcher/program_empty_query
Return results when query is empty for Program
2025-07-05 16:07:26 +08:00
Jack Ye
aedfbfc632
Merge pull request #3797 from Flow-Launcher/theme_mode_api
Add Theme mode API Function
2025-07-05 15:37:14 +08:00
Jack251970
d1a5cc8e84 Add theme mode api functions 2025-07-05 13:35:28 +08:00
Jack251970
d7d17a93cd Use ActualApplicationThemeChanged in MainWindow 2025-07-05 13:35:17 +08:00
Jack251970
5b2b272f76 Add ActualApplicationThemeChanged in MainViewModel 2025-07-05 13:35:02 +08:00
Jack251970
80b7775412 Return results when query is empty for program 2025-07-04 10:50:50 +08:00
Jack Ye
446c61fac6
Use concurrent bag for multiple thread support
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-03 21:01:09 +08:00
Jack251970
c06e6d1a6b Check modified state when updating plugins 2025-07-03 20:53:15 +08:00
Jack251970
d3f5274b24 Make GetPluginsForInterface private & Code quality 2025-07-03 20:45:20 +08:00
Jeremy Wu
b440aaab37
New Crowdin updates (#3730) 2025-07-03 15:01:52 +03:00
Jeremy
ae206c35a5 update Plugin Store & Plugins Manager texts 2025-07-03 21:53:39 +10:00
Jeremy Wu
e1f9d60eb9
Merge branch 'dev' into plugin_store_item_vm_null 2025-07-03 21:23:27 +10:00
Jack Ye
352c0f875d
Merge pull request #3791 from Flow-Launcher/modified_plugins
Check if plugin is modified before querying
2025-07-01 19:49:05 +08:00
Jack251970
d7b13276a2 Check if plugin is modified before querying 2025-07-01 13:35:44 +08:00
Jack251970
650a156736 Improve strings 2025-07-01 13:21:48 +08:00
Jack251970
5bfdc583b1 Change the notification message title about plugin already installed/uninstalled from 'Fail to install ' to ' is already installed'. Do the same for uninstalling notification title. 2025-07-01 13:18:26 +08:00
Jack251970
07947c5e1e Fix an issue that pm install/uninstall same plugin without restart says correct message but another message also pops up to say it's successfully installed. It should only one pop up notification to say that the plugin already installed/uninstalled 2025-07-01 12:34:48 +08:00
Jack251970
71043be078 Fix string format issue 2025-07-01 12:00:43 +08:00
Jack251970
2a4bd508c1 Resolve conflicts 2025-07-01 09:06:58 +08:00
Jack251970
ce6c2cb1b2 Add return value for api functions 2025-07-01 09:05:21 +08:00
Jack251970
a8bc55dcb5 Add return for UninstallPluginAsync 2025-07-01 08:59:32 +08:00
Jack251970
1bb7286920 Show error message instead of message 2025-07-01 08:57:32 +08:00
Jeremy Wu
4e4b59ebda
Merge branch 'dev' into plugin_store_item_vm_null 2025-07-01 09:28:34 +10:00
Jack Ye
09883130ce
Merge pull request #3786 from Flow-Launcher/removePluginSettings
Fix Plugin Uninstallation Issue
2025-07-01 07:28:20 +08:00
Jack251970
3fc0abfbab Fix removePluginSettings parameter pass issue 2025-06-30 20:26:14 +08:00
Jack Ye
e965b56824
Merge pull request #3785 from Flow-Launcher/row_issue
Fix grid row issue
2025-06-30 14:55:52 +08:00
Jack251970
0c1fcad06f Fix grid row issue 2025-06-30 14:39:29 +08:00
Jack251970
6318bbe187 Fix an issue that pm install/uninstall same plugin without restart says correct message but another message also pops up to say it's successfully installed 2025-06-30 13:16:59 +08:00
Jack251970
01e749ac88 Fix an issue that store install/uninstall same plugin without restart shows error message error, should say already installed/uninstalled 2025-06-30 13:07:19 +08:00
Jack251970
5b8b84a34c Fix code comments 2025-06-30 12:58:44 +08:00
Jack251970
ea25a661ee Fix an issue that after uninstalling pm, store no longer fetches plugin until clicking on refresh. 2025-06-30 12:46:41 +08:00
Jack251970
dafb0caac4 Remove unused using 2025-06-30 12:42:09 +08:00
Jack Ye
077a86e1b4
Merge pull request #3784 from Flow-Launcher/explorer_preview_margin
Fix explorer plugin preview margin
2025-06-30 12:28:02 +08:00
Jack251970
0290675e11 Fix explorer plugin preview margin 2025-06-30 12:12:47 +08:00
Jack251970
9e868e7e3f Move plugin installer location 2025-06-29 22:35:14 +08:00
Jack251970
19cb3eaf6a Fix typos 2025-06-29 18:20:47 +08:00
Jack251970
8e6a410cfc Use url host 2025-06-29 17:06:29 +08:00
Jack251970
bdb3616977 Improve string resource 2025-06-29 16:34:18 +08:00
Jack251970
a3a0c59fa3 Check url nullability 2025-06-29 16:29:45 +08:00
Jack251970
3e9e91d71c Fix possible exception when extracting zip file 2025-06-29 16:24:21 +08:00
Jack251970
104b4b2680 Improve code quality 2025-06-29 16:09:06 +08:00
Jack251970
c5dd19ef30 Use Microsoft.Win32.OpenFileDialog instead 2025-06-29 16:03:19 +08:00
Jack251970
135fd03f88 Improve code quality 2025-06-29 15:58:52 +08:00
Jack Ye
64f3738c77
Merge branch 'dev' into double-pin 2025-06-29 15:49:17 +08:00
Jack251970
73a6fb6f65 Move codes to new place 2025-06-29 15:48:08 +08:00
Jack251970
7c3c7680c0 Add type for card elements inside card group 2025-06-29 15:47:46 +08:00
Jack Ye
a9486362a8
Merge branch 'dev' into plugin_store_item_vm_null 2025-06-28 18:27:18 +08:00
Jack Ye
1d2a7bf85a
Merge pull request #3782 from Flow-Launcher/quick_access_link_volume
Support volume type for quick access link
2025-06-28 15:58:49 +08:00
Jack251970
d6310bc4f0 Add quick access result score for folder results 2025-06-28 11:02:44 +08:00
Jack251970
03c1e58922 Support volume type for quick access link 2025-06-28 11:00:56 +08:00
Jeremy Wu
71e975a68c
Update Calculator plugin description (#3781) 2025-06-27 15:58:20 +03:00
DB P
fd9790147b
Merge pull request #3779 from onesounds/FixXamlError
Fix Card Control
2025-06-27 20:47:11 +09:00
Jack251970
bafb5d7b0a Removed RenameFileHotkey 2025-06-27 19:38:36 +08:00
DB P
f464f2a6e7
Merge pull request #3780 from onesounds/250627-AddSeparatorReleaseNote
Add Separator and margin in release note
2025-06-27 20:23:49 +09:00
Jack251970
35a38f6c33 Improve code comments 2025-06-27 19:16:00 +08:00
DB P
9f8582cd7a Add Separator and margin 2025-06-27 19:53:09 +09:00
DB P
c83921f4f9 Add Proxy Card type 2025-06-27 19:11:15 +09:00
DB P
059dcdfc11 Fix Card Error 2025-06-27 19:10:43 +09:00
DB P
c9de2f02f8 Fix Card Error 2025-06-27 18:51:51 +09:00
Jack Ye
b1832983a5
Merge pull request #3774 from TBM13/feature/calculatorHexNumbers
Calculator: Support hex numbers
2025-06-26 19:26:05 +08:00
TBM13
d68964bfa2 Calculator: Support hex numbers 2025-06-26 02:58:24 -03:00
Jack251970
c4cbf941cf Add directory null check 2025-06-23 13:13:45 +08:00
Jack251970
9be2ef0924 Remove unused class 2025-06-23 13:03:45 +08:00
Jack251970
107da050a5 Fix build issue 2025-06-23 13:02:04 +08:00
Jack251970
6143c99454 Remove useless class 2025-06-23 12:47:44 +08:00
Jack251970
68fc103d7f Use trick to get the cache directory path 2025-06-23 12:45:31 +08:00
Jack251970
1b05643b64 Use Flow.Launcher.Localization to improve code quality 2025-06-23 12:38:27 +08:00
Jack251970
e6a38ad6e2 Adjust indent 2025-06-23 12:37:58 +08:00
Jack251970
fe7985d564 Use Flow.Launcher.Localization to improve code quality 2025-06-23 12:25:16 +08:00
Jeremy Wu
9b02e1f74e
fix typo
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-21 21:52:54 +10:00
Jeremy Wu
60d5966863 formatting 2025-06-21 11:46:12 +00:00
Jeremy
4e57e3a66c determine milestone from release PR instead of querying milestones 2025-06-21 21:37:12 +10:00
Jeremy
247355e142 simplify assignee by using filtered PR list 2025-06-21 21:34:49 +10:00
VictoriousRaptor
1bc80d5dd9 Fix translated length 2025-06-19 21:52:15 +08:00
VictoriousRaptor
f064a815cf
Merge branch 'dev' into double-pin 2025-06-19 20:06:20 +08:00
VictoriousRaptor
d2dc307bc0 Fix logic of ShouldTranslate() 2025-06-19 20:06:01 +08:00
VictoriousRaptor
b18959514d Add OnPropertyChanged() for double pinyin properties 2025-06-19 19:35:01 +08:00
VictoriousRaptor
64a3aa583f Fix Off-by-one in index mapping when consecutive Chinese chars
Found by code rabbit
2025-06-19 19:29:50 +08:00
Jeremy Wu
40e5c04ca1
Fix false failing spell check rules (#3745) 2025-06-19 14:05:26 +03:00
Jeremy Wu
7487368abc
Merge pull request #3742 from onesounds/250618-AddTimeFormat
Add new time format option "dd MMMM yyyy"
2025-06-18 22:33:26 +10:00
Jeremy Wu
f7d41eb81b
Merge pull request #3743 from Flow-Launcher/update_prerelease_message
Update pre-release build's release message
2025-06-18 21:33:50 +10:00
Jeremy
8a54cc20ce update pre-release's release message 2025-06-18 21:17:04 +10:00
DB P
7b726d86d3 Add new time format option "dd MMMM yyyy" 2025-06-18 18:23:00 +09:00
DB P
368883779e
Merge pull request #3739 from onesounds/250617-Fixquicklink
Improve Quick Access Link Adding UI
2025-06-17 18:10:10 +09:00
Jack251970
bc8e77ae02 Fix format 2025-06-17 17:00:28 +08:00
Jack251970
548435b4fd Improve code quality 2025-06-17 16:58:48 +08:00
Jack251970
b18d0a8bdb Improve code quality 2025-06-17 16:55:23 +08:00
Jack251970
054ee4cdbe Default to folder 2025-06-17 16:37:17 +08:00
Jack251970
b28bb38f52 Add new link if selected link is null 2025-06-17 16:34:43 +08:00
Jack251970
af92bda06d Initialize default access link type 2025-06-17 16:33:25 +08:00
Jack251970
1c652320e6 Get access link type 2025-06-17 16:32:18 +08:00
Jack251970
76972d3849 Use form dialog 2025-06-17 16:31:25 +08:00
Jack251970
12b51eb239 Code quality 2025-06-17 16:31:11 +08:00
DB P
42dc582ec1 Change default value to file 2025-06-17 17:07:02 +09:00
DB P
52da1c97d9 Adjust Button Size 2025-06-17 16:50:42 +09:00
DB P
4dd7833603 UI improvements and file/folder selection feature added
- Updated "Name" and "Path" keys and added new keys in en.xaml.
- Modified QuickAccessLinkSettings.xaml: increased Window height and improved layout using Grid structure.
- Added new UI elements: implemented radio buttons for file/folder selection.
- Added and initialized QuickAccessLinks property in QuickAccessLinkSettings.xaml.cs.
- Added file selection dialog in SelectPath_OnClick method.
- Added IsFileSelected and IsFolderSelected properties to implement selection functionality.
2025-06-17 16:45:44 +09:00
Jack Ye
00a80ae7a7
Merge pull request #3737 from Flow-Launcher/tab_sequence
Fix tab sequence
2025-06-17 13:44:16 +08:00
Jack251970
f3ce6975c9 Fix tab sequence 2025-06-17 13:28:04 +08:00
Jeremy Wu
5e026c0c20
Merge pull request #3566 from Flow-Launcher/winform_language_issue
Fix plugin culture info issue for those that use WinForm
2025-06-16 23:52:42 +10:00
Jack251970
9e24d2cb91 Output log info 2025-06-16 21:31:51 +08:00
Jack251970
be15b836fc Add blank line 2025-06-16 21:12:06 +08:00
Jack251970
30d10bb37f Add try catch for register key check 2025-06-16 21:11:50 +08:00
Jack Ye
43c74f04ac
Merge branch 'dev' into winform_language_issue 2025-06-16 21:10:19 +08:00
Jack Ye
93bc231076
Merge pull request #3734 from Flow-Launcher/shell_plugin_issue
Fix one more whitespace after commands
2025-06-16 20:48:03 +08:00
Jack251970
89214fb58a Improve code quality 2025-06-16 12:38:31 +08:00
Jack251970
07f440768e Fix one more whitespace after commands 2025-06-15 21:21:46 +08:00
Jack Ye
19c81049dd
Merge branch 'dev' into plugin_store_item_vm_null 2025-06-15 17:25:19 +08:00
Jack Ye
58a47aa35d
Merge pull request #3712 from Flow-Launcher/fix_folder_preview
Add preview information back for external preview
2025-06-15 12:09:53 +08:00
Jack Ye
bb76e4e601
Merge pull request #3728 from Flow-Launcher/topmost_description
Improve Topmost Feature
2025-06-15 01:25:59 +08:00
DB P
4c7f966e7c UI improvement and text update
- en.xaml: Updated "showAtTopmost" and tooltip text
- SettingsPaneGeneral.xaml: Added "Hide Notify Icon" card and updated icon
- Adjusted card group margins
2025-06-14 23:55:34 +09:00
DB P
27daffe515 Revert "Modified strings and added BoolNegationConverter"
This reverts commit f30c6fc577.
2025-06-14 23:26:08 +09:00
VictoriousRaptor
4b6231ba8b Extract methods for readability 2025-06-14 22:22:25 +08:00
Jeremy Wu
6a22a16997
Merge pull request #3729 from Flow-Launcher/skip_tag_build
Add AppVeyor skip build on tag
2025-06-14 23:26:44 +10:00
DB P
f30c6fc577 Modified strings and added BoolNegationConverter 2025-06-14 22:19:31 +09:00
Jeremy Wu
d9a30f6450
add skip build on tag 2025-06-14 22:56:08 +10:00
Jack251970
c06fdb9134 Set topmost default value to false 2025-06-14 20:35:09 +08:00
Jack Ye
34f53fd6b0
Merge pull request #3702 from stefanroelofs/dev
Refactor ActionKeywords UI and improve keyword handling
2025-06-14 20:33:12 +08:00
Jack Ye
9367dfc180
Merge pull request #3703 from onesounds/250611-BlockReleaseNoteInDev
Skip release note notification for dev builds
2025-06-14 20:31:27 +08:00
Jeremy Wu
7784fe439c
Merge release v1.20.1 back into dev 2025-06-14 21:25:55 +09:00
Jeremy Wu
7ae91b1af3
Release 1.20.1 | Plugin 4.6.0 (#3706) 2025-06-14 13:45:30 +03:00
Jeremy Wu
566b7d3f0a
Merge pull request #3616 from Flow-Launcher/force_web_url
Add New API to Force Web Url
2025-06-14 20:14:58 +10:00
VictoriousRaptor
4fb2e3d14e Fix translation mapping logic 2025-06-14 16:34:11 +08:00
VictoriousRaptor
3c2581d7f1
Merge branch 'dev' into double-pin 2025-06-14 15:46:42 +08:00
VictoriousRaptor
31cd8949fe Compress json 2025-06-14 14:39:01 +08:00
VictoriousRaptor
818aac715e Use lookup table to translate full pinyin to double pinyin 2025-06-14 14:32:00 +08:00
Jack Ye
e79239eddc
Merge branch 'dev' into plugin_store_item_vm_null 2025-06-13 23:49:16 +08:00
VictoriousRaptor
b31a7408d3 Simple refactor 2025-06-13 23:39:14 +08:00
VictoriousRaptor
fba42fff17 Fix transaltion logic 2025-06-13 23:23:39 +08:00
Jack251970
16fd256fd6 Fix typos 2025-06-13 23:23:39 +08:00
Jack251970
44304f25d6 Change code comments 2025-06-13 23:23:39 +08:00
Jack251970
aaa8e4dc78 Use AddRange 2025-06-13 23:23:39 +08:00
Jack251970
ceb05e8651 Add error handling for directory operation 2025-06-13 23:23:39 +08:00
Jack251970
281e042ab7 Fix IsRelative logic. 2025-06-13 23:23:39 +08:00
Jack251970
8f43de6967 Support Msix FireFox bookmarks 2025-06-13 23:23:39 +08:00
Jeremy
3eb5fead1f remove on tag deployment & change NuGet publish to on master push 2025-06-13 23:23:39 +08:00
VictoriousRaptor
78ffeb8cf2 Delete Flow.Launcher/Properties/Resources.he-IL.resx 2025-06-13 23:23:39 +08:00
VictoriousRaptor
74d54990c9 Delete Flow.Launcher/Properties/Resources.fr-FR.resx 2025-06-13 23:23:39 +08:00
Jack Ye
1415df2003
Merge pull request #3674 from Flow-Launcher/clear_pool
Cache connection and clear pool after all operations to avoid ObjectDisposedException
2025-06-13 21:13:46 +08:00
Jeremy Wu
a77c80f7c1
New Crowdin updates (#3615) 2025-06-13 15:37:36 +03:00
Jeremy
eb7a89292b update OpenUrl method summary 2025-06-13 22:02:34 +10:00
Jack Ye
b7d9f4cedb
Merge pull request #3664 from Flow-Launcher/firefox_msix
Support Loading Msix FireFox Bookmarks & Fix IsRelative logic
2025-06-13 19:33:48 +08:00
Jeremy Wu
222d4919a7
Merge pull request #3705 from Flow-Launcher/remove_ci_tag_trigger
Remove on tag release upload & change NuGet publish to on master push
2025-06-13 21:27:49 +10:00
VictoriousRaptor
a0a20f0599
Merge pull request #3718 from Flow-Launcher/fix-resx
Delete resx files
2025-06-13 12:31:58 +08:00
VictoriousRaptor
cce8d1ac23
Delete Flow.Launcher/Properties/Resources.he-IL.resx 2025-06-13 00:24:40 +08:00
VictoriousRaptor
a34a4790b7
Delete Flow.Launcher/Properties/Resources.fr-FR.resx 2025-06-13 00:21:49 +08:00
Jack251970
b6cca4547c Fix typos 2025-06-12 16:27:48 +08:00
Jack251970
b166b18ab7 Add preview information back for external preview 2025-06-12 14:25:10 +08:00
Jack251970
6f516ee120 Change code comments 2025-06-11 21:14:05 +08:00
Jeremy
3220a0b8f3 remove on tag deployment & change NuGet publish to on master push 2025-06-11 21:10:56 +10:00
DB P
0f94ec673a Skip release notes notification for developer builds 2025-06-11 15:54:38 +09:00
Stefan Roelofs
8e740c8e3a Automatic refactor by XamlStyler extension 2025-06-11 08:44:21 +02:00
DB P
0a6c534228 Adjust Button Size 2025-06-11 15:27:55 +09:00
DB P
393c861660 Adjust Margin 2025-06-11 15:26:15 +09:00
Stefan Roelofs
9d5a58c395 Refactor ActionKeywords UI and improve keyword handling
Updated `ActionKeywords.xaml` to use a new `Grid` layout, enhancing the structure and flexibility of the UI. Removed a couple of layers of nested grids and stackpanels. Changed tbOldActionKeyword from a TextBlock to a TextBox so user can copy the text.
Modified `ActionKeywords.xaml.cs` so the old keywords are already filled in, in the TextBox.
2025-06-11 07:54:07 +02:00
VictoriousRaptor
672649cb9c
Merge branch 'dev' into double-pin 2025-06-11 00:03:09 +08:00
Jack Ye
34f2b7d7f7
Merge pull request #3694 from stefanroelofs/dev
Update Button width for responsive design
2025-06-10 16:45:34 +08:00
Stefan Roelofs
6d2c7b0561 Update Button width for responsive design
Modified Button to use auto width with a minimum width of 100. This change allows the button to grow when keywords do not fit.
2025-06-10 10:04:12 +02:00
Jack Ye
c4402c6fc8
Merge pull request #3541 from Flow-Launcher/topmost
Support Showing Search Window At Topmost
2025-06-10 10:32:37 +08:00
VictoriousRaptor
e07b33c882 Merge branch 'dev' into double-pin 2025-06-09 23:14:53 +08:00
Jack251970
248b098e54 Support installing from local path 2025-06-09 20:28:18 +08:00
Jack Ye
ba28621b84
Merge pull request #3668 from Flow-Launcher/derive_class_save
Fix Derive Class Save Method Calling Issue
2025-06-09 16:27:19 +08:00
DB P
680c6cc51a
Merge pull request #3682 from onesounds/PreviewMedia
Add additional media file extensions for Preview
2025-06-09 17:07:32 +09:00
Jack Ye
60770da446
Merge pull request #3671 from Flow-Launcher/release_notes
Release Notes Window
2025-06-09 16:01:07 +08:00
DB P
b0c3b714dc
Merge pull request #3683 from onesounds/250609-FixLaunguageLayout
Fix issue with multilingual display
2025-06-09 16:59:54 +09:00
Jack Ye
a3f98a69b7
Merge branch 'dev' into PreviewMedia 2025-06-09 15:54:52 +08:00
Jack251970
f75a11c5bc Manually trigger string update 2025-06-09 15:51:43 +08:00
Jack251970
2fd5b388ea Remove unused data context binding 2025-06-09 15:49:56 +08:00
Jack251970
ad24aeffed Use ConcurrentDictionary 2025-06-09 14:48:16 +08:00
Jack251970
77d7b9d077 Do not remove action when called 2025-06-09 14:45:23 +08:00
Jack251970
d787bba9a7 Display message box with button 2025-06-09 14:42:48 +08:00
Jack251970
3d555e7442 Add new api function to show message with button 2025-06-09 14:39:44 +08:00
Jack251970
d313812580 Add legacy message with button 2025-06-09 14:08:01 +08:00
Jack251970
ad14684a98 Format xaml file 2025-06-09 14:07:50 +08:00
Jack251970
bee2cfa1e0 Added error message 2025-06-09 13:13:40 +08:00
Jack Ye
d04f493e57
Merge pull request #3631 from Flow-Launcher/file_tooltip
Support More Information for Files, Folders and Volumes Tooltips & Add Custom Preview Panel Support for Folder Results
2025-06-09 13:00:16 +08:00
DB P
8fac98d0a1 Fix HotkeyDialog Button Size 2025-06-09 12:21:07 +09:00
DB P
219e00334f Adjust Welcome Window Size and text overflow 2025-06-09 12:11:34 +09:00
DB P
3fef743874 Fix Always Preview Binding 2025-06-09 11:48:03 +09:00
DB P
8f10f4a9ee Add additional media file extensions to ResultManager 2025-06-09 11:06:40 +09:00
Jack251970
754533f78b Remove null check 2025-06-08 14:28:37 +08:00
Jack251970
d7b8f85f4a Use FaviconHelper 2025-06-08 12:35:39 +08:00
Jack251970
2ed32b318d Do not use pooling so that we do not need to clear pool 2025-06-08 12:17:47 +08:00
Jack251970
c323646b1a Use AddRange 2025-06-08 12:09:07 +08:00
Jack251970
c8e82cbd09 Cache connection and clear pool after all operations to avoid ObjectDisposedException 2025-06-08 00:14:31 +08:00
Jack251970
e08dea95c8 Adjust margins 2025-06-07 23:40:06 +08:00
Jack251970
4c00ff3169 Fix height issue 2025-06-07 23:39:57 +08:00
Jack251970
3444dcdaee Code quality 2025-06-07 23:24:20 +08:00
Jack251970
94f716fcab Code quality 2025-06-07 23:24:07 +08:00
Jack251970
4436a1efee Support smooth scroll 2025-06-07 23:23:45 +08:00
Jack251970
a9eb17ee50 Make sure horizontally size good 2025-06-07 23:01:54 +08:00
Jack251970
b4d5e57b17 Fix size change issue 2025-06-07 16:47:19 +08:00
Jack251970
88a2088987 Update min width & min height 2025-06-07 16:12:35 +08:00
Jeremy Wu
2a4b4de3ef
Merge pull request #3595 from Flow-Launcher/explorer_plugin_code_quality
Remove obsolete TranslationConverter & project reference in Explorer plugin
2025-06-07 18:10:39 +10:00
Jack251970
52fe90bfb4 Revert "Remove Droplex reference"
This reverts commit cec6f309d8.
2025-06-07 15:55:53 +08:00
Jack251970
cec6f309d8 Remove Droplex reference 2025-06-07 15:54:39 +08:00
Jack Ye
b8afbd7332
Merge pull request #3586 from 01Dri/rename-quick-access-links
Enhancement: Rename Quick Access Links
2025-06-07 14:27:17 +08:00
Jack251970
a6b6076e08 Set IsEdit get only & Add type & Improve code quality 2025-06-07 14:20:21 +08:00
Jack251970
6d206c5a6d Adjust columns 2025-06-07 14:12:22 +08:00
Jack251970
bdbd1e77e8 Remove blank lines 2025-06-07 14:12:06 +08:00
Jack251970
ae9a755013 Explictly add isEdit 2025-06-07 14:07:07 +08:00
Jack251970
9d3d3c80f2 Save when editing 2025-06-07 14:05:48 +08:00
Jack251970
201d3731d9 Add blank line 2025-06-07 14:05:18 +08:00
Jack251970
16371af1ce Mark static 2025-06-07 14:04:21 +08:00
Jack251970
c19e9ad197 Fix format 2025-06-07 14:03:33 +08:00
Jack251970
74d6016695 Revert to original style 2025-06-07 14:02:54 +08:00
Jack251970
fdb0cf508a Fix null check 2025-06-07 14:02:32 +08:00
Jack251970
ab1fe67dd4 Remove command parameter 2025-06-07 14:00:56 +08:00
Jack251970
d429304a26 Improve code quality 2025-06-07 13:54:15 +08:00
Jack251970
fd2952b44d Improve code quality 2025-06-07 13:52:12 +08:00
Jack251970
4218b636fe Add size changed event 2025-06-07 13:46:57 +08:00
Jack251970
29b74624c7 Add setting ui 2025-06-07 13:41:15 +08:00
Jack Ye
37cab1b30c
Merge branch 'dev' into rename-quick-access-links 2025-06-07 13:39:00 +08:00
Jack251970
6693cb13f5 Revert changes in ExplorerSettings.xaml 2025-06-07 13:38:09 +08:00
Jack251970
54c6eaa0cd Add see more uri button 2025-06-07 11:34:06 +08:00
Jack251970
749dcc6ed2 Show release version after update 2025-06-07 11:24:35 +08:00
Jack251970
414db19fa7 Open release notes in about page 2025-06-07 11:21:25 +08:00
Jack251970
458a8b3efb Add mdxaml plugins 2025-06-07 11:11:40 +08:00
Jack251970
eee57e29d0 Improve code quality 2025-06-07 10:34:26 +08:00
Jack251970
84e91d2bdc Fix display issue 2025-06-07 10:14:37 +08:00
Jack251970
394fd1d44c Support light/dark style 2025-06-07 10:13:45 +08:00
Jack251970
1f61d934f8 Follow theme change 2025-06-07 00:12:54 +08:00
Jack251970
1ef8d65dce Adjust size 2025-06-07 00:07:52 +08:00
Jack251970
02496214ea Add progress ring 2025-06-07 00:07:44 +08:00
Jack251970
af0e1180a5 Use loaded event & Add style 2025-06-06 23:52:34 +08:00
Jack251970
c241d21a4a Fix height 2025-06-06 23:51:26 +08:00
Jack251970
7117ba05ee Test release notes window 2025-06-06 23:08:41 +08:00
Jack251970
1f4578c04c Add release notes window 2025-06-06 23:08:32 +08:00
Jack251970
ba35f1269b Add GetString api function 2025-06-06 23:07:01 +08:00
Jack Ye
a02a236ca4
Merge branch 'dev' into explorer_plugin_code_quality 2025-06-06 21:30:54 +08:00
Jack251970
a04829b04e Add setting ui back 2025-06-06 20:14:33 +08:00
Jack251970
475db2bbe8 Add blank line back 2025-06-06 20:09:47 +08:00
Jack Ye
d0c240f50f
Merge branch 'dev' into file_tooltip 2025-06-06 20:09:02 +08:00
Jack251970
82f3e99f0e Revert changes in ExplorerSettings.xaml 2025-06-06 20:08:11 +08:00
DB P
a2ee428d26
Merge pull request #3666 from onesounds/Fix-ExplorerSettingPanel-Dpi
Replace TabControl with Expander in Explorer Plugin Setting Panel
2025-06-06 20:52:17 +09:00
DB P
600137c911 Fix Border 2025-06-06 20:38:37 +09:00
DB P
dc3fada88f Fix Border 2025-06-06 20:38:07 +09:00
DB P
0c474a979f Add Hover Style / Adjust Arrow / Adjust Padding 2025-06-06 20:36:30 +09:00
Jack251970
062cd2ad1d Use ISavable instead of object 2025-06-06 18:38:05 +08:00
Jack251970
baef9ddacd Expose ISaveable interface in derived class to make sure we are calling the new version of Save method 2025-06-06 18:37:17 +08:00
Jack251970
6614771c3d Move initialization to constructor 2025-06-06 17:03:20 +08:00
Jack251970
569cb8bd44 Remove grid & scroll viewer & Remove bottom margin 2025-06-06 16:57:48 +08:00
Jack251970
b29bc75dcb Improve setting panel design 2025-06-06 16:53:31 +08:00
Jack251970
05cd01d1d6 Use loaded event to ensure accessability 2025-06-06 16:20:43 +08:00
Jack251970
c2eca7dc39 Improve code quality 2025-06-06 16:15:26 +08:00
DB P
6cd830c5b5 Fix Font Color 2025-06-06 17:07:57 +09:00
DB P
8c5e0c948f Change Tabcontrol to Expander 2025-06-06 17:05:12 +09:00
Jack251970
f59e2399b9 Add error handling for directory operation 2025-06-06 13:25:43 +08:00
Jack251970
57470a9799 Fix IsRelative logic. 2025-06-06 13:21:51 +08:00
Jack251970
33a5ca845a Support Msix FireFox bookmarks 2025-06-06 13:12:40 +08:00
Jack Ye
9d3888f71d
Merge pull request #3655 from Flow-Launcher/everything_installation_issue_tip
Catch exception when installing Everything
2025-06-06 11:47:27 +08:00
Jack Ye
6c4e9488e6
Merge pull request #3657 from Flow-Launcher/shell_problem
Fix command issue for pswh.exe
2025-06-06 11:39:05 +08:00
Jack Ye
9093e4f8c6
Merge pull request #3658 from Flow-Launcher/short_problem_dialogs
Do not show UI when shortcuts cannot be resolved
2025-06-06 11:33:14 +08:00
Jeremy Wu
dbd5e2c289
Merge pull request #3659 from Flow-Launcher/firefox_index_out_of_range
Check index out of range for FireFox
2025-06-06 13:31:36 +10:00
Jack Ye
407ad03bcb
Merge branch 'dev' into file_tooltip 2025-06-06 09:33:52 +08:00
Jack251970
abe287f47c Add MMM date format 2025-06-06 09:33:34 +08:00
Jack Ye
4c4092d8dc
Merge pull request #3660 from Flow-Launcher/bug_report_prerelease
Add prerelease version checkbox for reporting issues
2025-06-06 00:03:41 +08:00
Jack Ye
71e224a131
Add prerelease version checkbox for reporting issues 2025-06-05 23:05:02 +08:00
Jack251970
79d35bba5f Check index before returning PlacesPath 2025-06-05 22:54:34 +08:00
Jack251970
1697544cb6 Add example code comments 2025-06-05 22:41:21 +08:00
Jeremy Wu
a74a59914b
Add get PR authors to workflow (#3611) 2025-06-05 22:54:28 +10:00
Jack251970
08c8b8c09f Do not show UI when shortcuts cannot be resolved 2025-06-05 19:15:26 +08:00
Jeremy Wu
bf98a731b9
Update default plugin publish to follow production version (#3656) 2025-06-05 13:58:40 +03:00
Jack251970
d7c7ec8f5e Apply for all wt.exe 2025-06-05 18:55:58 +08:00
Jack251970
b2e754dfd5 Fix command issue for pswh.exe 2025-06-05 18:48:26 +08:00
Jack251970
7d5001de18 Catch exception when installing Everything 2025-06-05 18:16:13 +08:00
Jack Ye
36804402bd
Merge pull request #3652 from Flow-Launcher/pinvoke_improvement
Use PInvoke & Improve code comments & Use switch sentences
2025-06-05 16:30:52 +08:00
Jack251970
5fcb166060 Use PInvoke & Improve code comments & Use switch sentences 2025-06-05 16:12:55 +08:00
Jack Ye
ed064fbe24
Merge branch 'dev' into file_tooltip 2025-06-05 12:04:28 +08:00
Jack251970
4c7a4fec1f Add more info tooltip support for volumes 2025-06-05 11:56:21 +08:00
Jack251970
1a95632ddf Handle AggregateException 2025-06-05 11:45:18 +08:00
Jack251970
14ffe3c675 Handle more exceptions 2025-06-05 11:38:08 +08:00
Jack251970
617c62f6c6 Add error log message 2025-06-05 11:25:48 +08:00
Jack251970
19061df586 Use concurrent version 2025-06-05 11:24:20 +08:00
Jack251970
e61151dc43 Improve preview panel performance 2025-06-05 11:18:07 +08:00
Jeremy Wu
9be59385d0
Merge pull request #3606 from onesounds/FixWindowSnap
Fix issues related to window maximization and Snap behavior
2025-06-05 13:09:51 +10:00
Jack Ye
4cd08917cf
Merge branch 'dev' into file_tooltip 2025-06-05 11:01:19 +08:00
Jeremy Wu
86ca41ae73
Merge pull request #3639 from Flow-Launcher/websearch_issue
Fix OpenUri Exception
2025-06-05 13:00:59 +10:00
Jeremy Wu
25a62c8700
fix wording 2025-06-05 12:46:46 +10:00
Jack Ye
a865a3b35d
Merge pull request #3641 from Flow-Launcher/favorite_icons_enabled_disabled
Support Enable / Disable Favicons & Use Concurrent Way to Load Favicons
2025-06-05 10:36:51 +08:00
Jack251970
94b8a66db7 Rename to favicon 2025-06-05 10:21:24 +08:00
Jack251970
69dd038311 No need to convert to array 2025-06-05 10:16:02 +08:00
Jeremy Wu
d87a7ce32b
use favicons instead of favourite icons 2025-06-05 12:15:47 +10:00
Jeremy Wu
76dc35433b
update wording 2025-06-05 12:12:55 +10:00
Jack251970
49a42dd1b4 Improve code quality 2025-06-05 01:31:06 +08:00
Jack251970
f717376a17 Remove dulplicated comments 2025-06-05 01:30:26 +08:00
Jack251970
8bb2b50a4c Use concurrent way to load favorite icons for firefox 2025-06-05 01:13:48 +08:00
Jack251970
d6f40ec488 Use concurrent way to load favorite icons for chromium 2025-06-05 01:12:46 +08:00
Jack251970
0ec38faf1d Add code comments & Improve code quality 2025-06-05 01:12:12 +08:00
Jack251970
72bd4c202a Info favorite icons cost 2025-06-05 00:27:03 +08:00
Jack251970
a761ec880b Check file exists 2025-06-04 23:31:18 +08:00
Jack251970
6d5b95c616 Improve code comments 2025-06-04 23:31:10 +08:00
Jack251970
b68b299cd4 Support enable favorite icons for firefox bookmarks 2025-06-04 23:26:32 +08:00
Jack251970
f982d80ffe Support enable favorite icons for chromiun bookmarks 2025-06-04 23:21:57 +08:00
Jack251970
cef3e8e4bb Make settings internal 2025-06-04 23:18:01 +08:00
Jack251970
fd8222ba80 Organize usings 2025-06-04 23:17:51 +08:00
Jack251970
2f8e5b91b0 Add new setting to enable favorite icons 2025-06-04 23:17:38 +08:00
Jack251970
f6c75c2107 Handle exception from OpenInBrowserTab & OpenInBrowserWindow 2025-06-04 22:29:45 +08:00
Jack251970
4bc34f64e8 Re-throw the exception if we cannot open the URL in the default browser 2025-06-04 22:22:02 +08:00
Jack251970
32ae3a1b67 Improve code quality 2025-06-04 22:21:08 +08:00
Jack Ye
4e4707a9de
Merge branch 'dev' into file_tooltip 2025-06-04 21:53:32 +08:00
Jeremy Wu
524fb6a07e
Merge pull request #3629 from Flow-Launcher/handle_download_exception
Handle download exception
2025-06-04 21:56:21 +10:00
Jack Ye
dae84aef16
Merge pull request #3627 from Flow-Launcher/task_exception
Silent handle TaskSchedulerUnobservedTaskException
2025-06-04 19:50:23 +08:00
Jack251970
f4d6ef371a Fix typos 2025-06-04 18:57:20 +08:00
Jack Ye
ed1d3685fa
Merge branch 'dev' into force_web_url 2025-06-04 18:55:19 +08:00
DB P
9755186195
Merge pull request #3632 from onesounds/FixPendingLine
Add PendingLineStyle in Base.xaml
2025-06-04 18:53:01 +09:00
Jack251970
32b75b4a4f Fix CancellationTokenSource dispose state 2025-06-04 17:44:24 +08:00
Jack Ye
f2a7536298
Use EnumerateFiles instead of GetFiles
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-04 17:42:56 +08:00
DB P
2f9e7e11dc
Merge branch 'dev' into FixPendingLine 2025-06-04 18:36:52 +09:00
Jack251970
edb4d743e9 Add timeout for folder size calculation 2025-06-04 17:36:37 +08:00
Jack251970
b1557dd4af Add try-catch for preview panel operation 2025-06-04 17:32:30 +08:00
DB P
93b3ac4b89 Add PendingLineStyle based on BasePendingLineStyle for improved styling 2025-06-04 18:28:50 +09:00
Jack251970
ce0729923c Add try-catch for tooltip getter 2025-06-04 17:26:33 +08:00
Jack251970
85c038719e Support custom preview panel for folder results 2025-06-04 17:12:53 +08:00
Jack251970
f069ee9094 Show more info in file & folder tooltip 2025-06-04 17:01:46 +08:00
Jack251970
16551396b9 Add ui for new setting 2025-06-04 17:01:15 +08:00
Jack251970
155eecd86f Expose part functions to helper 2025-06-04 16:17:14 +08:00
DB P
93c2b1bf7c
Merge pull request #3614 from Flow-Launcher/crowdin-contribution
Add crowdin contribution tip
2025-06-04 15:22:18 +09:00
DB P
ab3513a51c
Merge branch 'dev' into crowdin-contribution 2025-06-04 15:02:32 +09:00
Jack251970
f60585f609 Use try-catch to handle download issue 2025-06-04 13:44:50 +08:00
Jack251970
8737217513 Show error message 2025-06-04 13:44:10 +08:00
Jack251970
bdd349e242 Improve format 2025-06-04 13:39:11 +08:00
Jack251970
2582a9fa20 Log exception instead of reporting 2025-06-04 13:31:41 +08:00
Jack251970
6b54ca28b0 Silent handle TaskSchedulerUnobservedTaskException 2025-06-04 12:32:57 +08:00
Jack251970
c001041ba8 Fix build issue 2025-06-03 23:08:51 +08:00
Jack251970
54c2cd13f6 Force web url for WebSearch plugin 2025-06-03 23:06:38 +08:00
Jack251970
3b0def8159 Add new api OpenWebUrl 2025-06-03 23:06:24 +08:00
Jack Ye
5017fa462c
Add crowdin contribution tip 2025-06-03 20:56:36 +08:00
Jack Ye
f9e7b82b15
Merge branch 'dev' into plugin_store_item_vm_null 2025-06-03 14:36:17 +08:00
Jack Ye
87c6e3085a
Merge branch 'dev' into winform_language_issue 2025-06-03 14:36:02 +08:00
Jack Ye
2c40d2fe43
Merge branch 'dev' into topmost 2025-06-03 14:35:48 +08:00
Jack251970
5aebf3f281 Remove unnecessary changes 2025-06-02 19:00:50 +08:00
DB P
0ecc01d328 Remove BlockWindowMaximize method from Win32Helper 2025-06-02 19:19:41 +09:00
DB P
556a4c881c Update Comment 2025-06-02 19:16:01 +09:00
DB P
08038dec94 Improve window drag behavior when transitioning from maximized state 2025-06-02 18:33:45 +09:00
DB P
c6ed0bdc2a Adjust window border style for blur themes and update border brush resource 2025-06-02 18:01:09 +09:00
DB P
8baf1d437e Prevent window height updates when exiting snap state and adjust border brush style 2025-06-02 17:44:41 +09:00
DB P
2b4e95e64a Block window maximize and prevent resizing in WndProc 2025-06-02 14:55:59 +09:00
Jack251970
6bf7f00f0a Add unknown source warning setting 2025-06-01 16:28:16 +08:00
Jack Ye
406c29f0d6
Merge branch 'dev' into plugin_store_item_vm_null 2025-06-01 15:55:59 +08:00
Jack251970
4a50eec281 Remove TranslationConverter & project reference in Explorer plugin 2025-05-30 10:59:22 +08:00
Diego Henrique
65eb6484a9
Merge branch 'dev' into rename-quick-access-links 2025-05-27 10:38:37 -03:00
01Dri
29831d61bb AI Review Refactor suggestion 2025-05-25 16:48:20 -03:00
01Dri
cd62e7b5dc uP 2025-05-25 16:11:03 -03:00
01Dri
bc2648c216 Code quality 2025-05-25 16:05:26 -03:00
01Dri
589e37a646 Action QuickAccessLinks 2025-05-25 15:49:25 -03:00
01Dri
fdeec11896 FixLegacyQuickAccessLinkNames 2025-05-25 14:45:44 -03:00
01Dri
a6a544a488 Translations 2025-05-25 14:26:47 -03:00
01Dri
287f3f8a5f Settings 2025-05-25 14:12:15 -03:00
01Dri
4b15add524 Code quality 2025-05-25 04:51:56 -03:00
01Dri
3b425413a2 Edit access link 2025-05-25 04:31:08 -03:00
01Dri
74167a91e3 Strings translations and warning box 2025-05-25 03:04:18 -03:00
01Dri
61aca74096 Separating add commands between QuickAccessLink and IndexSearchExcludedPaths 2025-05-25 02:41:17 -03:00
01Dri
3777e2b6d8 Changing QuickAccessLinks property to static
This change is necessary for quick access settings window
2025-05-25 02:36:55 -03:00
01Dri
179babe313 New window to add/edit quick access link 2025-05-25 02:35:09 -03:00
01Dri
59d5cd62bc AccessLink Refactor 2025-05-25 02:34:48 -03:00
01Dri
5bae202031 Columns - Path and Name 2025-05-25 02:34:24 -03:00
Jack251970
383c0aeffc Improve code quality 2025-05-23 13:41:59 +08:00
Jack251970
6044f87e80 Do not restart on failure 2025-05-22 17:41:13 +08:00
Jack251970
c6c7ff882e Handle default 2025-05-22 17:40:26 +08:00
Jack Ye
76736b7850
Fix typo
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-22 17:37:27 +08:00
Jack251970
949344a51e Add internal model for plugin management 2025-05-22 17:32:33 +08:00
Jack251970
b0997449c1 Handle CultureNotFoundException 2025-05-21 14:38:15 +08:00
Jack251970
f7f52e269a Set culture info before creating application 2025-05-21 14:31:09 +08:00
Jack Ye
9e85d2e364
Merge branch 'dev' into topmost 2025-05-14 20:26:13 +08:00
Jack251970
b636f253e6 Add blank line 2025-05-13 22:52:06 +08:00
Jack251970
b8f743eb4c Add ui in general setting page 2025-05-13 22:06:04 +08:00
Jack251970
4e88ce3f48 Set ShowAtTopmost default to true 2025-05-13 22:01:36 +08:00
Jack251970
587ab629aa Support changing ShowAtTopmost 2025-05-13 21:54:03 +08:00
Jack251970
8aa36f38c8 Add ShowAtTopmost in settings 2025-05-13 21:50:13 +08:00
Jack Ye
2cf6f81729
Merge branch 'dev' into double-pin 2025-04-10 09:58:33 +08:00
Jack251970
f5fd6b569c Use ReadOnlySpan instead 2025-04-10 09:56:27 +08:00
Jack251970
4b7db3cbd6 Make function static 2025-04-10 09:36:47 +08:00
Jack Ye
3f45c6a94a
Merge branch 'dev' into double-pin 2025-04-09 20:06:37 +08:00
Jack251970
1f458d3b56 Fix typos & Code quality 2025-04-09 20:06:17 +08:00
Jack251970
5be732d533 Use var when neccessary 2025-04-05 23:04:12 +08:00
Jack251970
a8a305fce0 Improve code quality 2025-04-05 23:02:28 +08:00
Jack251970
9e8a950580 Fix build issue & Improve code quality 2025-04-05 23:01:09 +08:00
Jack Ye
c8a9e5e947
Merge branch 'dev' into double-pin 2025-04-05 22:03:08 +08:00
Kevin Zhang
d2229f69b6
Update README.md 2025-03-25 22:35:20 -05:00
dependabot[bot]
02662a390d
Bump System.Data.OleDb from 8.0.1 to 9.0.3
Bumps [System.Data.OleDb](https://github.com/dotnet/runtime) from 8.0.1 to 9.0.3.
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v8.0.1...v9.0.3)

---
updated-dependencies:
- dependency-name: System.Data.OleDb
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-11 22:13:25 +00:00
Hongtao Zhang
e7ec5e3dd4 remove space from the font name 2025-02-28 23:40:23 +08:00
Hongtao Zhang
a3a819cacb update packages.lock.json 2025-02-28 23:08:19 +08:00
Kevin Zhang
9df53637d1
Merge branch 'dev' into net9 2025-02-28 22:55:43 +08:00
Hongtao Zhang
d57eb6c8d4 add packages.lock.json 2025-02-28 02:27:29 -06:00
Hongtao Zhang
555188058d restore package with lock file 2025-02-28 02:23:45 -06:00
Hongtao Zhang
debd4159f1 update system.drawing.common 2025-02-28 02:15:52 -06:00
Hongtao Zhang
684ff1080b add ignored publish profile file 2025-02-26 23:12:29 +08:00
Hongtao Zhang
563cb74997 add ignored publish profile 2025-02-26 22:57:31 +08:00
Hongtao Zhang
bfa1c91d33 fix typo 2025-02-26 22:46:08 +08:00
Hongtao Zhang
865c865942 update to .net 9 2025-02-26 22:37:41 +08:00
Hongtao Zhang
dd29b4ad44 velopack prepare 2024-06-22 19:31:47 -05:00
VictoriousRaptor
b816d1b866 Remove unused key in pinyin alphabet 2024-06-02 15:21:04 +08:00
VictoriousRaptor
b10a6e19df Capitalize first letter 2024-06-02 15:18:24 +08:00
VictoriousRaptor
f673000d67 Fix ShouldTranslate() 2024-06-02 15:00:45 +08:00
VictoriousRaptor
12c4e37a95 Developing 2024-06-02 15:00:45 +08:00
VictoriousRaptor
a2efa11699 Merge DoublePinAlphabet logic 2024-06-02 15:00:45 +08:00
VictoriousRaptor
6807afbe6d Remove unused alphabet arg in PublicAPIInstance 2024-06-02 15:00:45 +08:00
VictoriousRaptor
b1cb852673 Extract classes 2024-06-02 15:00:45 +08:00
VictoriousRaptor
46d49d8fdf Only translate when string is double pinyin 2024-06-02 15:00:45 +08:00
VictoriousRaptor
99ff3b2ec5 Fix wrong condition 2024-06-02 15:00:45 +08:00
VictoriousRaptor
e5285b1992 Temp: compatibility with full pinyin option 2024-06-02 15:00:45 +08:00
VictoriousRaptor
f6ae71a99f Only convert to double pinyin when meeting Chinese 2024-06-02 15:00:45 +08:00
VictoriousRaptor
fb6635344b Test double pinyin 2024-06-02 15:00:45 +08:00
VictoriousRaptor
3abd05f6b3 Implement double pinyin 2024-06-02 15:00:45 +08:00
574 changed files with 32447 additions and 16934 deletions

View file

@ -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:

View file

@ -6,3 +6,6 @@ runcount
Firefox
Português
Português (Brasil)
favicons
moz
workaround

View file

@ -1,3 +1,5 @@
# This file should contain names of products, companies, or individuals that aren't in a standard dictionary (e.g., GitHub, Keptn, VSCode).
crowdin
DWM
workflows
@ -34,7 +36,6 @@ mscorlib
pythonw
dotnet
winget
jjw24
wolframalpha
gmail
duckduckgo
@ -49,7 +50,6 @@ srchadmin
EWX
dlgtext
CMD
appref-ms
appref
TSource
runas
@ -57,7 +57,6 @@ dpi
popup
ptr
pluginindicator
TobiasSekan
img
resx
bak
@ -68,9 +67,6 @@ dlg
ddd
dddd
clearlogfolder
ACCENT_ENABLE_TRANSPARENTGRADIENT
ACCENT_ENABLE_BLURBEHIND
WCA_ACCENT_POLICY
HGlobal
dopusrt
firefox
@ -91,22 +87,24 @@ keyevent
KListener
requery
vkcode
čeština
Polski
Srpski
Português
Português (Brasil)
Italiano
Slovenský
quicklook
Tiếng Việt
Droplex
Preinstalled
errormetadatafile
noresult
pluginsmanager
alreadyexists
JsonRPC
JsonRPCV2
Softpedia
img
Reloadable
metadatas
WMP
VSTHRD
CJK
Msix
dummyprofile
browserbookmark
copyurl

View file

@ -1,4 +1,6 @@
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns
# This file should contain strings that contain a mix of letters and numbers, or specific symbols
# Questionably acceptable forms of `in to`
# Personally, I prefer `log into`, but people object
@ -121,3 +123,28 @@
# version suffix <word>v#
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
# Non-English
[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*
\bjjw24\b
\bappref-ms\b
\bTobiasSekan\b
\bJsonRPC\b
\bJsonRPCV2\b
\bTiếng Việt\b
\bPortuguês (Brasil)\b
\bčeština\b
\bPortuguês\b
\bIoc\b
\bXiao\s*He\b
\bZi\s*Ran\s*Ma\b
\bWei\s*Ruan\b
\bZhi\s*Neng\s*ABC\b
\bZi\s*Guang\s*Pin\s*Yin\b
\bPin\s*Yin\s*Jia\s*Jia\b
\bXing\s*Kong\s*Jian\s*Dao\b
\bDa\s*Niu\b
\bXiao\s*Lang\b
\b[Ss]ettings [Ss]ettings\b

View file

@ -1,17 +1,18 @@
from os import getenv
from typing import Optional
import requests
def get_github_prs(token: str, owner: str, repo: str, label: str = "", state: str = "all") -> list[dict]:
"""
Fetches pull requests from a GitHub repository that match a given milestone and label.
Fetches pull requests from a GitHub repository that match a given label and state.
Args:
token (str): GitHub token.
owner (str): The owner of the repository.
repo (str): The name of the repository.
label (str): The label name.
label (str): The label name. Filter is not applied when empty string.
state (str): State of PR, e.g. open, closed, all
Returns:
@ -23,39 +24,10 @@ def get_github_prs(token: str, owner: str, repo: str, label: str = "", state: st
"Accept": "application/vnd.github.v3+json",
}
milestone_id = None
milestone_url = f"https://api.github.com/repos/{owner}/{repo}/milestones"
params = {"state": "open"}
try:
response = requests.get(milestone_url, headers=headers, params=params)
response.raise_for_status()
milestones = response.json()
if len(milestones) > 2:
print("More than two milestones found, unable to determine the milestone required.")
exit(1)
# milestones.pop()
for ms in milestones:
if ms["title"] != "Future":
milestone_id = ms["number"]
print(f"Gathering PRs with milestone {ms['title']}...")
break
if not milestone_id:
print(f"No suitable milestone found in repository '{owner}/{repo}'.")
exit(1)
except requests.exceptions.RequestException as e:
print(f"Error fetching milestones: {e}")
exit(1)
# This endpoint allows filtering by milestone and label. A PR in GH's perspective is a type of issue.
# This endpoint allows filtering by label(and milestone). A PR in GH's perspective is a type of issue.
prs_url = f"https://api.github.com/repos/{owner}/{repo}/issues"
params = {
"state": state,
"milestone": milestone_id,
"labels": label,
"per_page": 100,
}
@ -83,14 +55,18 @@ def get_github_prs(token: str, owner: str, repo: str, label: str = "", state: st
return all_prs
def get_prs(pull_request_items: list[dict], label: str = "", state: str = "all") -> list[dict]:
def get_prs(
pull_request_items: list[dict], label: str = "", state: str = "all", milestone_title: Optional[str] = None
) -> list[dict]:
"""
Returns a list of pull requests after applying the label and state filters.
Args:
pull_request_items (list[dict]): List of PR items.
label (str): The label name.
label (str): The label name. Filter is not applied when empty string.
state (str): State of PR, e.g. open, closed, all
milestone_title (Optional[str]): The milestone title to filter by. This is the milestone number you created
in GitHub, e.g. '1.20.0'. If None, no milestone filtering is applied.
Returns:
list: A list of dictionaries, where each dictionary represents a pull request.
@ -99,15 +75,47 @@ def get_prs(pull_request_items: list[dict], label: str = "", state: str = "all")
pr_list = []
count = 0
for pr in pull_request_items:
if pr["state"] == state and [item for item in pr["labels"] if item["name"] == label]:
pr_list.append(pr)
count += 1
if state not in [pr["state"], "all"]:
continue
print(f"Found {count} PRs with {label if label else 'no'} label and state as {state}")
if label and not [item for item in pr["labels"] if item["name"] == label]:
continue
if milestone_title:
if pr["milestone"] is None or pr["milestone"]["title"] != milestone_title:
continue
pr_list.append(pr)
count += 1
print(
f"Found {count} PRs with {label if label else 'no filter on'} label, state as {state}, and milestone {milestone_title if milestone_title else "any"}"
)
return pr_list
def get_prs_assignees(pull_request_items: list[dict]) -> list[str]:
"""
Returns a list of pull request assignees, excludes jjw24.
Args:
pull_request_items (list[dict]): List of PR items to get the assignees from.
Returns:
list: A list of strs, where each string is an assignee name. List is not distinct, so can contain
duplicate names.
Returns an empty list if none are found.
"""
assignee_list = []
for pr in pull_request_items:
[assignee_list.append(assignee["login"]) for assignee in pr["assignees"] if assignee["login"] != "jjw24"]
print(f"Found {len(assignee_list)} assignees")
return assignee_list
def get_pr_descriptions(pull_request_items: list[dict]) -> str:
"""
Returns the concatenated string of pr title and number in the format of
@ -185,15 +193,16 @@ if __name__ == "__main__":
print(f"Fetching {state} PRs for {repository_owner}/{repository_name} ...")
pull_requests = get_github_prs(github_token, repository_owner, repository_name)
# First, get all PRs to find the release PR and determine the milestone
all_pull_requests = get_github_prs(github_token, repository_owner, repository_name)
if not pull_requests:
print("No matching pull requests found")
if not all_pull_requests:
print("No pull requests found")
exit(1)
print(f"\nFound total of {len(pull_requests)} pull requests")
print(f"\nFound total of {len(all_pull_requests)} pull requests")
release_pr = get_prs(pull_requests, "release", "open")
release_pr = get_prs(all_pull_requests, "release", "open")
if len(release_pr) != 1:
print(f"Unable to find the exact release PR. Returned result: {release_pr}")
@ -201,13 +210,29 @@ if __name__ == "__main__":
print(f"Found release PR: {release_pr[0]['title']}")
enhancement_prs = get_prs(pull_requests, "enhancement", "closed")
bug_fix_prs = get_prs(pull_requests, "bug", "closed")
release_milestone_title = release_pr[0].get("milestone", {}).get("title", None)
if not release_milestone_title:
print("Release PR does not have a milestone assigned.")
exit(1)
print(f"Using milestone number: {release_milestone_title}")
enhancement_prs = get_prs(all_pull_requests, "enhancement", "closed", release_milestone_title)
bug_fix_prs = get_prs(all_pull_requests, "bug", "closed", release_milestone_title)
if len(enhancement_prs) == 0 and len(bug_fix_prs) == 0:
print(f"No PRs with {release_milestone_title} milestone were found")
description_content = "# Release notes\n"
description_content += f"## Features\n{get_pr_descriptions(enhancement_prs)}" if enhancement_prs else ""
description_content += f"## Bug fixes\n{get_pr_descriptions(bug_fix_prs)}" if bug_fix_prs else ""
assignees = list(set(get_prs_assignees(enhancement_prs) + get_prs_assignees(bug_fix_prs)))
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
)

View file

@ -3,53 +3,37 @@ name: Publish Default Plugins
on:
push:
branches: ['master']
paths: ['Plugins/**']
workflow_dispatch:
jobs:
build:
publish:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup .NET
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: 7.0.x
dotnet-version: 9.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"
dotnet publish 'Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj' --framework net9.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"
dotnet publish 'Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj' --framework net9.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"
dotnet publish 'Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj' --framework net9.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"
dotnet publish 'Plugins/Flow.Launcher.Plugin.PluginIndicator/Flow.Launcher.Plugin.PluginIndicator.csproj' --framework net9.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"
dotnet publish 'Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj' --framework net9.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"
dotnet publish 'Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj' --framework net9.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"
dotnet publish 'Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj' --framework net9.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"
dotnet publish 'Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj' --framework net9.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"
dotnet publish 'Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj' --framework net9.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"
dotnet publish 'Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj' --framework net9.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"
dotnet publish 'Plugins/Flow.Launcher.Plugin.WebSearch/Flow.Launcher.Plugin.WebSearch.csproj' --framework net9.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"
dotnet publish 'Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj' --framework net9.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"

View file

@ -16,11 +16,11 @@ jobs:
runs-on: windows-latest
env:
FlowVersion: 1.19.5
FlowVersion: 1.20.2
NUGET_CERT_REVOCATION_MODE: offline
BUILD_NUMBER: ${{ github.run_number }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set Flow.Launcher.csproj version
id: update
uses: vers-one/dotnet-project-version-updater@v1.7
@ -29,9 +29,9 @@ jobs:
"**/SolutionAssemblyInfo.cs"
version: ${{ env.FlowVersion }}.${{ env.BUILD_NUMBER }}
- name: Setup .NET
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: 7.0.x
dotnet-version: 9.0.x
# cache: true
# cache-dependency-path: |
# Flow.Launcher/packages.lock.json
@ -54,28 +54,28 @@ jobs:
shell: powershell
run: .\Scripts\post_build.ps1
- name: Upload Plugin Nupkg
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: Plugin nupkg
path: |
Output\Release\Flow.Launcher.Plugin.*.nupkg
compression-level: 0
- name: Upload Setup
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: Flow Installer
path: |
Output\Packages\Flow-Launcher-*.exe
compression-level: 0
- name: Upload Portable Version
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: Portable Version
path: |
Output\Packages\Flow-Launcher-Portable.zip
compression-level: 0
- name: Upload Full Nupkg
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: Full nupkg
path: |
@ -83,7 +83,7 @@ jobs:
compression-level: 0
- name: Upload Release Information
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: RELEASES
path: |

View file

@ -14,4 +14,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Assign PR to creator
uses: toshimaru/auto-author-assign@v2.1.1
uses: toshimaru/auto-author-assign@v3.0.1

View file

@ -11,9 +11,9 @@ jobs:
update-pr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: "3.x"

View file

@ -72,7 +72,7 @@ jobs:
steps:
- name: check-spelling
id: spelling
uses: check-spelling/check-spelling@prerelease
uses: check-spelling/check-spelling@v0.0.25
with:
suppress_push_for_open_pull_request: 1
checkout: true
@ -128,7 +128,7 @@ jobs:
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
steps:
- name: comment
uses: check-spelling/check-spelling@prerelease
uses: check-spelling/check-spelling@v0.0.25
with:
checkout: true
spell_check_this: check-spelling/spell-check-this@main

View file

@ -18,7 +18,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
- uses: actions/stale@v10
with:
stale-issue-message: 'This issue is stale because it has been open ${{ env.days-before-stale }} days with no activity. Remove stale label or comment or this will be closed in ${{ env.days-before-stale }} days.\n\nAlternatively this issue can be kept open by adding one of the following labels:\n${{ env.exempt-issue-labels }}'
days-before-stale: ${{ env.days-before-stale }}

View file

@ -1,5 +1,7 @@
<Project>
<PropertyGroup>
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
<!-- Workaround https://github.com/dotnet/runtime/issues/109682 -->
<CETCompat>false</CETCompat>
</PropertyGroup>
</Project>

View file

@ -3,10 +3,8 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
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;
@ -17,8 +15,6 @@ namespace Flow.Launcher.Core.Configuration
{
private static readonly string ClassName = nameof(Portable);
private readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();
/// <summary>
/// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish
/// </summary>
@ -45,14 +41,13 @@ namespace Flow.Launcher.Core.Configuration
#endif
IndicateDeletion(DataLocation.PortableDataPath);
API.ShowMsgBox("Flow Launcher needs to restart to finish disabling portable mode, " +
"after the restart your portable data profile will be deleted and roaming data profile kept");
PublicApi.Instance.ShowMsgBox(Localize.restartToDisablePortableMode());
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
catch (Exception e)
{
API.LogException(ClassName, "Error occurred while disabling portable mode", e);
PublicApi.Instance.LogException(ClassName, "Error occurred while disabling portable mode", e);
}
}
@ -69,14 +64,13 @@ namespace Flow.Launcher.Core.Configuration
#endif
IndicateDeletion(DataLocation.RoamingDataPath);
API.ShowMsgBox("Flow Launcher needs to restart to finish enabling portable mode, " +
"after the restart your roaming data profile will be deleted and portable data profile kept");
PublicApi.Instance.ShowMsgBox(Localize.restartToEnablePortableMode());
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
catch (Exception e)
{
API.LogException(ClassName, "Error occurred while enabling portable mode", e);
PublicApi.Instance.LogException(ClassName, "Error occurred while enabling portable mode", e);
}
}
@ -96,13 +90,13 @@ namespace Flow.Launcher.Core.Configuration
public void MoveUserDataFolder(string fromLocation, string toLocation)
{
FilesFolders.CopyAll(fromLocation, toLocation, (s) => API.ShowMsgBox(s));
FilesFolders.CopyAll(fromLocation, toLocation, (s) => PublicApi.Instance.ShowMsgBox(s));
VerifyUserDataAfterMove(fromLocation, toLocation);
}
public void VerifyUserDataAfterMove(string fromLocation, string toLocation)
{
FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, (s) => API.ShowMsgBox(s));
FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, (s) => PublicApi.Instance.ShowMsgBox(s));
}
public void CreateShortcuts()
@ -141,8 +135,8 @@ namespace Flow.Launcher.Core.Configuration
public void PreStartCleanUpAfterPortabilityUpdate()
{
// Specify here so this method does not rely on other environment variables to initialise
var portableDataDir = Path.Combine(Directory.GetParent(Assembly.GetExecutingAssembly().Location.NonNull()).ToString(), "UserData");
var roamingDataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FlowLauncher");
var portableDataDir = DataLocation.PortableDataPath;
var roamingDataDir = DataLocation.RoamingDataPath;
// Get full path to the .dead files for each case
var portableDataDeleteFilePath = Path.Combine(portableDataDir, DataLocation.DeletionIndicatorFile);
@ -152,13 +146,12 @@ namespace Flow.Launcher.Core.Configuration
// delete it and prompt the user to pick the portable data location
if (File.Exists(roamingDataDeleteFilePath))
{
FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => API.ShowMsgBox(s));
FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => PublicApi.Instance.ShowMsgBox(s));
if (API.ShowMsgBox("Flow Launcher has detected you enabled portable mode, " +
"would you like to move it to a different location?", string.Empty,
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
if (PublicApi.Instance.ShowMsgBox(Localize.moveToDifferentLocation(),
string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
FilesFolders.OpenPath(Constant.RootDirectory, (s) => API.ShowMsgBox(s));
FilesFolders.OpenPath(Constant.RootDirectory, (s) => PublicApi.Instance.ShowMsgBox(s));
Environment.Exit(0);
}
@ -167,10 +160,9 @@ namespace Flow.Launcher.Core.Configuration
// delete it and notify the user about it.
else if (File.Exists(portableDataDeleteFilePath))
{
FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => API.ShowMsgBox(s));
FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => PublicApi.Instance.ShowMsgBox(s));
API.ShowMsgBox("Flow Launcher has detected you disabled portable mode, " +
"the relevant shortcuts and uninstaller entry have been created");
PublicApi.Instance.ShowMsgBox(Localize.shortcutsUninstallerCreated());
}
}
@ -181,9 +173,7 @@ namespace Flow.Launcher.Core.Configuration
if (roamingLocationExists && portableLocationExists)
{
API.ShowMsgBox(string.Format("Flow Launcher detected your user data exists both in {0} and " +
"{1}. {2}{2}Please delete {1} in order to proceed. No changes have occurred.",
DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
PublicApi.Instance.ShowMsgBox(Localize.userDataDuplicated(DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));
return false;
}

View file

@ -8,7 +8,6 @@ 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;
@ -18,13 +17,9 @@ namespace Flow.Launcher.Core.ExternalPlugins
{
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<IPublicAPI>();
private string latestEtag = "";
private List<UserPlugin> plugins = new();
private List<UserPlugin> plugins = [];
private static readonly JsonSerializerOptions PluginStoreItemSerializationOption = new()
{
@ -41,7 +36,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
/// </remarks>
public async Task<List<UserPlugin>> FetchAsync(CancellationToken token)
{
API.LogInfo(ClassName, $"Loading plugins from {ManifestFileUrl}");
PublicApi.Instance.LogInfo(ClassName, $"Loading plugins from {ManifestFileUrl}");
var request = new HttpRequestMessage(HttpMethod.Get, ManifestFileUrl);
@ -59,29 +54,40 @@ namespace Flow.Launcher.Core.ExternalPlugins
.ConfigureAwait(false);
latestEtag = response.Headers.ETag?.Tag;
API.LogInfo(ClassName, $"Loaded {plugins.Count} plugins from {ManifestFileUrl}");
PublicApi.Instance.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.");
PublicApi.Instance.LogInfo(ClassName, $"Resource {ManifestFileUrl} has not been modified.");
return plugins;
}
else
{
API.LogWarn(ClassName, $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
PublicApi.Instance.LogWarn(ClassName, $"Failed to load resource {ManifestFileUrl} with response {response.StatusCode}");
return null;
}
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
PublicApi.Instance.LogDebug(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller.");
return null;
}
catch (TaskCanceledException)
{
// Likely an HttpClient timeout or external cancellation not requested by our token
PublicApi.Instance.LogWarn(ClassName, $"Fetching from {ManifestFileUrl} timed out.");
return null;
}
catch (Exception e)
{
if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)
{
API.LogException(ClassName, $"Check your connection and proxy settings to {ManifestFileUrl}.", e);
PublicApi.Instance.LogException(ClassName, $"Check your connection and proxy settings to {ManifestFileUrl}.", e);
}
else
{
API.LogException(ClassName, "Error Occurred", e);
PublicApi.Instance.LogException(ClassName, "Error Occurred", e);
}
return null;
}

View file

@ -4,7 +4,6 @@ using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Forms;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
@ -15,7 +14,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
private static readonly string ClassName = nameof(AbstractPluginEnvironment);
protected readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();
protected readonly IPublicAPI API = PublicApi.Instance;
internal abstract string Language { get; }
@ -58,15 +57,10 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
return SetPathForPluginPairs(PluginsSettingsFilePath, Language);
}
var noRuntimeMessage = string.Format(
API.GetTranslation("runtimePluginInstalledChooseRuntimePrompt"),
Language,
EnvName,
Environment.NewLine
);
var noRuntimeMessage = Localize.runtimePluginInstalledChooseRuntimePrompt(Language, EnvName, Environment.NewLine);
if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
{
var msg = string.Format(API.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName);
var msg = Localize.runtimePluginChooseRuntimeExecutable(EnvName);
var selectedFile = GetFileFromDialog(msg, FileDialogFilter);
@ -77,12 +71,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
// Nothing selected because user pressed cancel from the file dialog window
else
{
var forceDownloadMessage = string.Format(
API.GetTranslation("runtimeExecutableInvalidChooseDownload"),
Language,
EnvName,
Environment.NewLine
);
var forceDownloadMessage = Localize.runtimeExecutableInvalidChooseDownload(Language, EnvName, Environment.NewLine);
// Let users select valid path or choose to download
while (string.IsNullOrEmpty(selectedFile))
@ -120,7 +109,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
}
else
{
API.ShowMsgBox(string.Format(API.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
API.ShowMsgBox(Localize.runtimePluginUnableToSetExecutablePath(Language));
API.LogError(ClassName,
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
$"{Language}Environment");

View file

@ -11,6 +11,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
internal class PythonEnvironment : AbstractPluginEnvironment
{
private static readonly string ClassName = nameof(PythonEnvironment);
internal override string Language => AllowedLanguage.Python;
internal override string EnvName => DataLocation.PythonEnvironmentName;
@ -39,9 +41,20 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
// 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
JTF.Run(() => DroplexPackage.Drop(App.python_3_11_4_embeddable, InstallPath));
JTF.Run(async () =>
{
try
{
await DroplexPackage.Drop(App.python_3_11_4_embeddable, InstallPath);
PluginsSettingsFilePath = ExecutablePath;
PluginsSettingsFilePath = ExecutablePath;
}
catch (System.Exception e)
{
API.ShowMsgError(Localize.failToInstallPythonEnv());
API.LogException(ClassName, "Failed to install Python environment", e);
}
});
}
internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata)

View file

@ -11,6 +11,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
internal class TypeScriptEnvironment : AbstractPluginEnvironment
{
private static readonly string ClassName = nameof(TypeScriptEnvironment);
internal override string Language => AllowedLanguage.TypeScript;
internal override string EnvName => DataLocation.NodeEnvironmentName;
@ -34,9 +36,20 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
JTF.Run(() => DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath));
JTF.Run(async () =>
{
try
{
await DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath);
PluginsSettingsFilePath = ExecutablePath;
PluginsSettingsFilePath = ExecutablePath;
}
catch (System.Exception e)
{
API.ShowMsgError(Localize.failToInstallTypeScriptEnv());
API.LogException(ClassName, "Failed to install TypeScript environment", e);
}
});
}
internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata)

View file

@ -11,6 +11,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
internal class TypeScriptV2Environment : AbstractPluginEnvironment
{
private static readonly string ClassName = nameof(TypeScriptV2Environment);
internal override string Language => AllowedLanguage.TypeScriptV2;
internal override string EnvName => DataLocation.NodeEnvironmentName;
@ -34,9 +36,20 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
JTF.Run(() => DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath));
JTF.Run(async () =>
{
try
{
await DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath);
PluginsSettingsFilePath = ExecutablePath;
PluginsSettingsFilePath = ExecutablePath;
}
catch (System.Exception e)
{
API.ShowMsgError(Localize.failToInstallTypeScriptEnv());
API.LogException(ClassName, "Failed to install TypeScript environment", e);
}
});
}
internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata)

View file

@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
using Flow.Launcher.Core.Plugin;
namespace Flow.Launcher.Core.ExternalPlugins
{
@ -12,10 +12,10 @@ namespace Flow.Launcher.Core.ExternalPlugins
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",
"https://gcore.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json",
"https://cdn.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json");
new("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/main/plugins.json",
"https://fastly.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@main/plugins.json",
"https://gcore.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@main/plugins.json",
"https://cdn.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@main/plugins.json");
private static readonly SemaphoreSlim manifestUpdateLock = new(1);
@ -26,31 +26,47 @@ namespace Flow.Launcher.Core.ExternalPlugins
public static async Task<bool> UpdateManifestAsync(bool usePrimaryUrlOnly = false, CancellationToken token = default)
{
bool lockAcquired = false;
try
{
await manifestUpdateLock.WaitAsync(token).ConfigureAwait(false);
lockAcquired = true;
if (UserPlugins == null || usePrimaryUrlOnly || DateTime.Now.Subtract(lastFetchedAt) >= fetchTimeout)
{
var results = await mainPluginStore.FetchAsync(token, usePrimaryUrlOnly).ConfigureAwait(false);
// If the results are empty, we shouldn't update the manifest because the results are invalid.
if (results.Count != 0)
{
UserPlugins = results;
lastFetchedAt = DateTime.Now;
if (results.Count == 0)
return false;
return true;
var updatedPluginResults = new List<UserPlugin>();
for (int i = 0; i < results.Count; i++)
{
if (PluginManager.IsMinimumAppVersionSatisfied(results[i].Name, results[i].MinimumAppVersion))
updatedPluginResults.Add(results[i]);
}
UserPlugins = updatedPluginResults;
lastFetchedAt = DateTime.Now;
return true;
}
}
catch (OperationCanceledException)
{
// Ignored
}
catch (Exception e)
{
Ioc.Default.GetRequiredService<IPublicAPI>().LogException(ClassName, "Http request failed", e);
PublicApi.Instance.LogException(ClassName, "Http request failed", e);
}
finally
{
manifestUpdateLock.Release();
// Only release the lock if it was acquired
if (lockAcquired) manifestUpdateLock.Release();
}
return false;

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<TargetFramework>net9.0-windows</TargetFramework>
<UseWpf>true</UseWpf>
<UseWindowsForms>true</UseWindowsForms>
<OutputType>Library</OutputType>
@ -12,6 +12,7 @@
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -33,6 +34,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -54,11 +56,25 @@
<ItemGroup>
<PackageReference Include="Droplex" Version="1.7.0" />
<PackageReference Include="FSharp.Core" Version="9.0.201" />
<PackageReference Include="Meziantou.Framework.Win32.Jobs" Version="3.4.0" />
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="FSharp.Core" Version="9.0.303" />
<PackageReference Include="Meziantou.Framework.Win32.Jobs" Version="3.4.5" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />
<PackageReference Include="StreamJsonRpc" Version="2.21.10" />
<PackageReference Include="SemanticVersioning" Version="3.0.0" />
<!-- Do not upgrade this to higher version since higher version removes UpdateManager instance -->
<PackageReference Include="squirrel.windows" Version="1.9.0" NoWarn="NU1701" />
<PackageReference Include="StreamJsonRpc" Version="2.22.11" />
</ItemGroup>
<PropertyGroup>
<FLLUseDependencyInjection>true</FLLUseDependencyInjection>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Remove="Languages\en.xaml" />
<AdditionalFiles Include="..\Flow.Launcher\Languages\en.xaml">
<Link>Languages\en.xaml</Link>
</AdditionalFiles>
</ItemGroup>
<ItemGroup>

View file

@ -0,0 +1,12 @@
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin;
public interface IResultUpdateRegister
{
/// <summary>
/// Register a plugin to receive results updated event.
/// </summary>
/// <param name="pair"></param>
void RegisterResultsUpdatedEvent(PluginPair pair);
}

View file

@ -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; }
@ -27,6 +27,7 @@ namespace Flow.Launcher.Core.Plugin
private JsonStorage<ConcurrentDictionary<string, object?>> _storage = null!;
private static readonly double MainGridColumn0MaxWidthRatio = 0.6;
private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin");
private static readonly Thickness SettingPanelItemLeftMargin = (Thickness)Application.Current.FindResource("SettingPanelItemLeftMargin");
private static readonly Thickness SettingPanelItemTopBottomMargin = (Thickness)Application.Current.FindResource("SettingPanelItemTopBottomMargin");
@ -156,7 +157,7 @@ namespace Flow.Launcher.Core.Plugin
{
if (!NeedCreateSettingPanel()) return null!;
// Create main grid with two columns (Column 1: Auto, Column 2: *)
// Create main grid with two columns (Column 0: Auto, Column 1: *)
var mainPanel = new Grid { Margin = SettingPanelMargin, VerticalAlignment = VerticalAlignment.Center };
mainPanel.ColumnDefinitions.Add(new ColumnDefinition()
{
@ -200,7 +201,7 @@ namespace Flow.Launcher.Core.Plugin
{
Text = attributes.Label,
VerticalAlignment = VerticalAlignment.Center,
TextWrapping = TextWrapping.WrapWithOverflow
TextWrapping = TextWrapping.Wrap
};
// Create a text block for description
@ -211,7 +212,7 @@ namespace Flow.Launcher.Core.Plugin
{
Text = attributes.Description,
VerticalAlignment = VerticalAlignment.Center,
TextWrapping = TextWrapping.WrapWithOverflow
TextWrapping = TextWrapping.Wrap
};
desc.SetResourceReference(TextBlock.StyleProperty, "SettingPanelTextBlockDescriptionStyle"); // for theme change
@ -247,7 +248,8 @@ namespace Flow.Launcher.Core.Plugin
VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemLeftTopBottomMargin,
Text = Settings[attributes.Name] as string ?? string.Empty,
ToolTip = attributes.Description
ToolTip = attributes.Description,
TextWrapping = TextWrapping.Wrap
};
textBox.TextChanged += (_, _) =>
@ -269,7 +271,8 @@ namespace Flow.Launcher.Core.Plugin
VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemLeftMargin,
Text = Settings[attributes.Name] as string ?? string.Empty,
ToolTip = attributes.Description
ToolTip = attributes.Description,
TextWrapping = TextWrapping.Wrap
};
textBox.TextChanged += (_, _) =>
@ -282,7 +285,7 @@ namespace Flow.Launcher.Core.Plugin
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemLeftMargin,
Content = API.GetTranslation("select")
Content = Localize.select()
};
Btn.Click += (_, _) =>
@ -333,7 +336,7 @@ namespace Flow.Launcher.Core.Plugin
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Center,
Margin = SettingPanelItemLeftTopBottomMargin,
TextWrapping = TextWrapping.WrapWithOverflow,
TextWrapping = TextWrapping.Wrap,
AcceptsReturn = true,
Text = Settings[attributes.Name] as string ?? string.Empty,
ToolTip = attributes.Description
@ -488,6 +491,8 @@ namespace Flow.Launcher.Core.Plugin
rowCount++;
}
mainPanel.SizeChanged += MainPanel_SizeChanged;
// Wrap the main grid in a user control
return new UserControl()
{
@ -495,6 +500,28 @@ namespace Flow.Launcher.Core.Plugin
};
}
private void MainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (sender is not Grid grid) return;
var workingWidth = grid.ActualWidth;
if (workingWidth <= 0) return;
var constrainedWidth = MainGridColumn0MaxWidthRatio * workingWidth;
// Set MaxWidth of column 0 and its children
// We must set MaxWidth of its children to make text wrapping work correctly
grid.ColumnDefinitions[0].MaxWidth = constrainedWidth;
foreach (var child in grid.Children)
{
if (child is FrameworkElement element && Grid.GetColumn(element) == 0 && Grid.GetColumnSpan(element) == 1)
{
element.MaxWidth = constrainedWidth;
}
}
}
private static bool NeedSaveInSettings(string type)
{
return type != "textBlock" && type != "separator" && type != "hyperlink";

View file

@ -100,11 +100,11 @@ namespace Flow.Launcher.Core.Plugin
RPC = new JsonRpc(handler, new JsonRPCPublicAPI(Context.API));
RPC.AddLocalRpcMethod("UpdateResults", new Action<string, JsonRPCQueryResponseModel>((rawQuery, response) =>
RPC.AddLocalRpcMethod("UpdateResults", new Action<string, JsonRPCQueryResponseModel>((trimmedQuery, response) =>
{
var results = ParseResults(response);
ResultsUpdated?.Invoke(this,
new ResultUpdatedEventArgs { Query = new Query() { RawQuery = rawQuery }, Results = results });
new ResultUpdatedEventArgs { Query = new Query() { TrimmedQuery = trimmedQuery }, Results = results });
}));
RPC.SynchronizationContext = null;
RPC.StartListening();

View file

@ -5,7 +5,8 @@ using System.IO;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin;
using System.Text.Json;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Core.Plugin
{
@ -13,10 +14,6 @@ namespace Flow.Launcher.Core.Plugin
{
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<IPublicAPI>();
/// <summary>
/// Parse plugin metadata in the given directories
/// </summary>
@ -30,15 +27,26 @@ namespace Flow.Launcher.Core.Plugin
// todo use linq when diable plugin is implmented since parallel.foreach + list is not thread saft
foreach (var directory in directories)
{
if (File.Exists(Path.Combine(directory, "NeedDelete.txt")))
if (File.Exists(Path.Combine(directory, DataLocation.PluginDeleteFile)))
{
try
{
Directory.Delete(directory, true);
var fullyDeleted = FilesFolders.TryDeleteDirectoryRobust(directory, maxRetries: 3, retryDelayMs: 200);
if (!fullyDeleted)
{
PublicApi.Instance.LogWarn(ClassName, $"Directory <{directory}> was not fully deleted.");
// Directory was not fully deleted, recreate the marker file so deletion will be retried on next startup
var markerFilePath = Path.Combine(directory, DataLocation.PluginDeleteFile);
if (!File.Exists(markerFilePath))
{
File.WriteAllText(markerFilePath, string.Empty);
}
}
}
catch (Exception e)
{
API.LogException(ClassName, $"Can't delete <{directory}>", e);
PublicApi.Instance.LogException(ClassName, $"Can't delete <{directory}>", e);
}
}
else
@ -55,7 +63,7 @@ namespace Flow.Launcher.Core.Plugin
duplicateList
.ForEach(
x => API.LogWarn(ClassName,
x => PublicApi.Instance.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),
@ -107,7 +115,7 @@ namespace Flow.Launcher.Core.Plugin
string configPath = Path.Combine(pluginDirectory, Constant.PluginMetadataFileName);
if (!File.Exists(configPath))
{
API.LogError(ClassName, $"Didn't find config file <{configPath}>");
PublicApi.Instance.LogError(ClassName, $"Didn't find config file <{configPath}>");
return null;
}
@ -123,19 +131,19 @@ namespace Flow.Launcher.Core.Plugin
}
catch (Exception e)
{
API.LogException(ClassName, $"Invalid json for config <{configPath}>", e);
PublicApi.Instance.LogException(ClassName, $"Invalid json for config <{configPath}>", e);
return null;
}
if (!AllowedLanguage.IsAllowed(metadata.Language))
{
API.LogError(ClassName, $"Invalid language <{metadata.Language}> for config <{configPath}>");
PublicApi.Instance.LogError(ClassName, $"Invalid language <{metadata.Language}> for config <{configPath}>");
return null;
}
if (!File.Exists(metadata.ExecuteFilePath))
{
API.LogError(ClassName, $"Execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}");
PublicApi.Instance.LogError(ClassName, $"Execute file path didn't exist <{metadata.ExecuteFilePath}> for conifg <{configPath}");
return null;
}

View file

@ -0,0 +1,462 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin;
/// <summary>
/// Class for installing, updating, and uninstalling plugins.
/// </summary>
public static class PluginInstaller
{
private static readonly string ClassName = nameof(PluginInstaller);
private static readonly Settings Settings = Ioc.Default.GetRequiredService<Settings>();
/// <summary>
/// Installs a plugin and restarts the application if required by settings. Prompts user for confirmation and handles download if needed.
/// </summary>
/// <param name="newPlugin">The plugin to install.</param>
/// <returns>A Task representing the asynchronous install operation.</returns>
public static async Task InstallPluginAndCheckRestartAsync(UserPlugin newPlugin)
{
if (PublicApi.Instance.PluginModified(newPlugin.ID))
{
PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(newPlugin.Name),
Localize.pluginModifiedAlreadyMessage());
return;
}
if (PublicApi.Instance.ShowMsgBox(
Localize.InstallPromptSubtitle(newPlugin.Name, newPlugin.Author, Environment.NewLine),
Localize.InstallPromptTitle(),
button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
try
{
// at minimum should provide a name, but handle plugin that is not downloaded from plugins manifest and is a url download
var downloadFilename = string.IsNullOrEmpty(newPlugin.Version)
? $"{newPlugin.Name}-{Guid.NewGuid()}.zip"
: $"{newPlugin.Name}-{newPlugin.Version}.zip";
var filePath = Path.Combine(Path.GetTempPath(), downloadFilename);
using var cts = new CancellationTokenSource();
if (!newPlugin.IsFromLocalInstallPath)
{
await DownloadFileAsync(
$"{Localize.DownloadingPlugin()} {newPlugin.Name}",
newPlugin.UrlDownload, filePath, cts);
}
else
{
filePath = newPlugin.LocalInstallPath;
}
// check if user cancelled download before installing plugin
if (cts.IsCancellationRequested)
{
return;
}
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"Plugin {newPlugin.ID} zip file not found at {filePath}", filePath);
}
if (!PublicApi.Instance.InstallPlugin(newPlugin, filePath))
{
return;
}
if (!newPlugin.IsFromLocalInstallPath)
{
File.Delete(filePath);
}
}
catch (Exception e)
{
PublicApi.Instance.LogException(ClassName, "Failed to install plugin", e);
PublicApi.Instance.ShowMsgError(Localize.ErrorInstallingPlugin());
return; // do not restart on failure
}
if (Settings.AutoRestartAfterChanging)
{
PublicApi.Instance.RestartApp();
}
else
{
PublicApi.Instance.ShowMsg(
Localize.installbtn(),
Localize.InstallSuccessNoRestart(newPlugin.Name));
}
}
/// <summary>
/// Installs a plugin from a local zip file and restarts the application if required by settings. Validates the zip and prompts user for confirmation.
/// </summary>
/// <param name="filePath">The path to the plugin zip file.</param>
/// <returns>A Task representing the asynchronous install operation.</returns>
public static async Task InstallPluginAndCheckRestartAsync(string filePath)
{
UserPlugin plugin;
try
{
using ZipArchive archive = ZipFile.OpenRead(filePath);
var pluginJsonEntry = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json") ??
throw new FileNotFoundException("The zip file does not contain a plugin.json file.");
using Stream stream = pluginJsonEntry.Open();
plugin = JsonSerializer.Deserialize<UserPlugin>(stream);
plugin.IcoPath = "Images\\zipfolder.png";
plugin.LocalInstallPath = filePath;
}
catch (Exception e)
{
PublicApi.Instance.LogException(ClassName, "Failed to validate zip file", e);
PublicApi.Instance.ShowMsgError(Localize.ZipFileNotHavePluginJson());
return;
}
if (PublicApi.Instance.PluginModified(plugin.ID))
{
PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name),
Localize.pluginModifiedAlreadyMessage());
return;
}
if (Settings.ShowUnknownSourceWarning)
{
if (!InstallSourceKnown(plugin.Website)
&& PublicApi.Instance.ShowMsgBox(Localize.InstallFromUnknownSourceSubtitle(Environment.NewLine),
Localize.InstallFromUnknownSourceTitle(),
MessageBoxButton.YesNo) == MessageBoxResult.No)
return;
}
await InstallPluginAndCheckRestartAsync(plugin);
}
/// <summary>
/// Uninstalls a plugin and restarts the application if required by settings. Prompts user for confirmation and whether to keep plugin settings.
/// </summary>
/// <param name="oldPlugin">The plugin metadata to uninstall.</param>
/// <returns>A Task representing the asynchronous uninstall operation.</returns>
public static async Task UninstallPluginAndCheckRestartAsync(PluginMetadata oldPlugin)
{
if (PublicApi.Instance.PluginModified(oldPlugin.ID))
{
PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(oldPlugin.Name),
Localize.pluginModifiedAlreadyMessage());
return;
}
if (PublicApi.Instance.ShowMsgBox(
Localize.UninstallPromptSubtitle(oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
Localize.UninstallPromptTitle(),
button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
var removePluginSettings = PublicApi.Instance.ShowMsgBox(
Localize.KeepPluginSettingsSubtitle(),
Localize.KeepPluginSettingsTitle(),
button: MessageBoxButton.YesNo) == MessageBoxResult.No;
try
{
if (!await PublicApi.Instance.UninstallPluginAsync(oldPlugin, removePluginSettings))
{
return;
}
}
catch (Exception e)
{
PublicApi.Instance.LogException(ClassName, "Failed to uninstall plugin", e);
PublicApi.Instance.ShowMsgError(Localize.ErrorUninstallingPlugin());
return; // don not restart on failure
}
if (Settings.AutoRestartAfterChanging)
{
PublicApi.Instance.RestartApp();
}
else
{
PublicApi.Instance.ShowMsg(
Localize.uninstallbtn(),
Localize.UninstallSuccessNoRestart(oldPlugin.Name));
}
}
/// <summary>
/// Updates a plugin to a new version and restarts the application if required by settings. Prompts user for confirmation and handles download if needed.
/// </summary>
/// <param name="newPlugin">The new plugin version to install.</param>
/// <param name="oldPlugin">The existing plugin metadata to update.</param>
/// <returns>A Task representing the asynchronous update operation.</returns>
public static async Task UpdatePluginAndCheckRestartAsync(UserPlugin newPlugin, PluginMetadata oldPlugin)
{
if (PublicApi.Instance.ShowMsgBox(
Localize.UpdatePromptSubtitle(oldPlugin.Name, oldPlugin.Author, Environment.NewLine),
Localize.UpdatePromptTitle(),
button: MessageBoxButton.YesNo) != MessageBoxResult.Yes) return;
try
{
var filePath = Path.Combine(Path.GetTempPath(), $"{newPlugin.Name}-{newPlugin.Version}.zip");
using var cts = new CancellationTokenSource();
if (!newPlugin.IsFromLocalInstallPath)
{
await DownloadFileAsync(
$"{Localize.DownloadingPlugin()} {newPlugin.Name}",
newPlugin.UrlDownload, filePath, cts);
}
else
{
filePath = newPlugin.LocalInstallPath;
}
// check if user cancelled download before installing plugin
if (cts.IsCancellationRequested)
{
return;
}
if (!await PublicApi.Instance.UpdatePluginAsync(oldPlugin, newPlugin, filePath))
{
return;
}
}
catch (Exception e)
{
PublicApi.Instance.LogException(ClassName, "Failed to update plugin", e);
PublicApi.Instance.ShowMsgError(Localize.ErrorUpdatingPlugin());
return; // do not restart on failure
}
if (Settings.AutoRestartAfterChanging)
{
PublicApi.Instance.RestartApp();
}
else
{
PublicApi.Instance.ShowMsg(
Localize.updatebtn(),
Localize.UpdateSuccessNoRestart(newPlugin.Name));
}
}
/// <summary>
/// Updates the plugin to the latest version available from its source.
/// </summary>
/// <param name="updateAllPlugins">Action to execute when the user chooses to update all plugins.</param>
/// <param name="silentUpdate">If true, do not show any messages when there is no update available.</param>
/// <param name="usePrimaryUrlOnly">If true, only use the primary URL for updates.</param>
/// <param name="token">Cancellation token to cancel the update operation.</param>
/// <returns></returns>
public static async Task CheckForPluginUpdatesAsync(Action<List<PluginUpdateInfo>> updateAllPlugins, bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default)
{
// Update the plugin manifest
await PublicApi.Instance.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
// Get all plugins that can be updated
var resultsForUpdate = (
from existingPlugin in PublicApi.Instance.GetAllPlugins()
join pluginUpdateSource in PublicApi.Instance.GetPluginManifest()
on existingPlugin.Metadata.ID equals pluginUpdateSource.ID
where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version,
StringComparison.InvariantCulture) <
0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest)
&& !PublicApi.Instance.PluginModified(existingPlugin.Metadata.ID)
select
new PluginUpdateInfo()
{
ID = existingPlugin.Metadata.ID,
Name = existingPlugin.Metadata.Name,
Author = existingPlugin.Metadata.Author,
CurrentVersion = existingPlugin.Metadata.Version,
NewVersion = pluginUpdateSource.Version,
IcoPath = existingPlugin.Metadata.IcoPath,
PluginExistingMetadata = existingPlugin.Metadata,
PluginNewUserPlugin = pluginUpdateSource
}).ToList();
// No updates
if (resultsForUpdate.Count == 0)
{
if (!silentUpdate)
{
PublicApi.Instance.ShowMsg(Localize.updateNoResultTitle(), Localize.updateNoResultSubtitle());
}
return;
}
// If all plugins are modified, just return
if (resultsForUpdate.All(x => PublicApi.Instance.PluginModified(x.ID)))
{
return;
}
// Show message box with button to update all plugins
PublicApi.Instance.ShowMsgWithButton(
Localize.updateAllPluginsTitle(),
Localize.updateAllPluginsButtonContent(),
() =>
{
updateAllPlugins(resultsForUpdate);
},
string.Join(", ", resultsForUpdate.Select(x => x.PluginExistingMetadata.Name)));
}
/// <summary>
/// Updates all plugins that have available updates.
/// </summary>
/// <param name="resultsForUpdate"></param>
/// <param name="restart"></param>
public static async Task UpdateAllPluginsAsync(IEnumerable<PluginUpdateInfo> resultsForUpdate, bool restart)
{
var anyPluginSuccess = false;
await Task.WhenAll(resultsForUpdate.Select(async plugin =>
{
var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip");
try
{
using var cts = new CancellationTokenSource();
await DownloadFileAsync(
$"{Localize.DownloadingPlugin()} {plugin.PluginNewUserPlugin.Name}",
plugin.PluginNewUserPlugin.UrlDownload, downloadToFilePath, cts);
// check if user cancelled download before installing plugin
if (cts.IsCancellationRequested)
{
return;
}
if (!await PublicApi.Instance.UpdatePluginAsync(plugin.PluginExistingMetadata, plugin.PluginNewUserPlugin, downloadToFilePath))
{
return;
}
anyPluginSuccess = true;
}
catch (Exception e)
{
PublicApi.Instance.LogException(ClassName, "Failed to update plugin", e);
PublicApi.Instance.ShowMsgError(Localize.ErrorUpdatingPlugin());
}
}));
if (!anyPluginSuccess) return;
if (restart)
{
PublicApi.Instance.RestartApp();
}
else
{
PublicApi.Instance.ShowMsg(
Localize.updatebtn(),
Localize.PluginsUpdateSuccessNoRestart());
}
}
/// <summary>
/// Downloads a file from a URL to a local path, optionally showing a progress box and handling cancellation.
/// </summary>
/// <param name="progressBoxTitle">The title for the progress box.</param>
/// <param name="downloadUrl">The URL to download from.</param>
/// <param name="filePath">The local file path to save to.</param>
/// <param name="cts">Cancellation token source for cancelling the download.</param>
/// <param name="deleteFile">Whether to delete the file if it already exists.</param>
/// <param name="showProgress">Whether to show a progress box during download.</param>
/// <returns>A Task representing the asynchronous download operation.</returns>
private static async Task DownloadFileAsync(string progressBoxTitle, string downloadUrl, string filePath, CancellationTokenSource cts, bool deleteFile = true, bool showProgress = true)
{
if (deleteFile && File.Exists(filePath))
File.Delete(filePath);
if (showProgress)
{
var exceptionHappened = false;
await PublicApi.Instance.ShowProgressBoxAsync(progressBoxTitle,
async (reportProgress) =>
{
if (reportProgress == null)
{
// when reportProgress is null, it means there is exception with the progress box
// so we record it with exceptionHappened and return so that progress box will close instantly
exceptionHappened = true;
return;
}
else
{
await PublicApi.Instance.HttpDownloadAsync(downloadUrl, filePath, reportProgress, cts.Token).ConfigureAwait(false);
}
}, cts.Cancel);
// if exception happened while downloading and user does not cancel downloading,
// we need to redownload the plugin
if (exceptionHappened && (!cts.IsCancellationRequested))
await PublicApi.Instance.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
}
else
{
await PublicApi.Instance.HttpDownloadAsync(downloadUrl, filePath, token: cts.Token).ConfigureAwait(false);
}
}
/// <summary>
/// Determines if the plugin install source is a known/approved source (e.g., GitHub and matches an existing plugin author).
/// </summary>
/// <param name="url">The URL to check.</param>
/// <returns>True if the source is known, otherwise false.</returns>
private static bool InstallSourceKnown(string url)
{
if (string.IsNullOrEmpty(url))
return false;
var pieces = url.Split('/');
if (pieces.Length < 4)
return false;
var author = pieces[3];
var acceptedHost = "github.com";
var acceptedSource = "https://github.com";
var constructedUrlPart = string.Format("{0}/{1}/", acceptedSource, author);
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || uri.Host != acceptedHost)
return false;
return PublicApi.Instance.GetAllPlugins().Any(x =>
!string.IsNullOrEmpty(x.Metadata.Website) &&
x.Metadata.Website.StartsWith(constructedUrlPart)
);
}
}
public record PluginUpdateInfo
{
public string ID { get; init; }
public string Name { get; init; }
public string Author { get; init; }
public string CurrentVersion { get; init; }
public string NewVersion { get; init; }
public string IcoPath { get; init; }
public PluginMetadata PluginExistingMetadata { get; init; }
public UserPlugin PluginNewUserPlugin { get; init; }
}

File diff suppressed because it is too large Load diff

View file

@ -2,9 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins.Environments;
#pragma warning disable IDE0005
using Flow.Launcher.Infrastructure.Logger;
@ -18,10 +15,6 @@ namespace Flow.Launcher.Core.Plugin
{
private static readonly string ClassName = nameof(PluginsLoader);
// 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<IPublicAPI>();
public static List<PluginPair> Plugins(List<PluginMetadata> metadatas, PluginsSettings settings)
{
var dotnetPlugins = DotNetPlugins(metadatas);
@ -55,7 +48,7 @@ namespace Flow.Launcher.Core.Plugin
return plugins;
}
private static IEnumerable<PluginPair> DotNetPlugins(List<PluginMetadata> source)
private static List<PluginPair> DotNetPlugins(List<PluginMetadata> source)
{
var erroredPlugins = new List<string>();
@ -64,73 +57,71 @@ namespace Flow.Launcher.Core.Plugin
foreach (var metadata in metadatas)
{
var milliseconds = API.StopwatchLogDebug(ClassName, $"Constructor init cost for {metadata.Name}", () =>
var milliseconds = PublicApi.Instance.StopwatchLogDebug(ClassName, $"Constructor init cost for {metadata.Name}", () =>
{
Assembly assembly = null;
IAsyncPlugin plugin = null;
try
{
Assembly assembly = null;
IAsyncPlugin plugin = null;
var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath);
assembly = assemblyLoader.LoadAssemblyAndDependencies();
try
{
var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath);
assembly = assemblyLoader.LoadAssemblyAndDependencies();
var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly,
typeof(IAsyncPlugin));
var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly,
typeof(IAsyncPlugin));
plugin = Activator.CreateInstance(type) as IAsyncPlugin;
plugin = Activator.CreateInstance(type) as IAsyncPlugin;
metadata.AssemblyName = assembly.GetName().Name;
}
metadata.AssemblyName = assembly.GetName().Name;
}
#if DEBUG
catch (Exception)
{
throw;
}
catch (Exception)
{
throw;
}
#else
catch (Exception e) when (assembly == null)
{
Log.Exception(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e);
}
catch (InvalidOperationException e)
{
Log.Exception(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
}
catch (ReflectionTypeLoadException e)
{
Log.Exception(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
}
catch (Exception e)
{
Log.Exception(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
}
catch (Exception e) when (assembly == null)
{
PublicApi.Instance.LogException(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e);
}
catch (InvalidOperationException e)
{
PublicApi.Instance.LogException(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e);
}
catch (ReflectionTypeLoadException e)
{
PublicApi.Instance.LogException(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e);
}
catch (Exception e)
{
PublicApi.Instance.LogException(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e);
}
#endif
if (plugin == null)
{
erroredPlugins.Add(metadata.Name);
return;
}
if (plugin == null)
{
erroredPlugins.Add(metadata.Name);
return;
}
plugins.Add(new PluginPair { Plugin = plugin, Metadata = metadata });
});
plugins.Add(new PluginPair { Plugin = plugin, Metadata = metadata });
});
metadata.InitTime += milliseconds;
PublicApi.Instance.LogDebug(ClassName, $"Constructor cost for <{metadata.Name}> is <{metadata.InitTime}ms>");
}
if (erroredPlugins.Count > 0)
{
var errorPluginString = string.Join(Environment.NewLine, erroredPlugins);
var errorMessage = "The following "
+ (erroredPlugins.Count > 1 ? "plugins have " : "plugin has ")
+ "errored and cannot be loaded:";
var errorMessage = erroredPlugins.Count > 1 ?
Localize.pluginsHaveErrored():
Localize.pluginHasErrored();
_ = Task.Run(() =>
{
Ioc.Default.GetRequiredService<IPublicAPI>().ShowMsgBox($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
$"Please refer to the logs for more information", "",
MessageBoxButton.OK, MessageBoxImage.Warning);
});
PublicApi.Instance.ShowMsgError($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
Localize.referToLogs());
}
return plugins;

View file

@ -6,15 +6,16 @@ namespace Flow.Launcher.Core.Plugin
{
public static class QueryBuilder
{
public static Query Build(string text, Dictionary<string, PluginPair> nonGlobalPlugins)
public static Query Build(string originalQuery, string trimmedQuery, Dictionary<string, PluginPair> nonGlobalPlugins)
{
// home query
if (string.IsNullOrEmpty(text))
if (string.IsNullOrEmpty(trimmedQuery))
{
return new Query()
{
Search = string.Empty,
RawQuery = string.Empty,
OriginalQuery = string.Empty,
TrimmedQuery = string.Empty,
SearchTerms = Array.Empty<string>(),
ActionKeyword = string.Empty,
IsHomeQuery = true
@ -22,14 +23,13 @@ namespace Flow.Launcher.Core.Plugin
}
// replace multiple white spaces with one white space
var terms = text.Split(Query.TermSeparator, StringSplitOptions.RemoveEmptyEntries);
var terms = trimmedQuery.Split(Query.TermSeparator, StringSplitOptions.RemoveEmptyEntries);
if (terms.Length == 0)
{
// nothing was typed
return null;
}
var rawQuery = text;
string actionKeyword, search;
string possibleActionKeyword = terms[0];
string[] searchTerms;
@ -38,21 +38,22 @@ namespace Flow.Launcher.Core.Plugin
{
// use non global plugin for query
actionKeyword = possibleActionKeyword;
search = terms.Length > 1 ? rawQuery[(actionKeyword.Length + 1)..].TrimStart() : string.Empty;
search = terms.Length > 1 ? trimmedQuery[(actionKeyword.Length + 1)..].TrimStart() : string.Empty;
searchTerms = terms[1..];
}
else
{
// non action keyword
actionKeyword = string.Empty;
search = rawQuery.TrimStart();
search = trimmedQuery.TrimStart();
searchTerms = terms;
}
return new Query()
{
Search = search,
RawQuery = rawQuery,
OriginalQuery = originalQuery,
TrimmedQuery = trimmedQuery,
SearchTerms = searchTerms,
ActionKeyword = actionKeyword,
IsHomeQuery = false

View file

@ -17,6 +17,7 @@ namespace Flow.Launcher.Core.Resource
public static Language German = new Language("de", "Deutsch");
public static Language Korean = new Language("ko", "한국어");
public static Language Serbian = new Language("sr", "Srpski");
public static Language Serbian_Cyrillic = new Language("sr-Cyrl-RS", "Српски");
public static Language Portuguese_Portugal = new Language("pt-pt", "Português");
public static Language Portuguese_Brazil = new Language("pt-br", "Português (Brasil)");
public static Language Spanish = new Language("es", "Spanish");
@ -47,6 +48,7 @@ namespace Flow.Launcher.Core.Resource
German,
Korean,
Serbian,
Serbian_Cyrillic,
Portuguese_Portugal,
Portuguese_Brazil,
Spanish,
@ -79,7 +81,8 @@ namespace Flow.Launcher.Core.Resource
"da" => "System",
"de" => "System",
"ko" => "시스템",
"sr" => "Систем",
"sr" => "Sistem",
"sr-Cyrl-RS" => "Систем",
"pt-pt" => "Sistema",
"pt-br" => "Sistema",
"es" => "Sistema",

View file

@ -1,50 +1,43 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using System.Globalization;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Core.Resource
{
public class Internationalization
public class Internationalization : IDisposable
{
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<IPublicAPI>();
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<string> _languageDirectories = new();
private readonly List<ResourceDictionary> _oldResources = new();
private readonly string SystemLanguageCode;
private readonly List<string> _languageDirectories = [];
private readonly List<ResourceDictionary> _oldResources = [];
private static string SystemLanguageCode;
private readonly SemaphoreSlim _langChangeLock = new(1, 1);
public Internationalization(Settings settings)
{
_settings = settings;
AddFlowLauncherLanguageDirectory();
SystemLanguageCode = GetSystemLanguageCodeAtStartup();
}
private void AddFlowLauncherLanguageDirectory()
{
var directory = Path.Combine(Constant.ProgramDirectory, Folder);
_languageDirectories.Add(directory);
}
#region Initialization
private static string GetSystemLanguageCodeAtStartup()
/// <summary>
/// Initialize the system language code based on the current culture.
/// </summary>
public static void InitSystemLanguageCode()
{
var availableLanguages = AvailableLanguages.GetAvailableLanguages();
@ -65,40 +58,11 @@ namespace Flow.Launcher.Core.Resource
string.Equals(languageCode, threeLetterCode, StringComparison.OrdinalIgnoreCase) ||
string.Equals(languageCode, fullName, StringComparison.OrdinalIgnoreCase))
{
return languageCode;
SystemLanguageCode = languageCode;
}
}
return DefaultLanguageCode;
}
private void AddPluginLanguageDirectories()
{
foreach (var plugin in PluginManager.GetPluginsForInterface<IPluginI18n>())
{
var location = Assembly.GetAssembly(plugin.Plugin.GetType()).Location;
var dir = Path.GetDirectoryName(location);
if (dir != null)
{
var pluginThemeDirectory = Path.Combine(dir, Folder);
_languageDirectories.Add(pluginThemeDirectory);
}
else
{
API.LogError(ClassName, $"Can't find plugin path <{location}> for <{plugin.Metadata.Name}>");
}
}
LoadDefaultLanguage();
}
private void LoadDefaultLanguage()
{
// Removes language files loaded before any plugins were loaded.
// Prevents the language Flow started in from overwriting English if the user switches back to English
RemoveOldLanguageFiles();
LoadLanguage(AvailableLanguages.English);
_oldResources.Clear();
SystemLanguageCode = DefaultLanguageCode;
}
/// <summary>
@ -116,13 +80,64 @@ namespace Flow.Launcher.Core.Resource
// Get language by language code and change language
var language = GetLanguageByLanguageCode(languageCode);
// Add Flow Launcher language directory
AddFlowLauncherLanguageDirectory();
// Add plugin language directories first so that we can load language files from plugins
AddPluginLanguageDirectories();
// Load default language resources
LoadDefaultLanguage();
// Change language
await ChangeLanguageAsync(language);
await ChangeLanguageAsync(language, false);
}
private void AddFlowLauncherLanguageDirectory()
{
// Check if Flow Launcher language directory exists
var directory = Path.Combine(Constant.ProgramDirectory, Folder);
if (!Directory.Exists(directory))
{
PublicApi.Instance.LogError(ClassName, $"Flow Launcher language directory can't be found <{directory}>");
return;
}
_languageDirectories.Add(directory);
}
private void AddPluginLanguageDirectories()
{
foreach (var pluginsDir in PluginManager.Directories)
{
if (!Directory.Exists(pluginsDir)) continue;
// Enumerate all top directories in the plugin directory
foreach (var dir in Directory.GetDirectories(pluginsDir))
{
// Check if the directory contains a language folder
var pluginLanguageDir = Path.Combine(dir, Folder);
if (!Directory.Exists(pluginLanguageDir)) continue;
// Check if the language directory contains default language file since it will be checked later
_languageDirectories.Add(pluginLanguageDir);
}
}
}
private void LoadDefaultLanguage()
{
// Removes language files loaded before any plugins were loaded.
// Prevents the language Flow started in from overwriting English if the user switches back to English
RemoveOldLanguageFiles();
LoadLanguage(AvailableLanguages.English);
_oldResources.Clear();
}
#endregion
#region Change Language
/// <summary>
/// Change language during runtime. Will change app language and plugin language & save settings.
/// </summary>
@ -151,11 +166,11 @@ namespace Flow.Launcher.Core.Resource
private static Language GetLanguageByLanguageCode(string languageCode)
{
var lowercase = languageCode.ToLower();
var language = AvailableLanguages.GetAvailableLanguages().FirstOrDefault(o => o.LanguageCode.ToLower() == lowercase);
var language = AvailableLanguages.GetAvailableLanguages().
FirstOrDefault(o => o.LanguageCode.Equals(languageCode, StringComparison.OrdinalIgnoreCase));
if (language == null)
{
API.LogError(ClassName, $"Language code can't be found <{languageCode}>");
PublicApi.Instance.LogError(ClassName, $"Language code can't be found <{languageCode}>");
return AvailableLanguages.English;
}
else
@ -164,24 +179,62 @@ namespace Flow.Launcher.Core.Resource
}
}
private async Task ChangeLanguageAsync(Language language)
private async Task ChangeLanguageAsync(Language language, bool updateMetadata = true)
{
// Remove old language files and load language
RemoveOldLanguageFiles();
if (language != AvailableLanguages.English)
{
LoadLanguage(language);
}
await _langChangeLock.WaitAsync();
try
{
// Remove old language files and load language
RemoveOldLanguageFiles();
if (language != AvailableLanguages.English)
{
LoadLanguage(language);
}
// Change culture info
ChangeCultureInfo(language.LanguageCode);
if (updateMetadata)
{
// Raise event for plugins after culture is set
await Task.Run(UpdatePluginMetadataTranslations);
}
}
catch (Exception e)
{
PublicApi.Instance.LogException(ClassName, $"Failed to change language to <{language.LanguageCode}>", e);
}
finally
{
_langChangeLock.Release();
}
}
public static void ChangeCultureInfo(string languageCode)
{
// Culture of main thread
// Use CreateSpecificCulture to preserve possible user-override settings in Windows, if Flow's language culture is the same as Windows's
CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture(language.LanguageCode);
CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture;
// Raise event for plugins after culture is set
await Task.Run(UpdatePluginMetadataTranslations);
CultureInfo currentCulture;
try
{
currentCulture = CultureInfo.CreateSpecificCulture(languageCode);
}
catch (CultureNotFoundException)
{
currentCulture = CultureInfo.CreateSpecificCulture(SystemLanguageCode);
}
CultureInfo.CurrentCulture = currentCulture;
CultureInfo.CurrentUICulture = currentCulture;
var thread = Thread.CurrentThread;
thread.CurrentCulture = currentCulture;
thread.CurrentUICulture = currentCulture;
}
#endregion
#region Prompt Pinyin
public bool PromptShouldUsePinyin(string languageCodeToSet)
{
var languageToSet = GetLanguageByLanguageCode(languageCodeToSet);
@ -194,14 +247,18 @@ namespace Flow.Launcher.Core.Resource
// No other languages should show the following text so just make it hard-coded
// "Do you want to search with pinyin?"
string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?" ;
string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?";
if (Ioc.Default.GetRequiredService<IPublicAPI>().ShowMsgBox(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
if (PublicApi.Instance.ShowMsgBox(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
return false;
return true;
}
#endregion
#region Language Resources Management
private void RemoveOldLanguageFiles()
{
var dicts = Application.Current.Resources.MergedDictionaries;
@ -209,6 +266,7 @@ namespace Flow.Launcher.Core.Resource
{
dicts.Remove(r);
}
_oldResources.Clear();
}
private void LoadLanguage(Language language)
@ -237,45 +295,6 @@ namespace Flow.Launcher.Core.Resource
}
}
public List<Language> LoadAvailableLanguages()
{
var list = AvailableLanguages.GetAvailableLanguages();
list.Insert(0, new Language(Constant.SystemLanguageCode, AvailableLanguages.GetSystemTranslation(SystemLanguageCode)));
return list;
}
public static string GetTranslation(string key)
{
var translation = Application.Current.TryFindResource(key);
if (translation is string)
{
return translation.ToString();
}
else
{
API.LogError(ClassName, $"No Translation for key {key}");
return $"No Translation for key {key}";
}
}
private void UpdatePluginMetadataTranslations()
{
foreach (var p in PluginManager.GetPluginsForInterface<IPluginI18n>())
{
if (p.Plugin is not IPluginI18n pluginI18N) return;
try
{
p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle();
p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription();
pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture);
}
catch (Exception e)
{
API.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e);
}
}
}
private static string LanguageFile(string folder, string language)
{
if (Directory.Exists(folder))
@ -287,7 +306,7 @@ namespace Flow.Launcher.Core.Resource
}
else
{
API.LogError(ClassName, $"Language path can't be found <{path}>");
PublicApi.Instance.LogError(ClassName, $"Language path can't be found <{path}>");
var english = Path.Combine(folder, DefaultFile);
if (File.Exists(english))
{
@ -295,7 +314,7 @@ namespace Flow.Launcher.Core.Resource
}
else
{
API.LogError(ClassName, $"Default English Language path can't be found <{path}>");
PublicApi.Instance.LogError(ClassName, $"Default English Language path can't be found <{path}>");
return string.Empty;
}
}
@ -305,5 +324,85 @@ namespace Flow.Launcher.Core.Resource
return string.Empty;
}
}
#endregion
#region Available Languages
public List<Language> LoadAvailableLanguages()
{
var list = AvailableLanguages.GetAvailableLanguages();
list.Insert(0, new Language(Constant.SystemLanguageCode, AvailableLanguages.GetSystemTranslation(SystemLanguageCode)));
return list;
}
#endregion
#region Get Translations
public static string GetTranslation(string key)
{
var translation = Application.Current.TryFindResource(key);
if (translation is string)
{
return translation.ToString();
}
else
{
PublicApi.Instance.LogError(ClassName, $"No Translation for key {key}");
return $"No Translation for key {key}";
}
}
#endregion
#region Update Metadata
public static void UpdatePluginMetadataTranslations()
{
// Update plugin metadata name & description
foreach (var p in PluginManager.GetTranslationPlugins())
{
if (p.Plugin is not IPluginI18n pluginI18N) return;
try
{
p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle();
p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription();
pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture);
}
catch (Exception e)
{
PublicApi.Instance.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e);
}
}
}
public static void UpdatePluginMetadataTranslation(PluginPair p)
{
// Update plugin metadata name & description
if (p.Plugin is not IPluginI18n pluginI18N) return;
try
{
p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle();
p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription();
pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture);
}
catch (Exception e)
{
PublicApi.Instance.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e);
}
}
#endregion
#region IDisposable
public void Dispose()
{
RemoveOldLanguageFiles();
_langChangeLock.Dispose();
}
#endregion
}
}

View file

@ -1,38 +0,0 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Windows.Data;
namespace Flow.Launcher.Core.Resource
{
[Obsolete("LocalizationConverter is obsolete. Use with Flow.Launcher.Localization NuGet package instead.")]
public class LocalizationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof(string) && value != null)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
if (fi != null)
{
string localizedDescription = string.Empty;
var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description)))
{
localizedDescription = attributes[0].Description;
}
return (!String.IsNullOrEmpty(localizedDescription)) ? localizedDescription : value.ToString();
}
}
return string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View file

@ -1,30 +0,0 @@
using System.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Resource
{
public class LocalizedDescriptionAttribute : DescriptionAttribute
{
// 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<IPublicAPI>();
private readonly string _resourceKey;
public LocalizedDescriptionAttribute(string resourceKey)
{
_resourceKey = resourceKey;
}
public override string Description
{
get
{
string description = API.GetTranslation(_resourceKey);
return string.IsNullOrWhiteSpace(description) ?
string.Format("[[{0}]]", _resourceKey) : description;
}
}
}
}

View file

@ -124,8 +124,8 @@ namespace Flow.Launcher.Core.Resource
try
{
// Load a ResourceDictionary for the specified theme.
var themeName = _settings.Theme;
var dict = GetThemeResourceDictionary(themeName);
var theme = _settings.Theme;
var dict = GetThemeResourceDictionary(theme);
// Apply font settings to the theme resource.
ApplyFontSettings(dict);
@ -292,10 +292,10 @@ namespace Flow.Launcher.Core.Resource
dict["ItemHotkeyStyle"] is Style resultHotkeyItemStyle &&
dict["ItemHotkeySelectedStyle"] is Style resultHotkeyItemSelectedStyle)
{
Setter fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(_settings.ResultFont));
Setter fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.ResultFontStyle));
Setter fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.ResultFontWeight));
Setter fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.ResultFontStretch));
var fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(_settings.ResultFont));
var fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.ResultFontStyle));
var fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.ResultFontWeight));
var fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.ResultFontStretch));
Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch };
Array.ForEach(
@ -307,10 +307,10 @@ namespace Flow.Launcher.Core.Resource
dict["ItemSubTitleStyle"] is Style resultSubItemStyle &&
dict["ItemSubTitleSelectedStyle"] is Style resultSubItemSelectedStyle)
{
Setter fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(_settings.ResultSubFont));
Setter fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.ResultSubFontStyle));
Setter fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.ResultSubFontWeight));
Setter fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.ResultSubFontStretch));
var fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(_settings.ResultSubFont));
var fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.ResultSubFontStyle));
var fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.ResultSubFontWeight));
var fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.ResultSubFontStretch));
Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch };
Array.ForEach(
@ -395,7 +395,7 @@ namespace Flow.Launcher.Core.Resource
public List<ThemeData> GetAvailableThemes()
{
List<ThemeData> themes = new List<ThemeData>();
var themes = new List<ThemeData>();
foreach (var themeDirectory in _themeDirectories)
{
var filePaths = Directory
@ -410,8 +410,7 @@ namespace Flow.Launcher.Core.Resource
public bool ChangeTheme(string theme = null)
{
if (string.IsNullOrEmpty(theme))
theme = _settings.Theme;
if (string.IsNullOrEmpty(theme)) theme = _settings.Theme;
string path = GetThemePath(theme);
try
@ -426,13 +425,14 @@ namespace Flow.Launcher.Core.Resource
_settings.Theme = theme;
//always allow re-loading default theme, in case of failure of switching to a new theme from default theme
// Always allow re-loading default theme, in case of failure of switching to a new theme from default theme
if (_oldTheme != theme || theme == Constant.DefaultTheme)
{
_oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath);
}
BlurEnabled = IsBlurTheme();
// Check if blur is enabled
BlurEnabled = Win32Helper.IsBackdropSupported() && IsThemeBlurEnabled(resourceDict);
// Apply blur and drop shadow effect so that we do not need to call it again
_ = RefreshFrameAsync();
@ -444,17 +444,27 @@ namespace Flow.Launcher.Core.Resource
_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));
_api.ShowMsgBox(Localize.theme_load_failure_path_not_exists(theme));
ChangeTheme(Constant.DefaultTheme);
}
return false;
}
catch (XamlParseException)
catch (XamlParseException e)
{
_api.LogError(ClassName, $"Theme <{theme}> fail to parse");
_api.LogException(ClassName, $"Theme <{theme}> fail to parse xaml", e);
if (theme != Constant.DefaultTheme)
{
_api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_parse_error"), theme));
_api.ShowMsgBox(Localize.theme_load_failure_parse_error(theme));
ChangeTheme(Constant.DefaultTheme);
}
return false;
}
catch (Exception e)
{
_api.LogException(ClassName, $"Theme <{theme}> fail to load", e);
if (theme != Constant.DefaultTheme)
{
_api.ShowMsgBox(Localize.theme_load_failure_parse_error(theme));
ChangeTheme(Constant.DefaultTheme);
}
return false;
@ -657,21 +667,29 @@ namespace Flow.Launcher.Core.Resource
if (mainWindow == null) return;
// Check if the theme supports blur
bool hasBlur = dict.Contains("ThemeBlurEnabled") && dict["ThemeBlurEnabled"] is bool b && b;
var hasBlur = IsThemeBlurEnabled(dict);
if (BlurEnabled && hasBlur && Win32Helper.IsBackdropSupported())
{
// If the BackdropType is Mica or MicaAlt, set the windowborderstyle's background to transparent
if (backdropType == BackdropTypes.Mica || backdropType == BackdropTypes.MicaAlt)
if (backdropType is BackdropTypes.Mica or BackdropTypes.MicaAlt)
{
windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType<Setter>().FirstOrDefault(x => x.Property.Name == "Background"));
windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Color.FromArgb(1, 0, 0, 0))));
windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType<Setter>().FirstOrDefault(x => x.Property == Control.BackgroundProperty));
windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, ThemeHelper.GetFrozenSolidColorBrush(Color.FromArgb(1, 0, 0, 0))));
}
else if (backdropType == BackdropTypes.Acrylic)
{
windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType<Setter>().FirstOrDefault(x => x.Property.Name == "Background"));
windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Colors.Transparent)));
windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType<Setter>().FirstOrDefault(x => x.Property == Control.BackgroundProperty));
windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, Brushes.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<Setter>().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);
@ -780,12 +798,12 @@ namespace Flow.Launcher.Core.Resource
Application.Current.Resources["WindowBorderStyle"] is Style originalStyle)
{
// Copy the original style, including the base style if it exists
CopyStyle(originalStyle, previewStyle);
ThemeHelper.CopyStyle(originalStyle, previewStyle);
}
// Apply background color (remove transparency in color)
Color backgroundColor = Color.FromRgb(bgColor.Value.R, bgColor.Value.G, bgColor.Value.B);
previewStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(backgroundColor)));
var backgroundColor = Color.FromRgb(bgColor.Value.R, bgColor.Value.G, bgColor.Value.B);
previewStyle.Setters.Add(new Setter(Border.BackgroundProperty, ThemeHelper.GetFrozenSolidColorBrush(backgroundColor)));
// The blur theme keeps the corner round fixed (applying DWM code to modify it causes rendering issues).
// The non-blur theme retains the previously set WindowBorderStyle.
@ -799,21 +817,6 @@ namespace Flow.Launcher.Core.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<Setter>())
{
targetStyle.Setters.Add(new Setter(setter.Property, setter.Value));
}
}
private void ColorizeWindow(string theme, BackdropTypes backdropType)
{
var dict = GetThemeResourceDictionary(theme);
@ -823,20 +826,16 @@ namespace Flow.Launcher.Core.Resource
if (mainWindow == null) return;
// Check if the theme supports blur
bool hasBlur = dict.Contains("ThemeBlurEnabled") && dict["ThemeBlurEnabled"] is bool b && b;
var hasBlur = IsThemeBlurEnabled(dict);
// SystemBG value check (Auto, Light, Dark)
string systemBG = dict.Contains("SystemBG") ? dict["SystemBG"] as string : "Auto"; // 기본값 Auto
var systemBG = dict.Contains("SystemBG") ? dict["SystemBG"] as string : "Auto"; // 기본값 Auto
// Check the user's ColorScheme setting
string colorScheme = _settings.ColorScheme;
// Check system dark mode setting (read AppsUseLightTheme value)
int themeValue = (int)Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme", 1);
bool isSystemDark = themeValue == 0;
var colorScheme = _settings.ColorScheme;
// Final decision on whether to use dark mode
bool useDarkMode = false;
var useDarkMode = false;
// If systemBG is not "Auto", prioritize it over ColorScheme and set the mode based on systemBG value
if (systemBG == "Dark")
@ -851,11 +850,20 @@ namespace Flow.Launcher.Core.Resource
{
// If systemBG is "Auto", decide based on ColorScheme
if (colorScheme == "Dark")
{
useDarkMode = true;
}
else if (colorScheme == "Light")
{
useDarkMode = false;
}
else
{
// Check system dark mode setting (read AppsUseLightTheme value)
var themeValue = (int)Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme", 1);
var isSystemDark = themeValue == 0;
useDarkMode = isSystemDark; // Auto (based on system setting)
}
}
// Apply DWM Dark Mode
@ -897,25 +905,20 @@ namespace Flow.Launcher.Core.Resource
else
{
// Only set the background to transparent if the theme supports blur
if (backdropType == BackdropTypes.Mica || backdropType == BackdropTypes.MicaAlt)
if (backdropType is BackdropTypes.Mica or BackdropTypes.MicaAlt)
{
mainWindow.Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0));
mainWindow.Background = ThemeHelper.GetFrozenSolidColorBrush(Color.FromArgb(1, 0, 0, 0));
}
else
{
mainWindow.Background = new SolidColorBrush(selectedBG);
mainWindow.Background = ThemeHelper.GetFrozenSolidColorBrush(selectedBG);
}
}
}
private static bool IsBlurTheme()
private static bool IsThemeBlurEnabled(ResourceDictionary dict)
{
if (!Win32Helper.IsBackdropSupported()) // Windows 11 미만이면 무조건 false
return false;
var resource = Application.Current.TryFindResource("ThemeBlurEnabled");
return resource is bool b && b;
return dict.Contains("ThemeBlurEnabled") && dict["ThemeBlurEnabled"] is bool enabled && enabled;
}
#endregion

View file

@ -0,0 +1,30 @@
using System.Linq;
using System.Windows;
using System.Windows.Media;
namespace Flow.Launcher.Core.Resource;
public static class ThemeHelper
{
public static 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<Setter>())
{
targetStyle.Setters.Add(new Setter(setter.Property, setter.Value));
}
}
public static SolidColorBrush GetFrozenSolidColorBrush(Color color)
{
var brush = new SolidColorBrush(color);
brush.Freeze();
return brush;
}
}

View file

@ -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<IPublicAPI>();
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();
}
}

View file

@ -41,29 +41,30 @@ namespace Flow.Launcher.Core
try
{
if (!silentUpdate)
_api.ShowMsg(_api.GetTranslation("pleaseWait"),
_api.GetTranslation("update_flowlauncher_update_check"));
_api.ShowMsg(Localize.pleaseWait(),
Localize.update_flowlauncher_update_check());
using var updateManager = await GitHubUpdateManagerAsync(GitHubRepository).ConfigureAwait(false);
// UpdateApp CheckForUpdate will return value only if the app is squirrel installed
var newUpdateInfo = await updateManager.CheckForUpdate().NonNull().ConfigureAwait(false);
var newReleaseVersion = Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
var currentVersion = Version.Parse(Constant.Version);
var newReleaseVersion =
SemanticVersioning.Version.Parse(newUpdateInfo.FutureReleaseEntry.Version.ToString());
var currentVersion = SemanticVersioning.Version.Parse(Constant.Version);
_api.LogInfo(ClassName, $"Future Release <{Formatted(newUpdateInfo.FutureReleaseEntry)}>");
if (newReleaseVersion <= currentVersion)
{
if (!silentUpdate)
_api.ShowMsgBox(_api.GetTranslation("update_flowlauncher_already_on_latest"));
_api.ShowMsgBox(Localize.update_flowlauncher_already_on_latest());
return;
}
if (!silentUpdate)
_api.ShowMsg(_api.GetTranslation("update_flowlauncher_update_found"),
_api.GetTranslation("update_flowlauncher_updating"));
_api.ShowMsg(Localize.update_flowlauncher_update_found(),
Localize.update_flowlauncher_updating());
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false);
@ -71,12 +72,12 @@ namespace Flow.Launcher.Core
if (DataLocation.PortableDataLocationInUse())
{
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion}\\{DataLocation.PortableFolderName}";
var targetDestination = updateManager.RootAppDirectory +
$"\\app-{newReleaseVersion}\\{DataLocation.PortableFolderName}";
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s));
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)))
_api.ShowMsgBox(string.Format(_api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
DataLocation.PortableDataPath,
targetDestination));
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination,
(s) => _api.ShowMsgBox(s)))
_api.ShowMsgBox(Localize.update_flowlauncher_fail_moving_portable_user_profile_data(DataLocation.PortableDataPath, targetDestination));
}
else
{
@ -87,16 +88,19 @@ namespace Flow.Launcher.Core
_api.LogInfo(ClassName, $"Update success:{newVersionTips}");
if (_api.ShowMsgBox(newVersionTips, _api.GetTranslation("update_flowlauncher_new_update"), MessageBoxButton.YesNo) == MessageBoxResult.Yes)
if (_api.ShowMsgBox(newVersionTips, Localize.update_flowlauncher_new_update(),
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
}
catch (Exception e)
{
if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)
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);
_api.LogException(ClassName,
$"Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
}
else
{
@ -104,8 +108,8 @@ namespace Flow.Launcher.Core
}
if (!silentUpdate)
_api.ShowMsg(_api.GetTranslation("update_flowlauncher_fail"),
_api.GetTranslation("update_flowlauncher_check_connection"));
_api.ShowMsgError(Localize.update_flowlauncher_fail(),
Localize.update_flowlauncher_check_connection());
}
finally
{
@ -116,14 +120,11 @@ namespace Flow.Launcher.Core
[UsedImplicitly]
private class GithubRelease
{
[JsonPropertyName("prerelease")]
public bool Prerelease { get; [UsedImplicitly] set; }
[JsonPropertyName("prerelease")] public bool Prerelease { get; [UsedImplicitly] set; }
[JsonPropertyName("published_at")]
public DateTime PublishedAt { get; [UsedImplicitly] set; }
[JsonPropertyName("published_at")] public DateTime PublishedAt { get; [UsedImplicitly] set; }
[JsonPropertyName("html_url")]
public string HtmlUrl { get; [UsedImplicitly] set; }
[JsonPropertyName("html_url")] public string HtmlUrl { get; [UsedImplicitly] set; }
}
// https://github.com/Squirrel/Squirrel.Windows/blob/master/src/Squirrel/UpdateManager.Factory.cs
@ -138,10 +139,7 @@ namespace Flow.Launcher.Core
var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First();
var latestUrl = latest.HtmlUrl.Replace("/tag/", "/download/");
var client = new WebClient
{
Proxy = Http.WebProxy
};
var client = new WebClient { Proxy = Http.WebProxy };
var downloader = new FileDownloader(client);
var manager = new UpdateManager(latestUrl, urlDownloader: downloader);
@ -149,19 +147,16 @@ namespace Flow.Launcher.Core
return manager;
}
private string NewVersionTips(string version)
private static string NewVersionTips(string version)
{
var tips = string.Format(_api.GetTranslation("newVersionTips"), version);
var tips = Localize.newVersionTips(version);
return tips;
}
private static string Formatted<T>(T t)
{
var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions
{
WriteIndented = true
});
var formatted = JsonSerializer.Serialize(t, new JsonSerializerOptions { WriteIndented = true });
return formatted;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,63 @@
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.DialogJump;
public class DialogJumpExplorerPair
{
public IDialogJumpExplorer Plugin { get; init; }
public PluginMetadata Metadata { get; init; }
public override string ToString()
{
return Metadata.Name;
}
public override bool Equals(object obj)
{
if (obj is DialogJumpExplorerPair r)
{
return string.Equals(r.Metadata.ID, Metadata.ID);
}
else
{
return false;
}
}
public override int GetHashCode()
{
var hashcode = Metadata.ID?.GetHashCode() ?? 0;
return hashcode;
}
}
public class DialogJumpDialogPair
{
public IDialogJumpDialog Plugin { get; init; }
public PluginMetadata Metadata { get; init; }
public override string ToString()
{
return Metadata.Name;
}
public override bool Equals(object obj)
{
if (obj is DialogJumpDialogPair r)
{
return string.Equals(r.Metadata.ID, Metadata.ID);
}
else
{
return false;
}
}
public override int GetHashCode()
{
var hashcode = Metadata.ID?.GetHashCode() ?? 0;
return hashcode;
}
}

View file

@ -0,0 +1,345 @@
using System;
using System.Threading;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
using WindowsInput;
using WindowsInput.Native;
namespace Flow.Launcher.Infrastructure.DialogJump.Models
{
/// <summary>
/// Class for handling Windows File Dialog instances in DialogJump.
/// </summary>
public class WindowsDialog : IDialogJumpDialog
{
private const string WindowsDialogClassName = "#32770";
public IDialogJumpDialogWindow CheckDialogWindow(IntPtr hwnd)
{
// Is it a Win32 dialog box?
if (GetClassName(new(hwnd)) == WindowsDialogClassName)
{
// Is it a windows file dialog?
var dialogType = GetFileDialogType(new(hwnd));
if (dialogType != DialogType.Others)
{
return new WindowsDialogWindow(hwnd, dialogType);
}
}
return null;
}
public void Dispose()
{
}
#region Help Methods
private static unsafe string GetClassName(HWND handle)
{
fixed (char* buf = new char[256])
{
return PInvoke.GetClassName(handle, buf, 256) switch
{
0 => string.Empty,
_ => new string(buf),
};
}
}
private static DialogType GetFileDialogType(HWND handle)
{
// Is it a Windows Open file dialog?
var fileEditor = PInvoke.GetDlgItem(handle, 0x047C);
if (fileEditor != HWND.Null && GetClassName(fileEditor) == "ComboBoxEx32") return DialogType.Open;
// Is it a Windows Save or Save As file dialog?
fileEditor = PInvoke.GetDlgItem(handle, 0x0000);
if (fileEditor != HWND.Null && GetClassName(fileEditor) == "DUIViewWndClassName") return DialogType.SaveOrSaveAs;
return DialogType.Others;
}
#endregion
}
public class WindowsDialogWindow : IDialogJumpDialogWindow
{
public IntPtr Handle { get; private set; } = IntPtr.Zero;
// After jumping folder, file editor handle of Save / SaveAs file dialogs cannot be found anymore
// So we need to cache the current tab and use the original handle
private IDialogJumpDialogWindowTab _currentTab { get; set; } = null;
private readonly DialogType _dialogType;
internal WindowsDialogWindow(IntPtr handle, DialogType dialogType)
{
Handle = handle;
_dialogType = dialogType;
}
public IDialogJumpDialogWindowTab GetCurrentTab()
{
return _currentTab ??= new WindowsDialogTab(Handle, _dialogType);
}
public void Dispose()
{
}
}
public class WindowsDialogTab : IDialogJumpDialogWindowTab
{
#region Public Properties
public IntPtr Handle { get; private set; } = IntPtr.Zero;
#endregion
#region Private Fields
private static readonly string ClassName = nameof(WindowsDialogTab);
private static readonly InputSimulator _inputSimulator = new();
private readonly DialogType _dialogType;
private bool _legacy { get; set; } = false;
private HWND _pathControl { get; set; } = HWND.Null;
private HWND _pathEditor { get; set; } = HWND.Null;
private HWND _fileEditor { get; set; } = HWND.Null;
private HWND _openButton { get; set; } = HWND.Null;
#endregion
#region Constructor
internal WindowsDialogTab(IntPtr handle, DialogType dialogType)
{
Handle = handle;
_dialogType = dialogType;
Log.Debug(ClassName, $"File dialog type: {dialogType}");
}
#endregion
#region Public Methods
public string GetCurrentFolder()
{
if (_pathEditor.IsNull && !GetPathControlEditor()) return string.Empty;
return GetWindowText(_pathEditor);
}
public string GetCurrentFile()
{
if (_fileEditor.IsNull && !GetFileEditor()) return string.Empty;
return GetWindowText(_fileEditor);
}
public bool JumpFolder(string path, bool auto)
{
if (auto)
{
// Use legacy jump folder method for auto Dialog Jump because file editor is default value.
// After setting path using file editor, we do not need to revert its value.
return JumpFolderWithFileEditor(path, false);
}
// Alt-D or Ctrl-L to focus on the path input box
// "ComboBoxEx32" is not visible when the path editor is not with the keyboard focus
_inputSimulator.Keyboard.ModifiedKeyStroke(VirtualKeyCode.LMENU, VirtualKeyCode.VK_D);
// _inputSimulator.Keyboard.ModifiedKeyStroke(VirtualKeyCode.LCONTROL, VirtualKeyCode.VK_L);
if (_pathControl.IsNull && !GetPathControlEditor())
{
// https://github.com/idkidknow/Flow.Launcher.Plugin.DirQuickJump/issues/1
// The dialog is a legacy one, so we can only edit file editor directly.
Log.Debug(ClassName, "Legacy dialog, using legacy jump folder method");
return JumpFolderWithFileEditor(path, true);
}
var timeOut = !SpinWait.SpinUntil(() =>
{
var style = PInvoke.GetWindowLongPtr(_pathControl, WINDOW_LONG_PTR_INDEX.GWL_STYLE);
return (style & (int)WINDOW_STYLE.WS_VISIBLE) != 0;
}, 1000);
if (timeOut)
{
// Path control is not visible, so we can only edit file editor directly.
Log.Debug(ClassName, "Path control is not visible, using legacy jump folder method");
return JumpFolderWithFileEditor(path, true);
}
if (_pathEditor.IsNull)
{
// Path editor cannot be found, so we can only edit file editor directly.
Log.Debug(ClassName, "Path editor cannot be found, using legacy jump folder method");
return JumpFolderWithFileEditor(path, true);
}
SetWindowText(_pathEditor, path);
_inputSimulator.Keyboard.KeyPress(VirtualKeyCode.RETURN);
return true;
}
public bool JumpFile(string path)
{
if (_fileEditor.IsNull && !GetFileEditor()) return false;
SetWindowText(_fileEditor, path);
return true;
}
public bool Open()
{
if (_openButton.IsNull && !GetOpenButton()) return false;
PInvoke.PostMessage(_openButton, PInvoke.BM_CLICK, 0, 0);
return true;
}
public void Dispose()
{
}
#endregion
#region Helper Methods
#region Get Handles
private bool GetPathControlEditor()
{
// Get the handle of the path editor
// Must use PInvoke.FindWindowEx because PInvoke.GetDlgItem(Handle, 0x0000) will get another control
_pathControl = PInvoke.FindWindowEx(new(Handle), HWND.Null, "WorkerW", null); // 0x0000
_pathControl = PInvoke.FindWindowEx(_pathControl, HWND.Null, "ReBarWindow32", null); // 0xA005
_pathControl = PInvoke.FindWindowEx(_pathControl, HWND.Null, "Address Band Root", null); // 0xA205
_pathControl = PInvoke.FindWindowEx(_pathControl, HWND.Null, "msctls_progress32", null); // 0x0000
_pathControl = PInvoke.FindWindowEx(_pathControl, HWND.Null, "ComboBoxEx32", null); // 0xA205
if (_pathControl == HWND.Null)
{
_pathEditor = HWND.Null;
_legacy = true;
Log.Info(ClassName, "Legacy dialog");
}
else
{
_pathEditor = PInvoke.GetDlgItem(_pathControl, 0xA205); // ComboBox
_pathEditor = PInvoke.GetDlgItem(_pathEditor, 0xA205); // Edit
if (_pathEditor == HWND.Null)
{
_legacy = true;
Log.Error(ClassName, "Failed to find path editor handle");
}
}
return !_legacy;
}
private bool GetFileEditor()
{
if (_dialogType == DialogType.Open)
{
// Get the handle of the file name editor of Open file dialog
_fileEditor = PInvoke.GetDlgItem(new(Handle), 0x047C); // ComboBoxEx32
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x047C); // ComboBox
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x047C); // Edit
}
else
{
// Get the handle of the file name editor of Save / SaveAs file dialog
_fileEditor = PInvoke.GetDlgItem(new(Handle), 0x0000); // DUIViewWndClassName
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x0000); // DirectUIHWND
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x0000); // FloatNotifySink
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x0000); // ComboBox
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x03E9); // Edit
}
if (_fileEditor == HWND.Null)
{
Log.Error(ClassName, "Failed to find file name editor handle");
return false;
}
return true;
}
private bool GetOpenButton()
{
// Get the handle of the open button
_openButton = PInvoke.GetDlgItem(new(Handle), 0x0001); // Open/Save/SaveAs Button
if (_openButton == HWND.Null)
{
Log.Error(ClassName, "Failed to find open button handle");
return false;
}
return true;
}
#endregion
#region Windows Text
private static unsafe string GetWindowText(HWND handle)
{
int length;
Span<char> buffer = stackalloc char[1000];
fixed (char* pBuffer = buffer)
{
// If the control has no title bar or text, or if the control handle is invalid, the return value is zero.
length = (int)PInvoke.SendMessage(handle, PInvoke.WM_GETTEXT, 1000, (nint)pBuffer);
}
return buffer[..length].ToString();
}
private static unsafe nint SetWindowText(HWND handle, string text)
{
fixed (char* textPtr = text + '\0')
{
return PInvoke.SendMessage(handle, PInvoke.WM_SETTEXT, 0, (nint)textPtr).Value;
}
}
#endregion
#region Legacy Jump Folder
private bool JumpFolderWithFileEditor(string path, bool resetFocus)
{
// For Save / Save As dialog, the default value in file editor is not null and it can cause strange behaviors.
if (resetFocus && _dialogType == DialogType.SaveOrSaveAs) return false;
if (_fileEditor.IsNull && !GetFileEditor()) return false;
SetWindowText(_fileEditor, path);
if (_openButton.IsNull && !GetOpenButton()) return false;
PInvoke.SendMessage(_openButton, PInvoke.BM_CLICK, 0, 0);
return true;
}
#endregion
#endregion
}
internal enum DialogType
{
Others,
Open,
SaveOrSaveAs
}
}

View file

@ -0,0 +1,260 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Flow.Launcher.Plugin;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
using Windows.Win32.UI.Shell;
namespace Flow.Launcher.Infrastructure.DialogJump.Models
{
/// <summary>
/// Class for handling Windows Explorer instances in DialogJump.
/// </summary>
public class WindowsExplorer : IDialogJumpExplorer
{
public IDialogJumpExplorerWindow CheckExplorerWindow(IntPtr hwnd)
{
IDialogJumpExplorerWindow explorerWindow = null;
// Is it from Explorer?
var processName = Win32Helper.GetProcessNameFromHwnd(new(hwnd));
if (processName.Equals("explorer.exe", StringComparison.OrdinalIgnoreCase))
{
EnumerateShellWindows((shellWindow) =>
{
try
{
if (shellWindow is not IWebBrowser2 explorer) return true;
if (explorer.HWND != hwnd) return true;
explorerWindow = new WindowsExplorerWindow(hwnd);
return false;
}
catch
{
// Ignored
}
return true;
});
}
return explorerWindow;
}
internal static unsafe void EnumerateShellWindows(Func<object, bool> action)
{
// Create an instance of ShellWindows
var clsidShellWindows = new Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39"); // ShellWindowsClass
var iidIShellWindows = typeof(IShellWindows).GUID; // IShellWindows
var result = PInvoke.CoCreateInstance(
&clsidShellWindows,
null,
CLSCTX.CLSCTX_ALL,
&iidIShellWindows,
out var shellWindowsObj);
if (result.Failed) return;
var shellWindows = (IShellWindows)shellWindowsObj;
// Enumerate the shell windows
var count = shellWindows.Count;
for (var i = 0; i < count; i++)
{
if (!action(shellWindows.Item(i)))
{
return;
}
}
}
public void Dispose()
{
}
}
public class WindowsExplorerWindow : IDialogJumpExplorerWindow
{
public IntPtr Handle { get; }
private static Guid _shellBrowserGuid = typeof(IShellBrowser).GUID;
internal WindowsExplorerWindow(IntPtr handle)
{
Handle = handle;
}
public string GetExplorerPath()
{
if (Handle == IntPtr.Zero) return null;
var activeTabHandle = GetActiveTabHandle(new(Handle));
if (activeTabHandle.IsNull) return null;
var window = GetExplorerByTabHandle(activeTabHandle);
if (window == null) return null;
var path = GetLocation(window);
return path;
}
public void Dispose()
{
}
#region Helper Methods
// Inspired by: https://github.com/w4po/ExplorerTabUtility
private static HWND GetActiveTabHandle(HWND windowHandle)
{
// Active tab always at the top of the z-index, so it is the first child of the ShellTabWindowClass.
var activeTab = PInvoke.FindWindowEx(windowHandle, HWND.Null, "ShellTabWindowClass", null);
return activeTab;
}
private static IWebBrowser2 GetExplorerByTabHandle(HWND tabHandle)
{
if (tabHandle.IsNull) return null;
IWebBrowser2 window = null;
WindowsExplorer.EnumerateShellWindows((shellWindow) =>
{
try
{
return StartSTAThread(() =>
{
if (shellWindow is not IWebBrowser2 explorer) return true;
if (explorer is not IServiceProvider sp) return true;
sp.QueryService(ref _shellBrowserGuid, ref _shellBrowserGuid, out var shellBrowser);
if (shellBrowser == null) return true;
try
{
shellBrowser.GetWindow(out var hWnd); // Must execute in STA thread to get this hWnd
if (hWnd == tabHandle)
{
window = explorer;
return false;
}
}
catch
{
// Ignored
}
finally
{
Marshal.ReleaseComObject(shellBrowser);
}
return true;
}) ?? true;
}
catch
{
// Ignored
}
return true;
});
return window;
}
private static bool? StartSTAThread(Func<bool> action)
{
bool? result = null;
var thread = new Thread(() =>
{
result = action();
})
{
IsBackground = true
};
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
return result;
}
private static string GetLocation(IWebBrowser2 window)
{
var path = window.LocationURL.ToString();
if (!string.IsNullOrWhiteSpace(path)) return NormalizeLocation(path);
// Recycle Bin, This PC, etc
if (window.Document is not IShellFolderViewDual folderView) return null;
// Attempt to get the path from the folder view
try
{
// CSWin32 Folder does not have Self, so we need to use dynamic type here
// Use dynamic to bypass static typing
dynamic folder = folderView.Folder;
// Access the Self property via dynamic binding
dynamic folderItem = folder.Self;
// Get path from the folder item
path = folderItem.Path;
}
catch
{
return null;
}
return NormalizeLocation(path);
}
private static string NormalizeLocation(string location)
{
if (location.IndexOf('%') > -1)
location = Environment.ExpandEnvironmentVariables(location);
if (location.StartsWith("::", StringComparison.Ordinal))
location = $"shell:{location}";
else if (location.StartsWith("{", StringComparison.Ordinal))
location = $"shell:::{location}";
location = location.Trim(' ', '/', '\\', '\n', '\'', '"');
return location.Replace('/', '\\');
}
#endregion
}
#region COM Interfaces
// Inspired by: https://github.com/w4po/ExplorerTabUtility
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
[ComImport]
public interface IServiceProvider
{
[PreserveSig]
int QueryService(ref Guid guidService, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellBrowser ppvObject);
}
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214E2-0000-0000-C000-000000000046")]
[ComImport]
public interface IShellBrowser
{
[PreserveSig]
int GetWindow(out nint handle);
}
#endregion
}

View file

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Win32;
namespace Flow.Launcher.Infrastructure
{
@ -13,9 +9,10 @@ namespace Flow.Launcher.Infrastructure
/// </summary>
public static string GetActiveExplorerPath()
{
var explorerWindow = GetActiveExplorer();
string locationUrl = explorerWindow?.LocationURL;
return !string.IsNullOrEmpty(locationUrl) ? GetDirectoryPath(new Uri(locationUrl).LocalPath) : null;
var explorerPath = DialogJump.DialogJump.GetActiveExplorerPath();
return !string.IsNullOrEmpty(explorerPath) ?
GetDirectoryPath(new Uri(explorerPath).LocalPath) :
null;
}
/// <summary>
@ -23,74 +20,12 @@ namespace Flow.Launcher.Infrastructure
/// </summary>
private static string GetDirectoryPath(string path)
{
if (!path.EndsWith("\\"))
if (!path.EndsWith('\\'))
{
return path + "\\";
}
return path;
}
/// <summary>
/// Gets the file explorer that is currently in the foreground
/// </summary>
private static dynamic GetActiveExplorer()
{
Type type = Type.GetTypeFromProgID("Shell.Application");
if (type == null) return null;
dynamic shell = Activator.CreateInstance(type);
if (shell == null)
{
return null;
}
var explorerWindows = new List<dynamic>();
var openWindows = shell.Windows();
for (int i = 0; i < openWindows.Count; i++)
{
var window = openWindows.Item(i);
if (window == null) continue;
// find the desired window and make sure that it is indeed a file explorer
// we don't want the Internet Explorer or the classic control panel
// ToLower() is needed, because Windows can report the path as "C:\\Windows\\Explorer.EXE"
if (Path.GetFileName((string)window.FullName)?.ToLower() == "explorer.exe")
{
explorerWindows.Add(window);
}
}
if (explorerWindows.Count == 0) return null;
var zOrders = GetZOrder(explorerWindows);
return explorerWindows.Zip(zOrders).MinBy(x => x.Second).First;
}
/// <summary>
/// Gets the z-order for one or more windows atomically with respect to each other. In Windows, smaller z-order is higher. If the window is not top level, the z order is returned as -1.
/// </summary>
private static IEnumerable<int> GetZOrder(List<dynamic> hWnds)
{
var z = new int[hWnds.Count];
for (var i = 0; i < hWnds.Count; i++) z[i] = -1;
var index = 0;
var numRemaining = hWnds.Count;
PInvoke.EnumWindows((wnd, _) =>
{
var searchIndex = hWnds.FindIndex(x => new IntPtr(x.HWND) == wnd);
if (searchIndex != -1)
{
z[searchIndex] = index;
numRemaining--;
if (numRemaining == 0) return false;
}
index++;
return true;
}, IntPtr.Zero);
return z;
}
}
}

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<TargetFramework>net9.0-windows</TargetFramework>
<ProjectGuid>{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}</ProjectGuid>
<OutputType>Library</OutputType>
<UseWpf>true</UseWpf>
@ -12,6 +12,7 @@
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -33,6 +34,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>$(NoWarn);FLSG0007</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -53,28 +55,43 @@
<ItemGroup>
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="BitFaster.Caching" Version="2.5.3" />
<PackageReference Include="BitFaster.Caching" Version="2.5.4" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Fody" Version="6.5.5">
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
<PackageReference Include="Fody" Version="6.9.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="ini-parser" Version="2.5.2" />
<PackageReference Include="InputSimulator" Version="1.0.4" />
<PackageReference Include="MemoryPack" Version="1.21.4" />
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.12.19" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.14.15" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.205">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NLog" Version="4.7.10" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0">
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
<PackageReference Include="NLog" Version="6.0.4" />
<PackageReference Include="NLog.OutputDebugString" Version="6.0.4" />
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="SharpVectors.Wpf" Version="1.8.4.2" />
<PackageReference Include="SharpVectors.Wpf" Version="1.8.5" />
<!-- Do not upgrade this to higher version since it can cause this issue on WinForm platform: -->
<!-- PlatformNotSupportedException: SystemEvents is not supported on this platform. -->
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
<!--ToolGood.Words.Pinyin v3.0.2.6 results in high memory usage when search with pinyin is enabled-->
<!--Bumping to it or higher needs to test and ensure this is no longer a problem-->
<PackageReference Include="ToolGood.Words.Pinyin" Version="3.0.1.4" />
<PackageReference Include="ToolGood.Words.Pinyin" Version="3.1.0.3" />
</ItemGroup>
<PropertyGroup>
<FLLUseDependencyInjection>true</FLLUseDependencyInjection>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Remove="Languages\en.xaml" />
<AdditionalFiles Include="..\Flow.Launcher\Languages\en.xaml">
<Link>Languages\en.xaml</Link>
</AdditionalFiles>
</ItemGroup>
</Project>

View file

@ -4,10 +4,8 @@ using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using JetBrains.Annotations;
namespace Flow.Launcher.Infrastructure.Http
@ -78,7 +76,7 @@ namespace Flow.Launcher.Infrastructure.Http
}
catch (UriFormatException e)
{
Ioc.Default.GetRequiredService<IPublicAPI>().ShowMsg("Please try again", "Unable to parse Http Proxy");
PublicApi.Instance.ShowMsgError(Localize.pleaseTryAgain(), Localize.parseProxyFailed());
Log.Exception(ClassName, "Unable to parse Uri", e);
}
}
@ -220,5 +218,18 @@ namespace Flow.Launcher.Infrastructure.Http
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
public static async Task<string> 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;
}
}
}
}

View file

@ -0,0 +1,22 @@
namespace Flow.Launcher.Infrastructure
{
/// <summary>
/// Translate a language to English letters using a given rule.
/// </summary>
public interface IAlphabet
{
/// <summary>
/// Translate a string to English letters, using a given rule.
/// </summary>
/// <param name="stringToTranslate">String to translate.</param>
/// <returns></returns>
public (string translation, TranslationMapping map) Translate(string stringToTranslate);
/// <summary>
/// Determine if a string should be translated to English letter with this Alphabet.
/// </summary>
/// <param name="stringToTranslate">String to translate.</param>
/// <returns></returns>
public bool ShouldTranslate(string stringToTranslate);
}
}

View file

@ -19,37 +19,42 @@ namespace Flow.Launcher.Infrastructure.Image
private static readonly string ClassName = nameof(ImageLoader);
private static readonly ImageCache ImageCache = new();
private static SemaphoreSlim storageLock { get; } = new SemaphoreSlim(1, 1);
private static Lock storageLock { get; } = new();
private static BinaryStorage<List<(string, bool)>> _storage;
private static readonly ConcurrentDictionary<string, string> GuidToKey = new();
private static IImageHashGenerator _hashGenerator;
private static ImageHashGenerator _hashGenerator;
private static readonly bool EnableImageHash = true;
public static ImageSource Image { get; } = new BitmapImage(new Uri(Constant.ImageIcon));
public static ImageSource MissingImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon));
public static ImageSource LoadingImage { get; } = new BitmapImage(new Uri(Constant.LoadingImgIcon));
public static ImageSource Image => ImageCache[Constant.ImageIcon, false];
public static ImageSource MissingImage => ImageCache[Constant.MissingImgIcon, false];
public static ImageSource LoadingImage => ImageCache[Constant.LoadingImgIcon, false];
public const int SmallIconSize = 64;
public const int FullIconSize = 256;
public const int FullImageSize = 320;
private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" };
private static readonly string[] ImageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico"];
private static readonly string SvgExtension = ".svg";
public static async Task InitializeAsync()
{
_storage = new BinaryStorage<List<(string, bool)>>("Image");
_hashGenerator = new ImageHashGenerator();
var usage = await LoadStorageToConcurrentDictionaryAsync();
_storage.ClearData();
ImageCache.Initialize(usage);
foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon })
var usage = await Task.Run(() =>
{
ImageSource img = new BitmapImage(new Uri(icon));
img.Freeze();
ImageCache[icon, false] = img;
}
_storage = new BinaryStorage<List<(string, bool)>>("Image");
_hashGenerator = new ImageHashGenerator();
var usage = LoadStorageToConcurrentDictionary();
_storage.ClearData();
ImageCache.Initialize(usage);
foreach (var icon in new[] { Constant.DefaultIcon, Constant.ImageIcon, Constant.MissingImgIcon, Constant.LoadingImgIcon })
{
ImageSource img = new BitmapImage(new Uri(icon));
img.Freeze();
ImageCache[icon, false] = img;
}
return usage;
});
_ = Task.Run(async () =>
{
@ -64,42 +69,26 @@ namespace Flow.Launcher.Infrastructure.Image
});
}
public static async Task SaveAsync()
public static void Save()
{
await storageLock.WaitAsync();
try
lock (storageLock)
{
await _storage.SaveAsync(ImageCache.EnumerateEntries()
.Select(x => x.Key)
.ToList());
}
catch (System.Exception e)
{
Log.Exception(ClassName, "Failed to save image cache to file", e);
}
finally
{
storageLock.Release();
try
{
_storage.Save([.. ImageCache.EnumerateEntries().Select(x => x.Key)]);
}
catch (System.Exception e)
{
Log.Exception(ClassName, "Failed to save image cache to file", e);
}
}
}
public static async Task WaitSaveAsync()
private static List<(string, bool)> LoadStorageToConcurrentDictionary()
{
await storageLock.WaitAsync();
storageLock.Release();
}
private static async Task<List<(string, bool)>> LoadStorageToConcurrentDictionaryAsync()
{
await storageLock.WaitAsync();
try
lock (storageLock)
{
return await _storage.TryLoadAsync(new List<(string, bool)>());
}
finally
{
storageLock.Release();
return _storage.TryLoad([]);
}
}
@ -174,7 +163,7 @@ namespace Flow.Launcher.Infrastructure.Image
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];
ImageSource image = MissingImage;
ImageCache[path, false] = image;
imageResult = new ImageResult(image, ImageType.Error);
}
@ -273,7 +262,7 @@ namespace Flow.Launcher.Infrastructure.Image
}
else
{
image = ImageCache[Constant.MissingImgIcon, false];
image = MissingImage;
path = Constant.MissingImgIcon;
}
@ -338,7 +327,7 @@ namespace Flow.Launcher.Infrastructure.Image
return img;
}
private static ImageSource LoadFullImage(string path)
private static BitmapImage LoadFullImage(string path)
{
BitmapImage image = new BitmapImage();
image.BeginInit();
@ -375,7 +364,7 @@ namespace Flow.Launcher.Infrastructure.Image
return image;
}
private static ImageSource LoadSvgImage(string path, bool loadFullImage = false)
private static RenderTargetBitmap LoadSvgImage(string path, bool loadFullImage = false)
{
// Set up drawing settings
var desiredHeight = loadFullImage ? FullImageSize : SmallIconSize;

View file

@ -1,13 +1,14 @@
using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using IniParser;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.Shell;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.Shell;
namespace Flow.Launcher.Infrastructure.Image
{
@ -35,9 +36,32 @@ namespace Flow.Launcher.Infrastructure.Image
private static readonly HRESULT S_PATHNOTFOUND = (HRESULT)0x8004B205;
private const string UrlExtension = ".url";
/// <summary>
/// Obtains a BitmapSource thumbnail for the specified file.
/// </summary>
/// <remarks>
/// If the file is a Windows URL shortcut (".url"), the method attempts to resolve the shortcut's icon and use that for the thumbnail; otherwise it requests a thumbnail for the file path. The native HBITMAP used to create the BitmapSource is always released to avoid native memory leaks.
/// </remarks>
/// <param name="fileName">Path to the file (can be a regular file or a ".url" shortcut).</param>
/// <param name="width">Requested thumbnail width in pixels.</param>
/// <param name="height">Requested thumbnail height in pixels.</param>
/// <param name="options">Thumbnail extraction options (flags) controlling fallback and caching behavior.</param>
/// <returns>A BitmapSource representing the requested thumbnail.</returns>
public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
{
HBITMAP hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
HBITMAP hBitmap;
var extension = Path.GetExtension(fileName);
if (string.Equals(extension, UrlExtension, StringComparison.OrdinalIgnoreCase))
{
hBitmap = GetHBitmapForUrlFile(fileName, width, height, options);
}
else
{
hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
}
try
{
@ -50,6 +74,21 @@ namespace Flow.Launcher.Infrastructure.Image
}
}
/// <summary>
/// Obtains a native HBITMAP for the specified file at the requested size using the Windows Shell image factory.
/// </summary>
/// <remarks>
/// If <paramref name="options"/> is <see cref="ThumbnailOptions.ThumbnailOnly"/> and thumbnail extraction fails
/// due to extraction errors or a missing path, the method falls back to requesting an icon (<see cref="ThumbnailOptions.IconOnly"/>).
/// The returned HBITMAP is a raw GDI handle; the caller is responsible for releasing it (e.g., via DeleteObject) to avoid native memory leaks.
/// </remarks>
/// <param name="fileName">Path to the file to thumbnail.</param>
/// <param name="width">Requested thumbnail width in pixels.</param>
/// <param name="height">Requested thumbnail height in pixels.</param>
/// <param name="options">Thumbnail request flags that control behavior (e.g., ThumbnailOnly, IconOnly).</param>
/// <returns>An HBITMAP handle containing the image. Caller must free the handle when finished.</returns>
/// <exception cref="COMException">If creating the shell item fails (HRESULT returned by SHCreateItemFromParsingName).</exception>
/// <exception cref="InvalidOperationException">If the shell item does not expose IShellItemImageFactory or if an unexpected error occurs while obtaining the image.</exception>
private static unsafe HBITMAP GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
{
var retCode = PInvoke.SHCreateItemFromParsingName(
@ -108,5 +147,44 @@ namespace Flow.Launcher.Infrastructure.Image
return hBitmap;
}
/// <summary>
/// Obtains an HBITMAP for a Windows .url shortcut by resolving its IconFile entry and delegating to GetHBitmap.
/// </summary>
/// <remarks>
/// The method parses the .url file as an INI, looks in the "InternetShortcut" section for the "IconFile" entry,
/// and requests a bitmap for that icon path. If no IconFile is present or any error occurs while reading or
/// resolving the icon, it falls back to requesting a thumbnail for the .url file itself.
/// </remarks>
/// <param name="fileName">Path to the .url shortcut file.</param>
/// <param name="width">Requested thumbnail width (pixels).</param>
/// <param name="height">Requested thumbnail height (pixels).</param>
/// <param name="options">ThumbnailOptions flags controlling extraction behavior.</param>
/// <returns>An HBITMAP containing the requested image; callers are responsible for freeing the native handle.</returns>
private static unsafe HBITMAP GetHBitmapForUrlFile(string fileName, int width, int height, ThumbnailOptions options)
{
HBITMAP hBitmap;
try
{
var parser = new FileIniDataParser();
var data = parser.ReadFile(fileName);
var urlSection = data["InternetShortcut"];
var iconPath = urlSection?["IconFile"];
if (!File.Exists(iconPath))
{
// If the IconFile is missing, throw exception to fallback to the default icon
throw new FileNotFoundException("Icon file not specified in Internet shortcut (.url) file.");
}
hBitmap = GetHBitmap(Path.GetFullPath(iconPath), width, height, options);
}
catch
{
hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
}
return hBitmap;
}
}
}

View file

@ -34,7 +34,7 @@ namespace Flow.Launcher.Infrastructure.Logger
var fileTarget = new FileTarget
{
FileName = CurrentLogDirectory.Replace(@"\", "/") + "/${shortdate}.txt",
FileName = CurrentLogDirectory.Replace(@"\", "/") + "/Flow.Launcher.${date:format=yyyy-MM-dd}.log",
Layout = layout
};
@ -65,26 +65,22 @@ namespace Flow.Launcher.Infrastructure.Logger
public static void SetLogLevel(LOGLEVEL level)
{
switch (level)
var rule = LogManager.Configuration.FindRuleByName("file");
var nlogLevel = level switch
{
case LOGLEVEL.DEBUG:
UseDebugLogLevel();
break;
default:
UseInfoLogLevel();
break;
}
Info(nameof(Logger), $"Using log level: {level}.");
}
LOGLEVEL.NONE => LogLevel.Off,
LOGLEVEL.ERROR => LogLevel.Error,
LOGLEVEL.DEBUG => LogLevel.Debug,
_ => LogLevel.Info
};
private static void UseDebugLogLevel()
{
LogManager.Configuration.FindRuleByName("file").SetLoggingLevels(LogLevel.Debug, LogLevel.Fatal);
}
rule.SetLoggingLevels(nlogLevel, LogLevel.Fatal);
private static void UseInfoLogLevel()
{
LogManager.Configuration.FindRuleByName("file").SetLoggingLevels(LogLevel.Info, LogLevel.Fatal);
LogManager.ReconfigExistingLoggers();
// We can't log Info when level is set to Error or None, so we use Debug
Debug(nameof(Logger), $"Using log level: {level}.");
}
private static void LogFaultyFormat(string message)
@ -169,7 +165,9 @@ namespace Flow.Launcher.Infrastructure.Logger
public enum LOGLEVEL
{
DEBUG,
INFO
NONE,
ERROR,
INFO,
DEBUG
}
}

View file

@ -34,14 +34,13 @@ WINDOW_STYLE
SetLastError
WINDOW_EX_STYLE
GetSystemMetrics
EnumDisplayMonitors
MonitorFromWindow
GetMonitorInfo
MONITORINFOEXW
WM_ENTERSIZEMOVE
WM_EXITSIZEMOVE
WM_NCLBUTTONDBLCLK
WM_SYSCOMMAND
SC_MAXIMIZE
SC_MINIMIZE
OleInitialize
OleUninitialize
@ -61,3 +60,37 @@ LOCALE_TRANSIENT_KEYBOARD4
SHParseDisplayName
SHOpenFolderAndSelectItems
CoTaskMemFree
SetWinEventHook
UnhookWinEvent
SendMessage
EVENT_SYSTEM_FOREGROUND
WINEVENT_OUTOFCONTEXT
WM_SETTEXT
IShellFolderViewDual2
CoCreateInstance
CLSCTX
IShellWindows
IWebBrowser2
EVENT_OBJECT_DESTROY
EVENT_OBJECT_LOCATIONCHANGE
EVENT_SYSTEM_MOVESIZESTART
EVENT_SYSTEM_MOVESIZEEND
GetDlgItem
PostMessage
BM_CLICK
WM_GETTEXT
OpenProcess
QueryFullProcessImageName
EVENT_OBJECT_HIDE
EVENT_SYSTEM_DIALOGEND
DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
WM_POWERBROADCAST
PBT_APMRESUMEAUTOMATIC
PBT_APMRESUMESUSPEND
PowerRegisterSuspendResumeNotification
PowerUnregisterSuspendResumeNotification
DeviceNotifyCallbackRoutine
MonitorFromWindow

View file

@ -1,209 +1,196 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;
using JetBrains.Annotations;
using System.Text.Json;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.UserSettings;
using ToolGood.Words.Pinyin;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Logger;
namespace Flow.Launcher.Infrastructure
{
public class TranslationMapping
{
private bool constructed;
private List<int> originalIndexs = new List<int>();
private List<int> translatedIndexs = new List<int>();
private int translatedLength = 0;
public string key { get; private set; }
public void setKey(string key)
{
this.key = key;
}
public void AddNewIndex(int originalIndex, int translatedIndex, int length)
{
if (constructed)
throw new InvalidOperationException("Mapping shouldn't be changed after constructed");
originalIndexs.Add(originalIndex);
translatedIndexs.Add(translatedIndex);
translatedIndexs.Add(translatedIndex + length);
translatedLength += length - 1;
}
public int MapToOriginalIndex(int translatedIndex)
{
if (translatedIndex > translatedIndexs.Last())
return translatedIndex - translatedLength - 1;
int lowerBound = 0;
int upperBound = originalIndexs.Count - 1;
int count = 0;
// Corner case handle
if (translatedIndex < translatedIndexs[0])
return translatedIndex;
if (translatedIndex > translatedIndexs.Last())
{
int indexDef = 0;
for (int k = 0; k < originalIndexs.Count; k++)
{
indexDef += translatedIndexs[k * 2 + 1] - translatedIndexs[k * 2];
}
return translatedIndex - indexDef - 1;
}
// Binary Search with Range
for (int i = originalIndexs.Count / 2;; count++)
{
if (translatedIndex < translatedIndexs[i * 2])
{
// move to lower middle
upperBound = i;
i = (i + lowerBound) / 2;
}
else if (translatedIndex > translatedIndexs[i * 2 + 1] - 1)
{
lowerBound = i;
// move to upper middle
// due to floor of integer division, move one up on corner case
i = (i + upperBound + 1) / 2;
}
else
return originalIndexs[i];
if (upperBound - lowerBound <= 1 &&
translatedIndex > translatedIndexs[lowerBound * 2 + 1] &&
translatedIndex < translatedIndexs[upperBound * 2])
{
int indexDef = 0;
for (int j = 0; j < upperBound; j++)
{
indexDef += translatedIndexs[j * 2 + 1] - translatedIndexs[j * 2];
}
return translatedIndex - indexDef - 1;
}
}
}
public void endConstruct()
{
if (constructed)
throw new InvalidOperationException("Mapping has already been constructed");
constructed = true;
}
}
/// <summary>
/// Translate a language to English letters using a given rule.
/// </summary>
public interface IAlphabet
{
/// <summary>
/// Translate a string to English letters, using a given rule.
/// </summary>
/// <param name="stringToTranslate">String to translate.</param>
/// <returns></returns>
public (string translation, TranslationMapping map) Translate(string stringToTranslate);
/// <summary>
/// Determine if a string can be translated to English letter with this Alphabet.
/// </summary>
/// <param name="stringToTranslate">String to translate.</param>
/// <returns></returns>
public bool CanBeTranslated(string stringToTranslate);
}
public class PinyinAlphabet : IAlphabet
{
private ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache =
new ConcurrentDictionary<string, (string translation, TranslationMapping map)>();
private Settings _settings;
private readonly ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache = new();
private readonly Settings _settings;
private ReadOnlyDictionary<string, string> currentDoublePinyinTable;
public PinyinAlphabet()
{
Initialize(Ioc.Default.GetRequiredService<Settings>());
_settings = Ioc.Default.GetRequiredService<Settings>();
LoadDoublePinyinTable();
_settings.PropertyChanged += (sender, e) =>
{
switch (e.PropertyName)
{
case nameof(Settings.ShouldUsePinyin):
if (_settings.ShouldUsePinyin)
{
Reload();
}
break;
case nameof(Settings.UseDoublePinyin):
case nameof(Settings.DoublePinyinSchema):
if (_settings.UseDoublePinyin)
{
Reload();
}
break;
}
};
}
private void Initialize([NotNull] Settings settings)
public void Reload()
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
LoadDoublePinyinTable();
_pinyinCache.Clear();
}
public bool CanBeTranslated(string stringToTranslate)
private void CreateDoublePinyinTableFromStream(Stream jsonStream)
{
return WordsHelper.HasChinese(stringToTranslate);
var table = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string>>>(jsonStream) ??
throw new InvalidOperationException("Failed to deserialize double pinyin table: result is null");
var schemaKey = _settings.DoublePinyinSchema.ToString();
if (!table.TryGetValue(schemaKey, out var schemaDict))
{
throw new ArgumentException($"DoublePinyinSchema '{schemaKey}' is invalid or double pinyin table is broken.");
}
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(schemaDict);
}
private void LoadDoublePinyinTable()
{
if (!_settings.UseDoublePinyin)
{
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
return;
}
var tablePath = Path.Combine(AppContext.BaseDirectory, "Resources", "double_pinyin.json");
try
{
using var fs = File.OpenRead(tablePath);
CreateDoublePinyinTableFromStream(fs);
}
catch (FileNotFoundException e)
{
Log.Exception(nameof(PinyinAlphabet), $"Double pinyin table file not found: {tablePath}", e);
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
}
catch (DirectoryNotFoundException e)
{
Log.Exception(nameof(PinyinAlphabet), $"Directory not found for double pinyin table: {tablePath}", e);
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
}
catch (UnauthorizedAccessException e)
{
Log.Exception(nameof(PinyinAlphabet), $"Access denied to double pinyin table: {tablePath}", e);
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
}
catch (System.Exception e)
{
Log.Exception(nameof(PinyinAlphabet), $"Failed to load double pinyin table from file: {tablePath}", e);
currentDoublePinyinTable = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
}
}
public bool ShouldTranslate(string stringToTranslate)
{
// If the query (stringToTranslate) does NOT contain Chinese characters,
// we should translate the target string to pinyin for matching
return _settings.ShouldUsePinyin && !ContainsChinese(stringToTranslate);
}
public (string translation, TranslationMapping map) Translate(string content)
{
if (_settings.ShouldUsePinyin)
{
if (!_pinyinCache.ContainsKey(content))
{
return BuildCacheFromContent(content);
}
else
{
return _pinyinCache[content];
}
}
return (content, null);
if (!_settings.ShouldUsePinyin || !ContainsChinese(content))
return (content, null);
return _pinyinCache.TryGetValue(content, out var cached) ? cached : BuildCacheFromContent(content);
}
private (string translation, TranslationMapping map) BuildCacheFromContent(string content)
{
if (WordsHelper.HasChinese(content))
var resultList = WordsHelper.GetPinyinList(content);
var resultBuilder = new StringBuilder(_settings.UseDoublePinyin ? 3 : 4); // Pre-allocate with estimated capacity
var map = new TranslationMapping();
var previousIsChinese = false;
for (var i = 0; i < resultList.Length; i++)
{
var resultList = WordsHelper.GetPinyinList(content);
StringBuilder resultBuilder = new StringBuilder();
TranslationMapping map = new TranslationMapping();
bool pre = false;
for (int i = 0; i < resultList.Length; i++)
if (IsChineseCharacter(content[i]))
{
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
var translated = _settings.UseDoublePinyin ? ToDoublePinyin(resultList[i]) : resultList[i];
if (i > 0 && content[i - 1] != ' ')
{
map.AddNewIndex(i, resultBuilder.Length, resultList[i].Length + 1);
resultBuilder.Append(' ');
resultBuilder.Append(resultList[i]);
pre = true;
}
else
map.AddNewIndex(resultBuilder.Length, translated.Length);
resultBuilder.Append(translated);
previousIsChinese = true;
}
else
{
// Add space after Chinese characters before non-Chinese characters
if (previousIsChinese)
{
if (pre)
previousIsChinese = false;
if (content[i] != ' ')
{
pre = false;
resultBuilder.Append(' ');
}
resultBuilder.Append(resultList[i]);
}
map.AddNewIndex(resultBuilder.Length, 1);
resultBuilder.Append(content[i]);
}
map.endConstruct();
var key = resultBuilder.ToString();
map.setKey(key);
return _pinyinCache[content] = (key, map);
}
else
map.EndConstruct();
var translation = resultBuilder.ToString();
var result = (translation, map);
return _pinyinCache[content] = result;
}
/// <summary>
/// Optimized Chinese character detection using the comprehensive CJK Unicode ranges
/// </summary>
private static bool ContainsChinese(ReadOnlySpan<char> text)
{
foreach (var c in text)
{
return (content, null);
if (IsChineseCharacter(c))
return true;
}
return false;
}
/// <summary>
/// Check if a character is a Chinese character using comprehensive Unicode ranges
/// Covers CJK Unified Ideographs, Extension A
/// </summary>
private static bool IsChineseCharacter(char c)
{
return (c >= 0x4E00 && c <= 0x9FFF) || // CJK Unified Ideographs
(c >= 0x3400 && c <= 0x4DBF); // CJK Extension A
}
private string ToDoublePinyin(string fullPinyin)
{
return currentDoublePinyinTable.TryGetValue(fullPinyin, out var doublePinyinValue)
? doublePinyinValue
: fullPinyin;
}
}
}

View file

@ -12,7 +12,7 @@ using MemoryPack;
namespace Flow.Launcher.Infrastructure.Storage
{
/// <summary>
/// Stroage object using binary data
/// Storage object using binary data
/// Normally, it has better performance, but not readable
/// </summary>
/// <remarks>
@ -53,6 +53,45 @@ namespace Flow.Launcher.Infrastructure.Storage
FilePath = Path.Combine(DirectoryPath, $"{filename}{FileSuffix}");
}
public T TryLoad(T defaultData)
{
if (Data != null) return Data;
if (File.Exists(FilePath))
{
if (new FileInfo(FilePath).Length == 0)
{
Log.Error(ClassName, $"Zero length cache file <{FilePath}>");
Data = defaultData;
Save();
}
var bytes = File.ReadAllBytes(FilePath);
Data = Deserialize(bytes, defaultData);
}
else
{
Log.Info(ClassName, "Cache file not exist, load default data");
Data = defaultData;
Save();
}
return Data;
}
private T Deserialize(ReadOnlySpan<byte> bytes, T defaultData)
{
try
{
var t = MemoryPackSerializer.Deserialize<T>(bytes);
return t ?? defaultData;
}
catch (System.Exception e)
{
Log.Exception(ClassName, $"Deserialize error for file <{FilePath}>", e);
return defaultData;
}
}
public async ValueTask<T> TryLoadAsync(T defaultData)
{
if (Data != null) return Data;
@ -79,26 +118,31 @@ namespace Flow.Launcher.Infrastructure.Storage
return Data;
}
private static async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
private async ValueTask<T> DeserializeAsync(Stream stream, T defaultData)
{
try
{
var t = await MemoryPackSerializer.DeserializeAsync<T>(stream);
return t ?? defaultData;
}
catch (System.Exception)
catch (System.Exception e)
{
// Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e);
Log.Exception(ClassName, $"Deserialize error for file <{FilePath}>", e);
return defaultData;
}
}
public void Save()
{
Save(Data.NonNull());
}
public void Save(T data)
{
// User may delete the directory, so we need to check it
FilesFolders.ValidateDirectory(DirectoryPath);
var serialized = MemoryPackSerializer.Serialize(Data);
var serialized = MemoryPackSerializer.Serialize(data);
File.WriteAllBytes(FilePath, serialized);
}
@ -107,15 +151,6 @@ namespace Flow.Launcher.Infrastructure.Storage
await SaveAsync(Data.NonNull());
}
// ImageCache need to convert data into concurrent dictionary for usage,
// so we would better to clear the data
public void ClearData()
{
Data = default;
}
// ImageCache storages data in its class,
// 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
@ -124,5 +159,12 @@ namespace Flow.Launcher.Infrastructure.Storage
await using var stream = new FileStream(FilePath, FileMode.Create);
await MemoryPackSerializer.SerializeAsync(stream, data);
}
// ImageCache need to convert data into concurrent dictionary for usage,
// so we would better to clear the data
public void ClearData()
{
Data = default;
}
}
}

View file

@ -2,11 +2,13 @@
using System.Threading.Tasks;
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<T> : JsonStorage<T> where T : new()
// Expose ISaveable interface in derived class to make sure we are calling the new version of Save method
public class FlowLauncherJsonStorage<T> : JsonStorage<T>, ISavable where T : new()
{
private static readonly string ClassName = "FlowLauncherJsonStorage";

View file

@ -1,11 +1,13 @@
using System.IO;
using System.Threading.Tasks;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Infrastructure.Storage
{
public class PluginBinaryStorage<T> : BinaryStorage<T> where T : new()
// Expose ISaveable interface in derived class to make sure we are calling the new version of Save method
public class PluginBinaryStorage<T> : BinaryStorage<T>, ISavable where T : new()
{
private static readonly string ClassName = "PluginBinaryStorage";

View file

@ -2,11 +2,13 @@
using System.Threading.Tasks;
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<T> : JsonStorage<T> where T : new()
// Expose ISaveable interface in derived class to make sure we are calling the new version of Save method
public class PluginJsonStorage<T> : JsonStorage<T>, ISavable where T : new()
{
// Use assembly name to check which plugin is using this storage
public readonly string AssemblyName;

View file

@ -68,7 +68,7 @@ namespace Flow.Launcher.Infrastructure
query = query.Trim();
TranslationMapping translationMapping = null;
if (_alphabet is not null && !_alphabet.CanBeTranslated(query))
if (_alphabet is not null && _alphabet.ShouldTranslate(query))
{
// We assume that if a query can be translated (containing characters of a language, like Chinese)
// it actually means user doesn't want it to be translated to English letters.
@ -228,7 +228,7 @@ namespace Flow.Launcher.Infrastructure
return new MatchResult(false, UserSettingSearchPrecision);
}
private bool IsAcronym(string stringToCompare, int compareStringIndex)
private static bool IsAcronym(string stringToCompare, int compareStringIndex)
{
if (IsAcronymChar(stringToCompare, compareStringIndex) || IsAcronymNumber(stringToCompare, compareStringIndex))
return true;
@ -237,7 +237,7 @@ namespace Flow.Launcher.Infrastructure
}
// When counting acronyms, treat a set of numbers as one acronym ie. Visual 2019 as 2 acronyms instead of 5
private bool IsAcronymCount(string stringToCompare, int compareStringIndex)
private static bool IsAcronymCount(string stringToCompare, int compareStringIndex)
{
if (IsAcronymChar(stringToCompare, compareStringIndex))
return true;

View file

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
namespace Flow.Launcher.Infrastructure
{
public class TranslationMapping
{
private bool _isConstructed;
// Assuming one original item maps to multi translated items
// list[i] is the last translated index + 1 of original index i
private readonly List<int> _originalToTranslated = new();
public void AddNewIndex(int translatedIndex, int length)
{
if (_isConstructed)
throw new InvalidOperationException("Mapping shouldn't be changed after construction");
_originalToTranslated.Add(translatedIndex + length);
}
public int MapToOriginalIndex(int translatedIndex)
{
var searchResult = _originalToTranslated.BinarySearch(translatedIndex);
return searchResult >= 0 ? searchResult + 1 : ~searchResult;
}
public void EndConstruct()
{
if (_isConstructed)
throw new InvalidOperationException("Mapping has already been constructed");
_isConstructed = true;
}
}
}

View file

@ -1,58 +0,0 @@
using System;
using System.Windows.Markup;
namespace Flow.Launcher.Infrastructure.UI
{
[Obsolete("EnumBindingSourceExtension is obsolete. Use with Flow.Launcher.Localization NuGet package instead.")]
public class EnumBindingSourceExtension : MarkupExtension
{
private Type _enumType;
public Type EnumType
{
get { return _enumType; }
set
{
if (value != _enumType)
{
if (value != null)
{
Type enumType = Nullable.GetUnderlyingType(value) ?? value;
if (!enumType.IsEnum)
{
throw new ArgumentException("Type must represent an enum.");
}
}
_enumType = value;
}
}
}
public EnumBindingSourceExtension() { }
public EnumBindingSourceExtension(Type enumType)
{
EnumType = enumType;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (_enumType == null)
{
throw new InvalidOperationException("The EnumType must be specified.");
}
Type actualEnumType = Nullable.GetUnderlyingType(_enumType) ?? _enumType;
Array enumValues = Enum.GetValues(actualEnumType);
if (actualEnumType == _enumType)
{
return enumValues;
}
Array tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1);
enumValues.CopyTo(tempArray, 1);
return tempArray;
}
}
}

View file

@ -1,11 +1,13 @@
using System.Text.Json.Serialization;
using Flow.Launcher.Plugin;
using System.Text.Json.Serialization;
namespace Flow.Launcher.Infrastructure.UserSettings
{
public class CustomBrowserViewModel : BaseModel
{
public string Name { get; set; }
[JsonIgnore]
public string DisplayName => Name == "Default" ? Localize.defaultBrowser_default() : Name;
public string Path { get; set; }
public string PrivateArg { get; set; }
public bool EnablePrivate { get; set; }
@ -26,8 +28,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Editable = Editable
};
}
public void OnDisplayNameChanged()
{
OnPropertyChanged(nameof(DisplayName));
}
}
}

View file

@ -1,10 +1,13 @@
using Flow.Launcher.Plugin;
using System.Text.Json.Serialization;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.ViewModel
namespace Flow.Launcher.Infrastructure.UserSettings
{
public class CustomExplorerViewModel : BaseModel
{
public string Name { get; set; }
[JsonIgnore]
public string DisplayName => Name == "Explorer" ? Localize.fileManagerExplorer() : Name;
public string Path { get; set; }
public string FileArgument { get; set; } = "\"%d\"";
public string DirectoryArgument { get; set; } = "\"%d\"";
@ -21,5 +24,10 @@ namespace Flow.Launcher.ViewModel
Editable = Editable
};
}
public void OnDisplayNameChanged()
{
OnPropertyChanged(nameof(DisplayName));
}
}
}

View file

@ -53,6 +53,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
public string Description { get; set; }
public string LocalizedDescription => PublicApi.Instance.GetTranslation(Description);
public BaseBuiltinShortcutModel(string key, string description)
{
Key = key;

View file

@ -7,8 +7,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
public const string PortableFolderName = "UserData";
public const string DeletionIndicatorFile = ".dead";
public static string PortableDataPath = Path.Combine(Constant.ProgramDirectory, PortableFolderName);
public static string RoamingDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FlowLauncher");
public static readonly string PortableDataPath = Path.Combine(Constant.ProgramDirectory, PortableFolderName);
public static readonly string RoamingDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FlowLauncher");
public static string DataDirectory()
{
if (PortableDataLocationInUse())
@ -19,7 +19,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public static bool PortableDataLocationInUse()
{
if (Directory.Exists(PortableDataPath) && !File.Exists(DeletionIndicatorFile))
if (Directory.Exists(PortableDataPath) &&
!File.Exists(Path.Combine(PortableDataPath, DeletionIndicatorFile)))
return true;
return false;
@ -39,6 +40,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public const string PythonEnvironmentName = "Python";
public const string NodeEnvironmentName = "Node.js";
public const string PluginEnvironments = "Environments";
public const string PluginDeleteFile = "NeedDelete.txt";
public static readonly string PluginEnvironmentsPath = Path.Combine(DataDirectory(), PluginEnvironments);
}
}

View file

@ -1,4 +1,5 @@
using Flow.Launcher.Plugin;
using System;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings
{
@ -6,5 +7,26 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
public string Hotkey { get; set; }
public string ActionKeyword { get; set; }
public CustomPluginHotkey(string hotkey, string actionKeyword)
{
Hotkey = hotkey;
ActionKeyword = actionKeyword;
}
public override bool Equals(object other)
{
if (other is CustomPluginHotkey otherHotkey)
{
return Hotkey == otherHotkey.Hotkey && ActionKeyword == otherHotkey.ActionKeyword;
}
return false;
}
public override int GetHashCode()
{
return HashCode.Combine(Hotkey, ActionKeyword);
}
}
}

View file

@ -7,9 +7,9 @@ using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Localization.Attributes;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.Infrastructure.UserSettings
{
@ -25,7 +25,13 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public void Initialize()
{
// Initialize dependency injection instances after Ioc.Default is created
_stringMatcher = Ioc.Default.GetRequiredService<StringMatcher>();
// Initialize application resources after application is created
var settingWindowFont = new FontFamily(SettingWindowFont);
Application.Current.Resources["SettingWindowFont"] = settingWindowFont;
Application.Current.Resources["ContentControlThemeFontFamily"] = settingWindowFont;
}
public void Save()
@ -33,11 +39,38 @@ namespace Flow.Launcher.Infrastructure.UserSettings
_storage.Save();
}
private string _theme = Constant.DefaultTheme;
public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}";
public string OpenResultModifiers { get; set; } = KeyConstant.Alt;
private string _openResultModifiers = KeyConstant.Alt;
public string OpenResultModifiers
{
get => _openResultModifiers;
set
{
if (_openResultModifiers != value)
{
_openResultModifiers = value;
OnPropertyChanged();
}
}
}
public string ColorScheme { get; set; } = "System";
public bool ShowOpenResultHotkey { get; set; } = true;
private bool _showOpenResultHotkey = true;
public bool ShowOpenResultHotkey
{
get => _showOpenResultHotkey;
set
{
if (_showOpenResultHotkey != value)
{
_showOpenResultHotkey = value;
OnPropertyChanged();
}
}
}
public double WindowSize { get; set; } = 580;
public string PreviewHotkey { get; set; } = $"F1";
public string AutoCompleteHotkey { get; set; } = $"{KeyConstant.Ctrl} + Tab";
@ -53,6 +86,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public string OpenHistoryHotkey { get; set; } = $"Ctrl+H";
public string CycleHistoryUpHotkey { get; set; } = $"{KeyConstant.Alt} + Up";
public string CycleHistoryDownHotkey { get; set; } = $"{KeyConstant.Alt} + Down";
public string DialogJumpHotkey { get; set; } = $"{KeyConstant.Alt} + G";
private string _language = Constant.SystemLanguageCode;
public string Language
@ -60,16 +94,20 @@ namespace Flow.Launcher.Infrastructure.UserSettings
get => _language;
set
{
_language = value;
OnPropertyChanged();
if (_language != value)
{
_language = value;
OnPropertyChanged();
}
}
}
private string _theme = Constant.DefaultTheme;
public string Theme
{
get => _theme;
set
{
if (value != _theme)
if (_theme != value)
{
_theme = value;
OnPropertyChanged();
@ -78,7 +116,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
}
}
public bool UseDropShadowEffect { get; set; } = true;
public BackdropTypes BackdropType{ get; set; } = BackdropTypes.None;
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;
@ -115,8 +154,11 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
_settingWindowFont = value;
OnPropertyChanged();
Application.Current.Resources["SettingWindowFont"] = new FontFamily(value);
Application.Current.Resources["ContentControlThemeFontFamily"] = new FontFamily(value);
if (Application.Current != null)
{
Application.Current.Resources["SettingWindowFont"] = new FontFamily(value);
Application.Current.Resources["ContentControlThemeFontFamily"] = new FontFamily(value);
}
}
}
}
@ -190,6 +232,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public int MaxHistoryResultsToShowForHomePage { get; set; } = 5;
public bool AutoRestartAfterChanging { get; set; } = false;
public bool ShowUnknownSourceWarning { get; set; } = true;
public bool AutoUpdatePlugins { get; set; } = true;
public int CustomExplorerIndex { get; set; } = 0;
[JsonIgnore]
@ -278,13 +324,70 @@ namespace Flow.Launcher.Infrastructure.UserSettings
}
};
public bool EnableDialogJump { get; set; } = true;
public bool AutoDialogJump { get; set; } = false;
public bool ShowDialogJumpWindow { get; set; } = false;
[JsonConverter(typeof(JsonStringEnumConverter))]
public DialogJumpWindowPositions DialogJumpWindowPosition { get; set; } = DialogJumpWindowPositions.UnderDialog;
[JsonConverter(typeof(JsonStringEnumConverter))]
public DialogJumpResultBehaviours DialogJumpResultBehaviour { get; set; } = DialogJumpResultBehaviours.LeftClick;
[JsonConverter(typeof(JsonStringEnumConverter))]
public DialogJumpFileResultBehaviours DialogJumpFileResultBehaviour { get; set; } = DialogJumpFileResultBehaviours.FullPath;
[JsonConverter(typeof(JsonStringEnumConverter))]
public LOGLEVEL LogLevel { get; set; } = LOGLEVEL.INFO;
/// <summary>
/// when false Alphabet static service will always return empty results
/// </summary>
public bool ShouldUsePinyin { get; set; } = false;
private bool _useAlphabet = true;
public bool ShouldUsePinyin
{
get => _useAlphabet;
set
{
if (_useAlphabet != value)
{
_useAlphabet = value;
OnPropertyChanged();
}
}
}
private bool _useDoublePinyin = false;
public bool UseDoublePinyin
{
get => _useDoublePinyin;
set
{
if (_useDoublePinyin != value)
{
_useDoublePinyin = value;
OnPropertyChanged();
}
}
}
private DoublePinyinSchemas _doublePinyinSchema = DoublePinyinSchemas.XiaoHe;
[JsonInclude, JsonConverter(typeof(JsonStringEnumConverter))]
public DoublePinyinSchemas DoublePinyinSchema
{
get => _doublePinyinSchema;
set
{
if (_doublePinyinSchema != value)
{
_doublePinyinSchema = value;
OnPropertyChanged();
}
}
}
public bool AlwaysPreview { get; set; } = false;
@ -297,9 +400,12 @@ namespace Flow.Launcher.Infrastructure.UserSettings
get => _querySearchPrecision;
set
{
_querySearchPrecision = value;
if (_stringMatcher != null)
_stringMatcher.UserSettingSearchPrecision = value;
if (_querySearchPrecision != value)
{
_querySearchPrecision = value;
if (_stringMatcher != null)
_stringMatcher.UserSettingSearchPrecision = value;
}
}
}
@ -366,12 +472,30 @@ namespace Flow.Launcher.Infrastructure.UserSettings
get => _hideNotifyIcon;
set
{
_hideNotifyIcon = value;
OnPropertyChanged();
if (_hideNotifyIcon != value)
{
_hideNotifyIcon = value;
OnPropertyChanged();
}
}
}
public bool LeaveCmdOpen { get; set; }
public bool HideWhenDeactivated { get; set; } = true;
public bool ShowTaskbarWhenInvoked { get; set; } = false;
private bool _showAtTopmost = false;
public bool ShowAtTopmost
{
get => _showAtTopmost;
set
{
if (_showAtTopmost != value)
{
_showAtTopmost = value;
OnPropertyChanged();
}
}
}
public bool SearchQueryResultsWithDelay { get; set; }
public int SearchDelayTime { get; set; } = 150;
@ -391,6 +515,21 @@ namespace Flow.Launcher.Infrastructure.UserSettings
[JsonConverter(typeof(JsonStringEnumConverter))]
public LastQueryMode LastQueryMode { get; set; } = LastQueryMode.Selected;
private HistoryStyle _historyStyle = HistoryStyle.Query;
[JsonConverter(typeof(JsonStringEnumConverter))]
public HistoryStyle HistoryStyle
{
get => _historyStyle;
set
{
if (_historyStyle != value)
{
_historyStyle = value;
OnPropertyChanged();
}
}
}
[JsonConverter(typeof(JsonStringEnumConverter))]
public AnimationSpeeds AnimationSpeed { get; set; } = AnimationSpeeds.Medium;
public int CustomAnimationLength { get; set; } = 360;
@ -408,7 +547,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
var list = FixedHotkeys();
// Customizeable hotkeys
// Customizable hotkeys
if (!string.IsNullOrEmpty(Hotkey))
list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = ""));
if (!string.IsNullOrEmpty(PreviewHotkey))
@ -439,6 +578,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = ""));
if (!string.IsNullOrEmpty(CycleHistoryDownHotkey))
list.Add(new(CycleHistoryDownHotkey, "CycleHistoryDownHotkey", () => CycleHistoryDownHotkey = ""));
if (!string.IsNullOrEmpty(DialogJumpHotkey))
list.Add(new(DialogJumpHotkey, "dialogJumpHotkey", () => DialogJumpHotkey = ""));
// Custom Query Hotkeys
foreach (var customPluginHotkey in CustomPluginHotkeys)
@ -539,4 +680,46 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Mica,
MicaAlt
}
public enum DoublePinyinSchemas
{
XiaoHe,
ZiRanMa,
WeiRuan,
ZhiNengABC,
ZiGuangPinYin,
PinYinJiaJia,
XingKongJianDao,
DaNiu,
XiaoLang
}
public enum DialogJumpWindowPositions
{
UnderDialog,
FollowDefault
}
public enum DialogJumpResultBehaviours
{
LeftClick,
RightClick
}
public enum DialogJumpFileResultBehaviours
{
FullPath,
FullPathOpen,
Directory
}
[EnumLocalize]
public enum HistoryStyle
{
[EnumLocalizeKey(nameof(Localize.queryHistory))]
Query,
[EnumLocalizeKey(nameof(Localize.executedHistory))]
LastOpened
}
}

View file

@ -13,10 +13,14 @@ using System.Windows.Interop;
using System.Windows.Markup;
using System.Windows.Media;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedModels;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
using Windows.Win32.System.Power;
using Windows.Win32.System.Threading;
using Windows.Win32.UI.Input.KeyboardAndMouse;
using Windows.Win32.UI.Shell.Common;
using Windows.Win32.UI.WindowsAndMessaging;
@ -118,9 +122,9 @@ namespace Flow.Launcher.Infrastructure
#region Window Foreground
public static nint GetForegroundWindow()
public static unsafe nint GetForegroundWindow()
{
return PInvoke.GetForegroundWindow().Value;
return (nint)PInvoke.GetForegroundWindow().Value;
}
public static bool SetForegroundWindow(Window window)
@ -138,6 +142,11 @@ namespace Flow.Launcher.Infrastructure
return IsForegroundWindow(GetWindowHandle(window));
}
public static bool IsForegroundWindow(nint handle)
{
return IsForegroundWindow(new HWND(handle));
}
internal static bool IsForegroundWindow(HWND handle)
{
return handle.Equals(PInvoke.GetForegroundWindow());
@ -279,15 +288,15 @@ namespace Flow.Launcher.Infrastructure
{
var hWndDesktop = PInvoke.FindWindowEx(hWnd, HWND.Null, "SHELLDLL_DefView", null);
hWndDesktop = PInvoke.FindWindowEx(hWndDesktop, HWND.Null, "SysListView32", "FolderView");
if (hWndDesktop.Value != IntPtr.Zero)
if (hWndDesktop != HWND.Null)
{
return false;
}
}
var monitorInfo = MonitorInfo.GetNearestDisplayMonitor(hWnd);
return (appBounds.bottom - appBounds.top) == monitorInfo.RectMonitor.Height &&
(appBounds.right - appBounds.left) == monitorInfo.RectMonitor.Width;
return (appBounds.bottom - appBounds.top) == monitorInfo.Bounds.Height &&
(appBounds.right - appBounds.left) == monitorInfo.Bounds.Width;
}
#endregion
@ -324,6 +333,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
@ -339,6 +353,16 @@ namespace Flow.Launcher.Infrastructure
return new(windowHelper.Handle);
}
internal static HWND GetMainWindowHandle()
{
// When application is exiting, the Application.Current will be null
if (Application.Current == null) return HWND.Null;
// Get the FL main window
var hwnd = GetWindowHandle(Application.Current.MainWindow, true);
return hwnd;
}
#endregion
#region STA Thread
@ -475,7 +499,7 @@ namespace Flow.Launcher.Infrastructure
/// Restores the previously backed-up keyboard layout.
/// If it wasn't backed up or has already been restored, this method does nothing.
/// </summary>
public static void RestorePreviousKeyboardLayout()
public unsafe static void RestorePreviousKeyboardLayout()
{
if (_previousLayout == HKL.Null) return;
@ -486,7 +510,7 @@ namespace Flow.Launcher.Infrastructure
hwnd,
PInvoke.WM_INPUTLANGCHANGEREQUEST,
PInvoke.INPUTLANGCHANGE_FORWARD,
_previousLayout.Value
new LPARAM((nint)_previousLayout.Value)
);
_previousLayout = HKL.Null;
@ -756,6 +780,62 @@ namespace Flow.Launcher.Infrastructure
#endregion
#region Window Rect
public static unsafe bool GetWindowRect(nint handle, out Rect outRect)
{
var rect = new RECT();
var result = PInvoke.GetWindowRect(new(handle), &rect);
if (!result)
{
outRect = new Rect();
return false;
}
// Convert RECT to Rect
outRect = new Rect(
rect.left,
rect.top,
rect.right - rect.left,
rect.bottom - rect.top
);
return true;
}
#endregion
#region Window Process
internal static unsafe string GetProcessNameFromHwnd(HWND hWnd)
{
return Path.GetFileName(GetProcessPathFromHwnd(hWnd));
}
internal static unsafe string GetProcessPathFromHwnd(HWND hWnd)
{
uint pid;
var threadId = PInvoke.GetWindowThreadProcessId(hWnd, &pid);
if (threadId == 0) return string.Empty;
var process = PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
if (process != HWND.Null)
{
using var safeHandle = new SafeProcessHandle((nint)process.Value, true);
uint capacity = 2000;
Span<char> buffer = new char[capacity];
if (!PInvoke.QueryFullProcessImageName(safeHandle, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, buffer, ref capacity))
{
return string.Empty;
}
return buffer[..(int)capacity].ToString();
}
return string.Empty;
}
#endregion
#region Explorer
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shopenfolderandselectitems
@ -786,5 +866,182 @@ namespace Flow.Launcher.Infrastructure
}
#endregion
#region Win32 Dark Mode
/*
* Inspired by https://github.com/ysc3839/win32-darkmode
*/
[DllImport("uxtheme.dll", EntryPoint = "#135", SetLastError = true)]
private static extern int SetPreferredAppMode(int appMode);
public static void EnableWin32DarkMode(string colorScheme)
{
try
{
// Undocumented API from Windows 10 1809
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
Environment.OSVersion.Version.Build >= 17763)
{
var flag = colorScheme switch
{
Constant.Light => 3, // ForceLight
Constant.Dark => 2, // ForceDark
Constant.System => 1, // AllowDark
_ => 0 // Default
};
_ = SetPreferredAppMode(flag);
}
}
catch
{
// Ignore errors on unsupported OS
}
}
#endregion
#region File / Folder Dialog
public static string SelectFile()
{
var dlg = new OpenFileDialog();
var result = dlg.ShowDialog();
if (result == true)
return dlg.FileName;
return string.Empty;
}
#endregion
#region Sleep Mode Listener
private static Action _func;
private static PDEVICE_NOTIFY_CALLBACK_ROUTINE _callback = null;
private static DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS _recipient;
private static SafeHandle _recipientHandle;
private static HPOWERNOTIFY _handle = HPOWERNOTIFY.Null;
/// <summary>
/// Registers a listener for sleep mode events.
/// Inspired from: https://github.com/XKaguya/LenovoLegionToolkit
/// https://blog.csdn.net/mochounv/article/details/114668594
/// </summary>
/// <param name="func"></param>
/// <exception cref="Win32Exception"></exception>
public static unsafe void RegisterSleepModeListener(Action func)
{
if (_callback != null)
{
// Only register if not already registered
return;
}
_func = func;
_callback = new PDEVICE_NOTIFY_CALLBACK_ROUTINE(DeviceNotifyCallback);
_recipient = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS()
{
Callback = _callback,
Context = null
};
_recipientHandle = new StructSafeHandle<DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS>(_recipient);
_handle = PInvoke.PowerRegisterSuspendResumeNotification(
REGISTER_NOTIFICATION_FLAGS.DEVICE_NOTIFY_CALLBACK,
_recipientHandle,
out var handle) == WIN32_ERROR.ERROR_SUCCESS ?
new HPOWERNOTIFY(new IntPtr(handle)) :
HPOWERNOTIFY.Null;
if (_handle.IsNull)
{
throw new Win32Exception("Error registering for power notifications: " + Marshal.GetLastWin32Error());
}
}
/// <summary>
/// Unregisters the sleep mode listener.
/// </summary>
public static void UnregisterSleepModeListener()
{
if (!_handle.IsNull)
{
PInvoke.PowerUnregisterSuspendResumeNotification(_handle);
_handle = HPOWERNOTIFY.Null;
_func = null;
_callback = null;
_recipientHandle = null;
}
}
private static unsafe uint DeviceNotifyCallback(void* context, uint type, void* setting)
{
switch (type)
{
case PInvoke.PBT_APMRESUMEAUTOMATIC:
// Operation is resuming automatically from a low-power state.This message is sent every time the system resumes
_func?.Invoke();
break;
case PInvoke.PBT_APMRESUMESUSPEND:
// Operation is resuming from a low-power state.This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key
_func?.Invoke();
break;
}
return 0;
}
private sealed class StructSafeHandle<T> : SafeHandle where T : struct
{
private readonly nint _ptr = nint.Zero;
public StructSafeHandle(T recipient) : base(nint.Zero, true)
{
var pRecipient = Marshal.AllocHGlobal(Marshal.SizeOf<T>());
Marshal.StructureToPtr(recipient, pRecipient, false);
SetHandle(pRecipient);
_ptr = pRecipient;
}
public override bool IsInvalid => handle == nint.Zero;
protected override bool ReleaseHandle()
{
Marshal.FreeHGlobal(_ptr);
return true;
}
}
#endregion
#region Taskbar
public static unsafe void ShowTaskbar()
{
// Find the taskbar window
var taskbarHwnd = PInvoke.FindWindowEx(HWND.Null, HWND.Null, "Shell_TrayWnd", null);
if (taskbarHwnd == HWND.Null) return;
// Magic from https://github.com/Oliviaophia/SmartTaskbar
const uint TrayBarFlag = 0x05D1;
var mon = PInvoke.MonitorFromWindow(taskbarHwnd, Windows.Win32.Graphics.Gdi.MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
PInvoke.PostMessage(taskbarHwnd, TrayBarFlag, new WPARAM(1), new LPARAM((nint)mon.Value));
}
public static void HideTaskbar()
{
// Find the taskbar window
var taskbarHwnd = PInvoke.FindWindowEx(HWND.Null, HWND.Null, "Shell_TrayWnd", null);
if (taskbarHwnd == HWND.Null) return;
// Magic from https://github.com/Oliviaophia/SmartTaskbar
const uint TrayBarFlag = 0x05D1;
PInvoke.PostMessage(taskbarHwnd, TrayBarFlag, new WPARAM(0), IntPtr.Zero);
}
#endregion
}
}

View file

@ -0,0 +1,210 @@
{
"version": 1,
"dependencies": {
"net9.0-windows7.0": {
"Ben.Demystifier": {
"type": "Direct",
"requested": "[0.4.1, )",
"resolved": "0.4.1",
"contentHash": "axFeEMfmEORy3ipAzOXG/lE+KcNptRbei3F0C4kQCdeiQtW+qJW90K5iIovITGrdLt8AjhNCwk5qLSX9/rFpoA==",
"dependencies": {
"System.Reflection.Metadata": "5.0.0"
}
},
"BitFaster.Caching": {
"type": "Direct",
"requested": "[2.5.4, )",
"resolved": "2.5.4",
"contentHash": "1QroTY1PVCZOSG9FnkkCrmCKk/+bZCgI/YXq376HnYwUDJ4Ho0EaV4YaA/5v5WYLnwIwIO7RZkdWbg9pxIpueQ=="
},
"CommunityToolkit.Mvvm": {
"type": "Direct",
"requested": "[8.4.0, )",
"resolved": "8.4.0",
"contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw=="
},
"Flow.Launcher.Localization": {
"type": "Direct",
"requested": "[0.0.6, )",
"resolved": "0.0.6",
"contentHash": "WNI/TLGPDr3XdOW8gaALN0Uyz9h+bzqOaNZev2nHEuA3HW9o7XuqaM6C0PqNi96mNgxiypwWpVazBNzaylJ2Aw=="
},
"Fody": {
"type": "Direct",
"requested": "[6.9.3, )",
"resolved": "6.9.3",
"contentHash": "1CUGgFdyECDKgi5HaUBhdv6k+VG9Iy4OCforGfHyar3xQXAJypZkzymgKtWj/4SPd6nSG0Qi7NH71qHrDSZLaA=="
},
"ini-parser": {
"type": "Direct",
"requested": "[2.5.2, )",
"resolved": "2.5.2",
"contentHash": "hp3gKmC/14+6eKLgv7Jd1Z7OV86lO+tNfOXr/stQbwmRhdQuXVSvrRAuAe7G5+lwhkov0XkqZ8/bn1PYWMx6eg=="
},
"InputSimulator": {
"type": "Direct",
"requested": "[1.0.4, )",
"resolved": "1.0.4",
"contentHash": "D0LvRCPQMX6/FJHBjng+RO+wRDuHTJrfo7IAc7rmkPvRqchdVGJWg3y70peOtDy3OLNK+HSOwVkH4GiuLnkKgA=="
},
"MemoryPack": {
"type": "Direct",
"requested": "[1.21.4, )",
"resolved": "1.21.4",
"contentHash": "wy3JTBNBsO8LfQcBvvYsWr3lm2Oakolrfu0UQ3oSJSEiD+7ye0GUhYTaXuYYBowqsXBXWD9gf2218ae0JRiYVQ==",
"dependencies": {
"MemoryPack.Core": "1.21.4",
"MemoryPack.Generator": "1.21.4"
}
},
"Microsoft.VisualStudio.Threading": {
"type": "Direct",
"requested": "[17.14.15, )",
"resolved": "17.14.15",
"contentHash": "1DrCusT3xNLSlaJg77BsUSAzrhjdZBAvvsS0PMzyPM+fGais6SnISOhqdZQop8VVMIBLsYm2gyF9W7THjgavwA==",
"dependencies": {
"Microsoft.VisualStudio.Threading.Analyzers": "17.14.15",
"Microsoft.VisualStudio.Threading.Only": "17.14.15",
"Microsoft.VisualStudio.Validation": "17.8.8"
}
},
"Microsoft.Windows.CsWin32": {
"type": "Direct",
"requested": "[0.3.205, )",
"resolved": "0.3.205",
"contentHash": "U5wGAnyKd7/I2YMd43nogm81VMtjiKzZ9dsLMVI4eAB7jtv5IEj0gprj0q/F3iRmAIaGv5omOf8iSYx2+nE6BQ==",
"dependencies": {
"Microsoft.Windows.SDK.Win32Docs": "0.1.42-alpha",
"Microsoft.Windows.SDK.Win32Metadata": "61.0.15-preview",
"Microsoft.Windows.WDK.Win32Metadata": "0.12.8-experimental"
}
},
"NHotkey.Wpf": {
"type": "Direct",
"requested": "[3.0.0, )",
"resolved": "3.0.0",
"contentHash": "BIUKlhTG5KtFf9OQzWvkmVmktt5/FFj6AOEgag8Uf0R2YdZt5ajUzs3sVskcJcT2TztWlEHKQr1jFj3KQ0D9Nw==",
"dependencies": {
"NHotkey": "3.0.0"
}
},
"NLog": {
"type": "Direct",
"requested": "[6.0.4, )",
"resolved": "6.0.4",
"contentHash": "Xr+lIk1ZlTTFXEqnxQVLxrDqZlt2tm5X+/AhJbaY2emb/dVtGDiU5QuEtj3gHtwV/SWlP/rJ922I/BPuOJXlRw=="
},
"NLog.OutputDebugString": {
"type": "Direct",
"requested": "[6.0.4, )",
"resolved": "6.0.4",
"contentHash": "TOP2Ap9BbE98B/l/TglnguowOD0rXo8B/20xAgvj9shO/kf6IJ5M4QMhVxq72mrneJ/ANhHY7Jcd+xJbzuI5PA==",
"dependencies": {
"NLog": "6.0.4"
}
},
"PropertyChanged.Fody": {
"type": "Direct",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "6v+f9cI8YjnZH2WBHuOqWEAo8DFFNGFIdU8xD3AsL6fhV6Y8oAmVWd7XKk49t8DpeUBwhR/X+97+6Epvek0Y3A==",
"dependencies": {
"Fody": "6.6.4"
}
},
"SharpVectors.Wpf": {
"type": "Direct",
"requested": "[1.8.5, )",
"resolved": "1.8.5",
"contentHash": "WURdBDq5AE8RjKV9pFS7lNkJe81gxja9SaMGE4URq9GJUZ6M+5DGUL0Lm3B0iYW2/Meyowaz4ffGsyW+RBSTtg=="
},
"System.Drawing.Common": {
"type": "Direct",
"requested": "[7.0.0, )",
"resolved": "7.0.0",
"contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==",
"dependencies": {
"Microsoft.Win32.SystemEvents": "7.0.0"
}
},
"ToolGood.Words.Pinyin": {
"type": "Direct",
"requested": "[3.1.0.3, )",
"resolved": "3.1.0.3",
"contentHash": "VKcf8sUq/+LyY99WgLhOu7Q32ROEyR30/2xCCj9ADRi45wVC7kpXrYCf9vH1qirkmrIfpL8inoxAbrqAlfXxsQ=="
},
"JetBrains.Annotations": {
"type": "Transitive",
"resolved": "2025.2.2",
"contentHash": "0X56ZRizuHdrnPpgXjWV7f2tQO1FlQg5O1967OGKnI/4ZRNOK642J8L7brM1nYvrxTTU5TP1yRyXLRLaXLPQ8A=="
},
"MemoryPack.Core": {
"type": "Transitive",
"resolved": "1.21.4",
"contentHash": "6RszGorZ0ejNmp37ZcboPBMvvPCuNW2jlrdQfcs/lMzE5b3pmPF6hsm/laDc34hRlbAST1ZxaX/DvYu2DF5sBQ=="
},
"MemoryPack.Generator": {
"type": "Transitive",
"resolved": "1.21.4",
"contentHash": "g14EsSS85yn0lHTi0J9ivqlZMf09A2iI51fmI+0KkzIzyCbWOBWPi5mdaY7YWmXprk12aYh9u/qfWHQUYthlwg=="
},
"Microsoft.VisualStudio.Threading.Analyzers": {
"type": "Transitive",
"resolved": "17.14.15",
"contentHash": "mXQPJsbuUD2ydq4/ffd8h8tSOFCXec+2xJOVNCvXjuMOq/+5EKHq3D2m2MC2+nUaXeFMSt66VS/J4HdKBixgcw=="
},
"Microsoft.VisualStudio.Threading.Only": {
"type": "Transitive",
"resolved": "17.14.15",
"contentHash": "NqONyw1RXyj9P3k5e1uU2k9kc1ptwuU5NJQzG+MPq7vQVHUzBY8HLuJf/N2Rw5H/myD96CVxziDxmjawPuzntw==",
"dependencies": {
"Microsoft.VisualStudio.Validation": "17.8.8"
}
},
"Microsoft.VisualStudio.Validation": {
"type": "Transitive",
"resolved": "17.8.8",
"contentHash": "rWXThIpyQd4YIXghNkiv2+VLvzS+MCMKVRDR0GAMlflsdo+YcAN2g2r5U1Ah98OFjQMRexTFtXQQ2LkajxZi3g=="
},
"Microsoft.Win32.SystemEvents": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ=="
},
"Microsoft.Windows.SDK.Win32Docs": {
"type": "Transitive",
"resolved": "0.1.42-alpha",
"contentHash": "Z/9po23gUA9aoukirh2ItMU2ZS9++Js9Gdds9fu5yuMojDrmArvY2y+tq9985tR3cxFxpZO1O35Wjfo0khj5HA=="
},
"Microsoft.Windows.SDK.Win32Metadata": {
"type": "Transitive",
"resolved": "61.0.15-preview",
"contentHash": "cysex3dazKtCPALCluC2XX3f5Aedy9H2pw5jb+TW5uas2rkem1Z7FRnbUrg2vKx0pk0Qz+4EJNr37HdYTEcvEQ=="
},
"Microsoft.Windows.WDK.Win32Metadata": {
"type": "Transitive",
"resolved": "0.12.8-experimental",
"contentHash": "3n8R44/Z96Ly+ty4eYVJfESqbzvpw96lRLs3zOzyDmr1x1Kw7FNn5CyE416q+bZQV3e1HRuMUvyegMeRE/WedA==",
"dependencies": {
"Microsoft.Windows.SDK.Win32Metadata": "61.0.15-preview"
}
},
"NHotkey": {
"type": "Transitive",
"resolved": "3.0.0",
"contentHash": "IEghs0QqWsQYH0uUmvIl0Ye6RaebWRh38eB6ToOkDnQucTYRGFOgtig0gSxlwCszTilYFz3n1ZuY762x+kDR3A=="
},
"System.Reflection.Metadata": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ=="
},
"flow.launcher.plugin": {
"type": "Project",
"dependencies": {
"JetBrains.Annotations": "[2025.2.2, )"
}
}
}
}
}

View file

@ -0,0 +1,92 @@
namespace Flow.Launcher.Plugin
{
/// <summary>
/// Describes a result of a <see cref="Query"/> executed by a plugin in Dialog Jump window
/// </summary>
public class DialogJumpResult : Result
{
/// <summary>
/// This holds the path which can be provided by plugin to be navigated to the
/// file dialog when records in Dialog Jump window is right clicked on a result.
/// </summary>
public required string DialogJumpPath { get; init; }
/// <summary>
/// Clones the current Dialog Jump result
/// </summary>
public new DialogJumpResult Clone()
{
return new DialogJumpResult
{
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,
DialogJumpPath = DialogJumpPath
};
}
/// <summary>
/// Convert <see cref="Result"/> to <see cref="DialogJumpResult"/>.
/// </summary>
public static DialogJumpResult From(Result result, string dialogJumpPath)
{
return new DialogJumpResult
{
Title = result.Title,
SubTitle = result.SubTitle,
ActionKeywordAssigned = result.ActionKeywordAssigned,
CopyText = result.CopyText,
AutoCompleteText = result.AutoCompleteText,
IcoPath = result.IcoPath,
BadgeIcoPath = result.BadgeIcoPath,
RoundedIcon = result.RoundedIcon,
Icon = result.Icon,
BadgeIcon = result.BadgeIcon,
Glyph = result.Glyph,
Action = result.Action,
AsyncAction = result.AsyncAction,
Score = result.Score,
TitleHighlightData = result.TitleHighlightData,
OriginQuery = result.OriginQuery,
PluginDirectory = result.PluginDirectory,
ContextData = result.ContextData,
PluginID = result.PluginID,
TitleToolTip = result.TitleToolTip,
SubTitleToolTip = result.SubTitleToolTip,
PreviewPanel = result.PreviewPanel,
ProgressBar = result.ProgressBar,
ProgressBarColor = result.ProgressBarColor,
Preview = result.Preview,
AddSelectedCount = result.AddSelectedCount,
RecordKey = result.RecordKey,
ShowBadge = result.ShowBadge,
DialogJumpPath = dialogJumpPath
};
}
}
}

View file

@ -40,6 +40,13 @@ namespace Flow.Launcher.Plugin
/// <param name="args"></param>
public delegate void VisibilityChangedEventHandler(object sender, VisibilityChangedEventArgs args);
/// <summary>
/// A delegate for when the actual application theme is changed
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
public delegate void ActualApplicationThemeChangedEventHandler(object sender, ActualApplicationThemeChangedEventArgs args);
/// <summary>
/// The event args for <see cref="VisibilityChangedEventHandler"/>
/// </summary>
@ -77,4 +84,15 @@ namespace Flow.Launcher.Plugin
/// </summary>
public Query Query { get; set; }
}
/// <summary>
/// The event args for <see cref="ActualApplicationThemeChangedEventHandler"/>
/// </summary>
public class ActualApplicationThemeChangedEventArgs : EventArgs
{
/// <summary>
/// <see langword="true"/> if the application has changed actual theme
/// </summary>
public bool IsDark { get; init; }
}
}

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<TargetFramework>net9.0-windows</TargetFramework>
<ProjectGuid>{8451ECDD-2EA4-4966-BB0A-7BBC40138E80}</ProjectGuid>
<UseWPF>true</UseWPF>
<OutputType>Library</OutputType>
@ -11,13 +11,14 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<PropertyGroup>
<Version>4.5.0</Version>
<PackageVersion>4.5.0</PackageVersion>
<AssemblyVersion>4.5.0</AssemblyVersion>
<FileVersion>4.5.0</FileVersion>
<Version>5.2.0</Version>
<PackageVersion>5.2.0</PackageVersion>
<AssemblyVersion>5.2.0</AssemblyVersion>
<FileVersion>5.2.0</FileVersion>
<PackageId>Flow.Launcher.Plugin</PackageId>
<Authors>Flow-Launcher</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
@ -27,6 +28,7 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<PackageReadmeFile>Readme.md</PackageReadmeFile>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(APPVEYOR)' == 'True'">
@ -66,17 +68,17 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fody" Version="6.5.5">
<PackageReference Include="Fody" Version="6.9.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.103" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2025.2.2" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.269">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0">
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

View file

@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
namespace Flow.Launcher.Plugin
{
/// <summary>
/// Asynchronous Dialog Jump Model
/// </summary>
public interface IAsyncDialogJump : IFeatures
{
/// <summary>
/// Asynchronous querying for Dialog Jump window
/// </summary>
/// <para>
/// If the Querying method requires high IO transmission
/// or performing CPU intense jobs (performing better with cancellation), please use this IAsyncDialogJump interface
/// </para>
/// <param name="query">Query to search</param>
/// <param name="token">Cancel when querying job is obsolete</param>
/// <returns></returns>
Task<List<DialogJumpResult>> QueryDialogJumpAsync(Query query, CancellationToken token);
}
}

View file

@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Flow.Launcher.Plugin
{
/// <summary>
/// Synchronous Dialog Jump Model
/// <para>
/// If the Querying method requires high IO transmission
/// or performing CPU intense jobs (performing better with cancellation), please try the IAsyncDialogJump interface
/// </para>
/// </summary>
public interface IDialogJump : IAsyncDialogJump
{
/// <summary>
/// Querying for Dialog Jump window
/// <para>
/// This method will be called within a Task.Run,
/// so please avoid synchrously wait for long.
/// </para>
/// </summary>
/// <param name="query">Query to search</param>
/// <returns></returns>
List<DialogJumpResult> QueryDialogJump(Query query);
Task<List<DialogJumpResult>> IAsyncDialogJump.QueryDialogJumpAsync(Query query, CancellationToken token) => Task.Run(() => QueryDialogJump(query), token);
}
}

View file

@ -0,0 +1,96 @@
using System;
#nullable enable
namespace Flow.Launcher.Plugin
{
/// <summary>
/// Interface for handling file dialog instances in DialogJump.
/// </summary>
public interface IDialogJumpDialog : IFeatures, IDisposable
{
/// <summary>
/// Check if the foreground window is a file dialog instance.
/// </summary>
/// <param name="hwnd">
/// The handle of the foreground window to check.
/// </param>
/// <returns>
/// The window if the foreground window is a file dialog instance. Null if it is not.
/// </returns>
IDialogJumpDialogWindow? CheckDialogWindow(IntPtr hwnd);
}
/// <summary>
/// Interface for handling a specific file dialog window in DialogJump.
/// </summary>
public interface IDialogJumpDialogWindow : IDisposable
{
/// <summary>
/// The handle of the dialog window.
/// </summary>
IntPtr Handle { get; }
/// <summary>
/// Get the current tab of the dialog window.
/// </summary>
/// <returns></returns>
IDialogJumpDialogWindowTab GetCurrentTab();
}
/// <summary>
/// Interface for handling a specific tab in a file dialog window in DialogJump.
/// </summary>
public interface IDialogJumpDialogWindowTab : IDisposable
{
/// <summary>
/// The handle of the dialog tab.
/// </summary>
IntPtr Handle { get; }
/// <summary>
/// Get the current folder path of the dialog tab.
/// </summary>
/// <returns></returns>
string GetCurrentFolder();
/// <summary>
/// Get the current file of the dialog tab.
/// </summary>
/// <returns></returns>
string GetCurrentFile();
/// <summary>
/// Jump to a folder in the dialog tab.
/// </summary>
/// <param name="path">
/// The path to the folder to jump to.
/// </param>
/// <param name="auto">
/// Whether folder jump is under automatical mode.
/// </param>
/// <returns>
/// True if the jump was successful, false otherwise.
/// </returns>
bool JumpFolder(string path, bool auto);
/// <summary>
/// Jump to a file in the dialog tab.
/// </summary>
/// <param name="path">
/// The path to the file to jump to.
/// </param>
/// <returns>
/// True if the jump was successful, false otherwise.
/// </returns>
bool JumpFile(string path);
/// <summary>
/// Open the file in the dialog tab.
/// </summary>
/// <returns>
/// True if the file was opened successfully, false otherwise.
/// </returns>
bool Open();
}
}

View file

@ -0,0 +1,40 @@
using System;
#nullable enable
namespace Flow.Launcher.Plugin
{
/// <summary>
/// Interface for handling file explorer instances in DialogJump.
/// </summary>
public interface IDialogJumpExplorer : IFeatures, IDisposable
{
/// <summary>
/// Check if the foreground window is a Windows Explorer instance.
/// </summary>
/// <param name="hwnd">
/// The handle of the foreground window to check.
/// </param>
/// <returns>
/// The window if the foreground window is a file explorer instance. Null if it is not.
/// </returns>
IDialogJumpExplorerWindow? CheckExplorerWindow(IntPtr hwnd);
}
/// <summary>
/// Interface for handling a specific file explorer window in DialogJump.
/// </summary>
public interface IDialogJumpExplorerWindow : IDisposable
{
/// <summary>
/// The handle of the explorer window.
/// </summary>
IntPtr Handle { get; }
/// <summary>
/// Get the current folder path of the explorer window.
/// </summary>
/// <returns></returns>
string? GetExplorerPath();
}
}

View file

@ -32,6 +32,6 @@ namespace Flow.Launcher.Plugin
Task IAsyncPlugin.InitAsync(PluginInitContext context) => Task.Run(() => Init(context));
Task<List<Result>> IAsyncPlugin.QueryAsync(Query query, CancellationToken token) => Task.Run(() => Query(query));
Task<List<Result>> IAsyncPlugin.QueryAsync(Query query, CancellationToken token) => Task.Run(() => Query(query), token);
}
}

View file

@ -24,7 +24,7 @@ namespace Flow.Launcher.Plugin
/// <param name="query">query text</param>
/// <param name="requery">
/// Force requery. By default, Flow Launcher will not fire query if your query is same with existing one.
/// Set this to <see langword="true"/> to force Flow Launcher requerying
/// Set this to <see langword="true"/> to force Flow Launcher re-querying
/// </param>
void ChangeQuery(string query, bool requery = false);
@ -84,6 +84,15 @@ namespace Flow.Launcher.Plugin
/// <param name="subTitle">Optional message subtitle</param>
void ShowMsgError(string title, string subTitle = "");
/// <summary>
/// Show the error message using Flow's standard error icon.
/// </summary>
/// <param name="title">Message title</param>
/// <param name="buttonText">Message button content</param>
/// <param name="buttonAction">Message button action</param>
/// <param name="subTitle">Optional message subtitle</param>
void ShowMsgErrorWithButton(string title, string buttonText, Action buttonAction, string subTitle = "");
/// <summary>
/// Show the MainWindow when hiding
/// </summary>
@ -127,6 +136,27 @@ namespace Flow.Launcher.Plugin
/// <param name="useMainWindowAsOwner">when true will use main windows as the owner</param>
void ShowMsg(string title, string subTitle, string iconPath, bool useMainWindowAsOwner = true);
/// <summary>
/// Show message box with button
/// </summary>
/// <param name="title">Message title</param>
/// <param name="buttonText">Message button content</param>
/// <param name="buttonAction">Message button action</param>
/// <param name="subTitle">Message subtitle</param>
/// <param name="iconPath">Message icon path (relative path to your plugin folder)</param>
void ShowMsgWithButton(string title, string buttonText, Action buttonAction, string subTitle = "", string iconPath = "");
/// <summary>
/// Show message box with button
/// </summary>
/// <param name="title">Message title</param>
/// <param name="buttonText">Message button content</param>
/// <param name="buttonAction">Message button action</param>
/// <param name="subTitle">Message subtitle</param>
/// <param name="iconPath">Message icon path (relative path to your plugin folder)</param>
/// <param name="useMainWindowAsOwner">when true will use main windows as the owner</param>
void ShowMsgWithButton(string title, string buttonText, Action buttonAction, string subTitle, string iconPath, bool useMainWindowAsOwner = true);
/// <summary>
/// Open setting dialog
/// </summary>
@ -143,9 +173,21 @@ namespace Flow.Launcher.Plugin
/// <summary>
/// Get all loaded plugins
/// </summary>
/// <remarks>
/// Will also return any plugins not fully initialized yet
/// </remarks>
/// <returns></returns>
List<PluginPair> GetAllPlugins();
/// <summary>
/// Get all initialized plugins
/// </summary>
/// <param name="includeFailed">
/// Whether to include plugins that failed to initialize
/// </param>
/// <returns></returns>
List<PluginPair> GetAllInitializedPlugins(bool includeFailed);
/// <summary>
/// Registers a callback function for global keyboard events.
/// </summary>
@ -199,7 +241,7 @@ namespace Flow.Launcher.Plugin
MatchResult FuzzySearch(string query, string stringToCompare);
/// <summary>
/// Http download the spefic url and return as string
/// Http download the specific url and return as string
/// </summary>
/// <param name="url">URL to call Http Get</param>
/// <param name="token">Cancellation Token</param>
@ -207,7 +249,7 @@ namespace Flow.Launcher.Plugin
Task<string> HttpGetStringAsync(string url, CancellationToken token = default);
/// <summary>
/// Http download the spefic url and return as stream
/// Http download the specific url and return as stream
/// </summary>
/// <param name="url">URL to call Http Get</param>
/// <param name="token">Cancellation Token</param>
@ -306,13 +348,28 @@ namespace Flow.Launcher.Plugin
public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null);
/// <summary>
/// Opens the URL with the given Uri object.
/// Opens the URL using the browser with the given Uri object, even if the URL is a local file.
/// The browser and mode used is based on what's configured in Flow's default browser settings.
/// </summary>
public void OpenWebUrl(Uri url, bool? inPrivate = null);
/// <summary>
/// Opens the URL using the browser with the given string, even if the URL is a local file.
/// The browser and mode used is based on what's configured in Flow's default browser settings.
/// Non-C# plugins should use this method.
/// </summary>
public void OpenWebUrl(string url, bool? inPrivate = null);
/// <summary>
/// Opens the URL with the given Uri object in browser if scheme is Http or Https.
/// If the URL is a local file, it will instead be opened with the default application for that file type.
/// The browser and mode used is based on what's configured in Flow's default browser settings.
/// </summary>
public void OpenUrl(Uri url, bool? inPrivate = null);
/// <summary>
/// Opens the URL with the given string.
/// Opens the URL with the given string in browser if scheme is Http or Https.
/// If the URL is a local file, it will instead be opened with the default application for that file type.
/// The browser and mode used is based on what's configured in Flow's default browser settings.
/// Non-C# plugins should use this method.
/// </summary>
@ -348,7 +405,7 @@ namespace Flow.Launcher.Plugin
/// <summary>
/// Reloads the query.
/// When current results are from context menu or history, it will go back to query results before requerying.
/// When current results are from context menu or history, it will go back to query results before re-querying.
/// </summary>
/// <param name="reselect">Choose the first result after reload if true; keep the last selected result if false. Default is true.</param>
public void ReQuery(bool reselect = true);
@ -502,8 +559,10 @@ namespace Flow.Launcher.Plugin
/// <param name="zipFilePath">
/// Path to the zip file containing the plugin. It will be unzipped to the temporary directory, removed and installed.
/// </param>
/// <returns></returns>
public Task UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath);
/// <returns>
/// True if the plugin is updated successfully, false otherwise.
/// </returns>
public Task<bool> UpdatePluginAsync(PluginMetadata pluginMetadata, UserPlugin plugin, string zipFilePath);
/// <summary>
/// Install a plugin. By default will remove the zip file if installation is from url,
@ -513,7 +572,10 @@ namespace Flow.Launcher.Plugin
/// <param name="zipFilePath">
/// Path to the zip file containing the plugin. It will be unzipped to the temporary directory, removed and installed.
/// </param>
public void InstallPlugin(UserPlugin plugin, string zipFilePath);
/// <returns>
/// True if the plugin is installed successfully, false otherwise.
/// </returns>
public bool InstallPlugin(UserPlugin plugin, string zipFilePath);
/// <summary>
/// Uninstall a plugin
@ -522,8 +584,10 @@ namespace Flow.Launcher.Plugin
/// <param name="removePluginSettings">
/// Plugin has their own settings. If this is set to true, the plugin settings will be removed.
/// </param>
/// <returns></returns>
public Task UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false);
/// <returns>
/// True if the plugin is updated successfully, false otherwise.
/// </returns>
public Task<bool> UninstallPluginAsync(PluginMetadata pluginMetadata, bool removePluginSettings = false);
/// <summary>
/// Log debug message of the time taken to execute a method
@ -550,5 +614,28 @@ namespace Flow.Launcher.Plugin
/// </summary>
/// <returns>The time taken to execute the method in milliseconds</returns>
public Task<long> StopwatchLogInfoAsync(string className, string message, Func<Task> action, [CallerMemberName] string methodName = "");
/// <summary>
/// Representing whether the application is using a dark theme
/// </summary>
/// <returns></returns>
bool IsApplicationDarkTheme();
/// <summary>
/// Invoked when the actual theme of the application has changed. Currently, the plugin will continue to be subscribed even if it is turned off.
/// </summary>
event ActualApplicationThemeChangedEventHandler ActualApplicationThemeChanged;
/// <summary>
/// Get the user data directory of Flow Launcher.
/// </summary>
/// <returns></returns>
string GetDataDirectory();
/// <summary>
/// Get the log directory of Flow Launcher.
/// </summary>
/// <returns></returns>
string GetLogDirectory();
}
}

View file

@ -6,3 +6,11 @@ WM_KEYDOWN
WM_KEYUP
WM_SYSKEYDOWN
WM_SYSKEYUP
GetSystemMetrics
EnumDisplayMonitors
MonitorFromWindow
GetMonitorInfo
MONITORINFOEXW
GetCursorPos
MonitorFromPoint

View file

@ -137,6 +137,11 @@ namespace Flow.Launcher.Plugin
[JsonIgnore]
public int QueryCount { get; set; }
/// <summary>
/// The minimum Flow Launcher version required for this plugin. Default is "".
/// </summary>
public string MinimumAppVersion { get; set; } = string.Empty;
/// <summary>
/// The path to the plugin settings directory which is not validated.
/// It is used to store plugin settings files and data files.

View file

@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using System;
using System.Text.Json.Serialization;
namespace Flow.Launcher.Plugin
{
@ -8,11 +9,29 @@ namespace Flow.Launcher.Plugin
public class Query
{
/// <summary>
/// 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.
/// Original query, exactly how the user has typed into the search box.
/// We don't recommend using this property directly. You should always use Search property.
/// </summary>
public string RawQuery { get; internal init; }
public string OriginalQuery { get; internal init; }
/// <summary>
/// Raw query, this includes action keyword if it has.
/// It has handled built-in custom query hotkeys and built-in shortcuts, and it trims the whitespace.
/// We don't recommend using this property directly. You should always use Search property.
/// </summary>
[Obsolete("RawQuery is renamed to TrimmedQuery. This property will be removed. Update the code to use TrimmedQuery instead.")]
public string RawQuery {
get => TrimmedQuery;
internal init { TrimmedQuery = value; }
}
/// <summary>
/// Original query but with trimmed whitespace. Includes action keyword.
/// It has handled built-in custom query hotkeys and build-in shortcuts.
/// If you need the exact original query from the search box, use OriginalQuery property instead.
/// We don't recommend using this property directly. You should always use Search property.
/// </summary>
public string TrimmedQuery { get; internal init; }
/// <summary>
/// Determines whether the query was forced to execute again.
@ -28,7 +47,7 @@ namespace Flow.Launcher.Plugin
/// <summary>
/// Search part of a query.
/// This will not include action keyword if exclusive plugin gets it, otherwise it should be same as RawQuery.
/// This will not include action keyword if exclusive plugin gets it, otherwise it should be same as TrimmedQuery.
/// Since we allow user to switch a exclusive plugin to generic plugin,
/// so this property will always give you the "real" query part of the query
/// </summary>
@ -103,6 +122,6 @@ namespace Flow.Launcher.Plugin
}
/// <inheritdoc />
public override string ToString() => RawQuery;
public override string ToString() => TrimmedQuery;
}
}

View file

@ -4,11 +4,13 @@ using System.IO;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Media;
using System.Text.Json.Serialization;
namespace Flow.Launcher.Plugin
{
/// <summary>
/// Describes a result of a <see cref="Query"/> executed by a plugin
/// Describes a result of a <see cref="Query"/> executed by a plugin.
/// This or its child classes is serializable.
/// </summary>
public class Result
{
@ -21,6 +23,8 @@ namespace Flow.Launcher.Plugin
private string _icoPath;
private string _icoPathAbsolute;
private string _copyText = string.Empty;
private string _badgeIcoPath;
@ -57,19 +61,34 @@ namespace Flow.Launcher.Plugin
/// for user on the plugin result. If autocomplete action for example is tab, pressing tab will have
/// the default constructed autocomplete text (result's Title), or the text provided here if not empty.
/// </summary>
/// <remarks>When a value is not set, the <see cref="Title"/> will be used.</remarks>
/// <remarks>
/// When a value is not set, the <see cref="Title"/> will be used.
/// Please include the action keyword prefix when necessary because Flow does not prepend it automatically.
/// </remarks>
public string AutoCompleteText { get; set; }
/// <summary>
/// The image to be displayed for the result.
/// Path or URI to the icon image for this result.
/// Updates <see cref="IcoPathAbsolute"/> appropriately when set.
/// </summary>
/// <value>Can be a local file path or a URL.</value>
/// <remarks>GlyphInfo is prioritized if not null</remarks>
/// <remarks>
/// Preferred usage: provide a path relative to the plugin directory (for example: "Images\icon.png").
/// Because <see cref="IcoPath"/> is serialized, using relative paths keeps the icon reference portable
/// when Flow is moved.
///
/// Accepted formats:
/// - Relative file paths (resolved against <see cref="PluginDirectory"/> into <see cref="IcoPathAbsolute"/>)
/// - Absolute file paths (left as-is)
/// - HTTP/HTTPS URLs (left as-is)
/// - Data URIs (left as-is)
/// </remarks>
public string IcoPath
{
get => _icoPath;
set
{
_icoPath = value;
// 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)
@ -78,15 +97,23 @@ namespace Flow.Launcher.Plugin
&& !value.StartsWith("https://", StringComparison.OrdinalIgnoreCase)
&& !value.StartsWith("data:image", StringComparison.OrdinalIgnoreCase))
{
_icoPath = Path.Combine(PluginDirectory, value);
_icoPathAbsolute = Path.Combine(PluginDirectory, value);
}
else
{
_icoPath = value;
_icoPathAbsolute = value;
}
}
}
/// <summary>
/// Absolute path or URI which is used to load and display the result icon for Flow.
/// This is populated by the <see cref="IcoPath"/> setter.
/// If a relative path was provided to <see cref="IcoPath"/>, this property will contain the resolved
/// absolute local path after combining with <see cref="PluginDirectory"/>.
/// </summary>
public string IcoPathAbsolute => _icoPathAbsolute;
/// <summary>
/// The image to be displayed for the badge of the result.
/// </summary>
@ -128,17 +155,34 @@ namespace Flow.Launcher.Plugin
/// <summary>
/// Delegate to load an icon for this result.
/// </summary>
[JsonIgnore]
public IconDelegate Icon = null;
/// <summary>
/// Delegate to load an icon for the badge of this result.
/// </summary>
[JsonIgnore]
public IconDelegate BadgeIcon = null;
private GlyphInfo _glyph;
/// <summary>
/// Information for Glyph Icon (Prioritized than IcoPath/Icon if user enable Glyph Icons)
/// </summary>
public GlyphInfo Glyph { get; init; }
public GlyphInfo Glyph
{
get => _glyph;
init => _glyph = value;
}
/// <summary>
/// Set the Glyph Icon after initialization
/// </summary>
/// <param name="glyph"></param>
public void SetGlyph(GlyphInfo glyph)
{
_glyph = glyph;
}
/// <summary>
/// An action to take in the form of a function call when the result has been selected.
@ -148,6 +192,7 @@ namespace Flow.Launcher.Plugin
/// Its result determines what happens to Flow Launcher's query form:
/// when true, the form will be hidden; when false, it will stay in focus.
/// </remarks>
[JsonIgnore]
public Func<ActionContext, bool> Action { get; set; }
/// <summary>
@ -158,6 +203,7 @@ namespace Flow.Launcher.Plugin
/// Its result determines what happens to Flow Launcher's query form:
/// when true, the form will be hidden; when false, it will stay in focus.
/// </remarks>
[JsonIgnore]
public Func<ActionContext, ValueTask<bool>> AsyncAction { get; set; }
/// <summary>
@ -200,11 +246,13 @@ namespace Flow.Launcher.Plugin
/// <example>
/// As external information for ContextMenu
/// </example>
[JsonIgnore]
public object ContextData { get; set; }
/// <summary>
/// Plugin ID that generated this result
/// </summary>
[JsonInclude]
public string PluginID { get; internal set; }
/// <summary>
@ -220,6 +268,7 @@ namespace Flow.Launcher.Plugin
/// <summary>
/// Customized Preview Panel
/// </summary>
[JsonIgnore]
public Lazy<UserControl> PreviewPanel { get; set; }
/// <summary>
@ -257,6 +306,17 @@ namespace Flow.Launcher.Plugin
/// </summary>
public bool ShowBadge { get; set; } = false;
/// <summary>
/// This holds the text which can be shown as a query suggestion.
/// </summary>
/// <remarks>
/// When a value is not set, the <see cref="Title"/> will be used.
/// Do not include the action keyword prefix because Flow prepends it automatically.
/// If the it does not start with the query text, it will not be shown as a suggestion.
/// So make sure to set this value to start with the query text.
/// </remarks>
public string QuerySuggestionText { get; set; }
/// <summary>
/// Run this result, asynchronously
/// </summary>
@ -308,6 +368,7 @@ namespace Flow.Launcher.Plugin
AddSelectedCount = AddSelectedCount,
RecordKey = RecordKey,
ShowBadge = ShowBadge,
QuerySuggestionText = QuerySuggestionText
};
}
@ -337,6 +398,7 @@ namespace Flow.Launcher.Plugin
/// <summary>
/// Delegate to get the preview panel's image
/// </summary>
[JsonIgnore]
public IconDelegate PreviewDelegate { get; set; } = null;
/// <summary>

View file

@ -130,6 +130,119 @@ namespace Flow.Launcher.Plugin.SharedCommands
}
}
/// <summary>
/// Attempts to delete a directory robustly with retry logic for locked files.
/// This method tries to delete files individually with retries, then removes empty directories.
/// Returns true if the directory was completely deleted, false if some files/folders remain.
/// </summary>
/// <param name="path">The directory path to delete</param>
/// <param name="maxRetries">Maximum number of retry attempts for locked files (default: 3)</param>
/// <param name="retryDelayMs">Delay in milliseconds between retries (default: 100ms)</param>
/// <returns>True if directory was fully deleted, false if some items remain</returns>
public static bool TryDeleteDirectoryRobust(string path, int maxRetries = 3, int retryDelayMs = 100)
{
if (!Directory.Exists(path))
return true;
bool fullyDeleted = true;
try
{
// First, try to delete all files in the directory tree
var files = Directory.GetFiles(path, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
bool fileDeleted = false;
for (int attempt = 0; attempt <= maxRetries; attempt++)
{
try
{
// Remove read-only attribute if present
var fileInfo = new FileInfo(file);
if (fileInfo.Exists && fileInfo.IsReadOnly)
{
fileInfo.IsReadOnly = false;
}
File.Delete(file);
fileDeleted = true;
break;
}
catch (UnauthorizedAccessException)
{
// File is in use or access denied, wait and retry
if (attempt < maxRetries)
{
System.Threading.Thread.Sleep(retryDelayMs);
}
}
catch (IOException)
{
// File is in use, wait and retry
if (attempt < maxRetries)
{
System.Threading.Thread.Sleep(retryDelayMs);
}
}
catch
{
// Other exceptions, don't retry
break;
}
}
if (!fileDeleted)
{
fullyDeleted = false;
}
}
// Then, try to delete all empty directories (from deepest to shallowest)
var directories = Directory.GetDirectories(path, "*", SearchOption.AllDirectories)
.OrderByDescending(d => d.Length) // Delete deeper directories first
.ToArray();
foreach (var directory in directories)
{
try
{
if (Directory.Exists(directory) && !Directory.EnumerateFileSystemEntries(directory).Any())
{
Directory.Delete(directory, false);
}
}
catch
{
// If we can't delete an empty directory, mark as not fully deleted
fullyDeleted = false;
}
}
// Finally, try to delete the root directory itself
try
{
if (Directory.Exists(path) && !Directory.EnumerateFileSystemEntries(path).Any())
{
Directory.Delete(path, false);
}
else if (Directory.Exists(path))
{
fullyDeleted = false;
}
}
catch
{
fullyDeleted = false;
}
}
catch
{
fullyDeleted = false;
}
return fullyDeleted;
}
/// <summary>
/// Checks if a directory exists
/// </summary>
@ -150,6 +263,16 @@ namespace Flow.Launcher.Plugin.SharedCommands
return File.Exists(filePath);
}
/// <summary>
/// Checks if a file or directory exists
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static bool FileOrLocationExists(this string path)
{
return LocationExists(path) || FileExists(path);
}
/// <summary>
/// Open a directory window (using the OS's default handler, usually explorer)
/// </summary>

View file

@ -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
}
}
}
}

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System;
using System.Runtime.InteropServices;
using System.Windows;
using Windows.Win32;
@ -7,13 +6,16 @@ using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Flow.Launcher.Infrastructure;
namespace Flow.Launcher.Plugin.SharedModels;
/// <summary>
/// Contains full information about a display monitor.
/// Codes are edited from: <see href="https://github.com/Jack251970/DesktopWidgets3">.
/// Inspired from: https://github.com/Jack251970/DesktopWidgets3.
/// </summary>
internal class MonitorInfo
/// <remarks>
/// Use this class to replace the System.Windows.Forms.Screen class which can cause possible System.PlatformNotSupportedException.
/// </remarks>
public class MonitorInfo
{
/// <summary>
/// Gets the display monitors (including invisible pseudo-monitors associated with the mirroring drivers).
@ -23,14 +25,14 @@ internal class MonitorInfo
{
var monitorCount = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CMONITORS);
var list = new List<MonitorInfo>(monitorCount);
var callback = new MONITORENUMPROC((HMONITOR monitor, HDC deviceContext, RECT* rect, LPARAM data) =>
var callback = new MONITORENUMPROC((monitor, deviceContext, rect, data) =>
{
list.Add(new MonitorInfo(monitor, rect));
return true;
});
var dwData = new LPARAM();
var hdc = new HDC();
bool ok = PInvoke.EnumDisplayMonitors(hdc, (RECT?)null, callback, dwData);
bool ok = PInvoke.EnumDisplayMonitors(hdc, null, callback, dwData);
if (!ok)
{
Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
@ -43,11 +45,11 @@ internal class MonitorInfo
/// </summary>
/// <param name="hwnd">Window handle</param>
/// <returns>The display monitor that is nearest to a given window, or null if no monitor is found.</returns>
public static unsafe MonitorInfo GetNearestDisplayMonitor(HWND hwnd)
public static unsafe MonitorInfo GetNearestDisplayMonitor(nint hwnd)
{
var nearestMonitor = PInvoke.MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
var nearestMonitor = PInvoke.MonitorFromWindow(new(hwnd), MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
MonitorInfo nearestMonitorInfo = null;
var callback = new MONITORENUMPROC((HMONITOR monitor, HDC deviceContext, RECT* rect, LPARAM data) =>
var callback = new MONITORENUMPROC((monitor, deviceContext, rect, data) =>
{
if (monitor == nearestMonitor)
{
@ -58,7 +60,7 @@ internal class MonitorInfo
});
var dwData = new LPARAM();
var hdc = new HDC();
bool ok = PInvoke.EnumDisplayMonitors(hdc, (RECT?)null, callback, dwData);
bool ok = PInvoke.EnumDisplayMonitors(hdc, null, callback, dwData);
if (!ok)
{
Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
@ -66,17 +68,75 @@ internal class MonitorInfo
return nearestMonitorInfo;
}
/// <summary>
/// Gets the primary display monitor (the one that contains the taskbar).
/// </summary>
/// <returns>The primary display monitor, or null if no monitor is found.</returns>
public static unsafe MonitorInfo GetPrimaryDisplayMonitor()
{
var primaryMonitor = PInvoke.MonitorFromWindow(new HWND(nint.Zero), MONITOR_FROM_FLAGS.MONITOR_DEFAULTTOPRIMARY);
MonitorInfo primaryMonitorInfo = null;
var callback = new MONITORENUMPROC((monitor, deviceContext, rect, data) =>
{
if (monitor == primaryMonitor)
{
primaryMonitorInfo = new MonitorInfo(monitor, rect);
return false;
}
return true;
});
var dwData = new LPARAM();
var hdc = new HDC();
bool ok = PInvoke.EnumDisplayMonitors(hdc, null, callback, dwData);
if (!ok)
{
Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
}
return primaryMonitorInfo;
}
/// <summary>
/// Gets the display monitor that contains the cursor.
/// </summary>
/// <returns>The display monitor that contains the cursor, or null if no monitor is found.</returns>
public static unsafe MonitorInfo GetCursorDisplayMonitor()
{
if (!PInvoke.GetCursorPos(out var pt))
{
Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
}
var cursorMonitor = PInvoke.MonitorFromPoint(pt, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
MonitorInfo cursorMonitorInfo = null;
var callback = new MONITORENUMPROC((monitor, deviceContext, rect, data) =>
{
if (monitor == cursorMonitor)
{
cursorMonitorInfo = new MonitorInfo(monitor, rect);
return false;
}
return true;
});
var dwData = new LPARAM();
var hdc = new HDC();
bool ok = PInvoke.EnumDisplayMonitors(hdc, null, callback, dwData);
if (!ok)
{
Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
}
return cursorMonitorInfo;
}
private readonly HMONITOR _monitor;
internal unsafe MonitorInfo(HMONITOR monitor, RECT* rect)
{
RectMonitor =
Bounds =
new Rect(new Point(rect->left, rect->top),
new Point(rect->right, rect->bottom));
_monitor = monitor;
var info = new MONITORINFOEXW() { monitorInfo = new MONITORINFO() { cbSize = (uint)sizeof(MONITORINFOEXW) } };
GetMonitorInfo(monitor, ref info);
RectWork =
WorkingArea =
new Rect(new Point(info.monitorInfo.rcWork.left, info.monitorInfo.rcWork.top),
new Point(info.monitorInfo.rcWork.right, info.monitorInfo.rcWork.bottom));
Name = new string(info.szDevice.AsSpan()).Replace("\0", "").Trim();
@ -93,7 +153,7 @@ internal class MonitorInfo
/// <remarks>
/// <note>If the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.</note>
/// </remarks>
public Rect RectMonitor { get; }
public Rect Bounds { get; }
/// <summary>
/// Gets the work area rectangle of the display monitor, expressed in virtual-screen coordinates.
@ -101,15 +161,15 @@ internal class MonitorInfo
/// <remarks>
/// <note>If the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.</note>
/// </remarks>
public Rect RectWork { get; }
public Rect WorkingArea { get; }
/// <summary>
/// Gets if the monitor is the the primary display monitor.
/// Gets if the monitor is the primary display monitor.
/// </summary>
public bool IsPrimary => _monitor == PInvoke.MonitorFromWindow(new(IntPtr.Zero), MONITOR_FROM_FLAGS.MONITOR_DEFAULTTOPRIMARY);
public bool IsPrimary => _monitor == PInvoke.MonitorFromWindow(new(nint.Zero), MONITOR_FROM_FLAGS.MONITOR_DEFAULTTOPRIMARY);
/// <inheritdoc />
public override string ToString() => $"{Name} {RectMonitor.Width}x{RectMonitor.Height}";
public override string ToString() => $"{Name} {Bounds.Width}x{Bounds.Height}";
private static unsafe bool GetMonitorInfo(HMONITOR hMonitor, ref MONITORINFOEXW lpmi)
{

View file

@ -76,5 +76,10 @@ namespace Flow.Launcher.Plugin
/// Indicates whether the plugin is installed from a local path
/// </summary>
public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath);
/// <summary>
/// The minimum Flow Launcher version required for this plugin. Default is "".
/// </summary>
public string MinimumAppVersion { get; set; } = string.Empty;
}
}

View file

@ -0,0 +1,77 @@
{
"version": 1,
"dependencies": {
"net9.0-windows7.0": {
"Fody": {
"type": "Direct",
"requested": "[6.9.3, )",
"resolved": "6.9.3",
"contentHash": "1CUGgFdyECDKgi5HaUBhdv6k+VG9Iy4OCforGfHyar3xQXAJypZkzymgKtWj/4SPd6nSG0Qi7NH71qHrDSZLaA=="
},
"JetBrains.Annotations": {
"type": "Direct",
"requested": "[2025.2.2, )",
"resolved": "2025.2.2",
"contentHash": "0X56ZRizuHdrnPpgXjWV7f2tQO1FlQg5O1967OGKnI/4ZRNOK642J8L7brM1nYvrxTTU5TP1yRyXLRLaXLPQ8A=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[10.0.103, )",
"resolved": "10.0.103",
"contentHash": "qZk7r40ftpZY+/sO019sgWAWfNqC2CLSspDdAxNYCJU/bCi/8jVwvOMjzb/d5gjCRNzQ4OCYgBfhdpQyVwLTyw==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "10.0.103",
"Microsoft.SourceLink.Common": "10.0.103"
}
},
"Microsoft.Windows.CsWin32": {
"type": "Direct",
"requested": "[0.3.269, )",
"resolved": "0.3.269",
"contentHash": "O4GVJ0ymxcoFRGS07VcoEClj7A9PIciHIjWDrPymzonhYlOfM7V0ZqGBUK19cUH3BPca9MfSOH0KLK/9JzQ8+Q==",
"dependencies": {
"Microsoft.Windows.SDK.Win32Docs": "0.1.42-alpha",
"Microsoft.Windows.SDK.Win32Metadata": "69.0.7-preview",
"Microsoft.Windows.WDK.Win32Metadata": "0.13.25-experimental"
}
},
"PropertyChanged.Fody": {
"type": "Direct",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "6v+f9cI8YjnZH2WBHuOqWEAo8DFFNGFIdU8xD3AsL6fhV6Y8oAmVWd7XKk49t8DpeUBwhR/X+97+6Epvek0Y3A==",
"dependencies": {
"Fody": "6.6.4"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "10.0.103",
"contentHash": "QoiCMcPuxC6eqRQmrmF9zBY96ejIznXtve/lJJbonGD9I5Aygf2AUCOWslGiCEtBbfWRSuUnepBjuuVOdAl5ag=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "10.0.103",
"contentHash": "cMtGW5/r0ck72Jg2QwZcNTX59z+iB/B1kW84VMa/eX8L19DhHIuIcQjfK0pgLLBxd60Jl0Bj9GUolcM0MnJnZA=="
},
"Microsoft.Windows.SDK.Win32Docs": {
"type": "Transitive",
"resolved": "0.1.42-alpha",
"contentHash": "Z/9po23gUA9aoukirh2ItMU2ZS9++Js9Gdds9fu5yuMojDrmArvY2y+tq9985tR3cxFxpZO1O35Wjfo0khj5HA=="
},
"Microsoft.Windows.SDK.Win32Metadata": {
"type": "Transitive",
"resolved": "69.0.7-preview",
"contentHash": "RJoNjQJVCIDNLPbvYuaygCFknTyAxOUE45of1voj0jjOgJa9MB2m1/G8L8F3IYc+2EFG5aqa/9y8PEx7Tk2tLQ=="
},
"Microsoft.Windows.WDK.Win32Metadata": {
"type": "Transitive",
"resolved": "0.13.25-experimental",
"contentHash": "IM50tb/+UIwBr9FMr6ZKcZjCMW+Axo6NjGqKxgjUfyCY8dRnYUfrJEXxAaXoWtYP4X8EmASmC1Jtwh4XucseZg==",
"dependencies": {
"Microsoft.Windows.SDK.Win32Metadata": "63.0.31-preview"
}
}
}
}
}

View file

@ -0,0 +1,265 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using NUnit.Framework.Legacy;
using Flow.Launcher.Infrastructure;
using ToolGood.Words.Pinyin;
namespace Flow.Launcher.Test
{
/// <summary>
/// Performance test comparing ContainsChinese() vs WordsHelper.HasChinese()
///
/// This test verifies:
/// 1. Both methods produce identical results (correctness)
/// 2. Performance characteristics of both implementations
/// 3. Memory allocation patterns
///
/// The ContainsChinese() method uses optimized Unicode range checking with ReadOnlySpan
/// while WordsHelper.HasChinese() uses the ToolGood.Words library implementation.
/// </summary>
[TestFixture]
public class ChineseDetectionPerformanceTest
{
private readonly List<string> _testStrings = new()
{
// Pure English - should return false
"Hello World",
"Visual Studio Code",
"Microsoft Office 2023",
"Adobe Photoshop Creative Suite",
"Google Chrome Browser Application",
// Pure Chinese - should return true
"你好世界",
"微软办公软件",
"谷歌浏览器",
"北京大学计算机科学与技术学院",
"中华人民共和国国家发展和改革委员会",
// Mixed content - should return true
"Hello 世界",
"Visual Studio 代码编辑器",
"QQ音乐 Music Player",
"Windows 10 操作系统",
"GitHub 代码仓库管理平台",
// Edge cases
"",
" ",
"123456",
"!@#$%^&*()",
"café résumé naïve", // Accented characters (not Chinese)
// Long strings for performance testing
"This is a very long English string that contains no Chinese characters but is designed to test performance with longer text content that might appear in file names or application descriptions",
"这是一个非常长的中文字符串,包含了很多汉字,用来测试在处理较长中文文本时的性能表现,比如可能出现在文件名或应用程序描述中的文本内容",
"This is a mixed 混合内容的字符串 that contains both English and Chinese characters 中英文混合 to test performance with 复杂的文本内容 in real-world scenarios 真实场景中的应用"
};
[Test]
public void ContainsChinese_CorrectnessTest()
{
// Verify ContainsChinese works correctly for known cases
ClassicAssert.IsFalse(ContainsChinese("Hello World"), "Pure English should return false");
ClassicAssert.IsTrue(ContainsChinese("你好世界"), "Pure Chinese should return true");
ClassicAssert.IsTrue(ContainsChinese("Hello 世界"), "Mixed content should return true");
ClassicAssert.IsFalse(ContainsChinese(""), "Empty string should return false");
ClassicAssert.IsFalse(ContainsChinese("123456"), "Numbers should return false");
ClassicAssert.IsFalse(ContainsChinese("café résumé"), "Accented characters should return false");
}
[Test]
public void WordsHelper_CorrectnessTest()
{
// Verify WordsHelper.HasChinese works correctly for known cases
ClassicAssert.IsFalse(WordsHelper.HasChinese("Hello World"), "Pure English should return false");
ClassicAssert.IsTrue(WordsHelper.HasChinese("你好世界"), "Pure Chinese should return true");
ClassicAssert.IsTrue(WordsHelper.HasChinese("Hello 世界"), "Mixed content should return true");
ClassicAssert.IsFalse(WordsHelper.HasChinese(""), "Empty string should return false");
ClassicAssert.IsFalse(WordsHelper.HasChinese("123456"), "Numbers should return false");
ClassicAssert.IsFalse(WordsHelper.HasChinese("café résumé"), "Accented characters should return false");
}
[Test]
public void BothMethods_ShouldProduceSameResults()
{
// Critical test: verify both methods produce identical results for all test cases
foreach (var testString in _testStrings)
{
var wordsHelperResult = WordsHelper.HasChinese(testString);
var containsChineseResult = ContainsChinese(testString);
ClassicAssert.AreEqual(wordsHelperResult, containsChineseResult,
$"Results differ for string: '{testString}'. WordsHelper: {wordsHelperResult}, ContainsChinese: {containsChineseResult}");
}
Console.WriteLine($"✓ Both methods produce identical results for all {_testStrings.Count} test cases");
}
[Test]
public void PerformanceComparison_BasicBenchmark()
{
const int iterations = 1000000;
Console.WriteLine("=== CHINESE CHARACTER DETECTION PERFORMANCE TEST ===");
Console.WriteLine($"Test iterations: {iterations:N0}");
Console.WriteLine($"Test strings: {_testStrings.Count}");
Console.WriteLine($"Total operations: {iterations * _testStrings.Count:N0}");
Console.WriteLine();
// Warmup to ensure JIT compilation
Console.WriteLine("Warming up...");
for (int i = 0; i < 1000; i++)
{
foreach (var testString in _testStrings)
{
_ = ContainsChinese(testString);
_ = WordsHelper.HasChinese(testString);
}
}
// Benchmark ContainsChinese method
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var sw1 = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
foreach (var testString in _testStrings)
{
_ = ContainsChinese(testString);
}
}
sw1.Stop();
// Benchmark WordsHelper.HasChinese method
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var sw2 = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
foreach (var testString in _testStrings)
{
_ = WordsHelper.HasChinese(testString);
}
}
sw2.Stop();
// Calculate and display results
var containsChineseMs = sw1.Elapsed.TotalMilliseconds;
var wordsHelperMs = sw2.Elapsed.TotalMilliseconds;
var speedRatio = wordsHelperMs / containsChineseMs;
var timeDifference = wordsHelperMs - containsChineseMs;
Console.WriteLine("RESULTS:");
Console.WriteLine($"ContainsChinese(): {containsChineseMs:F3} ms");
Console.WriteLine($"WordsHelper.HasChinese(): {wordsHelperMs:F3} ms");
Console.WriteLine($"Time difference: {timeDifference:F3} ms");
Console.WriteLine($"Speed improvement: {speedRatio:F2}x");
Console.WriteLine($"Performance gain: {((speedRatio - 1) * 100):F1}%");
Console.WriteLine();
if (speedRatio > 1.0)
{
Console.WriteLine($"✓ ContainsChinese() is {speedRatio:F2}x faster than WordsHelper.HasChinese()");
}
else
{
Console.WriteLine($"⚠ WordsHelper.HasChinese() is {(1/speedRatio):F2}x faster than ContainsChinese()");
}
// Test always passes - this is a measurement test
ClassicAssert.IsTrue(true);
}
[Test]
public void PerformanceComparison_ByStringType()
{
Console.WriteLine("=== PERFORMANCE BY STRING TYPE ===");
var categories = new Dictionary<string, List<string>>
{
["Pure English"] = _testStrings.Where(s => !ContainsChinese(s) && s.All(c => c <= 127)).ToList(),
["Pure Chinese"] = _testStrings.Where(s => ContainsChinese(s) && s.All(c => IsChineseCharacter(c) || char.IsWhiteSpace(c))).ToList(),
["Mixed Content"] = _testStrings.Where(s => ContainsChinese(s) && s.Any(c => c <= 127 && char.IsLetter(c))).ToList(),
["Edge Cases"] = _testStrings.Where(s => string.IsNullOrWhiteSpace(s) || s.All(c => !char.IsLetter(c))).ToList()
};
foreach (var category in categories)
{
if (category.Value.Count == 0) continue;
Console.WriteLine($"\n{category.Key} ({category.Value.Count} strings):");
var sample = category.Value.First();
var displayText = sample.Length > 40 ? sample.Substring(0, 40) + "..." : sample;
Console.WriteLine($" Sample: '{displayText}'");
const int categoryIterations = 5000;
// Test each method
var sw1 = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < categoryIterations; i++)
{
foreach (var str in category.Value)
{
_ = ContainsChinese(str);
}
}
sw1.Stop();
var sw2 = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < categoryIterations; i++)
{
foreach (var str in category.Value)
{
_ = WordsHelper.HasChinese(str);
}
}
sw2.Stop();
var ratio = (double)sw2.ElapsedTicks / sw1.ElapsedTicks;
Console.WriteLine($" Performance: ContainsChinese is {ratio:F2}x faster");
}
ClassicAssert.IsTrue(true);
}
/// <summary>
/// Optimized Chinese character detection using comprehensive CJK Unicode ranges
/// This method uses ReadOnlySpan for better performance and covers all CJK character ranges
/// </summary>
private static bool ContainsChinese(ReadOnlySpan<char> text)
{
foreach (var c in text)
{
if (IsChineseCharacter(c))
return true;
}
return false;
}
/// <summary>
/// Check if a character is a Chinese character using comprehensive Unicode ranges
/// Covers CJK Unified Ideographs and all extension blocks
/// </summary>
private static bool IsChineseCharacter(char c)
{
return (c >= 0x4E00 && c <= 0x9FFF) || // CJK Unified Ideographs (most common Chinese characters)
(c >= 0x3400 && c <= 0x4DBF) || // CJK Extension A
(c >= 0x20000 && c <= 0x2A6DF) || // CJK Extension B
(c >= 0x2A700 && c <= 0x2B73F) || // CJK Extension C
(c >= 0x2B740 && c <= 0x2B81F) || // CJK Extension D
(c >= 0x2B820 && c <= 0x2CEAF) || // CJK Extension E
(c >= 0x2CEB0 && c <= 0x2EBEF) || // CJK Extension F
(c >= 0xF900 && c <= 0xFAFF) || // CJK Compatibility Ideographs
(c >= 0x2F800 && c <= 0x2FA1F); // CJK Compatibility Supplement
}
}
}

View file

@ -1,6 +1,7 @@
using Flow.Launcher.Plugin.SharedCommands;
using NUnit.Framework;
using NUnit.Framework.Legacy;
using System.IO;
namespace Flow.Launcher.Test
{
@ -50,5 +51,89 @@ namespace Flow.Launcher.Test
{
ClassicAssert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path, allowEqual: expectedResult));
}
[Test]
public void TryDeleteDirectoryRobust_WhenDirectoryDoesNotExist_ReturnsTrue()
{
// Arrange
string nonExistentPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
// Act
bool result = FilesFolders.TryDeleteDirectoryRobust(nonExistentPath);
// Assert
ClassicAssert.IsTrue(result);
}
[Test]
public void TryDeleteDirectoryRobust_WhenDirectoryIsEmpty_DeletesSuccessfully()
{
// Arrange
string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tempDir);
// Act
bool result = FilesFolders.TryDeleteDirectoryRobust(tempDir);
// Assert
ClassicAssert.IsTrue(result);
ClassicAssert.IsFalse(Directory.Exists(tempDir));
}
[Test]
public void TryDeleteDirectoryRobust_WhenDirectoryHasFiles_DeletesSuccessfully()
{
// Arrange
string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tempDir);
File.WriteAllText(Path.Combine(tempDir, "test.txt"), "test content");
// Act
bool result = FilesFolders.TryDeleteDirectoryRobust(tempDir);
// Assert
ClassicAssert.IsTrue(result);
ClassicAssert.IsFalse(Directory.Exists(tempDir));
}
[Test]
public void TryDeleteDirectoryRobust_WhenDirectoryHasNestedStructure_DeletesSuccessfully()
{
// Arrange
string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tempDir);
string subDir1 = Path.Combine(tempDir, "SubDir1");
string subDir2 = Path.Combine(tempDir, "SubDir2");
Directory.CreateDirectory(subDir1);
Directory.CreateDirectory(subDir2);
File.WriteAllText(Path.Combine(subDir1, "file1.txt"), "content1");
File.WriteAllText(Path.Combine(subDir2, "file2.txt"), "content2");
File.WriteAllText(Path.Combine(tempDir, "root.txt"), "root content");
// Act
bool result = FilesFolders.TryDeleteDirectoryRobust(tempDir);
// Assert
ClassicAssert.IsTrue(result);
ClassicAssert.IsFalse(Directory.Exists(tempDir));
}
[Test]
public void TryDeleteDirectoryRobust_WhenFileIsReadOnly_RemovesAttributeAndDeletes()
{
// Arrange
string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tempDir);
string filePath = Path.Combine(tempDir, "readonly.txt");
File.WriteAllText(filePath, "readonly content");
File.SetAttributes(filePath, FileAttributes.ReadOnly);
// Act
bool result = FilesFolders.TryDeleteDirectoryRobust(tempDir);
// Assert
ClassicAssert.IsTrue(result);
ClassicAssert.IsFalse(Directory.Exists(tempDir));
}
}
}

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
<ProjectGuid>{FF742965-9A80-41A5-B042-D6C7D3A21708}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
@ -39,6 +39,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Calculator\Flow.Launcher.Plugin.Calculator.csproj" />
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Explorer\Flow.Launcher.Plugin.Explorer.csproj" />
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Program\Flow.Launcher.Plugin.Program.csproj" />
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Url\Flow.Launcher.Plugin.Url.csproj" />
@ -48,13 +49,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="nunit" Version="4.3.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0">
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="nunit" Version="4.4.0" />
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Flow.Launcher.Plugin.Calculator;
using Mages.Core;
using NUnit.Framework;
using NUnit.Framework.Legacy;
namespace Flow.Launcher.Test.Plugins
{
[TestFixture]
public class CalculatorPluginTest
{
private readonly Main _plugin;
private readonly Settings _settings = new()
{
DecimalSeparator = DecimalSeparator.UseSystemLocale,
MaxDecimalPlaces = 10,
ShowErrorMessage = false, // Make sure we return the empty results when error occurs
UseThousandsSeparator = true // Default value
};
private readonly Engine _engine = new(new Configuration
{
Scope = new Dictionary<string, object>
{
{ "e", Math.E }, // e is not contained in the default mages engine
}
});
public CalculatorPluginTest()
{
_plugin = new Main();
var settingField = typeof(Main).GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance);
if (settingField == null)
Assert.Fail("Could not find field '_settings' on Flow.Launcher.Plugin.Calculator.Main");
settingField.SetValue(_plugin, _settings);
var engineField = typeof(Main).GetField("MagesEngine", BindingFlags.NonPublic | BindingFlags.Static);
if (engineField == null)
Assert.Fail("Could not find static field 'MagesEngine' on Flow.Launcher.Plugin.Calculator.Main");
engineField.SetValue(null, _engine);
}
[Test]
public void ThousandsSeparatorTest_Enabled()
{
_settings.UseThousandsSeparator = true;
_settings.DecimalSeparator = DecimalSeparator.Dot;
var result = GetCalculationResult("1000+234");
// When thousands separator is enabled, the result should contain a separator
// Since decimal separator is dot, thousands separator should be comma
ClassicAssert.AreEqual("1,234", result);
_settings.DecimalSeparator = DecimalSeparator.Comma;
var result2 = GetCalculationResult("1000+234");
// When thousands separator is enabled, the result should contain a separator
// Since decimal separator is comma, thousands separator should be dot
ClassicAssert.AreEqual("1.234", result2);
}
[Test]
public void ThousandsSeparatorTest_Disabled()
{
_settings.UseThousandsSeparator = false;
_settings.DecimalSeparator = DecimalSeparator.UseSystemLocale;
var result = GetCalculationResult("1000+234");
ClassicAssert.AreEqual("1234", result);
}
[Test]
public void ThousandsSeparatorTest_LargeNumber()
{
_settings.UseThousandsSeparator = false;
_settings.DecimalSeparator = DecimalSeparator.UseSystemLocale;
var result = GetCalculationResult("1000000+234567");
ClassicAssert.AreEqual("1234567", result);
}
// Basic operations
[TestCase(@"1+1", "2")]
[TestCase(@"2-1", "1")]
[TestCase(@"2*2", "4")]
[TestCase(@"4/2", "2")]
[TestCase(@"2^3", "8")]
// Decimal places
[TestCase(@"10/3", "3.3333333333")]
// Parentheses
[TestCase(@"(1+2)*3", "9")]
[TestCase(@"2^(1+2)", "8")]
// Functions
[TestCase(@"pow(2,3)", "8")]
[TestCase(@"min(1,-1,-2)", "-2")]
[TestCase(@"max(1,-1,-2)", "1")]
[TestCase(@"sqrt(16)", "4")]
[TestCase(@"sin(pi)", "0.0000000000")]
[TestCase(@"cos(0)", "1")]
[TestCase(@"tan(0)", "0")]
[TestCase(@"log10(100)", "2")]
[TestCase(@"log(100)", "2")]
[TestCase(@"log2(8)", "3")]
[TestCase(@"ln(e)", "1")]
[TestCase(@"abs(-5)", "5")]
// Constants
[TestCase(@"pi", "3.1415926536")]
// Complex expressions
[TestCase(@"(2+3)*sqrt(16)-log(100)/ln(e)", "18")]
[TestCase(@"sin(pi/2)+cos(0)+tan(0)", "2")]
// Error handling (should return empty result)
[TestCase(@"10/0", "")]
[TestCase(@"sqrt(-1)", "")]
[TestCase(@"log(0)", "")]
[TestCase(@"invalid_expression", "")]
public void CalculatorTest(string expression, string result)
{
_settings.UseThousandsSeparator = false;
_settings.DecimalSeparator = DecimalSeparator.Dot;
ClassicAssert.AreEqual(GetCalculationResult(expression), result);
}
private string GetCalculationResult(string expression)
{
var results = _plugin.Query(new Plugin.Query()
{
Search = expression
});
return results.Count > 0 ? results[0].Title : string.Empty;
}
}
}

View file

@ -16,9 +16,9 @@ namespace Flow.Launcher.Test
{">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List<string> {">"}}}}
};
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins);
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", "> ping google.com -n 20 -6", nonGlobalPlugins);
ClassicAssert.AreEqual("> ping google.com -n 20 -6", q.RawQuery);
ClassicAssert.AreEqual("> ping google.com -n 20 -6", q.TrimmedQuery);
ClassicAssert.AreEqual("ping google.com -n 20 -6", q.Search, "Search should not start with the ActionKeyword.");
ClassicAssert.AreEqual(">", q.ActionKeyword);
@ -39,10 +39,10 @@ namespace Flow.Launcher.Test
{">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List<string> {">"}, Disabled = true}}}
};
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins);
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", "> ping google.com -n 20 -6", nonGlobalPlugins);
ClassicAssert.AreEqual("> ping google.com -n 20 -6", q.Search);
ClassicAssert.AreEqual(q.Search, q.RawQuery, "RawQuery should be equal to Search.");
ClassicAssert.AreEqual(q.Search, q.TrimmedQuery, "TrimmedQuery should be equal to Search.");
ClassicAssert.AreEqual(6, q.SearchTerms.Length, "The length of SearchTerms should match.");
ClassicAssert.AreNotEqual(">", q.ActionKeyword, "ActionKeyword should not match that of a disabled plugin.");
ClassicAssert.AreEqual("ping google.com -n 20 -6", q.SecondToEndSearch, "SecondToEndSearch should be trimmed of multiple whitespace characters");
@ -51,7 +51,7 @@ namespace Flow.Launcher.Test
[Test]
public void GenericPluginQueryTest()
{
Query q = QueryBuilder.Build("file.txt file2 file3", new Dictionary<string, PluginPair>());
Query q = QueryBuilder.Build("file.txt file2 file3", "file.txt file2 file3", new Dictionary<string, PluginPair>());
ClassicAssert.AreEqual("file.txt file2 file3", q.Search);
ClassicAssert.AreEqual("", q.ActionKeyword);

View file

@ -0,0 +1,71 @@
using System.Collections.Generic;
using System.Reflection;
using Flow.Launcher.Infrastructure;
using NUnit.Framework;
using NUnit.Framework.Legacy;
namespace Flow.Launcher.Test
{
[TestFixture]
public class TranslationMappingTest
{
[Test]
public void AddNewIndex_ShouldAddTranslatedIndexPlusLength()
{
var mapping = new TranslationMapping();
mapping.AddNewIndex(5, 3);
mapping.AddNewIndex(8, 2);
// 5+3=8, 8+2=10
ClassicAssert.AreEqual(2, GetOriginalToTranslatedCount(mapping));
ClassicAssert.AreEqual(8, GetOriginalToTranslatedAt(mapping, 0));
ClassicAssert.AreEqual(10, GetOriginalToTranslatedAt(mapping, 1));
}
[TestCase(0, 0)] // "F" -> "F"
[TestCase(1, 1)] // "l" -> "l"
[TestCase(2, 2)] // "o" -> "o"
[TestCase(3, 3)] // "w" -> "w"
[TestCase(4, 4)] // " " -> " "
[TestCase(5, 5)] // "Y" (translated from "用") -> original index 5
[TestCase(6, 5)] // "o" (translated from "用") -> original index 5
[TestCase(7, 5)] // "n" (translated from "用") -> original index 5
[TestCase(8, 5)] // "g" (translated from "用") -> original index 5
[TestCase(10, 6)] // "H" (translated from "户") -> original index 6
[TestCase(11, 6)] // "u" (translated from "户") -> original index 6
public void MapToOriginalIndex_ShouldReturnExpectedIndex(int translatedIndex, int expectedOriginalIndex)
{
var mapping = new TranslationMapping();
// Test case :
// 0123456
// Flow 用户
// 012345678901
// Flow Yong Hu
mapping.AddNewIndex(0, 1); // F
mapping.AddNewIndex(1, 1); // l
mapping.AddNewIndex(2, 1); // o
mapping.AddNewIndex(3, 1); // w
mapping.AddNewIndex(4, 1); // ' '
mapping.AddNewIndex(5, 4); // 用 -> Yong
mapping.AddNewIndex(10, 2); // 户 -> Hu
var result = mapping.MapToOriginalIndex(translatedIndex);
ClassicAssert.AreEqual(expectedOriginalIndex, result);
}
private static int GetOriginalToTranslatedCount(TranslationMapping mapping)
{
var field = typeof(TranslationMapping).GetField("_originalToTranslated", BindingFlags.NonPublic | BindingFlags.Instance);
var list = (List<int>)field.GetValue(mapping);
return list.Count;
}
private static int GetOriginalToTranslatedAt(TranslationMapping mapping, int index)
{
var field = typeof(TranslationMapping).GetField("_originalToTranslated", BindingFlags.NonPublic | BindingFlags.Instance);
var list = (List<int>)field.GetValue(mapping);
return list[index];
}
}
}

View file

@ -14,102 +14,102 @@
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
</WindowChrome.WindowChrome>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition Height="80" />
</Grid.RowDefinitions>
<Grid>
<StackPanel Grid.Row="0">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="4"
Click="BtnCancel_OnClick"
Style="{StaticResource TitleBarCloseButtonStyle}">
<Path
Width="46"
Height="32"
Data="M 18,11 27,20 M 18,20 27,11"
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
StrokeThickness="1">
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
</Grid>
</StackPanel>
<StackPanel Margin="26 12 26 0">
<StackPanel Grid.Row="0" Margin="0 0 0 12">
<TextBlock
Grid.Column="0"
Margin="0 0 0 0"
FontSize="20"
FontWeight="SemiBold"
Text="{DynamicResource actionKeywordsTitle}"
TextAlignment="Left" />
</StackPanel>
<StackPanel>
<TextBlock
FontSize="14"
Text="{DynamicResource actionkeyword_tips}"
TextAlignment="Left"
TextWrapping="WrapWithOverflow" />
</StackPanel>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Margin="0 18 0 0" Orientation="Horizontal">
<TextBlock
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource currentActionKeywords}" />
<TextBlock
x:Name="tbOldActionKeyword"
Grid.Row="0"
Grid.Column="1"
Margin="14 10 10 10"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource Color05B}" />
</StackPanel>
<StackPanel Margin="0 0 0 10" Orientation="Horizontal">
<TextBlock
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource newActionKeyword}" />
<TextBox
x:Name="tbAction"
Width="105"
Margin="10 10 15 10"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
</StackPanel>
</StackPanel>
</StackPanel>
</Grid>
<Border
<Button
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
Click="BtnCancel_OnClick"
Style="{StaticResource TitleBarCloseButtonStyle}">
<Path
Width="46"
Height="32"
Data="M 18,11 27,20 M 18,20 27,11"
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
StrokeThickness="1">
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
<Setter Property="Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="26 12 26 12"
FontSize="20"
FontWeight="SemiBold"
Text="{DynamicResource actionKeywordsTitle}"
TextAlignment="Left" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="26 0 26 10"
FontSize="14"
Text="{DynamicResource actionkeyword_tips}"
TextAlignment="Left"
TextWrapping="WrapWithOverflow" />
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="26 0 10 0"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource currentActionKeywords}" />
<TextBox
x:Name="tbOldActionKeyword"
Grid.Row="3"
Grid.Column="1"
Margin="10 10 26 6"
VerticalAlignment="Center"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource Color05B}"
IsReadOnly="True"
Text="List of old keyword(s)" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="26 6 10 12"
VerticalAlignment="Center"
FontSize="14"
Text="{DynamicResource newActionKeyword}" />
<TextBox
x:Name="tbAction"
Grid.Row="4"
Grid.Column="1"
Margin="10 6 26 10"
VerticalAlignment="Center" />
<Border
Grid.Row="5"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0 10 0 0"
Background="{DynamicResource PopupButtonAreaBGColor}"
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
BorderThickness="0 1 0 0">
@ -117,15 +117,15 @@
<Button
x:Name="btnCancel"
Width="145"
Height="30"
Margin="10 0 5 0"
Height="38"
Margin="10 0 10 0"
Click="BtnCancel_OnClick"
Content="{DynamicResource cancel}" />
<Button
x:Name="btnDone"
Width="145"
Height="30"
Margin="5 0 10 0"
Height="38"
Margin="10 0 10 0"
Click="btnDone_OnClick"
Style="{StaticResource AccentButtonStyle}">
<TextBlock x:Name="lblAdd" Text="{DynamicResource done}" />

View file

@ -20,7 +20,9 @@ namespace Flow.Launcher
private void ActionKeyword_OnLoaded(object sender, RoutedEventArgs e)
{
tbOldActionKeyword.Text = string.Join(Query.ActionKeywordSeparator, _plugin.Metadata.ActionKeywords.ToArray());
tbOldActionKeyword.Text = string.Join(Query.ActionKeywordSeparator, _plugin.Metadata.ActionKeywords);
tbAction.Text = tbOldActionKeyword.Text;
tbAction.SelectAll();
tbAction.Focus();
}
@ -33,38 +35,39 @@ namespace Flow.Launcher
{
var oldActionKeywords = _plugin.Metadata.ActionKeywords;
var newActionKeywords = tbAction.Text.Split(Query.ActionKeywordSeparator).ToList();
newActionKeywords.RemoveAll(string.IsNullOrEmpty);
newActionKeywords = newActionKeywords.Distinct().ToList();
var newActionKeywords = tbAction.Text.Split(Query.ActionKeywordSeparator)
.Where(s => !string.IsNullOrEmpty(s))
.Distinct()
.ToList();
newActionKeywords = newActionKeywords.Count > 0 ? newActionKeywords : new() { Query.GlobalPluginWildcardSign };
var addedActionKeywords = newActionKeywords.Except(oldActionKeywords).ToList();
var removedActionKeywords = oldActionKeywords.Except(newActionKeywords).ToList();
if (!addedActionKeywords.Any(App.API.ActionKeywordAssigned))
if (addedActionKeywords.Any(App.API.ActionKeywordAssigned))
{
if (oldActionKeywords.Count != newActionKeywords.Count)
{
ReplaceActionKeyword(_plugin.Metadata.ID, removedActionKeywords, addedActionKeywords);
return;
}
App.API.ShowMsgBox(Localize.newActionKeywordsHasBeenAssigned());
return;
}
var sortedOldActionKeywords = oldActionKeywords.OrderBy(s => s).ToList();
var sortedNewActionKeywords = newActionKeywords.OrderBy(s => s).ToList();
if (oldActionKeywords.Count != newActionKeywords.Count)
{
ReplaceActionKeyword(_plugin.Metadata.ID, removedActionKeywords, addedActionKeywords);
return;
}
if (sortedOldActionKeywords.SequenceEqual(sortedNewActionKeywords))
{
// User just changes the sequence of action keywords
App.API.ShowMsgBox(App.API.GetTranslation("newActionKeywordsSameAsOld"));
}
else
{
ReplaceActionKeyword(_plugin.Metadata.ID, removedActionKeywords, addedActionKeywords);
}
var sortedOldActionKeywords = oldActionKeywords.OrderBy(s => s).ToList();
var sortedNewActionKeywords = newActionKeywords.OrderBy(s => s).ToList();
if (sortedOldActionKeywords.SequenceEqual(sortedNewActionKeywords))
{
// User just changes the sequence of action keywords
App.API.ShowMsgBox(Localize.newActionKeywordsSameAsOld());
}
else
{
App.API.ShowMsgBox(App.API.GetTranslation("newActionKeywordsHasBeenAssigned"));
ReplaceActionKeyword(_plugin.Metadata.ID, removedActionKeywords, addedActionKeywords);
}
}

View file

@ -2,7 +2,8 @@
x:Class="Flow.Launcher.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
ShutdownMode="OnMainWindowClose"
Startup="OnStartup">
<Application.Resources>
@ -10,17 +11,17 @@
<ResourceDictionary.MergedDictionaries>
<ui:ThemeResources>
<ui:ThemeResources.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<ResourceDictionary x:Key="Light" ui:ThemeDictionary.Key="Light">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Resources/Light.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<ResourceDictionary x:Key="Dark" ui:ThemeDictionary.Key="Dark">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Resources/Dark.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<ResourceDictionary x:Key="HighContrast" ui:ThemeDictionary.Key="HighContrast">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Resources/Dark.xaml" />
</ResourceDictionary.MergedDictionaries>
@ -33,6 +34,15 @@
<ResourceDictionary Source="pack://application:,,,/Themes/Win11Light.xaml" />
<ResourceDictionary Source="pack://application:,,,/Languages/en.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Override styles in UI.Modern.WPF -->
<Thickness x:Key="ListViewItemCompactSelectedBorderThemeThickness">2</Thickness>
<sys:Double x:Key="CheckBoxMinWidth">0</sys:Double>
<sys:Double x:Key="GridViewItemMinWidth">0</sys:Double>
<sys:Double x:Key="GridViewItemMinHeight">40</sys:Double>
<sys:Double x:Key="ListViewItemMinWidth">0</sys:Double>
<sys:Double x:Key="ListViewItemMinHeight">36</sys:Double>
<SolidColorBrush x:Key="NavigationViewSelectionIndicatorForeground" Color="#FF0063B1" />
</ResourceDictionary>
</Application.Resources>
</Application>

View file

@ -16,11 +16,13 @@ using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.DialogJump;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.SettingPages.ViewModels;
using Flow.Launcher.ViewModel;
using iNKORE.UI.WPF.Modern.Common;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.Threading;
@ -41,9 +43,10 @@ namespace Flow.Launcher
private static readonly string ClassName = nameof(App);
private static bool _disposed;
private static Settings _settings;
private static MainWindow _mainWindow;
private readonly MainViewModel _mainVM;
private readonly Settings _settings;
private readonly Internationalization _internationalization;
// To prevent two disposals running at the same time.
private static readonly object _disposingLock = new();
@ -54,19 +57,11 @@ namespace Flow.Launcher
public App()
{
// Do not use bitmap cache since it can cause WPF second window freezing issue
ShadowAssist.UseBitmapCache = false;
// Initialize settings
try
{
var storage = new FlowLauncherJsonStorage<Settings>();
_settings = storage.Load();
_settings.SetStorage(storage);
_settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled();
}
catch (Exception e)
{
ShowErrorMsgBoxAndFailFast("Cannot load setting storage, please check local data directory", e);
return;
}
_settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled();
// Configure the dependency injection container
try
@ -117,22 +112,13 @@ namespace Flow.Launcher
API = Ioc.Default.GetRequiredService<IPublicAPI>();
_settings.Initialize();
_mainVM = Ioc.Default.GetRequiredService<MainViewModel>();
_internationalization = Ioc.Default.GetRequiredService<Internationalization>();
}
catch (Exception e)
{
ShowErrorMsgBoxAndFailFast("Cannot initialize api and settings, please open new issue in Flow.Launcher", e);
return;
}
// Local function
static void ShowErrorMsgBoxAndFailFast(string message, Exception e)
{
// Firstly show users the message
MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error);
// Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info.
Environment.FailFast(message, e);
}
}
#endregion
@ -142,6 +128,29 @@ namespace Flow.Launcher
[STAThread]
public static void Main()
{
// Initialize settings so that we can get language code
try
{
var storage = new FlowLauncherJsonStorage<Settings>();
_settings = storage.Load();
_settings.SetStorage(storage);
}
catch (Exception e)
{
ShowErrorMsgBoxAndFailFast("Cannot load setting storage, please check local data directory", e);
return;
}
// Initialize system language before changing culture info
Internationalization.InitSystemLanguageCode();
// Change culture info before application creation to localize WinForm windows
if (_settings.Language != Constant.SystemLanguageCode)
{
Internationalization.ChangeCultureInfo(_settings.Language);
}
// Start the application as a single instance
if (SingleInstance<App>.InitializeAsFirstInstance())
{
using var application = new App();
@ -152,6 +161,19 @@ namespace Flow.Launcher
#endregion
#region Fail Fast
private static void ShowErrorMsgBoxAndFailFast(string message, Exception e)
{
// Firstly show users the message
MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error);
// Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info.
Environment.FailFast(message, e);
}
#endregion
#region App Events
#pragma warning disable VSTHRD100 // Avoid async void methods
@ -165,12 +187,23 @@ namespace Flow.Launcher
// So set to OnExplicitShutdown to prevent the application from shutting down before main window is created
Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
// Setup log level before any logging is done
Log.SetLogLevel(_settings.LogLevel);
// Update dynamic resources base on settings
Current.Resources["SettingWindowFont"] = new FontFamily(_settings.SettingWindowFont);
Current.Resources["ContentControlThemeFontFamily"] = new FontFamily(_settings.SettingWindowFont);
// Initialize notification system before any notification api is called
Notification.Install();
// Enable Win32 dark mode if the system is in dark mode before creating all windows
Win32Helper.EnableWin32DarkMode(_settings.ColorScheme);
// Initialize language before portable clean up since it needs translations
await _internationalization.InitializeLanguageAsync();
// Clean up after portability update
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
API.LogInfo(ClassName, "Begin Flow Launcher startup ----------------------------------------------------");
@ -180,29 +213,25 @@ namespace Flow.Launcher
RegisterDispatcherUnhandledException();
RegisterTaskSchedulerUnhandledException();
var imageLoadertask = ImageLoader.InitializeAsync();
AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings);
PluginManager.LoadPlugins(_settings.PluginSettings);
// Register ResultsUpdated event after all plugins are loaded
Ioc.Default.GetRequiredService<MainViewModel>().RegisterResultsUpdatedEvent();
var imageLoaderTask = ImageLoader.InitializeAsync();
Http.Proxy = _settings.Proxy;
await PluginManager.InitializePluginsAsync();
// Initialize plugin manifest before initializing plugins so that they can use the manifest instantly
await API.UpdatePluginManifestAsync();
// Change language after all plugins are initialized because we need to update plugin title based on their api
await Ioc.Default.GetRequiredService<Internationalization>().InitializeLanguageAsync();
await imageLoadertask;
await imageLoaderTask;
_mainWindow = new MainWindow();
Current.MainWindow = _mainWindow;
Current.MainWindow.Title = Constant.FlowLauncher;
// Initialize Dialog Jump before hotkey mapper since hotkey mapper will register its hotkey
// Initialize Dialog Jump after main window is created so that it can access main window handle
DialogJump.InitializeDialogJump();
DialogJump.SetupDialogJump(_settings.EnableDialogJump);
// Initialize hotkey mapper instantly after main window is created because
// it will steal focus from main window which causes window hide
HotKeyMapper.Initialize();
@ -218,7 +247,35 @@ namespace Flow.Launcher
AutoUpdates();
API.SaveAppAllSettings();
API.LogInfo(ClassName, "End Flow Launcher startup ----------------------------------------------------");
API.LogInfo(ClassName, "End Flow Launcher startup ------------------------------------------------------");
_ = API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>
{
API.LogInfo(ClassName, "Begin plugin initialization ----------------------------------------------------");
AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings);
PluginManager.LoadPlugins(_settings.PluginSettings);
await PluginManager.InitializePluginsAsync(_mainVM);
// Refresh the history results after plugins are initialized so that we can parse the absolute icon paths
_mainVM.RefreshLastOpenedHistoryResults();
// Refresh home page after plugins are initialized because users may open main window during plugin initialization
// And home page is created without full plugin list
if (_settings.ShowHomePage && _mainVM.QueryResultsSelected() && string.IsNullOrEmpty(_mainVM.QueryText))
{
_mainVM.QueryResults();
}
AutoPluginUpdates();
// Save all settings since we possibly update the plugin environment paths
API.SaveAppAllSettings();
API.LogInfo(ClassName, "End plugin initialization ------------------------------------------------------");
});
});
}
@ -228,7 +285,7 @@ namespace Flow.Launcher
/// Check startup only for Release
/// </summary>
[Conditional("RELEASE")]
private void AutoStartup()
private static void AutoStartup()
{
// we try to enable auto-startup on first launch, or reenable if it was removed
// but the user still has the setting set
@ -243,13 +300,13 @@ namespace Flow.Launcher
// but if it fails (permissions, etc) then don't keep retrying
// this also gives the user a visual indication in the Settings widget
_settings.StartFlowLauncherOnSystemStartup = false;
API.ShowMsg(API.GetTranslation("setAutoStartFailed"), e.Message);
API.ShowMsgError(Localize.setAutoStartFailed(), e.Message);
}
}
}
[Conditional("RELEASE")]
private void AutoUpdates()
private static void AutoUpdates()
{
_ = Task.Run(async () =>
{
@ -266,6 +323,38 @@ namespace Flow.Launcher
});
}
[Conditional("RELEASE")]
private static void AutoPluginUpdates()
{
_ = Task.Run(async () =>
{
if (_settings.AutoUpdatePlugins)
{
// check plugin updates every 5 hour
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
{
Current.Dispatcher.Invoke(() =>
{
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
pluginUpdateWindow.ShowDialog();
});
});
while (await timer.WaitForNextTickAsync())
// check updates on startup
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
{
Current.Dispatcher.Invoke(() =>
{
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
pluginUpdateWindow.ShowDialog();
});
});
}
});
}
#endregion
#region Register Events
@ -357,6 +446,8 @@ namespace Flow.Launcher
// since some resources owned by the thread need to be disposed.
_mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose);
_mainVM?.Dispose();
DialogJump.Dispose();
_internationalization.Dispose();
}
API.LogInfo(ClassName, "End Flow Launcher dispose ----------------------------------------------------");

View file

@ -5,7 +5,7 @@ using System.Windows.Input;
namespace Flow.Launcher.Converters;
internal class BoolToIMEConversionModeConverter : IValueConverter
public class BoolToIMEConversionModeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
@ -22,7 +22,7 @@ internal class BoolToIMEConversionModeConverter : IValueConverter
}
}
internal class BoolToIMEStateConverter : IValueConverter
public class BoolToIMEStateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{

View file

@ -0,0 +1,91 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace Flow.Launcher.Converters;
public class CornerRadiusFilterConverter : DependencyObject, IValueConverter
{
public CornerRadiusFilterKind Filter { get; set; }
public double Scale { get; set; } = 1.0;
public static CornerRadius Convert(CornerRadius radius, CornerRadiusFilterKind filterKind)
{
CornerRadius result = radius;
switch (filterKind)
{
case CornerRadiusFilterKind.Top:
result.BottomLeft = 0;
result.BottomRight = 0;
break;
case CornerRadiusFilterKind.Right:
result.TopLeft = 0;
result.BottomLeft = 0;
break;
case CornerRadiusFilterKind.Bottom:
result.TopLeft = 0;
result.TopRight = 0;
break;
case CornerRadiusFilterKind.Left:
result.TopRight = 0;
result.BottomRight = 0;
break;
}
return result;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var cornerRadius = (CornerRadius)value;
var scale = Scale;
if (!double.IsNaN(scale))
{
cornerRadius.TopLeft *= scale;
cornerRadius.TopRight *= scale;
cornerRadius.BottomRight *= scale;
cornerRadius.BottomLeft *= scale;
}
var filterType = Filter;
if (filterType == CornerRadiusFilterKind.TopLeftValue ||
filterType == CornerRadiusFilterKind.BottomRightValue)
{
return GetDoubleValue(cornerRadius, filterType);
}
return Convert(cornerRadius, filterType);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
private static double GetDoubleValue(CornerRadius radius, CornerRadiusFilterKind filterKind)
{
switch (filterKind)
{
case CornerRadiusFilterKind.TopLeftValue:
return radius.TopLeft;
case CornerRadiusFilterKind.BottomRightValue:
return radius.BottomRight;
}
return 0;
}
}
public enum CornerRadiusFilterKind
{
None,
Top,
Right,
Bottom,
Left,
TopLeftValue,
BottomRightValue
}

View file

@ -0,0 +1,32 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace Flow.Launcher.Converters;
public class PlacementRectangleConverter : IMultiValueConverter
{
public Thickness Margin { get; set; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length == 2 &&
values[0] is double width &&
values[1] is double height)
{
var margin = Margin;
var topLeft = new Point(margin.Left, margin.Top);
var bottomRight = new Point(width - margin.Right, height - margin.Bottom);
var rect = new Rect(topLeft, bottomRight);
return rect;
}
return Rect.Empty;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View file

@ -33,15 +33,39 @@ public class QuerySuggestionBoxConverter : IMultiValueConverter
{
var selectedResult = selectedItem.Result;
var selectedResultActionKeyword = string.IsNullOrEmpty(selectedResult.ActionKeywordAssigned) ? "" : selectedResult.ActionKeywordAssigned + " ";
var selectedResultPossibleSuggestion = selectedResultActionKeyword + selectedResult.Title;
if (!selectedResultPossibleSuggestion.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase))
string selectedResultPossibleSuggestion = null;
// Firstly check if the result has QuerySuggestionText
if (!string.IsNullOrEmpty(selectedResult.QuerySuggestionText))
{
selectedResultPossibleSuggestion = selectedResultActionKeyword + selectedResult.QuerySuggestionText;
// If this QuerySuggestionText does not start with the queryText, set it to null
if (!selectedResultPossibleSuggestion.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase))
{
selectedResultPossibleSuggestion = null;
}
}
// Then check Title as suggestion
if (string.IsNullOrEmpty(selectedResultPossibleSuggestion))
{
selectedResultPossibleSuggestion = selectedResultActionKeyword + selectedResult.Title;
// If this QuerySuggestionText does not start with the queryText, set it to null
if (!selectedResultPossibleSuggestion.StartsWith(queryText, StringComparison.CurrentCultureIgnoreCase))
{
selectedResultPossibleSuggestion = null;
}
}
if (string.IsNullOrEmpty(selectedResultPossibleSuggestion))
return string.Empty;
// For AutocompleteQueryCommand.
// When user typed lower case and result title is uppercase, we still want to display suggestion
selectedItem.QuerySuggestionText = queryText + selectedResultPossibleSuggestion.Substring(queryText.Length);
selectedItem.QuerySuggestionText = string.Concat(queryText, selectedResultPossibleSuggestion.AsSpan(queryText.Length));
// Check if Text will be larger than our QueryTextBox
Typeface typeface = new Typeface(queryTextBox.FontFamily, queryTextBox.FontStyle, queryTextBox.FontWeight, queryTextBox.FontStretch);

View file

@ -1,16 +1,15 @@
using Flow.Launcher.Plugin.Everything.Everything;
using Flow.Launcher.Plugin.Explorer.Helper;
using System;
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace Flow.Launcher.Plugin.Explorer.Views.Converters;
namespace Flow.Launcher.Converters;
public class EnumNameConverter : IValueConverter
public class SharedSizeGroupConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is SortOption option ? option.GetTranslatedName() : value;
return (Visibility)value != Visibility.Collapsed ? (string)parameter : null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

View file

@ -5,7 +5,7 @@ using System.Windows.Input;
namespace Flow.Launcher.Converters;
class StringToKeyBindingConverter : IValueConverter
public class StringToKeyBindingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{

View file

@ -119,7 +119,8 @@
Grid.Column="1"
Margin="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Center" />
VerticalAlignment="Center"
Text="{Binding ActionKeyword}" />
<Button
x:Name="btnTestActionKeyword"
Grid.Row="1"
@ -150,7 +151,20 @@
Margin="5 0 10 0"
Click="btnAdd_OnClick"
Style="{StaticResource AccentButtonStyle}">
<TextBlock x:Name="lblAdd" Text="{DynamicResource done}" />
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock
x:Name="tbAdd"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{DynamicResource done}"
Visibility="Collapsed" />
<TextBlock
x:Name="tbUpdate"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{DynamicResource update}"
Visibility="Collapsed" />
</Grid>
</Button>
</StackPanel>
</Border>

Some files were not shown because too many files have changed in this diff Show more