triggerhappy: the machine's hotkeys as a module holding node-hotkeys (hq ADR 0212)

The laptop's model module owned triggerhappy's trigger file and service, although the daemon is a
general piece others have keys for. triggerhappy now owns the daemon, reads only the mesh's file,
runs every trigger as the account, and the model module contributes its vendor keys.
This commit is contained in:
jochen
2026-10-04 16:50:57 +02:00
parent 2d613ae274
commit afce6b03f4
11 changed files with 607 additions and 45 deletions
@@ -0,0 +1,98 @@
package main
import (
"context"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"time"
)
func fakeRoot(t *testing.T, files map[string]string) string {
t.Helper()
root := t.TempDir()
for p, c := range files {
full := filepath.Join(root, p)
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(full, []byte(c), 0o644); err != nil {
t.Fatal(err)
}
}
return root
}
const meshConf = "# The machine's hotkeys, written by the mesh (module triggerhappy).\n" +
"# asus-zephyrus-g14\nKEY_PROG1\t1\t/usr/local/lib/x/media play-pause\n# a note of several words\nKEY_F21\t1\t/usr/local/lib/x/touchpad reset\n" +
"# other\nKEY_MUTE\t1\tmute\n"
func TestTriggersAreAttributedToTheModuleThatContributedThem(t *testing.T) {
m := &Machine{Root: fakeRoot(t, map[string]string{TriggerDir + "/mesh.conf": meshConf})}
ts, err := m.Triggers()
if err != nil {
t.Fatal(err)
}
var got []string
for _, tr := range ts {
got = append(got, tr.Key+"="+tr.Module+"/"+tr.When)
}
want := []string{"KEY_PROG1=asus-zephyrus-g14/pressed", "KEY_F21=asus-zephyrus-g14/pressed", "KEY_MUTE=other/pressed"}
if !reflect.DeepEqual(got, want) {
t.Fatalf("%v, want %v", got, want)
}
}
func TestAKeyBoundTwiceIsWarnedAboutAndFailsTheCheck(t *testing.T) {
m := &Machine{Root: fakeRoot(t, map[string]string{
TriggerDir + "/mesh.conf": meshConf,
TriggerDir + "/asus-g14.conf": "KEY_PROG1\t1\t/home/op/scripts/as-user media-control play-pause\n",
}), Timeout: time.Second, Run: func(_ context.Context, name string, args ...string) (string, error) {
if strings.Join(args, " ") == "is-active triggerhappy.service" {
return "active\n", nil
}
return "/usr/bin/thd --user op", nil
}}
k, err := m.Keys()
if err != nil {
t.Fatal(err)
}
if len(k.Warnings) != 1 || !strings.Contains(k.Warnings[0], "KEY_PROG1 pressed is bound 2 times") {
t.Fatalf("%v", k.Warnings)
}
c, err := m.Check()
if err != nil {
t.Fatal(err)
}
if c["failing"] != 2 {
t.Fatalf("the predecessor's file and the duplicate should fail: %+v", c)
}
}
func TestADumpSaysEachKeyAndStateOnce(t *testing.T) {
out := "EV_KEY\tKEY_PROG1\t1\t/dev/input/event5\n# KEY_PROG1\t1\tcommand\n" +
"EV_KEY\tKEY_PROG1\t0\t/dev/input/event5\n# KEY_PROG1\t0\tcommand\n# KEY_PROG1\t1\tcommand\n"
m := &Machine{Timeout: time.Second, Run: func(context.Context, string, ...string) (string, error) {
return out, context.DeadlineExceeded
}}
a, err := m.Dump(2)
if err != nil {
t.Fatal(err)
}
if want := []string{"KEY_PROG1 pressed", "KEY_PROG1 released"}; !reflect.DeepEqual(a.Events, want) {
t.Fatalf("%v, want %v", a.Events, want)
}
}
func TestDevicesWithAnEventHandlerAreListed(t *testing.T) {
m := &Machine{Root: fakeRoot(t, map[string]string{"/proc/bus/input/devices": "N: Name=\"Asus WMI hotkeys\"\nH: Handlers=kbd event5\n\nN: Name=\"Nothing\"\nH: Handlers=mouse0\n\n"})}
d, err := m.Devices()
if err != nil {
t.Fatal(err)
}
if len(d) != 1 || d[0].Name != "Asus WMI hotkeys" {
t.Fatalf("%+v", d)
}
}