feat/hints a471fa4

outputoverrides_test.go RawHistoryFolder

1package core
2
3import (
4	"image"
5	"testing"
6
7	"github.com/bnema/neferwl/internal/ports"
8)
9
10func TestOutputSettingsDisableAndHeadlessMode(t *testing.T) {
11	mode := ports.OutputMode{Width: 1920, Height: 1080, RefreshMilli: 60000}
12	state := NewOutputOverrides(ports.Config{}, true)
13	state.heads = ports.OutputHeads{Heads: []ports.OutputHead{{Info: ports.OutputInfo{Name: "HEADLESS-1"}, Enabled: true, Current: &mode, Modes: []ports.OutputMode{mode}}}}
14	if _, err := state.Apply(ports.OutputApply{Heads: []ports.HeadChange{{Name: "HEADLESS-1", Enabled: false}}}); err == nil {
15		t.Fatal("headless disable accepted")
16	}
17	state.headless = false
18	cfg, err := state.Apply(ports.OutputApply{Heads: []ports.HeadChange{{Name: "HEADLESS-1", Enabled: false}}})
19	if err != nil || !cfg.Outputs[0].Off {
20		t.Fatalf("disable: %+v, %v", cfg, err)
21	}
22}
23
24// A reload that only changes output scales (a saved scale bind) keeps the
25// runtime overrides; any other change drops them.
26func TestReloadScaleOnlyKeepsOverrides(t *testing.T) {
27	mode := ports.OutputMode{Width: 1920, Height: 1080, RefreshMilli: 60000}
28	file := ports.Config{Outputs: []ports.OutputConfig{{Name: "DP-1", Scale: 2}}}
29	state := NewOutputOverrides(file, false)
30	state.heads = ports.OutputHeads{Heads: []ports.OutputHead{{Info: ports.OutputInfo{Name: "DP-1"}, Enabled: true, Current: &mode, Modes: []ports.OutputMode{mode}}, {Info: ports.OutputInfo{Name: "DP-2"}, Enabled: true, Current: &mode, Modes: []ports.OutputMode{mode}}}}
31	if _, err := state.Apply(ports.OutputApply{Heads: []ports.HeadChange{{Name: "DP-1", Enabled: true, Pos: &image.Point{X: 100}}, {Name: "DP-2", Enabled: true}}}); err != nil {
32		t.Fatal(err)
33	}
34	scaled := file
35	scaled.Outputs = []ports.OutputConfig{{Name: "DP-1", Scale: 1.5}, {Name: "DP-2", Scale: 1.25, ScaleOnly: true, SDRBrightness: ports.DefaultSDRBrightness}}
36	cfg := state.Reload(scaled)
37	if len(cfg.Outputs) != 2 || cfg.Outputs[0].Pos == nil || cfg.Outputs[0].Scale != 1.5 || cfg.Outputs[1].Scale != 1.25 {
38		t.Fatalf("scale-only reload: %+v", cfg.Outputs)
39	}
40	other := scaled
41	other.Layout.Gaps = 4
42	if cfg := state.Reload(other); cfg.Outputs[0].Pos != nil {
43		t.Fatalf("other reload kept overrides: %+v", cfg.Outputs)
44	}
45}
46
47// A transform change takes the full reload path; a scale change alone does not.
48func TestScaleChangesTransform(t *testing.T) {
49	old := ports.Config{Outputs: []ports.OutputConfig{{Name: "DP-1", Scale: 2}}}
50	cur := ports.Config{Outputs: []ports.OutputConfig{{Name: "DP-1", Scale: 2, Transform: 1}}}
51	if _, only := scaleChanges(old, cur); only {
52		t.Fatal("a transform change must take the full reload path")
53	}
54	cur.Outputs[0].Transform = 0
55	cur.Outputs[0].Scale = 1.5
56	if _, only := scaleChanges(old, cur); !only {
57		t.Fatal("a scale-only change stays a scale change")
58	}
59}
60
61// A runtime transform lands in the effective configuration and survives a
62// reload that only changes file scales.
63func TestOutputApplyTransform(t *testing.T) {
64	mode := ports.OutputMode{Width: 1920, Height: 1080, RefreshMilli: 60000}
65	file := ports.Config{Outputs: []ports.OutputConfig{{Name: "DP-1", Scale: 2}}}
66	state := NewOutputOverrides(file, false)
67	state.heads = ports.OutputHeads{Heads: []ports.OutputHead{{Info: ports.OutputInfo{Name: "DP-1"}, Enabled: true, Current: &mode, Modes: []ports.OutputMode{mode}}}}
68	tr := ports.BufferTransform(3)
69	cfg, err := state.Apply(ports.OutputApply{Heads: []ports.HeadChange{{Name: "DP-1", Enabled: true, Transform: &tr}}})
70	if err != nil {
71		t.Fatal(err)
72	}
73	if len(cfg.Outputs) != 1 || cfg.Outputs[0].Transform != 3 {
74		t.Fatalf("applied transform: %+v", cfg.Outputs)
75	}
76	scaled := ports.Config{Outputs: []ports.OutputConfig{{Name: "DP-1", Scale: 1.5}}}
77	cfg = state.Reload(scaled)
78	if cfg.Outputs[0].Transform != 3 || cfg.Outputs[0].Scale != 1.5 {
79		t.Fatalf("scale reload lost the runtime transform: %+v", cfg.Outputs)
80	}
81	// Without a transform in the change, the current one stays.
82	cfg, err = state.Apply(ports.OutputApply{Heads: []ports.HeadChange{{Name: "DP-1", Enabled: true}}})
83	if err != nil || cfg.Outputs[0].Transform != 3 {
84		t.Fatalf("unset transform changed: %+v, %v", cfg.Outputs, err)
85	}
86	bad := ports.BufferTransform(8)
87	if _, err := state.Apply(ports.OutputApply{Heads: []ports.HeadChange{{Name: "DP-1", Enabled: true, Transform: &bad}}}); err == nil {
88		t.Fatal("transform 8 accepted")
89	}
90}
91
92// A runtime position never replaces a file relation: it only takes
93// precedence over it in core, and the relation is followed again once the
94// override is dropped.
95func TestOutputApplyKeepsFileAnchor(t *testing.T) {
96	mode := ports.OutputMode{Width: 1920, Height: 1080, RefreshMilli: 60000}
97	anchor := ports.OutputAnchor{Relation: ports.RelationRightOf, To: "DP-1", Offset: 40}
98	file := ports.Config{Outputs: []ports.OutputConfig{{Name: "DP-2", Anchor: anchor, ScaleOnly: true}}}
99	o := NewOutputOverrides(file, false)
100	head := func(name string) ports.OutputHead {
101		return ports.OutputHead{Info: ports.OutputInfo{Name: name}, Enabled: true, Current: &mode, Modes: []ports.OutputMode{mode}}
102	}
103	o.heads = ports.OutputHeads{Heads: []ports.OutputHead{head("DP-1"), head("DP-2")}}
104	change := func(name string, x int) ports.HeadChange {
105		return ports.HeadChange{Name: name, Enabled: true, Pos: &image.Point{X: x}}
106	}
107	byName := func(cfg ports.Config, name string) ports.OutputConfig {
108		t.Helper()
109		var found []ports.OutputConfig
110		for _, v := range cfg.Outputs {
111			if v.Name == name {
112				found = append(found, v)
113			}
114		}
115		if len(found) != 1 {
116			t.Fatalf("%s entries: %+v", name, cfg.Outputs)
117		}
118		return found[0]
119	}
120
121	cfg, err := o.Apply(ports.OutputApply{ID: 1, Heads: []ports.HeadChange{change("DP-1", 100), {Name: "DP-2", Enabled: true}}})
122	if err != nil {
123		t.Fatal(err)
124	}
125	dp1, dp2 := byName(cfg, "DP-1"), byName(cfg, "DP-2")
126	if dp1.Pos == nil || dp1.Pos.X != 100 || dp2.Pos != nil || dp2.Anchor != anchor {
127		t.Fatalf("position on the reference only: DP-1 %+v, DP-2 %+v", dp1, dp2)
128	}
129
130	cfg, err = o.Apply(ports.OutputApply{ID: 2, Heads: []ports.HeadChange{change("DP-1", 100), change("DP-2", 900)}})
131	if err != nil {
132		t.Fatal(err)
133	}
134	dp1, dp2 = byName(cfg, "DP-1"), byName(cfg, "DP-2")
135	if dp1.Pos == nil || dp1.Pos.X != 100 || dp2.Pos == nil || dp2.Pos.X != 900 || dp2.Anchor != anchor {
136		t.Fatalf("position on both: DP-1 %+v, DP-2 %+v", dp1, dp2)
137	}
138
139	// A non scale-only reload drops the overrides: the file relation is back alone.
140	file.Keyboard.RepeatRate = 33
141	cfg = o.Reload(file)
142	if dp2 = byName(cfg, "DP-2"); dp2.Pos != nil || dp2.Anchor != anchor || len(cfg.Outputs) != 1 {
143		t.Fatalf("after reload: %+v", cfg.Outputs)
144	}
145}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close