185 lines
5.6 KiB
Rust
185 lines
5.6 KiB
Rust
|
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(()),
|
||
|
}
|
||
|
}
|