bscreensaver/xcb-xembed/src/embedder.rs
Brian J. Tarricone d3de9f7fad Be more aggressive about handling user activity
Previously we only trigger unblanking or showing the unlock dialog if
core key or motion events come through.  Now we also do this when xinput
events come in.  We also now reset the last-user-activity time for core
events.

This also changes xcb-xembed to explicitly only handle core events.
2022-06-08 15:22:09 -07:00

352 lines
15 KiB
Rust

use log::{debug, info, trace};
use xcb::{x, xfixes, Xid};
use crate::*;
pub struct Embedder<'a> {
conn: &'a xcb::Connection,
embedder: x::Window,
client: x::Window,
flags: XEmbedFlags,
}
struct XEmbedInfo {
version: u32,
flags: XEmbedFlags,
}
impl<'a> Embedder<'a> {
pub fn start(conn: &'a xcb::Connection, embedder: x::Window, client: x::Window) -> Result<Embedder<'a>, Error> {
debug!("Reparenting {} to be a child of {}", client.resource_id(), embedder.resource_id());
conn.send_and_check_request(&x::ReparentWindow {
window: client,
parent: embedder,
x: 0,
y: 0,
})?;
let event_mask = x::EventMask::PROPERTY_CHANGE | x::EventMask::STRUCTURE_NOTIFY;
let cookie = conn.send_request(&x::GetWindowAttributes {
window: client,
});
let reply = conn.wait_for_reply(cookie)?;
if reply.your_event_mask().intersection(event_mask) != event_mask {
conn.send_and_check_request(&x::ChangeWindowAttributes {
window: client,
value_list: &[
x::Cw::EventMask(reply.your_event_mask() | event_mask),
],
})?;
}
let info = fetch_xembed_info(conn, client)?;
let supported_version = std::cmp::min(info.version, XEMBED_VERSION);
if let Err(err) = conn.send_and_check_request(&xfixes::ChangeSaveSet {
mode: xfixes::SaveSetMode::Insert,
target: xfixes::SaveSetTarget::Root,
map: xfixes::SaveSetMapping::Unmap,
window: client,
}) {
info!("Failed to send XFIXES ChangeSaveSet request: {}", err);
}
debug!("sending EMBEDDED_NOTIFY to client with embedder wid {} and version {}", embedder.resource_id(), supported_version);
send_xembed_message(conn, client, x::CURRENT_TIME, XEmbedMessage::EmbeddedNotify, None, Some(embedder.resource_id()), Some(supported_version))?;
let cookie = conn.send_request(&x::GetInputFocus {});
let reply = conn.wait_for_reply(cookie)?;
let focus_window = reply.focus();
let mut cur_window = embedder;
let activated_message = if focus_window == embedder {
XEmbedMessage::WindowActivate
} else {
loop {
let cookie = conn.send_request(&x::QueryTree {
window: cur_window,
});
let reply = conn.wait_for_reply(cookie)?;
if reply.root() == reply.parent() {
break XEmbedMessage::WindowDeactivate;
} else if reply.parent() == focus_window {
break XEmbedMessage::WindowActivate;
} else {
cur_window = reply.parent();
}
}
};
send_xembed_message(conn, client, x::CURRENT_TIME, activated_message, None, None, None)?;
let (focus_message, focus_detail) = if focus_window == embedder {
(XEmbedMessage::FocusIn, Some(XEmbedFocus::Current))
} else {
(XEmbedMessage::FocusOut, None)
};
send_xembed_message(conn, client, x::CURRENT_TIME, focus_message, focus_detail.map(|fd| fd as u32), None, None)?;
// XXX: how do we know if there's something modal active?
send_xembed_message(conn, client, x::CURRENT_TIME, XEmbedMessage::ModalityOff, None, None, None)?;
Ok(Embedder {
conn,
embedder,
client,
flags: info.flags,
})
}
pub fn end(mut self) -> Result<(), Error> {
self.end_internal()
}
fn end_internal(&mut self) -> Result<(), Error> {
if self.client == x::WINDOW_NONE {
return Err(Error::ClientDestroyed);
}
debug!("Ending XEMBED");
let cookie = self.conn.send_request(&x::QueryTree {
window: self.client,
});
let reply = self.conn.wait_for_reply(cookie)?;
self.conn.send_and_check_request(&x::UnmapWindow {
window: self.client,
})?;
self.conn.send_and_check_request(&x::ReparentWindow {
window: self.client,
parent: reply.root(),
x: 0,
y: 0,
})?;
Ok(())
}
pub fn event(&mut self, event: &x::Event) -> Result<bool, Error> {
if self.client == x::WINDOW_NONE {
return Err(Error::ClientDestroyed);
}
match event {
x::Event::PropertyNotify(ev) if ev.window() == self.client && ev.atom() == intern_atom(self.conn, XEMBED_INFO_ATOM_NAME)? => {
let info = fetch_xembed_info(self.conn, self.client)?;
if (self.flags & XEmbedFlags::MAPPED) != (info.flags & XEmbedFlags::MAPPED) {
if info.flags.contains(XEmbedFlags::MAPPED) {
debug!("Mapping client window");
self.conn.send_and_check_request(&x::MapWindow {
window: self.client,
})?;
} else {
debug!("Unmapping client window");
self.conn.send_and_check_request(&x::UnmapWindow {
window: self.client,
})?;
}
self.flags = info.flags;
}
Ok(true)
},
x::Event::ConfigureNotify(ev) if ev.window() == self.client => {
let mut cookies = Vec::new();
if ev.x() != 0 || ev.y() != 0 {
cookies.push(self.conn.send_request_checked(&x::ConfigureWindow {
window: self.client,
value_list: &[
x::ConfigWindow::X(0),
x::ConfigWindow::Y(0),
],
}));
}
cookies.push(self.conn.send_request_checked(&x::ConfigureWindow {
window: self.embedder,
value_list: &[
x::ConfigWindow::Width(ev.width() as u32),
x::ConfigWindow::Height(ev.height() as u32),
],
}));
for cookie in cookies {
self.conn.check_request(cookie)?;
}
Ok(true)
},
x::Event::ClientMessage(ev) if ev.window() == self.embedder && ev.r#type() == intern_atom(self.conn, XEMBED_MESSAGE_ATOM_NAME)? => {
match ev.data() {
x::ClientMessageData::Data32(data) if data[1] == XEmbedMessage::RequestFocus as u32 => {
debug!("Client requests focus");
self.conn.send_and_check_request(&x::SetInputFocus {
revert_to: x::InputFocus::Parent,
focus: self.client,
time: x::CURRENT_TIME,
})?;
send_xembed_message(self.conn, self.client, x::CURRENT_TIME, XEmbedMessage::FocusIn, Some(XEmbedFocus::Current as u32), None, None)?;
Ok(true)
},
// TODO: XEMBED_FOCUS_NEXT
// TODO: XEMBED_FOCUS_PREV
// TODO: XEMBED_REGISTER_ACCELERATOR
// TODO: XEMBED_UNREGISTER_ACCELERATOR
_ => Ok(false),
}
},
x::Event::KeyPress(ev) if ev.event() == self.embedder && self.flags.contains(XEmbedFlags::MAPPED) => {
trace!("Forwarding key press to client ({:?} + {})", ev.state(), ev.detail());
self.conn.send_and_check_request(&x::SendEvent {
propagate: false,
destination: x::SendEventDest::Window(self.client),
event_mask: x::EventMask::NO_EVENT,
event: &x::KeyPressEvent::new(ev.detail(), ev.time(), ev.root(), self.client, ev.child(), ev.root_x(), ev.root_y(), ev.event_x(), ev.event_y(), ev.state(), ev.same_screen()),
})?;
Ok(true)
},
/*
x::Event::KeyRelease(ev) if ev.event() == self.embedder && self.flags.contains(XEmbedFlags::MAPPED) => {
trace!("Forwarding key release to client ({:?} + {})", ev.state(), ev.detail());
self.conn.send_and_check_request(&x::SendEvent {
propagate: false,
destination: x::SendEventDest::Window(self.client),
event_mask: x::EventMask::NO_EVENT,
event: &x::KeyReleaseEvent::new(ev.detail(), ev.time(), ev.root(), self.client, ev.child(), ev.root_x(), ev.root_y(), ev.event_x(), ev.event_y(), ev.state(), ev.same_screen()),
})?;
Ok(true)
},
*/
x::Event::MotionNotify(ev) if ev.event() == self.embedder && self.flags.contains(XEmbedFlags::MAPPED) => {
trace!("Forwarding pointer motion to client ({}, {})", ev.event_x(), ev.event_y());
self.conn.send_and_check_request(&x::SendEvent {
propagate: false,
destination: x::SendEventDest::Window(self.client),
event_mask: x::EventMask::NO_EVENT,
event: &x::MotionNotifyEvent::new(ev.detail(), ev.time(), ev.root(), self.client, ev.child(), ev.root_x(), ev.root_y(), ev.event_x(), ev.event_y(), ev.state(), ev.same_screen()),
})?;
Ok(true)
},
x::Event::ButtonPress(ev) if ev.event() == self.embedder && self.flags.contains(XEmbedFlags::MAPPED) => {
trace!("Forwarding button press to client ({:?} + {}: {}, {})", ev.state(), ev.detail(), ev.event_x(), ev.event_y());
self.conn.send_and_check_request(&x::SendEvent {
propagate: false,
destination: x::SendEventDest::Window(self.client),
event_mask: x::EventMask::NO_EVENT,
event: &x::ButtonPressEvent::new(ev.detail(), ev.time(), ev.root(), self.client, ev.child(), ev.root_x(), ev.root_y(), ev.event_x(), ev.event_y(), ev.state(), ev.same_screen()),
})?;
Ok(true)
},
x::Event::ButtonRelease(ev) if ev.event() == self.embedder && self.flags.contains(XEmbedFlags::MAPPED) => {
trace!("Forwarding button release to client ({:?} + {}: {}, {})", ev.state(), ev.detail(), ev.event_x(), ev.event_y());
self.conn.send_and_check_request(&x::SendEvent {
propagate: false,
destination: x::SendEventDest::Window(self.client),
event_mask: x::EventMask::NO_EVENT,
event: &x::ButtonReleaseEvent::new(ev.detail(), ev.time(), ev.root(), self.client, ev.child(), ev.root_x(), ev.root_y(), ev.event_x(), ev.event_y(), ev.state(), ev.same_screen()),
})?;
Ok(true)
},
x::Event::UnmapNotify(ev) if ev.window() == self.client => {
debug!("Client was unmapped");
self.flags -= XEmbedFlags::MAPPED;
Ok(true)
},
x::Event::DestroyNotify(ev) if ev.window() == self.client => {
debug!("Client was destroyed");
self.flags -= XEmbedFlags::MAPPED;
self.client = x::WINDOW_NONE;
Ok(true)
},
event => {
trace!("Not handling event {:#?}", event);
Ok(false)
},
}
}
pub fn activate_client(&self) -> Result<(), Error> {
if self.client != x::WINDOW_NONE {
send_xembed_message(&self.conn, self.client, x::CURRENT_TIME, XEmbedMessage::WindowActivate, None, None, None)?;
Ok(())
} else {
Err(Error::ClientDestroyed)
}
}
pub fn focus_client(&self, mode: super::XEmbedFocus) -> Result<(), Error> {
if self.client != x::WINDOW_NONE {
self.conn.send_and_check_request(&x::SetInputFocus {
revert_to: x::InputFocus::Parent,
focus: self.client,
time: x::CURRENT_TIME,
})?;
send_xembed_message(&self.conn, self.client, x::CURRENT_TIME, XEmbedMessage::FocusIn, Some(mode as u32), None, None)?;
Ok(())
} else {
Err(Error::ClientDestroyed)
}
}
pub fn embedder_window(&self) -> x::Window {
self.embedder
}
pub fn client_window(&self) -> x::Window {
self.client
}
pub fn is_client_mapped(&self) -> bool {
self.flags.contains(XEmbedFlags::MAPPED)
}
}
impl<'a> Drop for Embedder<'a> {
fn drop(&mut self) {
let _ = self.end_internal();
}
}
fn fetch_xembed_info(conn: &xcb::Connection, client: x::Window) -> Result<XEmbedInfo, Error> {
let xembed_info_atom = intern_atom(conn, XEMBED_INFO_ATOM_NAME)?;
let cookie = conn.send_request(&x::GetProperty {
delete: false,
window: client,
property: xembed_info_atom,
r#type: xembed_info_atom,
long_offset: 0,
long_length: 8,
});
let reply = conn.wait_for_reply(cookie)?;
if reply.value::<u32>().len() < 2 {
Err(Error::ProtocolError("Invalid format of _XEMBED_INFO property".to_string()))
} else {
debug!("_XEMBED_INFO -> ({}, 0x{:x})", reply.value::<u32>()[0], reply.value::<u32>()[1]);
Ok(XEmbedInfo {
version: reply.value()[0],
flags: XEmbedFlags::from_bits_truncate(reply.value()[1]),
})
}
}
fn intern_atom(conn: &xcb::Connection, name: &str) -> xcb::Result<x::Atom> {
let cookie = conn.send_request(&x::InternAtom {
only_if_exists: false,
name: name.as_bytes(),
});
conn.wait_for_reply(cookie).map(|reply| reply.atom())
}
fn send_xembed_message(conn: &xcb::Connection, window: x::Window, time: u32, message: XEmbedMessage, detail: Option<u32>, data1: Option<u32>, data2: Option<u32>) -> xcb::Result<()> {
conn.send_and_check_request(&x::SendEvent {
propagate: false,
destination: x::SendEventDest::Window(window),
event_mask: x::EventMask::NO_EVENT,
event: &x::ClientMessageEvent::new(
window,
intern_atom(conn, XEMBED_MESSAGE_ATOM_NAME)?,
x::ClientMessageData::Data32([
time,
message as u32,
detail.unwrap_or(0),
data1.unwrap_or(0),
data2.unwrap_or(0),
]),
),
})?;
Ok(())
}