▍ humdrum codex / ticktock v0.0.2
license AGPL-3.0
3.0 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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//go:build darwin

package autotrack

import (
	"context"
	"os/exec"
	"strings"
	"time"
)

// osTimeout bounds every OS shell-out so a wedged osascript can never stall
// the poll loop (parity with the Python daemon's timeout=4).
const osTimeout = 4 * time.Second

// browsers maps app names to the osascript that reads their active-tab URL.
var browsers = map[string]string{
	"Safari":         `tell application "Safari" to get URL of front document`,
	"Google Chrome":  `tell application "Google Chrome" to get URL of active tab of front window`,
	"Brave Browser":  `tell application "Brave Browser" to get URL of active tab of front window`,
	"Microsoft Edge": `tell application "Microsoft Edge" to get URL of active tab of front window`,
	"Arc":            `tell application "Arc" to get URL of active tab of front window`,
	"Dia":            `tell application "Dia" to get URL of active tab of front window`,
}

// runCmd executes a command with the standard timeout, returning stdout
// ("" on any failure — an OS hiccup must never panic the loop).
func runCmd(name string, args ...string) string {
	ctx, cancel := context.WithTimeout(context.Background(), osTimeout)
	defer cancel()
	out, err := exec.CommandContext(ctx, name, args...).Output()
	if err != nil {
		return ""
	}
	return string(out)
}

func osa(script string) string { return strings.TrimSpace(runCmd("osascript", "-e", script)) }

func frontApp() string {
	return osa(`tell application "System Events" to get name of first application process whose frontmost is true`)
}

func frontTitle() string {
	return osa(`tell application "System Events" to tell (first application process whose frontmost is true) to get name of front window`)
}

func browserURL(app string) string {
	script, ok := browsers[app]
	if !ok {
		return ""
	}
	return osa(script)
}

func idleSecs() int { return parseHIDIdle(runCmd("ioreg", "-c", "IOHIDSystem")) }

func screenLocked() bool {
	return parseLocked(runCmd("ioreg", "-n", "Root", "-d1", "-r", "-k", "CGSSessionScreenIsLocked"))
}

// winctxApp reads the frontmost {app, title} via the bundled AX helper
// (richer titles than System Events). Empties when the helper is missing,
// denied, or errors — the caller falls back to osascript.
func winctxApp() (string, string) {
	bin := HelperPath("winctx")
	if bin == "" {
		return "", ""
	}
	out := strings.TrimSpace(runCmd(bin))
	if out == "" {
		return "", ""
	}
	return parseWinctx([]byte(out))
}

// readContext samples the current foreground context; nil when no app.
// Prefers the AX helper's app+title, falling back to System Events per field.
func readContext() *Ctx {
	app, title := winctxApp()
	if app == "" {
		app = frontApp()
	}
	if app == "" {
		return nil
	}
	if title == "" {
		title = frontTitle()
	}
	u := browserURL(app)
	return &Ctx{App: app, Title: title, URL: u, Domain: Domain(u)}
}

// ReadSample gathers one full OS observation for the poll loop.
func ReadSample() Sample {
	return Sample{Idle: idleSecs(), Raw: readContext(), Locked: screenLocked()}
}