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 13 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.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds deep-link parsing and dispatch, single-instance payload forwarding, Windows registry registration, a settings toggle, and plugin installation 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.
All reported issues were addressed across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…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 |
|
@codex Review it |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 061d53c8de
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return; | ||
| } | ||
|
|
||
| DeepLink.Dispatch(payload); |
There was a problem hiding this comment.
Queue second-instance links until startup completes
When a browser or script invokes a link while the first process is still starting, the mutex and pipe server already exist, but _mainWindow and the plugins may not. This immediately dispatches the payload rather than deferring it like _pendingDeepLink: query activation is suppressed by App.LoadingOrExiting, and plugin-install handlers can run against an uninitialized PluginManager, so the link can be lost or fail. Queue these activations until the same post-plugin-initialization point used for the cold-start payload.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 568b3f8: OnSecondAppStarted now queues the payload into _pendingDeepLink (behind a lock shared with the startup flush) until plugin initialization completes, and the startup path dispatches whatever is pending at that point, latest link wins.
|
|
||
| var plugin = new UserPlugin | ||
| { | ||
| ID = string.Empty, |
There was a problem hiding this comment.
Preserve a unique ID for URL-installed plugins
When AutoRestartAfterChanging is false, successfully installing the first ?url= plugin causes PluginManager.InstallPlugin to add this empty ID to ModifiedPlugins. Every subsequent URL install in the same session is also created with an empty ID, so the initial PluginModified(newPlugin.ID) check rejects it as already modified even when it is a different plugin. Populate the actual ID from the downloaded package before entering the normal install path, or otherwise avoid tracking all URL installs under string.Empty.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 568b3f8: after the download completes, the real ID is read from the zip's plugin.json before entering the install path, so ModifiedPlugins tracks the actual plugin ID and later URL installs are no longer rejected. If the zip is unreadable the ID stays empty and the existing InstallPlugin error path reports it.
- Queue deep links received by a second instance until plugin initialization completes, so warm links arriving during startup are no longer lost or dispatched against an uninitialized PluginManager - Recover the real plugin ID from the downloaded zip for url installs, so ModifiedPlugins no longer tracks every url install under an empty ID and rejects subsequent installs - Fall back to a default plugin name when URL sanitization collapses the basename to an empty string - Extract DerivePluginNameFromUrl and HasExactlyOneInstallIdentifier as testable helpers with unit tests
…line-arg # Conflicts: # Flow.Launcher.Test/PluginInstallerTest.cs
There was a problem hiding this comment.
All reported issues were addressed across 6 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
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
Summary of changes
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. When the URI scheme is disabled or no args are provided, launch behavior is unchanged; second-instance invocations forward a normalized deep link to the running app over IPC.App.Main(string[] args)normalizes args to a deep link; dispatch happens after plugins initialize. Links received during startup are queued so none are lost.OnSecondAppStarted(string payload)routes a payload; empty payload focuses the window.--queryand-q; empty queries are ignored..flowpluginpath, store id, or https URL). HelperHasExactlyOneInstallIdentifiervalidates install links..flowpluginand theflow-launcher://scheme, gated by a new General Settings toggle “Allow websites to open Flow Launcher”; registration is ensured at startup.PluginInstaller.InstallPluginFromWebAndCheckRestartAsyncwith unknown-source confirmation;DerivePluginNameFromUrlsanitizes filenames and falls back to a safe default;TryPopulateIdFromZiprecovers the real plugin ID for URL installs to keep modified-plugin tracking correct.DeepLinkTest(arg normalization, scheme-disabled behavior, URI parsing) andPluginInstallerTest(URL filename derivation edge cases).OnSecondAppStarted()overload; no user-visible features removed.DeepLinkTestandPluginInstallerTest; manual verification of cold/warm starts, empty queries, startup-queued links, and all plugin install paths.Release Note
Open Flow Launcher from your browser or scripts to search, open settings, or install plugins using special links or the new --query option.
Written for commit cd2a1b9. Summary will update on new commits.