727 lines
29 KiB
Rust
727 lines
29 KiB
Rust
use smithay::{
|
|
backend::{drm::DrmNode, renderer::{ImportDma, gles::GlesRenderer}},
|
|
desktop::{PopupManager, Space, Window},
|
|
input::{Seat, SeatHandler, SeatState, pointer::PointerHandle},
|
|
output::Output,
|
|
reexports::wayland_server::{
|
|
Display, DisplayHandle, Resource, backend::{ClientData as WlClientData, ClientId, DisconnectReason, ObjectId}, protocol::wl_surface::WlSurface
|
|
},
|
|
utils::{Logical, Point, SERIAL_COUNTER},
|
|
wayland::{
|
|
compositor::{CompositorClientState, CompositorState, with_states}, cursor_shape::CursorShapeManagerState, dmabuf::{DmabufFeedback, DmabufFeedbackBuilder, DmabufState}, fractional_scale::{
|
|
self as fractional_scale_mod,
|
|
FractionalScaleHandler, FractionalScaleManagerState,
|
|
}, output::OutputManagerState, selection::{
|
|
SelectionHandler, data_device::{
|
|
ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler, set_data_device_focus
|
|
}
|
|
},
|
|
relative_pointer::RelativePointerManagerState,
|
|
pointer_constraints::{self, PointerConstraintsHandler, PointerConstraintsState},
|
|
keyboard_shortcuts_inhibit::{KeyboardShortcutsInhibitHandler, KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor},
|
|
shell::xdg::{XdgShellState, decoration::XdgDecorationState}, shm::ShmState, single_pixel_buffer::SinglePixelBufferState, tablet_manager::TabletSeatHandler, viewporter::ViewporterState, xdg_activation::{XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData}, xwayland_shell::XWaylandShellState
|
|
},
|
|
xwayland::{XWayland, xwm::X11Wm},
|
|
};
|
|
use smithay::reexports::wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1::TrancheFlags;
|
|
|
|
pub mod portal;
|
|
use crate::{
|
|
config::Config,
|
|
cursor::CursorThemeManager,
|
|
dmabuf::TextureCache,
|
|
electron::{ElectronIpc, ElectronProcess},
|
|
syscall::SyscallHandler,
|
|
};
|
|
use portal::WoPortal;
|
|
use smithay::backend::allocator::dmabuf::Dmabuf;
|
|
|
|
/// A damage rectangle in buffer coordinates.
|
|
#[derive(Debug, Clone)]
|
|
pub struct DamageRect {
|
|
pub x: i32,
|
|
pub y: i32,
|
|
pub width: i32,
|
|
pub height: i32,
|
|
}
|
|
|
|
/// Buffered SHM pixel export with per-surface damage information.
|
|
pub struct ShmExport {
|
|
pub window_name: String,
|
|
pub width: u32,
|
|
pub height: u32,
|
|
pub stride: u32,
|
|
pub pixels: Vec<u8>,
|
|
pub damage_rects: Vec<DamageRect>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct ClientData {
|
|
pub compositor_state: CompositorClientState,
|
|
}
|
|
|
|
impl WlClientData for ClientData {
|
|
fn initialized(&self, _client_id: ClientId) {}
|
|
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
|
|
}
|
|
|
|
pub struct BackendData {
|
|
pub renderer: Option<std::sync::Arc<std::sync::Mutex<GlesRenderer>>>,
|
|
pub output: Output,
|
|
pub size: (u32, u32),
|
|
pub dmabuf_formats: Option<Vec<drm_fourcc::DrmFormat>>,
|
|
}
|
|
|
|
impl BackendData {
|
|
pub fn renderer(&self) -> Option<std::sync::Arc<std::sync::Mutex<GlesRenderer>>> {
|
|
self.renderer.clone()
|
|
}
|
|
}
|
|
|
|
pub struct WoState {
|
|
pub display_handle: DisplayHandle,
|
|
pub compositor_state: CompositorState,
|
|
pub xdg_shell_state: XdgShellState,
|
|
pub xdg_decoration_state: XdgDecorationState,
|
|
pub shm_state: ShmState,
|
|
pub dmabuf_state: DmabufState,
|
|
// pub dmabuf_global: DmabufGlobal,
|
|
pub dmabuf_default_feedback: DmabufFeedback,
|
|
pub dmabuf_scanout_feedback: DmabufFeedback,
|
|
pub seat_state: SeatState<Self>,
|
|
pub data_device_state: DataDeviceState,
|
|
pub popup_manager: PopupManager,
|
|
pub viewporter_state: ViewporterState,
|
|
pub xdg_activation_state: XdgActivationState,
|
|
pub single_pixel_buffer_state: SinglePixelBufferState,
|
|
pub fractional_scale_manager_state: FractionalScaleManagerState,
|
|
pub cursor_shape_manager_state: CursorShapeManagerState,
|
|
pub relative_pointer_manager_state: RelativePointerManagerState,
|
|
pub pointer_constraints_state: PointerConstraintsState,
|
|
pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState,
|
|
pub output_manager: OutputManagerState,
|
|
|
|
pub seat: Seat<Self>,
|
|
pub pointer_location: Point<f64, Logical>,
|
|
pub cursor_status: smithay::input::pointer::CursorImageStatus,
|
|
pub output_size: (u32, u32),
|
|
pub pointer_window_focus: Option<String>,
|
|
pub keyboard_window_focus: Option<String>,
|
|
|
|
pub space: Space<Window>,
|
|
pub output: Output,
|
|
|
|
pub electron_processes: Vec<ElectronProcess>,
|
|
pub texture_cache: TextureCache,
|
|
pub window_positions: std::collections::HashMap<String, (i32, i32, u32, u32)>,
|
|
pub electron_ipc: Option<ElectronIpc>,
|
|
pub syscall_handler: Option<SyscallHandler>,
|
|
pub portal: Option<std::sync::Arc<WoPortal>>,
|
|
/// Mapping state for named Electron windows. If false, the window should
|
|
/// not be rendered (unmapped/minimized).
|
|
pub window_mapped: std::collections::HashMap<String, bool>,
|
|
/// Damage tracking: which window textures have been updated and need rendering
|
|
pub damaged_windows: std::collections::HashSet<String>,
|
|
/// Dirty flag for window metadata; set when windows appear/close/move/resize
|
|
pub metadata_dirty: bool,
|
|
/// Wayland surfaces that committed a new buffer and need offscreen capture.
|
|
pub dirty_surfaces: std::collections::HashSet<WlSurface>,
|
|
/// Ping-pong SHM memfd pairs per window. Index 0 is being written by
|
|
/// the compositor while Electron reads index 1 (and vice versa).
|
|
pub window_shm_buffers: std::collections::HashMap<String, [std::os::unix::io::OwnedFd; 2]>,
|
|
/// Tracks which of the two memfds is the current write target (0 or 1).
|
|
pub window_shm_write_idx: std::collections::HashMap<String, usize>,
|
|
/// Buffered pixel exports from offscreen rendering, written to SHM after render block.
|
|
pub pending_shm_exports: Vec<ShmExport>,
|
|
pub grab_state: Option<crate::handlers::xdg_shell::GrabState>,
|
|
pub cursor_theme_manager: CursorThemeManager,
|
|
|
|
/// Set to true inside new_toplevel to enable checkpoint logging on the
|
|
/// same main-loop tick where the window appears.
|
|
pub debug_new_toplevel: bool,
|
|
|
|
/// Stable name mapping for Wayland windows (surface ObjectId → "wayland-N").
|
|
pub wayland_window_names: std::collections::HashMap<ObjectId, String>,
|
|
/// Reverse lookup: stable name → surface ObjectId for action dispatch.
|
|
pub wayland_name_to_id: std::collections::HashMap<String, ObjectId>,
|
|
/// Monotonic counter for assigning stable Wayland window IDs.
|
|
pub next_wayland_id: u32,
|
|
/// Surfaces that negotiated SSD via the xdg-decoration protocol.
|
|
/// For these windows comraw renders its own title bar; for others (CSD)
|
|
/// the client draws its own decorations and no extra title offset is added.
|
|
pub ssd_windows: std::collections::HashSet<ObjectId>,
|
|
/// Stable name mapping for X11 (XWayland) windows (X11 window_id → "x11-N").
|
|
pub x11_window_names: std::collections::HashMap<u32, String>,
|
|
|
|
/// XWayland instance handle (if running).
|
|
pub xwayland: Option<XWayland>,
|
|
/// X11 window manager attached to the XWayland instance.
|
|
pub xwm: Option<X11Wm>,
|
|
/// XWayland shell protocol state for associating X11 windows with wl_surfaces.
|
|
pub xwayland_shell_state: Option<XWaylandShellState>,
|
|
pub all_windows: Vec<Window>,
|
|
|
|
pub backend: &'static BackendData,
|
|
pub config: Config,
|
|
pub running: bool,
|
|
pub can_switch_vt: bool,
|
|
pub pending_vt_switch: Option<u32>,
|
|
}
|
|
|
|
impl WoState {
|
|
pub fn new(
|
|
display: &mut Display<Self>,
|
|
config: Config,
|
|
render_node: DrmNode,
|
|
backend: BackendData,
|
|
) -> Self {
|
|
let dh = display.handle();
|
|
|
|
let compositor_state = CompositorState::new::<Self>(&dh);
|
|
let xdg_shell_state = XdgShellState::new::<Self>(&dh);
|
|
let xdg_decoration_state = XdgDecorationState::new::<Self>(&dh);
|
|
let relative_pointer_manager_state = RelativePointerManagerState::new::<Self>(&dh);
|
|
let pointer_constraints_state = PointerConstraintsState::new::<Self>(&dh);
|
|
let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::<Self>(&dh);
|
|
let shm_state = ShmState::new::<Self>(&dh, vec![]);
|
|
let viewporter_state = ViewporterState::new::<Self>(&dh);
|
|
let xdg_activation_state = XdgActivationState::new::<Self>(&dh);
|
|
let single_pixel_buffer_state = SinglePixelBufferState::new::<Self>(&dh);
|
|
let fractional_scale_manager_state = FractionalScaleManagerState::new::<Self>(&dh);
|
|
let cursor_shape_manager_state = CursorShapeManagerState::new::<Self>(&dh);
|
|
// Register zxdg_output_manager_v1 so clients like GTK4 can get logical
|
|
// output dimensions via the XDG output protocol.
|
|
let output_manager = OutputManagerState::new_with_xdg_output::<Self>(&dh);
|
|
let output_size = backend.size;
|
|
let output = backend.output.clone();
|
|
|
|
let mut seat_state = SeatState::new();
|
|
let mut seat: Seat<Self> = seat_state.new_wl_seat(&dh, "wo");
|
|
seat.add_keyboard(Default::default(), 200, 25)
|
|
.expect("keyboard");
|
|
seat.add_pointer();
|
|
|
|
// Initialize syscall handler if enabled
|
|
let syscall_handler = if config.compositor.enable_syscalls {
|
|
let apps: Vec<serde_json::Value> = config
|
|
.compositor
|
|
.applications
|
|
.iter()
|
|
.map(|app| app.to_json())
|
|
.collect();
|
|
Some(SyscallHandler::new(true, false).with_applications(apps))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// Initialize portal if enabled
|
|
let portal = if config.compositor.enable_portal {
|
|
Some(std::sync::Arc::new(WoPortal::new()))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let data_device_state = DataDeviceState::new::<Self>(&dh);
|
|
let dmabuf_state = DmabufState::new();
|
|
|
|
// Get DMABUF formats from renderer if available
|
|
let (default_feedback, scanout_feedback) = if let Some(renderer_arc) = &backend.renderer {
|
|
let renderer_guard = renderer_arc.lock().expect("renderer mutex poisoned");
|
|
let dmabuf_formats = renderer_guard.dmabuf_formats();
|
|
let default_fb =
|
|
DmabufFeedbackBuilder::new(render_node.dev_id(), dmabuf_formats.clone())
|
|
.build()
|
|
.expect("building default dmabuf feedback");
|
|
let scanout_fb =
|
|
DmabufFeedbackBuilder::new(render_node.dev_id(), dmabuf_formats.clone())
|
|
.add_preference_tranche(
|
|
render_node.dev_id(),
|
|
Some(TrancheFlags::Scanout),
|
|
dmabuf_formats,
|
|
)
|
|
.build()
|
|
.expect("building scanout dmabuf feedback");
|
|
(default_fb, scanout_fb)
|
|
} else if let Some(ref formats) = backend.dmabuf_formats {
|
|
// For nested mode with pre-collected formats
|
|
let default_fb = DmabufFeedbackBuilder::new(render_node.dev_id(), formats.clone())
|
|
.build()
|
|
.expect("building default dmabuf feedback");
|
|
let scanout_fb = DmabufFeedbackBuilder::new(render_node.dev_id(), formats.clone())
|
|
.add_preference_tranche(
|
|
render_node.dev_id(),
|
|
Some(TrancheFlags::Scanout),
|
|
formats.clone(),
|
|
)
|
|
.build()
|
|
.expect("building scanout dmabuf feedback");
|
|
(default_fb, scanout_fb)
|
|
} else {
|
|
// Fallback: create empty DMABUF feedbacks
|
|
let default_fb = DmabufFeedbackBuilder::new(render_node.dev_id(), vec![])
|
|
.build()
|
|
.expect("building empty default dmabuf feedback");
|
|
let scanout_fb = DmabufFeedbackBuilder::new(render_node.dev_id(), vec![])
|
|
.build()
|
|
.expect("building empty scanout dmabuf feedback");
|
|
(default_fb, scanout_fb)
|
|
};
|
|
// Disable DMABUF advertising to force clients to use SHM for the pixel export pipeline.
|
|
/*
|
|
let dmabuf_global = dmabuf_state.create_global_with_default_feedback::<Self>(
|
|
&dh,
|
|
&default_feedback,
|
|
);
|
|
*/
|
|
let popup_manager = PopupManager::default();
|
|
let space = Space::default();
|
|
|
|
let cursor_theme_manager = CursorThemeManager::new(
|
|
&config.compositor.cursor_theme,
|
|
config.compositor.cursor_size,
|
|
);
|
|
|
|
let mut state = Self {
|
|
display_handle: dh,
|
|
compositor_state,
|
|
xdg_shell_state,
|
|
xdg_decoration_state,
|
|
shm_state,
|
|
dmabuf_state,
|
|
// dmabuf_global,
|
|
dmabuf_default_feedback: default_feedback,
|
|
dmabuf_scanout_feedback: scanout_feedback,
|
|
seat_state,
|
|
data_device_state,
|
|
popup_manager,
|
|
viewporter_state,
|
|
xdg_activation_state,
|
|
single_pixel_buffer_state,
|
|
fractional_scale_manager_state,
|
|
cursor_shape_manager_state,
|
|
relative_pointer_manager_state,
|
|
pointer_constraints_state,
|
|
keyboard_shortcuts_inhibit_state,
|
|
output_manager,
|
|
seat,
|
|
pointer_location: (0.0, 0.0).into(),
|
|
cursor_status: smithay::input::pointer::CursorImageStatus::default_named(),
|
|
pointer_window_focus: None,
|
|
keyboard_window_focus: None,
|
|
output_size,
|
|
space,
|
|
output,
|
|
electron_processes: vec![],
|
|
texture_cache: TextureCache::default(),
|
|
window_positions: std::collections::HashMap::new(),
|
|
electron_ipc: None,
|
|
syscall_handler,
|
|
portal,
|
|
xwayland: None,
|
|
xwm: None,
|
|
xwayland_shell_state: None,
|
|
all_windows: Vec::new(),
|
|
backend: Box::leak(Box::new(backend)),
|
|
config,
|
|
running: true,
|
|
window_mapped: std::collections::HashMap::new(),
|
|
damaged_windows: std::collections::HashSet::new(),
|
|
metadata_dirty: true,
|
|
dirty_surfaces: std::collections::HashSet::new(),
|
|
window_shm_buffers: std::collections::HashMap::new(),
|
|
window_shm_write_idx: std::collections::HashMap::new(),
|
|
pending_shm_exports: Vec::new(),
|
|
grab_state: None,
|
|
cursor_theme_manager,
|
|
debug_new_toplevel: false,
|
|
wayland_window_names: std::collections::HashMap::new(),
|
|
wayland_name_to_id: std::collections::HashMap::new(),
|
|
next_wayland_id: 0,
|
|
ssd_windows: std::collections::HashSet::new(),
|
|
x11_window_names: std::collections::HashMap::new(),
|
|
can_switch_vt: false,
|
|
pending_vt_switch: None,
|
|
};
|
|
|
|
// Map the output into the space at origin.
|
|
state.space.map_output(&state.output, (0, 0));
|
|
|
|
// Initialize mapping state for configured windows as mapped (visible).
|
|
for win in &state.config.windows {
|
|
state.window_mapped.insert(win.name.clone(), true);
|
|
}
|
|
|
|
state
|
|
}
|
|
|
|
/// Return the effective title-bar height for a Wayland window.
|
|
/// SSD windows get TITLE_H (comraw draws the title bar); CSD windows get 0
|
|
/// (the client draws its own decorations).
|
|
pub fn title_h_for_surface(&self, surface: &WlSurface) -> i32 {
|
|
if self.ssd_windows.contains(&surface.id()) {
|
|
crate::handlers::xdg_shell::TITLE_H
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
/// Return the effective title-bar height for a Window element.
|
|
/// X11 windows always use SSD (comraw draws the chrome).
|
|
pub fn title_h_for_window(&self, window: &Window) -> i32 {
|
|
if window.x11_surface().is_some() {
|
|
return crate::handlers::xdg_shell::TITLE_H;
|
|
}
|
|
window
|
|
.toplevel()
|
|
.map(|t| self.title_h_for_surface(t.wl_surface()))
|
|
.unwrap_or(0)
|
|
}
|
|
|
|
/// Clamp a requested size to the client's min/max size hints.
|
|
/// Returns `(width, height)` clamped to the XDG toplevel's declared
|
|
/// min_size / max_size constraints. A constraint component of 0 means
|
|
/// "no limit" per the xdg-shell protocol.
|
|
pub fn clamp_to_size_hints(&self, window: &Window, mut w: i32, mut h: i32) -> (i32, i32) {
|
|
if let Some(toplevel) = window.toplevel() {
|
|
use smithay::wayland::shell::xdg::SurfaceCachedState;
|
|
with_states(toplevel.wl_surface(), |states| {
|
|
let mut cached = states.cached_state.get::<SurfaceCachedState>();
|
|
let current = cached.current();
|
|
let min = current.min_size;
|
|
let max = current.max_size;
|
|
// min_size: (0,0) means no minimum
|
|
if min.w > 0 {
|
|
w = w.max(min.w);
|
|
}
|
|
if min.h > 0 {
|
|
h = h.max(min.h);
|
|
}
|
|
// max_size: (0,0) means no maximum
|
|
if max.w > 0 {
|
|
w = w.min(max.w);
|
|
}
|
|
if max.h > 0 {
|
|
h = h.min(max.h);
|
|
}
|
|
});
|
|
}
|
|
(w.max(1), h.max(1))
|
|
}
|
|
|
|
/// Return true if the given Wayland surface currently has keyboard focus.
|
|
pub fn surface_has_keyboard_focus(&self, surface: &WlSurface) -> bool {
|
|
self.seat
|
|
.get_keyboard()
|
|
.and_then(|kb| kb.current_focus())
|
|
.map(|focused| &focused == surface)
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Get the stable name for a window (Wayland or X11), if assigned.
|
|
pub fn wayland_window_name(&self, window: &Window) -> Option<&String> {
|
|
let surface_id = window.toplevel().map(|t| t.wl_surface().id());
|
|
if let Some(id) = surface_id {
|
|
if let Some(name) = self.wayland_window_names.get(&id) {
|
|
return Some(name);
|
|
}
|
|
}
|
|
if let Some(x11) = window.x11_surface() {
|
|
return self.x11_window_names.get(&x11.window_id());
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Find a Wayland or X11 Window element by its stable name (even if minimized).
|
|
pub fn find_wayland_window(&self, name: &str) -> Option<Window> {
|
|
self.all_windows.iter().find(|w| {
|
|
if let Some(toplevel) = w.toplevel() {
|
|
if let Some(win_name) = self.wayland_window_names.get(&toplevel.wl_surface().id()) {
|
|
if win_name == name { return true; }
|
|
}
|
|
}
|
|
if let Some(x11) = w.x11_surface() {
|
|
if let Some(win_name) = self.x11_window_names.get(&x11.window_id()) {
|
|
if win_name == name { return true; }
|
|
}
|
|
}
|
|
false
|
|
}).cloned()
|
|
}
|
|
|
|
/// Focus the topmost mapped native window in `space`.
|
|
pub fn refocus_topmost_native_window(&mut self) {
|
|
let top = self.space.elements().next().cloned();
|
|
|
|
let Some(window) = top else {
|
|
if let Some(keyboard) = self.seat.get_keyboard() {
|
|
let serial = SERIAL_COUNTER.next_serial();
|
|
keyboard.set_focus(self, None, serial);
|
|
}
|
|
self.keyboard_window_focus = None;
|
|
self.pointer_window_focus = None;
|
|
return;
|
|
};
|
|
|
|
let surface = window
|
|
.toplevel()
|
|
.map(|t| t.wl_surface().clone())
|
|
.or_else(|| window.x11_surface().and_then(|x| x.wl_surface()));
|
|
|
|
if let Some(surface) = surface {
|
|
if let Some(keyboard) = self.seat.get_keyboard() {
|
|
let serial = SERIAL_COUNTER.next_serial();
|
|
keyboard.set_focus(self, Some(surface), serial);
|
|
}
|
|
|
|
if let Some(name) = self.wayland_window_name(&window).cloned() {
|
|
self.keyboard_window_focus = Some(name.clone());
|
|
self.pointer_window_focus = Some(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Forward input events from the compositor to Electron windows.
|
|
/// This is called by the nested compositor's event loop.
|
|
/// The actual forwarding is handled by process_input_event.
|
|
pub fn forward_input_to_electron<B: smithay::backend::input::InputBackend>(
|
|
&mut self,
|
|
event: &smithay::backend::input::InputEvent<B>,
|
|
) {
|
|
// The actual forwarding is done by process_input_event which is called separately
|
|
// This is a no-op wrapper that exists for API compatibility with nested.rs
|
|
// All input forwarding is handled through the individual input event handlers
|
|
// (on_keyboard, on_pointer_motion, on_pointer_button, etc.)
|
|
}
|
|
|
|
/// Return an exported DMABUF for the most-recent texture of a named
|
|
/// Electron window, if available. Callers can use this to create a
|
|
/// Wayland `wl_buffer` via `DmabufState` APIs for zero-copy sharing.
|
|
pub fn exported_dmabuf_for_window(&self, name: &str) -> Option<Dmabuf> {
|
|
self.texture_cache
|
|
.get_dmabuf(name)
|
|
.map(|t| t.dmabuf.clone())
|
|
}
|
|
|
|
/// Return the Wayland surface (and local pointer position) currently under the pointer.
|
|
pub fn surface_under(
|
|
&self,
|
|
) -> Option<(
|
|
smithay::reexports::wayland_server::protocol::wl_surface::WlSurface,
|
|
Point<f64, Logical>,
|
|
)> {
|
|
let pos = self.pointer_location;
|
|
self.space
|
|
.element_under(pos)
|
|
.and_then(|(window, window_pos)| {
|
|
let local = pos - window_pos.to_f64();
|
|
// Translate global space coordinate to surface-local coordinate
|
|
// window.geometry().loc is the offset from surface origin to content area.
|
|
let geo = window.geometry();
|
|
let surface_local: Point<f64, Logical> = (local.x + geo.loc.x as f64, local.y + geo.loc.y as f64).into();
|
|
window
|
|
.surface_under(surface_local, smithay::desktop::WindowSurfaceType::ALL)
|
|
.map(|(s, p)| (s, p.to_f64() + window_pos.to_f64()))
|
|
})
|
|
}
|
|
}
|
|
|
|
impl SeatHandler for WoState {
|
|
type KeyboardFocus = smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
|
type PointerFocus = smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
|
type TouchFocus = smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
|
|
|
fn seat_state(&mut self) -> &mut SeatState<Self> {
|
|
&mut self.seat_state
|
|
}
|
|
|
|
fn focus_changed(&mut self, seat: &Seat<Self>, target: Option<&Self::KeyboardFocus>) {
|
|
let dh = &self.display_handle;
|
|
let _ = target;
|
|
let focus = None;
|
|
set_data_device_focus(dh, seat, focus);
|
|
}
|
|
|
|
fn cursor_image(
|
|
&mut self,
|
|
_seat: &Seat<Self>,
|
|
image: smithay::input::pointer::CursorImageStatus,
|
|
) {
|
|
self.cursor_status = image;
|
|
}
|
|
}
|
|
|
|
smithay::delegate_seat!(WoState);
|
|
|
|
impl SelectionHandler for WoState {
|
|
type SelectionUserData = ();
|
|
}
|
|
|
|
impl DataDeviceHandler for WoState {
|
|
fn data_device_state(&self) -> &DataDeviceState {
|
|
&self.data_device_state
|
|
}
|
|
}
|
|
|
|
impl ClientDndGrabHandler for WoState {}
|
|
impl ServerDndGrabHandler for WoState {}
|
|
|
|
smithay::delegate_data_device!(WoState);
|
|
|
|
impl XdgActivationHandler for WoState {
|
|
fn activation_state(&mut self) -> &mut XdgActivationState {
|
|
&mut self.xdg_activation_state
|
|
}
|
|
|
|
fn request_activation(
|
|
&mut self,
|
|
_token: XdgActivationToken,
|
|
_token_data: XdgActivationTokenData,
|
|
surface: WlSurface,
|
|
) {
|
|
// Grant all activation requests by giving keyboard focus to the surface.
|
|
let serial = SERIAL_COUNTER.next_serial();
|
|
if let Some(keyboard) = self.seat.get_keyboard() {
|
|
keyboard.set_focus(self, Some(surface), serial);
|
|
}
|
|
}
|
|
}
|
|
|
|
smithay::delegate_xdg_activation!(WoState);
|
|
|
|
impl FractionalScaleHandler for WoState {
|
|
fn new_fractional_scale(&mut self, surface: WlSurface) {
|
|
tracing::info!("new_fractional_scale: setting preferred_scale(1.0)");
|
|
// Immediately advertise scale 1.0. Without this, GTK4 apps (and
|
|
// Firefox's GTK layer) wait indefinitely for the preferred_scale
|
|
// event before committing their first buffer, causing a visible freeze.
|
|
with_states(&surface, |states| {
|
|
fractional_scale_mod::with_fractional_scale(states, |fs| {
|
|
fs.set_preferred_scale(1.0);
|
|
});
|
|
});
|
|
tracing::info!("new_fractional_scale: done");
|
|
}
|
|
}
|
|
|
|
smithay::delegate_fractional_scale!(WoState);
|
|
smithay::delegate_viewporter!(WoState);
|
|
smithay::delegate_single_pixel_buffer!(WoState);
|
|
|
|
impl TabletSeatHandler for WoState {}
|
|
|
|
smithay::delegate_cursor_shape!(WoState);
|
|
|
|
impl PointerConstraintsHandler for WoState {
|
|
fn cursor_position_hint(
|
|
&mut self,
|
|
_surface: &WlSurface,
|
|
_pointer: &PointerHandle<Self>,
|
|
_location: Point<f64, Logical>,
|
|
) {
|
|
}
|
|
|
|
fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle<Self>) {
|
|
// When a new constraint is created, check if it's a lock and notify Electron.
|
|
pointer_constraints::with_pointer_constraint(surface, pointer, |constraint| {
|
|
if let Some(constraint) = constraint {
|
|
if let smithay::wayland::pointer_constraints::PointerConstraint::Locked(_) =
|
|
&*constraint
|
|
{
|
|
// Call activate to actually lock it in Smithay
|
|
constraint.activate();
|
|
|
|
if let Some(target_window) = self.space.elements().find(|w| {
|
|
w.toplevel()
|
|
.map(|t| t.wl_surface() == surface)
|
|
.unwrap_or(false)
|
|
|| w.x11_surface()
|
|
.and_then(|x| x.wl_surface())
|
|
.map(|s| s == *surface)
|
|
.unwrap_or(false)
|
|
}) {
|
|
if let Some(name) = self.wayland_window_name(target_window) {
|
|
if let Some(ref ipc) = self.electron_ipc {
|
|
let _ = ipc.send_to_window(
|
|
&name,
|
|
&crate::electron::ElectronInputEvent::PointerLockRequest {
|
|
window_name: name.clone(),
|
|
lock: true,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
smithay::delegate_pointer_constraints!(WoState);
|
|
smithay::delegate_relative_pointer!(WoState);
|
|
|
|
impl KeyboardShortcutsInhibitHandler for WoState {
|
|
fn keyboard_shortcuts_inhibit_state(&mut self) -> &mut KeyboardShortcutsInhibitState {
|
|
&mut self.keyboard_shortcuts_inhibit_state
|
|
}
|
|
|
|
fn new_inhibitor(&mut self, inhibitor: KeyboardShortcutsInhibitor) {
|
|
// Automatically activate: games/fullscreen apps that request inhibition
|
|
// should receive all key events without compositor interception.
|
|
inhibitor.activate();
|
|
}
|
|
}
|
|
|
|
smithay::delegate_keyboard_shortcuts_inhibit!(WoState);
|
|
|
|
/// Propagate environment variables to the DBus session and systemd user
|
|
/// manager so that applications launched via desktop entries, D-Bus
|
|
/// activation, or Steam inherit DISPLAY and WAYLAND_DISPLAY.
|
|
pub fn propagate_environment(vars: &[&str]) {
|
|
use tracing::{info, warn};
|
|
|
|
let var_args: Vec<String> = vars
|
|
.iter()
|
|
.filter_map(|&name| {
|
|
std::env::var(name).ok().map(|val| format!("{name}={val}"))
|
|
})
|
|
.collect();
|
|
|
|
if var_args.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let names: Vec<&str> = vars
|
|
.iter()
|
|
.filter(|&&name| std::env::var(name).is_ok())
|
|
.copied()
|
|
.collect();
|
|
|
|
// dbus-update-activation-environment sets variables for D-Bus-activated services.
|
|
let mut dbus_cmd = std::process::Command::new("dbus-update-activation-environment");
|
|
dbus_cmd.arg("--systemd");
|
|
for arg in &var_args {
|
|
dbus_cmd.arg(arg);
|
|
}
|
|
match dbus_cmd.stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null()).spawn() {
|
|
Ok(mut child) => {
|
|
let _ = child.wait();
|
|
info!("Propagated {:?} to DBus activation environment", names);
|
|
}
|
|
Err(e) => warn!("dbus-update-activation-environment failed: {e}"),
|
|
}
|
|
|
|
// systemctl --user import-environment sets variables for systemd user services.
|
|
let mut systemctl_cmd = std::process::Command::new("systemctl");
|
|
systemctl_cmd.arg("--user").arg("import-environment");
|
|
for name in &names {
|
|
systemctl_cmd.arg(name);
|
|
}
|
|
match systemctl_cmd.stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null()).spawn() {
|
|
Ok(mut child) => {
|
|
let _ = child.wait();
|
|
info!("Propagated {:?} to systemd user environment", names);
|
|
}
|
|
Err(e) => warn!("systemctl --user import-environment failed: {e}"),
|
|
}
|
|
}
|