diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1076aa5655..d6a58d412e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,7 @@ jobs: - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" } + - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "kms,kms-ext" } - { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' } - { target: x86_64-apple-darwin, os: macos-latest, } - { target: x86_64-apple-ios, os: macos-latest, } @@ -61,10 +62,60 @@ jobs: with: rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }} targets: ${{ matrix.platform.target }} - - - name: Install GCC Multilib + - name: Update TZ + if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'x86_64') + run: sudo apt-get install -qqy libudev-dev libinput-dev libxkbcommon-dev meson ninja-build + - name: Setup cross linux toolchain if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') - run: sudo apt-get update && sudo apt-get install gcc-multilib + run: | + case "${{ matrix.platform.target }}" in + i686-*) SYSTEM_ARCH=i386 ;; + arm*) SYSTEM_ARCH=armhf ;; + aarch64*) SYSTEM_ARCH=arm64 ;; + esac + GCC_TARGET=$(printf "${{ matrix.platform.target }}" | sed 's/-unknown-/-/' | sed 's/arm[^-]*/arm/g') + ENV_TARGET=$(printf "${{ matrix.platform.target }}" | tr '-' '_') + ENV_TARGET_UC=$(printf "${ENV_TARGET}" | tr '[[:lower:]]' '[[:upper:]]') + sudo rm -f /etc/apt/sources.list.d/*.list + case "${{ matrix.platform.target }}" in + arm* | aarch64*) + sudo tee /etc/apt/sources.list << EOF + deb [arch=i386,amd64] http://archive.ubuntu.com/ubuntu/ focal main universe + deb [arch=i386,amd64] http://archive.ubuntu.com/ubuntu/ focal-updates main universe + deb [arch=i386,amd64] http://security.ubuntu.com/ubuntu/ focal-security main universe + deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ focal main universe + deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ focal-updates main universe + deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ focal-security main universe + EOF + ;; + esac + sudo dpkg --add-architecture ${SYSTEM_ARCH} + dpkg --print-foreign-architectures + sudo apt-get update -qqy + sudo apt-get dist-upgrade -qqy --fix-broken + sudo apt-get install -qqy --fix-broken -o Debug::pkgProblemResolver=yes gcc-${GCC_TARGET} pkg-config-${GCC_TARGET} meson ninja-build + sudo apt-get install -qqy --fix-broken -o Debug::pkgProblemResolver=yes libinput-dev:${SYSTEM_ARCH} libudev-dev:${SYSTEM_ARCH} libxkbcommon-dev:${SYSTEM_ARCH} gcc-multilib + echo "CARGO_TARGET_${ENV_TARGET_UC}_LINKER=${GCC_TARGET}-gcc" >> $GITHUB_ENV + echo "PKG_CONFIG_ALLOW_CROSS=1" >> $GITHUB_ENV + echo "PKG_CONFIG_${ENV_TARGET}=${GCC_TARGET}-pkg-config" >> $GITHUB_ENV + echo "PKG_CONFIG=${GCC_TARGET}-pkg-config" >> $GITHUB_ENV + echo "BINDGEN_EXTRA_CLANG_ARGS=\"-L/usr/lib/${GCC_TARGET} -L/lib/${GCC_TARGET}\"" >> $GITHUB_ENV + + - name: Install Build Deps + if: (matrix.platform.os == 'ubuntu-latest') + run: | + sudo apt-get update -qqy + sudo apt-get install -qqy --fix-broken -o Debug::pkgProblemResolver=yes curl gcc pkg-config libclang-dev dpkg-dev + - name: Build libseat + if: (matrix.platform.os == 'ubuntu-latest') && !contains(matrix.platform.target, 'android') + run: | + wget https://git.sr.ht/~kennylevinsen/seatd/archive/0.5.0.tar.gz -O libseat-source.tar.gz + tar xf libseat-source.tar.gz + cd seatd-0.5.0 + meson -Dbuiltin=enabled -Dserver=disabled -Dexamples=disabled -Dman-pages=disabled build . + ninja -C build + sudo meson install -C build + - name: Install cargo-apk if: contains(matrix.platform.target, 'android') run: cargo install cargo-apk diff --git a/CHANGELOG.md b/CHANGELOG.md index aa4571088a..b57cb5de5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- On Linux, initial support has been added for a DRM backend. This is a breaking change - On Wayland, fix bug where the cursor wouldn't hide in GNOME. - On macOS, Windows, and Wayland, add `set_cursor_hittest` to let the window ignore mouse events. - On Windows, added `WindowExtWindows::set_skip_taskbar` and `WindowBuilderExtWindows::with_skip_taskbar`. @@ -796,4 +797,4 @@ _Yanked_ - Changed the X11 fullscreen code to use `xrandr` instead of `xxf86vm`. - Fixed the Wayland backend to produce `Refresh` event after window creation. - Changed the `Suspended` event to be outside of `WindowEvent`. -- Fixed the X11 backend sometimes reporting the wrong virtual key (#273). +- Fixed the X11 backend sometimes reporting the wrong virtual key (#273). \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index d6ee8b53e9..57927ef123 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,36 +17,38 @@ default-target = "x86_64-unknown-linux-gnu" targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"] [features] -default = ["x11", "wayland", "wayland-dlopen"] +default = ["x11", "wayland", "wayland-dlopen", "kms"] x11 = ["x11-dl", "mio", "percent-encoding", "parking_lot"] wayland = ["wayland-client", "wayland-protocols", "sctk"] wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"] +kms = ["drm", "input", "calloop", "xkbcommon", "udev", "parking_lot"] +kms-ext = ["libseat"] [dependencies] -instant = { version = "0.1", features = ["wasm-bindgen"] } -lazy_static = "1" -log = "0.4" -serde = { version = "1", optional = true, features = ["serde_derive"] } -raw-window-handle = "0.4.2" -bitflags = "1" -mint = { version = "0.5.6", optional = true } +instant = { version = "0.1.12", features = ["wasm-bindgen"] } +lazy_static = "1.4.0" +log = "0.4.17" +serde = { version = "1.0.137", optional = true, features = ["serde_derive"] } +raw-window-handle = { git = "https://github.com/rust-windowing/raw-window-handle" } +bitflags = "1.3.2" +mint = { version = "0.5.9", optional = true } [dev-dependencies] -image = { version = "0.24.0", default-features = false, features = ["png"] } +image = { version = "0.24.2", default-features = false, features = ["png"] } simple_logger = "2.1.0" [target.'cfg(target_os = "android")'.dependencies] -ndk = "0.6" -ndk-sys = "0.3" -ndk-glue = "0.6" +ndk = "0.6.0" +ndk-sys = "0.3.0" +ndk-glue = "0.6.2" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] objc = "0.2.7" [target.'cfg(target_os = "macos")'.dependencies] -cocoa = "0.24" -core-foundation = "0.9" -core-graphics = "0.22" +cocoa = "0.24.0" +core-foundation = "0.9.3" +core-graphics = "0.22.3" dispatch = "0.2.0" [target.'cfg(target_os = "macos")'.dependencies.core-video-sys] @@ -55,10 +57,10 @@ default_features = false features = ["display_link"] [target.'cfg(target_os = "windows")'.dependencies] -parking_lot = "0.12" +parking_lot = "0.12.0" [target.'cfg(target_os = "windows")'.dependencies.windows-sys] -version = "0.36" +version = "0.36.1" features = [ "Win32_Devices_HumanInterfaceDevice", "Win32_Foundation", @@ -87,18 +89,27 @@ features = [ ] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] -wayland-client = { version = "0.29.4", default_features = false, features = ["use_system_lib"], optional = true } +wayland-client = { version = "0.29.4", default_features = false, features = ["use_system_lib"], optional = true } wayland-protocols = { version = "0.29.4", features = [ "staging_protocols"], optional = true } -sctk = { package = "smithay-client-toolkit", version = "0.15.4", default_features = false, features = ["calloop"], optional = true } -mio = { version = "0.8", features = ["os-ext"], optional = true } -x11-dl = { version = "2.18.5", optional = true } -percent-encoding = { version = "2.0", optional = true } +sctk = { package = "smithay-client-toolkit", version = "0.15.4", default_features = false, features = ["calloop"], optional = true } +mio = { version = "0.8.3", features = ["os-ext"], optional = true } +x11-dl = { version = "2.19.1", optional = true } +percent-encoding = { version = "2.1.0", optional = true } parking_lot = { version = "0.12.0", optional = true } -libc = "0.2.64" +libc = "0.2.125" +drm = { version = "0.6.2", optional = true } +input = { version = "0.7.1", optional = true } +libseat = { version = "0.1.4", optional = true } +udev = { version = "0.6.3", optional = true } +calloop = { version = "0.9.3", optional = true } +xkbcommon = { git = "https://github.com/StratusFearMe21/xkbcommon-rs", optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen= "0.2.80" [target.'cfg(target_arch = "wasm32")'.dependencies.web_sys] package = "web-sys" -version = "0.3.22" +version = "0.3.57" features = [ 'console', "AddEventListenerOptions", @@ -122,11 +133,8 @@ features = [ 'WheelEvent' ] -[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen] -version = "0.2.45" - [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -console_log = "0.2" +console_log = "0.2.0" [workspace] members = [ diff --git a/FEATURES.md b/FEATURES.md index 15aacb6b6b..77aba2a4e8 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -8,6 +8,7 @@ be used to create both games and applications. It supports the main graphical pl - Unix - via X11 - via Wayland + - via KMS/DRM - Mobile - iOS - Android @@ -168,57 +169,57 @@ Legend: - ❓: Unknown status ### Windowing -|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM | -|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | -|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ | -|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**| -|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**| -|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A**| -|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| -|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ | -|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**| -|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A | -|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| -|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| -|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| -|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ | -|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ | -|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**| -|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | -|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**| +|Feature | Windows |MacOS |Linux x11 |Linux Wayland | Linux KMS/DRM | |Android |iOS |WASM | +| -------------------------------- | ------- | ------ | ---------- | ------------ | ------------ | ------- | ----- | -------- | +|Window initialization |✔️ |✔️ |▢[#5] |✔️ |✔️ |▢[#33] |▢[#33] |✔️ | +|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**| +|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**| +|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A** |**N/A** |**N/A** |**N/A**| +|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A** |**N/A** |**N/A** |**N/A**| +|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A** |**N/A** |**N/A** |✔️ | +|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**| +|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A** |**N/A** |**N/A** |N/A | +|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A** |**N/A** |**N/A** |**N/A**| +|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A** |**N/A** |**N/A** |**N/A**| +|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A** |**N/A** |**N/A** |**N/A**| +|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A** |**N/A** |✔️ |✔️ | +|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A** |**N/A** |✔️ |✔️ | +|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |✔️ |❌ |✔️ |**N/A**| +|HiDPI support |✔️ |✔️ |✔️ |✔️ |❌ |▢[#721] |✔️ |✔️ | +|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**| ### System information -|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | -|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | -|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |**N/A**| -|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |**N/A**| +|Feature |Windows|MacOS |Linux x11|Linux Wayland|Linux KMS/DRM|Android|iOS |WASM | +|---------------- | ----- | ---- | ------- | ----------- | ----------- | ----- | ------- | -------- | +|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |✔️ |**N/A**| +|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |**N/A**| ### Input handling -|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | -|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | -|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ | -|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**| -|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|✔️ | -|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ | -|Cursor hittest |✔️ |✔️ |❌ |✔️ |**N/A**|**N/A**|❌ | -|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ | -|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❌ | -|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ | -|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ | -|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ | -|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ | -|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ | -|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ | -|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | +|Feature |Windows |MacOS |Linux x11|Linux Wayland|Linux KMS/DRM|Android|iOS |WASM | +|----------------------- | ----- | ---- | ------- | ----------- | ----------- | ----- | ----- | -------- | +|Mouse events |✔️ |▢[#63] |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ | +|Mouse set location |✔️ |✔️ |✔️ |❓ |✔️ |**N/A**|**N/A**|**N/A**| +|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |✔️ |**N/A**|**N/A**|✔️ | +|Cursor icon |✔️ |✔️ |✔️ |✔️ |❌ |**N/A**|**N/A**|✔️ | +|Cursor hittest |✔️ |✔️ |❌ |✔️ |❌ |**N/A**|**N/A**|❌ | +|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ | +|Touch pressure |✔️ |❌ |❌ |❌ |✔️ |❌ |✔️ |❌ | +|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ | +|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ | +|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A |**N/A**|**N/A**|❓ | +|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |✔️ |❌ |❌ |❓ | +|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❌ |❓ | +|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❌ |❓ | +|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A** |**N/A**|**N/A**|**N/A** | ### Pending API Reworks Changes in the API that have been agreed upon but aren't implemented across all platforms. -|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | -|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | -|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |❓ | -|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |❌ |✔️ |❓ | -|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❓ | +|Feature |Windows|MacOS |Linux x11|Linux Wayland|Linux KMS/DRM|Android|iOS |WASM | +|------------------------------ | ----- | ---- | ------- | ----------- | ----------- | ----- | ----- | -------- | +|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |❌ |▢[#721]|✔️ |❓ | +|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |❌ |❌ |✔️ |❓ | +|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❌ |❓ | ### Completed API Reworks |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | @@ -238,4 +239,4 @@ Changes in the API that have been agreed upon but aren't implemented across all [#721]: https://github.com/rust-windowing/winit/issues/721 [#750]: https://github.com/rust-windowing/winit/issues/750 [#804]: https://github.com/rust-windowing/winit/issues/804 -[#812]: https://github.com/rust-windowing/winit/issues/812 +[#812]: https://github.com/rust-windowing/winit/issues/812 \ No newline at end of file diff --git a/README.md b/README.md index 88e44f80f8..41811da541 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml` * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde). * `x11` (enabled by default): On Unix platform, compiles with the X11 backend * `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend +* `kms` (enabled by default): On Unix platform, compiles with the kmsdrm backend * `mint`: Enables mint (math interoperability standard types) conversions. ### Platform-specific usage diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 508cafb54e..5adbca61d3 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -6,9 +6,13 @@ target_os = "openbsd" ))] +#[cfg(any(feature = "x11", feature = "wayland"))] use std::os::raw; #[cfg(feature = "x11")] -use std::{ptr, sync::Arc}; +use std::ptr; + +#[cfg(feature = "x11")] +use std::sync::Arc; use crate::{ event_loop::{EventLoopBuilder, EventLoopWindowTarget}, @@ -18,29 +22,32 @@ use crate::{ #[cfg(feature = "x11")] use crate::dpi::Size; +#[cfg(feature = "kms")] +use crate::platform_impl::kms::MODE; #[cfg(feature = "x11")] use crate::platform_impl::x11::{ffi::XVisualInfo, XConnection}; -use crate::platform_impl::{ - ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget, - Window as LinuxWindow, -}; +#[cfg(any(feature = "x11", feature = "wayland"))] +use crate::platform_impl::Window as LinuxWindow; +use crate::platform_impl::{ApplicationName, EventLoopWindowTarget as LinuxEventLoopWindowTarget}; + +#[cfg(any(feature = "x11", feature = "wayland", feature = "kms"))] +pub use crate::platform_impl::Backend; // TODO: stupid hack so that glutin can do its work +#[cfg(feature = "kms")] +pub use crate::platform_impl::kms::Card; #[doc(hidden)] #[cfg(feature = "x11")] pub use crate::platform_impl::x11; #[cfg(feature = "x11")] pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported}; +#[cfg(feature = "kms")] +use drm::control::*; /// Additional methods on `EventLoopWindowTarget` that are specific to Unix. pub trait EventLoopWindowTargetExtUnix { - /// True if the `EventLoopWindowTarget` uses Wayland. - #[cfg(feature = "wayland")] - fn is_wayland(&self) -> bool; - - /// True if the `EventLoopWindowTarget` uses X11. - #[cfg(feature = "x11")] - fn is_x11(&self) -> bool; + /// Find out what backend winit is currently using. + fn unix_backend(&self) -> Backend; #[doc(hidden)] #[cfg(feature = "x11")] @@ -49,33 +56,62 @@ pub trait EventLoopWindowTargetExtUnix { /// Returns a pointer to the `wl_display` object of wayland that is used by this /// `EventLoopWindowTarget`. /// - /// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example). + /// Returns `None` if the `EventLoop` doesn't use wayland. /// /// The pointer will become invalid when the winit `EventLoop` is destroyed. #[cfg(feature = "wayland")] fn wayland_display(&self) -> Option<*mut raw::c_void>; + + /// Returns the drm device of the event loop's fd + /// + /// Returns `None` if the `EventLoop` doesn't use drm. + #[cfg(feature = "kms")] + fn drm_device(&self) -> Option<&Card>; + + /// Returns the current crtc of the drm device + /// + /// Returns `None` if the `EventLoop` doesn't use drm. + #[cfg(feature = "kms")] + fn drm_crtc(&self) -> Option<&crtc::Info>; + + /// Returns the current connector of the drm device + /// + /// Returns `None` if the `EventLoop` doesn't use drm. + #[cfg(feature = "kms")] + fn drm_connector(&self) -> Option<&connector::Info>; + + /// Returns the current mode of the drm device + /// + /// Returns `None` if the `EventLoop` doesn't use drm. + #[cfg(feature = "kms")] + fn drm_mode(&self) -> Option; + + /// Returns the primary plane of the drm device + /// + /// Returns `None` if the `EventLoop` doesn't use drm. + #[cfg(feature = "kms")] + fn drm_plane(&self) -> Option; } impl EventLoopWindowTargetExtUnix for EventLoopWindowTarget { #[inline] - #[cfg(feature = "wayland")] - fn is_wayland(&self) -> bool { - self.p.is_wayland() + fn unix_backend(&self) -> Backend { + match self.p { + #[cfg(feature = "x11")] + LinuxEventLoopWindowTarget::X(_) => Backend::X, + #[cfg(feature = "wayland")] + LinuxEventLoopWindowTarget::Wayland(_) => Backend::Wayland, + #[cfg(feature = "kms")] + LinuxEventLoopWindowTarget::Kms(_) => Backend::Kms, + } } - #[inline] #[cfg(feature = "x11")] - fn is_x11(&self) -> bool { - !self.p.is_wayland() - } - #[inline] - #[doc(hidden)] - #[cfg(feature = "x11")] fn xlib_xconnection(&self) -> Option> { match self.p { LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()), - #[cfg(feature = "wayland")] + #[cfg(any(feature = "wayland", feature = "kms"))] _ => None, } } @@ -87,7 +123,57 @@ impl EventLoopWindowTargetExtUnix for EventLoopWindowTarget { LinuxEventLoopWindowTarget::Wayland(ref p) => { Some(p.display().get_display_ptr() as *mut _) } - #[cfg(feature = "x11")] + #[cfg(any(feature = "x11", feature = "kms"))] + _ => None, + } + } + + #[inline] + #[cfg(feature = "kms")] + fn drm_device(&self) -> Option<&Card> { + match self.p { + LinuxEventLoopWindowTarget::Kms(ref evlp) => Some(&evlp.device), + #[cfg(any(feature = "x11", feature = "wayland"))] + _ => None, + } + } + + #[inline] + #[cfg(feature = "kms")] + fn drm_crtc(&self) -> Option<&crtc::Info> { + match self.p { + LinuxEventLoopWindowTarget::Kms(ref window) => Some(&window.crtc), + #[cfg(any(feature = "x11", feature = "wayland"))] + _ => None, + } + } + + #[inline] + #[cfg(feature = "kms")] + fn drm_connector(&self) -> Option<&connector::Info> { + match self.p { + LinuxEventLoopWindowTarget::Kms(ref window) => Some(&window.connector), + #[cfg(any(feature = "x11", feature = "wayland"))] + _ => None, + } + } + + #[inline] + #[cfg(feature = "kms")] + fn drm_mode(&self) -> Option { + match self.p { + LinuxEventLoopWindowTarget::Kms(_) => *MODE.lock(), + #[cfg(any(feature = "x11", feature = "wayland"))] + _ => None, + } + } + + #[inline] + #[cfg(feature = "kms")] + fn drm_plane(&self) -> Option { + match self.p { + LinuxEventLoopWindowTarget::Kms(ref window) => Some(window.plane), + #[cfg(any(feature = "x11", feature = "wayland"))] _ => None, } } @@ -103,6 +189,10 @@ pub trait EventLoopBuilderExtUnix { #[cfg(feature = "wayland")] fn with_wayland(&mut self) -> &mut Self; + /// Force using kms + #[cfg(feature = "kms")] + fn with_drm(&mut self) -> &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 @@ -125,6 +215,13 @@ impl EventLoopBuilderExtUnix for EventLoopBuilder { self } + #[inline] + #[cfg(feature = "kms")] + fn with_drm(&mut self) -> &mut Self { + self.platform_specific.forced_backend = Some(Backend::Kms); + self + } + #[inline] fn with_any_thread(&mut self, any_thread: bool) -> &mut Self { self.platform_specific.any_thread = any_thread; @@ -136,13 +233,13 @@ impl EventLoopBuilderExtUnix for EventLoopBuilder { pub trait WindowExtUnix { /// Returns the ID of the `Window` xlib object that is used by this window. /// - /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). + /// Returns `None` if the window doesn't use xlib. #[cfg(feature = "x11")] fn xlib_window(&self) -> Option; /// Returns a pointer to the `Display` object of xlib that is used by this window. /// - /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). + /// Returns `None` if the window doesn't use xlib. /// /// The pointer will become invalid when the glutin `Window` is destroyed. #[cfg(feature = "x11")] @@ -157,7 +254,7 @@ pub trait WindowExtUnix { /// This function returns the underlying `xcb_connection_t` of an xlib `Display`. /// - /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). + /// Returns `None` if the window doesn't use xlib. /// /// The pointer will become invalid when the glutin `Window` is destroyed. #[cfg(feature = "x11")] @@ -165,7 +262,7 @@ pub trait WindowExtUnix { /// Returns a pointer to the `wl_surface` object of wayland that is used by this window. /// - /// Returns `None` if the window doesn't use wayland (if it uses xlib for example). + /// Returns `None` if the window doesn't use wayland. /// /// The pointer will become invalid when the glutin `Window` is destroyed. #[cfg(feature = "wayland")] @@ -173,7 +270,7 @@ pub trait WindowExtUnix { /// Returns a pointer to the `wl_display` object of wayland that is used by this window. /// - /// Returns `None` if the window doesn't use wayland (if it uses xlib for example). + /// Returns `None` if the window doesn't use wayland. /// /// The pointer will become invalid when the glutin `Window` is destroyed. #[cfg(feature = "wayland")] @@ -195,7 +292,7 @@ impl WindowExtUnix for Window { fn xlib_window(&self) -> Option { match self.window { LinuxWindow::X(ref w) => Some(w.xlib_window()), - #[cfg(feature = "wayland")] + #[cfg(any(feature = "wayland", feature = "kms"))] _ => None, } } @@ -205,7 +302,7 @@ impl WindowExtUnix for Window { fn xlib_display(&self) -> Option<*mut raw::c_void> { match self.window { LinuxWindow::X(ref w) => Some(w.xlib_display()), - #[cfg(feature = "wayland")] + #[cfg(any(feature = "wayland", feature = "kms"))] _ => None, } } @@ -215,18 +312,17 @@ impl WindowExtUnix for Window { fn xlib_screen_id(&self) -> Option { match self.window { LinuxWindow::X(ref w) => Some(w.xlib_screen_id()), - #[cfg(feature = "wayland")] + #[cfg(any(feature = "wayland", feature = "kms"))] _ => None, } } - #[inline] - #[doc(hidden)] #[cfg(feature = "x11")] + #[inline] fn xlib_xconnection(&self) -> Option> { match self.window { LinuxWindow::X(ref w) => Some(w.xlib_xconnection()), - #[cfg(feature = "wayland")] + #[cfg(any(feature = "wayland", feature = "kms"))] _ => None, } } @@ -236,7 +332,7 @@ impl WindowExtUnix for Window { fn xcb_connection(&self) -> Option<*mut raw::c_void> { match self.window { LinuxWindow::X(ref w) => Some(w.xcb_connection()), - #[cfg(feature = "wayland")] + #[cfg(any(feature = "wayland", feature = "kms"))] _ => None, } } @@ -246,7 +342,7 @@ impl WindowExtUnix for Window { fn wayland_surface(&self) -> Option<*mut raw::c_void> { match self.window { LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _), - #[cfg(feature = "x11")] + #[cfg(any(feature = "x11", feature = "kms"))] _ => None, } } @@ -256,7 +352,7 @@ impl WindowExtUnix for Window { fn wayland_display(&self) -> Option<*mut raw::c_void> { match self.window { LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _), - #[cfg(feature = "x11")] + #[cfg(any(feature = "x11", feature = "kms"))] _ => None, } } diff --git a/src/platform_impl/linux/kms/event_loop.rs b/src/platform_impl/linux/kms/event_loop.rs new file mode 100644 index 0000000000..a7b68f70bf --- /dev/null +++ b/src/platform_impl/linux/kms/event_loop.rs @@ -0,0 +1,733 @@ +use parking_lot::Mutex; +use std::{ + cell::RefCell, + collections::VecDeque, + marker::PhantomData, + path::{Path, PathBuf}, + rc::Rc, + sync::{mpsc::SendError, Arc}, + time::{Duration, Instant}, +}; +#[cfg(feature = "kms-ext")] +use std::{collections::HashMap, sync::atomic::AtomicBool}; +use udev::Enumerator; +use xkbcommon::xkb; + +use drm::control::*; + +#[cfg(feature = "wayland")] +use sctk::reexports::calloop; + +use crate::error; + +use crate::{ + dpi::PhysicalPosition, + event::{DeviceId, Event, KeyboardInput, StartCause, WindowEvent}, + event_loop::{self, ControlFlow, EventLoopClosed}, + monitor::MonitorHandle, + platform::unix::Card, + platform_impl::{self, platform::sticky_exit_callback, OsError}, + window::WindowId, +}; + +use super::{ + input::{Interface, LibinputInputBackend, REPEAT_RATE}, + MODE, +}; + +macro_rules! to_platform_impl { + ($p:ident, $params:expr) => { + $p(platform_impl::$p::Kms($params)) + }; +} + +macro_rules! window_id { + () => { + to_platform_impl!(WindowId, super::WindowId) + }; +} + +/// An event loop's sink to deliver events from the Wayland event callbacks +/// to the winit's user. +type EventSink = Vec>; + +pub struct EventLoopWindowTarget { + /// Drm Connector + pub connector: connector::Info, + + /// Drm crtc + pub crtc: crtc::Info, + + /// Drm plane + pub plane: plane::Handle, + + /// Allows window to edit cursor position + pub(crate) cursor_arc: Arc>>, + + /// Drm device + pub device: Card, + + /// Event loop handle. + pub event_loop_handle: calloop::LoopHandle<'static, EventSink>, + + pub(crate) event_sink: EventSink, + + /// A proxy to wake up event loop. + pub event_loop_awakener: calloop::ping::Ping, + + _marker: std::marker::PhantomData, +} + +impl EventLoopWindowTarget { + #[inline] + pub fn primary_monitor(&self) -> Option { + Some(MonitorHandle { + inner: platform_impl::MonitorHandle::Kms(super::MonitorHandle { + connector: self.connector.clone(), + name: (*MODE.lock())?.name().to_string_lossy().into_owned(), + }), + }) + } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + if let Some(mode) = *MODE.lock() { + self.device + .resource_handles() + .unwrap() + .connectors() + .iter() + .map(|f| super::MonitorHandle { + connector: self.device.get_connector(*f).unwrap(), + name: mode.name().to_string_lossy().into_owned(), + }) + .collect() + } else { + VecDeque::new() + } + } +} + +fn find_plane( + planes: PlaneResourceHandles, + res: ResourceHandles, + crtc: &crtc::Info, + drm: &Card, +) -> plane::Handle { + let (p_better_planes, p_compatible_planes): ( + // The primary planes available to us + Vec, + // Other, not-ideal planes that are however useable + Vec, + ) = planes + .planes() + .iter() + .filter(|&&plane| { + // Get the plane info from a handle + drm.get_plane(plane) + .map(|plane_info| { + let compatible_crtcs = res.filter_crtcs(plane_info.possible_crtcs()); + // Makes sure that the plane can be used with the CRTC we selected earlier + compatible_crtcs.contains(&crtc.handle()) + }) + .unwrap_or(false) + }) + .partition(|&&plane| { + // Get the plane properties from a handle + if let Ok(props) = drm.get_properties(plane) { + let (ids, vals) = props.as_props_and_values(); + for (&id, &val) in ids.iter().zip(vals.iter()) { + if let Ok(info) = drm.get_property(id) { + // Checks if the plane is a primary plane, and returns true if it is, + // if not it returns false + if info.name().to_str().map(|x| x == "type").unwrap_or(false) { + return val == (PlaneType::Primary as u32).into(); + } + } + } + } + false + }); + + // Get the first (best) plane we find, or the first compatibile plane + *p_better_planes.get(0).unwrap_or(&p_compatible_planes[0]) +} + +fn find_card_path(seat_name: &str) -> Result { + let mut enumerator = Enumerator::new().map_err(|e| { + os_error!(OsError::KmsError(format!( + "failed to open udev enumerator: {}", + e + ))) + })?; + + enumerator.match_subsystem("drm").map_err(|e| { + os_error!(OsError::KmsError(format!( + "failed to enumerate drm subsystem: {}", + e + ))) + })?; + + enumerator.match_sysname("card[0-9]*").map_err(|e| { + os_error!(OsError::KmsError(format!( + "failed to find a valid card: {}", + e + ))) + })?; + + enumerator + .scan_devices() + .map_err(|e| os_error!(OsError::KmsError(format!("failed to scan devices: {}", e))))? + .filter(|device| { + let dev_seat_name = device + .property_value("ID_SEAT") + .map(|x| x.to_os_string()) + .unwrap_or_else(|| std::ffi::OsString::from("seat0")); + if dev_seat_name == seat_name { + if let Ok(Some(pci)) = device.parent_with_subsystem(Path::new("pci")) { + if let Some(id) = pci.attribute_value("boot_vga") { + return id == "1"; + } + } + } + false + }) + .flat_map(|device| device.devnode().map(std::path::PathBuf::from)) + .next() + .or_else(|| { + enumerator + .scan_devices() + .ok()? + .filter(|device| { + device + .property_value("ID_SEAT") + .map(|x| x.to_os_string()) + .unwrap_or_else(|| std::ffi::OsString::from("seat0")) + == seat_name + }) + .flat_map(|device| device.devnode().map(std::path::PathBuf::from)) + .next() + }) + .ok_or_else(|| os_error!(OsError::KmsMisc("failed to find suitable GPU"))) +} + +pub struct EventLoop { + /// Event loop. + event_loop: calloop::EventLoop<'static, EventSink>, + + /// Pending user events. + pending_user_events: Rc>>, + + /// Sender of user events. + user_events_sender: calloop::channel::Sender, + + /// Window target. + window_target: event_loop::EventLoopWindowTarget, +} + +impl EventLoop { + pub fn new() -> Result, error::OsError> { + #[cfg(feature = "kms-ext")] + // When we create the seat here, we should probably wait for it to become active before we + // use it. + let mut seat = { + use std::sync::atomic::Ordering; + // Allows us to know when the seat becomes active + let active = Arc::new(AtomicBool::new(false)); + let t_active = active.clone(); + let mut s = libseat::Seat::open( + move |_, event| { + if let libseat::SeatEvent::Enable = event { + t_active.store(true, Ordering::SeqCst); + } + }, + None, + ) + .map_err(|e| os_error!(OsError::KmsError(format!("failed to open libseat: {}", e))))?; + + // While our seat is not active dispatch it so that the seat will activate + while !active.load(Ordering::SeqCst) { + s.dispatch(-1).map_err(|e| { + os_error!(OsError::KmsError(format!("failed to dispatch seat: {}", e))) + })?; + } + s + }; + + #[cfg(feature = "kms-ext")] + // Safety + // + // This string value has the same lifetime as the seat in question, and will not be dropped + // until the seat is, which is not before `udev_assign_seat` is run. + let seat_name = unsafe { std::mem::transmute::<&str, &'static str>(seat.name()) }; + #[cfg(not(feature = "kms-ext"))] + let seat_name = "seat0"; + + // find_card_path uses `udev` to enumerate the cards that are currently available, and then + // choose the first (usually perferred) one + let card_path = std::env::var("WINIT_DRM_CARD") + .ok() + .map_or_else(|| find_card_path(seat_name), |p| Ok(Into::into(p)))?; + + #[cfg(feature = "kms-ext")] + // Opening the card using our seat allows us to do so unprivallaged + let dev = seat + .open_device(&card_path) + .map_err(|e| { + os_error!(OsError::KmsError(format!( + "failed to initialize DRM: {}", + e + ))) + })? + .1; + + #[cfg(not(feature = "kms-ext"))] + // Opening this card with no seat present means that we must have root + // (or be part of the `video` user group) + let dev = std::os::unix::prelude::IntoRawFd::into_raw_fd( + std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(&card_path) + .map_err(|e| { + os_error!(OsError::KmsError(format!( + "failed to initialize DRM: {}", + e + ))) + })?, + ); + let drm = Card(std::sync::Arc::new(dev)); + + #[cfg(feature = "kms-ext")] + // Using our seat to open our input manager allows us to do so unprivallaged + let mut input = input::Libinput::new_with_udev(Interface(seat, HashMap::new())); + #[cfg(not(feature = "kms-ext"))] + // Opening our input manager with no seat means we must do so as root + // (or be part of the `input` user group) + let mut input = input::Libinput::new_with_udev(Interface); + + input.udev_assign_seat(seat_name).unwrap(); + + // XKB allows us to keep track of the state of the keyboard and produce keyboard events + // very similarly to how a Wayland Compositor would. + let xkb_ctx = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); + // Empty strings translates to "default" for XKB (in this context) + let keymap = xkb::Keymap::new_from_names( + &xkb_ctx, + "", + "", + "", + "", + std::env::var("WINIT_XKB_OPTIONS").ok(), + xkb::KEYMAP_COMPILE_NO_FLAGS, + ) + .ok_or_else(|| os_error!(OsError::KmsMisc("failed to compile XKB keymap")))?; + + let state = xkb::State::new(&keymap); + + // It's not a strict requirement that we use a compose table, but it's ***sooo*** useful + // when using an english keyboard to write in other languages. Or even just speacial + // charecters + // + // For example, to type è, you would use + + + // Or to type •, you would use + <.> + <=> + let compose_table = xkb::compose::Table::new_from_locale( + &xkb_ctx, + // These env variables in Linux are the most likely to contain your locale, + // "en_US.UTF-8" for example + std::env::var_os("LC_ALL") + .unwrap_or_else(|| { + std::env::var_os("LC_CTYPE").unwrap_or_else(|| { + std::env::var_os("LANG").unwrap_or_else(|| std::ffi::OsString::from("C")) + }) + }) + .as_os_str(), + xkb::compose::COMPILE_NO_FLAGS, + ) + .map_err(|_| { + // e ^^^ would return () + os_error!(OsError::KmsMisc("failed to compile XKB compose table")) + })?; + let xkb_compose = xkb::compose::State::new(&compose_table, xkb::compose::STATE_NO_FLAGS); + + // Allows use to use the non-legacy atomic system + drm::Device::set_client_capability(&drm, drm::ClientCapability::Atomic, true).map_err( + |e| { + os_error!(OsError::KmsError(format!( + "drm device does not support atomic modesetting :{}", + e + ))) + }, + )?; + + // Load the information. + let res = drm.resource_handles().map_err(|e| { + os_error!(OsError::KmsError(format!( + "could not load normal resource ids: {}", + e + ))) + })?; + + // Enumerate available connectors + let coninfo: Vec = res + .connectors() + .iter() + .flat_map(|con| drm.get_connector(*con)) + .collect(); + + // Enumerate available CRTCs + let crtcinfo: Vec = res + .crtcs() + .iter() + .flat_map(|crtc| drm.get_crtc(*crtc)) + .collect(); + + // Filter each connector until we find one that's connected. + let con = coninfo + .iter() + .find(|&i| i.state() == connector::State::Connected) + .ok_or_else(|| os_error!(OsError::KmsMisc("no connected connectors")))?; + + // Get the first (usually perferred) CRTC + let crtc = crtcinfo + .get(0) + .ok_or_else(|| os_error!(OsError::KmsMisc("no crtcs found")))?; + + // Get the perferred (or first) mode + let &mode = con + .modes() + .iter() + .find(|f| f.mode_type().contains(ModeTypeFlags::PREFERRED)) + .or_else(|| con.modes().get(0)) + .ok_or_else(|| os_error!(OsError::KmsMisc("no modes found on connector")))?; + + *MODE.lock() = Some(mode); + + // Enumerate available planes + let planes = drm + .plane_handles() + .map_err(|e| os_error!(OsError::KmsError(format!("could not list planes: {}", e))))?; + + let p_plane = find_plane(planes, res, crtc, &drm); + + let (disp_width, disp_height) = mode.size(); + + let event_loop: calloop::EventLoop<'static, EventSink> = + calloop::EventLoop::try_new().unwrap(); + + let handle = event_loop.handle(); + + // A source of user events. + let pending_user_events = Rc::new(RefCell::new(Vec::new())); + let pending_user_events_clone = pending_user_events.clone(); + let (user_events_sender, user_events_channel) = calloop::channel::channel(); + + // User events channel. + handle + .insert_source(user_events_channel, move |event, _, _| { + if let calloop::channel::Event::Msg(msg) = event { + pending_user_events_clone.borrow_mut().push(msg); + } + }) + .unwrap(); + + // An event's loop awakener to wake up for redraw events from winit's windows. + let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping().unwrap(); + + let event_sink = EventSink::new(); + + // Handler of redraw requests. + handle + .insert_source( + event_loop_awakener_source, + move |_event, _metadata, data| { + data.push(Event::RedrawRequested(window_id!())); + }, + ) + .unwrap(); + + // This is used so that when you hold down a key, the same `KeyboardInput` event will be + // repeated until the key is released or another key is pressed down + let repeat_handler = calloop::timer::Timer::new().unwrap(); + + let repeat_handle = repeat_handler.handle(); + + let repeat_loop: calloop::Dispatcher< + 'static, + calloop::timer::Timer<(KeyboardInput, Option)>, + EventSink, + > = calloop::Dispatcher::new( + repeat_handler, + move |event, metadata, data: &mut EventSink| { + data.push(Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::KeyboardInput { + device_id: DeviceId(platform_impl::DeviceId::Kms(super::DeviceId)), + input: event.0, + is_synthetic: false, + }, + }); + + if let Some(c) = event.1 { + data.push(Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::ReceivedCharacter(c), + }); + } + + // Repeat the key with the same key event as was input from the LibinputInterface + metadata.add_timeout(Duration::from_millis(REPEAT_RATE), event); + }, + ); + + // It is an Arc> so that windows can change the cursor position + let cursor_arc = Arc::new(Mutex::new(PhysicalPosition::new(0.0, 0.0))); + + // Our input handler + let input_backend: LibinputInputBackend = LibinputInputBackend::new( + input, + (disp_width.into(), disp_height.into()), // plane, fb + repeat_handle, + state, + keymap, + xkb_compose, + cursor_arc.clone(), + ); + + // When an input is received, add it to our EventSink + let input_loop: calloop::Dispatcher<'static, LibinputInputBackend, EventSink> = + calloop::Dispatcher::new( + input_backend, + move |event, _metadata, data: &mut EventSink| { + data.push(event); + }, + ); + + handle.register_dispatcher(input_loop).unwrap(); + handle.register_dispatcher(repeat_loop).unwrap(); + + let window_target = event_loop::EventLoopWindowTarget { + p: platform_impl::EventLoopWindowTarget::Kms(EventLoopWindowTarget { + connector: con.clone(), + crtc: *crtc, + device: drm, + plane: p_plane, + cursor_arc, + event_loop_handle: handle, + event_sink, + event_loop_awakener, + _marker: PhantomData, + }), + _marker: PhantomData, + }; + + Ok(EventLoop { + event_loop, + pending_user_events, + user_events_sender, + window_target, + }) + } + + pub fn run(mut self, callback: F) -> ! + where + F: FnMut(Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow) + 'static, + { + let exit_code = self.run_return(callback); + std::process::exit(exit_code); + } + + pub fn run_return(&mut self, mut callback: F) -> i32 + where + F: FnMut(Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + let mut control_flow = ControlFlow::Poll; + let pending_user_events = self.pending_user_events.clone(); + let mut event_sink_back_buffer = Vec::new(); + + callback( + Event::NewEvents(StartCause::Init), + &self.window_target, + &mut control_flow, + ); + + callback( + Event::RedrawRequested(window_id!()), + &self.window_target, + &mut control_flow, + ); + + let exit_code = loop { + match control_flow { + ControlFlow::ExitWithCode(code) => break code, + ControlFlow::Poll => { + // Non-blocking dispatch. + let timeout = Duration::from_millis(0); + if let Err(error) = self.loop_dispatch(Some(timeout)) { + break error.raw_os_error().unwrap_or(1); + } + + callback( + Event::NewEvents(StartCause::Poll), + &self.window_target, + &mut control_flow, + ); + } + ControlFlow::Wait => { + if let Err(error) = self.loop_dispatch(None) { + break error.raw_os_error().unwrap_or(1); + } + + callback( + Event::NewEvents(StartCause::WaitCancelled { + start: Instant::now(), + requested_resume: None, + }), + &self.window_target, + &mut control_flow, + ); + } + ControlFlow::WaitUntil(deadline) => { + let start = Instant::now(); + + // Compute the amount of time we'll block for. + let duration = if deadline > start { + deadline - start + } else { + Duration::from_millis(0) + }; + + if let Err(error) = self.loop_dispatch(Some(duration)) { + break error.raw_os_error().unwrap_or(1); + } + + let now = Instant::now(); + + if now < deadline { + callback( + Event::NewEvents(StartCause::WaitCancelled { + start, + requested_resume: Some(deadline), + }), + &self.window_target, + &mut control_flow, + ) + } else { + callback( + Event::NewEvents(StartCause::ResumeTimeReached { + start, + requested_resume: deadline, + }), + &self.window_target, + &mut control_flow, + ) + } + } + } + + // Handle pending user events. We don't need back buffer, since we can't dispatch + // user events indirectly via callback to the user. + for user_event in pending_user_events.borrow_mut().drain(..) { + sticky_exit_callback( + Event::UserEvent(user_event), + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + + // The purpose of the back buffer and that swap is to not hold borrow_mut when + // we're doing callback to the user, since we can double borrow if the user decides + // to create a window in one of those callbacks. + self.with_window_target(|window_target| { + let state = &mut window_target.event_sink; + std::mem::swap::>>(&mut event_sink_back_buffer, state); + }); + + // Handle pending window events. + for event in event_sink_back_buffer.drain(..) { + let event = event.map_nonuser_event().unwrap(); + sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); + } + + // Send events cleared. + sticky_exit_callback( + Event::MainEventsCleared, + &self.window_target, + &mut control_flow, + &mut callback, + ); + + // Send RedrawEventCleared. + sticky_exit_callback( + Event::RedrawEventsCleared, + &self.window_target, + &mut control_flow, + &mut callback, + ); + }; + + callback(Event::LoopDestroyed, &self.window_target, &mut control_flow); + exit_code + } + + #[inline] + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy::new(self.user_events_sender.clone()) + } + + #[inline] + pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { + &self.window_target + } + + fn with_window_target) -> U>(&mut self, f: F) -> U { + let state = match &mut self.window_target.p { + platform_impl::EventLoopWindowTarget::Kms(window_target) => window_target, + #[cfg(any(feature = "x11", feature = "wayland"))] + _ => unreachable!(), + }; + + f(state) + } + + fn loop_dispatch>>( + &mut self, + timeout: D, + ) -> std::io::Result<()> { + let state = match &mut self.window_target.p { + platform_impl::EventLoopWindowTarget::Kms(window_target) => { + &mut window_target.event_sink + } + #[cfg(any(feature = "x11", feature = "wayland"))] + _ => unreachable!(), + }; + + self.event_loop.dispatch(timeout, state) + } +} + +/// A handle that can be sent across the threads and used to wake up the `EventLoop`. +pub struct EventLoopProxy { + user_events_sender: calloop::channel::Sender, +} + +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + EventLoopProxy { + user_events_sender: self.user_events_sender.clone(), + } + } +} + +impl EventLoopProxy { + pub fn new(user_events_sender: calloop::channel::Sender) -> Self { + Self { user_events_sender } + } + + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.user_events_sender + .send(event) + .map_err(|SendError(error)| EventLoopClosed(error)) + } +} diff --git a/src/platform_impl/linux/kms/input.rs b/src/platform_impl/linux/kms/input.rs new file mode 100644 index 0000000000..9e03a488b8 --- /dev/null +++ b/src/platform_impl/linux/kms/input.rs @@ -0,0 +1,681 @@ +use crate::{ + dpi::PhysicalPosition, + event::DeviceId, + event::{ + DeviceEvent, ElementState, Event, Force, KeyboardInput, ModifiersState, MouseButton, + MouseScrollDelta, Touch, TouchPhase, WindowEvent, + }, + platform_impl::{self, xkb_keymap}, + window::WindowId, +}; +use input::{ + event::{ + keyboard::KeyboardEventTrait, + pointer::PointerScrollEvent, + tablet_pad::{ButtonState, KeyState}, + tablet_tool::{TabletToolEventTrait, TipState}, + touch::{TouchEventPosition, TouchEventSlot}, + }, + LibinputInterface, +}; +use parking_lot::Mutex; +#[cfg(feature = "kms-ext")] +use std::collections::HashMap; +use std::{ + os::unix::prelude::{AsRawFd, FromRawFd, RawFd}, + path::Path, + sync::Arc, + time::Duration, +}; + +use calloop::{EventSource, Interest, Mode, Poll, PostAction, Readiness, Token, TokenFactory}; +use xkbcommon::xkb; + +pub const REPEAT_RATE: u64 = 25; +pub const REPEAT_DELAY: u64 = 600; + +macro_rules! to_platform_impl { + ($p:ident, $params:expr) => { + $p(platform_impl::$p::Kms($params)) + }; +} + +macro_rules! window_id { + () => { + to_platform_impl!(WindowId, super::WindowId) + }; +} + +macro_rules! device_id { + () => { + to_platform_impl!(DeviceId, super::DeviceId) + }; +} + +#[cfg(feature = "kms-ext")] +pub struct Interface(pub libseat::Seat, pub HashMap); +#[cfg(not(feature = "kms-ext"))] +pub struct Interface; + +#[cfg(feature = "kms-ext")] +impl LibinputInterface for Interface { + fn open_restricted(&mut self, path: &Path, _flags: i32) -> Result { + self.0 + .open_device(&path) + .map(|(id, file)| { + self.1.insert(file, id); + file + }) + .map_err(|err| err.into()) + } + + fn close_restricted(&mut self, fd: RawFd) { + if let Some(dev) = self.1.get(&fd).copied() { + self.0.close_device(dev).unwrap(); + } + + unsafe { std::fs::File::from_raw_fd(fd) }; + } +} + +#[cfg(not(feature = "kms-ext"))] +impl LibinputInterface for Interface { + fn open_restricted(&mut self, path: &Path, flags: i32) -> Result { + use std::os::unix::prelude::*; + + std::fs::OpenOptions::new() + .custom_flags(flags) + .read(flags & libc::O_RDWR != 0) + .write((flags & libc::O_WRONLY != 0) | (flags & libc::O_RDWR != 0)) + .open(path) + .map(|file| file.into_raw_fd()) + .map_err(|err| err.raw_os_error().unwrap()) + } + fn close_restricted(&mut self, fd: RawFd) { + unsafe { + std::fs::File::from_raw_fd(fd); + } + } +} + +pub struct LibinputInputBackend { + context: input::Libinput, + xkb_ctx: xkb::State, + xkb_keymap: xkb::Keymap, + xkb_compose: xkb::compose::State, + token: Token, + touch_location: PhysicalPosition, + screen_size: (u32, u32), + modifiers: ModifiersState, + cursor_positon: Arc>>, + timer_handle: calloop::timer::TimerHandle<(KeyboardInput, Option)>, +} + +impl LibinputInputBackend { + /// Initialize a new [`LibinputInputBackend`] from a given already initialized + /// [libinput context](input::Libinput). + pub fn new( + context: input::Libinput, + screen_size: (u32, u32), + timer_handle: calloop::timer::TimerHandle<(KeyboardInput, Option)>, + xkb_ctx: xkb::State, + xkb_keymap: xkb::Keymap, + xkb_compose: xkb::compose::State, + cursor_positon: Arc>>, + ) -> Self { + LibinputInputBackend { + context, + token: Token::invalid(), + touch_location: PhysicalPosition::new(0.0, 0.0), + modifiers: ModifiersState::empty(), + cursor_positon, + screen_size, + timer_handle, + xkb_ctx, + xkb_keymap, + xkb_compose, + } + } +} + +impl AsRawFd for LibinputInputBackend { + fn as_raw_fd(&self) -> RawFd { + self.context.as_raw_fd() + } +} + +macro_rules! handle_device_event { + ($ev:expr,$callback:expr) => { + match $ev { + input::event::DeviceEvent::Added(_) => { + $callback( + Event::DeviceEvent { + device_id: device_id!(), + event: DeviceEvent::Added, + }, + &mut (), + ); + } + input::event::DeviceEvent::Removed(_) => { + $callback( + Event::DeviceEvent { + device_id: device_id!(), + event: DeviceEvent::Removed, + }, + &mut (), + ); + } + _ => {} + } + }; +} + +macro_rules! handle_touch_event { + ($self:expr,$ev:expr,$callback:expr) => { + match $ev { + input::event::TouchEvent::Up(e) => $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::Touch(Touch { + device_id: device_id!(), + phase: TouchPhase::Ended, + location: $self.touch_location, + force: None, + id: e.slot().unwrap() as u64, + }), + }, + &mut (), + ), + input::event::TouchEvent::Down(e) => { + $self.touch_location.x = e.x_transformed($self.screen_size.0); + $self.touch_location.y = e.y_transformed($self.screen_size.1); + + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::Touch(Touch { + device_id: device_id!(), + phase: TouchPhase::Started, + location: $self.touch_location, + force: None, + id: e.slot().unwrap() as u64, + }), + }, + &mut (), + ) + } + input::event::TouchEvent::Motion(e) => { + $self.touch_location.x = e.x_transformed($self.screen_size.0); + $self.touch_location.y = e.y_transformed($self.screen_size.1); + + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::Touch(Touch { + device_id: device_id!(), + phase: TouchPhase::Moved, + location: $self.touch_location, + force: None, + id: e.slot().unwrap() as u64, + }), + }, + &mut (), + ); + } + input::event::TouchEvent::Cancel(e) => $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::Touch(Touch { + device_id: device_id!(), + phase: TouchPhase::Cancelled, + location: $self.touch_location, + force: None, + id: e.slot().unwrap() as u64, + }), + }, + &mut (), + ), + input::event::TouchEvent::Frame(_) => $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::Touch(Touch { + device_id: device_id!(), + phase: TouchPhase::Ended, + location: $self.touch_location, + force: None, + id: 0, // e.slot().unwrap() as u64, + }), + }, + &mut (), + ), + _ => {} + } + }; +} + +macro_rules! handle_tablet_tool_event { + ($self:expr,$ev:expr,$callback:expr) => { + match $ev { + input::event::TabletToolEvent::Tip(e) => $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::Touch(Touch { + device_id: device_id!(), + phase: match e.tip_state() { + TipState::Down => TouchPhase::Started, + TipState::Up => TouchPhase::Ended, + }, + location: PhysicalPosition::new( + e.x_transformed($self.screen_size.0), + e.y_transformed($self.screen_size.1), + ), + force: Some(Force::Calibrated { + force: e.pressure(), + max_possible_force: 1.0, + altitude_angle: None, + }), + id: 0, + }), + }, + &mut (), + ), + input::event::TabletToolEvent::Button(e) => { + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::MouseInput { + device_id: device_id!(), + state: match e.button_state() { + ButtonState::Pressed => ElementState::Pressed, + ButtonState::Released => ElementState::Released, + }, + button: match e.button() { + 1 => MouseButton::Right, + 2 => MouseButton::Middle, + _ => MouseButton::Left, + }, + modifiers: $self.modifiers, + }, + }, + &mut (), + ); + + $callback( + Event::DeviceEvent { + device_id: device_id!(), + event: DeviceEvent::Button { + button: e.button(), + state: match e.button_state() { + ButtonState::Pressed => ElementState::Pressed, + ButtonState::Released => ElementState::Released, + }, + }, + }, + &mut (), + ); + } + _ => {} + } + }; +} + +macro_rules! handle_pointer_event { + ($self:expr,$ev:expr,$callback:expr) => { + match $ev { + input::event::PointerEvent::Motion(e) => { + let mut lock = $self.cursor_positon.lock(); + + lock.x += e.dx(); + lock.x = lock.x.clamp(0.0, $self.screen_size.0 as f64); + + lock.y += e.dy(); + lock.y = lock.y.clamp(0.0, $self.screen_size.1 as f64); + + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::CursorMoved { + device_id: device_id!(), + position: *lock, + modifiers: $self.modifiers, + }, + }, + &mut (), + ); + + $callback( + Event::DeviceEvent { + device_id: device_id!(), + event: DeviceEvent::MouseMotion { + delta: (e.dx(), e.dy()), + }, + }, + &mut (), + ); + } + + input::event::PointerEvent::Button(e) => { + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::MouseInput { + device_id: device_id!(), + state: match e.button_state() { + ButtonState::Pressed => ElementState::Pressed, + ButtonState::Released => ElementState::Released, + }, + button: match e.button() { + 1 => MouseButton::Right, + 2 => MouseButton::Middle, + _ => MouseButton::Left, + }, + modifiers: $self.modifiers, + }, + }, + &mut (), + ); + + $callback( + Event::DeviceEvent { + device_id: device_id!(), + event: DeviceEvent::Button { + button: e.button(), + state: match e.button_state() { + ButtonState::Pressed => ElementState::Pressed, + ButtonState::Released => ElementState::Released, + }, + }, + }, + &mut (), + ); + } + + input::event::PointerEvent::ScrollWheel(e) => { + use input::event::pointer::Axis; + + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::MouseWheel { + device_id: device_id!(), + delta: MouseScrollDelta::LineDelta( + if e.has_axis(Axis::Horizontal) { + e.scroll_value(Axis::Horizontal) as f32 + } else { + 0.0 + }, + if e.has_axis(Axis::Vertical) { + e.scroll_value(Axis::Vertical) as f32 + } else { + 0.0 + }, + ), + phase: TouchPhase::Moved, + modifiers: $self.modifiers, + }, + }, + &mut (), + ); + } + + input::event::PointerEvent::ScrollFinger(e) => { + use input::event::pointer::Axis; + + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::MouseWheel { + device_id: device_id!(), + delta: MouseScrollDelta::PixelDelta(PhysicalPosition::new( + if e.has_axis(Axis::Horizontal) { + e.scroll_value(Axis::Horizontal) + } else { + 0.0 + }, + if e.has_axis(Axis::Vertical) { + e.scroll_value(Axis::Vertical) + } else { + 0.0 + }, + )), + phase: TouchPhase::Moved, + modifiers: $self.modifiers, + }, + }, + &mut (), + ); + } + + input::event::PointerEvent::MotionAbsolute(e) => { + let mut lock = $self.cursor_positon.lock(); + + lock.x = e.absolute_x_transformed($self.screen_size.0); + + lock.y = e.absolute_y_transformed($self.screen_size.1); + + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::CursorMoved { + device_id: device_id!(), + position: *lock, + modifiers: $self.modifiers, + }, + }, + &mut (), + ); + } + _ => {} + } + }; +} + +macro_rules! handle_keyboard_event { + ($self:expr,$ev:expr,$callback:expr) => {{ + let state = match $ev.key_state() { + KeyState::Pressed => ElementState::Pressed, + KeyState::Released => ElementState::Released, + }; + + let k = if let input::event::KeyboardEvent::Key(key) = $ev { + key.key() + } else { + unreachable!() + }; + + let key_offset = k + 8; + let keysym = $self.xkb_ctx.key_get_one_sym(key_offset); + let virtual_keycode = xkb_keymap::keysym_to_vkey(keysym); + + $self.xkb_ctx.update_key( + key_offset, + match state { + ElementState::Pressed => xkb::KeyDirection::Down, + ElementState::Released => xkb::KeyDirection::Up, + }, + ); + + #[allow(deprecated)] + let input = KeyboardInput { + scancode: k, + state: state.clone(), + virtual_keycode, + modifiers: $self.modifiers, + }; + + $self.timer_handle.cancel_all_timeouts(); + + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::KeyboardInput { + device_id: device_id!(), + input, + is_synthetic: false, + }, + }, + &mut (), + ); + + if let ElementState::Pressed = state { + $self.xkb_compose.feed(keysym); + + match $self.xkb_compose.status() { + xkb::compose::Status::Composed => { + if let Some(c) = $self.xkb_compose.utf8().and_then(|f| f.chars().next()) { + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::ReceivedCharacter(c), + }, + &mut (), + ); + } + $self.xkb_compose.reset(); + } + xkb::compose::Status::Cancelled | xkb::compose::Status::Nothing => { + let should_repeat = $self.xkb_keymap.key_repeats(key_offset); + let ch = $self.xkb_ctx.key_get_utf8(key_offset).chars().next(); + + if should_repeat { + $self + .timer_handle + .add_timeout(Duration::from_millis(REPEAT_DELAY), (input, ch)); + } + + if let Some(c) = ch { + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::ReceivedCharacter(c), + }, + &mut (), + ); + } + } + _ => {} + } + } + match keysym { + xkb_keymap::XKB_KEY_Alt_L | xkb_keymap::XKB_KEY_Alt_R => { + match state { + ElementState::Pressed => $self.modifiers |= ModifiersState::ALT, + ElementState::Released => $self.modifiers.remove(ModifiersState::ALT), + } + + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::ModifiersChanged($self.modifiers), + }, + &mut (), + ); + } + + xkb_keymap::XKB_KEY_Shift_L | xkb_keymap::XKB_KEY_Shift_R => { + match state { + ElementState::Pressed => $self.modifiers |= ModifiersState::SHIFT, + ElementState::Released => $self.modifiers.remove(ModifiersState::SHIFT), + } + + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::ModifiersChanged($self.modifiers), + }, + &mut (), + ); + } + + xkb_keymap::XKB_KEY_Control_L | xkb_keymap::XKB_KEY_Control_R => { + match state { + ElementState::Pressed => $self.modifiers |= ModifiersState::CTRL, + ElementState::Released => $self.modifiers.remove(ModifiersState::CTRL), + } + + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::ModifiersChanged($self.modifiers), + }, + &mut (), + ); + } + + xkb_keymap::XKB_KEY_Meta_L | xkb_keymap::XKB_KEY_Meta_R => { + match state { + ElementState::Pressed => $self.modifiers |= ModifiersState::LOGO, + ElementState::Released => $self.modifiers.remove(ModifiersState::LOGO), + } + + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::ModifiersChanged($self.modifiers), + }, + &mut (), + ); + } + + xkb_keymap::XKB_KEY_Sys_Req | xkb_keymap::XKB_KEY_Print => { + if $self.modifiers.is_empty() { + $callback( + Event::WindowEvent { + window_id: window_id!(), + event: WindowEvent::CloseRequested, + }, + &mut (), + ); + } + } + _ => {} + } + }}; +} + +impl EventSource for LibinputInputBackend { + type Event = Event<'static, ()>; + type Metadata = (); + type Ret = (); + + fn process_events( + &mut self, + _: Readiness, + token: Token, + mut callback: F, + ) -> std::io::Result + where + F: FnMut(Self::Event, &mut ()) -> Self::Ret, + { + if token == self.token { + self.context.dispatch()?; + + for event in &mut self.context { + match event { + input::Event::Device(ev) => handle_device_event!(ev, callback), + input::Event::Touch(ev) => handle_touch_event!(self, ev, callback), + input::Event::Tablet(ev) => handle_tablet_tool_event!(self, ev, callback), + input::Event::Pointer(ev) => handle_pointer_event!(self, ev, callback), + input::Event::Keyboard(ev) => handle_keyboard_event!(self, ev, callback), + _ => {} + } + } + } + Ok(PostAction::Continue) + } + + fn register(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> std::io::Result<()> { + self.token = factory.token(); + poll.register(self.as_raw_fd(), Interest::READ, Mode::Level, self.token) + } + + fn reregister(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> std::io::Result<()> { + self.token = factory.token(); + poll.reregister(self.as_raw_fd(), Interest::READ, Mode::Level, self.token) + } + + fn unregister(&mut self, poll: &mut Poll) -> std::io::Result<()> { + self.token = Token::invalid(); + poll.unregister(self.as_raw_fd()) + } +} diff --git a/src/platform_impl/linux/kms/mod.rs b/src/platform_impl/linux/kms/mod.rs new file mode 100644 index 0000000000..5ecddf114c --- /dev/null +++ b/src/platform_impl/linux/kms/mod.rs @@ -0,0 +1,161 @@ +use crate::dpi::{PhysicalPosition, PhysicalSize}; + +pub mod event_loop; +pub mod input; +pub mod window; +use crate::{monitor, platform_impl}; +pub use drm::SystemError; +use drm::{ + control::{Device as ControlDevice, *}, + Device, +}; +pub use event_loop::EventLoop; +pub use event_loop::EventLoopProxy; +pub use event_loop::EventLoopWindowTarget; +use parking_lot::Mutex; +use std::os::unix; +use std::os::unix::prelude::FromRawFd; +use std::sync::Arc; +pub use window::Window; + +pub static MODE: Mutex> = parking_lot::const_mutex(None); + +#[derive(Debug, Clone)] +/// A simple wrapper for a device node. +pub struct Card(pub(crate) Arc); + +/// Implementing `AsRawFd` is a prerequisite to implementing the traits found +/// in this crate. Here, we are just calling `as_raw_fd()` on the inner File. +impl unix::io::AsRawFd for Card { + fn as_raw_fd(&self) -> unix::io::RawFd { + *self.0 + } +} + +impl Drop for Card { + fn drop(&mut self) { + unsafe { std::fs::File::from_raw_fd(*self.0) }; + } +} + +/// With `AsRawFd` implemented, we can now implement `drm::Device`. +impl Device for Card {} +impl ControlDevice for Card {} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeviceId; + +#[allow(dead_code)] +impl DeviceId { + pub const unsafe fn dummy() -> Self { + DeviceId + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MonitorHandle { + connector: connector::Info, + name: String, +} + +impl PartialOrd for MonitorHandle { + fn partial_cmp(&self, other: &Self) -> Option { + Some( + self.connector + .interface_id() + .cmp(&other.connector.interface_id()), + ) + } +} + +impl Ord for MonitorHandle { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.connector + .interface_id() + .cmp(&other.connector.interface_id()) + } +} + +impl MonitorHandle { + #[inline] + pub fn name(&self) -> Option { + Some(self.name.to_string()) + } + + #[inline] + pub fn native_identifier(&self) -> u32 { + self.connector.interface_id() + } + + #[inline] + pub fn size(&self) -> PhysicalSize { + let size = self.connector.modes()[0].size(); + PhysicalSize::new(size.0 as u32, size.1 as u32) + } + + #[inline] + pub fn position(&self) -> PhysicalPosition { + PhysicalPosition::new(0, 0) + } + + #[inline] + pub fn scale_factor(&self) -> f64 { + 1.0 + } + + #[inline] + pub fn video_modes(&self) -> impl Iterator { + let modes = self.connector.modes().to_vec(); + let monitor = self.connector.clone(); + modes.into_iter().map(move |f| monitor::VideoMode { + video_mode: platform_impl::VideoMode::Kms(VideoMode { + mode: f, + connector: monitor.clone(), + }), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct VideoMode { + mode: Mode, + connector: connector::Info, +} + +impl VideoMode { + #[inline] + pub fn size(&self) -> PhysicalSize { + let size = self.mode.size(); + PhysicalSize::new(size.0 as u32, size.1 as u32) + } + + #[inline] + pub fn bit_depth(&self) -> u16 { + 32 + } + + #[inline] + pub fn refresh_rate(&self) -> u16 { + self.mode.vrefresh() as u16 + } + + #[inline] + pub fn monitor(&self) -> monitor::MonitorHandle { + monitor::MonitorHandle { + inner: platform_impl::MonitorHandle::Kms(MonitorHandle { + connector: self.connector.clone(), + name: self.mode.name().to_string_lossy().into_owned(), + }), + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WindowId; + +#[allow(dead_code)] +impl WindowId { + pub const unsafe fn dummy() -> Self { + Self + } +} diff --git a/src/platform_impl/linux/kms/window.rs b/src/platform_impl/linux/kms/window.rs new file mode 100644 index 0000000000..3b0051c77d --- /dev/null +++ b/src/platform_impl/linux/kms/window.rs @@ -0,0 +1,422 @@ +use std::{collections::VecDeque, os::unix::prelude::AsRawFd, sync::Arc}; + +use super::MODE; +use drm::control::*; +use parking_lot::Mutex; + +#[cfg(feature = "wayland")] +use sctk::reexports::calloop; + +use crate::error; + +use crate::{ + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, + error::{ExternalError, NotSupportedError}, + monitor::{MonitorHandle, VideoMode}, + platform::unix::Card, + platform_impl, + window::{CursorIcon, Fullscreen, WindowAttributes}, +}; + +pub struct Window { + connector: connector::Info, + crtc: crtc::Info, + ping: calloop::ping::Ping, + plane: plane::Handle, + cursor: Arc>>, + card: Card, +} + +fn find_prop_id( + card: &Card, + handle: T, + name: &'static str, +) -> Option { + let props = card.get_properties(handle).ok()?; + let (ids, _vals) = props.as_props_and_values(); + ids.iter() + .find(|&id| { + let info = card.get_property(*id).unwrap(); + info.name().to_str().map(|x| x == name).unwrap_or(false) + }) + .cloned() +} + +macro_rules! find_prop_id { + ($id_handle_1:expr,$handle:expr,$prop_name:literal) => { + find_prop_id(&$id_handle_1, $handle, $prop_name).ok_or_else(|| { + os_error!(platform_impl::OsError::KmsMisc(concat!( + "could not get ", + $prop_name + ))) + }) + }; +} + +macro_rules! add_property { + ($atomic_req:expr,$handle:expr,$id_handle_1:expr,$prop_name:literal,$property:expr,) => { + $atomic_req.add_property( + $handle, + find_prop_id!(&$id_handle_1, $handle, $prop_name)?, + $property, + ); + }; +} + +impl Window { + pub fn new( + event_loop_window_target: &super::event_loop::EventLoopWindowTarget, + _attributes: WindowAttributes, + _platform_attributes: platform_impl::PlatformSpecificWindowBuilderAttributes, + ) -> Result { + let mut atomic_req = atomic::AtomicModeReq::new(); + + add_property!( + atomic_req, + event_loop_window_target.connector.handle(), + event_loop_window_target.device, + "CRTC_ID", + property::Value::CRTC(Some(event_loop_window_target.crtc.handle())), + ); + + let mode = &(MODE.lock().ok_or_else(|| { + os_error!(platform_impl::OsError::KmsMisc("mode is not initialized")) + })?); + + let blob = event_loop_window_target + .device + .create_property_blob(mode) + .map_err(|_| os_error!(platform_impl::OsError::KmsMisc("failed to create blob")))?; + + add_property!( + atomic_req, + event_loop_window_target.crtc.handle(), + event_loop_window_target.device, + "MODE_ID", + blob, + ); + + add_property!( + atomic_req, + event_loop_window_target.crtc.handle(), + event_loop_window_target.device, + "ACTIVE", + property::Value::Boolean(true), + ); + + add_property!( + atomic_req, + event_loop_window_target.plane, + event_loop_window_target.device, + "CRTC_ID", + property::Value::CRTC(Some(event_loop_window_target.crtc.handle())), + ); + + add_property!( + atomic_req, + event_loop_window_target.plane, + event_loop_window_target.device, + "SRC_X", + property::Value::UnsignedRange(0), + ); + + add_property!( + atomic_req, + event_loop_window_target.plane, + event_loop_window_target.device, + "SRC_Y", + property::Value::UnsignedRange(0), + ); + + add_property!( + atomic_req, + event_loop_window_target.plane, + event_loop_window_target.device, + "SRC_W", + property::Value::UnsignedRange((mode.size().0 as u64) << 16), + ); + + add_property!( + atomic_req, + event_loop_window_target.plane, + event_loop_window_target.device, + "SRC_H", + property::Value::UnsignedRange((mode.size().1 as u64) << 16), + ); + + add_property!( + atomic_req, + event_loop_window_target.plane, + event_loop_window_target.device, + "CRTC_X", + property::Value::SignedRange(0), + ); + + add_property!( + atomic_req, + event_loop_window_target.plane, + event_loop_window_target.device, + "CRTC_Y", + property::Value::SignedRange(0), + ); + + add_property!( + atomic_req, + event_loop_window_target.plane, + event_loop_window_target.device, + "CRTC_W", + property::Value::UnsignedRange(mode.size().0 as u64), + ); + + add_property!( + atomic_req, + event_loop_window_target.plane, + event_loop_window_target.device, + "CRTC_H", + property::Value::UnsignedRange(mode.size().1 as u64), + ); + + event_loop_window_target + .device + .atomic_commit(AtomicCommitFlags::ALLOW_MODESET, atomic_req) + .map_err(|e| { + os_error!(platform_impl::OsError::KmsError(format!( + "failed to set mode: {}", + e + ))) + })?; + + Ok(Self { + connector: event_loop_window_target.connector.clone(), + crtc: event_loop_window_target.crtc, + plane: event_loop_window_target.plane, + cursor: event_loop_window_target.cursor_arc.clone(), + ping: event_loop_window_target.event_loop_awakener.clone(), + card: event_loop_window_target.device.clone(), + }) + } + #[inline] + pub fn id(&self) -> super::WindowId { + super::WindowId + } + + #[inline] + pub fn set_title(&self, _title: &str) {} + + #[inline] + pub fn set_visible(&self, _visible: bool) {} + + #[inline] + pub fn is_visible(&self) -> Option { + Some(true) + } + + #[inline] + pub fn outer_position(&self) -> Result, NotSupportedError> { + Ok(PhysicalPosition::new(0, 0)) + } + + #[inline] + pub fn inner_position(&self) -> Result, NotSupportedError> { + Ok(PhysicalPosition::new(0, 0)) + } + + #[inline] + pub fn set_outer_position(&self, _position: Position) {} + + #[inline] + pub fn inner_size(&self) -> PhysicalSize { + let size = MODE.lock().expect("mode is not initialized").size(); + PhysicalSize::new(size.0 as u32, size.1 as u32) + } + + #[inline] + pub fn outer_size(&self) -> PhysicalSize { + self.inner_size() + } + + #[inline] + pub fn set_inner_size(&self, _size: Size) {} + + #[inline] + pub fn set_min_inner_size(&self, _dimensions: Option) {} + + #[inline] + pub fn set_max_inner_size(&self, _dimensions: Option) {} + + #[inline] + pub fn set_resizable(&self, _resizable: bool) {} + + #[inline] + pub fn is_resizable(&self) -> bool { + false + } + + #[inline] + pub fn set_cursor_icon(&self, _cursor: CursorIcon) {} + + #[inline] + pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { + Ok(()) + } + + #[inline] + pub fn set_cursor_visible(&self, _visible: bool) {} + + #[inline] + pub fn drag_window(&self) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + #[inline] + pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + #[inline] + pub fn scale_factor(&self) -> f64 { + 1.0 + } + + #[inline] + pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { + *self.cursor.lock() = position.to_physical(1.0); + Ok(()) + } + + #[inline] + pub fn set_maximized(&self, _maximized: bool) {} + + #[inline] + pub fn is_maximized(&self) -> bool { + true + } + + #[inline] + pub fn set_minimized(&self, _minimized: bool) {} + + #[inline] + pub fn fullscreen(&self) -> Option { + Some(Fullscreen::Exclusive(VideoMode { + video_mode: platform_impl::VideoMode::Kms(super::VideoMode { + mode: MODE.lock().expect("mode is not initialized"), + connector: self.connector.clone(), + }), + })) + } + + #[inline] + pub fn set_fullscreen(&self, monitor: Option) { + if let Some(Fullscreen::Exclusive(fullscreen)) = monitor { + let modes = self.connector.modes().to_vec(); + let connector = self.connector.clone(); + if let Some(mo) = modes.into_iter().find(move |&f| { + VideoMode { + video_mode: platform_impl::VideoMode::Kms(super::VideoMode { + mode: f, + connector: connector.clone(), + }), + } == fullscreen + }) { + let mut atomic_req = atomic::AtomicModeReq::new(); + let blob = self.card.create_property_blob(&mo).unwrap(); + + atomic_req.add_property( + self.crtc.handle(), + find_prop_id!(&self.card, self.crtc.handle(), "MODE_ID").unwrap(), + blob, + ); + + atomic_req.add_property( + self.plane, + find_prop_id!(&self.card, self.plane, "SRC_W").unwrap(), + property::Value::UnsignedRange((mo.size().0 as u64) << 16), + ); + + atomic_req.add_property( + self.plane, + find_prop_id!(&self.card, self.plane, "SRC_H").unwrap(), + property::Value::UnsignedRange((mo.size().1 as u64) << 16), + ); + + atomic_req.add_property( + self.plane, + find_prop_id!(&self.card, self.plane, "CRTC_W").unwrap(), + property::Value::UnsignedRange(mo.size().0 as u64), + ); + + atomic_req.add_property( + self.plane, + find_prop_id!(&self.card, self.plane, "CRTC_H").unwrap(), + property::Value::UnsignedRange(mo.size().1 as u64), + ); + + self.card + .atomic_commit(AtomicCommitFlags::ALLOW_MODESET, atomic_req) + .unwrap(); + } + } + } + + #[inline] + pub fn set_decorations(&self, _decorations: bool) {} + + pub fn is_decorated(&self) -> bool { + false + } + + #[inline] + pub fn set_ime_position(&self, _position: Position) {} + + #[inline] + pub fn set_ime_allowed(&self, _allowed: bool) {} + + #[inline] + pub fn request_redraw(&self) { + self.ping.ping(); + } + + #[inline] + pub fn current_monitor(&self) -> Option { + Some(super::MonitorHandle { + connector: self.connector.clone(), + name: (*MODE.lock())?.name().to_string_lossy().into_owned(), + }) + } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + if let Some(mode) = *MODE.lock() { + self.card + .resource_handles() + .unwrap() + .connectors() + .iter() + .map(|f| super::MonitorHandle { + connector: self.card.get_connector(*f).unwrap(), + name: mode.name().to_string_lossy().into_owned(), + }) + .collect() + } else { + VecDeque::new() + } + } + + #[inline] + pub fn raw_window_handle(&self) -> raw_window_handle::DrmHandle { + let mut rwh = raw_window_handle::DrmHandle::empty(); + rwh.fd = self.card.as_raw_fd(); + rwh.plane = self.plane.into(); + rwh + } + + #[inline] + pub fn primary_monitor(&self) -> Option { + Some(MonitorHandle { + inner: platform_impl::MonitorHandle::Kms(super::MonitorHandle { + connector: self.connector.clone(), + name: (*MODE.lock())?.name().to_string_lossy().into_owned(), + }), + }) + } +} diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index cee7f94d8e..c63d0c1ec8 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -6,8 +6,8 @@ target_os = "openbsd" ))] -#[cfg(all(not(feature = "x11"), not(feature = "wayland")))] -compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); +#[cfg(all(not(feature = "x11"), not(feature = "wayland"), not(feature = "kms")))] +compile_error!("Please select a feature to build for unix: `x11`, `wayland`, `kms`"); #[cfg(feature = "wayland")] use std::error::Error; @@ -35,10 +35,14 @@ use crate::{ pub(crate) use crate::icon::RgbaIcon as PlatformIcon; +#[cfg(feature = "kms")] +pub mod kms; #[cfg(feature = "wayland")] pub mod wayland; #[cfg(feature = "x11")] pub mod x11; +#[cfg(any(feature = "kms", feature = "wayland"))] +pub mod xkb_keymap; /// Environment variable specifying which backend should be used on unix platform. /// @@ -50,11 +54,13 @@ pub mod x11; const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND"; #[derive(Debug, Copy, Clone, PartialEq, Hash)] -pub(crate) enum Backend { +pub enum Backend { #[cfg(feature = "x11")] X, #[cfg(feature = "wayland")] Wayland, + #[cfg(feature = "kms")] + Kms, } #[derive(Debug, Copy, Clone, PartialEq, Hash)] @@ -139,6 +145,10 @@ pub enum OsError { XMisc(&'static str), #[cfg(feature = "wayland")] WaylandMisc(&'static str), + #[cfg(feature = "kms")] + KmsError(String), + #[cfg(feature = "kms")] + KmsMisc(&'static str), } impl fmt::Display for OsError { @@ -150,6 +160,10 @@ impl fmt::Display for OsError { OsError::XMisc(e) => _f.pad(e), #[cfg(feature = "wayland")] OsError::WaylandMisc(e) => _f.pad(e), + #[cfg(feature = "kms")] + OsError::KmsError(ref e) => _f.pad(e), + #[cfg(feature = "kms")] + OsError::KmsMisc(e) => _f.pad(e), } } } @@ -159,6 +173,8 @@ pub enum Window { X(x11::Window), #[cfg(feature = "wayland")] Wayland(wayland::Window), + #[cfg(feature = "kms")] + Kms(kms::Window), } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -167,23 +183,28 @@ pub enum WindowId { X(x11::WindowId), #[cfg(feature = "wayland")] Wayland(wayland::WindowId), + #[cfg(feature = "kms")] + Kms(kms::WindowId), } impl WindowId { pub const unsafe fn dummy() -> Self { #[cfg(feature = "wayland")] return WindowId::Wayland(wayland::WindowId::dummy()); - #[cfg(all(not(feature = "wayland"), feature = "x11"))] + #[cfg(all(not(all(feature = "wayland")), feature = "x11"))] return WindowId::X(x11::WindowId::dummy()); + #[cfg(all(not(all(feature = "wayland", feature = "x11")), feature = "kms"))] + return WindowId::Kms(kms::WindowId::dummy()); } } - #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum DeviceId { #[cfg(feature = "x11")] X(x11::DeviceId), #[cfg(feature = "wayland")] Wayland(wayland::DeviceId), + #[cfg(feature = "kms")] + Kms(kms::DeviceId), } impl DeviceId { @@ -192,6 +213,8 @@ impl DeviceId { return DeviceId::Wayland(wayland::DeviceId::dummy()); #[cfg(all(not(feature = "wayland"), feature = "x11"))] return DeviceId::X(x11::DeviceId::dummy()); + #[cfg(all(not(all(feature = "wayland", feature = "x11")), feature = "kms"))] + return DeviceId::Kms(kms::DeviceId::dummy()); } } @@ -201,6 +224,8 @@ pub enum MonitorHandle { X(x11::MonitorHandle), #[cfg(feature = "wayland")] Wayland(wayland::MonitorHandle), + #[cfg(feature = "kms")] + Kms(kms::MonitorHandle), } /// `x11_or_wayland!(match expr; Enum(foo) => foo.something())` @@ -212,13 +237,15 @@ pub enum MonitorHandle { /// } /// ``` /// The result can be converted to another enum by adding `; as AnotherEnum` -macro_rules! x11_or_wayland { +macro_rules! x11_or_wayland_or_drm { (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr; as $enum2:ident ) => { match $what { #[cfg(feature = "x11")] $enum::X($($c1)*) => $enum2::X($x), #[cfg(feature = "wayland")] $enum::Wayland($($c1)*) => $enum2::Wayland($x), + #[cfg(feature = "kms")] + $enum::Kms($($c1)*) => $enum2::Kms($x) } }; (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr) => { @@ -227,6 +254,8 @@ macro_rules! x11_or_wayland { $enum::X($($c1)*) => $x, #[cfg(feature = "wayland")] $enum::Wayland($($c1)*) => $x, + #[cfg(feature = "kms")] + $enum::Kms($($c1)*) => $x } }; } @@ -234,32 +263,32 @@ macro_rules! x11_or_wayland { impl MonitorHandle { #[inline] pub fn name(&self) -> Option { - x11_or_wayland!(match self; MonitorHandle(m) => m.name()) + x11_or_wayland_or_drm!(match self; MonitorHandle(m) => m.name()) } #[inline] pub fn native_identifier(&self) -> u32 { - x11_or_wayland!(match self; MonitorHandle(m) => m.native_identifier()) + x11_or_wayland_or_drm!(match self; MonitorHandle(m) => m.native_identifier()) } #[inline] pub fn size(&self) -> PhysicalSize { - x11_or_wayland!(match self; MonitorHandle(m) => m.size()) + x11_or_wayland_or_drm!(match self; MonitorHandle(m) => m.size()) } #[inline] pub fn position(&self) -> PhysicalPosition { - x11_or_wayland!(match self; MonitorHandle(m) => m.position()) + x11_or_wayland_or_drm!(match self; MonitorHandle(m) => m.position()) } #[inline] pub fn scale_factor(&self) -> f64 { - x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as f64) + x11_or_wayland_or_drm!(match self; MonitorHandle(m) => m.scale_factor() as f64) } #[inline] pub fn video_modes(&self) -> Box> { - x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes())) + x11_or_wayland_or_drm!(match self; MonitorHandle(m) => Box::new(m.video_modes())) } } @@ -269,27 +298,29 @@ pub enum VideoMode { X(x11::VideoMode), #[cfg(feature = "wayland")] Wayland(wayland::VideoMode), + #[cfg(feature = "kms")] + Kms(kms::VideoMode), } impl VideoMode { #[inline] pub fn size(&self) -> PhysicalSize { - x11_or_wayland!(match self; VideoMode(m) => m.size()) + x11_or_wayland_or_drm!(match self; VideoMode(m) => m.size()) } #[inline] pub fn bit_depth(&self) -> u16 { - x11_or_wayland!(match self; VideoMode(m) => m.bit_depth()) + x11_or_wayland_or_drm!(match self; VideoMode(m) => m.bit_depth()) } #[inline] pub fn refresh_rate(&self) -> u16 { - x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate()) + x11_or_wayland_or_drm!(match self; VideoMode(m) => m.refresh_rate()) } #[inline] pub fn monitor(&self) -> RootMonitorHandle { - x11_or_wayland!(match self; VideoMode(m) => m.monitor()) + x11_or_wayland_or_drm!(match self; VideoMode(m) => m.monitor()) } } @@ -309,147 +340,151 @@ impl Window { EventLoopWindowTarget::X(ref window_target) => { x11::Window::new(window_target, attribs, pl_attribs).map(Window::X) } + #[cfg(feature = "kms")] + EventLoopWindowTarget::Kms(ref window_target) => { + kms::Window::new(window_target, attribs, pl_attribs).map(Window::Kms) + } } } #[inline] pub fn id(&self) -> WindowId { - x11_or_wayland!(match self; Window(w) => w.id(); as WindowId) + x11_or_wayland_or_drm!(match self; Window(w) => w.id(); as WindowId) } #[inline] pub fn set_title(&self, title: &str) { - x11_or_wayland!(match self; Window(w) => w.set_title(title)); + x11_or_wayland_or_drm!(match self; Window(w) => w.set_title(title)); } #[inline] pub fn set_visible(&self, visible: bool) { - x11_or_wayland!(match self; Window(w) => w.set_visible(visible)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_visible(visible)) } #[inline] pub fn is_visible(&self) -> Option { - x11_or_wayland!(match self; Window(w) => w.is_visible()) + x11_or_wayland_or_drm!(match self; Window(w) => w.is_visible()) } #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { - x11_or_wayland!(match self; Window(w) => w.outer_position()) + x11_or_wayland_or_drm!(match self; Window(w) => w.outer_position()) } #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { - x11_or_wayland!(match self; Window(w) => w.inner_position()) + x11_or_wayland_or_drm!(match self; Window(w) => w.inner_position()) } #[inline] pub fn set_outer_position(&self, position: Position) { - x11_or_wayland!(match self; Window(w) => w.set_outer_position(position)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_outer_position(position)) } #[inline] pub fn inner_size(&self) -> PhysicalSize { - x11_or_wayland!(match self; Window(w) => w.inner_size()) + x11_or_wayland_or_drm!(match self; Window(w) => w.inner_size()) } #[inline] pub fn outer_size(&self) -> PhysicalSize { - x11_or_wayland!(match self; Window(w) => w.outer_size()) + x11_or_wayland_or_drm!(match self; Window(w) => w.outer_size()) } #[inline] pub fn set_inner_size(&self, size: Size) { - x11_or_wayland!(match self; Window(w) => w.set_inner_size(size)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_inner_size(size)) } #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { - x11_or_wayland!(match self; Window(w) => w.set_min_inner_size(dimensions)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_min_inner_size(dimensions)) } #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { - x11_or_wayland!(match self; Window(w) => w.set_max_inner_size(dimensions)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_max_inner_size(dimensions)) } #[inline] pub fn set_resizable(&self, resizable: bool) { - x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_resizable(resizable)) } #[inline] pub fn is_resizable(&self) -> bool { - x11_or_wayland!(match self; Window(w) => w.is_resizable()) + x11_or_wayland_or_drm!(match self; Window(w) => w.is_resizable()) } #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { - x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_cursor_icon(cursor)) } #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(grab)) + x11_or_wayland_or_drm!(match self; Window(window) => window.set_cursor_grab(grab)) } #[inline] pub fn set_cursor_visible(&self, visible: bool) { - x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible)) + x11_or_wayland_or_drm!(match self; Window(window) => window.set_cursor_visible(visible)) } #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { - x11_or_wayland!(match self; Window(window) => window.drag_window()) + x11_or_wayland_or_drm!(match self; Window(window) => window.drag_window()) } #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { - x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_cursor_hittest(hittest)) } #[inline] pub fn scale_factor(&self) -> f64 { - x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64) + x11_or_wayland_or_drm!(match self; Window(w) => w.scale_factor() as f64) } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { - x11_or_wayland!(match self; Window(w) => w.set_cursor_position(position)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_cursor_position(position)) } #[inline] pub fn set_maximized(&self, maximized: bool) { - x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_maximized(maximized)) } #[inline] pub fn is_maximized(&self) -> bool { - x11_or_wayland!(match self; Window(w) => w.is_maximized()) + x11_or_wayland_or_drm!(match self; Window(w) => w.is_maximized()) } #[inline] pub fn set_minimized(&self, minimized: bool) { - x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_minimized(minimized)) } #[inline] pub fn fullscreen(&self) -> Option { - x11_or_wayland!(match self; Window(w) => w.fullscreen()) + x11_or_wayland_or_drm!(match self; Window(w) => w.fullscreen()) } #[inline] pub fn set_fullscreen(&self, monitor: Option) { - x11_or_wayland!(match self; Window(w) => w.set_fullscreen(monitor)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_fullscreen(monitor)) } #[inline] pub fn set_decorations(&self, decorations: bool) { - x11_or_wayland!(match self; Window(w) => w.set_decorations(decorations)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_decorations(decorations)) } #[inline] pub fn is_decorated(&self) -> bool { - x11_or_wayland!(match self; Window(w) => w.is_decorated()) + x11_or_wayland_or_drm!(match self; Window(w) => w.is_decorated()) } #[inline] @@ -459,6 +494,8 @@ impl Window { Window::X(ref w) => w.set_always_on_top(_always_on_top), #[cfg(feature = "wayland")] Window::Wayland(_) => (), + #[cfg(feature = "kms")] + Window::Kms(_) => (), } } @@ -469,17 +506,19 @@ impl Window { Window::X(ref w) => w.set_window_icon(_window_icon), #[cfg(feature = "wayland")] Window::Wayland(_) => (), + #[cfg(feature = "kms")] + Window::Kms(_) => (), } } #[inline] pub fn set_ime_position(&self, position: Position) { - x11_or_wayland!(match self; Window(w) => w.set_ime_position(position)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_ime_position(position)) } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { - x11_or_wayland!(match self; Window(w) => w.set_ime_allowed(allowed)) + x11_or_wayland_or_drm!(match self; Window(w) => w.set_ime_allowed(allowed)) } #[inline] @@ -489,20 +528,24 @@ impl Window { Window::X(ref w) => w.focus_window(), #[cfg(feature = "wayland")] Window::Wayland(_) => (), + #[cfg(feature = "kms")] + Window::Kms(_) => (), } } - pub fn request_user_attention(&self, request_type: Option) { + pub fn request_user_attention(&self, _request_type: Option) { match self { #[cfg(feature = "x11")] - Window::X(ref w) => w.request_user_attention(request_type), + Window::X(ref w) => w.request_user_attention(_request_type), #[cfg(feature = "wayland")] - Window::Wayland(ref w) => w.request_user_attention(request_type), + Window::Wayland(ref w) => w.request_user_attention(_request_type), + #[cfg(feature = "kms")] + Window::Kms(_) => (), } } #[inline] pub fn request_redraw(&self) { - x11_or_wayland!(match self; Window(w) => w.request_redraw()) + x11_or_wayland_or_drm!(match self; Window(w) => w.request_redraw()) } #[inline] @@ -522,6 +565,13 @@ impl Window { inner: current_monitor, }) } + #[cfg(feature = "kms")] + Window::Kms(ref window) => { + let current_monitor = MonitorHandle::Kms(window.current_monitor()?); + Some(RootMonitorHandle { + inner: current_monitor, + }) + } } } @@ -540,6 +590,12 @@ impl Window { .into_iter() .map(MonitorHandle::Wayland) .collect(), + #[cfg(feature = "kms")] + Window::Kms(ref window) => window + .available_monitors() + .into_iter() + .map(MonitorHandle::Kms) + .collect(), } } @@ -555,6 +611,8 @@ impl Window { } #[cfg(feature = "wayland")] Window::Wayland(ref window) => window.primary_monitor(), + #[cfg(feature = "kms")] + Window::Kms(ref window) => window.primary_monitor(), } } @@ -564,6 +622,8 @@ impl Window { Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()), #[cfg(feature = "wayland")] Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()), + #[cfg(feature = "kms")] + Window::Kms(ref window) => RawWindowHandle::Drm(window.raw_window_handle()), } } } @@ -606,6 +666,8 @@ pub enum EventLoop { Wayland(wayland::EventLoop), #[cfg(feature = "x11")] X(x11::EventLoop), + #[cfg(feature = "kms")] + Kms(kms::EventLoop), } pub enum EventLoopProxy { @@ -613,11 +675,13 @@ pub enum EventLoopProxy { X(x11::EventLoopProxy), #[cfg(feature = "wayland")] Wayland(wayland::EventLoopProxy), + #[cfg(feature = "kms")] + Kms(kms::EventLoopProxy), } impl Clone for EventLoopProxy { fn clone(&self) -> Self { - x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.clone(); as EventLoopProxy) + x11_or_wayland_or_drm!(match self; EventLoopProxy(proxy) => proxy.clone(); as EventLoopProxy) } } @@ -644,6 +708,12 @@ impl EventLoop { return EventLoop::new_wayland_any_thread().expect("failed to open Wayland connection"); } + #[cfg(feature = "kms")] + if attributes.forced_backend == Some(Backend::Kms) { + // TODO: Propagate + return EventLoop::new_drm_any_thread().expect("failed to open drm connection"); + } + if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) { match env_var.as_str() { "x11" => { @@ -661,6 +731,13 @@ impl EventLoop { #[cfg(not(feature = "wayland"))] panic!("wayland feature is not enabled"); } + "drm" | "kms" | "gbm" | "tty" => { + #[cfg(feature = "kms")] + return EventLoop::new_drm_any_thread() + .expect("Failed to initialize drm backend"); + #[cfg(not(feature = "kms"))] + panic!("kms feature is not enabled"); + } _ => panic!( "Unknown environment variable value for {}, try one of `x11`,`wayland`", BACKEND_PREFERENCE_ENV_VAR, @@ -680,15 +757,23 @@ impl EventLoop { Err(err) => err, }; + #[cfg(feature = "kms")] + let drm_err = match EventLoop::new_drm_any_thread() { + Ok(event_loop) => return event_loop, + Err(err) => err, + }; + #[cfg(not(feature = "wayland"))] let wayland_err = "backend disabled"; #[cfg(not(feature = "x11"))] let x11_err = "backend disabled"; + #[cfg(not(feature = "kms"))] + let drm_err = "backend disabled"; panic!( - "Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}", - wayland_err, x11_err, - ); + "Failed to initialize any backend! Wayland status: {:?} X11 status: {:?} DRM status: {:?}", + wayland_err, x11_err, drm_err + ); } #[cfg(feature = "wayland")] @@ -706,32 +791,37 @@ impl EventLoop { Ok(EventLoop::X(x11::EventLoop::new(xconn))) } + #[cfg(feature = "kms")] + fn new_drm_any_thread() -> Result, RootOsError> { + kms::EventLoop::new().map(EventLoop::Kms) + } + pub fn create_proxy(&self) -> EventLoopProxy { - x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy) + x11_or_wayland_or_drm!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy) } pub fn run_return(&mut self, callback: F) -> i32 where - F: FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { - x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_return(callback)) + x11_or_wayland_or_drm!(match self; EventLoop(evlp) => evlp.run_return(callback)) } pub fn run(self, callback: F) -> ! where - F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), + F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { - x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(callback)) + x11_or_wayland_or_drm!(match self; EventLoop(evlp) => evlp.run(callback)) } - pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget { - x11_or_wayland!(match self; EventLoop(evl) => evl.window_target()) + pub fn window_target(&self) -> &RootELW { + x11_or_wayland_or_drm!(match self; EventLoop(evl) => evl.window_target()) } } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event)) + x11_or_wayland_or_drm!(match self; EventLoopProxy(proxy) => proxy.send_event(event)) } } @@ -740,19 +830,11 @@ pub enum EventLoopWindowTarget { Wayland(wayland::EventLoopWindowTarget), #[cfg(feature = "x11")] X(x11::EventLoopWindowTarget), + #[cfg(feature = "kms")] + Kms(kms::EventLoopWindowTarget), } impl EventLoopWindowTarget { - #[inline] - pub fn is_wayland(&self) -> bool { - match *self { - #[cfg(feature = "wayland")] - EventLoopWindowTarget::Wayland(_) => true, - #[cfg(feature = "x11")] - _ => false, - } - } - #[inline] pub fn available_monitors(&self) -> VecDeque { match *self { @@ -769,6 +851,12 @@ impl EventLoopWindowTarget { .into_iter() .map(MonitorHandle::X) .collect(), + #[cfg(feature = "kms")] + EventLoopWindowTarget::Kms(ref evlp) => evlp + .available_monitors() + .into_iter() + .map(MonitorHandle::Kms) + .collect(), } } @@ -784,6 +872,8 @@ impl EventLoopWindowTarget { inner: primary_monitor, }) } + #[cfg(feature = "kms")] + EventLoopWindowTarget::Kms(ref evlp) => evlp.primary_monitor(), } } } diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 84dcb9fabb..1837849992 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -6,12 +6,11 @@ use std::process; use std::rc::Rc; use std::time::{Duration, Instant}; +use sctk::reexports::calloop; use sctk::reexports::client::protocol::wl_compositor::WlCompositor; use sctk::reexports::client::protocol::wl_shm::WlShm; use sctk::reexports::client::Display; -use sctk::reexports::calloop; - use sctk::environment::Environment; use sctk::seat::pointer::{ThemeManager, ThemeSpec}; use sctk::WaylandSource; @@ -249,7 +248,7 @@ impl EventLoop { PlatformEventLoopWindowTarget::Wayland(window_target) => { window_target.state.get_mut() } - #[cfg(feature = "x11")] + #[cfg(any(feature = "x11", feature = "kms"))] _ => unreachable!(), }; @@ -538,7 +537,7 @@ impl EventLoop { fn with_window_target) -> U>(&mut self, f: F) -> U { let state = match &mut self.window_target.p { PlatformEventLoopWindowTarget::Wayland(window_target) => window_target, - #[cfg(feature = "x11")] + #[cfg(any(feature = "x11", feature = "kms"))] _ => unreachable!(), }; @@ -548,7 +547,7 @@ impl EventLoop { fn loop_dispatch>>(&mut self, timeout: D) -> IOResult<()> { let state = match &mut self.window_target.p { PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(), - #[cfg(feature = "x11")] + #[cfg(any(feature = "x11", feature = "kms"))] _ => unreachable!(), }; diff --git a/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs b/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs index 7c320973b8..1164b2fb22 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs @@ -8,8 +8,8 @@ use crate::event::{ElementState, KeyboardInput, ModifiersState, WindowEvent}; use crate::platform_impl::wayland::event_loop::WinitState; use crate::platform_impl::wayland::{self, DeviceId}; -use super::keymap; use super::KeyboardInner; +use crate::platform_impl::xkb_keymap; #[inline] pub(super) fn handle_keyboard( @@ -68,7 +68,7 @@ pub(super) fn handle_keyboard( _ => unreachable!(), }; - let virtual_keycode = keymap::keysym_to_vkey(keysym); + let virtual_keycode = xkb_keymap::keysym_to_vkey(keysym); event_sink.push_window_event( #[allow(deprecated)] @@ -109,7 +109,7 @@ pub(super) fn handle_keyboard( None => return, }; - let virtual_keycode = keymap::keysym_to_vkey(keysym); + let virtual_keycode = xkb_keymap::keysym_to_vkey(keysym); event_sink.push_window_event( #[allow(deprecated)] diff --git a/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs b/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs deleted file mode 100644 index 991afff2c9..0000000000 --- a/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs +++ /dev/null @@ -1,192 +0,0 @@ -//! Convert Wayland keys to winit keys. - -use crate::event::VirtualKeyCode; - -pub fn keysym_to_vkey(keysym: u32) -> Option { - use sctk::seat::keyboard::keysyms; - match keysym { - // Numbers. - keysyms::XKB_KEY_1 => Some(VirtualKeyCode::Key1), - keysyms::XKB_KEY_2 => Some(VirtualKeyCode::Key2), - keysyms::XKB_KEY_3 => Some(VirtualKeyCode::Key3), - keysyms::XKB_KEY_4 => Some(VirtualKeyCode::Key4), - keysyms::XKB_KEY_5 => Some(VirtualKeyCode::Key5), - keysyms::XKB_KEY_6 => Some(VirtualKeyCode::Key6), - keysyms::XKB_KEY_7 => Some(VirtualKeyCode::Key7), - keysyms::XKB_KEY_8 => Some(VirtualKeyCode::Key8), - keysyms::XKB_KEY_9 => Some(VirtualKeyCode::Key9), - keysyms::XKB_KEY_0 => Some(VirtualKeyCode::Key0), - // Letters. - keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A), - keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B), - keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C), - keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D), - keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E), - keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F), - keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G), - keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H), - keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I), - keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J), - keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K), - keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L), - keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M), - keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N), - keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O), - keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P), - keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q), - keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R), - keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S), - keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T), - keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U), - keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V), - keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W), - keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X), - keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y), - keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z), - // Escape. - keysyms::XKB_KEY_Escape => Some(VirtualKeyCode::Escape), - // Function keys. - keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1), - keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2), - keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3), - keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4), - keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5), - keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6), - keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7), - keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8), - keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9), - keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10), - keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11), - keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12), - keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13), - keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14), - keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15), - keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16), - keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17), - keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18), - keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19), - keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20), - keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21), - keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22), - keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23), - keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24), - // Flow control. - keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot), - keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll), - keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause), - keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert), - keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home), - keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete), - keysyms::XKB_KEY_End => Some(VirtualKeyCode::End), - keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown), - keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp), - // Arrows. - keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left), - keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up), - keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right), - keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down), - - keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back), - keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return), - keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space), - - keysyms::XKB_KEY_Multi_key => Some(VirtualKeyCode::Compose), - keysyms::XKB_KEY_caret => Some(VirtualKeyCode::Caret), - - // Keypad. - keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock), - keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0), - keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1), - keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2), - keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3), - keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4), - keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5), - keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6), - keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7), - keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8), - keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9), - // Misc. - // => Some(VirtualKeyCode::AbntC1), - // => Some(VirtualKeyCode::AbntC2), - keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Plus), - keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe), - // => Some(VirtualKeyCode::Apps), - keysyms::XKB_KEY_at => Some(VirtualKeyCode::At), - // => Some(VirtualKeyCode::Ax), - keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash), - keysyms::XKB_KEY_XF86Calculator => Some(VirtualKeyCode::Calculator), - // => Some(VirtualKeyCode::Capital), - keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon), - keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma), - // => Some(VirtualKeyCode::Convert), - keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals), - keysyms::XKB_KEY_grave => Some(VirtualKeyCode::Grave), - // => Some(VirtualKeyCode::Kana), - keysyms::XKB_KEY_Kanji => Some(VirtualKeyCode::Kanji), - keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt), - keysyms::XKB_KEY_bracketleft => Some(VirtualKeyCode::LBracket), - keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl), - keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift), - keysyms::XKB_KEY_Super_L => Some(VirtualKeyCode::LWin), - keysyms::XKB_KEY_XF86Mail => Some(VirtualKeyCode::Mail), - // => Some(VirtualKeyCode::MediaSelect), - // => Some(VirtualKeyCode::MediaStop), - keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus), - keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk), - keysyms::XKB_KEY_XF86AudioMute => Some(VirtualKeyCode::Mute), - // => Some(VirtualKeyCode::MyComputer), - keysyms::XKB_KEY_XF86AudioNext => Some(VirtualKeyCode::NextTrack), - // => Some(VirtualKeyCode::NoConvert), - keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma), - keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter), - keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals), - keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::NumpadAdd), - keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract), - keysyms::XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply), - keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide), - keysyms::XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal), - keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp), - keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown), - keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home), - keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End), - keysyms::XKB_KEY_KP_Left => Some(VirtualKeyCode::Left), - keysyms::XKB_KEY_KP_Up => Some(VirtualKeyCode::Up), - keysyms::XKB_KEY_KP_Right => Some(VirtualKeyCode::Right), - keysyms::XKB_KEY_KP_Down => Some(VirtualKeyCode::Down), - // => Some(VirtualKeyCode::OEM102), - keysyms::XKB_KEY_period => Some(VirtualKeyCode::Period), - // => Some(VirtualKeyCode::Playpause), - keysyms::XKB_KEY_XF86PowerOff => Some(VirtualKeyCode::Power), - keysyms::XKB_KEY_XF86AudioPrev => Some(VirtualKeyCode::PrevTrack), - keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt), - keysyms::XKB_KEY_bracketright => Some(VirtualKeyCode::RBracket), - keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl), - keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift), - keysyms::XKB_KEY_Super_R => Some(VirtualKeyCode::RWin), - keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon), - keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash), - keysyms::XKB_KEY_XF86Sleep => Some(VirtualKeyCode::Sleep), - // => Some(VirtualKeyCode::Stop), - // => Some(VirtualKeyCode::Sysrq), - keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab), - keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab), - keysyms::XKB_KEY_underscore => Some(VirtualKeyCode::Underline), - // => Some(VirtualKeyCode::Unlabeled), - keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown), - keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp), - // => Some(VirtualKeyCode::Wake), - // => Some(VirtualKeyCode::Webback), - // => Some(VirtualKeyCode::WebFavorites), - // => Some(VirtualKeyCode::WebForward), - // => Some(VirtualKeyCode::WebHome), - // => Some(VirtualKeyCode::WebRefresh), - // => Some(VirtualKeyCode::WebSearch), - // => Some(VirtualKeyCode::WebStop), - keysyms::XKB_KEY_yen => Some(VirtualKeyCode::Yen), - keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy), - keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste), - keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut), - // Fallback. - _ => None, - } -} diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs index c6e0ad456e..a705d41d19 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -16,7 +16,6 @@ use crate::platform_impl::wayland::event_loop::WinitState; use crate::platform_impl::wayland::WindowId; mod handlers; -mod keymap; pub(crate) struct Keyboard { pub keyboard: WlKeyboard, diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs index 2d7b7533d0..c6c4b02b61 100644 --- a/src/platform_impl/linux/wayland/seat/mod.rs +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -6,12 +6,12 @@ use std::rc::Rc; use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1; use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1; use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; +use sctk::reexports::calloop::LoopHandle; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::Attached; use sctk::environment::Environment; -use sctk::reexports::calloop::LoopHandle; use sctk::seat::pointer::ThemeManager; use sctk::seat::{SeatData, SeatListener}; diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 04ca01893a..ff1f5fd334 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -2,11 +2,10 @@ use std::collections::VecDeque; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; +use sctk::reexports::calloop; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::Display; -use sctk::reexports::calloop; - use raw_window_handle::WaylandHandle; use sctk::window::{Decorations, FallbackFrame}; @@ -181,6 +180,8 @@ impl Window { PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), #[cfg(feature = "x11")] PlatformMonitorHandle::X(_) => None, + #[cfg(feature = "kms")] + PlatformMonitorHandle::Kms(_) => None, }); window.set_fullscreen(monitor.as_ref()); @@ -426,6 +427,8 @@ impl Window { PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), #[cfg(feature = "x11")] PlatformMonitorHandle::X(_) => None, + #[cfg(feature = "kms")] + PlatformMonitorHandle::Kms(_) => None, }); WindowRequest::Fullscreen(monitor) diff --git a/src/platform_impl/linux/xkb_keymap.rs b/src/platform_impl/linux/xkb_keymap.rs new file mode 100644 index 0000000000..a1a38d394f --- /dev/null +++ b/src/platform_impl/linux/xkb_keymap.rs @@ -0,0 +1,419 @@ +//! Convert Wayland keys to winit keys. +#![allow(non_upper_case_globals)] + +use crate::event::VirtualKeyCode; + +// TTY function keys, cleverly chosen to map to ASCII, for convenience of +// programming, but could have been arbitrary (at the cost of lookup +// tables in client code). + +pub const XKB_KEY_BackSpace: u32 = 0xff08; // Back space, back char +pub const XKB_KEY_Tab: u32 = 0xff09; +pub const XKB_KEY_Return: u32 = 0xff0d; // Return, enter +pub const XKB_KEY_Pause: u32 = 0xff13; // Pause, hold +pub const XKB_KEY_Scroll_Lock: u32 = 0xff14; +pub const XKB_KEY_Sys_Req: u32 = 0xff15; +pub const XKB_KEY_Escape: u32 = 0xff1b; +pub const XKB_KEY_Delete: u32 = 0xffff; // Delete, rubout +pub const XKB_KEY_Caps_Lock: u32 = 0xffe5; + +// International & multi-key character composition + +pub const XKB_KEY_Multi_key: u32 = 0xff20; // Multi-key character compose + +// Japanese keyboard support + +pub const XKB_KEY_Kanji: u32 = 0xff21; // Kanji, Kanji convert + +// Cursor control & motion + +pub const XKB_KEY_Home: u32 = 0xff50; +pub const XKB_KEY_Left: u32 = 0xff51; // Move left, left arrow +pub const XKB_KEY_Up: u32 = 0xff52; // Move up, up arrow +pub const XKB_KEY_Right: u32 = 0xff53; // Move right, right arrow +pub const XKB_KEY_Down: u32 = 0xff54; // Move down, down arrow +pub const XKB_KEY_Page_Up: u32 = 0xff55; +pub const XKB_KEY_Page_Down: u32 = 0xff56; +pub const XKB_KEY_End: u32 = 0xff57; // EOL + +// Misc functions + +pub const XKB_KEY_Print: u32 = 0xff61; +pub const XKB_KEY_Insert: u32 = 0xff63; // Insert, insert here +pub const XKB_KEY_Num_Lock: u32 = 0xff7f; + +// Keypad functions, keypad numbers cleverly chosen to map to ASCII + +pub const XKB_KEY_KP_Enter: u32 = 0xff8d; // Enter +pub const XKB_KEY_KP_Home: u32 = 0xff95; +pub const XKB_KEY_KP_Left: u32 = 0xff96; +pub const XKB_KEY_KP_Up: u32 = 0xff97; +pub const XKB_KEY_KP_Right: u32 = 0xff98; +pub const XKB_KEY_KP_Down: u32 = 0xff99; +pub const XKB_KEY_KP_Page_Up: u32 = 0xff9a; +pub const XKB_KEY_KP_Page_Down: u32 = 0xff9b; +pub const XKB_KEY_KP_End: u32 = 0xff9c; +pub const XKB_KEY_KP_Equal: u32 = 0xffbd; // Equals +pub const XKB_KEY_KP_Multiply: u32 = 0xffaa; +pub const XKB_KEY_KP_Add: u32 = 0xffab; +pub const XKB_KEY_KP_Separator: u32 = 0xffac; // Separator, often comma +pub const XKB_KEY_KP_Subtract: u32 = 0xffad; +pub const XKB_KEY_KP_Decimal: u32 = 0xffae; +pub const XKB_KEY_KP_Divide: u32 = 0xffaf; + +pub const XKB_KEY_KP_0: u32 = 0xffb0; +pub const XKB_KEY_KP_1: u32 = 0xffb1; +pub const XKB_KEY_KP_2: u32 = 0xffb2; +pub const XKB_KEY_KP_3: u32 = 0xffb3; +pub const XKB_KEY_KP_4: u32 = 0xffb4; +pub const XKB_KEY_KP_5: u32 = 0xffb5; +pub const XKB_KEY_KP_6: u32 = 0xffb6; +pub const XKB_KEY_KP_7: u32 = 0xffb7; +pub const XKB_KEY_KP_8: u32 = 0xffb8; +pub const XKB_KEY_KP_9: u32 = 0xffb9; + +// Auxiliary functions; note the duplicate definitions for left and right +// function keys; Sun keyboards and a few other manufacturers have such +// function key groups on the left and/or right sides of the keyboard. +// We've not found a keyboard with more than 35 function keys total. + +pub const XKB_KEY_F1: u32 = 0xffbe; +pub const XKB_KEY_F2: u32 = 0xffbf; +pub const XKB_KEY_F3: u32 = 0xffc0; +pub const XKB_KEY_F4: u32 = 0xffc1; +pub const XKB_KEY_F5: u32 = 0xffc2; +pub const XKB_KEY_F6: u32 = 0xffc3; +pub const XKB_KEY_F7: u32 = 0xffc4; +pub const XKB_KEY_F8: u32 = 0xffc5; +pub const XKB_KEY_F9: u32 = 0xffc6; +pub const XKB_KEY_F10: u32 = 0xffc7; +pub const XKB_KEY_F11: u32 = 0xffc8; +pub const XKB_KEY_F12: u32 = 0xffc9; +pub const XKB_KEY_F13: u32 = 0xffca; +pub const XKB_KEY_F14: u32 = 0xffcb; +pub const XKB_KEY_F15: u32 = 0xffcc; +pub const XKB_KEY_F16: u32 = 0xffcd; +pub const XKB_KEY_F17: u32 = 0xffce; +pub const XKB_KEY_F18: u32 = 0xffcf; +pub const XKB_KEY_F19: u32 = 0xffd0; +pub const XKB_KEY_F20: u32 = 0xffd1; +pub const XKB_KEY_F21: u32 = 0xffd2; +pub const XKB_KEY_F22: u32 = 0xffd3; +pub const XKB_KEY_F23: u32 = 0xffd4; +pub const XKB_KEY_F24: u32 = 0xffd5; + +// Modifiers + +pub const XKB_KEY_Shift_L: u32 = 0xffe1; // Left shift +pub const XKB_KEY_Shift_R: u32 = 0xffe2; // Right shift +pub const XKB_KEY_Control_L: u32 = 0xffe3; // Left control +pub const XKB_KEY_Control_R: u32 = 0xffe4; // Right control + +pub const XKB_KEY_Meta_L: u32 = 0xffe7; // Left meta +pub const XKB_KEY_Meta_R: u32 = 0xffe8; // Right meta +pub const XKB_KEY_Alt_L: u32 = 0xffe9; // Left alt +pub const XKB_KEY_Alt_R: u32 = 0xffea; // Right alt +pub const XKB_KEY_Super_L: u32 = 0xffeb; // Left super +pub const XKB_KEY_Super_R: u32 = 0xffec; // Right super + +pub const XKB_KEY_ISO_Left_Tab: u32 = 0xfe20; + +// Latin 1 +// (ISO/IEC 8859-1 = Unicode U+0020..U+00FF) +// Byte 3 = 0 + +pub const XKB_KEY_space: u32 = 0x0020; // U+0020 SPACE +pub const XKB_KEY_apostrophe: u32 = 0x0027; // U+0027 APOSTROPHE +pub const XKB_KEY_asterisk: u32 = 0x002a; // U+002A ASTERISK +pub const XKB_KEY_plus: u32 = 0x002b; // U+002B PLUS SIGN +pub const XKB_KEY_comma: u32 = 0x002c; // U+002C COMMA +pub const XKB_KEY_minus: u32 = 0x002d; // U+002D HYPHEN-MINUS +pub const XKB_KEY_period: u32 = 0x002e; // U+002E FULL STOP +pub const XKB_KEY_slash: u32 = 0x002f; // U+002F SOLIDUS +pub const XKB_KEY_0: u32 = 0x0030; // U+0030 DIGIT ZERO +pub const XKB_KEY_1: u32 = 0x0031; // U+0031 DIGIT ONE +pub const XKB_KEY_2: u32 = 0x0032; // U+0032 DIGIT TWO +pub const XKB_KEY_3: u32 = 0x0033; // U+0033 DIGIT THREE +pub const XKB_KEY_4: u32 = 0x0034; // U+0034 DIGIT FOUR +pub const XKB_KEY_5: u32 = 0x0035; // U+0035 DIGIT FIVE +pub const XKB_KEY_6: u32 = 0x0036; // U+0036 DIGIT SIX +pub const XKB_KEY_7: u32 = 0x0037; // U+0037 DIGIT SEVEN +pub const XKB_KEY_8: u32 = 0x0038; // U+0038 DIGIT EIGHT +pub const XKB_KEY_9: u32 = 0x0039; // U+0039 DIGIT NINE +pub const XKB_KEY_colon: u32 = 0x003a; // U+003A COLON +pub const XKB_KEY_semicolon: u32 = 0x003b; // U+003B SEMICOLON +pub const XKB_KEY_equal: u32 = 0x003d; // U+003D EQUALS SIGN +pub const XKB_KEY_at: u32 = 0x0040; // U+0040 COMMERCIAL AT +pub const XKB_KEY_A: u32 = 0x0041; // U+0041 LATIN CAPITAL LETTER A +pub const XKB_KEY_B: u32 = 0x0042; // U+0042 LATIN CAPITAL LETTER B +pub const XKB_KEY_C: u32 = 0x0043; // U+0043 LATIN CAPITAL LETTER C +pub const XKB_KEY_D: u32 = 0x0044; // U+0044 LATIN CAPITAL LETTER D +pub const XKB_KEY_E: u32 = 0x0045; // U+0045 LATIN CAPITAL LETTER E +pub const XKB_KEY_F: u32 = 0x0046; // U+0046 LATIN CAPITAL LETTER F +pub const XKB_KEY_G: u32 = 0x0047; // U+0047 LATIN CAPITAL LETTER G +pub const XKB_KEY_H: u32 = 0x0048; // U+0048 LATIN CAPITAL LETTER H +pub const XKB_KEY_I: u32 = 0x0049; // U+0049 LATIN CAPITAL LETTER I +pub const XKB_KEY_J: u32 = 0x004a; // U+004A LATIN CAPITAL LETTER J +pub const XKB_KEY_K: u32 = 0x004b; // U+004B LATIN CAPITAL LETTER K +pub const XKB_KEY_L: u32 = 0x004c; // U+004C LATIN CAPITAL LETTER L +pub const XKB_KEY_M: u32 = 0x004d; // U+004D LATIN CAPITAL LETTER M +pub const XKB_KEY_N: u32 = 0x004e; // U+004E LATIN CAPITAL LETTER N +pub const XKB_KEY_O: u32 = 0x004f; // U+004F LATIN CAPITAL LETTER O +pub const XKB_KEY_P: u32 = 0x0050; // U+0050 LATIN CAPITAL LETTER P +pub const XKB_KEY_Q: u32 = 0x0051; // U+0051 LATIN CAPITAL LETTER Q +pub const XKB_KEY_R: u32 = 0x0052; // U+0052 LATIN CAPITAL LETTER R +pub const XKB_KEY_S: u32 = 0x0053; // U+0053 LATIN CAPITAL LETTER S +pub const XKB_KEY_T: u32 = 0x0054; // U+0054 LATIN CAPITAL LETTER T +pub const XKB_KEY_U: u32 = 0x0055; // U+0055 LATIN CAPITAL LETTER U +pub const XKB_KEY_V: u32 = 0x0056; // U+0056 LATIN CAPITAL LETTER V +pub const XKB_KEY_W: u32 = 0x0057; // U+0057 LATIN CAPITAL LETTER W +pub const XKB_KEY_X: u32 = 0x0058; // U+0058 LATIN CAPITAL LETTER X +pub const XKB_KEY_Y: u32 = 0x0059; // U+0059 LATIN CAPITAL LETTER Y +pub const XKB_KEY_Z: u32 = 0x005a; // U+005A LATIN CAPITAL LETTER Z +pub const XKB_KEY_bracketleft: u32 = 0x005b; // U+005B LEFT SQUARE BRACKET +pub const XKB_KEY_backslash: u32 = 0x005c; // U+005C REVERSE SOLIDUS +pub const XKB_KEY_bracketright: u32 = 0x005d; // U+005D RIGHT SQUARE BRACKET +pub const XKB_KEY_underscore: u32 = 0x005f; // U+005F LOW LINE +pub const XKB_KEY_grave: u32 = 0x0060; // U+0060 GRAVE ACCENT +pub const XKB_KEY_a: u32 = 0x0061; // U+0061 LATIN SMALL LETTER A +pub const XKB_KEY_b: u32 = 0x0062; // U+0062 LATIN SMALL LETTER B +pub const XKB_KEY_c: u32 = 0x0063; // U+0063 LATIN SMALL LETTER C +pub const XKB_KEY_d: u32 = 0x0064; // U+0064 LATIN SMALL LETTER D +pub const XKB_KEY_e: u32 = 0x0065; // U+0065 LATIN SMALL LETTER E +pub const XKB_KEY_f: u32 = 0x0066; // U+0066 LATIN SMALL LETTER F +pub const XKB_KEY_g: u32 = 0x0067; // U+0067 LATIN SMALL LETTER G +pub const XKB_KEY_h: u32 = 0x0068; // U+0068 LATIN SMALL LETTER H +pub const XKB_KEY_i: u32 = 0x0069; // U+0069 LATIN SMALL LETTER I +pub const XKB_KEY_j: u32 = 0x006a; // U+006A LATIN SMALL LETTER J +pub const XKB_KEY_k: u32 = 0x006b; // U+006B LATIN SMALL LETTER K +pub const XKB_KEY_l: u32 = 0x006c; // U+006C LATIN SMALL LETTER L +pub const XKB_KEY_m: u32 = 0x006d; // U+006D LATIN SMALL LETTER M +pub const XKB_KEY_n: u32 = 0x006e; // U+006E LATIN SMALL LETTER N +pub const XKB_KEY_o: u32 = 0x006f; // U+006F LATIN SMALL LETTER O +pub const XKB_KEY_p: u32 = 0x0070; // U+0070 LATIN SMALL LETTER P +pub const XKB_KEY_q: u32 = 0x0071; // U+0071 LATIN SMALL LETTER Q +pub const XKB_KEY_r: u32 = 0x0072; // U+0072 LATIN SMALL LETTER R +pub const XKB_KEY_s: u32 = 0x0073; // U+0073 LATIN SMALL LETTER S +pub const XKB_KEY_t: u32 = 0x0074; // U+0074 LATIN SMALL LETTER T +pub const XKB_KEY_u: u32 = 0x0075; // U+0075 LATIN SMALL LETTER U +pub const XKB_KEY_v: u32 = 0x0076; // U+0076 LATIN SMALL LETTER V +pub const XKB_KEY_w: u32 = 0x0077; // U+0077 LATIN SMALL LETTER W +pub const XKB_KEY_x: u32 = 0x0078; // U+0078 LATIN SMALL LETTER X +pub const XKB_KEY_y: u32 = 0x0079; // U+0079 LATIN SMALL LETTER Y +pub const XKB_KEY_z: u32 = 0x007a; // U+007A LATIN SMALL LETTER Z +pub const XKB_KEY_yen: u32 = 0x00a5; // U+00A5 YEN SIGN +pub const XKB_KEY_caret: u32 = 0x0afc; // U+2038 CARET + +// Keys found on some "Internet" keyboards. + +pub const XKB_KEY_XF86AudioLowerVolume: u32 = 0x1008FF11; // Volume control down +pub const XKB_KEY_XF86AudioMute: u32 = 0x1008FF12; // Mute sound from the system +pub const XKB_KEY_XF86AudioRaiseVolume: u32 = 0x1008FF13; // Volume control up +pub const XKB_KEY_XF86AudioPrev: u32 = 0x1008FF16; // Previous track +pub const XKB_KEY_XF86AudioNext: u32 = 0x1008FF17; // Next track +pub const XKB_KEY_XF86Mail: u32 = 0x1008FF19; // Invoke user's mail program + +// These are sometimes found on PDA's (e.g. Palm, PocketPC or elsewhere) +pub const XKB_KEY_XF86Calculator: u32 = 0x1008FF1D; // Invoke calculator program +pub const XKB_KEY_XF86PowerOff: u32 = 0x1008FF2A; // Power off system entirely +pub const XKB_KEY_XF86Sleep: u32 = 0x1008FF2F; // Put system to sleep +pub const XKB_KEY_XF86Copy: u32 = 0x1008FF57; // Copy selection +pub const XKB_KEY_XF86Cut: u32 = 0x1008FF58; // Cut selection +pub const XKB_KEY_XF86Paste: u32 = 0x1008FF6D; // Paste +pub const XKB_KEY_XF86AudioStop: u32 = 0x1008FF15; +pub const XKB_KEY_XF86MyComputer: u32 = 0x1008FF33; +pub const XKB_KEY_XF86Stop: u32 = 0x1008FF28; +pub const XKB_KEY_XF86WakeUp: u32 = 0x1008FF2B; +pub const XKB_KEY_XF86Favorites: u32 = 0x1008FF30; +pub const XKB_KEY_XF86HomePage: u32 = 0x1008FF18; +pub const XKB_KEY_XF86Refresh: u32 = 0x1008FF29; + +pub fn keysym_to_vkey(keysym: u32) -> Option { + match keysym { + // Numbers. + XKB_KEY_1 => Some(VirtualKeyCode::Key1), + XKB_KEY_2 => Some(VirtualKeyCode::Key2), + XKB_KEY_3 => Some(VirtualKeyCode::Key3), + XKB_KEY_4 => Some(VirtualKeyCode::Key4), + XKB_KEY_5 => Some(VirtualKeyCode::Key5), + XKB_KEY_6 => Some(VirtualKeyCode::Key6), + XKB_KEY_7 => Some(VirtualKeyCode::Key7), + XKB_KEY_8 => Some(VirtualKeyCode::Key8), + XKB_KEY_9 => Some(VirtualKeyCode::Key9), + XKB_KEY_0 => Some(VirtualKeyCode::Key0), + // Letters. + XKB_KEY_A | XKB_KEY_a => Some(VirtualKeyCode::A), + XKB_KEY_B | XKB_KEY_b => Some(VirtualKeyCode::B), + XKB_KEY_C | XKB_KEY_c => Some(VirtualKeyCode::C), + XKB_KEY_D | XKB_KEY_d => Some(VirtualKeyCode::D), + XKB_KEY_E | XKB_KEY_e => Some(VirtualKeyCode::E), + XKB_KEY_F | XKB_KEY_f => Some(VirtualKeyCode::F), + XKB_KEY_G | XKB_KEY_g => Some(VirtualKeyCode::G), + XKB_KEY_H | XKB_KEY_h => Some(VirtualKeyCode::H), + XKB_KEY_I | XKB_KEY_i => Some(VirtualKeyCode::I), + XKB_KEY_J | XKB_KEY_j => Some(VirtualKeyCode::J), + XKB_KEY_K | XKB_KEY_k => Some(VirtualKeyCode::K), + XKB_KEY_L | XKB_KEY_l => Some(VirtualKeyCode::L), + XKB_KEY_M | XKB_KEY_m => Some(VirtualKeyCode::M), + XKB_KEY_N | XKB_KEY_n => Some(VirtualKeyCode::N), + XKB_KEY_O | XKB_KEY_o => Some(VirtualKeyCode::O), + XKB_KEY_P | XKB_KEY_p => Some(VirtualKeyCode::P), + XKB_KEY_Q | XKB_KEY_q => Some(VirtualKeyCode::Q), + XKB_KEY_R | XKB_KEY_r => Some(VirtualKeyCode::R), + XKB_KEY_S | XKB_KEY_s => Some(VirtualKeyCode::S), + XKB_KEY_T | XKB_KEY_t => Some(VirtualKeyCode::T), + XKB_KEY_U | XKB_KEY_u => Some(VirtualKeyCode::U), + XKB_KEY_V | XKB_KEY_v => Some(VirtualKeyCode::V), + XKB_KEY_W | XKB_KEY_w => Some(VirtualKeyCode::W), + XKB_KEY_X | XKB_KEY_x => Some(VirtualKeyCode::X), + XKB_KEY_Y | XKB_KEY_y => Some(VirtualKeyCode::Y), + XKB_KEY_Z | XKB_KEY_z => Some(VirtualKeyCode::Z), + // Escape. + XKB_KEY_Escape => Some(VirtualKeyCode::Escape), + // Function keys. + XKB_KEY_F1 => Some(VirtualKeyCode::F1), + XKB_KEY_F2 => Some(VirtualKeyCode::F2), + XKB_KEY_F3 => Some(VirtualKeyCode::F3), + XKB_KEY_F4 => Some(VirtualKeyCode::F4), + XKB_KEY_F5 => Some(VirtualKeyCode::F5), + XKB_KEY_F6 => Some(VirtualKeyCode::F6), + XKB_KEY_F7 => Some(VirtualKeyCode::F7), + XKB_KEY_F8 => Some(VirtualKeyCode::F8), + XKB_KEY_F9 => Some(VirtualKeyCode::F9), + XKB_KEY_F10 => Some(VirtualKeyCode::F10), + XKB_KEY_F11 => Some(VirtualKeyCode::F11), + XKB_KEY_F12 => Some(VirtualKeyCode::F12), + XKB_KEY_F13 => Some(VirtualKeyCode::F13), + XKB_KEY_F14 => Some(VirtualKeyCode::F14), + XKB_KEY_F15 => Some(VirtualKeyCode::F15), + XKB_KEY_F16 => Some(VirtualKeyCode::F16), + XKB_KEY_F17 => Some(VirtualKeyCode::F17), + XKB_KEY_F18 => Some(VirtualKeyCode::F18), + XKB_KEY_F19 => Some(VirtualKeyCode::F19), + XKB_KEY_F20 => Some(VirtualKeyCode::F20), + XKB_KEY_F21 => Some(VirtualKeyCode::F21), + XKB_KEY_F22 => Some(VirtualKeyCode::F22), + XKB_KEY_F23 => Some(VirtualKeyCode::F23), + XKB_KEY_F24 => Some(VirtualKeyCode::F24), + // Flow control. + XKB_KEY_Print => Some(VirtualKeyCode::Snapshot), + XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll), + XKB_KEY_Sys_Req => Some(VirtualKeyCode::Sysrq), + XKB_KEY_Pause => Some(VirtualKeyCode::Pause), + XKB_KEY_Insert => Some(VirtualKeyCode::Insert), + XKB_KEY_Home => Some(VirtualKeyCode::Home), + XKB_KEY_Delete => Some(VirtualKeyCode::Delete), + XKB_KEY_End => Some(VirtualKeyCode::End), + XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown), + XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp), + // Arrows. + XKB_KEY_Left => Some(VirtualKeyCode::Left), + XKB_KEY_Up => Some(VirtualKeyCode::Up), + XKB_KEY_Right => Some(VirtualKeyCode::Right), + XKB_KEY_Down => Some(VirtualKeyCode::Down), + + XKB_KEY_BackSpace => Some(VirtualKeyCode::Back), + XKB_KEY_Return => Some(VirtualKeyCode::Return), + XKB_KEY_space => Some(VirtualKeyCode::Space), + + XKB_KEY_Multi_key => Some(VirtualKeyCode::Compose), + XKB_KEY_caret => Some(VirtualKeyCode::Caret), + + // Keypad. + XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock), + XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0), + XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1), + XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2), + XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3), + XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4), + XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5), + XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6), + XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7), + XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8), + XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9), + // Misc. + // => Some(VirtualKeyCode::AbntC1), + // => Some(VirtualKeyCode::AbntC2), + XKB_KEY_plus => Some(VirtualKeyCode::Plus), + XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe), + // => Some(VirtualKeyCode::Apps), + XKB_KEY_at => Some(VirtualKeyCode::At), + // => Some(VirtualKeyCode::Ax), + XKB_KEY_backslash => Some(VirtualKeyCode::Backslash), + XKB_KEY_XF86Calculator => Some(VirtualKeyCode::Calculator), + XKB_KEY_Caps_Lock => Some(VirtualKeyCode::Capital), + XKB_KEY_colon => Some(VirtualKeyCode::Colon), + XKB_KEY_comma => Some(VirtualKeyCode::Comma), + // => Some(VirtualKeyCode::Convert), + XKB_KEY_equal => Some(VirtualKeyCode::Equals), + XKB_KEY_grave => Some(VirtualKeyCode::Grave), + // => Some(VirtualKeyCode::Kana), + XKB_KEY_Kanji => Some(VirtualKeyCode::Kanji), + XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt), + XKB_KEY_bracketleft => Some(VirtualKeyCode::LBracket), + XKB_KEY_Control_L => Some(VirtualKeyCode::LControl), + XKB_KEY_Meta_L => Some(VirtualKeyCode::LWin), + XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift), + XKB_KEY_Super_L => Some(VirtualKeyCode::LWin), + XKB_KEY_XF86Mail => Some(VirtualKeyCode::Mail), + XKB_KEY_XF86AudioStop => Some(VirtualKeyCode::MediaStop), + // => Some(VirtualKeyCode::MediaSelect), + XKB_KEY_minus => Some(VirtualKeyCode::Minus), + XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk), + XKB_KEY_XF86AudioMute => Some(VirtualKeyCode::Mute), + XKB_KEY_XF86MyComputer => Some(VirtualKeyCode::MyComputer), + XKB_KEY_XF86AudioNext => Some(VirtualKeyCode::NextTrack), + // => Some(VirtualKeyCode::NoConvert), + XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma), + XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter), + XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals), + XKB_KEY_KP_Add => Some(VirtualKeyCode::NumpadAdd), + XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract), + XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply), + XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide), + XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal), + XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp), + XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown), + XKB_KEY_KP_Home => Some(VirtualKeyCode::Home), + XKB_KEY_KP_End => Some(VirtualKeyCode::End), + XKB_KEY_KP_Left => Some(VirtualKeyCode::Left), + XKB_KEY_KP_Up => Some(VirtualKeyCode::Up), + XKB_KEY_KP_Right => Some(VirtualKeyCode::Right), + XKB_KEY_KP_Down => Some(VirtualKeyCode::Down), + // => Some(VirtualKeyCode::OEM102), + XKB_KEY_period => Some(VirtualKeyCode::Period), + // => Some(VirtualKeyCode::Playpause), + XKB_KEY_XF86PowerOff => Some(VirtualKeyCode::Power), + XKB_KEY_XF86AudioPrev => Some(VirtualKeyCode::PrevTrack), + XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt), + XKB_KEY_bracketright => Some(VirtualKeyCode::RBracket), + XKB_KEY_Control_R => Some(VirtualKeyCode::RControl), + XKB_KEY_Meta_R => Some(VirtualKeyCode::RWin), + XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift), + XKB_KEY_Super_R => Some(VirtualKeyCode::RWin), + XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon), + XKB_KEY_slash => Some(VirtualKeyCode::Slash), + XKB_KEY_XF86Sleep => Some(VirtualKeyCode::Sleep), + XKB_KEY_XF86Stop => Some(VirtualKeyCode::Stop), + XKB_KEY_Tab => Some(VirtualKeyCode::Tab), + XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab), + XKB_KEY_underscore => Some(VirtualKeyCode::Underline), + // => Some(VirtualKeyCode::Unlabeled), + XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown), + XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp), + XKB_KEY_XF86WakeUp => Some(VirtualKeyCode::Wake), + // => Some(VirtualKeyCode::Webback), + XKB_KEY_XF86Favorites => Some(VirtualKeyCode::WebFavorites), + // => Some(VirtualKeyCode::WebForward), + XKB_KEY_XF86HomePage => Some(VirtualKeyCode::WebHome), + XKB_KEY_XF86Refresh => Some(VirtualKeyCode::WebRefresh), + // => Some(VirtualKeyCode::WebSearch), + // => Some(VirtualKeyCode::WebStop), + XKB_KEY_yen => Some(VirtualKeyCode::Yen), + XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy), + XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste), + XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut), + // Fallback. + _ => None, + } +} diff --git a/src/window.rs b/src/window.rs index 96189872f8..87d5184733 100644 --- a/src/window.rs +++ b/src/window.rs @@ -632,7 +632,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android:** Unsupported. + /// - **iOS / Android / DRM:** Unsupported. #[inline] pub fn set_title(&self, title: &str) { self.window.set_title(title) @@ -643,7 +643,7 @@ impl Window { /// If `false`, this will hide the window. If `true`, this will show the window. /// ## Platform-specific /// - /// - **Android / Wayland / Web:** Unsupported. + /// - **Android / Wayland / Web / DRM:** Unsupported. /// - **iOS:** Can only be called on the main thread. #[inline] pub fn set_visible(&self, visible: bool) { @@ -657,7 +657,7 @@ impl Window { /// ## Platform-specific /// /// - **X11:** Not implemented. - /// - **Wayland / iOS / Android / Web:** Unsupported. + /// - **Wayland / DRM / iOS / Android / Web:** Unsupported. #[inline] pub fn is_visible(&self) -> Option { self.window.is_visible() @@ -677,7 +677,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web:** Unsupported. + /// - **iOS / Android / Web / DRM:** Unsupported. #[inline] pub fn set_resizable(&self, resizable: bool) { self.window.set_resizable(resizable) @@ -698,7 +698,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web:** Unsupported. + /// - **DRM / iOS / Android / Web:** Unsupported. /// - **Wayland:** Un-minimize is unsupported. #[inline] pub fn set_minimized(&self, minimized: bool) { @@ -709,7 +709,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web:** Unsupported. + /// - **DRM / iOS / Android / Web:** Unsupported. #[inline] pub fn set_maximized(&self, maximized: bool) { self.window.set_maximized(maximized) @@ -744,6 +744,8 @@ impl Window { /// - **iOS:** Can only be called on the main thread. /// - **Wayland:** Does not support exclusive fullscreen mode and will no-op a request. /// - **Windows:** Screen saver is disabled in fullscreen mode. + /// - **DRM:** Fullscreen mode must be a valid exclusive fullscreen containing a video mode + /// from [`MonitorHandle::video_modes`] (will not panic if this condition is not met) /// - **Android:** Unsupported. #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { @@ -757,6 +759,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. /// - **Android:** Will always return `None`. /// - **Wayland:** Can return `Borderless(None)` when there are no monitors. + /// - **DRM:** Always returns the current video mode containing the current [`VideoMode`] #[inline] pub fn fullscreen(&self) -> Option { self.window.fullscreen() @@ -766,7 +769,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web:** Unsupported. + /// - **DRM / iOS / Android / Web:** Unsupported. /// /// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc #[inline] @@ -779,7 +782,7 @@ impl Window { /// ## Platform-specific /// /// - **X11:** Not implemented. - /// - **iOS / Android / Web:** Unsupported. + /// - **DRM / iOS / Android / Web:** Unsupported. #[inline] pub fn is_decorated(&self) -> bool { self.window.is_decorated() @@ -789,7 +792,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Wayland:** Unsupported. + /// - **DRM / iOS / Android / Web / Wayland:** Unsupported. #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { self.window.set_always_on_top(always_on_top) @@ -800,7 +803,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Wayland / macOS:** Unsupported. + /// - **iOS / Android / Web / Wayland / DRM / macOS:** Unsupported. /// /// On Windows, this sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32. @@ -836,7 +839,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web:** Unsupported. + /// - **DRM / iOS / Android / Web:** Unsupported. /// /// [chinese]: https://support.apple.com/guide/chinese-input-method/use-the-candidate-window-cim12992/104/mac/12.0 /// [japanese]: https://support.apple.com/guide/japanese-input-method/use-the-candidate-window-jpim10262/6.3/mac/12.0 @@ -862,7 +865,7 @@ impl Window { /// ## Platform-specific /// /// - **macOS:** IME must be enabled to receive text-input where dead-key sequences are combined. - /// - ** iOS / Android / Web :** Unsupported. + /// - **DRM / iOS / Android / Web :** Unsupported. /// /// [`Ime`]: crate::event::WindowEvent::Ime /// [`KeyboardInput`]: crate::event::WindowEvent::KeyboardInput @@ -881,7 +884,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Wayland:** Unsupported. + /// - **DRM / iOS / Android / Web / Wayland:** Unsupported. #[inline] pub fn focus_window(&self) { self.window.focus_window() @@ -896,7 +899,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web :** Unsupported. + /// - **DRM / iOS / Android / Web :** Unsupported. /// - **macOS:** `None` has no effect. /// - **X11:** Requests for user attention must be manually cleared. /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. @@ -912,7 +915,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android:** Unsupported. + /// - **DRM / iOS / Android:** Unsupported. #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { self.window.set_cursor_icon(cursor); @@ -949,6 +952,7 @@ impl Window { /// ## Platform-specific /// /// - **macOS:** This locks the cursor in a fixed location, which looks visually awkward. + /// - **DRM:** Has no effect /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { @@ -966,7 +970,7 @@ impl Window { /// - **Wayland:** The cursor is only hidden within the confines of the window. /// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is /// outside of the window. - /// - **iOS / Android:** Unsupported. + /// - **DRM / iOS / Android:** Unsupported. #[inline] pub fn set_cursor_visible(&self, visible: bool) { self.window.set_cursor_visible(visible) @@ -982,7 +986,7 @@ impl Window { /// - **X11:** Un-grabs the cursor. /// - **Wayland:** Requires the cursor to be inside the window to be dragged. /// - **macOS:** May prevent the button release event to be triggered. - /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. + /// - **DRM / iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { self.window.drag_window() @@ -995,7 +999,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / X11:** Always returns an [`ExternalError::NotSupported`]. + /// - **DRM / iOS / Android / Web / X11:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { self.window.set_cursor_hittest(hittest)