-
-
Notifications
You must be signed in to change notification settings - Fork 359
Quickswitch #1018
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Quickswitch #1018
Conversation
Some threading issues seem appearing. Not sure the detailed reason. |
Okay, my main questions would be
|
I think the major use case is to sync the path of an opened explorer to a open file dialog to select a file easily.
Sry I don't get the idea. |
Okay, I understand what this is used for now. I'd have to dig a lot deeper into what the IUIAutomation can do to be able to improve this. I think the rule of thumb is to avoid sending keyboard events, and instead always use an API if one exists. Keyboard events can be delayed and whatnot. |
Yeah that's what I would like to see. It is possible to use PInvoke directly without IUIAutomation though, so it will be cool if you are familiar with that as well. Another thing is the original listary seems implement this feature without changing the textbox and sending an enter signal, so I wonder whether you may have some clues about that. |
I tried searching for what I could, but that's apparently quite tricky to hook into. So I don't really have a better solution at the moment. |
okay thanks🤣 |
There might be a alternate design: So the file manager has the "quick access" sidebar. Flow could add its own entry there, and that entry always redirects to the currently open folder. An additional advantage might be that it's easier to discover this, compared to a keyboard shortcut. Screenshot for context: (Note: I have no idea how hard that would be to efficiently pull that off.) |
So you mean to add a entry that redirect to the most recent opened explorer path?🤔Interesting |
Yep, spot-on. |
If that's the case, we may be able to create a plugin for it. |
Do you have any docs for that? |
@taooceros I haven't looked into this all that much (just a few cursory google searches) Programmatic accessApparently there's a way of programmatically adding folders to the quick access area. Special Links folderhttps://blogs.msmvps.com/kenlin/2017/06/14/537/ Steps:
Symbolic links or HardlinkI bet there's some trickery that could be done with those Extra harddriveWe could add an in-memory harddrive, mount it and provide a single shortcut in there. |
Could this be done? I really love this feature. |
Apparently Windows 11 can add files to quick access. That might let us pin a program to quick access Such a program could then update the list of files in the quick access window. |
The dialog swapping works well. But I think we should improve the search feature that shows up at the bottom. Right now, it’s just a display-only feature. In Listary, when you type a path or filename at the bottom of the dialog, it directly inputs that into the dialog. (In other words, typing a specific path or file and hitting enter inputs it into the dialog.) If we’re not going to support that behavior just yet, I think the “show” feature should be turned off and either hidden or disabled in the UI, otherwise we shouldn't include it in the release. |
You can use right click to open file or directory in dialog. (I override the original context menu feature because I think we do not need to support context menu for quick switch results.) And Explorer plugin already supports quick switch. I am planning release this feature as beta in this version if you think it is good to go. |
There was a problem hiding this 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
♻️ Duplicate comments (7)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (7)
488-511
:⚠️ Potential issueRelease COM objects to prevent memory leaks.
The
EnumerateShellWindows
method creates COM objects (shellWindowsObj
andshellWindows
) but doesn't release them after use, which can lead to memory leaks if called repeatedly.Add proper COM object release:
private static unsafe void EnumerateShellWindows(Action<object> action) { // Create an instance of ShellWindows var clsidShellWindows = new Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39"); // ShellWindowsClass var iidIShellWindows = typeof(IShellWindows).GUID; // IShellWindows var result = PInvoke.CoCreateInstance( &clsidShellWindows, null, CLSCTX.CLSCTX_ALL, &iidIShellWindows, out var shellWindowsObj); if (result.Failed) return; var shellWindows = (IShellWindows)shellWindowsObj; + try + { // Enumerate the shell windows var count = shellWindows.Count; for (var i = 0; i < count; i++) { action(shellWindows.Item(i)); } + } + finally + { + Marshal.ReleaseComObject(shellWindows); + Marshal.ReleaseComObject(shellWindowsObj); + } }
153-159
:⚠️ Potential issueAdd exception handling to prevent hotkey failures.
The
OnToggleHotkey
method doesn't handle exceptions fromNavigateDialogPath
, which could cause the application to crash if something goes wrong during navigation.Add exception handling:
public static void OnToggleHotkey(object sender, HotkeyEventArgs args) { if (_isInitialized) { - NavigateDialogPath(); + try + { + NavigateDialogPath(); + } + catch (Exception ex) + { + Log.Exception(ClassName, "Error processing quickswitch hotkey", ex); + } } }
205-208
: 🛠️ Refactor suggestionAdd logging for exception cases to aid troubleshooting.
This catch block silently swallows exceptions without logging them, making it difficult to diagnose issues in production.
Add logging:
catch { + Log.Error(ClassName, "Failed to retrieve or process path from folder view"); return; }
402-405
: 🛠️ Refactor suggestionAdd logging for COM exceptions in catch block.
Ignoring COM exceptions without logging makes debugging difficult. Consider adding logging to understand what's happening when these errors occur.
Add logging:
catch (COMException ex) { - // Ignored + Log.Debug(ClassName, "COM exception while processing shell window: " + ex.Message); }
409-412
: 🛠️ Refactor suggestionAdd logging for caught exceptions.
The catch block swallows exceptions without logging them, which can make debugging difficult.
Add logging:
catch (System.Exception ex) { - // Ignored + Log.Debug(ClassName, "Error while enumerating shell windows: " + ex.Message); }
133-140
: 🛠️ Refactor suggestionHandle partial hook initialization failure properly.
If one hook fails but others succeed, the currently initialized hooks aren't disposed, leading to a potential resource leak.
Add proper cleanup:
if (_foregroundChangeHook.IsInvalid || _locationChangeHook.IsInvalid || /*_moveSizeHook.IsInvalid ||*/ _destroyChangeHook.IsInvalid) { Log.Error(ClassName, "Failed to initialize QuickSwitch"); + // Clean up any hooks that were successfully created + DisposeHooks(); return; } +private static void DisposeHooks() +{ + if (_foregroundChangeHook != null && !_foregroundChangeHook.IsInvalid) + { + _foregroundChangeHook.Dispose(); + _foregroundChangeHook = null; + } + if (_locationChangeHook != null && !_locationChangeHook.IsInvalid) + { + _locationChangeHook.Dispose(); + _locationChangeHook = null; + } + if (_destroyChangeHook != null && !_destroyChangeHook.IsInvalid) + { + _destroyChangeHook.Dispose(); + _destroyChangeHook = null; + } +}
65-151
: 🛠️ Refactor suggestionInitialize COM system explicitly before use.
The code interacts with COM interfaces without ensuring COM is initialized for the current thread, which may cause failures in certain scenarios.
Add COM initialization:
public static void Initialize() { if (_isInitialized) return; + // Initialize COM for the current thread + try + { + PInvoke.CoInitialize(null); + } + catch (Exception ex) + { + Log.Exception(ClassName, "Failed to initialize COM", ex); + return; + } // Rest of the initialization code...And add corresponding cleanup in the Dispose method:
public static void Dispose() { // Dispose handle // ... existing code ... // Release ComObjects if (_lastExplorerView != null) { Marshal.ReleaseComObject(_lastExplorerView); _lastExplorerView = null; } + // Uninitialize COM for this thread + try + { + PInvoke.CoUninitialize(); + } + catch (Exception ex) + { + Log.Exception(ClassName, "Error uninitializing COM", ex); + } + + _isInitialized = false; }
🧹 Nitpick comments (1)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (1)
219-270
: Consider using ThreadPool instead of creating new threads.Creating a new Thread for each navigation operation can lead to resource overhead. Using ThreadPool would be more efficient, especially if this operation happens frequently.
Replace Thread creation with ThreadPool:
-var t = new Thread(async () => +ThreadPool.QueueUserWorkItem(async _ => { // Jump after flow launcher window vanished (after JumpAction returned true) // and the dialog had been in the foreground. var timeOut = !SpinWait.SpinUntil(() => GetWindowClassName(PInvoke.GetForegroundWindow()) == DialogWindowClassName, 1000); if (timeOut) { return; }; // Rest of the method remains the same... }); -t.Start(); return;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs
(1 hunks)Flow.Launcher.Infrastructure/Win32Helper.cs
(6 hunks)Flow.Launcher/MainWindow.xaml.cs
(11 hunks)Flow.Launcher/ViewModel/MainViewModel.cs
(16 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- Flow.Launcher/MainWindow.xaml.cs
- Flow.Launcher.Infrastructure/Win32Helper.cs
- Flow.Launcher/ViewModel/MainViewModel.cs
🔇 Additional comments (4)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (4)
1-17
: LGTM! The imports and namespace organization look good.The file imports all necessary dependencies for Windows API interaction, COM interop, threading, and UI integration.
288-303
: LGTM! The helper method for retrieving window class names is well-implemented.The method correctly uses fixed blocks and native methods to retrieve window class names safely.
22-62
: LGTM! The class structure and static fields are well organized.The Quick Switch functionality is organized with clear action delegates and proper locking objects for thread safety.
513-544
: LGTM! The Dispose method correctly releases resources.The method properly disposes of event hooks and COM objects. Just make sure to add COM uninitialization as suggested in a previous comment.
// TODO: Use a better way to detect dragging | ||
// Here we do not start & stop the timer beacause the start time is not accurate (more than 1s delay) | ||
// So we start & stop the timer when we find a file dialog window | ||
/*private static void MoveSizeCallBack( | ||
HWINEVENTHOOK hWinEventHook, | ||
uint eventType, | ||
HWND hwnd, | ||
int idObject, | ||
int idChild, | ||
uint dwEventThread, | ||
uint dwmsEventTime | ||
) | ||
{ | ||
// If the dialog window is moved or resized, update the quick switch window position | ||
if (_dialogWindowHandle != HWND.Null && _dialogWindowHandle == hwnd && _dragMoveTimer != null) | ||
{ | ||
switch (eventType) | ||
{ | ||
case PInvoke.EVENT_SYSTEM_MOVESIZESTART: | ||
_dragMoveTimer.Start(); // Start dragging position | ||
break; | ||
case PInvoke.EVENT_SYSTEM_MOVESIZEEND: | ||
_dragMoveTimer.Stop(); // Stop dragging | ||
break; | ||
} | ||
} | ||
}*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Consider implementing a more reliable window dragging detection system.
The current system for detecting window dragging is commented out, with a TODO noting it needs improvement. The current implementation based on timers may not be reliable.
Research more reliable methods for detecting window dragging in Windows, such as using the Windows UI Automation framework or Windows hooks that specifically track window movement.
Consider:
- Using UI Automation events for tracking window movements
- Using low-level mouse hooks to detect drag operations
- Using a combination of window position monitoring and input state tracking
🏁 Script executed:
#!/bin/bash
# Search for reliable window drag detection implementations
rg -A 5 -B 5 "window.+drag" --glob="*.cs"
rg -A 5 -B 5 "EVENT_SYSTEM_MOVESIZESTART" --glob="*.cs"
Length of output: 2316
Implement a robust window move/size detection mechanism
The commented‐out WinEventHook callback in Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (around lines 433–459) leaves QuickSwitch unaware of dialog drags. Rather than a delayed timer, consider one of the following to retarget the window immediately:
- Re‑enable and refine the existing
SetWinEventHook
approach, handlingEVENT_SYSTEM_MOVESIZESTART
/END
directly inMoveSizeCallBack
. - Use UI Automation’s WindowPattern or StructureChanged events to detect when the dialog’s bounding rectangle changes.
- Install a
WH_CALLWNDPROC
hook (viaSetWindowsHookEx
) to interceptWM_MOVING
/WM_SIZING
messages from the dialog window. - As a fallback, employ a low‑level mouse hook (
WH_MOUSE_LL
) to track drag start/end over the window’s non‐client area.
Addressing this will eliminate the 1 sec timer delay and ensure QuickSwitch follows the dialog smoothly.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this 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
♻️ Duplicate comments (3)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (3)
222-273
: PreferTask.Run
/ thread‑pool instead of spawning raw threads each timeEvery jump creates a brand‑new
Thread
, which is expensive (1 MB stack, extra scheduling overhead) and can accumulate if users trigger the hot‑key repeatedly. Using the thread pool keeps resource usage bounded and simplifies the async logic.-var t = new Thread(async () => +_ = Task.Run(async () => { ... }); -t.Start();(This suggestion was raised earlier and remains outstanding.)
65-92
: 🛠️ Refactor suggestion
⚠️ Potential issueAdd explicit COM initialization to avoid sporadic failures on some systems
Initialize()
uses COM objects (IWebBrowser2
,IShellWindows
) before ensuring COM is initialized for the calling thread. On machines where the STA model is not already in place this can throwRPC_E_CHANGED_MODE
or silently fail to marshal objects, breaking quick‑switch detection at startup.public static void Initialize() { if (_isInitialized) return; + // Initialise COM for this thread – required before touching shell COM objects + var hr = PInvoke.CoInitialize(IntPtr.Zero); + if (hr.Failed) + { + Log.Error(ClassName, $"CoInitialize failed: {hr}"); + return; + } + // Check all foreground windows and check if there are explorer windowsand remember to balance it in
Dispose()
(see comment further down).
491-514
:⚠️ Potential issueRelease COM objects in
EnumerateShellWindows
to prevent leaks
CoCreateInstance
gives you ownership of bothshellWindowsObj
and the typed castshellWindows
, and every element returned byshellWindows.Item(i)
. None of them are released, so every call leaks three COM references.var shellWindows = (IShellWindows)shellWindowsObj; -// Enumerate the shell windows -var count = shellWindows.Count; -for (var i = 0; i < count; i++) -{ - action(shellWindows.Item(i)); -} +try +{ + var count = shellWindows.Count; + for (var i = 0; i < count; i++) + { + var wnd = shellWindows.Item(i); + try + { + action(wnd); + } + finally + { + Marshal.ReleaseComObject(wnd); + } + } +} +finally +{ + Marshal.ReleaseComObject(shellWindows); + Marshal.ReleaseComObject(shellWindowsObj); +}Without this, opening and closing many dialogs during one session will steadily increase private bytes.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs
(1 hunks)Flow.Launcher.Infrastructure/UserSettings/Settings.cs
(1 hunks)Flow.Launcher.Infrastructure/Win32Helper.cs
(6 hunks)Flow.Launcher/App.xaml.cs
(3 hunks)Flow.Launcher/Languages/en.xaml
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- Flow.Launcher/Languages/en.xaml
🚧 Files skipped from review as they are similar to previous changes (3)
- Flow.Launcher/App.xaml.cs
- Flow.Launcher.Infrastructure/UserSettings/Settings.cs
- Flow.Launcher.Infrastructure/Win32Helper.cs
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this 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
♻️ Duplicate comments (11)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (11)
437-463
: Implement a robust window move/size detection mechanism.The commented-out
MoveSizeCallBack
indicates an unimplemented feature for detecting window dragging. Without proper implementation, the current timer-based approach may not reliably update the quick switch window position during dialog dragging.Consider implementing one of these alternatives:
- Re-enable and refine the existing
SetWinEventHook
approach to handleEVENT_SYSTEM_MOVESIZESTART
/END
directly.- Use UI Automation to detect dialog window position changes.
- Install a
WH_CALLWNDPROC
hook to interceptWM_MOVING
/WM_SIZING
messages.This would eliminate the timer delay and ensure the quick switch window follows the dialog smoothly.
492-515
: 🛠️ Refactor suggestionRelease COM objects to prevent memory leaks.
The
EnumerateShellWindows
method creates COM objects but doesn't release them, which can lead to memory leaks when called repeatedly during foreground window changes.Use a
try/finally
block to ensure proper cleanup:private static unsafe void EnumerateShellWindows(Action<object> action) { // Create an instance of ShellWindows var clsidShellWindows = new Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39"); // ShellWindowsClass var iidIShellWindows = typeof(IShellWindows).GUID; // IShellWindows var result = PInvoke.CoCreateInstance( &clsidShellWindows, null, CLSCTX.CLSCTX_ALL, &iidIShellWindows, out var shellWindowsObj); if (result.Failed) return; var shellWindows = (IShellWindows)shellWindowsObj; + try + { // Enumerate the shell windows var count = shellWindows.Count; for (var i = 0; i < count; i++) { action(shellWindows.Item(i)); } + } + finally + { + Marshal.ReleaseComObject(shellWindows); + Marshal.ReleaseComObject(shellWindowsObj); + } }
153-159
: 🛠️ Refactor suggestionAdd exception handling to prevent hotkey failures.
The
OnToggleHotkey
method doesn't handle exceptions, which could cause the application to become unresponsive if an error occurs while processing the hotkey.Add a try-catch block to prevent exceptions from propagating:
public static void OnToggleHotkey(object sender, HotkeyEventArgs args) { if (_isInitialized) { + try + { NavigateDialogPath(Win32Helper.GetForegroundWindowHWND()); + } + catch (Exception ex) + { + Log.Exception(ClassName, "Error processing quickswitch hotkey", ex); + } } }
406-409
: 🛠️ Refactor suggestionAdd logging for COM exceptions to aid troubleshooting.
The code catches COMException but doesn't log it, making it difficult to diagnose issues in production.
catch (COMException ex) { - // Ignored + Log.Debug(ClassName, $"COM exception when processing shell window: {ex.Message}"); }
413-416
: 🛠️ Refactor suggestionAdd logging for caught exceptions.
The catch block swallows exceptions without logging them, which can make debugging difficult.
catch (System.Exception ex) { - // Ignored + Log.Debug(ClassName, $"Error while enumerating shell windows: {ex.Message}"); }
177-180
: 🛠️ Refactor suggestionAdd detailed logging for COM exceptions.
The COM exception is caught but not logged, making it difficult to diagnose issues with explorer view document access.
catch (COMException ex) { + Log.Debug(ClassName, $"COM exception when accessing explorer view document: {ex.Message}"); return; }
65-151
: 🛠️ Refactor suggestionInitialize COM for the current thread before accessing COM interfaces.
The code accesses COM interfaces without ensuring COM is initialized, which may cause failures in certain environments.
public static void Initialize() { if (_isInitialized) return; + // Initialize COM for the current thread + try + { + PInvoke.CoInitialize(null); + } + catch (Exception ex) + { + Log.Exception(ClassName, "Failed to initialize COM", ex); + return; + } // Check all foreground windows and check if there are explorer windows lock (_lastExplorerViewLock) { // Rest of the implementation... } // Rest of initialization... if (_foregroundChangeHook.IsInvalid || _locationChangeHook.IsInvalid || /*_moveSizeHook.IsInvalid ||*/ _destroyChangeHook.IsInvalid) { Log.Error(ClassName, "Failed to initialize QuickSwitch"); + // Uninitialize COM before returning + PInvoke.CoUninitialize(); return; } // Rest of initialization... _isInitialized = true; return; }
517-557
: 🛠️ Refactor suggestionAdd CoUninitialize call in Dispose method.
If CoInitialize is called during initialization, CoUninitialize should be called during cleanup to maintain COM balance.
public static void Dispose() { // Reset initialize flag _isInitialized = false; // Dispose handle if (_foregroundChangeHook != null) { _foregroundChangeHook.Dispose(); _foregroundChangeHook = null; } // Other hook disposals... // Release ComObjects if (_lastExplorerView != null) { Marshal.ReleaseComObject(_lastExplorerView); _lastExplorerView = null; } // Stop drag move timer if (_dragMoveTimer != null) { _dragMoveTimer.Stop(); _dragMoveTimer = null; } + // Uninitialize COM for this thread + try + { + PInvoke.CoUninitialize(); + } + catch (Exception ex) + { + Log.Debug(ClassName, $"Error uninitializing COM: {ex.Message}"); + } }
133-140
: 🛠️ Refactor suggestionHandle partial hook failure to prevent resource leak.
If any of the event hooks is invalid, the code immediately returns without disposing already-created hooks, potentially leading to resource leaks.
if (_foregroundChangeHook.IsInvalid || _locationChangeHook.IsInvalid || /*_moveSizeHook.IsInvalid ||*/ _destroyChangeHook.IsInvalid) { Log.Error(ClassName, "Failed to initialize QuickSwitch"); + // Clean up any successfully created hooks + DisposeHooks(); return; } +private static void DisposeHooks() +{ + if (_foregroundChangeHook != null && !_foregroundChangeHook.IsInvalid) + { + _foregroundChangeHook.Dispose(); + _foregroundChangeHook = null; + } + if (_locationChangeHook != null && !_locationChangeHook.IsInvalid) + { + _locationChangeHook.Dispose(); + _locationChangeHook = null; + } + if (_destroyChangeHook != null && !_destroyChangeHook.IsInvalid) + { + _destroyChangeHook.Dispose(); + _destroyChangeHook = null; + } +}
19-62
:⚠️ Potential issueHandle potentially stale COM references in event callbacks.
The static
_lastExplorerView
COM reference might become invalid across different callbacks, especially when multiple Explorer windows are opened/closed in quick succession.Consider implementing a solution that verifies COM references before use:
private static IWebBrowser2 _lastExplorerView = null; + // Helper method to safely check if COM reference is still valid + private static bool IsComObjectAlive(object comObject) + { + if (comObject == null) return false; + try + { + // GetHashCode is a simple call that should succeed if object is alive + comObject.GetHashCode(); + return true; + } + catch (COMException) + { + return false; + } + } // Then use this check before accessing COM objects: private static void NavigateDialogPath(HWND dialog, Action action = null) { object document = null; try { lock (_lastExplorerViewLock) { + if (!IsComObjectAlive(_lastExplorerView)) + { + Log.Debug(ClassName, "COM reference to explorer view is no longer valid"); + _lastExplorerView = null; + return; + } if (_lastExplorerView != null) { // Rest of implementation... } } } }
221-275
: 🛠️ Refactor suggestionUse ThreadPool instead of creating new threads for JumpToPath operations.
Creating a new thread for each operation is expensive and could lead to thread proliferation if many path jumps are performed in quick succession.
- var t = new Thread(async () => + ThreadPool.QueueUserWorkItem(async _ => { // Jump after flow launcher window vanished (after JumpAction returned true) // and the dialog had been in the foreground. var timeOut = !SpinWait.SpinUntil(() => Win32Helper.GetForegroundWindow() == dialog, 1000); if (timeOut) { + Log.Debug(ClassName, "Timed out waiting for dialog window to become foreground"); return; }; // Assume that the dialog is in the foreground now await _navigationLock.WaitAsync(); try { // Rest of the implementation... } finally { _navigationLock.Release(); } // Invoke action if provided action?.Invoke(); -}); -t.Start(); +}); return;
🧹 Nitpick comments (2)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (2)
319-370
: Refactor dialog window handling for improved readability and maintainability.The current structure with nested if-else statements and duplicate code blocks for showing the quick switch window makes the code harder to maintain.
Consider extracting common functionality to a separate method:
// File dialog window if (GetWindowClassName(hwnd) == DialogWindowClassName) { Log.Debug(ClassName, $"Hwnd: {hwnd}"); lock (_dialogWindowHandleLock) { _dialogWindowHandle = hwnd; } + bool showWindowNow = true; + Action showWindowAction = () => { + if (_settings.ShowQuickSwitchWindow) + { + ShowQuickSwitchWindow?.Invoke(_dialogWindowHandle.Value); + _dragMoveTimer?.Start(); + } + }; // Navigate to path if (_settings.AutoQuickSwitch) { // Check if we have already switched for this dialog bool alreadySwitched; lock (_autoSwitchedDialogsLock) { alreadySwitched = _autoSwitchedDialogs.Contains(hwnd); } // Just show quick switch window if (alreadySwitched) { - if (_settings.ShowQuickSwitchWindow) - { - ShowQuickSwitchWindow?.Invoke(_dialogWindowHandle.Value); - _dragMoveTimer?.Start(); - } + showWindowAction(); } // Show quick switch window after navigating the path else { + showWindowNow = false; - NavigateDialogPath(hwnd, () => - { - if (_settings.ShowQuickSwitchWindow) - { - ShowQuickSwitchWindow?.Invoke(_dialogWindowHandle.Value); - _dragMoveTimer?.Start(); - } - }); + NavigateDialogPath(hwnd, showWindowAction); } } - else + + if (showWindowNow && !_settings.AutoQuickSwitch) { - // Show quick switch window - if (_settings.ShowQuickSwitchWindow) - { - ShowQuickSwitchWindow?.Invoke(_dialogWindowHandle.Value); - _dragMoveTimer?.Start(); - } + showWindowAction(); } }
1-16
: Consider adding a mechanism to handle COM initialization on a per-thread basis.The current implementation assumes a single thread context, but the class uses multiple threads (e.g., in
JumpToPath
). Each thread accessing COM should have its own initialization.Consider implementing a COM initialization helper:
private static void EnsureComInitialized() { try { // Initialize COM for the current thread (if not already initialized) PInvoke.CoInitialize(null); } catch (Exception ex) { // Only log at debug level as this might be called on already initialized threads Log.Debug(ClassName, $"Error initializing COM: {ex.Message}"); } } private static void CleanupCom() { try { PInvoke.CoUninitialize(); } catch (Exception ex) { Log.Debug(ClassName, $"Error uninitializing COM: {ex.Message}"); } }Then call
EnsureComInitialized()
at the beginning of methods that need COM andCleanupCom()
at the end, particularly in any thread-based operations.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs
(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: gitStream.cm
- GitHub Check: gitStream.cm
- GitHub Check: gitStream.cm
- GitHub Check: gitStream.cm
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this 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.Infrastructure/NativeMethods.txt (1)
58-79
: Organize WinEvent, COM and message declarations
The new native methods (SetWinEventHook
,SendMessage
, etc.), COM interfaces (IShellFolderViewDual2
,IShellWindows
,IWebBrowser2
,CoCreateInstance
,CLSCTX
), and message/event constants are all appended as a single block. Consider grouping them into labeled sections (e.g., “WinEvent Hooks”, “COM Interfaces & CLSCTX Flags”, “Windows Messages”) and sorting each section alphabetically to improve maintainability and discoverability.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Flow.Launcher.Infrastructure/NativeMethods.txt
(1 hunks)Flow.Launcher.Infrastructure/Win32Helper.cs
(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Flow.Launcher.Infrastructure/Win32Helper.cs
🔇 Additional comments (2)
Flow.Launcher.Infrastructure/NativeMethods.txt (2)
56-56
: VerifyLOCALE_TRANSIENT_KEYBOARD4
constant accuracy
Ensure thatLOCALE_TRANSIENT_KEYBOARD4
matches the corresponding value in the Windows SDK (e.g., WinNls.h) and is supported on all targeted Windows versions.
69-72
: Validate event coverage for QuickSwitch logic
You’ve addedEVENT_OBJECT_DESTROY
,EVENT_OBJECT_LOCATIONCHANGE
,EVENT_SYSTEM_MOVESIZESTART
, andEVENT_SYSTEM_MOVESIZEEND
. Verify that these are the exact events yourQuickSwitch
implementation requires. If you need to track window creation or other lifecycle events, consider adding those here as well.
SendMessage | ||
EVENT_SYSTEM_FOREGROUND | ||
WINEVENT_OUTOFCONTEXT | ||
WM_KEYDOWN | ||
WM_SETTEXT | ||
IShellFolderViewDual2 | ||
CoCreateInstance | ||
CLSCTX |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add missing UnhookWinEvent
P/Invoke
You’ve imported SetWinEventHook
for event listening but did not declare UnhookWinEvent
, which is required to unhook WinEvent callbacks properly. The UnhookWindowsHookEx
API does not apply to WinEvent hooks. Please add the correct declaration for UnhookWinEvent
.
GetFocus | ||
SetFocus | ||
MapVirtualKey | ||
WM_KEYUP | ||
WM_KEYDOWN | ||
GetCurrentThreadId | ||
AttachThreadInput |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove duplicate WM_KEYDOWN
entry
The WM_KEYDOWN
constant is declared twice (once at line 62 and again at line 77). This duplication will cause conflicts in the generated interop code. Please remove the redundant declaration on line 77.
This comment has been minimized.
This comment has been minimized.
@check-spelling-bot Report🔴 Please reviewSee the 📂 files view, the 📜action log, or 📝 job summary for details.
See ❌ Event descriptions for more information. Forbidden patterns 🙅 (1)In order to address this, you could change the content to not match the forbidden patterns (comments before forbidden patterns may help explain why they're forbidden), add patterns for acceptable instances, or adjust the forbidden patterns themselves. These forbidden patterns matched content: s.b. workaround(s)
If the flagged items are 🤯 false positivesIf items relate to a ...
|
Important
All test codes
LogDebug
inQuickSwitch
should be removed.Nothing more than quickswitch. We may integrate flow's path system to this feature instead of relying explorer.
Setup Quick Switch
Quick switch automaticallyHowever, for the Save As dialog, the path does not apply immediately when the dialog is opened. It only works after switching focus to File Explorer and then returning.
Use Quick Switch
Open explorer -> Open file dialog -> Use hotkey to navigate to that path.
Open file dialog -> Query window (quick switch window) fixed under file dialog -> Right click results to navigate to the selected path
Quick Switch API
Implement new api interfaces to let plugin be queried on quick switch window.
Additionally,
Explorer
plugin already supports quick switch.TODO