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, 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: &xcb::Event) -> Result { 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) } } 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 { 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::().len() < 2 { Err(Error::ProtocolError("Invalid format of _XEMBED_INFO property".to_string())) } else { debug!("_XEMBED_INFO -> ({}, 0x{:x})", reply.value::()[0], reply.value::()[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 { 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, data1: Option, data2: Option) -> 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(()) }