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