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 { 31 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 42 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> { 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 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 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 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 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 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 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 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 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 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 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 325 fn handle_expose(&mut self, event: ExposeEvent) { 326 self.pending_expose.insert(event.window); 327 } 328 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 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 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 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 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 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