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:
9
command/Cargo.toml
Normal file
9
command/Cargo.toml
Normal 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
184
command/src/lib.rs
Normal 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
72
command/src/main.rs
Normal 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(())
|
||||
}
|
Reference in New Issue
Block a user