libmars 0.6.2

Library to build window managers and accompanying utilities
Documentation
extern crate x11;

use std::ptr;
use std::ffi::*;
use x11::xlib::{self, XDefaultRootWindow};

use crate::common::error::*;
use crate::interfaces::control::*;
use crate::platforms::x11::misc::{
    atoms::X11Atom,
    atoms::X11Atom::*,
    send_client_message,
    window::X11Window,
};


pub type EventHandlerFn = Option<unsafe extern "C" fn(_: *mut xlib::Display, _: *mut xlib::XErrorEvent) -> c_int>;

pub struct X11Controller {
    display: *mut xlib::Display,
    root: xlib::Window,
    event_handler: EventHandlerFn,
}


impl X11Controller {
    pub fn new_from_display(display: *mut xlib::Display, event_handler: EventHandlerFn) -> X11Controller {
        let root = unsafe { XDefaultRootWindow(display) };
        X11Controller {
            display, root, event_handler,
        }
    }

    pub fn new() -> Result<X11Controller> {
        unsafe {
            let display = xlib::XOpenDisplay(ptr::null());
            if display.is_null() {
                return Err(MarsError::x11_open_display());
            }
            Ok(Self::new_from_display(display, None))
        }
    }
}


impl WMController<xlib::Window> for X11Controller {
    fn activate_window(&self, window: xlib::Window) -> Result<()> {
        require_ewmh_atom(self.display, NetActiveWindow)?;
        let data = xlib::ClientMessageData::new();
        send_client_message(self.display, NetActiveWindow, window, data);
        Ok(())
    }

    fn center_window(&self, window: xlib::Window) -> Result<()> {
        require_ewmh_atom(self.display, MarsCenter)?;
        let data = xlib::ClientMessageData::new();
        send_client_message(self.display, MarsCenter, window, data);
        Ok(())
    }

    fn close_window(&self, window: xlib::Window) -> Result<()> {
        if require_ewmh_atom(self.display, NetCloseWindow).is_ok() {
            let data = xlib::ClientMessageData::new();
            send_client_message(self.display, NetCloseWindow, window, data);
        } else {
            window.x11_close(self.display, self.event_handler);
        }

        Ok(())
    }

    fn count_workspaces(&self) -> Result<u32> {
        require_ewmh_atom(self.display, NetNumberOfDesktops)?;
        let data = self.root.x11_read_property_long(self.display, NetNumberOfDesktops, xlib::XA_CARDINAL)
            .map_err(MarsError::unknown)?;
        let ws_u64 = data.first()
            .ok_or(MarsError::invalid_response("reading property _NET_NUMBER_OF_DESKTOPS"))?;
        (*ws_u64).try_into()
            .map_err(|_| MarsError::failed_conversion(ws_u64, stringify!(u64), stringify!(u32)))
    }

    fn current_workspace(&self) -> Result<u32> {
        require_ewmh_atom(self.display, NetCurrentDesktop)?;
        let data = self.root.x11_read_property_long(self.display, NetCurrentDesktop, xlib::XA_CARDINAL)
            .map_err(MarsError::unknown)?;
        let ws_u64 = data.first()
            .ok_or(MarsError::invalid_response("reading property _NET_CURRENT_DESKTOP"))?;
        (*ws_u64).try_into()
            .map_err(|_| MarsError::failed_conversion(ws_u64, stringify!(u64), stringify!(u32)))
    }

    fn fullscreen_window(&self, window: xlib::Window, mode: SettingMode) -> Result<()> {
        require_ewmh_atom(self.display, NetWMState)?;
        require_ewmh_atom(self.display, NetWMStateFullscreen)?;
        let mut data = xlib::ClientMessageData::new();
        data.set_long(0, i64::from(mode));
        data.set_long(1, NetWMStateFullscreen.to_xlib_atom(self.display) as i64);
        send_client_message(self.display, NetWMState, window, data);
        Ok(())
    }

    fn get_active_window(&self) -> Result<xlib::Window> {
        require_ewmh_atom(self.display, NetActiveWindow)?;
        let data = self.root.x11_read_property_long(self.display, NetActiveWindow, xlib::XA_WINDOW)
                .map_err(MarsError::unknown)?;
        data.first().copied()
            .ok_or(MarsError::invalid_response("reading property _NET_ACTIVE_WINDOW"))
    }

    fn get_workspace(&self, window: xlib::Window) -> Result<u32> {
        require_ewmh_atom(self.display, NetWMDesktop)?;

        if self.window_is_pinned(window)? {
            let data = window.x11_read_property_long(self.display, NetCurrentDesktop, xlib::XA_CARDINAL)
                .map_err(MarsError::unknown)?;
            let ws_u64 = data.first()
                .ok_or(MarsError::invalid_response("reading property _NET_WM_DESKTOP"))?;
            (*ws_u64).try_into()
                .map_err(|_| MarsError::failed_conversion(ws_u64, stringify!(u64), stringify!(u32)))
        } else {
            self.current_workspace()
        }
    }

    fn pin_window(&self, window: xlib::Window, mode: SettingMode) -> Result<()> {
        require_ewmh_atom(self.display, NetWMDesktop)?;
        match mode {
            SettingMode::Set => {
                let mut data = xlib::ClientMessageData::new();
                data.set_long(0, -1);
                send_client_message(self.display, NetWMDesktop, window, data);
            },
            SettingMode::Unset => {
                let current_workspace = self.current_workspace()?;
                self.send_window_to_workspace(window, current_workspace)?;
            },
            SettingMode::Toggle => {
                if self.window_is_pinned(window)? {
                    return self.pin_window(window, SettingMode::Unset);
                } else {
                    return self.pin_window(window, SettingMode::Set);
                }
            }
        }

        Ok(())
    }

    fn send_window_to_workspace(&self, window: xlib::Window, workspace: u32) -> Result<()> {
        require_ewmh_atom(self.display, NetWMDesktop)?;
        let mut data = xlib::ClientMessageData::new();
        data.set_long(0, workspace.into());
        send_client_message(self.display, NetWMDesktop, window, data);
        Ok(())
    }

    fn set_status(&self, status: String) -> Result<()> {
        let data = vec!(status);
        self.root.x11_set_text_list_property(self.display, MarsStatus, &data);
        unsafe { xlib::XFlush(self.display); }
        Ok(())
    }

    fn switch_workspace(&self, workspace: u32) -> Result<()> {
        require_ewmh_atom(self.display, NetCurrentDesktop)?;
        let mut data = xlib::ClientMessageData::new();
        data.set_long(0, workspace.into());
        send_client_message(self.display, NetCurrentDesktop, 0, data);
        Ok(())
    }

    fn tile_window(&self, window: xlib::Window, mode: SettingMode) -> Result<()> {
        require_ewmh_atom(self.display, NetWMState)?;
        require_ewmh_atom(self.display, MarsWMStateTiled)?;
        let mut data = xlib::ClientMessageData::new();
        data.set_long(0, i64::from(mode));
        data.set_long(1, MarsWMStateTiled.to_xlib_atom(self.display) as i64);
        send_client_message(self.display, NetWMState, window, data);
        Ok(())
    }

    fn window_is_fullscreen(&self, window: xlib::Window) -> Result<bool> {
        require_ewmh_atom(self.display, NetWMState)?;
        let is_fullscreen = window.x11_net_wm_state(self.display)
            .map_err(MarsError::unknown)?
            .contains(&NetWMStateFullscreen);
        Ok(is_fullscreen)
    }

    fn window_is_pinned(&self, window: xlib::Window) -> Result<bool> {
        require_ewmh_atom(self.display, NetWMDesktop)?;
        let data = window.x11_read_property_long(self.display, NetWMDesktop, xlib::XA_CARDINAL)
            .map_err(MarsError::unknown)?;
        let ws_u64 = data.first()
            .ok_or(MarsError::invalid_response("reading property _NET_WM_DESKTOP"))?;
        Ok(*ws_u64 == u64::MAX)
    }

    fn window_is_tiled(&self, window: xlib::Window) -> Result<bool> {
        require_ewmh_atom(self.display, NetWMState)?;
        let is_fullscreen = window.x11_net_wm_state(self.display)
            .map_err(MarsError::unknown)?
            .contains(&MarsWMStateTiled);
        Ok(is_fullscreen)
    }

    fn workspaces(&self) -> Result<Vec<String>> {
        require_ewmh_atom(self.display, NetDesktopNames)?;
        self.root.x11_get_text_list_property(self.display, NetDesktopNames)
            .map_err(MarsError::unknown)
    }
}

impl From<SettingMode> for i64 {
    fn from(value: SettingMode) -> Self {
        match value {
            SettingMode::Set => 1,
            SettingMode::Unset => 0,
            SettingMode::Toggle => 2,
        }
    }
}

fn require_ewmh_atom(display: *mut xlib::Display, atom: X11Atom) -> Result<()> {
    let root = unsafe {
        xlib::XDefaultRootWindow(display)
    };
    let xatom = atom.to_xlib_atom(display);
    let supported = root.x11_read_property_long(display, NetSupported, xlib::XA_ATOM)
        .map_err(|_| MarsError::x11_unsupported_atom(NetSupported))?;

    if supported.contains(&xatom) {
        Ok(())
    } else {
        Err(MarsError::x11_unsupported_atom(atom))
    }
}