1#![cfg(free_unix)]
2
3#[cfg(all(not(x11_platform), not(wayland_platform)))]
4compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
5
6use std::env;
7use std::num::{NonZeroU16, NonZeroU32};
8use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
9use std::time::Duration;
10#[cfg(x11_platform)]
11use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc, sync::Mutex};
12
13use smol_str::SmolStr;
14
15pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey};
16#[cfg(x11_platform)]
17use self::x11::{XConnection, XError, XNotSupported};
18use crate::application::ApplicationHandler;
19pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
20#[cfg(x11_platform)]
21use crate::dpi::Size;
22use crate::dpi::{PhysicalPosition, PhysicalSize};
23use crate::error::{EventLoopError, NotSupportedError};
24use crate::event_loop::ActiveEventLoop;
25pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
26use crate::keyboard::Key;
27use crate::platform::pump_events::PumpStatus;
28#[cfg(x11_platform)]
29use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
30#[cfg(x11_platform)]
31use crate::utils::Lazy;
32use crate::window::ActivationToken;
33
34pub(crate) mod common;
35#[cfg(wayland_platform)]
36pub(crate) mod wayland;
37#[cfg(x11_platform)]
38pub(crate) mod x11;
39
40#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
41pub(crate) enum Backend {
42 #[cfg(x11_platform)]
43 X,
44 #[cfg(wayland_platform)]
45 Wayland,
46}
47
48#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
49pub(crate) struct PlatformSpecificEventLoopAttributes {
50 pub(crate) forced_backend: Option<Backend>,
51 pub(crate) any_thread: bool,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct ApplicationName {
56 pub general: String,
57 pub instance: String,
58}
59
60impl ApplicationName {
61 pub fn new(general: String, instance: String) -> Self {
62 Self { general, instance }
63 }
64}
65
66#[derive(Clone, Debug, PartialEq)]
67pub struct PlatformSpecificWindowAttributes {
68 pub name: Option<ApplicationName>,
69 pub activation_token: Option<ActivationToken>,
70 #[cfg(x11_platform)]
71 pub x11: X11WindowAttributes,
72}
73
74#[derive(Clone, Debug, PartialEq)]
75#[cfg(x11_platform)]
76pub struct X11WindowAttributes {
77 pub visual_id: Option<x11rb::protocol::xproto::Visualid>,
78 pub screen_id: Option<i32>,
79 pub base_size: Option<Size>,
80 pub override_redirect: bool,
81 pub x11_window_types: Vec<XWindowType>,
82
83 pub embed_window: Option<x11rb::protocol::xproto::Window>,
85}
86
87#[cfg_attr(not(x11_platform), allow(clippy::derivable_impls))]
88impl Default for PlatformSpecificWindowAttributes {
89 fn default() -> Self {
90 Self {
91 name: None,
92 activation_token: None,
93 #[cfg(x11_platform)]
94 x11: X11WindowAttributes {
95 visual_id: None,
96 screen_id: None,
97 base_size: None,
98 override_redirect: false,
99 x11_window_types: vec![XWindowType::Normal],
100 embed_window: None,
101 },
102 }
103 }
104}
105
106#[cfg(x11_platform)]
107pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
108 Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)));
109
110#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
111pub struct WindowId(u64);
112
113impl From<WindowId> for u64 {
114 fn from(window_id: WindowId) -> Self {
115 window_id.0
116 }
117}
118
119impl From<u64> for WindowId {
120 fn from(raw_id: u64) -> Self {
121 Self(raw_id)
122 }
123}
124
125impl WindowId {
126 pub const fn dummy() -> Self {
127 Self(0)
128 }
129}
130
131#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
132pub enum DeviceId {
133 #[cfg(x11_platform)]
134 X(x11::DeviceId),
135 #[cfg(wayland_platform)]
136 Wayland(wayland::DeviceId),
137}
138
139impl DeviceId {
140 pub const fn dummy() -> Self {
141 #[cfg(wayland_platform)]
142 return DeviceId::Wayland(wayland::DeviceId::dummy());
143 #[cfg(all(not(wayland_platform), x11_platform))]
144 return DeviceId::X(x11::DeviceId::dummy());
145 }
146}
147
148#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
149pub enum FingerId {
150 #[cfg(x11_platform)]
151 X(x11::FingerId),
152 #[cfg(wayland_platform)]
153 Wayland(wayland::FingerId),
154}
155
156impl FingerId {
157 pub const fn dummy() -> Self {
158 #[cfg(wayland_platform)]
159 return FingerId::Wayland(wayland::FingerId::dummy());
160 #[cfg(all(not(wayland_platform), x11_platform))]
161 return FingerId::X(x11::FingerId::dummy());
162 }
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
166pub enum MonitorHandle {
167 #[cfg(x11_platform)]
168 X(x11::MonitorHandle),
169 #[cfg(wayland_platform)]
170 Wayland(wayland::MonitorHandle),
171}
172
173macro_rules! x11_or_wayland {
183 (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr; as $enum2:ident ) => {
184 match $what {
185 #[cfg(x11_platform)]
186 $enum::X($($c1)*) => $enum2::X($x),
187 #[cfg(wayland_platform)]
188 $enum::Wayland($($c1)*) => $enum2::Wayland($x),
189 }
190 };
191 (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr) => {
192 match $what {
193 #[cfg(x11_platform)]
194 $enum::X($($c1)*) => $x,
195 #[cfg(wayland_platform)]
196 $enum::Wayland($($c1)*) => $x,
197 }
198 };
199}
200
201impl MonitorHandle {
202 #[inline]
203 pub fn name(&self) -> Option<String> {
204 x11_or_wayland!(match self; MonitorHandle(m) => m.name())
205 }
206
207 #[inline]
208 pub fn native_identifier(&self) -> u32 {
209 x11_or_wayland!(match self; MonitorHandle(m) => m.native_identifier())
210 }
211
212 #[inline]
213 pub fn position(&self) -> Option<PhysicalPosition<i32>> {
214 x11_or_wayland!(match self; MonitorHandle(m) => m.position())
215 }
216
217 #[inline]
218 pub fn scale_factor(&self) -> f64 {
219 x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as _)
220 }
221
222 #[inline]
223 pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
224 x11_or_wayland!(match self; MonitorHandle(m) => m.current_video_mode())
225 }
226
227 #[inline]
228 pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoModeHandle>> {
229 x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes()))
230 }
231}
232
233#[derive(Debug, Clone, PartialEq, Eq, Hash)]
234pub enum VideoModeHandle {
235 #[cfg(x11_platform)]
236 X(x11::VideoModeHandle),
237 #[cfg(wayland_platform)]
238 Wayland(wayland::VideoModeHandle),
239}
240
241impl VideoModeHandle {
242 #[inline]
243 pub fn size(&self) -> PhysicalSize<u32> {
244 x11_or_wayland!(match self; VideoModeHandle(m) => m.size())
245 }
246
247 #[inline]
248 pub fn bit_depth(&self) -> Option<NonZeroU16> {
249 x11_or_wayland!(match self; VideoModeHandle(m) => m.bit_depth())
250 }
251
252 #[inline]
253 pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
254 x11_or_wayland!(match self; VideoModeHandle(m) => m.refresh_rate_millihertz())
255 }
256
257 #[inline]
258 pub fn monitor(&self) -> MonitorHandle {
259 x11_or_wayland!(match self; VideoModeHandle(m) => m.monitor(); as MonitorHandle)
260 }
261}
262
263#[derive(Debug, Clone, Eq, PartialEq, Hash)]
264pub struct KeyEventExtra {
265 pub text_with_all_modifiers: Option<SmolStr>,
266 pub key_without_modifiers: Key,
267}
268
269#[derive(Clone, Debug, Eq, Hash, PartialEq)]
270pub(crate) enum PlatformCustomCursor {
271 #[cfg(wayland_platform)]
272 Wayland(wayland::CustomCursor),
273 #[cfg(x11_platform)]
274 X(x11::CustomCursor),
275}
276
277#[cfg(x11_platform)]
279pub(crate) static XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
280
281#[cfg(x11_platform)]
282unsafe extern "C" fn x_error_callback(
283 display: *mut x11::ffi::Display,
284 event: *mut x11::ffi::XErrorEvent,
285) -> c_int {
286 let xconn_lock = X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner());
287 if let Ok(ref xconn) = *xconn_lock {
288 let mut error_handled = false;
290 for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
291 error_handled |= hook(display as *mut _, event as *mut _);
292 }
293
294 let mut buf: [MaybeUninit<c_char>; 1024] = unsafe { MaybeUninit::uninit().assume_init() };
297 unsafe {
298 (xconn.xlib.XGetErrorText)(
299 display,
300 (*event).error_code as c_int,
301 buf.as_mut_ptr() as *mut c_char,
302 buf.len() as c_int,
303 )
304 };
305 let description =
306 unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }.to_string_lossy();
307
308 let error = unsafe {
309 XError {
310 description: description.into_owned(),
311 error_code: (*event).error_code,
312 request_code: (*event).request_code,
313 minor_code: (*event).minor_code,
314 }
315 };
316
317 if !error_handled {
319 tracing::error!("X11 error: {:#?}", error);
320 *xconn.latest_error.lock().unwrap() = Some(error);
322 }
323 }
324 0
326}
327
328pub enum EventLoop {
329 #[cfg(wayland_platform)]
330 Wayland(Box<wayland::EventLoop>),
331 #[cfg(x11_platform)]
332 X(x11::EventLoop),
333}
334
335#[derive(Clone)]
336pub enum EventLoopProxy {
337 #[cfg(x11_platform)]
338 X(x11::EventLoopProxy),
339 #[cfg(wayland_platform)]
340 Wayland(wayland::EventLoopProxy),
341}
342
343impl EventLoop {
344 pub(crate) fn new(
345 attributes: &PlatformSpecificEventLoopAttributes,
346 ) -> Result<Self, EventLoopError> {
347 if !attributes.any_thread && !is_main_thread() {
348 panic!(
349 "Initializing the event loop outside of the main thread is a significant \
350 cross-platform compatibility hazard. If you absolutely need to create an \
351 EventLoop on a different thread, you can use the \
352 `EventLoopBuilderExtX11::any_thread` or `EventLoopBuilderExtWayland::any_thread` \
353 functions."
354 );
355 }
356
357 let backend = match (
360 attributes.forced_backend,
361 env::var("WAYLAND_DISPLAY")
362 .ok()
363 .filter(|var| !var.is_empty())
364 .or_else(|| env::var("WAYLAND_SOCKET").ok())
365 .filter(|var| !var.is_empty())
366 .is_some(),
367 env::var("DISPLAY").map(|var| !var.is_empty()).unwrap_or(false),
368 ) {
369 (Some(backend), ..) => backend,
371 #[cfg(wayland_platform)]
373 (None, true, _) => Backend::Wayland,
374 #[cfg(x11_platform)]
376 (None, _, true) => Backend::X,
377 (_, wayland_display, x11_display) => {
379 let msg = if wayland_display && !cfg!(wayland_platform) {
380 "DISPLAY is not set; note: enable the `winit/wayland` feature to support \
381 Wayland"
382 } else if x11_display && !cfg!(x11_platform) {
383 "neither WAYLAND_DISPLAY nor WAYLAND_SOCKET is set; note: enable the \
384 `winit/x11` feature to support X11"
385 } else {
386 "neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set."
387 };
388 return Err(NotSupportedError::new(msg).into());
389 },
390 };
391
392 match backend {
394 #[cfg(wayland_platform)]
395 Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
396 #[cfg(x11_platform)]
397 Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
398 }
399 }
400
401 #[cfg(wayland_platform)]
402 fn new_wayland_any_thread() -> Result<EventLoop, EventLoopError> {
403 wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
404 }
405
406 #[cfg(x11_platform)]
407 fn new_x11_any_thread() -> Result<EventLoop, EventLoopError> {
408 let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() {
409 Ok(xconn) => xconn.clone(),
410 Err(err) => return Err(os_error!(err.clone()).into()),
411 };
412
413 Ok(EventLoop::X(x11::EventLoop::new(xconn)))
414 }
415
416 #[inline]
417 pub fn is_wayland(&self) -> bool {
418 match *self {
419 #[cfg(wayland_platform)]
420 EventLoop::Wayland(_) => true,
421 #[cfg(x11_platform)]
422 _ => false,
423 }
424 }
425
426 pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
427 x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app))
428 }
429
430 pub fn run_app_on_demand<A: ApplicationHandler>(
431 &mut self,
432 app: A,
433 ) -> Result<(), EventLoopError> {
434 x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app_on_demand(app))
435 }
436
437 pub fn pump_app_events<A: ApplicationHandler>(
438 &mut self,
439 timeout: Option<Duration>,
440 app: A,
441 ) -> PumpStatus {
442 x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_app_events(timeout, app))
443 }
444
445 pub fn window_target(&self) -> &dyn ActiveEventLoop {
446 x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target())
447 }
448}
449
450impl AsFd for EventLoop {
451 fn as_fd(&self) -> BorrowedFd<'_> {
452 x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd())
453 }
454}
455
456impl AsRawFd for EventLoop {
457 fn as_raw_fd(&self) -> RawFd {
458 x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd())
459 }
460}
461
462impl EventLoopProxy {
463 pub fn wake_up(&self) {
464 x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.wake_up())
465 }
466}
467
468#[derive(Clone)]
469#[allow(dead_code)]
470pub(crate) enum OwnedDisplayHandle {
471 #[cfg(x11_platform)]
472 X(Arc<XConnection>),
473 #[cfg(wayland_platform)]
474 Wayland(wayland_client::Connection),
475}
476
477impl OwnedDisplayHandle {
478 #[cfg(feature = "rwh_06")]
479 #[inline]
480 pub fn raw_display_handle_rwh_06(
481 &self,
482 ) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
483 use std::ptr::NonNull;
484
485 match self {
486 #[cfg(x11_platform)]
487 Self::X(xconn) => Ok(rwh_06::XlibDisplayHandle::new(
488 NonNull::new(xconn.display.cast()),
489 xconn.default_screen_index() as _,
490 )
491 .into()),
492
493 #[cfg(wayland_platform)]
494 Self::Wayland(conn) => {
495 use sctk::reexports::client::Proxy;
496
497 Ok(rwh_06::WaylandDisplayHandle::new(
498 NonNull::new(conn.display().id().as_ptr().cast()).unwrap(),
499 )
500 .into())
501 },
502 }
503 }
504}
505
506impl PartialEq for OwnedDisplayHandle {
507 fn eq(&self, other: &Self) -> bool {
508 match (self, other) {
509 #[cfg(x11_platform)]
510 (Self::X(this), Self::X(other)) => Arc::as_ptr(this).eq(&Arc::as_ptr(other)),
511 #[cfg(wayland_platform)]
512 (Self::Wayland(this), Self::Wayland(other)) => this.eq(other),
513 #[cfg(all(x11_platform, wayland_platform))]
514 _ => false,
515 }
516 }
517}
518
519impl Eq for OwnedDisplayHandle {}
520
521fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
525 a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
526}
527
528#[cfg(target_os = "linux")]
529fn is_main_thread() -> bool {
530 rustix::thread::gettid() == rustix::process::getpid()
531}
532
533#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
534fn is_main_thread() -> bool {
535 use libc::pthread_main_np;
536
537 unsafe { pthread_main_np() == 1 }
538}
539
540#[cfg(target_os = "netbsd")]
541fn is_main_thread() -> bool {
542 std::thread::current().name() == Some("main")
543}