use std::{error::Error as StdError, fmt}; use xcb::{x, Xid}; use bscreensaver_util::{create_atom, BSCREENSAVER_WM_CLASS}; const COMMAND_WINDOW_ID_ATOM_NAME: &[u8] = b"BSCREENSAVER_COMMAND_WINDOW_ID"; const COMMAND_WINDOW_WM_NAME: &[u8] = b"bscreensaver command window"; const BSCREENSAVER_BLANK_ATOM_NAME: &[u8] = b"BSCREENSAVER_BLANK"; const BSCREENSAVER_LOCK_ATOM_NAME: &[u8] = b"BSCREENSAVER_LOCK"; const BSCREENSAVER_DEACTIVATE_ATOM_NAME: &[u8] = b"BSCREENSAVER_DEACTIVATE"; const BSCREENSAVER_RESTART_ATOM_NAME: &[u8] = b"BSCREENSAVER_RESTART"; const BSCREENSAVER_EXIT_ATOM_NAME: &[u8] = b"BSCREENSAVER_EXIT"; #[derive(Debug, Clone, Copy)] pub enum BCommand { Blank, Lock, Deactivate, Restart, Exit, } impl BCommand { pub fn atom_name(&self) -> &'static [u8] { match self { Self::Blank => BSCREENSAVER_BLANK_ATOM_NAME, Self::Lock => BSCREENSAVER_LOCK_ATOM_NAME, Self::Deactivate => BSCREENSAVER_DEACTIVATE_ATOM_NAME, Self::Restart => BSCREENSAVER_RESTART_ATOM_NAME, Self::Exit => BSCREENSAVER_EXIT_ATOM_NAME, } } } #[derive(Debug)] pub enum Error { X(xcb::Error), NotRunning, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::X(err) => write!(f, "{}", err), Self::NotRunning => write!(f, "bscreensaver is not running"), } } } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { Self::X(err) => Some(err), Self::NotRunning => None, } } } impl From for Error { fn from(error: xcb::Error) -> Self { Self::X(error) } } impl From for Error { fn from(error: xcb::ProtocolError) -> Self { Self::X(xcb::Error::Protocol(error)) } } impl From for Error { fn from(error: xcb::ConnError) -> Self { Self::X(xcb::Error::Connection(error)) } } pub fn create_command_window(conn: &xcb::Connection, screen: &x::Screen) -> Result { let mut cookies = Vec::new(); let msg_win: x::Window = conn.generate_id(); cookies.push(conn.send_request_checked(&x::CreateWindow { depth: x::COPY_FROM_PARENT as u8, wid: msg_win, parent: screen.root(), x: -50, y: -50, width: 1, height: 1, border_width: 0, class: x::WindowClass::InputOnly, visual: x::COPY_FROM_PARENT, value_list: &[ x::Cw::OverrideRedirect(true), x::Cw::EventMask(x::EventMask::STRUCTURE_NOTIFY | x::EventMask::PROPERTY_CHANGE), ], })); cookies.push(conn.send_request_checked(&x::ChangeProperty { mode: x::PropMode::Replace, window: msg_win, property: x::ATOM_WM_NAME, r#type: x::ATOM_STRING, data: COMMAND_WINDOW_WM_NAME, })); cookies.push(conn.send_request_checked(&x::ChangeProperty { mode: x::PropMode::Replace, window: msg_win, property: x::ATOM_WM_CLASS, r#type: x::ATOM_STRING, data: BSCREENSAVER_WM_CLASS, })); let msg_win_atom = create_atom(&conn, COMMAND_WINDOW_ID_ATOM_NAME)?; cookies.push(conn.send_request_checked(&x::ChangeProperty { mode: x::PropMode::Replace, window: screen.root(), property: msg_win_atom, r#type: x::ATOM_WINDOW, data: &[msg_win.resource_id()], })); for cookie in cookies { conn.check_request(cookie)?; } Ok(msg_win) } pub fn bscreensaver_command(command: BCommand) -> Result<(), Error> { let (conn, _) = xcb::Connection::connect(None)?; let setup = conn.get_setup(); let msg_window_id_atom = create_atom(&conn, COMMAND_WINDOW_ID_ATOM_NAME)?; let msg_window_id = 'outer: loop { for screen in setup.roots() { let cookie = conn.send_request(&x::GetProperty { delete: false, window: screen.root(), property: msg_window_id_atom, r#type: x::ATOM_WINDOW, long_offset: 0, long_length: std::mem::size_of::() as u32, }); let reply = conn.wait_for_reply(cookie)?; let windows = reply.value::(); if windows.is_empty() { continue; } let window = windows[0]; let cookie = conn.send_request(&x::GetProperty { delete: false, window, property: x::ATOM_WM_NAME, r#type: x::ATOM_STRING, long_offset: 0, long_length: COMMAND_WINDOW_WM_NAME.len() as u32 + 1, }); let reply = conn.wait_for_reply(cookie)?; if reply.value::() == COMMAND_WINDOW_WM_NAME { break 'outer Ok(window); } } break Err(Error::NotRunning); }?; let command_atom = create_atom(&conn, command.atom_name())?; let res = conn.send_and_check_request(&x::SendEvent { propagate: false, destination: x::SendEventDest::Window(msg_window_id), event_mask: x::EventMask::STRUCTURE_NOTIFY, event: &x::ClientMessageEvent::new( msg_window_id, command_atom, x::ClientMessageData::Data32([0; 5]) ), }); match res { Err(xcb::ProtocolError::X(x::Error::Window(x::WindowError { .. }), _)) => Err(Error::NotRunning), Err(err) => Err(Error::X(xcb::Error::Protocol(err))), Ok(_) => Ok(()), } }