Release / Build and publish (push) Successful in 22s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
101 lines
2.7 KiB
Go
101 lines
2.7 KiB
Go
package linkhandler
|
|
|
|
import (
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// macOS apps: where a link was clicked (the frontmost app; the link
|
|
// handler runs in the background, so it doesn't change it), and which app
|
|
// a process's terminal belongs to. Elsewhere these know nothing.
|
|
|
|
// App is a running macOS app: its bundle path and process ID.
|
|
type App struct {
|
|
Bundle string
|
|
PID int
|
|
}
|
|
|
|
// FrontApp is the frontmost app.
|
|
func FrontApp() App {
|
|
if runtime.GOOS != "darwin" {
|
|
return App{}
|
|
}
|
|
asn, err := exec.Command("lsappinfo", "front").Output()
|
|
if err != nil {
|
|
return App{}
|
|
}
|
|
out, err := exec.Command("lsappinfo", "info", "-only", "pid", "-only", "bundlepath", strings.TrimSpace(string(asn))).Output()
|
|
if err != nil {
|
|
return App{}
|
|
}
|
|
var a App
|
|
for _, l := range strings.Split(string(out), "\n") {
|
|
k, v, _ := strings.Cut(strings.TrimSpace(l), "=")
|
|
switch strings.Trim(k, `"`) {
|
|
case "pid":
|
|
a.PID, _ = strconv.Atoi(v)
|
|
case "LSBundlePath":
|
|
a.Bundle = strings.Trim(v, `"`)
|
|
}
|
|
}
|
|
return a
|
|
}
|
|
|
|
// HostApp is the app bundle among pid's ancestors (the terminal it runs
|
|
// in), "" if none.
|
|
func HostApp(pid int) string {
|
|
if runtime.GOOS != "darwin" {
|
|
return ""
|
|
}
|
|
for range 32 {
|
|
out, err := exec.Command("ps", "-o", "ppid=,comm=", "-p", strconv.Itoa(pid)).Output()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
ppid, comm, _ := strings.Cut(strings.TrimSpace(string(out)), " ")
|
|
// The innermost bundle: kitty's quick access terminal is one
|
|
// inside kitty.app.
|
|
if i := strings.LastIndex(comm, ".app/Contents/MacOS/"); i >= 0 {
|
|
return comm[:i+len(".app")]
|
|
}
|
|
if pid, err = strconv.Atoi(ppid); err != nil || pid <= 1 {
|
|
return ""
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// IsTerminal reports whether bundle is a terminal app.
|
|
func IsTerminal(bundle string) bool {
|
|
name := strings.ToLower(filepath.Base(bundle))
|
|
for _, t := range []string{"kitty", "ghostty", "wezterm", "iterm", "terminal.app", "alacritty", "warp"} {
|
|
if strings.Contains(name, t) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsDropdown reports whether bundle is a terminal shown only by a hotkey
|
|
// (kitty's quick access terminal): activating it doesn't show it.
|
|
func IsDropdown(bundle string) bool { return strings.Contains(bundle, "quick-access") }
|
|
|
|
// ShowDropdown shows a hidden dropdown terminal (it toggles: call it only
|
|
// when the dropdown isn't frontmost).
|
|
func ShowDropdown(bundle string) {
|
|
if i := strings.Index(bundle, "kitty.app"); i >= 0 {
|
|
kitten := bundle[:i+len("kitty.app")] + "/Contents/MacOS/kitten"
|
|
_ = exec.Command(kitten, "quick-access-terminal").Start()
|
|
}
|
|
}
|
|
|
|
// Activate brings the app to the front.
|
|
func Activate(bundle string) {
|
|
if runtime.GOOS == "darwin" && bundle != "" {
|
|
_ = exec.Command("open", "-a", bundle).Run()
|
|
}
|
|
}
|