use super::*; impl X11Platform { pub(super) fn handle_event(&mut self, event: XEvent) -> PlatformResult> { match event { XEvent::MapRequest(ev) => self.manage_new_window(ev.window), XEvent::ConfigureRequest(ev) => { if self.xid_to_core.contains_key(&ev.window) { // We own layout for managed clients; just ack with a // synthetic ConfigureNotify carrying real geometry. let geom = self.conn.get_geometry(ev.window).map_err(err)?.reply().map_err(err)?; let notify = x11rb::protocol::xproto::ConfigureNotifyEvent { response_type: x11rb::protocol::xproto::CONFIGURE_NOTIFY_EVENT, sequence: 0, event: ev.window, window: ev.window, above_sibling: x11rb::NONE, x: geom.x, y: geom.y, width: geom.width, height: geom.height, border_width: 0, override_redirect: false, }; self.conn.send_event(false, ev.window, EventMask::STRUCTURE_NOTIFY, notify).map_err(err)?; } else { let aux = ConfigureWindowAux::from_configure_request(&ev); self.conn.configure_window(ev.window, &aux).map_err(err)?; } self.conn.flush().map_err(err)?; Ok(None) } XEvent::UnmapNotify(ev) => { let struts_changed = self.forget_strut_window(ev.window); let unmanaged = self.unmanage(ev.window); // `unmanage`'s own `Event::WindowDestroyed` wins if this // window was somehow both a managed client *and* a tracked // strut window (shouldn't happen in practice - see // `track_strut_window`'s own doc comment - but `unmanage` // returning `Some` either way takes priority, since a // closed window matters more to core than a reservation // change on the very same event). Ok(unmanaged.or_else(|| struts_changed.then(struts::monitors_changed_event))) } XEvent::DestroyNotify(ev) => { let struts_changed = self.forget_strut_window(ev.window); let unmanaged = self.unmanage(ev.window); Ok(unmanaged.or_else(|| struts_changed.then(struts::monitors_changed_event))) } // Only a window we aren't already managing as a regular // top-level client - see `track_strut_window`'s own doc // comment for why a real panel/dock typically needs this path // specifically (override-redirect, so it never goes through // `manage_new_window`/`MapRequest` at all). Fires for *every* // other window that maps too (a client's own subwindows, an // override-redirect tooltip/menu popup), but `read_strut` // inside it is a cheap couple of `GetProperty` round-trips // that come back empty for anything that never sets the // property, so this costs nothing beyond that for the common // case of "not a bar". XEvent::MapNotify(ev) => { if self.xid_to_core.contains_key(&ev.window) { return Ok(None); } Ok(self.track_strut_window(ev.window).then(struts::monitors_changed_event)) } XEvent::PropertyNotify(ev) if ev.atom == self.atoms._NET_WM_STRUT || ev.atom == self.atoms._NET_WM_STRUT_PARTIAL => { Ok(self.update_strut_property(ev.window).then(struts::monitors_changed_event)) } XEvent::ButtonPress(ev) => { let (x, y) = (ev.root_x as i32, ev.root_y as i32); let button = match ev.detail { 1 => MouseButton::Left, 2 => MouseButton::Middle, 3 => MouseButton::Right, other => MouseButton::Other(other), }; // The context menu, if open, captures every press: a click // inside resolves whichever row it landed on, a click // anywhere else just dismisses it - same "one click, one // action" rule the Wayland backend's own input/pointer.rs // follows, and why `open_context_menu` grabs the pointer // (see that method's own doc comment for why X11 needs to). if self.context_menu.is_some() { let (row_action, menu_window) = { let (menu, _) = self.context_menu.as_ref().unwrap(); (menu.row_at(x, y).map(|r| menu.items[r].1), menu.window) }; match row_action { Some(action) if !action.is_interactive() => {} Some(action) => { self.close_context_menu()?; self.run_context_menu_action(menu_window, action)?; } None => self.close_context_menu()?, } return Ok(Some(Event::MouseButtonPress { button, x, y })); } let hit = self.wm.borrow().hit_test(x, y); if let Some((id, hit)) = hit { self.raise_and_focus(id)?; match (button, hit) { (MouseButton::Left, TitlebarHit::Drag) => self.wm.borrow_mut().start_drag(id, x, y), (MouseButton::Left, TitlebarHit::Close) => self.request_close(id)?, (MouseButton::Left, TitlebarHit::Maximize) => { self.wm.borrow_mut().toggle_maximize(id); self.sync_geometry(id)?; } (MouseButton::Left, TitlebarHit::Minimize) => { self.wm.borrow_mut().minimize_window(id); if let Some(frame) = self.frame_for(id) { self.conn.unmap_window(frame).map_err(err)?; } } (MouseButton::Left, TitlebarHit::Resize(edge)) => self.wm.borrow_mut().start_resize(id, edge, x, y), // Right-click on the titlebar's own drag area: the // window menu (minimize/maximize/fullscreen/ // floating/always-on-top/move-to-workspace/close). // Any other button/hit combination - right-click // on a button, middle-click anywhere - just // raises/focuses (already done above) and does // nothing further, matching real desktops (a // right-click on a titlebar button is not itself // a button action). (MouseButton::Right, TitlebarHit::Drag) => self.open_context_menu(id, (x, y))?, _ => {} } self.conn.flush().map_err(err)?; } // Let the click through to the client (we grabbed it SYNC). self.conn.allow_events(x11rb::protocol::xproto::Allow::REPLAY_POINTER, ev.time).map_err(err)?; self.conn.flush().map_err(err)?; Ok(Some(Event::MouseButtonPress { button, x, y })) } XEvent::ButtonRelease(ev) => { let mut wm = self.wm.borrow_mut(); let was_dragging = wm.is_dragging(); let was_resizing = wm.is_resizing(); let dragged_id = wm.focused_id(); if was_dragging { wm.end_drag(); } else if was_resizing { wm.end_resize(); } drop(wm); if was_dragging || was_resizing { if let Some(id) = dragged_id { self.sync_geometry(id)?; } } Ok(Some(Event::MouseButtonRelease { button: MouseButton::Left, x: ev.root_x as i32, y: ev.root_y as i32 })) } XEvent::MotionNotify(ev) => { let (x, y) = (ev.root_x as i32, ev.root_y as i32); let mut wm = self.wm.borrow_mut(); let id = wm.focused_id(); if wm.is_dragging() { wm.update_drag(x, y); } else if wm.is_resizing() { wm.update_resize(x, y); } else { return Ok(Some(Event::MouseMotion { x, y })); } drop(wm); if let Some(id) = id { self.sync_geometry(id)?; } Ok(Some(Event::MouseMotion { x, y })) } XEvent::KeyPress(ev) => { let keysym = self.keycode_to_keysym(ev.detail); let Some(key_name) = keysyms::keysym_to_name(keysym) else { return Ok(None) }; Ok(Some(Event::KeyPress { key_name, modifiers: Self::modifiers_from_state(ev.state.into()) })) } XEvent::KeyRelease(ev) => { let keysym = self.keycode_to_keysym(ev.detail); let Some(key_name) = keysyms::keysym_to_name(keysym) else { return Ok(None) }; Ok(Some(Event::KeyRelease { key_name, modifiers: Self::modifiers_from_state(ev.state.into()) })) } XEvent::Expose(ev) => { // The popup has no backing store, unlike the compositor- // side Wayland menu - anything that uncovers it needs a // real repaint. if self.context_menu.as_ref().is_some_and(|(_, popup)| *popup == ev.window) { let _ = self.redraw_context_menu(); return Ok(None); } let target = self.frames.iter().find(|(_, f)| f.frame == ev.window).map(|(&id, _)| id); if let Some(id) = target { let w = self.wm.borrow().window(id).cloned_for_render(); if let Some(w) = w { let focused = self.wm.borrow().focused_id() == Some(id); let _ = self.redraw_decoration(id, &w, focused); } } Ok(None) } _ => Ok(None), } } }