From 03999f24a7b7d514d36bc6170e7b9b50719b49eb Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 22 Dec 2021 14:14:09 -0800 Subject: [PATCH] Initial layer-shell support Requires `layer-shell` feature to support, and patched GTK from https://github.com/pop-os/gtk4/tree/layer-shell_impish. This is generally working, but some things could be improved. Some of the methods implemented for `LayerShellWindow` lack logic that exists in `GtkWindow` that may be important. (For instance, some things related to CSS). And some things may require more private functions from GTK. The hard part is getting the necessary work upstreamed in some form. --- Cargo.lock | 141 ++++++++- Cargo.toml | 8 + src/application.rs | 8 +- src/main.rs | 4 + src/wayland.rs | 563 ++++++++++++++++++++++++++++++++++ src/wayland_custom_surface.rs | 144 +++++++++ src/window.rs | 51 +++ 7 files changed, 910 insertions(+), 9 deletions(-) create mode 100644 src/wayland.rs create mode 100644 src/wayland_custom_surface.rs diff --git a/Cargo.lock b/Cargo.lock index 682fd451..bd240099 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,9 +35,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" -version = "1.3.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "byte_string" @@ -156,6 +156,21 @@ dependencies = [ "syn", ] +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + [[package]] name = "either" version = "1.6.1" @@ -369,6 +384,31 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "gdk4-wayland" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c930875d2f466207eae96d0110a3233c22744c16087cd0035f73da507f1a1bf5" +dependencies = [ + "gdk4", + "gdk4-wayland-sys", + "gio", + "glib", + "libc", + "wayland-client", +] + +[[package]] +name = "gdk4-wayland-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c321379df46fc983d2a6aa0b639832e22ea0f85d64222a10e985b4378565ac" +dependencies = [ + "glib-sys", + "libc", + "system-deps 5.0.0", +] + [[package]] name = "gdk4-x11" version = "0.3.1" @@ -627,6 +667,16 @@ version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" +[[package]] +name = "libloading" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + [[package]] name = "log" version = "0.4.14" @@ -674,6 +724,19 @@ dependencies = [ "void", ] +[[package]] +name = "nix" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -777,10 +840,15 @@ dependencies = [ "byte_string", "cascade", "chrono", + "derivative", + "gdk4-wayland", "gdk4-x11", + "gobject-sys", "gtk4", "once_cell", "toml", + "wayland-client", + "wayland-protocols", "x11", "zbus", ] @@ -1098,6 +1166,67 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wayland-client" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ab332350e502f159382201394a78e3cc12d0f04db863429260164ea40e0355" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix 0.20.2", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21817947c7011bbd0a27e11b17b337bfd022e8544b071a2641232047966fbda" +dependencies = [ + "nix 0.20.2", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-protocols" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "286620ea4d803bacf61fa087a4242ee316693099ee5a140796aaba02b29f861f" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce923eb2deb61de332d1f356ec7b6bf37094dc5573952e1c8936db03b54c03f1" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d841fca9aed7febf9bed2e9796c49bf58d4152ceda8ac949ebe00868d8f0feb8" +dependencies = [ + "dlib", + "pkg-config", +] + [[package]] name = "wepoll-ffi" version = "0.1.2" @@ -1139,6 +1268,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + [[package]] name = "zbus" version = "1.9.1" @@ -1152,7 +1287,7 @@ dependencies = [ "fastrand", "futures", "nb-connect", - "nix", + "nix 0.17.0", "once_cell", "polling", "scoped-tls", diff --git a/Cargo.toml b/Cargo.toml index 2976d031..42d4b186 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,17 @@ license = "LGPL-3.0-or-later" cascade = "1" chrono = "0.4" byte_string = "1" +derivative = "2" gdk4-x11 = "0.3" +gdk4-wayland = { version = "0.3", optional = true } gtk4 = "0.3" +gobject-sys = "0.14.0" once_cell = "1" toml = "0.5" +wayland-client = { version = "0.28", optional = true } +wayland-protocols = { version = "0.28", features = [ "client", "unstable_protocols" ], optional = true } x11 = { version = "2", features = ["xlib"] } zbus = "1" + +[features] +layer-shell = ["gdk4-wayland", "wayland-client", "wayland-protocols"] diff --git a/src/application.rs b/src/application.rs index 197044a4..a549cb22 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,4 +1,3 @@ -use cascade::cascade; use gtk4::{ gdk, gio, glib::{self, clone}, @@ -10,7 +9,7 @@ use std::cell::Cell; use crate::deref_cell::DerefCell; use crate::notifications::Notifications; use crate::status_notifier_watcher; -use crate::window::PanelWindow; +use crate::window; #[derive(Default)] pub struct PanelAppInner { @@ -84,10 +83,7 @@ impl PanelApp { } fn add_window_for_monitor(&self, monitor: gdk::Monitor) { - cascade! { - PanelWindow::new(self, monitor); - ..show(); - }; + window::create(self, monitor); } pub fn notifications(&self) -> &Notifications { diff --git a/src/main.rs b/src/main.rs index d8a97ef5..fe436502 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,10 @@ mod status_area; mod status_menu; mod status_notifier_watcher; mod time_button; +#[cfg(feature = "layer-shell")] +mod wayland; +#[cfg(feature = "layer-shell")] +mod wayland_custom_surface; mod window; mod x; diff --git a/src/wayland.rs b/src/wayland.rs new file mode 100644 index 00000000..065147e5 --- /dev/null +++ b/src/wayland.rs @@ -0,0 +1,563 @@ +// TODO: scale-factor? + +use derivative::Derivative; +use gdk4_wayland::prelude::*; +use gtk4::{ + cairo, gdk, + glib::{self, clone, subclass::prelude::*, translate::*}, + gsk::{self, traits::RendererExt}, + prelude::*, + subclass::prelude::*, +}; +use std::{ + cell::{Cell, RefCell}, + os::raw::c_int, + ptr, + rc::Rc, +}; +use wayland_client::{event_enum, sys::client::wl_proxy, Filter, GlobalManager, Main, Proxy}; +use wayland_protocols::{ + wlr::unstable::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1}, + xdg_shell::client::xdg_popup, +}; + +use crate::{deref_cell::DerefCell, wayland_custom_surface::WaylandCustomSurface}; + +pub use wayland_protocols::wlr::unstable::layer_shell::v1::client::{ + zwlr_layer_shell_v1::Layer, + zwlr_layer_surface_v1::{Anchor, KeyboardInteractivity}, +}; + +event_enum!( + Events | + LayerSurface => zwlr_layer_surface_v1::ZwlrLayerSurfaceV1 +); + +struct CosmicWaylandDisplay { + event_queue: RefCell, + wayland_display: wayland_client::Display, + wlr_layer_shell: Option>, +} + +impl CosmicWaylandDisplay { + fn for_display(display: &gdk4_wayland::WaylandDisplay) -> Rc { + const DATA_KEY: &str = "cosmic-wayland-display"; + + // `GdkWaylandDisplay` already associated with a `CosmicWaylandDisplay` + if let Some(data) = unsafe { display.data::>(DATA_KEY) } { + return unsafe { data.as_ref() }.clone(); + } + + let wayland_display = unsafe { + wayland_client::Display::from_external_display( + display.wl_display().as_ref().c_ptr() as *mut _ + ) + }; // XXX is this sound? + + let mut event_queue = wayland_display.create_event_queue(); + let attached_display = wayland_display.attach(event_queue.token()); + let globals = GlobalManager::new(&attached_display); + + event_queue.sync_roundtrip(&mut (), |_, _, _| {}).unwrap(); + + let wlr_layer_shell = globals + .instantiate_exact::(1) + .ok(); + + let cosmic_wayland_display = Rc::new(Self { + event_queue: RefCell::new(event_queue), + wayland_display, + wlr_layer_shell, + }); + + unsafe { display.set_data(DATA_KEY, cosmic_wayland_display.clone()) }; + + // XXX Efficient way to poll? + // XXX unwrap? + glib::idle_add_local( + clone!(@weak cosmic_wayland_display => @default-return Continue(false), move || { + cosmic_wayland_display.wayland_display.flush().unwrap(); + let mut event_queue = cosmic_wayland_display.event_queue.borrow_mut(); + if let Some(guard) = event_queue.prepare_read() { + guard.read_events().unwrap(); + } + event_queue.dispatch_pending(&mut (), |_, _, _| {}).unwrap(); + Continue(true) + }), + ); + + cosmic_wayland_display + } +} + +#[derive(Derivative)] +#[derivative(Default)] +pub struct LayerShellWindowInner { + display: DerefCell, + surface: RefCell>, + renderer: RefCell>, + wlr_layer_surface: RefCell>>, + constraint_solver: DerefCell, + child: RefCell>, + monitor: DerefCell>, + #[derivative(Default(value = "Cell::new(Layer::Background)"))] + layer: Cell, + #[derivative(Default(value = "Cell::new(Anchor::empty())"))] + anchor: Cell, + exclusive_zone: Cell, + margin: Cell<(i32, i32, i32, i32)>, + #[derivative(Default(value = "Cell::new(KeyboardInteractivity::None)"))] + keyboard_interactivity: Cell, + namespace: DerefCell, + focus_widget: RefCell>, +} + +#[glib::object_subclass] +impl ObjectSubclass for LayerShellWindowInner { + const NAME: &'static str = "S76CosmicLayerShellWindow"; + type ParentType = gtk4::Widget; + type Interfaces = (gtk4::Native, gtk4::Root); + type Type = LayerShellWindow; +} + +impl ObjectImpl for LayerShellWindowInner { + fn constructed(&self, obj: &Self::Type) { + self.display + .set(gdk::Display::default().unwrap().downcast().unwrap()); // XXX any issue unwrapping? + self.constraint_solver.set(glib::Object::new(&[]).unwrap()); + + obj.add_css_class("background"); + } +} + +impl WidgetImpl for LayerShellWindowInner { + fn realize(&self, widget: &Self::Type) { + let surface = WaylandCustomSurface::new(&*self.display); + surface.set_get_popup_func(Some(Box::new(clone!(@strong widget => move |_surface, popup| { + if let Some(wlr_layer_surface) = widget.inner().wlr_layer_surface.borrow().as_ref() { + let xdg_popup: xdg_popup::XdgPopup = + unsafe { Proxy::from_c_ptr(gdk_wayland_popup_get_xdg_popup(popup.to_glib_none().0)).into() }; + wlr_layer_surface.get_popup(&xdg_popup); + } + true + })))); + widget.layer_shell_init(&surface); + + let widget_ptr: *mut _ = widget.to_glib_none().0; + let surface_ptr: *mut _ = surface.to_glib_none().0; + unsafe { gdk_surface_set_widget(surface_ptr as *mut _, widget_ptr as *mut _) }; + *self.surface.borrow_mut() = Some(surface.clone()); + surface.connect_render(move |_surface, region| { + unsafe { + gtk_widget_render( + widget_ptr as *mut _, + surface_ptr as *mut _, + region.to_glib_none().0, + ) + }; + true + }); + surface.connect_event(|_, event| { + unsafe { gtk_main_do_event(event.to_glib_none().0) }; + true + }); + + self.parent_realize(widget); + + *self.renderer.borrow_mut() = + Some(gsk::Renderer::for_surface(surface.upcast_ref()).unwrap()); // XXX unwrap? + + unsafe { gtk4::ffi::gtk_native_realize(widget_ptr as *mut _) }; + } + + fn unrealize(&self, widget: &Self::Type) { + let widget_ptr: *mut Self::Instance = widget.to_glib_none().0; + + unsafe { gtk4::ffi::gtk_native_unrealize(widget_ptr as *mut _) }; + + self.parent_unrealize(widget); + + if let Some(renderer) = self.renderer.borrow_mut().take() { + renderer.unrealize(); + } + + if let Some(surface) = self.surface.borrow().as_ref() { + let surface_ptr: *mut _ = surface.to_glib_none().0; + unsafe { gdk_surface_set_widget(surface_ptr as *mut _, ptr::null_mut()) }; + } + } + + fn map(&self, widget: &Self::Type) { + if let Some(surface) = self.surface.borrow().as_ref() { + let width = widget.measure(gtk4::Orientation::Horizontal, -1).1; + let height = widget.measure(gtk4::Orientation::Vertical, width).1; + widget.set_size(width as u32, height as u32); + surface.present(width, height); + } + + self.parent_map(widget); + + if let Some(child) = self.child.borrow().as_ref() { + child.map(); + } + } + + fn unmap(&self, widget: &Self::Type) { + self.parent_unmap(widget); + + if let Some(surface) = self.surface.borrow().as_ref() { + surface.hide(); + } + + if let Some(child) = self.child.borrow().as_ref() { + child.unmap(); + } + } + + fn measure( + &self, + _widget: &Self::Type, + orientation: gtk4::Orientation, + for_size: i32, + ) -> (i32, i32, i32, i32) { + if let Some(child) = self.child.borrow().as_ref() { + child.measure(orientation, for_size) + } else { + (0, 0, 0, 0) + } + } + + fn size_allocate(&self, _widget: &Self::Type, width: i32, height: i32, baseline: i32) { + if let Some(child) = self.child.borrow().as_ref() { + child.allocate(width, height, baseline, None) + } + } + + fn show(&self, widget: &Self::Type) { + widget.realize(); + self.parent_show(widget); + widget.map(); + } + + fn hide(&self, widget: &Self::Type) { + self.parent_hide(widget); + widget.unmap(); + } +} + +// TODO: Move into gtk4-rs when support merged/released in gtk +unsafe impl IsImplementable for gtk4::Native { + fn interface_init(iface: &mut glib::Interface) { + let iface = unsafe { &mut *(iface as *mut _ as *mut GtkNativeInterface) }; + iface.get_surface = Some(get_surface); + iface.get_renderer = Some(get_renderer); + iface.get_surface_transform = Some(get_surface_transform); + iface.layout = Some(layout); + } + + fn instance_init(_instance: &mut glib::subclass::InitializingObject) {} +} + +// TODO: Move into gtk4-rs when support merged/released in gtk +unsafe impl IsImplementable for gtk4::Root { + fn interface_init(iface: &mut glib::Interface) { + let iface = unsafe { &mut *(iface as *mut _ as *mut GtkRootInterface) }; + iface.get_display = Some(get_display); + iface.get_constraint_solver = Some(get_constraint_solver); + iface.get_focus = Some(get_focus); + iface.set_focus = Some(set_focus); + } + + fn instance_init(_instance: &mut glib::subclass::InitializingObject) {} +} + +glib::wrapper! { + pub struct LayerShellWindow(ObjectSubclass) + @extends gtk4::Widget, @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Native, gtk4::Root; +} +// TODO handle configure/destroy +// TODO presumably call destroy() when appropriate? +// What do wayland-client types do when associated connection is gone? Panic? UB? +impl LayerShellWindow { + pub fn new( + monitor: Option<&gdk4_wayland::WaylandMonitor>, + layer: Layer, + namespace: &str, + ) -> Self { + let obj: Self = glib::Object::new(&[]).unwrap(); + obj.inner().monitor.set(monitor.cloned()); + obj.inner().layer.set(layer); + obj.inner().namespace.set(namespace.to_string()); + obj + } + + fn inner(&self) -> &LayerShellWindowInner { + LayerShellWindowInner::from_instance(self) + } + + pub fn set_child>(&self, w: Option<&T>) { + let mut child = self.inner().child.borrow_mut(); + if let Some(child) = child.take() { + child.unparent(); + } + if let Some(w) = w { + w.set_parent(self); + } + *child = w.map(|x| x.clone().upcast()); + } + + fn layer_shell_init(&self, surface: &WaylandCustomSurface) { + let width = self.measure(gtk4::Orientation::Horizontal, -1).1; + let height = self.measure(gtk4::Orientation::Vertical, width).1; + // XXX needed for wl_surface to exist + surface.present(width, height); + + let wl_surface = surface.wl_surface(); + + let cosmic_wayland_display = CosmicWaylandDisplay::for_display(&*self.inner().display); + let wlr_layer_shell = match cosmic_wayland_display.wlr_layer_shell.as_ref() { + Some(wlr_layer_shell) => wlr_layer_shell, + None => { + eprintln!("Error: Layer shell not supported by compositor"); + return; + } + }; + + let output = self.inner().monitor.as_ref().map(|x| x.wl_output()); + let layer = self.layer(); + let namespace = self.namespace().to_string(); + let wlr_layer_surface = + wlr_layer_shell.get_layer_surface(&wl_surface, output.as_ref(), layer, namespace); + + wlr_layer_surface.set_anchor(self.anchor()); + wlr_layer_surface.set_exclusive_zone(self.exclusive_zone()); + let margin = self.margin(); + wlr_layer_surface.set_margin(margin.0, margin.1, margin.2, margin.3); + wlr_layer_surface.set_keyboard_interactivity(self.keyboard_interactivity()); + wlr_layer_surface.set_size(width as u32, height as u32); + + let filter = Filter::new( + clone!(@strong self as self_ => move |event, _, _| match event { + Events::LayerSurface { event, object } => match event { + zwlr_layer_surface_v1::Event::Configure { + serial, + width: _, + height: _, + } => { + // TODO: should size window to match `width`/`height` + object.ack_configure(serial); + } + zwlr_layer_surface_v1::Event::Closed => {} + _ => {} + }, + }), + ); + wlr_layer_surface.assign(filter); + + wl_surface.commit(); + + cosmic_wayland_display + .event_queue + .borrow_mut() + .sync_roundtrip(&mut (), |_, _, _| {}) + .unwrap(); + + *self.inner().wlr_layer_surface.borrow_mut() = Some(wlr_layer_surface); + } + + fn set_size(&self, width: u32, height: u32) { + if let Some(wlr_layer_surface) = self.inner().wlr_layer_surface.borrow().as_ref() { + wlr_layer_surface.set_size(width, height); + }; + } + + pub fn anchor(&self) -> Anchor { + self.inner().anchor.get() + } + + pub fn set_anchor(&self, anchor: Anchor) { + if let Some(wlr_layer_surface) = self.inner().wlr_layer_surface.borrow().as_ref() { + wlr_layer_surface.set_anchor(anchor); + }; + self.inner().anchor.set(anchor); + } + + pub fn exclusive_zone(&self) -> i32 { + self.inner().exclusive_zone.get() + } + + pub fn set_exclusive_zone(&self, zone: i32) { + if let Some(wlr_layer_surface) = self.inner().wlr_layer_surface.borrow().as_ref() { + wlr_layer_surface.set_exclusive_zone(zone); + }; + self.inner().exclusive_zone.set(zone); + } + + pub fn margin(&self) -> (i32, i32, i32, i32) { + self.inner().margin.get() + } + + pub fn set_margin(&self, top: i32, right: i32, bottom: i32, left: i32) { + if let Some(wlr_layer_surface) = self.inner().wlr_layer_surface.borrow().as_ref() { + wlr_layer_surface.set_margin(top, right, bottom, left); + }; + self.inner().margin.set((top, right, bottom, left)); + } + + pub fn keyboard_interactivity(&self) -> KeyboardInteractivity { + self.inner().keyboard_interactivity.get() + } + + pub fn set_keyboard_interactivity(&self, interactivity: KeyboardInteractivity) { + if let Some(wlr_layer_surface) = self.inner().wlr_layer_surface.borrow().as_ref() { + wlr_layer_surface.set_keyboard_interactivity(interactivity); + }; + self.inner().keyboard_interactivity.set(interactivity); + } + + pub fn layer(&self) -> Layer { + self.inner().layer.get() + } + + pub fn set_layer(&self, layer: Layer) { + if let Some(wlr_layer_surface) = self.inner().wlr_layer_surface.borrow().as_ref() { + wlr_layer_surface.set_layer(layer); + }; + self.inner().layer.set(layer); + } + + pub fn namespace(&self) -> &str { + self.inner().namespace.as_str() + } +} + +pub struct GtkConstraintSolver { + _private: [u8; 0], +} + +// XXX needs to be public in gtk +#[link(name = "gtk-4")] +extern "C" { + pub fn gtk_constraint_solver_get_type() -> glib::ffi::GType; + + pub fn gdk_surface_set_widget(surface: *mut gdk::ffi::GdkSurface, widget: glib::ffi::gpointer); + + pub fn _gtk_widget_set_visible_flag( + widget: *mut gtk4::ffi::GtkWidget, + visible: glib::ffi::gboolean, + ); + + pub fn gtk_widget_render( + widget: *mut gtk4::ffi::GtkWidget, + surface: *mut gdk::ffi::GdkSurface, + region: *const cairo::ffi::cairo_region_t, + ); + + pub fn gtk_main_do_event(event: *mut gdk::ffi::GdkEvent); + + // Added API + pub fn gdk_wayland_popup_get_xdg_popup( + popup: *mut gdk4_wayland::ffi::GdkWaylandPopup, + ) -> *mut wl_proxy; +} + +glib::wrapper! { + pub struct ConstraintSolver(Object); + + match fn { + type_ => || gtk_constraint_solver_get_type(), + } +} + +pub struct GtkNativeInterface { + pub g_iface: gobject_sys::GTypeInterface, + pub get_surface: + Option *mut gdk::ffi::GdkSurface>, + pub get_renderer: Option< + unsafe extern "C" fn(self_: *mut gtk4::ffi::GtkNative) -> *mut gsk::ffi::GskRenderer, + >, + pub get_surface_transform: + Option, + pub layout: + Option, +} + +pub struct GtkRootInterface { + pub g_iface: gobject_sys::GTypeInterface, + pub get_display: + Option *mut gdk::ffi::GdkDisplay>, + pub get_constraint_solver: + Option *mut GtkConstraintSolver>, + pub get_focus: + Option *mut gtk4::ffi::GtkWidget>, + pub set_focus: Option< + unsafe extern "C" fn(self_: *mut gtk4::ffi::GtkRoot, focus: *mut gtk4::ffi::GtkWidget), + >, +} + +unsafe extern "C" fn get_surface(native: *mut gtk4::ffi::GtkNative) -> *mut gdk::ffi::GdkSurface { + let instance = &*(native as *mut ::Instance); + let imp = instance.impl_(); + imp.surface.borrow().as_ref().map_or(ptr::null_mut(), |x| { + x.upcast_ref::().to_glib_none().0 + }) +} + +unsafe extern "C" fn get_renderer(native: *mut gtk4::ffi::GtkNative) -> *mut gsk::ffi::GskRenderer { + let instance = &*(native as *mut ::Instance); + let imp = instance.impl_(); + imp.renderer + .borrow() + .as_ref() + .map_or(ptr::null_mut(), |x| x.to_glib_none().0) +} + +unsafe extern "C" fn get_surface_transform( + _native: *mut gtk4::ffi::GtkNative, + x: *mut f64, + y: *mut f64, +) { + // TODO: add css logic like `GtkWindow` has + + *x = 0.; + *y = 0.; +} + +unsafe extern "C" fn layout(native: *mut gtk4::ffi::GtkNative, width: c_int, height: c_int) { + // TODO: `GtkWindow` has more here + gtk4::ffi::gtk_widget_allocate(native as *mut _, width, height, -1, ptr::null_mut()); +} + +unsafe extern "C" fn get_display(root: *mut gtk4::ffi::GtkRoot) -> *mut gdk::ffi::GdkDisplay { + let instance = &*(root as *mut ::Instance); + let imp = instance.impl_(); + imp.display.upcast_ref::().to_glib_none().0 +} + +unsafe extern "C" fn get_constraint_solver( + root: *mut gtk4::ffi::GtkRoot, +) -> *mut GtkConstraintSolver { + let instance = &*(root as *mut ::Instance); + let imp = instance.impl_(); + imp.constraint_solver.to_glib_none().0 +} + +unsafe extern "C" fn get_focus(root: *mut gtk4::ffi::GtkRoot) -> *mut gtk4::ffi::GtkWidget { + let instance = &*(root as *mut ::Instance); + let imp = instance.impl_(); + imp.focus_widget + .borrow() + .as_ref() + .map_or(ptr::null_mut(), |x| x.to_glib_none().0) +} + +unsafe extern "C" fn set_focus(root: *mut gtk4::ffi::GtkRoot, focus: *mut gtk4::ffi::GtkWidget) { + // TODO: `GtkWindow` does more here + let instance = &*(root as *mut ::Instance); + let imp = instance.impl_(); + *imp.focus_widget.borrow_mut() = if focus.is_null() { + None + } else { + Some(gtk4::Widget::from_glib_none(focus)) + }; +} diff --git a/src/wayland_custom_surface.rs b/src/wayland_custom_surface.rs new file mode 100644 index 00000000..fceb4cb5 --- /dev/null +++ b/src/wayland_custom_surface.rs @@ -0,0 +1,144 @@ +use gdk4_wayland::{WaylandDisplay, WaylandPopup}; +use gtk4::{ + gdk, + glib::{self, translate::*}, +}; +use std::boxed::Box as Box_; +use std::fmt; + +mod ffi { + use gdk4_wayland::ffi::{GdkWaylandDisplay, GdkWaylandPopup}; + use gtk4::{ + gdk::ffi::GdkFrameClock, + glib::ffi::{gboolean, gpointer, GDestroyNotify, GType}, + }; + use std::os::raw::c_int; + + pub type GdkWaylandCustomSurfaceGetPopupFunc = Option< + unsafe extern "C" fn( + *mut GdkWaylandCustomSurface, + *mut GdkWaylandPopup, + gpointer, + ) -> gboolean, + >; + + #[repr(C)] + pub struct GdkWaylandCustomSurface { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, + } + + impl ::std::fmt::Debug for GdkWaylandCustomSurface { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.debug_struct(&format!("GdkWaylandCustomSurface @ {:p}", self)) + .finish() + } + } + + extern "C" { + pub fn gdk_wayland_custom_surface_get_type() -> GType; + + pub fn gdk_wayland_custom_surface_new( + display: *mut GdkWaylandDisplay, + ) -> *mut GdkWaylandCustomSurface; + + pub fn gdk_wayland_custom_surface_present( + custom_surface: *mut GdkWaylandCustomSurface, + width: c_int, + height: c_int, + ); + + pub fn gdk_wayland_custom_surface_set_get_popup_func( + custom_surface: *mut GdkWaylandCustomSurface, + get_popup_func: GdkWaylandCustomSurfaceGetPopupFunc, + user_data: gpointer, + destroy: GDestroyNotify, + ); + + pub fn _gdk_frame_clock_idle_new() -> *mut GdkFrameClock; + } +} + +glib::wrapper! { + #[doc(alias = "GdkWaylandCustomSurface")] + pub struct WaylandCustomSurface(Object) @extends gdk4_wayland::WaylandSurface, gdk::Surface; + + match fn { + type_ => || ffi::gdk_wayland_custom_surface_get_type(), + } +} + +impl WaylandCustomSurface { + #[doc(alias = "gdk_wayland_custom_surface_new")] + pub fn new(display: &WaylandDisplay) -> WaylandCustomSurface { + unsafe { + from_glib_full(ffi::gdk_wayland_custom_surface_new( + display.to_glib_none().0, + )) + } + } + + #[doc(alias = "gdk_wayland_custom_surface_present")] + pub fn present(&self, width: i32, height: i32) { + unsafe { + ffi::gdk_wayland_custom_surface_present(self.to_glib_none().0, width, height); + } + } + + #[doc(alias = "gdk_wayland_custom_surface_set_get_popup_func")] + pub fn set_get_popup_func( + &self, + get_popup_func: Option< + Box_ bool + 'static>, + >, + ) { + let get_popup_func_data: Box_< + Option bool + 'static>>, + > = Box_::new(get_popup_func); + unsafe extern "C" fn get_popup_func_func( + custom_surface: *mut ffi::GdkWaylandCustomSurface, + popup: *mut gdk4_wayland::ffi::GdkWaylandPopup, + user_data: glib::ffi::gpointer, + ) -> glib::ffi::gboolean { + let custom_surface = from_glib_borrow(custom_surface); + let popup = from_glib_borrow(popup); + let callback: &Option< + Box_ bool + 'static>, + > = &*(user_data as *mut _); + let res = if let Some(ref callback) = *callback { + callback(&custom_surface, &popup) + } else { + panic!("cannot get closure...") + }; + res.into_glib() + } + let get_popup_func = if get_popup_func_data.is_some() { + Some(get_popup_func_func as _) + } else { + None + }; + unsafe extern "C" fn destroy_func(data: glib::ffi::gpointer) { + let _callback: Box_< + Option bool + 'static>>, + > = Box_::from_raw(data as *mut _); + } + let destroy_call3 = Some(destroy_func as _); + let super_callback0: Box_< + Option bool + 'static>>, + > = get_popup_func_data; + unsafe { + ffi::gdk_wayland_custom_surface_set_get_popup_func( + self.to_glib_none().0, + get_popup_func, + Box_::into_raw(super_callback0) as *mut _, + destroy_call3, + ); + } + } +} + +impl fmt::Display for WaylandCustomSurface { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("WaylandCustomSurface") + } +} diff --git a/src/window.rs b/src/window.rs index 950dc9a0..770e5c3f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -11,6 +11,57 @@ use crate::x; const BOTTOM: bool = false; +pub fn create(app: &PanelApp, monitor: gdk::Monitor) { + #[cfg(feature = "layer-shell")] + if let Some(wayland_monitor) = monitor.downcast_ref() { + wayland_create(app, wayland_monitor); + return; + } + + cascade! { + PanelWindow::new(app, monitor); + ..show(); + }; +} + +#[cfg(feature = "layer-shell")] +fn wayland_create(app: &PanelApp, monitor: &gdk4_wayland::WaylandMonitor) { + use crate::wayland::{Anchor, Layer, LayerShellWindow}; + + let window = LayerShellWindow::new(Some(monitor), Layer::Top, ""); + + window.connect_realize(|window| { + let surface = window.surface().unwrap(); + surface.connect_layout(clone!(@weak window => move |_surface, _width, height| { + window.set_exclusive_zone(height); + })); + }); + + window.set_child(Some(&window_box(app))); + window.set_size_request(monitor.geometry().width, 0); + window.set_anchor(if BOTTOM { Anchor::Bottom } else { Anchor::Top }); + window.show(); + + // XXX + unsafe { window.set_data("cosmic-app-hold", app.hold()) }; +} + +// XXX better handle duplication +#[cfg(feature = "layer-shell")] +fn window_box(app: &PanelApp) -> gtk4::Widget { + let widget = cascade! { + gtk4::CenterBox::new(); + ..set_start_widget(Some(&cascade! { + gtk4::Box::new(gtk4::Orientation::Horizontal, 0); + ..append(&button("Workspaces")); + ..append(&button("Applications")); + })); + ..set_center_widget(Some(&TimeButton::new(app))); + ..set_end_widget(Some(&StatusArea::new())); + }; + widget.upcast() +} + fn button(text: &str) -> gtk4::Button { let label = cascade! { gtk4::Label::new(Some(text));