feat: rudimentary implementation with Layer Shell

This commit is contained in:
Naman Agrawal
2025-08-20 17:46:35 +05:30
parent 64508506d1
commit 93b879c937
9 changed files with 5857 additions and 3 deletions
Generated
+5371
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -4,3 +4,8 @@ version = "0.1.0"
edition = "2024"
[dependencies]
bevy = "0.16.1"
lazy_static = "1.5.0"
raw-window-handle = "0.6.2"
smithay-client-toolkit = "0.20.0"
wayland-backend = { version = "0.3.11", features = ["client_system"] }
+18
View File
@@ -0,0 +1,18 @@
use bevy::{prelude::*, winit::WinitPlugin};
use bevy_wayland::{layer_shell::LayerShellWindow, WaylandPlugin};
fn main() {
App::new()
.add_plugins((
DefaultPlugins.build().disable::<WinitPlugin>(),
WaylandPlugin,
))
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands, windows: Query<Entity, With<Window>>) {
for entity in &windows {
commands.entity(entity).insert(LayerShellWindow::default());
}
}
+62
View File
@@ -0,0 +1,62 @@
use bevy::prelude::*;
use smithay_client_toolkit::{
delegate_seat,
reexports::client::QueueHandle,
seat::{SeatHandler, SeatState},
};
use crate::WaylandState;
pub struct InputHandlerPlugin;
impl Plugin for InputHandlerPlugin {
fn build(&self, app: &mut App) {
let globals = app.world().non_send_resource();
let queue_handle: &QueueHandle<WaylandState> = app.world().non_send_resource();
let seat_state = SeatState::new(globals, queue_handle);
app.insert_non_send_resource(seat_state);
}
}
impl SeatHandler for WaylandState {
fn seat_state(&mut self) -> &mut SeatState {
self.world_mut()
.non_send_resource_mut::<SeatState>()
.into_inner()
}
fn new_seat(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
) {
}
fn new_capability(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
_capability: smithay_client_toolkit::seat::Capability,
) {
}
fn remove_capability(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
_capability: smithay_client_toolkit::seat::Capability,
) {
}
fn remove_seat(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_seat: smithay_client_toolkit::reexports::client::protocol::wl_seat::WlSeat,
) {
}
}
delegate_seat!(WaylandState);
+75
View File
@@ -0,0 +1,75 @@
use bevy::prelude::*;
use smithay_client_toolkit::{
delegate_layer,
reexports::client::{globals::GlobalList, QueueHandle},
shell::{
wlr_layer::{Layer, LayerShell, LayerShellHandler},
WaylandSurface,
},
};
use crate::{surface_handler::WaylandSurfaces, WaylandState};
#[derive(Component, Default)]
pub struct LayerShellWindow {}
#[derive(Component)]
struct LayerShellRoleAssigned;
pub struct LayerShellPlugin;
impl Plugin for LayerShellPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, assign_layer_shell_role);
}
}
fn assign_layer_shell_role(
mut commands: Commands,
wayland_surfaces: NonSend<WaylandSurfaces>,
queue_handle: NonSend<QueueHandle<WaylandState>>,
globals: NonSend<GlobalList>,
layer_shell_windows: Query<
(Entity, &Window, &LayerShellWindow),
Without<LayerShellRoleAssigned>,
>,
) {
for (entity, _window, _layer_shell_settings) in &layer_shell_windows {
let window_wrapper = wayland_surfaces.get_window_wrapper(entity);
let surface = window_wrapper
.expect("tried to assign role before creating surface!")
.wl_surface();
let layer_shell =
LayerShell::bind(&globals, &queue_handle).expect("layer shell not available!");
let layer = layer_shell.create_layer_surface(
&queue_handle,
surface.clone(),
Layer::Top,
Some("simple_layer"),
None,
);
layer.commit();
Box::leak(Box::new(layer));
commands.entity(entity).insert(LayerShellRoleAssigned);
}
}
impl LayerShellHandler for WaylandState {
fn closed(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_layer: &smithay_client_toolkit::shell::wlr_layer::LayerSurface,
) {
}
fn configure(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_layer: &smithay_client_toolkit::shell::wlr_layer::LayerSurface,
_configure: smithay_client_toolkit::shell::wlr_layer::LayerSurfaceConfigure,
_serial: u32,
) {
}
}
delegate_layer!(WaylandState);
+79
View File
@@ -0,0 +1,79 @@
use bevy::{app::PluginsState, prelude::*};
use smithay_client_toolkit::{
delegate_registry,
output::OutputState,
reexports::{
calloop::EventLoop,
calloop_wayland_source::WaylandSource,
client::{globals::registry_queue_init, Connection},
},
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
seat::SeatState,
};
mod input_handler;
pub mod layer_shell;
mod output_handler;
mod surface_handler;
#[derive(Default)]
pub struct WaylandPlugin;
impl Plugin for WaylandPlugin {
fn build(&self, app: &mut App) {
let connection =
Connection::connect_to_env().expect("failed to connect to wayland socket!");
let event_loop =
EventLoop::<WaylandState>::try_new().expect("failed to create event_loop!");
let (globals, event_queue) = registry_queue_init::<WaylandState>(&connection)
.expect("failed to init registry queue");
let qh = event_queue.handle();
let loop_handle = event_loop.handle();
WaylandSource::new(connection.clone(), event_queue)
.insert(loop_handle.clone())
.expect("failed to insert wayland source to event loop");
app.insert_non_send_resource(RegistryState::new(&globals));
app.insert_non_send_resource(connection.clone());
app.insert_non_send_resource(globals);
app.insert_non_send_resource(qh);
app.add_plugins((
output_handler::OutputHandlerPlugin,
input_handler::InputHandlerPlugin,
surface_handler::SurfaceHandlerPlugin,
layer_shell::LayerShellPlugin,
));
app.set_runner(|app| runner(app, event_loop));
}
}
pub fn runner(mut app: App, mut event_loop: EventLoop<'_, WaylandState>) -> AppExit {
if app.plugins_state() == PluginsState::Ready {
app.finish();
app.cleanup();
}
let mut state = WaylandState(app);
loop {
// TODO: Error handling
let _ = event_loop.dispatch(None, &mut state);
if state.plugins_state() == PluginsState::Cleaned {
state.update();
}
}
}
#[derive(Deref, DerefMut)]
pub struct WaylandState(App);
impl ProvidesRegistryState for WaylandState {
fn registry(&mut self) -> &mut smithay_client_toolkit::registry::RegistryState {
self.world_mut()
.non_send_resource_mut::<RegistryState>()
.into_inner()
}
registry_handlers!(OutputState, SeatState);
}
delegate_registry!(WaylandState);
-3
View File
@@ -1,3 +0,0 @@
fn main() {
println!("Hello, world!");
}
+53
View File
@@ -0,0 +1,53 @@
use bevy::prelude::*;
use smithay_client_toolkit::{
delegate_output,
output::{OutputHandler, OutputState},
reexports::client::QueueHandle,
};
use crate::WaylandState;
pub struct OutputHandlerPlugin;
impl Plugin for OutputHandlerPlugin {
fn build(&self, app: &mut App) {
let globals = app.world().non_send_resource();
let queue_handle: &QueueHandle<WaylandState> = app.world().non_send_resource();
let output_state = OutputState::new(globals, queue_handle);
app.insert_non_send_resource(output_state);
}
}
impl OutputHandler for WaylandState {
fn output_state(&mut self) -> &mut OutputState {
self.world_mut()
.non_send_resource_mut::<OutputState>()
.into_inner()
}
fn new_output(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_output: smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput,
) {
info!("new output was added");
}
fn update_output(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_output: smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput,
) {
}
fn output_destroyed(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_output: smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput,
) {
}
}
delegate_output!(WaylandState);
+194
View File
@@ -0,0 +1,194 @@
use std::collections::HashMap;
use bevy::{
ecs::entity::EntityHashMap,
prelude::*,
window::{RawHandleWrapper, RawHandleWrapperHolder, WindowCreated, WindowWrapper},
};
use raw_window_handle::{
DisplayHandle, HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle,
WaylandDisplayHandle, WaylandWindowHandle, WindowHandle,
};
use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
delegate_compositor,
reexports::client::{
backend::ObjectId, protocol::wl_surface::WlSurface, Connection, Proxy, QueueHandle,
},
};
use crate::WaylandState;
pub struct SurfaceHandlerPlugin;
impl Plugin for SurfaceHandlerPlugin {
fn build(&self, app: &mut App) {
let queue_handle: &QueueHandle<WaylandState> = app.world().non_send_resource();
let globals = app.world().non_send_resource();
app.insert_non_send_resource(
CompositorState::bind(globals, queue_handle).expect("failed to bind compositor!"),
);
app.insert_non_send_resource(WaylandSurfaces::default());
app.add_systems(PreUpdate, create_windows);
}
}
impl CompositorHandler for WaylandState {
fn scale_factor_changed(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
_new_factor: i32,
) {
}
fn transform_changed(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
_new_transform: smithay_client_toolkit::reexports::client::protocol::wl_output::Transform,
) {
}
fn frame(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
_time: u32,
) {
}
fn surface_enter(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
_output: &smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput,
) {
}
fn surface_leave(
&mut self,
_conn: &smithay_client_toolkit::reexports::client::Connection,
_qh: &QueueHandle<Self>,
_surface: &smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface,
_output: &smithay_client_toolkit::reexports::client::protocol::wl_output::WlOutput,
) {
}
}
delegate_compositor!(WaylandState);
#[derive(Default)]
pub struct WaylandSurfaces {
windows: HashMap<ObjectId, WindowWrapper<WaylandSurface>>,
entity_to_surface: EntityHashMap<ObjectId>,
surface_to_entity: HashMap<ObjectId, Entity>,
_not_send_sync: core::marker::PhantomData<*const ()>,
}
impl WaylandSurfaces {
pub fn create_surface(
&mut self,
entity: Entity,
queue_handle: &QueueHandle<WaylandState>,
connection: Connection,
compositor_state: &CompositorState,
) -> &WindowWrapper<WaylandSurface> {
let wl_surface = compositor_state.create_surface(queue_handle);
let wayland_surface = WaylandSurface::new(wl_surface, connection);
let surface_id = wayland_surface.id();
self.windows
.insert(wayland_surface.id(), WindowWrapper::new(wayland_surface));
self.entity_to_surface.insert(entity, surface_id.clone());
self.surface_to_entity.insert(surface_id.clone(), entity);
self.windows.get(&surface_id).unwrap()
}
pub fn get_window_wrapper(&self, entity: Entity) -> Option<&WindowWrapper<WaylandSurface>> {
self.entity_to_surface
.get(&entity)
.map(|surface_id| self.windows.get(surface_id))?
}
}
pub struct WaylandSurface {
surface: WlSurface,
connection: Connection,
}
impl WaylandSurface {
pub fn new(surface: WlSurface, connection: Connection) -> Self {
Self {
surface,
connection,
}
}
pub fn wl_surface(&self) -> &WlSurface {
&self.surface
}
pub fn id(&self) -> ObjectId {
self.surface.id()
}
}
impl HasWindowHandle for WaylandSurface {
fn window_handle(
&self,
) -> std::result::Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError>
{
let raw_window_handle = RawWindowHandle::Wayland(WaylandWindowHandle::new(
core::ptr::NonNull::new(self.wl_surface().id().as_ptr() as *mut _).unwrap(),
));
unsafe { Ok(WindowHandle::borrow_raw(raw_window_handle)) }
}
}
impl HasDisplayHandle for WaylandSurface {
fn display_handle(
&self,
) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
let raw_display_handle = RawDisplayHandle::Wayland(WaylandDisplayHandle::new(
core::ptr::NonNull::new(self.connection.backend().display_ptr() as *mut _).unwrap(),
));
unsafe { Ok(DisplayHandle::borrow_raw(raw_display_handle)) }
}
}
pub fn create_windows(
mut commands: Commands,
mut wayland_surfaces: NonSendMut<WaylandSurfaces>,
compositor_state: NonSend<CompositorState>,
connection: NonSend<Connection>,
queue_handle: NonSend<QueueHandle<WaylandState>>,
bevy_windows: Query<(Entity, Option<&RawHandleWrapperHolder>), With<Window>>,
mut window_created_event: EventWriter<WindowCreated>,
) {
for (entity, handle_holder) in &bevy_windows {
if wayland_surfaces.get_window_wrapper(entity).is_some() {
continue;
}
println!("Creating Window");
let surface = wayland_surfaces.create_surface(
entity,
&queue_handle,
connection.clone(),
&compositor_state,
);
let mut wrapper: Option<_> = None;
if let Ok(handle_wrapper) = RawHandleWrapper::new(surface) {
wrapper = Some(handle_wrapper.clone());
if let Some(handle_holder) = handle_holder {
*handle_holder.0.lock().unwrap() = Some(handle_wrapper);
}
}
commands.entity(entity).insert(wrapper.unwrap());
window_created_event.write(WindowCreated { window: entity });
}
}