bscreensaver/command/src/lib.rs

185 lines
5.6 KiB
Rust
Raw Normal View History

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<xcb::Error> for Error {
fn from(error: xcb::Error) -> Self {
Self::X(error)
}
}
impl From<xcb::ProtocolError> for Error {
fn from(error: xcb::ProtocolError) -> Self {
Self::X(xcb::Error::Protocol(error))
}
}
impl From<xcb::ConnError> 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<x::Window, Error> {
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::<u32>() as u32,
});
let reply = conn.wait_for_reply(cookie)?;
let windows = reply.value::<x::Window>();
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::<u8>() == 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(()),
}
}