iced-window-chrome 0.1.0

Native-only window chrome patches for iced on Windows, macOS, and Linux
Documentation
use crate::{ChromeSettings, Error, Result};

use raw_window_handle::{XlibDisplayHandle, XlibWindowHandle};
use x11rb::connection::Connection;
use x11rb::protocol::xproto::{self, ConnectionExt as _, Window};
use x11rb::wrapper::ConnectionExt as _;

x11rb::atom_manager! {
    pub MotifAtoms: MotifAtomsCookie {
        _MOTIF_WM_HINTS,
    }
}

const MWM_HINTS_FUNCTIONS: u32 = 1 << 0;
const MWM_HINTS_DECORATIONS: u32 = 1 << 1;

const MWM_FUNC_ALL: u32 = 1 << 0;
const MWM_FUNC_MINIMIZE: u32 = 1 << 3;
const MWM_FUNC_MAXIMIZE: u32 = 1 << 4;
const MWM_FUNC_CLOSE: u32 = 1 << 5;

#[derive(Debug, Clone, Copy)]
struct MotifHints {
    flags: u32,
    functions: u32,
    decorations: u32,
    input_mode: u32,
    status: u32,
}

impl MotifHints {
    const fn new() -> Self {
        Self {
            flags: 0,
            functions: 0,
            decorations: 0,
            input_mode: 0,
            status: 0,
        }
    }

    fn set_decorations(&mut self, decorations: bool) {
        self.flags |= MWM_HINTS_DECORATIONS;
        self.decorations = decorations.into();
    }

    fn set_close_button(&mut self, enabled: bool) {
        self.set_function(MWM_FUNC_CLOSE, enabled);
    }

    fn set_minimize_button(&mut self, enabled: bool) {
        self.set_function(MWM_FUNC_MINIMIZE, enabled);
    }

    fn set_maximize_button(&mut self, enabled: bool) {
        self.set_function(MWM_FUNC_MAXIMIZE, enabled);
    }

    fn set_function(&mut self, func: u32, enabled: bool) {
        if enabled {
            self.add_func(func);
        } else {
            self.remove_func(func);
        }
    }

    fn add_func(&mut self, func: u32) {
        if self.flags & MWM_HINTS_FUNCTIONS != 0 {
            if self.functions & MWM_FUNC_ALL != 0 {
                self.functions &= !func;
            } else {
                self.functions |= func;
            }
        }
    }

    fn remove_func(&mut self, func: u32) {
        if self.flags & MWM_HINTS_FUNCTIONS == 0 {
            self.flags |= MWM_HINTS_FUNCTIONS;
            self.functions = MWM_FUNC_ALL;
        }

        if self.functions & MWM_FUNC_ALL != 0 {
            self.functions |= func;
        } else {
            self.functions &= !func;
        }
    }

    fn from_property(values: &[u32]) -> Self {
        Self {
            flags: values.first().copied().unwrap_or_default(),
            functions: values.get(1).copied().unwrap_or_default(),
            decorations: values.get(2).copied().unwrap_or_default(),
            input_mode: values.get(3).copied().unwrap_or_default(),
            status: values.get(4).copied().unwrap_or_default(),
        }
    }

    fn as_property(self) -> [u32; 5] {
        [
            self.flags,
            self.functions,
            self.decorations,
            self.input_mode,
            self.status,
        ]
    }
}

pub fn apply_xlib(
    _display_handle: XlibDisplayHandle,
    window_handle: XlibWindowHandle,
    settings: &ChromeSettings,
) -> Result<()> {
    let window = window_handle.window as Window;
    let (conn, _) =
        x11rb::connect(None).map_err(|_| Error::Linux("failed to open X11 connection"))?;
    let atoms = MotifAtoms::new(&conn)
        .map_err(|_| Error::Linux("failed to request X11 atoms"))?
        .reply()
        .map_err(|_| Error::Linux("failed to resolve X11 atoms"))?;
    let mut hints = get_motif_hints(&conn, window, atoms._MOTIF_WM_HINTS)?;
    let chrome = &settings.linux;

    hints.set_decorations(chrome.decorations);
    hints.set_close_button(chrome.buttons.close);
    hints.set_minimize_button(chrome.buttons.minimize);
    hints.set_maximize_button(chrome.buttons.maximize);

    conn.change_property32(
        xproto::PropMode::REPLACE,
        window,
        atoms._MOTIF_WM_HINTS,
        atoms._MOTIF_WM_HINTS,
        &hints.as_property(),
    )
    .map_err(|_| Error::Linux("failed to set Motif WM hints"))?;
    conn.flush()
        .map_err(|_| Error::Linux("failed to flush X11 requests"))?;

    Ok(())
}

pub fn apply_wayland(_settings: &ChromeSettings) -> Result<()> {
    Ok(())
}

fn get_motif_hints(
    conn: &impl Connection,
    window: Window,
    motif_hints: xproto::Atom,
) -> Result<MotifHints> {
    let reply = conn
        .get_property(false, window, motif_hints, motif_hints, 0, 5)
        .map_err(|_| Error::Linux("failed to request Motif WM hints"))?
        .reply()
        .map_err(|_| Error::Linux("failed to read Motif WM hints"))?;

    if reply.format != 32 || reply.type_ != motif_hints {
        return Ok(MotifHints::new());
    }

    let Some(values) = reply.value32() else {
        return Ok(MotifHints::new());
    };

    let values: Vec<u32> = values.take(5).collect();

    if values.len() < 5 {
        Ok(MotifHints::new())
    } else {
        Ok(MotifHints::from_property(&values))
    }
}