feat: wayland

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

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

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

View File

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

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

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

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

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

View File

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

View File

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

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

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

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

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