feat: wayland
This commit is contained in:
@@ -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}")]
|
||||
|
||||
7
src/core/graphics_info.rs
Normal file
7
src/core/graphics_info.rs
Normal 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>>,
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
12
src/main.rs
12
src/main.rs
@@ -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>>());
|
||||
});
|
||||
}
|
||||
|
||||
5
src/nodes/drawable/holdout.wgsl
Normal file
5
src/nodes/drawable/holdout.wgsl
Normal file
@@ -0,0 +1,5 @@
|
||||
fn fragment(
|
||||
in: VertexOutput
|
||||
) -> FragmentOutput {
|
||||
return vec4(0.0);
|
||||
}
|
||||
@@ -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(),
|
||||
});
|
||||
|
||||
@@ -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."]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
89
src/wayland/core/buffer.rs
Normal file
89
src/wayland/core/buffer.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
10
src/wayland/core/callback.rs
Normal file
10
src/wayland/core/callback.rs
Normal 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 {}
|
||||
70
src/wayland/core/compositor.rs
Normal file
70
src/wayland/core/compositor.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
75
src/wayland/core/display.rs
Normal file
75
src/wayland/core/display.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
259
src/wayland/core/keyboard.rs
Normal file
259
src/wayland/core/keyboard.rs
Normal 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
14
src/wayland/core/mod.rs
Normal 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;
|
||||
16
src/wayland/core/output.rs
Normal file
16
src/wayland/core/output.rs
Normal 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
183
src/wayland/core/pointer.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
148
src/wayland/core/registry.rs
Normal file
148
src/wayland/core/registry.rs
Normal 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
191
src/wayland/core/seat.rs
Normal 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
39
src/wayland/core/shm.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
102
src/wayland/core/shm_buffer_backing.rs
Normal file
102
src/wayland/core/shm_buffer_backing.rs
Normal 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
|
||||
}
|
||||
}
|
||||
74
src/wayland/core/shm_pool.rs
Normal file
74
src/wayland/core/shm_pool.rs
Normal 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
367
src/wayland/core/surface.rs
Normal 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, ¤t_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
75
src/wayland/core/touch.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
@@ -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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
200
src/wayland/dmabuf/buffer_backing.rs
Normal file
200
src/wayland/dmabuf/buffer_backing.rs
Normal 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
|
||||
}
|
||||
}
|
||||
163
src/wayland/dmabuf/buffer_params.rs
Normal file
163
src/wayland/dmabuf/buffer_params.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
92
src/wayland/dmabuf/feedback.rs
Normal file
92
src/wayland/dmabuf/feedback.rs
Normal 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
108
src/wayland/dmabuf/mod.rs
Normal 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(¶ms);
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
42
src/wayland/util.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
@@ -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
246
src/wayland/xdg/backend.rs
Normal 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
6
src/wayland/xdg/mod.rs
Normal 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
89
src/wayland/xdg/popup.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
229
src/wayland/xdg/positioner.rs
Normal file
229
src/wayland/xdg/positioner.rs
Normal 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
206
src/wayland/xdg/surface.rs
Normal 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
310
src/wayland/xdg/toplevel.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
48
src/wayland/xdg/wm_base.rs
Normal file
48
src/wayland/xdg/wm_base.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user