use std::{
cell::{Cell, RefCell},
ffi::c_void,
ptr::NonNull,
rc::Weak,
};
use gpui::{Pixels, Point, point, px};
use gtk::prelude::*;
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WaylandWindowHandle};
use wayland_client::{
Connection, Dispatch, EventQueue, Proxy, QueueHandle,
backend::{Backend, ObjectId},
globals::{GlobalListContents, registry_queue_init},
protocol::{
wl_compositor::WlCompositor, wl_region::WlRegion, wl_registry::WlRegistry,
wl_subcompositor::WlSubcompositor, wl_subsurface::WlSubsurface, wl_surface::WlSurface,
},
};
use super::window::WindowInner;
use crate::events;
pub(crate) struct Content {
connection: Connection,
queue: RefCell<EventQueue<Globals>>,
subcompositor: WlSubcompositor,
surface: WlSurface,
subsurface: RefCell<Option<WlSubsurface>>,
area: Cell<(i32, i32, i32, i32)>,
scale: Cell<i32>,
}
impl Content {
pub(crate) fn new(window: RawWindowHandle, display: RawDisplayHandle) -> Option<Self> {
let (RawWindowHandle::Wayland(window), RawDisplayHandle::Wayland(display)) =
(window, display)
else {
return None;
};
let backend = unsafe { Backend::from_foreign_display(display.display.as_ptr().cast()) };
let connection = Connection::from_backend(backend);
let (globals, queue) = registry_queue_init::<Globals>(&connection)
.map_err(|error| log::warn!("tauri-plugin-gpui: Wayland registry: {error}"))
.ok()?;
let qh = queue.handle();
let bind = |error| log::warn!("tauri-plugin-gpui: Wayland globals: {error}");
let compositor: WlCompositor = globals.bind(&qh, 1..=4, ()).map_err(bind).ok()?;
let subcompositor: WlSubcompositor = globals.bind(&qh, 1..=1, ()).map_err(bind).ok()?;
let surface = compositor.create_surface(&qh, ());
let region = compositor.create_region(&qh, ());
surface.set_input_region(Some(®ion));
region.destroy();
let content = Self {
connection,
queue: RefCell::new(queue),
subcompositor,
surface,
subsurface: RefCell::default(),
area: Cell::new((0, 0, 0, 0)),
scale: Cell::new(1),
};
content.attach(window.surface.as_ptr());
Some(content)
}
pub(crate) fn window_handle(&self) -> RawWindowHandle {
let surface = NonNull::new(self.surface.id().as_ptr().cast::<c_void>())
.expect("a live wl_surface has a pointer");
RawWindowHandle::Wayland(WaylandWindowHandle::new(surface))
}
pub(crate) fn is_attached(&self) -> bool {
self.subsurface.borrow().is_some()
}
pub(crate) fn origin(&self) -> Point<Pixels> {
let (x, y, ..) = self.area.get();
point(px(x as f32), px(y as f32))
}
fn attach(&self, parent: *mut c_void) {
self.detach();
if parent.is_null() {
return;
}
let parent = unsafe { ObjectId::from_ptr(WlSurface::interface(), parent.cast()) }
.and_then(|id| WlSurface::from_id(&self.connection, id));
let parent = match parent {
Ok(parent) => parent,
Err(error) => {
log::warn!("tauri-plugin-gpui: not a wl_surface: {error}");
return;
}
};
let qh = self.queue.borrow().handle();
let subsurface = self
.subcompositor
.get_subsurface(&self.surface, &parent, &qh, ());
subsurface.set_desync();
let (x, y, ..) = self.area.get();
subsurface.set_position(x, y);
self.surface.commit();
*self.subsurface.borrow_mut() = Some(subsurface);
}
fn detach(&self) {
if let Some(subsurface) = self.subsurface.borrow_mut().take() {
subsurface.destroy();
}
}
fn place(&self, inner: &WindowInner, area: (i32, i32, i32, i32), scale: i32) {
let (x, y, width, height) = area;
let old = self.area.replace(area);
if (old.0, old.1) != (x, y)
&& let Some(subsurface) = &*self.subsurface.borrow()
{
subsurface.set_position(x, y);
}
if self.scale.replace(scale) != scale {
self.surface.set_buffer_scale(scale);
}
inner.resized(
events::physical_size((width * scale) as u32, (height * scale) as u32),
scale as f32,
);
}
pub(crate) fn rescale(&self, inner: &WindowInner, scale: f64) {
let area = self.area.get();
if area.2 > 0 && area.3 > 0 {
self.place(inner, area, (scale.round() as i32).max(1));
}
}
pub(crate) fn flush(&self) {
if let Err(error) = self.queue.borrow_mut().dispatch_pending(&mut Globals) {
log::warn!("tauri-plugin-gpui: Wayland dispatch failed: {error}");
}
}
}
impl Drop for Content {
fn drop(&mut self) {
self.detach();
self.surface.destroy();
let _ = self.connection.flush();
}
}
pub(crate) fn install(window: &tauri::Window<tauri::Wry>, inner: Weak<WindowInner>) {
let gtk_window = match window.gtk_window() {
Ok(window) => window,
Err(error) => {
log::warn!("tauri-plugin-gpui: no GTK window for the Wayland surface: {error}");
return;
}
};
gtk_window.connect_map({
let inner = inner.clone();
move |window| {
crate::ffi::callback("map", (), || {
let Some(inner) = inner.upgrade() else { return };
let Some(content) = &inner.wayland else {
return;
};
let parent = window.window().map_or(std::ptr::null_mut(), |window| {
unsafe {
gdk_wayland_sys::gdk_wayland_window_get_wl_surface(window.as_ptr().cast())
}
});
content.attach(parent);
inner.window_mode_changed();
inner.waker.wake();
})
}
});
gtk_window.connect_unmap({
let inner = inner.clone();
move |_| {
crate::ffi::callback("unmap", (), || {
let Some(inner) = inner.upgrade() else { return };
let Some(content) = &inner.wayland else {
return;
};
content.detach();
inner.window_mode_changed();
inner.waker.wake();
})
}
});
let Some(child) = gtk_window.child() else {
log::warn!("tauri-plugin-gpui: the GTK window has no content widget");
return;
};
let resize = move |widget: >k::Widget| {
let Some(inner) = inner.upgrade() else { return };
let Some(content) = &inner.wayland else {
return;
};
let allocation = widget.allocation();
if allocation.width() <= 1 || allocation.height() <= 1 {
return;
}
let area = (
allocation.x(),
allocation.y(),
allocation.width(),
allocation.height(),
);
content.place(&inner, area, widget.scale_factor().max(1));
widget.queue_draw();
};
resize(&child);
child.connect_size_allocate(move |widget, _| {
crate::ffi::callback("size-allocate", (), || resize(widget))
});
}
struct Globals;
impl Dispatch<WlRegistry, GlobalListContents> for Globals {
fn event(
_: &mut Self,
_: &WlRegistry,
_: <WlRegistry as Proxy>::Event,
_: &GlobalListContents,
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
macro_rules! ignore_events {
($($proxy:ty),*) => {$(
impl Dispatch<$proxy, ()> for Globals {
fn event(
_: &mut Self,
_: &$proxy,
_: <$proxy as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
)*};
}
ignore_events!(
WlCompositor,
WlSubcompositor,
WlSurface,
WlSubsurface,
WlRegion
);