detached 8342bf4

Merge branch 'fix/x11-fullscreen-restore' into feat/xwayland-builtin

commit
8342bf495991eb66be6d7616c5247dbaac52877e
author
brice · · 2026-10-22 18:34 UTC
merge of
41da69c + 61f9ab3 · diff against the first parent
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +40 -11
M Makefile +1 -1
@@ -70,7 +70,7 @@ adapter-imports:
7070 [ -z "$$bad" ] || { echo "$$bad"; echo 'adapter imports another adapter: use ports or a shared library (Makefile ADAPTER_LIBS)' >&2; exit 1; }
7171 # Fast, mandatory guards for tiled content publications and SHM copies.
7272 perf-check:
73- CGO_ENABLED=0 go test ./internal/core ./internal/app ./internal/adapters/drm ./internal/adapters/headless ./internal/adapters/vulkan ./internal/adapters/wayland ./internal/adapters/outputkit/capture ./internal/adapters/screencast ./internal/adapters/wayland/xwm -run '^(TestAnimateAllocations|TestPublishAllocations|TestPublishSubscribersAllocations|TestPublishStashSlideAllocations|TestPublishOverviewAllocations|TestPublishLeavingAllocations|TestOutputRoutingAllocations|TestReportSeenAllocations|TestFrameLifecycleTransitionsAllocations|TestFullscreenShownAllocations|TestFrameDecisionAllocations|TestCommitFrameAllocations|TestColorPipelineAllocations|TestShownBySnapshotAllocations|TestFlipShowsAllocations|TestMRUNoteFocusDoesNotAllocateWhenSettled|TestDueFramesAllocations|TestRenderSteadyStateAllocations|TestRenderSteadyStateDMABufAllocations|TestSceneOpaqueRegionAllocations|TestSceneDeltaAllocations|TestAccountFlipAllocations|TestFlipDoneAllocations|TestSceneWalkUnchangedTiledSHM|TestTiledCommitPublishAllocations|TestCapturedCommitApplyAllocations|TestCapturedViewportApplyAllocations|TestEffectiveInputEmptyTreeAllocations|TestHeadlessTiledSHMCallbackAndCopyBudget|TestReleaseHeldAllocations|TestCommitDamageAllocations|TestEmitContentAllocations|TestPresentationPushAllocations|TestAnimatingFrameSendAllocations|TestContentBatchCycleAllocations|TestWriteMappedAllocations|TestCapturedForwardAllocations|TestCommandForwardAllocations|TestOutboxQueuedDeliveryAllocations|TestCastFrameAllocations|TestCastProducerAllocations|TestCaptureCycleAllocations|TestLogScenesAllocations|TestOverviewNamedRowsAllocations|TestBufferContentAllocations|TestDueFramesFireScratch|TestRetireSemaphoreAllocatesNothing|TestTableLookupAllocations|TestTableInsertRemoveAllocations|TestPropertyUnchangedAllocations|TestDispatchAllocations|TestConfigureFlushAllocations|TestFocusAllocations|TestSpaceAllocations|TestUnmanagedGeometryAllocations|TestFocusInUnmanagedAllocations|TestUnmanagedPublishAllocations|TestUnmanagedHitAllocations|TestOverrideMovedUnchangedAllocations|TestFullscreenConfigureAllocations|TestKeyboardGrabSyncAllocations|TestSelectionCopyAllocations|TestSelectionCopyAllocationsWaylandToX|TestSelectionINCRChunkAllocations|TestXSettingsNoChangeAllocations)$$' -count=1
73+ CGO_ENABLED=0 go test ./internal/core ./internal/app ./internal/adapters/drm ./internal/adapters/headless ./internal/adapters/vulkan ./internal/adapters/wayland ./internal/adapters/outputkit/capture ./internal/adapters/screencast ./internal/adapters/wayland/xwm -run '^(TestAnimateAllocations|TestPublishAllocations|TestPublishSubscribersAllocations|TestPublishStashSlideAllocations|TestPublishOverviewAllocations|TestPublishLeavingAllocations|TestOutputRoutingAllocations|TestReportSeenAllocations|TestFrameLifecycleTransitionsAllocations|TestFullscreenShownAllocations|TestFrameDecisionAllocations|TestCommitFrameAllocations|TestColorPipelineAllocations|TestShownBySnapshotAllocations|TestFlipShowsAllocations|TestMRUNoteFocusDoesNotAllocateWhenSettled|TestDueFramesAllocations|TestRenderSteadyStateAllocations|TestRenderSteadyStateDMABufAllocations|TestSceneOpaqueRegionAllocations|TestSceneDeltaAllocations|TestAccountFlipAllocations|TestFlipDoneAllocations|TestSceneWalkUnchangedTiledSHM|TestTiledCommitPublishAllocations|TestCapturedCommitApplyAllocations|TestCapturedViewportApplyAllocations|TestEffectiveInputEmptyTreeAllocations|TestHeadlessTiledSHMCallbackAndCopyBudget|TestReleaseHeldAllocations|TestCommitDamageAllocations|TestEmitContentAllocations|TestPresentationPushAllocations|TestAnimatingFrameSendAllocations|TestContentBatchCycleAllocations|TestWriteMappedAllocations|TestCapturedForwardAllocations|TestCommandForwardAllocations|TestOutboxQueuedDeliveryAllocations|TestCastFrameAllocations|TestCastProducerAllocations|TestCaptureCycleAllocations|TestLogScenesAllocations|TestOverviewNamedRowsAllocations|TestBufferContentAllocations|TestDueFramesFireScratch|TestRetireSemaphoreAllocatesNothing|TestTableLookupAllocations|TestTableInsertRemoveAllocations|TestPropertyUnchangedAllocations|TestDispatchAllocations|TestConfigureFlushAllocations|TestFocusAllocations|TestSpaceAllocations|TestUnmanagedGeometryAllocations|TestFocusInUnmanagedAllocations|TestUnmanagedPublishAllocations|TestUnmanagedHitAllocations|TestOverrideMovedUnchangedAllocations|TestFullscreenConfigureAllocations|TestKeyboardGrabSyncAllocations|TestSelectionCopyAllocations|TestSelectionCopyAllocationsWaylandToX|TestSelectionINCRChunkAllocations|TestXSettingsNoChangeAllocations|TestSettleLeftAllocs)$$' -count=1
7474 CGO_ENABLED=0 go test -tags xturbo_poison ./internal/adapters/wayland/xwm -count=1
7575 check: vet test arch fakes-check log-check perf-check
7676
@@ -70,7 +70,7 @@ adapter-imports:
70 [ -z "$$bad" ] || { echo "$$bad"; echo 'adapter imports another adapter: use ports or a shared library (Makefile ADAPTER_LIBS)' >&2; exit 1; }70 [ -z "$$bad" ] || { echo "$$bad"; echo 'adapter imports another adapter: use ports or a shared library (Makefile ADAPTER_LIBS)' >&2; exit 1; }
71 # Fast, mandatory guards for tiled content publications and SHM copies.71 # Fast, mandatory guards for tiled content publications and SHM copies.
72 perf-check:72 perf-check:
73- CGO_ENABLED=0 go test ./internal/core ./internal/app ./internal/adapters/drm ./internal/adapters/headless ./internal/adapters/vulkan ./internal/adapters/wayland ./internal/adapters/outputkit/capture ./internal/adapters/screencast ./internal/adapters/wayland/xwm -run '^(TestAnimateAllocations|TestPublishAllocations|TestPublishSubscribersAllocations|TestPublishStashSlideAllocations|TestPublishOverviewAllocations|TestPublishLeavingAllocations|TestOutputRoutingAllocations|TestReportSeenAllocations|TestFrameLifecycleTransitionsAllocations|TestFullscreenShownAllocations|TestFrameDecisionAllocations|TestCommitFrameAllocations|TestColorPipelineAllocations|TestShownBySnapshotAllocations|TestFlipShowsAllocations|TestMRUNoteFocusDoesNotAllocateWhenSettled|TestDueFramesAllocations|TestRenderSteadyStateAllocations|TestRenderSteadyStateDMABufAllocations|TestSceneOpaqueRegionAllocations|TestSceneDeltaAllocations|TestAccountFlipAllocations|TestFlipDoneAllocations|TestSceneWalkUnchangedTiledSHM|TestTiledCommitPublishAllocations|TestCapturedCommitApplyAllocations|TestCapturedViewportApplyAllocations|TestEffectiveInputEmptyTreeAllocations|TestHeadlessTiledSHMCallbackAndCopyBudget|TestReleaseHeldAllocations|TestCommitDamageAllocations|TestEmitContentAllocations|TestPresentationPushAllocations|TestAnimatingFrameSendAllocations|TestContentBatchCycleAllocations|TestWriteMappedAllocations|TestCapturedForwardAllocations|TestCommandForwardAllocations|TestOutboxQueuedDeliveryAllocations|TestCastFrameAllocations|TestCastProducerAllocations|TestCaptureCycleAllocations|TestLogScenesAllocations|TestOverviewNamedRowsAllocations|TestBufferContentAllocations|TestDueFramesFireScratch|TestRetireSemaphoreAllocatesNothing|TestTableLookupAllocations|TestTableInsertRemoveAllocations|TestPropertyUnchangedAllocations|TestDispatchAllocations|TestConfigureFlushAllocations|TestFocusAllocations|TestSpaceAllocations|TestUnmanagedGeometryAllocations|TestFocusInUnmanagedAllocations|TestUnmanagedPublishAllocations|TestUnmanagedHitAllocations|TestOverrideMovedUnchangedAllocations|TestFullscreenConfigureAllocations|TestKeyboardGrabSyncAllocations|TestSelectionCopyAllocations|TestSelectionCopyAllocationsWaylandToX|TestSelectionINCRChunkAllocations|TestXSettingsNoChangeAllocations)$$' -count=173+ CGO_ENABLED=0 go test ./internal/core ./internal/app ./internal/adapters/drm ./internal/adapters/headless ./internal/adapters/vulkan ./internal/adapters/wayland ./internal/adapters/outputkit/capture ./internal/adapters/screencast ./internal/adapters/wayland/xwm -run '^(TestAnimateAllocations|TestPublishAllocations|TestPublishSubscribersAllocations|TestPublishStashSlideAllocations|TestPublishOverviewAllocations|TestPublishLeavingAllocations|TestOutputRoutingAllocations|TestReportSeenAllocations|TestFrameLifecycleTransitionsAllocations|TestFullscreenShownAllocations|TestFrameDecisionAllocations|TestCommitFrameAllocations|TestColorPipelineAllocations|TestShownBySnapshotAllocations|TestFlipShowsAllocations|TestMRUNoteFocusDoesNotAllocateWhenSettled|TestDueFramesAllocations|TestRenderSteadyStateAllocations|TestRenderSteadyStateDMABufAllocations|TestSceneOpaqueRegionAllocations|TestSceneDeltaAllocations|TestAccountFlipAllocations|TestFlipDoneAllocations|TestSceneWalkUnchangedTiledSHM|TestTiledCommitPublishAllocations|TestCapturedCommitApplyAllocations|TestCapturedViewportApplyAllocations|TestEffectiveInputEmptyTreeAllocations|TestHeadlessTiledSHMCallbackAndCopyBudget|TestReleaseHeldAllocations|TestCommitDamageAllocations|TestEmitContentAllocations|TestPresentationPushAllocations|TestAnimatingFrameSendAllocations|TestContentBatchCycleAllocations|TestWriteMappedAllocations|TestCapturedForwardAllocations|TestCommandForwardAllocations|TestOutboxQueuedDeliveryAllocations|TestCastFrameAllocations|TestCastProducerAllocations|TestCaptureCycleAllocations|TestLogScenesAllocations|TestOverviewNamedRowsAllocations|TestBufferContentAllocations|TestDueFramesFireScratch|TestRetireSemaphoreAllocatesNothing|TestTableLookupAllocations|TestTableInsertRemoveAllocations|TestPropertyUnchangedAllocations|TestDispatchAllocations|TestConfigureFlushAllocations|TestFocusAllocations|TestSpaceAllocations|TestUnmanagedGeometryAllocations|TestFocusInUnmanagedAllocations|TestUnmanagedPublishAllocations|TestUnmanagedHitAllocations|TestOverrideMovedUnchangedAllocations|TestFullscreenConfigureAllocations|TestKeyboardGrabSyncAllocations|TestSelectionCopyAllocations|TestSelectionCopyAllocationsWaylandToX|TestSelectionINCRChunkAllocations|TestXSettingsNoChangeAllocations|TestSettleLeftAllocs)$$' -count=1
74 CGO_ENABLED=0 go test -tags xturbo_poison ./internal/adapters/wayland/xwm -count=174 CGO_ENABLED=0 go test -tags xturbo_poison ./internal/adapters/wayland/xwm -count=1
75 check: vet test arch fakes-check log-check perf-check75 check: vet test arch fakes-check log-check perf-check
76 76
M internal/core/core_test.go +2 -3
@@ -1033,7 +1033,7 @@ func fullscreenAtMapIgnored(t *testing.T, animated bool) {
10331033 // Under fixed overflow, a window opening over a fullscreen game waits
10341034 // hidden: the game keeps the screen and the keyboard. An activation (a
10351035 // user action in the new client) leaves fullscreen and shows both tiles;
1036-// a focus move back to the game and the bind make it fullscreen again.
1036+// a focus move back to the game makes it fullscreen again.
10371037 func TestFixedFullscreenArrivalWaits(t *testing.T) {
10381038 r := startMulti(t, func(c *ports.Config) { c.Layout.Overflow = "fixed" }, left)
10391039 r.mapWindow(t, 1)
@@ -1055,8 +1055,7 @@ func TestFixedFullscreenArrivalWaits(t *testing.T) {
10551055 break
10561056 }
10571057 }
1058- r.key(t, "Left", ports.ModAlt)
1059- set = r.key(t, "f", ports.ModAlt|ports.ModShift)
1058+ set = r.key(t, "Left", ports.ModAlt)
10601059 if got, focused := windowsOf(set, "DP-1"); !reflect.DeepEqual(got, []ports.WindowID{1}) || focused != 1 {
10611060 t.Fatal("game again:", got, focused)
10621061 }
@@ -1033,7 +1033,7 @@ func fullscreenAtMapIgnored(t *testing.T, animated bool) {
1033 // Under fixed overflow, a window opening over a fullscreen game waits1033 // Under fixed overflow, a window opening over a fullscreen game waits
1034 // hidden: the game keeps the screen and the keyboard. An activation (a1034 // hidden: the game keeps the screen and the keyboard. An activation (a
1035 // user action in the new client) leaves fullscreen and shows both tiles;1035 // user action in the new client) leaves fullscreen and shows both tiles;
1036-// a focus move back to the game and the bind make it fullscreen again.1036+// a focus move back to the game makes it fullscreen again.
1037 func TestFixedFullscreenArrivalWaits(t *testing.T) {1037 func TestFixedFullscreenArrivalWaits(t *testing.T) {
1038 r := startMulti(t, func(c *ports.Config) { c.Layout.Overflow = "fixed" }, left)1038 r := startMulti(t, func(c *ports.Config) { c.Layout.Overflow = "fixed" }, left)
1039 r.mapWindow(t, 1)1039 r.mapWindow(t, 1)
@@ -1055,8 +1055,7 @@ func TestFixedFullscreenArrivalWaits(t *testing.T) {
1055 break1055 break
1056 }1056 }
1057 }1057 }
1058- r.key(t, "Left", ports.ModAlt)1058+ set = r.key(t, "Left", ports.ModAlt)
1059- set = r.key(t, "f", ports.ModAlt|ports.ModShift)
1060 if got, focused := windowsOf(set, "DP-1"); !reflect.DeepEqual(got, []ports.WindowID{1}) || focused != 1 {1059 if got, focused := windowsOf(set, "DP-1"); !reflect.DeepEqual(got, []ports.WindowID{1}) || focused != 1 {
1061 t.Fatal("game again:", got, focused)1060 t.Fatal("game again:", got, focused)
1062 }1061 }
M internal/core/fullscreen_left_core_test.go +1 -6
@@ -64,13 +64,8 @@ func TestCoreLeftFullscreenLatch(t *testing.T) {
6464 if id := full(); id != 0 {
6565 t.Fatalf("re-request covered: fullscreen %d", id)
6666 }
67+ // Focus back on 2 restores its fullscreen: an X11 client never re-asks.
6768 client <- ports.WindowActivate{ID: 2}
68- full()
69- client <- ports.WindowActivate{ID: 1}
70- full()
71- // The latch settled when 2 had the focus: scroll overflow honours an
72- // unfocused window's request again.
73- client <- ports.WindowFullscreenRequest{ID: 2, Fullscreen: true}
7469 if id := full(); id != 2 {
7570 t.Fatalf("after refocus: fullscreen %d, want 2", id)
7671 }
@@ -64,13 +64,8 @@ func TestCoreLeftFullscreenLatch(t *testing.T) {
64 if id := full(); id != 0 {64 if id := full(); id != 0 {
65 t.Fatalf("re-request covered: fullscreen %d", id)65 t.Fatalf("re-request covered: fullscreen %d", id)
66 }66 }
67+ // Focus back on 2 restores its fullscreen: an X11 client never re-asks.
67 client <- ports.WindowActivate{ID: 2}68 client <- ports.WindowActivate{ID: 2}
68- full()
69- client <- ports.WindowActivate{ID: 1}
70- full()
71- // The latch settled when 2 had the focus: scroll overflow honours an
72- // unfocused window's request again.
73- client <- ports.WindowFullscreenRequest{ID: 2, Fullscreen: true}
74 if id := full(); id != 2 {69 if id := full(); id != 2 {
75 t.Fatalf("after refocus: fullscreen %d, want 2", id)70 t.Fatalf("after refocus: fullscreen %d, want 2", id)
76 }71 }
M internal/core/fullscreen_left_test.go +29 -0
@@ -111,3 +111,32 @@ func TestLeftFullscreenRemoved(t *testing.T) {
111111 t.Fatalf("left %d", w.left)
112112 }
113113 }
114+
115+// Focus back on a window whose fullscreen the user left restores it, with
116+// no request from the client (X11 clients never re-ask), and allocates
117+// nothing once settled.
118+func TestLeftFullscreenRestoredOnFocus(t *testing.T) {
119+ for _, tc := range pinnedCases {
120+ t.Run(tc.name, func(t *testing.T) {
121+ m := coverMonitor(tc.overflow, tc.float)
122+ w := m.Current()
123+ w.Activate(1)
124+ if w.fullscreen != 0 || w.left != 2 {
125+ t.Fatalf("leave: fullscreen %d, left %d", w.fullscreen, w.left)
126+ }
127+ w.Activate(2)
128+ w.settleLeft()
129+ if w.fullscreen != 2 || w.left != 0 {
130+ t.Fatalf("back: fullscreen %d, left %d", w.fullscreen, w.left)
131+ }
132+ })
133+ }
134+}
135+
136+func TestSettleLeftAllocs(t *testing.T) {
137+ m := coverMonitor(OverflowScroll, false)
138+ w := m.Current()
139+ if n := testing.AllocsPerRun(100, w.settleLeft); n != 0 {
140+ t.Fatalf("settleLeft allocs %v", n)
141+ }
142+}
@@ -111,3 +111,32 @@ func TestLeftFullscreenRemoved(t *testing.T) {
111 t.Fatalf("left %d", w.left)111 t.Fatalf("left %d", w.left)
112 }112 }
113 }113 }
114+
115+// Focus back on a window whose fullscreen the user left restores it, with
116+// no request from the client (X11 clients never re-ask), and allocates
117+// nothing once settled.
118+func TestLeftFullscreenRestoredOnFocus(t *testing.T) {
119+ for _, tc := range pinnedCases {
120+ t.Run(tc.name, func(t *testing.T) {
121+ m := coverMonitor(tc.overflow, tc.float)
122+ w := m.Current()
123+ w.Activate(1)
124+ if w.fullscreen != 0 || w.left != 2 {
125+ t.Fatalf("leave: fullscreen %d, left %d", w.fullscreen, w.left)
126+ }
127+ w.Activate(2)
128+ w.settleLeft()
129+ if w.fullscreen != 2 || w.left != 0 {
130+ t.Fatalf("back: fullscreen %d, left %d", w.fullscreen, w.left)
131+ }
132+ })
133+ }
134+}
135+
136+func TestSettleLeftAllocs(t *testing.T) {
137+ m := coverMonitor(OverflowScroll, false)
138+ w := m.Current()
139+ if n := testing.AllocsPerRun(100, w.settleLeft); n != 0 {
140+ t.Fatalf("settleLeft allocs %v", n)
141+ }
142+}
M internal/core/layout.go +7 -1
@@ -1435,6 +1435,9 @@ func (w *Workspace) SetFullscreen(id WindowID, on bool) {
14351435 return
14361436 }
14371437 if !on {
1438+ if w.left == id {
1439+ w.left = 0 // the client itself gave up: nothing to restore
1440+ }
14381441 if w.fullscreen == id {
14391442 w.endFullscreen()
14401443 }
@@ -1522,7 +1525,10 @@ func (w *Workspace) gap() int { return min(w.Gaps, w.Usable.W/2, w.Usable.H/2) }
15221525 // settleLeft drops the latch once its window has the focus again, however
15231526 // it got it; core calls it after each event.
15241527 func (w *Workspace) settleLeft() {
1525- if id, ok := w.Focused(); ok && id == w.left {
1528+ if id, ok := w.Focused(); ok && id != 0 && id == w.left {
1529+ // Focus is back: the window gets its fullscreen back, as a client
1530+ // that re-asks would (X11 clients never do).
1531+ w.SetFullscreen(id, true)
15261532 w.left = 0
15271533 }
15281534 }
@@ -1435,6 +1435,9 @@ func (w *Workspace) SetFullscreen(id WindowID, on bool) {
1435 return1435 return
1436 }1436 }
1437 if !on {1437 if !on {
1438+ if w.left == id {
1439+ w.left = 0 // the client itself gave up: nothing to restore
1440+ }
1438 if w.fullscreen == id {1441 if w.fullscreen == id {
1439 w.endFullscreen()1442 w.endFullscreen()
1440 }1443 }
@@ -1522,7 +1525,10 @@ func (w *Workspace) gap() int { return min(w.Gaps, w.Usable.W/2, w.Usable.H/2) }
1522 // settleLeft drops the latch once its window has the focus again, however1525 // settleLeft drops the latch once its window has the focus again, however
1523 // it got it; core calls it after each event.1526 // it got it; core calls it after each event.
1524 func (w *Workspace) settleLeft() {1527 func (w *Workspace) settleLeft() {
1525- if id, ok := w.Focused(); ok && id == w.left {1528+ if id, ok := w.Focused(); ok && id != 0 && id == w.left {
1529+ // Focus is back: the window gets its fullscreen back, as a client
1530+ // that re-asks would (X11 clients never do).
1531+ w.SetFullscreen(id, true)
1526 w.left = 01532 w.left = 0
1527 }1533 }
1528 }1534 }

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