Initial import. Most things seem working.

This includes an abortive attempt to do a gtk4 dialog (which I don't
think is possible, as gtk4 doesn't allow embedding toplevels anymore),
and an iced dialog, which I just never started writing.
This commit is contained in:
2022-05-03 17:05:06 -07:00
commit 2e86445c3d
29 changed files with 4597 additions and 0 deletions

9
command/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "bscreensaver-command"
version = "0.1.0"
edition = "2021"
[dependencies]
bscreensaver-util = { path = "../util" }
clap = "3"
xcb = { git = "https://github.com/rust-x-bindings/rust-xcb", rev = "d09b5f91bc07d56673f1bc0d6c7ecd72b5ff7b3e" }

184
command/src/lib.rs Normal file
View File

@ -0,0 +1,184 @@
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(()),
}
}

72
command/src/main.rs Normal file
View File

@ -0,0 +1,72 @@
use clap::{Arg, Command};
use std::{env, io, process::exit};
use bscreensaver_command::{BCommand, Error, bscreensaver_command};
fn main() -> io::Result<()> {
let mut command = Command::new("bscreensaver-command")
.author(env!("CARGO_PKG_AUTHORS"))
.version(env!("CARGO_PKG_VERSION"))
.about("Send commands to the running bscreensaver instance")
.arg(
Arg::new("blank")
.long("blank")
.short('b')
.help("Blanks the screen right now")
)
.arg(
Arg::new("lock")
.long("lock")
.short('l')
.help("Lock the screen right now")
)
.arg(
Arg::new("deactivate")
.long("deactivate")
.short('d')
.help("Deactivates the screen lock, presenting the unlock dialog if needed. This can be used to 'reset' things so the screensaver thinks there has been user input")
)
.arg(
Arg::new("restart")
.long("restart")
.short('r')
.help("Restarts the bscreensaver daemon")
)
.arg(
Arg::new("exit")
.long("exit")
.short('x')
.help("Causes the bscreensaver daemon to exit now, even if the screen is locked")
);
let args = command.get_matches_mut();
let command =
if args.is_present("blank") {
BCommand::Blank
} else if args.is_present("lock") {
BCommand::Lock
} else if args.is_present("deactivate") {
BCommand::Deactivate
} else if args.is_present("restart") {
BCommand::Restart
} else if args.is_present("exit") {
BCommand::Exit
} else {
command.print_help()?;
exit(1);
};
match bscreensaver_command(command) {
Err(Error::NotRunning) => {
eprintln!("bscreensaver is not running");
exit(1);
},
Err(Error::X(err)) => {
eprintln!("Failed to communicate with X server: {}", err);
exit(1);
},
Ok(_) => (),
}
Ok(())
}