Repository navigation
Keep slow work off the UI thread and contain failures - #6
Merged
Merged
Conversation
The low-level keyboard hook runs on the UI thread, so anything that blocks that thread delays key presses and can make Windows drop the hook. Several things did block it, and a number of failures could take the whole application down or leave it unable to start or exit. Responsiveness - Load window icons on a background thread. WPF runs a value converter on the UI thread even for an IsAsync binding, so retrieving icons (which waits for a reply from each window) froze the switcher. Cached icons are attached before the list is shown. - Only claim the key press inside the Alt+Tab hook callback and show the window from the dispatcher afterwards. - Install the Alt+Tab hook only while that option is enabled. - Read window and process titles once per load and format them before the list is bound, instead of filling them in afterwards in batches separated by sleeps. - Stop compiling a new regex for every keystroke in SignificantCharactersMatcher; interpreting it is about nine times cheaper for the few dozen titles it is matched against. - Parse the highlight markup as plain XML instead of with XamlReader. - Read process start times through one limited-access handle. Resilience - Recover from a corrupt user.config by setting it aside and restarting, instead of crashing on every launch. - Log unhandled exceptions from the dispatcher, Windows Forms callbacks and the hook callback, hide the overlay and keep running. - Never let disposal throw: a hook that Windows already removed made Exit fail and left a process without tray icon holding the mutex. - Drop characters that XML cannot represent from window titles; one such title used to leave the rest of the list blank. - Treat a process that exited or cannot be queried as having no name instead of aborting the whole filter run. - Skip windows that are destroyed while being enumerated. - Pass a real 256-byte key state to ToUnicodeEx and use pointer-sized parameters for the EnumPropsEx callback. Behaviour - Remove the one-second window handle cache. It saved under a millisecond and reused a stale window order when the switcher was reopened quickly. - Apply a pending filter before Enter, the arrow keys or Ctrl+W act on the list, so a command issued right after typing uses the typed text. - Keep the "Press Alt + S to search" hint, which was cleared as soon as the list had loaded. WindowHandleToIconConverter and WindowHandleToCachedIconConverter are replaced by WindowIconImageLoader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Native hooks, P/Invoke changes, and UI-thread behavior require the manual rendering and keyboard testing identified in the PR description.
0 open findings
What changed in this PR
Moves blocking window discovery, icon loading, and filtering work off the UI thread while improving recovery from configuration, hook, and callback failures.
Changes:
- Performs window metadata, icon loading, and filtering asynchronously with cancellation and caching.
- Adds exception containment, settings recovery, and safer shutdown/disposal.
- Optimizes highlighting, matching, keyboard translation, and Win32 interop.
| File | Description |
|---|---|
TaskSwitcher/WindowIconImageLoader.cs |
Adds background-safe icon loading. |
TaskSwitcher/WindowHandleToIconConverter.cs |
Removes synchronous icon converter. |
TaskSwitcher/WindowHandleToCachedIconConverter.cs |
Removes cached-icon converter. |
TaskSwitcher/Program.cs |
Adds settings recovery and global exception logging. |
TaskSwitcher/MainWindow.xaml.cs |
Refactors loading, filtering, hooks, and disposal. |
TaskSwitcher/MainWindow.xaml |
Binds rows to preloaded icons. |
TaskSwitcher/FormattedTextAttribute.cs |
Replaces XAML parsing with XML parsing. |
TaskSwitcher/BoolToWhateverConverter.cs |
Safely handles non-boolean values. |
TaskSwitcher/AppWindowViewModel.cs |
Caches titles and exposes icon state. |
TaskSwitcher/App.xaml.cs |
Handles dispatcher failures. |
TaskSwitcher/AltTabHook.cs |
Contains hook callback exceptions. |
ManagedWinapi/Hook.cs |
Makes hook callbacks and disposal safer. |
Core/XamlHighlighter.cs |
Adds fast, safe markup escaping. |
Core/WindowFinder.cs |
Skips windows that fail inspection. |
Core/WinApi.cs |
Corrects Win32 interop declarations. |
Core/Matchers/SignificantCharactersMatcher.cs |
Avoids per-query regex compilation. |
Core/KeyboardHelper.cs |
Corrects keyboard-state translation. |
Core/AppWindow.cs |
Contains process-query failures and optimizes identity lookup. |
Core.UnitTests/XamlHighlighterTests.cs |
Covers escaping and invalid characters. |
Core.UnitTests/SignificantCharactersMatcherTests.cs |
Expands matching and cache coverage. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The low-level keyboard hook runs on the UI thread, so anything that blocks that thread delays key presses and can make Windows silently drop the hook. Several things did block it, and a number of failures could crash the app or leave it unable to start or exit.
What changed
Responsiveness
IsAsyncbinding, so icon retrieval froze the switcher (194 ms for 93 windows on the test machine, 156 ms of it one busy window). Cached icons are attached before the list is shown.SignificantCharactersMatcherno longer compiles a regex per keystroke (16.7 ms -> 1.8 ms for 60 titles).XamlReader(280 us -> 6 us per title, and no 110 ms first-use cost).Resilience
user.configis set aside and the app restarts with defaults, instead of crashing on every launch.ToUnicodeExgets a real 256-byte key state, and theEnumPropsExcallback uses pointer-sized parameters.Behaviour changes to review
WindowHandleToIconConverterandWindowHandleToCachedIconConverterare replaced byWindowIconImageLoader.Testing
🤖 Generated with Claude Code