Consolidate all 22 open PRs: DI/testability, perf, SetTimer, path fixes, regex escaping#109
Conversation
…reen hotkey fix, VLC optimization, Lossless Scaling DI (PRs 104, 107, 91, 95, 85, 102, 92)
…health (PRs 76-106)
There was a problem hiding this comment.
Code Review
This pull request introduces various refactorings, optimizations, and test coverage across multiple AutoHotkey scripts, including refactoring blocking loops to timer-based execution in bo6-afk.ahk, adding dependency injection for window APIs to facilitate testing in Keys.ahk and WindowManager.ahk, and optimizing file reading and configuration updates. However, several critical issues were identified in the review: a typo in RegExEscape leaves a regex character class unclosed, backslashes and dollar signs are not properly escaped in SetKey's replacement string, removing the try-catch block in WaitForWindow will cause unhandled TimeoutError exceptions, using FileExist with wildcards in an AHK v1 script breaks empty directory detection, and using RunWait instead of Run for VLC blocks script execution.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| WaitForWindow(winTitle, timeout := 30, api := "") { | ||
| if !api | ||
| api := SystemWindowAPI() | ||
|
|
||
| return api.WinWait(winTitle, , timeout) != 0 | ||
| } |
There was a problem hiding this comment.
In AutoHotkey v2, the native WinWait function throws a TimeoutError when it times out. By removing the try-catch block, WaitForWindow will propagate this unhandled exception to the caller instead of returning false as expected. We should restore the try-catch block to handle the timeout gracefully.
WaitForWindow(winTitle, timeout := 30, api := "") {
if !api
api := SystemWindowAPI()
try {
api.WinWait(winTitle, , timeout)
return true
} catch TimeoutError {
return false
}
}
| RegExEscape(str) { | ||
| return RegExReplace(str, "([\\()\[\]{}?*+|^$.])", "\$1") | ||
| return RegExReplace(str, "([\\()\[\]{}?*+|^$.})", "\\$1") | ||
| } |
There was a problem hiding this comment.
The closing square bracket ] of the character class was accidentally replaced with } in the regex pattern, leaving the character class unclosed. This will cause RegExReplace to throw a runtime ValueError (invalid regular expression) on any call to RegExEscape. The original character class already matched both { and } correctly.
RegExEscape(str) {
return RegExReplace(str, '([\\()\[\]{}?*+|^$.])', '\\$1')
}
| dirHasItems := true ;Mark that we found at least one item | ||
| Break ;Optimization: Break after finding the first item | ||
| } | ||
| dirHasItems := FileExist(Checkdir "\*") != "" |
There was a problem hiding this comment.
This is an AutoHotkey v1 script. In AutoHotkey v1, FileExist does not support wildcards (unlike v2). Calling FileExist(Checkdir "\*") will always return an empty string, meaning dirHasItems will always be false. This breaks the empty directory check and will cause the script to always attempt to copy files, potentially overwriting existing data. We should revert to the original Loop, Files approach.
dirHasItems := false
Loop, Files, %Checkdir%\*, DF
{
dirHasItems := true
break
}
| SetKey(content, key, value) { | ||
| pat := "m)^(" . RegExEscape(key) . ")\s*=.*$" | ||
| pat := "m)^(" . RegExEscape(key) . ")\\s*=.*$" | ||
| if RegExMatch(content, pat) | ||
| return RegExReplace(content, pat, "$1=" value, , 1) | ||
| return RegExReplace(content, pat, "$1=" StrReplace(value, "$", "$$"), , 1) | ||
| else | ||
| return content "`n" key "=" value | ||
| } |
There was a problem hiding this comment.
In AutoHotkey RegExReplace, backslashes (\) in the replacement string have special meaning (such as backreferences like \1). If value contains backslashes (which is extremely common for file paths in configuration files), they will be interpreted by the regex engine, leading to corrupted values or unexpected behavior. Both backslashes and dollar signs must be escaped in the replacement string.
SetKey(content, key, value) {
pat := "m)^(" . RegExEscape(key) . ")\\s*=.*$"
if RegExMatch(content, pat) {
escapedValue := StrReplace(StrReplace(value, '\'', '\\\\''), '$', '$$')
return RegExReplace(content, pat, '$1=' . escapedValue, , 1)
} else {
return content "`n" key "=" value
}
}
| vlcArgs := '--fullscreen --video-on-top --play-and-exit --no-video-title -Idummy "' . bootVideo . '"' | ||
| RunWait('cmd.exe /c START "" "' . vlcPath . '" ' . vlcArgs, , "hide") | ||
| DllCall("kernel32.dll\Sleep", "UInt", 3000) | ||
| RunWait('"' . vlcPath . '" ' . vlcArgs, , "hide") |
There was a problem hiding this comment.
Using RunWait directly on VLC will block the script execution and wait for the entire boot video to finish playing before continuing to launch Playnite. If the intention is to start VLC asynchronously (as it was previously with cmd.exe /c START), you should use Run instead of RunWait.
Run('"' . vlcPath . '" ' . vlcArgs, , "hide")
Summary
This PR consolidates all 22 open pull requests into a single, cleanly integrated branch with conflicts resolved intelligently.
Changes by area
Testability / Dependency Injection
ahk/Keys.ahk: AddedKeysWindowAPIclass + optionalapiparameter toMoveWindowLeft,MoveWindowRight,RestoreWindowPosition,SaveWindowPosition,IsResizable; addedA_LineFile == A_ScriptFullPathguard around auto-execute block; removed unusedphvariable (PRs 76, 94)ahk/test_Keys.ahk: New test file usingMockKeysAPI(PR 94)Lib/v2/WindowManager.ahk:WaitForWindowandGetMonitorAtPosacceptapiparameter;SystemWindowAPIclass withWinWaitmethod (PRs 104, 107)ahk/test_WindowManager.ahk: AddedWinWaittoMockWindowAPI+TestWaitForWindow_Timeout(PR 104)Other/Lossless_Scaling_Manager.ahk:ToggleLSaccepts mock parameters;EnsureAdminand dispatch wrapped withA_LineFileguard (PR 92)tests/WindowManager_Test.ahk: New — testsGetMonitorAtPoswith zero/single/multi monitors (PR 107)tests/Other/Lossless_Scaling_Manager_Test.ahk: New — testsToggleLSwith mocks (PR 92)Performance / I/O
ahk/Black_ops_6/bo6-afk.ahk: All blockingwhile+Sleeploops replaced withSetTimer-based state machines (PR 84)Other/Citra_mods/Citra_3DS_Manager.ahk:LoadDestinationsusesFileRead+Loop Parseinstead ofLoop Read;UpdateConfigcleaned up (PRs 98, 88)Other/Citra_mods/Citra_Mod_Manager.ahk: CSV reading viaFileRead+Loop Parse; directory empty check usesFileExistinstead ofLoop Files(PRs 99, 81)Other/Citra_per_game_config/tf.ahk:TF_Mergeremoves redundantIfExistbeforeFileRead; splitsOutput .=concatenation (PR 106)Other/playnite-all.ahk:PlayBootVideodropscmd.exe /c STARTwrapper +DllCall Sleep(PR 102)Bug fixes
ahk/Fullscreen.ahk:End::hotkey callsToggleFakeFullscreen("A")instead of brokenToggleFakeFullscreenMultiMonitor; comments updated (PR 85 over PR 83)Lib/v2/AHK_Common.ahk:FindExeshort-circuits on empty name; PATH entries trimmed of whitespace and quotes viaTrim(..., " \t`"")+ trailing slashes stripped withRTrim(p, "/")` (PRs 91, 95)Other/Citra_per_game_config/v2/CitraConfigHelpers.ahk:SetKeyescapes$signs in values withStrReplaceto prevent regex backreference misinterpretation (PR 100)Dead code removal
Other/7zEmuPrepper/Final.ahk: Removed unusedsevenZEmuPrepperPathvariable and dead single-argSelectFileOrDiroverload (PRs 78, 80)New tests
ahk/GUI/QuotePathIfNeeded_Test.ahk: Tests forQuotePathIfNeeded(PR 79)Other/Citra_per_game_config/v2/CitraConfigHelpers_Test.ahk: Added tests 6–9 forSetKeywith$signs (PR 100)tests/FindExe_Test.ahk: Added tests 6–11 for whitespace in PATH, quoted PATH, trailing slashes, subdirectory names, empty name, empty fallback (PRs 91, 95)Conflict resolutions
Fullscreen.ahk(keepsEndkey functional with correct function call)AHK_Common.ahk(best of both path-trimming approaches)Citra_3DS_Manager.ahk(cleanerUpdateConfig— no intermediatevalvariable).github/workflows/ahk-lint-format-compile.ymlalready had the more complete retry logic from PRs 95/102Generated by Claude Code