- external list now accepts serial: and size: prefixes; desc: matches serial too. Fixes docked-mode deadlock where Hyprland's synthetic FALLBACK output (created when the built-in is disabled by a stale docked config) was treated as a real external monitor, leaving the daemon stuck in docked after undocking. - modes name resolution gains serial: prefix (resolveSerial). - add -log-level flag for observability. - add unit tests for external matching and name resolution. - update README and example config.
81 lines
2.7 KiB
Go
81 lines
2.7 KiB
Go
package backend
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"testing"
|
|
)
|
|
|
|
func testLogger() *slog.Logger {
|
|
return slog.New(slog.NewTextHandler(testDiscard{}, nil))
|
|
}
|
|
|
|
type testDiscard struct{}
|
|
|
|
func (testDiscard) Write(p []byte) (int, error) { return len(p), nil }
|
|
|
|
func TestResolveMonitorNamesSerial(t *testing.T) {
|
|
current := []MonitorInfo{
|
|
{Name: "eDP-1", Description: "Lenovo", Width: 1920, Height: 1200},
|
|
{Name: "DP-12", Description: "Xiaomi Mi Monitor", Serial: "3342300033911", Width: 2560, Height: 1440},
|
|
{Name: "DP-11", Description: "Xiaomi Mi Monitor", Serial: "", Width: 3440, Height: 1440},
|
|
}
|
|
getMonitors := func(context.Context) ([]MonitorInfo, error) { return current, nil }
|
|
|
|
monitors := []MonitorConfig{
|
|
{Name: "serial:3342300033911", Enabled: true},
|
|
{Name: "eDP-1", Enabled: false},
|
|
}
|
|
resolved, err := resolveMonitorNames(context.Background(), monitors, getMonitors, testLogger())
|
|
if err != nil {
|
|
t.Fatalf("resolveMonitorNames: %v", err)
|
|
}
|
|
if resolved[0].Name != "DP-12" {
|
|
t.Errorf("serial should resolve to DP-12, got %q", resolved[0].Name)
|
|
}
|
|
if resolved[1].Name != "eDP-1" {
|
|
t.Errorf("plain name should pass through, got %q", resolved[1].Name)
|
|
}
|
|
}
|
|
|
|
func TestResolveMonitorNamesSerialNoMatch(t *testing.T) {
|
|
current := []MonitorInfo{
|
|
{Name: "DP-12", Description: "Xiaomi", Serial: "3342300033911"},
|
|
}
|
|
getMonitors := func(context.Context) ([]MonitorInfo, error) { return current, nil }
|
|
|
|
monitors := []MonitorConfig{{Name: "serial:9999999999", Enabled: true}}
|
|
if _, err := resolveMonitorNames(context.Background(), monitors, getMonitors, testLogger()); err == nil {
|
|
t.Error("unresolvable serial should return an error")
|
|
}
|
|
}
|
|
|
|
func TestResolveMonitorNamesSerialMultipleMatches(t *testing.T) {
|
|
current := []MonitorInfo{
|
|
{Name: "DP-1", Serial: "SN-XYZ"},
|
|
{Name: "DP-2", Serial: "SN-XYZ"},
|
|
}
|
|
getMonitors := func(context.Context) ([]MonitorInfo, error) { return current, nil }
|
|
|
|
monitors := []MonitorConfig{{Name: "serial:SN-XYZ", Enabled: true}}
|
|
if _, err := resolveMonitorNames(context.Background(), monitors, getMonitors, testLogger()); err == nil {
|
|
t.Error("ambiguous serial match should return an error")
|
|
}
|
|
}
|
|
|
|
func TestResolveMonitorNamesDescAlsoMatchesSerial(t *testing.T) {
|
|
current := []MonitorInfo{
|
|
{Name: "DP-12", Description: "Generic", Serial: "3342300033911"},
|
|
}
|
|
getMonitors := func(context.Context) ([]MonitorInfo, error) { return current, nil }
|
|
|
|
monitors := []MonitorConfig{{Name: "desc:3342300033911", Enabled: true}}
|
|
resolved, err := resolveMonitorNames(context.Background(), monitors, getMonitors, testLogger())
|
|
if err != nil {
|
|
t.Fatalf("resolveMonitorNames: %v", err)
|
|
}
|
|
if resolved[0].Name != "DP-12" {
|
|
t.Errorf("desc should match via serial, got %q", resolved[0].Name)
|
|
}
|
|
}
|