Add flow-launcher:// deep link protocol, .flowplugin file association, and --query command line argument#4565
Add flow-launcher:// deep link protocol, .flowplugin file association, and --query command line argument#4565Garulf wants to merge 10 commits into
Conversation
Support `Flow.Launcher.exe --query "text"` (also `-query`). On cold start the query is applied once plugin initialization completes; when an instance is already running, the second process sends the query to it over the existing single-instance named pipe (which previously carried no payload) and the first instance shows the window with the query searched. Launching with no arguments behaves exactly as before.
📝 WalkthroughWalkthroughAdds deep-link parsing and dispatch, single-instance payload forwarding, registry registration, a settings toggle, and plugin install handling from deep-link URLs. ChangesDeep link handling and registration
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Flow.Launcher/Helper/SingleInstance.cs`:
- Around line 113-130: The timeout path in SingleInstance.HandleClient leaves
the pending ReadLineAsync running after Task.WhenAny returns on the delay
branch, which can fault later as an unobserved task exception. Update the pipe
read logic to use cancellation so the read is explicitly canceled when the
2000ms timeout wins, and ensure the code in the try block around
StreamReader.ReadLineAsync and the later
Dispatcher.Invoke(ActivateFirstInstance) only proceeds with a completed read
task. Also confirm the ReadLineAsync(CancellationToken) overload is available
for the project’s target framework before wiring the cancellation token through.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b9edbf7c-5932-46a5-9718-29538c9bef9f
📒 Files selected for processing (2)
Flow.Launcher/App.xaml.csFlow.Launcher/Helper/SingleInstance.cs
There was a problem hiding this comment.
3 issues found across 2 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…ng, url filename derivation
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Flow.Launcher/Helper/DeepLink.cs`:
- Around line 41-44: The deep link builder in DeepLink.Build should treat an
empty `--query` or `-query` value the same as no query at all. Update the
query-handling branch to detect when args[i + 1] is empty or whitespace and skip
creating the `query?q=` deep link, returning the no-payload behavior instead.
Keep the fix localized to the argument parsing logic in DeepLink.
In `@Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs`:
- Around line 108-124: The deep link toggle is being persisted before the
registry update succeeds, so a failure in
SettingsPaneGeneralViewModel.SetEnableDeepLinkProtocol can leave
Settings.EnableDeepLinkProtocol out of sync with Windows. Update the logic
around DeepLinkRegistration.RegisterUriScheme() and
DeepLinkRegistration.UnregisterUriScheme() so the previous setting is restored
in the catch block if the registry call throws, and keep the UI/persisted value
aligned with the actual registration state.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: beecc1f5-a5de-4af2-b556-e55facbfe387
📒 Files selected for processing (10)
Flow.Launcher.Core/Plugin/PluginInstaller.csFlow.Launcher.Infrastructure/UserSettings/Settings.csFlow.Launcher.Test/DeepLinkTest.csFlow.Launcher/App.xaml.csFlow.Launcher/Helper/DeepLink.csFlow.Launcher/Helper/DeepLinkRegistration.csFlow.Launcher/Helper/SingleInstance.csFlow.Launcher/Languages/en.xamlFlow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.csFlow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
✅ Files skipped from review due to trivial changes (2)
- Flow.Launcher/Languages/en.xaml
- Flow.Launcher.Test/DeepLinkTest.cs
🚧 Files skipped from review as they are similar to previous changes (1)
- Flow.Launcher/Helper/SingleInstance.cs
There was a problem hiding this comment.
6 issues found across 10 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- Rename -query to -q to match single-dash single-character convention (per review discussion) and treat an empty query value as no query - Cancel the pending pipe read on timeout instead of abandoning it, to avoid an unobserved task exception; widen the client connect timeout past the server's read guard so a stalled client can't starve a legitimate second launch; observe exceptions from the fire-and-forget dispatcher activation - Revert the deep link protocol setting if registry (un)registration fails, instead of persisting a value that disagrees with the OS state - Sanitize the filename derived from a plugin download URL so a percent-decoded path segment can't reintroduce characters that are invalid in Windows filenames - Stop logging full deep link payloads (query text, paths, URLs) at Warn level; keep only the actionable fact at Warn and move the raw payload to Debug
There was a problem hiding this comment.
2 issues found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Flow.Launcher.Core/Plugin/PluginInstaller.cs">
<violation number="1" location="Flow.Launcher.Core/Plugin/PluginInstaller.cs:167">
P2: Installing a plugin from a web URL can produce an empty or misleading plugin name when the URL path contains percent-encoded characters that decode to invalid Windows filename characters (e.g., `%3F` → `?`). After `Path.GetFileName` and the invalid-character stripping loop, a basename like `?.zip` collapses to `.zip`, and removing the extension yields an empty string. That empty value flows into `UserPlugin.Name` and `Version`, so user-facing prompts show a blank plugin name, and the temp download filename becomes `-$'{Guid.NewGuid()}.zip`. Consider validating the sanitized result and falling back to a safe default (for example, `"plugin"` or the last non-empty path segment) before constructing the `UserPlugin`.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| var filename = Uri.TryCreate(url, UriKind.Absolute, out var uri) | ||
| ? Path.GetFileName(uri.LocalPath) | ||
| : url.Split('/').Last(); | ||
| foreach (var c in Path.GetInvalidFileNameChars()) |
There was a problem hiding this comment.
P2: Installing a plugin from a web URL can produce an empty or misleading plugin name when the URL path contains percent-encoded characters that decode to invalid Windows filename characters (e.g., %3F → ?). After Path.GetFileName and the invalid-character stripping loop, a basename like ?.zip collapses to .zip, and removing the extension yields an empty string. That empty value flows into UserPlugin.Name and Version, so user-facing prompts show a blank plugin name, and the temp download filename becomes -$'{Guid.NewGuid()}.zip. Consider validating the sanitized result and falling back to a safe default (for example, "plugin" or the last non-empty path segment) before constructing the UserPlugin.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher.Core/Plugin/PluginInstaller.cs, line 167:
<comment>Installing a plugin from a web URL can produce an empty or misleading plugin name when the URL path contains percent-encoded characters that decode to invalid Windows filename characters (e.g., `%3F` → `?`). After `Path.GetFileName` and the invalid-character stripping loop, a basename like `?.zip` collapses to `.zip`, and removing the extension yields an empty string. That empty value flows into `UserPlugin.Name` and `Version`, so user-facing prompts show a blank plugin name, and the temp download filename becomes `-$'{Guid.NewGuid()}.zip`. Consider validating the sanitized result and falling back to a safe default (for example, `"plugin"` or the last non-empty path segment) before constructing the `UserPlugin`.</comment>
<file context>
@@ -159,9 +159,15 @@ public static async Task InstallPluginFromWebAndCheckRestartAsync(string url)
var filename = Uri.TryCreate(url, UriKind.Absolute, out var uri)
? Path.GetFileName(uri.LocalPath)
: url.Split('/').Last();
+ foreach (var c in Path.GetInvalidFileNameChars())
+ {
+ filename = filename.Replace(c.ToString(), string.Empty);
</file context>
…ailures - Widen the outer signal-delivery wait from 3s to 5s so a slow ConnectAsync(3000ms) can no longer consume the entire budget and starve the subsequent payload write, silently dropping a deep link - Distinguish the expected read-timeout (client connected but never wrote) from a genuine pipe read failure; log the latter instead of swallowing it silently
|
Personally tested:
Everything has been tested to work |
What
Adds a
flow-launcher://deep link protocol and.flowpluginfile association, plus a--query "text"(also-query) command-line flag, so scripts, browsers, and other apps can open/focus Flow Launcher to run a query, open settings, or install a plugin.--query "text"/-q "text": opens/focuses Flow Launcher with the query pre-filled and searched.flow-launcher://query?q=...,flow-launcher://settings,flow-launcher://plugin/install?path=|id=|url=: routed through a general verb-dispatch framework. Plugin installs always show the existing Yes/No confirmation dialog — never silent, whether triggered by a local.flowplugindouble-click, a store id, or an https download URL..flowpluginfile association and theflow-launcher://URI scheme are self-healing HKCU registrations applied at every startup (no admin rights required), with a new Settings toggle, "Allow websites to open Flow Launcher", to opt out.flow-launcher://URI and sends it to the first instance over the existing single-instance named pipe, then exits; the first instance dispatches it.How
Flow.Launcher/Helper/DeepLink.cs: normalizes command-line args (--query,.flowpluginpaths, rawflow-launcher://URIs) into a single URI string, parses a URI into a verb + query parameters, and dispatches to handlers forquery,settings, andplugin/install.Flow.Launcher/Helper/DeepLinkRegistration.cs: idempotent HKCU registration for the.flowpluginProgID and theflow-launcher://scheme, called on every startup so stale executable paths self-heal after updates.Flow.Launcher.Core/Plugin/PluginInstaller.cs: addsInstallPluginFromWebAndCheckRestartAsync(url), reusing the existing unknown-source warning and the always-promptingInstallPluginAndCheckRestartAsync.App.xaml.cs:Main(string[] args)normalizes args viaDeepLink.FromCommandLineArgs; the pending deep link is dispatched afterPluginManager.InitializePluginsAsync, before the home-page refresh check;OnSecondAppStarted(string payload)dispatches the warm-path payload (or just shows the window when empty).SingleInstance.cs: the named pipe now carries an optional payload — the normalized deep link URI (or legacy plain query text). The client writes it as a newline-terminated UTF-8 line and blocks (up to 3 s) since the process exits immediately after; the server reads one line guarded by a 2 s timeout and try/catch, so a client that connects but never writes degrades to a plain activation and can never hang the server loop.Settings.EnableDeepLinkProtocol(defaulttrue) gates the URI scheme registration; toggling it in Settings registers/unregisters the scheme immediately viaDeepLinkRegistration.Test matrix (manual, Windows)
--query "foo"/-query "foo", cold and warm — window opens/focuses with "foo" searched--query ""— treated as no query.flowplugin(Flow running and not running) — confirmation dialog, install only on Yesflow-launcher://query?q=foo,flow-launcher://settings,flow-launcher://plugin/install?id=.../?path=.../?url=https://...zipfrom a browser — routed correctly, every install prompts?url=http://...(non-https) — rejected with an https-only messageflow-launcher://nope— error notification, no crashHKCU\Software\Classes\flow-launcherremoved, browser links no longer open Flow;.flowplugindouble-click and--querystill work. Toggle on — restoredSummary by cubic
Adds a
flow-launcher://deep link and.flowpluginassociation plus a--query/-qflag so scripts and websites can open Flow Launcher, run searches, open settings, or install plugins. Command-line args are normalized to deep links and sent to the running instance via IPC; behavior is unchanged when disabled or with no args.Summary of changes
App.Main(args)normalizes args to a deep link and dispatches after plugin init;OnSecondAppStarted(string)now accepts a payload and routes it (empty payload just focuses the window).-qas a shorthand for--query; empty query values are ignored..flowpluginpath, store id, or https URL)..flowpluginand theflow-launcher://scheme, gated by a new Settings toggle “Allow websites to open Flow Launcher”; registration is ensured at startup.PluginInstaller.InstallPluginFromWebAndCheckRestartAsyncwith unknown-source confirmation and sanitized filenames derived from URLs.DeepLinkTest) for arg normalization (--query/-q),.flowpluginpaths, scheme-disabled behavior, and URI parsing.OnSecondAppStarted()overload; no user-visible features removed.DeepLinkTest; manually verified cold/warm starts, empty queries, no-arg launches, and plugin install flows.Release Note
Open Flow Launcher from the browser or scripts to search or install plugins using
flow-launcher://links, or start it with a pre-filled search via: Flow.Launcher.exe--query "your text"(or-q).Written for commit ab882e6. Summary will update on new commits.