Files
server/src/wayland/mod.rs
2025-08-09 00:00:58 +02:00

392 lines
11 KiB
Rust

mod core;
mod display;
mod dmabuf;
mod mesa_drm;
mod presentation;
mod registry;
mod util;
mod vulkano_data;
mod xdg;
use crate::core::registry::OwnedRegistry;
use crate::nodes::drawable::model::ModelNodeSystemSet;
use crate::wayland::core::seat::SeatMessage;
use crate::wayland::core::surface::Surface;
use crate::wayland::presentation::MonotonicTimestamp;
use crate::{
BevyMaterial,
core::{
error::{Result, ServerError},
task,
},
};
use bevy::app::{App, Plugin, Update};
use bevy::diagnostic::FrameCount;
use bevy::ecs::schedule::IntoScheduleConfigs;
use bevy::ecs::system::{Local, Res, ResMut};
use bevy::prelude::{Deref, DerefMut};
use bevy::render::renderer::RenderDevice;
use bevy::render::{Render, RenderApp};
use bevy::{asset::Assets, ecs::resource::Resource, image::Image};
use bevy_dmabuf::import::ImportedDmatexs;
use bevy_mod_openxr::render::end_frame;
use bevy_mod_xr::session::XrRenderSet;
use cluFlock::{FlockLock, ToFlock};
use core::buffer::BufferUsage;
use core::{buffer::Buffer, callback::Callback, surface::WL_SURFACE_REGISTRY};
use display::Display;
use mint::Vector2;
use std::fs::File;
use std::time::Duration;
use std::{
fs::{self, OpenOptions},
io::{self, ErrorKind},
os::unix::fs::OpenOptionsExt,
path::PathBuf,
sync::{Arc, OnceLock},
};
use tokio::{net::UnixStream, sync::mpsc, task::AbortHandle};
use tokio_stream::StreamExt;
use tracing::{debug_span, instrument};
use vulkano_data::setup_vulkano_context;
use waynest::{
server::{
self,
protocol::{
core::wayland::{wl_buffer::WlBuffer, wl_callback::WlCallback, wl_display::WlDisplay},
stable::xdg_shell::xdg_toplevel::XdgToplevel,
},
},
wire::{DecodeError, ObjectId},
};
use xdg::toplevel::Toplevel;
pub static WAYLAND_DISPLAY: OnceLock<PathBuf> = OnceLock::new();
impl From<waynest::server::Error> for ServerError {
fn from(err: waynest::server::Error) -> Self {
ServerError::WaylandError(err)
}
}
pub fn get_free_wayland_socket_path() -> Option<(PathBuf, FlockLock<File>)> {
// 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 Ok(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)
else {
continue;
};
// Atomic mutual exclusion: fail if another process holds the lock\
let Ok(lock) = lock.try_exclusive_lock() else {
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, lock));
}
None // Exhausted all conventional display numbers
}
pub enum Message {
Disconnect,
Frame(Arc<Callback>),
ReleaseBuffer(Arc<Buffer>),
CloseToplevel(Arc<Toplevel>),
ResizeToplevel {
toplevel: Arc<Toplevel>,
size: Option<Vector2<u32>>,
},
SetToplevelVisualActive {
toplevel: Arc<Toplevel>,
active: bool,
},
Seat(SeatMessage),
SendPresentationFeedback {
surface: Arc<Surface>,
display_timestamp: MonotonicTimestamp,
refresh_cycle: u64,
},
}
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 && 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<bool, waynest::server::Error> {
match message {
Message::Disconnect => return Ok(true),
Message::Frame(callback) => {
let now = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic);
let now = Duration::new(now.tv_sec as u64, now.tv_nsec as u32);
let ms = (now.as_millis() % (u32::MAX as u128)) as u32;
callback.done(client, callback.0, ms).await?;
client
.get::<Display>(ObjectId::DISPLAY)
.unwrap()
.delete_id(client, ObjectId::DISPLAY, callback.0.as_raw())
.await?;
client.remove(callback.0);
}
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?;
}
}
Message::SendPresentationFeedback {
surface,
display_timestamp,
refresh_cycle,
} => {
surface
.send_presentation_feedback(client, display_timestamp, refresh_cycle)
.await?;
}
}
Ok(false)
}
}
impl Drop for WaylandClient {
fn drop(&mut self) {
self.abort_handle.abort();
}
}
#[derive(Debug, Resource)]
pub struct Wayland {
_lockfile: FlockLock<File>,
abort_handle: AbortHandle,
}
impl Wayland {
pub fn new() -> Result<Self> {
let (socket_path, _lockfile) =
get_free_wayland_socket_path().ok_or(ServerError::WaylandError(
waynest::server::Error::IoError(std::io::ErrorKind::AddrNotAvailable.into()),
))?;
let _ = WAYLAND_DISPLAY.set(socket_path.clone());
let listener =
server::Listener::new_with_path(&socket_path).map_err(ServerError::WaylandError)?;
let abort_handle =
task::new(|| "wayland loop", Self::handle_wayland_loop(listener))?.abort_handle();
Ok(Self {
_lockfile,
abort_handle,
})
}
async fn handle_wayland_loop(mut listener: server::Listener) -> Result<()> {
let mut clients = Vec::new();
loop {
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(())
}
}
impl Drop for Wayland {
fn drop(&mut self) {
self.abort_handle.abort();
}
}
static RENDER_DEVICE: OnceLock<RenderDevice> = OnceLock::new();
pub struct WaylandPlugin;
impl Plugin for WaylandPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, update_graphics.before(ModelNodeSystemSet));
app.init_resource::<UsedBuffers>();
app.sub_app_mut(RenderApp)
.init_resource::<UsedBuffers>()
.add_systems(
Render,
init_render_device.run_if(|| RENDER_DEVICE.get().is_none()),
);
}
fn finish(&self, app: &mut App) {
app.sub_app_mut(RenderApp)
.add_systems(Render, setup_vulkano_context)
.add_systems(Render, before_render.in_set(XrRenderSet::PreRender))
.add_systems(Render, after_render.in_set(XrRenderSet::PostRender))
.add_systems(
Render,
submit_frame_timings
.in_set(XrRenderSet::PostRender)
.after(end_frame),
);
}
}
fn init_render_device(dev: Res<RenderDevice>) {
_ = RENDER_DEVICE.set(dev.clone());
}
#[derive(Resource, Deref, DerefMut)]
struct UsedBuffers(OwnedRegistry<BufferUsage>);
impl Default for UsedBuffers {
fn default() -> Self {
Self(OwnedRegistry::new())
}
}
fn before_render(buffers: Res<UsedBuffers>) {
for buf in WL_SURFACE_REGISTRY
.get_valid_contents()
.into_iter()
.filter_map(|surface| surface.current_state().buffer)
.filter_map(|buffer| buffer.usage)
{
buffers.add_raw(buf);
}
for surface in WL_SURFACE_REGISTRY.get_valid_contents() {
surface.frame_event();
}
}
fn after_render(buffers: Res<UsedBuffers>) {
buffers.clear();
}
#[instrument(level = "debug", name = "Wayland frame", skip_all)]
fn update_graphics(
dmatexes: Res<ImportedDmatexs>,
mut materials: ResMut<Assets<BevyMaterial>>,
mut images: ResMut<Assets<Image>>,
) {
for surface in WL_SURFACE_REGISTRY.get_valid_contents() {
surface.update_graphics(&dmatexes, &mut materials, &mut images);
}
}
#[instrument(level = "debug", name = "Wayland frame", skip_all)]
fn submit_frame_timings(mut frame_count: Local<u64>) {
*frame_count += 1;
for surface in WL_SURFACE_REGISTRY.get_valid_contents() {
let display_timestamp =
rustix::time::clock_gettime(rustix::time::ClockId::Monotonic).into();
surface.submit_presentation_feedback(display_timestamp, *frame_count);
}
}