use tauri::{Runtime, WebviewWindow};
use x11rb::connection::Connection;
use x11rb::protocol::xproto::{AtomEnum, ConnectionExt as XprotoConnectionExt, PropMode};
use x11rb::wrapper::ConnectionExt as WrapperConnectionExt;
pub fn pin_widget_window<R: Runtime>(win: &WebviewWindow<R>, skip_taskbar: bool) {
let is_x11 = x11_xid(win).is_some();
#[cfg(feature = "layer-shell")]
{
if !is_x11 {
match apply_layer_shell(win) {
Ok(()) => {
log::debug!("linux: gtk-layer-shell Background applied");
return;
}
Err(e) => {
log::warn!("linux: layer-shell failed ({e}); keeping normal window");
}
}
}
}
if is_x11 {
if let Err(e) = apply_x11_desktop_hints(win, skip_taskbar) {
log::warn!("linux: X11 desktop hints failed: {e}");
}
}
}
fn apply_x11_desktop_hints<R: Runtime>(
win: &WebviewWindow<R>,
skip_taskbar: bool,
) -> Result<(), String> {
let xid = x11_xid(win).ok_or_else(|| "no X11 window id".to_string())?;
let (conn, _) = x11rb::connect(None).map_err(|e| format!("x11 connect: {e}"))?;
let type_atom = intern(&conn, b"_NET_WM_WINDOW_TYPE")?;
let desktop_atom = intern(&conn, b"_NET_WM_WINDOW_TYPE_DESKTOP")?;
WrapperConnectionExt::change_property32(
&conn,
PropMode::REPLACE,
xid,
type_atom,
AtomEnum::ATOM,
&[desktop_atom],
)
.map_err(|e| format!("change_property TYPE: {e}"))?;
if skip_taskbar {
let state_atom = intern(&conn, b"_NET_WM_STATE")?;
let skip_atom = intern(&conn, b"_NET_WM_STATE_SKIP_TASKBAR")?;
let root = conn.setup().roots.first().map(|s| s.root).unwrap_or(0);
let event = x11rb::protocol::xproto::ClientMessageEvent::new(
32,
xid,
state_atom,
[1u32, skip_atom, 0, 0, 0], );
conn.send_event(
false,
root,
x11rb::protocol::xproto::EventMask::SUBSTRUCTURE_REDIRECT
| x11rb::protocol::xproto::EventMask::SUBSTRUCTURE_NOTIFY,
event,
)
.map_err(|e| format!("send_event STATE ADD: {e}"))?;
}
conn.flush().map_err(|e| format!("x11 flush: {e}"))?;
log::debug!("linux: X11 DESKTOP hints set on xid={xid}");
Ok(())
}
fn x11_xid<R: Runtime>(win: &WebviewWindow<R>) -> Option<u32> {
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
let handle = win.window_handle().ok()?;
match handle.as_raw() {
RawWindowHandle::Xlib(h) => Some(h.window as u32),
RawWindowHandle::Xcb(h) => Some(h.window.get()),
_ => None,
}
}
fn intern(conn: &impl Connection, name: &[u8]) -> Result<u32, String> {
Ok(XprotoConnectionExt::intern_atom(conn, false, name)
.map_err(|e| format!("intern_atom: {e}"))?
.reply()
.map_err(|e| format!("intern_atom reply: {e}"))?
.atom)
}
#[cfg(feature = "layer-shell")]
fn apply_layer_shell<R: Runtime>(win: &WebviewWindow<R>) -> Result<(), String> {
use gtk::prelude::*;
use gtk_layer_shell::{Edge, Layer, LayerShell};
let _ = win.hide();
let old = win.gtk_window().map_err(|e| format!("gtk_window: {e}"))?;
let app = old
.application()
.ok_or_else(|| "gtk window has no application".to_string())?;
let vbox = win
.default_vbox()
.map_err(|e| format!("default_vbox: {e}"))?;
old.remove(&vbox);
let layer_win = gtk::ApplicationWindow::new(&app);
layer_win.set_app_paintable(true);
layer_win.add(&vbox);
layer_win.init_layer_shell();
layer_win.set_layer(Layer::Background);
layer_win.set_anchor(Edge::Left, false);
layer_win.set_anchor(Edge::Right, false);
layer_win.set_anchor(Edge::Top, false);
layer_win.set_anchor(Edge::Bottom, false);
#[allow(unused_must_use)]
{
layer_win.set_keyboard_interactivity(false);
}
let (w, h) = win
.inner_size()
.map(|s| (s.width as i32, s.height as i32))
.unwrap_or((340, 340));
layer_win.set_default_size(w, h);
layer_win.show_all();
Ok(())
}