Skip to content

feat: Add double-tap hotkey support for toggling Flow Launcher#4478

Open
RuoLv wants to merge 5 commits into
Flow-Launcher:devfrom
RuoLv:dev
Open

feat: Add double-tap hotkey support for toggling Flow Launcher#4478
RuoLv wants to merge 5 commits into
Flow-Launcher:devfrom
RuoLv:dev

Conversation

@RuoLv
Copy link
Copy Markdown

@RuoLv RuoLv commented May 20, 2026

Summary

This PR adds support for double-tap hotkeys — a new way to toggle Flow Launcher by pressing the same modifier key twice within a configurable time interval (e.g., pressing Ctrl twice quickly).

This is a common interaction pattern in launchers (e.g., Alfred, Hain) and provides an ergonomic alternative to traditional combo hotkeys, especially for users who find modifier+key combos awkward or conflict-prone.

What's New

  • Double-Tap Hotkey Setting — A dedicated hotkey control in Settings → Hotkey that lets users bind a double-tap gesture (Ctrl, Alt, Shift, or Win) to toggle Flow Launcher
  • Configurable Interval — A slider (100–500ms, default 300ms) controls how quickly the two presses must occur to count as a double-tap
  • Smart Key Capture Dialog — The hotkey capture dialog now distinguishes between combo hotkeys (Ctrl+Space) and double-tap hotkeys (Ctrl+Ctrl) in real-time using a three-state detection state machine:
    • Idle → waiting for input
    • ModifierDown → lone modifier pressed, waiting to see if another key follows (combo) or the modifier is released (potential double-tap)
    • WaitingSecondPress → modifier released, waiting 300ms for a second press

Technical Details

Component Description
DoubleTapDetector New class that monitors global keyboard events via the existing WH_KEYBOARD_LL hook. Tracks key-down/key-up to distinguish genuine double-taps from auto-repeat (key held down). Includes desync recovery for missed key-up events.
HotkeyModel Extended with a DoubleTap property and parsing for "Key + Key" format strings (e.g., "Ctrl + Ctrl"). Validation restricts double-tap to modifier keys only.
HotKeyMapper Registers double-tap hotkeys through the global keyboard callback (RegisterGlobalKeyboardCallback) instead of NHotkey, since NHotkey doesn't support double-tap semantics.
HotkeyControlDialog Implements a modifier detection state machine with PreviewKeyUp tracking. In dedicated double-tap mode (_isDoubleTapMode), any key press immediately creates a double-tap binding.

Files Changed

  • Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.csNew (424 lines)
  • Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs — Double-tap parsing, display, validation
  • Flow.Launcher.Infrastructure/UserSettings/Settings.csDoubleTapHotkey & DoubleTapHotkeyInterval settings
  • Flow.Launcher/Helper/HotKeyMapper.cs — Double-tap registration/removal/availability check
  • Flow.Launcher/HotkeyControl.xaml.csHotkeyType.DoubleTapHotkey enum & wiring
  • Flow.Launcher/HotkeyControlDialog.xamlPreviewKeyUp & Unloaded events
  • Flow.Launcher/HotkeyControlDialog.xaml.cs — Modifier detection state machine
  • Flow.Launcher/Languages/en.xaml — English localization
  • Flow.Launcher/Languages/zh-cn.xaml — Chinese localization
  • Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml — Settings UI

How to Test

  1. Open Settings → Hotkey
  2. Set a Double-Tap Hotkey (e.g., press Ctrl in the capture dialog — it should show "Ctrl + Ctrl")
  3. Adjust the Double-Tap Interval slider if needed
  4. Press the chosen key twice quickly → Flow Launcher should toggle
  5. Press the key once and wait → Flow Launcher should not toggle
  6. Verify existing combo hotkeys still work normally

Screenshots

企业微信截图_17792690042478

Notes

  • Double-tap hotkeys operate independently from the main toggle hotkey — both can be configured simultaneously
  • Only modifier keys (Ctrl, Alt, Shift, Win) are valid for double-tap bindings
  • The detector suppresses auto-repeat events to prevent false triggers when a key is held down

Summary by cubic

Adds double‑tap hotkeys to toggle Flow Launcher by pressing the same modifier twice within a configurable interval. Also routes double‑tap via a shared global keyboard callback that short‑circuits on consume, and uses the user‑configured interval end‑to‑end.

Summary of changes

  • Changed: Hotkey parsing/validation to detect double‑tap (e.g., "Ctrl + Ctrl") and restrict to modifiers only; HotKeyMapper routes both main and separate double‑tap hotkeys via one global keyboard callback and registers/unregisters it only when detectors exist; OnGlobalKeyboardEvent now short‑circuits after a detector consumes an event to avoid double toggles; hotkey dialog adds key‑up tracking and a small state machine to distinguish combos vs double‑tap; both capture and runtime use DoubleTapHotkeyInterval; bumped Flow.Launcher.Plugin to 5.3.0.
  • Added: DoubleTapDetector (global key handling, timeout, auto‑repeat guard, left/right modifier family matching); support for setting the main toggle as a double‑tap; separate DoubleTapHotkey with DoubleTapHotkeyInterval (100–500ms, default 300ms); Settings UI card and interval slider; localization (en, zh‑cn).
  • Removed: None (combo hotkeys and NHotkey path remain; double‑tap intentionally bypasses NHotkey).
  • Memory: One or two small detector instances with a DispatcherTimer when active; global callback enabled only when needed; negligible impact.
  • Security: No new privileges; reuses the existing global keyboard hook; no keystrokes stored or transmitted.
  • Unit tests: None in this PR.

Release Note
You can now toggle Flow Launcher by double‑tapping a modifier key (like pressing Ctrl twice) within a configurable time window.

Written for commit f18d60d. Summary will update on new commits.

Review in cubic

@github-actions github-actions Bot added this to the 2.2.0 milestone May 20, 2026
@RuoLv
Copy link
Copy Markdown
Author

RuoLv commented May 20, 2026

#4213

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Adds full double-tap hotkey support: detection engine, HotkeyModel changes, settings, HotKeyMapper integration, capture dialog/UI changes, localization, and lockfile updates.

Changes

Double-Tap Hotkey Implementation

Layer / File(s) Summary
DoubleTapDetector core engine
Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.cs
DoubleTapDetector parses hotkey strings, uses DispatcherTimer to measure intervals between key presses, processes global key events to detect double-taps within the configured window, suppresses auto-repeat false positives via release tracking, and invokes either double-tap or single-tap actions based on timing.
HotkeyModel double-tap support
Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs
HotkeyModel adds DoubleTap property, parses "Key+Key" hotkey strings (e.g., Ctrl+Ctrl) by mapping friendly modifier names to Key values, displays double-tap hotkeys as "Key ×2", validates double-tap only for modifier keys, and includes DoubleTap in hash computation.
Settings and persistence
Flow.Launcher.Infrastructure/UserSettings/Settings.cs
New settings DoubleTapHotkey (string, default empty) and DoubleTapHotkeyInterval (int, default 300ms) are registered as configurable hotkeys with action key "doubleTapHotkey".
HotKeyMapper system integration
Flow.Launcher/Helper/HotKeyMapper.cs
HotKeyMapper branches Initialize between single-tap NHotkey and double-tap registration, manages DoubleTapDetector lifecycle, registers global keyboard callbacks, validates formats, and logs detector state; errors surface as localized message boxes.
HotkeyControl UI routing
Flow.Launcher/HotkeyControl.xaml.cs
HotkeyType enum gains DoubleTapHotkey, hotkey getter/setter route this type through _settings.DoubleTapHotkey, dialog removal branches on double-tap flag, validation uses dedicated CheckDoubleTapAvailability, and Delete() handles both single and double-tap removal.
Hotkey capture dialog enhancements
Flow.Launcher/HotkeyControlDialog.xaml, Flow.Launcher/HotkeyControlDialog.xaml.cs
Dialog accepts isDoubleTapMode parameter and implements a modifier detection state machine with DispatcherTimer: in dedicated mode it immediately creates double-tap hotkeys; in normal mode it detects lone modifier presses, pending states, and double-taps of the same modifier family within the interval. Reset/Delete/Cancel/Save/Unload handlers reset detection state. Validation branches for DoubleTap hotkeys.
Localization and settings UI
Flow.Launcher/Languages/en.xaml, Flow.Launcher/Languages/zh-cn.xaml, Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml
English and Chinese strings for double-tap labels, tooltips, and interval description (default 300ms). New SettingsCard UI sections for DoubleTapHotkey control and DoubleTapHotkeyInterval slider in settings pane.
Lockfile & interface
Flow.Launcher/packages.lock.json, Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs
Adjusts Flow.Launcher.Plugin dependency range from [5.0.0, ) to [5.3.0, ) and adds DoubleTapHotkeyInterval to IHotkeySettings.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Jack251970
  • jjw24
  • taooceros
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main feature: adding double-tap hotkey support for toggling Flow Launcher, which is the primary change across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 97.26% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description comprehensively documents the double-tap hotkey feature implementation, including objectives, technical details, files changed, testing steps, and screenshots.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml (1)

99-112: ⚡ Quick win

Consider using a distinct icon glyph for the DoubleTapHotkey feature.

The glyph  on line 104 is the same as the one used for DialogJumpHotkey (line 88). Using distinct icons would improve visual differentiation between these two separate features and enhance user clarity.

🤖 Prompt for 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.

In `@Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml` around lines 99 -
112, The SettingsCard for the DoubleTapHotkey is using the same icon glyph
(Glyph="&`#xE8AB`;") as DialogJumpHotkey; change the DoubleTapHotkey header icon
to a distinct glyph to improve differentiation by updating the
SettingsCard.HeaderIcon Glyph value in SettingsPaneHotkey.xaml (the UI element
with Header="{DynamicResource doubleTapHotkey}"), e.g. choose an alternative
Segoe MDL2 Assets codepoint that better represents double-tap semantics and
replace the existing codepoint only in this SettingsCard so DialogJumpHotkey’s
icon remains unchanged.
🤖 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.Infrastructure/Hotkey/DoubleTapDetector.cs`:
- Around line 369-393: IsValidDoubleTapKey currently allows any parseable Key
(excluding specific modifier variants), which contradicts HotkeyModel.Validate
that only permits modifier keys when DoubleTap==true; update IsValidDoubleTapKey
so the default branch does not accept arbitrary Key values and instead only
returns true for the explicit modifier names ("Ctrl", "Alt", "Shift", "Win") or
their corresponding Key enum modifier values—i.e., remove/replace the Enum.Parse
logic so non-modifier keys return false, keeping the method aligned with
HotkeyModel.Validate and the PR description.

In `@Flow.Launcher/Helper/HotKeyMapper.cs`:
- Line 20: The single shared _doubleTapDetector causes the main Hotkey
double-tap registration to be overwritten when SetDoubleTapHotkey runs; modify
SetDoubleTapHotkey to first detect whether the main Hotkey is already a
double-tap (the same double-tap pattern as DoubleTapHotkey) and if so skip
creating/removing the separate detector (i.e., do not call RemoveDoubleTapHotkey
nor overwrite _doubleTapDetector). Use the existing Hotkey and DoubleTapHotkey
values to compare patterns and bail out early in SetDoubleTapHotkey to preserve
the main registration; alternatively, if you intend to support both
simultaneously, create distinct detector instances (e.g., additional field or
collection) instead of reusing _doubleTapDetector.

---

Nitpick comments:
In `@Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml`:
- Around line 99-112: The SettingsCard for the DoubleTapHotkey is using the same
icon glyph (Glyph="&`#xE8AB`;") as DialogJumpHotkey; change the DoubleTapHotkey
header icon to a distinct glyph to improve differentiation by updating the
SettingsCard.HeaderIcon Glyph value in SettingsPaneHotkey.xaml (the UI element
with Header="{DynamicResource doubleTapHotkey}"), e.g. choose an alternative
Segoe MDL2 Assets codepoint that better represents double-tap semantics and
replace the existing codepoint only in this SettingsCard so DialogJumpHotkey’s
icon remains unchanged.
🪄 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: bbb5a0d9-91e2-44e2-801c-dc5872b8d0f9

📥 Commits

Reviewing files that changed from the base of the PR and between 4a06af8 and fb515e0.

📒 Files selected for processing (10)
  • Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.cs
  • Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs
  • Flow.Launcher.Infrastructure/UserSettings/Settings.cs
  • Flow.Launcher/Helper/HotKeyMapper.cs
  • Flow.Launcher/HotkeyControl.xaml.cs
  • Flow.Launcher/HotkeyControlDialog.xaml
  • Flow.Launcher/HotkeyControlDialog.xaml.cs
  • Flow.Launcher/Languages/en.xaml
  • Flow.Launcher/Languages/zh-cn.xaml
  • Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml

Comment thread Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.cs
Comment thread Flow.Launcher/Helper/HotKeyMapper.cs Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 10 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Flow.Launcher/Helper/HotKeyMapper.cs Outdated
Comment thread Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.cs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.cs (1)

205-209: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Double-tap must return false to comply with the consume/continue contract, and OnGlobalKeyboardEvent must propagate detector results.

ProcessKeyEvent documents false as "consumed" and true as "continue to other handlers", but the double-tap path returns true. This violates the contract—when a double-tap is detected, the method should return false to signal the event was consumed. Additionally, OnGlobalKeyboardEvent (the hook callback) unconditionally returns true and ignores the detector's return value, so even if fixed, the suppression would never reach the hook layer. The hook callback should compute whether any detector consumed the event and return that result accordingly.

Also applies to: 281–290

🤖 Prompt for 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.

In `@Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.cs` around lines 205 -
209, ProcessKeyEvent currently returns true for the double-tap branch even
though its contract documents false = consumed; change the double-tap path in
DoubleTapDetector.ProcessKeyEvent to return false when a double-tap is detected.
Then update OnGlobalKeyboardEvent to use and propagate the detector result
instead of always returning true: call ProcessKeyEvent (and other detectors),
compute whether any detector returned false (meaning consumed) and return false
from the hook when consumed, otherwise return true; apply the same fix for the
other similar block around the 281–290 region.
Flow.Launcher/HotkeyControlDialog.xaml.cs (1)

69-73: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the configured double-tap interval for capture.

The dialog hard-codes a 300 ms window here, but the actual detectors are registered with _hotkeySettings.DoubleTapHotkeyInterval. Once the user changes that setting, capture and runtime detection disagree, so a tap cadence can be accepted in one place and rejected in the other.

💡 Suggested fix
-    /// <summary>
-    /// The double-tap detection interval in milliseconds.
-    /// </summary>
-    private const int DoubleTapDetectionIntervalMs = 300;
-
     public enum EResultType
     {
         Cancel,
@@
         _doubleTapDetectionTimer = new DispatcherTimer(DispatcherPriority.Input)
         {
-            Interval = TimeSpan.FromMilliseconds(DoubleTapDetectionIntervalMs)
+            Interval = TimeSpan.FromMilliseconds(_hotkeySettings.DoubleTapHotkeyInterval)
         };

Also applies to: 101-104

🤖 Prompt for 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.

In `@Flow.Launcher/HotkeyControlDialog.xaml.cs` around lines 69 - 73, The
hard-coded DoubleTapDetectionIntervalMs constant causes capture to use a fixed
300ms window that can disagree with runtime detection using
_hotkeySettings.DoubleTapHotkeyInterval; remove/stop using the constant and read
the configured interval from _hotkeySettings.DoubleTapHotkeyInterval wherever
DoubleTapDetectionIntervalMs is currently referenced (e.g., in the capture/tap
timing logic in HotkeyControlDialog.xaml.cs and the other occurrences around the
DoubleTap handling), ensuring you convert units consistently (ms vs seconds) and
use the same value for both capture and detector registration so the tap cadence
is evaluated identically at runtime.
🤖 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.Infrastructure/Hotkey/DoubleTapDetector.cs`:
- Around line 146-162: The parser in DoubleTapDetector (ParseTargetKey)
currently treats a single modifier like "Ctrl" as a valid _targetVkCode, causing
mismatch with IsValidDoubleTapHotkey/HotkeyModel; change ParseTargetKey so it
does not enable the detector for single-part inputs — remove or disable the
parts.Length == 1 branch and ensure _targetVkCode is set to (VIRTUAL_KEY)0 for
single-token inputs, keeping only the valid "Key + Key" parsing path so behavior
aligns with IsValidDoubleTapHotkey and HotkeyModel.

In `@Flow.Launcher/HotkeyControlDialog.xaml.cs`:
- Around line 119-123: Reset currently updates only the displayed chips via
SetKeysToDisplay(new HotkeyModel(DefaultHotkey)) and leaves CurrentHotkey (and
thus ResultValue on Save) unchanged; modify Reset (while keeping
ResetDetectionState()) to also set CurrentHotkey to the new default hotkey
(e.g., assign CurrentHotkey = new HotkeyModel(DefaultHotkey) or otherwise update
the backing ResultValue) so the saved value matches the displayed default when
the user clicks Save.
- Around line 499-515: CreateDoubleTapHotkey currently maps non-modifier keys to
the default/empty HotkeyModel which SetKeysToDisplay treats as "delete";
instead, for non-modifier keys return a HotkeyModel that preserves the attempted
key so the UI shows the invalid input (so it won't silently clear existing
bindings) and still fails HotkeyModel.Validate() on save. Update
CreateDoubleTapHotkey to, when key is not a modifier, construct and return a
HotkeyModel that sets CharKey to the original non-modifier key and
DoubleTap=true (or otherwise marks it non-empty) so SetKeysToDisplay displays
the attempted key and Validate() still rejects it.

---

Outside diff comments:
In `@Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.cs`:
- Around line 205-209: ProcessKeyEvent currently returns true for the double-tap
branch even though its contract documents false = consumed; change the
double-tap path in DoubleTapDetector.ProcessKeyEvent to return false when a
double-tap is detected. Then update OnGlobalKeyboardEvent to use and propagate
the detector result instead of always returning true: call ProcessKeyEvent (and
other detectors), compute whether any detector returned false (meaning consumed)
and return false from the hook when consumed, otherwise return true; apply the
same fix for the other similar block around the 281–290 region.

In `@Flow.Launcher/HotkeyControlDialog.xaml.cs`:
- Around line 69-73: The hard-coded DoubleTapDetectionIntervalMs constant causes
capture to use a fixed 300ms window that can disagree with runtime detection
using _hotkeySettings.DoubleTapHotkeyInterval; remove/stop using the constant
and read the configured interval from _hotkeySettings.DoubleTapHotkeyInterval
wherever DoubleTapDetectionIntervalMs is currently referenced (e.g., in the
capture/tap timing logic in HotkeyControlDialog.xaml.cs and the other
occurrences around the DoubleTap handling), ensuring you convert units
consistently (ms vs seconds) and use the same value for both capture and
detector registration so the tap cadence is evaluated identically at runtime.
🪄 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: 1663f9de-973c-4cba-ad64-cf8160ca483d

📥 Commits

Reviewing files that changed from the base of the PR and between fb515e0 and 0c2fbb2.

📒 Files selected for processing (6)
  • Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.cs
  • Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs
  • Flow.Launcher/Helper/HotKeyMapper.cs
  • Flow.Launcher/HotkeyControl.xaml.cs
  • Flow.Launcher/HotkeyControlDialog.xaml.cs
  • Flow.Launcher/packages.lock.json

Comment on lines +146 to +162
else if (parts.Length == 1)
{
// Single key format — only valid if it's a modifier key name
var keyName = parts[0];
_targetVkCode = keyName switch
{
"Ctrl" => VIRTUAL_KEY.VK_CONTROL,
"Alt" => VIRTUAL_KEY.VK_MENU,
"Shift" => VIRTUAL_KEY.VK_SHIFT,
"Win" => VIRTUAL_KEY.VK_LWIN,
_ => (VIRTUAL_KEY)0
};
}
else
{
_targetVkCode = (VIRTUAL_KEY)0;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep detector parsing aligned with the "Key + Key" contract.

ParseTargetKey("Ctrl") still enables the detector, but IsValidDoubleTapHotkey("Ctrl") is false and HotkeyModel rejects the same string. That means an invalid or legacy setting can behave differently depending on which layer reads it.

Suggested fix
-            else if (parts.Length == 1)
-            {
-                // Single key format — only valid if it's a modifier key name
-                var keyName = parts[0];
-                _targetVkCode = keyName switch
-                {
-                    "Ctrl" => VIRTUAL_KEY.VK_CONTROL,
-                    "Alt" => VIRTUAL_KEY.VK_MENU,
-                    "Shift" => VIRTUAL_KEY.VK_SHIFT,
-                    "Win" => VIRTUAL_KEY.VK_LWIN,
-                    _ => (VIRTUAL_KEY)0
-                };
-            }
             else
             {
                 _targetVkCode = (VIRTUAL_KEY)0;
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else if (parts.Length == 1)
{
// Single key format — only valid if it's a modifier key name
var keyName = parts[0];
_targetVkCode = keyName switch
{
"Ctrl" => VIRTUAL_KEY.VK_CONTROL,
"Alt" => VIRTUAL_KEY.VK_MENU,
"Shift" => VIRTUAL_KEY.VK_SHIFT,
"Win" => VIRTUAL_KEY.VK_LWIN,
_ => (VIRTUAL_KEY)0
};
}
else
{
_targetVkCode = (VIRTUAL_KEY)0;
}
else if (parts.Length == 2)
{
// ... existing code for two-part format ...
}
else
{
_targetVkCode = (VIRTUAL_KEY)0;
}
🤖 Prompt for 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.

In `@Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.cs` around lines 146 -
162, The parser in DoubleTapDetector (ParseTargetKey) currently treats a single
modifier like "Ctrl" as a valid _targetVkCode, causing mismatch with
IsValidDoubleTapHotkey/HotkeyModel; change ParseTargetKey so it does not enable
the detector for single-part inputs — remove or disable the parts.Length == 1
branch and ensure _targetVkCode is set to (VIRTUAL_KEY)0 for single-token
inputs, keeping only the valid "Key + Key" parsing path so behavior aligns with
IsValidDoubleTapHotkey and HotkeyModel.

Comment on lines 119 to 123
private void Reset(object sender, RoutedEventArgs routedEventArgs)
{
ResetDetectionState();
SetKeysToDisplay(new HotkeyModel(DefaultHotkey));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep CurrentHotkey in sync when Reset is clicked.

Reset() only updates the chips. If the user clicks Reset and then Save, ResultValue still comes from the old CurrentHotkey, so the previous binding is saved instead of DefaultHotkey.

💡 Suggested fix
     private void Reset(object sender, RoutedEventArgs routedEventArgs)
     {
         ResetDetectionState();
-        SetKeysToDisplay(new HotkeyModel(DefaultHotkey));
+        CurrentHotkey = new HotkeyModel(DefaultHotkey);
+        SetKeysToDisplay(CurrentHotkey);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private void Reset(object sender, RoutedEventArgs routedEventArgs)
{
ResetDetectionState();
SetKeysToDisplay(new HotkeyModel(DefaultHotkey));
}
private void Reset(object sender, RoutedEventArgs routedEventArgs)
{
ResetDetectionState();
CurrentHotkey = new HotkeyModel(DefaultHotkey);
SetKeysToDisplay(CurrentHotkey);
}
🤖 Prompt for 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.

In `@Flow.Launcher/HotkeyControlDialog.xaml.cs` around lines 119 - 123, Reset
currently updates only the displayed chips via SetKeysToDisplay(new
HotkeyModel(DefaultHotkey)) and leaves CurrentHotkey (and thus ResultValue on
Save) unchanged; modify Reset (while keeping ResetDetectionState()) to also set
CurrentHotkey to the new default hotkey (e.g., assign CurrentHotkey = new
HotkeyModel(DefaultHotkey) or otherwise update the backing ResultValue) so the
saved value matches the displayed default when the user clicks Save.

Comment on lines +499 to +515
private static HotkeyModel CreateDoubleTapHotkey(Key key)
{
// Only modifier keys are valid for double-tap hotkeys.
// For modifier keys, we use the left variant as the CharKey.
// Non-modifier keys return a HotkeyModel with DoubleTap=true but CharKey=None,
// which will fail HotkeyModel.Validate() and prevent registration.
var doubleTapKey = key switch
{
Key.LeftCtrl or Key.RightCtrl => Key.LeftCtrl,
Key.LeftAlt or Key.RightAlt => Key.LeftAlt,
Key.LeftShift or Key.RightShift => Key.LeftShift,
Key.LWin or Key.RWin => Key.LWin,
_ => Key.None // Non-modifier keys are not valid for double-tap
};

return new HotkeyModel(false, false, false, false, doubleTapKey, doubleTapKey != Key.None);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't collapse invalid double-tap input into "delete".

For non-modifier keys this returns the default HotkeyModel. SetKeysToDisplay() treats that as the empty-hotkey state, so pressing something like A in dedicated double-tap mode can end up clearing the existing binding on save instead of surfacing invalid input.

💡 Suggested fix
-        return new HotkeyModel(false, false, false, false, doubleTapKey, doubleTapKey != Key.None);
+        return new HotkeyModel(false, false, false, false, doubleTapKey, true);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static HotkeyModel CreateDoubleTapHotkey(Key key)
{
// Only modifier keys are valid for double-tap hotkeys.
// For modifier keys, we use the left variant as the CharKey.
// Non-modifier keys return a HotkeyModel with DoubleTap=true but CharKey=None,
// which will fail HotkeyModel.Validate() and prevent registration.
var doubleTapKey = key switch
{
Key.LeftCtrl or Key.RightCtrl => Key.LeftCtrl,
Key.LeftAlt or Key.RightAlt => Key.LeftAlt,
Key.LeftShift or Key.RightShift => Key.LeftShift,
Key.LWin or Key.RWin => Key.LWin,
_ => Key.None // Non-modifier keys are not valid for double-tap
};
return new HotkeyModel(false, false, false, false, doubleTapKey, doubleTapKey != Key.None);
}
private static HotkeyModel CreateDoubleTapHotkey(Key key)
{
// Only modifier keys are valid for double-tap hotkeys.
// For modifier keys, we use the left variant as the CharKey.
// Non-modifier keys return a HotkeyModel with DoubleTap=true but CharKey=None,
// which will fail HotkeyModel.Validate() and prevent registration.
var doubleTapKey = key switch
{
Key.LeftCtrl or Key.RightCtrl => Key.LeftCtrl,
Key.LeftAlt or Key.RightAlt => Key.LeftAlt,
Key.LeftShift or Key.RightShift => Key.LeftShift,
Key.LWin or Key.RWin => Key.LWin,
_ => Key.None // Non-modifier keys are not valid for double-tap
};
return new HotkeyModel(false, false, false, false, doubleTapKey, true);
}
🤖 Prompt for 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.

In `@Flow.Launcher/HotkeyControlDialog.xaml.cs` around lines 499 - 515,
CreateDoubleTapHotkey currently maps non-modifier keys to the default/empty
HotkeyModel which SetKeysToDisplay treats as "delete"; instead, for non-modifier
keys return a HotkeyModel that preserves the attempted key so the UI shows the
invalid input (so it won't silently clear existing bindings) and still fails
HotkeyModel.Validate() on save. Update CreateDoubleTapHotkey to, when key is not
a modifier, construct and return a HotkeyModel that sets CharKey to the original
non-modifier key and DoubleTap=true (or otherwise marks it non-empty) so
SetKeysToDisplay displays the attempted key and Validate() still rejects it.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/HotKeyMapper.cs`:
- Around line 467-486: The OnGlobalKeyboardEvent method currently lets both
detectors see the same key event causing double toggles; modify it so that as
soon as either _mainDoubleTapDetector or _separateDoubleTapDetector's
ProcessKeyEvent returns false (meaning the event was consumed), the method stops
further dispatch and returns false immediately instead of setting a flag and
continuing—update OnGlobalKeyboardEvent to short-circuit after a consumed
ProcessKeyEvent (references: _mainDoubleTapDetector, _separateDoubleTapDetector,
ProcessKeyEvent, OnDoubleTapToggleHotkey).
🪄 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: a3ea3a8d-fd21-4a36-b2c5-1a87423054c7

📥 Commits

Reviewing files that changed from the base of the PR and between 0c2fbb2 and bd80209.

📒 Files selected for processing (4)
  • Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.cs
  • Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs
  • Flow.Launcher/Helper/HotKeyMapper.cs
  • Flow.Launcher/HotkeyControlDialog.xaml.cs

Comment thread Flow.Launcher/Helper/HotKeyMapper.cs Outdated
@RuoLv RuoLv marked this pull request as draft May 21, 2026 08:46
@RuoLv RuoLv marked this pull request as ready for review May 21, 2026 08:47
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 12 files

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/Languages/zh-cn.xaml">

<violation number="1" location="Flow.Launcher/Languages/zh-cn.xaml:426">
P2: Manual edits added to non-English language file `zh-cn.xaml`, violating the repository's Crowdin-based localization workflow. Only `en.xaml` should contain new strings in PRs; translations for other locales must be submitted via Crowdin.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

<system:String x:Key="showBadgesGlobalOnlyToolTip">仅对全局查询结果显示徽章</system:String>
<system:String x:Key="dialogJumpHotkey">对话框跳转</system:String>
<system:String x:Key="dialogJumpHotkeyToolTip">输入快捷键以快速导航打开或保存作为对话框窗口到当前文件管理器路径。</system:String>
<system:String x:Key="doubleTapHotkey">双击热键</system:String>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Manual edits added to non-English language file zh-cn.xaml, violating the repository's Crowdin-based localization workflow. Only en.xaml should contain new strings in PRs; translations for other locales must be submitted via Crowdin.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Flow.Launcher/Languages/zh-cn.xaml, line 426:

<comment>Manual edits added to non-English language file `zh-cn.xaml`, violating the repository's Crowdin-based localization workflow. Only `en.xaml` should contain new strings in PRs; translations for other locales must be submitted via Crowdin.</comment>

<file context>
@@ -423,6 +423,10 @@
     <system:String x:Key="showBadgesGlobalOnlyToolTip">仅对全局查询结果显示徽章</system:String>
     <system:String x:Key="dialogJumpHotkey">对话框跳转</system:String>
     <system:String x:Key="dialogJumpHotkeyToolTip">输入快捷键以快速导航打开或保存作为对话框窗口到当前文件管理器路径。</system:String>
+    <system:String x:Key="doubleTapHotkey">双击热键</system:String>
+    <system:String x:Key="doubleTapHotkeyToolTip">快速连续按两次相同的键来切换 Flow Launcher。例如,在间隔时间内按两次 Ctrl 将打开/隐藏搜索窗口。</system:String>
+    <system:String x:Key="doubleTapHotkeyInterval">双击间隔</system:String>
</file context>

@DavidGBrett DavidGBrett linked an issue May 23, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"double-click Ctrl key" shortcut to launch flow

1 participant