markdown · 4005 bytes Raw Blame History

Sprint 0: Project Setup

Goal: Buildable skeleton that connects to X server and appears in the display manager greeter.

Objectives

  • Initialize Rust project with proper workspace structure
  • Establish logging and error handling patterns
  • Connect to X server and become a window manager
  • Create a stable event loop
  • Make gar selectable from the login greeter

Tasks

0.1 Initialize Cargo Project

  • Create Cargo.toml with workspace members (gar, garctl)
  • Set up src/main.rs entry point
  • Set up src/lib.rs for library code
  • Create module structure skeleton

Dependencies to add:

[dependencies]
x11rb = { version = "0.13", features = ["allow-unsafe-code"] }
tracing = "0.1"
tracing-subscriber = "0.3"
thiserror = "1.0"

0.2 Logging Infrastructure

  • Initialize tracing subscriber in main
  • Set up log levels (RUST_LOG environment variable)
  • Add structured logging for key events
  • Log to file for debugging sessions

0.3 X Server Connection

  • Create src/x11/mod.rs module
  • Create src/x11/connection.rs
  • Connect to X server using x11rb::connect()
  • Get root window and screen info
  • Handle connection errors gracefully

Key code pattern:

use x11rb::connection::Connection;
use x11rb::rust_connection::RustConnection;

pub fn connect() -> Result<(RustConnection, usize), Error> {
    let (conn, screen_num) = x11rb::connect(None)?;
    Ok((conn, screen_num))
}

0.4 Become Window Manager

  • Request SubstructureRedirect on root window
  • Handle "another WM running" error gracefully
  • Set up necessary event masks on root window

Critical X11 concept: Only ONE client can have SubstructureRedirect on the root window. This is how X11 ensures only one window manager runs.

let change = ChangeWindowAttributesAux::new()
    .event_mask(EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY);
conn.change_window_attributes(root, &change)?.check()?;

0.5 Event Loop

  • Create src/x11/events.rs
  • Implement basic event loop structure
  • Handle ConfigureRequest (pass through for now)
  • Handle MapRequest (map windows for now)
  • Log all received events
  • Clean shutdown on SIGTERM/SIGINT

Event loop pattern:

loop {
    let event = conn.wait_for_event()?;
    match event {
        Event::ConfigureRequest(e) => handle_configure_request(&conn, e)?,
        Event::MapRequest(e) => handle_map_request(&conn, e)?,
        _ => tracing::trace!("Unhandled event: {:?}", event),
    }
}

0.6 Desktop Entry

  • Create gar.desktop file
  • Install to /usr/share/xsessions/ (or document install location)
  • Test selection from GDM/LightDM/SDDM

Desktop entry content:

[Desktop Entry]
Name=gar
Comment=Tiling window manager with smart splits
Exec=gar
Type=XSession

Acceptance Criteria

  1. cargo build succeeds without errors
  2. cargo run connects to X server (or fails gracefully if one isn't available)
  3. Running in a nested X session (Xephyr) shows gar as the WM
  4. gar.desktop can be created and would appear in a greeter
  5. Existing windows are not lost (ConfigureRequest/MapRequest passthrough)
  6. Logs show event activity

Testing Strategy

Manual testing with Xephyr:

# Terminal 1: Start nested X server
Xephyr -br -ac -noreset -screen 1280x720 :1

# Terminal 2: Run gar in nested session
DISPLAY=:1 cargo run

# Terminal 3: Launch apps in nested session
DISPLAY=:1 xterm

Notes

  • Keep this sprint minimal - just enough to have a working foundation
  • Don't implement actual window management yet (Sprint 1)
  • Focus on correctness over features
  • Document any X11 quirks encountered

Resources

View source
1 # Sprint 0: Project Setup
2
3 **Goal:** Buildable skeleton that connects to X server and appears in the display manager greeter.
4
5 ## Objectives
6
7 - Initialize Rust project with proper workspace structure
8 - Establish logging and error handling patterns
9 - Connect to X server and become a window manager
10 - Create a stable event loop
11 - Make gar selectable from the login greeter
12
13 ## Tasks
14
15 ### 0.1 Initialize Cargo Project
16 - [ ] Create `Cargo.toml` with workspace members (gar, garctl)
17 - [ ] Set up `src/main.rs` entry point
18 - [ ] Set up `src/lib.rs` for library code
19 - [ ] Create module structure skeleton
20
21 **Dependencies to add:**
22 ```toml
23 [dependencies]
24 x11rb = { version = "0.13", features = ["allow-unsafe-code"] }
25 tracing = "0.1"
26 tracing-subscriber = "0.3"
27 thiserror = "1.0"
28 ```
29
30 ### 0.2 Logging Infrastructure
31 - [ ] Initialize tracing subscriber in main
32 - [ ] Set up log levels (RUST_LOG environment variable)
33 - [ ] Add structured logging for key events
34 - [ ] Log to file for debugging sessions
35
36 ### 0.3 X Server Connection
37 - [ ] Create `src/x11/mod.rs` module
38 - [ ] Create `src/x11/connection.rs`
39 - [ ] Connect to X server using `x11rb::connect()`
40 - [ ] Get root window and screen info
41 - [ ] Handle connection errors gracefully
42
43 **Key code pattern:**
44 ```rust
45 use x11rb::connection::Connection;
46 use x11rb::rust_connection::RustConnection;
47
48 pub fn connect() -> Result<(RustConnection, usize), Error> {
49 let (conn, screen_num) = x11rb::connect(None)?;
50 Ok((conn, screen_num))
51 }
52 ```
53
54 ### 0.4 Become Window Manager
55 - [ ] Request SubstructureRedirect on root window
56 - [ ] Handle "another WM running" error gracefully
57 - [ ] Set up necessary event masks on root window
58
59 **Critical X11 concept:** Only ONE client can have SubstructureRedirect on the root window. This is how X11 ensures only one window manager runs.
60
61 ```rust
62 let change = ChangeWindowAttributesAux::new()
63 .event_mask(EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY);
64 conn.change_window_attributes(root, &change)?.check()?;
65 ```
66
67 ### 0.5 Event Loop
68 - [ ] Create `src/x11/events.rs`
69 - [ ] Implement basic event loop structure
70 - [ ] Handle ConfigureRequest (pass through for now)
71 - [ ] Handle MapRequest (map windows for now)
72 - [ ] Log all received events
73 - [ ] Clean shutdown on SIGTERM/SIGINT
74
75 **Event loop pattern:**
76 ```rust
77 loop {
78 let event = conn.wait_for_event()?;
79 match event {
80 Event::ConfigureRequest(e) => handle_configure_request(&conn, e)?,
81 Event::MapRequest(e) => handle_map_request(&conn, e)?,
82 _ => tracing::trace!("Unhandled event: {:?}", event),
83 }
84 }
85 ```
86
87 ### 0.6 Desktop Entry
88 - [ ] Create `gar.desktop` file
89 - [ ] Install to `/usr/share/xsessions/` (or document install location)
90 - [ ] Test selection from GDM/LightDM/SDDM
91
92 **Desktop entry content:**
93 ```ini
94 [Desktop Entry]
95 Name=gar
96 Comment=Tiling window manager with smart splits
97 Exec=gar
98 Type=XSession
99 ```
100
101 ## Acceptance Criteria
102
103 1. `cargo build` succeeds without errors
104 2. `cargo run` connects to X server (or fails gracefully if one isn't available)
105 3. Running in a nested X session (Xephyr) shows gar as the WM
106 4. `gar.desktop` can be created and would appear in a greeter
107 5. Existing windows are not lost (ConfigureRequest/MapRequest passthrough)
108 6. Logs show event activity
109
110 ## Testing Strategy
111
112 **Manual testing with Xephyr:**
113 ```bash
114 # Terminal 1: Start nested X server
115 Xephyr -br -ac -noreset -screen 1280x720 :1
116
117 # Terminal 2: Run gar in nested session
118 DISPLAY=:1 cargo run
119
120 # Terminal 3: Launch apps in nested session
121 DISPLAY=:1 xterm
122 ```
123
124 ## Notes
125
126 - Keep this sprint minimal - just enough to have a working foundation
127 - Don't implement actual window management yet (Sprint 1)
128 - Focus on correctness over features
129 - Document any X11 quirks encountered
130
131 ## Resources
132
133 - [x11rb documentation](https://docs.rs/x11rb/latest/x11rb/)
134 - [X11 Protocol Reference](https://www.x.org/releases/current/doc/xproto/x11protocol.html)
135 - [ICCCM Spec](https://www.x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html)