package autotrack import ( "encoding/json" "os" "os/exec" "path/filepath" "strconv" "strings" ) // Sample is one OS observation handed to the segmentation loop. type Sample struct { Idle int // HID idle seconds Raw *Ctx // foreground context; nil when no foreground app Locked bool // screen locked (hard, immediate away signal) } // parseHIDIdle extracts HIDIdleTime (nanoseconds) from `ioreg -c IOHIDSystem` // output and returns whole seconds. 0 when absent or malformed. func parseHIDIdle(out string) int { for _, line := range strings.Split(out, "\n") { if !strings.Contains(line, "HIDIdleTime") { continue } i := strings.LastIndex(line, "=") if i < 0 { continue } ns, err := strconv.ParseInt(strings.TrimSpace(line[i+1:]), 10, 64) if err != nil { continue } return int(ns / 1_000_000_000) } return 0 } // parseLocked reports whether ioreg output shows the lock flag. The // CGSSessionScreenIsLocked key is present only while the screen is locked. func parseLocked(out string) bool { return strings.Contains(strings.ReplaceAll(out, " ", ""), `"CGSSessionScreenIsLocked"=Yes`) } // parseWinctx decodes the winctx helper's {"app","title"} JSON. Empty on error. func parseWinctx(b []byte) (app, title string) { var v struct { App string `json:"app"` Title string `json:"title"` } if err := json.Unmarshal(b, &v); err != nil { return "", "" } return v.App, v.Title } // HelperPath finds a bundled native helper (winctx, cal-events): first next // to the symlink-resolved tt binary (make install symlinks ~/.local/bin/tt → // /bin/tt, and the helpers build into /bin), then on $PATH. // "" when not found — callers degrade gracefully. func HelperPath(name string) string { if exe, err := os.Executable(); err == nil { if resolved, rerr := filepath.EvalSymlinks(exe); rerr == nil { p := filepath.Join(filepath.Dir(resolved), name) if st, serr := os.Stat(p); serr == nil && !st.IsDir() { return p } } } if p, err := exec.LookPath(name); err == nil { return p } return "" }