use smithay_client_toolkit::
{
compositor::CompositorHandler,
delegate_compositor, delegate_subcompositor, delegate_foreign_toplevel_list, 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, delegate_session_lock,
foreign_toplevel_list::{ ForeignToplevelList, ForeignToplevelListHandler },
output::{ OutputHandler, OutputState },
registry::{ ProvidesRegistryState, RegistryState },
registry_handlers,
seat::{ Capability, SeatHandler, SeatState },
shell::
{
WaylandSurface,
wlr_layer::{ LayerShellHandler, LayerSurface, LayerSurfaceConfigure },
xdg::XdgSurface,
xdg::popup::{ Popup, PopupConfigure, PopupHandler },
xdg::window::{ DecorationMode, Window, WindowConfigure, WindowHandler },
},
shm::{ Shm, ShmHandler },
session_lock::{ SessionLock, SessionLockHandler, SessionLockSurface, SessionLockSurfaceConfigure },
};
use smithay_client_toolkit::reexports::protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1;
use smithay_client_toolkit::reexports::client::
{
protocol::
{
wl_callback::{ self, WlCallback },
wl_output::{ self, WlOutput },
wl_surface::WlSurface,
wl_seat::WlSeat,
},
Connection, Dispatch, Proxy, 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, ToplevelEvent };
use crate::event_loop::{ AppData, SurfaceFocus, SurfaceState };
use super::{ SurfaceKind, WaylandSurfaceState };
fn apply_surface_scale<Msg: Clone>(
ss: &mut SurfaceState<Msg>,
shm: &Shm,
surface: &WlSurface,
new_factor: i32,
) -> Option<( u32, u32 )>
{
if new_factor as f32 == ss.scale_factor { return None; }
ss.scale_factor = new_factor as f32;
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;
if let Some( ref es ) = ss.platform.egl_surface
{
es.resize( pw as i32, ph as i32 );
} else if ss.platform.pool.is_some() {
ss.platform.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();
Some( ( pw, ph ) )
}
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
{
let shm = &self.platform.shm;
if let Some( ( _, ss ) ) = self.secondaries.iter_mut()
.find( |( _, ss )| ss.platform.surface.try_wl_surface() == Some( surface ) )
{
apply_surface_scale( ss, shm, surface, new_factor );
}
return;
};
let ( pw, ph ) = {
let shm = &self.platform.shm;
let ss = match focus
{
SurfaceFocus::Main => &mut self.main,
SurfaceFocus::Overlay( id ) => match self.overlays.get_mut( &id )
{
Some( s ) => s,
None => return,
},
};
match apply_surface_scale( ss, shm, surface, new_factor )
{
Some( dims ) => dims,
None => return,
}
};
self.cancel_tooltip();
self.overlays_dirty = true;
if matches!( focus, SurfaceFocus::Main )
{
crate::types::set_viewport_size( pw, ph );
crate::types::set_density( new_factor as f32 );
self.app.on_scale_changed( new_factor as u32 );
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,
)
{
}
fn surface_enter( &mut self, _: &Connection, _: &QueueHandle<Self>, surface: &WlSurface, output: &WlOutput )
{
if self.main.platform.surface.try_wl_surface() == Some( surface )
{
self.publish_output_size( Some( output ) );
}
}
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,
)
{
if self.is_secondary_surface( layer.wl_surface() )
{
self.secondaries.retain( |( _, ss )| ss.platform.surface.try_wl_surface() != Some( layer.wl_surface() ) );
return;
}
match self.focus_for_surface( layer.wl_surface() )
{
Some( SurfaceFocus::Main ) | None =>
{
self.request_close();
}
Some( SurfaceFocus::Overlay( id ) ) =>
{
self.discard_overlay( 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 ) );
for ( _, ss ) in self.secondaries.iter_mut()
{
if ss.platform.surface.try_wl_surface() == Some( layer.wl_surface() )
{
ss.on_configure( &self.platform.shm, self.platform.egl_context.as_ref(), w, h );
return;
}
}
match self.focus_for_surface( layer.wl_surface() )
{
Some( SurfaceFocus::Main ) | None =>
{
self.on_configure( w, h );
}
Some( SurfaceFocus::Overlay( id ) ) =>
{
if let Some( ss ) = self.overlays.get_mut( &id )
{
ss.on_configure( &self.platform.shm, self.platform.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 let Some( SurfaceFocus::Overlay( id ) ) = self.focus_for_surface( window.wl_surface() )
{
if let Some( msg ) = self.overlay_dismiss_msg( id )
{
self.pending_msgs.push( msg );
}
return;
}
self.request_close();
}
fn configure(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
window: &Window,
configure: WindowConfigure,
_serial: u32,
)
{
if let Some( SurfaceFocus::Overlay( id ) ) = self.focus_for_surface( window.wl_surface() )
{
let Some( ss ) = self.overlays.get_mut( &id ) else { return };
let sf = ss.scale_factor;
let ( rw, rh ) = ss.platform.last_requested_size;
let fallback = ( ( rw as f32 / sf ).round().max( 1.0 ) as u32, ( rh as f32 / sf ).round().max( 1.0 ) as u32 );
let w = configure.new_size.0.map( |v| v.get() ).unwrap_or( fallback.0 );
let h = configure.new_size.1.map( |v| v.get() ).unwrap_or( fallback.1 );
window.xdg_surface().set_window_geometry( 0, 0, w as i32, h as i32 );
ss.on_configure( &self.platform.shm, self.platform.egl_context.as_ref(), w, h );
return;
}
if self.platform.pending_fullscreen
{
window.set_fullscreen( None );
self.platform.pending_fullscreen = false;
}
if self.platform.pending_size_hint_unpin
{
window.set_min_size( None );
window.set_max_size( None );
self.platform.pending_size_hint_unpin = false;
}
let tb = if configure.decoration_mode == DecorationMode::Server || configure.is_fullscreen() { 0.0 }
else { self.main.platform.titlebar_base };
if self.main.titlebar_height != tb
{
self.main.titlebar_height = tb;
self.main.content_dirty = true;
self.view_dirty = true;
}
let fitted = self.fit_size_hint( window, configure.suggested_bounds );
let ( hint_w, hint_h ) = self.platform.fitted_size_hint.or( self.app.window_size_hint() ).unwrap_or( ( 800, 600 ) );
let ( w, h ) = if fitted
{
( hint_w, hint_h )
} else {
(
configure.new_size.0.map( |v| v.get() ).unwrap_or( hint_w ),
configure.new_size.1.map( |v| v.get() ).unwrap_or( hint_h ),
)
};
window.xdg_surface().set_window_geometry( 0, 0, w as i32, h as i32 );
self.on_configure( w, h );
}
}
impl<A: App> ShmHandler for AppData<A>
{
fn shm_state( &mut self ) -> &mut Shm
{
&mut self.platform.shm
}
}
impl<A: App> OutputHandler for AppData<A>
{
fn output_state( &mut self ) -> &mut OutputState
{
&mut self.platform.output_state
}
fn new_output( &mut self, _: &Connection, qh: &QueueHandle<Self>, output: WlOutput )
{
self.publish_output_size( None );
if let Some( ref layer_shell ) = self.platform.layer_shell
{
if self.main.platform.surface.materialize( &self.platform.compositor_state, layer_shell, qh, &output )
{
self.platform.main_output = Some( output.clone() );
}
for ss in self.overlays.values_mut()
{
ss.platform.surface.materialize( &self.platform.compositor_state, layer_shell, qh, &output );
}
if self.app.shell_mode() == crate::app::ShellMode::Layer( crate::app::Layer::Background )
&& self.platform.main_output.is_some()
&& self.platform.main_output.as_ref() != Some( &output )
&& !self.secondaries.iter().any( |( o, _ )| o == &output )
{
use smithay_client_toolkit::shell::wlr_layer;
let surface = self.platform.compositor_state.create_surface( qh );
let layer_surface = layer_shell.create_layer_surface(
qh,
surface,
wlr_layer::Layer::Background,
Some( "ltk-secondary" ),
Some( &output ),
);
layer_surface.set_anchor(
wlr_layer::Anchor::TOP | wlr_layer::Anchor::BOTTOM
| wlr_layer::Anchor::LEFT | wlr_layer::Anchor::RIGHT,
);
layer_surface.set_exclusive_zone( 0 );
layer_surface.set_keyboard_interactivity( wlr_layer::KeyboardInteractivity::None );
layer_surface.set_size( 0, 0 );
layer_surface.commit();
self.secondaries.push( (
output.clone(),
SurfaceState::new( WaylandSurfaceState::from( SurfaceKind::Layer( layer_surface ) ), 0.0, String::new() ),
) );
}
}
if let Some( ref lock ) = self.platform.session_lock
{
if matches!( self.main.platform.surface, SurfaceKind::Lock( .. ) )
&& self.platform.main_output.as_ref() != Some( &output )
&& !self.secondaries.iter().any( |( o, _ )| o == &output )
{
let surface = self.platform.compositor_state.create_surface( qh );
let lock_surface = lock.create_lock_surface( surface, &output, qh );
self.secondaries.push( (
output,
SurfaceState::new( WaylandSurfaceState::from( SurfaceKind::Lock( lock_surface ) ), 0.0, String::new() ),
) );
}
}
}
fn update_output( &mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput )
{
self.publish_output_size( None );
}
fn output_destroyed( &mut self, _: &Connection, _: &QueueHandle<Self>, output: WlOutput )
{
self.secondaries.retain( |( o, _ )| o != &output );
if self.platform.main_output.as_ref() == Some( &output )
{
self.platform.main_output = None;
}
self.publish_output_size( None );
}
}
impl<A: App> SeatHandler for AppData<A>
{
fn seat_state( &mut self ) -> &mut SeatState
{
&mut self.platform.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,
)
{
if self.platform.data_device.is_none()
{
if let Some( ref ddm ) = self.platform.data_device_manager
{
self.platform.data_device = Some( ddm.get_data_device( qh, &seat ) );
}
}
self.bind_primary_selection( qh, &seat );
match capability
{
Capability::Keyboard if self.platform.keyboard.is_none() =>
{
self.platform.keyboard = Some(
self.platform.seat_state
.get_keyboard( qh, &seat, None )
.expect( "keyboard" ),
);
}
Capability::Pointer if self.platform.pointer.is_none() =>
{
let pointer = self.platform.seat_state
.get_pointer( qh, &seat )
.expect( "pointer" );
if let Some( ref mgr ) = self.platform.cursor_shape_manager
{
self.platform.cursor_shape_device = Some( mgr.get_shape_device( &pointer, qh ) );
}
self.platform.pointer = Some( pointer );
}
Capability::Touch if self.platform.touch.is_none() =>
{
if self.reset_touch_state()
{
eprintln!( "[ltk] touch capability re-added — cleared touch state stranded across the gap" );
}
self.platform.touch = Some(
self.platform.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.platform.keyboard = None; }
Capability::Pointer => { self.platform.pointer = None; }
Capability::Touch =>
{
self.platform.touch = None;
if self.reset_touch_state()
{
eprintln!( "[ltk] touch capability removed mid-gesture — reset stale touch state" );
}
}
_ => {}
}
}
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( SurfaceFocus::Overlay( id ) ) = self.focus_for_surface( popup.wl_surface() )
{
if let Some( ss ) = self.overlays.get_mut( &id )
{
ss.on_configure( &self.platform.shm, self.platform.egl_context.as_ref(), w, h );
}
}
}
fn done(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
popup: &Popup,
)
{
if let Some( 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.discard_overlay( id );
}
}
}
impl<A: App> SessionLockHandler for AppData<A>
{
fn locked( &mut self, _conn: &Connection, qh: &QueueHandle<Self>, session_lock: SessionLock )
{
let outputs: Vec<WlOutput> = self.platform.output_state.outputs().collect();
let mut outputs = outputs.into_iter();
if let Some( output ) = outputs.next()
{
let surface = self.platform.compositor_state.create_surface( qh );
let lock_surface = session_lock.create_lock_surface( surface, &output, qh );
self.main.platform.surface = SurfaceKind::Lock( lock_surface );
self.platform.main_output = Some( output );
}
for output in outputs
{
let surface = self.platform.compositor_state.create_surface( qh );
let lock_surface = session_lock.create_lock_surface( surface, &output, qh );
self.secondaries.push( (
output,
SurfaceState::new( WaylandSurfaceState::from( SurfaceKind::Lock( lock_surface ) ), 0.0, String::new() ),
) );
}
self.platform.session_lock = Some( session_lock );
}
fn finished( &mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _session_lock: SessionLock )
{
self.exit_requested = true;
}
fn configure(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
surface: SessionLockSurface,
configure: SessionLockSurfaceConfigure,
_serial: u32,
)
{
let ( w, h ) = configure.new_size;
let ( w, h ) = ( w.max( 1 ), h.max( 1 ) );
let wl = surface.wl_surface();
if self.main.platform.surface.try_wl_surface() == Some( wl )
{
self.on_configure( w, h );
return;
}
for ( _, ss ) in self.secondaries.iter_mut()
{
if ss.platform.surface.try_wl_surface() == Some( wl )
{
ss.on_configure( &self.platform.shm, self.platform.egl_context.as_ref(), w, h );
return;
}
}
}
}
delegate_compositor!( @<A: App> AppData<A> );
delegate_subcompositor!( @<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_session_lock!( @<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_foreign_toplevel_list!( @<A: App> AppData<A> );
delegate_registry!( @<A: App> AppData<A> );
smithay_client_toolkit::delegate_activation!( @<A: App> AppData<A> );
smithay_client_toolkit::delegate_data_device!( @<A: App> AppData<A> );
smithay_client_toolkit::delegate_primary_selection!( @<A: App> AppData<A> );
impl<A: App> smithay_client_toolkit::activation::ActivationHandler for AppData<A>
{
type RequestData = smithay_client_toolkit::activation::RequestData;
fn new_token( &mut self, token: String, data: &Self::RequestData )
{
use smithay_client_toolkit::activation::RequestDataExt;
if let Some( tag ) = data.app_id()
{
if let Some( msg ) = self.app.on_activation_token( tag.to_string(), token )
{
self.pending_msgs.push( msg );
}
return;
}
if let ( Some( activation ), Some( wl ) ) = ( self.platform.activation_state.as_ref(), self.main.platform.surface.try_wl_surface() )
{
activation.activate::<Self>( wl, token );
}
}
}
impl<A: App> ProvidesRegistryState for AppData<A>
{
fn registry( &mut self ) -> &mut RegistryState
{
&mut self.platform.registry_state
}
registry_handlers![ OutputState, SeatState ];
}
fn toplevel_opened(
list: &ForeignToplevelList,
handle: &ExtForeignToplevelHandleV1,
) -> ToplevelEvent
{
let id = handle.id().protocol_id();
match list.info( handle )
{
Some( info ) => ToplevelEvent::Opened { id, app_id: info.app_id, identifier: info.identifier, title: info.title },
None => ToplevelEvent::Opened { id, app_id: String::new(), identifier: String::new(), title: String::new() },
}
}
impl<A: App> ForeignToplevelListHandler for AppData<A>
{
fn foreign_toplevel_list_state( &mut self ) -> &mut ForeignToplevelList
{
&mut self.platform.foreign_toplevel_list
}
fn new_toplevel(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
handle: ExtForeignToplevelHandleV1,
)
{
let event = toplevel_opened( &self.platform.foreign_toplevel_list, &handle );
if let Some( msg ) = self.app.on_toplevel_event( event )
{
self.pending_msgs.push( msg );
}
}
fn update_toplevel(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
handle: ExtForeignToplevelHandleV1,
)
{
let event = toplevel_opened( &self.platform.foreign_toplevel_list, &handle );
if let Some( msg ) = self.app.on_toplevel_event( event )
{
self.pending_msgs.push( msg );
}
}
fn toplevel_closed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
handle: ExtForeignToplevelHandleV1,
)
{
let id = handle.id().protocol_id();
if let Some( msg ) = self.app.on_toplevel_event( ToplevelEvent::Closed { id } )
{
self.pending_msgs.push( msg );
}
}
}
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>,
)
{
}
}
impl<A: App> Dispatch<WlCallback, SurfaceFocus> for AppData<A>
{
fn event(
state: &mut Self,
_proxy: &WlCallback,
_event: wl_callback::Event,
focus: &SurfaceFocus,
_conn: &Connection,
_qh: &QueueHandle<Self>,
)
{
let hosts_subsurfaces = match *focus
{
SurfaceFocus::Main => true,
SurfaceFocus::Overlay( id ) => state.platform.subsurfaces.values()
.any( |s| s.parent() == crate::app::SubsurfaceParent::Overlay( id ) ),
};
state.frame_presented( *focus, hosts_subsurfaces );
}
}
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;
let external = state.external_text_input.is_some() && !state.focused_text_input( focus );
match event
{
zwp_text_input_v3::Event::CommitString { text } =>
{
if let Some( text ) = text.filter( |t| !t.is_empty() )
{
if external { state.app.on_external_text_commit( &text ); }
else { state.handle_text_insert( focus, &text ); }
}
}
zwp_text_input_v3::Event::DeleteSurroundingText { before_length, after_length } =>
{
if external
{
state.app.on_external_text_delete( before_length, after_length );
} else {
state.handle_delete_surrounding( focus, before_length, after_length );
}
}
zwp_text_input_v3::Event::Done { .. } =>
{
if let Some( ss ) = state.try_surface_mut( focus )
{
ss.request_redraw();
}
}
zwp_text_input_v3::Event::Enter { .. } =>
{
state.reenable_text_input();
}
_ => {}
}
}
}