🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119
use std::{
collections::HashMap,
os::unix::io::OwnedFd,
sync::{atomic::AtomicBool, Arc},
time::Duration,
};
use tracing::{info, warn};
use smithay::{
backend::{
input::TabletToolDescriptor,
renderer::element::{
default_primary_scanout_output_compare, utils::select_dmabuf_feedback, RenderElementStates,
},
},
delegate_compositor, delegate_data_control, delegate_data_device, delegate_fractional_scale,
delegate_input_method_manager, delegate_keyboard_shortcuts_inhibit, delegate_layer_shell,
delegate_output, delegate_pointer_constraints, delegate_pointer_gestures, delegate_presentation,
delegate_primary_selection, delegate_relative_pointer, delegate_seat, delegate_security_context,
delegate_shm, delegate_tablet_manager, delegate_text_input_manager, delegate_viewporter,
delegate_virtual_keyboard_manager, delegate_xdg_activation, delegate_xdg_decoration, delegate_xdg_shell,
desktop::{
space::SpaceElement,
utils::{
surface_presentation_feedback_flags_from_states, surface_primary_scanout_output,
update_surface_primary_scanout_output, with_surfaces_surface_tree, OutputPresentationFeedback,
},
PopupKind, PopupManager, Space,
},
input::{
keyboard::{Keysym, LedState, XkbConfig},
pointer::{CursorImageStatus, CursorImageSurfaceData, PointerHandle},
Seat, SeatHandler, SeatState,
},
output::Output,
reexports::{
calloop::{generic::Generic, Interest, LoopHandle, Mode, PostAction},
wayland_protocols::xdg::decoration::{
self as xdg_decoration, zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode,
},
wayland_server::{
backend::{ClientData, ClientId, DisconnectReason},
protocol::{wl_data_source::WlDataSource, wl_surface::WlSurface},
Client, Display, DisplayHandle, Resource,
},
},
utils::{Clock, Logical, Monotonic, Point, Rectangle, Time},
wayland::{
commit_timing::{CommitTimerBarrierStateUserData, CommitTimingManagerState},
compositor::{get_parent, with_states, CompositorClientState, CompositorHandler, CompositorState},
dmabuf::DmabufFeedback,
fifo::{FifoBarrierCachedState, FifoManagerState},
fractional_scale::{with_fractional_scale, FractionalScaleHandler, FractionalScaleManagerState},
input_method::{InputMethodHandler, InputMethodManagerState, PopupSurface},
keyboard_shortcuts_inhibit::{
KeyboardShortcutsInhibitHandler, KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor,
},
output::{OutputHandler, OutputManagerState},
pointer_constraints::{with_pointer_constraint, PointerConstraintsHandler, PointerConstraintsState},
pointer_gestures::PointerGesturesState,
presentation::PresentationState,
relative_pointer::RelativePointerManagerState,
seat::WaylandFocus,
security_context::{
SecurityContext, SecurityContextHandler, SecurityContextListenerSource, SecurityContextState,
},
selection::{
data_device::{
set_data_device_focus, ClientDndGrabHandler, DataDeviceHandler, DataDeviceState,
ServerDndGrabHandler,
},
primary_selection::{set_primary_focus, PrimarySelectionHandler, PrimarySelectionState},
wlr_data_control::{DataControlHandler, DataControlState},
SelectionHandler,
},
shell::{
wlr_layer::WlrLayerShellState,
xdg::{
decoration::{XdgDecorationHandler, XdgDecorationState},
ToplevelSurface, XdgShellState,
},
},
shm::{ShmHandler, ShmState},
single_pixel_buffer::SinglePixelBufferState,
socket::ListeningSocketSource,
tablet_manager::{TabletManagerState, TabletSeatHandler},
text_input::TextInputManagerState,
viewporter::ViewporterState,
virtual_keyboard::VirtualKeyboardManagerState,
xdg_activation::{
XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData,
},
xdg_foreign::{XdgForeignHandler, XdgForeignState},
},
};
#[cfg(feature = "xwayland")]
use crate::cursor::Cursor;
use crate::{
focus::{KeyboardFocusTarget, PointerFocusTarget},
shell::WindowElement,
};
#[cfg(feature = "xwayland")]
use smithay::{
delegate_xwayland_keyboard_grab, delegate_xwayland_shell,
utils::Size,
wayland::selection::{SelectionSource, SelectionTarget},
wayland::xwayland_keyboard_grab::{XWaylandKeyboardGrabHandler, XWaylandKeyboardGrabState},
wayland::xwayland_shell,
xwayland::{X11Wm, XWayland, XWaylandEvent},
};
#[derive(Debug, Default)]
pub struct ClientState {
pub compositor_state: CompositorClientState,
pub security_context: Option<SecurityContext>,
}
impl ClientData for ClientState {
/// Notification that a client was initialized
fn initialized(&self, _client_id: ClientId) {}
/// Notification that a client is disconnected
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
}
#[derive(Debug)]
pub struct AnvilState<BackendData: Backend + 'static> {
pub backend_data: BackendData,
pub socket_name: Option<String>,
pub display_handle: DisplayHandle,
pub running: Arc<AtomicBool>,
pub handle: LoopHandle<'static, AnvilState<BackendData>>,
// desktop
pub space: Space<WindowElement>,
pub popups: PopupManager,
// smithay state
pub compositor_state: CompositorState,
pub data_device_state: DataDeviceState,
pub layer_shell_state: WlrLayerShellState,
pub output_manager_state: OutputManagerState,
pub primary_selection_state: PrimarySelectionState,
pub data_control_state: DataControlState,
pub seat_state: SeatState<AnvilState<BackendData>>,
pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState,
pub shm_state: ShmState,
pub viewporter_state: ViewporterState,
pub xdg_activation_state: XdgActivationState,
pub xdg_decoration_state: XdgDecorationState,
pub xdg_shell_state: XdgShellState,
pub presentation_state: PresentationState,
pub fractional_scale_manager_state: FractionalScaleManagerState,
pub xdg_foreign_state: XdgForeignState,
#[cfg(feature = "xwayland")]
pub xwayland_shell_state: xwayland_shell::XWaylandShellState,
pub single_pixel_buffer_state: SinglePixelBufferState,
pub fifo_manager_state: FifoManagerState,
pub commit_timing_manager_state: CommitTimingManagerState,
pub dnd_icon: Option<DndIcon>,
// input-related fields
pub suppressed_keys: Vec<Keysym>,
pub cursor_status: CursorImageStatus,
pub seat_name: String,
pub seat: Seat<AnvilState<BackendData>>,
pub clock: Clock<Monotonic>,
pub pointer: PointerHandle<AnvilState<BackendData>>,
#[cfg(feature = "xwayland")]
pub xwm: Option<X11Wm>,
#[cfg(feature = "xwayland")]
pub xdisplay: Option<u32>,
#[cfg(feature = "debug")]
pub renderdoc: Option<renderdoc::RenderDoc<renderdoc::V141>>,
pub show_window_preview: bool,
}
#[derive(Debug)]
pub struct DndIcon {
pub surface: WlSurface,
pub offset: Point<i32, Logical>,
}
delegate_compositor!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> DataDeviceHandler for AnvilState<BackendData> {
fn data_device_state(&self) -> &DataDeviceState {
&self.data_device_state
}
}
impl<BackendData: Backend> ClientDndGrabHandler for AnvilState<BackendData> {
fn started(&mut self, _source: Option<WlDataSource>, icon: Option<WlSurface>, _seat: Seat<Self>) {
let offset = if let CursorImageStatus::Surface(ref surface) = self.cursor_status {
with_states(surface, |states| {
let hotspot = states
.data_map
.get::<CursorImageSurfaceData>()
.unwrap()
.lock()
.unwrap()
.hotspot;
Point::from((-hotspot.x, -hotspot.y))
})
} else {
(0, 0).into()
};
self.dnd_icon = icon.map(|surface| DndIcon { surface, offset });
}
fn dropped(&mut self, _target: Option<WlSurface>, _validated: bool, _seat: Seat<Self>) {
self.dnd_icon = None;
}
}
impl<BackendData: Backend> ServerDndGrabHandler for AnvilState<BackendData> {
fn send(&mut self, _mime_type: String, _fd: OwnedFd, _seat: Seat<Self>) {
unreachable!("Anvil doesn't do server-side grabs");
}
}
delegate_data_device!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> OutputHandler for AnvilState<BackendData> {}
delegate_output!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> SelectionHandler for AnvilState<BackendData> {
type SelectionUserData = ();
#[cfg(feature = "xwayland")]
fn new_selection(&mut self, ty: SelectionTarget, source: Option<SelectionSource>, _seat: Seat<Self>) {
if let Some(xwm) = self.xwm.as_mut() {
if let Err(err) = xwm.new_selection(ty, source.map(|source| source.mime_types())) {
warn!(?err, ?ty, "Failed to set Xwayland selection");
}
}
}
#[cfg(feature = "xwayland")]
fn send_selection(
&mut self,
ty: SelectionTarget,
mime_type: String,
fd: OwnedFd,
_seat: Seat<Self>,
_user_data: &(),
) {
if let Some(xwm) = self.xwm.as_mut() {
if let Err(err) = xwm.send_selection(ty, mime_type, fd, self.handle.clone()) {
warn!(?err, "Failed to send primary (X11 -> Wayland)");
}
}
}
}
impl<BackendData: Backend> PrimarySelectionHandler for AnvilState<BackendData> {
fn primary_selection_state(&self) -> &PrimarySelectionState {
&self.primary_selection_state
}
}
delegate_primary_selection!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> DataControlHandler for AnvilState<BackendData> {
fn data_control_state(&self) -> &DataControlState {
&self.data_control_state
}
}
delegate_data_control!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> ShmHandler for AnvilState<BackendData> {
fn shm_state(&self) -> &ShmState {
&self.shm_state
}
}
delegate_shm!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> SeatHandler for AnvilState<BackendData> {
type KeyboardFocus = KeyboardFocusTarget;
type PointerFocus = PointerFocusTarget;
type TouchFocus = PointerFocusTarget;
fn seat_state(&mut self) -> &mut SeatState<AnvilState<BackendData>> {
&mut self.seat_state
}
fn focus_changed(&mut self, seat: &Seat<Self>, target: Option<&KeyboardFocusTarget>) {
let dh = &self.display_handle;
let wl_surface = target.and_then(WaylandFocus::wl_surface);
let focus = wl_surface.and_then(|s| dh.get_client(s.id()).ok());
set_data_device_focus(dh, seat, focus.clone());
set_primary_focus(dh, seat, focus);
}
fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
self.cursor_status = image;
}
fn led_state_changed(&mut self, _seat: &Seat<Self>, led_state: LedState) {
self.backend_data.update_led_state(led_state)
}
}
delegate_seat!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> TabletSeatHandler for AnvilState<BackendData> {
fn tablet_tool_image(&mut self, _tool: &TabletToolDescriptor, image: CursorImageStatus) {
// TODO: tablet tools should have their own cursors
self.cursor_status = image;
}
}
delegate_tablet_manager!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
delegate_text_input_manager!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> InputMethodHandler for AnvilState<BackendData> {
fn new_popup(&mut self, surface: PopupSurface) {
if let Err(err) = self.popups.track_popup(PopupKind::from(surface)) {
warn!("Failed to track popup: {}", err);
}
}
fn popup_repositioned(&mut self, _: PopupSurface) {}
fn dismiss_popup(&mut self, surface: PopupSurface) {
if let Some(parent) = surface.get_parent().map(|parent| parent.surface.clone()) {
let _ = PopupManager::dismiss_popup(&parent, &PopupKind::from(surface));
}
}
fn parent_geometry(&self, parent: &WlSurface) -> Rectangle<i32, smithay::utils::Logical> {
self.space
.elements()
.find_map(|window| (window.wl_surface().as_deref() == Some(parent)).then(|| window.geometry()))
.unwrap_or_default()
}
}
delegate_input_method_manager!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> KeyboardShortcutsInhibitHandler for AnvilState<BackendData> {
fn keyboard_shortcuts_inhibit_state(&mut self) -> &mut KeyboardShortcutsInhibitState {
&mut self.keyboard_shortcuts_inhibit_state
}
fn new_inhibitor(&mut self, inhibitor: KeyboardShortcutsInhibitor) {
// Just grant the wish for everyone
inhibitor.activate();
}
}
delegate_keyboard_shortcuts_inhibit!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
delegate_virtual_keyboard_manager!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
delegate_pointer_gestures!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
delegate_relative_pointer!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> PointerConstraintsHandler for AnvilState<BackendData> {
fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle<Self>) {
// XXX region
let Some(current_focus) = pointer.current_focus() else {
return;
};
if current_focus.wl_surface().as_deref() == Some(surface) {
with_pointer_constraint(surface, pointer, |constraint| {
constraint.unwrap().activate();
});
}
}
fn cursor_position_hint(
&mut self,
surface: &WlSurface,
pointer: &PointerHandle<Self>,
location: Point<f64, Logical>,
) {
if with_pointer_constraint(surface, pointer, |constraint| {
constraint.is_some_and(|c| c.is_active())
}) {
let origin = self
.space
.elements()
.find_map(|window| {
(window.wl_surface().as_deref() == Some(surface)).then(|| window.geometry())
})
.unwrap_or_default()
.loc
.to_f64();
pointer.set_location(origin + location);
}
}
}
delegate_pointer_constraints!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
delegate_viewporter!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> XdgActivationHandler for AnvilState<BackendData> {
fn activation_state(&mut self) -> &mut XdgActivationState {
&mut self.xdg_activation_state
}
fn token_created(&mut self, _token: XdgActivationToken, data: XdgActivationTokenData) -> bool {
if let Some((serial, seat)) = data.serial {
let keyboard = self.seat.get_keyboard().unwrap();
Seat::from_resource(&seat) == Some(self.seat.clone())
&& keyboard
.last_enter()
.map(|last_enter| serial.is_no_older_than(&last_enter))
.unwrap_or(false)
} else {
false
}
}
fn request_activation(
&mut self,
_token: XdgActivationToken,
token_data: XdgActivationTokenData,
surface: WlSurface,
) {
if token_data.timestamp.elapsed().as_secs() < 10 {
// Just grant the wish
let w = self
.space
.elements()
.find(|window| window.wl_surface().map(|s| *s == surface).unwrap_or(false))
.cloned();
if let Some(window) = w {
self.space.raise_element(&window, true);
}
}
}
}
delegate_xdg_activation!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> XdgDecorationHandler for AnvilState<BackendData> {
fn new_decoration(&mut self, toplevel: ToplevelSurface) {
use xdg_decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode;
// Set the default to client side
toplevel.with_pending_state(|state| {
state.decoration_mode = Some(Mode::ClientSide);
});
}
fn request_mode(&mut self, toplevel: ToplevelSurface, mode: DecorationMode) {
use xdg_decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode;
toplevel.with_pending_state(|state| {
state.decoration_mode = Some(match mode {
DecorationMode::ServerSide => Mode::ServerSide,
_ => Mode::ClientSide,
});
});
if toplevel.is_initial_configure_sent() {
toplevel.send_pending_configure();
}
}
fn unset_mode(&mut self, toplevel: ToplevelSurface) {
use xdg_decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode;
toplevel.with_pending_state(|state| {
state.decoration_mode = Some(Mode::ClientSide);
});
if toplevel.is_initial_configure_sent() {
toplevel.send_pending_configure();
}
}
}
delegate_xdg_decoration!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
delegate_xdg_shell!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
delegate_layer_shell!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
delegate_presentation!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> FractionalScaleHandler for AnvilState<BackendData> {
fn new_fractional_scale(
&mut self,
surface: smithay::reexports::wayland_server::protocol::wl_surface::WlSurface,
) {
// Here we can set the initial fractional scale
//
// First we look if the surface already has a primary scan-out output, if not
// we test if the surface is a subsurface and try to use the primary scan-out output
// of the root surface. If the root also has no primary scan-out output we just try
// to use the first output of the toplevel.
// If the surface is the root we also try to use the first output of the toplevel.
//
// If all the above tests do not lead to a output we just use the first output
// of the space (which in case of anvil will also be the output a toplevel will
// initially be placed on)
#[allow(clippy::redundant_clone)]
let mut root = surface.clone();
while let Some(parent) = get_parent(&root) {
root = parent;
}
with_states(&surface, |states| {
let primary_scanout_output = surface_primary_scanout_output(&surface, states)
.or_else(|| {
if root != surface {
with_states(&root, |states| {
surface_primary_scanout_output(&root, states).or_else(|| {
self.window_for_surface(&root).and_then(|window| {
self.space.outputs_for_element(&window).first().cloned()
})
})
})
} else {
self.window_for_surface(&root)
.and_then(|window| self.space.outputs_for_element(&window).first().cloned())
}
})
.or_else(|| self.space.outputs().next().cloned());
if let Some(output) = primary_scanout_output {
with_fractional_scale(states, |fractional_scale| {
fractional_scale.set_preferred_scale(output.current_scale().fractional_scale());
});
}
});
}
}
delegate_fractional_scale!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend + 'static> SecurityContextHandler for AnvilState<BackendData> {
fn context_created(&mut self, source: SecurityContextListenerSource, security_context: SecurityContext) {
self.handle
.insert_source(source, move |client_stream, _, data| {
let client_state = ClientState {
security_context: Some(security_context.clone()),
..ClientState::default()
};
if let Err(err) = data
.display_handle
.insert_client(client_stream, Arc::new(client_state))
{
warn!("Error adding wayland client: {}", err);
};
})
.expect("Failed to init wayland socket source");
}
}
delegate_security_context!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
#[cfg(feature = "xwayland")]
impl<BackendData: Backend + 'static> XWaylandKeyboardGrabHandler for AnvilState<BackendData> {
fn keyboard_focus_for_xsurface(&self, surface: &WlSurface) -> Option<KeyboardFocusTarget> {
let elem = self
.space
.elements()
.find(|elem| elem.wl_surface().as_deref() == Some(surface))?;
Some(KeyboardFocusTarget::Window(elem.0.clone()))
}
}
#[cfg(feature = "xwayland")]
delegate_xwayland_keyboard_grab!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
#[cfg(feature = "xwayland")]
delegate_xwayland_shell!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend> XdgForeignHandler for AnvilState<BackendData> {
fn xdg_foreign_state(&mut self) -> &mut XdgForeignState {
&mut self.xdg_foreign_state
}
}
smithay::delegate_xdg_foreign!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
smithay::delegate_single_pixel_buffer!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
smithay::delegate_fifo!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
smithay::delegate_commit_timing!(@<BackendData: Backend + 'static> AnvilState<BackendData>);
impl<BackendData: Backend + 'static> AnvilState<BackendData> {
pub fn init(
display: Display<AnvilState<BackendData>>,
handle: LoopHandle<'static, AnvilState<BackendData>>,
backend_data: BackendData,
listen_on_socket: bool,
) -> AnvilState<BackendData> {
let dh = display.handle();
let clock = Clock::new();
// init wayland clients
let socket_name = if listen_on_socket {
let source = ListeningSocketSource::new_auto().unwrap();
let socket_name = source.socket_name().to_string_lossy().into_owned();
handle
.insert_source(source, |client_stream, _, data| {
if let Err(err) = data
.display_handle
.insert_client(client_stream, Arc::new(ClientState::default()))
{
warn!("Error adding wayland client: {}", err);
};
})
.expect("Failed to init wayland socket source");
info!(name = socket_name, "Listening on wayland socket");
Some(socket_name)
} else {
None
};
handle
.insert_source(
Generic::new(display, Interest::READ, Mode::Level),
|_, display, data| {
profiling::scope!("dispatch_clients");
// Safety: we don't drop the display
unsafe {
display.get_mut().dispatch_clients(data).unwrap();
}
Ok(PostAction::Continue)
},
)
.expect("Failed to init wayland server source");
// init globals
let compositor_state = CompositorState::new::<Self>(&dh);
let data_device_state = DataDeviceState::new::<Self>(&dh);
let layer_shell_state = WlrLayerShellState::new::<Self>(&dh);
let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&dh);
let primary_selection_state = PrimarySelectionState::new::<Self>(&dh);
let data_control_state =
DataControlState::new::<Self, _>(&dh, Some(&primary_selection_state), |_| true);
let mut seat_state = SeatState::new();
let shm_state = ShmState::new::<Self>(&dh, vec![]);
let viewporter_state = ViewporterState::new::<Self>(&dh);
let xdg_activation_state = XdgActivationState::new::<Self>(&dh);
let xdg_decoration_state = XdgDecorationState::new::<Self>(&dh);
let xdg_shell_state = XdgShellState::new::<Self>(&dh);
let presentation_state = PresentationState::new::<Self>(&dh, clock.id() as u32);
let fractional_scale_manager_state = FractionalScaleManagerState::new::<Self>(&dh);
let xdg_foreign_state = XdgForeignState::new::<Self>(&dh);
let single_pixel_buffer_state = SinglePixelBufferState::new::<Self>(&dh);
let fifo_manager_state = FifoManagerState::new::<Self>(&dh);
let commit_timing_manager_state = CommitTimingManagerState::new::<Self>(&dh);
TextInputManagerState::new::<Self>(&dh);
InputMethodManagerState::new::<Self, _>(&dh, |_client| true);
VirtualKeyboardManagerState::new::<Self, _>(&dh, |_client| true);
// Expose global only if backend supports relative motion events
if BackendData::HAS_RELATIVE_MOTION {
RelativePointerManagerState::new::<Self>(&dh);
}
PointerConstraintsState::new::<Self>(&dh);
if BackendData::HAS_GESTURES {
PointerGesturesState::new::<Self>(&dh);
}
TabletManagerState::new::<Self>(&dh);
SecurityContextState::new::<Self, _>(&dh, |client| {
client
.get_data::<ClientState>()
.map_or(true, |client_state| client_state.security_context.is_none())
});
// init input
let seat_name = backend_data.seat_name();
let mut seat = seat_state.new_wl_seat(&dh, seat_name.clone());
let pointer = seat.add_pointer();
seat.add_keyboard(XkbConfig::default(), 200, 25)
.expect("Failed to initialize the keyboard");
let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::<Self>(&dh);
#[cfg(feature = "xwayland")]
let xwayland_shell_state = xwayland_shell::XWaylandShellState::new::<Self>(&dh.clone());
#[cfg(feature = "xwayland")]
XWaylandKeyboardGrabState::new::<Self>(&dh.clone());
AnvilState {
backend_data,
display_handle: dh,
socket_name,
running: Arc::new(AtomicBool::new(true)),
handle,
space: Space::default(),
popups: PopupManager::default(),
compositor_state,
data_device_state,
layer_shell_state,
output_manager_state,
primary_selection_state,
data_control_state,
seat_state,
keyboard_shortcuts_inhibit_state,
shm_state,
viewporter_state,
xdg_activation_state,
xdg_decoration_state,
xdg_shell_state,
presentation_state,
fractional_scale_manager_state,
xdg_foreign_state,
single_pixel_buffer_state,
fifo_manager_state,
commit_timing_manager_state,
dnd_icon: None,
suppressed_keys: Vec::new(),
cursor_status: CursorImageStatus::default_named(),
seat_name,
seat,
pointer,
clock,
#[cfg(feature = "xwayland")]
xwayland_shell_state,
#[cfg(feature = "xwayland")]
xwm: None,
#[cfg(feature = "xwayland")]
xdisplay: None,
#[cfg(feature = "debug")]
renderdoc: renderdoc::RenderDoc::new().ok(),
show_window_preview: false,
}
}
#[cfg(feature = "xwayland")]
pub fn start_xwayland(&mut self) {
use std::process::Stdio;
use smithay::wayland::compositor::CompositorHandler;
let (xwayland, client) = XWayland::spawn(
&self.display_handle,
None,
std::iter::empty::<(String, String)>(),
true,
Stdio::null(),
Stdio::null(),
|_| (),
)
.expect("failed to start XWayland");
let ret = self
.handle
.insert_source(xwayland, move |event, _, data| match event {
XWaylandEvent::Ready {
x11_socket,
display_number,
} => {
let xwayland_scale = std::env::var("ANVIL_XWAYLAND_SCALE")
.ok()
.and_then(|s| s.parse::<f64>().ok())
.unwrap_or(1.);
data.client_compositor_state(&client)
.set_client_scale(xwayland_scale);
let mut wm = X11Wm::start_wm(data.handle.clone(), x11_socket, client.clone())
.expect("Failed to attach X11 Window Manager");
let cursor = Cursor::load();
let image = cursor.get_image(1, Duration::ZERO);
wm.set_cursor(
&image.pixels_rgba,
Size::from((image.width as u16, image.height as u16)),
Point::from((image.xhot as u16, image.yhot as u16)),
)
.expect("Failed to set xwayland default cursor");
data.xwm = Some(wm);
data.xdisplay = Some(display_number);
}
XWaylandEvent::Error => {
warn!("XWayland crashed on startup");
}
});
if let Err(e) = ret {
tracing::error!("Failed to insert the XWaylandSource into the event loop: {}", e);
}
}
}
impl<BackendData: Backend + 'static> AnvilState<BackendData> {
pub fn pre_repaint(&mut self, output: &Output, frame_target: impl Into<Time<Monotonic>>) {
let frame_target = frame_target.into();
#[allow(clippy::mutable_key_type)]
let mut clients: HashMap<ClientId, Client> = HashMap::new();
self.space.elements().for_each(|window| {
window.with_surfaces(|surface, states| {
if let Some(mut commit_timer_state) = states
.data_map
.get::<CommitTimerBarrierStateUserData>()
.map(|commit_timer| commit_timer.lock().unwrap())
{
commit_timer_state.signal_until(frame_target);
let client = surface.client().unwrap();
clients.insert(client.id(), client);
}
});
});
let map = smithay::desktop::layer_map_for_output(output);
for layer_surface in map.layers() {
layer_surface.with_surfaces(|surface, states| {
if let Some(mut commit_timer_state) = states
.data_map
.get::<CommitTimerBarrierStateUserData>()
.map(|commit_timer| commit_timer.lock().unwrap())
{
commit_timer_state.signal_until(frame_target);
let client = surface.client().unwrap();
clients.insert(client.id(), client);
}
});
}
// Drop the lock to the layer map before calling blocker_cleared, which might end up
// calling the commit handler which in turn again could access the layer map.
std::mem::drop(map);
if let CursorImageStatus::Surface(ref surface) = self.cursor_status {
with_surfaces_surface_tree(surface, |surface, states| {
if let Some(mut commit_timer_state) = states
.data_map
.get::<CommitTimerBarrierStateUserData>()
.map(|commit_timer| commit_timer.lock().unwrap())
{
commit_timer_state.signal_until(frame_target);
let client = surface.client().unwrap();
clients.insert(client.id(), client);
}
});
}
if let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) {
with_surfaces_surface_tree(surface, |surface, states| {
if let Some(mut commit_timer_state) = states
.data_map
.get::<CommitTimerBarrierStateUserData>()
.map(|commit_timer| commit_timer.lock().unwrap())
{
commit_timer_state.signal_until(frame_target);
let client = surface.client().unwrap();
clients.insert(client.id(), client);
}
});
}
let dh = self.display_handle.clone();
for client in clients.into_values() {
self.client_compositor_state(&client).blocker_cleared(self, &dh);
}
}
pub fn post_repaint(
&mut self,
output: &Output,
time: impl Into<Duration>,
dmabuf_feedback: Option<SurfaceDmabufFeedback>,
render_element_states: &RenderElementStates,
) {
let time = time.into();
let throttle = Some(Duration::from_secs(1));
#[allow(clippy::mutable_key_type)]
let mut clients: HashMap<ClientId, Client> = HashMap::new();
self.space.elements().for_each(|window| {
window.with_surfaces(|surface, states| {
let primary_scanout_output = surface_primary_scanout_output(surface, states);
if let Some(output) = primary_scanout_output.as_ref() {
with_fractional_scale(states, |fraction_scale| {
fraction_scale.set_preferred_scale(output.current_scale().fractional_scale());
});
}
if primary_scanout_output
.as_ref()
.map(|o| o == output)
.unwrap_or(true)
{
let fifo_barrier = states
.cached_state
.get::<FifoBarrierCachedState>()
.current()
.barrier
.take();
if let Some(fifo_barrier) = fifo_barrier {
fifo_barrier.signal();
let client = surface.client().unwrap();
clients.insert(client.id(), client);
}
}
});
if self.space.outputs_for_element(window).contains(output) {
window.send_frame(output, time, throttle, surface_primary_scanout_output);
if let Some(dmabuf_feedback) = dmabuf_feedback.as_ref() {
window.send_dmabuf_feedback(output, surface_primary_scanout_output, |surface, _| {
select_dmabuf_feedback(
surface,
render_element_states,
&dmabuf_feedback.render_feedback,
&dmabuf_feedback.scanout_feedback,
)
});
}
}
});
let map = smithay::desktop::layer_map_for_output(output);
for layer_surface in map.layers() {
layer_surface.with_surfaces(|surface, states| {
let primary_scanout_output = surface_primary_scanout_output(surface, states);
if let Some(output) = primary_scanout_output.as_ref() {
with_fractional_scale(states, |fraction_scale| {
fraction_scale.set_preferred_scale(output.current_scale().fractional_scale());
});
}
if primary_scanout_output
.as_ref()
.map(|o| o == output)
.unwrap_or(true)
{
let fifo_barrier = states
.cached_state
.get::<FifoBarrierCachedState>()
.current()
.barrier
.take();
if let Some(fifo_barrier) = fifo_barrier {
fifo_barrier.signal();
let client = surface.client().unwrap();
clients.insert(client.id(), client);
}
}
});
layer_surface.send_frame(output, time, throttle, surface_primary_scanout_output);
if let Some(dmabuf_feedback) = dmabuf_feedback.as_ref() {
layer_surface.send_dmabuf_feedback(output, surface_primary_scanout_output, |surface, _| {
select_dmabuf_feedback(
surface,
render_element_states,
&dmabuf_feedback.render_feedback,
&dmabuf_feedback.scanout_feedback,
)
});
}
}
// Drop the lock to the layer map before calling blocker_cleared, which might end up
// calling the commit handler which in turn again could access the layer map.
std::mem::drop(map);
if let CursorImageStatus::Surface(ref surface) = self.cursor_status {
with_surfaces_surface_tree(surface, |surface, states| {
let primary_scanout_output = surface_primary_scanout_output(surface, states);
if let Some(output) = primary_scanout_output.as_ref() {
with_fractional_scale(states, |fraction_scale| {
fraction_scale.set_preferred_scale(output.current_scale().fractional_scale());
});
}
if primary_scanout_output
.as_ref()
.map(|o| o == output)
.unwrap_or(true)
{
let fifo_barrier = states
.cached_state
.get::<FifoBarrierCachedState>()
.current()
.barrier
.take();
if let Some(fifo_barrier) = fifo_barrier {
fifo_barrier.signal();
let client = surface.client().unwrap();
clients.insert(client.id(), client);
}
}
});
}
if let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) {
with_surfaces_surface_tree(surface, |surface, states| {
let primary_scanout_output = surface_primary_scanout_output(surface, states);
if let Some(output) = primary_scanout_output.as_ref() {
with_fractional_scale(states, |fraction_scale| {
fraction_scale.set_preferred_scale(output.current_scale().fractional_scale());
});
}
if primary_scanout_output
.as_ref()
.map(|o| o == output)
.unwrap_or(true)
{
let fifo_barrier = states
.cached_state
.get::<FifoBarrierCachedState>()
.current()
.barrier
.take();
if let Some(fifo_barrier) = fifo_barrier {
fifo_barrier.signal();
let client = surface.client().unwrap();
clients.insert(client.id(), client);
}
}
});
}
let dh = self.display_handle.clone();
for client in clients.into_values() {
self.client_compositor_state(&client).blocker_cleared(self, &dh);
}
}
}
pub fn update_primary_scanout_output(
space: &Space<WindowElement>,
output: &Output,
dnd_icon: &Option<DndIcon>,
cursor_status: &CursorImageStatus,
render_element_states: &RenderElementStates,
) {
space.elements().for_each(|window| {
window.with_surfaces(|surface, states| {
update_surface_primary_scanout_output(
surface,
output,
states,
render_element_states,
default_primary_scanout_output_compare,
);
});
});
let map = smithay::desktop::layer_map_for_output(output);
for layer_surface in map.layers() {
layer_surface.with_surfaces(|surface, states| {
update_surface_primary_scanout_output(
surface,
output,
states,
render_element_states,
default_primary_scanout_output_compare,
);
});
}
if let CursorImageStatus::Surface(ref surface) = cursor_status {
with_surfaces_surface_tree(surface, |surface, states| {
update_surface_primary_scanout_output(
surface,
output,
states,
render_element_states,
default_primary_scanout_output_compare,
);
});
}
if let Some(surface) = dnd_icon.as_ref().map(|icon| &icon.surface) {
with_surfaces_surface_tree(surface, |surface, states| {
update_surface_primary_scanout_output(
surface,
output,
states,
render_element_states,
default_primary_scanout_output_compare,
);
});
}
}
#[derive(Debug, Clone)]
pub struct SurfaceDmabufFeedback {
pub render_feedback: DmabufFeedback,
pub scanout_feedback: DmabufFeedback,
}
#[profiling::function]
pub fn take_presentation_feedback(
output: &Output,
space: &Space<WindowElement>,
render_element_states: &RenderElementStates,
) -> OutputPresentationFeedback {
let mut output_presentation_feedback = OutputPresentationFeedback::new(output);
space.elements().for_each(|window| {
if space.outputs_for_element(window).contains(output) {
window.take_presentation_feedback(
&mut output_presentation_feedback,
surface_primary_scanout_output,
|surface, _| surface_presentation_feedback_flags_from_states(surface, render_element_states),
);
}
});
let map = smithay::desktop::layer_map_for_output(output);
for layer_surface in map.layers() {
layer_surface.take_presentation_feedback(
&mut output_presentation_feedback,
surface_primary_scanout_output,
|surface, _| surface_presentation_feedback_flags_from_states(surface, render_element_states),
);
}
output_presentation_feedback
}
pub trait Backend {
const HAS_RELATIVE_MOTION: bool = false;
const HAS_GESTURES: bool = false;
fn seat_name(&self) -> String;
fn reset_buffers(&mut self, output: &Output);
fn early_import(&mut self, surface: &WlSurface);
fn update_led_state(&mut self, led_state: LedState);
}