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));