Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion winit-wayland/src/event_loop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ use super::{WindowId, logical_to_physical_rounded};

type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;

static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);

/// Release an explicit-connection creation reservation unless initialization completes.
struct CreationReservation;

impl Drop for CreationReservation {
fn drop(&mut self) {
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
}
}

#[derive(Debug)]
pub(crate) enum Event {
WindowEvent { window_id: WindowId, event: WindowEvent },
Expand Down Expand Up @@ -91,14 +102,33 @@ pub struct EventLoop {

impl EventLoop {
pub fn new() -> Result<EventLoop, EventLoopError> {
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
// For better cross-platformness.
return Err(EventLoopError::RecreationAttempt);
}

let connection = Connection::connect_to_env().map_err(|err| os_error!(err))?;

Self::from_connection(connection)
}

/// Create an event loop using an owned connection without consulting the environment.
///
/// A fresh event queue is created on the supplied connection. Other queues remain
/// the caller's responsibility. Failed initialization releases the creation reservation
/// so another explicit connection can be tried. Successful creation permanently consumes
/// the one-event-loop allowance, including after the event loop is dropped.
pub fn new_with_connection(connection: Connection) -> Result<EventLoop, EventLoopError> {
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
return Err(EventLoopError::RecreationAttempt);
}
let reservation = CreationReservation;
let event_loop = Self::from_connection(connection)?;
mem::forget(reservation);
Ok(event_loop)
}

fn from_connection(connection: Connection) -> Result<EventLoop, EventLoopError> {
let (globals, mut event_queue) =
globals::registry_queue_init(&connection).map_err(|err| os_error!(err))?;
let queue_handle = event_queue.handle();
Expand Down
16 changes: 16 additions & 0 deletions winit-wayland/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ pub use self::dnd::{DataOffer, DragSource, MimeData, MimeType};
pub use self::event_loop::{ActiveEventLoop, EventLoop};
pub use self::popup::Popup;
pub use self::window::Window;
/// An owned connection to a Wayland compositor.
pub use wayland_client::Connection as WaylandConnection;

/// Additional methods on [`ActiveEventLoop`] that are specific to Wayland.
pub trait ActiveEventLoopExtWayland {
Expand All @@ -76,6 +78,20 @@ pub trait EventLoopBuilderExtWayland {
/// Force using Wayland.
fn with_wayland(&mut self) -> &mut Self;

/// Use an owned Wayland connection instead of connecting through the environment.
///
/// This also selects the Wayland backend. The connection is retained by the builder;
/// the event loop holds its own clone and creates its own event queue. Existing queues
/// on this connection remain owned by their callers.
///
/// For an explicit socket, construct a [`WaylandConnection`] with
/// [`WaylandConnection::from_socket`] and a connected `std::os::unix::net::UnixStream`.
/// Neither `WAYLAND_DISPLAY`, `WAYLAND_SOCKET` nor `XDG_RUNTIME_DIR` is consulted.
/// Main-thread requirements are unchanged. Failed initialization with an explicit
/// connection permits retry; successful creation still forbids another event loop,
/// even after it is dropped. A later backend-selection method can override Wayland.
fn with_wayland_connection(&mut self, connection: WaylandConnection) -> &mut Self;

/// Whether to allow the event loop to be created off of the main thread.
///
/// By default, the window is only allowed to be created on the main
Expand Down
9 changes: 9 additions & 0 deletions winit/src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,15 @@ impl winit_wayland::EventLoopBuilderExtWayland for EventLoopBuilder {
self
}

#[inline]
fn with_wayland_connection(
&mut self,
connection: winit_wayland::WaylandConnection,
) -> &mut Self {
self.platform_specific.wayland_connection = Some(connection);
self.with_wayland()
}

#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
Expand Down
35 changes: 31 additions & 4 deletions winit/src/platform_impl/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,24 @@ pub(crate) enum Backend {
Wayland,
}

#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) forced_backend: Option<Backend>,
pub(crate) any_thread: bool,
#[cfg(wayland_platform)]
pub(crate) wayland_connection: Option<wayland::WaylandConnection>,
}

impl std::hash::Hash for PlatformSpecificEventLoopAttributes {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.forced_backend.hash(state);
self.any_thread.hash(state);
#[cfg(wayland_platform)]
self.wayland_connection
.as_ref()
.map(|connection| connection.backend().display_id())
.hash(state);
}
}

/// `x11_or_wayland!(match expr; Enum(foo) => foo.something())`
Expand Down Expand Up @@ -86,6 +100,13 @@ impl EventLoop {
);
}

#[cfg(wayland_platform)]
if attributes.forced_backend == Some(Backend::Wayland)
&& attributes.wayland_connection.is_some()
{
return Self::new_wayland_any_thread(attributes);
}

// NOTE: Wayland first because of X11 could be present under Wayland as well. Empty
// variables are also treated as not set.
let backend = match (
Expand Down Expand Up @@ -124,15 +145,21 @@ impl EventLoop {
// Create the display based on the backend.
match backend {
#[cfg(wayland_platform)]
Backend::Wayland => EventLoop::new_wayland_any_thread(),
Backend::Wayland => EventLoop::new_wayland_any_thread(attributes),
#[cfg(x11_platform)]
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)))
fn new_wayland_any_thread(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<EventLoop, EventLoopError> {
let event_loop = match &attributes.wayland_connection {
Some(connection) => wayland::EventLoop::new_with_connection(connection.clone()),
None => wayland::EventLoop::new(),
};
event_loop.map(|evlp| EventLoop::Wayland(Box::new(evlp)))
}

#[cfg(x11_platform)]
Expand Down
103 changes: 103 additions & 0 deletions winit/tests/wayland_connection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//! Explicit-connection selection and failed-initialization recovery.
#![cfg(all(target_os = "linux", feature = "wayland"))]

use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::os::unix::net::UnixStream;

use winit::error::EventLoopError;
use winit::event_loop::{EventLoop, EventLoopBuilder};
use winit::platform::wayland::{
EventLoopBuilderExtWayland, EventLoopExtWayland, WaylandConnection,
};

fn disconnected_connection() -> WaylandConnection {
let (client, server) = UnixStream::pair().unwrap();
drop(server);
WaylandConnection::from_socket(client).unwrap()
}

fn hash(builder: &EventLoopBuilder) -> u64 {
let mut state = DefaultHasher::new();
builder.hash(&mut state);
state.finish()
}

#[test]
fn builder_connection_identity_preserves_equality_and_hashing() {
let connection = disconnected_connection();
let mut first = EventLoop::builder();
let mut second = EventLoop::builder();
first.with_wayland_connection(connection.clone());
second.with_wayland_connection(connection);
assert_eq!(first, second);
assert_eq!(hash(&first), hash(&second));
second.with_wayland_connection(disconnected_connection());
assert_ne!(first, second);
assert_ne!(first, EventLoop::builder());
}

#[test]
fn failed_explicit_initialization_can_retry_without_relaxing_thread_policy() {
let mut builder = EventLoop::builder();
builder.with_wayland_connection(disconnected_connection());
// Rust test functions run off the OS main thread. Connection injection must
// not bypass the ordinary platform check, or consume the creation allowance.
assert!(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| builder.build())).is_err());
builder.with_any_thread(true);
for _ in 0..2 {
let error = builder.build().unwrap_err();
assert!(matches!(error, EventLoopError::Os(_)), "unexpected error: {error}");
builder.with_wayland_connection(disconnected_connection());
}
}

#[test]
fn environment_connection_keeps_native_one_shot_failure_policy() {
const CHILD: &str = "WINIT_TEST_DEFAULT_CONNECTION_CHILD";
if std::env::var_os(CHILD).is_none() {
let status = std::process::Command::new(std::env::current_exe().unwrap())
.args(["--exact", "environment_connection_keeps_native_one_shot_failure_policy"])
.env(CHILD, "1")
.env("WAYLAND_DISPLAY", "/nonexistent/winit-test-wayland")
.env_remove("WAYLAND_SOCKET")
.env_remove("DISPLAY")
.status()
.unwrap();
assert!(status.success());
return;
}
let mut builder = EventLoop::builder();
builder.with_wayland().with_any_thread(true);
assert!(matches!(builder.build(), Err(EventLoopError::Os(_))));
assert!(matches!(builder.build(), Err(EventLoopError::RecreationAttempt)));
builder.with_wayland_connection(disconnected_connection());
assert!(matches!(builder.build(), Err(EventLoopError::RecreationAttempt)));
}

#[test]
#[ignore = "requires a private compositor socket in WINIT_TEST_WAYLAND_SOCKET"]
fn failed_connection_then_real_explicit_display_and_permanent_success_guard() {
use winit::raw_window_handle::{HasDisplayHandle, RawDisplayHandle};

let socket = std::env::var_os("WINIT_TEST_WAYLAND_SOCKET")
.expect("select an isolated compositor explicitly");
let mut builder = EventLoop::builder();
builder.with_any_thread(true).with_wayland_connection(disconnected_connection());
assert!(matches!(builder.build(), Err(EventLoopError::Os(_))));

let connection = WaylandConnection::from_socket(UnixStream::connect(socket).unwrap()).unwrap();
let display = connection.backend().display_ptr();
builder.with_wayland_connection(connection);
let event_loop = builder.build().unwrap();
assert!(event_loop.is_wayland());
let handle = event_loop.owned_display_handle();
let RawDisplayHandle::Wayland(raw) = handle.display_handle().unwrap().as_raw() else {
panic!("explicit connection selected another backend");
};
assert_eq!(raw.display.as_ptr(), display.cast());
assert!(matches!(builder.build(), Err(EventLoopError::RecreationAttempt)));
drop(handle);
drop(event_loop);
assert!(matches!(builder.build(), Err(EventLoopError::RecreationAttempt)));
}