feat: Add double-tap hotkey support for toggling Flow Launcher#4478
feat: Add double-tap hotkey support for toggling Flow Launcher#4478RuoLv wants to merge 5 commits into
Conversation
📝 WalkthroughWalkthroughAdds full double-tap hotkey support: detection engine, HotkeyModel changes, settings, HotKeyMapper integration, capture dialog/UI changes, localization, and lockfile updates. ChangesDouble-Tap Hotkey Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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: 2
🧹 Nitpick comments (1)
Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml (1)
99-112: ⚡ Quick winConsider 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
📒 Files selected for processing (10)
Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.csFlow.Launcher.Infrastructure/Hotkey/HotkeyModel.csFlow.Launcher.Infrastructure/UserSettings/Settings.csFlow.Launcher/Helper/HotKeyMapper.csFlow.Launcher/HotkeyControl.xaml.csFlow.Launcher/HotkeyControlDialog.xamlFlow.Launcher/HotkeyControlDialog.xaml.csFlow.Launcher/Languages/en.xamlFlow.Launcher/Languages/zh-cn.xamlFlow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml
There was a problem hiding this comment.
2 issues found across 10 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
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 winDouble-tap must return
falseto comply with the consume/continue contract, andOnGlobalKeyboardEventmust propagate detector results.
ProcessKeyEventdocumentsfalseas "consumed" andtrueas "continue to other handlers", but the double-tap path returnstrue. This violates the contract—when a double-tap is detected, the method should returnfalseto signal the event was consumed. Additionally,OnGlobalKeyboardEvent(the hook callback) unconditionally returnstrueand 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 winUse 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
📒 Files selected for processing (6)
Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.csFlow.Launcher.Infrastructure/Hotkey/HotkeyModel.csFlow.Launcher/Helper/HotKeyMapper.csFlow.Launcher/HotkeyControl.xaml.csFlow.Launcher/HotkeyControlDialog.xaml.csFlow.Launcher/packages.lock.json
| 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; | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| private void Reset(object sender, RoutedEventArgs routedEventArgs) | ||
| { | ||
| ResetDetectionState(); | ||
| SetKeysToDisplay(new HotkeyModel(DefaultHotkey)); | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| 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); | ||
| } |
There was a problem hiding this comment.
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.
| 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.
…l in double-tap detection
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/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
📒 Files selected for processing (4)
Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.csFlow.Launcher.Infrastructure/Hotkey/IHotkeySettings.csFlow.Launcher/Helper/HotKeyMapper.csFlow.Launcher/HotkeyControlDialog.xaml.cs
…ent double toggle
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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>
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
Technical Details
DoubleTapDetectorWH_KEYBOARD_LLhook. Tracks key-down/key-up to distinguish genuine double-taps from auto-repeat (key held down). Includes desync recovery for missed key-up events.HotkeyModelDoubleTapproperty and parsing for"Key + Key"format strings (e.g.,"Ctrl + Ctrl"). Validation restricts double-tap to modifier keys only.HotKeyMapperRegisterGlobalKeyboardCallback) instead of NHotkey, since NHotkey doesn't support double-tap semantics.HotkeyControlDialogPreviewKeyUptracking. In dedicated double-tap mode (_isDoubleTapMode), any key press immediately creates a double-tap binding.Files Changed
Flow.Launcher.Infrastructure/Hotkey/DoubleTapDetector.cs— New (424 lines)Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs— Double-tap parsing, display, validationFlow.Launcher.Infrastructure/UserSettings/Settings.cs—DoubleTapHotkey&DoubleTapHotkeyIntervalsettingsFlow.Launcher/Helper/HotKeyMapper.cs— Double-tap registration/removal/availability checkFlow.Launcher/HotkeyControl.xaml.cs—HotkeyType.DoubleTapHotkeyenum & wiringFlow.Launcher/HotkeyControlDialog.xaml—PreviewKeyUp&UnloadedeventsFlow.Launcher/HotkeyControlDialog.xaml.cs— Modifier detection state machineFlow.Launcher/Languages/en.xaml— English localizationFlow.Launcher/Languages/zh-cn.xaml— Chinese localizationFlow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml— Settings UIHow to Test
Screenshots
Notes
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
HotKeyMapperroutes both main and separate double‑tap hotkeys via one global keyboard callback and registers/unregisters it only when detectors exist;OnGlobalKeyboardEventnow 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 useDoubleTapHotkeyInterval; bumpedFlow.Launcher.Pluginto5.3.0.DoubleTapDetector(global key handling, timeout, auto‑repeat guard, left/right modifier family matching); support for setting the main toggle as a double‑tap; separateDoubleTapHotkeywithDoubleTapHotkeyInterval(100–500ms, default 300ms); Settings UI card and interval slider; localization (en, zh‑cn).NHotkeypath remain; double‑tap intentionally bypassesNHotkey).DispatcherTimerwhen active; global callback enabled only when needed; negligible impact.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