-
-
Notifications
You must be signed in to change notification settings - Fork 391
Quick Switch #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?
Quick Switch #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. |
Really hope we can get the quick switch function :( the Ctrl+G in Listary is so useful |
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 (5)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (5)
598-610
: Fix cross-thread timer access.
MoveSizeCallBack
is executed on the WinEvent hook thread, not the WPF UI thread where_dragMoveTimer
was created. CallingStart()
/Stop()
directly can raise anInvalidOperationException
.case PInvoke.EVENT_SYSTEM_MOVESIZESTART: - _dragMoveTimer.Start(); + _dragMoveTimer.Dispatcher.BeginInvoke(() => _dragMoveTimer.Start()); break; case PInvoke.EVENT_SYSTEM_MOVESIZEEND: - _dragMoveTimer.Stop(); + _dragMoveTimer.Dispatcher.BeginInvoke(() => _dragMoveTimer.Stop()); break;
416-419
: Add exception handling to prevent hotkey failures.The hotkey handler doesn't catch exceptions, which could lead to hotkey failures if the navigation logic throws exceptions, potentially disrupting the application's functionality.
public static void OnToggleHotkey(object sender, HotkeyEventArgs args) { - _ = Task.Run(() => NavigateDialogPathAsync(PInvoke.GetForegroundWindow())); + try + { + _ = Task.Run(async () => + { + try + { + await NavigateDialogPathAsync(PInvoke.GetForegroundWindow()); + } + catch (Exception ex) + { + Log.Exception(ClassName, "Error in NavigateDialogPathAsync from hotkey", ex); + } + }); + } + catch (Exception ex) + { + Log.Exception(ClassName, "Error processing quickswitch hotkey", ex); + } }
791-794
: Add null check for Path.GetDirectoryName result.
Path.GetDirectoryName()
can return null for root paths or invalid paths, which would cause issues in theDirJump
method.case QuickSwitchFileResultBehaviours.Directory: Log.Debug(ClassName, $"File Jump Directory (Auto: {auto}): {path}"); - result = DirJump(Path.GetDirectoryName(path), dialog, auto); + var dirPath = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(dirPath)) + { + Log.Error(ClassName, $"Could not get directory for file: {path}"); + return false; + } + result = DirJump(dirPath, dialog, auto); break;
425-560
: Add exception handling to async void method.The
ForegroundChangeCallback
is marked withSuppressMessage
for async void, but lacks comprehensive exception handling. Any unhandled exceptions could terminate the application.[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")] private static async void ForegroundChangeCallback( // parameters... ) { + try + { await _foregroundChangeLock.WaitAsync(); try { // existing implementation... } finally { _foregroundChangeLock.Release(); } + } + catch (Exception ex) + { + Log.Exception(ClassName, "Unhandled exception in ForegroundChangeCallback", ex); + } }
134-203
: Initialize COM before using shell APIs.The
SetupQuickSwitch
method uses Windows shell APIs throughRefreshLastExplorer()
but never initializes COM. This could cause failures on some systems where COM hasn't been initialized.if (enabled) { + // Initialize COM for this thread + var hr = PInvoke.CoInitializeEx(null, COINIT.COINIT_APARTMENTTHREADED); + if (hr.Failed) + { + Log.Error(ClassName, $"Failed to initialize COM: {hr}"); + return; + } + // Check if there are explorer windows and get the topmost oneRemember to balance this with
CoUninitialize()
in theDispose()
method.
🧹 Nitpick comments (2)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (2)
87-90
: Optimize data structure for better performance.Using
List<HWND>
for_autoSwitchedDialogs
results in O(n) performance forContains
,Add
, andRemove
operations. Since these operations occur in foreground-change callbacks, consider usingHashSet<HWND>
for O(1) performance and automatic duplicate prevention.-private static readonly List<HWND> _autoSwitchedDialogs = new(); +private static readonly HashSet<HWND> _autoSwitchedDialogs = new();
628-628
: Fix typo in log message.There's a spelling error in the log message.
-Log.Debug(ClassName, $"Destory dialog: {hwnd}"); +Log.Debug(ClassName, $"Destroy dialog: {hwnd}");
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs
(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: Yusyuriv
PR: Flow-Launcher/Flow.Launcher#3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:40:11.014Z
Learning: In Flow Launcher, when using Windows Forms dialogs (e.g., in `JsonRPCPluginSettings.cs`), path validation is enabled by default in `OpenFileDialog` and `FolderBrowserDialog`, preventing users from selecting invalid paths, but it's possible to opt out of this validation on individual dialogs.
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (7)
Learnt from: Yusyuriv
PR: Flow-Launcher/Flow.Launcher#3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:40:11.014Z
Learning: In Flow Launcher, when using Windows Forms dialogs (e.g., in `JsonRPCPluginSettings.cs`), path validation is enabled by default in `OpenFileDialog` and `FolderBrowserDialog`, preventing users from selecting invalid paths, but it's possible to opt out of this validation on individual dialogs.
Learnt from: Jack251970
PR: Flow-Launcher/Flow.Launcher#3791
File: Flow.Launcher.Core/Plugin/PluginManager.cs:293-295
Timestamp: 2025-07-01T05:46:13.251Z
Learning: In Flow.Launcher.Core/Plugin/PluginManager.cs, when checking if a plugin is modified within the PluginManager class itself, prefer using the internal static PluginModified(string id) method directly rather than going through API.PluginModified() for better performance and architectural design.
Learnt from: Jack251970
PR: Flow-Launcher/Flow.Launcher#3672
File: Flow.Launcher/MainWindow.xaml.cs:244-247
Timestamp: 2025-06-08T14:12:21.348Z
Learning: In Flow.Launcher, App.NotifyIcon is created before MainWindow creation, so null checks for App.NotifyIcon are not necessary when accessing it from MainWindow code.
Learnt from: Yusyuriv
PR: Flow-Launcher/Flow.Launcher#3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:34:24.926Z
Learning: In Windows Forms dialogs, Windows handles invalid paths and prevents the user from clicking "Ok" if the path is incorrect, so additional path validation is unnecessary.
Learnt from: Jack251970
PR: Flow-Launcher/Flow.Launcher#3572
File: Flow.Launcher/App.xaml.cs:214-216
Timestamp: 2025-07-06T12:21:37.927Z
Learning: In Flow Launcher, the UpdatePluginManifestAsync method in PluginsManifest.cs already has comprehensive internal try-catch handling that logs exceptions and returns false on failure rather than throwing, making external try-catch wrappers unnecessary.
Learnt from: Jack251970
PR: Flow-Launcher/Flow.Launcher#3672
File: Flow.Launcher/MainWindow.xaml.cs:318-318
Timestamp: 2025-06-08T14:12:12.842Z
Learning: In Flow.Launcher, the App.NotifyIcon static property is initialized in the App class before MainWindow creation, so null checks are not needed when accessing App.NotifyIcon in MainWindow lifecycle methods.
Learnt from: onesounds
PR: Flow-Launcher/Flow.Launcher#3394
File: Flow.Launcher/Themes/Darker Glass.xaml:134-141
Timestamp: 2025-03-28T21:12:13.386Z
Learning: In Flow.Launcher, hotkey styling is implemented with a two-component structure: a Border element with style `ItemHotkeyBGStyle` that provides background and border styling, containing a TextBlock with style `ItemHotkeyStyle` that handles the text styling.
🪛 GitHub Actions: Check Spelling
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs
[warning] 272-42: Spell check warning: 'Wnd' is not a recognized word.
[warning] 279-86: Spell check warning: 'Wnd' is not a recognized word.
[warning] 12-14: Spell check warning: 'NHotkey' is not a recognized word.
[warning] 628-51: Spell check warning: 'Destory' is not a recognized word.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: gitStream.cm
- GitHub Check: build
🔇 Additional comments (1)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (1)
713-764
: Fix logic error in dialog window search.The method has flawed logic - it may return the wrong dialog window because it doesn't properly validate the HWND match before returning. Additionally, the cache isn't updated when a dialog window is found.
// Finally search for the dialog window again foreach (var dialog in _quickSwitchDialogs.Keys) { if (API.PluginModified(dialog.Metadata.ID) || // Plugin is modified dialog.Metadata.Disabled) continue; // Plugin is disabled IQuickSwitchDialogWindow dialogWindow; var existingDialogWindow = _quickSwitchDialogs[dialog]; if (existingDialogWindow != null && existingDialogWindow.Handle == hwnd) { // If the dialog window is already in the list, no need to check again dialogWindow = existingDialogWindow; } else { dialogWindow = dialog.Plugin.CheckDialogWindow(hwnd); } // Update dialog window if found if (dialogWindow != null) { _quickSwitchDialogs[dialog] = dialogWindow; - return dialogWindow; + // Only return if this dialog window matches the target HWND + if (dialogWindow.Handle == hwnd) + { + return dialogWindow; + } } } return null;⛔ Skipped due to learnings
Learnt from: Jack251970 PR: Flow-Launcher/Flow.Launcher#3791 File: Flow.Launcher.Core/Plugin/PluginManager.cs:293-295 Timestamp: 2025-07-01T05:46:13.251Z Learning: In Flow.Launcher.Core/Plugin/PluginManager.cs, when checking if a plugin is modified within the PluginManager class itself, prefer using the internal static PluginModified(string id) method directly rather than going through API.PluginModified() for better performance and architectural design.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Seems like not working well with multi tab explorer in windows 11. It goes for the first tab rather than the active tab. |
I noticed this problem before but I cannot find the correct API to get the active tab path. Feel free to change the codes in |
This comment has been minimized.
This comment has been minimized.
This might help. |
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: 0
♻️ Duplicate comments (10)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (10)
134-203
: Initialize COM before using Windows shell APIs.The code uses COM interfaces through shell APIs but never initializes COM. This could cause failures on some systems.
Add COM initialization at the start of SetupQuickSwitch when enabling:
if (enabled) { + // Initialize COM for this thread + var hr = PInvoke.CoInitializeEx(null, COINIT.COINIT_APARTMENTTHREADED); + if (hr.Failed) + { + Log.Error(ClassName, $"Failed to initialize COM: {hr}"); + return; + } + // Check if there are explorer windows and get the topmost one
329-332
: Add error handling to ShowQuickSwitchWindowAsync invocation.The method invokes external code but doesn't include error handling, which could lead to unhandled exceptions disrupting the QuickSwitch functionality.
if (dialogWindow != null && ShowQuickSwitchWindowAsync != null) { - await ShowQuickSwitchWindowAsync.Invoke(dialogWindow.Handle); + try + { + await ShowQuickSwitchWindowAsync.Invoke(dialogWindow.Handle); + } + catch (Exception ex) + { + Log.Exception(ClassName, "Error showing QuickSwitch window", ex); + } }
416-419
: Add exception handling to prevent hotkey failures.The hotkey handler doesn't catch exceptions, which could lead to hotkey failures if the navigation logic throws exceptions, potentially disrupting the application's functionality.
public static void OnToggleHotkey(object sender, HotkeyEventArgs args) { - _ = Task.Run(() => NavigateDialogPathAsync(PInvoke.GetForegroundWindow())); + try + { + _ = Task.Run(async () => + { + try + { + await NavigateDialogPathAsync(PInvoke.GetForegroundWindow()); + } + catch (Exception ex) + { + Log.Exception(ClassName, "Error in NavigateDialogPathAsync from hotkey", ex); + } + }); + } + catch (Exception ex) + { + Log.Exception(ClassName, "Error processing quickswitch hotkey", ex); + } }
482-485
: Incorrect lock object – potential race condition in auto‑switch trackingThe read‑side lock guarding
_autoSwitchedDialogs
uses_dialogWindowHandleLock
, not_autoSwitchedDialogsLock
, which means concurrent reads/writes can hit a race whenJumpToPath
adds to the list.- lock (_dialogWindowHandleLock) + lock (_autoSwitchedDialogsLock) { alreadySwitched = _autoSwitchedDialogs.Contains(hwnd); }
598-608
:DispatcherTimer.Start/Stop
called from non-UI thread → cross-thread exception risk
MoveSizeCallBack
is executed on the WinEvent hook thread, not the WPF UI thread where_dragMoveTimer
was created.
InvokingStart()
/Stop()
directly can raise anInvalidOperationException
.Invoke via the dispatcher that owns the timer:
case PInvoke.EVENT_SYSTEM_MOVESIZESTART: - _dragMoveTimer.Start(); + _dragMoveTimer.Dispatcher.BeginInvoke(() => _dragMoveTimer.Start()); break; case PInvoke.EVENT_SYSTEM_MOVESIZEEND: - _dragMoveTimer.Stop(); + _dragMoveTimer.Dispatcher.BeginInvoke(() => _dragMoveTimer.Stop()); break;
743-760
: Fix logic error in dialog window search.The method returns the first dialog window found instead of the matching one, which could return the wrong dialog window.
-return dialogWindow; +if (dialogWindow != null) +{ + _quickSwitchDialogs[dialog] = dialogWindow; + return dialogWindow; +}
766-824
:JumpToPathAsync
always returnstrue
– success/failure swallowed
result
is computed but never returned; the method unconditionally returnstrue
, so callers cannot detect failure. Fix by returning the real status:- try + bool result = false; + try { - bool result; + // … existing code … if (result) { lock (_autoSwitchedDialogsLock) { _autoSwitchedDialogs.Add(new(dialogHandle)); } } } catch (System.Exception e) { Log.Exception(ClassName, "Failed to jump to path", e); } finally { _navigationLock.Release(); } - return true; + return result;
793-793
: Add null check for Path.GetDirectoryName result.
Path.GetDirectoryName()
can return null for root paths or invalid paths, which would cause issues in the DirJump method.case QuickSwitchFileResultBehaviours.Directory: Log.Debug(ClassName, $"File Jump Directory (Auto: {auto}): {path}"); - result = DirJump(Path.GetDirectoryName(path), dialog, auto); + var dirPath = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(dirPath)) + { + Log.Error(ClassName, $"Could not get directory for file: {path}"); + return false; + } + result = DirJump(dirPath, dialog, auto); break;
858-918
: Enhance resource cleanup in Dispose method.The
Dispose
method should include COM uninitialization to balance anyCoInitialize
calls, and clear the auto-switched dialogs list.// Stop drag move timer if (_dragMoveTimer != null) { _dragMoveTimer.Stop(); _dragMoveTimer = null; } +// Clear auto-switched dialogs list +lock (_autoSwitchedDialogsLock) +{ + _autoSwitchedDialogs.Clear(); +} + +// Uninitialize COM (ignore failure – nothing we can do) +try +{ + PInvoke.CoUninitialize(); +} +catch (Exception ex) +{ + Log.Debug(ClassName, $"Error uninitializing COM: {ex.Message}"); +}
87-90
: Use HashSet for better performance.The
_autoSwitchedDialogs
collection performs Contains, Add, and Remove operations which are O(n) with List. Since these operations happen in event callbacks, using HashSet would provide O(1) performance.Apply this diff to improve performance:
-// A list of all file dialog windows that are auto switched already -private static readonly List<HWND> _autoSwitchedDialogs = new(); +// A set of all file dialog windows that are auto switched already +private static readonly HashSet<HWND> _autoSwitchedDialogs = new();
🧹 Nitpick comments (1)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (1)
628-628
: Fix typo in log message.-Log.Debug(ClassName, $"Destory dialog: {hwnd}"); +Log.Debug(ClassName, $"Destroy dialog: {hwnd}");
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs
(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: Yusyuriv
PR: Flow-Launcher/Flow.Launcher#3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:40:11.014Z
Learning: In Flow Launcher, when using Windows Forms dialogs (e.g., in `JsonRPCPluginSettings.cs`), path validation is enabled by default in `OpenFileDialog` and `FolderBrowserDialog`, preventing users from selecting invalid paths, but it's possible to opt out of this validation on individual dialogs.
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (7)
Learnt from: Yusyuriv
PR: Flow-Launcher/Flow.Launcher#3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:40:11.014Z
Learning: In Flow Launcher, when using Windows Forms dialogs (e.g., in `JsonRPCPluginSettings.cs`), path validation is enabled by default in `OpenFileDialog` and `FolderBrowserDialog`, preventing users from selecting invalid paths, but it's possible to opt out of this validation on individual dialogs.
Learnt from: Jack251970
PR: Flow-Launcher/Flow.Launcher#3791
File: Flow.Launcher.Core/Plugin/PluginManager.cs:293-295
Timestamp: 2025-07-01T05:46:13.251Z
Learning: In Flow.Launcher.Core/Plugin/PluginManager.cs, when checking if a plugin is modified within the PluginManager class itself, prefer using the internal static PluginModified(string id) method directly rather than going through API.PluginModified() for better performance and architectural design.
Learnt from: Jack251970
PR: Flow-Launcher/Flow.Launcher#3672
File: Flow.Launcher/MainWindow.xaml.cs:244-247
Timestamp: 2025-06-08T14:12:21.348Z
Learning: In Flow.Launcher, App.NotifyIcon is created before MainWindow creation, so null checks for App.NotifyIcon are not necessary when accessing it from MainWindow code.
Learnt from: Yusyuriv
PR: Flow-Launcher/Flow.Launcher#3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:34:24.926Z
Learning: In Windows Forms dialogs, Windows handles invalid paths and prevents the user from clicking "Ok" if the path is incorrect, so additional path validation is unnecessary.
Learnt from: Jack251970
PR: Flow-Launcher/Flow.Launcher#3572
File: Flow.Launcher/App.xaml.cs:214-216
Timestamp: 2025-07-06T12:21:37.947Z
Learning: In Flow Launcher, the UpdatePluginManifestAsync method in PluginsManifest.cs already has comprehensive internal try-catch handling that logs exceptions and returns false on failure rather than throwing, making external try-catch wrappers unnecessary.
Learnt from: Jack251970
PR: Flow-Launcher/Flow.Launcher#3672
File: Flow.Launcher/MainWindow.xaml.cs:318-318
Timestamp: 2025-06-08T14:12:12.842Z
Learning: In Flow.Launcher, the App.NotifyIcon static property is initialized in the App class before MainWindow creation, so null checks are not needed when accessing App.NotifyIcon in MainWindow lifecycle methods.
Learnt from: onesounds
PR: Flow-Launcher/Flow.Launcher#3394
File: Flow.Launcher/Themes/Darker Glass.xaml:134-141
Timestamp: 2025-03-28T21:12:13.386Z
Learning: In Flow.Launcher, hotkey styling is implemented with a two-component structure: a Border element with style `ItemHotkeyBGStyle` that provides background and border styling, containing a TextBlock with style `ItemHotkeyStyle` that handles the text styling.
🪛 GitHub Actions: Check Spelling
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs
[warning] 272-272: Spell check warning: Wnd
is not a recognized word. (unrecognized-spelling)
[warning] 279-279: Spell check warning: Wnd
is not a recognized word. (unrecognized-spelling)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
It helps, and now active explorer path feature is implemented |
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: 0
♻️ Duplicate comments (9)
Flow.Launcher.Infrastructure/QuickSwitch/Models/WindowsExplorer.cs (1)
47-73
: COM object leak in shell window enumeration.The
EnumerateShellWindows
method creates a COM object viaCoCreateInstance
but never releases it, leading to potential memory leaks.Apply this fix to properly release the COM object:
var shellWindows = (IShellWindows)shellWindowsObj; +try +{ // Enumerate the shell windows var count = shellWindows.Count; for (var i = 0; i < count; i++) { if (!action(shellWindows.Item(i))) { return; } } +} +finally +{ + Marshal.ReleaseComObject(shellWindows); +}Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (8)
87-89
: Use HashSet for better performance.The
_autoSwitchedDialogs
list uses linear search operations (O(n)) forContains
,Add
, andRemove
. AHashSet<HWND>
would provide O(1) performance.Apply this change:
-private static readonly List<HWND> _autoSwitchedDialogs = new(); +private static readonly HashSet<HWND> _autoSwitchedDialogs = new();
134-203
: Missing COM initialization before shell API usage.The
SetupQuickSwitch
method uses Windows shell APIs that require COM initialization but doesn't initialize COM, which could cause failures.Add COM initialization when enabling QuickSwitch:
if (enabled) { + // Initialize COM for this thread + var hr = PInvoke.CoInitializeEx(null, COINIT.COINIT_APARTMENTTHREADED); + if (hr.Failed) + { + Log.Error(ClassName, $"Failed to initialize COM: {hr}"); + return; + } + // Check if there are explorer windows and get the topmost one
329-332
: Add error handling to async event invocation.The
ShowQuickSwitchWindowAsync
invocation lacks error handling, which could lead to unhandled exceptions.Wrap the invocation in error handling:
if (dialogWindow != null && ShowQuickSwitchWindowAsync != null) { - await ShowQuickSwitchWindowAsync.Invoke(dialogWindow.Handle); + try + { + await ShowQuickSwitchWindowAsync.Invoke(dialogWindow.Handle); + } + catch (Exception ex) + { + Log.Exception(ClassName, "Error showing QuickSwitch window", ex); + } }
362-374
: Validate SetWinEventHook success and add cleanup.The
SetMoveProc
method doesn't validate ifSetWinEventHook
succeeds, which could lead to null hook handles.Add validation and error handling:
_moveSizeHook = PInvoke.SetWinEventHook( PInvoke.EVENT_SYSTEM_MOVESIZESTART, PInvoke.EVENT_SYSTEM_MOVESIZEEND, PInvoke.GetModuleHandle((PCWSTR)null), _moveProc, processId, threadId, PInvoke.WINEVENT_OUTOFCONTEXT); + +if (_moveSizeHook.IsNull) +{ + Log.Error(ClassName, $"Failed to hook move/size events for dialog {handle}"); + return; +}
416-419
: Missing exception handling in hotkey handler.The
OnToggleHotkey
method lacks exception handling, which could cause hotkey failures and disrupt application functionality.Add comprehensive exception handling:
public static void OnToggleHotkey(object sender, HotkeyEventArgs args) { - _ = Task.Run(() => NavigateDialogPathAsync(PInvoke.GetForegroundWindow())); + try + { + _ = Task.Run(async () => + { + try + { + await NavigateDialogPathAsync(PInvoke.GetForegroundWindow()); + } + catch (Exception ex) + { + Log.Exception(ClassName, "Error in NavigateDialogPathAsync from hotkey", ex); + } + }); + } + catch (Exception ex) + { + Log.Exception(ClassName, "Error processing quickswitch hotkey", ex); + } }
598-608
: Cross-thread timer access in MoveSizeCallBack.The
MoveSizeCallBack
is executed on a Win32 event hook thread, but_dragMoveTimer
operations require UI thread access.Use dispatcher to ensure thread safety:
case PInvoke.EVENT_SYSTEM_MOVESIZESTART: - _dragMoveTimer.Start(); + _dragMoveTimer.Dispatcher.BeginInvoke(() => _dragMoveTimer.Start()); break; case PInvoke.EVENT_SYSTEM_MOVESIZEEND: - _dragMoveTimer.Stop(); + _dragMoveTimer.Dispatcher.BeginInvoke(() => _dragMoveTimer.Stop()); break;
801-804
: Missing null check for Path.GetDirectoryName.
Path.GetDirectoryName()
can return null for root paths, which would cause issues in theDirJump
method.Add null validation:
case QuickSwitchFileResultBehaviours.Directory: Log.Debug(ClassName, $"File Jump Directory (Auto: {auto}): {path}"); - result = DirJump(Path.GetDirectoryName(path), dialog, auto); + var dirPath = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(dirPath)) + { + Log.Error(ClassName, $"Could not get directory for file: {path}"); + return false; + } + result = DirJump(dirPath, dialog, auto); break;
868-928
: Incomplete resource cleanup in Dispose method.The
Dispose
method doesn't clean up all resources properly, potentially leaving COM uninitialized and other resources.Add comprehensive cleanup:
// Stop drag move timer if (_dragMoveTimer != null) { _dragMoveTimer.Stop(); _dragMoveTimer = null; } + +// Clear auto-switched dialogs list +lock (_autoSwitchedDialogsLock) +{ + _autoSwitchedDialogs.Clear(); +} + +// Uninitialize COM (ignore failure – nothing we can do) +try +{ + PInvoke.CoUninitialize(); +} +catch (Exception ex) +{ + Log.Debug(ClassName, $"Error uninitializing COM: {ex.Message}"); +}
🧹 Nitpick comments (1)
Flow.Launcher.Infrastructure/QuickSwitch/Models/WindowsExplorer.cs (1)
75-78
: Empty Dispose method should be implemented.The
Dispose
method is empty but should handle cleanup if needed or be marked as intentionally empty.Consider adding a comment to clarify this is intentionally empty:
public void Dispose() { - + // No resources to dispose in this implementation }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Flow.Launcher.Infrastructure/QuickSwitch/Models/WindowsExplorer.cs
(1 hunks)Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs
(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Yusyuriv
PR: Flow-Launcher/Flow.Launcher#3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:40:11.014Z
Learning: In Flow Launcher, when using Windows Forms dialogs (e.g., in `JsonRPCPluginSettings.cs`), path validation is enabled by default in `OpenFileDialog` and `FolderBrowserDialog`, preventing users from selecting invalid paths, but it's possible to opt out of this validation on individual dialogs.
Flow.Launcher.Infrastructure/QuickSwitch/Models/WindowsExplorer.cs (1)
Learnt from: Koisu-unavailable
PR: Flow-Launcher/Flow.Launcher#3770
File: Flow.Launcher/ViewModel/MainViewModel.cs:0-0
Timestamp: 2025-06-24T19:06:48.344Z
Learning: In Flow.Launcher's Explorer plugin results, the SubTitle property always contains the directory containing the file. For file results, Title contains the filename and SubTitle contains the parent directory. For directory results, SubTitle contains the directory path itself.
Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (7)
Learnt from: Yusyuriv
PR: Flow-Launcher/Flow.Launcher#3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:40:11.014Z
Learning: In Flow Launcher, when using Windows Forms dialogs (e.g., in `JsonRPCPluginSettings.cs`), path validation is enabled by default in `OpenFileDialog` and `FolderBrowserDialog`, preventing users from selecting invalid paths, but it's possible to opt out of this validation on individual dialogs.
Learnt from: Jack251970
PR: Flow-Launcher/Flow.Launcher#3791
File: Flow.Launcher.Core/Plugin/PluginManager.cs:293-295
Timestamp: 2025-07-01T05:46:13.251Z
Learning: In Flow.Launcher.Core/Plugin/PluginManager.cs, when checking if a plugin is modified within the PluginManager class itself, prefer using the internal static PluginModified(string id) method directly rather than going through API.PluginModified() for better performance and architectural design.
Learnt from: Jack251970
PR: Flow-Launcher/Flow.Launcher#3672
File: Flow.Launcher/MainWindow.xaml.cs:244-247
Timestamp: 2025-06-08T14:12:21.348Z
Learning: In Flow.Launcher, App.NotifyIcon is created before MainWindow creation, so null checks for App.NotifyIcon are not necessary when accessing it from MainWindow code.
Learnt from: Yusyuriv
PR: Flow-Launcher/Flow.Launcher#3057
File: Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs:0-0
Timestamp: 2024-11-03T07:34:24.926Z
Learning: In Windows Forms dialogs, Windows handles invalid paths and prevents the user from clicking "Ok" if the path is incorrect, so additional path validation is unnecessary.
Learnt from: Jack251970
PR: Flow-Launcher/Flow.Launcher#3572
File: Flow.Launcher/App.xaml.cs:214-216
Timestamp: 2025-07-06T12:21:37.947Z
Learning: In Flow Launcher, the UpdatePluginManifestAsync method in PluginsManifest.cs already has comprehensive internal try-catch handling that logs exceptions and returns false on failure rather than throwing, making external try-catch wrappers unnecessary.
Learnt from: Jack251970
PR: Flow-Launcher/Flow.Launcher#3672
File: Flow.Launcher/MainWindow.xaml.cs:318-318
Timestamp: 2025-06-08T14:12:12.842Z
Learning: In Flow.Launcher, the App.NotifyIcon static property is initialized in the App class before MainWindow creation, so null checks are not needed when accessing App.NotifyIcon in MainWindow lifecycle methods.
Learnt from: onesounds
PR: Flow-Launcher/Flow.Launcher#3394
File: Flow.Launcher/Themes/Darker Glass.xaml:134-141
Timestamp: 2025-03-28T21:12:13.386Z
Learning: In Flow.Launcher, hotkey styling is implemented with a two-component structure: a Border element with style `ItemHotkeyBGStyle` that provides background and border styling, containing a TextBlock with style `ItemHotkeyStyle` that handles the text styling.
🪛 GitHub Actions: Check Spelling
Flow.Launcher.Infrastructure/QuickSwitch/Models/WindowsExplorer.cs
[warning] 29-29: IWeb
is not a recognized word. (unrecognized-spelling)
[warning] 57-57: iid
is not a recognized word. (unrecognized-spelling)
[warning] 122-122: IWeb
is not a recognized word. (unrecognized-spelling)
[warning] 126-126: IWeb
is not a recognized word. (unrecognized-spelling)
[warning] 133-133: IWeb
is not a recognized word. (unrecognized-spelling)
[warning] 142-142: Wnd
is not a recognized word. (unrecognized-spelling)
[warning] 250-250: IUnknown
is not a recognized word. (unrecognized-spelling)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (8)
Flow.Launcher.Infrastructure/QuickSwitch/Models/WindowsExplorer.cs (7)
15-45
: Class structure and interface implementation look good.The
WindowsExplorer
class properly implementsIQuickSwitchExplorer
with appropriate process name checking and shell window enumeration.
92-104
: Path retrieval logic is sound.The
GetExplorerPath
method correctly handles the active tab detection and path retrieval with proper null checks.
122-171
: COM object release is properly handled in finally block.The
GetExplorerByTabHandle
method correctly releases theshellBrowser
COM object in the finally block, preventing resource leaks.
173-187
: STA thread creation pattern is appropriate.The
StartSTAThread
method correctly creates an STA thread for COM operations that require specific apartment threading.
189-216
: Dynamic COM access with proper error handling.The
GetLocation
method uses dynamic typing appropriately to access COM properties that aren't available in static typing, with proper exception handling.
218-232
: Path normalization logic is comprehensive.The
NormalizeLocation
method handles various Windows path formats including environment variables and special shell locations.
237-260
: COM interface definitions are correctly structured.The
IServiceProvider
andIShellBrowser
interfaces are properly defined with correct GUIDs and method signatures for COM interop.Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs (1)
81-84
: Delegate rooting prevents garbage collection issues.The static delegate fields (
_fgProc
,_locProc
,_desProc
) properly root the callback methods to prevent garbage collection from breaking the Win32 event hooks.
<system:String x:Key="autoQuickSwitch">Quick Switch Automatically</system:String> | ||
<system:String x:Key="autoQuickSwitchToolTip">Automatically navigate to the path of the current file manager when a file dialog is opened. (Experimental)</system:String> | ||
<system:String x:Key="showQuickSwitchWindow">Show Quick Switch Window</system:String> | ||
<system:String x:Key="showQuickSwitchWindowToolTip">Show quick switch search window when file dialogs are opened to navigate its path (Only work for file open dialog).</system:String> |
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.
I have been able to use it for file save dialogue as well, can you also confirm?
Co-authored-by: Jeremy Wu <[email protected]>
Co-authored-by: Jeremy Wu <[email protected]>
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 🙅 (2)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)
Reject duplicate words
If the flagged items are 🤯 false positivesIf items relate to a ...
|
// make a clone to avoid possible issue that plugin will also change the list and items when updating view model | ||
var resultsCopy = DeepCloneResults(e.Results, token); | ||
IReadOnlyList<Result> resultsCopy; | ||
if (e.Results == null) |
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.
Why do we need this? When will it be null?
Nothing more than quickswitch. We may integrate flow's path system to this feature instead of relying explorer.
Setup Quick Switch
Quick switch automaticallyUse 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 -> Click results to navigate to the selected path
Quick Switch API
Implement new api interfaces to develop third party explorers & dialogs which are for dotnet plugins only.
Additionally,
Explorer
plugin already supports quick switch.Test
Automatically quick switch can work under most cases (since it is an experimental feature)Todos
However, 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.
Future