Skip to content

Consolidate all 22 open PRs: DI/testability, perf, SetTimer, path fixes, regex escaping#109

Merged
Ven0m0 merged 4 commits into
mainfrom
claude/practical-hopper-DzAhS
Jun 3, 2026
Merged

Consolidate all 22 open PRs: DI/testability, perf, SetTimer, path fixes, regex escaping#109
Ven0m0 merged 4 commits into
mainfrom
claude/practical-hopper-DzAhS

Conversation

@Ven0m0
Copy link
Copy Markdown
Owner

@Ven0m0 Ven0m0 commented Jun 3, 2026

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: Added KeysWindowAPI class + optional api parameter to MoveWindowLeft, MoveWindowRight, RestoreWindowPosition, SaveWindowPosition, IsResizable; added A_LineFile == A_ScriptFullPath guard around auto-execute block; removed unused ph variable (PRs 76, 94)
  • ahk/test_Keys.ahk: New test file using MockKeysAPI (PR 94)
  • Lib/v2/WindowManager.ahk: WaitForWindow and GetMonitorAtPos accept api parameter; SystemWindowAPI class with WinWait method (PRs 104, 107)
  • ahk/test_WindowManager.ahk: Added WinWait to MockWindowAPI + TestWaitForWindow_Timeout (PR 104)
  • Other/Lossless_Scaling_Manager.ahk: ToggleLS accepts mock parameters; EnsureAdmin and dispatch wrapped with A_LineFile guard (PR 92)
  • tests/WindowManager_Test.ahk: New — tests GetMonitorAtPos with zero/single/multi monitors (PR 107)
  • tests/Other/Lossless_Scaling_Manager_Test.ahk: New — tests ToggleLS with mocks (PR 92)

Performance / I/O

  • ahk/Black_ops_6/bo6-afk.ahk: All blocking while+Sleep loops replaced with SetTimer-based state machines (PR 84)
  • Other/Citra_mods/Citra_3DS_Manager.ahk: LoadDestinations uses FileRead+Loop Parse instead of Loop Read; UpdateConfig cleaned up (PRs 98, 88)
  • Other/Citra_mods/Citra_Mod_Manager.ahk: CSV reading via FileRead+Loop Parse; directory empty check uses FileExist instead of Loop Files (PRs 99, 81)
  • Other/Citra_per_game_config/tf.ahk: TF_Merge removes redundant IfExist before FileRead; splits Output .= concatenation (PR 106)
  • Other/playnite-all.ahk: PlayBootVideo drops cmd.exe /c START wrapper + DllCall Sleep (PR 102)

Bug fixes

  • ahk/Fullscreen.ahk: End:: hotkey calls ToggleFakeFullscreen("A") instead of broken ToggleFakeFullscreenMultiMonitor; comments updated (PR 85 over PR 83)
  • Lib/v2/AHK_Common.ahk: FindExe short-circuits on empty name; PATH entries trimmed of whitespace and quotes via Trim(..., " \t`"")+ trailing slashes stripped withRTrim(p, "/")` (PRs 91, 95)
  • Other/Citra_per_game_config/v2/CitraConfigHelpers.ahk: SetKey escapes $ signs in values with StrReplace to prevent regex backreference misinterpretation (PR 100)

Dead code removal

  • Other/7zEmuPrepper/Final.ahk: Removed unused sevenZEmuPrepperPath variable and dead single-arg SelectFileOrDir overload (PRs 78, 80)

New tests

  • ahk/GUI/QuotePathIfNeeded_Test.ahk: Tests for QuotePathIfNeeded (PR 79)
  • Other/Citra_per_game_config/v2/CitraConfigHelpers_Test.ahk: Added tests 6–9 for SetKey with $ 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

  • PR 85 wins over PR 83 on Fullscreen.ahk (keeps End key functional with correct function call)
  • PRs 91+95 combined on AHK_Common.ahk (best of both path-trimming approaches)
  • PR 88 wins over PR 101 on Citra_3DS_Manager.ahk (cleaner UpdateConfig — no intermediate val variable)
  • Main's .github/workflows/ahk-lint-format-compile.yml already had the more complete retry logic from PRs 95/102

Generated by Claude Code

@Ven0m0 Ven0m0 merged commit 10c9107 into main Jun 3, 2026
4 of 6 checks passed
@Ven0m0 Ven0m0 deleted the claude/practical-hopper-DzAhS branch June 3, 2026 21:18
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread Lib/v2/WindowManager.ahk
Comment on lines +36 to 41
WaitForWindow(winTitle, timeout := 30, api := "") {
if !api
api := SystemWindowAPI()

return api.WinWait(winTitle, , timeout) != 0
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

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
    }
}

Comment on lines 20 to 22
RegExEscape(str) {
return RegExReplace(str, "([\\()\[\]{}?*+|^$.])", "\$1")
return RegExReplace(str, "([\\()\[\]{}?*+|^$.})", "\\$1")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

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 "\*") != ""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

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
        }

Comment on lines 38 to 44
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
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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
    }
}

Comment thread Other/playnite-all.ahk
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")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant