Skip to content

Instantly share code, notes, and snippets.

@RealKC

RealKC/xembed.rs Secret

Last active September 9, 2022 16:22
Show Gist options
  • Save RealKC/2d8dfc59fb4c016aaec7958775b728d2 to your computer and use it in GitHub Desktop.
Save RealKC/2d8dfc59fb4c016aaec7958775b728d2 to your computer and use it in GitHub Desktop.
Attempt #2
use std::thread::Builder as ThreadBuilder;
use gdk4_x11::{X11Display, X11Screen, X11Surface};
use gtk4::prelude::*;
use xcb::{
x::{self, ATOM_CARDINAL, ATOM_NONE},
Xid, XidNew,
};
use super::Initialised;
use crate::kcshot::KCShot;
#[derive(thiserror::Error, Debug)]
enum Error {
#[error("Failed to estabilish a connection to the X server: {0:?}")]
XcbConnection(#[from] xcb::ConnError),
#[error("Encountered an X protocol error: {0:?}")]
XcbProtocol(xcb::ProtocolError),
#[error("XEmbed-based system tray not supported")]
XEmbedSystrayNotSupported,
#[error("Failed to get root window")]
FailedToGetRootWindow,
// #[error("Failed to spawn thread that forwards x events")]
// FailedToSpawnThread,
}
impl From<xcb::Error> for Error {
fn from(xerror: xcb::Error) -> Self {
match xerror {
xcb::Error::Connection(err) => Self::XcbConnection(err),
xcb::Error::Protocol(err) => Self::XcbProtocol(err),
}
}
}
/// Attempts to intiliase the systray icon using the [System Tray Protocol Specification][`systray_spec`]
/// freedesktop spec, which is based on [XEmbed][`xembed`].
///
/// [`systray_spec`]: https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-latest.html
/// [`xembed`]: https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html
pub(super) fn try_init(_app: &KCShot) -> Initialised {
match try_init_impl(_app) {
Ok(_) => Initialised::Yes,
Err(why) => {
tracing::warn!("Failed to initialised xembed-based systray icon due to {why}");
Initialised::No
}
}
}
fn try_init_impl(app: &KCShot) -> Result<(), Error> {
let gtk_managed_window = gtk4::Window::new();
gtk_managed_window.set_default_size(22, 22);
gtk_managed_window.set_decorated(false);
// The Gtk window must be shown for it to gain a surface
gtk_managed_window.show();
let surface = gtk_managed_window
.native()
.unwrap()
.surface()
.downcast::<X11Surface>()
.unwrap();
let gdk_display = surface.display().downcast::<X11Display>().unwrap();
let display = unsafe { gdk_display.xdisplay() };
let screen_id = gdk_display.screen().screen_number();
let connection = unsafe { xcb::Connection::from_xlib_display(display) };
let screen = connection
.get_setup()
.roots()
.next()
.ok_or(Error::FailedToGetRootWindow)?;
let systray = get_systray(&connection, dbg!(screen_id))?;
let tray_icon = make_tray_icon_parent(&connection, screen, systray)?;
let gtk_window_xid = unsafe { x::Window::new(surface.xid() as u32) };
let co = connection.send_request_checked(&x::ReparentWindow {
window: gtk_window_xid,
parent: tray_icon,
x: 0,
y: 0,
});
connection.flush().unwrap();
let _r = connection.into_raw_conn();
let image = gtk4::Image::from_resource("/kc/kcshot/logo/tray.png");
gtk_managed_window.set_child(Some(&image));
app.add_window(&gtk_managed_window);
Ok(())
}
/// This function attempts to get the Window that owns the systray area on the wm/de/x11 side
fn get_systray(connection: &xcb::Connection, screen: i32) -> Result<x::Window, Error> {
// https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-latest.html#locating
let net_system_tray = connection.send_request(&x::InternAtom {
only_if_exists: true,
name: format!("_NET_SYSTEM_TRAY_S{screen}").as_bytes(),
});
let net_system_tray = connection.wait_for_reply(net_system_tray)?;
let net_system_tray = if net_system_tray.atom() != ATOM_NONE {
net_system_tray.atom()
} else {
return Err(Error::XEmbedSystrayNotSupported);
};
let systray_owner = connection.send_request(&x::GetSelectionOwner {
selection: net_system_tray,
});
let systray_owner = connection.wait_for_reply(systray_owner)?.owner();
Ok(systray_owner)
}
/// This function creates the Window that will act as 'host' for our Gtk window in the systray area
fn make_tray_icon_parent(
connection: &xcb::Connection,
screen: &x::Screen,
systray: x::Window,
) -> Result<x::Window, Error> {
let tray_icon = connection.generate_id::<x::Window>();
connection.send_request(&x::CreateWindow {
depth: x::COPY_FROM_PARENT as u8,
wid: tray_icon,
parent: screen.root(),
x: 0,
y: 0,
width: 22,
height: 22,
border_width: 0,
class: x::WindowClass::InputOutput,
visual: screen.root_visual(),
value_list: &[],
});
let system_tray_opcode = connection.send_request(&x::InternAtom {
only_if_exists: true,
name: b"_NET_SYSTEM_TRAY_OPCODE",
});
let system_tray_opcode = connection.wait_for_reply(system_tray_opcode)?;
let system_tray_opcode = if system_tray_opcode.atom() != ATOM_NONE {
system_tray_opcode.atom()
} else {
return Err(Error::XEmbedSystrayNotSupported);
};
const SYSTEM_TRAY_REQUEST_DOCK: u32 = 0;
connection.send_request(&x::SendEvent {
propagate: false,
destination: x::SendEventDest::Window(systray),
event_mask: x::EventMask::NO_EVENT,
event: &x::ClientMessageEvent::new(
systray,
system_tray_opcode,
x::ClientMessageData::Data32([
xcb::x::CURRENT_TIME,
SYSTEM_TRAY_REQUEST_DOCK,
tray_icon.resource_id(),
0,
0,
]),
),
});
Ok(tray_icon)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment