winit/platform_impl/linux/x11/
activation.rs
1use super::atoms::*;
9use super::{VoidCookie, X11Error, XConnection};
10
11use std::ffi::CString;
12use std::fmt::Write;
13
14use x11rb::protocol::xproto::{self, ConnectionExt as _};
15
16impl XConnection {
17 pub(crate) fn request_activation_token(&self, window_title: &str) -> Result<String, X11Error> {
19 let uname = rustix::system::uname();
21 let pid = rustix::process::getpid();
22 let time = self.timestamp();
23
24 let activation_token = format!(
25 "{}{}_TIME{}",
26 uname.nodename().to_str().unwrap_or("winit"),
27 pid.as_raw_nonzero(),
28 time
29 );
30
31 let notification = {
33 let mut buffer = Vec::new();
34 buffer.extend_from_slice(b"new: ID=");
35 quote_string(&activation_token, &mut buffer);
36 buffer.extend_from_slice(b" NAME=");
37 quote_string(window_title, &mut buffer);
38 buffer.extend_from_slice(b" SCREEN=");
39 push_display(&mut buffer, &self.default_screen_index());
40
41 CString::new(buffer)
42 .map_err(|err| X11Error::InvalidActivationToken(err.into_vec()))?
43 .into_bytes_with_nul()
44 };
45 self.send_message(¬ification)?;
46
47 Ok(activation_token)
48 }
49
50 pub(crate) fn remove_activation_token(
52 &self,
53 window: xproto::Window,
54 startup_id: &str,
55 ) -> Result<(), X11Error> {
56 let atoms = self.atoms();
57
58 self.xcb_connection()
60 .change_property(
61 xproto::PropMode::REPLACE,
62 window,
63 atoms[_NET_STARTUP_ID],
64 xproto::AtomEnum::STRING,
65 8,
66 startup_id.len().try_into().unwrap(),
67 startup_id.as_bytes(),
68 )?
69 .check()?;
70
71 let message = {
73 const MESSAGE_ROOT: &str = "remove: ID=";
74
75 let mut buffer = Vec::with_capacity(
76 MESSAGE_ROOT
77 .len()
78 .checked_add(startup_id.len())
79 .and_then(|x| x.checked_add(1))
80 .unwrap(),
81 );
82 buffer.extend_from_slice(MESSAGE_ROOT.as_bytes());
83 quote_string(startup_id, &mut buffer);
84 CString::new(buffer)
85 .map_err(|err| X11Error::InvalidActivationToken(err.into_vec()))?
86 .into_bytes_with_nul()
87 };
88
89 self.send_message(&message)
90 }
91
92 fn send_message(&self, message: &[u8]) -> Result<(), X11Error> {
94 let atoms = self.atoms();
95
96 let screen = self.default_root();
98 let window = xproto::WindowWrapper::create_window(
99 self.xcb_connection(),
100 screen.root_depth,
101 screen.root,
102 -100,
103 -100,
104 1,
105 1,
106 0,
107 xproto::WindowClass::INPUT_OUTPUT,
108 screen.root_visual,
109 &xproto::CreateWindowAux::new().override_redirect(1).event_mask(
110 xproto::EventMask::STRUCTURE_NOTIFY | xproto::EventMask::PROPERTY_CHANGE,
111 ),
112 )?;
113
114 let mut message_type = atoms[_NET_STARTUP_INFO_BEGIN];
116 message
117 .chunks(20)
118 .map(|chunk| {
119 let mut buffer = [0u8; 20];
120 buffer[..chunk.len()].copy_from_slice(chunk);
121 let event =
122 xproto::ClientMessageEvent::new(8, window.window(), message_type, buffer);
123
124 message_type = atoms[_NET_STARTUP_INFO];
126
127 event
128 })
129 .try_for_each(|event| {
130 self.xcb_connection()
132 .send_event(false, screen.root, xproto::EventMask::PROPERTY_CHANGE, event)
133 .map(VoidCookie::ignore_error)
134 })?;
135
136 Ok(())
137 }
138}
139
140fn quote_string(s: &str, target: &mut Vec<u8>) {
142 let total_len = s.len().checked_add(3).expect("quote string overflow");
143 target.reserve(total_len);
144
145 target.push(b'"');
147
148 s.as_bytes().split(|&b| b == b'"').for_each(|part| {
150 target.extend_from_slice(part);
152
153 target.push(b'\\');
155 target.push(b'"');
156 });
157
158 target.remove(target.len() - 2);
160}
161
162fn push_display(buffer: &mut Vec<u8>, display: &impl std::fmt::Display) {
164 struct Writer<'a> {
165 buffer: &'a mut Vec<u8>,
166 }
167
168 impl std::fmt::Write for Writer<'_> {
169 fn write_str(&mut self, s: &str) -> std::fmt::Result {
170 self.buffer.extend_from_slice(s.as_bytes());
171 Ok(())
172 }
173 }
174
175 write!(Writer { buffer }, "{}", display).unwrap();
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn properly_escapes_x11_messages() {
184 let assert_eq = |input: &str, output: &[u8]| {
185 let mut buf = vec![];
186 quote_string(input, &mut buf);
187 assert_eq!(buf, output);
188 };
189
190 assert_eq("", b"\"\"");
191 assert_eq("foo", b"\"foo\"");
192 assert_eq("foo\"bar", b"\"foo\\\"bar\"");
193 }
194}