markdown · 5469 bytes Raw Blame History

Sprint 1: Basic Window Management

Goal: Manage windows in a single workspace with manual tiling using a BSP tree.

Objectives

  • Track window lifecycle (create, destroy)
  • Implement Binary Space Partition tree data structure
  • Tile windows using the BSP tree
  • Basic focus handling (click-to-focus)
  • One hardcoded keybind to test input grabbing

Prerequisites

  • Sprint 0 complete (X connection, event loop working)

Tasks

1.1 Window Tracking

  • Create src/core/window.rs
  • Define Window struct to track window state
  • Create window registry (HashMap<XWindow, Window>)
  • Handle MapRequest: add window to registry
  • Handle UnmapNotify: remove window from registry
  • Handle DestroyNotify: clean up window

Window struct:

pub struct Window {
    pub id: x11rb::protocol::xproto::Window,
    pub x: i16,
    pub y: i16,
    pub width: u16,
    pub height: u16,
    pub mapped: bool,
}

1.2 BSP Tree Data Structure

  • Create src/core/tree.rs
  • Implement SplitDirection enum (Horizontal, Vertical)
  • Implement Node enum (Internal, Leaf)
  • Implement tree traversal methods
  • Implement node insertion (always vertical for now)
  • Implement node removal
  • Implement geometry calculation from tree

Core tree structure:

#[derive(Debug, Clone, Copy)]
pub enum SplitDirection {
    Horizontal,  // Top/Bottom
    Vertical,    // Left/Right
}

#[derive(Debug)]
pub enum Node {
    Internal {
        split: SplitDirection,
        ratio: f32,
        left: Box<Node>,
        right: Box<Node>,
    },
    Leaf {
        window: Option<WindowId>,
    },
}

1.3 Geometry Calculation

  • Calculate window geometries from BSP tree
  • Pass root geometry (screen size minus any reserved space)
  • Recursively divide space based on splits and ratios
  • Return list of (WindowId, Rect) pairs

Algorithm:

impl Node {
    pub fn calculate_geometries(&self, rect: Rect) -> Vec<(WindowId, Rect)> {
        match self {
            Node::Leaf { window: Some(id) } => vec![(*id, rect)],
            Node::Leaf { window: None } => vec![],
            Node::Internal { split, ratio, left, right } => {
                let (left_rect, right_rect) = rect.split(*split, *ratio);
                let mut result = left.calculate_geometries(left_rect);
                result.extend(right.calculate_geometries(right_rect));
                result
            }
        }
    }
}

1.4 Apply Geometries to X11

  • Create function to configure window geometry
  • Use ConfigureWindow request
  • Handle border width in calculations
  • Apply all geometries after tree changes
fn apply_geometry(conn: &impl Connection, window: Window, rect: Rect) -> Result<()> {
    let aux = ConfigureWindowAux::new()
        .x(rect.x as i32)
        .y(rect.y as i32)
        .width(rect.width as u32)
        .height(rect.height as u32);
    conn.configure_window(window, &aux)?;
    Ok(())
}

1.5 Window Insertion

  • Find the focused leaf node
  • Insert new window next to focused window
  • If no focused window, insert at root
  • Split vertically (for now - smart split is Sprint 2)
  • Recalculate and apply all geometries

1.6 Window Removal

  • Find and remove window from tree
  • Collapse parent if only one child remains
  • Handle removing last window (empty tree)
  • Update focus to sibling or parent's other child
  • Recalculate and apply geometries

1.7 Focus Handling

  • Track currently focused window
  • Handle ButtonPress on windows (click-to-focus)
  • Set input focus using SetInputFocus
  • Visual feedback: different border color for focused window
  • Grab button on unfocused windows, ungrab on focused
// Set focus
conn.set_input_focus(InputFocus::PARENT, window, CURRENT_TIME)?;

// Visual feedback
let border_color = if focused { 0x5294e2 } else { 0x2d2d2d };
conn.change_window_attributes(window, &ChangeWindowAttributesAux::new()
    .border_pixel(border_color))?;

1.8 Basic Keyboard Grab

  • Grab Mod4+Return (super+enter) globally
  • On keypress, spawn terminal (hardcoded: alacritty or xterm)
  • Use std::process::Command to spawn
// Grab the key
conn.grab_key(
    false,
    root,
    ModMask::M4,  // Super/Win key
    keycode_for_return,
    GrabMode::ASYNC,
    GrabMode::ASYNC,
)?;

Acceptance Criteria

  1. Opening windows tiles them vertically (side by side)
  2. Closing a window removes it and re-tiles remaining windows
  3. Clicking a window focuses it (border color changes)
  4. Mod+Enter spawns a terminal
  5. Windows properly fill available screen space
  6. No crashes on rapid window open/close

Testing Strategy

# In Xephyr session
DISPLAY=:1 cargo run &

# Open multiple terminals
DISPLAY=:1 xterm &
DISPLAY=:1 xterm &
DISPLAY=:1 xterm &

# Verify: 3 windows tiled vertically
# Click each to verify focus changes
# Close middle window, verify remaining 2 expand

Edge Cases to Handle

  • First window (empty tree)
  • Last window closed (empty tree again)
  • Rapid window creation
  • Windows that set their own size (override redirect)
  • Transient windows (handle in later sprint)

Notes

  • Keep split ratio at 0.5 for now (equal splits)
  • Vertical-only splits for now (Sprint 2 adds smart splitting)
  • No gaps yet (Sprint 8)
  • Focused border color hardcoded (Sprint 4 makes configurable)
View source
1 # Sprint 1: Basic Window Management
2
3 **Goal:** Manage windows in a single workspace with manual tiling using a BSP tree.
4
5 ## Objectives
6
7 - Track window lifecycle (create, destroy)
8 - Implement Binary Space Partition tree data structure
9 - Tile windows using the BSP tree
10 - Basic focus handling (click-to-focus)
11 - One hardcoded keybind to test input grabbing
12
13 ## Prerequisites
14
15 - Sprint 0 complete (X connection, event loop working)
16
17 ## Tasks
18
19 ### 1.1 Window Tracking
20 - [ ] Create `src/core/window.rs`
21 - [ ] Define `Window` struct to track window state
22 - [ ] Create window registry (HashMap<XWindow, Window>)
23 - [ ] Handle MapRequest: add window to registry
24 - [ ] Handle UnmapNotify: remove window from registry
25 - [ ] Handle DestroyNotify: clean up window
26
27 **Window struct:**
28 ```rust
29 pub struct Window {
30 pub id: x11rb::protocol::xproto::Window,
31 pub x: i16,
32 pub y: i16,
33 pub width: u16,
34 pub height: u16,
35 pub mapped: bool,
36 }
37 ```
38
39 ### 1.2 BSP Tree Data Structure
40 - [ ] Create `src/core/tree.rs`
41 - [ ] Implement `SplitDirection` enum (Horizontal, Vertical)
42 - [ ] Implement `Node` enum (Internal, Leaf)
43 - [ ] Implement tree traversal methods
44 - [ ] Implement node insertion (always vertical for now)
45 - [ ] Implement node removal
46 - [ ] Implement geometry calculation from tree
47
48 **Core tree structure:**
49 ```rust
50 #[derive(Debug, Clone, Copy)]
51 pub enum SplitDirection {
52 Horizontal, // Top/Bottom
53 Vertical, // Left/Right
54 }
55
56 #[derive(Debug)]
57 pub enum Node {
58 Internal {
59 split: SplitDirection,
60 ratio: f32,
61 left: Box<Node>,
62 right: Box<Node>,
63 },
64 Leaf {
65 window: Option<WindowId>,
66 },
67 }
68 ```
69
70 ### 1.3 Geometry Calculation
71 - [ ] Calculate window geometries from BSP tree
72 - [ ] Pass root geometry (screen size minus any reserved space)
73 - [ ] Recursively divide space based on splits and ratios
74 - [ ] Return list of (WindowId, Rect) pairs
75
76 **Algorithm:**
77 ```rust
78 impl Node {
79 pub fn calculate_geometries(&self, rect: Rect) -> Vec<(WindowId, Rect)> {
80 match self {
81 Node::Leaf { window: Some(id) } => vec![(*id, rect)],
82 Node::Leaf { window: None } => vec![],
83 Node::Internal { split, ratio, left, right } => {
84 let (left_rect, right_rect) = rect.split(*split, *ratio);
85 let mut result = left.calculate_geometries(left_rect);
86 result.extend(right.calculate_geometries(right_rect));
87 result
88 }
89 }
90 }
91 }
92 ```
93
94 ### 1.4 Apply Geometries to X11
95 - [ ] Create function to configure window geometry
96 - [ ] Use `ConfigureWindow` request
97 - [ ] Handle border width in calculations
98 - [ ] Apply all geometries after tree changes
99
100 ```rust
101 fn apply_geometry(conn: &impl Connection, window: Window, rect: Rect) -> Result<()> {
102 let aux = ConfigureWindowAux::new()
103 .x(rect.x as i32)
104 .y(rect.y as i32)
105 .width(rect.width as u32)
106 .height(rect.height as u32);
107 conn.configure_window(window, &aux)?;
108 Ok(())
109 }
110 ```
111
112 ### 1.5 Window Insertion
113 - [ ] Find the focused leaf node
114 - [ ] Insert new window next to focused window
115 - [ ] If no focused window, insert at root
116 - [ ] Split vertically (for now - smart split is Sprint 2)
117 - [ ] Recalculate and apply all geometries
118
119 ### 1.6 Window Removal
120 - [ ] Find and remove window from tree
121 - [ ] Collapse parent if only one child remains
122 - [ ] Handle removing last window (empty tree)
123 - [ ] Update focus to sibling or parent's other child
124 - [ ] Recalculate and apply geometries
125
126 ### 1.7 Focus Handling
127 - [ ] Track currently focused window
128 - [ ] Handle ButtonPress on windows (click-to-focus)
129 - [ ] Set input focus using `SetInputFocus`
130 - [ ] Visual feedback: different border color for focused window
131 - [ ] Grab button on unfocused windows, ungrab on focused
132
133 ```rust
134 // Set focus
135 conn.set_input_focus(InputFocus::PARENT, window, CURRENT_TIME)?;
136
137 // Visual feedback
138 let border_color = if focused { 0x5294e2 } else { 0x2d2d2d };
139 conn.change_window_attributes(window, &ChangeWindowAttributesAux::new()
140 .border_pixel(border_color))?;
141 ```
142
143 ### 1.8 Basic Keyboard Grab
144 - [ ] Grab Mod4+Return (super+enter) globally
145 - [ ] On keypress, spawn terminal (hardcoded: `alacritty` or `xterm`)
146 - [ ] Use `std::process::Command` to spawn
147
148 ```rust
149 // Grab the key
150 conn.grab_key(
151 false,
152 root,
153 ModMask::M4, // Super/Win key
154 keycode_for_return,
155 GrabMode::ASYNC,
156 GrabMode::ASYNC,
157 )?;
158 ```
159
160 ## Acceptance Criteria
161
162 1. Opening windows tiles them vertically (side by side)
163 2. Closing a window removes it and re-tiles remaining windows
164 3. Clicking a window focuses it (border color changes)
165 4. Mod+Enter spawns a terminal
166 5. Windows properly fill available screen space
167 6. No crashes on rapid window open/close
168
169 ## Testing Strategy
170
171 ```bash
172 # In Xephyr session
173 DISPLAY=:1 cargo run &
174
175 # Open multiple terminals
176 DISPLAY=:1 xterm &
177 DISPLAY=:1 xterm &
178 DISPLAY=:1 xterm &
179
180 # Verify: 3 windows tiled vertically
181 # Click each to verify focus changes
182 # Close middle window, verify remaining 2 expand
183 ```
184
185 ## Edge Cases to Handle
186
187 - First window (empty tree)
188 - Last window closed (empty tree again)
189 - Rapid window creation
190 - Windows that set their own size (override redirect)
191 - Transient windows (handle in later sprint)
192
193 ## Notes
194
195 - Keep split ratio at 0.5 for now (equal splits)
196 - Vertical-only splits for now (Sprint 2 adds smart splitting)
197 - No gaps yet (Sprint 8)
198 - Focused border color hardcoded (Sprint 4 makes configurable)