-
-
Save RealKC/2d8dfc59fb4c016aaec7958775b728d2 to your computer and use it in GitHub Desktop.
Attempt #2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(>k_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