Have remote commands return a success/failure message

This commit is contained in:
2022-05-14 14:55:22 -07:00
parent aafe026092
commit 8394d45d1a
8 changed files with 181 additions and 34 deletions

View File

@ -6,4 +6,5 @@ edition = "2021"
[dependencies]
bscreensaver-util = { path = "../util" }
clap = "3"
nix = "0.23"
xcb = { git = "https://github.com/rust-x-bindings/rust-xcb", rev = "d09b5f91bc07d56673f1bc0d6c7ecd72b5ff7b3e" }

View File

@ -1,4 +1,5 @@
use std::{error::Error as StdError, fmt};
use nix::poll::{poll, PollFd, PollFlags};
use std::{cmp, error::Error as StdError, fmt, time::{Duration, Instant}, os::unix::prelude::AsRawFd};
use xcb::{x, Xid};
use bscreensaver_util::{create_atom, BSCREENSAVER_WM_CLASS};
@ -12,6 +13,8 @@ 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";
const BSCREENSAVER_RESULT_ATOM_NANE: &[u8] = b"BSCREENSAVER_RESULT";
#[derive(Debug, Clone, Copy)]
pub enum BCommand {
Blank,
@ -36,14 +39,20 @@ impl BCommand {
#[derive(Debug)]
pub enum Error {
X(xcb::Error),
Sys(nix::errno::Errno),
NotRunning,
CommandError,
Timeout,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::X(err) => write!(f, "{}", err),
Self::X(err) => write!(f, "X error: {}", err),
Self::Sys(err) => write!(f, "System error: {}", err),
Self::NotRunning => write!(f, "bscreensaver is not running"),
Self::CommandError => write!(f, "bscreensaver returned an error"),
Self::Timeout => write!(f, "Timed out waiting for a response"),
}
}
}
@ -52,7 +61,10 @@ impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::X(err) => Some(err),
Self::Sys(err) => Some(err),
Self::NotRunning => None,
Self::CommandError => None,
Self::Timeout => None,
}
}
}
@ -75,6 +87,12 @@ impl From<xcb::ConnError> for Error {
}
}
impl From<nix::errno::Errno> for Error {
fn from(error: nix::errno::Errno) -> Self {
Self::Sys(error)
}
}
pub fn create_command_window(conn: &xcb::Connection, screen: &x::Screen) -> Result<x::Window, Error> {
let mut cookies = Vec::new();
@ -126,12 +144,33 @@ pub fn create_command_window(conn: &xcb::Connection, screen: &x::Screen) -> Resu
Ok(msg_win)
}
pub fn bscreensaver_command(command: BCommand) -> Result<(), Error> {
pub fn bscreensaver_command_response(conn: &xcb::Connection, reply_window: x::Window, is_success: bool) -> Result<(), Error> {
let message_type = create_atom(conn, BSCREENSAVER_RESULT_ATOM_NANE)?;
conn.send_and_check_request(&x::SendEvent {
propagate: false,
destination: x::SendEventDest::Window(reply_window),
event_mask: x::EventMask::STRUCTURE_NOTIFY,
event: &x::ClientMessageEvent::new(
reply_window,
message_type,
x::ClientMessageData::Data32([
if is_success { 1 } else { 0 },
0,
0,
0,
0,
])
),
})?;
Ok(())
}
pub fn bscreensaver_command(command: BCommand, timeout: Option<Duration>) -> 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 {
let (msg_window_screen, msg_window_id) = 'outer: loop {
for screen in setup.roots() {
let cookie = conn.send_request(&x::GetProperty {
delete: false,
@ -158,12 +197,31 @@ pub fn bscreensaver_command(command: BCommand) -> Result<(), Error> {
});
let reply = conn.wait_for_reply(cookie)?;
if reply.value::<u8>() == COMMAND_WINDOW_WM_NAME {
break 'outer Ok(window);
break 'outer Ok((screen, window));
}
}
break Err(Error::NotRunning);
}?;
let command_atom = create_atom(&conn, command.atom_name())?;
let result_atom = create_atom(&conn, BSCREENSAVER_RESULT_ATOM_NANE)?;
let reply_window = conn.generate_id();
conn.send_and_check_request(&x::CreateWindow {
depth: x::COPY_FROM_PARENT as u8,
wid: reply_window,
parent: msg_window_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),
],
})?;
let res = conn.send_and_check_request(&x::SendEvent {
propagate: false,
@ -172,13 +230,54 @@ pub fn bscreensaver_command(command: BCommand) -> Result<(), Error> {
event: &x::ClientMessageEvent::new(
msg_window_id,
command_atom,
x::ClientMessageData::Data32([0; 5])
x::ClientMessageData::Data32([
reply_window.resource_id(),
0,
0,
0,
0,
])
),
});
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(()),
Err(xcb::ProtocolError::X(x::Error::Window(x::WindowError { .. }), _)) => Err(Error::NotRunning)?,
Err(err) => Err(Error::X(xcb::Error::Protocol(err)))?,
Ok(_) => (),
}
let start = Instant::now();
'outer1: loop {
while let Some(event) = conn.poll_for_event()? {
match event {
xcb::Event::X(x::Event::ClientMessage(ev)) if ev.r#type() == result_atom => {
match ev.data() {
x::ClientMessageData::Data32(data) => {
if data[0] != 1 {
break 'outer1 Err(Error::CommandError);
}
},
_ => break 'outer1 Err(Error::CommandError),
}
break 'outer1 Ok(());
},
_ => (),
}
}
let poll_timeout = timeout.map(|to| {
let since_start = start.elapsed();
if since_start > to {
0i32
} else {
cmp::max(i32::MAX as u128, (to - since_start).as_millis()) as i32
}
}).unwrap_or(-1);
if poll_timeout == 0 {
break 'outer1 Err(Error::Timeout);
}
let mut pfds = vec![PollFd::new(conn.as_raw_fd(), PollFlags::POLLIN)];
poll(pfds.as_mut_slice(), poll_timeout)?;
}
}

View File

@ -1,5 +1,5 @@
use clap::{Arg, Command};
use std::{env, io, process::exit};
use std::{env, io, process::exit, time::Duration};
use bscreensaver_command::{BCommand, Error, bscreensaver_command};
@ -56,7 +56,7 @@ fn main() -> io::Result<()> {
exit(1);
};
match bscreensaver_command(command) {
match bscreensaver_command(command, Some(Duration::from_secs(4))) {
Err(Error::NotRunning) => {
eprintln!("bscreensaver is not running");
exit(1);
@ -65,6 +65,10 @@ fn main() -> io::Result<()> {
eprintln!("Failed to communicate with X server: {}", err);
exit(1);
},
Err(err) => {
eprintln!("Failed to communicate with X server: {}", err);
exit(1);
},
Ok(_) => (),
}