detached 41f250a

fix(layout): return a client fullscreen window to its column with Cmd+F (#33)

Wine makes monitor-sized apps (Battle.net) fullscreen on map. Under fixed overflow this hid every other window and Cmd+F did nothing. maximize-column now leaves that fullscreen first; the next press maximizes the column.
commit
41f250abc04b8848c5675e329eeffcedd094de04
author
bnema · · 2026-09-27 19:33 UTC
committer
GitHub · · 2026-09-27 19:33 UTC
parent
3aaf9f2
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
3 files · +39 -1
M docs/config.md +1 -1
@@ -75,7 +75,7 @@ HDR requires DRM HDR connector properties, suitable KMS planes, and Vulkan fp16
7575 | `spawn-terminal` | Run `terminal` |
7676 | `close-window` | Close the focused window |
7777 | `toggle-fullscreen` | Fullscreen the focused window |
78-| `maximize-column` | Toggle full usable width for the focused column, preserving gaps and its saved width; in fixed overflow, other columns are hidden until focus moves or it is toggled off |
78+| `maximize-column` | Toggle full usable width for the focused column, preserving gaps and its saved width; in fixed overflow, other columns are hidden until focus moves or it is toggled off. On a window that made itself fullscreen (e.g. a Wine app at monitor size), it first returns the window to its column |
7979 | `cycle-column-width` | Step through `layout.presets` |
8080 | `focus-column-left/right` | Focus the neighbor column; at the edge, the neighbor monitor |
8181 | `focus-window-up/down` | Focus in the column; past the edge, the next workspace |
@@ -75,7 +75,7 @@ HDR requires DRM HDR connector properties, suitable KMS planes, and Vulkan fp16
75 | `spawn-terminal` | Run `terminal` |75 | `spawn-terminal` | Run `terminal` |
76 | `close-window` | Close the focused window |76 | `close-window` | Close the focused window |
77 | `toggle-fullscreen` | Fullscreen the focused window |77 | `toggle-fullscreen` | Fullscreen the focused window |
78-| `maximize-column` | Toggle full usable width for the focused column, preserving gaps and its saved width; in fixed overflow, other columns are hidden until focus moves or it is toggled off |78+| `maximize-column` | Toggle full usable width for the focused column, preserving gaps and its saved width; in fixed overflow, other columns are hidden until focus moves or it is toggled off. On a window that made itself fullscreen (e.g. a Wine app at monitor size), it first returns the window to its column |
79 | `cycle-column-width` | Step through `layout.presets` |79 | `cycle-column-width` | Step through `layout.presets` |
80 | `focus-column-left/right` | Focus the neighbor column; at the edge, the neighbor monitor |80 | `focus-column-left/right` | Focus the neighbor column; at the edge, the neighbor monitor |
81 | `focus-window-up/down` | Focus in the column; past the edge, the next workspace |81 | `focus-window-up/down` | Focus in the column; past the edge, the next workspace |
M internal/core/action.go +9 -0
@@ -218,6 +218,15 @@ func (m *Monitor) Apply(a Action) Effect {
218218 case ActionToggleFullscreen:
219219 m.ToggleFullscreen()
220220 return Effect{}
221+ case ActionMaximizeColumn:
222+ // A client that made itself fullscreen (Wine at monitor size) goes
223+ // back to its column width; the next press maximizes the column.
224+ if w := m.Current(); w.fullscreen != 0 {
225+ if id, ok := w.Focused(); ok && id == w.fullscreen {
226+ m.ToggleFullscreen()
227+ return Effect{}
228+ }
229+ }
221230 case ActionFocusWorkspaceUp, ActionFocusWorkspaceDown:
222231 if m.shown == nil && a == ActionFocusWorkspaceUp {
223232 m.Focus(m.Active - 1)
@@ -218,6 +218,15 @@ func (m *Monitor) Apply(a Action) Effect {
218 case ActionToggleFullscreen:218 case ActionToggleFullscreen:
219 m.ToggleFullscreen()219 m.ToggleFullscreen()
220 return Effect{}220 return Effect{}
221+ case ActionMaximizeColumn:
222+ // A client that made itself fullscreen (Wine at monitor size) goes
223+ // back to its column width; the next press maximizes the column.
224+ if w := m.Current(); w.fullscreen != 0 {
225+ if id, ok := w.Focused(); ok && id == w.fullscreen {
226+ m.ToggleFullscreen()
227+ return Effect{}
228+ }
229+ }
221 case ActionFocusWorkspaceUp, ActionFocusWorkspaceDown:230 case ActionFocusWorkspaceUp, ActionFocusWorkspaceDown:
222 if m.shown == nil && a == ActionFocusWorkspaceUp {231 if m.shown == nil && a == ActionFocusWorkspaceUp {
223 m.Focus(m.Active - 1)232 m.Focus(m.Active - 1)
M internal/core/monitor_test.go +29 -0
@@ -664,3 +664,32 @@ func TestMonitorFixedFullscreenEdges(t *testing.T) {
664664 }
665665 })
666666 }
667+
668+// Cmd+F on a window that made itself fullscreen returns it to its column
669+// (Wine opens monitor-sized apps fullscreen); the next press maximizes.
670+func TestMaximizeLeavesClientFullscreen(t *testing.T) {
671+ for _, o := range []Overflow{OverflowScroll, OverflowFixed} {
672+ t.Run(string(o), func(t *testing.T) {
673+ m := monitor()
674+ m.SetOverflow(o)
675+ m.AddWindow(1)
676+ m.AddWindow(2)
677+ m.SetFullscreen(2, true)
678+ m.Apply(ActionMaximizeColumn)
679+ if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2}, {}}) || m.Active != 0 {
680+ t.Fatal(got, m.Active)
681+ }
682+ w := m.Current()
683+ if w.fullscreen != 0 || w.Columns[1].FullWidth {
684+ t.Fatal(w.fullscreen, w.Columns)
685+ }
686+ if id, _ := m.Focused(); id != 2 {
687+ t.Fatal("focus", id)
688+ }
689+ m.Apply(ActionMaximizeColumn)
690+ if !w.Columns[1].FullWidth {
691+ t.Fatal(w.Columns)
692+ }
693+ })
694+ }
695+}
@@ -664,3 +664,32 @@ func TestMonitorFixedFullscreenEdges(t *testing.T) {
664 }664 }
665 })665 })
666 }666 }
667+
668+// Cmd+F on a window that made itself fullscreen returns it to its column
669+// (Wine opens monitor-sized apps fullscreen); the next press maximizes.
670+func TestMaximizeLeavesClientFullscreen(t *testing.T) {
671+ for _, o := range []Overflow{OverflowScroll, OverflowFixed} {
672+ t.Run(string(o), func(t *testing.T) {
673+ m := monitor()
674+ m.SetOverflow(o)
675+ m.AddWindow(1)
676+ m.AddWindow(2)
677+ m.SetFullscreen(2, true)
678+ m.Apply(ActionMaximizeColumn)
679+ if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2}, {}}) || m.Active != 0 {
680+ t.Fatal(got, m.Active)
681+ }
682+ w := m.Current()
683+ if w.fullscreen != 0 || w.Columns[1].FullWidth {
684+ t.Fatal(w.fullscreen, w.Columns)
685+ }
686+ if id, _ := m.Focused(); id != 2 {
687+ t.Fatal("focus", id)
688+ }
689+ m.Apply(ActionMaximizeColumn)
690+ if !w.Columns[1].FullWidth {
691+ t.Fatal(w.Columns)
692+ }
693+ })
694+ }
695+}

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