use super::atoms::*;
use super::{VoidCookie, X11Error, XConnection};
use std::ffi::CString;
use std::fmt::Write;
use x11rb::protocol::xproto::{self, ConnectionExt as _};
impl XConnection {
pub(crate) fn request_activation_token(&self, window_title: &str) -> Result<String, X11Error> {
let uname = rustix::system::uname();
let pid = rustix::process::getpid();
let time = self.timestamp();
let activation_token = format!(
"{}{}_TIME{}",
uname.nodename().to_str().unwrap_or("winit"),
pid.as_raw_nonzero(),
time
);
let notification = {
let mut buffer = Vec::new();
buffer.extend_from_slice(b"new: ID=");
quote_string(&activation_token, &mut buffer);
buffer.extend_from_slice(b" NAME=");
quote_string(window_title, &mut buffer);
buffer.extend_from_slice(b" SCREEN=");
push_display(&mut buffer, &self.default_screen_index());
CString::new(buffer)
.map_err(|err| X11Error::InvalidActivationToken(err.into_vec()))?
.into_bytes_with_nul()
};
self.send_message(¬ification)?;
Ok(activation_token)
}
pub(crate) fn remove_activation_token(
&self,
window: xproto::Window,
startup_id: &str,
) -> Result<(), X11Error> {
let atoms = self.atoms();
self.xcb_connection()
.change_property(
xproto::PropMode::REPLACE,
window,
atoms[_NET_STARTUP_ID],
xproto::AtomEnum::STRING,
8,
startup_id.len().try_into().unwrap(),
startup_id.as_bytes(),
)?
.check()?;
let message = {
const MESSAGE_ROOT: &str = "remove: ID=";
let mut buffer = Vec::with_capacity(
MESSAGE_ROOT
.len()
.checked_add(startup_id.len())
.and_then(|x| x.checked_add(1))
.unwrap(),
);
buffer.extend_from_slice(MESSAGE_ROOT.as_bytes());
quote_string(startup_id, &mut buffer);
CString::new(buffer)
.map_err(|err| X11Error::InvalidActivationToken(err.into_vec()))?
.into_bytes_with_nul()
};
self.send_message(&message)
}
fn send_message(&self, message: &[u8]) -> Result<(), X11Error> {
let atoms = self.atoms();
let screen = self.default_root();
let window = xproto::WindowWrapper::create_window(
self.xcb_connection(),
screen.root_depth,
screen.root,
-100,
-100,
1,
1,
0,
xproto::WindowClass::INPUT_OUTPUT,
screen.root_visual,
&xproto::CreateWindowAux::new().override_redirect(1).event_mask(
xproto::EventMask::STRUCTURE_NOTIFY | xproto::EventMask::PROPERTY_CHANGE,
),
)?;
let mut message_type = atoms[_NET_STARTUP_INFO_BEGIN];
message
.chunks(20)
.map(|chunk| {
let mut buffer = [0u8; 20];
buffer[..chunk.len()].copy_from_slice(chunk);
let event =
xproto::ClientMessageEvent::new(8, window.window(), message_type, buffer);
message_type = atoms[_NET_STARTUP_INFO];
event
})
.try_for_each(|event| {
self.xcb_connection()
.send_event(false, screen.root, xproto::EventMask::PROPERTY_CHANGE, event)
.map(VoidCookie::ignore_error)
})?;
Ok(())
}
}
fn quote_string(s: &str, target: &mut Vec<u8>) {
let total_len = s.len().checked_add(3).expect("quote string overflow");
target.reserve(total_len);
target.push(b'"');
s.as_bytes().split(|&b| b == b'"').for_each(|part| {
target.extend_from_slice(part);
target.push(b'\\');
target.push(b'"');
});
target.remove(target.len() - 2);
}
fn push_display(buffer: &mut Vec<u8>, display: &impl std::fmt::Display) {
struct Writer<'a> {
buffer: &'a mut Vec<u8>,
}
impl<'a> std::fmt::Write for Writer<'a> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.buffer.extend_from_slice(s.as_bytes());
Ok(())
}
}
write!(Writer { buffer }, "{}", display).unwrap();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn properly_escapes_x11_messages() {
let assert_eq = |input: &str, output: &[u8]| {
let mut buf = vec![];
quote_string(input, &mut buf);
assert_eq!(buf, output);
};
assert_eq("", b"\"\"");
assert_eq("foo", b"\"foo\"");
assert_eq("foo\"bar", b"\"foo\\\"bar\"");
}
}