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:
18
xcb-xembed/Cargo.toml
Normal file
18
xcb-xembed/Cargo.toml
Normal 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
341
xcb-xembed/src/embedder.rs
Normal 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
96
xcb-xembed/src/lib.rs
Normal 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))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user