From 4ea8aed04bcf4d61306cdd722f1b5f30a88720d5 Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Tue, 2 Jun 2026 12:18:59 -0400 Subject: [PATCH] Add pure process-path to class normalization for Windows --- internal/winapi/class.go | 21 +++++++++++++++++++++ internal/winapi/class_test.go | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 internal/winapi/class.go create mode 100644 internal/winapi/class_test.go diff --git a/internal/winapi/class.go b/internal/winapi/class.go new file mode 100644 index 0000000..4a26deb --- /dev/null +++ b/internal/winapi/class.go @@ -0,0 +1,21 @@ +// Package winapi is the Windows Win32 binding layer for AntiDrift's OS ports. +// The syscall-bound code lives in windows-tagged files; this untagged file +// holds the pure path logic so it builds and is tested on every platform. +package winapi + +import "strings" + +// ClassFromImagePath derives the on-task class identity from a process image +// path: the base file name with any trailing ".exe" removed (case-insensitive), +// lowercased. It is the Windows analog of an X11 WM_CLASS. It parses both `\` +// and `/` separators so it is correct regardless of the host OS running the +// test. Returns "" for an empty path. +func ClassFromImagePath(p string) string { + if i := strings.LastIndexAny(p, `\/`); i >= 0 { + p = p[i+1:] + } + if len(p) >= 4 && strings.EqualFold(p[len(p)-4:], ".exe") { + p = p[:len(p)-4] + } + return strings.ToLower(p) +} diff --git a/internal/winapi/class_test.go b/internal/winapi/class_test.go new file mode 100644 index 0000000..a1d9a4b --- /dev/null +++ b/internal/winapi/class_test.go @@ -0,0 +1,21 @@ +package winapi + +import "testing" + +func TestClassFromImagePath(t *testing.T) { + cases := []struct{ in, want string }{ + {`C:\Program Files\Microsoft VS Code\Code.exe`, "code"}, + {`C:\Windows\explorer.exe`, "explorer"}, + {`chrome.exe`, "chrome"}, + {`C:\x\FOO.EXE`, "foo"}, + {`C:\x\My.App.exe`, "my.app"}, + {`C:/forward/slash/Code.exe`, "code"}, + {`firefox`, "firefox"}, + {``, ""}, + } + for _, c := range cases { + if got := ClassFromImagePath(c.in); got != c.want { + t.Errorf("ClassFromImagePath(%q) = %q, want %q", c.in, got, c.want) + } + } +}