feat(layout): fixed-overflow fullscreen gets its own workspace below
- commit
- 4b313c6cdd5c716b3f0206d9a0c4329d6f6b2986
- author
- brice · · 2026-09-25 19:59 UTC
- parent
- 9bc7abf
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +157 -5
M internal/core/action.go +3 -0
| @@ -175,6 +175,9 @@ func (m *Monitor) Apply(a Action) Effect { | |||
| 175 | 175 | m.Focus(m.Active + dir) | |
| 176 | 176 | } | |
| 177 | 177 | return Effect{} | |
| 178 | + | case ActionToggleFullscreen: | |
| 179 | + | m.ToggleFullscreen() | |
| 180 | + | return Effect{} | |
| 178 | 181 | case ActionFocusWorkspaceUp, ActionFocusWorkspaceDown: | |
| 179 | 182 | if m.shown == nil && a == ActionFocusWorkspaceUp { | |
| 180 | 183 | m.Focus(m.Active - 1) | |
| @@ -175,6 +175,9 @@ func (m *Monitor) Apply(a Action) Effect { | |||||
| 175 | m.Focus(m.Active + dir) | 175 | m.Focus(m.Active + dir) | ||
| 176 | } | 176 | } | ||
| 177 | return Effect{} | 177 | return Effect{} | ||
| 178 | + | case ActionToggleFullscreen: | |||
| 179 | + | m.ToggleFullscreen() | |||
| 180 | + | return Effect{} | |||
| 178 | case ActionFocusWorkspaceUp, ActionFocusWorkspaceDown: | 181 | case ActionFocusWorkspaceUp, ActionFocusWorkspaceDown: | ||
| 179 | if m.shown == nil && a == ActionFocusWorkspaceUp { | 182 | if m.shown == nil && a == ActionFocusWorkspaceUp { | ||
| 180 | m.Focus(m.Active - 1) | 183 | m.Focus(m.Active - 1) | ||
M internal/core/layout.go +4 -0
| @@ -136,6 +136,10 @@ type Workspace struct { | |||
| 136 | 136 | homePos int | |
| 137 | 137 | // termAt is when core last spawned a terminal for this workspace. | |
| 138 | 138 | termAt time.Time | |
| 139 | + | // origin is set on the workspace a fixed-overflow fullscreen window | |
| 140 | + | // moved to; the window returns to column originCol there. | |
| 141 | + | origin *Workspace | |
| 142 | + | originCol int | |
| 139 | 143 | } | |
| 140 | 144 | ||
| 141 | 145 | // Float is a floating window and its client size, logical. | |
| @@ -136,6 +136,10 @@ type Workspace struct { | |||||
| 136 | homePos int | 136 | homePos int | ||
| 137 | // termAt is when core last spawned a terminal for this workspace. | 137 | // termAt is when core last spawned a terminal for this workspace. | ||
| 138 | termAt time.Time | 138 | termAt time.Time | ||
| 139 | + | // origin is set on the workspace a fixed-overflow fullscreen window | |||
| 140 | + | // moved to; the window returns to column originCol there. | |||
| 141 | + | origin *Workspace | |||
| 142 | + | originCol int | |||
| 139 | } | 143 | } | ||
| 140 | 144 | ||||
| 141 | // Float is a floating window and its client size, logical. | 145 | // Float is a floating window and its client size, logical. | ||
M internal/core/monitor.go +92 -5
| @@ -117,7 +117,7 @@ func (m *Monitor) Current() *Workspace { | |||
| 117 | 117 | func (m *Monitor) newWorkspace() *Workspace { | |
| 118 | 118 | w := m.template | |
| 119 | 119 | w.presets = append([]Width(nil), m.template.presets...) | |
| 120 | - | w.Columns, w.Floats, w.floatFocus, w.home = nil, nil, false, "" | |
| 120 | + | w.Columns, w.Floats, w.floatFocus, w.home, w.origin = nil, nil, false, "", nil | |
| 121 | 121 | return &w | |
| 122 | 122 | } | |
| 123 | 123 | ||
| @@ -240,20 +240,107 @@ func (m *Monitor) AddFloating(id WindowID, width, height int) { | |||
| 240 | 240 | m.normalize() | |
| 241 | 241 | } | |
| 242 | 242 | ||
| 243 | - | // RemoveWindow drops the window wherever it is; focus stays on the active workspace. | |
| 243 | + | // RemoveWindow drops the window wherever it is; focus stays on the active | |
| 244 | + | // workspace, except that closing a fullscreen workspace goes back to its origin. | |
| 244 | 245 | func (m *Monitor) RemoveWindow(id WindowID) { | |
| 245 | 246 | if w, _ := m.find(id); w != nil { | |
| 246 | 247 | w.RemoveWindow(id) | |
| 248 | + | if w.origin != nil && w.empty() { | |
| 249 | + | m.leaveFullscreen(w) | |
| 250 | + | } | |
| 247 | 251 | m.normalize() | |
| 248 | 252 | } | |
| 249 | 253 | } | |
| 250 | 254 | ||
| 251 | - | // SetFullscreen applies a client request on the window's own workspace. It does | |
| 252 | - | // not switch workspaces: client requests never move the user. | |
| 255 | + | // SetFullscreen applies a client request. Under fixed overflow the window | |
| 256 | + | // gets its own workspace, as with the bind; the view follows only when the | |
| 257 | + | // window is the focused one on screen, so a request never takes the user | |
| 258 | + | // away from what they look at. | |
| 253 | 259 | func (m *Monitor) SetFullscreen(id WindowID, on bool) { | |
| 254 | - | if w, _ := m.find(id); w != nil { | |
| 260 | + | w, _ := m.find(id) | |
| 261 | + | switch { | |
| 262 | + | case w == nil: | |
| 263 | + | case on && w.origin == nil && w.fullscreen != id && m.ownWorkspace(w, id): | |
| 264 | + | focused, _ := w.Focused() | |
| 265 | + | m.enterFullscreen(w, id, w == m.Current() && focused == id) | |
| 266 | + | case !on && w.origin != nil && w.fullscreen == id: | |
| 267 | + | m.leaveFullscreen(w) | |
| 268 | + | default: | |
| 255 | 269 | w.SetFullscreen(id, on) | |
| 256 | 270 | } | |
| 271 | + | m.normalize() | |
| 272 | + | } | |
| 273 | + | ||
| 274 | + | // ToggleFullscreen is the bind. Under fixed overflow the focused window | |
| 275 | + | // moves to a new workspace below the current one, so workspace up/down | |
| 276 | + | // still reach the others; toggling again sends it back where it was. | |
| 277 | + | func (m *Monitor) ToggleFullscreen() { | |
| 278 | + | w := m.Current() | |
| 279 | + | id, ok := w.Focused() | |
| 280 | + | switch { | |
| 281 | + | case !ok: | |
| 282 | + | case w.origin != nil && w.fullscreen == id: | |
| 283 | + | m.leaveFullscreen(w) | |
| 284 | + | case w.fullscreen != id && m.ownWorkspace(w, id): | |
| 285 | + | m.enterFullscreen(w, id, true) | |
| 286 | + | default: | |
| 287 | + | w.ToggleFullscreen() | |
| 288 | + | } | |
| 289 | + | m.normalize() | |
| 290 | + | } | |
| 291 | + | ||
| 292 | + | // ownWorkspace reports whether going fullscreen moves the window to its own | |
| 293 | + | // workspace: fixed overflow, a tiled window, and others to leave behind. | |
| 294 | + | func (m *Monitor) ownWorkspace(w *Workspace, id WindowID) bool { | |
| 295 | + | return w.Overflow == OverflowFixed && w.floatIndex(id) < 0 && len(w.windows()) > 1 && indexOf(m.Workspaces, w) >= 0 | |
| 296 | + | } | |
| 297 | + | ||
| 298 | + | // enterFullscreen moves the tiled window id from w to a new workspace just | |
| 299 | + | // below it, fullscreen, and shows it when show is set. | |
| 300 | + | func (m *Monitor) enterFullscreen(w *Workspace, id WindowID, show bool) { | |
| 301 | + | col := slices.IndexFunc(w.Columns, func(c Column) bool { return slices.Contains(c.Windows, id) }) | |
| 302 | + | width := w.Columns[col].Width | |
| 303 | + | w.RemoveWindow(id) | |
| 304 | + | fs := m.newWorkspace() | |
| 305 | + | fs.Overflow, fs.MaxColumns = w.Overflow, w.MaxColumns | |
| 306 | + | fs.origin, fs.originCol = w, col | |
| 307 | + | fs.AddWindow(id) | |
| 308 | + | fs.Columns[0].Width = width | |
| 309 | + | fs.SetFullscreen(id, true) | |
| 310 | + | at := indexOf(m.Workspaces, w) + 1 | |
| 311 | + | m.Workspaces = slices.Insert(m.Workspaces, at, fs) | |
| 312 | + | if at <= m.Active { | |
| 313 | + | m.Active++ | |
| 314 | + | } | |
| 315 | + | if show { | |
| 316 | + | m.Focus(at) | |
| 317 | + | } | |
| 318 | + | } | |
| 319 | + | ||
| 320 | + | // leaveFullscreen sends the window of the fullscreen workspace fs back to | |
| 321 | + | // its origin column and drops fs. The view goes along when fs was on | |
| 322 | + | // screen. Without the origin, fs becomes a normal workspace. | |
| 323 | + | func (m *Monitor) leaveFullscreen(fs *Workspace) { | |
| 324 | + | origin := fs.origin | |
| 325 | + | fs.origin = nil | |
| 326 | + | fs.fullscreen = 0 | |
| 327 | + | if !m.has(origin) { | |
| 328 | + | return | |
| 329 | + | } | |
| 330 | + | if len(fs.Columns) > 0 { | |
| 331 | + | col := fs.Columns[0] | |
| 332 | + | col.Focus = 0 | |
| 333 | + | fs.Columns = fs.Columns[1:] | |
| 334 | + | origin.insertColumn(fs.originCol, col) | |
| 335 | + | } | |
| 336 | + | if !fs.empty() { | |
| 337 | + | return | |
| 338 | + | } | |
| 339 | + | shown := m.Current() == fs | |
| 340 | + | m.take(fs) | |
| 341 | + | if shown { | |
| 342 | + | m.show(origin) | |
| 343 | + | } | |
| 257 | 344 | } | |
| 258 | 345 | ||
| 259 | 346 | // MoveToWorkspace moves the focused window to numbered workspace index i; | |
| @@ -117,7 +117,7 @@ func (m *Monitor) Current() *Workspace { | |||||
| 117 | func (m *Monitor) newWorkspace() *Workspace { | 117 | func (m *Monitor) newWorkspace() *Workspace { | ||
| 118 | w := m.template | 118 | w := m.template | ||
| 119 | w.presets = append([]Width(nil), m.template.presets...) | 119 | w.presets = append([]Width(nil), m.template.presets...) | ||
| 120 | - | w.Columns, w.Floats, w.floatFocus, w.home = nil, nil, false, "" | 120 | + | w.Columns, w.Floats, w.floatFocus, w.home, w.origin = nil, nil, false, "", nil |
| 121 | return &w | 121 | return &w | ||
| 122 | } | 122 | } | ||
| 123 | 123 | ||||
| @@ -240,20 +240,107 @@ func (m *Monitor) AddFloating(id WindowID, width, height int) { | |||||
| 240 | m.normalize() | 240 | m.normalize() | ||
| 241 | } | 241 | } | ||
| 242 | 242 | ||||
| 243 | - | // RemoveWindow drops the window wherever it is; focus stays on the active workspace. | 243 | + | // RemoveWindow drops the window wherever it is; focus stays on the active |
| 244 | + | // workspace, except that closing a fullscreen workspace goes back to its origin. | |||
| 244 | func (m *Monitor) RemoveWindow(id WindowID) { | 245 | func (m *Monitor) RemoveWindow(id WindowID) { | ||
| 245 | if w, _ := m.find(id); w != nil { | 246 | if w, _ := m.find(id); w != nil { | ||
| 246 | w.RemoveWindow(id) | 247 | w.RemoveWindow(id) | ||
| 248 | + | if w.origin != nil && w.empty() { | |||
| 249 | + | m.leaveFullscreen(w) | |||
| 250 | + | } | |||
| 247 | m.normalize() | 251 | m.normalize() | ||
| 248 | } | 252 | } | ||
| 249 | } | 253 | } | ||
| 250 | 254 | ||||
| 251 | - | // SetFullscreen applies a client request on the window's own workspace. It does | 255 | + | // SetFullscreen applies a client request. Under fixed overflow the window |
| 252 | - | // not switch workspaces: client requests never move the user. | 256 | + | // gets its own workspace, as with the bind; the view follows only when the |
| 257 | + | // window is the focused one on screen, so a request never takes the user | |||
| 258 | + | // away from what they look at. | |||
| 253 | func (m *Monitor) SetFullscreen(id WindowID, on bool) { | 259 | func (m *Monitor) SetFullscreen(id WindowID, on bool) { | ||
| 254 | - | if w, _ := m.find(id); w != nil { | 260 | + | w, _ := m.find(id) |
| 261 | + | switch { | |||
| 262 | + | case w == nil: | |||
| 263 | + | case on && w.origin == nil && w.fullscreen != id && m.ownWorkspace(w, id): | |||
| 264 | + | focused, _ := w.Focused() | |||
| 265 | + | m.enterFullscreen(w, id, w == m.Current() && focused == id) | |||
| 266 | + | case !on && w.origin != nil && w.fullscreen == id: | |||
| 267 | + | m.leaveFullscreen(w) | |||
| 268 | + | default: | |||
| 255 | w.SetFullscreen(id, on) | 269 | w.SetFullscreen(id, on) | ||
| 256 | } | 270 | } | ||
| 271 | + | m.normalize() | |||
| 272 | + | } | |||
| 273 | + | ||||
| 274 | + | // ToggleFullscreen is the bind. Under fixed overflow the focused window | |||
| 275 | + | // moves to a new workspace below the current one, so workspace up/down | |||
| 276 | + | // still reach the others; toggling again sends it back where it was. | |||
| 277 | + | func (m *Monitor) ToggleFullscreen() { | |||
| 278 | + | w := m.Current() | |||
| 279 | + | id, ok := w.Focused() | |||
| 280 | + | switch { | |||
| 281 | + | case !ok: | |||
| 282 | + | case w.origin != nil && w.fullscreen == id: | |||
| 283 | + | m.leaveFullscreen(w) | |||
| 284 | + | case w.fullscreen != id && m.ownWorkspace(w, id): | |||
| 285 | + | m.enterFullscreen(w, id, true) | |||
| 286 | + | default: | |||
| 287 | + | w.ToggleFullscreen() | |||
| 288 | + | } | |||
| 289 | + | m.normalize() | |||
| 290 | + | } | |||
| 291 | + | ||||
| 292 | + | // ownWorkspace reports whether going fullscreen moves the window to its own | |||
| 293 | + | // workspace: fixed overflow, a tiled window, and others to leave behind. | |||
| 294 | + | func (m *Monitor) ownWorkspace(w *Workspace, id WindowID) bool { | |||
| 295 | + | return w.Overflow == OverflowFixed && w.floatIndex(id) < 0 && len(w.windows()) > 1 && indexOf(m.Workspaces, w) >= 0 | |||
| 296 | + | } | |||
| 297 | + | ||||
| 298 | + | // enterFullscreen moves the tiled window id from w to a new workspace just | |||
| 299 | + | // below it, fullscreen, and shows it when show is set. | |||
| 300 | + | func (m *Monitor) enterFullscreen(w *Workspace, id WindowID, show bool) { | |||
| 301 | + | col := slices.IndexFunc(w.Columns, func(c Column) bool { return slices.Contains(c.Windows, id) }) | |||
| 302 | + | width := w.Columns[col].Width | |||
| 303 | + | w.RemoveWindow(id) | |||
| 304 | + | fs := m.newWorkspace() | |||
| 305 | + | fs.Overflow, fs.MaxColumns = w.Overflow, w.MaxColumns | |||
| 306 | + | fs.origin, fs.originCol = w, col | |||
| 307 | + | fs.AddWindow(id) | |||
| 308 | + | fs.Columns[0].Width = width | |||
| 309 | + | fs.SetFullscreen(id, true) | |||
| 310 | + | at := indexOf(m.Workspaces, w) + 1 | |||
| 311 | + | m.Workspaces = slices.Insert(m.Workspaces, at, fs) | |||
| 312 | + | if at <= m.Active { | |||
| 313 | + | m.Active++ | |||
| 314 | + | } | |||
| 315 | + | if show { | |||
| 316 | + | m.Focus(at) | |||
| 317 | + | } | |||
| 318 | + | } | |||
| 319 | + | ||||
| 320 | + | // leaveFullscreen sends the window of the fullscreen workspace fs back to | |||
| 321 | + | // its origin column and drops fs. The view goes along when fs was on | |||
| 322 | + | // screen. Without the origin, fs becomes a normal workspace. | |||
| 323 | + | func (m *Monitor) leaveFullscreen(fs *Workspace) { | |||
| 324 | + | origin := fs.origin | |||
| 325 | + | fs.origin = nil | |||
| 326 | + | fs.fullscreen = 0 | |||
| 327 | + | if !m.has(origin) { | |||
| 328 | + | return | |||
| 329 | + | } | |||
| 330 | + | if len(fs.Columns) > 0 { | |||
| 331 | + | col := fs.Columns[0] | |||
| 332 | + | col.Focus = 0 | |||
| 333 | + | fs.Columns = fs.Columns[1:] | |||
| 334 | + | origin.insertColumn(fs.originCol, col) | |||
| 335 | + | } | |||
| 336 | + | if !fs.empty() { | |||
| 337 | + | return | |||
| 338 | + | } | |||
| 339 | + | shown := m.Current() == fs | |||
| 340 | + | m.take(fs) | |||
| 341 | + | if shown { | |||
| 342 | + | m.show(origin) | |||
| 343 | + | } | |||
| 257 | } | 344 | } | ||
| 258 | 345 | ||||
| 259 | // MoveToWorkspace moves the focused window to numbered workspace index i; | 346 | // MoveToWorkspace moves the focused window to numbered workspace index i; | ||
M internal/core/monitor_test.go +58 -0
| @@ -2,6 +2,7 @@ package core | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "reflect" | |
| 5 | + | "slices" | |
| 5 | 6 | "testing" | |
| 6 | 7 | ) | |
| 7 | 8 | ||
| @@ -417,3 +418,60 @@ func TestSetNamedTransitions(t *testing.T) { | |||
| 417 | 418 | }) | |
| 418 | 419 | } | |
| 419 | 420 | } | |
| 421 | + | ||
| 422 | + | // Under fixed overflow, fullscreen moves the window to its own workspace | |
| 423 | + | // below; workspace up/down still work and leaving brings it back in place. | |
| 424 | + | func TestMonitorFixedFullscreenOwnWorkspace(t *testing.T) { | |
| 425 | + | m := monitor() | |
| 426 | + | m.SetOverflow(OverflowFixed) | |
| 427 | + | for id := WindowID(1); id <= 3; id++ { | |
| 428 | + | m.AddWindow(id) | |
| 429 | + | } | |
| 430 | + | m.Current().FocusID(2) | |
| 431 | + | m.ToggleFullscreen() | |
| 432 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 3}, {2}, {}}) || m.Active != 1 { | |
| 433 | + | t.Fatal(got, m.Active) | |
| 434 | + | } | |
| 435 | + | if p := m.Layout(); !slices.ContainsFunc(p, func(p Placement) bool { return p.ID == 2 && p.Fullscreen && !p.Hidden }) { | |
| 436 | + | t.Fatal(p) | |
| 437 | + | } | |
| 438 | + | m.Apply(ActionFocusWorkspaceUp) | |
| 439 | + | if m.Active != 0 { | |
| 440 | + | t.Fatal("stuck on the fullscreen workspace") | |
| 441 | + | } | |
| 442 | + | m.Apply(ActionFocusWorkspaceDown) | |
| 443 | + | m.ToggleFullscreen() | |
| 444 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2, 3}, {}}) || m.Active != 0 { | |
| 445 | + | t.Fatal(got, m.Active) | |
| 446 | + | } | |
| 447 | + | if id, _ := m.Focused(); id != 2 { | |
| 448 | + | t.Fatal("focus", id) | |
| 449 | + | } | |
| 450 | + | ||
| 451 | + | // A client request moves it too, without taking the user along | |
| 452 | + | // unless the window is the focused one on screen. | |
| 453 | + | m.Current().FocusID(1) | |
| 454 | + | m.SetFullscreen(3, true) | |
| 455 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2}, {3}, {}}) || m.Active != 0 { | |
| 456 | + | t.Fatal(got, m.Active) | |
| 457 | + | } | |
| 458 | + | m.SetFullscreen(3, false) | |
| 459 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2, 3}, {}}) { | |
| 460 | + | t.Fatal(got) | |
| 461 | + | } | |
| 462 | + | ||
| 463 | + | // Closing the fullscreen window drops its workspace and returns. | |
| 464 | + | m.Current().FocusID(3) | |
| 465 | + | m.ToggleFullscreen() | |
| 466 | + | m.RemoveWindow(3) | |
| 467 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2}, {}}) || m.Active != 0 { | |
| 468 | + | t.Fatal(got, m.Active) | |
| 469 | + | } | |
| 470 | + | ||
| 471 | + | // Scroll overflow keeps the niri behaviour: same workspace. | |
| 472 | + | m.SetOverflow(OverflowScroll) | |
| 473 | + | m.ToggleFullscreen() | |
| 474 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2}, {}}) { | |
| 475 | + | t.Fatal(got) | |
| 476 | + | } | |
| 477 | + | } | |
| @@ -2,6 +2,7 @@ package core | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "reflect" | 4 | "reflect" | ||
| 5 | + | "slices" | |||
| 5 | "testing" | 6 | "testing" | ||
| 6 | ) | 7 | ) | ||
| 7 | 8 | ||||
| @@ -417,3 +418,60 @@ func TestSetNamedTransitions(t *testing.T) { | |||||
| 417 | }) | 418 | }) | ||
| 418 | } | 419 | } | ||
| 419 | } | 420 | } | ||
| 421 | + | ||||
| 422 | + | // Under fixed overflow, fullscreen moves the window to its own workspace | |||
| 423 | + | // below; workspace up/down still work and leaving brings it back in place. | |||
| 424 | + | func TestMonitorFixedFullscreenOwnWorkspace(t *testing.T) { | |||
| 425 | + | m := monitor() | |||
| 426 | + | m.SetOverflow(OverflowFixed) | |||
| 427 | + | for id := WindowID(1); id <= 3; id++ { | |||
| 428 | + | m.AddWindow(id) | |||
| 429 | + | } | |||
| 430 | + | m.Current().FocusID(2) | |||
| 431 | + | m.ToggleFullscreen() | |||
| 432 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 3}, {2}, {}}) || m.Active != 1 { | |||
| 433 | + | t.Fatal(got, m.Active) | |||
| 434 | + | } | |||
| 435 | + | if p := m.Layout(); !slices.ContainsFunc(p, func(p Placement) bool { return p.ID == 2 && p.Fullscreen && !p.Hidden }) { | |||
| 436 | + | t.Fatal(p) | |||
| 437 | + | } | |||
| 438 | + | m.Apply(ActionFocusWorkspaceUp) | |||
| 439 | + | if m.Active != 0 { | |||
| 440 | + | t.Fatal("stuck on the fullscreen workspace") | |||
| 441 | + | } | |||
| 442 | + | m.Apply(ActionFocusWorkspaceDown) | |||
| 443 | + | m.ToggleFullscreen() | |||
| 444 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2, 3}, {}}) || m.Active != 0 { | |||
| 445 | + | t.Fatal(got, m.Active) | |||
| 446 | + | } | |||
| 447 | + | if id, _ := m.Focused(); id != 2 { | |||
| 448 | + | t.Fatal("focus", id) | |||
| 449 | + | } | |||
| 450 | + | ||||
| 451 | + | // A client request moves it too, without taking the user along | |||
| 452 | + | // unless the window is the focused one on screen. | |||
| 453 | + | m.Current().FocusID(1) | |||
| 454 | + | m.SetFullscreen(3, true) | |||
| 455 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2}, {3}, {}}) || m.Active != 0 { | |||
| 456 | + | t.Fatal(got, m.Active) | |||
| 457 | + | } | |||
| 458 | + | m.SetFullscreen(3, false) | |||
| 459 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2, 3}, {}}) { | |||
| 460 | + | t.Fatal(got) | |||
| 461 | + | } | |||
| 462 | + | ||||
| 463 | + | // Closing the fullscreen window drops its workspace and returns. | |||
| 464 | + | m.Current().FocusID(3) | |||
| 465 | + | m.ToggleFullscreen() | |||
| 466 | + | m.RemoveWindow(3) | |||
| 467 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2}, {}}) || m.Active != 0 { | |||
| 468 | + | t.Fatal(got, m.Active) | |||
| 469 | + | } | |||
| 470 | + | ||||
| 471 | + | // Scroll overflow keeps the niri behaviour: same workspace. | |||
| 472 | + | m.SetOverflow(OverflowScroll) | |||
| 473 | + | m.ToggleFullscreen() | |||
| 474 | + | if got := windows(m); !reflect.DeepEqual(got, [][]WindowID{{1, 2}, {}}) { | |||
| 475 | + | t.Fatal(got) | |||
| 476 | + | } | |||
| 477 | + | } | |||