feat(state): expose each tiled window's column and row (#140)
Closes #138
- commit
- 454609dab85d23c760ad4df94dd34780c38f5e4f
- author
- bnema · · 2026-10-05 17:53 UTC
- committer
- GitHub · · 2026-10-05 17:53 UTC
- parent
- 091596c
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
9 files · +66 -14
M docs/desktop.md +1 -0
| @@ -107,6 +107,7 @@ While it runs, NeferWL writes its state to `$XDG_RUNTIME_DIR/neferwl/<wayland so | |||
| 107 | 107 | - the focused output and window; | |
| 108 | 108 | - every window, with its app ID, PID, output, workspace, `workspace_id` (the ID string of its workspace, numbered or hidden) and whether it is on screen (`visible`); | |
| 109 | 109 | - `floating: true` for floating windows. Stashed windows also have their 1-based place in their workspace's stash (`stash_index` of `stash_count`, both `0` outside it) and `hidden: true` while the stash is hidden. | |
| 110 | + | - for tiled windows, their 1-based `column` (layout order, left to right with `overflow = "scroll"`; with `overflow = "fixed"`, spiral and expanded columns break that, so a later column can sit below or beside an earlier one) and `row` (top to bottom in the column); both are `0` when `floating` is true. Sort by `column`, `row` for layout order. | |
| 110 | 111 | ||
| 111 | 112 | ```sh | |
| 112 | 113 | neferwl state # the whole state as JSON | |
| @@ -107,6 +107,7 @@ While it runs, NeferWL writes its state to `$XDG_RUNTIME_DIR/neferwl/<wayland so | |||||
| 107 | - the focused output and window; | 107 | - the focused output and window; | ||
| 108 | - every window, with its app ID, PID, output, workspace, `workspace_id` (the ID string of its workspace, numbered or hidden) and whether it is on screen (`visible`); | 108 | - every window, with its app ID, PID, output, workspace, `workspace_id` (the ID string of its workspace, numbered or hidden) and whether it is on screen (`visible`); | ||
| 109 | - `floating: true` for floating windows. Stashed windows also have their 1-based place in their workspace's stash (`stash_index` of `stash_count`, both `0` outside it) and `hidden: true` while the stash is hidden. | 109 | - `floating: true` for floating windows. Stashed windows also have their 1-based place in their workspace's stash (`stash_index` of `stash_count`, both `0` outside it) and `hidden: true` while the stash is hidden. | ||
| 110 | + | - for tiled windows, their 1-based `column` (layout order, left to right with `overflow = "scroll"`; with `overflow = "fixed"`, spiral and expanded columns break that, so a later column can sit below or beside an earlier one) and `row` (top to bottom in the column); both are `0` when `floating` is true. Sort by `column`, `row` for layout order. | |||
| 110 | 111 | ||||
| 111 | ```sh | 112 | ```sh | ||
| 112 | neferwl state # the whole state as JSON | 113 | neferwl state # the whole state as JSON | ||
M internal/adapters/statefile/statefile.go +5 -1
| @@ -69,6 +69,10 @@ type Window struct { | |||
| 69 | 69 | // workspace's stash of StashCount windows; both are 0 outside it. | |
| 70 | 70 | StashIndex int `json:"stash_index"` | |
| 71 | 71 | StashCount int `json:"stash_count"` | |
| 72 | + | // Column and Row are the 1-based place of a tiled window; both are 0 | |
| 73 | + | // for floating windows. | |
| 74 | + | Column int `json:"column"` | |
| 75 | + | Row int `json:"row"` | |
| 72 | 76 | // Hidden is set for a stashed window while the stash is hidden. | |
| 73 | 77 | Hidden bool `json:"hidden"` | |
| 74 | 78 | } | |
| @@ -89,7 +93,7 @@ func fromPorts(st ports.State, ids *workspaceid.IDs) State { | |||
| 89 | 93 | } | |
| 90 | 94 | ||
| 91 | 95 | func window(w ports.WindowState, ids *workspaceid.IDs) Window { | |
| 92 | - | return Window{ID: uint64(w.ID), AppID: w.AppID, PID: w.PID, Output: w.Output, Workspace: w.Workspace, WorkspaceID: ids.ID(w.WorkspaceID, w.WorkspaceName), Visible: w.Visible, Floating: w.Floating, StashIndex: w.StashIndex, StashCount: w.StashCount, Hidden: w.Hidden} | |
| 96 | + | return Window{ID: uint64(w.ID), AppID: w.AppID, PID: w.PID, Output: w.Output, Workspace: w.Workspace, WorkspaceID: ids.ID(w.WorkspaceID, w.WorkspaceName), Visible: w.Visible, Floating: w.Floating, StashIndex: w.StashIndex, StashCount: w.StashCount, Column: w.Column, Row: w.Row, Hidden: w.Hidden} | |
| 93 | 97 | } | |
| 94 | 98 | ||
| 95 | 99 | // Run writes every state it receives to path until ctx ends, then removes | |
| @@ -69,6 +69,10 @@ type Window struct { | |||||
| 69 | // workspace's stash of StashCount windows; both are 0 outside it. | 69 | // workspace's stash of StashCount windows; both are 0 outside it. | ||
| 70 | StashIndex int `json:"stash_index"` | 70 | StashIndex int `json:"stash_index"` | ||
| 71 | StashCount int `json:"stash_count"` | 71 | StashCount int `json:"stash_count"` | ||
| 72 | + | // Column and Row are the 1-based place of a tiled window; both are 0 | |||
| 73 | + | // for floating windows. | |||
| 74 | + | Column int `json:"column"` | |||
| 75 | + | Row int `json:"row"` | |||
| 72 | // Hidden is set for a stashed window while the stash is hidden. | 76 | // Hidden is set for a stashed window while the stash is hidden. | ||
| 73 | Hidden bool `json:"hidden"` | 77 | Hidden bool `json:"hidden"` | ||
| 74 | } | 78 | } | ||
| @@ -89,7 +93,7 @@ func fromPorts(st ports.State, ids *workspaceid.IDs) State { | |||||
| 89 | } | 93 | } | ||
| 90 | 94 | ||||
| 91 | func window(w ports.WindowState, ids *workspaceid.IDs) Window { | 95 | func window(w ports.WindowState, ids *workspaceid.IDs) Window { | ||
| 92 | - | return Window{ID: uint64(w.ID), AppID: w.AppID, PID: w.PID, Output: w.Output, Workspace: w.Workspace, WorkspaceID: ids.ID(w.WorkspaceID, w.WorkspaceName), Visible: w.Visible, Floating: w.Floating, StashIndex: w.StashIndex, StashCount: w.StashCount, Hidden: w.Hidden} | 96 | + | return Window{ID: uint64(w.ID), AppID: w.AppID, PID: w.PID, Output: w.Output, Workspace: w.Workspace, WorkspaceID: ids.ID(w.WorkspaceID, w.WorkspaceName), Visible: w.Visible, Floating: w.Floating, StashIndex: w.StashIndex, StashCount: w.StashCount, Column: w.Column, Row: w.Row, Hidden: w.Hidden} |
| 93 | } | 97 | } | ||
| 94 | 98 | ||||
| 95 | // Run writes every state it receives to path until ctx ends, then removes | 99 | // Run writes every state it receives to path until ctx ends, then removes | ||
M internal/adapters/statefile/statefile_test.go +5 -5
| @@ -64,17 +64,17 @@ func TestRunWritesAndRemoves(t *testing.T) { | |||
| 64 | 64 | } | |
| 65 | 65 | } | |
| 66 | 66 | ||
| 67 | - | // Stash fields reach the file with their script-facing names. | |
| 68 | - | func TestWindowStashFields(t *testing.T) { | |
| 69 | - | got := window(ports.WindowState{ID: 3, Floating: true, StashIndex: 2, StashCount: 4, Hidden: true}, workspaceid.WithPrefix("0a1b2c3d")) | |
| 70 | - | if !got.Floating || got.StashIndex != 2 || got.StashCount != 4 || !got.Hidden { | |
| 67 | + | // Stash and cell fields reach the file with their script-facing names. | |
| 68 | + | func TestWindowScriptFields(t *testing.T) { | |
| 69 | + | got := window(ports.WindowState{ID: 3, Floating: true, StashIndex: 2, StashCount: 4, Column: 5, Row: 6, Hidden: true}, workspaceid.WithPrefix("0a1b2c3d")) | |
| 70 | + | if !got.Floating || got.StashIndex != 2 || got.StashCount != 4 || got.Column != 5 || got.Row != 6 || !got.Hidden { | |
| 71 | 71 | t.Fatalf("%+v", got) | |
| 72 | 72 | } | |
| 73 | 73 | data, err := json.Marshal(got) | |
| 74 | 74 | if err != nil { | |
| 75 | 75 | t.Fatal(err) | |
| 76 | 76 | } | |
| 77 | - | for _, key := range []string{`"floating":true`, `"stash_index":2`, `"stash_count":4`, `"hidden":true`} { | |
| 77 | + | for _, key := range []string{`"floating":true`, `"stash_index":2`, `"stash_count":4`, `"column":5`, `"row":6`, `"hidden":true`} { | |
| 78 | 78 | if !strings.Contains(string(data), key) { | |
| 79 | 79 | t.Fatalf("%s missing %s", data, key) | |
| 80 | 80 | } | |
| @@ -64,17 +64,17 @@ func TestRunWritesAndRemoves(t *testing.T) { | |||||
| 64 | } | 64 | } | ||
| 65 | } | 65 | } | ||
| 66 | 66 | ||||
| 67 | - | // Stash fields reach the file with their script-facing names. | 67 | + | // Stash and cell fields reach the file with their script-facing names. |
| 68 | - | func TestWindowStashFields(t *testing.T) { | 68 | + | func TestWindowScriptFields(t *testing.T) { |
| 69 | - | got := window(ports.WindowState{ID: 3, Floating: true, StashIndex: 2, StashCount: 4, Hidden: true}, workspaceid.WithPrefix("0a1b2c3d")) | 69 | + | got := window(ports.WindowState{ID: 3, Floating: true, StashIndex: 2, StashCount: 4, Column: 5, Row: 6, Hidden: true}, workspaceid.WithPrefix("0a1b2c3d")) |
| 70 | - | if !got.Floating || got.StashIndex != 2 || got.StashCount != 4 || !got.Hidden { | 70 | + | if !got.Floating || got.StashIndex != 2 || got.StashCount != 4 || got.Column != 5 || got.Row != 6 || !got.Hidden { |
| 71 | t.Fatalf("%+v", got) | 71 | t.Fatalf("%+v", got) | ||
| 72 | } | 72 | } | ||
| 73 | data, err := json.Marshal(got) | 73 | data, err := json.Marshal(got) | ||
| 74 | if err != nil { | 74 | if err != nil { | ||
| 75 | t.Fatal(err) | 75 | t.Fatal(err) | ||
| 76 | } | 76 | } | ||
| 77 | - | for _, key := range []string{`"floating":true`, `"stash_index":2`, `"stash_count":4`, `"hidden":true`} { | 77 | + | for _, key := range []string{`"floating":true`, `"stash_index":2`, `"stash_count":4`, `"column":5`, `"row":6`, `"hidden":true`} { |
| 78 | if !strings.Contains(string(data), key) { | 78 | if !strings.Contains(string(data), key) { | ||
| 79 | t.Fatalf("%s missing %s", data, key) | 79 | t.Fatalf("%s missing %s", data, key) | ||
| 80 | } | 80 | } | ||
M internal/core/layout.go +10 -0
| @@ -307,6 +307,16 @@ func (w *Workspace) windows() []WindowID { | |||
| 307 | 307 | return ids | |
| 308 | 308 | } | |
| 309 | 309 | ||
| 310 | + | // cell is the 1-based column and row of a tiled window, 0, 0 otherwise. | |
| 311 | + | func (w *Workspace) cell(id WindowID) (col, row int) { | |
| 312 | + | for ci, c := range w.Columns { | |
| 313 | + | if ri := slices.Index(c.Windows, id); ri >= 0 { | |
| 314 | + | return ci + 1, ri + 1 | |
| 315 | + | } | |
| 316 | + | } | |
| 317 | + | return 0, 0 | |
| 318 | + | } | |
| 319 | + | ||
| 310 | 320 | func (w *Workspace) floatIndex(id WindowID) int { | |
| 311 | 321 | return slices.IndexFunc(w.Floats, func(f Float) bool { return f.ID == id }) | |
| 312 | 322 | } | |
| @@ -307,6 +307,16 @@ func (w *Workspace) windows() []WindowID { | |||||
| 307 | return ids | 307 | return ids | ||
| 308 | } | 308 | } | ||
| 309 | 309 | ||||
| 310 | + | // cell is the 1-based column and row of a tiled window, 0, 0 otherwise. | |||
| 311 | + | func (w *Workspace) cell(id WindowID) (col, row int) { | |||
| 312 | + | for ci, c := range w.Columns { | |||
| 313 | + | if ri := slices.Index(c.Windows, id); ri >= 0 { | |||
| 314 | + | return ci + 1, ri + 1 | |||
| 315 | + | } | |||
| 316 | + | } | |||
| 317 | + | return 0, 0 | |||
| 318 | + | } | |||
| 319 | + | ||||
| 310 | func (w *Workspace) floatIndex(id WindowID) int { | 320 | func (w *Workspace) floatIndex(id WindowID) int { | ||
| 311 | return slices.IndexFunc(w.Floats, func(f Float) bool { return f.ID == id }) | 321 | return slices.IndexFunc(w.Floats, func(f Float) bool { return f.ID == id }) | ||
| 312 | } | 322 | } | ||
M internal/core/overview_stack.go +2 -6
| @@ -32,12 +32,8 @@ func (w *Workspace) overviewMaximized() bool { | |||
| 32 | 32 | } | |
| 33 | 33 | ||
| 34 | 34 | func (w *Workspace) columnOf(id WindowID) int { | |
| 35 | - | for i, c := range w.Columns { | |
| 36 | - | if slices.Contains(c.Windows, id) { | |
| 37 | - | return i | |
| 38 | - | } | |
| 39 | - | } | |
| 40 | - | return -1 | |
| 35 | + | c, _ := w.cell(id) | |
| 36 | + | return c - 1 | |
| 41 | 37 | } | |
| 42 | 38 | ||
| 43 | 39 | // stack lists what is on screen followed by what it hides, top to bottom. | |
| @@ -32,12 +32,8 @@ func (w *Workspace) overviewMaximized() bool { | |||||
| 32 | } | 32 | } | ||
| 33 | 33 | ||||
| 34 | func (w *Workspace) columnOf(id WindowID) int { | 34 | func (w *Workspace) columnOf(id WindowID) int { | ||
| 35 | - | for i, c := range w.Columns { | 35 | + | c, _ := w.cell(id) |
| 36 | - | if slices.Contains(c.Windows, id) { | 36 | + | return c - 1 |
| 37 | - | return i | |||
| 38 | - | } | |||
| 39 | - | } | |||
| 40 | - | return -1 | |||
| 41 | } | 37 | } | ||
| 42 | 38 | ||||
| 43 | // stack lists what is on screen followed by what it hides, top to bottom. | 39 | // stack lists what is on screen followed by what it hides, top to bottom. | ||
M internal/core/screens_test.go +2 -2
| @@ -799,8 +799,8 @@ func TestStateSnapshot(t *testing.T) { | |||
| 799 | 799 | Output: "DP-2", | |
| 800 | 800 | Outputs: []ports.OutputState{{Name: "DP-1", Active: 1, Count: 1, WorkspaceID: 1}, {Name: "DP-2", Active: 1, Count: 1, WorkspaceID: 2}}, | |
| 801 | 801 | Windows: []ports.WindowState{ | |
| 802 | - | {ID: 1, AppID: "foot", PID: 100, Output: "DP-1", Workspace: 1, WorkspaceID: 1, Visible: true}, | |
| 803 | - | {ID: 2, AppID: "firefox", PID: 200, Output: "DP-2", Workspace: 1, WorkspaceID: 2, Visible: true}, | |
| 802 | + | {ID: 1, AppID: "foot", PID: 100, Output: "DP-1", Workspace: 1, WorkspaceID: 1, Visible: true, Column: 1, Row: 1}, | |
| 803 | + | {ID: 2, AppID: "firefox", PID: 200, Output: "DP-2", Workspace: 1, WorkspaceID: 2, Visible: true, Column: 1, Row: 1}, | |
| 804 | 804 | }, | |
| 805 | 805 | } | |
| 806 | 806 | want.Window = &want.Windows[1] | |
| @@ -799,8 +799,8 @@ func TestStateSnapshot(t *testing.T) { | |||||
| 799 | Output: "DP-2", | 799 | Output: "DP-2", | ||
| 800 | Outputs: []ports.OutputState{{Name: "DP-1", Active: 1, Count: 1, WorkspaceID: 1}, {Name: "DP-2", Active: 1, Count: 1, WorkspaceID: 2}}, | 800 | Outputs: []ports.OutputState{{Name: "DP-1", Active: 1, Count: 1, WorkspaceID: 1}, {Name: "DP-2", Active: 1, Count: 1, WorkspaceID: 2}}, | ||
| 801 | Windows: []ports.WindowState{ | 801 | Windows: []ports.WindowState{ | ||
| 802 | - | {ID: 1, AppID: "foot", PID: 100, Output: "DP-1", Workspace: 1, WorkspaceID: 1, Visible: true}, | 802 | + | {ID: 1, AppID: "foot", PID: 100, Output: "DP-1", Workspace: 1, WorkspaceID: 1, Visible: true, Column: 1, Row: 1}, |
| 803 | - | {ID: 2, AppID: "firefox", PID: 200, Output: "DP-2", Workspace: 1, WorkspaceID: 2, Visible: true}, | 803 | + | {ID: 2, AppID: "firefox", PID: 200, Output: "DP-2", Workspace: 1, WorkspaceID: 2, Visible: true, Column: 1, Row: 1}, |
| 804 | }, | 804 | }, | ||
| 805 | } | 805 | } | ||
| 806 | want.Window = &want.Windows[1] | 806 | want.Window = &want.Windows[1] | ||
M internal/core/state.go +3 -0
| @@ -52,6 +52,9 @@ func (c *Core) state() ports.State { | |||
| 52 | 52 | if i := w.stashIndex(id); i >= 0 { | |
| 53 | 53 | v.StashIndex, v.StashCount, v.Hidden = i+1, len(w.Stash), w.stashHidden | |
| 54 | 54 | } | |
| 55 | + | if !v.Floating { | |
| 56 | + | v.Column, v.Row = w.cell(id) | |
| 57 | + | } | |
| 55 | 58 | st.Windows = append(st.Windows, v) | |
| 56 | 59 | } | |
| 57 | 60 | } | |
| @@ -52,6 +52,9 @@ func (c *Core) state() ports.State { | |||||
| 52 | if i := w.stashIndex(id); i >= 0 { | 52 | if i := w.stashIndex(id); i >= 0 { | ||
| 53 | v.StashIndex, v.StashCount, v.Hidden = i+1, len(w.Stash), w.stashHidden | 53 | v.StashIndex, v.StashCount, v.Hidden = i+1, len(w.Stash), w.stashHidden | ||
| 54 | } | 54 | } | ||
| 55 | + | if !v.Floating { | |||
| 56 | + | v.Column, v.Row = w.cell(id) | |||
| 57 | + | } | |||
| 55 | st.Windows = append(st.Windows, v) | 58 | st.Windows = append(st.Windows, v) | ||
| 56 | } | 59 | } | ||
| 57 | } | 60 | } | ||
A internal/core/state_cell_test.go +34 -0
| @@ -0,0 +1,34 @@ | |||
| 1 | + | package core | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/neferwl/internal/ports" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // Tiled windows report their column and row; stashed windows and native | |
| 10 | + | // floats report 0, 0. | |
| 11 | + | func TestStateColumnRow(t *testing.T) { | |
| 12 | + | cfg := ports.Config{} | |
| 13 | + | cfg.Keyboard.CmdKey = "super" | |
| 14 | + | cfg.Layout.MaxColumns = 1 | |
| 15 | + | c, err := New(cfg, Channels{Commands: make(chan ports.ClientCommand, 16), Scenes: make(chan []ports.Scene, 1)}) | |
| 16 | + | if err != nil { | |
| 17 | + | t.Fatal(err) | |
| 18 | + | } | |
| 19 | + | c.addScreen(ports.OutputInfo{Name: "OUT-1", Width: 400, Height: 300}) | |
| 20 | + | w := c.cur().mon.Current() | |
| 21 | + | w.Columns = []Column{{Windows: []WindowID{1}}, {Windows: []WindowID{65, 66}}, {Windows: []WindowID{2}}} | |
| 22 | + | w.Stash = []Float{{ID: 7}} | |
| 23 | + | w.Floats = []Float{{ID: 8}} | |
| 24 | + | want := map[WindowID][2]int{1: {1, 1}, 65: {2, 1}, 66: {2, 2}, 2: {3, 1}, 7: {0, 0}, 8: {0, 0}} | |
| 25 | + | got := map[WindowID][2]int{} | |
| 26 | + | for _, s := range c.state().Windows { | |
| 27 | + | got[s.ID] = [2]int{s.Column, s.Row} | |
| 28 | + | } | |
| 29 | + | for id, cr := range want { | |
| 30 | + | if g, ok := got[id]; !ok || g != cr { | |
| 31 | + | t.Errorf("window %d: column,row %v, want %v", id, g, cr) | |
| 32 | + | } | |
| 33 | + | } | |
| 34 | + | } | |
| @@ -0,0 +1,34 @@ | |||||
| 1 | + | package core | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | ||||
| 6 | + | "github.com/bnema/neferwl/internal/ports" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | // Tiled windows report their column and row; stashed windows and native | |||
| 10 | + | // floats report 0, 0. | |||
| 11 | + | func TestStateColumnRow(t *testing.T) { | |||
| 12 | + | cfg := ports.Config{} | |||
| 13 | + | cfg.Keyboard.CmdKey = "super" | |||
| 14 | + | cfg.Layout.MaxColumns = 1 | |||
| 15 | + | c, err := New(cfg, Channels{Commands: make(chan ports.ClientCommand, 16), Scenes: make(chan []ports.Scene, 1)}) | |||
| 16 | + | if err != nil { | |||
| 17 | + | t.Fatal(err) | |||
| 18 | + | } | |||
| 19 | + | c.addScreen(ports.OutputInfo{Name: "OUT-1", Width: 400, Height: 300}) | |||
| 20 | + | w := c.cur().mon.Current() | |||
| 21 | + | w.Columns = []Column{{Windows: []WindowID{1}}, {Windows: []WindowID{65, 66}}, {Windows: []WindowID{2}}} | |||
| 22 | + | w.Stash = []Float{{ID: 7}} | |||
| 23 | + | w.Floats = []Float{{ID: 8}} | |||
| 24 | + | want := map[WindowID][2]int{1: {1, 1}, 65: {2, 1}, 66: {2, 2}, 2: {3, 1}, 7: {0, 0}, 8: {0, 0}} | |||
| 25 | + | got := map[WindowID][2]int{} | |||
| 26 | + | for _, s := range c.state().Windows { | |||
| 27 | + | got[s.ID] = [2]int{s.Column, s.Row} | |||
| 28 | + | } | |||
| 29 | + | for id, cr := range want { | |||
| 30 | + | if g, ok := got[id]; !ok || g != cr { | |||
| 31 | + | t.Errorf("window %d: column,row %v, want %v", id, g, cr) | |||
| 32 | + | } | |||
| 33 | + | } | |||
| 34 | + | } | |||
M internal/ports/state.go +4 -0
| @@ -45,6 +45,10 @@ type WindowState struct { | |||
| 45 | 45 | // StashIndex is the 1-based place of a stashed window in its stash of | |
| 46 | 46 | // StashCount windows; both are 0 outside the stash. | |
| 47 | 47 | StashIndex, StashCount int | |
| 48 | + | // Column and Row are the 1-based place of a tiled window: its column in | |
| 49 | + | // layout order (not geometry under fixed overflow), its row top to | |
| 50 | + | // bottom in that column; both are 0 for floating windows. | |
| 51 | + | Column, Row int | |
| 48 | 52 | // Hidden is set for a stashed window while its stash is hidden. | |
| 49 | 53 | Hidden bool | |
| 50 | 54 | } | |
| @@ -45,6 +45,10 @@ type WindowState struct { | |||||
| 45 | // StashIndex is the 1-based place of a stashed window in its stash of | 45 | // StashIndex is the 1-based place of a stashed window in its stash of | ||
| 46 | // StashCount windows; both are 0 outside the stash. | 46 | // StashCount windows; both are 0 outside the stash. | ||
| 47 | StashIndex, StashCount int | 47 | StashIndex, StashCount int | ||
| 48 | + | // Column and Row are the 1-based place of a tiled window: its column in | |||
| 49 | + | // layout order (not geometry under fixed overflow), its row top to | |||
| 50 | + | // bottom in that column; both are 0 for floating windows. | |||
| 51 | + | Column, Row int | |||
| 48 | // Hidden is set for a stashed window while its stash is hidden. | 52 | // Hidden is set for a stashed window while its stash is hidden. | ||
| 49 | Hidden bool | 53 | Hidden bool | ||
| 50 | } | 54 | } | ||