First commit. Version 0.1.0
This commit is contained in:
495
src/event_loop/handlers.rs
Normal file
495
src/event_loop/handlers.rs
Normal file
@@ -0,0 +1,495 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
use smithay_client_toolkit::
|
||||
{
|
||||
compositor::CompositorHandler,
|
||||
delegate_compositor, delegate_layer, delegate_output, delegate_registry,
|
||||
delegate_seat, delegate_keyboard, delegate_pointer, delegate_touch, delegate_shm,
|
||||
delegate_xdg_popup, delegate_xdg_shell, delegate_xdg_window,
|
||||
output::{ OutputHandler, OutputState },
|
||||
registry::{ ProvidesRegistryState, RegistryState },
|
||||
registry_handlers,
|
||||
seat::{ Capability, SeatHandler, SeatState },
|
||||
shell::
|
||||
{
|
||||
WaylandSurface,
|
||||
wlr_layer::{ LayerShellHandler, LayerSurface, LayerSurfaceConfigure },
|
||||
xdg::popup::{ Popup, PopupConfigure, PopupHandler },
|
||||
xdg::window::{ Window, WindowConfigure, WindowHandler },
|
||||
},
|
||||
shm::{ Shm, ShmHandler },
|
||||
};
|
||||
use smithay_client_toolkit::reexports::client::
|
||||
{
|
||||
protocol::
|
||||
{
|
||||
wl_callback::{ self, WlCallback },
|
||||
wl_output::{ self, WlOutput },
|
||||
wl_surface::WlSurface,
|
||||
wl_seat::WlSeat,
|
||||
},
|
||||
Connection, Dispatch, QueueHandle,
|
||||
};
|
||||
use wayland_protocols::wp::text_input::zv3::client::
|
||||
{
|
||||
zwp_text_input_manager_v3::ZwpTextInputManagerV3,
|
||||
zwp_text_input_v3::{ self, ZwpTextInputV3 },
|
||||
};
|
||||
|
||||
use crate::app::App;
|
||||
use super::app_data::AppData;
|
||||
|
||||
impl<A: App> CompositorHandler for AppData<A>
|
||||
{
|
||||
fn scale_factor_changed( &mut self, _: &Connection, _: &QueueHandle<Self>, surface: &WlSurface, new_factor: i32 )
|
||||
{
|
||||
if new_factor <= 0 { return; }
|
||||
let Some( focus ) = self.focus_for_surface( surface ) else { return };
|
||||
let ( pw, ph ) = {
|
||||
let shm = &self.shm;
|
||||
let ss = match focus
|
||||
{
|
||||
super::SurfaceFocus::Main => &mut self.main,
|
||||
super::SurfaceFocus::Overlay( id ) => match self.overlays.get_mut( &id )
|
||||
{
|
||||
Some( s ) => s,
|
||||
None => return,
|
||||
},
|
||||
};
|
||||
if new_factor == ss.scale_factor { return; }
|
||||
ss.scale_factor = new_factor;
|
||||
surface.set_buffer_scale( new_factor );
|
||||
if let Some( ref mut canvas ) = ss.canvas
|
||||
{
|
||||
canvas.set_dpi_scale( new_factor as f32 );
|
||||
}
|
||||
let pw = ss.width * new_factor as u32;
|
||||
let ph = ss.height * new_factor as u32;
|
||||
// Resize whichever rendering target is active. The two are mutually
|
||||
// exclusive so only one branch runs.
|
||||
if let Some( ref es ) = ss.egl_surface
|
||||
{
|
||||
es.resize( pw as i32, ph as i32 );
|
||||
// Canvas FBO reallocation is deferred to the draw path: it needs
|
||||
// `eglMakeCurrent` first.
|
||||
} else {
|
||||
ss.pool = Some(
|
||||
smithay_client_toolkit::shm::slot::SlotPool::new(
|
||||
( pw * ph * 4 ) as usize, shm,
|
||||
).expect( "pool" ),
|
||||
);
|
||||
if let Some( ref mut canvas ) = ss.canvas
|
||||
{
|
||||
canvas.resize( pw, ph );
|
||||
}
|
||||
}
|
||||
ss.request_redraw();
|
||||
( pw, ph )
|
||||
};
|
||||
// Notify the app of the new physical dimensions. The previous
|
||||
// `on_resize` it received was scaled with the OLD factor, so any
|
||||
// app-side state keyed off those pixels is now stale. Only fire
|
||||
// for the main surface — overlay resizes don't go through
|
||||
// `App::on_resize`.
|
||||
if matches!( focus, super::SurfaceFocus::Main )
|
||||
{
|
||||
self.app.on_resize( pw, ph );
|
||||
self.dirty_caches();
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_changed( &mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: wl_output::Transform ) {}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_surface: &WlSurface,
|
||||
_time: u32,
|
||||
)
|
||||
{
|
||||
// Do NOT set needs_redraw here — that would create an infinite 60 fps loop
|
||||
// (commit → frame callback → redraw → commit → ...). Redraws are driven
|
||||
// exclusively by input events, poll_external messages, and configure events.
|
||||
}
|
||||
|
||||
fn surface_enter( &mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: &WlOutput ) {}
|
||||
|
||||
fn surface_leave( &mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: &WlOutput ) {}
|
||||
}
|
||||
|
||||
impl<A: App> LayerShellHandler for AppData<A>
|
||||
{
|
||||
fn closed(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
layer: &LayerSurface,
|
||||
)
|
||||
{
|
||||
match self.focus_for_surface( layer.wl_surface() )
|
||||
{
|
||||
Some( super::SurfaceFocus::Main ) | None =>
|
||||
{
|
||||
if self.app.on_close_requested()
|
||||
{
|
||||
self.exit_requested = true;
|
||||
}
|
||||
}
|
||||
Some( super::SurfaceFocus::Overlay( id ) ) =>
|
||||
{
|
||||
// Compositor asked us to destroy this overlay. Remove it;
|
||||
// the next reconcile will not recreate it unless the app
|
||||
// still returns its id from `App::overlays()`.
|
||||
self.overlays.remove( &id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn configure(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
layer: &LayerSurface,
|
||||
configure: LayerSurfaceConfigure,
|
||||
_serial: u32,
|
||||
)
|
||||
{
|
||||
let ( w, h ) = configure.new_size;
|
||||
let ( w, h ) = ( w.max( 1 ), h.max( 1 ) );
|
||||
match self.focus_for_surface( layer.wl_surface() )
|
||||
{
|
||||
Some( super::SurfaceFocus::Main ) | None =>
|
||||
{
|
||||
self.on_configure( w, h );
|
||||
self.app.on_resize( w, h );
|
||||
}
|
||||
Some( super::SurfaceFocus::Overlay( id ) ) =>
|
||||
{
|
||||
if let Some( ss ) = self.overlays.get_mut( &id )
|
||||
{
|
||||
ss.on_configure( &self.shm, self.egl_context.as_ref(), w, h );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: App> WindowHandler for AppData<A>
|
||||
{
|
||||
fn request_close(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_window: &Window,
|
||||
)
|
||||
{
|
||||
if self.app.on_close_requested()
|
||||
{
|
||||
self.exit_requested = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn configure(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
window: &Window,
|
||||
configure: WindowConfigure,
|
||||
_serial: u32,
|
||||
)
|
||||
{
|
||||
// Mutter ignores set_fullscreen sent before the surface is
|
||||
// mapped, so reapply on the first configure.
|
||||
if self.pending_fullscreen
|
||||
{
|
||||
window.set_fullscreen( None );
|
||||
self.pending_fullscreen = false;
|
||||
}
|
||||
let w = configure.new_size.0.map( |v| v.get() ).unwrap_or( 800 );
|
||||
let h = configure.new_size.1.map( |v| v.get() ).unwrap_or( 600 );
|
||||
self.on_configure( w, h );
|
||||
self.app.on_resize( w, h );
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: App> ShmHandler for AppData<A>
|
||||
{
|
||||
fn shm_state( &mut self ) -> &mut Shm
|
||||
{
|
||||
&mut self.shm
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: App> OutputHandler for AppData<A>
|
||||
{
|
||||
fn output_state( &mut self ) -> &mut OutputState
|
||||
{
|
||||
&mut self.output_state
|
||||
}
|
||||
|
||||
fn new_output( &mut self, _: &Connection, qh: &QueueHandle<Self>, output: WlOutput )
|
||||
{
|
||||
// If we were waiting for an output to assign the layer surface to, create it now.
|
||||
// Same for any overlays that were created before an output existed.
|
||||
if let Some( ref layer_shell ) = self.layer_shell
|
||||
{
|
||||
self.main.surface.materialize( &self.compositor_state, layer_shell, qh, &output );
|
||||
for ss in self.overlays.values_mut()
|
||||
{
|
||||
ss.surface.materialize( &self.compositor_state, layer_shell, qh, &output );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_output( &mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput ) {}
|
||||
fn output_destroyed( &mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput ) {}
|
||||
}
|
||||
|
||||
impl<A: App> SeatHandler for AppData<A>
|
||||
{
|
||||
fn seat_state( &mut self ) -> &mut SeatState
|
||||
{
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
||||
fn new_seat(
|
||||
&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _seat: WlSeat,
|
||||
) {}
|
||||
|
||||
fn new_capability(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
seat: WlSeat,
|
||||
capability: Capability,
|
||||
)
|
||||
{
|
||||
match capability
|
||||
{
|
||||
Capability::Keyboard if self.keyboard.is_none() =>
|
||||
{
|
||||
self.keyboard = Some(
|
||||
self.seat_state
|
||||
.get_keyboard( qh, &seat, None )
|
||||
.expect( "keyboard" ),
|
||||
);
|
||||
}
|
||||
Capability::Pointer if self.pointer.is_none() =>
|
||||
{
|
||||
let pointer = self.seat_state
|
||||
.get_pointer( qh, &seat )
|
||||
.expect( "pointer" );
|
||||
// Create a per-pointer cursor-shape device when the
|
||||
// compositor advertises wp_cursor_shape_v1. The device
|
||||
// outlives the WlPointer we just created and is what
|
||||
// `set_shape(serial, shape)` is called on.
|
||||
if let Some( ref mgr ) = self.cursor_shape_manager
|
||||
{
|
||||
self.cursor_shape_device = Some( mgr.get_shape_device( &pointer, qh ) );
|
||||
}
|
||||
self.pointer = Some( pointer );
|
||||
}
|
||||
Capability::Touch if self.touch.is_none() =>
|
||||
{
|
||||
self.touch = Some(
|
||||
self.seat_state
|
||||
.get_touch( qh, &seat )
|
||||
.expect( "touch" ),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_capability(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_seat: WlSeat,
|
||||
capability: Capability,
|
||||
)
|
||||
{
|
||||
match capability
|
||||
{
|
||||
Capability::Keyboard => { self.keyboard = None; }
|
||||
Capability::Pointer => { self.pointer = None; }
|
||||
Capability::Touch => { self.touch = None; }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_seat(
|
||||
&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _seat: WlSeat,
|
||||
) {}
|
||||
}
|
||||
|
||||
impl<A: App> PopupHandler for AppData<A>
|
||||
{
|
||||
fn configure(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
popup: &Popup,
|
||||
config: PopupConfigure,
|
||||
)
|
||||
{
|
||||
let ( w, h ) = ( config.width.max( 1 ) as u32, config.height.max( 1 ) as u32 );
|
||||
if let Some( super::SurfaceFocus::Overlay( id ) ) = self.focus_for_surface( popup.wl_surface() )
|
||||
{
|
||||
if let Some( ss ) = self.overlays.get_mut( &id )
|
||||
{
|
||||
ss.on_configure( &self.shm, self.egl_context.as_ref(), w, h );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn done(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
popup: &Popup,
|
||||
)
|
||||
{
|
||||
if let Some( super::SurfaceFocus::Overlay( id ) ) = self.focus_for_surface( popup.wl_surface() )
|
||||
{
|
||||
if let Some( msg ) = self.overlay_dismiss_msg( id )
|
||||
{
|
||||
self.pending_msgs.push( msg );
|
||||
}
|
||||
self.overlays.remove( &id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Delegate macros ---
|
||||
|
||||
delegate_compositor!( @<A: App> AppData<A> );
|
||||
delegate_output!( @<A: App> AppData<A> );
|
||||
delegate_shm!( @<A: App> AppData<A> );
|
||||
delegate_seat!( @<A: App> AppData<A> );
|
||||
delegate_keyboard!( @<A: App> AppData<A> );
|
||||
delegate_pointer!( @<A: App> AppData<A> );
|
||||
delegate_touch!( @<A: App> AppData<A> );
|
||||
delegate_layer!( @<A: App> AppData<A> );
|
||||
delegate_xdg_shell!( @<A: App> AppData<A> );
|
||||
delegate_xdg_window!( @<A: App> AppData<A> );
|
||||
delegate_xdg_popup!( @<A: App> AppData<A> );
|
||||
delegate_registry!( @<A: App> AppData<A> );
|
||||
|
||||
impl<A: App> ProvidesRegistryState for AppData<A>
|
||||
{
|
||||
fn registry( &mut self ) -> &mut RegistryState
|
||||
{
|
||||
&mut self.registry_state
|
||||
}
|
||||
registry_handlers![ OutputState, SeatState ];
|
||||
}
|
||||
|
||||
// --- Dispatch impls for zwp_text_input_v3 ---
|
||||
|
||||
impl<A: App> Dispatch<ZwpTextInputManagerV3, ()> for AppData<A>
|
||||
{
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &ZwpTextInputManagerV3,
|
||||
_event: <ZwpTextInputManagerV3 as smithay_client_toolkit::reexports::client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
)
|
||||
{
|
||||
// no events from manager
|
||||
}
|
||||
}
|
||||
|
||||
// Frame-callback routing.
|
||||
//
|
||||
// Each `draw_*` path requests a `wl_surface.frame` with `SurfaceFocus` user-
|
||||
// data identifying which `SurfaceState` it belongs to. The compositor fires
|
||||
// the callback when the surface is ready for its next commit (display refresh,
|
||||
// VRR cadence, "screen is on", …). Clearing `frame_pending` unblocks the run
|
||||
// loop so the next iteration is allowed to draw that surface again.
|
||||
//
|
||||
// While the app is animating we re-arm the surface for redraw inline so the
|
||||
// loop keeps ticking at the compositor's pace without needing a fixed-period
|
||||
// timer.
|
||||
impl<A: App> Dispatch<WlCallback, super::SurfaceFocus> for AppData<A>
|
||||
{
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_proxy: &WlCallback,
|
||||
_event: wl_callback::Event,
|
||||
focus: &super::SurfaceFocus,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
)
|
||||
{
|
||||
let is_animating = state.app.is_animating();
|
||||
match *focus
|
||||
{
|
||||
super::SurfaceFocus::Main =>
|
||||
{
|
||||
state.main.frame_pending = false;
|
||||
if is_animating
|
||||
{
|
||||
state.view_dirty = true;
|
||||
state.main.request_redraw();
|
||||
}
|
||||
}
|
||||
super::SurfaceFocus::Overlay( id ) =>
|
||||
{
|
||||
if let Some( ss ) = state.overlays.get_mut( &id )
|
||||
{
|
||||
ss.frame_pending = false;
|
||||
if is_animating
|
||||
{
|
||||
state.overlays_dirty = true;
|
||||
ss.request_redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: App> Dispatch<ZwpTextInputV3, ()> for AppData<A>
|
||||
{
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_proxy: &ZwpTextInputV3,
|
||||
event: zwp_text_input_v3::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
)
|
||||
{
|
||||
let focus = state.keyboard_focus;
|
||||
match event
|
||||
{
|
||||
zwp_text_input_v3::Event::CommitString { text } =>
|
||||
{
|
||||
if let Some( text ) = text
|
||||
{
|
||||
if !text.is_empty()
|
||||
{
|
||||
state.handle_text_insert( focus, &text );
|
||||
}
|
||||
}
|
||||
}
|
||||
zwp_text_input_v3::Event::DeleteSurroundingText { before_length, .. } =>
|
||||
{
|
||||
for _ in 0..before_length
|
||||
{
|
||||
state.handle_backspace( focus );
|
||||
}
|
||||
}
|
||||
zwp_text_input_v3::Event::Done { .. } =>
|
||||
{
|
||||
state.surface_mut( focus ).request_redraw();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user