Turn the desktop client into a system tray application
Started without a command, keeporg now shows a padlock in the system tray with a menu like midi-webhook's: a status line, the mirror folder, each database with its state, Sync Now, browser sign-in and sign-out, the web interface, the configuration, the log, Start at Login, About and Quit. The commands are unchanged. config.json now carries every client setting (device_label, request_timeout_seconds, ca_file, log_file, notifications), is written in full on first start and is reloaded by the tray when saved. A lock in the mirror directory admits one agent at a time, since the tray, `keeporg run` and a service unit could otherwise race each other. The tray uses fyne.io/systray: pure Go on Linux, BSD and Windows, cgo on macOS only. The server keeps no third-party dependencies. Desktop versions go to 0.5.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d314188247
commit
ead82dc777
@@ -0,0 +1,54 @@
|
||||
package icon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"image/png"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTrayIsOpaqueOnTheBodyAndClearInTheKeyhole(t *testing.T) {
|
||||
img := Tray(32, Brand)
|
||||
if a := img.NRGBAAt(8, 26).A; a != 255 {
|
||||
t.Errorf("body pixel alpha %d, want 255", a)
|
||||
}
|
||||
if a := img.NRGBAAt(16, 20).A; a != 0 {
|
||||
t.Errorf("keyhole pixel alpha %d, want 0", a)
|
||||
}
|
||||
if a := img.NRGBAAt(1, 1).A; a != 0 {
|
||||
t.Errorf("corner pixel alpha %d, want 0", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppHasAWhiteLockOnTheBadge(t *testing.T) {
|
||||
img := App(64)
|
||||
if c := img.NRGBAAt(32, 50); c.R != 255 || c.A != 255 {
|
||||
t.Errorf("lock body %v, want opaque white", c)
|
||||
}
|
||||
if c := img.NRGBAAt(4, 32); c != Brand {
|
||||
t.Errorf("badge edge %v, want %v", c, Brand)
|
||||
}
|
||||
}
|
||||
|
||||
func TestICOHoldsEachSizeAsPNG(t *testing.T) {
|
||||
ico := ICO(Tray(16, Brand), Tray(256, Brand))
|
||||
var hdr [3]uint16
|
||||
if err := binary.Read(bytes.NewReader(ico), binary.LittleEndian, &hdr); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hdr != [3]uint16{0, 1, 2} {
|
||||
t.Fatalf("header %v", hdr)
|
||||
}
|
||||
for i, want := range []int{16, 256} {
|
||||
e := ico[6+16*i:]
|
||||
size := binary.LittleEndian.Uint32(e[8:])
|
||||
off := binary.LittleEndian.Uint32(e[12:])
|
||||
img, err := png.Decode(bytes.NewReader(ico[off : off+size]))
|
||||
if err != nil {
|
||||
t.Fatalf("entry %d: %v", i, err)
|
||||
}
|
||||
if img.Bounds().Dx() != want {
|
||||
t.Errorf("entry %d is %dpx, want %d", i, img.Bounds().Dx(), want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user