From e5e8f86550d52171c114c7f3531ce990d610a4e9 Mon Sep 17 00:00:00 2001 From: sumerc Date: Mon, 1 Jun 2026 18:16:20 +0300 Subject: [PATCH 1/2] fix plist conflict for dev --- login/login_darwin.go | 47 +++++++++++++++++--------------- tray/icons.go | 62 +++++++------------------------------------ tray/tray_darwin.go | 4 +-- 3 files changed, 37 insertions(+), 76 deletions(-) diff --git a/login/login_darwin.go b/login/login_darwin.go index 4dfb4d6..10af335 100644 --- a/login/login_darwin.go +++ b/login/login_darwin.go @@ -12,9 +12,9 @@ import ( ) const ( - plistName = "com.zee.app.plist" - appBundle = "/Applications/Zee.app/Contents/MacOS/zee" - bundleSig = ".app/Contents/MacOS/" + plistNameApp = "com.zee.app.plist" // installed /Applications/Zee.app + plistNameDev = "com.zee.app.dev.plist" // local dev build + bundleSig = ".app/Contents/MacOS/" ) func xmlEscape(s string) string { @@ -23,12 +23,31 @@ func xmlEscape(s string) string { return b.String() } +// isRunningFromApp reports whether this binary is the installed Zee.app bundle +// rather than a local dev build. The login item (plist filename, launchd Label, +// and target binary) is keyed off this so a dev build never clobbers — or gets +// clobbered by — the installed app's entry. +func isRunningFromApp() bool { + exe, err := os.Executable() + if err != nil { + return false + } + return strings.Contains(exe, bundleSig) +} + +func plistName() string { + if isRunningFromApp() { + return plistNameApp + } + return plistNameDev +} + func plistPath() (string, error) { home, err := os.UserHomeDir() if err != nil { return "", err } - return filepath.Join(home, "Library", "LaunchAgents", plistName), nil + return filepath.Join(home, "Library", "LaunchAgents", plistName()), nil } func Enabled() bool { @@ -40,24 +59,10 @@ func Enabled() bool { return err == nil } -func bundleExe() (string, error) { - exe, err := os.Executable() - if err != nil { - return "", fmt.Errorf("resolve executable: %w", err) - } - if strings.Contains(exe, bundleSig) { - return exe, nil - } - if _, err := os.Stat(appBundle); err == nil { - return appBundle, nil - } - return "", fmt.Errorf("Zee.app not found in /Applications — install it first") -} - func Enable() error { - exe, err := bundleExe() + exe, err := os.Executable() if err != nil { - return err + return fmt.Errorf("resolve executable: %w", err) } var env strings.Builder @@ -86,7 +91,7 @@ func Enable() error { %s -`, plistName, xmlEscape(exe), env.String()) +`, plistName(), xmlEscape(exe), env.String()) path, err := plistPath() if err != nil { diff --git a/tray/icons.go b/tray/icons.go index e1b4bcf..60e16bb 100644 --- a/tray/icons.go +++ b/tray/icons.go @@ -2,60 +2,16 @@ package tray -import ( - "bytes" - "image" - "image/color" - "image/png" - "math" -) +import _ "embed" -var ( - iconIdle []byte - iconIdleHi []byte - iconRecHi []byte - iconWarnHi []byte -) +//go:embed icon_idle_22.png +var iconIdle []byte -func init() { - transparent := color.RGBA{A: 0} - red := color.RGBA{R: 255, G: 59, B: 48, A: 255} - amber := color.RGBA{R: 255, G: 230, B: 0, A: 255} - dotR := 44.0 / 6.5 - iconIdle = renderIcon(22, &transparent, 22.0/8, nil, 0) - iconIdleHi = renderIcon(44, &transparent, 44.0/8, nil, 0) - iconRecHi = renderIcon(44, &red, dotR, nil, 0) - iconWarnHi = renderIcon(44, &amber, dotR, nil, 0) -} +//go:embed icon_idle_44.png +var iconIdleHi []byte -func encodePNG(img image.Image) []byte { - var buf bytes.Buffer - if err := png.Encode(&buf, img); err != nil { - panic("encodePNG: " + err.Error()) - } - return buf.Bytes() -} - -func drawCircleIcon(img *image.RGBA, size int, dot *color.RGBA, dotR float64, inner *color.RGBA, innerR float64) { - cx, cy := float64(size)/2, float64(size)/2 - r := float64(size)/2 - 1 - for y := range size { - for x := range size { - d := math.Hypot(float64(x)+0.5-cx, float64(y)+0.5-cy) - if inner != nil && d <= innerR { - img.Set(x, y, inner) - } else if dot != nil && d <= dotR { - img.Set(x, y, dot) - } else if d <= r { - img.Set(x, y, color.Black) - } - } - } -} - -func renderIcon(size int, dot *color.RGBA, dotR float64, inner *color.RGBA, innerR float64) []byte { - img := image.NewRGBA(image.Rect(0, 0, size, size)) - drawCircleIcon(img, size, dot, dotR, inner, innerR) - return encodePNG(img) -} +//go:embed icon_rec_44.png +var iconRecHi []byte +//go:embed icon_warn_44.png +var iconWarnHi []byte diff --git a/tray/tray_darwin.go b/tray/tray_darwin.go index 4740a9a..1c0e8b6 100644 --- a/tray/tray_darwin.go +++ b/tray/tray_darwin.go @@ -51,7 +51,7 @@ func updateRecordingIcon(rec bool) { mRecord.SetTitle("● Stop Recording (Shift+Control+Space)") } } else { - systray.SetTemplateIcon(iconIdleHi, iconIdle) + systray.SetIcon(iconIdleHi) if mRecord != nil { mRecord.SetTitle("○ Start Recording (Shift+Control+Space)") } @@ -155,7 +155,7 @@ func RefreshDevices(names []string, selected string) { } func onReady() { - systray.SetTemplateIcon(iconIdleHi, iconIdle) + systray.SetIcon(iconIdleHi) systray.SetTooltip("zee – push to talk") mStatus = systray.AddMenuItem(statusText(), "") From 69dd709c7e6d01002ba869cd1c59aa1568bc591a Mon Sep 17 00:00:00 2001 From: sumerc Date: Tue, 2 Jun 2026 14:10:50 +0300 Subject: [PATCH 2/2] revert icon --- tray/icons.go | 62 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/tray/icons.go b/tray/icons.go index 60e16bb..e1b4bcf 100644 --- a/tray/icons.go +++ b/tray/icons.go @@ -2,16 +2,60 @@ package tray -import _ "embed" +import ( + "bytes" + "image" + "image/color" + "image/png" + "math" +) -//go:embed icon_idle_22.png -var iconIdle []byte +var ( + iconIdle []byte + iconIdleHi []byte + iconRecHi []byte + iconWarnHi []byte +) -//go:embed icon_idle_44.png -var iconIdleHi []byte +func init() { + transparent := color.RGBA{A: 0} + red := color.RGBA{R: 255, G: 59, B: 48, A: 255} + amber := color.RGBA{R: 255, G: 230, B: 0, A: 255} + dotR := 44.0 / 6.5 + iconIdle = renderIcon(22, &transparent, 22.0/8, nil, 0) + iconIdleHi = renderIcon(44, &transparent, 44.0/8, nil, 0) + iconRecHi = renderIcon(44, &red, dotR, nil, 0) + iconWarnHi = renderIcon(44, &amber, dotR, nil, 0) +} -//go:embed icon_rec_44.png -var iconRecHi []byte +func encodePNG(img image.Image) []byte { + var buf bytes.Buffer + if err := png.Encode(&buf, img); err != nil { + panic("encodePNG: " + err.Error()) + } + return buf.Bytes() +} + +func drawCircleIcon(img *image.RGBA, size int, dot *color.RGBA, dotR float64, inner *color.RGBA, innerR float64) { + cx, cy := float64(size)/2, float64(size)/2 + r := float64(size)/2 - 1 + for y := range size { + for x := range size { + d := math.Hypot(float64(x)+0.5-cx, float64(y)+0.5-cy) + if inner != nil && d <= innerR { + img.Set(x, y, inner) + } else if dot != nil && d <= dotR { + img.Set(x, y, dot) + } else if d <= r { + img.Set(x, y, color.Black) + } + } + } +} + +func renderIcon(size int, dot *color.RGBA, dotR float64, inner *color.RGBA, innerR float64) []byte { + img := image.NewRGBA(image.Rect(0, 0, size, size)) + drawCircleIcon(img, size, dot, dotR, inner, innerR) + return encodePNG(img) +} -//go:embed icon_warn_44.png -var iconWarnHi []byte