detached dc722bb

refactor(xwm): share one Space with the wayland server and drop dead code

commit
dc722bb0b8eb6a07fabe0d2f03a8e7b7372a273e
author
brice · · 2026-10-22 15:27 UTC
committer
brice · · 2026-10-22 17:54 UTC
parent
8ab1d07
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
16 files · +81 -117
M internal/adapters/wayland/outputs.go +3 -17
@@ -91,25 +91,10 @@ func (o *output) sendXDG(x *xdgoutput.ZxdgOutputV1, withName bool) {
9191 }
9292 }
9393
94-// xOrigin is the layout point that is X (0,0): the top-left of the layout
95-// extents, so every X coordinate is non-negative. A plain copy of the
96-// xwm.Space rule at scale 1 until that type merges.
97-func (s *Server) xOrigin() (ox, oy int) {
98- for i, p := range s.outputPlaces {
99- if i == 0 || p.X < ox {
100- ox = p.X
101- }
102- if i == 0 || p.Y < oy {
103- oy = p.Y
104- }
105- }
106- return ox, oy
107-}
108-
10994 // sendXDGX sends the private xdg-output state: the layout position moved to
11095 // X space and the logical size.
11196 func (o *output) sendXDGX(s *Server, x *xdgoutput.ZxdgOutputV1, withName bool) {
112- ox, oy := s.xOrigin()
97+ ox, oy := s.xspace.Origin()
11398 x.SendLogicalPosition(int32(o.place.X-ox), int32(o.place.Y-oy))
11499 x.SendLogicalSize(int32(o.place.Width), int32(o.place.Height))
115100 if withName && x.Version() >= 2 {
@@ -209,8 +194,9 @@ func (s *Server) setOutputs(c ports.SetOutputs) {
209194 changed := !slices.Equal(s.outputPlaces, c.Outputs)
210195 focusChanged := s.focusedOutput != c.Focused
211196 s.outputPlaces = append(ports.Layout(nil), c.Outputs...)
197+ xChanged := s.xspace.Set(s.outputPlaces)
212198 if s.xwm != nil {
213- s.xwm.SetLayout(s.outputPlaces)
199+ s.xwm.LayoutChanged(xChanged)
214200 }
215201 defer func() {
216202 if changed {
@@ -91,25 +91,10 @@ func (o *output) sendXDG(x *xdgoutput.ZxdgOutputV1, withName bool) {
91 }91 }
92 }92 }
93 93
94-// xOrigin is the layout point that is X (0,0): the top-left of the layout
95-// extents, so every X coordinate is non-negative. A plain copy of the
96-// xwm.Space rule at scale 1 until that type merges.
97-func (s *Server) xOrigin() (ox, oy int) {
98- for i, p := range s.outputPlaces {
99- if i == 0 || p.X < ox {
100- ox = p.X
101- }
102- if i == 0 || p.Y < oy {
103- oy = p.Y
104- }
105- }
106- return ox, oy
107-}
108-
109 // sendXDGX sends the private xdg-output state: the layout position moved to94 // sendXDGX sends the private xdg-output state: the layout position moved to
110 // X space and the logical size.95 // X space and the logical size.
111 func (o *output) sendXDGX(s *Server, x *xdgoutput.ZxdgOutputV1, withName bool) {96 func (o *output) sendXDGX(s *Server, x *xdgoutput.ZxdgOutputV1, withName bool) {
112- ox, oy := s.xOrigin()97+ ox, oy := s.xspace.Origin()
113 x.SendLogicalPosition(int32(o.place.X-ox), int32(o.place.Y-oy))98 x.SendLogicalPosition(int32(o.place.X-ox), int32(o.place.Y-oy))
114 x.SendLogicalSize(int32(o.place.Width), int32(o.place.Height))99 x.SendLogicalSize(int32(o.place.Width), int32(o.place.Height))
115 if withName && x.Version() >= 2 {100 if withName && x.Version() >= 2 {
@@ -209,8 +194,9 @@ func (s *Server) setOutputs(c ports.SetOutputs) {
209 changed := !slices.Equal(s.outputPlaces, c.Outputs)194 changed := !slices.Equal(s.outputPlaces, c.Outputs)
210 focusChanged := s.focusedOutput != c.Focused195 focusChanged := s.focusedOutput != c.Focused
211 s.outputPlaces = append(ports.Layout(nil), c.Outputs...)196 s.outputPlaces = append(ports.Layout(nil), c.Outputs...)
197+ xChanged := s.xspace.Set(s.outputPlaces)
212 if s.xwm != nil {198 if s.xwm != nil {
213- s.xwm.SetLayout(s.outputPlaces)199+ s.xwm.LayoutChanged(xChanged)
214 }200 }
215 defer func() {201 defer func() {
216 if changed {202 if changed {
M internal/adapters/wayland/server.go +1 -0
@@ -293,6 +293,7 @@ type Server struct {
293293 workspaceIDs *workspaceid.IDs
294294 outputHeads ports.OutputHeads
295295 outputPlaces ports.Layout
296+ xspace xwm.Space // the one layout <-> X mapping, shared with the XWM
296297 managementSerial uint32
297298 nextOutputApply uint64
298299 outputReplies map[uint64]*outputConfiguration
@@ -293,6 +293,7 @@ type Server struct {
293 workspaceIDs *workspaceid.IDs293 workspaceIDs *workspaceid.IDs
294 outputHeads ports.OutputHeads294 outputHeads ports.OutputHeads
295 outputPlaces ports.Layout295 outputPlaces ports.Layout
296+ xspace xwm.Space // the one layout <-> X mapping, shared with the XWM
296 managementSerial uint32297 managementSerial uint32
297 nextOutputApply uint64298 nextOutputApply uint64
298 outputReplies map[uint64]*outputConfiguration299 outputReplies map[uint64]*outputConfiguration
M internal/adapters/wayland/x11window.go +4 -3
@@ -97,6 +97,7 @@ func (xwaylandSurface) Destroy(*xwaylandshell.XwaylandSurfaceV1) {}
9797 func (s *Server) installXWM(x *xwm.XWM) {
9898 s.teardownXWM()
9999 s.xwm = x
100+ x.UseSpace(&s.xspace)
100101 if err := x.Attach(s.display, hostImpl{s}); err != nil {
101102 s.log.Warn().Err(err).Msg("X11 window management disabled")
102103 s.xwm = nil
@@ -109,7 +110,7 @@ func (s *Server) installXWM(x *xwm.XWM) {
109110 x.SetSerial(surf.identity, surf.x11Serial)
110111 }
111112 }
112- x.SetLayout(s.outputPlaces)
113+ x.LayoutChanged(true)
113114 }
114115
115116 // teardownXWM closes the window manager, dropping its windows. Idempotent.
@@ -329,7 +330,7 @@ func (h hostImpl) OverrideMapped(slot xwm.Slot, xid uint32, surfaceID uint64, x,
329330 return
330331 }
331332 if old := s.x11Slots[slot]; old != nil {
332- h.OverrideGone(slot, true)
333+ h.OverrideGone(slot)
333334 }
334335 win := &window{id: s.nextWindow}
335336 win.x11 = &x11Window{server: s, slot: slot, xid: xid, override: true, noInput: noInput, rect: ports.Rect{X: x, Y: y, W: w, H: h2}}
@@ -372,7 +373,7 @@ func (h hostImpl) OverrideMoved(slot xwm.Slot, x, y, w, h2 int) {
372373 }
373374
374375 // OverrideGone ends an override window: unmapped, or destroyed.
375-func (h hostImpl) OverrideGone(slot xwm.Slot, destroyed bool) {
376+func (h hostImpl) OverrideGone(slot xwm.Slot) {
376377 s := h.s
377378 win := s.x11Slots[slot]
378379 if win == nil || win.x11 == nil || !win.x11.override {
@@ -97,6 +97,7 @@ func (xwaylandSurface) Destroy(*xwaylandshell.XwaylandSurfaceV1) {}
97 func (s *Server) installXWM(x *xwm.XWM) {97 func (s *Server) installXWM(x *xwm.XWM) {
98 s.teardownXWM()98 s.teardownXWM()
99 s.xwm = x99 s.xwm = x
100+ x.UseSpace(&s.xspace)
100 if err := x.Attach(s.display, hostImpl{s}); err != nil {101 if err := x.Attach(s.display, hostImpl{s}); err != nil {
101 s.log.Warn().Err(err).Msg("X11 window management disabled")102 s.log.Warn().Err(err).Msg("X11 window management disabled")
102 s.xwm = nil103 s.xwm = nil
@@ -109,7 +110,7 @@ func (s *Server) installXWM(x *xwm.XWM) {
109 x.SetSerial(surf.identity, surf.x11Serial)110 x.SetSerial(surf.identity, surf.x11Serial)
110 }111 }
111 }112 }
112- x.SetLayout(s.outputPlaces)113+ x.LayoutChanged(true)
113 }114 }
114 115
115 // teardownXWM closes the window manager, dropping its windows. Idempotent.116 // teardownXWM closes the window manager, dropping its windows. Idempotent.
@@ -329,7 +330,7 @@ func (h hostImpl) OverrideMapped(slot xwm.Slot, xid uint32, surfaceID uint64, x,
329 return330 return
330 }331 }
331 if old := s.x11Slots[slot]; old != nil {332 if old := s.x11Slots[slot]; old != nil {
332- h.OverrideGone(slot, true)333+ h.OverrideGone(slot)
333 }334 }
334 win := &window{id: s.nextWindow}335 win := &window{id: s.nextWindow}
335 win.x11 = &x11Window{server: s, slot: slot, xid: xid, override: true, noInput: noInput, rect: ports.Rect{X: x, Y: y, W: w, H: h2}}336 win.x11 = &x11Window{server: s, slot: slot, xid: xid, override: true, noInput: noInput, rect: ports.Rect{X: x, Y: y, W: w, H: h2}}
@@ -372,7 +373,7 @@ func (h hostImpl) OverrideMoved(slot xwm.Slot, x, y, w, h2 int) {
372 }373 }
373 374
374 // OverrideGone ends an override window: unmapped, or destroyed.375 // OverrideGone ends an override window: unmapped, or destroyed.
375-func (h hostImpl) OverrideGone(slot xwm.Slot, destroyed bool) {376+func (h hostImpl) OverrideGone(slot xwm.Slot) {
376 s := h.s377 s := h.s
377 win := s.x11Slots[slot]378 win := s.x11Slots[slot]
378 if win == nil || win.x11 == nil || !win.x11.override {379 if win == nil || win.x11 == nil || !win.x11.override {
M internal/adapters/wayland/xdgoutputx_test.go +9 -0
@@ -132,3 +132,12 @@ func TestGlobalFilterAllocations(t *testing.T) {
132132 t.Errorf("globalVisible: %.1f allocs, want 0", allocs)
133133 }
134134 }
135+
136+// The X origin is derived by one Space, kept without a window manager.
137+func TestXSpaceFollowsLayoutWithoutXWM(t *testing.T) {
138+ r := newX11Rig(t)
139+ applyOutputs(t, r.s, r.commands, twoOutputs(-1920))
140+ if ox, oy := r.s.xspace.Origin(); ox != -1920 || oy != 0 {
141+ t.Fatalf("origin %d,%d, want -1920,0", ox, oy)
142+ }
143+}
@@ -132,3 +132,12 @@ func TestGlobalFilterAllocations(t *testing.T) {
132 t.Errorf("globalVisible: %.1f allocs, want 0", allocs)132 t.Errorf("globalVisible: %.1f allocs, want 0", allocs)
133 }133 }
134 }134 }
135+
136+// The X origin is derived by one Space, kept without a window manager.
137+func TestXSpaceFollowsLayoutWithoutXWM(t *testing.T) {
138+ r := newX11Rig(t)
139+ applyOutputs(t, r.s, r.commands, twoOutputs(-1920))
140+ if ox, oy := r.s.xspace.Origin(); ox != -1920 || oy != 0 {
141+ t.Fatalf("origin %d,%d, want -1920,0", ox, oy)
142+ }
143+}
M internal/adapters/wayland/xwm/configure.go +13 -1
@@ -106,7 +106,19 @@ func (x *XWM) SetLayout(l ports.Layout) {
106106 if x.closed {
107107 return
108108 }
109- changed := x.space.Set(l)
109+ x.LayoutChanged(x.space.Set(l))
110+}
111+
112+// UseSpace makes sp the coordinate mapping, owned by the host, which calls
113+// Set on it and then LayoutChanged. Call before Attach.
114+func (x *XWM) UseSpace(sp *Space) { x.space = sp }
115+
116+// LayoutChanged repositions windows after the shared Space was set; changed
117+// is Set's result. Display goroutine only.
118+func (x *XWM) LayoutChanged(changed bool) {
119+ if x.closed {
120+ return
121+ }
110122 if n := x.space.Dropped(); n > 0 && !x.warnedSpace {
111123 x.warnedSpace = true
112124 x.log.Warn().Int("dropped", n).Int("max", maxRegions).Msg("more outputs than the X11 layout holds, extra outputs ignored")
@@ -106,7 +106,19 @@ func (x *XWM) SetLayout(l ports.Layout) {
106 if x.closed {106 if x.closed {
107 return107 return
108 }108 }
109- changed := x.space.Set(l)109+ x.LayoutChanged(x.space.Set(l))
110+}
111+
112+// UseSpace makes sp the coordinate mapping, owned by the host, which calls
113+// Set on it and then LayoutChanged. Call before Attach.
114+func (x *XWM) UseSpace(sp *Space) { x.space = sp }
115+
116+// LayoutChanged repositions windows after the shared Space was set; changed
117+// is Set's result. Display goroutine only.
118+func (x *XWM) LayoutChanged(changed bool) {
119+ if x.closed {
120+ return
121+ }
110 if n := x.space.Dropped(); n > 0 && !x.warnedSpace {122 if n := x.space.Dropped(); n > 0 && !x.warnedSpace {
111 x.warnedSpace = true123 x.warnedSpace = true
112 x.log.Warn().Int("dropped", n).Int("max", maxRegions).Msg("more outputs than the X11 layout holds, extra outputs ignored")124 x.log.Warn().Int("dropped", n).Int("max", maxRegions).Msg("more outputs than the X11 layout holds, extra outputs ignored")
M internal/adapters/wayland/xwm/events.go +2 -3
@@ -173,9 +173,9 @@ func (x *XWM) onCreateNotify(ev *xturbo.Event) {
173173 return
174174 }
175175 r := x.tab.rec(s)
176- r.override = e.OverrideRedirect()
176+ override := e.OverrideRedirect()
177177 r.x, r.y, r.w, r.h = int32(e.X()), int32(e.Y()), uint32(e.Width()), uint32(e.Height())
178- if r.override {
178+ if override {
179179 r.kind = kindUnmanaged
180180 x.stats.UnmanagedSeen++
181181 return
@@ -232,7 +232,6 @@ func (x *XWM) onAttrReply(rec *record, r *xturbo.Reply) {
232232 return
233233 }
234234 if a.OverrideRedirect() {
235- rec.override = true
236235 rec.kind = kindUnmanaged
237236 rec.mapped = a.MapState() == xproto.MapStateViewable
238237 x.stats.UnmanagedSeen++
@@ -173,9 +173,9 @@ func (x *XWM) onCreateNotify(ev *xturbo.Event) {
173 return173 return
174 }174 }
175 r := x.tab.rec(s)175 r := x.tab.rec(s)
176- r.override = e.OverrideRedirect()176+ override := e.OverrideRedirect()
177 r.x, r.y, r.w, r.h = int32(e.X()), int32(e.Y()), uint32(e.Width()), uint32(e.Height())177 r.x, r.y, r.w, r.h = int32(e.X()), int32(e.Y()), uint32(e.Width()), uint32(e.Height())
178- if r.override {178+ if override {
179 r.kind = kindUnmanaged179 r.kind = kindUnmanaged
180 x.stats.UnmanagedSeen++180 x.stats.UnmanagedSeen++
181 return181 return
@@ -232,7 +232,6 @@ func (x *XWM) onAttrReply(rec *record, r *xturbo.Reply) {
232 return232 return
233 }233 }
234 if a.OverrideRedirect() {234 if a.OverrideRedirect() {
235- rec.override = true
236 rec.kind = kindUnmanaged235 rec.kind = kindUnmanaged
237 rec.mapped = a.MapState() == xproto.MapStateViewable236 rec.mapped = a.MapState() == xproto.MapStateViewable
238 x.stats.UnmanagedSeen++237 x.stats.UnmanagedSeen++
M internal/adapters/wayland/xwm/focus.go +4 -1
@@ -158,6 +158,9 @@ func (x *XWM) removeClient(xid uint32) {
158158 // queue when it stops being mapped (unmap, destroy). It is idempotent.
159159 func (x *XWM) dropWindow(s Slot) {
160160 r := x.tab.rec(s)
161+ if r != nil {
162+ r.ovQueued = false // a reused slot must not inherit a queued report
163+ }
161164 if r == nil || r.kind != kindManaged {
162165 return
163166 }
@@ -183,7 +186,7 @@ func (x *XWM) forgetWindow(s Slot) {
183186 x.dropWindow(s)
184187 if r.kind == kindUnmanaged && r.ovPaired && x.host != nil {
185188 r.ovPaired = false
186- x.host.OverrideGone(s, true)
189+ x.host.OverrideGone(s)
187190 }
188191 if r.kind == kindManaged && r.id != 0 && x.host != nil {
189192 r.id = 0
@@ -158,6 +158,9 @@ func (x *XWM) removeClient(xid uint32) {
158 // queue when it stops being mapped (unmap, destroy). It is idempotent.158 // queue when it stops being mapped (unmap, destroy). It is idempotent.
159 func (x *XWM) dropWindow(s Slot) {159 func (x *XWM) dropWindow(s Slot) {
160 r := x.tab.rec(s)160 r := x.tab.rec(s)
161+ if r != nil {
162+ r.ovQueued = false // a reused slot must not inherit a queued report
163+ }
161 if r == nil || r.kind != kindManaged {164 if r == nil || r.kind != kindManaged {
162 return165 return
163 }166 }
@@ -183,7 +186,7 @@ func (x *XWM) forgetWindow(s Slot) {
183 x.dropWindow(s)186 x.dropWindow(s)
184 if r.kind == kindUnmanaged && r.ovPaired && x.host != nil {187 if r.kind == kindUnmanaged && r.ovPaired && x.host != nil {
185 r.ovPaired = false188 r.ovPaired = false
186- x.host.OverrideGone(s, true)189+ x.host.OverrideGone(s)
187 }190 }
188 if r.kind == kindManaged && r.id != 0 && x.host != nil {191 if r.kind == kindManaged && r.id != 0 && x.host != nil {
189 r.id = 0192 r.id = 0
M internal/adapters/wayland/xwm/host.go +2 -2
@@ -46,8 +46,8 @@ type host interface {
4646 OverrideMapped(slot Slot, xid uint32, surfaceID uint64, x, y, w, h int, noInput bool)
4747 // OverrideMoved reports a changed layout rect of a mapped override window.
4848 OverrideMoved(slot Slot, x, y, w, h int)
49- // OverrideGone ends a mapped override window: unmapped, or destroyed.
50- OverrideGone(slot Slot, destroyed bool)
49+ // OverrideGone ends a mapped override window: unmapped or destroyed.
50+ OverrideGone(slot Slot)
5151 }
5252
5353 // fdSource is the display's event source for the X socket (*server.Source).
@@ -46,8 +46,8 @@ type host interface {
46 OverrideMapped(slot Slot, xid uint32, surfaceID uint64, x, y, w, h int, noInput bool)46 OverrideMapped(slot Slot, xid uint32, surfaceID uint64, x, y, w, h int, noInput bool)
47 // OverrideMoved reports a changed layout rect of a mapped override window.47 // OverrideMoved reports a changed layout rect of a mapped override window.
48 OverrideMoved(slot Slot, x, y, w, h int)48 OverrideMoved(slot Slot, x, y, w, h int)
49- // OverrideGone ends a mapped override window: unmapped, or destroyed.49+ // OverrideGone ends a mapped override window: unmapped or destroyed.
50- OverrideGone(slot Slot, destroyed bool)50+ OverrideGone(slot Slot)
51 }51 }
52 52
53 // fdSource is the display's event source for the X socket (*server.Source).53 // fdSource is the display's event source for the X socket (*server.Source).
M internal/adapters/wayland/xwm/host_mock_test.go +6 -12
@@ -287,8 +287,8 @@ func (_c *mockhost_Fullscreen_Call) RunAndReturn(run func(slot Slot, on bool)) *
287287 }
288288
289289 // OverrideGone provides a mock function for the type mockhost
290-func (_mock *mockhost) OverrideGone(slot Slot, destroyed bool) {
291- _mock.Called(slot, destroyed)
290+func (_mock *mockhost) OverrideGone(slot Slot) {
291+ _mock.Called(slot)
292292 return
293293 }
294294
@@ -299,24 +299,18 @@ type mockhost_OverrideGone_Call struct {
299299
300300 // OverrideGone is a helper method to define mock.On call
301301 // - slot Slot
302-// - destroyed bool
303-func (_e *mockhost_Expecter) OverrideGone(slot any, destroyed any) *mockhost_OverrideGone_Call {
304- return &mockhost_OverrideGone_Call{Call: _e.mock.On("OverrideGone", slot, destroyed)}
302+func (_e *mockhost_Expecter) OverrideGone(slot any) *mockhost_OverrideGone_Call {
303+ return &mockhost_OverrideGone_Call{Call: _e.mock.On("OverrideGone", slot)}
305304 }
306305
307-func (_c *mockhost_OverrideGone_Call) Run(run func(slot Slot, destroyed bool)) *mockhost_OverrideGone_Call {
306+func (_c *mockhost_OverrideGone_Call) Run(run func(slot Slot)) *mockhost_OverrideGone_Call {
308307 _c.Call.Run(func(args mock.Arguments) {
309308 var arg0 Slot
310309 if args[0] != nil {
311310 arg0 = args[0].(Slot)
312311 }
313- var arg1 bool
314- if args[1] != nil {
315- arg1 = args[1].(bool)
316- }
317312 run(
318313 arg0,
319- arg1,
320314 )
321315 })
322316 return _c
@@ -327,7 +321,7 @@ func (_c *mockhost_OverrideGone_Call) Return() *mockhost_OverrideGone_Call {
327321 return _c
328322 }
329323
330-func (_c *mockhost_OverrideGone_Call) RunAndReturn(run func(slot Slot, destroyed bool)) *mockhost_OverrideGone_Call {
324+func (_c *mockhost_OverrideGone_Call) RunAndReturn(run func(slot Slot)) *mockhost_OverrideGone_Call {
331325 _c.Run(run)
332326 return _c
333327 }
@@ -287,8 +287,8 @@ func (_c *mockhost_Fullscreen_Call) RunAndReturn(run func(slot Slot, on bool)) *
287 }287 }
288 288
289 // OverrideGone provides a mock function for the type mockhost289 // OverrideGone provides a mock function for the type mockhost
290-func (_mock *mockhost) OverrideGone(slot Slot, destroyed bool) {290+func (_mock *mockhost) OverrideGone(slot Slot) {
291- _mock.Called(slot, destroyed)291+ _mock.Called(slot)
292 return292 return
293 }293 }
294 294
@@ -299,24 +299,18 @@ type mockhost_OverrideGone_Call struct {
299 299
300 // OverrideGone is a helper method to define mock.On call300 // OverrideGone is a helper method to define mock.On call
301 // - slot Slot301 // - slot Slot
302-// - destroyed bool302+func (_e *mockhost_Expecter) OverrideGone(slot any) *mockhost_OverrideGone_Call {
303-func (_e *mockhost_Expecter) OverrideGone(slot any, destroyed any) *mockhost_OverrideGone_Call {303+ return &mockhost_OverrideGone_Call{Call: _e.mock.On("OverrideGone", slot)}
304- return &mockhost_OverrideGone_Call{Call: _e.mock.On("OverrideGone", slot, destroyed)}
305 }304 }
306 305
307-func (_c *mockhost_OverrideGone_Call) Run(run func(slot Slot, destroyed bool)) *mockhost_OverrideGone_Call {306+func (_c *mockhost_OverrideGone_Call) Run(run func(slot Slot)) *mockhost_OverrideGone_Call {
308 _c.Call.Run(func(args mock.Arguments) {307 _c.Call.Run(func(args mock.Arguments) {
309 var arg0 Slot308 var arg0 Slot
310 if args[0] != nil {309 if args[0] != nil {
311 arg0 = args[0].(Slot)310 arg0 = args[0].(Slot)
312 }311 }
313- var arg1 bool
314- if args[1] != nil {
315- arg1 = args[1].(bool)
316- }
317 run(312 run(
318 arg0,313 arg0,
319- arg1,
320 )314 )
321 })315 })
322 return _c316 return _c
@@ -327,7 +321,7 @@ func (_c *mockhost_OverrideGone_Call) Return() *mockhost_OverrideGone_Call {
327 return _c321 return _c
328 }322 }
329 323
330-func (_c *mockhost_OverrideGone_Call) RunAndReturn(run func(slot Slot, destroyed bool)) *mockhost_OverrideGone_Call {324+func (_c *mockhost_OverrideGone_Call) RunAndReturn(run func(slot Slot)) *mockhost_OverrideGone_Call {
331 _c.Run(run)325 _c.Run(run)
332 return _c326 return _c
333 }327 }
M internal/adapters/wayland/xwm/mapflow.go +2 -3
@@ -256,9 +256,8 @@ func (x *XWM) onReparentNotify(ev *xturbo.Event) {
256256 return
257257 }
258258 rec := x.tab.rec(s)
259- rec.override = e.OverrideRedirect()
260259 rec.x, rec.y = int32(e.X()), int32(e.Y())
261- if rec.override {
260+ if e.OverrideRedirect() {
262261 rec.kind = kindUnmanaged
263262 x.stats.UnmanagedSeen++
264263 return
@@ -348,7 +347,7 @@ func (x *XWM) SurfaceGone(surfaceID uint64) {
348347 r.surface = 0
349348 if r.kind == kindUnmanaged && r.ovPaired {
350349 r.ovPaired = false
351- x.host.OverrideGone(Slot(i+1), false)
350+ x.host.OverrideGone(Slot(i + 1))
352351 }
353352 }
354353 }
@@ -256,9 +256,8 @@ func (x *XWM) onReparentNotify(ev *xturbo.Event) {
256 return256 return
257 }257 }
258 rec := x.tab.rec(s)258 rec := x.tab.rec(s)
259- rec.override = e.OverrideRedirect()
260 rec.x, rec.y = int32(e.X()), int32(e.Y())259 rec.x, rec.y = int32(e.X()), int32(e.Y())
261- if rec.override {260+ if e.OverrideRedirect() {
262 rec.kind = kindUnmanaged261 rec.kind = kindUnmanaged
263 x.stats.UnmanagedSeen++262 x.stats.UnmanagedSeen++
264 return263 return
@@ -348,7 +347,7 @@ func (x *XWM) SurfaceGone(surfaceID uint64) {
348 r.surface = 0347 r.surface = 0
349 if r.kind == kindUnmanaged && r.ovPaired {348 if r.kind == kindUnmanaged && r.ovPaired {
350 r.ovPaired = false349 r.ovPaired = false
351- x.host.OverrideGone(Slot(i+1), false)350+ x.host.OverrideGone(Slot(i + 1))
352 }351 }
353 }352 }
354 }353 }
M internal/adapters/wayland/xwm/space.go +10 -42
@@ -1,18 +1,13 @@
11 package xwm
22
3-import (
4- "math"
5-
6- "github.com/bnema/neferwl/internal/ports"
7-)
3+import "github.com/bnema/neferwl/internal/ports"
84
95 const maxRegions = 16
106
117 // Region is one output's part of the layout and where it is in X space.
12-// X = X.Origin + (layout - Layout.Origin) * Scale. Scale is 1 in v1.
8+// X = layout - Origin.
139 type Region struct {
1410 Layout, X ports.Rect // logical layout rect; X rect in X pixels
15- Scale float64 // 1 in v1; reserved for native resolution
1611 }
1712
1813 // Space maps layout coordinates (which may be negative) to X coordinates
@@ -20,14 +15,14 @@ type Region struct {
2015 type Space struct {
2116 regions [maxRegions]Region
2217 n int
23- dropped int // outputs beyond maxRegions in the last Set
24- ox, oy int // layout point that is X (0,0)
25- w, h int // X screen size: the extents
26- gen uint32 // bumped on every change
18+ dropped int // outputs beyond maxRegions in the last Set
19+ ox, oy int // layout point that is X (0,0)
20+ w, h int // X screen size: the extents
2721 }
2822
29-// Gen returns the change counter.
30-func (s *Space) Gen() uint32 { return s.gen }
23+// Origin is the layout point that is X (0,0): the top-left of the layout
24+// extents.
25+func (s *Space) Origin() (ox, oy int) { return s.ox, s.oy }
3126
3227 // Dropped is how many outputs of the last Set did not fit.
3328 func (s *Space) Dropped() int { return s.dropped }
@@ -62,7 +57,6 @@ func (s *Space) Set(l ports.Layout) bool {
6257 next.regions[i] = Region{
6358 Layout: ports.Rect{X: o.X, Y: o.Y, W: o.Width, H: o.Height},
6459 X: ports.Rect{X: o.X - next.ox, Y: o.Y - next.oy, W: o.Width, H: o.Height},
65- Scale: 1,
6660 }
6761 }
6862 next.w, next.h = maxX-next.ox, maxY-next.oy
@@ -71,7 +65,6 @@ func (s *Space) Set(l ports.Layout) bool {
7165 s.dropped = next.dropped
7266 return false
7367 }
74- next.gen = s.gen + 1
7568 *s = next
7669 return true
7770 }
@@ -128,7 +121,7 @@ func (s *Space) ToX(lx, ly int) (x, y int) {
128121 return lx, ly
129122 }
130123 r := &s.regions[i]
131- return r.X.X + scaled(lx-r.Layout.X, r.Scale), r.X.Y + scaled(ly-r.Layout.Y, r.Scale)
124+ return r.X.X + lx - r.Layout.X, r.X.Y + ly - r.Layout.Y
132125 }
133126
134127 // ToLayout converts an X point to layout coordinates.
@@ -138,30 +131,5 @@ func (s *Space) ToLayout(x, y int) (lx, ly int) {
138131 return x, y
139132 }
140133 r := &s.regions[i]
141- return r.Layout.X + unscaled(x-r.X.X, r.Scale), r.Layout.Y + unscaled(y-r.X.Y, r.Scale)
142-}
143-
144-// RegionAtLayout returns the region containing the layout point.
145-func (s *Space) RegionAtLayout(lx, ly int) (Region, bool) {
146- for i := 0; i < s.n; i++ {
147- r := s.regions[i].Layout
148- if lx >= r.X && lx < r.X+r.W && ly >= r.Y && ly < r.Y+r.H {
149- return s.regions[i], true
150- }
151- }
152- return Region{}, false
153-}
154-
155-func scaled(v int, sc float64) int {
156- if sc == 1 || sc == 0 {
157- return v
158- }
159- return int(math.Round(float64(v) * sc))
160-}
161-
162-func unscaled(v int, sc float64) int {
163- if sc == 1 || sc == 0 {
164- return v
165- }
166- return int(math.Round(float64(v) / sc))
134+ return r.Layout.X + x - r.X.X, r.Layout.Y + y - r.X.Y
167135 }
@@ -1,18 +1,13 @@
1 package xwm1 package xwm
2 2
3-import (3+import "github.com/bnema/neferwl/internal/ports"
4- "math"
5-
6- "github.com/bnema/neferwl/internal/ports"
7-)
8 4
9 const maxRegions = 165 const maxRegions = 16
10 6
11 // Region is one output's part of the layout and where it is in X space.7 // Region is one output's part of the layout and where it is in X space.
12-// X = X.Origin + (layout - Layout.Origin) * Scale. Scale is 1 in v1.8+// X = layout - Origin.
13 type Region struct {9 type Region struct {
14 Layout, X ports.Rect // logical layout rect; X rect in X pixels10 Layout, X ports.Rect // logical layout rect; X rect in X pixels
15- Scale float64 // 1 in v1; reserved for native resolution
16 }11 }
17 12
18 // Space maps layout coordinates (which may be negative) to X coordinates13 // Space maps layout coordinates (which may be negative) to X coordinates
@@ -20,14 +15,14 @@ type Region struct {
20 type Space struct {15 type Space struct {
21 regions [maxRegions]Region16 regions [maxRegions]Region
22 n int17 n int
23- dropped int // outputs beyond maxRegions in the last Set18+ dropped int // outputs beyond maxRegions in the last Set
24- ox, oy int // layout point that is X (0,0)19+ ox, oy int // layout point that is X (0,0)
25- w, h int // X screen size: the extents20+ w, h int // X screen size: the extents
26- gen uint32 // bumped on every change
27 }21 }
28 22
29-// Gen returns the change counter.23+// Origin is the layout point that is X (0,0): the top-left of the layout
30-func (s *Space) Gen() uint32 { return s.gen }24+// extents.
25+func (s *Space) Origin() (ox, oy int) { return s.ox, s.oy }
31 26
32 // Dropped is how many outputs of the last Set did not fit.27 // Dropped is how many outputs of the last Set did not fit.
33 func (s *Space) Dropped() int { return s.dropped }28 func (s *Space) Dropped() int { return s.dropped }
@@ -62,7 +57,6 @@ func (s *Space) Set(l ports.Layout) bool {
62 next.regions[i] = Region{57 next.regions[i] = Region{
63 Layout: ports.Rect{X: o.X, Y: o.Y, W: o.Width, H: o.Height},58 Layout: ports.Rect{X: o.X, Y: o.Y, W: o.Width, H: o.Height},
64 X: ports.Rect{X: o.X - next.ox, Y: o.Y - next.oy, W: o.Width, H: o.Height},59 X: ports.Rect{X: o.X - next.ox, Y: o.Y - next.oy, W: o.Width, H: o.Height},
65- Scale: 1,
66 }60 }
67 }61 }
68 next.w, next.h = maxX-next.ox, maxY-next.oy62 next.w, next.h = maxX-next.ox, maxY-next.oy
@@ -71,7 +65,6 @@ func (s *Space) Set(l ports.Layout) bool {
71 s.dropped = next.dropped65 s.dropped = next.dropped
72 return false66 return false
73 }67 }
74- next.gen = s.gen + 1
75 *s = next68 *s = next
76 return true69 return true
77 }70 }
@@ -128,7 +121,7 @@ func (s *Space) ToX(lx, ly int) (x, y int) {
128 return lx, ly121 return lx, ly
129 }122 }
130 r := &s.regions[i]123 r := &s.regions[i]
131- return r.X.X + scaled(lx-r.Layout.X, r.Scale), r.X.Y + scaled(ly-r.Layout.Y, r.Scale)124+ return r.X.X + lx - r.Layout.X, r.X.Y + ly - r.Layout.Y
132 }125 }
133 126
134 // ToLayout converts an X point to layout coordinates.127 // ToLayout converts an X point to layout coordinates.
@@ -138,30 +131,5 @@ func (s *Space) ToLayout(x, y int) (lx, ly int) {
138 return x, y131 return x, y
139 }132 }
140 r := &s.regions[i]133 r := &s.regions[i]
141- return r.Layout.X + unscaled(x-r.X.X, r.Scale), r.Layout.Y + unscaled(y-r.X.Y, r.Scale)134+ return r.Layout.X + x - r.X.X, r.Layout.Y + y - r.X.Y
142-}
143-
144-// RegionAtLayout returns the region containing the layout point.
145-func (s *Space) RegionAtLayout(lx, ly int) (Region, bool) {
146- for i := 0; i < s.n; i++ {
147- r := s.regions[i].Layout
148- if lx >= r.X && lx < r.X+r.W && ly >= r.Y && ly < r.Y+r.H {
149- return s.regions[i], true
150- }
151- }
152- return Region{}, false
153-}
154-
155-func scaled(v int, sc float64) int {
156- if sc == 1 || sc == 0 {
157- return v
158- }
159- return int(math.Round(float64(v) * sc))
160-}
161-
162-func unscaled(v int, sc float64) int {
163- if sc == 1 || sc == 0 {
164- return v
165- }
166- return int(math.Round(float64(v) / sc))
167 }135 }
M internal/adapters/wayland/xwm/space_test.go +6 -23
@@ -47,32 +47,14 @@ func TestSpaceLayouts(t *testing.T) {
4747 }
4848 }
4949
50-func TestSpaceHoleAndRegion(t *testing.T) {
51- var s Space
52- s.Set(ports.Layout{out(0, 0, 100, 100), out(300, 0, 100, 100)})
53- if _, ok := s.RegionAtLayout(200, 50); ok {
54- t.Fatal("hole must have no region")
55- }
56- r, ok := s.RegionAtLayout(350, 50)
57- if !ok || r.X.X != 300 {
58- t.Fatalf("region = %+v %v", r, ok)
59- }
60-}
61-
62-func TestSpaceGenAndEmpty(t *testing.T) {
50+func TestSpaceEmptyAndOrigin(t *testing.T) {
6351 var s Space
6452 if x, y := s.ToX(5, 6); x != 5 || y != 6 {
6553 t.Fatalf("empty ToX = %d,%d", x, y)
6654 }
67- s.Set(ports.Layout{out(0, 0, 10, 10)})
68- g := s.Gen()
69- s.Set(ports.Layout{out(0, 0, 10, 10)})
70- if s.Gen() != g {
71- t.Fatal("gen bumped without change")
72- }
73- s.Set(ports.Layout{out(5, 0, 10, 10)})
74- if s.Gen() == g {
75- t.Fatal("gen not bumped")
55+ s.Set(ports.Layout{out(-640, -20, 640, 480), out(0, 0, 1920, 1080)})
56+ if ox, oy := s.Origin(); ox != -640 || oy != -20 {
57+ t.Fatalf("origin = %d,%d", ox, oy)
7658 }
7759 }
7860
@@ -102,7 +84,8 @@ func TestSpaceAllocations(t *testing.T) {
10284 s.Set(l)
10385 x, y := s.ToX(-600, 10)
10486 x, y = s.ToLayout(x, y)
105- _, _ = s.RegionAtLayout(x, y)
87+ _, _ = x, y
88+ _, _ = s.Origin()
10689 _, _ = s.Size()
10790 })
10891 if n != 0 {
@@ -47,32 +47,14 @@ func TestSpaceLayouts(t *testing.T) {
47 }47 }
48 }48 }
49 49
50-func TestSpaceHoleAndRegion(t *testing.T) {50+func TestSpaceEmptyAndOrigin(t *testing.T) {
51- var s Space
52- s.Set(ports.Layout{out(0, 0, 100, 100), out(300, 0, 100, 100)})
53- if _, ok := s.RegionAtLayout(200, 50); ok {
54- t.Fatal("hole must have no region")
55- }
56- r, ok := s.RegionAtLayout(350, 50)
57- if !ok || r.X.X != 300 {
58- t.Fatalf("region = %+v %v", r, ok)
59- }
60-}
61-
62-func TestSpaceGenAndEmpty(t *testing.T) {
63 var s Space51 var s Space
64 if x, y := s.ToX(5, 6); x != 5 || y != 6 {52 if x, y := s.ToX(5, 6); x != 5 || y != 6 {
65 t.Fatalf("empty ToX = %d,%d", x, y)53 t.Fatalf("empty ToX = %d,%d", x, y)
66 }54 }
67- s.Set(ports.Layout{out(0, 0, 10, 10)})55+ s.Set(ports.Layout{out(-640, -20, 640, 480), out(0, 0, 1920, 1080)})
68- g := s.Gen()56+ if ox, oy := s.Origin(); ox != -640 || oy != -20 {
69- s.Set(ports.Layout{out(0, 0, 10, 10)})57+ t.Fatalf("origin = %d,%d", ox, oy)
70- if s.Gen() != g {
71- t.Fatal("gen bumped without change")
72- }
73- s.Set(ports.Layout{out(5, 0, 10, 10)})
74- if s.Gen() == g {
75- t.Fatal("gen not bumped")
76 }58 }
77 }59 }
78 60
@@ -102,7 +84,8 @@ func TestSpaceAllocations(t *testing.T) {
102 s.Set(l)84 s.Set(l)
103 x, y := s.ToX(-600, 10)85 x, y := s.ToX(-600, 10)
104 x, y = s.ToLayout(x, y)86 x, y = s.ToLayout(x, y)
105- _, _ = s.RegionAtLayout(x, y)87+ _, _ = x, y
88+ _, _ = s.Origin()
106 _, _ = s.Size()89 _, _ = s.Size()
107 })90 })
108 if n != 0 {91 if n != 0 {
M internal/adapters/wayland/xwm/table.go +3 -3
@@ -40,9 +40,9 @@ type record struct {
4040 gen uint16 // bumped on insert; stale Expect replies are told apart by it
4141
4242 // X state
43- override, mapped, mapping, ready bool
44- x, y int32
45- w, h uint32
43+ mapped, mapping, ready bool
44+ x, y int32
45+ w, h uint32
4646
4747 // properties
4848 pending uint16 // propMask bits still awaited
@@ -40,9 +40,9 @@ type record struct {
40 gen uint16 // bumped on insert; stale Expect replies are told apart by it40 gen uint16 // bumped on insert; stale Expect replies are told apart by it
41 41
42 // X state42 // X state
43- override, mapped, mapping, ready bool43+ mapped, mapping, ready bool
44- x, y int3244+ x, y int32
45- w, h uint3245+ w, h uint32
46 46
47 // properties47 // properties
48 pending uint16 // propMask bits still awaited48 pending uint16 // propMask bits still awaited
M internal/adapters/wayland/xwm/unmanaged.go +1 -1
@@ -68,7 +68,7 @@ func (x *XWM) overrideUnmapped(s Slot, rec *record) {
6868 }
6969 if rec.ovPaired {
7070 rec.ovPaired = false
71- x.host.OverrideGone(s, false)
71+ x.host.OverrideGone(s)
7272 }
7373 }
7474
@@ -68,7 +68,7 @@ func (x *XWM) overrideUnmapped(s Slot, rec *record) {
68 }68 }
69 if rec.ovPaired {69 if rec.ovPaired {
70 rec.ovPaired = false70 rec.ovPaired = false
71- x.host.OverrideGone(s, false)71+ x.host.OverrideGone(s)
72 }72 }
73 }73 }
74 74
M internal/adapters/wayland/xwm/unmanaged_test.go +12 -3
@@ -145,7 +145,7 @@ func TestUnmanagedMoveAndLayoutChangeAndUnmap(t *testing.T) {
145145 x.SetLayout(layoutOf(out(-100, 0, 100, 100), out(0, 0, 1920, 1080)))
146146 x.flush()
147147
148- h.EXPECT().OverrideGone(s, false).Once()
148+ h.EXPECT().OverrideGone(s).Once()
149149 x.feed(t, p, func() { p.unmapNotify(win, false) })
150150 require.False(t, x.tab.rec(s).ovPaired)
151151 }
@@ -158,7 +158,7 @@ func TestUnmanagedDestroyReportsGone(t *testing.T) {
158158 h.EXPECT().OverrideMapped(s, uint32(win), uint64(7), 30, 40, 100, 50, false).Once()
159159 x.feed(t, p, func() { p.clientMessage(win, p.atom("WL_SURFACE_SERIAL"), 9, 0) })
160160 readUntil(t, x, func() bool { return x.tab.rec(s).ovPaired })
161- h.EXPECT().OverrideGone(s, true).Once()
161+ h.EXPECT().OverrideGone(s).Once()
162162 x.feed(t, p, func() { p.destroyNotify(win) })
163163 require.Zero(t, x.tab.lookup(win))
164164 }
@@ -204,7 +204,7 @@ func TestUnmanagedGeometryAllocations(t *testing.T) {
204204 x.SetLayout(layoutOf(out(0, 0, 1920, 1080)))
205205 s := x.insert(0x500009)
206206 r := x.tab.rec(s)
207- r.kind, r.override, r.mapped, r.ready, r.ovPaired = kindUnmanaged, true, true, true, true
207+ r.kind, r.mapped, r.ready, r.ovPaired = kindUnmanaged, true, true, true
208208 r.x, r.y, r.w, r.h = 30, 40, 100, 50
209209 r.ov = x.layoutRect(r)
210210 sinkPeer(t, x, p)
@@ -236,3 +236,12 @@ func TestFocusInUnmanagedAllocations(t *testing.T) {
236236 require.Zero(t, allocs)
237237 require.False(t, x.refocus)
238238 }
239+
240+func TestDropWindowClearsQueuedOverride(t *testing.T) {
241+ x, _, _, _, _ := attached(t)
242+ s := x.insert(0x500009)
243+ r := x.tab.rec(s)
244+ r.kind, r.ovQueued = kindUnmanaged, true
245+ x.dropWindow(s)
246+ require.False(t, r.ovQueued)
247+}
@@ -145,7 +145,7 @@ func TestUnmanagedMoveAndLayoutChangeAndUnmap(t *testing.T) {
145 x.SetLayout(layoutOf(out(-100, 0, 100, 100), out(0, 0, 1920, 1080)))145 x.SetLayout(layoutOf(out(-100, 0, 100, 100), out(0, 0, 1920, 1080)))
146 x.flush()146 x.flush()
147 147
148- h.EXPECT().OverrideGone(s, false).Once()148+ h.EXPECT().OverrideGone(s).Once()
149 x.feed(t, p, func() { p.unmapNotify(win, false) })149 x.feed(t, p, func() { p.unmapNotify(win, false) })
150 require.False(t, x.tab.rec(s).ovPaired)150 require.False(t, x.tab.rec(s).ovPaired)
151 }151 }
@@ -158,7 +158,7 @@ func TestUnmanagedDestroyReportsGone(t *testing.T) {
158 h.EXPECT().OverrideMapped(s, uint32(win), uint64(7), 30, 40, 100, 50, false).Once()158 h.EXPECT().OverrideMapped(s, uint32(win), uint64(7), 30, 40, 100, 50, false).Once()
159 x.feed(t, p, func() { p.clientMessage(win, p.atom("WL_SURFACE_SERIAL"), 9, 0) })159 x.feed(t, p, func() { p.clientMessage(win, p.atom("WL_SURFACE_SERIAL"), 9, 0) })
160 readUntil(t, x, func() bool { return x.tab.rec(s).ovPaired })160 readUntil(t, x, func() bool { return x.tab.rec(s).ovPaired })
161- h.EXPECT().OverrideGone(s, true).Once()161+ h.EXPECT().OverrideGone(s).Once()
162 x.feed(t, p, func() { p.destroyNotify(win) })162 x.feed(t, p, func() { p.destroyNotify(win) })
163 require.Zero(t, x.tab.lookup(win))163 require.Zero(t, x.tab.lookup(win))
164 }164 }
@@ -204,7 +204,7 @@ func TestUnmanagedGeometryAllocations(t *testing.T) {
204 x.SetLayout(layoutOf(out(0, 0, 1920, 1080)))204 x.SetLayout(layoutOf(out(0, 0, 1920, 1080)))
205 s := x.insert(0x500009)205 s := x.insert(0x500009)
206 r := x.tab.rec(s)206 r := x.tab.rec(s)
207- r.kind, r.override, r.mapped, r.ready, r.ovPaired = kindUnmanaged, true, true, true, true207+ r.kind, r.mapped, r.ready, r.ovPaired = kindUnmanaged, true, true, true
208 r.x, r.y, r.w, r.h = 30, 40, 100, 50208 r.x, r.y, r.w, r.h = 30, 40, 100, 50
209 r.ov = x.layoutRect(r)209 r.ov = x.layoutRect(r)
210 sinkPeer(t, x, p)210 sinkPeer(t, x, p)
@@ -236,3 +236,12 @@ func TestFocusInUnmanagedAllocations(t *testing.T) {
236 require.Zero(t, allocs)236 require.Zero(t, allocs)
237 require.False(t, x.refocus)237 require.False(t, x.refocus)
238 }238 }
239+
240+func TestDropWindowClearsQueuedOverride(t *testing.T) {
241+ x, _, _, _, _ := attached(t)
242+ s := x.insert(0x500009)
243+ r := x.tab.rec(s)
244+ r.kind, r.ovQueued = kindUnmanaged, true
245+ x.dropWindow(s)
246+ require.False(t, r.ovQueued)
247+}
M internal/adapters/wayland/xwm/xwm.go +3 -3
@@ -81,13 +81,13 @@ type XWM struct {
8181 unpaired [unpairedCap]struct{ serial, surface uint64 }
8282 unpairedN int
8383
84- space Space // layout <-> X coordinates
84+ space *Space // layout <-> X coordinates, shared with the host
8585 warnedSpace bool // more outputs than the Space holds, logged once
8686 ovDirty []Slot // mapped unmanaged slots whose geometry changed, capacity == table size
8787 }
8888
8989 func newXWM(conn *xturbo.Conn, log zerowrap.Logger) *XWM {
90- x := &XWM{conn: conn, log: log, root: conn.Root(), tab: newTable(initialSlots)}
90+ x := &XWM{conn: conn, log: log, root: conn.Root(), tab: newTable(initialSlots), space: new(Space)}
9191 x.h = xturbo.Handler{OnEvent: x.onEvent, OnReply: x.onReply, OnError: x.onError}
9292 x.fit()
9393 return x
@@ -360,7 +360,7 @@ func (x *XWM) Close() {
360360 x.host.Dissociate(Slot(i+1), true)
361361 case r.kind == kindUnmanaged && r.ovPaired:
362362 r.ovPaired = false
363- x.host.OverrideGone(Slot(i+1), true)
363+ x.host.OverrideGone(Slot(i + 1))
364364 }
365365 }
366366 }
@@ -81,13 +81,13 @@ type XWM struct {
81 unpaired [unpairedCap]struct{ serial, surface uint64 }81 unpaired [unpairedCap]struct{ serial, surface uint64 }
82 unpairedN int82 unpairedN int
83 83
84- space Space // layout <-> X coordinates84+ space *Space // layout <-> X coordinates, shared with the host
85 warnedSpace bool // more outputs than the Space holds, logged once85 warnedSpace bool // more outputs than the Space holds, logged once
86 ovDirty []Slot // mapped unmanaged slots whose geometry changed, capacity == table size86 ovDirty []Slot // mapped unmanaged slots whose geometry changed, capacity == table size
87 }87 }
88 88
89 func newXWM(conn *xturbo.Conn, log zerowrap.Logger) *XWM {89 func newXWM(conn *xturbo.Conn, log zerowrap.Logger) *XWM {
90- x := &XWM{conn: conn, log: log, root: conn.Root(), tab: newTable(initialSlots)}90+ x := &XWM{conn: conn, log: log, root: conn.Root(), tab: newTable(initialSlots), space: new(Space)}
91 x.h = xturbo.Handler{OnEvent: x.onEvent, OnReply: x.onReply, OnError: x.onError}91 x.h = xturbo.Handler{OnEvent: x.onEvent, OnReply: x.onReply, OnError: x.onError}
92 x.fit()92 x.fit()
93 return x93 return x
@@ -360,7 +360,7 @@ func (x *XWM) Close() {
360 x.host.Dissociate(Slot(i+1), true)360 x.host.Dissociate(Slot(i+1), true)
361 case r.kind == kindUnmanaged && r.ovPaired:361 case r.kind == kindUnmanaged && r.ovPaired:
362 r.ovPaired = false362 r.ovPaired = false
363- x.host.OverrideGone(Slot(i+1), true)363+ x.host.OverrideGone(Slot(i + 1))
364 }364 }
365 }365 }
366 }366 }

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