fix(core): keep a column moved onto a fullscreen workspace visible
Cross-monitor move-column and move-column/window-to-workspace now share Workspace.receive: windows float above a fixed-overflow fullscreen workspace, and an in-place fullscreen is left so the moved window shows.
- commit
- 1ddb3e5e483103aa8f179fd4ab0ad5572699b6cb
- author
- brice · · 2026-09-26 14:40 UTC
- parent
- 503de12
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +41 -9
M internal/core/action.go +1 -1
| @@ -156,7 +156,7 @@ func (c *Core) applyAction(a Action) Effect { | |||
| 156 | 156 | if dir < 0 { | |
| 157 | 157 | at = len(to.Columns) | |
| 158 | 158 | } | |
| 159 | - | to.insertColumn(at, col) | |
| 159 | + | to.receive(col, at) | |
| 160 | 160 | from.mon.normalize() | |
| 161 | 161 | c.cur().mon.normalize() | |
| 162 | 162 | return Effect{} | |
| @@ -156,7 +156,7 @@ func (c *Core) applyAction(a Action) Effect { | |||||
| 156 | if dir < 0 { | 156 | if dir < 0 { | ||
| 157 | at = len(to.Columns) | 157 | at = len(to.Columns) | ||
| 158 | } | 158 | } | ||
| 159 | - | to.insertColumn(at, col) | 159 | + | to.receive(col, at) |
| 160 | from.mon.normalize() | 160 | from.mon.normalize() | ||
| 161 | c.cur().mon.normalize() | 161 | c.cur().mon.normalize() | ||
| 162 | return Effect{} | 162 | return Effect{} | ||
M internal/core/monitor.go +21 -8
| @@ -493,6 +493,7 @@ func (m *Monitor) MoveToWorkspace(i int, column bool) { | |||
| 493 | 493 | fl := cur.Floats[f] | |
| 494 | 494 | cur.RemoveWindow(id) | |
| 495 | 495 | to.AddFloating(id, fl.W, fl.H) | |
| 496 | + | to.FocusID(id) | |
| 496 | 497 | } else { | |
| 497 | 498 | col := Column{Windows: []WindowID{id}} | |
| 498 | 499 | if column { | |
| @@ -500,22 +501,34 @@ func (m *Monitor) MoveToWorkspace(i int, column bool) { | |||
| 500 | 501 | } else { | |
| 501 | 502 | cur.RemoveWindow(id) | |
| 502 | 503 | } | |
| 503 | - | if to.origin != nil { | |
| 504 | - | for _, w := range col.Windows { | |
| 505 | - | to.joinFullscreen(w) | |
| 506 | - | } | |
| 507 | - | } else { | |
| 508 | - | to.addColumn(col) | |
| 509 | - | } | |
| 504 | + | to.receive(col, -1) | |
| 510 | 505 | } | |
| 511 | 506 | if m.followMove { | |
| 512 | 507 | m.Active = i | |
| 513 | 508 | m.shown = nil | |
| 514 | - | to.FocusID(id) | |
| 515 | 509 | } | |
| 516 | 510 | m.normalize() | |
| 517 | 511 | } | |
| 518 | 512 | ||
| 513 | + | // receive adds a column moved in from another workspace and focuses it: at | |
| 514 | + | // index at, or where a new window goes when at < 0. It stays visible: on a | |
| 515 | + | // fixed-overflow fullscreen workspace its windows float above, and an | |
| 516 | + | // in-place fullscreen is left. | |
| 517 | + | func (w *Workspace) receive(col Column, at int) { | |
| 518 | + | id := col.Windows[col.Focus] | |
| 519 | + | switch { | |
| 520 | + | case w.origin != nil: | |
| 521 | + | for _, v := range col.Windows { | |
| 522 | + | w.joinFullscreen(v) | |
| 523 | + | } | |
| 524 | + | case at < 0: | |
| 525 | + | w.addColumn(col) | |
| 526 | + | default: | |
| 527 | + | w.insertColumn(at, col) | |
| 528 | + | } | |
| 529 | + | w.Activate(id) | |
| 530 | + | } | |
| 531 | + | ||
| 519 | 532 | // joinFullscreen adds a tiled window to a fullscreen workspace fs: it floats | |
| 520 | 533 | // above the fullscreen window, which would hide it (fixed overflow), and | |
| 521 | 534 | // tiles again once home. | |
| @@ -493,6 +493,7 @@ func (m *Monitor) MoveToWorkspace(i int, column bool) { | |||||
| 493 | fl := cur.Floats[f] | 493 | fl := cur.Floats[f] | ||
| 494 | cur.RemoveWindow(id) | 494 | cur.RemoveWindow(id) | ||
| 495 | to.AddFloating(id, fl.W, fl.H) | 495 | to.AddFloating(id, fl.W, fl.H) | ||
| 496 | + | to.FocusID(id) | |||
| 496 | } else { | 497 | } else { | ||
| 497 | col := Column{Windows: []WindowID{id}} | 498 | col := Column{Windows: []WindowID{id}} | ||
| 498 | if column { | 499 | if column { | ||
| @@ -500,22 +501,34 @@ func (m *Monitor) MoveToWorkspace(i int, column bool) { | |||||
| 500 | } else { | 501 | } else { | ||
| 501 | cur.RemoveWindow(id) | 502 | cur.RemoveWindow(id) | ||
| 502 | } | 503 | } | ||
| 503 | - | if to.origin != nil { | 504 | + | to.receive(col, -1) |
| 504 | - | for _, w := range col.Windows { | |||
| 505 | - | to.joinFullscreen(w) | |||
| 506 | - | } | |||
| 507 | - | } else { | |||
| 508 | - | to.addColumn(col) | |||
| 509 | - | } | |||
| 510 | } | 505 | } | ||
| 511 | if m.followMove { | 506 | if m.followMove { | ||
| 512 | m.Active = i | 507 | m.Active = i | ||
| 513 | m.shown = nil | 508 | m.shown = nil | ||
| 514 | - | to.FocusID(id) | |||
| 515 | } | 509 | } | ||
| 516 | m.normalize() | 510 | m.normalize() | ||
| 517 | } | 511 | } | ||
| 518 | 512 | ||||
| 513 | + | // receive adds a column moved in from another workspace and focuses it: at | |||
| 514 | + | // index at, or where a new window goes when at < 0. It stays visible: on a | |||
| 515 | + | // fixed-overflow fullscreen workspace its windows float above, and an | |||
| 516 | + | // in-place fullscreen is left. | |||
| 517 | + | func (w *Workspace) receive(col Column, at int) { | |||
| 518 | + | id := col.Windows[col.Focus] | |||
| 519 | + | switch { | |||
| 520 | + | case w.origin != nil: | |||
| 521 | + | for _, v := range col.Windows { | |||
| 522 | + | w.joinFullscreen(v) | |||
| 523 | + | } | |||
| 524 | + | case at < 0: | |||
| 525 | + | w.addColumn(col) | |||
| 526 | + | default: | |||
| 527 | + | w.insertColumn(at, col) | |||
| 528 | + | } | |||
| 529 | + | w.Activate(id) | |||
| 530 | + | } | |||
| 531 | + | ||||
| 519 | // joinFullscreen adds a tiled window to a fullscreen workspace fs: it floats | 532 | // joinFullscreen adds a tiled window to a fullscreen workspace fs: it floats | ||
| 520 | // above the fullscreen window, which would hide it (fixed overflow), and | 533 | // above the fullscreen window, which would hide it (fixed overflow), and | ||
| 521 | // tiles again once home. | 534 | // tiles again once home. | ||
M internal/core/screens_test.go +19 -0
| @@ -504,3 +504,22 @@ func TestStateFollowsAppIDAndHiddenWorkspace(t *testing.T) { | |||
| 504 | 504 | t.Fatalf("%+v", st) | |
| 505 | 505 | } | |
| 506 | 506 | } | |
| 507 | + | ||
| 508 | + | func TestMoveColumnOntoFullscreenStaysVisible(t *testing.T) { | |
| 509 | + | r := startMulti(t, func(c *ports.Config) { c.Layout.Overflow = "fixed" }, left, right) | |
| 510 | + | r.mapWindow(t, 1) | |
| 511 | + | r.mapWindow(t, 2) | |
| 512 | + | // Window 2 goes to DP-2, alone, and goes fullscreen in place. | |
| 513 | + | r.key(t, "Right", ports.ModAlt|ports.ModShift) | |
| 514 | + | r.key(t, "f", ports.ModAlt|ports.ModShift) | |
| 515 | + | r.key(t, "Left", ports.ModAlt|ports.ModCtrl) | |
| 516 | + | // Window 1 joins from DP-1: the fullscreen ends so it is visible and focused. | |
| 517 | + | set := r.key(t, "Right", ports.ModAlt|ports.ModShift) | |
| 518 | + | for _, s := range set { | |
| 519 | + | for _, w := range s.Windows { | |
| 520 | + | if w.ID == 1 && (s.Output != "DP-2" || w.Hidden || !w.Focused) { | |
| 521 | + | t.Fatalf("%s %+v", s.Output, w) | |
| 522 | + | } | |
| 523 | + | } | |
| 524 | + | } | |
| 525 | + | } | |
| @@ -504,3 +504,22 @@ func TestStateFollowsAppIDAndHiddenWorkspace(t *testing.T) { | |||||
| 504 | t.Fatalf("%+v", st) | 504 | t.Fatalf("%+v", st) | ||
| 505 | } | 505 | } | ||
| 506 | } | 506 | } | ||
| 507 | + | ||||
| 508 | + | func TestMoveColumnOntoFullscreenStaysVisible(t *testing.T) { | |||
| 509 | + | r := startMulti(t, func(c *ports.Config) { c.Layout.Overflow = "fixed" }, left, right) | |||
| 510 | + | r.mapWindow(t, 1) | |||
| 511 | + | r.mapWindow(t, 2) | |||
| 512 | + | // Window 2 goes to DP-2, alone, and goes fullscreen in place. | |||
| 513 | + | r.key(t, "Right", ports.ModAlt|ports.ModShift) | |||
| 514 | + | r.key(t, "f", ports.ModAlt|ports.ModShift) | |||
| 515 | + | r.key(t, "Left", ports.ModAlt|ports.ModCtrl) | |||
| 516 | + | // Window 1 joins from DP-1: the fullscreen ends so it is visible and focused. | |||
| 517 | + | set := r.key(t, "Right", ports.ModAlt|ports.ModShift) | |||
| 518 | + | for _, s := range set { | |||
| 519 | + | for _, w := range s.Windows { | |||
| 520 | + | if w.ID == 1 && (s.Output != "DP-2" || w.Hidden || !w.Focused) { | |||
| 521 | + | t.Fatalf("%s %+v", s.Output, w) | |||
| 522 | + | } | |||
| 523 | + | } | |||
| 524 | + | } | |||
| 525 | + | } | |||