winit/src/platform_impl/linux/mod.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

268 lines
8.4 KiB
Rust
Raw Normal View History

#![cfg(free_unix)]
#[cfg(all(not(x11_platform), not(wayland_platform)))]
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
use std::env;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::time::Duration;
#[cfg(x11_platform)]
use dpi::Size;
use winit_core::application::ApplicationHandler;
use winit_core::error::{EventLoopError, NotSupportedError};
use winit_core::event_loop::pump_events::PumpStatus;
use winit_core::event_loop::ActiveEventLoop;
use winit_core::window::ActivationToken;
pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey};
#[cfg(x11_platform)]
use crate::platform::x11::WindowType as XWindowType;
pub(crate) mod common;
#[cfg(wayland_platform)]
pub(crate) mod wayland;
#[cfg(x11_platform)]
pub(crate) mod x11;
2017-03-03 20:56:27 +01:00
2022-06-10 13:43:33 +03:00
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) enum Backend {
#[cfg(x11_platform)]
X,
#[cfg(wayland_platform)]
Wayland,
}
2022-06-10 13:43:33 +03:00
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) forced_backend: Option<Backend>,
pub(crate) any_thread: bool,
}
2022-06-10 13:43:33 +03:00
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApplicationName {
pub general: String,
pub instance: String,
}
impl ApplicationName {
pub fn new(general: String, instance: String) -> Self {
Self { general, instance }
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PlatformSpecificWindowAttributes {
pub name: Option<ApplicationName>,
pub activation_token: Option<ActivationToken>,
#[cfg(x11_platform)]
pub x11: X11WindowAttributes,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg(x11_platform)]
pub struct X11WindowAttributes {
pub visual_id: Option<x11rb::protocol::xproto::Visualid>,
pub screen_id: Option<i32>,
pub base_size: Option<Size>,
pub override_redirect: bool,
pub x11_window_types: Vec<XWindowType>,
/// The parent window to embed this window into.
pub embed_window: Option<x11rb::protocol::xproto::Window>,
}
#[cfg_attr(not(x11_platform), allow(clippy::derivable_impls))]
impl Default for PlatformSpecificWindowAttributes {
fn default() -> Self {
Self {
name: None,
activation_token: None,
#[cfg(x11_platform)]
x11: X11WindowAttributes {
visual_id: None,
screen_id: None,
base_size: None,
override_redirect: false,
x11_window_types: vec![XWindowType::Normal],
embed_window: None,
},
}
}
}
/// `x11_or_wayland!(match expr; Enum(foo) => foo.something())`
/// expands to the equivalent of
/// ```ignore
/// match self {
/// Enum::X(foo) => foo.something(),
/// Enum::Wayland(foo) => foo.something(),
/// }
/// ```
/// The result can be converted to another enum by adding `; as AnotherEnum`
macro_rules! x11_or_wayland {
(match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr; as $enum2:ident ) => {
match $what {
#[cfg(x11_platform)]
$enum::X($($c1)*) => $enum2::X($x),
#[cfg(wayland_platform)]
$enum::Wayland($($c1)*) => $enum2::Wayland($x),
}
};
(match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr) => {
match $what {
#[cfg(x11_platform)]
$enum::X($($c1)*) => $x,
#[cfg(wayland_platform)]
$enum::Wayland($($c1)*) => $x,
}
};
}
#[derive(Debug)]
2025-05-17 03:56:51 +02:00
#[allow(clippy::large_enum_variant)]
pub enum EventLoop {
#[cfg(wayland_platform)]
Wayland(Box<wayland::EventLoop>),
#[cfg(x11_platform)]
X(x11::EventLoop),
}
impl EventLoop {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
if !attributes.any_thread && !is_main_thread() {
panic!(
"Initializing the event loop outside of the main thread is a significant \
cross-platform compatibility hazard. If you absolutely need to create an \
EventLoop on a different thread, you can use the \
`EventLoopBuilderExtX11::with_any_thread` or \
`EventLoopBuilderExtWayland::with_any_thread` functions."
);
}
// NOTE: Wayland first because of X11 could be present under Wayland as well. Empty
// variables are also treated as not set.
let backend = match (
attributes.forced_backend,
env::var("WAYLAND_DISPLAY")
.ok()
.filter(|var| !var.is_empty())
.or_else(|| env::var("WAYLAND_SOCKET").ok())
.filter(|var| !var.is_empty())
.is_some(),
env::var("DISPLAY").map(|var| !var.is_empty()).unwrap_or(false),
) {
// User is forcing a backend.
(Some(backend), ..) => backend,
// Wayland is present.
#[cfg(wayland_platform)]
(None, true, _) => Backend::Wayland,
// X11 is present.
#[cfg(x11_platform)]
(None, _, true) => Backend::X,
// No backend is present.
(_, wayland_display, x11_display) => {
let msg = if wayland_display && !cfg!(wayland_platform) {
"DISPLAY is not set; note: enable the `winit/wayland` feature to support \
Wayland"
} else if x11_display && !cfg!(x11_platform) {
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET is set; note: enable the \
`winit/x11` feature to support X11"
} else {
"neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set."
};
return Err(NotSupportedError::new(msg).into());
},
};
// Create the display based on the backend.
match backend {
#[cfg(wayland_platform)]
2025-02-20 20:21:07 +03:00
Backend::Wayland => EventLoop::new_wayland_any_thread(),
#[cfg(x11_platform)]
2025-02-20 20:21:07 +03:00
Backend::X => EventLoop::new_x11_any_thread(),
}
}
#[cfg(wayland_platform)]
fn new_wayland_any_thread() -> Result<EventLoop, EventLoopError> {
wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
}
#[cfg(x11_platform)]
fn new_x11_any_thread() -> Result<EventLoop, EventLoopError> {
x11::EventLoop::new().map(EventLoop::X)
}
#[inline]
pub fn is_wayland(&self) -> bool {
match *self {
#[cfg(wayland_platform)]
EventLoop::Wayland(_) => true,
#[cfg(x11_platform)]
_ => false,
}
}
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app))
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: A,
) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app_on_demand(app))
Linux: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand Wayland: I found the calloop abstraction a little awkward to work with while I was trying to understand why there was surprising workaround code in the wayland backend for manually dispatching pending events. Investigating this further it looks like there may currently be several issues with the calloop WaylandSource (with how prepare_read is used and with (not) flushing writes before polling) Considering the current minimal needs for polling in all winit backends I do personally tend to think it would be simpler to just own the responsibility for polling more directly, so the logic for wayland-client `prepare_read` wouldn't be in a separate crate (and in this current situation would also be easier to fix) I've tried to maintain the status quo with calloop + workarounds. X11: I found that the recent changes (4ac2006cbc5a) to port the X11 backend from mio to calloop lost the ability to check for pending events before needing to poll/dispatch. (The `has_pending` state being queried before dispatching() was based on state that was filled in during dispatching) As part of the rebase this re-introduces the PeekableReceiver and WakeSender which are small utilities on top of `std::sync::mpsc::channel()`. This adds a calloop `PingSource` so we can use a `Ping` as a generic event loop waker. For taking into account false positive wake ups the X11 source now tracks when the file descriptor is readable so after we poll via calloop we can then specifically check if there are new X11 events or pending redraw/user events when deciding whether to skip the event loop iteration.
2023-06-18 12:40:03 +01:00
}
pub fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: A,
) -> PumpStatus {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_app_events(timeout, app))
Linux: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand Wayland: I found the calloop abstraction a little awkward to work with while I was trying to understand why there was surprising workaround code in the wayland backend for manually dispatching pending events. Investigating this further it looks like there may currently be several issues with the calloop WaylandSource (with how prepare_read is used and with (not) flushing writes before polling) Considering the current minimal needs for polling in all winit backends I do personally tend to think it would be simpler to just own the responsibility for polling more directly, so the logic for wayland-client `prepare_read` wouldn't be in a separate crate (and in this current situation would also be easier to fix) I've tried to maintain the status quo with calloop + workarounds. X11: I found that the recent changes (4ac2006cbc5a) to port the X11 backend from mio to calloop lost the ability to check for pending events before needing to poll/dispatch. (The `has_pending` state being queried before dispatching() was based on state that was filled in during dispatching) As part of the rebase this re-introduces the PeekableReceiver and WakeSender which are small utilities on top of `std::sync::mpsc::channel()`. This adds a calloop `PingSource` so we can use a `Ping` as a generic event loop waker. For taking into account false positive wake ups the X11 source now tracks when the file descriptor is readable so after we poll via calloop we can then specifically check if there are new X11 events or pending redraw/user events when deciding whether to skip the event loop iteration.
2023-06-18 12:40:03 +01:00
}
pub fn window_target(&self) -> &dyn ActiveEventLoop {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target())
}
}
impl AsFd for EventLoop {
fn as_fd(&self) -> BorrowedFd<'_> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd())
}
}
impl AsRawFd for EventLoop {
fn as_raw_fd(&self) -> RawFd {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd())
}
}
Linux: Implement EventLoopExtPumpEvents and EventLoopExtRunOnDemand Wayland: I found the calloop abstraction a little awkward to work with while I was trying to understand why there was surprising workaround code in the wayland backend for manually dispatching pending events. Investigating this further it looks like there may currently be several issues with the calloop WaylandSource (with how prepare_read is used and with (not) flushing writes before polling) Considering the current minimal needs for polling in all winit backends I do personally tend to think it would be simpler to just own the responsibility for polling more directly, so the logic for wayland-client `prepare_read` wouldn't be in a separate crate (and in this current situation would also be easier to fix) I've tried to maintain the status quo with calloop + workarounds. X11: I found that the recent changes (4ac2006cbc5a) to port the X11 backend from mio to calloop lost the ability to check for pending events before needing to poll/dispatch. (The `has_pending` state being queried before dispatching() was based on state that was filled in during dispatching) As part of the rebase this re-introduces the PeekableReceiver and WakeSender which are small utilities on top of `std::sync::mpsc::channel()`. This adds a calloop `PingSource` so we can use a `Ping` as a generic event loop waker. For taking into account false positive wake ups the X11 source now tracks when the file descriptor is readable so after we poll via calloop we can then specifically check if there are new X11 events or pending redraw/user events when deciding whether to skip the event loop iteration.
2023-06-18 12:40:03 +01:00
/// Returns the minimum `Option<Duration>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)
fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}
#[cfg(target_os = "linux")]
fn is_main_thread() -> bool {
rustix::thread::gettid() == rustix::process::getpid()
}
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
fn is_main_thread() -> bool {
use libc::pthread_main_np;
unsafe { pthread_main_np() == 1 }
}
#[cfg(target_os = "netbsd")]
fn is_main_thread() -> bool {
std::thread::current().name() == Some("main")
}