tauri_plugin_widgets/linux/
mod.rs1use tauri::{Runtime, WebviewWindow};
5
6use crate::desktop::placement::WindowModes;
7use crate::models::{WidgetWindowConfig, WindowLayer};
8
9pub(crate) fn native<T, E: std::fmt::Display>(
11 what: &str,
12 f: impl FnOnce() -> Result<T, E>,
13) -> Result<T, String> {
14 std::panic::catch_unwind(std::panic::AssertUnwindSafe(f))
18 .map_err(|_| format!("{what}: not available"))?
19 .map_err(|e| format!("{what}: {e}"))
20}
21
22pub(crate) fn pin_widget_window<R: Runtime>(
34 win: &WebviewWindow<R>,
35 cfg: &WidgetWindowConfig,
36 modes: &WindowModes,
37 pos: Option<(f64, f64)>,
38) -> bool {
39 let is_x11 = x11_xid(win).is_some();
42
43 #[cfg(feature = "layer-shell")]
44 if !is_x11 && modes.layer != WindowLayer::Normal {
45 match apply_layer_shell(win, cfg, modes, pos) {
46 Ok(()) => {
47 log::debug!("linux: gtk-layer-shell surface for '{}'", cfg.label);
48 return true;
49 }
50 Err(e) => log::warn!("linux: layer-shell failed ({e}); keeping normal window"),
51 }
52 }
53 #[cfg(not(feature = "layer-shell"))]
54 let _ = (pos, is_x11);
55
56 let island = cfg.label == crate::desktop::island::LABEL;
57 if let Err(e) = apply_gtk_hints(win, modes, cfg.skip_taskbar, island) {
58 log::warn!("linux: desktop hints failed: {e}");
59 }
60 false
61}
62
63fn apply_gtk_hints<R: Runtime>(
64 win: &WebviewWindow<R>,
65 modes: &WindowModes,
66 skip_taskbar: bool,
67 island: bool,
68) -> Result<(), String> {
69 use gtk::prelude::*;
70 let gw = native("gtk_window", || win.gtk_window())?;
71 if modes.layer == WindowLayer::Desktop {
72 gw.set_type_hint(gtk::gdk::WindowTypeHint::Desktop);
73 }
74 if island {
76 gw.set_type_hint(gtk::gdk::WindowTypeHint::Dock);
77 }
78 if skip_taskbar {
79 gw.set_skip_taskbar_hint(true);
80 gw.set_skip_pager_hint(true);
81 }
82 if modes.all_workspaces {
83 gw.stick();
84 }
85 gw.set_keep_below(modes.on_bottom());
86 gw.set_keep_above(modes.on_top());
87 Ok(())
88}
89
90fn x11_xid<R: Runtime>(win: &WebviewWindow<R>) -> Option<u32> {
91 use raw_window_handle::{HasWindowHandle, RawWindowHandle};
92
93 let handle = win.window_handle().ok()?;
94 match handle.as_raw() {
95 RawWindowHandle::Xlib(h) => Some(h.window as u32),
96 RawWindowHandle::Xcb(h) => Some(h.window.get()),
97 _ => None,
98 }
99}
100
101#[cfg(feature = "layer-shell")]
102fn apply_layer_shell<R: Runtime>(
103 win: &WebviewWindow<R>,
104 cfg: &WidgetWindowConfig,
105 modes: &WindowModes,
106 pos: Option<(f64, f64)>,
107) -> Result<(), String> {
108 use crate::desktop::placement::{layer_anchor, Rect};
109 use gtk::prelude::*;
110 use gtk_layer_shell::{Edge, Layer, LayerShell};
111
112 let island = cfg.label == crate::desktop::island::LABEL;
113 let layer = match modes.layer {
114 _ if island => Layer::Overlay,
115 WindowLayer::Desktop => Layer::Background,
116 WindowLayer::Bottom => Layer::Bottom,
117 WindowLayer::Top => Layer::Top,
118 WindowLayer::Normal => return Err("normal layer".into()),
119 };
120
121 let old = native("gtk_window", || win.gtk_window())?;
123 let app = old
124 .application()
125 .ok_or_else(|| "gtk window has no application".to_string())?;
126 let vbox = native("default_vbox", || win.default_vbox())?;
127 old.remove(&vbox);
128
129 let layer_win = gtk::ApplicationWindow::new(&app);
130 layer_win.set_app_paintable(true);
131 if let Some(v) = WidgetExt::screen(&layer_win).and_then(|s| s.rgba_visual()) {
132 layer_win.set_visual(Some(&v));
133 }
134 layer_win.add(&vbox);
135
136 layer_win.init_layer_shell();
137 layer_win.set_layer(layer);
138 layer_win.set_namespace(&cfg.label);
139
140 let monitor = gtk::gdk::Display::default().and_then(|d| match pos {
141 Some((x, y)) => d.monitor_at_point(x as i32, y as i32),
142 None => d.primary_monitor(),
143 });
144 let geom = monitor.as_ref().map_or(Rect::default(), |m| {
145 let g = m.geometry();
146 Rect {
147 x: g.x() as f64,
148 y: g.y() as f64,
149 w: g.width() as f64,
150 h: g.height() as f64,
151 }
152 });
153 if let Some(m) = &monitor {
154 layer_win.set_monitor(m);
155 }
156 let mut a = layer_anchor(cfg.placement.as_ref(), pos, geom);
157 if island {
159 a.left = None;
160 a.right = None;
161 }
162 for (edge, margin) in [
163 (Edge::Top, a.top),
164 (Edge::Bottom, a.bottom),
165 (Edge::Left, a.left),
166 (Edge::Right, a.right),
167 ] {
168 layer_win.set_anchor(edge, margin.is_some());
169 layer_win.set_layer_shell_margin(edge, margin.unwrap_or(0));
170 }
171 #[allow(deprecated)]
172 layer_win.set_keyboard_interactivity(modes.focusable);
173 if modes.click_through {
174 layer_win.input_shape_combine_region(Some(>k::cairo::Region::create()));
175 }
176
177 layer_win.set_default_size(cfg.width.round() as i32, cfg.height.round() as i32);
178 layer_win.show_all();
179
180 let layer = layer_win.clone();
183 old.connect_destroy(move |_| layer.close());
184
185 Ok(())
186}