Initial import. Most things seem working.

This includes an abortive attempt to do a gtk4 dialog (which I don't
think is possible, as gtk4 doesn't allow embedding toplevels anymore),
and an iced dialog, which I just never started writing.
This commit is contained in:
2022-05-03 17:05:06 -07:00
commit 2e86445c3d
29 changed files with 4597 additions and 0 deletions

18
xcb-xembed/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "xcb-xembed"
version = "0.1.0"
authors = [
"Brian Tarricone <brian@tarricone.org>",
]
edition = "2021"
description = "XCB-based implementation of the X11 XEMBED protocol"
license = "LGPL-3.0"
repository = "https://github.com/kelnos/xcb-xembed"
readme = "README.md"
keywords = ["gui", "x11", "xcb", "xembed"]
categories = ["gui"]
[dependencies]
bitflags = "1"
log = "0.4"
xcb = { git = "https://github.com/rust-x-bindings/rust-xcb", rev = "d09b5f91bc07d56673f1bc0d6c7ecd72b5ff7b3e", features = ["randr", "screensaver", "xfixes"] }

341
xcb-xembed/src/embedder.rs Normal file
View File

@ -0,0 +1,341 @@
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(())
}

96
xcb-xembed/src/lib.rs Normal file
View File

@ -0,0 +1,96 @@
use bitflags::bitflags;
use std::{
error::Error as StdError,
fmt,
};
pub mod embedder;
pub(crate) const XEMBED_VERSION: u32 = 0;
pub(crate) const XEMBED_INFO_ATOM_NAME: &str = "_XEMBED_INFO";
pub(crate) const XEMBED_MESSAGE_ATOM_NAME: &str = "_XEMBED";
#[allow(dead_code)]
pub(crate) enum XEmbedMessage {
EmbeddedNotify = 0,
WindowActivate = 1,
WindowDeactivate = 2,
RequestFocus = 3,
FocusIn = 4,
FocusOut = 5,
FocusNext = 6,
FocusPrev = 7,
ModalityOn = 10,
ModalityOff = 11,
RegisterAccelerator = 12,
UnregisterAccelerator = 13,
ActivateAccelerator = 14,
}
pub enum XEmbedFocus {
Current = 0,
First = 1,
Last = 2,
}
bitflags! {
struct XEmbedFlags: u32 {
const MAPPED = (1 << 0);
}
struct XEmbedModifier: u32 {
const SHIFT = (1 << 0);
const CONTROL = (1 << 1);
const ALT = (1 << 2);
const SUPER = (1 << 3);
const HYPER = (1 << 4);
}
struct XEmbedAcceleratorFlags: u32 {
const OVERLOADED = (1 << 0);
}
}
#[derive(Debug)]
pub enum Error {
ProtocolError(String),
Xcb(xcb::Error),
ClientDestroyed,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ProtocolError(reason) => write!(f, "XEMBED: Protocol error: {}", reason),
Self::Xcb(err) => write!(f, "XEMBED: {}", err),
Self::ClientDestroyed => write!(f, "XEMBED: client destroyed"),
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::Xcb(err) => Some(err),
_ => None,
}
}
}
impl From<xcb::Error> for Error {
fn from(error: xcb::Error) -> Self {
Self::Xcb(error)
}
}
impl From<xcb::ProtocolError> for Error {
fn from(error: xcb::ProtocolError) -> Self {
Self::Xcb(xcb::Error::Protocol(error))
}
}
impl From<xcb::ConnError> for Error {
fn from(error: xcb::ConnError) -> Self {
Self::Xcb(xcb::Error::Connection(error))
}
}