Give the remaining tray applets their modules, each with one start

openrazer (the official driver, daemon and library), polychromatic (the
AUR tray, kept as found; its i3 line moves out of the i3 module into its
own node-display-session contribution), forticlient (the AUR VPN client:
its service declared, its configuration never read) and nm-applet (the
desktop half of NetworkManager, apart from the server-side module).

The openrazer daemon fails on both workstations because the account is
not in the openrazer group; openrazer_check names it and the README
carries the one-off step, since the account's user resource is zsh's.

desktop.go learns to tell a program from another sharing its 15-character
command name, so polychromatic's tools never count or end themselves, and
a copies test holds the six carriers to one text.
This commit is contained in:
jochen
2026-10-05 15:10:30 +02:00
parent 2a99b3c3e9
commit d0d5546088
59 changed files with 6591 additions and 50 deletions
@@ -0,0 +1,37 @@
package main
// The desktop applications whose bundles carry desktop.go. Each builds alone, so each has its own copy;
// this test, itself one of the copied files, holds them to one text wherever the siblings are present.
import (
"bytes"
"os"
"path/filepath"
"testing"
)
var carriers = []string{"blueman", "forticlient", "nextcloud-client", "nm-applet", "openrazer", "polychromatic", "slack"}
func TestEveryDesktopApplicationCarriesTheSameCopy(t *testing.T) {
compared := 0
for _, module := range carriers {
dir := filepath.Join("..", "..", "..", module, "cmd", module+"-tools")
if _, err := os.Stat(dir); err != nil {
continue
}
for _, f := range []string{"desktop.go", "desktop_test.go", "copies_test.go"} {
mine, err := os.ReadFile(f)
if err != nil {
t.Fatal(err)
}
theirs, err := os.ReadFile(filepath.Join(dir, f))
if err != nil || !bytes.Equal(mine, theirs) {
t.Errorf("%s's copy of %s differs from this one: change every copy together", module, f)
}
}
compared++
}
if compared == 0 {
t.Log("no sibling copies beside this module")
}
}
+36 -10
View File
@@ -1,8 +1,8 @@
package main
// desktop.go is the same file in the nextcloud-client, blueman and slack bundles: a
// tray application of the operator's graphical session, seen from the node's tool runtime (novox/hq
// ADR 0208).
// desktop.go is the same file in every desktop application's bundle (copies_test.go names them and
// holds them to one text): a tray application of the operator's graphical session, seen from the
// node's tool runtime (novox/hq ADR 0208).
//
// The runtime is a system service running as the operator account (ADR 0175): it has the account's
// uid and none of the session's environment. A tool that starts something on the desktop finds the
@@ -252,6 +252,22 @@ func (m *Machine) procs(comm string) []Proc {
return out
}
// procsOf are the account's processes named comm whose program is word. The kernel keeps 15
// characters of a command name, so a longer name can share them with another program's: this bundle's
// own binary among them (polychromatic-tools and polychromatic-tray-applet are both polychromatic-t).
// The program is the first word of the command line, or the second for a script run by its
// interpreter. An empty word keeps every process named comm.
func (m *Machine) procsOf(comm, word string) []Proc {
var out []Proc
for _, p := range m.procs(comm) {
f := strings.Fields(p.Command)
if word == "" || (len(f) > 0 && filepath.Base(f[0]) == word) || (len(f) > 1 && filepath.Base(f[1]) == word) {
out = append(out, p)
}
}
return out
}
// uidOf is the real uid on a process's status, -1 when unreadable.
func (m *Machine) uidOf(dir string) int {
for _, l := range strings.Split(readTrimmed(filepath.Join(dir, "status")), "\n") {
@@ -412,12 +428,19 @@ func (m *Machine) detach(s Session, unit string, argv ...string) error {
// stop ends every process of the account named in comms: SIGTERM, then SIGKILL for what is still
// there after grace. It answers the pids that ended and those that had to be killed.
func (m *Machine) stop(grace time.Duration, comms ...string) (ended, killed []int) {
var pids []int
var ps []Proc
for _, c := range comms {
for _, p := range m.procs(c) {
if m.Kill(p.PID, syscall.SIGTERM) == nil {
pids = append(pids, p.PID)
}
ps = append(ps, m.procs(c)...)
}
return m.stopProcs(grace, ps)
}
// stopProcs ends the processes given, as stop does.
func (m *Machine) stopProcs(grace time.Duration, ps []Proc) (ended, killed []int) {
var pids []int
for _, p := range ps {
if m.Kill(p.PID, syscall.SIGTERM) == nil {
pids = append(pids, p.PID)
}
}
alive := func() []int {
@@ -452,10 +475,13 @@ func (m *Machine) stop(grace time.Duration, comms ...string) (ended, killed []in
}
// waitFor waits up to d for a process of the account named comm, and answers what it found.
func (m *Machine) waitFor(comm string, d time.Duration) []Proc {
func (m *Machine) waitFor(comm string, d time.Duration) []Proc { return m.waitForOf(comm, "", d) }
// waitForOf waits up to d for a process of the account named comm whose program is word (procsOf).
func (m *Machine) waitForOf(comm, word string, d time.Duration) []Proc {
step := 250 * time.Millisecond
for waited := time.Duration(0); ; waited += step {
if p := m.procs(comm); len(p) > 0 || waited >= d {
if p := m.procsOf(comm, word); len(p) > 0 || waited >= d {
return p
}
m.Sleep(step)
@@ -1,7 +1,7 @@
package main
// The fake machine the tests run against, and the tests of desktop.go. The same in the
// nextcloud-client, blueman and slack bundles.
// The fake machine the tests run against, and the tests of desktop.go. The same in every desktop
// application's bundle (copies_test.go).
import (
"context"
@@ -113,6 +113,23 @@ func TestProcessesAreTheAccountsOwnWithWhereAndWhenTheyStarted(t *testing.T) {
}
}
func TestAProgramIsToldFromAnotherSharingItsCutName(t *testing.T) {
f := newFake(t)
f.proc(10, 1000, "polychromatic-t", []string{"/usr/bin/python", "/usr/bin/polychromatic-tray-applet"}, "s.scope")
f.proc(11, 1000, "polychromatic-t", []string{"polychromatic-tray-applet"}, "s.scope")
f.proc(12, 1000, "polychromatic-t", []string{"/usr/lib/mesh/polychromatic-tools"}, "s.scope")
if got := f.procsOf("polychromatic-t", "polychromatic-tray-applet"); len(got) != 2 || got[0].PID != 10 || got[1].PID != 11 {
t.Fatalf("%+v", got)
}
if got := f.procsOf("polychromatic-t", ""); len(got) != 3 {
t.Fatalf("%+v", got)
}
ended, _ := f.stopProcs(time.Second, f.procsOf("polychromatic-t", "polychromatic-tray-applet"))
if len(ended) != 2 || len(f.procs("polychromatic-t")) != 1 {
t.Fatalf("ended %v; the tools' own process must stay", ended)
}
}
func TestTheSessionIsTheWindowManagersAndNoneIsSaidPlainly(t *testing.T) {
f := newFake(t)
if _, err := f.session(); err == nil || !strings.Contains(err.Error(), "no graphical session") {