Skip to content

Commit 10c9107

Browse files
authored
Consolidate 22 open PRs: DI/testability, perf, SetTimer, path fixes, regex escaping
Squash-merge of branch claude/practical-hopper-DzAhS consolidating all 22 open PRs. Key changes: - ahk/Keys.ahk: KeysWindowAPI DI class, optional api param, A_LineFile guard (PRs 76, 94) - ahk/test_Keys.ahk: new test file with MockKeysAPI (PR 94) - Lib/v2/WindowManager.ahk: WaitForWindow + GetMonitorAtPos accept api param (PRs 104, 107) - ahk/test_WindowManager.ahk: WinWait mock + TestWaitForWindow_Timeout (PR 104) - ahk/GUI/QuotePathIfNeeded_Test.ahk: new tests (PR 79) - tests/WindowManager_Test.ahk: new GetMonitorAtPos tests (PR 107) - tests/Other/Lossless_Scaling_Manager_Test.ahk: new ToggleLS tests (PR 92) - ahk/Black_ops_6/bo6-afk.ahk: SetTimer-based state machines (PR 84) - ahk/Fullscreen.ahk: fix End hotkey to use ToggleFakeFullscreen (PR 85 over PR 83) - Lib/v2/AHK_Common.ahk: FindExe empty-name guard, PATH whitespace+quote+slash trim (PRs 91, 95) - Other/Lossless_Scaling_Manager.ahk: ToggleLS mockable params, A_LineFile guards (PR 92) - Other/playnite-all.ahk: drop cmd.exe wrapper in PlayBootVideo (PR 102) - Other/7zEmuPrepper/Final.ahk: remove dead code (PRs 78, 80) - Other/Citra_per_game_config/v2/CitraConfigHelpers.ahk: escape $ in SetKey (PR 100) - Other/Citra_per_game_config/v2/CitraConfigHelpers_Test.ahk: $ sign tests (PR 100) - Other/Citra_per_game_config/tf.ahk: TF_Merge IfExist removal (PR 106) - Other/Citra_mods/Citra_3DS_Manager.ahk: FileRead+Loop Parse, UpdateConfig cleanup (PRs 98, 88) - Other/Citra_mods/Citra_Mod_Manager.ahk: FileRead+Loop Parse, FileExist dir check (PRs 99, 81) - tests/FindExe_Test.ahk: tests 6-11 for path trimming edge cases (PRs 91, 95)
1 parent 1f32272 commit 10c9107

19 files changed

Lines changed: 791 additions & 159 deletions

Lib/v2/AHK_Common.ahk

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,16 @@ InitScript(requireUIA := true, requireAdmin := false, optimize := true) {
5757
}
5858

5959
FindExe(name, fallbacks := []) {
60+
if (name == "")
61+
return ""
6062
if FileExist(name)
6163
return name
6264
Loop Parse, EnvGet("PATH"), ";"
6365
{
64-
p := Trim(A_LoopField)
66+
p := Trim(A_LoopField, " `t`"")
6567
if !p
6668
continue
69+
p := RTrim(p, "\/")
6770
cand := p . "\" . name
6871
if FileExist(cand)
6972
return cand

Lib/v2/WindowManager.ahk

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,25 @@ MakeFullscreen(winTitle) {
3333
MaximizeWindow(winTitle)
3434
}
3535

36-
WaitForWindow(winTitle, timeout := 30) {
37-
try {
38-
WinWait(winTitle, , timeout)
39-
return true
40-
} catch TimeoutError {
41-
return false
42-
}
36+
WaitForWindow(winTitle, timeout := 30, api := "") {
37+
if !api
38+
api := SystemWindowAPI()
39+
40+
return api.WinWait(winTitle, , timeout) != 0
4341
}
4442

4543
WaitForProcess(processName, timeout := 30) {
4644
return ProcessWait(processName, timeout) != 0
4745
}
4846

4947
GetMonitorAtPos(x, y, api := "") {
50-
if !api
51-
api := SystemWindowAPI()
52-
53-
count := api.MonitorGetCount()
48+
count := IsObject(api) ? api.MonitorGetCount() : MonitorGetCount()
5449
Loop count {
55-
api.MonitorGet(A_Index, &l, &t, &r, &b)
50+
if IsObject(api)
51+
api.MonitorGet(A_Index, &l, &t, &r, &b)
52+
else
53+
MonitorGet(A_Index, &l, &t, &r, &b)
54+
5655
if (l <= x && x <= r && t <= y && y <= b)
5756
return A_Index
5857
}
@@ -93,6 +92,7 @@ RestoreWindowBorders(winTitle) {
9392
}
9493

9594
class SystemWindowAPI {
95+
WinWait(winTitle, winText?, timeout?) => WinWait(winTitle, winText?, timeout?)
9696
WinGetStyle(winTitle) => WinGetStyle(winTitle)
9797
WinSetStyle(style, winTitle) => WinSetStyle(style, winTitle)
9898
WinMove(x, y, w, h, winTitle) => WinMove(x, y, w, h, winTitle)

Other/7zEmuPrepper/Final.ahk

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
; 7zEmuPrepper command builder (for 7zEmuPrepper.ps1)
55
; Fields -> emits a ready-to-run PowerShell command. Includes a Copy button.
66

7-
sevenZEmuPrepperPath := ""
87
sevenZipPath := ""
98
emulatorPath := ""
109
arguments := ""
@@ -46,15 +45,6 @@ gui.Add("Button", "x340 y430 w80 h23", "Exit").OnEvent("Click", (*) => ExitApp()
4645
gui.Show("w480 h470")
4746
return
4847

49-
SelectFileOrDir(btn) {
50-
global
51-
text := btn.Text
52-
if (text = "") {
53-
; infer based on label order
54-
idx := btn.Hwnd
55-
}
56-
}
57-
5848
; Simplified per-control selection based on Y position
5949
SelectFileOrDir(btn, *) {
6050
y := btn.Pos.Y

Other/Citra_mods/Citra_3DS_Manager.ahk

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ LoadDestinations(){
2727
DestMap := {}
2828
if !FileExist(DestCsv)
2929
return
30-
Loop, Read, %DestCsv%
30+
FileRead, csvData, %DestCsv%
31+
Loop, Parse, csvData, `n, `r
3132
{
32-
line := Trim(A_LoopReadLine)
33+
line := Trim(A_LoopField)
3334
if (line = "" || !InStr(line, ","))
3435
continue
3536
StringSplit, p, line, `,
@@ -56,29 +57,35 @@ ScanMods(){
5657
;----------------- Config Helpers -----------------
5758
UpdateConfig(content, updates){
5859
remaining := updates.Clone()
60+
remCount := remaining.Count()
5961

6062
; Pre-allocate buffer for performance.
61-
; content size + estimated size for updates + null terminator
62-
VarSetCapacity(newContent, (StrLen(content) + updates.Count() * 100 + 1) * (A_IsUnicode ? 2 : 1))
6363
newContent := ""
64+
VarSetCapacity(newContent, (StrLen(content) + remCount * 100 + 1) * (A_IsUnicode ? 2 : 1))
6465

6566
Loop, Parse, content, `n, `r
6667
{
6768
line := A_LoopField
68-
pos := InStr(line, "=")
69-
if (pos > 1){
70-
keyCandidate := RTrim(SubStr(line, 1, pos-1))
71-
if (remaining.HasKey(keyCandidate)){
72-
val := remaining[keyCandidate]
73-
line := keyCandidate "=" val
74-
remaining.Delete(keyCandidate)
69+
if (remCount > 0){
70+
pos := InStr(line, "=")
71+
if (pos > 1){
72+
keyCandidate := RTrim(SubStr(line, 1, pos-1))
73+
if (remaining.HasKey(keyCandidate)){
74+
line := keyCandidate "=" remaining[keyCandidate]
75+
remaining.Delete(keyCandidate)
76+
remCount--
77+
}
7578
}
7679
}
77-
newContent .= line "`n"
80+
newContent .= line
81+
newContent .= "`n"
7882
}
7983

8084
for k, v in remaining {
81-
newContent .= k "=" v "`n"
85+
newContent .= k
86+
newContent .= "="
87+
newContent .= v
88+
newContent .= "`n"
8289
}
8390

8491
return SubStr(newContent, 1, -1)

Other/Citra_mods/Citra_Mod_Manager.ahk

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ root := OneDrive "\Backup\Game\Emul\Citra\nightly-mingw\Mods"
1616

1717
; Read CSV once and cache destinations in an associative array
1818
Destinations := {}
19-
loop Read, % A_ScriptDir "\Destination.csv"
19+
FileRead, csvContent, % A_ScriptDir "\Destination.csv"
20+
loop Parse, csvContent, `n, `r
2021
{
21-
if (InStr(A_LoopReadLine, ","))
22+
if (InStr(A_LoopField, ","))
2223
{
23-
parts := StrSplit(A_LoopReadLine, ",")
24+
parts := StrSplit(A_LoopField, ",")
2425
if (parts.Length() >= 2)
2526
Destinations[parts[1]] := parts[2]
2627
}
@@ -61,12 +62,7 @@ FileActions(Root, Button)
6162
Fullpath := Zielpfad "\" button.Ziel "\" button.Name
6263
Checkdir := Zielpfad "\" button.Ziel
6364
Quellpfad := button.Path
64-
dirHasItems := false
65-
Loop, Files, %Checkdir%\*, DF ;Loop through items in dir
66-
{
67-
dirHasItems := true ;Mark that we found at least one item
68-
Break ;Optimization: Break after finding the first item
69-
}
65+
dirHasItems := FileExist(Checkdir "\*") != ""
7066
If !dirHasItems ;Is directory empty?
7167
{
7268
FileCopyDir, %Quellpfad%, %Fullpath%

Other/Citra_per_game_config/tf.ahk

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -889,12 +889,11 @@ TF_Merge(FileList, Separator = "`n", FileName = "merged.txt")
889889
OW=0
890890
Loop, Parse, FileList, `n, `r
891891
{
892-
Append2File= ; Just make sure it is empty
893-
IfExist, %A_LoopField%
892+
FileRead, Append2File, %A_LoopField%
893+
If not ErrorLevel ; Successfully loaded
894894
{
895-
FileRead, Append2File, %A_LoopField%
896-
If not ErrorLevel ; Successfully loaded
897-
Output .= Append2File Separator
895+
Output .= Append2File
896+
Output .= Separator
898897
}
899898
}
900899

Other/Citra_per_game_config/v2/CitraConfigHelpers.ahk

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
; Escaped string safe for use in regex patterns
1919
; ============================================================================
2020
RegExEscape(str) {
21-
return RegExReplace(str, "([\\()\[\]{}?*+|^$.])", "\$1")
21+
return RegExReplace(str, "([\\()\[\]{}?*+|^$.})", "\\$1")
2222
}
2323

2424
; ============================================================================
@@ -36,9 +36,9 @@ RegExEscape(str) {
3636
; Modified configuration content
3737
; ============================================================================
3838
SetKey(content, key, value) {
39-
pat := "m)^(" . RegExEscape(key) . ")\s*=.*$"
39+
pat := "m)^(" . RegExEscape(key) . ")\\s*=.*$"
4040
if RegExMatch(content, pat)
41-
return RegExReplace(content, pat, "$1=" value, , 1)
41+
return RegExReplace(content, pat, "$1=" StrReplace(value, "$", "$$"), , 1)
4242
else
4343
return content "`n" key "=" value
4444
}

Other/Citra_per_game_config/v2/CitraConfigHelpers_Test.ahk

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ TestSetKey() {
7373
content2 := "path\to\file=exists`n"
7474
result := SetKey(content2, "path\to\file", "updated")
7575
AssertEqual(InStr(result, "path\to\file=updated") > 0, true, "SetKey updates key with backslashes")
76+
77+
; Test 6: Empty value
78+
result := SetKey(content, "key1", "")
79+
AssertEqual(InStr(result, "key1=") > 0 && InStr(result, "key1=value1") == 0, true, "SetKey handles empty value")
80+
81+
; Test 7: Value with literal $ sign
82+
result := SetKey(content, "key1", "val$ue")
83+
AssertEqual(InStr(result, "key1=val$ue") > 0, true, "SetKey handles value with literal $ sign")
84+
85+
; Test 8: Value with literal $1
86+
result := SetKey(content, "key2", "val$1ue")
87+
AssertEqual(InStr(result, "key2=val$1ue") > 0, true, "SetKey handles value with literal $1")
88+
89+
; Test 9: Add new key with $ sign
90+
result := SetKey(content, "key4", "new$val")
91+
AssertEqual(InStr(result, "key4=new$val") > 0, true, "SetKey adds new key with $ sign")
7692
}
7793

7894
TestReplaceInFile() {
@@ -116,7 +132,6 @@ TestReplaceInFile() {
116132
AssertEqual(content, "goodbye world`ngoodbye citra", "ReplaceInFile leaves content unchanged when text not found")
117133

118134
; Test 5: ReplaceInFile handles try/catch error (SaveConfig fails or LoadConfig fails)
119-
; In this case, we can pass a directory to trigger an error
120135
testDir := A_ScriptDir . "\test_replace_dir"
121136
if !DirExist(testDir)
122137
DirCreate(testDir)

Other/Lossless_Scaling_Manager.ahk

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ EnsureAdmin() {
1313
try Run('*RunAs "' A_AhkPath '" "' A_ScriptFullPath '" ' A_Args.Join(" "))
1414
ExitApp
1515
}
16-
EnsureAdmin()
16+
if (A_LineFile == A_ScriptFullPath) {
17+
EnsureAdmin()
18+
}
1719

1820
MinimizeLS() {
1921
try {
@@ -35,23 +37,29 @@ StopLS() {
3537
try ProcessClose("LosslessScaling.exe")
3638
}
3739

38-
ToggleLS() {
39-
if ProcessExist("LosslessScaling.exe")
40-
StopLS()
40+
ToggleLS(mockProcessExist := "", mockStopLS := "", mockStartLS := "") {
41+
fnProcessExist := (mockProcessExist != "") ? mockProcessExist : ProcessExist
42+
fnStopLS := (mockStopLS != "") ? mockStopLS : StopLS
43+
fnStartLS := (mockStartLS != "") ? mockStartLS : StartLS
44+
45+
if fnProcessExist("LosslessScaling.exe")
46+
fnStopLS()
4147
else
42-
StartLS()
48+
fnStartLS()
4349
}
4450

4551
; ---------- Dispatch ----------
46-
cmd := (A_Args.Length ? StrLower(A_Args[1]) : "toggle")
47-
try {
48-
switch cmd {
49-
case "start": StartLS()
50-
case "stop", "close": StopLS()
51-
case "toggle": ToggleLS()
52-
default:
53-
MsgBox("Usage: " A_ScriptName " [start|stop|toggle]")
52+
if (A_LineFile == A_ScriptFullPath) {
53+
cmd := (A_Args.Length ? StrLower(A_Args[1]) : "toggle")
54+
try {
55+
switch cmd {
56+
case "start": StartLS()
57+
case "stop", "close": StopLS()
58+
case "toggle": ToggleLS()
59+
default:
60+
MsgBox("Usage: " A_ScriptName " [start|stop|toggle]")
61+
}
62+
} catch Error as err {
63+
MsgBox("Error: " err.Message)
5464
}
55-
} catch Error as err {
56-
MsgBox("Error: " err.Message)
5765
}

Other/playnite-all.ahk

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ PlayBootVideo() {
1919
vlcPath := MustGetExe("vlc.exe", ["C:\Program Files\VideoLAN\VLC\vlc.exe"])
2020
bootVideo := A_ScriptDir . "\BootVideo.mp4"
2121
vlcArgs := '--fullscreen --video-on-top --play-and-exit --no-video-title -Idummy "' . bootVideo . '"'
22-
RunWait('cmd.exe /c START "" "' . vlcPath . '" ' . vlcArgs, , "hide")
23-
DllCall("kernel32.dll\Sleep", "UInt", 3000)
22+
RunWait('"' . vlcPath . '" ' . vlcArgs, , "hide")
2423
}
2524
LaunchPlaynite() {
2625
playniteExe := MustGetExe(

0 commit comments

Comments
 (0)