use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use std::{
cell::{Cell, RefCell},
os::raw::c_ulong,
rc::Rc,
};
use tauri_runtime::ProgressBarState;
use tauri_runtime::dpi::PhysicalSize;
use tauri_utils::config::Color;
use winit::platform::gtk4::WindowExtGtk4;
use crate::window::AppWindow;
use super::{taskbar, utils::set_wm_state};
pub(crate) struct CefX11Host {
default_vbox: gtk::Box,
xid: c_ulong,
colormap: c_ulong,
geometry: Rc<HostGeometry>,
background_color_provider: RefCell<Option<gtk::CssProvider>>,
}
#[derive(Default)]
struct HostGeometry {
size: Cell<PhysicalSize<u32>>,
needs_relayout: Cell<bool>,
}
impl CefX11Host {
pub(crate) fn new(window: &dyn winit::window::Window) -> Option<Self> {
use gtk::prelude::*;
let gtk_window = window.gtk_window()?;
let default_vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
default_vbox.set_hexpand(true);
default_vbox.set_vexpand(true);
let webview_area = gtk::Box::new(gtk::Orientation::Vertical, 0);
webview_area.set_hexpand(true);
webview_area.set_vexpand(true);
default_vbox.append(&webview_area);
gtk_window.set_child(Some(&default_vbox));
let parent_xid = window_xid(window);
let initial_size = window.surface_size();
let (xid, colormap) = create_cef_container(parent_xid, initial_size)?;
let geometry = Rc::new(HostGeometry {
size: Cell::new(initial_size),
needs_relayout: Cell::new(false),
});
if let Some(surface) = gtk_window.surface() {
let gtk_window = gtk_window.clone();
let layout_webview_area = webview_area.clone();
let layout_geometry = geometry.clone();
surface.connect_layout(move |surface, _, _| {
let size = set_cef_container_bounds(
xid,
>k_window,
&layout_webview_area,
surface.scale_factor().max(1) as f64,
);
if layout_geometry.size.replace(size) != size {
layout_geometry.needs_relayout.set(true);
}
});
}
Some(Self {
default_vbox,
xid,
colormap,
geometry,
background_color_provider: RefCell::new(None),
})
}
pub(crate) fn default_vbox(&self) -> gtk::Box {
self.default_vbox.clone()
}
fn background_color_class(&self) -> String {
format!("tauri-cef-window-background-{}", self.xid)
}
pub(crate) fn size(&self) -> PhysicalSize<u32> {
self.geometry.size.get()
}
pub(crate) fn take_needs_relayout(&self) -> bool {
self.geometry.needs_relayout.replace(false)
}
fn take_background_color_provider(&self) -> Option<gtk::CssProvider> {
self.background_color_provider.borrow_mut().take()
}
fn set_background_color_provider(&self, provider: gtk::CssProvider) {
self.background_color_provider.replace(Some(provider));
}
}
impl Drop for CefX11Host {
fn drop(&mut self) {
if let Some(provider) = self.background_color_provider.borrow_mut().take() {
gtk::style_context_remove_provider_for_display(
>k::prelude::WidgetExt::display(&self.default_vbox),
&provider,
);
}
super::utils::with_x11((), |xlib, display| unsafe {
(xlib.XDestroyWindow)(display, self.xid);
(xlib.XFreeColormap)(display, self.colormap);
});
}
}
impl AppWindow {
pub(crate) fn cef_host_handle(&self) -> cef::sys::cef_window_handle_t {
self.cef_host.xid as cef::sys::cef_window_handle_t
}
pub(crate) fn xid(&self) -> c_ulong {
window_xid(self.window.as_ref())
}
pub(crate) fn raise_native(&self) {
super::utils::activate_window(self.xid());
}
pub(crate) fn apply_transient_for(&self) {
use gtk::prelude::GtkWindowExt;
let Some(parent) = &self.attrs.transient_for else {
return;
};
if let Some(window) = self.window.gtk_window() {
window.set_transient_for(Some(parent));
}
}
pub(crate) fn set_enabled(&self, enabled: bool) {
use gtk::prelude::*;
if let Some(window) = self.window.gtk_window() {
window.set_sensitive(enabled);
}
}
pub(crate) fn is_enabled(&self) -> bool {
use gtk::prelude::*;
self
.window
.gtk_window()
.map(|window| window.is_sensitive())
.unwrap_or(true)
}
pub(crate) fn set_background_color(&self, color: Option<Color>) {
use gtk::prelude::*;
let Some(window) = self.window.gtk_window() else {
return;
};
let display = gtk::prelude::WidgetExt::display(&window);
let class = self.cef_host.background_color_class();
if let Some(previous) = self.cef_host.take_background_color_provider() {
gtk::style_context_remove_provider_for_display(&display, &previous);
}
let Some(color) = color else {
window.remove_css_class(&class);
return;
};
let provider = gtk::CssProvider::new();
let css = format!(
".{class} {{ background-color: rgba({}, {}, {}, {:.3}); }}",
color.0,
color.1,
color.2,
f64::from(color.3) / 255.
);
provider.load_from_bytes(>k::glib::Bytes::from_owned(css));
gtk::style_context_add_provider_for_display(
&display,
&provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
window.add_css_class(&class);
self.cef_host.set_background_color_provider(provider);
}
pub(crate) fn set_skip_taskbar(&self, skip: bool) {
set_wm_state(self.xid(), skip, "_NET_WM_STATE_SKIP_TASKBAR", None);
}
pub(crate) fn set_visible_on_all_workspaces(&self, visible: bool) {
set_wm_state(self.xid(), visible, "_NET_WM_STATE_STICKY", None);
}
pub(crate) fn set_progress_bar(&self, state: ProgressBarState) {
taskbar::set_progress_bar(state);
}
}
fn window_xid(window: &dyn winit::window::Window) -> c_ulong {
let handle = window.window_handle().expect("failed to get window handle");
match handle.as_raw() {
RawWindowHandle::Xlib(handle) => handle.window as c_ulong,
RawWindowHandle::Xcb(handle) => handle.window.get() as c_ulong,
other => panic!("expected X11 window handle, got {other:?}"),
}
}
fn create_cef_container(
parent_xid: c_ulong,
initial_size: PhysicalSize<u32>,
) -> Option<(c_ulong, c_ulong)> {
use x11_dl::xlib::*;
super::utils::with_x11(None, |xlib, display| unsafe {
let screen = (xlib.XDefaultScreen)(display);
let root = (xlib.XRootWindow)(display, screen);
let mut visual_info: x11_dl::xlib::XVisualInfo = std::mem::zeroed();
if (xlib.XMatchVisualInfo)(
display,
screen,
24,
x11_dl::xlib::TrueColor,
&mut visual_info,
) == 0
{
return None;
}
let colormap = (xlib.XCreateColormap)(display, root, visual_info.visual, AllocNone);
if colormap == 0 {
return None;
}
let mut attrs: XSetWindowAttributes = std::mem::zeroed();
attrs.event_mask = ExposureMask | StructureNotifyMask;
attrs.colormap = colormap;
attrs.border_pixel = 0;
let xid = (xlib.XCreateWindow)(
display,
parent_xid as Window,
0,
0,
initial_size.width,
initial_size.height,
0,
visual_info.depth,
InputOutput as _,
visual_info.visual,
CWEventMask | CWColormap | CWBorderPixel,
&mut attrs,
);
if xid == 0 {
(xlib.XFreeColormap)(display, colormap);
return None;
}
(xlib.XMapWindow)(display, xid);
Some((xid, colormap))
})
}
fn set_cef_container_bounds(
xid: c_ulong,
gtk_window: >k::ApplicationWindow,
webview_area: >k::Box,
scale_factor: f64,
) -> PhysicalSize<u32> {
use gtk::prelude::*;
let width = (webview_area.width() as f64 * scale_factor).round() as u32;
let height = (webview_area.height() as f64 * scale_factor).round() as u32;
let point = gtk::graphene::Point::new(0.0, 0.0);
let point = webview_area
.compute_point(gtk_window, &point)
.unwrap_or_else(|| gtk::graphene::Point::new(0.0, 0.0));
let x = (point.x() as f64 * scale_factor).round() as i32;
let y = (point.y() as f64 * scale_factor).round() as i32;
super::utils::with_x11((), |xlib, display| unsafe {
(xlib.XMoveResizeWindow)(display, xid as _, x, y, width, height);
});
PhysicalSize::new(width, height)
}