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