feat: wayland

This commit is contained in:
Nova
2025-07-05 19:51:40 -07:00
parent 7b126557df
commit f4d08dac9c
51 changed files with 3941 additions and 2691 deletions

View File

@@ -28,6 +28,9 @@ pub enum ServerError {
DeserializationError(#[from] DeserializationError),
#[error("Reader error: {0}")]
ReaderError(#[from] ReaderError),
#[cfg(feature = "wayland")]
#[error("Wayland error: {0}")]
WaylandError(waynest::server::Error),
#[error("Aspect {} does not exist for node", 0.to_string())]
NoAspect(TypeId),
#[error("{0}")]

View File

@@ -0,0 +1,7 @@
use bevy::asset::Assets;
use bevy::ecs::world::Mut;
use bevy::image::Image;
pub struct GraphicsInfo<'w> {
pub _images: Mut<'w, Assets<Image>>,
}

View File

@@ -6,6 +6,7 @@ pub mod delta;
pub mod destroy_queue;
pub mod entity_handle;
pub mod error;
pub mod graphics_info;
pub mod registry;
pub mod resource;
pub mod scenegraph;

View File

@@ -44,6 +44,7 @@ use bevy_sk::vr_materials::PbrMaterial;
use clap::Parser;
use core::client::{Client, tick_internal_client};
use core::entity_handle::EntityHandlePlugin;
use core::graphics_info::GraphicsInfo;
use core::task;
use directories::ProjectDirs;
use nodes::audio::AudioNodePlugin;
@@ -68,6 +69,7 @@ use tokio::task::JoinError;
use tracing::metadata::LevelFilter;
use tracing::{error, info};
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
use wayland::Wayland;
use zbus::Connection;
use zbus::fdo::ObjectManager;
@@ -188,6 +190,8 @@ async fn main() -> Result<AppExit, JoinError> {
"Couldn't make the object registry to find all objects with given interfaces in d-bus",
);
let _wayland = Wayland::new(None).expect("Couldn't create Wayland instance");
let ready_notifier = Arc::new(Notify::new());
let io_loop = tokio::task::spawn_blocking({
let ready_notifier = ready_notifier.clone();
@@ -422,7 +426,9 @@ fn update_cameras(mut camera: Query<&mut Projection, (With<Camera3d>,)>) {
fn xr_step(world: &mut World) {
// camera::update(token);
#[cfg(feature = "wayland")]
wayland.frame_event();
Wayland::early_frame(&mut GraphicsInfo {
_images: world.resource_mut::<Assets<Image>>(),
});
destroy_queue::clear();
// update things like the Xr input methods
@@ -457,5 +463,7 @@ fn xr_step(world: &mut World) {
tick_internal_client();
#[cfg(feature = "wayland")]
wayland.update();
world.resource_scope::<Assets<BevyMaterial>, _>(|world, mut materials| {
Wayland::update_graphics(&mut materials, &mut world.resource_mut::<Assets<Image>>());
});
}

View File

@@ -0,0 +1,5 @@
fn fragment(
in: VertexOutput
) -> FragmentOutput {
return vec4(0.0);
}

View File

@@ -10,8 +10,11 @@ use crate::nodes::Node;
use crate::nodes::alias::{Alias, AliasList};
use crate::nodes::spatial::{Spatial, SpatialNode};
use crate::{BevyMaterial, bail};
use bevy::asset::{load_internal_asset, weak_handle};
use bevy::pbr::{ExtendedMaterial, MaterialExtension};
use bevy::prelude::*;
use bevy::render::primitives::Aabb;
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
use color_eyre::eyre::eyre;
use parking_lot::Mutex;
use rustc_hash::{FxHashMap, FxHasher};
@@ -19,24 +22,48 @@ use stardust_xr::values::ResourceID;
use std::ffi::OsStr;
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock, Weak};
static LOAD_MODEL: BevyChannel<(Arc<Model>, PathBuf)> = BevyChannel::new();
type HoldoutMaterial = ExtendedMaterial<BevyMaterial, HoldoutExtension>;
const HOLDOUT_SHADER_HANDLE: Handle<Shader> = weak_handle!("92b481b7-d3da-4188-b252-2335ec814ee2");
const HOLDOUT_MATERIAL_HANDLE: Handle<HoldoutMaterial> =
weak_handle!("d56f1d62-9121-434b-a34f-9f0bbd6b3390");
pub struct ModelNodePlugin;
impl Plugin for ModelNodePlugin {
fn build(&self, app: &mut App) {
LOAD_MODEL.init(app);
load_internal_asset!(
app,
HOLDOUT_SHADER_HANDLE,
"holdout.wgsl",
Shader::from_wgsl
);
app.add_plugins(MaterialPlugin::<HoldoutMaterial>::default());
app.world_mut()
.resource_mut::<Assets<HoldoutMaterial>>()
.insert(&HOLDOUT_MATERIAL_HANDLE, HoldoutMaterial::default());
app.init_resource::<MaterialRegistry>();
app.add_systems(Update, load_models);
app.add_systems(
PostUpdate,
(
gen_model_parts,
apply_materials,
)
.chain(),
);
app.add_systems(PostUpdate, (gen_model_parts, apply_materials).chain());
}
}
// No extra data needed for a simple holdout
#[derive(Default, Asset, AsBindGroup, TypePath, Debug, Clone)]
pub struct HoldoutExtension {}
impl MaterialExtension for HoldoutExtension {
fn fragment_shader() -> ShaderRef {
HOLDOUT_SHADER_HANDLE.into()
}
fn alpha_mode() -> Option<AlphaMode> {
Some(AlphaMode::Opaque)
}
}
@@ -64,6 +91,7 @@ fn load_models(
}
fn apply_materials(
mut commands: Commands,
mut query: Query<&mut MeshMaterial3d<BevyMaterial>>,
mut material_registry: ResMut<MaterialRegistry>,
asset_server: Res<AssetServer>,
@@ -75,12 +103,21 @@ fn apply_materials(
.filter_map(|p| p.parts.get())
.flatten()
{
let Ok(mut mesh_mat) = query.get_mut(**model_part.mesh_entity.get().unwrap()) else {
let entity = **model_part.mesh_entity.get().unwrap();
let Ok(mut mesh_mat) = query.get_mut(entity) else {
continue;
};
if let Some(material) = model_part.pending_material_replacement.lock().take() {
let pbr_mat = material.to_pbr_mat(&asset_server);
let handle = material_registry.get_handle(pbr_mat, &mut materials);
if model_part.holdout.load(Ordering::Relaxed) {
commands
.entity(entity)
.remove::<MeshMaterial3d<BevyMaterial>>()
.insert(MeshMaterial3d(HOLDOUT_MATERIAL_HANDLE));
continue;
}
if let Some(material) = model_part.pending_material_replacement.lock().take()
&& let Some(material) = materials.get(&material)
{
let handle = material_registry.get_handle(material.clone(), &mut materials);
mesh_mat.0 = handle;
}
for (param_name, param) in model_part.pending_material_parameters.lock().drain() {
@@ -157,6 +194,7 @@ fn gen_model_parts(
_model: Arc::downgrade(&model),
pending_material_parameters: Mutex::default(),
pending_material_replacement: Mutex::default(),
holdout: AtomicBool::new(false),
aliases: AliasList::default(),
bounds: OnceLock::new(),
});
@@ -393,50 +431,6 @@ impl MaterialParameter {
}
}
pub struct Material {
pub color: Color,
pub emission_factor: Color,
pub metallic: f32,
pub roughness: f32,
pub alpha_mode: AlphaMode,
pub double_sided: bool,
pub diffuse_texture: Option<PathBuf>,
pub emission_texture: Option<PathBuf>,
pub metal_texture: Option<PathBuf>,
pub occlusion_texture: Option<PathBuf>,
}
impl Material {
fn to_pbr_mat(&self, asset_server: &AssetServer) -> BevyMaterial {
BevyMaterial {
color: self.color,
emission_factor: self.emission_factor,
metallic: self.metallic,
roughness: self.roughness,
alpha_mode: self.alpha_mode,
double_sided: self.double_sided,
diffuse_texture: self
.diffuse_texture
.as_ref()
.map(|p| asset_server.load(p.as_path())),
emission_texture: self
.emission_texture
.as_ref()
.map(|p| asset_server.load(p.as_path())),
metal_texture: self
.metal_texture
.as_ref()
.map(|p| asset_server.load(p.as_path())),
occlusion_texture: self
.occlusion_texture
.as_ref()
.map(|p| asset_server.load(p.as_path())),
..Default::default() // spherical_harmonics: bevy_sk::skytext::SPHERICAL_HARMONICS_HANDLE,
}
}
}
pub struct ModelPart {
entity: OnceLock<EntityHandle>,
mesh_entity: OnceLock<EntityHandle>,
@@ -444,12 +438,13 @@ pub struct ModelPart {
space: Arc<Spatial>,
_model: Weak<Model>,
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
pending_material_replacement: Mutex<Option<Material>>,
pending_material_replacement: Mutex<Option<Handle<BevyMaterial>>>,
holdout: AtomicBool,
aliases: AliasList,
bounds: OnceLock<Aabb>,
}
impl ModelPart {
pub fn replace_material(&self, replacement: Material) {
pub fn replace_material(&self, replacement: Handle<BevyMaterial>) {
self.pending_material_replacement
.lock()
.replace(replacement);
@@ -464,18 +459,7 @@ impl ModelPartAspect for ModelPart {
#[doc = "Set this model part's material to one that cuts a hole in the world. Often used for overlays/passthrough where you want to show the background through an object."]
fn apply_holdout_material(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
let model_part = node.get_aspect::<ModelPart>()?;
model_part.replace_material(Material {
color: Color::BLACK.with_alpha(0.0),
emission_factor: Color::BLACK,
metallic: 0.0,
roughness: 1.0,
alpha_mode: AlphaMode::Opaque,
double_sided: false,
diffuse_texture: None,
emission_texture: None,
metal_texture: None,
occlusion_texture: None,
});
model_part.holdout.store(true, Ordering::Relaxed);
Ok(())
}
@@ -584,6 +568,7 @@ impl Model {
_model: Arc::downgrade(self),
pending_material_parameters: Mutex::default(),
pending_material_replacement: Mutex::default(),
holdout: AtomicBool::new(false),
aliases: AliasList::default(),
bounds: OnceLock::new(),
});

View File

@@ -23,7 +23,7 @@ use mint::Vector2;
use parking_lot::Mutex;
use slotmap::{DefaultKey, Key, KeyData, SlotMap};
use std::sync::{Arc, Weak};
use tracing::{debug, info};
use tracing::debug;
stardust_xr_server_codegen::codegen_item_panel_protocol!();
impl Default for Geometry {
@@ -34,6 +34,7 @@ impl Default for Geometry {
}
}
}
impl Copy for Geometry {}
lazy_static! {
pub static ref KEYMAPS: Mutex<SlotMap<DefaultKey, String>> = Mutex::new(SlotMap::default());
@@ -97,6 +98,7 @@ pub trait PanelItemTrait: Send + Sync + 'static {
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>);
}
#[derive(Debug)]
pub struct PanelItem<B: Backend> {
pub node: Weak<Node>,
pub backend: Box<B>,
@@ -460,12 +462,6 @@ impl<B: Backend> PanelItemTrait for PanelItem<B> {
let _ = panel_item_acceptor_client::capture_item(node, item, init_data);
}
}
impl<B: Backend> Drop for PanelItem<B> {
fn drop(&mut self) {
// Dropped panel item, basically just a debug breakpoint place
info!("Dropped panel item");
}
}
impl InterfaceAspect for Interface {
#[doc = "Register this client to manage the items of a certain type and create default 3D UI for them."]

View File

@@ -303,7 +303,8 @@ impl MousePointer {
let handler = handler.clone();
let dbus_connection = dbus_connection.clone();
join_set.spawn(async move {
timeout(Duration::from_millis(1), async {
// TODO: refactor the whole thing so picking the keyboardhandler to send input to is separate from sending
timeout(Duration::from_millis(10), async {
let field_ref = handler
.to_typed_proxy::<FieldRefProxy>(&dbus_connection)
.await

View File

@@ -86,14 +86,11 @@ pub fn run_client(mut command: Command, debug_launched_clients: bool) -> Option<
}
pub fn connection_env() -> FxHashMap<String, String> {
macro_rules! var_env_insert {
($env:ident, $name:ident) => {
$env.insert(stringify!($name).to_string(), $name.get().unwrap().clone());
};
}
let mut env: FxHashMap<String, String> = FxHashMap::default();
var_env_insert!(env, STARDUST_INSTANCE);
env.insert(
stringify!(STARDUST_INSTANCE).to_string(),
STARDUST_INSTANCE.get().unwrap().clone(),
);
if let Some(flat_wayland_display) = std::env::var_os("WAYLAND_DISPLAY") {
env.insert(
@@ -103,7 +100,10 @@ pub fn connection_env() -> FxHashMap<String, String> {
}
#[cfg(feature = "wayland")]
{
var_env_insert!(env, WAYLAND_DISPLAY);
env.insert(
stringify!(WAYLAND_DISPLAY).to_string(),
WAYLAND_DISPLAY.get().unwrap().to_string_lossy().to_string(),
);
env.insert("XDG_SESSION_TYPE".to_string(), "wayland".to_string());
}
env

View File

@@ -1,152 +0,0 @@
use super::{
state::{ClientState, WaylandState},
utils::{ChildInfoExt, WlSurfaceExt},
xdg_shell::surface_panel_item,
};
use crate::{
nodes::items::panel::{ChildInfo, Geometry, SurfaceId},
wayland::surface::CoreSurface,
};
use parking_lot::Mutex;
use rand::Rng;
use smithay::{
backend::renderer::utils::{RendererSurfaceStateUserData, on_commit_buffer_handler},
delegate_compositor,
desktop::PopupKind,
reexports::wayland_server::{Client, protocol::wl_surface::WlSurface},
wayland::compositor::{
CompositorClientState, CompositorHandler, CompositorState, add_post_commit_hook,
},
};
use std::sync::Arc;
use tracing::{debug, warn};
pub struct ConfiguredSurface;
impl CompositorHandler for WaylandState {
fn compositor_state(&mut self) -> &mut CompositorState {
&mut self.compositor_state
}
fn commit(&mut self, surface: &WlSurface) {
debug!(?surface, "Surface commit");
on_commit_buffer_handler::<WaylandState>(surface);
if let Some(toplevel) = self
.xdg_shell
.toplevel_surfaces()
.iter()
.find(|s| s.wl_surface() == surface)
{
if !toplevel.is_initial_configure_sent() {
debug!("Sending initial configure for toplevel surface");
toplevel.send_configure();
surface.insert_data(ConfiguredSurface);
}
}
self.popup_manager.commit(surface);
if let Some(PopupKind::Xdg(popup)) = self.popup_manager.find_popup(surface) {
if surface.insert_data(ConfiguredSurface) {
debug!("Configuring popup surface");
let _ = popup.send_configure();
}
}
}
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
&client.get_data::<ClientState>().unwrap().compositor_state
}
fn new_subsurface(&mut self, surface: &WlSurface, parent: &WlSurface) {
let id = rand::thread_rng().gen_range(0..u64::MAX);
surface.insert_data(SurfaceId::Child(id));
CoreSurface::add_to(surface);
let Some(parent_surface_id) = parent.get_data::<SurfaceId>() else {
warn!("Parent surface has no SurfaceId");
return;
};
surface.insert_data(Mutex::new(ChildInfo {
id,
parent: parent_surface_id,
geometry: Geometry {
origin: [0; 2].into(),
size: [256; 2].into(),
},
z_order: 1,
receives_input: false,
}));
let Some(panel_item) = surface_panel_item(parent) else {
warn!("Parent has no panel item");
return;
};
let panel_item_weak = Arc::downgrade(&panel_item);
add_post_commit_hook(surface, move |_: &mut WaylandState, _dh, surf| {
if surface_panel_item(surf).is_some() {
return;
}
debug!("Linking surface to panel item");
surf.insert_data(panel_item_weak.clone());
let Some(panel_item) = surface_panel_item(surf) else {
warn!("Failed to link surface to panel item");
return;
};
surf.with_child_info(|_info| {
panel_item.backend.reposition_child(surf);
});
debug!("Adding new child to panel item");
panel_item.backend.new_child(surf);
});
add_post_commit_hook(surface, move |_: &mut WaylandState, _dh, surf| {
let Some(view) = surf
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|s| s.lock().ok()?.view())
.flatten()
else {
debug!("No view data for surface");
return;
};
let mut changed = false;
surf.with_child_info(|info| {
if info.geometry.origin.x != view.offset.x
&& info.geometry.origin.y != view.offset.y
{
changed = true;
debug!("Surface position changed");
}
if info.geometry.size.x != view.dst.w as u32
&& info.geometry.size.y != view.dst.h as u32
{
changed = true;
debug!("Surface size changed");
}
info.geometry.size = [view.dst.w as u32, view.dst.h as u32].into();
});
let Some(panel_item) = surface_panel_item(surf) else {
return;
};
if changed {
debug!("Repositioning child due to geometry change");
panel_item.backend.reposition_child(surf);
}
});
}
fn destroyed(&mut self, surface: &WlSurface) {
let Some(panel_item) = surface_panel_item(surface) else {
return;
};
if surface.get_child_info().is_some() {
debug!("Dropping destroyed child surface");
panel_item.backend.drop_child(surface);
}
}
}
delegate_compositor!(WaylandState);

View File

@@ -0,0 +1,89 @@
use std::sync::Arc;
#[cfg(feature = "dmabuf")]
use crate::wayland::dmabuf::buffer_backing::DmabufBacking;
use crate::{
core::registry::Registry,
wayland::{GraphicsInfo, core::shm_buffer_backing::ShmBufferBacking},
};
use bevy::{
asset::{Assets, Handle},
image::Image,
};
use mint::Vector2;
pub use waynest::server::protocol::core::wayland::wl_buffer::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
pub static BUFFER_REGISTRY: Registry<Buffer> = Registry::new();
#[derive(Debug)]
pub enum BufferBacking {
Shm(ShmBufferBacking),
#[cfg(feature = "dmabuf")]
Dmabuf(DmabufBacking),
}
impl BufferBacking {
// Returns true if the buffer can be released immediately after texture update
pub fn can_release_after_update(&self) -> bool {
matches!(self, BufferBacking::Shm(_))
}
}
#[derive(Debug, Dispatcher)]
pub struct Buffer {
pub id: ObjectId,
backing: BufferBacking,
}
impl Buffer {
pub fn new(client: &mut Client, id: ObjectId, backing: BufferBacking) -> Arc<Self> {
let buffer = client.insert(id, Self { id, backing });
BUFFER_REGISTRY.add_raw(&buffer);
buffer
}
#[allow(unused)]
pub fn init_tex(self: Arc<Self>, graphics_info: &mut GraphicsInfo) {
match &self.backing {
BufferBacking::Shm(_) => (),
#[cfg(feature = "dmabuf")]
BufferBacking::Dmabuf(backing) => backing.init_tex(graphics_info, self.clone()),
}
}
/// Returns the tex if it was updated
pub fn update_tex(&self, images: &mut Assets<Image>) -> Option<Handle<Image>> {
tracing::debug!("Updating texture for buffer {:?}", self.id);
match &self.backing {
BufferBacking::Shm(backing) => backing.update_tex(images),
#[cfg(feature = "dmabuf")]
BufferBacking::Dmabuf(backing) => backing
.get_tex()
.map(|tex| tex.get_id().to_string())
.and_then(|tex_id| Tex::find(tex_id).ok()),
}
}
pub fn can_release_after_update(&self) -> bool {
self.backing.can_release_after_update()
}
pub fn size(&self) -> Vector2<usize> {
match &self.backing {
BufferBacking::Shm(backing) => backing.size(),
#[cfg(feature = "dmabuf")]
BufferBacking::Dmabuf(backing) => backing.size(),
}
}
}
impl WlBuffer for Buffer {
/// https://wayland.app/protocols/wayland#wl_buffer:request:destroy
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
tracing::info!("Destroying buffer {:?}", self.id);
Ok(())
}
}

View File

@@ -0,0 +1,10 @@
pub use waynest::server::protocol::core::wayland::wl_callback::*;
use waynest::{
server::{Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug, Dispatcher, Clone)]
pub struct Callback(pub ObjectId);
/// https://wayland.app/protocols/wayland#wl_callback
impl WlCallback for Callback {}

View File

@@ -0,0 +1,70 @@
use super::surface::WL_SURFACE_REGISTRY;
use crate::wayland::core::surface::Surface;
pub use waynest::server::protocol::core::wayland::wl_compositor::*;
use waynest::{
server::{Client, Dispatcher, Result, protocol::core::wayland::wl_region::WlRegion},
wire::ObjectId,
};
#[derive(Debug, Dispatcher, Default)]
pub struct Compositor;
impl WlCompositor for Compositor {
/// https://wayland.app/protocols/wayland#wl_compositor:request:create_surface
async fn create_surface(
&self,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> Result<()> {
let surface = client.insert(id, Surface::new(client, id));
WL_SURFACE_REGISTRY.add_raw(&surface);
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_compositor:request:create_region
async fn create_region(
&self,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> Result<()> {
client.insert(id, Region::default());
Ok(())
}
}
#[derive(Debug, Dispatcher, Default)]
pub struct Region {}
impl WlRegion for Region {
/// https://wayland.app/protocols/wayland#wl_region:request:add
async fn add(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> Result<()> {
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_region:request:subtract
async fn subtract(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> Result<()> {
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_region:request:destroy
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

View File

@@ -0,0 +1,75 @@
#![allow(unused)]
use crate::wayland::{
MessageSink,
core::{
callback::{Callback, WlCallback},
registry::Registry,
seat::Seat,
},
};
use global_counter::primitive::exact::CounterU32;
use std::{
sync::{Arc, OnceLock},
time::Instant,
};
pub use waynest::server::protocol::core::wayland::wl_display::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Dispatcher)]
pub struct Display {
pub message_sink: MessageSink,
pub pid: Option<i32>,
pub seat: OnceLock<Arc<Seat>>,
id_counter: CounterU32,
pub creation_time: Instant,
}
impl Display {
pub fn new(message_sink: MessageSink, pid: Option<i32>) -> Self {
Self {
message_sink,
pid,
seat: OnceLock::new(),
id_counter: CounterU32::new(0xff000000), // Start at 0xff000000 to avoid conflicts with client-generated IDs
creation_time: Instant::now(),
}
}
pub fn next_server_id(&self) -> ObjectId {
unsafe { ObjectId::from_raw(self.id_counter.inc()) }
}
}
impl WlDisplay for Display {
/// https://wayland.app/protocols/wayland#wl_display:request:sync
async fn sync(
&self,
client: &mut Client,
sender_id: ObjectId,
callback_id: ObjectId,
) -> Result<()> {
let serial = client.next_event_serial();
Callback(callback_id)
.done(client, callback_id, serial)
.await?;
self.delete_id(client, sender_id, callback_id.as_raw())
.await?;
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_display:request:get_registry
async fn get_registry(
&self,
client: &mut Client,
_sender_id: ObjectId,
registry_id: ObjectId,
) -> Result<()> {
let registry = client.insert(registry_id, Registry);
registry.advertise_globals(client, registry_id).await?;
Ok(())
}
}

View File

@@ -0,0 +1,259 @@
use crate::{
nodes::items::panel::KEYMAPS,
wayland::{core::surface::Surface, util::ClientExt},
};
use dashmap::{DashMap, DashSet};
use memfd::MemfdOptions;
use slotmap::{DefaultKey, KeyData};
use std::{
collections::HashSet,
os::{
fd::{AsRawFd, IntoRawFd},
unix::io::{FromRawFd, OwnedFd},
},
sync::{Arc, Weak},
};
use tokio::sync::Mutex;
pub use waynest::server::protocol::core::wayland::wl_keyboard::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Default)]
struct ModifierState {
pressed_keys: HashSet<u32>,
mods_depressed: u32,
mods_latched: u32,
mods_locked: u32,
group: u32,
}
impl ModifierState {
fn update_key(&mut self, key: u32, pressed: bool) -> bool {
let changed = if pressed {
self.pressed_keys.insert(key)
} else {
self.pressed_keys.remove(&key)
};
if changed {
self.update_modifiers();
}
changed
}
fn update_modifiers(&mut self) {
let mut mods = 0;
// Update modifier state based on currently pressed keys
for key in &self.pressed_keys {
match *key {
input_event_codes::KEY_LEFTSHIFT!() | input_event_codes::KEY_RIGHTSHIFT!() => {
mods |= 1
}
input_event_codes::KEY_LEFTCTRL!() | input_event_codes::KEY_RIGHTCTRL!() => {
mods |= 4
}
input_event_codes::KEY_LEFTALT!() | input_event_codes::KEY_RIGHTALT!() => mods |= 8,
input_event_codes::KEY_LEFTMETA!() | input_event_codes::KEY_RIGHTMETA!() => {
mods |= 64
}
input_event_codes::KEY_CAPSLOCK!() => self.mods_locked ^= 1,
_ => {}
}
}
self.mods_depressed = mods;
}
}
#[derive(Dispatcher)]
pub struct Keyboard {
pub id: ObjectId,
focused_surface: Mutex<Weak<Surface>>,
modifier_state: Mutex<ModifierState>,
pressed_keys: DashMap<ObjectId, DashSet<u32>>,
current_keymap_id: Mutex<u64>,
}
impl Keyboard {
pub fn new(id: ObjectId) -> Self {
Self {
id,
focused_surface: Mutex::new(Weak::new()),
modifier_state: Mutex::new(ModifierState::default()),
pressed_keys: DashMap::default(),
current_keymap_id: Mutex::new(0),
}
}
async fn send_keymap(&self, client: &mut Client, keymap: &[u8]) -> Result<()> {
let file = MemfdOptions::default()
.create("stardust-keymap")
.map_err(|e| waynest::server::Error::Custom(e.to_string()))?
.into_file();
file.set_len(keymap.len() as u64)?;
// file.write_all(keymap)?;
// file.flush()?;
// let map = libc::mmap(addr, len, prot, flags, fd, offset)
let mut map = unsafe { memmap2::MmapMut::map_mut(file.as_raw_fd()) }?;
map.copy_from_slice(keymap);
let fd = unsafe { OwnedFd::from_raw_fd(file.into_raw_fd()) };
// Send keymap to client
self.keymap(
client,
self.id,
KeymapFormat::XkbV1,
fd,
keymap.len() as u32,
)
.await?;
Ok(())
}
/// has to be the wayland key, so -8 or whatever
pub async fn handle_keyboard_key(
&self,
client: &mut Client,
surface: Arc<Surface>,
keymap_id: u64,
key: u32,
pressed: bool,
) -> Result<()> {
// KEYMAP UPDATES
{
let mut old_keymap_id = self.current_keymap_id.lock().await;
if *old_keymap_id != keymap_id {
// println!("Updating keymap to {keymap_id}");
let keymap_key = DefaultKey::from(KeyData::from_ffi(keymap_id));
// Get keymap data and drop the lock immediately
let keymap_data = {
let keymap_lock = KEYMAPS.lock();
keymap_lock
.get(keymap_key)
.map(|s| s.as_bytes().to_vec())
.unwrap_or_default()
};
// Now we can safely await
self.send_keymap(client, &keymap_data).await?;
};
*old_keymap_id = keymap_id;
drop(old_keymap_id);
}
// PRESSED KEYS UPDATE
let pressed_keys = self.pressed_keys.entry(surface.id).or_default();
if pressed {
pressed_keys.insert(key);
} else {
pressed_keys.remove(&key);
}
// println!("pressed keys: {:?}", &*pressed_keys);
// FOCUS UPDATES
let mut focused = self.focused_surface.lock().await;
let mut modifier_state = self.modifier_state.lock().await;
let refocus = focused.as_ptr() != Arc::as_ptr(&surface);
// If we're entering a new surface
if refocus {
// Send leave to old surface if it exists and is still alive
if let Some(old_surface) = focused.upgrade() {
let serial = client.next_event_serial();
self.leave(client, old_surface.id, serial, self.id).await?;
// println!("Left surface {}", old_surface.id);
}
// Send enter to new surface
let serial = client.next_event_serial();
self.enter(
client,
self.id,
serial,
surface.id,
pressed_keys.iter().flat_map(|k| k.to_ne_bytes()).collect(),
)
.await?;
// println!("Entered new surface {}", surface.id);
// Update focused surface
*focused = Arc::downgrade(&surface);
}
// KEY EVENT SENDING
let serial = client.next_event_serial();
// println!(
// "Sent key {key} {}",
// if pressed { "pressed" } else { "released" }
// );
self.key(
client,
self.id,
serial,
client.display().creation_time.elapsed().as_millis() as u32, // time
key,
if pressed {
KeyState::Pressed
} else {
KeyState::Released
},
)
.await?;
// MODIFIER UPDATES
// Update modifier state and send modifiers event if changed
if refocus || modifier_state.update_key(key, pressed) {
// println!("Update modifiers");
let serial = client.next_event_serial();
self.modifiers(
client,
self.id,
serial,
modifier_state.mods_depressed,
modifier_state.mods_latched,
modifier_state.mods_locked,
modifier_state.group,
)
.await?;
}
Ok(())
}
pub async fn reset(&self, client: &mut Client) -> Result<()> {
let mut modifier_state = self.modifier_state.lock().await;
modifier_state.pressed_keys.clear();
modifier_state.mods_depressed = 0;
modifier_state.mods_latched = 0;
modifier_state.mods_locked = 0;
modifier_state.group = 0;
let serial = client.next_event_serial();
self.modifiers(
client,
self.id,
serial,
modifier_state.mods_depressed,
modifier_state.mods_latched,
modifier_state.mods_locked,
modifier_state.group,
)
.await
}
}
impl WlKeyboard for Keyboard {
/// https://wayland.app/protocols/wayland#wl_keyboard:request:release
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

14
src/wayland/core/mod.rs Normal file
View File

@@ -0,0 +1,14 @@
pub mod buffer;
pub mod callback;
pub mod compositor;
pub mod display;
pub mod keyboard;
pub mod output;
pub mod pointer;
pub mod registry;
pub mod seat;
pub mod shm;
pub mod shm_buffer_backing;
pub mod shm_pool;
pub mod surface;
pub mod touch;

View File

@@ -0,0 +1,16 @@
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
pub use waynest::server::protocol::core::wayland::wl_output::*;
#[derive(Debug, Dispatcher, Default)]
pub struct Output;
impl WlOutput for Output {
/// https://wayland.app/protocols/wayland#wl_output:request:release
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
todo!()
}
}

183
src/wayland/core/pointer.rs Normal file
View File

@@ -0,0 +1,183 @@
use crate::wayland::core::{seat::fixed_from_f32, surface::Surface};
use mint::Vector2;
use std::sync::Arc;
use std::sync::Weak;
use tokio::sync::Mutex;
use tracing;
pub use waynest::server::protocol::core::wayland::wl_pointer::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Dispatcher)]
pub struct Pointer {
pub id: ObjectId,
focused_surface: Mutex<Weak<Surface>>,
}
impl Pointer {
pub fn new(id: ObjectId) -> Self {
Self {
id,
focused_surface: Mutex::new(Weak::new()),
}
}
pub async fn handle_pointer_motion(
&self,
client: &mut Client,
surface: Arc<Surface>,
position: Vector2<f32>,
) -> Result<()> {
tracing::debug!(
"Handling pointer motion at ({}, {})",
position.x,
position.y
);
let mut focused = self.focused_surface.lock().await;
// If we're entering a new surface
if focused.as_ptr() != Arc::as_ptr(&surface) {
tracing::debug!("Surface transition detected");
// Send leave to old surface if it exists and is still alive
if let Some(old_surface) = focused.upgrade() {
let serial = client.next_event_serial();
tracing::debug!("Sending leave event with serial {}", serial);
self.leave(client, self.id, serial, old_surface.id).await?;
}
// Send enter to new surface
let serial = client.next_event_serial();
tracing::debug!(
"Sending enter event with serial {} to surface {:?}",
serial,
surface.id
);
self.enter(
client,
self.id,
serial,
surface.id,
fixed_from_f32(position.x),
fixed_from_f32(position.y),
)
.await?;
// Update focused surface
*focused = Arc::downgrade(&surface);
}
// Send motion event to current surface
tracing::debug!("Sending motion event to surface");
self.motion(
client,
self.id,
0, // time
fixed_from_f32(position.x),
fixed_from_f32(position.y),
)
.await?;
self.frame(client, self.id).await?;
Ok(())
}
pub async fn handle_pointer_button(
&self,
client: &mut Client,
surface: Arc<Surface>,
button: u32,
pressed: bool,
) -> Result<()> {
tracing::debug!(
"Handling pointer button {} {} on surface {:?}",
button,
if pressed { "pressed" } else { "released" },
surface.id
);
let serial = client.next_event_serial();
self.button(
client,
self.id,
serial,
0, // time
button,
if pressed {
ButtonState::Pressed
} else {
ButtonState::Released
},
)
.await?;
self.frame(client, self.id).await
}
pub async fn handle_pointer_scroll(
&self,
client: &mut Client,
_surface: Arc<Surface>,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) -> Result<()> {
tracing::debug!(
"Handling pointer scroll: distance={:?}, steps={:?}",
scroll_distance,
scroll_steps
);
if let Some(distance) = scroll_distance {
self.axis(
client,
self.id,
0, // time
Axis::HorizontalScroll,
fixed_from_f32(distance.x),
)
.await?;
self.axis(
client,
self.id,
0, // time
Axis::VerticalScroll,
fixed_from_f32(distance.y),
)
.await?;
}
if let Some(steps) = scroll_steps {
self.axis_discrete(client, self.id, Axis::HorizontalScroll, steps.x as i32)
.await?;
self.axis_discrete(client, self.id, Axis::VerticalScroll, steps.y as i32)
.await?;
}
self.frame(client, self.id).await
}
pub async fn reset(&self, client: &mut Client) -> Result<()> {
let mut focused = self.focused_surface.lock().await;
if let Some(old_surface) = focused.upgrade() {
let serial = client.next_event_serial();
self.leave(client, self.id, serial, old_surface.id).await?;
self.frame(client, self.id).await?;
}
*focused = Weak::new();
Ok(())
}
}
impl WlPointer for Pointer {
/// https://wayland.app/protocols/wayland#wl_pointer:request:set_cursor
async fn set_cursor(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_serial: u32,
_surface: Option<ObjectId>,
_hotspot_x: i32,
_hotspot_y: i32,
) -> Result<()> {
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_pointer:request:release
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

View File

@@ -0,0 +1,148 @@
use crate::wayland::{
core::{
compositor::{Compositor, WlCompositor},
display::Display,
output::{Output, WlOutput},
seat::{Seat, WlSeat},
shm::{Shm, WlShm},
},
xdg::wm_base::{WmBase, XdgWmBase},
};
pub use waynest::server::protocol::core::wayland::wl_registry::*;
#[cfg(feature = "dmabuf")]
use waynest::server::protocol::stable::linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1;
use waynest::{
server::{Client, Dispatcher, Error, Result},
wire::{NewId, ObjectId},
};
struct RegistryGlobals;
impl RegistryGlobals {
pub const COMPOSITOR: u32 = 0;
pub const SHM: u32 = 1;
pub const WM_BASE: u32 = 2;
pub const SEAT: u32 = 3;
pub const OUTPUT: u32 = 4;
#[cfg(feature = "dmabuf")]
pub const DMABUF: u32 = 5;
}
#[derive(Debug, Dispatcher, Default)]
pub struct Registry;
impl Registry {
pub async fn advertise_globals(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
self.global(
client,
sender_id,
RegistryGlobals::COMPOSITOR,
Compositor::INTERFACE.to_string(),
Compositor::VERSION,
)
.await?;
self.global(
client,
sender_id,
RegistryGlobals::SHM,
Shm::INTERFACE.to_string(),
Shm::VERSION,
)
.await?;
self.global(
client,
sender_id,
RegistryGlobals::WM_BASE,
WmBase::INTERFACE.to_string(),
WmBase::VERSION,
)
.await?;
self.global(
client,
sender_id,
RegistryGlobals::SEAT,
Seat::INTERFACE.to_string(),
Seat::VERSION,
)
.await?;
self.global(
client,
sender_id,
RegistryGlobals::OUTPUT,
Output::INTERFACE.to_string(),
Output::VERSION,
)
.await?;
#[cfg(feature = "dmabuf")]
self.global(
client,
sender_id,
RegistryGlobals::DMABUF,
crate::wayland::dmabuf::Dmabuf::INTERFACE.to_string(),
Dmabuf::VERSION,
)
.await?;
Ok(())
}
}
impl WlRegistry for Registry {
async fn bind(
&self,
client: &mut Client,
_sender_id: ObjectId,
name: u32,
new_id: NewId,
) -> Result<()> {
match name {
RegistryGlobals::COMPOSITOR => {
tracing::info!("Binding compositor");
client.insert(new_id.object_id, Compositor);
}
RegistryGlobals::SHM => {
tracing::info!("Binding SHM");
let shm = client.insert(new_id.object_id, Shm);
shm.advertise_formats(client, new_id.object_id).await?;
}
RegistryGlobals::WM_BASE => {
tracing::info!("Binding WM_BASE");
client.insert(new_id.object_id, WmBase);
}
RegistryGlobals::SEAT => {
tracing::info!("Binding seat with id {}", new_id.object_id);
let seat = client.insert(new_id.object_id, Seat::new());
if let Some(display) = client.get::<Display>(ObjectId::DISPLAY) {
tracing::info!("Setting seat in display");
let _ = display.seat.set(seat.clone());
tracing::info!("Seat set successfully");
} else {
tracing::warn!("No display found to set seat");
}
seat.advertise_capabilities(client, new_id.object_id)
.await?;
tracing::info!("Seat capabilities advertised");
}
RegistryGlobals::OUTPUT => {
tracing::info!("Binding output");
client.insert(new_id.object_id, Output);
}
#[cfg(feature = "dmabuf")]
RegistryGlobals::DMABUF => {
tracing::info!("Binding dmabuf");
let dmabuf = client.insert(new_id.object_id, Dmabuf::new());
dmabuf.send_modifiers(client, new_id.object_id).await?;
}
id => {
tracing::error!(id, "Wayland: failed to bind to registry global");
return Err(Error::MissingObject(unsafe { ObjectId::from_raw(name) }));
}
}
Ok(())
}
}

191
src/wayland/core/seat.rs Normal file
View File

@@ -0,0 +1,191 @@
use crate::wayland::core::{keyboard::Keyboard, pointer::Pointer, surface::Surface, touch::Touch};
use mint::Vector2;
use std::sync::Arc;
use std::sync::OnceLock;
pub use waynest::server::protocol::core::wayland::wl_seat::*;
use waynest::server::{Client, Dispatcher, Result};
use waynest::wire::{Fixed, ObjectId};
#[derive(Debug)]
pub enum SeatMessage {
PointerMotion {
surface: Arc<Surface>,
position: Vector2<f32>,
},
PointerButton {
surface: Arc<Surface>,
button: u32,
pressed: bool,
},
PointerScroll {
surface: Arc<Surface>,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
},
KeyboardKey {
surface: Arc<Surface>,
keymap_id: u64,
key: u32,
pressed: bool,
},
TouchDown {
surface: Arc<Surface>,
id: u32,
position: Vector2<f32>,
},
TouchMove {
id: u32,
position: Vector2<f32>,
},
TouchUp {
id: u32,
},
Reset,
}
pub fn fixed_from_f32(f: f32) -> Fixed {
unsafe { Fixed::from_raw((f * 256.0).round() as u32) }
}
#[derive(Default, Dispatcher)]
pub struct Seat {
pointer: OnceLock<Arc<Pointer>>,
keyboard: OnceLock<Arc<Keyboard>>,
touch: OnceLock<Arc<Touch>>,
}
impl Seat {
pub fn new() -> Self {
Self::default()
}
pub async fn advertise_capabilities(&self, client: &mut Client, id: ObjectId) -> Result<()> {
tracing::debug!("Advertising seat capabilities with id {}", id);
let capabilities = Capability::Pointer | Capability::Keyboard | Capability::Touch;
WlSeat::capabilities(self, client, id, capabilities).await?;
tracing::debug!("Capabilities advertised: {:?}", capabilities);
Ok(())
}
pub async fn handle_message(&self, client: &mut Client, message: SeatMessage) -> Result<()> {
match message {
SeatMessage::PointerMotion { surface, position } => {
if let Some(pointer) = self.pointer.get() {
pointer
.handle_pointer_motion(client, surface, position)
.await?;
}
}
SeatMessage::PointerButton {
surface,
button,
pressed,
} => {
if let Some(pointer) = self.pointer.get() {
pointer
.handle_pointer_button(client, surface, button, pressed)
.await?;
}
}
SeatMessage::PointerScroll {
surface,
scroll_distance,
scroll_steps,
} => {
if let Some(pointer) = self.pointer.get() {
pointer
.handle_pointer_scroll(client, surface, scroll_distance, scroll_steps)
.await?;
}
}
SeatMessage::KeyboardKey {
surface,
keymap_id,
key,
pressed,
} => {
if let Some(keyboard) = self.keyboard.get() {
keyboard
.handle_keyboard_key(client, surface, keymap_id, key - 8, pressed)
.await?;
}
}
SeatMessage::TouchDown {
surface,
id,
position,
} => {
if let Some(touch) = self.touch.get() {
touch
.handle_touch_down(client, surface, id, position)
.await?;
}
}
SeatMessage::TouchMove { id, position } => {
if let Some(touch) = self.touch.get() {
touch.handle_touch_move(client, id, position).await?;
}
}
SeatMessage::TouchUp { id } => {
if let Some(touch) = self.touch.get() {
touch.handle_touch_up(client, id).await?;
}
}
SeatMessage::Reset => {
if let Some(pointer) = self.pointer.get() {
pointer.reset(client).await?;
}
if let Some(keyboard) = self.keyboard.get() {
keyboard.reset(client).await?;
}
if let Some(touch) = self.touch.get() {
touch.reset(client).await?;
}
}
}
Ok(())
}
}
impl WlSeat for Seat {
/// https://wayland.app/protocols/wayland#wl_seat:request:get_pointer
async fn get_pointer(
&self,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> Result<()> {
let pointer = client.insert(id, Pointer::new(id));
let _ = self.pointer.set(pointer);
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_seat:request:get_keyboard
async fn get_keyboard(
&self,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> Result<()> {
tracing::info!("Getting keyboard");
let keyboard = client.insert(id, Keyboard::new(id));
let _ = self.keyboard.set(keyboard);
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_seat:request:get_touch
async fn get_touch(
&self,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> Result<()> {
let touch = client.insert(id, Touch(id));
let _ = self.touch.set(touch);
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_seat:request:release
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

39
src/wayland/core/shm.rs Normal file
View File

@@ -0,0 +1,39 @@
use std::os::fd::OwnedFd;
use crate::wayland::core::shm_pool::ShmPool;
pub use waynest::server::protocol::core::wayland::wl_shm::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug, Dispatcher, Default)]
pub struct Shm;
impl Shm {
pub async fn advertise_formats(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
self.format(client, sender_id, Format::Argb8888).await?;
self.format(client, sender_id, Format::Xrgb8888).await?;
Ok(())
}
}
impl WlShm for Shm {
/// https://wayland.app/protocols/wayland#wl_shm:request:create_pool
async fn create_pool(
&self,
client: &mut Client,
_sender_id: ObjectId,
pool_id: ObjectId,
fd: OwnedFd,
size: i32,
) -> Result<()> {
client.insert(pool_id, ShmPool::new(fd, size)?);
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_shm:request:release
async fn release(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

View File

@@ -0,0 +1,102 @@
use super::shm_pool::ShmPool;
use bevy::{
asset::{Assets, Handle, RenderAssetUsages},
image::Image,
render::render_resource::{Extent3d, TextureDimension, TextureFormat},
};
use mint::Vector2;
use std::sync::{Arc, OnceLock};
use waynest::server::protocol::core::wayland::wl_shm::Format;
/// Parameters for a shared memory buffer
#[derive(Debug)]
pub struct ShmBufferBacking {
pool: Arc<ShmPool>,
offset: usize,
stride: usize,
size: Vector2<usize>,
format: Format,
image: OnceLock<Handle<Image>>,
}
impl ShmBufferBacking {
pub fn new(
pool: Arc<ShmPool>,
offset: usize,
stride: usize,
size: Vector2<usize>,
format: Format,
) -> Self {
Self {
pool,
offset,
stride,
size,
format,
image: OnceLock::new(),
}
}
pub fn update_tex(&self, images: &mut Assets<Image>) -> Option<Handle<Image>> {
let image = self.image.get_or_init(|| {
let image = Image::new_fill(
Extent3d {
width: self.size.x as u32,
height: self.size.y as u32,
depth_or_array_layers: 1,
},
TextureDimension::D2,
&[255, 0, 255, 255],
TextureFormat::Rgba8Unorm,
RenderAssetUsages::all(),
);
images.add(image)
});
let src_data_lock = self.pool.data_lock();
let mut src_cursor = self.offset;
// Calculate maximum cursor position needed - stride is already in bytes
let max_cursor = self.offset + (self.size.y * self.stride);
// Check if we have enough data
if max_cursor > src_data_lock.len() {
return None;
}
let dst_data = images.get_mut(image).unwrap().data.get_or_insert_with(|| {
let length = self.size.x as usize * self.size.y as usize * 4;
vec![255; length]
});
let mut dst_cursor = 0;
for _y in 0..self.size.y {
for _x in 0..self.size.x {
match self.format {
Format::Xrgb8888 => {
dst_data[dst_cursor] = src_data_lock[src_cursor + 2]; // Red is byte 2
dst_data[dst_cursor + 1] = src_data_lock[src_cursor + 1]; // Green is byte 1
dst_data[dst_cursor + 2] = src_data_lock[src_cursor]; // Blue is byte 0
dst_data[dst_cursor + 3] = 255; // X means ignore alpha, treat as fully opaque
}
Format::Argb8888 => {
dst_data[dst_cursor] = src_data_lock[src_cursor + 2]; // Red is byte 2
dst_data[dst_cursor + 1] = src_data_lock[src_cursor + 1]; // Green is byte 1
dst_data[dst_cursor + 2] = src_data_lock[src_cursor]; // Blue is byte 0
dst_data[dst_cursor + 3] = src_data_lock[src_cursor + 3]; // Alpha is byte 3
}
_ => panic!("Unsupported format {:?}", self.format),
}
src_cursor += 4;
dst_cursor += 4;
}
src_cursor += self.stride - (self.size.x * 4);
}
Some(image.clone())
}
pub fn size(&self) -> Vector2<usize> {
self.size
}
}

View File

@@ -0,0 +1,74 @@
use memmap2::{MmapOptions, RemapOptions};
use parking_lot::{Mutex, MutexGuard, RawMutex, lock_api::MappedMutexGuard};
use std::os::fd::{IntoRawFd, OwnedFd};
use waynest::{
server::{Client, Dispatcher, Result, protocol::core::wayland::wl_shm::Format},
wire::ObjectId,
};
use crate::wayland::core::buffer::{Buffer, BufferBacking};
pub use waynest::server::protocol::core::wayland::wl_shm_pool::*;
use super::shm_buffer_backing::ShmBufferBacking;
#[derive(Debug, Dispatcher)]
pub struct ShmPool {
inner: Mutex<memmap2::MmapMut>,
}
impl ShmPool {
pub fn new(fd: OwnedFd, size: i32) -> Result<Self> {
let map = unsafe {
MmapOptions::new()
.len(size as usize)
.map_mut(fd.into_raw_fd())?
};
Ok(Self {
inner: Mutex::new(map),
})
}
pub fn data_lock(&self) -> MappedMutexGuard<RawMutex, [u8]> {
MutexGuard::map(self.inner.lock(), |i| i.as_mut())
}
}
impl WlShmPool for ShmPool {
/// https://wayland.app/protocols/wayland#wl_shm_pool:request:create_buffer
async fn create_buffer(
&self,
client: &mut Client,
sender_id: ObjectId,
id: ObjectId,
offset: i32,
width: i32,
height: i32,
stride: i32,
format: Format,
) -> Result<()> {
let params = ShmBufferBacking::new(
client.get::<ShmPool>(sender_id).unwrap(),
offset as usize,
stride as usize,
[width as usize, height as usize].into(),
format,
);
Buffer::new(client, id, BufferBacking::Shm(params));
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_shm_pool:request:resize
async fn resize(&self, _client: &mut Client, _sender_id: ObjectId, size: i32) -> Result<()> {
let mut inner = self.inner.lock();
unsafe { inner.remap(size as usize, RemapOptions::new().may_move(true))? };
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_shm_pool:request:destroy
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

367
src/wayland/core/surface.rs Normal file
View File

@@ -0,0 +1,367 @@
use super::{buffer::Buffer, callback::Callback};
use crate::{
BevyMaterial,
core::registry::Registry,
nodes::{drawable::model::ModelPart, items::panel::Geometry},
wayland::{
Message, MessageSink,
util::{ClientExt, DoubleBuffer},
xdg::{popup::Popup, toplevel::Toplevel},
},
};
use bevy::{
asset::{Assets, Handle},
image::Image,
};
use mint::Vector2;
use parking_lot::Mutex;
use std::sync::{Arc, OnceLock};
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::core::wayland::{wl_output::Transform, wl_surface::*},
},
wire::ObjectId,
};
pub static WL_SURFACE_REGISTRY: Registry<Surface> = Registry::new();
#[derive(Debug, Clone)]
pub enum SurfaceRole {
XdgToplevel(Arc<Toplevel>),
XDGPopup(Arc<Popup>),
}
#[derive(Debug, Clone)]
pub struct SurfaceState {
pub buffer: Option<Arc<Buffer>>,
pub density: f32,
pub geometry: Option<Geometry>,
pub min_size: Option<Vector2<u32>>,
pub max_size: Option<Vector2<u32>>,
clean_lock: OnceLock<()>,
}
impl Default for SurfaceState {
fn default() -> Self {
Self {
buffer: Default::default(),
density: 1.0,
geometry: None,
min_size: None,
max_size: None,
clean_lock: Default::default(),
}
}
}
// if returning false, don't run this callback again... just remove it
pub type OnCommitCallback = Box<dyn Fn(&Surface, &SurfaceState) -> bool + Send + Sync>;
#[derive(Dispatcher)]
pub struct Surface {
pub id: ObjectId,
state: Mutex<DoubleBuffer<SurfaceState>>,
pub message_sink: MessageSink,
pub role: Mutex<Option<SurfaceRole>>,
frame_callback_object: Mutex<Option<Arc<Callback>>>,
on_commit_handlers: Mutex<Vec<OnCommitCallback>>,
material: OnceLock<Handle<BevyMaterial>>,
pending_material_applications: Registry<ModelPart>,
}
impl std::fmt::Debug for Surface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Surface")
.field("state", &self.state)
.field("message_sink", &self.message_sink)
.field("role", &self.role)
.field("frame_callback_object", &self.frame_callback_object)
.field(
"on_commit_handlers",
&format!("<{} handlers>", self.on_commit_handlers.lock().len()),
)
.finish()
}
}
impl Surface {
pub fn new(client: &Client, id: ObjectId) -> Self {
Surface {
id,
state: Default::default(),
message_sink: client.message_sink(),
role: Mutex::new(None),
frame_callback_object: Default::default(),
on_commit_handlers: Mutex::new(Vec::new()),
material: OnceLock::new(),
pending_material_applications: Registry::new(),
}
}
pub fn pending_state(&self) -> parking_lot::MutexGuard<'_, DoubleBuffer<SurfaceState>> {
self.state.lock()
}
pub fn add_commit_handler<F: Fn(&Surface, &SurfaceState) -> bool + Send + Sync + 'static>(
&self,
handler: F,
) {
let mut handlers = self.on_commit_handlers.lock();
handlers.push(Box::new(handler));
}
pub fn update_graphics(
&self,
materials: &mut Assets<BevyMaterial>,
images: &mut Assets<Image>,
) {
let state_lock = self.state.lock();
if state_lock.current().clean_lock.get().is_some() {
// then we don't need to reupload the texture
return;
}
let Some(buffer) = state_lock.current().buffer.clone() else {
return;
};
let material = self.material.get_or_init(|| {
// // Set default shader parameters
// let mut params = mat_wrapper.0.get_all_param_info();
// params.set_vec2("uv_scale", stereokit_rust::maths::Vec2::new(1.0, 1.0));
// params.set_vec2("uv_offset", stereokit_rust::maths::Vec2::new(0.0, 0.0));
// params.set_float("fcFactor", 1.0);
// params.set_float("ripple", 4.0);
// params.set_float("alpha_min", 0.0);
// params.set_float("alpha_max", 1.0);
let material = BevyMaterial::default();
materials.add(material)
});
if let Some(new_tex) = buffer.update_tex(images) {
materials
.get_mut(material)
.unwrap()
.diffuse_texture
.replace(new_tex);
// For SHM buffers, we can release immediately after copying to GPU
if buffer.can_release_after_update() {
let _ = self.message_sink.send(Message::ReleaseBuffer(buffer));
}
}
self.apply_surface_materials();
let _ = state_lock.current().clean_lock.set(());
}
pub fn apply_material(&self, model_part: &Arc<ModelPart>) {
// tracing::info!("uwu applying material");
self.pending_material_applications.add_raw(model_part)
}
fn apply_surface_materials(&self) {
let Some(mat) = self.material.get() else {
return;
};
for model_node in self.pending_material_applications.get_valid_contents() {
model_node.replace_material(mat.clone());
}
self.pending_material_applications.clear();
}
fn mark_dirty(&self) {
self.state.lock().pending.clean_lock = Default::default();
}
pub fn current_state(&self) -> SurfaceState {
self.state.lock().current().clone()
}
pub fn frame_event(&self) {
if let Some(callback_obj) = self.frame_callback_object.lock().take() {
let _ = self.message_sink.send(Message::Frame(callback_obj));
}
}
// pub fn size(&self) -> Option<Vector2<u32>> {
// self.state
// .lock()
// .current()
// .buffer
// .as_ref()
// .map(|b| [b.size.x as u32, b.size.y as u32].into())
// }
// pub async fn release_old_buffer(&self, client: &mut Client) -> Result<()> {
// let (old_buffer, object) = {
// let lock = self.state.lock();
// let Some(old_buffer) = lock.current().buffer.clone() else {
// return Ok(());
// };
// let new_buffer = lock.pending.buffer.as_ref();
// if new_buffer.map(Arc::as_ptr) == Some(Arc::as_ptr(&old_buffer)) {
// return Ok(());
// }
// drop(lock);
// (old_buffer.clone(), old_buffer.id)
// };
// old_buffer.release(client, object).await?;
// Ok(())
// }
}
impl WlSurface for Surface {
/// https://wayland.app/protocols/wayland#wl_surface:request:attach
async fn attach(
&self,
client: &mut Client,
_sender_id: ObjectId,
buffer: Option<ObjectId>,
_x: i32,
_y: i32,
) -> Result<()> {
self.state.lock().pending.buffer = buffer.and_then(|b| client.get::<Buffer>(b));
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:damage
async fn damage(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> Result<()> {
// should be more intelligent about this but for now just make it copy everything to gpu next frame again
self.mark_dirty();
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:frame
async fn frame(
&self,
client: &mut Client,
_sender_id: ObjectId,
callback_id: ObjectId,
) -> Result<()> {
let callback = client.insert(callback_id, Callback(callback_id));
self.frame_callback_object.lock().replace(callback);
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:set_opaque_region
async fn set_opaque_region(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_region: Option<ObjectId>,
) -> Result<()> {
// nothing we can really do to repaint behind this so ignore it
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:set_input_region
async fn set_input_region(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_region: Option<ObjectId>,
) -> Result<()> {
// too complicated to implement this for now so who the hell cares
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:commit
async fn commit(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
{
let mut lock = self.state.lock();
// If we're getting a new buffer and the current one is DMA-BUF, release it
if let Some(new_buffer) = &lock.pending.buffer {
if let Some(current_buffer) = &lock.current().buffer {
// Don't release if it's the same buffer being reused
if !Arc::ptr_eq(new_buffer, current_buffer)
&& !current_buffer.can_release_after_update()
{
let _ = self
.message_sink
.send(Message::ReleaseBuffer(current_buffer.clone()));
}
}
}
let dirty = lock.current().clean_lock.get().is_none()
|| lock.pending.clean_lock.take().is_none();
lock.apply();
if !dirty {
let _ = lock.current().clean_lock.set(());
}
}
let current_state = self.current_state();
let mut handlers = self.on_commit_handlers.lock();
handlers.retain(|f| (f)(self, &current_state));
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:set_buffer_transform
async fn set_buffer_transform(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_transform: Transform,
) -> Result<()> {
// we just don't have the output transform or fullscreen at all so this optimization is never needed
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:set_buffer_scale
async fn set_buffer_scale(
&self,
_client: &mut Client,
_sender_id: ObjectId,
scale: i32,
) -> Result<()> {
self.state.lock().pending.density = scale as f32;
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:damage_buffer
async fn damage_buffer(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> Result<()> {
// we should upload only chunks to the gpu and do subimage copy but that's a lot rn so we won't
self.mark_dirty();
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:offset
async fn offset(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
) -> Result<()> {
Ok(())
}
/// https://wayland.app/protocols/wayland#wl_surface:request:destroy
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}
impl Drop for Surface {
fn drop(&mut self) {
self.role.lock().take();
}
}

75
src/wayland/core/touch.rs Normal file
View File

@@ -0,0 +1,75 @@
use crate::wayland::core::surface::Surface;
use mint::Vector2;
use std::sync::Arc;
pub use waynest::server::protocol::core::wayland::wl_touch::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
use super::seat::fixed_from_f32;
#[derive(Debug, Dispatcher)]
pub struct Touch(pub ObjectId);
impl Touch {
pub async fn handle_touch_down(
&self,
client: &mut Client,
surface: Arc<Surface>,
id: u32,
position: Vector2<f32>,
) -> Result<()> {
let serial = client.next_event_serial();
self.down(
client,
self.0,
serial,
0,
surface.id,
id as i32,
fixed_from_f32(position.x),
fixed_from_f32(position.y),
)
.await?;
self.frame(client, self.0).await
}
pub async fn handle_touch_move(
&self,
client: &mut Client,
id: u32,
position: Vector2<f32>,
) -> Result<()> {
self.motion(
client,
self.0,
0,
id as i32,
fixed_from_f32(position.x),
fixed_from_f32(position.y),
)
.await?;
self.frame(client, self.0).await
}
pub async fn handle_touch_up(&self, client: &mut Client, id: u32) -> Result<()> {
let serial = client.next_event_serial();
self.up(client, self.0, serial, 0, id as i32).await?;
self.frame(client, self.0).await
}
pub async fn reset(&self, client: &mut Client) -> Result<()> {
self.frame(client, self.0).await
}
}
impl WlTouch for Touch {
/// https://wayland.app/protocols/wayland#wl_touch:request:release
async fn release(
&self,
_client: &mut waynest::server::Client,
_sender_id: waynest::wire::ObjectId,
) -> Result<()> {
Ok(())
}
}

View File

@@ -1,100 +0,0 @@
use smithay::reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
protocol::{
wl_data_device::{
Request::{Release, SetSelection, StartDrag},
WlDataDevice,
},
wl_data_device_manager::{
Request::{CreateDataSource, GetDataDevice},
WlDataDeviceManager,
},
wl_data_source::{
Request::{Destroy, Offer, SetActions},
WlDataSource,
},
},
};
use super::state::WaylandState;
impl GlobalDispatch<WlDataDeviceManager, (), WaylandState> for WaylandState {
fn bind(
_state: &mut WaylandState,
_handle: &DisplayHandle,
_client: &Client,
resource: New<WlDataDeviceManager>,
_global_data: &(),
data_init: &mut DataInit<'_, WaylandState>,
) {
let _resource = data_init.init(resource, ());
}
}
impl Dispatch<WlDataDeviceManager, (), WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlDataDeviceManager,
request: <WlDataDeviceManager as Resource>::Request,
_data: &(),
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
CreateDataSource { id } => {
data_init.init(id, ());
}
GetDataDevice { id, seat: _ } => {
data_init.init(id, ());
}
_ => unreachable!(),
}
}
}
impl Dispatch<WlDataSource, (), WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlDataSource,
request: <WlDataSource as Resource>::Request,
_data: &(),
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
Offer { mime_type: _ } => {}
Destroy => {}
SetActions { dnd_actions: _ } => {}
_ => unreachable!(),
}
}
}
impl Dispatch<WlDataDevice, (), WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlDataDevice,
request: <WlDataDevice as Resource>::Request,
_data: &(),
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
StartDrag {
source: _,
origin: _,
icon: _,
serial: _,
} => {}
SetSelection {
source: _,
serial: _,
} => {}
Release => {}
_ => unreachable!(),
}
}
}

View File

@@ -1,97 +0,0 @@
use super::state::WaylandState;
use smithay::{
delegate_kde_decoration,
reexports::{
wayland_protocols::xdg::{
decoration::zv1::server::{
zxdg_decoration_manager_v1::{self, ZxdgDecorationManagerV1},
zxdg_toplevel_decoration_v1::{self, Mode, ZxdgToplevelDecorationV1},
},
shell::server::xdg_toplevel::XdgToplevel,
},
wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration::{
Mode as KdeMode, OrgKdeKwinServerDecoration,
},
wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum, Weak,
protocol::wl_surface::WlSurface,
},
},
wayland::shell::{self, kde::decoration::KdeDecorationHandler},
};
impl GlobalDispatch<ZxdgDecorationManagerV1, (), WaylandState> for WaylandState {
fn bind(
_state: &mut WaylandState,
_handle: &DisplayHandle,
_client: &Client,
resource: New<ZxdgDecorationManagerV1>,
_global_data: &(),
data_init: &mut DataInit<'_, WaylandState>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<ZxdgDecorationManagerV1, (), WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &ZxdgDecorationManagerV1,
request: zxdg_decoration_manager_v1::Request,
_data: &(),
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
zxdg_decoration_manager_v1::Request::Destroy => (),
zxdg_decoration_manager_v1::Request::GetToplevelDecoration { id, toplevel } => {
data_init.init(id, toplevel.downgrade());
}
_ => unreachable!(),
}
}
}
impl Dispatch<ZxdgToplevelDecorationV1, Weak<XdgToplevel>, WaylandState> for WaylandState {
fn request(
_state: &mut WaylandState,
_client: &Client,
resource: &ZxdgToplevelDecorationV1,
request: zxdg_toplevel_decoration_v1::Request,
_data: &Weak<XdgToplevel>,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
zxdg_toplevel_decoration_v1::Request::SetMode { mode: _ } => {
resource.configure(Mode::ServerSide);
}
zxdg_toplevel_decoration_v1::Request::UnsetMode => {
resource.configure(Mode::ServerSide);
}
zxdg_toplevel_decoration_v1::Request::Destroy => (),
_ => unreachable!(),
}
}
}
impl KdeDecorationHandler for WaylandState {
fn kde_decoration_state(&self) -> &shell::kde::decoration::KdeDecorationState {
&self.kde_decoration_state
}
fn new_decoration(&mut self, _surface: &WlSurface, decoration: &OrgKdeKwinServerDecoration) {
decoration.mode(KdeMode::Server);
}
fn request_mode(
&mut self,
_surface: &WlSurface,
decoration: &OrgKdeKwinServerDecoration,
mode: WEnum<KdeMode>,
) {
let Ok(mode) = mode.into_result() else { return };
decoration.mode(mode);
}
}
delegate_kde_decoration!(WaylandState);

View File

@@ -0,0 +1,200 @@
use super::buffer_params::BufferParams;
use crate::wayland::{GraphicsInfo, Message, MessageSink, core::buffer::Buffer};
use drm_fourcc::DrmFourcc;
use khronos_egl::{self as egl, ClientBuffer};
use mint::Vector2;
use std::{
os::fd::AsRawFd,
sync::{Arc, OnceLock},
};
use stereokit_rust::tex::{Tex, TexFormat, TexType};
use waynest::server::protocol::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::Flags;
// EGL extension constants for DMA-BUF
const EGL_WIDTH: i32 = 0x3057;
const EGL_HEIGHT: i32 = 0x3056;
const EGL_LINUX_DRM_FOURCC_EXT: i32 = 0x3272;
const EGL_DMA_BUF_PLANE0_FD_EXT: i32 = 0x3373;
const EGL_DMA_BUF_PLANE0_OFFSET_EXT: i32 = 0x3273;
const EGL_DMA_BUF_PLANE0_PITCH_EXT: i32 = 0x3275;
const EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT: i32 = 0x3443;
const EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT: i32 = 0x3444;
const EGL_LINUX_DMA_BUF_EXT: i32 = 0x3270;
const EGL_NO_BUFFER: *mut std::ffi::c_void = std::ptr::null_mut();
/// Parameters for a shared memory buffer
#[derive(Debug)]
pub struct DmabufBacking {
params: Arc<BufferParams>,
message_sink: Option<MessageSink>,
size: Vector2<usize>,
format: DrmFourcc,
_flags: Flags,
tex: OnceLock<Tex>,
}
impl DmabufBacking {
pub fn new(
params: Arc<BufferParams>,
message_sink: Option<MessageSink>,
size: Vector2<usize>,
format: DrmFourcc,
flags: Flags,
) -> Self {
tracing::info!(
"Creating new DmabufBacking with BufferParams {:?}",
params.id
);
Self {
params,
message_sink,
size,
format,
_flags: flags,
tex: OnceLock::new(),
}
}
fn import_dmabuf(&self, graphics_info: &mut GraphicsInfo) -> Result<Tex, khronos_egl::Error> {
// Sanity check for required EGL extensions
let extensions = graphics_info
.instance
.query_string(Some(graphics_info.display), egl::EXTENSIONS)?;
let extensions_str = extensions.to_string_lossy();
let extensions_list: Vec<&str> = extensions_str.split_whitespace().collect();
if !extensions_list.contains(&"EGL_EXT_image_dma_buf_import") {
tracing::error!("EGL extension EGL_EXT_image_dma_buf_import is not supported");
return Err(khronos_egl::Error::BadParameter);
}
if !extensions_list.contains(&"EGL_EXT_image_dma_buf_import_modifiers") {
tracing::error!(
"EGL extension EGL_EXT_image_dma_buf_import_modifiers is not supported"
);
return Err(khronos_egl::Error::BadParameter);
}
let mut tex = Tex::new(
TexType::ImageNomips | TexType::Dynamic,
TexFormat::RGBA32,
nanoid::nanoid!(),
);
tracing::info!(format=?self.format, "Wayland: Updating DMABuf tex");
// Get plane info from params
let planes = self.params.lock_planes();
let Some(plane) = planes.get(&0) else {
tracing::error!(
"Wayland: Failed to get plane 0 from BufferParams {:?}",
self.params.id
);
return Err(khronos_egl::Error::BadParameter);
};
tracing::info!(
"Using plane 0 with fd {} from BufferParams {:?}",
plane.fd.as_raw_fd(),
self.params.id
);
// Create EGL image
let image = match graphics_info.instance.create_image(
graphics_info.display,
graphics_info.context,
EGL_LINUX_DMA_BUF_EXT as u32,
unsafe { ClientBuffer::from_ptr(EGL_NO_BUFFER) },
&[
EGL_LINUX_DRM_FOURCC_EXT as usize,
self.format as usize,
EGL_DMA_BUF_PLANE0_FD_EXT as usize,
plane.fd.as_raw_fd() as usize, // EGL will dup() this fd internally
EGL_DMA_BUF_PLANE0_OFFSET_EXT as usize,
plane.offset as usize,
EGL_DMA_BUF_PLANE0_PITCH_EXT as usize,
plane.stride as usize,
egl::ATTRIB_NONE,
],
) {
Ok(image) => image,
Err(e) => {
tracing::error!(
"Wayland: Failed to create EGL image. Error: {:?}, Params: size=({:?}, {:?}), format={:?}, fd={}, stride={}, offset={}",
e,
self.size.x,
self.size.y,
self.format,
plane.fd.as_raw_fd(),
plane.stride,
plane.offset
);
return Err(e);
}
};
// The cloned fd will be consumed by create_image, so we don't need to explicitly close it
// Create and bind GL texture
let mut gl_tex = 0;
unsafe {
gl::GenTextures(1, &mut gl_tex);
if gl_tex == 0 {
tracing::error!("Wayland: Failed to generate GL texture.");
return Err(khronos_egl::Error::BadParameter);
}
gl::BindTexture(gl::TEXTURE_2D, gl_tex);
}
// Set the native texture handle directly
// Mesa will handle the OES texture implicitly
unsafe {
tex.set_native_surface(
gl_tex as *mut std::os::raw::c_void,
TexType::ImageNomips | TexType::Dynamic,
0x8058, // GL_RGBA8
self.size.x as i32,
self.size.y as i32,
1, // single surface
true, // we own this texture
)
};
// Clean up EGL image
if let Err(e) = graphics_info
.instance
.destroy_image(graphics_info.display, image)
{
tracing::error!("Wayland: Failed to destroy EGL image. Error: {:?}", e);
}
Ok(tex)
}
pub fn init_tex(&self, graphics_info: &Arc<GraphicsInfo>, buffer: Arc<Buffer>) {
if self.tex.get().is_none() {
match self.import_dmabuf(graphics_info) {
Ok(tex) => {
let _ = self.tex.set(tex);
let _ = self
.message_sink
.as_ref()
.unwrap()
.send(Message::DmabufImportSuccess(self.params.clone(), buffer));
}
Err(e) => {
tracing::error!("Wayland: Error initializing DMABuf tex: {:?}", e);
let _ = self
.message_sink
.as_ref()
.unwrap()
.send(Message::DmabufImportFailure(self.params.clone()));
}
};
}
}
pub fn get_tex(&self) -> Option<&Tex> {
self.tex.get()
}
pub fn size(&self) -> Vector2<usize> {
self.size
}
}

View File

@@ -0,0 +1,163 @@
use super::buffer_backing::DmabufBacking;
use crate::wayland::{
core::buffer::{Buffer, BufferBacking},
util::ClientExt,
};
use drm_fourcc::DrmFourcc;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use std::os::fd::{AsRawFd, OwnedFd};
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::{
Flags, ZwpLinuxBufferParamsV1,
},
},
wire::ObjectId,
};
/// A single plane in a DMA-BUF buffer
#[derive(Debug)]
pub struct DmabufPlane {
pub fd: OwnedFd,
pub offset: u32,
pub stride: u32,
pub _modifier: u64,
}
/// Parameters for creating a DMA-BUF-based wl_buffer
///
/// This is a temporary object that collects dmabufs and other parameters
/// that together form a single logical buffer. The object may eventually
/// create one wl_buffer unless cancelled by destroying it.
#[derive(Debug, Dispatcher)]
pub struct BufferParams {
pub id: ObjectId,
planes: Mutex<FxHashMap<u32, DmabufPlane>>,
}
impl BufferParams {
pub fn new(id: ObjectId) -> Self {
tracing::info!("Creating new BufferParams with id {:?}", id);
Self {
id,
planes: Mutex::new(FxHashMap::default()),
}
}
pub fn lock_planes(&self) -> parking_lot::MutexGuard<'_, FxHashMap<u32, DmabufPlane>> {
self.planes.lock()
}
}
impl ZwpLinuxBufferParamsV1 for BufferParams {
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
tracing::info!("Destroying BufferParams {:?}", self.id);
Ok(())
}
async fn add(
&self,
_client: &mut Client,
_sender_id: ObjectId,
fd: OwnedFd,
plane_idx: u32,
offset: u32,
stride: u32,
modifier_hi: u32,
modifier_lo: u32,
) -> Result<()> {
let fd_num = fd.as_raw_fd();
tracing::info!(
"Adding plane {} with fd {} to BufferParams {:?}",
plane_idx,
fd_num,
self.id
);
let mut planes = self.planes.lock();
// Check if plane index is already set
if planes.contains_key(&plane_idx) {
tracing::error!(
"Plane {} already exists in BufferParams {:?}",
plane_idx,
self.id
);
return Err(waynest::server::Error::MissingObject(self.id));
}
// Create plane with the provided parameters
let plane = DmabufPlane {
fd,
offset,
stride,
_modifier: ((modifier_hi as u64) << 32) | (modifier_lo as u64),
};
// Store the plane
planes.insert(plane_idx, plane);
Ok(())
}
async fn create(
&self,
client: &mut Client,
_sender_id: ObjectId,
width: i32,
height: i32,
format: u32,
flags: Flags,
) -> Result<()> {
tracing::info!("Creating buffer from BufferParams {:?}", self.id);
// Create the buffer with DMA-BUF backing using self as the backing
let size = [width as usize, height as usize].into();
let backing = DmabufBacking::new(
client.get::<Self>(self.id).unwrap(),
Some(client.display().message_sink.clone()),
size,
DrmFourcc::try_from(format).unwrap(),
flags,
);
let id = client.display().next_server_id();
Buffer::new(client, id, BufferBacking::Dmabuf(backing));
Ok(())
}
async fn create_immed(
&self,
client: &mut Client,
_sender_id: ObjectId,
buffer_id: ObjectId,
width: i32,
height: i32,
format: u32,
flags: Flags,
) -> Result<()> {
// Create the buffer with DMA-BUF backing using self as the backing
let size = [width as usize, height as usize].into();
let backing = DmabufBacking::new(
client.get::<Self>(self.id).unwrap(),
None,
size,
DrmFourcc::try_from(format).unwrap(),
flags,
);
Buffer::new(client, buffer_id, BufferBacking::Dmabuf(backing));
Ok(())
}
}
impl Drop for BufferParams {
fn drop(&mut self) {
let planes = self.planes.get_mut();
tracing::info!("BufferParams being dropped with {} planes", planes.len());
for (idx, plane) in planes.iter() {
tracing::info!("Dropping plane {} with fd {}", idx, plane.fd.as_raw_fd());
}
planes.clear();
}
}

View File

@@ -0,0 +1,92 @@
use drm_fourcc::DrmFourcc;
use memfd::MemfdOptions;
use std::{
io::Write,
os::fd::{FromRawFd, IntoRawFd, OwnedFd},
};
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::stable::linux_dmabuf_v1::zwp_linux_dmabuf_feedback_v1::{
TrancheFlags, ZwpLinuxDmabufFeedbackV1,
},
},
wire::ObjectId,
};
#[derive(Debug, Dispatcher)]
pub struct DmabufFeedback;
impl DmabufFeedback {
pub async fn send_params(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
// Send format table first
self.send_format_table(client, sender_id).await?;
// let graphics_info = &client.display().graphics_info;
// Get the DRM device file path using the new method
// let device_file = graphics_info.get_drm_device_file_path()?;
// let dev_stat = std::fs::metadata(device_file)?;
// let dev_id = dev_stat.rdev().to_ne_bytes().to_vec();
// self.main_device(client, sender_id, dev_id.clone()).await?;
// Send single tranche with same device since we only support the main GPU
// self.tranche_target_device(client, sender_id, dev_id)
// .await?;
// We only have one format at index 0
let indices = vec![0u16]
.into_iter()
.flat_map(|i| i.to_ne_bytes())
.collect();
self.tranche_formats(client, sender_id, indices).await?;
// No special flags needed for simple EGL texture usage
self.tranche_flags(client, sender_id, TrancheFlags::empty())
.await?;
// Mark tranche complete
self.tranche_done(client, sender_id).await?;
// Mark overall feedback complete
self.done(client, sender_id).await?;
Ok(())
}
pub async fn send_format_table(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
// Create a temporary file for the format table
let size = 16u32; // One format+modifier pair
let mfd = MemfdOptions::default()
.create("stardustxr-format-table")
.map_err(|e| waynest::server::Error::Custom(e.to_string()))?;
mfd.as_file().set_len(size as u64)?;
// Format + modifier pair (16 bytes):
// - format: u32
// - padding: 4 bytes
// - modifier: u64
let format = DrmFourcc::Xrgb8888 as u32; // This is what clients typically want
let modifier: u64 = 0; // Linear modifier
// Write the format+modifier pair
mfd.as_file().write_all(&format.to_ne_bytes())?;
mfd.as_file().write_all(&modifier.to_ne_bytes())?;
self.format_table(
client,
sender_id,
unsafe { OwnedFd::from_raw_fd(mfd.into_raw_fd()) },
size,
)
.await?;
Ok(())
}
}
impl ZwpLinuxDmabufFeedbackV1 for DmabufFeedback {
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

108
src/wayland/dmabuf/mod.rs Normal file
View File

@@ -0,0 +1,108 @@
pub mod buffer_backing;
pub mod buffer_params;
pub mod feedback;
use buffer_params::BufferParams;
use drm_fourcc::DrmFourcc;
use feedback::DmabufFeedback;
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::stable::linux_dmabuf_v1::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
},
wire::ObjectId,
};
use crate::core::registry::Registry;
/// Main DMA-BUF interface implementation
///
/// This interface allows clients to create wl_buffers from DMA-BUFs.
/// It handles:
/// - Format/modifier advertisement
/// - Buffer parameter creation
/// - Default/surface-specific feedback
///
/// The implementation ensures:
/// - Coherency for read access in dmabuf data
/// - Proper lifetime management of dmabuf file descriptors
/// - Safe handling of buffer attachments
#[derive(Dispatcher)]
pub struct Dmabuf {
// Track supported formats and modifiers
// formats: Mutex<FxHashSet<DrmFormat>>,
// Track active buffer parameters objects by their ID
active_params: Registry<BufferParams>,
}
impl Dmabuf {
/// Create a new DMA-BUF interface instance
pub fn new() -> Self {
// let mut formats = FxHashSet::default();
Self {
// formats: Mutex::new(formats),
active_params: Registry::new(),
}
}
pub async fn send_modifiers(&self, client: &mut Client, sender_id: ObjectId) -> Result<()> {
let format = DrmFourcc::Xrgb8888 as u32;
let modifier_hi = 0u32; // Linear modifier high 32 bits
let modifier_lo = 0u32; // Linear modifier low 32 bits
self.modifier(client, sender_id, format, modifier_hi, modifier_lo)
.await?;
Ok(())
}
/// Remove a buffer parameters object from tracking
pub(crate) fn remove_params(&self, params_id: ObjectId) {
self.active_params.retain(|params| params.id != params_id);
}
}
impl ZwpLinuxDmabufV1 for Dmabuf {
async fn destroy(&self, _client: &mut Client, sender_id: ObjectId) -> Result<()> {
self.remove_params(sender_id);
Ok(())
}
async fn create_params(
&self,
client: &mut Client,
_sender_id: ObjectId,
params_id: ObjectId,
) -> Result<()> {
// Create new buffer parameters object
let params = client.insert(params_id, BufferParams::new(params_id));
self.active_params.add_raw(&params);
Ok(())
}
async fn get_default_feedback(
&self,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> Result<()> {
// Create feedback object for default (non-surface-specific) settings
let feedback = client.insert(id, DmabufFeedback);
feedback.send_params(client, id).await?;
Ok(())
}
async fn get_surface_feedback(
&self,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
_surface: ObjectId,
) -> Result<()> {
// Create feedback object for surface-specific settings
// Note: Surface-specific feedback could be optimized based on the surface's
// requirements, but for now we use the same feedback as default
let feedback = client.insert(id, DmabufFeedback);
feedback.send_params(client, id).await?;
Ok(())
}
}

View File

@@ -1,144 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
// Re-export only the actual code, and then only use this re-export
// The `generated` module below is just some boilerplate to properly isolate stuff
// and avoid exposing internal details.
//
// You can use all the types from my_protocol as if they went from `wayland_client::protocol`.
pub use generated::wl_drm;
#[allow(non_upper_case_globals, non_camel_case_types)]
mod generated {
use smithay::reexports::wayland_server::{self, protocol::*};
pub mod __interfaces {
use smithay::reexports::wayland_server::protocol::__interfaces::*;
wayland_scanner::generate_interfaces!("src/wayland/wayland-drm.xml");
}
use self::__interfaces::*;
wayland_scanner::generate_server_code!("src/wayland/wayland-drm.xml");
}
use super::state::WaylandState;
use smithay::{
backend::allocator::{
Fourcc, Modifier,
dmabuf::{Dmabuf, DmabufFlags},
},
reexports::wayland_server::{
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
},
};
use std::convert::TryFrom;
impl GlobalDispatch<wl_drm::WlDrm, (), WaylandState> for WaylandState {
fn bind(
state: &mut WaylandState,
_dh: &DisplayHandle,
_client: &Client,
resource: New<wl_drm::WlDrm>,
_global_data: &(),
data_init: &mut DataInit<'_, WaylandState>,
) {
let drm_instance = data_init.init(resource, ());
drm_instance.device("/dev/dri/renderD128".to_string());
if drm_instance.version() >= 2 {
drm_instance.capabilities(wl_drm::Capability::Prime as u32);
}
for format in state.drm_formats.iter() {
if let Ok(converted) = wl_drm::Format::try_from(*format as u32) {
drm_instance.format(converted as u32);
}
}
}
fn can_view(_client: Client, _global_dataa: &()) -> bool {
true
}
}
impl Dispatch<wl_drm::WlDrm, (), WaylandState> for WaylandState {
fn request(
state: &mut WaylandState,
_client: &Client,
drm: &wl_drm::WlDrm,
request: wl_drm::Request,
_data: &(),
_dh: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
wl_drm::Request::Authenticate { .. } => drm.authenticated(),
wl_drm::Request::CreateBuffer { .. } => drm.post_error(
wl_drm::Error::InvalidName,
String::from("Flink handles are unsupported, use PRIME"),
),
wl_drm::Request::CreatePlanarBuffer { .. } => drm.post_error(
wl_drm::Error::InvalidName,
String::from("Flink handles are unsupported, use PRIME"),
),
wl_drm::Request::CreatePrimeBuffer {
id,
name,
width,
height,
format,
offset0,
stride0,
..
} => {
let format = match Fourcc::try_from(format) {
Ok(format) => {
if !state.drm_formats.contains(&format) {
drm.post_error(
wl_drm::Error::InvalidFormat,
String::from("Format not advertised by wl_drm"),
);
return;
}
format
}
Err(_) => {
drm.post_error(
wl_drm::Error::InvalidFormat,
String::from("Format unknown / not advertised by wl_drm"),
);
return;
}
};
if width < 1 || height < 1 {
drm.post_error(
wl_drm::Error::InvalidFormat,
String::from("width or height not positive"),
);
return;
}
let mut dma = Dmabuf::builder(
(width, height),
format,
Modifier::Invalid,
DmabufFlags::empty(),
);
dma.add_plane(name, 0, offset0 as u32, stride0 as u32);
match dma.build() {
Some(dmabuf) => {
state.dmabuf_tx.send((dmabuf.clone(), None)).unwrap();
data_init.init(id, dmabuf);
}
None => {
// Buffer import failed. The protocol documentation heavily implies killing the
// client is the right thing to do here.
drm.post_error(
wl_drm::Error::InvalidName,
"dmabuf global was destroyed on server",
);
}
}
}
}
}
}

View File

@@ -1,209 +1,306 @@
mod compositor;
mod data_device;
mod decoration;
mod seat;
mod state;
mod surface;
// mod xdg_activation;
mod drm;
mod utils;
mod xdg_shell;
pub mod core;
#[cfg(feature = "dmabuf")]
pub mod dmabuf;
pub mod util;
pub mod xdg;
use self::{state::WaylandState, surface::CORE_SURFACES};
use crate::{core::task, wayland::state::ClientState};
use color_eyre::eyre::{Result, ensure};
use parking_lot::Mutex;
use smithay::{
backend::{
allocator::dmabuf::Dmabuf,
egl::EGLContext,
renderer::{ImportDma, Renderer, gles::GlesRenderer},
use crate::wayland::core::seat::SeatMessage;
use crate::{
BevyMaterial,
core::{
error::{Result, ServerError},
graphics_info::GraphicsInfo,
task,
},
output::Output,
reexports::wayland_server::{Display, DisplayHandle, ListeningSocket},
wayland::dmabuf,
};
use bevy::{asset::Assets, ecs::resource::Resource, image::Image};
use cluFlock::ToFlock;
use core::{
buffer::{BUFFER_REGISTRY, Buffer},
callback::Callback,
display::Display,
surface::WL_SURFACE_REGISTRY,
};
#[cfg(feature = "dmabuf")]
use dmabuf::buffer_params::BufferParams;
use mint::Vector2;
use std::{
ffi::{OsStr, c_void},
os::fd::AsFd,
fs::{self, OpenOptions},
io::{self, ErrorKind},
os::unix::fs::OpenOptionsExt,
path::PathBuf,
sync::{Arc, OnceLock},
};
use stereokit_rust::system::{Backend, BackendGraphics};
use tokio::{
io::unix::AsyncFd,
sync::{
Notify,
mpsc::{self, UnboundedReceiver},
use tokio::{net::UnixStream, sync::mpsc, task::AbortHandle};
use tokio_stream::StreamExt;
use tracing::{debug_span, instrument};
#[cfg(feature = "dmabuf")]
use waynest::server::protocol::stable::linux_dmabuf_v1::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1;
use waynest::{
server::{
self,
protocol::{
core::wayland::{wl_buffer::WlBuffer, wl_callback::WlCallback, wl_display::WlDisplay},
stable::xdg_shell::xdg_toplevel::XdgToplevel,
},
},
task::AbortHandle,
wire::{DecodeError, ObjectId},
};
use tracing::{debug_span, info, instrument};
use xdg::toplevel::Toplevel;
pub static WAYLAND_DISPLAY: OnceLock<String> = OnceLock::new();
pub static WAYLAND_DISPLAY: OnceLock<PathBuf> = OnceLock::new();
struct EGLRawHandles {
display: *const c_void,
config: *const c_void,
context: *const c_void,
impl From<waynest::server::Error> for ServerError {
fn from(err: waynest::server::Error) -> Self {
ServerError::WaylandError(err)
}
}
fn get_sk_egl() -> Result<EGLRawHandles> {
ensure!(
Backend::graphics() == BackendGraphics::OpenGLESEGL,
"StereoKit is not running using EGL!"
);
Ok(unsafe {
EGLRawHandles {
display: stereokit_rust::system::backend_opengl_egl_get_display() as *const c_void,
config: stereokit_rust::system::backend_opengl_egl_get_config() as *const c_void,
context: stereokit_rust::system::backend_opengl_egl_get_context() as *const c_void,
pub fn get_free_wayland_socket_path() -> Option<PathBuf> {
// Use XDG runtime directory for secure, user-specific sockets
let base_dirs = directories::BaseDirs::new()?;
let runtime_dir = base_dirs.runtime_dir()?;
// Iterate through conventional display numbers (matches X11 behavior)
for display in 0..=32 {
let socket_path = runtime_dir.join(format!("wayland-{display}"));
let socket_lock_path = runtime_dir.join(format!("wayland-{display}.lock"));
// Open lock file without truncation to preserve existing locks
let mut _lock = OpenOptions::new()
.create(true)
.truncate(false) // Prevent destroying other processes' locks
.read(true)
.write(true)
.mode(0o660) // Match Wayland-compositor permissions
.open(&socket_lock_path)
.ok()?;
// Atomic mutual exclusion: fail if another process holds the lock
if _lock.try_exclusive_lock().is_err() {
continue; // Lock held by active compositor
}
})
// Check for zombie sockets (file exists but nothing listening)
if socket_path.exists() {
match std::os::unix::net::UnixStream::connect(&socket_path) {
Ok(_) => continue, // Active compositor found - skip
Err(e) if e.kind() == ErrorKind::ConnectionRefused => {
// Stale socket - safe to remove since we hold the lock
let _ = fs::remove_file(&socket_path);
}
Err(_) => continue, // Transient error - conservative skip
}
}
// Found viable candidate: lock held, socket cleared/available
return Some(socket_path);
}
None // Exhausted all conventional display numbers
}
pub enum Message {
Frame(Arc<Callback>),
ReleaseBuffer(Arc<Buffer>),
#[cfg(feature = "dmabuf")]
DmabufImportSuccess(Arc<BufferParams>, Arc<Buffer>),
#[cfg(feature = "dmabuf")]
DmabufImportFailure(Arc<BufferParams>),
CloseToplevel(Arc<Toplevel>),
ResizeToplevel {
toplevel: Arc<Toplevel>,
size: Option<Vector2<u32>>,
},
SetToplevelVisualActive {
toplevel: Arc<Toplevel>,
active: bool,
},
Seat(SeatMessage),
}
pub type MessageSink = mpsc::UnboundedSender<Message>;
#[derive(Debug)]
struct WaylandClient {
abort_handle: AbortHandle,
}
impl WaylandClient {
pub fn from_stream(socket: UnixStream) -> Result<Self> {
let pid = socket.peer_cred().ok().and_then(|c| c.pid());
let mut client = server::Client::new(socket)?;
let (message_sink, message_source) = mpsc::unbounded_channel();
client.insert(ObjectId::DISPLAY, Display::new(message_sink, pid));
let abort_handle = task::new(
|| "wayland client",
Self::handle_client_messages(client, message_source),
)?
.abort_handle();
Ok(WaylandClient { abort_handle })
}
async fn handle_client_messages(
mut client: server::Client,
mut render_message_rx: mpsc::UnboundedReceiver<Message>,
) -> Result<()> {
loop {
tokio::select! {
// send all queued up messages
msg = render_message_rx.recv() => {
if let Some(msg) = msg {
Self::handle_render_message(&mut client, msg).await?;
}
}
// handle the next message
msg = client.next_message() => {
match msg {
Ok(Some(mut msg)) => {
if let Err(e) = client.handle_message(&mut msg).await {
tracing::error!("Wayland: Error handling message: {:?}", e);
break;
}
}
Err(e) => {
// wayland clients really aren't nice when disconnecting properly, are they? :p
if let server::Error::Decode(DecodeError::IoError(e)) = &e {
if e.kind() == io::ErrorKind::ConnectionReset {
if let Some(pid) = client.get::<Display>(ObjectId::DISPLAY).and_then(|d| d.pid) {
tracing::info!("Wayland: Client with pid: {pid} disconnected from server");
} else {
tracing::info!("Wayland: Unknown client disconnected from server");
}
break;
}
}
tracing::error!("Wayland: Error reading message: {:?}", e);
break;
}
Ok(None) => {
if let Some(pid) = client.get::<Display>(ObjectId::DISPLAY).and_then(|d| d.pid) {
tracing::info!("Wayland: Client with pid: {pid} disconnected from server");
} else {
tracing::info!("Wayland: Unknown client disconnected from server");
}
// Message stream ended
break;
}
}
}
}
}
Ok(())
}
async fn handle_render_message(
client: &mut server::Client,
message: Message,
) -> Result<(), waynest::server::Error> {
match message {
Message::Frame(callback) => {
let serial = client.next_event_serial();
callback.done(client, callback.0, serial).await?;
client
.get::<Display>(ObjectId::DISPLAY)
.unwrap()
.delete_id(client, ObjectId::DISPLAY, callback.0.as_raw())
.await?;
client.remove(callback.0);
Ok(())
}
#[cfg(feature = "dmabuf")]
Message::DmabufImportSuccess(params, buffer) => {
params.created(client, params.id, buffer.id).await
}
#[cfg(feature = "dmabuf")]
Message::DmabufImportFailure(params) => {
client.remove(params.id);
params.failed(client, params.id).await
}
Message::ReleaseBuffer(buffer) => buffer.release(client, buffer.id).await,
Message::CloseToplevel(toplevel) => toplevel.close(client, toplevel.id).await,
Message::ResizeToplevel { toplevel, size } => {
toplevel.set_size(size);
toplevel.reconfigure(client).await
}
Message::SetToplevelVisualActive { toplevel, active } => {
toplevel.set_activated(active);
toplevel.reconfigure(client).await
}
Message::Seat(seat_message) => {
if let Some(seat) = client.get::<Display>(ObjectId::DISPLAY).unwrap().seat.get() {
seat.handle_message(client, seat_message).await?;
}
Ok(())
}
}
}
}
impl Drop for WaylandClient {
fn drop(&mut self) {
self.abort_handle.abort();
}
}
#[derive(Debug, Resource)]
pub struct Wayland {
flush_notify: Arc<Notify>,
client_listener: AbortHandle,
client_dispatcher: AbortHandle,
renderer: GlesRenderer,
output: Output,
dmabuf_rx: UnboundedReceiver<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
abort_handle: AbortHandle,
}
impl Wayland {
pub fn new() -> Result<Self> {
let egl_raw_handles = get_sk_egl()?;
let renderer = unsafe {
GlesRenderer::new(EGLContext::from_raw(
egl_raw_handles.display,
egl_raw_handles.config,
egl_raw_handles.context,
)?)?
pub fn new(socket_path: Option<PathBuf>) -> Result<Self> {
let socket_path = if let Some(path) = socket_path {
path
} else {
get_free_wayland_socket_path().ok_or(ServerError::WaylandError(
waynest::server::Error::IoError(std::io::ErrorKind::AddrNotAvailable.into()),
))?
};
let display: Display<WaylandState> = Display::new()?;
let display_handle = display.handle();
let _ = WAYLAND_DISPLAY.set(socket_path.clone());
let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel();
let listener =
server::Listener::new_with_path(&socket_path).map_err(ServerError::WaylandError)?;
let wayland_state = WaylandState::new(display_handle.clone(), &renderer, dmabuf_tx);
let output = wayland_state.lock().output.clone();
let abort_handle =
task::new(|| "wayland loop", Self::handle_wayland_loop(listener))?.abort_handle();
let socket = ListeningSocket::bind_auto("wayland", 0..33)?;
let socket_name = socket
.socket_name()
.and_then(OsStr::to_str)
.map(ToString::to_string);
if let Some(socket_name) = &socket_name {
let _ = WAYLAND_DISPLAY.set(socket_name.clone());
}
info!(socket_name, "Wayland active");
let flush_notify = Arc::new(Notify::new());
let client_listener = task::new(
|| "Wayland client listener loop",
Wayland::client_listener_loop(display_handle, socket, wayland_state.clone()),
)?
.abort_handle();
let client_dispatcher = task::new(
|| "Wayland dispatch client loop",
Wayland::dispatch_client_loop(display, flush_notify.clone(), wayland_state),
)?
.abort_handle();
Ok(Wayland {
flush_notify,
client_listener,
client_dispatcher,
renderer,
output,
dmabuf_rx,
})
Ok(Self { abort_handle })
}
async fn client_listener_loop(
mut display_handle: DisplayHandle,
socket: ListeningSocket,
state: Arc<Mutex<WaylandState>>,
) -> Result<()> {
let async_fd = AsyncFd::new(socket.as_fd())?;
async fn handle_wayland_loop(mut listener: server::Listener) -> Result<()> {
let mut clients = Vec::new();
loop {
let mut guard = async_fd.readable().await?;
let Ok(Some(stream)) = socket.accept() else {
guard.clear_ready();
continue;
};
let stream = tokio::net::UnixStream::from_std(stream)?;
let pid = stream.peer_cred().ok().and_then(|c| c.pid());
// New client connected
let client_state = Arc::new(ClientState {
pid,
id: OnceLock::new(),
compositor_state: Default::default(),
seat: state.lock().seat.clone(),
});
let _client = display_handle.insert_client(stream.into_std()?, client_state.clone())?;
}
}
async fn dispatch_client_loop(
mut display: Display<WaylandState>,
flush_notify: Arc<Notify>,
state: Arc<Mutex<WaylandState>>,
) -> std::io::Result<()> {
loop {
let poll_fd = display.backend().poll_fd();
let async_fd = AsyncFd::new(poll_fd)?;
tokio::select! {
biased;
_ = async_fd.readable() => {
drop(async_fd);
let _span = debug_span!("Dispatch wayland event");
let _span = _span.enter();
let _ = display.dispatch_clients(&mut *state.lock());
let _ = display.flush_clients();
}
_ = flush_notify.notified() => {
drop(async_fd);
let _ = display.flush_clients();
},
if let Ok(Some(stream)) = listener.try_next().await {
debug_span!("Accept wayland client").in_scope(|| {
if let Ok(client) = WaylandClient::from_stream(stream) {
clients.push(client);
}
});
}
clients.retain(|client| !client.abort_handle.is_finished());
}
#[allow(unreachable_code)]
Ok(())
}
pub fn early_frame(graphics_info: &mut GraphicsInfo) {
for buffer in BUFFER_REGISTRY.get_valid_contents() {
buffer.init_tex(graphics_info);
}
for surface in WL_SURFACE_REGISTRY.get_valid_contents() {
surface.frame_event();
}
}
#[instrument(level = "debug", name = "Wayland frame", skip(self))]
pub fn update(&mut self) {
while let Ok((dmabuf, notifier)) = self.dmabuf_rx.try_recv() {
if self.renderer.import_dmabuf(&dmabuf, None).is_err() {
if let Some(notifier) = notifier {
notifier.failed();
}
}
}
for core_surface in CORE_SURFACES.get_valid_contents() {
core_surface.process(&mut self.renderer);
}
let _ = self.renderer.cleanup_texture_cache();
self.flush_notify.notify_waiters();
}
pub fn frame_event(&self) {
for core_surface in CORE_SURFACES.get_valid_contents() {
core_surface.frame(self.output.clone());
}
}
pub fn make_context_current(&self) {
unsafe {
let _ = self.renderer.egl_context().make_current();
#[instrument(level = "debug", name = "Wayland frame", skip_all)]
pub fn update_graphics(materials: &mut Assets<BevyMaterial>, images: &mut Assets<Image>) {
for surface in WL_SURFACE_REGISTRY.get_valid_contents() {
surface.update_graphics(materials, images);
}
}
}
impl Drop for Wayland {
fn drop(&mut self) {
self.client_listener.abort();
self.client_dispatcher.abort();
self.abort_handle.abort();
}
}

View File

@@ -1,313 +0,0 @@
use super::{state::WaylandState, surface::CoreSurface, utils::WlSurfaceExt};
use crate::{
core::task,
nodes::items::panel::{Backend, Geometry, KEYMAPS, PanelItem},
};
use mint::Vector2;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use slotmap::KeyData;
use smithay::{
backend::input::{AxisRelativeDirection, ButtonState, KeyState},
delegate_seat,
input::{
Seat, SeatHandler,
keyboard::{FilterResult, LedState},
pointer::{AxisFrame, ButtonEvent, CursorImageStatus, CursorImageSurfaceData, MotionEvent},
touch::{self, DownEvent, UpEvent},
},
reexports::wayland_server::{Resource, Weak as WlWeak, protocol::wl_surface::WlSurface},
utils::SERIAL_COUNTER,
wayland::compositor,
};
use std::sync::{Arc, Weak};
use tokio::sync::watch;
impl SeatHandler for WaylandState {
type PointerFocus = WlSurface;
type KeyboardFocus = WlSurface;
type TouchFocus = WlSurface;
fn seat_state(&mut self) -> &mut smithay::input::SeatState<Self> {
&mut self.seat_state
}
fn focus_changed(&mut self, _seat: &Seat<Self>, _focused: Option<&Self::KeyboardFocus>) {}
fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
self.seat.cursor_info_tx.send_modify(|c| match image {
CursorImageStatus::Hidden => c.surface = None,
CursorImageStatus::Surface(surface) => {
CoreSurface::add_to(&surface);
compositor::with_states(&surface, |data| {
if let Some(cursor_attributes) = data.data_map.get::<CursorImageSurfaceData>() {
let hotspot = cursor_attributes.lock().unwrap().hotspot;
c.hotspot_x = hotspot.x;
c.hotspot_y = hotspot.y;
}
if let Some(core_surface) = data.data_map.get::<Arc<CoreSurface>>() {
core_surface.set_material_offset(1);
}
});
c.surface = Some(surface.downgrade())
}
_ => (),
});
}
fn led_state_changed(&mut self, _seat: &Seat<Self>, _led_state: LedState) {}
}
delegate_seat!(WaylandState);
pub fn handle_cursor<B: Backend>(
panel_item: &Arc<PanelItem<B>>,
mut cursor: watch::Receiver<CursorInfo>,
) {
let panel_item_weak = Arc::downgrade(panel_item);
let _ = task::new(|| "cursor handler", async move {
while cursor.changed().await.is_ok() {
let Some(panel_item) = panel_item_weak.upgrade() else {
continue;
};
let cursor_info = cursor.borrow();
panel_item.set_cursor(cursor_info.cursor_data());
}
});
}
pub struct CursorInfo {
pub surface: Option<WlWeak<WlSurface>>,
pub hotspot_x: i32,
pub hotspot_y: i32,
}
impl CursorInfo {
pub fn cursor_data(&self) -> Option<Geometry> {
let cursor_size = self.surface.as_ref()?.upgrade().ok()?.get_size()?;
Some(Geometry {
origin: [self.hotspot_x, self.hotspot_y].into(),
size: cursor_size,
})
}
}
pub struct SeatWrapper {
wayland_state: Weak<Mutex<WaylandState>>,
cursor_info_tx: watch::Sender<CursorInfo>,
pub cursor_info_rx: watch::Receiver<CursorInfo>,
seat: Seat<WaylandState>,
touches: Mutex<FxHashMap<u32, WlWeak<WlSurface>>>,
}
impl SeatWrapper {
pub fn new(wayland_state: Weak<Mutex<WaylandState>>, seat: Seat<WaylandState>) -> Self {
let (cursor_info_tx, cursor_info_rx) = watch::channel(CursorInfo {
surface: None,
hotspot_x: 0,
hotspot_y: 0,
});
SeatWrapper {
wayland_state,
cursor_info_tx,
cursor_info_rx,
seat,
touches: Mutex::new(FxHashMap::default()),
}
}
pub fn unfocus_internal_state(&self, surface: &WlSurface) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
self.unfocus(surface, &mut state.lock());
}
pub fn unfocus(&self, surface: &WlSurface, state: &mut WaylandState) {
let pointer = self.seat.get_pointer().unwrap();
if pointer.current_focus() == Some(surface.clone()) {
pointer.motion(
state,
None,
&MotionEvent {
location: (0.0, 0.0).into(),
serial: SERIAL_COUNTER.next_serial(),
time: 0,
},
)
}
let keyboard = self.seat.get_keyboard().unwrap();
if keyboard.current_focus() == Some(surface.clone()) {
keyboard.set_focus(state, None, SERIAL_COUNTER.next_serial());
}
for (id, touch_surface) in self.touches.lock().iter() {
if touch_surface.id() == surface.id() {
self.touch_up(*id);
}
}
}
pub fn pointer_motion(&self, surface: WlSurface, position: Vector2<f32>) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let mut state = state.lock();
let Some(pointer) = self.seat.get_pointer() else {
return;
};
pointer.motion(
&mut state,
Some((surface, (0.0, 0.0).into())),
&MotionEvent {
location: (position.x as f64, position.y as f64).into(),
serial: SERIAL_COUNTER.next_serial(),
time: 0,
},
);
pointer.frame(&mut state);
}
pub fn pointer_button(&self, button: u32, pressed: bool) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let mut state = state.lock();
let Some(pointer) = self.seat.get_pointer() else {
return;
};
pointer.button(
&mut state,
&ButtonEvent {
button,
state: if pressed {
ButtonState::Pressed
} else {
ButtonState::Released
},
serial: SERIAL_COUNTER.next_serial(),
time: 0,
},
);
pointer.frame(&mut state);
}
pub fn pointer_scroll(
&self,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let mut state = state.lock();
let Some(pointer) = self.seat.get_pointer() else {
return;
};
pointer.axis(
&mut state,
AxisFrame {
source: None,
relative_direction: (
AxisRelativeDirection::Identical,
AxisRelativeDirection::Identical,
),
time: 0,
axis: scroll_distance
.map(|d| (d.x as f64, d.y as f64))
.unwrap_or((0.0, 0.0)),
v120: scroll_steps.map(|d| ((d.x * 120.0) as i32, (d.y * 120.0) as i32)),
stop: (false, false),
},
);
pointer.frame(&mut state);
}
pub fn keyboard_key(&self, surface: WlSurface, keymap_id: u64, key: u32, pressed: bool) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let Some(keyboard) = self.seat.get_keyboard() else {
return;
};
let keymaps = KEYMAPS.lock();
let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()).cloned() else {
return;
};
keyboard.set_focus(
&mut state.lock(),
Some(surface),
SERIAL_COUNTER.next_serial(),
);
if keyboard
.set_keymap_from_string(&mut state.lock(), keymap)
.is_err()
{
return;
}
keyboard.input(
&mut state.lock(),
key.into(),
if pressed {
KeyState::Pressed
} else {
KeyState::Released
},
SERIAL_COUNTER.next_serial(),
0,
|_, _, _| FilterResult::Forward::<()>,
);
}
pub fn touch_down(&self, surface: WlSurface, id: u32, position: Vector2<f32>) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let Some(touch) = self.seat.get_touch() else {
return;
};
touch.down(
&mut state.lock(),
Some((surface, (0.0, 0.0).into())),
&DownEvent {
slot: Some(id).into(),
location: (position.x as f64, position.y as f64).into(),
serial: SERIAL_COUNTER.next_serial(),
time: 0,
},
);
touch.frame(&mut state.lock());
}
pub fn touch_move(&self, id: u32, position: Vector2<f32>) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let Some(surface) = self.touches.lock().get(&id).and_then(|c| c.upgrade().ok()) else {
return;
};
let Some(touch) = self.seat.get_touch() else {
return;
};
touch.motion(
&mut state.lock(),
Some((surface, (0.0, 0.0).into())),
&touch::MotionEvent {
slot: Some(id).into(),
location: (position.x as f64, position.y as f64).into(),
time: 0,
},
);
touch.frame(&mut state.lock());
}
pub fn touch_up(&self, id: u32) {
let Some(state) = self.wayland_state.upgrade() else {
return;
};
let Some(touch) = self.seat.get_touch() else {
return;
};
touch.up(
&mut state.lock(),
&UpEvent {
slot: Some(id).into(),
serial: SERIAL_COUNTER.next_serial(),
time: 0,
},
);
touch.frame(&mut state.lock());
}
pub fn reset_input(&self) {
for id in self.touches.lock().keys() {
self.touch_up(*id)
}
}
}

View File

@@ -1,230 +0,0 @@
use super::seat::SeatWrapper;
use crate::wayland::drm::wl_drm::WlDrm;
use parking_lot::Mutex;
use smithay::{
backend::{
allocator::{Fourcc, dmabuf::Dmabuf},
egl::EGLDevice,
renderer::gles::GlesRenderer,
},
delegate_dmabuf, delegate_output, delegate_shm, delegate_viewporter,
desktop::PopupManager,
input::{SeatState, keyboard::XkbConfig},
output::{Mode, Output, Scale, Subpixel},
reexports::{
wayland_protocols::xdg::{
decoration::zv1::server::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1,
shell::server::xdg_toplevel::WmCapabilities,
},
wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as DecorationMode,
wayland_server::{
DisplayHandle,
backend::{ClientData, ClientId, DisconnectReason},
protocol::{
wl_buffer::WlBuffer, wl_data_device_manager::WlDataDeviceManager,
wl_output::WlOutput,
},
},
},
utils::{Size, Transform},
wayland::{
buffer::BufferHandler,
compositor::{CompositorClientState, CompositorState},
dmabuf::{
self, DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState,
},
output::OutputHandler,
shell::{
kde::decoration::KdeDecorationState,
xdg::{WmCapabilitySet, XdgShellState},
},
shm::{ShmHandler, ShmState},
viewporter::ViewporterState,
},
};
use std::sync::{Arc, OnceLock};
use tokio::sync::mpsc::UnboundedSender;
use tracing::{info, warn};
pub struct ClientState {
pub pid: Option<i32>,
pub id: OnceLock<ClientId>,
pub compositor_state: CompositorClientState,
pub seat: Arc<SeatWrapper>,
}
impl ClientData for ClientState {
fn initialized(&self, client_id: ClientId) {
info!("Wayland client {:?} connected", client_id);
let _ = self.id.set(client_id);
}
fn disconnected(&self, client_id: ClientId, reason: DisconnectReason) {
info!(
"Wayland client {:?} disconnected because {:#?}",
client_id, reason
);
}
}
pub struct WaylandState {
pub compositor_state: CompositorState,
// pub xdg_activation_state: XdgActivationState,
pub kde_decoration_state: KdeDecorationState,
pub shm_state: ShmState,
dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
pub _viewporter_state: ViewporterState,
pub drm_formats: Vec<Fourcc>,
pub dmabuf_tx: UnboundedSender<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
pub seat_state: SeatState<Self>,
pub seat: Arc<SeatWrapper>,
pub xdg_shell: XdgShellState,
pub popup_manager: PopupManager,
pub output: Output,
}
impl WaylandState {
pub fn new(
display_handle: DisplayHandle,
renderer: &GlesRenderer,
dmabuf_tx: UnboundedSender<(Dmabuf, Option<dmabuf::ImportNotifier>)>,
) -> Arc<Mutex<Self>> {
let compositor_state = CompositorState::new::<Self>(&display_handle);
// let xdg_activation_state = XdgActivationState::new::<Self, _>(&display_handle);
let kde_decoration_state =
KdeDecorationState::new::<Self>(&display_handle, DecorationMode::Server);
let shm_state = ShmState::new::<Self>(&display_handle, vec![]);
let render_node = EGLDevice::device_for_display(renderer.egl_context().display())
.and_then(|device| device.try_get_render_node());
let dmabuf_formats = renderer
.egl_context()
.dmabuf_render_formats()
.iter()
.cloned()
.collect::<Vec<_>>();
let drm_formats = dmabuf_formats.iter().map(|f| f.code).collect();
let dmabuf_default_feedback = match render_node {
Ok(Some(node)) => DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats.clone())
.build()
.ok(),
Ok(None) => {
warn!("failed to query render node, dmabuf will use v3");
None
}
Err(err) => {
warn!(?err, "failed to egl device for display, dmabuf will use v3");
None
}
};
// if we failed to build dmabuf feedback we fall back to dmabuf v3
// Note: egl on Mesa requires either v4 or wl_drm (initialized with bind_wl_display)
let dmabuf_state = if let Some(default_feedback) = dmabuf_default_feedback {
let mut dmabuf_state = DmabufState::new();
let dmabuf_global = dmabuf_state.create_global_with_default_feedback::<WaylandState>(
&display_handle,
&default_feedback,
);
(dmabuf_state, dmabuf_global, Some(default_feedback))
} else {
let mut dmabuf_state = DmabufState::new();
let dmabuf_global =
dmabuf_state.create_global::<WaylandState>(&display_handle, dmabuf_formats.clone());
(dmabuf_state, dmabuf_global, None)
};
let mut seat_state = SeatState::new();
let mut seat = seat_state.new_wl_seat(&display_handle, "seat0");
seat.add_pointer();
seat.add_keyboard(XkbConfig::default(), 200, 25).unwrap();
seat.add_touch();
let output = Output::new(
"1x".to_owned(),
smithay::output::PhysicalProperties {
size: Size::default(),
subpixel: Subpixel::None,
make: "Virtual XR Display".to_owned(),
model: "Your Headset Name Here".to_owned(),
},
);
let _output_global = output.create_global::<Self>(&display_handle);
let mode = Mode {
size: (1024, 1024).into(),
refresh: 60000,
};
output.change_current_state(
Some(mode),
Some(Transform::Normal),
Some(Scale::Integer(2)),
None,
);
output.set_preferred(mode);
let mut xdg_shell = XdgShellState::new::<Self>(&display_handle);
let _viewporter_state = ViewporterState::new::<Self>(&display_handle);
let popup_manager = PopupManager::default();
let mut capabilities = WmCapabilitySet::default();
capabilities.set(WmCapabilities::Maximize);
capabilities.set(WmCapabilities::Fullscreen);
capabilities.unset(WmCapabilities::Minimize);
capabilities.unset(WmCapabilities::WindowMenu);
xdg_shell.replace_capabilities(capabilities);
display_handle.create_global::<Self, WlDataDeviceManager, _>(3, ());
display_handle.create_global::<Self, ZxdgDecorationManagerV1, _>(1, ());
display_handle.create_global::<Self, WlDrm, _>(2, ());
info!("Init Wayland compositor");
Arc::new_cyclic(|weak| {
Mutex::new(WaylandState {
compositor_state,
// xdg_activation_state,
kde_decoration_state,
shm_state,
drm_formats,
dmabuf_state,
dmabuf_tx,
seat_state,
seat: Arc::new(SeatWrapper::new(weak.clone(), seat)),
xdg_shell,
popup_manager,
output,
_viewporter_state,
})
})
}
}
impl Drop for WaylandState {
fn drop(&mut self) {
info!("Cleanly shut down the Wayland compositor");
}
}
impl BufferHandler for WaylandState {
fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}
}
impl ShmHandler for WaylandState {
fn shm_state(&self) -> &ShmState {
&self.shm_state
}
}
impl DmabufHandler for WaylandState {
fn dmabuf_state(&mut self) -> &mut DmabufState {
&mut self.dmabuf_state.0
}
fn dmabuf_imported(
&mut self,
_global: &DmabufGlobal,
dmabuf: Dmabuf,
notifier: dmabuf::ImportNotifier,
) {
self.dmabuf_tx.send((dmabuf, Some(notifier))).unwrap();
}
}
impl OutputHandler for WaylandState {
fn output_bound(&mut self, _output: Output, _wl_output: WlOutput) {}
}
delegate_dmabuf!(WaylandState);
delegate_shm!(WaylandState);
delegate_output!(WaylandState);
delegate_viewporter!(WaylandState);

View File

@@ -1,202 +0,0 @@
use super::utils::WlSurfaceExt;
use crate::{
core::{delta::Delta, destroy_queue, registry::Registry},
nodes::{
drawable::{
model::{MaterialWrapper, ModelPart},
shaders::PANEL_SHADER_BYTES,
},
items::camera::TexWrapper,
},
};
use parking_lot::Mutex;
use send_wrapper::SendWrapper;
use smithay::{
backend::renderer::{
Renderer, Texture,
gles::{GlesRenderer, GlesTexture},
utils::{RendererSurfaceStateUserData, import_surface_tree},
},
desktop::utils::send_frames_surface_tree,
output::Output,
reexports::wayland_server::{self, Resource, protocol::wl_surface::WlSurface},
};
use std::{
ffi::c_void,
sync::{Arc, OnceLock},
time::Duration,
};
use stereokit_rust::{
material::{Material, Transparency},
shader::Shader,
tex::{Tex, TexAddress, TexFormat, TexSample, TexType},
util::Time,
};
pub static CORE_SURFACES: Registry<CoreSurface> = Registry::new();
pub struct CoreSurfaceData {
wl_tex: Option<SendWrapper<GlesTexture>>,
}
impl Drop for CoreSurfaceData {
fn drop(&mut self) {
destroy_queue::add(self.wl_tex.take());
}
}
pub struct CoreSurface {
pub weak_surface: wayland_server::Weak<WlSurface>,
mapped_data: Mutex<Option<CoreSurfaceData>>,
sk_tex: OnceLock<Mutex<TexWrapper>>,
sk_mat: OnceLock<Mutex<MaterialWrapper>>,
material_offset: Mutex<Delta<u32>>,
pub pending_material_applications: Registry<ModelPart>,
}
impl CoreSurface {
pub fn add_to(surface: &WlSurface) {
let core_surface = CORE_SURFACES.add(CoreSurface {
weak_surface: surface.downgrade(),
mapped_data: Mutex::new(None),
sk_tex: OnceLock::new(),
sk_mat: OnceLock::new(),
material_offset: Mutex::new(Delta::new(0)),
pending_material_applications: Registry::new(),
});
surface.insert_data(core_surface);
}
pub fn from_wl_surface(surf: &WlSurface) -> Option<Arc<CoreSurface>> {
surf.get_data()
}
pub fn process(&self, renderer: &mut GlesRenderer) {
let Some(wl_surface) = self.wl_surface() else {
return;
};
let sk_tex = self.sk_tex.get_or_init(|| {
Mutex::new(TexWrapper(Tex::new(
TexType::ImageNomips,
TexFormat::RGBA32Linear,
nanoid::nanoid!(),
)))
});
self.sk_mat.get_or_init(|| {
let shader = Shader::from_memory(PANEL_SHADER_BYTES).unwrap();
// let _ = renderer.with_context(|c| unsafe {
// shader_inject(c, &mut shader, SIMULA_VERT_STR, SIMULA_FRAG_STR)
// });
let mut mat = Material::new(shader, None);
mat.diffuse_tex(&sk_tex.lock().0);
mat.transparency(Transparency::Blend);
Mutex::new(MaterialWrapper(mat))
});
// Import all surface buffers into textures
if let Err(err) = import_surface_tree(renderer, &wl_surface) {
tracing::error!(
"Failed to import surface tree for surface {}: {}",
wl_surface.id(),
err
);
return;
}
self.update_textures(renderer);
self.apply_surface_materials();
}
pub fn update_textures(&self, renderer: &mut GlesRenderer) {
let Some(wl_surface) = self.wl_surface() else {
return;
};
let Some(smithay_tex) = wl_surface
.get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| {
let locked = surface_states.lock().unwrap();
locked.texture::<GlesRenderer>(renderer.id()).cloned()
})
.flatten()
else {
return;
};
let Some(sk_tex) = self.sk_tex.get() else {
tracing::error!("No sk_tex found for surface");
return;
};
let Some(sk_mat) = self.sk_mat.get() else {
tracing::error!("No sk_mat found for surface");
return;
};
sk_tex
.lock()
.0
.set_native_surface(
smithay_tex.tex_id() as usize as *mut c_void,
TexType::ImageNomips,
smithay::backend::renderer::gles::ffi::RGBA8.into(),
smithay_tex.width() as i32,
smithay_tex.height() as i32,
1,
false,
)
.sample_mode(TexSample::Point)
.address_mode(TexAddress::Clamp);
if let Some(material_offset) = self.material_offset.lock().delta() {
sk_mat.lock().0.queue_offset(*material_offset as i32);
}
let new_mapped_data = CoreSurfaceData {
wl_tex: Some(SendWrapper::new(smithay_tex)),
};
*self.mapped_data.lock() = Some(new_mapped_data);
}
pub fn frame(&self, output: Output) {
let Some(wl_surface) = self.wl_surface() else {
return;
};
send_frames_surface_tree(
&wl_surface,
&output,
Duration::from_secs_f64(Time::get_total_unscaled()),
None,
|_, _| Some(output.clone()),
);
}
pub fn set_material_offset(&self, material_offset: u32) {
*self.material_offset.lock().value_mut() = material_offset;
}
pub fn apply_material(&self, model_part: &Arc<ModelPart>) {
self.pending_material_applications.add_raw(model_part)
}
fn apply_surface_materials(&self) {
if let Some(sk_mat) = self.sk_mat.get() {
let sk_mat = sk_mat.lock();
for model_node in self.pending_material_applications.get_valid_contents() {
model_node.replace_material_now(&sk_mat.0);
}
self.pending_material_applications.clear();
}
}
pub fn wl_surface(&self) -> Option<WlSurface> {
self.weak_surface.upgrade().ok()
}
}
impl Drop for CoreSurface {
fn drop(&mut self) {
CORE_SURFACES.remove(self);
destroy_queue::add(self.sk_tex.take());
destroy_queue::add(self.sk_mat.take());
}
}

42
src/wayland/util.rs Normal file
View File

@@ -0,0 +1,42 @@
#![allow(unused)]
use super::{MessageSink, core::display::Display};
use std::{fmt::Debug, sync::Arc};
use waynest::{server::Client, wire::ObjectId};
pub trait ClientExt {
fn message_sink(&self) -> MessageSink;
fn display(&self) -> Arc<Display>;
}
impl ClientExt for Client {
fn message_sink(&self) -> MessageSink {
self.get::<Display>(ObjectId::DISPLAY)
.unwrap()
.message_sink
.clone()
}
fn display(&self) -> Arc<Display> {
self.get::<Display>(ObjectId::DISPLAY).unwrap()
}
}
#[derive(Debug, Default)]
pub struct DoubleBuffer<State: Debug + Clone> {
current: State,
pub pending: State,
}
impl<State: Debug + Clone> DoubleBuffer<State> {
pub fn new(initial_state: State) -> Self {
DoubleBuffer {
current: initial_state.clone(),
pending: initial_state,
}
}
pub fn apply(&mut self) {
self.current = self.pending.clone();
}
pub fn current(&self) -> &State {
&self.current
}
}

View File

@@ -1,121 +0,0 @@
use mint::Vector2;
use parking_lot::Mutex;
use smithay::{
backend::renderer::utils::RendererSurfaceStateUserData,
reexports::wayland_server::protocol::wl_surface::WlSurface,
wayland::{
compositor,
shell::xdg::{SurfaceCachedState, XdgToplevelSurfaceData},
},
};
use crate::nodes::items::panel::{ChildInfo, Geometry, ToplevelInfo};
use super::xdg_shell::surface_panel_item;
pub trait WlSurfaceExt {
fn insert_data<T: Send + Sync + 'static>(&self, data: T) -> bool;
fn get_data<T: Send + Sync + Clone + 'static>(&self) -> Option<T>;
fn get_data_raw<T: Send + Sync + 'static, O, F: FnOnce(&T) -> O>(&self, f: F) -> Option<O>;
fn get_current_surface_state(&self) -> SurfaceCachedState;
fn get_pending_surface_state(&self) -> SurfaceCachedState;
fn get_size(&self) -> Option<Vector2<u32>>;
fn get_geometry(&self) -> Option<Geometry>;
}
impl WlSurfaceExt for WlSurface {
fn insert_data<T: Send + Sync + 'static>(&self, data: T) -> bool {
compositor::with_states(self, |d| {
d.data_map.insert_if_missing_threadsafe(move || data)
})
}
fn get_data<T: Send + Sync + Clone + 'static>(&self) -> Option<T> {
compositor::with_states(self, |d| d.data_map.get::<T>().cloned())
}
fn get_data_raw<T: Send + Sync + 'static, O, F: FnOnce(&T) -> O>(&self, f: F) -> Option<O> {
compositor::with_states(self, |d| Some((f)(d.data_map.get::<T>()?)))
}
fn get_current_surface_state(&self) -> SurfaceCachedState {
compositor::with_states(self, |states| {
*states.cached_state.get::<SurfaceCachedState>().current()
})
}
fn get_pending_surface_state(&self) -> SurfaceCachedState {
compositor::with_states(self, |states| {
*states.cached_state.get::<SurfaceCachedState>().pending()
})
}
fn get_size(&self) -> Option<Vector2<u32>> {
self.get_data_raw::<RendererSurfaceStateUserData, _, _>(|surface_states| {
surface_states.lock().unwrap().surface_size()
})
.flatten()
.map(|size| Vector2::from([size.w as u32, size.h as u32]))
}
fn get_geometry(&self) -> Option<Geometry> {
self.get_current_surface_state().geometry.map(|r| r.into())
}
}
pub trait ToplevelInfoExt {
fn get_toplevel_info(&self) -> Option<ToplevelInfo>;
fn with_toplevel_info<O, F: FnOnce(&mut ToplevelInfo) -> O>(&self, f: F) -> Option<O>;
fn get_parent(&self) -> Option<u64>;
fn get_app_id(&self) -> Option<String>;
fn get_title(&self) -> Option<String>;
fn min_size(&self) -> Option<Vector2<u32>>;
fn max_size(&self) -> Option<Vector2<u32>>;
}
impl ToplevelInfoExt for WlSurface {
fn get_toplevel_info(&self) -> Option<ToplevelInfo> {
self.get_data_raw::<Mutex<ToplevelInfo>, _, _>(|c| c.lock().clone())
}
fn with_toplevel_info<O, F: FnOnce(&mut ToplevelInfo) -> O>(&self, f: F) -> Option<O> {
self.get_data_raw::<Mutex<ToplevelInfo>, _, _>(|r| (f)(&mut r.lock()))
}
fn get_parent(&self) -> Option<u64> {
self.get_data_raw::<XdgToplevelSurfaceData, _, _>(|d| d.lock().unwrap().parent.clone())
.flatten()
.and_then(|p| surface_panel_item(&p))
.and_then(|p| p.node.upgrade())
.map(|p| p.get_id())
}
fn get_app_id(&self) -> Option<String> {
self.get_data_raw::<XdgToplevelSurfaceData, _, _>(|d| d.lock().ok()?.app_id.clone())
.flatten()
}
fn get_title(&self) -> Option<String> {
self.get_data_raw::<XdgToplevelSurfaceData, _, _>(|d| d.lock().ok()?.title.clone())
.flatten()
}
fn min_size(&self) -> Option<Vector2<u32>> {
let state = self.get_pending_surface_state();
let size = state.min_size;
if size.w == 0 && size.h == 0 {
None
} else {
Some(Vector2::from([size.w as u32, size.h as u32]))
}
}
fn max_size(&self) -> Option<Vector2<u32>> {
let state = self.get_pending_surface_state();
let size = state.max_size;
if size.w == 0 && size.h == 0 {
None
} else {
Some(Vector2::from([size.w as u32, size.h as u32]))
}
}
}
pub trait ChildInfoExt {
fn get_child_info(&self) -> Option<ChildInfo>;
fn with_child_info<O, F: FnOnce(&mut ChildInfo) -> O>(&self, f: F) -> Option<O>;
}
impl ChildInfoExt for WlSurface {
fn get_child_info(&self) -> Option<ChildInfo> {
self.get_data_raw::<Mutex<ChildInfo>, _, _>(|c| c.lock().clone())
}
fn with_child_info<O, F: FnOnce(&mut ChildInfo) -> O>(&self, f: F) -> Option<O> {
self.get_data_raw::<Mutex<ChildInfo>, _, _>(|r| (f)(&mut r.lock()))
}
}

View File

@@ -1,189 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="drm">
<copyright>
Copyright © 2008-2011 Kristian Høgsberg
Copyright © 2010-2011 Intel Corporation
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that\n the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<!-- drm support. This object is created by the server and published
using the display's global event. -->
<interface name="wl_drm" version="2">
<enum name="error">
<entry name="authenticate_fail" value="0" />
<entry name="invalid_format" value="1" />
<entry name="invalid_name" value="2" />
</enum>
<enum name="format">
<!-- The drm format codes match the #defines in drm_fourcc.h.
The formats actually supported by the compositor will be
reported by the format event. New codes must not be added,
unless directly taken from drm_fourcc.h. -->
<entry name="c8" value="0x20203843" />
<entry name="rgb332" value="0x38424752" />
<entry name="bgr233" value="0x38524742" />
<entry name="xrgb4444" value="0x32315258" />
<entry name="xbgr4444" value="0x32314258" />
<entry name="rgbx4444" value="0x32315852" />
<entry name="bgrx4444" value="0x32315842" />
<entry name="argb4444" value="0x32315241" />
<entry name="abgr4444" value="0x32314241" />
<entry name="rgba4444" value="0x32314152" />
<entry name="bgra4444" value="0x32314142" />
<entry name="xrgb1555" value="0x35315258" />
<entry name="xbgr1555" value="0x35314258" />
<entry name="rgbx5551" value="0x35315852" />
<entry name="bgrx5551" value="0x35315842" />
<entry name="argb1555" value="0x35315241" />
<entry name="abgr1555" value="0x35314241" />
<entry name="rgba5551" value="0x35314152" />
<entry name="bgra5551" value="0x35314142" />
<entry name="rgb565" value="0x36314752" />
<entry name="bgr565" value="0x36314742" />
<entry name="rgb888" value="0x34324752" />
<entry name="bgr888" value="0x34324742" />
<entry name="xrgb8888" value="0x34325258" />
<entry name="xbgr8888" value="0x34324258" />
<entry name="rgbx8888" value="0x34325852" />
<entry name="bgrx8888" value="0x34325842" />
<entry name="argb8888" value="0x34325241" />
<entry name="abgr8888" value="0x34324241" />
<entry name="rgba8888" value="0x34324152" />
<entry name="bgra8888" value="0x34324142" />
<entry name="xrgb2101010" value="0x30335258" />
<entry name="xbgr2101010" value="0x30334258" />
<entry name="rgbx1010102" value="0x30335852" />
<entry name="bgrx1010102" value="0x30335842" />
<entry name="argb2101010" value="0x30335241" />
<entry name="abgr2101010" value="0x30334241" />
<entry name="rgba1010102" value="0x30334152" />
<entry name="bgra1010102" value="0x30334142" />
<entry name="yuyv" value="0x56595559" />
<entry name="yvyu" value="0x55595659" />
<entry name="uyvy" value="0x59565955" />
<entry name="vyuy" value="0x59555956" />
<entry name="ayuv" value="0x56555941" />
<entry name="xyuv8888" value="0x56555958" />
<entry name="nv12" value="0x3231564e" />
<entry name="nv21" value="0x3132564e" />
<entry name="nv16" value="0x3631564e" />
<entry name="nv61" value="0x3136564e" />
<entry name="yuv410" value="0x39565559" />
<entry name="yvu410" value="0x39555659" />
<entry name="yuv411" value="0x31315559" />
<entry name="yvu411" value="0x31315659" />
<entry name="yuv420" value="0x32315559" />
<entry name="yvu420" value="0x32315659" />
<entry name="yuv422" value="0x36315559" />
<entry name="yvu422" value="0x36315659" />
<entry name="yuv444" value="0x34325559" />
<entry name="yvu444" value="0x34325659" />
<entry name="abgr16f" value="0x48344241" />
<entry name="xbgr16f" value="0x48344258" />
</enum>
<!-- Call this request with the magic received from drmGetMagic().
It will be passed on to the drmAuthMagic() or
DRIAuthConnection() call. This authentication must be
completed before create_buffer could be used. -->
<request name="authenticate">
<arg name="id" type="uint" />
</request>
<!-- Create a wayland buffer for the named DRM buffer. The DRM
surface must have a name using the flink ioctl -->
<request name="create_buffer">
<arg name="id" type="new_id" interface="wl_buffer" />
<arg name="name" type="uint" />
<arg name="width" type="int" />
<arg name="height" type="int" />
<arg name="stride" type="uint" />
<arg name="format" type="uint" />
</request>
<!-- Create a wayland buffer for the named DRM buffer. The DRM
surface must have a name using the flink ioctl -->
<request name="create_planar_buffer">
<arg name="id" type="new_id" interface="wl_buffer" />
<arg name="name" type="uint" />
<arg name="width" type="int" />
<arg name="height" type="int" />
<arg name="format" type="uint" />
<arg name="offset0" type="int" />
<arg name="stride0" type="int" />
<arg name="offset1" type="int" />
<arg name="stride1" type="int" />
<arg name="offset2" type="int" />
<arg name="stride2" type="int" />
</request>
<!-- Notification of the path of the drm device which is used by
the server. The client should use this device for creating
local buffers. Only buffers created from this device should
be be passed to the server using this drm object's
create_buffer request. -->
<event name="device">
<arg name="name" type="string" />
</event>
<event name="format">
<arg name="format" type="uint" />
</event>
<!-- Raised if the authenticate request succeeded -->
<event name="authenticated" />
<enum name="capability" since="2">
<description summary="wl_drm capability bitmask">
Bitmask of capabilities.
</description>
<entry name="prime" value="1" summary="wl_drm prime available" />
</enum>
<event name="capabilities">
<arg name="value" type="uint" />
</event>
<!-- Version 2 additions -->
<!-- Create a wayland buffer for the prime fd. Use for regular and planar
buffers. Pass 0 for offset and stride for unused planes. -->
<request name="create_prime_buffer" since="2">
<arg name="id" type="new_id" interface="wl_buffer" />
<arg name="name" type="fd" />
<arg name="width" type="int" />
<arg name="height" type="int" />
<arg name="format" type="uint" />
<arg name="offset0" type="int" />
<arg name="stride0" type="int" />
<arg name="offset1" type="int" />
<arg name="stride1" type="int" />
<arg name="offset2" type="int" />
<arg name="stride2" type="int" />
</request>
</interface>
</protocol>

246
src/wayland/xdg/backend.rs Normal file
View File

@@ -0,0 +1,246 @@
use super::toplevel::Toplevel;
use crate::{
core::error::Result,
nodes::{
drawable::model::ModelPart,
items::panel::{Backend, Geometry, PanelItemInitData, SurfaceId, ToplevelInfo},
},
wayland::core::surface::Surface,
};
use mint::Vector2;
use std::sync::Arc;
use std::sync::Weak;
use tracing;
#[derive(Debug)]
pub struct XdgBackend {
toplevel: Weak<Toplevel>,
}
impl XdgBackend {
pub fn new(toplevel: Arc<Toplevel>) -> Self {
Self {
toplevel: Arc::downgrade(&toplevel),
}
}
// Since XdgBackend is created and owned by Mapped which is owned by Toplevel,
// we can safely assume the Toplevel reference will always be valid
fn toplevel(&self) -> Arc<Toplevel> {
self.toplevel
.upgrade()
.expect("Toplevel should always be valid while XdgBackend exists")
}
fn surface_from_id(&self, id: SurfaceId) -> Option<Arc<Surface>> {
match id {
SurfaceId::Toplevel(_) => Some(self.toplevel().surface()),
SurfaceId::Child(_) => None,
}
}
}
impl Backend for XdgBackend {
fn start_data(&self) -> Result<PanelItemInitData> {
let surface_state = self.toplevel().surface().current_state();
let size = surface_state
.buffer
.map(|b| [b.size().x as u32, b.size().y as u32].into())
.unwrap_or([0; 2].into());
let toplevel = ToplevelInfo {
parent: self.toplevel().parent(),
title: self.toplevel().title(),
app_id: self.toplevel().app_id(),
size,
min_size: surface_state
.min_size
.map(|v| [v.x as f32, v.y as f32].into()),
max_size: surface_state
.max_size
.map(|v| [v.x as f32, v.y as f32].into()),
logical_rectangle: surface_state.geometry.unwrap_or(Geometry {
origin: [0; 2].into(),
size,
}),
};
Ok(PanelItemInitData {
cursor: None,
toplevel,
children: vec![],
pointer_grab: None,
keyboard_grab: None,
})
}
fn apply_cursor_material(&self, _model_part: &Arc<ModelPart>) {}
fn apply_surface_material(&self, surface: SurfaceId, model_part: &Arc<ModelPart>) {
if let Some(surface) = self.surface_from_id(surface) {
surface.apply_material(model_part);
}
}
fn close_toplevel(&self) {
let _ =
self.toplevel()
.surface()
.message_sink
.send(crate::wayland::Message::CloseToplevel(
self.toplevel().clone(),
));
}
fn auto_size_toplevel(&self) {
let _ =
self.toplevel()
.surface()
.message_sink
.send(crate::wayland::Message::ResizeToplevel {
toplevel: self.toplevel().clone(),
size: None,
});
}
fn set_toplevel_size(&self, size: Vector2<u32>) {
let _ =
self.toplevel()
.surface()
.message_sink
.send(crate::wayland::Message::ResizeToplevel {
toplevel: self.toplevel().clone(),
size: Some(size),
});
}
fn set_toplevel_focused_visuals(&self, focused: bool) {
let _ = self.toplevel().surface().message_sink.send(
crate::wayland::Message::SetToplevelVisualActive {
toplevel: self.toplevel().clone(),
active: focused,
},
);
}
fn pointer_motion(&self, surface: &SurfaceId, position: Vector2<f32>) {
if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self
.toplevel()
.surface()
.message_sink
.send(crate::wayland::Message::Seat(
crate::wayland::core::seat::SeatMessage::PointerMotion { surface, position },
));
}
}
fn pointer_button(&self, surface: &SurfaceId, button: u32, pressed: bool) {
if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self
.toplevel()
.surface()
.message_sink
.send(crate::wayland::Message::Seat(
crate::wayland::core::seat::SeatMessage::PointerButton {
surface,
button,
pressed,
},
));
}
}
fn pointer_scroll(
&self,
surface: &SurfaceId,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self
.toplevel()
.surface()
.message_sink
.send(crate::wayland::Message::Seat(
crate::wayland::core::seat::SeatMessage::PointerScroll {
surface,
scroll_distance,
scroll_steps,
},
));
}
}
fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool) {
tracing::debug!(
"Backend: Keyboard key {} {}",
key,
if pressed { "pressed" } else { "released" }
);
if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self
.toplevel()
.surface()
.message_sink
.send(crate::wayland::Message::Seat(
crate::wayland::core::seat::SeatMessage::KeyboardKey {
surface,
keymap_id,
key,
pressed,
},
));
}
}
fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>) {
tracing::debug!(
"Backend: Touch down {} at ({}, {})",
id,
position.x,
position.y
);
if let Some(surface) = self.surface_from_id(surface.clone()) {
let _ = self
.toplevel()
.surface()
.message_sink
.send(crate::wayland::Message::Seat(
crate::wayland::core::seat::SeatMessage::TouchDown {
surface,
id,
position,
},
));
}
}
fn touch_move(&self, id: u32, position: Vector2<f32>) {
tracing::debug!(
"Backend: Touch move {} to ({}, {})",
id,
position.x,
position.y
);
let surface = self.toplevel().surface();
let _ = surface.message_sink.send(crate::wayland::Message::Seat(
crate::wayland::core::seat::SeatMessage::TouchMove { id, position },
));
}
fn touch_up(&self, id: u32) {
tracing::debug!("Backend: Touch up {}", id);
let surface = self.toplevel().surface();
let _ = surface.message_sink.send(crate::wayland::Message::Seat(
crate::wayland::core::seat::SeatMessage::TouchUp { id },
));
}
fn reset_input(&self) {
tracing::debug!("Backend: Reset input");
let surface = self.toplevel().surface();
let _ = surface.message_sink.send(crate::wayland::Message::Seat(
crate::wayland::core::seat::SeatMessage::Reset,
));
}
}

6
src/wayland/xdg/mod.rs Normal file
View File

@@ -0,0 +1,6 @@
pub mod backend;
pub mod popup;
pub mod positioner;
pub mod surface;
pub mod toplevel;
pub mod wm_base;

89
src/wayland/xdg/popup.rs Normal file
View File

@@ -0,0 +1,89 @@
use super::{
backend::XdgBackend,
positioner::{Positioner, PositionerData},
surface::Surface,
};
use crate::{
nodes::items::panel::{Geometry, PanelItem},
wayland::util::DoubleBuffer,
};
use parking_lot::Mutex;
use std::sync::{Arc, Weak, atomic::AtomicBool};
use waynest::{
server::{Client, Dispatcher, Result, protocol::stable::xdg_shell::xdg_popup::XdgPopup},
wire::ObjectId,
};
#[derive(Debug, Dispatcher)]
pub struct Popup {
id: ObjectId,
parent: Weak<Surface>,
surface: Weak<Surface>,
pub panel_item: Weak<PanelItem<XdgBackend>>,
positioner_data: Mutex<PositionerData>,
geometry: DoubleBuffer<Geometry>,
mapped: AtomicBool,
}
impl Popup {
pub fn new(
id: ObjectId,
parent: &Arc<Surface>,
panel_item: &Arc<PanelItem<XdgBackend>>,
xdg_surface: &Arc<Surface>,
positioner: &Positioner,
) -> Self {
let positioner_data = positioner.data();
Self {
id,
parent: Arc::downgrade(parent),
surface: Arc::downgrade(xdg_surface),
panel_item: Arc::downgrade(panel_item),
positioner_data: Mutex::new(positioner_data),
geometry: DoubleBuffer::new(positioner_data.infinite_geometry()),
mapped: AtomicBool::new(false),
}
}
}
impl XdgPopup for Popup {
/// https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab
async fn grab(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_seat: ObjectId,
_serial: u32,
) -> Result<()> {
Ok(())
}
/// https://wayland.app/protocols/xdg-shell#xdg_popup:request:reposition
async fn reposition(
&self,
client: &mut Client,
sender_id: ObjectId,
positioner: ObjectId,
token: u32,
) -> Result<()> {
let positioner = client.get::<Positioner>(positioner).unwrap();
let positioner_data = positioner.data();
*self.positioner_data.lock() = positioner_data;
self.repositioned(client, sender_id, token).await?;
let geometry = positioner_data.infinite_geometry();
self.configure(
client,
sender_id,
geometry.origin.x,
geometry.origin.y,
geometry.size.x as i32,
geometry.size.y as i32,
)
.await?;
self.surface.upgrade().unwrap().reconfigure(client).await?;
Ok(())
}
/// https://wayland.app/protocols/xdg-shell#xdg_popup:request:destroy
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

View File

@@ -0,0 +1,229 @@
use crate::nodes::items::panel::Geometry;
use mint::Vector2;
use parking_lot::Mutex;
use waynest::{
server::{
Client, Dispatcher, Result,
protocol::stable::xdg_shell::xdg_positioner::{
Anchor, ConstraintAdjustment, Gravity, XdgPositioner,
},
},
wire::ObjectId,
};
#[derive(Debug, Clone, Copy)]
pub struct PositionerData {
pub size: Vector2<u32>,
pub anchor_rect: Geometry,
pub offset: Vector2<i32>,
pub anchor: Anchor,
pub constraint_adjustment: ConstraintAdjustment,
pub reactive: bool,
pub parent_size: Vector2<u32>,
}
impl PositionerData {
pub fn infinite_geometry(&self) -> Geometry {
let anchor_point = match self.anchor {
Anchor::TopLeft => self.anchor_rect.origin,
Anchor::Top => [
self.anchor_rect.origin.x + (self.anchor_rect.size.x / 2) as i32,
self.anchor_rect.origin.y,
]
.into(),
Anchor::TopRight => [
self.anchor_rect.origin.x + self.anchor_rect.size.x as i32,
self.anchor_rect.origin.y,
]
.into(),
Anchor::Left => [
self.anchor_rect.origin.x,
self.anchor_rect.origin.y + (self.anchor_rect.size.y / 2) as i32,
]
.into(),
Anchor::Right => [
self.anchor_rect.origin.x + self.anchor_rect.size.x as i32,
self.anchor_rect.origin.y + (self.anchor_rect.size.y / 2) as i32,
]
.into(),
Anchor::BottomLeft => [
self.anchor_rect.origin.x,
self.anchor_rect.origin.y + self.anchor_rect.size.y as i32,
]
.into(),
Anchor::Bottom => [
self.anchor_rect.origin.x + (self.anchor_rect.size.x / 2) as i32,
self.anchor_rect.origin.y + self.anchor_rect.size.y as i32,
]
.into(),
Anchor::BottomRight => [
self.anchor_rect.origin.x + self.anchor_rect.size.x as i32,
self.anchor_rect.origin.y + self.anchor_rect.size.y as i32,
]
.into(),
_ => [
self.anchor_rect.origin.x + (self.anchor_rect.size.x / 2) as i32,
self.anchor_rect.origin.y + (self.anchor_rect.size.y / 2) as i32,
]
.into(),
};
let mut position = anchor_point;
// Apply gravity
if self
.constraint_adjustment
.contains(ConstraintAdjustment::FlipX)
{
position.x -= self.size.x as i32;
}
if self
.constraint_adjustment
.contains(ConstraintAdjustment::FlipY)
{
position.y -= self.size.y as i32;
}
// Apply offset
position.x += self.offset.x;
position.y += self.offset.y;
Geometry {
origin: position,
size: self.size,
}
}
}
impl Default for PositionerData {
fn default() -> Self {
Self {
size: [0; 2].into(),
anchor_rect: Default::default(),
offset: [0, 0].into(),
anchor: Anchor::TopLeft,
constraint_adjustment: ConstraintAdjustment::empty(),
reactive: false,
parent_size: [0; 2].into(),
}
}
}
#[derive(Debug, Dispatcher)]
pub struct Positioner {
data: Mutex<PositionerData>,
}
impl Default for Positioner {
fn default() -> Self {
Self {
data: Mutex::new(PositionerData::default()),
}
}
}
impl Positioner {
pub fn data(&self) -> PositionerData {
*self.data.lock()
}
}
impl XdgPositioner for Positioner {
async fn set_size(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_width: i32,
_height: i32,
) -> Result<()> {
let mut data = self.data.lock();
data.size = [_width.max(0) as u32, _height.max(0) as u32].into();
data.reactive = true;
Ok(())
}
async fn set_anchor_rect(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> Result<()> {
let mut data = self.data.lock();
data.anchor_rect.origin = [_x, _y].into();
data.anchor_rect.size = [_width.max(0) as u32, _height.max(0) as u32].into();
data.offset = [0, 0].into();
Ok(())
}
async fn set_anchor(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_anchor: Anchor,
) -> Result<()> {
let mut data = self.data.lock();
data.anchor = _anchor;
Ok(())
}
async fn set_gravity(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_gravity: Gravity,
) -> Result<()> {
Ok(())
}
async fn set_constraint_adjustment(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_constraint_adjustment: ConstraintAdjustment,
) -> Result<()> {
let mut data = self.data.lock();
data.constraint_adjustment = _constraint_adjustment;
Ok(())
}
async fn set_offset(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
) -> Result<()> {
let mut data = self.data.lock();
data.offset.x += _x;
data.offset.y += _y;
Ok(())
}
async fn set_reactive(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn set_parent_size(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_parent_width: i32,
_parent_height: i32,
) -> Result<()> {
let mut data = self.data.lock();
data.parent_size.x = _parent_width.max(0) as u32;
data.parent_size.y = _parent_height.max(0) as u32;
Ok(())
}
async fn set_parent_configure(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_serial: u32,
) -> Result<()> {
Ok(())
}
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
}

206
src/wayland/xdg/surface.rs Normal file
View File

@@ -0,0 +1,206 @@
use super::{popup::Popup, positioner::Positioner, toplevel::Mapped};
use crate::wayland::{
core::{display::Display, surface::SurfaceRole},
xdg::toplevel::Toplevel,
};
use std::sync::Arc;
use std::sync::Weak;
pub use waynest::server::protocol::stable::xdg_shell::xdg_surface::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug, Dispatcher)]
pub struct Surface {
id: ObjectId,
wl_surface: Weak<crate::wayland::core::surface::Surface>,
configured: Arc<std::sync::atomic::AtomicBool>,
}
impl Surface {
pub fn new(id: ObjectId, wl_surface: Arc<crate::wayland::core::surface::Surface>) -> Self {
Self {
id,
wl_surface: Arc::downgrade(&wl_surface),
configured: Arc::new(std::sync::atomic::AtomicBool::new(false)),
}
}
pub fn wl_surface(&self) -> Arc<crate::wayland::core::surface::Surface> {
// We can safely unwrap as the surface must exist for the lifetime of the xdg_surface
self.wl_surface
.upgrade()
.expect("Surface was dropped before xdg_surface")
}
pub async fn reconfigure(&self, client: &mut Client) -> Result<()> {
let serial = client.next_event_serial();
self.configure(client, self.id, serial).await
}
}
impl XdgSurface for Surface {
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:destroy
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:get_toplevel
async fn get_toplevel(
&self,
client: &mut Client,
sender_id: ObjectId,
toplevel_id: ObjectId,
) -> Result<()> {
let surface = self.wl_surface();
let toplevel = client.insert(
toplevel_id,
Toplevel::new(
toplevel_id,
surface.clone(),
client.get::<Self>(sender_id).unwrap(),
),
);
{
let mut surface_role = surface.role.lock();
// A surface must not have any existing role when assigning a new one
// "A surface must not have more than one role, and a role must not be assigned to more than one
// surface at a time. However, wl_surface role-specific interfaces may reassign the role, allow
// a role to be destroyed, or allow multiple role-specific interfaces to share the same role."
// - xdg_surface protocol doc
if surface_role.is_some() {
// We should send "role" error here as per xdg_wm_base.error enum
// But we'll ignore for now
} else {
surface_role.replace(SurfaceRole::XdgToplevel(toplevel.clone()));
}
}
toplevel.reconfigure(client).await?;
let serial = client.next_event_serial();
self.configure(client, sender_id, serial).await?;
let pid = client.get::<Display>(ObjectId::DISPLAY).unwrap().pid;
let configured = self.configured.clone();
surface.add_commit_handler(move |surface, state| {
let Some(SurfaceRole::XdgToplevel(toplevel)) = &mut *surface.role.lock() else {
return true;
};
// Only proceed if configured and has valid buffer
let has_valid_buffer = state
.buffer
.as_ref()
.is_some_and(|b| b.size().x > 0 && b.size().y > 0);
let mut mapped_lock = toplevel.mapped.lock();
if mapped_lock.is_none()
&& configured.load(std::sync::atomic::Ordering::SeqCst)
&& has_valid_buffer
{
mapped_lock.replace(Mapped::create(toplevel.clone(), pid));
return false;
}
true
});
Ok(())
}
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:get_popup
async fn get_popup(
&self,
client: &mut Client,
sender_id: ObjectId,
popup_id: ObjectId,
parent: Option<ObjectId>,
positioner: ObjectId,
) -> Result<()> {
let parent = client.get::<Surface>(parent.unwrap()).unwrap();
let panel_item = match parent.wl_surface().role.lock().as_ref().unwrap() {
SurfaceRole::XdgToplevel(toplevel) => {
let toplevel_lock = toplevel.mapped.lock();
toplevel_lock.as_ref().unwrap()._panel_item.clone()
}
SurfaceRole::XDGPopup(popup) => popup.panel_item.upgrade().unwrap(),
};
let positioner = client.get::<Positioner>(positioner).unwrap();
let surface = client.get::<Surface>(self.id).unwrap();
let popup = client.insert(
popup_id,
Popup::new(popup_id, &parent, &panel_item, &surface, &positioner),
);
{
let wl_surface = self.wl_surface();
let mut surface_role = wl_surface.role.lock();
if surface_role.is_some() {
// We should send "role" error here as per xdg_wm_base.error enum
// But we'll ignore for now
} else {
surface_role.replace(SurfaceRole::XDGPopup(popup.clone()));
}
}
let serial = client.next_event_serial();
self.configure(client, sender_id, serial).await?;
// let pid = client.get::<Display>(ObjectId::DISPLAY).unwrap().pid;
// let configured = self.configured.clone();
// surface.add_commit_handler(move |surface, state| {
// let Some(SurfaceRole::XDGPopup(popup)) = &mut *surface.role.lock() else {
// return true;
// };
// // Only proceed if configured and has valid buffer
// let has_valid_buffer = state
// .buffer
// .as_ref()
// .is_some_and(|b| b.size().x > 0 && b.size().y > 0);
// let mut mapped_lock = popup.mapped.lock();
// if mapped_lock.is_none()
// && configured.load(std::sync::atomic::Ordering::SeqCst)
// && has_valid_buffer
// {
// mapped_lock.replace(Mapped::create(popup.clone(), pid));
// return false;
// }
// true
// });
Ok(())
}
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:set_window_geometry
async fn set_window_geometry(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_x: i32,
_y: i32,
_width: i32,
_height: i32,
) -> Result<()> {
// we're gonna delegate literally all the window management
// to 3D stuff sooo we don't care, maximized is the floating state
Ok(())
}
/// https://wayland.app/protocols/xdg-shell#xdg_surface:request:ack_configure
async fn ack_configure(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_serial: u32,
) -> Result<()> {
self.configured
.store(true, std::sync::atomic::Ordering::SeqCst);
Ok(())
}
}

310
src/wayland/xdg/toplevel.rs Normal file
View File

@@ -0,0 +1,310 @@
use super::backend::XdgBackend;
use crate::{
nodes::{Node, items::panel::PanelItem},
wayland::core::surface::Surface,
};
use mint::Vector2;
use parking_lot::Mutex;
use std::sync::Arc;
use std::sync::Weak;
pub use waynest::server::protocol::stable::xdg_shell::xdg_toplevel::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
#[derive(Debug)]
pub struct Mapped {
pub panel_item_node: Arc<Node>,
pub _panel_item: Arc<PanelItem<XdgBackend>>,
}
impl Mapped {
pub fn create(toplevel: Arc<Toplevel>, pid: Option<i32>) -> Self {
let (panel_item_node, _panel_item) =
PanelItem::create(Box::new(XdgBackend::new(toplevel)), pid);
Self {
panel_item_node,
_panel_item,
}
}
}
#[derive(Debug, Clone)]
struct ToplevelData {
parent: Option<u64>,
app_id: Option<String>,
title: Option<String>,
activated: bool,
fullscreen: bool,
pub size: Option<Vector2<u32>>,
}
impl Default for ToplevelData {
fn default() -> Self {
Self {
parent: None,
app_id: None,
title: None,
activated: true,
fullscreen: false,
size: None,
}
}
}
#[derive(Debug, Dispatcher)]
pub struct Toplevel {
pub id: ObjectId,
wl_surface: Weak<Surface>,
xdg_surface: Weak<super::surface::Surface>,
pub mapped: Mutex<Option<Mapped>>,
data: Mutex<ToplevelData>,
}
impl Toplevel {
pub fn new(
object_id: ObjectId,
wl_surface: Arc<Surface>,
xdg_surface: Arc<super::surface::Surface>,
) -> Self {
Toplevel {
id: object_id,
wl_surface: Arc::downgrade(&wl_surface),
xdg_surface: Arc::downgrade(&xdg_surface),
mapped: Mutex::new(None),
data: Mutex::new(ToplevelData::default()),
}
}
pub fn surface(&self) -> Arc<Surface> {
// We can safely unwrap as the surface must exist for the lifetime of the toplevel
self.wl_surface
.upgrade()
.expect("Surface was dropped before toplevel")
}
pub fn title(&self) -> Option<String> {
self.data.lock().title.clone()
}
pub fn app_id(&self) -> Option<String> {
self.data.lock().app_id.clone()
}
pub fn parent(&self) -> Option<u64> {
self.data.lock().parent
}
pub fn set_size(&self, size: Option<Vector2<u32>>) {
self.data.lock().size = size;
}
pub fn set_activated(&self, activated: bool) {
self.data.lock().activated = activated;
}
// Helper to clamp size against constraints
fn clamp_size(&self, size: Vector2<u32>) -> Vector2<u32> {
let state = self.surface().current_state();
let mut clamped = size;
if let Some(min_size) = state.min_size {
clamped.x = clamped.x.max(min_size.x);
clamped.y = clamped.y.max(min_size.y);
}
if let Some(max_size) = state.max_size {
clamped.x = clamped.x.min(max_size.x);
clamped.y = clamped.y.min(max_size.y);
}
clamped
}
pub async fn reconfigure(&self, client: &mut Client) -> Result<()> {
let data = self.data.lock().clone();
// Use the explicitly set size, applying constraints
let size = data.size.map(|s| self.clamp_size(s));
let mut states = vec![
State::TiledTop,
State::TiledLeft,
State::TiledRight,
State::TiledBottom,
if data.fullscreen {
State::Fullscreen
} else {
State::Maximized
},
];
if data.activated {
states.push(State::Activated);
}
self.configure(
client,
self.id,
size.map(|v| v.x as i32).unwrap_or(0),
size.map(|v| v.y as i32).unwrap_or(0),
states
.into_iter()
.flat_map(|x| (x as u32).to_ne_bytes())
.collect(),
)
.await?;
self.xdg_surface
.upgrade()
.unwrap()
.reconfigure(client)
.await
}
}
impl XdgToplevel for Toplevel {
async fn set_parent(
&self,
client: &mut Client,
_sender_id: ObjectId,
parent: Option<ObjectId>,
) -> Result<()> {
// Handle case where parent is specified
if let Some(parent) = parent {
// Per spec: parent must be another xdg_toplevel surface
if let Some(parent_toplevel) = client.get::<Toplevel>(parent) {
let Some(mapped) = &*parent_toplevel.mapped.lock() else {
// Per spec: parent surfaces must be mapped before being used as a parent
// Setting an unmapped window as parent should raise a protocol error
// For now we just unset the parent as a fallback
self.data.lock().parent.take();
return Ok(());
};
// Per spec: store parent to ensure this surface is stacked above parent
// and other ancestor surfaces. Used for proper window stacking order.
self.data
.lock()
.parent
.replace(mapped.panel_item_node.get_id());
}
} else {
// Per spec: null parent unsets the parent, making this a top-level window
// This allows converting child windows back to independent top-level windows
self.data.lock().parent.take();
}
Ok(())
}
async fn set_title(
&self,
_client: &mut Client,
_sender_id: ObjectId,
title: String,
) -> Result<()> {
self.data.lock().title.replace(title);
Ok(())
}
async fn set_app_id(
&self,
_client: &mut Client,
_sender_id: ObjectId,
app_id: String,
) -> Result<()> {
self.data.lock().app_id.replace(app_id);
Ok(())
}
async fn show_window_menu(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_seat: ObjectId,
_serial: u32,
_x: i32,
_y: i32,
) -> Result<()> {
Ok(())
}
async fn r#move(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_seat: ObjectId,
_serial: u32,
) -> Result<()> {
Ok(())
}
async fn resize(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_seat: ObjectId,
_serial: u32,
_edges: ResizeEdge,
) -> Result<()> {
Ok(())
}
async fn set_max_size(
&self,
_client: &mut Client,
_sender_id: ObjectId,
width: i32,
height: i32,
) -> Result<()> {
self.surface().pending_state().pending.max_size = if width == 0 && height == 0 {
None
} else {
Some([width as u32, height as u32].into())
};
Ok(())
}
async fn set_min_size(
&self,
_client: &mut Client,
_sender_id: ObjectId,
width: i32,
height: i32,
) -> Result<()> {
self.surface().pending_state().pending.min_size = if width == 0 && height == 0 {
None
} else {
Some([width as u32, height as u32].into())
};
Ok(())
}
async fn set_maximized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn unset_maximized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn set_fullscreen(
&self,
_client: &mut Client,
_sender_id: ObjectId,
_output: Option<ObjectId>,
) -> Result<()> {
Ok(())
}
async fn unset_fullscreen(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn set_minimized(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
self.mapped.lock().take();
Ok(())
}
}
impl Drop for Toplevel {
fn drop(&mut self) {
self.mapped.lock().take();
}
}

View File

@@ -0,0 +1,48 @@
use crate::wayland::xdg::surface::Surface;
pub use waynest::server::protocol::stable::xdg_shell::xdg_wm_base::*;
use waynest::{
server::{Client, Dispatcher, Result},
wire::ObjectId,
};
use super::positioner::Positioner;
#[derive(Debug, Dispatcher, Default)]
pub struct WmBase;
impl XdgWmBase for WmBase {
async fn destroy(&self, _client: &mut Client, _sender_id: ObjectId) -> Result<()> {
Ok(())
}
async fn create_positioner(
&self,
client: &mut Client,
_sender_id: ObjectId,
id: ObjectId,
) -> Result<()> {
client.insert(id, Positioner::default());
Ok(())
}
async fn get_xdg_surface(
&self,
client: &mut Client,
_sender_id: ObjectId,
xdg_surface_id: ObjectId,
wl_surface_id: ObjectId,
) -> Result<()> {
let wl_surface = client
.get::<crate::wayland::core::surface::Surface>(wl_surface_id)
.ok_or(waynest::server::Error::Custom(
"can't get wayland surface id".to_string(),
))?;
let xdg_surface = Surface::new(xdg_surface_id, wl_surface);
client.insert(xdg_surface_id, xdg_surface);
Ok(())
}
async fn pong(&self, _client: &mut Client, _sender_id: ObjectId, _serial: u32) -> Result<()> {
Ok(())
}
}

View File

@@ -1,34 +0,0 @@
use smithay::{
delegate_xdg_activation,
reexports::wayland_server::protocol::wl_surface::WlSurface,
wayland::xdg_activation::{XdgActivationHandler, XdgActivationToken, XdgActivationTokenData},
};
use super::state::WaylandState;
impl XdgActivationHandler for WaylandState {
fn activation_state(&mut self) -> &mut smithay::wayland::xdg_activation::XdgActivationState {
&mut self.xdg_activation_state
}
fn request_activation(
&mut self,
token: XdgActivationToken,
token_data: XdgActivationTokenData,
_surface: WlSurface,
) {
dbg!(token);
dbg!(token_data);
}
fn destroy_activation(
&mut self,
token: XdgActivationToken,
token_data: XdgActivationTokenData,
_surface: WlSurface,
) {
dbg!(token);
dbg!(token_data);
}
}
delegate_xdg_activation!(WaylandState);

View File

@@ -1,549 +0,0 @@
use super::{
seat::{SeatWrapper, handle_cursor},
state::{ClientState, WaylandState},
surface::CoreSurface,
utils::*,
};
use crate::{
core::error::Result,
nodes::{
drawable::model::ModelPart,
items::panel::{
Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo,
},
},
};
use color_eyre::eyre::eyre;
use mint::Vector2;
use parking_lot::Mutex;
use rand::Rng;
use rustc_hash::FxHashMap;
use smithay::{
delegate_xdg_shell,
desktop::PopupKind,
reexports::{
wayland_protocols::xdg::{
decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
shell::server::xdg_toplevel::{ResizeEdge, State},
},
wayland_server::{
Resource,
protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface},
},
},
utils::{Logical, Rectangle, Serial},
wayland::{
compositor::add_post_commit_hook,
shell::xdg::{
PopupSurface, PositionerState, ShellClient, ToplevelSurface, XdgShellHandler,
XdgShellState,
},
},
};
use std::sync::{Arc, Weak};
use tracing::warn;
fn get_unconstrained_popup_geometry(positioner: &PositionerState) -> Geometry {
positioner
.get_unconstrained_geometry(Rectangle {
loc: (-100000, -100000).into(),
size: (200000, 200000).into(),
})
.into()
}
impl From<Rectangle<i32, Logical>> for Geometry {
fn from(value: Rectangle<i32, Logical>) -> Self {
Geometry {
origin: [value.loc.x, value.loc.y].into(),
size: [value.size.w as u32, value.size.h as u32].into(),
}
}
}
pub fn surface_panel_item(wl_surface: &WlSurface) -> Option<Arc<PanelItem<XdgBackend>>> {
let panel_item = wl_surface
.get_data::<Weak<PanelItem<XdgBackend>>>()
.as_ref()
.and_then(Weak::upgrade);
if panel_item.is_none() {
warn!("Couldn't get panel item");
}
panel_item
}
impl XdgShellHandler for WaylandState {
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
&mut self.xdg_shell
}
fn new_client(&mut self, _client: ShellClient) {}
fn client_destroyed(&mut self, _client: ShellClient) {}
fn new_toplevel(&mut self, toplevel: ToplevelSurface) {
toplevel.wl_surface().insert_data(SurfaceId::Toplevel(()));
toplevel.with_pending_state(|s| {
s.decoration_mode = Some(Mode::ServerSide);
s.states.set(State::TiledTop);
s.states.set(State::TiledBottom);
s.states.set(State::TiledLeft);
s.states.set(State::TiledRight);
s.states.set(State::Maximized);
s.states.unset(State::Fullscreen);
});
let initial_size = toplevel
.wl_surface()
.get_size()
.unwrap_or(Vector2::from([0; 2]));
let initial_toplevel_info = ToplevelInfo {
parent: toplevel.wl_surface().get_parent(),
title: toplevel.wl_surface().get_title(),
app_id: toplevel.wl_surface().get_app_id(),
size: initial_size,
min_size: toplevel
.wl_surface()
.min_size()
.map(|s| Vector2::from([s.x as f32, s.y as f32])),
max_size: toplevel
.wl_surface()
.max_size()
.map(|s| Vector2::from([s.x as f32, s.y as f32])),
logical_rectangle: toplevel.wl_surface().get_geometry().unwrap_or(Geometry {
origin: [0; 2].into(),
size: initial_size,
}),
};
toplevel
.wl_surface()
.insert_data(Mutex::new(initial_toplevel_info));
CoreSurface::add_to(toplevel.wl_surface());
add_post_commit_hook(
toplevel.wl_surface(),
|_state: &mut WaylandState, _dh, surf| {
let parent = surf.get_parent();
let new_size = surf.get_size().unwrap_or(Vector2::from([0; 2]));
let min_size = surf
.min_size()
.map(|s| Vector2::from([s.x as f32, s.y as f32]));
let max_size = surf
.max_size()
.map(|s| Vector2::from([s.x as f32, s.y as f32]));
let logical_rectangle = surf.get_geometry().unwrap_or_default();
let mut size_changed = false;
surf.with_toplevel_info(|info| {
info.parent = parent;
if new_size != info.size {
info.size = new_size;
size_changed = true;
}
info.min_size = min_size;
info.max_size = max_size;
info.logical_rectangle = logical_rectangle;
});
if size_changed {
let Some(panel_item) = surface_panel_item(surf) else {
return;
};
if let Some(toplevel_info) = surf.get_toplevel_info() {
panel_item.toplevel_size_changed(toplevel_info.size);
}
}
},
);
add_post_commit_hook(
toplevel.wl_surface(),
|state: &mut WaylandState, _dh, surf| {
if surface_panel_item(surf).is_some() {
return;
}
let client = surf.client().unwrap();
let client_state = client.get_data::<ClientState>().unwrap();
let Some(toplevel) = state
.xdg_shell
.toplevel_surfaces()
.iter()
.find(|s| s.wl_surface() == surf)
else {
return;
};
let (node, panel_item) = PanelItem::create(
Box::new(XdgBackend::create(
toplevel.clone(),
client_state.seat.clone(),
)),
client_state.pid,
);
handle_cursor(&panel_item, panel_item.backend.seat.cursor_info_rx.clone());
surf.insert_data(Arc::downgrade(&panel_item));
surf.insert_data(node);
},
);
}
fn toplevel_destroyed(&mut self, toplevel: ToplevelSurface) {
let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
return;
};
panel_item.backend.seat.unfocus(toplevel.wl_surface(), self);
panel_item.backend.toplevel.lock().take();
panel_item.backend.children.lock().clear();
}
fn app_id_changed(&mut self, toplevel: ToplevelSurface) {
let wl_surface = toplevel.wl_surface();
let Some(app_id) = wl_surface.get_app_id() else {
return;
};
wl_surface.with_toplevel_info(|info| {
info.app_id = Some(app_id.clone());
});
let Some(panel_item) = surface_panel_item(wl_surface) else {
return;
};
panel_item.toplevel_app_id_changed(&app_id)
}
fn title_changed(&mut self, toplevel: ToplevelSurface) {
let wl_surface = toplevel.wl_surface();
let Some(title) = wl_surface.get_title() else {
return;
};
wl_surface.with_toplevel_info(|info| {
info.title = Some(title.clone());
});
let Some(panel_item) = surface_panel_item(wl_surface) else {
return;
};
panel_item.toplevel_title_changed(&title)
}
fn new_popup(&mut self, popup: PopupSurface, positioner: PositionerState) {
self.popup_manager
.track_popup(PopupKind::Xdg(popup.clone()))
.unwrap();
let id = rand::thread_rng().gen_range(0..u64::MAX);
popup.wl_surface().insert_data(SurfaceId::Child(id));
let Some(parent) = popup.get_parent_surface() else {
warn!("No parent surface found for popup");
return;
};
CoreSurface::add_to(popup.wl_surface());
popup.wl_surface().insert_data(Mutex::new(ChildInfo {
id,
parent: parent.get_data::<SurfaceId>().unwrap(),
geometry: get_unconstrained_popup_geometry(&positioner),
z_order: 1,
receives_input: true,
}));
let Some(panel_item) = surface_panel_item(&parent) else {
warn!("No panel item found for popup parent");
return;
};
let panel_item_weak = Arc::downgrade(&panel_item);
add_post_commit_hook(
popup.wl_surface(),
move |_: &mut WaylandState, _dh, surf| {
if surface_panel_item(surf).is_some() {
return;
}
surf.insert_data(panel_item_weak.clone());
let Some(panel) = surface_panel_item(surf) else {
warn!("Failed to get panel item for popup surface");
return;
};
panel.backend.new_child(surf);
},
);
}
fn reposition_request(
&mut self,
popup: PopupSurface,
positioner: PositionerState,
_token: u32,
) {
let Some(panel_item) = surface_panel_item(popup.wl_surface()) else {
return;
};
popup.wl_surface().with_child_info(|ci| {
ci.geometry = get_unconstrained_popup_geometry(&positioner);
});
panel_item.backend.reposition_child(popup.wl_surface());
}
fn popup_destroyed(&mut self, popup: PopupSurface) {
let Some(panel_item) = surface_panel_item(popup.wl_surface()) else {
return;
};
panel_item.backend.seat.unfocus(popup.wl_surface(), self);
panel_item.backend.drop_child(popup.wl_surface());
}
fn grab(&mut self, _popup: PopupSurface, _seat: WlSeat, _serial: Serial) {}
fn move_request(&mut self, toplevel: ToplevelSurface, _seat: WlSeat, _serial: Serial) {
let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
return;
};
panel_item.toplevel_move_request();
}
fn resize_request(
&mut self,
toplevel: ToplevelSurface,
_seat: WlSeat,
_serial: Serial,
edges: ResizeEdge,
) {
let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
return;
};
let (up, down, left, right) = match edges {
ResizeEdge::None => (false, false, false, false),
ResizeEdge::Top => (true, false, false, false),
ResizeEdge::Bottom => (false, true, false, false),
ResizeEdge::Left => (false, false, true, false),
ResizeEdge::TopLeft => (true, false, true, false),
ResizeEdge::BottomLeft => (false, true, true, false),
ResizeEdge::Right => (false, false, false, true),
ResizeEdge::TopRight => (true, false, false, true),
ResizeEdge::BottomRight => (false, true, false, true),
_ => (false, false, false, false),
};
panel_item.toplevel_resize_request(up, down, left, right);
}
fn maximize_request(&mut self, toplevel: ToplevelSurface) {
toplevel.with_pending_state(|s| {
s.states.set(State::Maximized);
s.states.unset(State::Fullscreen);
});
toplevel.send_configure();
let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
return;
};
panel_item.toplevel_fullscreen_active(false);
}
fn fullscreen_request(&mut self, toplevel: ToplevelSurface, _output: Option<WlOutput>) {
toplevel.with_pending_state(|s| {
s.states.set(State::Fullscreen);
s.states.unset(State::Maximized);
});
toplevel.send_configure();
let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
return;
};
panel_item.toplevel_fullscreen_active(true);
}
}
delegate_xdg_shell!(WaylandState);
pub struct XdgBackend {
toplevel: Mutex<Option<ToplevelSurface>>,
pub children: Mutex<FxHashMap<u64, WlSurface>>,
seat: Arc<SeatWrapper>,
}
impl XdgBackend {
pub fn create(toplevel: ToplevelSurface, seat: Arc<SeatWrapper>) -> Self {
XdgBackend {
toplevel: Mutex::new(Some(toplevel)),
children: Mutex::new(FxHashMap::default()),
seat,
}
}
fn wl_surface_from_id(&self, id: &SurfaceId) -> Option<WlSurface> {
match id {
SurfaceId::Toplevel(_) => Some(self.toplevel.lock().clone()?.wl_surface().clone()),
SurfaceId::Child(id) => self.children.lock().get(id).cloned(),
}
}
fn panel_item(&self) -> Option<Arc<PanelItem<XdgBackend>>> {
surface_panel_item(self.toplevel.lock().clone()?.wl_surface())
}
pub fn new_child(&self, surface: &WlSurface) {
let Some(panel_item) = self.panel_item() else {
return;
};
let Some(child_info) = surface.get_child_info() else {
return;
};
self.children.lock().insert(child_info.id, surface.clone());
panel_item.create_child(child_info.id, &child_info);
}
pub fn reposition_child(&self, surface: &WlSurface) {
let Some(panel_item) = self.panel_item() else {
return;
};
let Some(child_info) = surface.get_child_info() else {
return;
};
panel_item.reposition_child(child_info.id, &child_info.geometry);
}
pub fn drop_child(&self, surface: &WlSurface) {
let Some(panel_item) = self.panel_item() else {
return;
};
let Some(child_info) = surface.get_child_info() else {
return;
};
panel_item.destroy_child(child_info.id);
self.children.lock().remove(&child_info.id);
}
}
impl Backend for XdgBackend {
fn start_data(&self) -> Result<PanelItemInitData> {
let cursor = self
.seat
.cursor_info_rx
.borrow()
.surface
.clone()
.and_then(|s| s.upgrade().ok())
.as_ref()
.and_then(|c| c.get_size())
.map(|size| Geometry {
origin: [0; 2].into(),
size,
});
let toplevel_info = self
.toplevel
.lock()
.as_ref()
.and_then(|toplevel| toplevel.wl_surface().get_toplevel_info())
.ok_or(eyre!("Internal: no toplevel or ToplevelInfo"))?;
let children = self
.children
.lock()
.values()
.filter_map(|v| v.get_child_info())
.collect();
Ok(PanelItemInitData {
cursor,
toplevel: toplevel_info,
children,
pointer_grab: None,
keyboard_grab: None,
})
}
fn apply_cursor_material(&self, model_part: &Arc<ModelPart>) {
let Some(surface) = self
.seat
.cursor_info_rx
.borrow()
.surface
.clone()
.and_then(|s| s.upgrade().ok())
else {
return;
};
let Some(core_surface) = CoreSurface::from_wl_surface(&surface) else {
return;
};
core_surface.apply_material(model_part);
}
fn apply_surface_material(&self, surface: SurfaceId, model_part: &Arc<ModelPart>) {
let Some(surface) = self.wl_surface_from_id(&surface) else {
return;
};
let Some(core_surface) = CoreSurface::from_wl_surface(&surface) else {
return;
};
core_surface.apply_material(model_part);
}
fn close_toplevel(&self) {
if let Some(toplevel) = self.toplevel.lock().clone() {
self.seat.unfocus_internal_state(toplevel.wl_surface());
toplevel.send_close();
}
}
fn auto_size_toplevel(&self) {
let Some(toplevel) = self.toplevel.lock().clone() else {
return;
};
toplevel.with_pending_state(|s| s.size = None);
toplevel.send_configure();
}
fn set_toplevel_size(&self, size: Vector2<u32>) {
let Some(toplevel) = self.toplevel.lock().clone() else {
return;
};
toplevel.with_pending_state(|s| {
s.size = Some((size.x.max(16) as i32, size.y.max(16) as i32).into())
});
toplevel.send_pending_configure();
}
fn set_toplevel_focused_visuals(&self, focused: bool) {
let Some(toplevel) = self.toplevel.lock().clone() else {
return;
};
toplevel.with_pending_state(|s| {
if focused {
s.states.set(State::Activated);
} else {
s.states.unset(State::Activated);
}
})
}
fn pointer_motion(&self, surface: &SurfaceId, position: Vector2<f32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat.pointer_motion(surface, position)
}
fn pointer_button(&self, _surface: &SurfaceId, button: u32, pressed: bool) {
self.seat.pointer_button(button, pressed)
}
fn pointer_scroll(
&self,
_surface: &SurfaceId,
scroll_distance: Option<Vector2<f32>>,
scroll_steps: Option<Vector2<f32>>,
) {
self.seat.pointer_scroll(scroll_distance, scroll_steps)
}
fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat.keyboard_key(surface, keymap_id, key, pressed)
}
fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>) {
let Some(surface) = self.wl_surface_from_id(surface) else {
return;
};
self.seat.touch_down(surface, id, position)
}
fn touch_move(&self, id: u32, position: Vector2<f32>) {
self.seat.touch_move(id, position)
}
fn touch_up(&self, id: u32) {
self.seat.touch_up(id)
}
fn reset_input(&self) {
self.seat.reset_input()
}
}