bscreensaver/xcb-xembed/src/embedder.rs

342 lines
14 KiB
Rust
Raw Normal View History

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(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: &xcb::Event) -> Result<bool, Error> {
if self.client == x::WINDOW_NONE {
return Err(Error::ClientDestroyed);
}
match event {
xcb::Event::X(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)
},
xcb::Event::X(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)
},
xcb::Event::X(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),
}
},
xcb::Event::X(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)
},
/*
xcb::Event::X(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)
},
*/
xcb::Event::X(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)
},
xcb::Event::X(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)
},
xcb::Event::X(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)
},
xcb::Event::X(x::Event::UnmapNotify(ev)) if ev.window() == self.client => {
debug!("Client was unmapped");
self.flags -= XEmbedFlags::MAPPED;
Ok(true)
},
xcb::Event::X(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)
}
}
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(())
}