1#![allow(clippy::used_underscore_binding)]
4#![allow(clippy::items_after_statements)]
7#![allow(clippy::similar_names)]
9use crate::XlibWindowHandle;
10
11use super::xatom::XAtom;
12use super::xcursor::XCursor;
13use super::{Screen, Window, WindowHandle, utils};
14use leftwm_core::config::{Config, WindowHidingStrategy};
15use leftwm_core::models::{FocusBehaviour, FocusOnActivationBehaviour, Mode};
16use leftwm_core::utils::modmask_lookup::ModMask;
17use std::ffi::CString;
18use std::os::raw::{c_char, c_double, c_int, c_long, c_short, c_ulong};
19use std::sync::Arc;
20use std::{ptr, slice};
21use tokio::sync::{Notify, oneshot};
22use tokio::time::Duration;
23
24use x11_dl::xlib;
25use x11_dl::xrandr::Xrandr;
26
27mod getters;
28mod mouse;
29mod setters;
30mod window;
31
32type WindowStateConst = c_long;
33pub const WITHDRAWN_STATE: WindowStateConst = 0;
34pub const NORMAL_STATE: WindowStateConst = 1;
35pub const ICONIC_STATE: WindowStateConst = 2;
36const MAX_PROPERTY_VALUE_LEN: c_long = 4096;
37
38pub const ROOT_EVENT_MASK: c_long = xlib::SubstructureRedirectMask
39 | xlib::SubstructureNotifyMask
40 | xlib::ButtonPressMask
41 | xlib::PointerMotionMask
42 | xlib::StructureNotifyMask;
43
44const BUTTONMASK: c_long = xlib::ButtonPressMask | xlib::ButtonReleaseMask | xlib::ButtonMotionMask;
45const MOUSEMASK: c_long = BUTTONMASK | xlib::PointerMotionMask;
46
47const X_CONFIGUREWINDOW: u8 = 12;
48const X_GRABBUTTON: u8 = 28;
49const X_GRABKEY: u8 = 33;
50const X_SETINPUTFOCUS: u8 = 42;
51const X_COPYAREA: u8 = 62;
52const X_POLYSEGMENT: u8 = 66;
53const X_POLYFILLRECTANGLE: u8 = 70;
54const X_POLYTEXT8: u8 = 74;
55
56const extern "C" fn on_error_from_xlib(_: *mut xlib::Display, er: *mut xlib::XErrorEvent) -> c_int {
57 let err = unsafe { *er };
58 let ec = err.error_code;
59 let rc = err.request_code;
60 let ba = ec == xlib::BadAccess;
61 let bd = ec == xlib::BadDrawable;
62 let bm = ec == xlib::BadMatch;
63
64 if ec == xlib::BadWindow
65 || (rc == X_CONFIGUREWINDOW && bm)
66 || (rc == X_GRABBUTTON && ba)
67 || (rc == X_GRABKEY && ba)
68 || (rc == X_SETINPUTFOCUS && bm)
69 || (rc == X_COPYAREA && bd)
70 || (rc == X_POLYSEGMENT && bd)
71 || (rc == X_POLYFILLRECTANGLE && bd)
72 || (rc == X_POLYTEXT8 && bd)
73 {
74 return 0;
75 }
76 1
77}
78
79pub extern "C" fn on_error_from_xlib_dummy(
80 _: *mut xlib::Display,
81 _: *mut xlib::XErrorEvent,
82) -> c_int {
83 1
84}
85
86pub struct Colors {
87 normal: c_ulong,
88 floating: c_ulong,
89 active: c_ulong,
90 background: c_ulong,
91}
92
93#[derive(Debug, Clone)]
94pub enum XlibError {
95 FailedStatus,
96 RootWindowNotFound,
97 InvalidXAtom,
98}
99
100pub struct XWrap {
102 xlib: xlib::Xlib,
103 display: *mut xlib::Display,
104 root: xlib::Window,
105 pub atoms: XAtom,
106 cursors: XCursor,
107 colors: Colors,
108 pub managed_windows: Vec<xlib::Window>,
109 pub focused_window: xlib::Window,
110 pub tag_labels: Vec<String>,
111 pub mode: Mode<XlibWindowHandle>,
112 pub focus_behaviour: FocusBehaviour,
113 pub focus_on_activation: FocusOnActivationBehaviour,
114 pub mouse_key_mask: ModMask,
115 pub mode_origin: (i32, i32),
116 _task_guard: oneshot::Receiver<()>,
117 pub task_notify: Arc<Notify>,
118 pub motion_event_limiter: c_ulong,
119 pub refresh_rate: c_short,
120 pub window_hiding_strategy: WindowHidingStrategy,
121}
122
123impl Default for XWrap {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129impl XWrap {
130 #[must_use]
140 #[allow(clippy::too_many_lines)]
141 pub fn new() -> Self {
142 const SERVER: mio::Token = mio::Token(0);
143 let xlib = xlib::Xlib::open().expect("Couldn't not connect to Xorg Server");
144 let display = unsafe { (xlib.XOpenDisplay)(ptr::null()) };
145 assert!(!display.is_null(), "Null pointer in display");
146
147 let fd = unsafe { (xlib.XConnectionNumber)(display) };
148
149 let (guard, _task_guard) = oneshot::channel();
150 let notify = Arc::new(Notify::new());
151 let task_notify = notify.clone();
152
153 let mut poll = mio::Poll::new().expect("Unable to boot Mio");
154 let mut events = mio::Events::with_capacity(1);
155 poll.registry()
156 .register(
157 &mut mio::unix::SourceFd(&fd),
158 SERVER,
159 mio::Interest::READABLE,
160 )
161 .expect("Unable to boot Mio");
162 let timeout = Duration::from_millis(100);
163 tokio::task::spawn_blocking(move || {
164 loop {
165 if guard.is_closed() {
166 return;
167 }
168
169 if let Err(err) = poll.poll(&mut events, Some(timeout)) {
170 tracing::warn!("Xlib socket poll failed with {:?}", err);
171 continue;
172 }
173
174 events
175 .iter()
176 .filter(|event| SERVER == event.token())
177 .for_each(|_| notify.notify_one());
178 }
179 });
180
181 let atoms = XAtom::new(&xlib, display);
182 let cursors = XCursor::new(&xlib, display);
183 let root = unsafe { (xlib.XDefaultRootWindow)(display) };
184
185 let colors = Colors {
186 normal: 0,
187 floating: 0,
188 active: 0,
189 background: 0,
190 };
191
192 let refresh_rate = match Xrandr::open() {
193 Ok(xrandr) => unsafe {
195 let screen_resources = (xrandr.XRRGetScreenResources)(display, root);
196 let crtcs = slice::from_raw_parts(
197 (*screen_resources).crtcs,
198 (*screen_resources).ncrtc as usize,
199 );
200 let active_modes: Vec<c_ulong> = crtcs
201 .iter()
202 .map(|crtc| (xrandr.XRRGetCrtcInfo)(display, screen_resources, *crtc))
203 .filter(|&crtc_info| (*crtc_info).mode != 0)
204 .map(|crtc_info| (*crtc_info).mode)
205 .collect();
206 let modes = slice::from_raw_parts(
207 (*screen_resources).modes,
208 (*screen_resources).nmode as usize,
209 );
210 modes
211 .iter()
212 .filter(|mode_info| active_modes.contains(&mode_info.id))
213 .map(|mode_info| {
214 (mode_info.dotClock as c_double
215 / c_double::from(mode_info.hTotal * mode_info.vTotal))
216 as c_short
217 })
218 .max()
219 .unwrap_or(60)
220 },
221 Err(_) => 60,
222 };
223
224 tracing::debug!("Refresh Rate: {}", refresh_rate);
225
226 let xw = Self {
227 xlib,
228 display,
229 root,
230 atoms,
231 cursors,
232 colors,
233 managed_windows: vec![],
234 focused_window: root,
235 tag_labels: vec![],
236 mode: Mode::Normal,
237 focus_behaviour: FocusBehaviour::Sloppy,
238 focus_on_activation: FocusOnActivationBehaviour::MarkUrgent,
239 mouse_key_mask: ModMask::Zero,
240 mode_origin: (0, 0),
241 _task_guard,
242 task_notify,
243 motion_event_limiter: 0,
244 refresh_rate,
245 window_hiding_strategy: WindowHidingStrategy::default(),
246 };
247
248 extern "C" fn startup_check_for_other_wm(
250 _: *mut xlib::Display,
251 _: *mut xlib::XErrorEvent,
252 ) -> c_int {
253 tracing::error!("ERROR: another window manager is already running");
254 ::std::process::exit(-1);
255 }
256 unsafe {
257 (xw.xlib.XSetErrorHandler)(Some(startup_check_for_other_wm));
258 (xw.xlib.XSelectInput)(xw.display, root, xlib::SubstructureRedirectMask);
259 };
260 xw.sync();
261
262 unsafe { (xw.xlib.XSetErrorHandler)(Some(on_error_from_xlib)) };
263 xw.sync();
264 xw
265 }
266
267 pub fn load_config(&mut self, config: &impl Config) {
268 self.focus_behaviour = config.focus_behaviour();
269 self.focus_on_activation = config.focus_on_activation();
270 self.mouse_key_mask = utils::modmask_lookup::into_modmask(&config.mousekey());
271 self.tag_labels = config.create_list_of_tag_labels();
272 self.colors = Colors {
273 normal: self.get_color(config.default_border_color()),
274 floating: self.get_color(config.floating_border_color()),
275 active: self.get_color(config.focused_border_color()),
276 background: self.get_color(config.background_color()),
277 };
278 self.window_hiding_strategy = config.window_hiding_strategy();
279 }
280
281 pub fn init(&mut self) {
286 let root = self.root;
287
288 let mut attrs: xlib::XSetWindowAttributes = unsafe { std::mem::zeroed() };
289 attrs.cursor = self.cursors.normal;
290 attrs.event_mask = ROOT_EVENT_MASK;
291
292 unsafe {
293 (self.xlib.XChangeWindowAttributes)(
294 self.display,
295 self.root,
296 xlib::CWEventMask | xlib::CWCursor,
297 &raw mut attrs,
298 );
299 }
300
301 self.subscribe_to_event(root, ROOT_EVENT_MASK);
302
303 unsafe {
305 let supported: Vec<c_long> = self
306 .atoms
307 .net_supported()
308 .iter()
309 .map(|&atom| atom as c_long)
310 .collect();
311 self.replace_property_long(root, self.atoms.NetSupported, xlib::XA_ATOM, &supported);
312 std::mem::forget(supported);
313 (self.xlib.XDeleteProperty)(self.display, root, self.atoms.NetClientList);
315 }
316
317 self.init_desktops_hints();
319
320 self.sync();
321 }
322
323 pub fn init_desktops_hints(&self) {
330 let tag_labels = &self.tag_labels;
331 let tag_length = tag_labels.len();
332 let data = vec![tag_length as u32];
334 self.set_desktop_prop(&data, self.atoms.NetNumberOfDesktops);
335 let data = vec![0_u32, xlib::CurrentTime as u32];
337 self.set_desktop_prop(&data, self.atoms.NetCurrentDesktop);
338 let mut text: xlib::XTextProperty = unsafe { std::mem::zeroed() };
340 unsafe {
341 let mut clist_tags: Vec<*mut c_char> = tag_labels
342 .iter()
343 .map(|x| CString::new(x.clone()).unwrap_or_default().into_raw())
344 .collect();
345 let ptr = clist_tags.as_mut_ptr();
346 (self.xlib.Xutf8TextListToTextProperty)(
347 self.display,
348 ptr,
349 clist_tags.len() as i32,
350 xlib::XUTF8StringStyle,
351 &raw mut text,
352 );
353 std::mem::forget(clist_tags);
354 (self.xlib.XSetTextProperty)(
355 self.display,
356 self.root,
357 &raw mut text,
358 self.atoms.NetDesktopNames,
359 );
360 }
361
362 self.set_desktop_prop_string("LeftWM", self.atoms.NetWMName, self.atoms.UTF8String);
364
365 self.set_desktop_prop_string("LeftWM", self.atoms.WMClass, xlib::XA_STRING);
366
367 self.set_desktop_prop_c_ulong(
368 self.root as c_ulong,
369 self.atoms.NetSupportingWmCheck,
370 xlib::XA_WINDOW,
371 );
372
373 let data = vec![0_u32, 0_u32];
375 self.set_desktop_prop(&data, self.atoms.NetDesktopViewport);
376 }
377
378 fn send_xevent_atom(&self, window: xlib::Window, atom: xlib::Atom) -> bool {
381 if self.can_send_xevent_atom(window, atom) {
382 let mut msg: xlib::XClientMessageEvent = unsafe { std::mem::zeroed() };
383 msg.type_ = xlib::ClientMessage;
384 msg.window = window;
385 msg.message_type = self.atoms.WMProtocols;
386 msg.format = 32;
387 msg.data.set_long(0, atom as c_long);
388 msg.data.set_long(1, xlib::CurrentTime as c_long);
389 let mut ev: xlib::XEvent = msg.into();
390 self.send_xevent(window, 0, xlib::NoEventMask, &mut ev);
391 return true;
392 }
393 false
394 }
395
396 pub fn send_xevent(
399 &self,
400 window: xlib::Window,
401 propogate: i32,
402 mask: c_long,
403 event: &mut xlib::XEvent,
404 ) {
405 unsafe { (self.xlib.XSendEvent)(self.display, window, propogate, mask, event) };
406 self.sync();
407 }
408
409 fn can_send_xevent_atom(&self, window: xlib::Window, atom: xlib::Atom) -> bool {
412 unsafe {
413 let mut array: *mut xlib::Atom = std::mem::zeroed();
414 let mut length: c_int = std::mem::zeroed();
415 let status: xlib::Status =
416 (self.xlib.XGetWMProtocols)(self.display, window, &raw mut array, &raw mut length);
417 let protocols: &[xlib::Atom] = slice::from_raw_parts(array, length as usize);
418 status > 0 && protocols.contains(&atom)
419 }
420 }
421
422 pub fn update_colors(
424 &mut self,
425 focused: Option<WindowHandle<XlibWindowHandle>>,
426 windows: &[Window<XlibWindowHandle>],
427 ) {
428 for window in windows {
429 let WindowHandle(XlibWindowHandle(handle)) = window.handle;
430 let color: c_ulong = if focused == Some(window.handle) {
431 self.colors.active
432 } else if window.floating() {
433 self.colors.floating
434 } else {
435 self.colors.normal
436 };
437 self.set_window_border_color(handle, color);
438 }
439 self.set_background_color(self.colors.background);
440 }
441
442 pub fn set_mode(&mut self, mode: Mode<XlibWindowHandle>) {
444 match mode {
445 Mode::MovingWindow(h)
447 | Mode::ResizingWindow(h)
448 | Mode::ReadyToMove(h)
449 | Mode::ReadyToResize(h)
450 if h == self.get_default_root_handle() => {}
451 Mode::ReadyToMove(_) | Mode::ReadyToResize(_) if self.mode == Mode::Normal => {
452 self.mode = mode;
453 if let Ok(loc) = self.get_cursor_point() {
454 self.mode_origin = loc;
455 }
456 let cursor = match mode {
457 Mode::ReadyToResize(_) | Mode::ResizingWindow(_) => self.cursors.resize,
458 Mode::ReadyToMove(_) | Mode::MovingWindow(_) => self.cursors.move_,
459 Mode::Normal => self.cursors.normal,
460 };
461 self.grab_pointer(cursor);
462 }
463 Mode::MovingWindow(h) | Mode::ResizingWindow(h)
464 if self.mode == Mode::ReadyToMove(h) || self.mode == Mode::ReadyToResize(h) =>
465 {
466 self.ungrab_pointer();
467 self.mode = mode;
468 let cursor = match mode {
469 Mode::ReadyToResize(_) | Mode::ResizingWindow(_) => self.cursors.resize,
470 Mode::ReadyToMove(_) | Mode::MovingWindow(_) => self.cursors.move_,
471 Mode::Normal => self.cursors.normal,
472 };
473 self.grab_pointer(cursor);
474 }
475 Mode::Normal => {
476 self.ungrab_pointer();
477 self.mode = mode;
478 }
479 _ => {}
480 }
481 }
482
483 pub async fn wait_readable(&mut self) {
485 self.task_notify.notified().await;
486 }
487
488 pub fn sync(&self) {
491 unsafe { (self.xlib.XSync)(self.display, xlib::False) };
492 }
493
494 pub fn flush(&self) {
497 unsafe { (self.xlib.XFlush)(self.display) };
498 }
499
500 #[must_use]
503 pub fn queue_len(&self) -> i32 {
504 unsafe { (self.xlib.XPending)(self.display) }
505 }
506}