v0.4.0 b6be3a8

outputconfiguration.go RawHistoryFolder

1package wayland
2
3import (
4	"image"
5	"math"
6
7	wlr "github.com/bnema/go-wayland-bindings/server/wlroutputmanagement"
8	"github.com/bnema/neferwl/internal/ports"
9	"github.com/bnema/purego-libwayland/server"
10)
11
12type outputConfiguration struct {
13	manager         *outputManager
14	res             *wlr.ZwlrOutputConfigurationV1
15	heads           map[string]*headConfiguration
16	used, cancelled bool
17	serial          uint32
18}
19type headConfiguration struct {
20	parent                                                *outputConfiguration
21	res                                                   *wlr.ZwlrOutputConfigurationHeadV1
22	change                                                ports.HeadChange
23	unsupported                                           bool
24	modeSet, positionSet, transformSet, scaleSet, syncSet bool
25}
26
27func (c *outputConfiguration) head(r *wlr.ZwlrOutputHeadV1) (string, bool) {
28	if r == nil || !r.Resource.Alive() || r.Client() != c.res.Client() {
29		return "", false
30	}
31	for name, h := range c.manager.heads {
32		if h.res.Resource == r.Resource {
33			return name, true
34		}
35	}
36	return "", false
37}
38func (c *outputConfiguration) add(r *wlr.ZwlrOutputConfigurationV1, head *wlr.ZwlrOutputHeadV1, id uint32, enabled bool) {
39	if c.used {
40		r.PostError(uint32(wlr.ZwlrOutputConfigurationV1ErrorAlreadyUsed), "configuration already used")
41		return
42	}
43	name, ok := c.head(head)
44	if !ok {
45		r.PostError(uint32(wlr.ZwlrOutputConfigurationV1ErrorAlreadyConfiguredHead), "unknown head")
46		return
47	}
48	if _, exists := c.heads[name]; exists {
49		r.PostError(uint32(wlr.ZwlrOutputConfigurationV1ErrorAlreadyConfiguredHead), "head configured twice")
50		return
51	}
52	h := &headConfiguration{parent: c, change: ports.HeadChange{Name: name, Enabled: enabled}}
53	c.heads[name] = h
54	if enabled {
55		res, err := wlr.NewZwlrOutputConfigurationHeadV1(r.Client(), r.Version(), id, h)
56		if err != nil {
57			delete(c.heads, name)
58			return
59		}
60		h.res = res
61		res.OnDestroy = func() { h.res = nil }
62	}
63}
64func (c *outputConfiguration) EnableHead(r *wlr.ZwlrOutputConfigurationV1, id uint32, head *wlr.ZwlrOutputHeadV1) {
65	c.add(r, head, id, true)
66}
67func (c *outputConfiguration) DisableHead(r *wlr.ZwlrOutputConfigurationV1, head *wlr.ZwlrOutputHeadV1) {
68	c.add(r, head, 0, false)
69}
70func (c *outputConfiguration) Destroy(*wlr.ZwlrOutputConfigurationV1) { c.used = true }
71func (c *outputConfiguration) Test(r *wlr.ZwlrOutputConfigurationV1)  { c.submit(r, true) }
72func (c *outputConfiguration) Apply(r *wlr.ZwlrOutputConfigurationV1) { c.submit(r, false) }
73func (c *outputConfiguration) submit(r *wlr.ZwlrOutputConfigurationV1, test bool) {
74	if c.used {
75		r.PostError(uint32(wlr.ZwlrOutputConfigurationV1ErrorAlreadyUsed), "configuration already used")
76		return
77	}
78	c.used = true
79	if c.manager.s.managementSerial != c.serial {
80		c.cancelled = true
81		r.SendCancelled()
82		return
83	}
84	if len(c.heads) != len(c.manager.heads) {
85		r.PostError(uint32(wlr.ZwlrOutputConfigurationV1ErrorUnconfiguredHead), "not all heads configured")
86		return
87	}
88	request := ports.OutputApply{ID: c.manager.s.nextOutputApply, Test: test}
89	c.manager.s.nextOutputApply++
90	for _, head := range c.manager.s.outputHeads.Heads {
91		change, ok := c.heads[head.Info.Name]
92		if !ok {
93			r.PostError(uint32(wlr.ZwlrOutputConfigurationV1ErrorUnconfiguredHead), "not all heads configured")
94			return
95		}
96		if change.unsupported {
97			r.SendFailed()
98			return
99		}
100		request.Heads = append(request.Heads, change.change)
101	}
102	if c.manager.s.channels.OutputApply == nil {
103		r.SendFailed()
104		return
105	}
106	c.manager.s.outputReplies[request.ID] = c
107	select {
108	case c.manager.s.channels.OutputApply <- request:
109	default:
110		delete(c.manager.s.outputReplies, request.ID)
111		r.SendFailed()
112	}
113}
114func (h *headConfiguration) once(field *bool, r *wlr.ZwlrOutputConfigurationHeadV1) bool {
115	if *field {
116		r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorAlreadySet), "property already set")
117		return false
118	}
119	*field = true
120	return true
121}
122func (h *headConfiguration) SetMode(r *wlr.ZwlrOutputConfigurationHeadV1, mode *wlr.ZwlrOutputModeV1) {
123	if !h.once(&h.modeSet, r) {
124		return
125	}
126	head := h.parent.manager.heads[h.change.Name]
127	if head == nil || mode == nil || !mode.Resource.Alive() {
128		r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorInvalidMode), "invalid mode")
129		return
130	}
131	m, ok := head.modes[mode.Resource]
132	if !ok {
133		r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorInvalidMode), "invalid mode")
134		return
135	}
136	h.change.Mode = &m
137}
138func (h *headConfiguration) SetCustomMode(r *wlr.ZwlrOutputConfigurationHeadV1, w, height, refresh int32) {
139	if !h.once(&h.modeSet, r) {
140		return
141	}
142	if w <= 0 || height <= 0 || refresh < 0 {
143		r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorInvalidCustomMode), "invalid custom mode")
144		return
145	}
146	h.unsupported = true
147	h.change.Mode = &ports.OutputMode{Width: int(w), Height: int(height), RefreshMilli: int(refresh)}
148}
149func (h *headConfiguration) SetPosition(r *wlr.ZwlrOutputConfigurationHeadV1, x, y int32) {
150	if h.once(&h.positionSet, r) {
151		h.change.Pos = &image.Point{X: int(x), Y: int(y)}
152	}
153}
154func (h *headConfiguration) SetTransform(r *wlr.ZwlrOutputConfigurationHeadV1, v int32) {
155	if !h.once(&h.transformSet, r) {
156		return
157	}
158	if v < 0 || v > 7 {
159		r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorInvalidTransform), "invalid transform")
160		return
161	}
162	t := ports.BufferTransform(v)
163	h.change.Transform = &t
164}
165func (h *headConfiguration) SetScale(r *wlr.ZwlrOutputConfigurationHeadV1, v server.Fixed) {
166	if !h.once(&h.scaleSet, r) {
167		return
168	}
169	scale := v.Float()
170	if scale <= 0 || math.IsNaN(scale) {
171		r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorInvalidScale), "invalid scale")
172		return
173	}
174	h.change.Scale = scale
175}
176func (h *headConfiguration) SetAdaptiveSync(r *wlr.ZwlrOutputConfigurationHeadV1, v uint32) {
177	if !h.once(&h.syncSet, r) {
178		return
179	}
180	if v > 1 {
181		r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorInvalidAdaptiveSyncState), "invalid adaptive sync")
182		return
183	}
184	h.unsupported = true
185}

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