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.
This commit is contained in:
parent
5dc055b143
commit
03999f24a7
7 changed files with 910 additions and 9 deletions
141
Cargo.lock
generated
141
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
563
src/wayland.rs
Normal file
563
src/wayland.rs
Normal file
|
|
@ -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_client::EventQueue>,
|
||||
wayland_display: wayland_client::Display,
|
||||
wlr_layer_shell: Option<Main<zwlr_layer_shell_v1::ZwlrLayerShellV1>>,
|
||||
}
|
||||
|
||||
impl CosmicWaylandDisplay {
|
||||
fn for_display(display: &gdk4_wayland::WaylandDisplay) -> Rc<Self> {
|
||||
const DATA_KEY: &str = "cosmic-wayland-display";
|
||||
|
||||
// `GdkWaylandDisplay` already associated with a `CosmicWaylandDisplay`
|
||||
if let Some(data) = unsafe { display.data::<Rc<Self>>(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::<zwlr_layer_shell_v1::ZwlrLayerShellV1>(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<gdk4_wayland::WaylandDisplay>,
|
||||
surface: RefCell<Option<WaylandCustomSurface>>,
|
||||
renderer: RefCell<Option<gsk::Renderer>>,
|
||||
wlr_layer_surface: RefCell<Option<Main<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>>>,
|
||||
constraint_solver: DerefCell<ConstraintSolver>,
|
||||
child: RefCell<Option<gtk4::Widget>>,
|
||||
monitor: DerefCell<Option<gdk4_wayland::WaylandMonitor>>,
|
||||
#[derivative(Default(value = "Cell::new(Layer::Background)"))]
|
||||
layer: Cell<Layer>,
|
||||
#[derivative(Default(value = "Cell::new(Anchor::empty())"))]
|
||||
anchor: Cell<Anchor>,
|
||||
exclusive_zone: Cell<i32>,
|
||||
margin: Cell<(i32, i32, i32, i32)>,
|
||||
#[derivative(Default(value = "Cell::new(KeyboardInteractivity::None)"))]
|
||||
keyboard_interactivity: Cell<KeyboardInteractivity>,
|
||||
namespace: DerefCell<String>,
|
||||
focus_widget: RefCell<Option<gtk4::Widget>>,
|
||||
}
|
||||
|
||||
#[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<LayerShellWindowInner> for gtk4::Native {
|
||||
fn interface_init(iface: &mut glib::Interface<Self>) {
|
||||
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<LayerShellWindowInner>) {}
|
||||
}
|
||||
|
||||
// TODO: Move into gtk4-rs when support merged/released in gtk
|
||||
unsafe impl IsImplementable<LayerShellWindowInner> for gtk4::Root {
|
||||
fn interface_init(iface: &mut glib::Interface<Self>) {
|
||||
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<LayerShellWindowInner>) {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct LayerShellWindow(ObjectSubclass<LayerShellWindowInner>)
|
||||
@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<T: IsA<gtk4::Widget>>(&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<GtkConstraintSolver>);
|
||||
|
||||
match fn {
|
||||
type_ => || gtk_constraint_solver_get_type(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GtkNativeInterface {
|
||||
pub g_iface: gobject_sys::GTypeInterface,
|
||||
pub get_surface:
|
||||
Option<unsafe extern "C" fn(self_: *mut gtk4::ffi::GtkNative) -> *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<unsafe extern "C" fn(self_: *mut gtk4::ffi::GtkNative, x: *mut f64, y: *mut f64)>,
|
||||
pub layout:
|
||||
Option<unsafe extern "C" fn(self_: *mut gtk4::ffi::GtkNative, width: c_int, height: c_int)>,
|
||||
}
|
||||
|
||||
pub struct GtkRootInterface {
|
||||
pub g_iface: gobject_sys::GTypeInterface,
|
||||
pub get_display:
|
||||
Option<unsafe extern "C" fn(self_: *mut gtk4::ffi::GtkRoot) -> *mut gdk::ffi::GdkDisplay>,
|
||||
pub get_constraint_solver:
|
||||
Option<unsafe extern "C" fn(self_: *mut gtk4::ffi::GtkRoot) -> *mut GtkConstraintSolver>,
|
||||
pub get_focus:
|
||||
Option<unsafe extern "C" fn(self_: *mut gtk4::ffi::GtkRoot) -> *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 <LayerShellWindowInner as ObjectSubclass>::Instance);
|
||||
let imp = instance.impl_();
|
||||
imp.surface.borrow().as_ref().map_or(ptr::null_mut(), |x| {
|
||||
x.upcast_ref::<gdk::Surface>().to_glib_none().0
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn get_renderer(native: *mut gtk4::ffi::GtkNative) -> *mut gsk::ffi::GskRenderer {
|
||||
let instance = &*(native as *mut <LayerShellWindowInner as ObjectSubclass>::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 <LayerShellWindowInner as ObjectSubclass>::Instance);
|
||||
let imp = instance.impl_();
|
||||
imp.display.upcast_ref::<gdk::Display>().to_glib_none().0
|
||||
}
|
||||
|
||||
unsafe extern "C" fn get_constraint_solver(
|
||||
root: *mut gtk4::ffi::GtkRoot,
|
||||
) -> *mut GtkConstraintSolver {
|
||||
let instance = &*(root as *mut <LayerShellWindowInner as ObjectSubclass>::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 <LayerShellWindowInner as ObjectSubclass>::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 <LayerShellWindowInner as ObjectSubclass>::Instance);
|
||||
let imp = instance.impl_();
|
||||
*imp.focus_widget.borrow_mut() = if focus.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(gtk4::Widget::from_glib_none(focus))
|
||||
};
|
||||
}
|
||||
144
src/wayland_custom_surface.rs
Normal file
144
src/wayland_custom_surface.rs
Normal file
|
|
@ -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<ffi::GdkWaylandCustomSurface>) @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_<dyn Fn(&WaylandCustomSurface, &WaylandPopup) -> bool + 'static>,
|
||||
>,
|
||||
) {
|
||||
let get_popup_func_data: Box_<
|
||||
Option<Box_<dyn Fn(&WaylandCustomSurface, &WaylandPopup) -> 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_<dyn Fn(&WaylandCustomSurface, &WaylandPopup) -> 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<Box_<dyn Fn(&WaylandCustomSurface, &WaylandPopup) -> bool + 'static>>,
|
||||
> = Box_::from_raw(data as *mut _);
|
||||
}
|
||||
let destroy_call3 = Some(destroy_func as _);
|
||||
let super_callback0: Box_<
|
||||
Option<Box_<dyn Fn(&WaylandCustomSurface, &WaylandPopup) -> 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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue