▍ humdrum codex / ticktock v0.0.2
license AGPL-3.0
1.8 KB raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env swift
// winctx — print the frontmost app + focused-window title as JSON, for autotrack.
//
// Uses the Accessibility (AX) API, which exposes window titles that AppleScript's
// "System Events … name of front window" misses for many apps (Electron, some
// native apps). Falls back to an empty title when AX is denied or the app has
// none — the daemon then tries its osascript path.
//
// Output: {"app":"<name>","title":"<focused window title>"}
// Requires Accessibility permission (System Settings → Privacy → Accessibility)
// for the process that runs it; without it, title comes back empty (app still works).

import Cocoa
import ApplicationServices

func jsonEsc(_ s: String) -> String {
    var o = ""
    for u in s.unicodeScalars {
        switch u {
        case "\"": o += "\\\""
        case "\\": o += "\\\\"
        case "\n": o += "\\n"
        case "\r": o += "\\r"
        case "\t": o += "\\t"
        default:
            if u.value < 0x20 { o += String(format: "\\u%04x", u.value) }
            else { o.unicodeScalars.append(u) }
        }
    }
    return o
}

func focusedTitle(_ pid: pid_t) -> String {
    let axApp = AXUIElementCreateApplication(pid)
    var winRef: CFTypeRef?
    guard AXUIElementCopyAttributeValue(axApp, kAXFocusedWindowAttribute as CFString, &winRef)
            == .success, let win = winRef else { return "" }
    var titleRef: CFTypeRef?
    guard AXUIElementCopyAttributeValue(win as! AXUIElement, kAXTitleAttribute as CFString,
            &titleRef) == .success, let t = titleRef as? String else { return "" }
    return t
}

guard let app = NSWorkspace.shared.frontmostApplication else {
    print("{\"app\":\"\",\"title\":\"\"}")
    exit(0)
}
let name = app.localizedName ?? ""
let title = focusedTitle(app.processIdentifier)
print("{\"app\":\"\(jsonEsc(name))\",\"title\":\"\(jsonEsc(title))\"}")