use crate::core::ObjectId;
use crate::core::PlatformFamily;
use crate::event::EventLoop;
use crate::platform::clipboard::RichClipboardBackend as _;
use crate::platform::types::{
Platform, PlatformCapabilities, WidgetTriggerEvent, WidgetTriggerKind,
};
use crate::platform::wayland::types::{WaylandHandleKind, WaylandPlatform};
const FRAME_INTERVAL_MS: u32 = 16;
#[cfg(all(feature = "wayland-native", unix_desktop))]
use wayland_client as wl_client;
#[cfg(all(feature = "wayland-native", unix_desktop))]
use wayland_protocols as wl_protocols;
#[cfg(all(feature = "wayland-native", unix_desktop))]
fn session_output(platform: &WaylandPlatform) -> Option<crate::platform::Display> {
let guard = platform.native_session.lock().ok()?;
let session = guard.as_ref()?;
let state = &session.state;
if !state.output_ready || state.output_width == 0 || state.output_height == 0 {
return None;
}
let scale =
if state.dpi_scale.is_finite() && state.dpi_scale > 0.0 { state.dpi_scale } else { 1.0 };
let width = (state.output_width as f32 / scale).round() as u32;
let height = (state.output_height as f32 / scale).round() as u32;
Some(crate::platform::Display {
id: crate::platform::DisplayId::new(1),
bounds: crate::core::Rect::new(state.output_x, state.output_y, width, height),
work_area: crate::core::Rect::new(state.output_x, state.output_y, width, height),
scale,
is_primary: true,
})
}
impl Platform for WaylandPlatform {
crate::impl_platform_state_properties!();
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn backend_name(&self) -> &'static str {
"wayland"
}
fn family(&self) -> PlatformFamily {
PlatformFamily::Desktop
}
fn total_memory_mb(&self) -> Option<u64> {
crate::platform::os_probes::total_memory_mb()
}
fn is_on_battery(&self) -> bool {
crate::platform::os_probes::is_on_battery()
}
fn process_memory_utilization(&self) -> Option<f32> {
crate::platform::os_probes::process_memory_utilization()
}
fn process_cpu_utilization(&self) -> Option<f32> {
crate::platform::os_probes::process_cpu_utilization()
}
fn spawn_print_job(&self, job_file: &std::path::Path) -> Result<(), String> {
crate::platform::os_probes::spawn_print_job(job_file)
}
fn has_print_support(&self) -> bool {
crate::platform::types::unix_print_clients_available()
}
fn capabilities(&self) -> PlatformCapabilities {
PlatformCapabilities {
dpi_scaling: true,
ime: self.ime.available(),
accessibility: false,
native_menu: false,
typed_widget_trigger: true,
clipboard: self.clipboard.has_channel(),
open_external: true,
file_dialog: false,
system_fonts: true,
}
}
fn safe_area_insets(
&self,
_window_id: ObjectId,
) -> Option<crate::platform::types::SafeAreaInsets> {
Some(crate::platform::types::SafeAreaInsets { top: 0, bottom: 0, left: 0, right: 0 })
}
fn open_external(&self, target: &str) -> Result<(), crate::platform::OpenError> {
crate::platform::external::spawn_handler("xdg-open", &[], target)
}
fn has_external_handler(&self) -> bool {
crate::platform::external::program_is_on_path("xdg-open")
}
fn high_contrast(&self) -> Option<bool> {
#[cfg(all(feature = "wayland-portal", unix_desktop))]
{
crate::platform::wayland::portal::contrast()
}
#[cfg(not(all(feature = "wayland-portal", unix_desktop)))]
{
None
}
}
fn reduce_motion(&self) -> Option<bool> {
None
}
fn system_theme(&self) -> Option<bool> {
#[cfg(all(feature = "wayland-portal", unix_desktop))]
{
crate::platform::wayland::portal::color_scheme()
}
#[cfg(not(all(feature = "wayland-portal", unix_desktop)))]
{
None
}
}
fn system_locale(&self) -> Option<&'static str> {
crate::platform::locale::intern(crate::platform::locale::from_environment())
}
fn environment_generation(&self) -> u64 {
crate::platform::locale::generation_hash(
self.system_theme(),
self.high_contrast(),
self.reduce_motion(),
self.system_locale(),
)
}
#[cfg(not(alloc_frugal))]
fn system_fonts(&self) -> Vec<crate::platform::types::SystemFont> {
crate::platform::fonts_unix::system_fonts()
}
#[cfg(alloc_frugal)]
fn system_fonts(&self) -> Vec<crate::platform::types::SystemFont> {
Vec::new()
}
fn displays(&self) -> Vec<crate::platform::Display> {
#[cfg(all(feature = "wayland-native", unix_desktop))]
{
if let Some(display) = session_output(self) {
return crate::compat::vec![display];
}
}
Vec::new()
}
fn display_of_window(&self, window_id: ObjectId) -> Option<crate::platform::DisplayId> {
if !self.state.is_kind(window_id, WaylandHandleKind::Window) {
return None;
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
{
if let Some(display) = session_output(self) {
return Some(display.id);
}
}
None
}
fn display_generation(&self) -> u64 {
#[cfg(all(feature = "wayland-native", unix_desktop))]
{
if let Ok(guard) = self.native_session.lock() {
if let Some(session) = guard.as_ref() {
return session.state.output_generation;
}
}
}
0
}
fn display_info(
&self,
display: crate::platform::DisplayId,
) -> Option<crate::platform::types::DisplayInfo> {
#[cfg(all(feature = "wayland-native", unix_desktop))]
{
let item = session_output(self)?;
if item.id != display {
return None;
}
let refresh = {
let guard = self.native_session.lock().ok()?;
guard.as_ref()?.state.output_refresh_mhz
};
Some(crate::platform::types::DisplayInfo {
refresh_rate_mhz: refresh,
orientation: Some(if item.bounds.width >= item.bounds.height {
crate::core::Orientation::Horizontal
} else {
crate::core::Orientation::Vertical
}),
color_space: None,
hdr: None,
})
}
#[cfg(not(all(feature = "wayland-native", unix_desktop)))]
{
let _ = display;
None
}
}
fn dpi_scale_factor(&self) -> f32 {
#[cfg(all(feature = "wayland-native", unix_desktop))]
{
if let Some(display) = session_output(self) {
return display.scale;
}
}
if let Ok(scale_str) = std::env::var("GDK_SCALE") {
if let Ok(scale) = scale_str.parse::<f32>() {
if scale > 0.0 {
return scale;
}
}
}
if let Ok(scale_str) = std::env::var("QT_SCALE_FACTOR") {
if let Ok(scale) = scale_str.parse::<f32>() {
if scale > 0.0 {
return scale;
}
}
}
if let Ok(scale_str) = std::env::var("RUST_WIDGETS_DPI_SCALE") {
if let Ok(scale) = scale_str.parse::<f32>() {
if scale > 0.0 {
return scale;
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
{
if let Ok(guard) = self.native_session.lock() {
if let Some(ref session) = *guard {
let scale = session.state.dpi_scale;
if scale > 0.0 {
return scale;
}
}
}
}
1.0
}
fn init(&self) {
self.runtime.initialized.store(true, std::sync::atomic::Ordering::SeqCst);
log::info!("[wayland] Platform initialized (state-only backend).");
}
fn run(&self) {
self.runtime.running.store(true, std::sync::atomic::Ordering::SeqCst);
let wayland_display =
std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| String::from("wayland-0"));
let xdg_session =
std::env::var("XDG_SESSION_TYPE").unwrap_or_else(|_| String::from("unknown"));
log::info!(
"[wayland] Platform running: XDG_SESSION_TYPE={}, WAYLAND_DISPLAY={}",
xdg_session,
wayland_display
);
let mut event_loop = EventLoop::new();
#[cfg(all(feature = "wayland-native", not(alloc_frugal), unix_desktop))]
if let Some(pump) = create_event_loop_pump() {
event_loop.set_native_pump(pump);
}
event_loop.start();
#[cfg(all(feature = "wayland-native", not(alloc_frugal), unix_desktop))]
{
self.run_native_event_loop();
}
#[cfg(not(all(feature = "wayland-native", not(alloc_frugal), unix_desktop)))]
while self.runtime.running.load(std::sync::atomic::Ordering::SeqCst) {
std::thread::sleep(std::time::Duration::from_millis(16));
}
}
fn quit(&self) -> bool {
self.runtime.running.store(false, std::sync::atomic::Ordering::SeqCst);
log::info!("[wayland] Platform quit.");
true
}
fn destroy_widget(&self, widget_id: ObjectId) -> bool {
if let Ok(mut menus) = self.menus.lock() {
menus.attached_menu_bar.remove(&widget_id);
menus.attached_menu_bar.retain(|_, bar| *bar != widget_id);
menus.menu_children.remove(&widget_id);
for children in menus.menu_children.values_mut() {
children.retain(|child| *child != widget_id);
}
menus.pending_menu_events.retain(|queued| *queued != widget_id);
menus.pending_widget_events.retain(|event| event.widget_id != widget_id);
} else {
log::error!("[wayland] destroy_widget: menu mutex poisoned");
}
self.state.destroy_widget(widget_id)
}
fn create_window(&self, title: &str, x: i32, y: i32, width: u32, height: u32) -> ObjectId {
#[cfg(all(feature = "wayland-native", unix_desktop))]
if let Some(id) = self.try_create_native_window(title, x, y, width, height) {
return id;
}
self.insert_widget(WaylandHandleKind::Window, title, x, y, width, height)
}
fn create_menu_bar(
&self,
parent: ObjectId,
x: i32,
y: i32,
width: u32,
height: u32,
) -> ObjectId {
if !matches!(self.state.kind_of(parent), Some(WaylandHandleKind::Window)) {
return 0;
}
let id = self.insert_widget(WaylandHandleKind::MenuBar, "", x, y, width, height);
if let Ok(mut menus) = self.menus.lock() {
menus.attached_menu_bar.insert(parent, id);
} else {
log::error!("[wayland] create_menu_bar: mutex poisoned");
}
id
}
fn create_menu(
&self,
parent: ObjectId,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> ObjectId {
if !matches!(
self.state.kind_of(parent),
Some(WaylandHandleKind::MenuBar | WaylandHandleKind::Menu)
) {
return 0;
}
let id = self.insert_widget(WaylandHandleKind::Menu, text, x, y, width, height);
if let Ok(mut menus) = self.menus.lock() {
menus.menu_children.entry(parent).or_default().push(id);
} else {
log::error!("[wayland] create_menu: mutex poisoned");
}
id
}
fn attach_menu_bar_to_window(&self, window: ObjectId, menu_bar: ObjectId) -> bool {
if !matches!(self.state.kind_of(window), Some(WaylandHandleKind::Window)) {
return false;
}
if !matches!(self.state.kind_of(menu_bar), Some(WaylandHandleKind::MenuBar)) {
return false;
}
if let Ok(mut menus) = self.menus.lock() {
menus.attached_menu_bar.insert(window, menu_bar);
true
} else {
log::error!("[wayland] attach_menu_bar_to_window: mutex poisoned");
false
}
}
fn menu_add_item(&self, parent_menu: ObjectId, text: &str, shortcut: Option<&str>) -> ObjectId {
if !matches!(self.state.kind_of(parent_menu), Some(WaylandHandleKind::Menu)) {
return 0;
}
let display = if let Some(shortcut) = shortcut {
format!("{}\t{}", text, shortcut)
} else {
text.to_string()
};
let id = self.insert_widget(WaylandHandleKind::MenuItem, &display, 0, 0, 0, 0);
if let Ok(mut menus) = self.menus.lock() {
menus.menu_children.entry(parent_menu).or_default().push(id);
}
id
}
fn poll_menu_triggered(&self) -> Option<ObjectId> {
if let Ok(mut menus) = self.menus.lock() {
menus.pending_menu_events.pop_front()
} else {
log::error!("[wayland] poll_menu_triggered: mutex poisoned");
None
}
}
fn inject_menu_trigger(&self, menu_item_id: ObjectId) -> bool {
if !matches!(self.state.kind_of(menu_item_id), Some(WaylandHandleKind::MenuItem)) {
return false;
}
if let Ok(mut menus) = self.menus.lock() {
menus.pending_menu_events.push_back(menu_item_id);
true
} else {
log::error!("[wayland] inject_menu_trigger: mutex poisoned");
false
}
}
fn poll_widget_triggered(&self) -> Option<ObjectId> {
if let Ok(mut menus) = self.menus.lock() {
menus.pending_widget_events.pop_front().map(|e| e.widget_id)
} else {
log::error!("[wayland] poll_widget_triggered: mutex poisoned");
None
}
}
fn poll_widget_trigger_event(&self) -> Option<WidgetTriggerEvent> {
if let Ok(mut menus) = self.menus.lock() {
menus.pending_widget_events.pop_front()
} else {
log::error!("[wayland] poll_widget_trigger_event: mutex poisoned");
None
}
}
fn inject_widget_trigger_event(&self, widget_id: ObjectId, kind: WidgetTriggerKind) -> bool {
if !self.state.contains_widget(widget_id) {
return false;
}
if let Ok(mut menus) = self.menus.lock() {
menus.pending_widget_events.push_back(WidgetTriggerEvent { widget_id, kind });
true
} else {
log::error!("[wayland] inject_widget_trigger_event: mutex poisoned");
false
}
}
fn window_client_size(&self, window_id: ObjectId) -> Option<(u32, u32)> {
crate::window_client_size(window_id).or_else(|| self.state.window_size(window_id))
}
fn queue_resize_trigger(&self, window_id: ObjectId, width: u32, height: u32) -> bool {
crate::queue_resize_trigger(window_id, width, height)
}
fn supports_surfaces(&self) -> bool {
true
}
fn mount_surface(&self, _parent: ObjectId, id: ObjectId, rect: crate::core::Rect) -> bool {
self.state.mount_surface_record(id, rect)
}
fn resize_surface(&self, id: ObjectId, rect: crate::core::Rect) -> bool {
self.state.resize_surface_record(id, rect)
}
fn unmount_surface(&self, id: ObjectId) -> bool {
self.state.unmount_surface_record(id)
}
fn invalidate_surface(&self, id: ObjectId) -> bool {
self.state.record_repaint_request(id)
}
fn take_pending_repaint(&self) -> Option<ObjectId> {
self.state.take_pending_repaint()
}
fn set_clipboard_text(&self, text: &str) -> bool {
let stored = self.state.set_clipboard_text(text);
let published = self
.clipboard
.set_contents(crate::platform::clipboard::ClipboardContent::Text(text.to_string()));
stored && published
}
fn get_clipboard_text(&self) -> String {
self.clipboard.text().unwrap_or_else(|| self.state.clipboard_text())
}
fn clipboard_backend(&self) -> Option<&dyn crate::platform::clipboard::RichClipboardBackend> {
Some(&self.clipboard)
}
fn clipboard_generation(&self) -> u64 {
self.clipboard.generation()
}
fn ime_bridge(&self) -> Option<&dyn crate::platform::ime::ImeBridge> {
Some(&self.ime)
}
fn pre_present_notify(&self, window: ObjectId) {
#[cfg(all(feature = "wayland-native", unix_desktop))]
{
let Ok(mut guard) = self.native_session.try_lock() else { return };
let Some(session) = guard.as_mut() else { return };
let Some(surface) = session.state.surfaces.get(&window).cloned() else { return };
let qh = session.event_queue.handle();
let _callback = surface.frame(&qh, ());
if let Err(e) = session.event_queue.flush() {
log::warn!("[wayland] frame request flush failed: {e}");
}
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl WaylandPlatform {
#[cfg(all(feature = "wayland-native", not(alloc_frugal), unix_desktop))]
fn run_native_event_loop(&self) {
use std::os::fd::AsRawFd;
const IDLE_TIMEOUT_MS: i32 = 50;
log::info!("[wayland] Entering native fd-based event loop");
loop {
if !self.runtime.running.load(std::sync::atomic::Ordering::SeqCst) {
break;
}
let mut session = match self.native_session.lock().unwrap().take() {
Some(session) => session,
None => {
crate::drive_frame(FRAME_INTERVAL_MS);
std::thread::sleep(std::time::Duration::from_millis(FRAME_INTERVAL_MS as u64));
continue;
}
};
DISPATCHING_SESSION.with(|slot| {
*slot.borrow_mut() = Some(&mut session as *mut WaylandSession);
});
if let Err(e) = session.event_queue.flush() {
log::warn!("[wayland] flush failed: {e}");
}
match session.event_queue.prepare_read() {
Some(read_guard) => {
let fd = read_guard.connection_fd().as_raw_fd();
let mut pollfd = libc::pollfd { fd, events: libc::POLLIN, revents: 0 };
let rc = unsafe { libc::poll(&mut pollfd, 1, IDLE_TIMEOUT_MS) };
if rc > 0 {
if let Err(e) = read_guard.read() {
log::warn!("[wayland] read failed: {e}");
}
if let Err(e) = session.event_queue.dispatch_pending(&mut session.state) {
log::warn!("[wayland] dispatch failed: {e}");
}
} else if rc < 0 {
let err = std::io::Error::last_os_error();
log::warn!("[wayland] poll failed: {err}");
std::thread::sleep(std::time::Duration::from_millis(16));
}
}
None => {
let _ = session.event_queue.dispatch_pending(&mut session.state);
}
}
DISPATCHING_SESSION.with(|slot| *slot.borrow_mut() = None);
*self.native_session.lock().unwrap() = Some(session);
crate::drive_frame(FRAME_INTERVAL_MS);
}
log::info!("[wayland] Native event loop exited");
}
#[cfg(not(alloc_frugal))]
pub(crate) fn dispatch_native_events(&self) {
let Some(mut session) = self.native_session.lock().unwrap().take() else {
crate::drive_frame(FRAME_INTERVAL_MS);
return;
};
DISPATCHING_SESSION.with(|slot| {
*slot.borrow_mut() = Some(&mut session as *mut WaylandSession);
});
let _ = session.event_queue.dispatch_pending(&mut session.state);
DISPATCHING_SESSION.with(|slot| *slot.borrow_mut() = None);
*self.native_session.lock().unwrap() = Some(session);
crate::drive_frame(FRAME_INTERVAL_MS);
}
fn try_create_native_window(
&self,
title: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Option<ObjectId> {
fn connect_wayland(
clipboard: super::clipboard::WaylandClipboard,
ime: super::ime::WaylandIme,
) -> Option<WaylandSession> {
let conn = wl_client::Connection::connect_to_env()
.inspect_err(|e| {
log::error!("[wayland] Failed to connect to Wayland display: {}", e)
})
.ok()?;
let display = conn.display();
let mut event_queue = conn.new_event_queue();
let qh = event_queue.handle();
let _registry = display.get_registry(&qh, ());
clipboard.clear_channel();
ime.clear_channel();
let mut state = WaylandSessionState {
compositor: None,
xdg_wm_base: None,
seat: None,
data_device_manager: None,
data_device: None,
keyboard: None,
pointer: None,
clipboard,
text_input_manager: None,
text_input: None,
ime,
surfaces: std::collections::HashMap::new(),
frame_done_generation: 0,
wl_output: None,
dpi_scale: 1.0,
output_x: 0,
output_y: 0,
output_width: 0,
output_height: 0,
output_refresh_mhz: None,
output_ready: false,
output_generation: 0,
};
event_queue
.roundtrip(&mut state)
.inspect_err(|e| log::error!("[wayland] Registry roundtrip failed: {}", e))
.ok()?;
if state.compositor.is_none() {
log::error!("[wayland] wl_compositor global not available");
return None;
}
if state.xdg_wm_base.is_none() {
log::error!("[wayland] xdg_wm_base global not available");
return None;
}
log::info!("[wayland] Connected to display; compositor and xdg_wm_base bound.");
Some(WaylandSession { conn, event_queue, state })
}
let reentrant = DISPATCHING_SESSION.with(|slot| *slot.borrow());
if let Some(ptr) = reentrant {
let session = unsafe { &mut *ptr };
return self.create_toplevel(session, title, x, y, width, height);
}
let mut guard = self.native_session.lock().unwrap();
if guard.is_none() {
*guard = connect_wayland(self.clipboard.clone(), self.ime.clone());
}
let session = guard.as_mut()?;
self.create_toplevel(session, title, x, y, width, height)
}
fn create_toplevel(
&self,
session: &mut WaylandSession,
title: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Option<ObjectId> {
let compositor = session.state.compositor.as_ref()?;
let xdg_wm_base = session.state.xdg_wm_base.as_ref()?;
let qh = session.event_queue.handle();
let surface = compositor.create_surface(&qh, ());
let xdg_surface = xdg_wm_base.get_xdg_surface(&surface, &qh, ());
let toplevel = xdg_surface.get_toplevel(&qh, ());
toplevel.set_title(title.to_string());
toplevel.set_app_id("rust_widgets".to_string());
if width > 0 && height > 0 {
toplevel.set_min_size(width as i32, height as i32);
}
surface.commit();
session.event_queue.roundtrip(&mut session.state).ok()?;
log::info!("[wayland] Created xdg_toplevel '{}' ({}x{})", title, width, height);
let id = self.insert_widget(WaylandHandleKind::Window, title, x, y, width, height);
session.state.surfaces.insert(id, surface.clone());
log::info!("[wayland] Window {} registered with state backend", id);
record_configured_window(&toplevel, id);
Some(id)
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
thread_local! {
static CONFIGURED_WINDOWS: core::cell::RefCell<crate::compat::HashMap<usize, ObjectId>> =
core::cell::RefCell::new(crate::compat::HashMap::new());
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
thread_local! {
static DISPATCHING_SESSION: core::cell::RefCell<Option<*mut WaylandSession>> =
const { core::cell::RefCell::new(None) };
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
fn record_configured_window(proxy: &impl wl_client::Proxy, id: ObjectId) {
let key = proxy.id().protocol_id() as usize;
CONFIGURED_WINDOWS.with(|map| {
map.borrow_mut().insert(key, id);
});
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
pub(crate) fn configured_window_id(proxy: &impl wl_client::Proxy) -> Option<ObjectId> {
let key = proxy.id().protocol_id() as usize;
CONFIGURED_WINDOWS.with(|map| map.borrow().get(&key).copied())
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
pub(crate) struct WaylandSessionState {
pub(crate) compositor: Option<wl_client::protocol::wl_compositor::WlCompositor>,
pub(crate) xdg_wm_base: Option<wl_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase>,
pub(crate) seat: Option<wl_client::protocol::wl_seat::WlSeat>,
pub(crate) data_device_manager:
Option<wl_client::protocol::wl_data_device_manager::WlDataDeviceManager>,
pub(crate) data_device: Option<wl_client::protocol::wl_data_device::WlDataDevice>,
#[allow(dead_code)]
pub(crate) keyboard: Option<wl_client::protocol::wl_keyboard::WlKeyboard>,
#[allow(dead_code)]
pub(crate) pointer: Option<wl_client::protocol::wl_pointer::WlPointer>,
pub(crate) clipboard: super::clipboard::WaylandClipboard,
pub(crate) text_input_manager: Option<
wl_protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3,
>,
pub(crate) text_input:
Option<wl_protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3>,
pub(crate) ime: super::ime::WaylandIme,
pub(crate) surfaces: std::collections::HashMap<u64, wl_client::protocol::wl_surface::WlSurface>,
pub(crate) frame_done_generation: u64,
pub(crate) wl_output: Option<wl_client::protocol::wl_output::WlOutput>,
pub(crate) dpi_scale: f32,
pub(crate) output_x: i32,
pub(crate) output_y: i32,
pub(crate) output_width: u32,
pub(crate) output_height: u32,
pub(crate) output_refresh_mhz: Option<u32>,
pub(crate) output_ready: bool,
pub(crate) output_generation: u64,
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
pub(crate) struct WaylandSession {
#[allow(dead_code)]
pub(crate) conn: wl_client::Connection,
pub(crate) event_queue: wl_client::EventQueue<WaylandSessionState>,
pub(crate) state: WaylandSessionState,
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl WaylandSessionState {
fn ensure_data_device(
&mut self,
qh: &wl_client::QueueHandle<WaylandSessionState>,
conn: &wl_client::Connection,
) {
if self.data_device.is_some() {
return;
}
let (Some(manager), Some(seat)) = (self.data_device_manager.as_ref(), self.seat.as_ref())
else {
return;
};
let device = manager.get_data_device(seat, qh, ());
self.clipboard.attach_channel(manager.clone(), device.clone(), qh.clone(), conn.clone());
self.data_device = Some(device);
log::info!("[wayland] clipboard: wl_data_device created");
}
fn bind_seat_input(
&mut self,
qh: &wl_client::QueueHandle<WaylandSessionState>,
capabilities: wl_client::WEnum<wl_client::protocol::wl_seat::Capability>,
) {
let wl_client::WEnum::Value(capabilities) = capabilities else {
return;
};
let Some(seat) = self.seat.as_ref() else { return };
if capabilities.contains(wl_client::protocol::wl_seat::Capability::Keyboard)
&& self.keyboard.is_none()
{
self.keyboard = Some(seat.get_keyboard(qh, ()));
}
if capabilities.contains(wl_client::protocol::wl_seat::Capability::Pointer)
&& self.pointer.is_none()
{
self.pointer = Some(seat.get_pointer(qh, ()));
}
}
fn ensure_text_input(
&mut self,
qh: &wl_client::QueueHandle<WaylandSessionState>,
conn: &wl_client::Connection,
) {
if self.text_input.is_some() {
return;
}
let (Some(manager), Some(seat)) = (self.text_input_manager.as_ref(), self.seat.as_ref())
else {
return;
};
let text_input = super::ime::create_text_input(&self.ime, manager, seat, qh, conn);
self.text_input = Some(text_input);
log::info!("[wayland] IME: text-input-v3 object created");
}
}
#[cfg(all(feature = "wayland-native", not(alloc_frugal), unix_desktop))]
pub fn create_event_loop_pump() -> Option<Box<dyn Fn() + Send + Sync>> {
let platform = crate::platform::runtime::get_platform();
let wayland = platform.as_any().downcast_ref::<WaylandPlatform>()?;
Some(Box::new(move || {
wayland.dispatch_native_events();
}))
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_client::protocol::wl_registry::WlRegistry, ()> for WaylandSessionState {
fn event(
state: &mut Self,
registry: &wl_client::protocol::wl_registry::WlRegistry,
event: wl_client::protocol::wl_registry::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
use wl_client::protocol::wl_registry::Event;
if let Event::Global { name, interface, version } = event {
log::debug!(
"[wayland] Registry global: interface='{}', version={}",
interface,
version
);
match interface.as_str() {
"wl_compositor" => {
let comp = registry
.bind::<wl_client::protocol::wl_compositor::WlCompositor, _, _>(
name,
version.min(4),
_qh,
(),
);
state.compositor = Some(comp);
log::info!("[wayland] Bound wl_compositor (v{})", version);
}
"xdg_wm_base" => {
let xdg = registry
.bind::<wl_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase, _, _>(
name,
version.min(6),
_qh,
(),
);
state.xdg_wm_base = Some(xdg);
log::info!("[wayland] Bound xdg_wm_base (v{})", version);
}
"wl_output" => {
let output = registry.bind::<wl_client::protocol::wl_output::WlOutput, _, _>(
name,
version.min(4),
_qh,
(),
);
state.wl_output = Some(output);
log::info!("[wayland] Bound wl_output (v{})", version);
}
"wl_seat" => {
let seat = registry.bind::<wl_client::protocol::wl_seat::WlSeat, _, _>(
name,
version.min(7),
_qh,
(),
);
state.seat = Some(seat);
state.ensure_data_device(_qh, _conn);
state.ensure_text_input(_qh, _conn);
log::info!("[wayland] Bound wl_seat (v{})", version);
}
"wl_data_device_manager" => {
let manager = registry
.bind::<wl_client::protocol::wl_data_device_manager::WlDataDeviceManager, _, _>(
name,
version.min(3),
_qh,
(),
);
state.data_device_manager = Some(manager);
state.ensure_data_device(_qh, _conn);
log::info!("[wayland] Bound wl_data_device_manager (v{})", version);
}
"zwp_text_input_manager_v3" => {
let manager = registry.bind::<
wl_protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3,
_,
_,
>(name, version.min(1), _qh, ());
state.text_input_manager = Some(manager);
state.ensure_text_input(_qh, _conn);
log::info!("[wayland] Bound zwp_text_input_manager_v3 (v{})", version);
}
_ => { }
}
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_client::protocol::wl_compositor::WlCompositor, ()>
for WaylandSessionState
{
fn event(
_state: &mut Self,
_proxy: &wl_client::protocol::wl_compositor::WlCompositor,
_event: wl_client::protocol::wl_compositor::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_client::protocol::wl_output::WlOutput, ()> for WaylandSessionState {
fn event(
state: &mut Self,
_proxy: &wl_client::protocol::wl_output::WlOutput,
event: wl_client::protocol::wl_output::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
use wl_client::protocol::wl_output::Event;
match event {
Event::Scale { factor } => {
state.dpi_scale = factor as f32;
state.output_generation = state.output_generation.wrapping_add(1);
log::info!("[wayland] wl_output scale factor: {} (DPI: {})", factor, factor * 96);
}
Event::Geometry { x, y, .. } => {
state.output_x = x;
state.output_y = y;
state.output_generation = state.output_generation.wrapping_add(1);
}
Event::Mode { flags, width, height, refresh } => {
if matches!(
flags,
wayland_client::WEnum::Value(
wayland_client::protocol::wl_output::Mode::Current
)
) {
state.output_width = width.max(0) as u32;
state.output_height = height.max(0) as u32;
state.output_refresh_mhz = (refresh > 0).then_some(refresh as u32);
state.output_ready = true;
state.output_generation = state.output_generation.wrapping_add(1);
}
}
Event::Done => {
log::trace!("[wayland] wl_output done");
}
Event::Name { .. } | Event::Description { .. } => {
log::trace!("[wayland] wl_output event: {:?}", event);
}
_ => {
log::trace!("[wayland] wl_output unhandled event: {:?}", event);
}
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_client::protocol::wl_surface::WlSurface, ()> for WaylandSessionState {
fn event(
_state: &mut Self,
_proxy: &wl_client::protocol::wl_surface::WlSurface,
_event: wl_client::protocol::wl_surface::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
log::trace!("[wayland] wl_surface event: {:?}", _event);
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_client::protocol::wl_seat::WlSeat, ()> for WaylandSessionState {
fn event(
state: &mut Self,
_proxy: &wl_client::protocol::wl_seat::WlSeat,
event: wl_client::protocol::wl_seat::Event,
_data: &(),
_conn: &wl_client::Connection,
qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
use wl_client::protocol::wl_seat::Event;
match event {
Event::Capabilities { capabilities } => state.bind_seat_input(qh, capabilities),
Event::Name { name } => log::debug!("[wayland] seat name: {name}"),
_ => log::trace!("[wayland] wl_seat event: {:?}", event),
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_client::protocol::wl_keyboard::WlKeyboard, ()> for WaylandSessionState {
fn event(
state: &mut Self,
_proxy: &wl_client::protocol::wl_keyboard::WlKeyboard,
event: wl_client::protocol::wl_keyboard::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
use wl_client::protocol::wl_keyboard::Event;
match event {
Event::Key { serial, .. } | Event::Enter { serial, .. } => {
state.clipboard.note_serial(serial);
}
_ => log::trace!("[wayland] wl_keyboard event: {:?}", event),
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_client::protocol::wl_pointer::WlPointer, ()> for WaylandSessionState {
fn event(
state: &mut Self,
_proxy: &wl_client::protocol::wl_pointer::WlPointer,
event: wl_client::protocol::wl_pointer::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
use wl_client::protocol::wl_pointer::Event;
match event {
Event::Enter { serial, .. } | Event::Button { serial, .. } => {
state.clipboard.note_serial(serial);
}
_ => log::trace!("[wayland] wl_pointer event: {:?}", event),
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_client::protocol::wl_data_device_manager::WlDataDeviceManager, ()>
for WaylandSessionState
{
fn event(
_state: &mut Self,
_proxy: &wl_client::protocol::wl_data_device_manager::WlDataDeviceManager,
_event: wl_client::protocol::wl_data_device_manager::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_client::protocol::wl_data_device::WlDataDevice, ()>
for WaylandSessionState
{
fn event(
state: &mut Self,
_proxy: &wl_client::protocol::wl_data_device::WlDataDevice,
event: wl_client::protocol::wl_data_device::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
use wl_client::protocol::wl_data_device::Event;
match event {
Event::DataOffer { id } => state.clipboard.note_offer(id),
Event::Selection { id } => state.clipboard.note_selection(id),
Event::Enter { .. } | Event::Leave | Event::Motion { .. } | Event::Drop => {
log::trace!("[wayland] wl_data_device dnd event: {:?}", event)
}
_ => log::trace!("[wayland] wl_data_device event: {:?}", event),
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_client::protocol::wl_data_offer::WlDataOffer, ()>
for WaylandSessionState
{
fn event(
state: &mut Self,
_proxy: &wl_client::protocol::wl_data_offer::WlDataOffer,
event: wl_client::protocol::wl_data_offer::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
use wl_client::protocol::wl_data_offer::Event;
match event {
Event::Offer { mime_type } => state.clipboard.note_mime(&mime_type),
_ => log::trace!("[wayland] wl_data_offer event: {:?}", event),
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_client::protocol::wl_data_source::WlDataSource, ()>
for WaylandSessionState
{
fn event(
state: &mut Self,
_proxy: &wl_client::protocol::wl_data_source::WlDataSource,
event: wl_client::protocol::wl_data_source::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
use wl_client::protocol::wl_data_source::Event;
match event {
Event::Send { mime_type, fd } => state.clipboard.serve(&mime_type, fd),
Event::Cancelled => {
log::debug!("[wayland] clipboard: selection was replaced");
}
Event::Target { mime_type } => {
log::trace!("[wayland] clipboard: target {mime_type:?}");
}
_ => log::trace!("[wayland] wl_data_source event: {:?}", event),
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl
wl_client::Dispatch<
wl_protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3,
(),
> for WaylandSessionState
{
fn event(
_state: &mut Self,
_proxy: &wl_protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3,
_event: wl_protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl
wl_client::Dispatch<
wl_protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3,
(),
> for WaylandSessionState
{
fn event(
state: &mut Self,
_proxy: &wl_protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3,
event: wl_protocols::wp::text_input::zv3::client::zwp_text_input_v3::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
use wl_protocols::wp::text_input::zv3::client::zwp_text_input_v3::Event;
match event {
Event::Enter { .. } => state.ime.set_active(true),
Event::Leave { .. } => state.ime.set_active(false),
Event::PreeditString { text, cursor_begin, cursor_end } => {
if let Some(text) = text {
state.ime.preedit(&text, cursor_begin, cursor_end);
}
}
Event::CommitString { text } => {
if let Some(text) = text {
state.ime.commit(&text);
}
}
Event::DeleteSurroundingText { .. } | Event::Done { .. } => {
log::trace!("[wayland] text-input event: {:?}", event);
}
_ => log::trace!("[wayland] text-input unhandled event: {:?}", event),
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_client::protocol::wl_callback::WlCallback, ()> for WaylandSessionState {
fn event(
state: &mut Self,
_proxy: &wl_client::protocol::wl_callback::WlCallback,
event: wl_client::protocol::wl_callback::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
use wl_client::protocol::wl_callback::Event;
if let Event::Done { callback_data } = event {
state.frame_done_generation = state.frame_done_generation.wrapping_add(1);
log::trace!("[wayland] frame presented (callback_data={callback_data})");
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase, ()>
for WaylandSessionState
{
fn event(
_state: &mut Self,
proxy: &wl_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase,
event: <wl_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase as wl_client::Proxy>::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
use wl_protocols::xdg::shell::client::xdg_wm_base::Event;
if let Event::Ping { serial } = event {
proxy.pong(serial);
log::trace!("[wayland] xdg_wm_base ping/pong (serial={})", serial);
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_protocols::xdg::shell::client::xdg_surface::XdgSurface, ()>
for WaylandSessionState
{
fn event(
_state: &mut Self,
proxy: &wl_protocols::xdg::shell::client::xdg_surface::XdgSurface,
event: <wl_protocols::xdg::shell::client::xdg_surface::XdgSurface as wl_client::Proxy>::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
use wl_protocols::xdg::shell::client::xdg_surface::Event;
if let Event::Configure { serial } = event {
proxy.ack_configure(serial);
log::trace!("[wayland] xdg_surface configure ack (serial={})", serial);
}
}
}
#[cfg(all(feature = "wayland-native", unix_desktop))]
impl wl_client::Dispatch<wl_protocols::xdg::shell::client::xdg_toplevel::XdgToplevel, ()>
for WaylandSessionState
{
fn event(
_state: &mut Self,
proxy: &wl_protocols::xdg::shell::client::xdg_toplevel::XdgToplevel,
event: <wl_protocols::xdg::shell::client::xdg_toplevel::XdgToplevel as wl_client::Proxy>::Event,
_data: &(),
_conn: &wl_client::Connection,
_qh: &wl_client::QueueHandle<WaylandSessionState>,
) {
use wl_protocols::xdg::shell::client::xdg_toplevel::Event;
match event {
Event::Configure { width, height, states } => {
log::trace!(
"[wayland] xdg_toplevel configure: {}x{}, states={:?}",
width,
height,
states
);
if width > 0 && height > 0 {
if let Some(id) = super::platform_impl::configured_window_id(proxy) {
crate::queue_resize_trigger(id, width as u32, height as u32);
}
}
}
Event::Close => {
if let Some(window_id) = super::platform_impl::configured_window_id(proxy) {
if !crate::close_widget(window_id) {
log::error!(
"[wayland] close requested for window widget {window_id}, which is not \
a live widget; no close callback was run"
);
}
} else {
log::warn!(
"[wayland] close requested for an unregistered xdg_toplevel; no window \
callback was run"
);
}
}
Event::ConfigureBounds { .. } | Event::WmCapabilities { .. } => {
log::trace!("[wayland] xdg_toplevel event: {:?}", event);
}
_ => {
log::trace!("[wayland] xdg_toplevel unhandled event: {:?}", event);
}
}
}
}