2022-05-04 00:05:06 +00:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|
2022-05-25 02:52:21 +00:00
|
|
|
|
|
|
|
pub fn end(mut self) -> Result<(), Error> {
|
|
|
|
self.end_internal()
|
|
|
|
}
|
2022-05-04 00:05:06 +00:00
|
|
|
|
2022-05-25 02:52:21 +00:00
|
|
|
fn end_internal(&mut self) -> Result<(), Error> {
|
2022-05-04 00:05:06 +00:00
|
|
|
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(())
|
|
|
|
}
|
|
|
|
|
2022-06-08 07:30:11 +00:00
|
|
|
pub fn event(&mut self, event: &x::Event) -> Result<bool, Error> {
|
2022-05-04 00:05:06 +00:00
|
|
|
if self.client == x::WINDOW_NONE {
|
|
|
|
return Err(Error::ClientDestroyed);
|
|
|
|
}
|
|
|
|
|
|
|
|
match event {
|
2022-06-08 07:30:11 +00:00
|
|
|
x::Event::PropertyNotify(ev) if ev.window() == self.client && ev.atom() == intern_atom(self.conn, XEMBED_INFO_ATOM_NAME)? => {
|
2022-05-04 00:05:06 +00:00
|
|
|
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)
|
|
|
|
},
|
2022-06-08 07:30:11 +00:00
|
|
|
x::Event::ConfigureNotify(ev) if ev.window() == self.client => {
|
2022-05-04 00:05:06 +00:00
|
|
|
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)
|
|
|
|
},
|
2022-06-08 07:30:11 +00:00
|
|
|
x::Event::ClientMessage(ev) if ev.window() == self.embedder && ev.r#type() == intern_atom(self.conn, XEMBED_MESSAGE_ATOM_NAME)? => {
|
2022-05-04 00:05:06 +00:00
|
|
|
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),
|
|
|
|
}
|
|
|
|
},
|
2022-06-08 07:30:11 +00:00
|
|
|
x::Event::KeyPress(ev) if ev.event() == self.embedder && self.flags.contains(XEmbedFlags::MAPPED) => {
|
2022-05-04 00:05:06 +00:00
|
|
|
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)
|
|
|
|
},
|
|
|
|
/*
|
2022-06-08 07:30:11 +00:00
|
|
|
x::Event::KeyRelease(ev) if ev.event() == self.embedder && self.flags.contains(XEmbedFlags::MAPPED) => {
|
2022-05-04 00:05:06 +00:00
|
|
|
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)
|
|
|
|
},
|
|
|
|
*/
|
2022-06-08 07:30:11 +00:00
|
|
|
x::Event::MotionNotify(ev) if ev.event() == self.embedder && self.flags.contains(XEmbedFlags::MAPPED) => {
|
2022-05-04 00:05:06 +00:00
|
|
|
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)
|
|
|
|
},
|
2022-06-08 07:30:11 +00:00
|
|
|
x::Event::ButtonPress(ev) if ev.event() == self.embedder && self.flags.contains(XEmbedFlags::MAPPED) => {
|
2022-05-04 00:05:06 +00:00
|
|
|
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)
|
|
|
|
},
|
2022-06-08 07:30:11 +00:00
|
|
|
x::Event::ButtonRelease(ev) if ev.event() == self.embedder && self.flags.contains(XEmbedFlags::MAPPED) => {
|
2022-05-04 00:05:06 +00:00
|
|
|
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)
|
|
|
|
},
|
2022-06-08 07:30:11 +00:00
|
|
|
x::Event::UnmapNotify(ev) if ev.window() == self.client => {
|
2022-05-04 00:05:06 +00:00
|
|
|
debug!("Client was unmapped");
|
|
|
|
self.flags -= XEmbedFlags::MAPPED;
|
|
|
|
Ok(true)
|
|
|
|
},
|
2022-06-08 07:30:11 +00:00
|
|
|
x::Event::DestroyNotify(ev) if ev.window() == self.client => {
|
2022-05-04 00:05:06 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-25 02:52:21 +00:00
|
|
|
impl<'a> Drop for Embedder<'a> {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
let _ = self.end_internal();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-04 00:05:06 +00:00
|
|
|
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(())
|
|
|
|
}
|