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:
Reactorcoremeltdown
2026-11-02 12:30:28 +01:00
co-authored by Claude Opus 5.5
parent d314188247
commit ead82dc777
37 changed files with 2301 additions and 108 deletions
+52 -11
View File
@@ -20,6 +20,14 @@ version_for = $(strip \
$(if $(filter windows,$(1)),$(VERSION_WINDOWS),\
$(if $(filter freebsd openbsd netbsd,$(1)),$(VERSION_BSD),0.0.0)))))
# So is the application identifier, which names the macOS login item.
appid_for = $(strip \
$(if $(filter linux,$(1)),$(APPID_LINUX),\
$(if $(filter darwin,$(1)),$(APPID_MACOS),\
$(if $(filter windows,$(1)),$(APPID_WINDOWS),$(APPID_BSD)))))
client_ldflags = -s -w -X main.Version=$(call version_for,$(1)) -X main.AppID=$(call appid_for,$(1))
os_of = $(word 1,$(subst -, ,$(1)))
arch_of = $(word 2,$(subst -, ,$(1)))
@@ -63,11 +71,23 @@ all: server client ## build the server and client for this host
server: $(HOST_SERVER) ## build the server for this host
client: $(HOST_CLIENT) ## build the desktop client for this host
# The macOS menu bar needs Cocoa, so the client links it with cgo when this
# host is a Mac. Built anywhere else it has every command but `tray`. This
# rule comes before the generic one because make 3.81, which macOS ships,
# takes the first pattern that matches.
DARWIN_CGO := $(if $(filter darwin,$(HOSTOS)),1,0)
$(BUILD)/darwin-%/keeporg: $(GO_SRC)
@mkdir -p $(dir $@)
GOOS=darwin GOARCH=$* CGO_ENABLED=$(DARWIN_CGO) \
$(GO) build $(GOFLAGS) -ldflags "$(call client_ldflags,darwin)" \
-o $@ ./cmd/keeporg
# Linux and the BSDs talk to the tray over D-Bus, which is pure Go.
$(BUILD)/%/keeporg: $(GO_SRC)
@mkdir -p $(dir $@)
GOOS=$(call os_of,$*) GOARCH=$(call arch_of,$*) CGO_ENABLED=0 \
$(GO) build $(GOFLAGS) \
-ldflags "-s -w -X main.Version=$(call version_for,$(call os_of,$*))" \
$(GO) build $(GOFLAGS) -ldflags "$(call client_ldflags,$(call os_of,$*))" \
-o $@ ./cmd/keeporg
$(BUILD)/%/keeporgd: $(GO_SRC)
@@ -77,19 +97,39 @@ $(BUILD)/%/keeporgd: $(GO_SRC)
-ldflags "-s -w -X main.Version=$(VERSION_SERVER)" \
-o $@ ./cmd/keeporgd
# Windows gets the program twice: keeporg.exe is a console program for the
# commands, keeporg-tray.exe the same code linked as a GUI program, so starting
# the tray opens no console window.
$(BUILD)/windows-%/keeporg.exe: $(GO_SRC)
@mkdir -p $(dir $@)
GOOS=windows GOARCH=$* CGO_ENABLED=0 \
$(GO) build $(GOFLAGS) -ldflags "-s -w -X main.Version=$(VERSION_WINDOWS)" \
$(GO) build $(GOFLAGS) -ldflags "$(call client_ldflags,windows)" \
-o $@ ./cmd/keeporg
$(BUILD)/windows-%/keeporg-tray.exe: $(GO_SRC)
@mkdir -p $(dir $@)
GOOS=windows GOARCH=$* CGO_ENABLED=0 \
$(GO) build $(GOFLAGS) -ldflags "$(call client_ldflags,windows) -H windowsgui" \
-o $@ ./cmd/keeporg
# The application icon, drawn by internal/icon, for the Linux icon theme and
# the macOS bundle.
ICONS_HICOLOR := $(BUILD)/icons/hicolor/.stamp
ICONSET := $(BUILD)/icons/KeepOrg.iconset/.stamp
$(ICONS_HICOLOR): internal/icon/icon.go tools/genicon/main.go
rm -rf $(dir $@) && $(GO) run ./tools/genicon -hicolor $(dir $@) && touch $@
$(ICONSET): internal/icon/icon.go tools/genicon/main.go
rm -rf $(dir $@) && $(GO) run ./tools/genicon -iconset $(dir $@) && touch $@
.PHONY: cross
cross: ## build every desktop and server binary
cross: $(BUILD)/linux-amd64/keeporg $(BUILD)/linux-arm64/keeporg \
$(BUILD)/linux-amd64/keeporgd $(BUILD)/linux-arm64/keeporgd \
$(BUILD)/darwin-amd64/keeporg $(BUILD)/darwin-arm64/keeporg \
$(BUILD)/freebsd-amd64/keeporg $(BUILD)/freebsd-arm64/keeporg \
$(BUILD)/windows-amd64/keeporg.exe
$(BUILD)/windows-amd64/keeporg.exe $(BUILD)/windows-amd64/keeporg-tray.exe
# ------------------------------------------------------------------ packaging
@@ -103,7 +143,8 @@ deb-client: $(DEB_CLIENT) ## build the client .deb
deb-server: $(DEB_SERVER) ## build the server .deb
$(DEB_CLIENT): $(BUILD)/linux-$(DEB_ARCH)/keeporg packaging/deb/client/control.in \
packaging/deb/client/changelog packaging/systemd/keeporg.service
packaging/deb/client/changelog packaging/systemd/keeporg.service \
packaging/linux/keeporg.desktop $(ICONS_HICOLOR)
scripts/build-deb.sh client $(VERSION_LINUX) $(DEB_ARCH) $@
$(DEB_SERVER): $(BUILD)/linux-$(DEB_ARCH)/keeporgd packaging/deb/server/control.in \
@@ -116,8 +157,7 @@ DMG := $(DIST)/KeepOrg-$(VERSION_MACOS)-$(DMG_ARCH).dmg
.PHONY: dmg
dmg: $(DMG) ## build the macOS DMG with an /Applications symlink (macOS only)
$(DMG): $(BUILD)/darwin-$(DMG_ARCH)/keeporg packaging/macos/Info.plist.in \
packaging/macos/launcher.sh packaging/macos/launch-agent.plist.in
$(DMG): $(BUILD)/darwin-$(DMG_ARCH)/keeporg packaging/macos/Info.plist.in $(ICONSET)
scripts/build-dmg.sh $(VERSION_MACOS) $(DMG_ARCH) $(APPID_MACOS) $@
ZIP := $(DIST)/keeporg-$(VERSION_WINDOWS)-windows-amd64.zip
@@ -125,10 +165,11 @@ ZIP := $(DIST)/keeporg-$(VERSION_WINDOWS)-windows-amd64.zip
.PHONY: windows
windows: $(ZIP) ## build the Windows zip
$(ZIP): $(BUILD)/windows-amd64/keeporg.exe packaging/windows/README.txt
$(ZIP): $(BUILD)/windows-amd64/keeporg.exe $(BUILD)/windows-amd64/keeporg-tray.exe \
packaging/windows/README.txt packaging/windows/keeporg-task.xml
@mkdir -p $(DIST)
rm -rf $(BUILD)/windows-stage && mkdir -p $(BUILD)/windows-stage
cp $(BUILD)/windows-amd64/keeporg.exe $(BUILD)/windows-stage/
cp $(BUILD)/windows-amd64/keeporg.exe $(BUILD)/windows-amd64/keeporg-tray.exe $(BUILD)/windows-stage/
cp packaging/windows/README.txt $(BUILD)/windows-stage/
cp packaging/windows/keeporg-task.xml $(BUILD)/windows-stage/
cd $(BUILD)/windows-stage && zip -q -r ../../$@ .
@@ -229,8 +270,8 @@ run-server: $(HOST_SERVER) ## run a server on 127.0.0.1:8765 with data in ./.dev
$(HOST_SERVER) -addr 127.0.0.1:8765 -data .devdata -url http://127.0.0.1:8765
.PHONY: run-client
run-client: $(HOST_CLIENT) ## run the sync agent with the local configuration
$(HOST_CLIENT) run
run-client: $(HOST_CLIENT) ## run the tray application with the local configuration
$(HOST_CLIENT) tray
nginx:
cat config/variables/main.json | tpl -f config/nginx/$(APP_NAME).conf.tmpl > /etc/nginx/sites-available/$(APP_NAME).conf