1 // A very simple reparenting window manager.
2 // This WM does NOT follow ICCCM!
3
4 extern crate x11rb;
5
6 use std::cmp::Reverse;
7 use std::collections::{BinaryHeap, HashSet};
8 use std::process::exit;
9
10 use x11rb::connection::Connection;
11 use x11rb::errors::{ReplyError, ReplyOrIdError};
12 use x11rb::protocol::xproto::*;
13 use x11rb::protocol::{ErrorKind, Event};
14 use x11rb::{COPY_DEPTH_FROM_PARENT, CURRENT_TIME};
15
16 const TITLEBAR_HEIGHT: u16 = 20;
17 const DRAG_BUTTON: Button = 1;
18
19 /// The state of a single window that we manage
20 #[derive(Debug)]
21 struct WindowState {
22 window: Window,
23 frame_window: Window,
24 x: i16,
25 y: i16,
26 width: u16,
27 height: u16,
28 }
29
30 impl WindowState {
new(window: Window, frame_window: Window, geom: &GetGeometryReply) -> WindowState31 fn new(window: Window, frame_window: Window, geom: &GetGeometryReply) -> WindowState {
32 WindowState {
33 window,
34 frame_window,
35 x: geom.x,
36 y: geom.y,
37 width: geom.width,
38 height: geom.height,
39 }
40 }
41
close_x_position(&self) -> i1642 fn close_x_position(&self) -> i16 {
43 std::cmp::max(0, self.width - TITLEBAR_HEIGHT) as _
44 }
45 }
46
47 /// The state of the full WM
48 #[derive(Debug)]
49 struct WMState<'a, C: Connection> {
50 conn: &'a C,
51 screen_num: usize,
52 black_gc: Gcontext,
53 windows: Vec<WindowState>,
54 pending_expose: HashSet<Window>,
55 wm_protocols: Atom,
56 wm_delete_window: Atom,
57 sequences_to_ignore: BinaryHeap<Reverse<u16>>,
58 // If this is Some, we are currently dragging the given window with the given offset relative
59 // to the mouse.
60 drag_window: Option<(Window, (i16, i16))>,
61 }
62
63 impl<'a, C: Connection> WMState<'a, C> {
new(conn: &'a C, screen_num: usize) -> Result<WMState<'a, C>, ReplyOrIdError>64 fn new(conn: &'a C, screen_num: usize) -> Result<WMState<'a, C>, ReplyOrIdError> {
65 let screen = &conn.setup().roots[screen_num];
66 let black_gc = conn.generate_id()?;
67 let font = conn.generate_id()?;
68 conn.open_font(font, b"9x15")?;
69
70 let gc_aux = CreateGCAux::new()
71 .graphics_exposures(0)
72 .background(screen.white_pixel)
73 .foreground(screen.black_pixel)
74 .font(font);
75 conn.create_gc(black_gc, screen.root, &gc_aux)?;
76 conn.close_font(font)?;
77
78 let wm_protocols = conn.intern_atom(false, b"WM_PROTOCOLS")?;
79 let wm_delete_window = conn.intern_atom(false, b"WM_DELETE_WINDOW")?;
80
81 Ok(WMState {
82 conn,
83 screen_num,
84 black_gc,
85 windows: Vec::default(),
86 pending_expose: HashSet::default(),
87 wm_protocols: wm_protocols.reply()?.atom,
88 wm_delete_window: wm_delete_window.reply()?.atom,
89 sequences_to_ignore: Default::default(),
90 drag_window: None,
91 })
92 }
93
94 /// Scan for already existing windows and manage them
scan_windows(&mut self) -> Result<(), ReplyOrIdError>95 fn scan_windows(&mut self) -> Result<(), ReplyOrIdError> {
96 // Get the already existing top-level windows.
97 let screen = &self.conn.setup().roots[self.screen_num];
98 let tree_reply = self.conn.query_tree(screen.root)?.reply()?;
99
100 // For each window, request its attributes and geometry *now*
101 let mut cookies = Vec::with_capacity(tree_reply.children.len());
102 for win in tree_reply.children {
103 let attr = self.conn.get_window_attributes(win)?;
104 let geom = self.conn.get_geometry(win)?;
105 cookies.push((win, attr, geom));
106 }
107 // Get the replies and manage windows
108 for (win, attr, geom) in cookies {
109 let (attr, geom) = (attr.reply(), geom.reply());
110 if attr.is_err() || geom.is_err() {
111 // Just skip this window
112 continue;
113 }
114 let (attr, geom) = (attr.unwrap(), geom.unwrap());
115 if !attr.override_redirect && attr.map_state != MapState::UNMAPPED {
116 self.manage_window(win, &geom)?;
117 }
118 }
119
120 Ok(())
121 }
122
123 /// Add a new window that should be managed by the WM
manage_window( &mut self, win: Window, geom: &GetGeometryReply, ) -> Result<(), ReplyOrIdError>124 fn manage_window(
125 &mut self,
126 win: Window,
127 geom: &GetGeometryReply,
128 ) -> Result<(), ReplyOrIdError> {
129 println!("Managing window {:?}", win);
130 let screen = &self.conn.setup().roots[self.screen_num];
131 assert!(self.find_window_by_id(win).is_none());
132
133 let frame_win = self.conn.generate_id()?;
134 let win_aux = CreateWindowAux::new()
135 .event_mask(
136 EventMask::EXPOSURE
137 | EventMask::SUBSTRUCTURE_NOTIFY
138 | EventMask::BUTTON_PRESS
139 | EventMask::BUTTON_RELEASE
140 | EventMask::POINTER_MOTION
141 | EventMask::ENTER_WINDOW,
142 )
143 .background_pixel(screen.white_pixel);
144 self.conn.create_window(
145 COPY_DEPTH_FROM_PARENT,
146 frame_win,
147 screen.root,
148 geom.x,
149 geom.y,
150 geom.width,
151 geom.height + TITLEBAR_HEIGHT,
152 1,
153 WindowClass::INPUT_OUTPUT,
154 0,
155 &win_aux,
156 )?;
157
158 self.conn.grab_server()?;
159 self.conn.change_save_set(SetMode::INSERT, win)?;
160 let cookie = self
161 .conn
162 .reparent_window(win, frame_win, 0, TITLEBAR_HEIGHT as _)?;
163 self.conn.map_window(win)?;
164 self.conn.map_window(frame_win)?;
165 self.conn.ungrab_server()?;
166
167 self.windows.push(WindowState::new(win, frame_win, geom));
168
169 // Ignore all events caused by reparent_window(). All those events have the sequence number
170 // of the reparent_window() request, thus remember its sequence number. The
171 // grab_server()/ungrab_server() is done so that the server does not handle other clients
172 // in-between, which could cause other events to get the same sequence number.
173 self.sequences_to_ignore
174 .push(Reverse(cookie.sequence_number() as u16));
175 Ok(())
176 }
177
178 /// Draw the titlebar of a window
redraw_titlebar(&self, state: &WindowState) -> Result<(), ReplyError>179 fn redraw_titlebar(&self, state: &WindowState) -> Result<(), ReplyError> {
180 let close_x = state.close_x_position();
181 self.conn.poly_line(
182 CoordMode::ORIGIN,
183 state.frame_window,
184 self.black_gc,
185 &[
186 Point { x: close_x, y: 0 },
187 Point {
188 x: state.width as _,
189 y: TITLEBAR_HEIGHT as _,
190 },
191 ],
192 )?;
193 self.conn.poly_line(
194 CoordMode::ORIGIN,
195 state.frame_window,
196 self.black_gc,
197 &[
198 Point {
199 x: close_x,
200 y: TITLEBAR_HEIGHT as _,
201 },
202 Point {
203 x: state.width as _,
204 y: 0,
205 },
206 ],
207 )?;
208 let reply = self
209 .conn
210 .get_property(
211 false,
212 state.window,
213 AtomEnum::WM_NAME,
214 AtomEnum::STRING,
215 0,
216 std::u32::MAX,
217 )?
218 .reply()?;
219 self.conn
220 .image_text8(state.frame_window, self.black_gc, 1, 10, &reply.value)?;
221 Ok(())
222 }
223
224 /// Do all pending work that was queued while handling some events
refresh(&mut self)225 fn refresh(&mut self) {
226 while let Some(&win) = self.pending_expose.iter().next() {
227 self.pending_expose.remove(&win);
228 if let Some(state) = self.find_window_by_id(win) {
229 if let Err(err) = self.redraw_titlebar(state) {
230 eprintln!(
231 "Error while redrawing window {:x?}: {:?}",
232 state.window, err
233 );
234 }
235 }
236 }
237 }
238
find_window_by_id(&self, win: Window) -> Option<&WindowState>239 fn find_window_by_id(&self, win: Window) -> Option<&WindowState> {
240 self.windows
241 .iter()
242 .find(|state| state.window == win || state.frame_window == win)
243 }
244
find_window_by_id_mut(&mut self, win: Window) -> Option<&mut WindowState>245 fn find_window_by_id_mut(&mut self, win: Window) -> Option<&mut WindowState> {
246 self.windows
247 .iter_mut()
248 .find(|state| state.window == win || state.frame_window == win)
249 }
250
251 /// Handle the given event
handle_event(&mut self, event: Event) -> Result<(), ReplyOrIdError>252 fn handle_event(&mut self, event: Event) -> Result<(), ReplyOrIdError> {
253 let mut should_ignore = false;
254 if let Some(seqno) = event.wire_sequence_number() {
255 // Check sequences_to_ignore and remove entries with old (=smaller) numbers.
256 while let Some(&Reverse(to_ignore)) = self.sequences_to_ignore.peek() {
257 // Sequence numbers can wrap around, so we cannot simply check for
258 // "to_ignore <= seqno". This is equivalent to "to_ignore - seqno <= 0", which is what we
259 // check instead. Since sequence numbers are unsigned, we need a trick: We decide
260 // that values from [MAX/2, MAX] count as "<= 0" and the rest doesn't.
261 if to_ignore.wrapping_sub(seqno) <= u16::max_value() / 2 {
262 // If the two sequence numbers are equal, this event should be ignored.
263 should_ignore = to_ignore == seqno;
264 break;
265 }
266 self.sequences_to_ignore.pop();
267 }
268 }
269
270 println!("Got event {:?}", event);
271 if should_ignore {
272 println!(" [ignored]");
273 return Ok(());
274 }
275 match event {
276 Event::UnmapNotify(event) => self.handle_unmap_notify(event),
277 Event::ConfigureRequest(event) => self.handle_configure_request(event)?,
278 Event::MapRequest(event) => self.handle_map_request(event)?,
279 Event::Expose(event) => self.handle_expose(event),
280 Event::EnterNotify(event) => self.handle_enter(event)?,
281 Event::ButtonPress(event) => self.handle_button_press(event),
282 Event::ButtonRelease(event) => self.handle_button_release(event)?,
283 Event::MotionNotify(event) => self.handle_motion_notify(event)?,
284 _ => {}
285 }
286 Ok(())
287 }
288
handle_unmap_notify(&mut self, event: UnmapNotifyEvent)289 fn handle_unmap_notify(&mut self, event: UnmapNotifyEvent) {
290 let root = self.conn.setup().roots[self.screen_num].root;
291 let conn = self.conn;
292 self.windows.retain(|state| {
293 if state.window != event.window {
294 return true;
295 }
296 conn.change_save_set(SetMode::DELETE, state.window).unwrap();
297 conn.reparent_window(state.window, root, state.x, state.y)
298 .unwrap();
299 conn.destroy_window(state.frame_window).unwrap();
300 false
301 });
302 }
303
handle_configure_request(&mut self, event: ConfigureRequestEvent) -> Result<(), ReplyError>304 fn handle_configure_request(&mut self, event: ConfigureRequestEvent) -> Result<(), ReplyError> {
305 if let Some(state) = self.find_window_by_id_mut(event.window) {
306 let _ = state;
307 unimplemented!();
308 }
309 // Allow clients to change everything, except sibling / stack mode
310 let aux = ConfigureWindowAux::from_configure_request(&event)
311 .sibling(None)
312 .stack_mode(None);
313 println!("Configure: {:?}", aux);
314 self.conn.configure_window(event.window, &aux)?;
315 Ok(())
316 }
317
handle_map_request(&mut self, event: MapRequestEvent) -> Result<(), ReplyOrIdError>318 fn handle_map_request(&mut self, event: MapRequestEvent) -> Result<(), ReplyOrIdError> {
319 self.manage_window(
320 event.window,
321 &self.conn.get_geometry(event.window)?.reply()?,
322 )
323 }
324
handle_expose(&mut self, event: ExposeEvent)325 fn handle_expose(&mut self, event: ExposeEvent) {
326 self.pending_expose.insert(event.window);
327 }
328
handle_enter(&mut self, event: EnterNotifyEvent) -> Result<(), ReplyError>329 fn handle_enter(&mut self, event: EnterNotifyEvent) -> Result<(), ReplyError> {
330 if let Some(state) = self.find_window_by_id(event.event) {
331 // Set the input focus (ignoring ICCCM's WM_PROTOCOLS / WM_TAKE_FOCUS)
332 self.conn
333 .set_input_focus(InputFocus::PARENT, state.window, CURRENT_TIME)?;
334 // Also raise the window to the top of the stacking order
335 self.conn.configure_window(
336 state.frame_window,
337 &ConfigureWindowAux::new().stack_mode(StackMode::ABOVE),
338 )?;
339 }
340 Ok(())
341 }
342
handle_button_press(&mut self, event: ButtonPressEvent)343 fn handle_button_press(&mut self, event: ButtonPressEvent) {
344 if event.detail != DRAG_BUTTON || event.state != 0 {
345 return;
346 }
347 if let Some(state) = self.find_window_by_id(event.event) {
348 if self.drag_window.is_none() && event.event_x < state.close_x_position() {
349 let (x, y) = (-event.event_x, -event.event_y);
350 self.drag_window = Some((state.frame_window, (x, y)));
351 }
352 }
353 }
354
handle_button_release(&mut self, event: ButtonReleaseEvent) -> Result<(), ReplyError>355 fn handle_button_release(&mut self, event: ButtonReleaseEvent) -> Result<(), ReplyError> {
356 if event.detail == DRAG_BUTTON {
357 self.drag_window = None;
358 }
359 if let Some(state) = self.find_window_by_id(event.event) {
360 if event.event_x >= state.close_x_position() {
361 let data = [self.wm_delete_window, 0, 0, 0, 0];
362 let event = ClientMessageEvent {
363 response_type: CLIENT_MESSAGE_EVENT,
364 format: 32,
365 sequence: 0,
366 window: state.window,
367 type_: self.wm_protocols,
368 data: data.into(),
369 };
370 self.conn
371 .send_event(false, state.window, EventMask::NO_EVENT, &event)?;
372 }
373 }
374 Ok(())
375 }
376
handle_motion_notify(&mut self, event: MotionNotifyEvent) -> Result<(), ReplyError>377 fn handle_motion_notify(&mut self, event: MotionNotifyEvent) -> Result<(), ReplyError> {
378 if let Some((win, (x, y))) = self.drag_window {
379 let (x, y) = (x + event.root_x, y + event.root_y);
380 // Sigh, X11 and its mixing up i16 and i32
381 let (x, y) = (x as i32, y as i32);
382 self.conn
383 .configure_window(win, &ConfigureWindowAux::new().x(x).y(y))?;
384 }
385 Ok(())
386 }
387 }
388
become_wm<C: Connection>(conn: &C, screen: &Screen) -> Result<(), ReplyError>389 fn become_wm<C: Connection>(conn: &C, screen: &Screen) -> Result<(), ReplyError> {
390 // Try to become the window manager. This causes an error if there is already another WM.
391 let change = ChangeWindowAttributesAux::default()
392 .event_mask(EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY);
393 let res = conn.change_window_attributes(screen.root, &change)?.check();
394 if let Err(ReplyError::X11Error(error)) = res {
395 if error.error_kind == ErrorKind::Access {
396 eprintln!("Another WM is already running.");
397 exit(1);
398 } else {
399 res
400 }
401 } else {
402 res
403 }
404 }
405
main()406 fn main() {
407 let (conn, screen_num) = x11rb::connect(None).unwrap();
408
409 // The following is only needed for start_timeout_thread(), which is used for 'tests'
410 let conn1 = std::sync::Arc::new(conn);
411 let conn = &*conn1;
412
413 let screen = &conn.setup().roots[screen_num];
414
415 become_wm(conn, screen).unwrap();
416
417 let mut wm_state = WMState::new(conn, screen_num).unwrap();
418 wm_state.scan_windows().unwrap();
419
420 util::start_timeout_thread(conn1.clone(), screen.root);
421
422 loop {
423 wm_state.refresh();
424 conn.flush().unwrap();
425
426 let event = conn.wait_for_event().unwrap();
427 let mut event_option = Some(event);
428 while let Some(event) = event_option {
429 if let Event::ClientMessage(_) = event {
430 // This is start_timeout_thread() signaling us to close (most likely).
431 return;
432 }
433
434 wm_state.handle_event(event).unwrap();
435 event_option = conn.poll_for_event().unwrap();
436 }
437 }
438 }
439
440 include!("integration_test_util/util.rs");
441