detached 2ff6b06

refactor(core): simplify column switcher state and flow

Drop switcherState.open (ws != nil says it), the dead checks of switcherTick and the switch actions from overviewBlocks, share the snapshot-transition-publish-rehit sequence in switcherRun, and name viewShot.preview for both previews.
commit
2ff6b061812e0a553d83e4311822491903400a50
author
brice · · 2026-10-08 18:19 UTC
parent
17b575a
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
8 files · +132 -122
M internal/core/input.go +1 -1
@@ -236,7 +236,7 @@ func (c *Core) keyEvent(ctx context.Context, key ports.KeyEvent) error {
236236 return err
237237 }
238238 }
239- if c.switching() && key.Pressed && keyName(key.Keysym) == "Escape" {
239+ if c.switching() && key.Pressed && key.Keysym == "Escape" {
240240 // Escape cancels the switcher; its release goes nowhere either.
241241 c.pressed[heldKey(key)] = true
242242 return c.abortSwitcher(ctx)
@@ -236,7 +236,7 @@ func (c *Core) keyEvent(ctx context.Context, key ports.KeyEvent) error {
236 return err236 return err
237 }237 }
238 }238 }
239- if c.switching() && key.Pressed && keyName(key.Keysym) == "Escape" {239+ if c.switching() && key.Pressed && key.Keysym == "Escape" {
240 // Escape cancels the switcher; its release goes nowhere either.240 // Escape cancels the switcher; its release goes nowhere either.
241 c.pressed[heldKey(key)] = true241 c.pressed[heldKey(key)] = true
242 return c.abortSwitcher(ctx)242 return c.abortSwitcher(ctx)
M internal/core/layers.go +1 -1
@@ -88,7 +88,7 @@ func (c *Core) layerOf(id WindowID) (*screen, ports.SceneLayer, bool) {
8888
8989 // hasFullscreen reports whether a fullscreen window covers the screen. In
9090 // scroll overflow, one scrolled off by the user no longer does; nor does
91-// one shown as an overview preview.
91+// one shown as a preview (the overview or the column switcher).
9292 func hasFullscreen(sc *screen) bool {
9393 return !sc.mon.previewing() && sc.mon.Current().cover() != 0
9494 }
@@ -88,7 +88,7 @@ func (c *Core) layerOf(id WindowID) (*screen, ports.SceneLayer, bool) {
88 88
89 // hasFullscreen reports whether a fullscreen window covers the screen. In89 // hasFullscreen reports whether a fullscreen window covers the screen. In
90 // scroll overflow, one scrolled off by the user no longer does; nor does90 // scroll overflow, one scrolled off by the user no longer does; nor does
91-// one shown as an overview preview.91+// one shown as a preview (the overview or the column switcher).
92 func hasFullscreen(sc *screen) bool {92 func hasFullscreen(sc *screen) bool {
93 return !sc.mon.previewing() && sc.mon.Current().cover() != 093 return !sc.mon.previewing() && sc.mon.Current().cover() != 0
94 }94 }
M internal/core/monitor.go +2 -1
@@ -449,7 +449,8 @@ func (m *Monitor) layoutInto(dst []Placement) []Placement {
449449 func (m *Monitor) Output() Rect { return m.template.Output }
450450
451451 // Frame is where the workspace on screen draws and takes input, in monitor
452-// coordinates: its viewport, or the whole monitor in the overview. Slides
452+// coordinates: its viewport, or the whole monitor in a preview (the
453+// overview or the column switcher). Slides
453454 // involving a sized workspace remain settled inside the current frame.
454455 func (m *Monitor) Frame() Rect {
455456 if m.previewing() {
@@ -449,7 +449,8 @@ func (m *Monitor) layoutInto(dst []Placement) []Placement {
449 func (m *Monitor) Output() Rect { return m.template.Output }449 func (m *Monitor) Output() Rect { return m.template.Output }
450 450
451 // Frame is where the workspace on screen draws and takes input, in monitor451 // Frame is where the workspace on screen draws and takes input, in monitor
452-// coordinates: its viewport, or the whole monitor in the overview. Slides452+// coordinates: its viewport, or the whole monitor in a preview (the
453+// overview or the column switcher). Slides
453 // involving a sized workspace remain settled inside the current frame.454 // involving a sized workspace remain settled inside the current frame.
454 func (m *Monitor) Frame() Rect {455 func (m *Monitor) Frame() Rect {
455 if m.previewing() {456 if m.previewing() {
M internal/core/overview.go +1 -2
@@ -202,8 +202,7 @@ func overviewBlocks(a Action) bool {
202202 ActionConsumeOrExpelRight, ActionMoveWorkspaceLeft, ActionMoveWorkspaceRight,
203203 ActionMoveWorkspaceToMonitorUp, ActionMoveWorkspaceToMonitorDown,
204204 ActionMoveWindowUp, ActionMoveWindowDown, ActionMoveWorkspacePrev,
205- ActionMoveWorkspaceNext, ActionToggleFloating, ActionSwitchColumnNext,
206- ActionSwitchColumnPrev:
205+ ActionMoveWorkspaceNext, ActionToggleFloating:
207206 return true
208207 }
209208 return false
@@ -202,8 +202,7 @@ func overviewBlocks(a Action) bool {
202 ActionConsumeOrExpelRight, ActionMoveWorkspaceLeft, ActionMoveWorkspaceRight,202 ActionConsumeOrExpelRight, ActionMoveWorkspaceLeft, ActionMoveWorkspaceRight,
203 ActionMoveWorkspaceToMonitorUp, ActionMoveWorkspaceToMonitorDown,203 ActionMoveWorkspaceToMonitorUp, ActionMoveWorkspaceToMonitorDown,
204 ActionMoveWindowUp, ActionMoveWindowDown, ActionMoveWorkspacePrev,204 ActionMoveWindowUp, ActionMoveWindowDown, ActionMoveWorkspacePrev,
205- ActionMoveWorkspaceNext, ActionToggleFloating, ActionSwitchColumnNext,205+ ActionMoveWorkspaceNext, ActionToggleFloating:
206- ActionSwitchColumnPrev:
207 return true206 return true
208 }207 }
209 return false208 return false
M internal/core/screens.go +2 -1
@@ -64,7 +64,8 @@ func (s *screen) settled() bool { return s.mon.settled() && len(s.rects) == 0 }
6464 func (s *screen) stopRects() { clear(s.rects); s.rectsWS = nil }
6565
6666 // rectsOwner is what the screen's rect motions belong to: its workspace on
67-// screen, or nil in the overview, where the rows change under the cards.
67+// screen, or nil in a preview (the overview or the column switcher), where
68+// the cards change under the windows.
6869 func (s *screen) rectsOwner() *Workspace {
6970 if s.mon.previewing() {
7071 return nil
@@ -64,7 +64,8 @@ func (s *screen) settled() bool { return s.mon.settled() && len(s.rects) == 0 }
64 func (s *screen) stopRects() { clear(s.rects); s.rectsWS = nil }64 func (s *screen) stopRects() { clear(s.rects); s.rectsWS = nil }
65 65
66 // rectsOwner is what the screen's rect motions belong to: its workspace on66 // rectsOwner is what the screen's rect motions belong to: its workspace on
67-// screen, or nil in the overview, where the rows change under the cards.67+// screen, or nil in a preview (the overview or the column switcher), where
68+// the cards change under the windows.
68 func (s *screen) rectsOwner() *Workspace {69 func (s *screen) rectsOwner() *Workspace {
69 if s.mon.previewing() {70 if s.mon.previewing() {
70 return nil71 return nil
M internal/core/switcher.go +38 -49
@@ -23,10 +23,11 @@ const switcherDelay = 150 * time.Millisecond
2323 // column in order, the focused column first. Nothing moves until the commit;
2424 // shown is set once the cards are drawn.
2525 type switcherState struct {
26- open, shown bool
27- ws *Workspace
28- order []WindowID
29- at int
26+ // ws is the workspace the switcher opened on; nil when closed.
27+ ws *Workspace
28+ shown bool
29+ order []WindowID
30+ at int
3031 // sizes are the client sizes the cards are scaled from.
3132 sizes map[WindowID]Rect
3233 }
@@ -94,7 +95,7 @@ func (w *Workspace) switchOrder() []WindowID {
9495 // heir is the window of id's column that is not id, or 0: call it before id
9596 // is removed, then hand it to remove.
9697 func (s *switcherState) heir(id WindowID) WindowID {
97- if !s.open || s.ws == nil || !slices.Contains(s.order, id) {
98+ if s.ws == nil || !slices.Contains(s.order, id) {
9899 return 0
99100 }
100101 if i := s.ws.columnOf(id); i >= 0 {
@@ -113,7 +114,7 @@ func (s *switcherState) heir(id WindowID) WindowID {
113114 // goes, and the selection stays on the same card (or the one that took its
114115 // place). With fewer than two columns left there is nothing to switch.
115116 func (s *switcherState) remove(id, heir WindowID) {
116- if !s.open {
117+ if s.ws == nil {
117118 return
118119 }
119120 i := slices.Index(s.order, id)
@@ -150,7 +151,7 @@ func (s *switcherState) remove(id, heir WindowID) {
150151 func (m *Monitor) commitSwitcher() bool {
151152 s := m.sw
152153 m.sw = switcherState{}
153- if !s.open || s.ws == nil || s.at < 0 || s.at >= len(s.order) {
154+ if s.ws == nil || s.at < 0 || s.at >= len(s.order) {
154155 return false
155156 }
156157 id, w := s.order[s.at], s.ws
@@ -185,14 +186,14 @@ func switchDir(a Action) int {
185186
186187 // switching reports whether a switcher is open.
187188 func (c *Core) switching() bool {
188- return c.switcher.sc != nil && c.switcher.sc.mon.sw.open
189+ return c.switcher.sc != nil && c.switcher.sc.mon.sw.ws != nil
189190 }
190191
191192 // switcherStale reports whether the open switcher belongs to a workspace
192193 // that is not on its screen any more.
193194 func (c *Core) switcherStale() bool {
194195 m := c.switcher.sc.mon
195- return m.sw.open && m.sw.ws != m.Current()
196+ return m.sw.ws != nil && m.sw.ws != m.Current()
196197 }
197198
198199 func (c *Core) stopSwitcherTimer() {
@@ -260,7 +261,7 @@ func (c *Core) switchStep(dir int) {
260261 if dir < 0 {
261262 at = len(order) - 1
262263 }
263- m.sw = switcherState{open: true, ws: w, order: order, at: at, sizes: sizes}
264+ m.sw = switcherState{ws: w, order: order, at: at, sizes: sizes}
264265 c.switcher.sc = sc
265266 opened = true
266267 } else {
@@ -276,12 +277,13 @@ func (c *Core) switchStep(dir int) {
276277 }
277278 }
278279
279-// switchBind runs a switch-column bind.
280-func (c *Core) switchBind(ctx context.Context, dir int) error {
280+// switcherRun runs f, which changes the switcher, between a snapshot and
281+// the transition of what the screens showed, then publishes and rehits so
282+// the pointer never points at a stale window.
283+func (c *Core) switcherRun(ctx context.Context, f func()) error {
281284 now := c.now()
282285 shots := c.snapshot(now)
283- c.keyboard.takeBack() // a bind acts on the windows
284- c.switchStep(dir)
286+ f()
285287 c.transition(shots, now)
286288 if err := c.publish(ctx); err != nil {
287289 return err
@@ -289,28 +291,22 @@ func (c *Core) switchBind(ctx context.Context, dir int) error {
289291 return c.rehit(ctx)
290292 }
291293
294+// switchBind runs a switch-column bind.
295+func (c *Core) switchBind(ctx context.Context, dir int) error {
296+ return c.switcherRun(ctx, func() {
297+ c.keyboard.takeBack() // a bind acts on the windows
298+ c.switchStep(dir)
299+ })
300+}
301+
292302 // releaseSwitcher commits the selection once the command key is up.
293303 func (c *Core) releaseSwitcher(ctx context.Context) error {
294- now := c.now()
295- shots := c.snapshot(now)
296- c.commitSwitcher()
297- c.transition(shots, now)
298- if err := c.publish(ctx); err != nil {
299- return err
300- }
301- return c.rehit(ctx)
304+ return c.switcherRun(ctx, c.commitSwitcher)
302305 }
303306
304307 // abortSwitcher cancels the switcher and publishes the workspace again.
305308 func (c *Core) abortSwitcher(ctx context.Context) error {
306- now := c.now()
307- shots := c.snapshot(now)
308- c.cancelSwitcher()
309- c.transition(shots, now)
310- if err := c.publish(ctx); err != nil {
311- return err
312- }
313- return c.rehit(ctx)
309+ return c.switcherRun(ctx, c.cancelSwitcher)
314310 }
315311
316312 // switcherTick runs when the delay passed with the command key held: the
@@ -323,13 +319,10 @@ func (c *Core) switcherTick() bool {
323319 return false
324320 }
325321 m := sc.mon
326- if !c.hasScreen(sc) || c.security.Protected || m.sw.ws != m.Current() {
322+ if m.sw.ws != m.Current() {
327323 c.cancelSwitcher()
328324 return true
329325 }
330- if m.sw.shown {
331- return false
332- }
333326 now := c.now()
334327 shots := c.snapshot(now)
335328 m.sw.shown = true
@@ -350,21 +343,17 @@ func (c *Core) switcherClick(ctx context.Context, button uint32) (handled bool,
350343 c.cancelSwitcher()
351344 return false, nil
352345 }
353- now := c.now()
354- shots := c.snapshot(now)
355- id := overviewIn(sc.shownLayout(), c.cursorX-float64(sc.x), c.cursorY-float64(sc.y))
356- if i := slices.Index(sc.mon.sw.order, id); id != 0 && i >= 0 {
357- sc.mon.sw.at = i
358- c.commitSwitcher()
359- } else {
360- c.cancelSwitcher()
361- }
362- c.buttons[button], c.swallow[button] = true, true
363- c.transition(shots, now)
364- if err := c.publish(ctx); err != nil {
365- return true, err
366- }
367- return true, c.rehit(ctx)
346+ err = c.switcherRun(ctx, func() {
347+ id := overviewIn(sc.shownLayout(), c.cursorX-float64(sc.x), c.cursorY-float64(sc.y))
348+ if i := slices.Index(sc.mon.sw.order, id); id != 0 && i >= 0 {
349+ sc.mon.sw.at = i
350+ c.commitSwitcher()
351+ } else {
352+ c.cancelSwitcher()
353+ }
354+ c.buttons[button], c.swallow[button] = true, true
355+ })
356+ return true, err
368357 }
369358
370359 // switcherLayout lays out the shown switcher: one card per column in
@@ -23,10 +23,11 @@ const switcherDelay = 150 * time.Millisecond
23 // column in order, the focused column first. Nothing moves until the commit;23 // column in order, the focused column first. Nothing moves until the commit;
24 // shown is set once the cards are drawn.24 // shown is set once the cards are drawn.
25 type switcherState struct {25 type switcherState struct {
26- open, shown bool26+ // ws is the workspace the switcher opened on; nil when closed.
27- ws *Workspace27+ ws *Workspace
28- order []WindowID28+ shown bool
29- at int29+ order []WindowID
30+ at int
30 // sizes are the client sizes the cards are scaled from.31 // sizes are the client sizes the cards are scaled from.
31 sizes map[WindowID]Rect32 sizes map[WindowID]Rect
32 }33 }
@@ -94,7 +95,7 @@ func (w *Workspace) switchOrder() []WindowID {
94 // heir is the window of id's column that is not id, or 0: call it before id95 // heir is the window of id's column that is not id, or 0: call it before id
95 // is removed, then hand it to remove.96 // is removed, then hand it to remove.
96 func (s *switcherState) heir(id WindowID) WindowID {97 func (s *switcherState) heir(id WindowID) WindowID {
97- if !s.open || s.ws == nil || !slices.Contains(s.order, id) {98+ if s.ws == nil || !slices.Contains(s.order, id) {
98 return 099 return 0
99 }100 }
100 if i := s.ws.columnOf(id); i >= 0 {101 if i := s.ws.columnOf(id); i >= 0 {
@@ -113,7 +114,7 @@ func (s *switcherState) heir(id WindowID) WindowID {
113 // goes, and the selection stays on the same card (or the one that took its114 // goes, and the selection stays on the same card (or the one that took its
114 // place). With fewer than two columns left there is nothing to switch.115 // place). With fewer than two columns left there is nothing to switch.
115 func (s *switcherState) remove(id, heir WindowID) {116 func (s *switcherState) remove(id, heir WindowID) {
116- if !s.open {117+ if s.ws == nil {
117 return118 return
118 }119 }
119 i := slices.Index(s.order, id)120 i := slices.Index(s.order, id)
@@ -150,7 +151,7 @@ func (s *switcherState) remove(id, heir WindowID) {
150 func (m *Monitor) commitSwitcher() bool {151 func (m *Monitor) commitSwitcher() bool {
151 s := m.sw152 s := m.sw
152 m.sw = switcherState{}153 m.sw = switcherState{}
153- if !s.open || s.ws == nil || s.at < 0 || s.at >= len(s.order) {154+ if s.ws == nil || s.at < 0 || s.at >= len(s.order) {
154 return false155 return false
155 }156 }
156 id, w := s.order[s.at], s.ws157 id, w := s.order[s.at], s.ws
@@ -185,14 +186,14 @@ func switchDir(a Action) int {
185 186
186 // switching reports whether a switcher is open.187 // switching reports whether a switcher is open.
187 func (c *Core) switching() bool {188 func (c *Core) switching() bool {
188- return c.switcher.sc != nil && c.switcher.sc.mon.sw.open189+ return c.switcher.sc != nil && c.switcher.sc.mon.sw.ws != nil
189 }190 }
190 191
191 // switcherStale reports whether the open switcher belongs to a workspace192 // switcherStale reports whether the open switcher belongs to a workspace
192 // that is not on its screen any more.193 // that is not on its screen any more.
193 func (c *Core) switcherStale() bool {194 func (c *Core) switcherStale() bool {
194 m := c.switcher.sc.mon195 m := c.switcher.sc.mon
195- return m.sw.open && m.sw.ws != m.Current()196+ return m.sw.ws != nil && m.sw.ws != m.Current()
196 }197 }
197 198
198 func (c *Core) stopSwitcherTimer() {199 func (c *Core) stopSwitcherTimer() {
@@ -260,7 +261,7 @@ func (c *Core) switchStep(dir int) {
260 if dir < 0 {261 if dir < 0 {
261 at = len(order) - 1262 at = len(order) - 1
262 }263 }
263- m.sw = switcherState{open: true, ws: w, order: order, at: at, sizes: sizes}264+ m.sw = switcherState{ws: w, order: order, at: at, sizes: sizes}
264 c.switcher.sc = sc265 c.switcher.sc = sc
265 opened = true266 opened = true
266 } else {267 } else {
@@ -276,12 +277,13 @@ func (c *Core) switchStep(dir int) {
276 }277 }
277 }278 }
278 279
279-// switchBind runs a switch-column bind.280+// switcherRun runs f, which changes the switcher, between a snapshot and
280-func (c *Core) switchBind(ctx context.Context, dir int) error {281+// the transition of what the screens showed, then publishes and rehits so
282+// the pointer never points at a stale window.
283+func (c *Core) switcherRun(ctx context.Context, f func()) error {
281 now := c.now()284 now := c.now()
282 shots := c.snapshot(now)285 shots := c.snapshot(now)
283- c.keyboard.takeBack() // a bind acts on the windows286+ f()
284- c.switchStep(dir)
285 c.transition(shots, now)287 c.transition(shots, now)
286 if err := c.publish(ctx); err != nil {288 if err := c.publish(ctx); err != nil {
287 return err289 return err
@@ -289,28 +291,22 @@ func (c *Core) switchBind(ctx context.Context, dir int) error {
289 return c.rehit(ctx)291 return c.rehit(ctx)
290 }292 }
291 293
294+// switchBind runs a switch-column bind.
295+func (c *Core) switchBind(ctx context.Context, dir int) error {
296+ return c.switcherRun(ctx, func() {
297+ c.keyboard.takeBack() // a bind acts on the windows
298+ c.switchStep(dir)
299+ })
300+}
301+
292 // releaseSwitcher commits the selection once the command key is up.302 // releaseSwitcher commits the selection once the command key is up.
293 func (c *Core) releaseSwitcher(ctx context.Context) error {303 func (c *Core) releaseSwitcher(ctx context.Context) error {
294- now := c.now()304+ return c.switcherRun(ctx, c.commitSwitcher)
295- shots := c.snapshot(now)
296- c.commitSwitcher()
297- c.transition(shots, now)
298- if err := c.publish(ctx); err != nil {
299- return err
300- }
301- return c.rehit(ctx)
302 }305 }
303 306
304 // abortSwitcher cancels the switcher and publishes the workspace again.307 // abortSwitcher cancels the switcher and publishes the workspace again.
305 func (c *Core) abortSwitcher(ctx context.Context) error {308 func (c *Core) abortSwitcher(ctx context.Context) error {
306- now := c.now()309+ return c.switcherRun(ctx, c.cancelSwitcher)
307- shots := c.snapshot(now)
308- c.cancelSwitcher()
309- c.transition(shots, now)
310- if err := c.publish(ctx); err != nil {
311- return err
312- }
313- return c.rehit(ctx)
314 }310 }
315 311
316 // switcherTick runs when the delay passed with the command key held: the312 // switcherTick runs when the delay passed with the command key held: the
@@ -323,13 +319,10 @@ func (c *Core) switcherTick() bool {
323 return false319 return false
324 }320 }
325 m := sc.mon321 m := sc.mon
326- if !c.hasScreen(sc) || c.security.Protected || m.sw.ws != m.Current() {322+ if m.sw.ws != m.Current() {
327 c.cancelSwitcher()323 c.cancelSwitcher()
328 return true324 return true
329 }325 }
330- if m.sw.shown {
331- return false
332- }
333 now := c.now()326 now := c.now()
334 shots := c.snapshot(now)327 shots := c.snapshot(now)
335 m.sw.shown = true328 m.sw.shown = true
@@ -350,21 +343,17 @@ func (c *Core) switcherClick(ctx context.Context, button uint32) (handled bool,
350 c.cancelSwitcher()343 c.cancelSwitcher()
351 return false, nil344 return false, nil
352 }345 }
353- now := c.now()346+ err = c.switcherRun(ctx, func() {
354- shots := c.snapshot(now)347+ id := overviewIn(sc.shownLayout(), c.cursorX-float64(sc.x), c.cursorY-float64(sc.y))
355- id := overviewIn(sc.shownLayout(), c.cursorX-float64(sc.x), c.cursorY-float64(sc.y))348+ if i := slices.Index(sc.mon.sw.order, id); id != 0 && i >= 0 {
356- if i := slices.Index(sc.mon.sw.order, id); id != 0 && i >= 0 {349+ sc.mon.sw.at = i
357- sc.mon.sw.at = i350+ c.commitSwitcher()
358- c.commitSwitcher()351+ } else {
359- } else {352+ c.cancelSwitcher()
360- c.cancelSwitcher()353+ }
361- }354+ c.buttons[button], c.swallow[button] = true, true
362- c.buttons[button], c.swallow[button] = true, true355+ })
363- c.transition(shots, now)356+ return true, err
364- if err := c.publish(ctx); err != nil {
365- return true, err
366- }
367- return true, c.rehit(ctx)
368 }357 }
369 358
370 // switcherLayout lays out the shown switcher: one card per column in359 // switcherLayout lays out the shown switcher: one card per column in
M internal/core/switcher_test.go +67 -49
@@ -248,7 +248,7 @@ func switcherMonitor(overflow Overflow, n int) (*Monitor, *Workspace) {
248248
249249 func openSwitcher(m *Monitor, at int) {
250250 w := m.Current()
251- m.sw = switcherState{open: true, ws: w, order: w.switchOrder(), at: at}
251+ m.sw = switcherState{ws: w, order: w.switchOrder(), at: at}
252252 }
253253
254254 func TestSwitchCommitFocusesSelectedColumn(t *testing.T) {
@@ -256,8 +256,8 @@ func TestSwitchCommitFocusesSelectedColumn(t *testing.T) {
256256 openSwitcher(m, 1)
257257 want := m.sw.order[1]
258258 m.commitSwitcher()
259- if id, _ := w.Focused(); id != want || m.sw.open {
260- t.Fatalf("focus %d (want %d), switcher open %v", id, want, m.sw.open)
259+ if id, _ := w.Focused(); id != want || m.sw.ws != nil {
260+ t.Fatalf("focus %d (want %d), switcher open %v", id, want, m.sw.ws != nil)
261261 }
262262 }
263263
@@ -308,13 +308,13 @@ func TestSwitchCommitIgnoresGoneWindowAndOtherWorkspace(t *testing.T) {
308308 openSwitcher(m, 1)
309309 m.sw.order[1] = 99 // not in the workspace any more
310310 m.commitSwitcher()
311- if id, _ := w.Focused(); id != before || m.sw.open {
312- t.Fatalf("focus %d, want %d; open %v", id, before, m.sw.open)
311+ if id, _ := w.Focused(); id != before || m.sw.ws != nil {
312+ t.Fatalf("focus %d, want %d; open %v", id, before, m.sw.ws != nil)
313313 }
314314 openSwitcher(m, 1)
315315 m.sw.ws = &Workspace{}
316316 m.commitSwitcher()
317- if id, _ := w.Focused(); id != before || m.sw.open {
317+ if id, _ := w.Focused(); id != before || m.sw.ws != nil {
318318 t.Fatalf("focus %d for a foreign workspace, want %d", id, before)
319319 }
320320 }
@@ -329,8 +329,8 @@ func TestSwitchStepOpensOnPreviousColumnAndCycles(t *testing.T) {
329329 t.Fatal(err)
330330 }
331331 sw := &c.cur().mon.sw
332- if !sw.open || sw.at != 1 || switcherFocus(c) != before {
333- t.Fatalf("open %v at %d focus %d (was %d)", sw.open, sw.at, switcherFocus(c), before)
332+ if sw.ws == nil || sw.at != 1 || switcherFocus(c) != before {
333+ t.Fatalf("open %v at %d focus %d (was %d)", sw.ws != nil, sw.at, switcherFocus(c), before)
334334 }
335335 if len(ic.timers) != 1 || ic.timers[0] != switcherDelay {
336336 t.Fatalf("timers %v, want one of %v", ic.timers, switcherDelay)
@@ -360,7 +360,7 @@ func TestSwitchPrevOpensOnOldestColumn(t *testing.T) {
360360 c, _, _ := switcherCore(t, 3)
361361 c.mods = ports.ModSuper
362362 bind(t, c, ActionSwitchColumnPrev)
363- if sw := c.cur().mon.sw; !sw.open || sw.at != len(sw.order)-1 {
363+ if sw := c.cur().mon.sw; sw.ws == nil || sw.at != len(sw.order)-1 {
364364 t.Fatalf("at %d of %d", sw.at, len(sw.order))
365365 }
366366 }
@@ -372,8 +372,8 @@ func TestSwitchWithoutCmdCommitsAtOnce(t *testing.T) {
372372 if err := c.runBind(context.Background(), ActionSwitchColumnNext); err != nil {
373373 t.Fatal(err)
374374 }
375- if c.cur().mon.sw.open || c.switcher.timerC != nil || len(ic.timers) != 0 {
376- t.Fatalf("open %v, timer %v %v", c.cur().mon.sw.open, c.switcher.timerC != nil, ic.timers)
375+ if c.cur().mon.sw.ws != nil || c.switcher.timerC != nil || len(ic.timers) != 0 {
376+ t.Fatalf("open %v, timer %v %v", c.cur().mon.sw.ws != nil, c.switcher.timerC != nil, ic.timers)
377377 }
378378 if switcherFocus(c) == start {
379379 t.Fatal("focus did not move")
@@ -388,14 +388,14 @@ func TestSwitchIgnoredWithOneColumnOrOverview(t *testing.T) {
388388 c, _, _ := switcherCore(t, 1)
389389 c.mods = ports.ModSuper
390390 bind(t, c, ActionSwitchColumnNext)
391- if c.cur().mon.sw.open || c.switcher.sc != nil {
391+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil {
392392 t.Fatal("switcher opened with one column")
393393 }
394394 c, _, _ = switcherCore(t, 3)
395395 c.cur().mon.ToggleOverview()
396396 before := switcherFocus(c)
397397 bind(t, c, ActionSwitchColumnNext)
398- if c.cur().mon.sw.open || c.switcher.sc != nil || switcherFocus(c) != before {
398+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || switcherFocus(c) != before {
399399 t.Fatal("switcher acted in the overview")
400400 }
401401 }
@@ -421,7 +421,7 @@ func TestSwitchQuickTapSwapsWithoutCards(t *testing.T) {
421421 t.Fatalf("cards published during a quick tap: %+v", p)
422422 }
423423 }
424- if c.cur().mon.sw.open || c.switcher.sc != nil || c.switcher.timerC != nil {
424+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || c.switcher.timerC != nil {
425425 t.Fatal("switcher left open")
426426 }
427427 }
@@ -440,13 +440,13 @@ func TestSwitchHoldCommitsOnReleaseAndForwardsIt(t *testing.T) {
440440 start := switcherFocus(c)
441441 order := c.cur().mon.Current().switchOrder()
442442 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true), tab(ports.ModSuper, false))
443- if switcherFocus(c) != start || !c.cur().mon.sw.open {
443+ if switcherFocus(c) != start || c.cur().mon.sw.ws == nil {
444444 t.Fatal("focus moved or switcher closed before the release")
445445 }
446446 forwarded(cmds)
447447 sendKeys(t, c, superKey(0, false))
448- if switcherFocus(c) != order[1] || c.cur().mon.sw.open || c.switcher.timerC != nil {
449- t.Fatalf("focus %d (want %d), open %v", switcherFocus(c), order[1], c.cur().mon.sw.open)
448+ if switcherFocus(c) != order[1] || c.cur().mon.sw.ws != nil || c.switcher.timerC != nil {
449+ t.Fatalf("focus %d (want %d), open %v", switcherFocus(c), order[1], c.cur().mon.sw.ws != nil)
450450 }
451451 fk := forwarded(cmds)
452452 if len(fk) != 1 || fk[0].Key.Keysym != "Super_L" || fk[0].Key.Pressed || fk[0].ID != order[1] {
@@ -489,8 +489,8 @@ func TestSwitchEscapeCancelsAndIsNotForwarded(t *testing.T) {
489489 sendKeys(t, c, esc)
490490 esc.Pressed = false
491491 sendKeys(t, c, esc, superKey(0, false))
492- if c.cur().mon.sw.open || c.switcher.sc != nil || switcherFocus(c) != start {
493- t.Fatalf("open %v focus %d (want %d)", c.cur().mon.sw.open, switcherFocus(c), start)
492+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || switcherFocus(c) != start {
493+ t.Fatalf("open %v focus %d (want %d)", c.cur().mon.sw.ws != nil, switcherFocus(c), start)
494494 }
495495 for _, k := range forwarded(cmds) {
496496 if k.Key.Keysym == "Escape" {
@@ -504,7 +504,7 @@ func TestSwitchAnotherBindCancelsThenRuns(t *testing.T) {
504504 start := switcherFocus(c)
505505 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))
506506 sendKeys(t, c, ports.KeyEvent{Keysym: "f", Keycode: 33, Mods: ports.ModSuper, Pressed: true})
507- if c.cur().mon.sw.open || c.switcher.sc != nil || c.switcher.timerC != nil {
507+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || c.switcher.timerC != nil {
508508 t.Fatal("switcher still open")
509509 }
510510 if switcherFocus(c) != start {
@@ -519,8 +519,8 @@ func TestSwitchShiftTabGoesBackward(t *testing.T) {
519519 c, _, _ := switcherCore(t, 3)
520520 sendKeys(t, c, superKey(ports.ModSuper, true), shiftTab(true))
521521 sw := c.cur().mon.sw
522- if !sw.open || sw.at != len(sw.order)-1 {
523- t.Fatalf("open %v at %d of %d", sw.open, sw.at, len(sw.order))
522+ if sw.ws == nil || sw.at != len(sw.order)-1 {
523+ t.Fatalf("open %v at %d of %d", sw.ws != nil, sw.at, len(sw.order))
524524 }
525525 sendKeys(t, c, shiftTab(false), tab(ports.ModSuper, true))
526526 if c.cur().mon.sw.at != 0 {
@@ -533,7 +533,7 @@ func TestSwitchSwipeBeginCancels(t *testing.T) {
533533 start := switcherFocus(c)
534534 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))
535535 c.swipeBegin(ports.SwipeBegin{Fingers: 3})
536- if c.cur().mon.sw.open || c.switcher.sc != nil || c.switcher.timerC != nil || switcherFocus(c) != start {
536+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || c.switcher.timerC != nil || switcherFocus(c) != start {
537537 t.Fatal("swipe did not cancel the switcher")
538538 }
539539 }
@@ -550,7 +550,7 @@ func TestSwitchSecurityChangeCancels(t *testing.T) {
550550 if !c.syncSecurity() {
551551 t.Fatal("security change not seen")
552552 }
553- if c.cur().mon.sw.open || c.switcher.sc != nil || c.switcher.timerC != nil || switcherFocus(c) != start {
553+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || c.switcher.timerC != nil || switcherFocus(c) != start {
554554 t.Fatal("security change did not cancel the switcher")
555555 }
556556 }
@@ -564,12 +564,12 @@ func TestSwitchCmdKeyFollowsConfig(t *testing.T) {
564564 }
565565 order := c.cur().mon.Current().switchOrder()
566566 sendKeys(t, c, tab(ports.ModAlt, true))
567- if !c.cur().mon.sw.open {
567+ if c.cur().mon.sw.ws == nil {
568568 t.Fatal("Alt+Tab did not open the switcher")
569569 }
570570 sendKeys(t, c, ports.KeyEvent{Keysym: "Alt_L", Keycode: 64, Pressed: false})
571- if c.cur().mon.sw.open || switcherFocus(c) != order[1] {
572- t.Fatalf("open %v focus %d, want %d", c.cur().mon.sw.open, switcherFocus(c), order[1])
571+ if c.cur().mon.sw.ws != nil || switcherFocus(c) != order[1] {
572+ t.Fatalf("open %v focus %d, want %d", c.cur().mon.sw.ws != nil, switcherFocus(c), order[1])
573573 }
574574 }
575575
@@ -712,8 +712,8 @@ func TestSwitcherClickOnCardCommits(t *testing.T) {
712712 if err := c.pointerButton(context.Background(), ports.PointerButton{Button: 0x110, Pressed: true}); err != nil {
713713 t.Fatal(err)
714714 }
715- if c.cur().mon.sw.open || c.switcher.sc != nil || switcherFocus(c) != target {
716- t.Fatalf("open %v focus %d, want %d", c.cur().mon.sw.open, switcherFocus(c), target)
715+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || switcherFocus(c) != target {
716+ t.Fatalf("open %v focus %d, want %d", c.cur().mon.sw.ws != nil, switcherFocus(c), target)
717717 }
718718 if err := c.pointerButton(context.Background(), ports.PointerButton{Button: 0x110}); err != nil {
719719 t.Fatal(err)
@@ -735,8 +735,8 @@ func TestSwitcherClickElsewhereCancels(t *testing.T) {
735735 if err := c.pointerButton(context.Background(), ports.PointerButton{Button: 0x110, Pressed: true}); err != nil {
736736 t.Fatal(err)
737737 }
738- if c.cur().mon.sw.open || c.switcher.sc != nil || switcherFocus(c) != start {
739- t.Fatalf("open %v focus %d, want %d", c.cur().mon.sw.open, switcherFocus(c), start)
738+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || switcherFocus(c) != start {
739+ t.Fatalf("open %v focus %d, want %d", c.cur().mon.sw.ws != nil, switcherFocus(c), start)
740740 }
741741 }
742742
@@ -748,7 +748,7 @@ func TestSwitcherPressBeforeCardsCancelsAndContinues(t *testing.T) {
748748 if err != nil || handled {
749749 t.Fatalf("handled %v err %v", handled, err)
750750 }
751- if c.cur().mon.sw.open || c.switcher.timerC != nil || switcherFocus(c) != start {
751+ if c.cur().mon.sw.ws != nil || c.switcher.timerC != nil || switcherFocus(c) != start {
752752 t.Fatal("press did not cancel")
753753 }
754754 }
@@ -762,8 +762,8 @@ func TestSwitcherClosingSelectedWindowKeepsSelectionValid(t *testing.T) {
762762 sw := &c.cur().mon.sw
763763 gone := sw.order[sw.at]
764764 c.cur().mon.RemoveWindow(gone)
765- if !sw.open || len(sw.order) != 2 || sw.at < 0 || sw.at >= len(sw.order) || slices.Contains(sw.order, gone) {
766- t.Fatalf("open %v order %v at %d", sw.open, sw.order, sw.at)
765+ if sw.ws == nil || len(sw.order) != 2 || sw.at < 0 || sw.at >= len(sw.order) || slices.Contains(sw.order, gone) {
766+ t.Fatalf("open %v order %v at %d", sw.ws != nil, sw.order, sw.at)
767767 }
768768 s := indicatorScene(t, c)
769769 if n := len(scenePreviews(s)); n != 2 {
@@ -783,7 +783,7 @@ func TestSwitcherClosingBeforeSelectionKeepsCard(t *testing.T) {
783783 sw := &c.cur().mon.sw
784784 selected := sw.order[sw.at]
785785 c.cur().mon.RemoveWindow(sw.order[0])
786- if !sw.open || sw.order[sw.at] != selected {
786+ if sw.ws == nil || sw.order[sw.at] != selected {
787787 t.Fatalf("selection moved from %d: order %v at %d", selected, sw.order, sw.at)
788788 }
789789 }
@@ -793,7 +793,7 @@ func TestSwitcherClosingDownToOneColumnClosesIt(t *testing.T) {
793793 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))
794794 c.switcherTick()
795795 c.cur().mon.RemoveWindow(c.cur().mon.sw.order[1])
796- if c.cur().mon.sw.open || c.cur().mon.sw.shown {
796+ if c.cur().mon.sw.ws != nil || c.cur().mon.sw.shown {
797797 t.Fatal("switcher still open with one column")
798798 }
799799 if s := indicatorScene(t, c); len(scenePreviews(s)) != 0 {
@@ -816,7 +816,7 @@ func TestSwitcherOutputRemovalCancels(t *testing.T) {
816816 t.Fatal("no switcher screen")
817817 }
818818 c.removeScreen(sc.name())
819- if c.switcher.sc != nil || c.switcher.timerC != nil || sc.mon.sw.open {
819+ if c.switcher.sc != nil || c.switcher.timerC != nil || sc.mon.sw.ws != nil {
820820 t.Fatal("switcher survived its output")
821821 }
822822 if err := c.publish(context.Background()); err != nil {
@@ -842,11 +842,11 @@ func TestSwitcherFocusMovingToAnotherOutputReopensThere(t *testing.T) {
842842 }
843843 c.focusScreen = c.screenIndex("B")
844844 bind(t, c, ActionSwitchColumnNext)
845- if first.mon.sw.open {
845+ if first.mon.sw.ws != nil {
846846 t.Fatal("old switcher left open")
847847 }
848- if c.switcher.sc != c.cur() || !c.cur().mon.sw.open || c.cur().mon.sw.at != 1 {
849- t.Fatalf("new switcher on %v open %v", c.switcher.sc == c.cur(), c.cur().mon.sw.open)
848+ if c.switcher.sc != c.cur() || c.cur().mon.sw.ws == nil || c.cur().mon.sw.at != 1 {
849+ t.Fatalf("new switcher on %v open %v", c.switcher.sc == c.cur(), c.cur().mon.sw.ws != nil)
850850 }
851851 }
852852
@@ -918,7 +918,7 @@ func TestSwitcherShownDropsWhenAnotherWorkspaceComesOnScreen(t *testing.T) {
918918 if len(scenePreviews(s)) != 0 || s.Dim != 0 || s.DimBehind {
919919 t.Fatalf("scene still dimmed or previewing: dim %v previews %+v", s.Dim, scenePreviews(s))
920920 }
921- if m.sw.open || c.switcher.sc != nil || c.switcher.timerC != nil {
921+ if m.sw.ws != nil || c.switcher.sc != nil || c.switcher.timerC != nil {
922922 t.Fatal("stale switcher left open")
923923 }
924924 var any bool
@@ -937,8 +937,8 @@ func TestSwitcherShownDropsWhenAnotherWorkspaceComesOnScreen(t *testing.T) {
937937 // The next switch opens fresh on the new workspace.
938938 c.mods = ports.ModSuper
939939 bind(t, c, ActionSwitchColumnNext)
940- if sw := m.sw; !sw.open || sw.ws != other || sw.at != 1 {
941- t.Fatalf("open %v on other %v at %d", sw.open, sw.ws == other, sw.at)
940+ if sw := m.sw; sw.ws == nil || sw.ws != other || sw.at != 1 {
941+ t.Fatalf("open %v on other %v at %d", sw.ws != nil, sw.ws == other, sw.at)
942942 }
943943 }
944944
@@ -953,18 +953,36 @@ func TestSwitcherStepOnStaleWorkspaceOpensFresh(t *testing.T) {
953953 }
954954 sendKeys(t, c, tab(ports.ModSuper, true))
955955 sw := c.cur().mon.sw
956- if !sw.open || sw.ws != other || sw.shown || sw.at != 1 {
957- t.Fatalf("open %v on other %v shown %v at %d", sw.open, sw.ws == other, sw.shown, sw.at)
956+ if sw.ws == nil || sw.ws != other || sw.shown || sw.at != 1 {
957+ t.Fatalf("open %v on other %v shown %v at %d", sw.ws != nil, sw.ws == other, sw.shown, sw.at)
958958 }
959959 // A release on a stale switcher commits nothing and closes it.
960960 c.cur().mon.show(c.cur().mon.Workspaces[0])
961961 focus := switcherFocus(c)
962962 sendKeys(t, c, superKey(0, false))
963- if c.cur().mon.sw.open || c.switcher.sc != nil || switcherFocus(c) != focus {
963+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || switcherFocus(c) != focus {
964964 t.Fatal("stale switcher survived the release")
965965 }
966966 }
967967
968+// The delay passes after another workspace came on screen with no publish
969+// in between: the tick drops the switcher instead of showing stale cards.
970+func TestSwitcherTickOnStaleWorkspaceCancels(t *testing.T) {
971+ c, other := twoWorkspaces(t)
972+ sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))
973+ m := c.cur().mon
974+ if m.sw.ws == nil {
975+ t.Fatal("switcher not open")
976+ }
977+ m.show(other)
978+ if !c.switcherTick() {
979+ t.Fatal("tick on a stale workspace asks for no scene")
980+ }
981+ if m.sw.ws != nil || m.sw.shown || c.switcher.sc != nil || c.switcher.timerC != nil {
982+ t.Fatal("stale switcher survived the tick")
983+ }
984+}
985+
968986 func TestSwitchIgnoredDuringDrag(t *testing.T) {
969987 c, _, _ := switcherCore(t, 3)
970988 start := switcherFocus(c)
@@ -972,7 +990,7 @@ func TestSwitchIgnoredDuringDrag(t *testing.T) {
972990 c.drag = d
973991 c.buttons[btnLeft] = true
974992 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))
975- if c.cur().mon.sw.open || c.switcher.sc != nil || c.switcher.timerC != nil {
993+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || c.switcher.timerC != nil {
976994 t.Fatal("switcher opened during a drag")
977995 }
978996 if c.drag != d || switcherFocus(c) != start {
@@ -1096,7 +1114,7 @@ func TestSwitchUnmodifiedBindThroughKeyEvent(t *testing.T) {
10961114 c, ic, _ := switcherCore(t, 3)
10971115 start := switcherFocus(c)
10981116 sendKeys(t, c, ports.KeyEvent{Keysym: "F9", Keycode: 75, Pressed: true})
1099- if c.cur().mon.sw.open || len(ic.timers) != 0 || switcherFocus(c) == start {
1100- t.Fatalf("open %v timers %v focus %d (was %d)", c.cur().mon.sw.open, ic.timers, switcherFocus(c), start)
1117+ if c.cur().mon.sw.ws != nil || len(ic.timers) != 0 || switcherFocus(c) == start {
1118+ t.Fatalf("open %v timers %v focus %d (was %d)", c.cur().mon.sw.ws != nil, ic.timers, switcherFocus(c), start)
11011119 }
11021120 }
@@ -248,7 +248,7 @@ func switcherMonitor(overflow Overflow, n int) (*Monitor, *Workspace) {
248 248
249 func openSwitcher(m *Monitor, at int) {249 func openSwitcher(m *Monitor, at int) {
250 w := m.Current()250 w := m.Current()
251- m.sw = switcherState{open: true, ws: w, order: w.switchOrder(), at: at}251+ m.sw = switcherState{ws: w, order: w.switchOrder(), at: at}
252 }252 }
253 253
254 func TestSwitchCommitFocusesSelectedColumn(t *testing.T) {254 func TestSwitchCommitFocusesSelectedColumn(t *testing.T) {
@@ -256,8 +256,8 @@ func TestSwitchCommitFocusesSelectedColumn(t *testing.T) {
256 openSwitcher(m, 1)256 openSwitcher(m, 1)
257 want := m.sw.order[1]257 want := m.sw.order[1]
258 m.commitSwitcher()258 m.commitSwitcher()
259- if id, _ := w.Focused(); id != want || m.sw.open {259+ if id, _ := w.Focused(); id != want || m.sw.ws != nil {
260- t.Fatalf("focus %d (want %d), switcher open %v", id, want, m.sw.open)260+ t.Fatalf("focus %d (want %d), switcher open %v", id, want, m.sw.ws != nil)
261 }261 }
262 }262 }
263 263
@@ -308,13 +308,13 @@ func TestSwitchCommitIgnoresGoneWindowAndOtherWorkspace(t *testing.T) {
308 openSwitcher(m, 1)308 openSwitcher(m, 1)
309 m.sw.order[1] = 99 // not in the workspace any more309 m.sw.order[1] = 99 // not in the workspace any more
310 m.commitSwitcher()310 m.commitSwitcher()
311- if id, _ := w.Focused(); id != before || m.sw.open {311+ if id, _ := w.Focused(); id != before || m.sw.ws != nil {
312- t.Fatalf("focus %d, want %d; open %v", id, before, m.sw.open)312+ t.Fatalf("focus %d, want %d; open %v", id, before, m.sw.ws != nil)
313 }313 }
314 openSwitcher(m, 1)314 openSwitcher(m, 1)
315 m.sw.ws = &Workspace{}315 m.sw.ws = &Workspace{}
316 m.commitSwitcher()316 m.commitSwitcher()
317- if id, _ := w.Focused(); id != before || m.sw.open {317+ if id, _ := w.Focused(); id != before || m.sw.ws != nil {
318 t.Fatalf("focus %d for a foreign workspace, want %d", id, before)318 t.Fatalf("focus %d for a foreign workspace, want %d", id, before)
319 }319 }
320 }320 }
@@ -329,8 +329,8 @@ func TestSwitchStepOpensOnPreviousColumnAndCycles(t *testing.T) {
329 t.Fatal(err)329 t.Fatal(err)
330 }330 }
331 sw := &c.cur().mon.sw331 sw := &c.cur().mon.sw
332- if !sw.open || sw.at != 1 || switcherFocus(c) != before {332+ if sw.ws == nil || sw.at != 1 || switcherFocus(c) != before {
333- t.Fatalf("open %v at %d focus %d (was %d)", sw.open, sw.at, switcherFocus(c), before)333+ t.Fatalf("open %v at %d focus %d (was %d)", sw.ws != nil, sw.at, switcherFocus(c), before)
334 }334 }
335 if len(ic.timers) != 1 || ic.timers[0] != switcherDelay {335 if len(ic.timers) != 1 || ic.timers[0] != switcherDelay {
336 t.Fatalf("timers %v, want one of %v", ic.timers, switcherDelay)336 t.Fatalf("timers %v, want one of %v", ic.timers, switcherDelay)
@@ -360,7 +360,7 @@ func TestSwitchPrevOpensOnOldestColumn(t *testing.T) {
360 c, _, _ := switcherCore(t, 3)360 c, _, _ := switcherCore(t, 3)
361 c.mods = ports.ModSuper361 c.mods = ports.ModSuper
362 bind(t, c, ActionSwitchColumnPrev)362 bind(t, c, ActionSwitchColumnPrev)
363- if sw := c.cur().mon.sw; !sw.open || sw.at != len(sw.order)-1 {363+ if sw := c.cur().mon.sw; sw.ws == nil || sw.at != len(sw.order)-1 {
364 t.Fatalf("at %d of %d", sw.at, len(sw.order))364 t.Fatalf("at %d of %d", sw.at, len(sw.order))
365 }365 }
366 }366 }
@@ -372,8 +372,8 @@ func TestSwitchWithoutCmdCommitsAtOnce(t *testing.T) {
372 if err := c.runBind(context.Background(), ActionSwitchColumnNext); err != nil {372 if err := c.runBind(context.Background(), ActionSwitchColumnNext); err != nil {
373 t.Fatal(err)373 t.Fatal(err)
374 }374 }
375- if c.cur().mon.sw.open || c.switcher.timerC != nil || len(ic.timers) != 0 {375+ if c.cur().mon.sw.ws != nil || c.switcher.timerC != nil || len(ic.timers) != 0 {
376- t.Fatalf("open %v, timer %v %v", c.cur().mon.sw.open, c.switcher.timerC != nil, ic.timers)376+ t.Fatalf("open %v, timer %v %v", c.cur().mon.sw.ws != nil, c.switcher.timerC != nil, ic.timers)
377 }377 }
378 if switcherFocus(c) == start {378 if switcherFocus(c) == start {
379 t.Fatal("focus did not move")379 t.Fatal("focus did not move")
@@ -388,14 +388,14 @@ func TestSwitchIgnoredWithOneColumnOrOverview(t *testing.T) {
388 c, _, _ := switcherCore(t, 1)388 c, _, _ := switcherCore(t, 1)
389 c.mods = ports.ModSuper389 c.mods = ports.ModSuper
390 bind(t, c, ActionSwitchColumnNext)390 bind(t, c, ActionSwitchColumnNext)
391- if c.cur().mon.sw.open || c.switcher.sc != nil {391+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil {
392 t.Fatal("switcher opened with one column")392 t.Fatal("switcher opened with one column")
393 }393 }
394 c, _, _ = switcherCore(t, 3)394 c, _, _ = switcherCore(t, 3)
395 c.cur().mon.ToggleOverview()395 c.cur().mon.ToggleOverview()
396 before := switcherFocus(c)396 before := switcherFocus(c)
397 bind(t, c, ActionSwitchColumnNext)397 bind(t, c, ActionSwitchColumnNext)
398- if c.cur().mon.sw.open || c.switcher.sc != nil || switcherFocus(c) != before {398+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || switcherFocus(c) != before {
399 t.Fatal("switcher acted in the overview")399 t.Fatal("switcher acted in the overview")
400 }400 }
401 }401 }
@@ -421,7 +421,7 @@ func TestSwitchQuickTapSwapsWithoutCards(t *testing.T) {
421 t.Fatalf("cards published during a quick tap: %+v", p)421 t.Fatalf("cards published during a quick tap: %+v", p)
422 }422 }
423 }423 }
424- if c.cur().mon.sw.open || c.switcher.sc != nil || c.switcher.timerC != nil {424+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || c.switcher.timerC != nil {
425 t.Fatal("switcher left open")425 t.Fatal("switcher left open")
426 }426 }
427 }427 }
@@ -440,13 +440,13 @@ func TestSwitchHoldCommitsOnReleaseAndForwardsIt(t *testing.T) {
440 start := switcherFocus(c)440 start := switcherFocus(c)
441 order := c.cur().mon.Current().switchOrder()441 order := c.cur().mon.Current().switchOrder()
442 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true), tab(ports.ModSuper, false))442 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true), tab(ports.ModSuper, false))
443- if switcherFocus(c) != start || !c.cur().mon.sw.open {443+ if switcherFocus(c) != start || c.cur().mon.sw.ws == nil {
444 t.Fatal("focus moved or switcher closed before the release")444 t.Fatal("focus moved or switcher closed before the release")
445 }445 }
446 forwarded(cmds)446 forwarded(cmds)
447 sendKeys(t, c, superKey(0, false))447 sendKeys(t, c, superKey(0, false))
448- if switcherFocus(c) != order[1] || c.cur().mon.sw.open || c.switcher.timerC != nil {448+ if switcherFocus(c) != order[1] || c.cur().mon.sw.ws != nil || c.switcher.timerC != nil {
449- t.Fatalf("focus %d (want %d), open %v", switcherFocus(c), order[1], c.cur().mon.sw.open)449+ t.Fatalf("focus %d (want %d), open %v", switcherFocus(c), order[1], c.cur().mon.sw.ws != nil)
450 }450 }
451 fk := forwarded(cmds)451 fk := forwarded(cmds)
452 if len(fk) != 1 || fk[0].Key.Keysym != "Super_L" || fk[0].Key.Pressed || fk[0].ID != order[1] {452 if len(fk) != 1 || fk[0].Key.Keysym != "Super_L" || fk[0].Key.Pressed || fk[0].ID != order[1] {
@@ -489,8 +489,8 @@ func TestSwitchEscapeCancelsAndIsNotForwarded(t *testing.T) {
489 sendKeys(t, c, esc)489 sendKeys(t, c, esc)
490 esc.Pressed = false490 esc.Pressed = false
491 sendKeys(t, c, esc, superKey(0, false))491 sendKeys(t, c, esc, superKey(0, false))
492- if c.cur().mon.sw.open || c.switcher.sc != nil || switcherFocus(c) != start {492+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || switcherFocus(c) != start {
493- t.Fatalf("open %v focus %d (want %d)", c.cur().mon.sw.open, switcherFocus(c), start)493+ t.Fatalf("open %v focus %d (want %d)", c.cur().mon.sw.ws != nil, switcherFocus(c), start)
494 }494 }
495 for _, k := range forwarded(cmds) {495 for _, k := range forwarded(cmds) {
496 if k.Key.Keysym == "Escape" {496 if k.Key.Keysym == "Escape" {
@@ -504,7 +504,7 @@ func TestSwitchAnotherBindCancelsThenRuns(t *testing.T) {
504 start := switcherFocus(c)504 start := switcherFocus(c)
505 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))505 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))
506 sendKeys(t, c, ports.KeyEvent{Keysym: "f", Keycode: 33, Mods: ports.ModSuper, Pressed: true})506 sendKeys(t, c, ports.KeyEvent{Keysym: "f", Keycode: 33, Mods: ports.ModSuper, Pressed: true})
507- if c.cur().mon.sw.open || c.switcher.sc != nil || c.switcher.timerC != nil {507+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || c.switcher.timerC != nil {
508 t.Fatal("switcher still open")508 t.Fatal("switcher still open")
509 }509 }
510 if switcherFocus(c) != start {510 if switcherFocus(c) != start {
@@ -519,8 +519,8 @@ func TestSwitchShiftTabGoesBackward(t *testing.T) {
519 c, _, _ := switcherCore(t, 3)519 c, _, _ := switcherCore(t, 3)
520 sendKeys(t, c, superKey(ports.ModSuper, true), shiftTab(true))520 sendKeys(t, c, superKey(ports.ModSuper, true), shiftTab(true))
521 sw := c.cur().mon.sw521 sw := c.cur().mon.sw
522- if !sw.open || sw.at != len(sw.order)-1 {522+ if sw.ws == nil || sw.at != len(sw.order)-1 {
523- t.Fatalf("open %v at %d of %d", sw.open, sw.at, len(sw.order))523+ t.Fatalf("open %v at %d of %d", sw.ws != nil, sw.at, len(sw.order))
524 }524 }
525 sendKeys(t, c, shiftTab(false), tab(ports.ModSuper, true))525 sendKeys(t, c, shiftTab(false), tab(ports.ModSuper, true))
526 if c.cur().mon.sw.at != 0 {526 if c.cur().mon.sw.at != 0 {
@@ -533,7 +533,7 @@ func TestSwitchSwipeBeginCancels(t *testing.T) {
533 start := switcherFocus(c)533 start := switcherFocus(c)
534 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))534 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))
535 c.swipeBegin(ports.SwipeBegin{Fingers: 3})535 c.swipeBegin(ports.SwipeBegin{Fingers: 3})
536- if c.cur().mon.sw.open || c.switcher.sc != nil || c.switcher.timerC != nil || switcherFocus(c) != start {536+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || c.switcher.timerC != nil || switcherFocus(c) != start {
537 t.Fatal("swipe did not cancel the switcher")537 t.Fatal("swipe did not cancel the switcher")
538 }538 }
539 }539 }
@@ -550,7 +550,7 @@ func TestSwitchSecurityChangeCancels(t *testing.T) {
550 if !c.syncSecurity() {550 if !c.syncSecurity() {
551 t.Fatal("security change not seen")551 t.Fatal("security change not seen")
552 }552 }
553- if c.cur().mon.sw.open || c.switcher.sc != nil || c.switcher.timerC != nil || switcherFocus(c) != start {553+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || c.switcher.timerC != nil || switcherFocus(c) != start {
554 t.Fatal("security change did not cancel the switcher")554 t.Fatal("security change did not cancel the switcher")
555 }555 }
556 }556 }
@@ -564,12 +564,12 @@ func TestSwitchCmdKeyFollowsConfig(t *testing.T) {
564 }564 }
565 order := c.cur().mon.Current().switchOrder()565 order := c.cur().mon.Current().switchOrder()
566 sendKeys(t, c, tab(ports.ModAlt, true))566 sendKeys(t, c, tab(ports.ModAlt, true))
567- if !c.cur().mon.sw.open {567+ if c.cur().mon.sw.ws == nil {
568 t.Fatal("Alt+Tab did not open the switcher")568 t.Fatal("Alt+Tab did not open the switcher")
569 }569 }
570 sendKeys(t, c, ports.KeyEvent{Keysym: "Alt_L", Keycode: 64, Pressed: false})570 sendKeys(t, c, ports.KeyEvent{Keysym: "Alt_L", Keycode: 64, Pressed: false})
571- if c.cur().mon.sw.open || switcherFocus(c) != order[1] {571+ if c.cur().mon.sw.ws != nil || switcherFocus(c) != order[1] {
572- t.Fatalf("open %v focus %d, want %d", c.cur().mon.sw.open, switcherFocus(c), order[1])572+ t.Fatalf("open %v focus %d, want %d", c.cur().mon.sw.ws != nil, switcherFocus(c), order[1])
573 }573 }
574 }574 }
575 575
@@ -712,8 +712,8 @@ func TestSwitcherClickOnCardCommits(t *testing.T) {
712 if err := c.pointerButton(context.Background(), ports.PointerButton{Button: 0x110, Pressed: true}); err != nil {712 if err := c.pointerButton(context.Background(), ports.PointerButton{Button: 0x110, Pressed: true}); err != nil {
713 t.Fatal(err)713 t.Fatal(err)
714 }714 }
715- if c.cur().mon.sw.open || c.switcher.sc != nil || switcherFocus(c) != target {715+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || switcherFocus(c) != target {
716- t.Fatalf("open %v focus %d, want %d", c.cur().mon.sw.open, switcherFocus(c), target)716+ t.Fatalf("open %v focus %d, want %d", c.cur().mon.sw.ws != nil, switcherFocus(c), target)
717 }717 }
718 if err := c.pointerButton(context.Background(), ports.PointerButton{Button: 0x110}); err != nil {718 if err := c.pointerButton(context.Background(), ports.PointerButton{Button: 0x110}); err != nil {
719 t.Fatal(err)719 t.Fatal(err)
@@ -735,8 +735,8 @@ func TestSwitcherClickElsewhereCancels(t *testing.T) {
735 if err := c.pointerButton(context.Background(), ports.PointerButton{Button: 0x110, Pressed: true}); err != nil {735 if err := c.pointerButton(context.Background(), ports.PointerButton{Button: 0x110, Pressed: true}); err != nil {
736 t.Fatal(err)736 t.Fatal(err)
737 }737 }
738- if c.cur().mon.sw.open || c.switcher.sc != nil || switcherFocus(c) != start {738+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || switcherFocus(c) != start {
739- t.Fatalf("open %v focus %d, want %d", c.cur().mon.sw.open, switcherFocus(c), start)739+ t.Fatalf("open %v focus %d, want %d", c.cur().mon.sw.ws != nil, switcherFocus(c), start)
740 }740 }
741 }741 }
742 742
@@ -748,7 +748,7 @@ func TestSwitcherPressBeforeCardsCancelsAndContinues(t *testing.T) {
748 if err != nil || handled {748 if err != nil || handled {
749 t.Fatalf("handled %v err %v", handled, err)749 t.Fatalf("handled %v err %v", handled, err)
750 }750 }
751- if c.cur().mon.sw.open || c.switcher.timerC != nil || switcherFocus(c) != start {751+ if c.cur().mon.sw.ws != nil || c.switcher.timerC != nil || switcherFocus(c) != start {
752 t.Fatal("press did not cancel")752 t.Fatal("press did not cancel")
753 }753 }
754 }754 }
@@ -762,8 +762,8 @@ func TestSwitcherClosingSelectedWindowKeepsSelectionValid(t *testing.T) {
762 sw := &c.cur().mon.sw762 sw := &c.cur().mon.sw
763 gone := sw.order[sw.at]763 gone := sw.order[sw.at]
764 c.cur().mon.RemoveWindow(gone)764 c.cur().mon.RemoveWindow(gone)
765- if !sw.open || len(sw.order) != 2 || sw.at < 0 || sw.at >= len(sw.order) || slices.Contains(sw.order, gone) {765+ if sw.ws == nil || len(sw.order) != 2 || sw.at < 0 || sw.at >= len(sw.order) || slices.Contains(sw.order, gone) {
766- t.Fatalf("open %v order %v at %d", sw.open, sw.order, sw.at)766+ t.Fatalf("open %v order %v at %d", sw.ws != nil, sw.order, sw.at)
767 }767 }
768 s := indicatorScene(t, c)768 s := indicatorScene(t, c)
769 if n := len(scenePreviews(s)); n != 2 {769 if n := len(scenePreviews(s)); n != 2 {
@@ -783,7 +783,7 @@ func TestSwitcherClosingBeforeSelectionKeepsCard(t *testing.T) {
783 sw := &c.cur().mon.sw783 sw := &c.cur().mon.sw
784 selected := sw.order[sw.at]784 selected := sw.order[sw.at]
785 c.cur().mon.RemoveWindow(sw.order[0])785 c.cur().mon.RemoveWindow(sw.order[0])
786- if !sw.open || sw.order[sw.at] != selected {786+ if sw.ws == nil || sw.order[sw.at] != selected {
787 t.Fatalf("selection moved from %d: order %v at %d", selected, sw.order, sw.at)787 t.Fatalf("selection moved from %d: order %v at %d", selected, sw.order, sw.at)
788 }788 }
789 }789 }
@@ -793,7 +793,7 @@ func TestSwitcherClosingDownToOneColumnClosesIt(t *testing.T) {
793 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))793 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))
794 c.switcherTick()794 c.switcherTick()
795 c.cur().mon.RemoveWindow(c.cur().mon.sw.order[1])795 c.cur().mon.RemoveWindow(c.cur().mon.sw.order[1])
796- if c.cur().mon.sw.open || c.cur().mon.sw.shown {796+ if c.cur().mon.sw.ws != nil || c.cur().mon.sw.shown {
797 t.Fatal("switcher still open with one column")797 t.Fatal("switcher still open with one column")
798 }798 }
799 if s := indicatorScene(t, c); len(scenePreviews(s)) != 0 {799 if s := indicatorScene(t, c); len(scenePreviews(s)) != 0 {
@@ -816,7 +816,7 @@ func TestSwitcherOutputRemovalCancels(t *testing.T) {
816 t.Fatal("no switcher screen")816 t.Fatal("no switcher screen")
817 }817 }
818 c.removeScreen(sc.name())818 c.removeScreen(sc.name())
819- if c.switcher.sc != nil || c.switcher.timerC != nil || sc.mon.sw.open {819+ if c.switcher.sc != nil || c.switcher.timerC != nil || sc.mon.sw.ws != nil {
820 t.Fatal("switcher survived its output")820 t.Fatal("switcher survived its output")
821 }821 }
822 if err := c.publish(context.Background()); err != nil {822 if err := c.publish(context.Background()); err != nil {
@@ -842,11 +842,11 @@ func TestSwitcherFocusMovingToAnotherOutputReopensThere(t *testing.T) {
842 }842 }
843 c.focusScreen = c.screenIndex("B")843 c.focusScreen = c.screenIndex("B")
844 bind(t, c, ActionSwitchColumnNext)844 bind(t, c, ActionSwitchColumnNext)
845- if first.mon.sw.open {845+ if first.mon.sw.ws != nil {
846 t.Fatal("old switcher left open")846 t.Fatal("old switcher left open")
847 }847 }
848- if c.switcher.sc != c.cur() || !c.cur().mon.sw.open || c.cur().mon.sw.at != 1 {848+ if c.switcher.sc != c.cur() || c.cur().mon.sw.ws == nil || c.cur().mon.sw.at != 1 {
849- t.Fatalf("new switcher on %v open %v", c.switcher.sc == c.cur(), c.cur().mon.sw.open)849+ t.Fatalf("new switcher on %v open %v", c.switcher.sc == c.cur(), c.cur().mon.sw.ws != nil)
850 }850 }
851 }851 }
852 852
@@ -918,7 +918,7 @@ func TestSwitcherShownDropsWhenAnotherWorkspaceComesOnScreen(t *testing.T) {
918 if len(scenePreviews(s)) != 0 || s.Dim != 0 || s.DimBehind {918 if len(scenePreviews(s)) != 0 || s.Dim != 0 || s.DimBehind {
919 t.Fatalf("scene still dimmed or previewing: dim %v previews %+v", s.Dim, scenePreviews(s))919 t.Fatalf("scene still dimmed or previewing: dim %v previews %+v", s.Dim, scenePreviews(s))
920 }920 }
921- if m.sw.open || c.switcher.sc != nil || c.switcher.timerC != nil {921+ if m.sw.ws != nil || c.switcher.sc != nil || c.switcher.timerC != nil {
922 t.Fatal("stale switcher left open")922 t.Fatal("stale switcher left open")
923 }923 }
924 var any bool924 var any bool
@@ -937,8 +937,8 @@ func TestSwitcherShownDropsWhenAnotherWorkspaceComesOnScreen(t *testing.T) {
937 // The next switch opens fresh on the new workspace.937 // The next switch opens fresh on the new workspace.
938 c.mods = ports.ModSuper938 c.mods = ports.ModSuper
939 bind(t, c, ActionSwitchColumnNext)939 bind(t, c, ActionSwitchColumnNext)
940- if sw := m.sw; !sw.open || sw.ws != other || sw.at != 1 {940+ if sw := m.sw; sw.ws == nil || sw.ws != other || sw.at != 1 {
941- t.Fatalf("open %v on other %v at %d", sw.open, sw.ws == other, sw.at)941+ t.Fatalf("open %v on other %v at %d", sw.ws != nil, sw.ws == other, sw.at)
942 }942 }
943 }943 }
944 944
@@ -953,18 +953,36 @@ func TestSwitcherStepOnStaleWorkspaceOpensFresh(t *testing.T) {
953 }953 }
954 sendKeys(t, c, tab(ports.ModSuper, true))954 sendKeys(t, c, tab(ports.ModSuper, true))
955 sw := c.cur().mon.sw955 sw := c.cur().mon.sw
956- if !sw.open || sw.ws != other || sw.shown || sw.at != 1 {956+ if sw.ws == nil || sw.ws != other || sw.shown || sw.at != 1 {
957- t.Fatalf("open %v on other %v shown %v at %d", sw.open, sw.ws == other, sw.shown, sw.at)957+ t.Fatalf("open %v on other %v shown %v at %d", sw.ws != nil, sw.ws == other, sw.shown, sw.at)
958 }958 }
959 // A release on a stale switcher commits nothing and closes it.959 // A release on a stale switcher commits nothing and closes it.
960 c.cur().mon.show(c.cur().mon.Workspaces[0])960 c.cur().mon.show(c.cur().mon.Workspaces[0])
961 focus := switcherFocus(c)961 focus := switcherFocus(c)
962 sendKeys(t, c, superKey(0, false))962 sendKeys(t, c, superKey(0, false))
963- if c.cur().mon.sw.open || c.switcher.sc != nil || switcherFocus(c) != focus {963+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || switcherFocus(c) != focus {
964 t.Fatal("stale switcher survived the release")964 t.Fatal("stale switcher survived the release")
965 }965 }
966 }966 }
967 967
968+// The delay passes after another workspace came on screen with no publish
969+// in between: the tick drops the switcher instead of showing stale cards.
970+func TestSwitcherTickOnStaleWorkspaceCancels(t *testing.T) {
971+ c, other := twoWorkspaces(t)
972+ sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))
973+ m := c.cur().mon
974+ if m.sw.ws == nil {
975+ t.Fatal("switcher not open")
976+ }
977+ m.show(other)
978+ if !c.switcherTick() {
979+ t.Fatal("tick on a stale workspace asks for no scene")
980+ }
981+ if m.sw.ws != nil || m.sw.shown || c.switcher.sc != nil || c.switcher.timerC != nil {
982+ t.Fatal("stale switcher survived the tick")
983+ }
984+}
985+
968 func TestSwitchIgnoredDuringDrag(t *testing.T) {986 func TestSwitchIgnoredDuringDrag(t *testing.T) {
969 c, _, _ := switcherCore(t, 3)987 c, _, _ := switcherCore(t, 3)
970 start := switcherFocus(c)988 start := switcherFocus(c)
@@ -972,7 +990,7 @@ func TestSwitchIgnoredDuringDrag(t *testing.T) {
972 c.drag = d990 c.drag = d
973 c.buttons[btnLeft] = true991 c.buttons[btnLeft] = true
974 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))992 sendKeys(t, c, superKey(ports.ModSuper, true), tab(ports.ModSuper, true))
975- if c.cur().mon.sw.open || c.switcher.sc != nil || c.switcher.timerC != nil {993+ if c.cur().mon.sw.ws != nil || c.switcher.sc != nil || c.switcher.timerC != nil {
976 t.Fatal("switcher opened during a drag")994 t.Fatal("switcher opened during a drag")
977 }995 }
978 if c.drag != d || switcherFocus(c) != start {996 if c.drag != d || switcherFocus(c) != start {
@@ -1096,7 +1114,7 @@ func TestSwitchUnmodifiedBindThroughKeyEvent(t *testing.T) {
1096 c, ic, _ := switcherCore(t, 3)1114 c, ic, _ := switcherCore(t, 3)
1097 start := switcherFocus(c)1115 start := switcherFocus(c)
1098 sendKeys(t, c, ports.KeyEvent{Keysym: "F9", Keycode: 75, Pressed: true})1116 sendKeys(t, c, ports.KeyEvent{Keysym: "F9", Keycode: 75, Pressed: true})
1099- if c.cur().mon.sw.open || len(ic.timers) != 0 || switcherFocus(c) == start {1117+ if c.cur().mon.sw.ws != nil || len(ic.timers) != 0 || switcherFocus(c) == start {
1100- t.Fatalf("open %v timers %v focus %d (was %d)", c.cur().mon.sw.open, ic.timers, switcherFocus(c), start)1118+ t.Fatalf("open %v timers %v focus %d (was %d)", c.cur().mon.sw.ws != nil, ic.timers, switcherFocus(c), start)
1101 }1119 }
1102 }1120 }
M internal/core/transition.go +20 -18
@@ -25,9 +25,10 @@ import (
2525 type viewShot struct {
2626 sc *screen
2727 ws *Workspace
28- // overview is set when the screen showed its overview: ws is then the
29- // row on screen, the camera fields stay zero, and rects are the cards.
30- overview bool
28+ // preview is set when the screen showed a preview (the overview or the
29+ // column switcher): ws is then the workspace on screen, the camera
30+ // fields stay zero, and rects are the cards.
31+ preview bool
3132 // settledView is ws's settled view and view where its columns were on screen
3233 // (settledView plus shift).
3334 settledView int
@@ -403,9 +404,9 @@ func (c *Core) mapWindow(v ports.WindowMapped) {
403404 }
404405 shots := c.snapshot(now)
405406 for i := range shots {
406- // A screen with its overview open re-lays its cards at once: a map
407- // is not an overview action (transition skips a shot without ws).
408- if shots[i].overview {
407+ // A screen showing a preview re-lays its cards at once: a map is
408+ // not a preview action (transition skips a shot without ws).
409+ if shots[i].preview {
409410 shots[i].ws = nil
410411 }
411412 }
@@ -416,7 +417,7 @@ func (c *Core) mapWindow(v ports.WindowMapped) {
416417
417418 // appearMapped starts the entrance of a window that just mapped when it
418419 // shows on its screen's current workspace as a tile or an ordinary float:
419-// not with the overview open or a session lock, not fullscreen (it keeps
420+// not with a preview open or a session lock, not fullscreen (it keeps
420421 // the direct scanout path), not a float below the columns, not a stash
421422 // peek.
422423 func (c *Core) appearMapped(id WindowID, now time.Time) {
@@ -442,7 +443,7 @@ func (c *Core) appearMapped(id WindowID, now time.Time) {
442443 // leaves from its drawn rect, fade and speed, not from its settled rect).
443444 // Like a map, an unmap is a client event that animates; the settled layout
444445 // is the same with animations off. A fullscreen window, one under a
445-// session lock or with the overview open just goes.
446+// session lock or with a preview open just goes.
446447 func (c *Core) unmapWindow(v ports.WindowUnmapped) {
447448 // The window is unmapped: a toplevel that maps again keeps its ID and
448449 // must be configured from nothing (a leaving entry keeps no configure).
@@ -471,7 +472,7 @@ func (c *Core) unmapWindow(v ports.WindowUnmapped) {
471472 shots := c.snapshot(now)
472473 var shot *rectShot
473474 for i := range shots {
474- if shots[i].overview {
475+ if shots[i].preview {
475476 shots[i].ws = nil
476477 }
477478 if shots[i].sc == sc {
@@ -620,7 +621,7 @@ func (c *Core) snapshot(now time.Time) []viewShot {
620621 }
621622 s.sc = sc
622623 w := m.Current()
623- s.ws, s.overview = w, m.previewing()
624+ s.ws, s.preview = w, m.previewing()
624625 if !m.previewing() {
625626 // The overview does not scroll or slide: no camera.
626627 s.settledView, s.view = w.View, float64(w.View)+w.view.off
@@ -705,7 +706,7 @@ func (c *Core) transition(before []viewShot, now time.Time) {
705706 if b.ws == nil || !c.hasScreen(b.sc) || c.following(b.sc) {
706707 continue
707708 }
708- if b.overview || b.sc.mon.previewing() {
709+ if b.preview || b.sc.mon.previewing() {
709710 c.transitionOverview(b, now)
710711 continue
711712 }
@@ -886,18 +887,19 @@ func (c *Core) peekDim(p Placement) float64 {
886887 }
887888
888889 // transitionOverview starts the motions of an action that opened or closed
889-// the overview, or moved inside it (the selection, the rows). The camera
890-// springs stay stopped (ToggleOverview): every window drawn before and
891-// after moves from its shown rect to its settled one, its content scaled
892-// with it (a card's Zoom, rectMotion.show). A window drawn after and not
893-// before (another row, a column the overview hid) fades in with no motion
890+// a preview (the overview or the column switcher), or moved inside it (the
891+// selection, the rows). The camera springs stay stopped (ToggleOverview):
892+// every window drawn before and after moves from its shown rect to its
893+// settled one, its content scaled with it (a card's Zoom,
894+// rectMotion.show). A window drawn after and not before (another row, a
895+// column a preview hid) fades in with no motion
894896 // of its own; one drawn before and not after just goes. Motions of the
895897 // other state are dropped first: their rects are not the ones snapshot
896898 // measured from. A fade that ran on a window drawn in both states carries
897899 // over (picking a card that still fades in does not pop it).
898900 func (c *Core) transitionOverview(b *viewShot, now time.Time) {
899901 sc := b.sc
900- changed := b.overview != sc.mon.previewing()
902+ changed := b.preview != sc.mon.previewing()
901903 if changed {
902904 sc.stopRects()
903905 }
@@ -937,7 +939,7 @@ func (c *Core) transitionOverview(b *viewShot, now time.Time) {
937939 func movedFrom(b *viewShot, before []viewShot) *viewShot {
938940 w := b.sc.mon.Current()
939941 for i := range before {
940- if o := &before[i]; o.ws == w && o.sc != b.sc && !o.overview {
942+ if o := &before[i]; o.ws == w && o.sc != b.sc && !o.preview {
941943 return o
942944 }
943945 }
@@ -25,9 +25,10 @@ import (
25 type viewShot struct {25 type viewShot struct {
26 sc *screen26 sc *screen
27 ws *Workspace27 ws *Workspace
28- // overview is set when the screen showed its overview: ws is then the28+ // preview is set when the screen showed a preview (the overview or the
29- // row on screen, the camera fields stay zero, and rects are the cards.29+ // column switcher): ws is then the workspace on screen, the camera
30- overview bool30+ // fields stay zero, and rects are the cards.
31+ preview bool
31 // settledView is ws's settled view and view where its columns were on screen32 // settledView is ws's settled view and view where its columns were on screen
32 // (settledView plus shift).33 // (settledView plus shift).
33 settledView int34 settledView int
@@ -403,9 +404,9 @@ func (c *Core) mapWindow(v ports.WindowMapped) {
403 }404 }
404 shots := c.snapshot(now)405 shots := c.snapshot(now)
405 for i := range shots {406 for i := range shots {
406- // A screen with its overview open re-lays its cards at once: a map407+ // A screen showing a preview re-lays its cards at once: a map is
407- // is not an overview action (transition skips a shot without ws).408+ // not a preview action (transition skips a shot without ws).
408- if shots[i].overview {409+ if shots[i].preview {
409 shots[i].ws = nil410 shots[i].ws = nil
410 }411 }
411 }412 }
@@ -416,7 +417,7 @@ func (c *Core) mapWindow(v ports.WindowMapped) {
416 417
417 // appearMapped starts the entrance of a window that just mapped when it418 // appearMapped starts the entrance of a window that just mapped when it
418 // shows on its screen's current workspace as a tile or an ordinary float:419 // shows on its screen's current workspace as a tile or an ordinary float:
419-// not with the overview open or a session lock, not fullscreen (it keeps420+// not with a preview open or a session lock, not fullscreen (it keeps
420 // the direct scanout path), not a float below the columns, not a stash421 // the direct scanout path), not a float below the columns, not a stash
421 // peek.422 // peek.
422 func (c *Core) appearMapped(id WindowID, now time.Time) {423 func (c *Core) appearMapped(id WindowID, now time.Time) {
@@ -442,7 +443,7 @@ func (c *Core) appearMapped(id WindowID, now time.Time) {
442 // leaves from its drawn rect, fade and speed, not from its settled rect).443 // leaves from its drawn rect, fade and speed, not from its settled rect).
443 // Like a map, an unmap is a client event that animates; the settled layout444 // Like a map, an unmap is a client event that animates; the settled layout
444 // is the same with animations off. A fullscreen window, one under a445 // is the same with animations off. A fullscreen window, one under a
445-// session lock or with the overview open just goes.446+// session lock or with a preview open just goes.
446 func (c *Core) unmapWindow(v ports.WindowUnmapped) {447 func (c *Core) unmapWindow(v ports.WindowUnmapped) {
447 // The window is unmapped: a toplevel that maps again keeps its ID and448 // The window is unmapped: a toplevel that maps again keeps its ID and
448 // must be configured from nothing (a leaving entry keeps no configure).449 // must be configured from nothing (a leaving entry keeps no configure).
@@ -471,7 +472,7 @@ func (c *Core) unmapWindow(v ports.WindowUnmapped) {
471 shots := c.snapshot(now)472 shots := c.snapshot(now)
472 var shot *rectShot473 var shot *rectShot
473 for i := range shots {474 for i := range shots {
474- if shots[i].overview {475+ if shots[i].preview {
475 shots[i].ws = nil476 shots[i].ws = nil
476 }477 }
477 if shots[i].sc == sc {478 if shots[i].sc == sc {
@@ -620,7 +621,7 @@ func (c *Core) snapshot(now time.Time) []viewShot {
620 }621 }
621 s.sc = sc622 s.sc = sc
622 w := m.Current()623 w := m.Current()
623- s.ws, s.overview = w, m.previewing()624+ s.ws, s.preview = w, m.previewing()
624 if !m.previewing() {625 if !m.previewing() {
625 // The overview does not scroll or slide: no camera.626 // The overview does not scroll or slide: no camera.
626 s.settledView, s.view = w.View, float64(w.View)+w.view.off627 s.settledView, s.view = w.View, float64(w.View)+w.view.off
@@ -705,7 +706,7 @@ func (c *Core) transition(before []viewShot, now time.Time) {
705 if b.ws == nil || !c.hasScreen(b.sc) || c.following(b.sc) {706 if b.ws == nil || !c.hasScreen(b.sc) || c.following(b.sc) {
706 continue707 continue
707 }708 }
708- if b.overview || b.sc.mon.previewing() {709+ if b.preview || b.sc.mon.previewing() {
709 c.transitionOverview(b, now)710 c.transitionOverview(b, now)
710 continue711 continue
711 }712 }
@@ -886,18 +887,19 @@ func (c *Core) peekDim(p Placement) float64 {
886 }887 }
887 888
888 // transitionOverview starts the motions of an action that opened or closed889 // transitionOverview starts the motions of an action that opened or closed
889-// the overview, or moved inside it (the selection, the rows). The camera890+// a preview (the overview or the column switcher), or moved inside it (the
890-// springs stay stopped (ToggleOverview): every window drawn before and891+// selection, the rows). The camera springs stay stopped (ToggleOverview):
891-// after moves from its shown rect to its settled one, its content scaled892+// every window drawn before and after moves from its shown rect to its
892-// with it (a card's Zoom, rectMotion.show). A window drawn after and not893+// settled one, its content scaled with it (a card's Zoom,
893-// before (another row, a column the overview hid) fades in with no motion894+// rectMotion.show). A window drawn after and not before (another row, a
895+// column a preview hid) fades in with no motion
894 // of its own; one drawn before and not after just goes. Motions of the896 // of its own; one drawn before and not after just goes. Motions of the
895 // other state are dropped first: their rects are not the ones snapshot897 // other state are dropped first: their rects are not the ones snapshot
896 // measured from. A fade that ran on a window drawn in both states carries898 // measured from. A fade that ran on a window drawn in both states carries
897 // over (picking a card that still fades in does not pop it).899 // over (picking a card that still fades in does not pop it).
898 func (c *Core) transitionOverview(b *viewShot, now time.Time) {900 func (c *Core) transitionOverview(b *viewShot, now time.Time) {
899 sc := b.sc901 sc := b.sc
900- changed := b.overview != sc.mon.previewing()902+ changed := b.preview != sc.mon.previewing()
901 if changed {903 if changed {
902 sc.stopRects()904 sc.stopRects()
903 }905 }
@@ -937,7 +939,7 @@ func (c *Core) transitionOverview(b *viewShot, now time.Time) {
937 func movedFrom(b *viewShot, before []viewShot) *viewShot {939 func movedFrom(b *viewShot, before []viewShot) *viewShot {
938 w := b.sc.mon.Current()940 w := b.sc.mon.Current()
939 for i := range before {941 for i := range before {
940- if o := &before[i]; o.ws == w && o.sc != b.sc && !o.overview {942+ if o := &before[i]; o.ws == w && o.sc != b.sc && !o.preview {
941 return o943 return o
942 }944 }
943 }945 }

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