From 66ca445caa4f66ece3f6159e439a1d0c7707ee39 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 5 Jan 2023 06:58:08 -0700 Subject: [PATCH] Redox OS support (#2588) * Add Redox OS support * Simplify control flow usage * Apply more recommendations * Update naming to indicate that Orbital is a platform * Adjust import order --- .github/CODEOWNERS | 4 + .github/workflows/ci.yml | 11 +- CHANGELOG.md | 1 + Cargo.toml | 4 + FEATURES.md | 93 ++-- README.md | 5 + build.rs | 6 +- examples/window_run_return.rs | 3 +- src/event.rs | 6 +- src/event_loop.rs | 2 +- src/monitor.rs | 2 +- src/platform/mod.rs | 5 +- src/platform/orbital.rs | 1 + src/platform_impl/mod.rs | 4 + src/platform_impl/orbital/event_loop.rs | 709 ++++++++++++++++++++++++ src/platform_impl/orbital/mod.rs | 253 +++++++++ src/platform_impl/orbital/window.rs | 396 +++++++++++++ src/window.rs | 56 +- 18 files changed, 1476 insertions(+), 85 deletions(-) create mode 100644 src/platform/orbital.rs create mode 100644 src/platform_impl/orbital/event_loop.rs create mode 100644 src/platform_impl/orbital/mod.rs create mode 100644 src/platform_impl/orbital/window.rs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index daa94dde..ce608746 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -35,3 +35,7 @@ # Windows /src/platform/windows.rs @msiglreith /src/platform_impl/windows @msiglreith + +# Orbital (Redox OS) +/src/platform/orbital.rs @jackpot51 +/src/platform_impl/orbital @jackpot51 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63f3c34e..54e62843 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,7 @@ jobs: - { 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: aarch64-linux-android, os: ubuntu-latest, options: -p winit, cmd: 'apk --', features: "android-native-activity" } + - { target: x86_64-unknown-redox, os: ubuntu-latest, } - { target: x86_64-apple-darwin, os: macos-latest, } - { target: x86_64-apple-ios, os: macos-latest, } - { target: aarch64-apple-ios, os: macos-latest, } @@ -83,7 +84,9 @@ jobs: - name: Build tests shell: bash - if: matrix.rust_version != '1.60.0' + if: > + !contains(matrix.platform.target, 'redox') && + matrix.rust_version != '1.60.0' run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES - name: Run tests @@ -92,6 +95,7 @@ jobs: !contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32') && + !contains(matrix.platform.target, 'redox') && matrix.rust_version != '1.60.0' run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES @@ -102,7 +106,9 @@ jobs: - name: Build tests with serde enabled shell: bash - if: matrix.rust_version != '1.60.0' + if: > + !contains(matrix.platform.target, 'redox') && + matrix.rust_version != '1.60.0' run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES - name: Run tests with serde enabled shell: bash @@ -110,5 +116,6 @@ jobs: !contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32') && + !contains(matrix.platform.target, 'redox') && matrix.rust_version != '1.60.0' run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES diff --git a/CHANGELOG.md b/CHANGELOG.md index b4d12343..242304d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ And please only add new entries to the top of this list, right below the `# Unre - **Breaking:** On Android, switched to using [`android-activity`](https://github.com/rib/android-activity) crate as a glue layer instead of [`ndk-glue`](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue). See [README.md#Android](https://github.com/rust-windowing/winit#Android) for more details. ([#2444](https://github.com/rust-windowing/winit/pull/2444)) - **Breaking:** Removed support for `raw-window-handle` version `0.4` - On Wayland, `RedrawRequested` not emitted during resize. +- Added Orbital support for Redox OS # 0.27.5 diff --git a/Cargo.toml b/Cargo.toml index c86d1dee..5c814180 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,6 +113,10 @@ x11-dl = { version = "2.18.5", optional = true } percent-encoding = { version = "2.0", optional = true } libc = "0.2.64" +[target.'cfg(target_os = "redox")'.dependencies] +orbclient = { version = "0.3.42", default-features = false } +redox_syscall = "0.3" + [target.'cfg(target_arch = "wasm32")'.dependencies.web_sys] package = "web-sys" version = "0.3.22" diff --git a/FEATURES.md b/FEATURES.md index 460f61ff..285332af 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 + - Redox OS, via Orbital - Mobile - iOS - Android @@ -171,62 +172,62 @@ 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 |✔️ |✔️ |✔️ |✔️ |**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**|**N/A**|**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 |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ | -|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**| +|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |Redox OS| +|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | +|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ | +|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ | +|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** | +|Window decorations |✔️ |✔️ |✔️ |✔️ |**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**|✔️ |✔️ | +|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** | +|Window transparency |✔️ |✔️ |✔️ |✔️ |**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**|**N/A** | +|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ | +|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** | ### System information -|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | -|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | -|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**| -|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**| +|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS| +|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ | +|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**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 |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**| -|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ | -|Cursor confining |✔️ |❌ |✔️ |✔️ |**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|Android|iOS |WASM |Redox OS| +|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | +|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ | +|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** | +|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ | +|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ | +|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** | +|Cursor hittest |✔️ |✔️ |❌ |✔️ |**N/A**|**N/A**|❌ |❌ | +|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** | +|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** | +|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** | +|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ | +|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |**N/A** | +|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** | +|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** | +|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** | +|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]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ | -|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |✔️ |✔️ |❓ | -|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❓ | +|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS| +|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | +|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ | +|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |✔️ |✔️ |❓ |❓ | +|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❓ |❓ | ### Completed API Reworks -|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | -|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | +|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS| +|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | [#165]: https://github.com/rust-windowing/winit/issues/165 [#219]: https://github.com/rust-windowing/winit/issues/219 diff --git a/README.md b/README.md index d4a39646..0c61de2d 100644 --- a/README.md +++ b/README.md @@ -209,3 +209,8 @@ inside `Event::NewEvents(StartCause::Init)`. [#2051]: https://github.com/rust-windowing/winit/issues/2051 [#2087]: https://github.com/rust-windowing/winit/issues/2087 [#1705]: https://github.com/rust-windowing/winit/issues/1705 + +#### Redox OS + +Redox OS has some functionality not present yet, that will be implemented when +its orbital display server provides it. diff --git a/build.rs b/build.rs index 3f802290..b35823ab 100644 --- a/build.rs +++ b/build.rs @@ -13,9 +13,11 @@ fn main() { windows_platform: { target_os = "windows" }, apple: { any(target_os = "ios", target_os = "macos") }, free_unix: { all(unix, not(apple), not(android_platform)) }, + redox: { target_os = "redox" }, // Native displays. - x11_platform: { all(feature = "x11", free_unix, not(wasm)) }, - wayland_platform: { all(feature = "wayland", free_unix, not(wasm)) }, + x11_platform: { all(feature = "x11", free_unix, not(wasm), not(redox)) }, + wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) }, + orbital_platform: { redox }, } } diff --git a/examples/window_run_return.rs b/examples/window_run_return.rs index f9ef8dbf..395076b5 100644 --- a/examples/window_run_return.rs +++ b/examples/window_run_return.rs @@ -6,7 +6,8 @@ macos_platform, x11_platform, wayland_platform, - android_platform + android_platform, + orbital_platform, ))] fn main() { use std::{thread::sleep, time::Duration}; diff --git a/src/event.rs b/src/event.rs index 7e169c4b..d2eb37eb 100644 --- a/src/event.rs +++ b/src/event.rs @@ -391,7 +391,7 @@ pub enum WindowEvent<'a> { /// /// ## Platform-specific /// - /// - **iOS / Android / Web:** Unsupported. + /// - **iOS / Android / Web / Orbital:** Unsupported. Ime(Ime), /// The cursor has moved on the window. @@ -508,7 +508,7 @@ pub enum WindowEvent<'a> { /// /// ## Platform-specific /// - /// - **iOS / Android / X11 / Wayland:** Unsupported. + /// - **iOS / Android / X11 / Wayland / Orbital:** Unsupported. ThemeChanged(Theme), /// The window has been occluded (completely hidden from view). @@ -517,7 +517,7 @@ pub enum WindowEvent<'a> { /// minimised, set invisible, or fully occluded by another window. /// /// Platform-specific behavior: - /// - **iOS / Android / Web / Wayland / Windows:** Unsupported. + /// - **iOS / Android / Web / Wayland / Windows / Orbital:** Unsupported. Occluded(bool), } diff --git a/src/event_loop.rs b/src/event_loop.rs index 6b0df65b..0b0c4ecf 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -335,7 +335,7 @@ impl EventLoopWindowTarget { /// /// ## Platform-specific /// - /// - **Wayland / macOS / iOS / Android / Web:** Unsupported. + /// - **Wayland / macOS / iOS / Android / Web / Orbital:** Unsupported. /// /// [`DeviceEvent`]: crate::event::DeviceEvent pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) { diff --git a/src/monitor.rs b/src/monitor.rs index 641214f3..d2682684 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -61,7 +61,7 @@ impl VideoMode { /// /// ## Platform-specific /// - /// - **Wayland:** Always returns 32. + /// - **Wayland / Orbital:** Always returns 32. /// - **iOS:** Always returns 32. #[inline] pub fn bit_depth(&self) -> u16 { diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 8ca16032..91c50777 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -21,6 +21,8 @@ pub mod android; pub mod ios; #[cfg(macos_platform)] pub mod macos; +#[cfg(orbital_platform)] +pub mod orbital; #[cfg(wayland_platform)] pub mod wayland; #[cfg(wasm_platform)] @@ -35,6 +37,7 @@ pub mod x11; macos_platform, android_platform, x11_platform, - wayland_platform + wayland_platform, + orbital_platform ))] pub mod run_return; diff --git a/src/platform/orbital.rs b/src/platform/orbital.rs new file mode 100644 index 00000000..7491c2e6 --- /dev/null +++ b/src/platform/orbital.rs @@ -0,0 +1 @@ +// There are no Orbital specific traits yet. diff --git a/src/platform_impl/mod.rs b/src/platform_impl/mod.rs index a81b4403..3b2b2613 100644 --- a/src/platform_impl/mod.rs +++ b/src/platform_impl/mod.rs @@ -19,6 +19,9 @@ mod platform; #[cfg(wasm_platform)] #[path = "web/mod.rs"] mod platform; +#[cfg(orbital_platform)] +#[path = "orbital/mod.rs"] +mod platform; pub use self::platform::*; @@ -59,5 +62,6 @@ impl From for RootFullscreen { not(x11_platform), not(wayland_platform), not(wasm_platform), + not(orbital_platform), ))] compile_error!("The platform you're compiling for is not supported by winit"); diff --git a/src/platform_impl/orbital/event_loop.rs b/src/platform_impl/orbital/event_loop.rs new file mode 100644 index 00000000..9df7dbd1 --- /dev/null +++ b/src/platform_impl/orbital/event_loop.rs @@ -0,0 +1,709 @@ +use std::{ + collections::VecDeque, + mem, slice, + sync::{mpsc, Arc, Mutex}, + time::Instant, +}; + +use orbclient::{ + ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MoveEvent, QuitEvent, + ResizeEvent, ScrollEvent, TextInputEvent, +}; +use raw_window_handle::{OrbitalDisplayHandle, RawDisplayHandle}; + +use crate::{ + event::{self, StartCause, VirtualKeyCode}, + event_loop::{self, ControlFlow}, + window::WindowId as RootWindowId, +}; + +use super::{ + DeviceId, MonitorHandle, PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket, + WindowId, WindowProperties, +}; + +fn convert_scancode(scancode: u8) -> Option { + match scancode { + orbclient::K_A => Some(VirtualKeyCode::A), + orbclient::K_B => Some(VirtualKeyCode::B), + orbclient::K_C => Some(VirtualKeyCode::C), + orbclient::K_D => Some(VirtualKeyCode::D), + orbclient::K_E => Some(VirtualKeyCode::E), + orbclient::K_F => Some(VirtualKeyCode::F), + orbclient::K_G => Some(VirtualKeyCode::G), + orbclient::K_H => Some(VirtualKeyCode::H), + orbclient::K_I => Some(VirtualKeyCode::I), + orbclient::K_J => Some(VirtualKeyCode::J), + orbclient::K_K => Some(VirtualKeyCode::K), + orbclient::K_L => Some(VirtualKeyCode::L), + orbclient::K_M => Some(VirtualKeyCode::M), + orbclient::K_N => Some(VirtualKeyCode::N), + orbclient::K_O => Some(VirtualKeyCode::O), + orbclient::K_P => Some(VirtualKeyCode::P), + orbclient::K_Q => Some(VirtualKeyCode::Q), + orbclient::K_R => Some(VirtualKeyCode::R), + orbclient::K_S => Some(VirtualKeyCode::S), + orbclient::K_T => Some(VirtualKeyCode::T), + orbclient::K_U => Some(VirtualKeyCode::U), + orbclient::K_V => Some(VirtualKeyCode::V), + orbclient::K_W => Some(VirtualKeyCode::W), + orbclient::K_X => Some(VirtualKeyCode::X), + orbclient::K_Y => Some(VirtualKeyCode::Y), + orbclient::K_Z => Some(VirtualKeyCode::Z), + orbclient::K_0 => Some(VirtualKeyCode::Key0), + orbclient::K_1 => Some(VirtualKeyCode::Key1), + orbclient::K_2 => Some(VirtualKeyCode::Key2), + orbclient::K_3 => Some(VirtualKeyCode::Key3), + orbclient::K_4 => Some(VirtualKeyCode::Key4), + orbclient::K_5 => Some(VirtualKeyCode::Key5), + orbclient::K_6 => Some(VirtualKeyCode::Key6), + orbclient::K_7 => Some(VirtualKeyCode::Key7), + orbclient::K_8 => Some(VirtualKeyCode::Key8), + orbclient::K_9 => Some(VirtualKeyCode::Key9), + + orbclient::K_TICK => Some(VirtualKeyCode::Grave), + orbclient::K_MINUS => Some(VirtualKeyCode::Minus), + orbclient::K_EQUALS => Some(VirtualKeyCode::Equals), + orbclient::K_BACKSLASH => Some(VirtualKeyCode::Backslash), + orbclient::K_BRACE_OPEN => Some(VirtualKeyCode::LBracket), + orbclient::K_BRACE_CLOSE => Some(VirtualKeyCode::RBracket), + orbclient::K_SEMICOLON => Some(VirtualKeyCode::Semicolon), + orbclient::K_QUOTE => Some(VirtualKeyCode::Apostrophe), + orbclient::K_COMMA => Some(VirtualKeyCode::Comma), + orbclient::K_PERIOD => Some(VirtualKeyCode::Period), + orbclient::K_SLASH => Some(VirtualKeyCode::Slash), + orbclient::K_BKSP => Some(VirtualKeyCode::Back), + orbclient::K_SPACE => Some(VirtualKeyCode::Space), + orbclient::K_TAB => Some(VirtualKeyCode::Tab), + //orbclient::K_CAPS => Some(VirtualKeyCode::CAPS), + orbclient::K_LEFT_SHIFT => Some(VirtualKeyCode::LShift), + orbclient::K_RIGHT_SHIFT => Some(VirtualKeyCode::RShift), + orbclient::K_CTRL => Some(VirtualKeyCode::LControl), + orbclient::K_ALT => Some(VirtualKeyCode::LAlt), + orbclient::K_ENTER => Some(VirtualKeyCode::Return), + orbclient::K_ESC => Some(VirtualKeyCode::Escape), + orbclient::K_F1 => Some(VirtualKeyCode::F1), + orbclient::K_F2 => Some(VirtualKeyCode::F2), + orbclient::K_F3 => Some(VirtualKeyCode::F3), + orbclient::K_F4 => Some(VirtualKeyCode::F4), + orbclient::K_F5 => Some(VirtualKeyCode::F5), + orbclient::K_F6 => Some(VirtualKeyCode::F6), + orbclient::K_F7 => Some(VirtualKeyCode::F7), + orbclient::K_F8 => Some(VirtualKeyCode::F8), + orbclient::K_F9 => Some(VirtualKeyCode::F9), + orbclient::K_F10 => Some(VirtualKeyCode::F10), + orbclient::K_HOME => Some(VirtualKeyCode::Home), + orbclient::K_UP => Some(VirtualKeyCode::Up), + orbclient::K_PGUP => Some(VirtualKeyCode::PageUp), + orbclient::K_LEFT => Some(VirtualKeyCode::Left), + orbclient::K_RIGHT => Some(VirtualKeyCode::Right), + orbclient::K_END => Some(VirtualKeyCode::End), + orbclient::K_DOWN => Some(VirtualKeyCode::Down), + orbclient::K_PGDN => Some(VirtualKeyCode::PageDown), + orbclient::K_DEL => Some(VirtualKeyCode::Delete), + orbclient::K_F11 => Some(VirtualKeyCode::F11), + orbclient::K_F12 => Some(VirtualKeyCode::F12), + + _ => None, + } +} + +fn element_state(pressed: bool) -> event::ElementState { + if pressed { + event::ElementState::Pressed + } else { + event::ElementState::Released + } +} + +bitflags! { + #[derive(Default)] + struct KeyboardModifierState: u8 { + const LSHIFT = 1 << 0; + const RSHIFT = 1 << 1; + const LCTRL = 1 << 2; + const RCTRL = 1 << 3; + const LALT = 1 << 4; + const RALT = 1 << 5; + const LSUPER = 1 << 6; + const RSUPER = 1 << 7; + } +} + +bitflags! { + #[derive(Default)] + struct MouseButtonState: u8 { + const LEFT = 1 << 0; + const MIDDLE = 1 << 1; + const RIGHT = 1 << 2; + } +} + +#[derive(Default)] +struct EventState { + keyboard: KeyboardModifierState, + mouse: MouseButtonState, + resize_opt: Option<(u32, u32)>, +} + +impl EventState { + fn key(&mut self, vk: VirtualKeyCode, pressed: bool) { + match vk { + VirtualKeyCode::LShift => self.keyboard.set(KeyboardModifierState::LSHIFT, pressed), + VirtualKeyCode::RShift => self.keyboard.set(KeyboardModifierState::RSHIFT, pressed), + VirtualKeyCode::LControl => self.keyboard.set(KeyboardModifierState::LCTRL, pressed), + VirtualKeyCode::RControl => self.keyboard.set(KeyboardModifierState::RCTRL, pressed), + VirtualKeyCode::LAlt => self.keyboard.set(KeyboardModifierState::LALT, pressed), + VirtualKeyCode::RAlt => self.keyboard.set(KeyboardModifierState::RALT, pressed), + VirtualKeyCode::LWin => self.keyboard.set(KeyboardModifierState::LSUPER, pressed), + VirtualKeyCode::RWin => self.keyboard.set(KeyboardModifierState::RSUPER, pressed), + _ => (), + } + } + + fn mouse( + &mut self, + left: bool, + middle: bool, + right: bool, + ) -> Option<(event::MouseButton, event::ElementState)> { + if self.mouse.contains(MouseButtonState::LEFT) != left { + self.mouse.set(MouseButtonState::LEFT, left); + return Some((event::MouseButton::Left, element_state(left))); + } + + if self.mouse.contains(MouseButtonState::MIDDLE) != middle { + self.mouse.set(MouseButtonState::MIDDLE, middle); + return Some((event::MouseButton::Middle, element_state(middle))); + } + + if self.mouse.contains(MouseButtonState::RIGHT) != right { + self.mouse.set(MouseButtonState::RIGHT, right); + return Some((event::MouseButton::Right, element_state(right))); + } + + None + } + + fn modifiers(&self) -> event::ModifiersState { + let mut modifiers = event::ModifiersState::empty(); + if self + .keyboard + .intersects(KeyboardModifierState::LSHIFT | KeyboardModifierState::RSHIFT) + { + modifiers |= event::ModifiersState::SHIFT; + } + if self + .keyboard + .intersects(KeyboardModifierState::LCTRL | KeyboardModifierState::RCTRL) + { + modifiers |= event::ModifiersState::CTRL; + } + if self + .keyboard + .intersects(KeyboardModifierState::LALT | KeyboardModifierState::RALT) + { + modifiers |= event::ModifiersState::ALT; + } + if self + .keyboard + .intersects(KeyboardModifierState::LSUPER | KeyboardModifierState::RSUPER) + { + modifiers |= event::ModifiersState::LOGO + } + modifiers + } +} + +pub struct EventLoop { + windows: Vec<(Arc, EventState)>, + window_target: event_loop::EventLoopWindowTarget, +} + +impl EventLoop { + pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self { + let (user_events_sender, user_events_receiver) = mpsc::channel(); + + let event_socket = Arc::new(RedoxSocket::event().unwrap()); + + let wake_socket = Arc::new(TimeSocket::open().unwrap()); + + event_socket + .write(&syscall::Event { + id: wake_socket.0.fd, + flags: syscall::EventFlags::EVENT_READ, + data: wake_socket.0.fd, + }) + .unwrap(); + + Self { + windows: Vec::new(), + window_target: event_loop::EventLoopWindowTarget { + p: EventLoopWindowTarget { + user_events_sender, + user_events_receiver, + creates: Mutex::new(VecDeque::new()), + redraws: Arc::new(Mutex::new(VecDeque::new())), + destroys: Arc::new(Mutex::new(VecDeque::new())), + event_socket, + wake_socket, + }, + _marker: std::marker::PhantomData, + }, + } + } + + pub fn run(mut self, event_handler: F) -> ! + where + F: 'static + + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + let exit_code = self.run_return(event_handler); + ::std::process::exit(exit_code); + } + + fn process_event( + window_id: WindowId, + event_option: EventOption, + event_state: &mut EventState, + mut event_handler: F, + ) where + F: FnMut(event::Event<'_, T>), + { + match event_option { + EventOption::Key(KeyEvent { + character: _, + scancode, + pressed, + }) => { + if scancode != 0 { + let vk_opt = convert_scancode(scancode); + if let Some(vk) = vk_opt { + event_state.key(vk, pressed); + } + event_handler( + #[allow(deprecated)] + event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::KeyboardInput { + device_id: event::DeviceId(DeviceId), + input: event::KeyboardInput { + scancode: scancode as u32, + state: element_state(pressed), + virtual_keycode: vk_opt, + modifiers: event_state.modifiers(), + }, + is_synthetic: false, + }, + }, + ); + } + } + EventOption::TextInput(TextInputEvent { character }) => { + event_handler(event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::ReceivedCharacter(character), + }); + } + EventOption::Mouse(MouseEvent { x, y }) => { + event_handler(event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::CursorMoved { + device_id: event::DeviceId(DeviceId), + position: (x, y).into(), + modifiers: event_state.modifiers(), + }, + }); + } + EventOption::Button(ButtonEvent { + left, + middle, + right, + }) => { + while let Some((button, state)) = event_state.mouse(left, middle, right) { + event_handler(event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::MouseInput { + device_id: event::DeviceId(DeviceId), + state, + button, + modifiers: event_state.modifiers(), + }, + }); + } + } + EventOption::Scroll(ScrollEvent { x, y }) => { + event_handler(event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::MouseWheel { + device_id: event::DeviceId(DeviceId), + delta: event::MouseScrollDelta::LineDelta(x as f32, y as f32), + phase: event::TouchPhase::Moved, + modifiers: event_state.modifiers(), + }, + }); + } + EventOption::Quit(QuitEvent {}) => { + event_handler(event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::CloseRequested, + }); + } + EventOption::Focus(FocusEvent { focused }) => { + event_handler(event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::Focused(focused), + }); + } + EventOption::Move(MoveEvent { x, y }) => { + event_handler(event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::Moved((x, y).into()), + }); + } + EventOption::Resize(ResizeEvent { width, height }) => { + event_handler(event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::Resized((width, height).into()), + }); + + // Acknowledge resize after event loop. + event_state.resize_opt = Some((width, height)); + } + //TODO: Clipboard + EventOption::Hover(HoverEvent { entered }) => { + if entered { + event_handler(event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::CursorEntered { + device_id: event::DeviceId(DeviceId), + }, + }); + } else { + event_handler(event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::CursorLeft { + device_id: event::DeviceId(DeviceId), + }, + }); + } + } + other => { + warn!("unhandled event: {:?}", other); + } + } + } + + pub fn run_return(&mut self, mut event_handler_inner: F) -> i32 + where + F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + // Wrapper for event handler function that prevents ExitWithCode from being unset. + let mut event_handler = + move |event: event::Event<'_, T>, + window_target: &event_loop::EventLoopWindowTarget, + control_flow: &mut ControlFlow| { + if let ControlFlow::ExitWithCode(code) = control_flow { + event_handler_inner( + event, + window_target, + &mut ControlFlow::ExitWithCode(*code), + ); + } else { + event_handler_inner(event, window_target, control_flow); + } + }; + + let mut control_flow = ControlFlow::default(); + let mut start_cause = StartCause::Init; + + let code = loop { + event_handler( + event::Event::NewEvents(start_cause), + &self.window_target, + &mut control_flow, + ); + + if start_cause == StartCause::Init { + event_handler( + event::Event::Resumed, + &self.window_target, + &mut control_flow, + ); + } + + // Handle window creates. + while let Some(window) = { + let mut creates = self.window_target.p.creates.lock().unwrap(); + creates.pop_front() + } { + let window_id = WindowId { + fd: window.fd as u64, + }; + + let mut buf: [u8; 4096] = [0; 4096]; + let path = window.fpath(&mut buf).expect("failed to read properties"); + let properties = WindowProperties::new(path); + + self.windows.push((window, EventState::default())); + + // Send resize event on create to indicate first size. + event_handler( + event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::Resized((properties.w, properties.h).into()), + }, + &self.window_target, + &mut control_flow, + ); + + // Send resize event on create to indicate first position. + event_handler( + event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::Moved((properties.x, properties.y).into()), + }, + &self.window_target, + &mut control_flow, + ); + } + + // Handle window destroys. + while let Some(destroy_id) = { + let mut destroys = self.window_target.p.destroys.lock().unwrap(); + destroys.pop_front() + } { + event_handler( + event::Event::WindowEvent { + window_id: RootWindowId(destroy_id), + event: event::WindowEvent::Destroyed, + }, + &self.window_target, + &mut control_flow, + ); + + self.windows + .retain(|(window, _event_state)| window.fd as u64 != destroy_id.fd); + } + + // Handle window events. + let mut i = 0; + // While loop is used here because the same window may be processed more than once. + while let Some((window, event_state)) = self.windows.get_mut(i) { + let window_id = WindowId { + fd: window.fd as u64, + }; + + let mut event_buf = [0u8; 16 * mem::size_of::()]; + let count = + syscall::read(window.fd, &mut event_buf).expect("failed to read window events"); + // Safety: orbclient::Event is a packed struct designed to be transferred over a socket. + let events = unsafe { + slice::from_raw_parts( + event_buf.as_ptr() as *const orbclient::Event, + count / mem::size_of::(), + ) + }; + + for orbital_event in events { + Self::process_event( + window_id, + orbital_event.to_option(), + event_state, + |event| event_handler(event, &self.window_target, &mut control_flow), + ); + } + + if count == event_buf.len() { + // If event buf was full, process same window again to ensure all events are drained. + continue; + } + + // Acknowledge the latest resize event. + if let Some((w, h)) = event_state.resize_opt.take() { + window + .write(format!("S,{},{}", w, h).as_bytes()) + .expect("failed to acknowledge resize"); + + // Require redraw after resize. + let mut redraws = self.window_target.p.redraws.lock().unwrap(); + if !redraws.contains(&window_id) { + redraws.push_back(window_id); + } + } + + // Move to next window. + i += 1; + } + + while let Ok(event) = self.window_target.p.user_events_receiver.try_recv() { + event_handler( + event::Event::UserEvent(event), + &self.window_target, + &mut control_flow, + ); + } + + event_handler( + event::Event::MainEventsCleared, + &self.window_target, + &mut control_flow, + ); + + // To avoid deadlocks the redraws lock is not held during event processing. + while let Some(window_id) = { + let mut redraws = self.window_target.p.redraws.lock().unwrap(); + redraws.pop_front() + } { + event_handler( + event::Event::RedrawRequested(RootWindowId(window_id)), + &self.window_target, + &mut control_flow, + ); + } + + event_handler( + event::Event::RedrawEventsCleared, + &self.window_target, + &mut control_flow, + ); + + let requested_resume = match control_flow { + ControlFlow::Poll => { + start_cause = StartCause::Poll; + continue; + } + ControlFlow::Wait => None, + ControlFlow::WaitUntil(instant) => Some(instant), + ControlFlow::ExitWithCode(code) => break code, + }; + + // Re-using wake socket caused extra wake events before because there were leftover + // timeouts, and then new timeouts were added each time a spurious timeout expired. + let timeout_socket = TimeSocket::open().unwrap(); + + self.window_target + .p + .event_socket + .write(&syscall::Event { + id: timeout_socket.0.fd, + flags: syscall::EventFlags::EVENT_READ, + data: 0, + }) + .unwrap(); + + let start = Instant::now(); + if let Some(instant) = requested_resume { + let mut time = timeout_socket.current_time().unwrap(); + + if let Some(duration) = instant.checked_duration_since(start) { + time.tv_sec += duration.as_secs() as i64; + time.tv_nsec += duration.subsec_nanos() as i32; + // Normalize timespec so tv_nsec is not greater than one second. + while time.tv_nsec >= 1_000_000_000 { + time.tv_sec += 1; + time.tv_nsec -= 1_000_000_000; + } + } + + timeout_socket.timeout(&time).unwrap(); + } + + // Wait for event if needed. + let mut event = syscall::Event::default(); + self.window_target.p.event_socket.read(&mut event).unwrap(); + + // TODO: handle spurious wakeups (redraw caused wakeup but redraw already handled) + match requested_resume { + Some(requested_resume) if event.id == timeout_socket.0.fd => { + // If the event is from the special timeout socket, report that resume + // time was reached. + start_cause = StartCause::ResumeTimeReached { + start, + requested_resume, + }; + } + _ => { + // Normal window event or spurious timeout. + start_cause = StartCause::WaitCancelled { + start, + requested_resume, + }; + } + } + }; + + event_handler( + event::Event::LoopDestroyed, + &self.window_target, + &mut control_flow, + ); + + code + } + + pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { + &self.window_target + } + + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { + user_events_sender: self.window_target.p.user_events_sender.clone(), + wake_socket: self.window_target.p.wake_socket.clone(), + } + } +} + +pub struct EventLoopProxy { + user_events_sender: mpsc::Sender, + wake_socket: Arc, +} + +impl EventLoopProxy { + pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { + self.user_events_sender + .send(event) + .map_err(|mpsc::SendError(x)| event_loop::EventLoopClosed(x))?; + + self.wake_socket.wake().unwrap(); + + Ok(()) + } +} + +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + Self { + user_events_sender: self.user_events_sender.clone(), + wake_socket: self.wake_socket.clone(), + } + } +} + +impl Unpin for EventLoopProxy {} + +pub struct EventLoopWindowTarget { + pub(super) user_events_sender: mpsc::Sender, + pub(super) user_events_receiver: mpsc::Receiver, + pub(super) creates: Mutex>>, + pub(super) redraws: Arc>>, + pub(super) destroys: Arc>>, + pub(super) event_socket: Arc, + pub(super) wake_socket: Arc, +} + +impl EventLoopWindowTarget { + pub fn primary_monitor(&self) -> Option { + Some(MonitorHandle) + } + + pub fn available_monitors(&self) -> VecDeque { + let mut v = VecDeque::with_capacity(1); + v.push_back(MonitorHandle); + v + } + + pub fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::Orbital(OrbitalDisplayHandle::empty()) + } +} diff --git a/src/platform_impl/orbital/mod.rs b/src/platform_impl/orbital/mod.rs new file mode 100644 index 00000000..7a8c1fea --- /dev/null +++ b/src/platform_impl/orbital/mod.rs @@ -0,0 +1,253 @@ +#![cfg(target_os = "redox")] + +use std::str; + +use crate::dpi::{PhysicalPosition, PhysicalSize}; + +pub use self::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; +mod event_loop; + +pub use self::window::Window; +mod window; + +struct RedoxSocket { + fd: usize, +} + +impl RedoxSocket { + fn event() -> syscall::Result { + Self::open_raw("event:") + } + + fn orbital(properties: &WindowProperties<'_>) -> syscall::Result { + Self::open_raw(&format!("{}", properties)) + } + + // Paths should be checked to ensure they are actually sockets and not normal files. If a + // non-socket path is used, it could cause read and write to not function as expected. For + // example, the seek would change in a potentially unpredictable way if either read or write + // were called at the same time by multiple threads. + fn open_raw(path: &str) -> syscall::Result { + let fd = syscall::open(path, syscall::O_RDWR | syscall::O_CLOEXEC)?; + Ok(Self { fd }) + } + + fn read(&self, buf: &mut [u8]) -> syscall::Result<()> { + let count = syscall::read(self.fd, buf)?; + if count == buf.len() { + Ok(()) + } else { + Err(syscall::Error::new(syscall::EINVAL)) + } + } + + fn write(&self, buf: &[u8]) -> syscall::Result<()> { + let count = syscall::write(self.fd, buf)?; + if count == buf.len() { + Ok(()) + } else { + Err(syscall::Error::new(syscall::EINVAL)) + } + } + + fn fpath<'a>(&self, buf: &'a mut [u8]) -> syscall::Result<&'a str> { + let count = syscall::fpath(self.fd, buf)?; + str::from_utf8(&buf[..count]).map_err(|_err| syscall::Error::new(syscall::EINVAL)) + } +} + +impl Drop for RedoxSocket { + fn drop(&mut self) { + let _ = syscall::close(self.fd); + } +} + +pub struct TimeSocket(RedoxSocket); + +impl TimeSocket { + fn open() -> syscall::Result { + RedoxSocket::open_raw("time:4").map(Self) + } + + // Read current time. + fn current_time(&self) -> syscall::Result { + let mut timespec = syscall::TimeSpec::default(); + self.0.read(&mut timespec)?; + Ok(timespec) + } + + // Write a timeout. + fn timeout(&self, timespec: &syscall::TimeSpec) -> syscall::Result<()> { + self.0.write(timespec) + } + + // Wake immediately. + fn wake(&self) -> syscall::Result<()> { + // Writing a default TimeSpec will always trigger a time event. + self.timeout(&syscall::TimeSpec::default()) + } +} + +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub(crate) struct PlatformSpecificEventLoopAttributes {} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct WindowId { + fd: u64, +} + +impl WindowId { + pub const fn dummy() -> Self { + WindowId { + fd: u64::max_value(), + } + } +} + +impl From for u64 { + fn from(id: WindowId) -> Self { + id.fd + } +} + +impl From for WindowId { + fn from(fd: u64) -> Self { + Self { fd } + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct DeviceId; + +impl DeviceId { + pub const fn dummy() -> Self { + DeviceId + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct PlatformSpecificWindowBuilderAttributes; + +struct WindowProperties<'a> { + flags: &'a str, + x: i32, + y: i32, + w: u32, + h: u32, + title: &'a str, +} + +impl<'a> WindowProperties<'a> { + fn new(path: &'a str) -> Self { + // orbital:flags/x/y/w/h/t + let mut parts = path.splitn(6, '/'); + let flags = parts.next().unwrap_or(""); + let x = parts + .next() + .map_or(0, |part| part.parse::().unwrap_or(0)); + let y = parts + .next() + .map_or(0, |part| part.parse::().unwrap_or(0)); + let w = parts + .next() + .map_or(0, |part| part.parse::().unwrap_or(0)); + let h = parts + .next() + .map_or(0, |part| part.parse::().unwrap_or(0)); + let title = parts.next().unwrap_or(""); + Self { + flags, + x, + y, + w, + h, + title, + } + } +} + +impl<'a> fmt::Display for WindowProperties<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "orbital:{}/{}/{}/{}/{}/{}", + self.flags, self.x, self.y, self.w, self.h, self.title + ) + } +} + +#[derive(Default, Clone, Debug)] +pub struct OsError; + +use std::fmt::{self, Display, Formatter}; +impl Display for OsError { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { + write!(fmt, "Redox OS Error") + } +} + +pub(crate) use crate::icon::NoIcon as PlatformIcon; + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct MonitorHandle; + +impl MonitorHandle { + pub fn name(&self) -> Option { + Some("Redox Device".to_owned()) + } + + pub fn size(&self) -> PhysicalSize { + PhysicalSize::new(0, 0) // TODO + } + + pub fn position(&self) -> PhysicalPosition { + (0, 0).into() + } + + pub fn scale_factor(&self) -> f64 { + 1.0 // TODO + } + + pub fn refresh_rate_millihertz(&self) -> Option { + // FIXME no way to get real refresh rate for now. + None + } + + pub fn video_modes(&self) -> impl Iterator { + let size = self.size().into(); + // FIXME this is not the real refresh rate + // (it is guaranteed to support 32 bit color though) + std::iter::once(VideoMode { + size, + bit_depth: 32, + refresh_rate_millihertz: 60000, + monitor: self.clone(), + }) + } +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct VideoMode { + size: (u32, u32), + bit_depth: u16, + refresh_rate_millihertz: u32, + monitor: MonitorHandle, +} + +impl VideoMode { + pub fn size(&self) -> PhysicalSize { + self.size.into() + } + + pub fn bit_depth(&self) -> u16 { + self.bit_depth + } + + pub fn refresh_rate_millihertz(&self) -> u32 { + self.refresh_rate_millihertz + } + + pub fn monitor(&self) -> MonitorHandle { + self.monitor.clone() + } +} diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs new file mode 100644 index 00000000..40ed39d1 --- /dev/null +++ b/src/platform_impl/orbital/window.rs @@ -0,0 +1,396 @@ +use std::{ + collections::VecDeque, + sync::{Arc, Mutex}, +}; + +use raw_window_handle::{ + OrbitalDisplayHandle, OrbitalWindowHandle, RawDisplayHandle, RawWindowHandle, +}; + +use crate::{ + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, + error, + platform_impl::Fullscreen, + window, +}; + +use super::{ + EventLoopWindowTarget, MonitorHandle, PlatformSpecificWindowBuilderAttributes, RedoxSocket, + TimeSocket, WindowId, WindowProperties, +}; + +// These values match the values uses in the `window_new` function in orbital: +// https://gitlab.redox-os.org/redox-os/orbital/-/blob/master/src/scheme.rs +const ORBITAL_FLAG_ASYNC: char = 'a'; +const ORBITAL_FLAG_BACK: char = 'b'; +const ORBITAL_FLAG_FRONT: char = 'f'; +const ORBITAL_FLAG_BORDERLESS: char = 'l'; +const ORBITAL_FLAG_RESIZABLE: char = 'r'; +const ORBITAL_FLAG_TRANSPARENT: char = 't'; + +pub struct Window { + window_socket: Arc, + redraws: Arc>>, + destroys: Arc>>, + wake_socket: Arc, +} + +impl Window { + pub(crate) fn new( + el: &EventLoopWindowTarget, + attrs: window::WindowAttributes, + _: PlatformSpecificWindowBuilderAttributes, + ) -> Result { + let scale = MonitorHandle.scale_factor(); + + let (x, y) = if let Some(pos) = attrs.position { + pos.to_physical::(scale).into() + } else { + // These coordinates are a special value to center the window. + (-1, -1) + }; + + let (w, h): (u32, u32) = if let Some(size) = attrs.inner_size { + size.to_physical::(scale).into() + } else { + (1024, 768) + }; + + //TODO: min/max inner_size + + // Async by default. + let mut flag_str = ORBITAL_FLAG_ASYNC.to_string(); + + if attrs.resizable { + flag_str.push(ORBITAL_FLAG_RESIZABLE); + } + + //TODO: maximized, fullscreen, visible + + if attrs.transparent { + flag_str.push(ORBITAL_FLAG_TRANSPARENT); + } + + if !attrs.decorations { + flag_str.push(ORBITAL_FLAG_BORDERLESS); + } + + match attrs.window_level { + window::WindowLevel::AlwaysOnBottom => { + flag_str.push(ORBITAL_FLAG_BACK); + } + window::WindowLevel::Normal => {} + window::WindowLevel::AlwaysOnTop => { + flag_str.push(ORBITAL_FLAG_FRONT); + } + } + + //TODO: window_icon + + // Open window. + let window = RedoxSocket::orbital(&WindowProperties { + flags: &flag_str, + x, + y, + w, + h, + title: &attrs.title, + }) + .expect("failed to open window"); + + // Add to event socket. + el.event_socket + .write(&syscall::Event { + id: window.fd, + flags: syscall::EventFlags::EVENT_READ, + data: window.fd, + }) + .unwrap(); + + let window_socket = Arc::new(window); + + // Notify event thread that this window was created, it will send some default events. + { + let mut creates = el.creates.lock().unwrap(); + creates.push_back(window_socket.clone()); + } + + el.wake_socket.wake().unwrap(); + + Ok(Self { + window_socket, + redraws: el.redraws.clone(), + destroys: el.destroys.clone(), + wake_socket: el.wake_socket.clone(), + }) + } + + #[inline] + pub fn id(&self) -> WindowId { + WindowId { + fd: self.window_socket.fd as u64, + } + } + + #[inline] + pub fn primary_monitor(&self) -> Option { + Some(MonitorHandle) + } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + let mut v = VecDeque::with_capacity(1); + v.push_back(MonitorHandle); + v + } + + #[inline] + pub fn current_monitor(&self) -> Option { + Some(MonitorHandle) + } + + #[inline] + pub fn scale_factor(&self) -> f64 { + MonitorHandle.scale_factor() + } + + #[inline] + pub fn request_redraw(&self) { + let window_id = self.id(); + let mut redraws = self.redraws.lock().unwrap(); + if !redraws.contains(&window_id) { + redraws.push_back(window_id); + + self.wake_socket.wake().unwrap(); + } + } + + #[inline] + pub fn inner_position(&self) -> Result, error::NotSupportedError> { + let mut buf: [u8; 4096] = [0; 4096]; + let path = self + .window_socket + .fpath(&mut buf) + .expect("failed to read properties"); + let properties = WindowProperties::new(path); + Ok((properties.x, properties.y).into()) + } + + #[inline] + pub fn outer_position(&self) -> Result, error::NotSupportedError> { + //TODO: adjust for window decorations + self.inner_position() + } + + #[inline] + pub fn set_outer_position(&self, position: Position) { + //TODO: adjust for window decorations + let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); + self.window_socket + .write(format!("P,{},{}", x, y).as_bytes()) + .expect("failed to set position"); + } + + #[inline] + pub fn inner_size(&self) -> PhysicalSize { + let mut buf: [u8; 4096] = [0; 4096]; + let path = self + .window_socket + .fpath(&mut buf) + .expect("failed to read properties"); + let properties = WindowProperties::new(path); + (properties.w, properties.h).into() + } + + #[inline] + pub fn set_inner_size(&self, size: Size) { + let (w, h): (u32, u32) = size.to_physical::(self.scale_factor()).into(); + self.window_socket + .write(format!("S,{},{}", w, h).as_bytes()) + .expect("failed to set size"); + } + + #[inline] + pub fn outer_size(&self) -> PhysicalSize { + //TODO: adjust for window decorations + self.inner_size() + } + + #[inline] + pub fn set_min_inner_size(&self, _: Option) {} + + #[inline] + pub fn set_max_inner_size(&self, _: Option) {} + + #[inline] + pub fn title(&self) -> String { + let mut buf: [u8; 4096] = [0; 4096]; + let path = self + .window_socket + .fpath(&mut buf) + .expect("failed to read properties"); + let properties = WindowProperties::new(path); + properties.title.to_string() + } + + #[inline] + pub fn set_title(&self, title: &str) { + self.window_socket + .write(format!("T,{}", title).as_bytes()) + .expect("failed to set title"); + } + + #[inline] + pub fn set_visible(&self, _visibility: bool) {} + + #[inline] + pub fn is_visible(&self) -> Option { + None + } + + #[inline] + pub fn resize_increments(&self) -> Option> { + None + } + + #[inline] + pub fn set_resize_increments(&self, _increments: Option) {} + + #[inline] + pub fn set_resizable(&self, _resizeable: bool) {} + + #[inline] + pub fn is_resizable(&self) -> bool { + let mut buf: [u8; 4096] = [0; 4096]; + let path = self + .window_socket + .fpath(&mut buf) + .expect("failed to read properties"); + let properties = WindowProperties::new(path); + properties.flags.contains(ORBITAL_FLAG_RESIZABLE) + } + + #[inline] + pub fn set_minimized(&self, _minimized: bool) {} + + #[inline] + pub fn set_maximized(&self, _maximized: bool) {} + + #[inline] + pub fn is_maximized(&self) -> bool { + false + } + + #[inline] + pub(crate) fn set_fullscreen(&self, _monitor: Option) {} + + #[inline] + pub(crate) fn fullscreen(&self) -> Option { + None + } + + #[inline] + pub fn set_decorations(&self, _decorations: bool) {} + + #[inline] + pub fn is_decorated(&self) -> bool { + let mut buf: [u8; 4096] = [0; 4096]; + let path = self + .window_socket + .fpath(&mut buf) + .expect("failed to read properties"); + let properties = WindowProperties::new(path); + !properties.flags.contains(ORBITAL_FLAG_BORDERLESS) + } + + #[inline] + pub fn set_window_level(&self, _level: window::WindowLevel) {} + + #[inline] + pub fn set_window_icon(&self, _window_icon: Option) {} + + #[inline] + pub fn set_ime_position(&self, _position: Position) {} + + #[inline] + pub fn set_ime_allowed(&self, _allowed: bool) {} + + #[inline] + pub fn focus_window(&self) {} + + #[inline] + pub fn request_user_attention(&self, _request_type: Option) {} + + #[inline] + pub fn set_cursor_icon(&self, _: window::CursorIcon) {} + + #[inline] + pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) + } + + #[inline] + pub fn set_cursor_grab(&self, _: window::CursorGrabMode) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) + } + + #[inline] + pub fn set_cursor_visible(&self, _: bool) {} + + #[inline] + pub fn drag_window(&self) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) + } + + #[inline] + pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) + } + + #[inline] + pub fn raw_window_handle(&self) -> RawWindowHandle { + let mut handle = OrbitalWindowHandle::empty(); + handle.window = self.window_socket.fd as *mut _; + RawWindowHandle::Orbital(handle) + } + + #[inline] + pub fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::Orbital(OrbitalDisplayHandle::empty()) + } + + #[inline] + pub fn set_enabled_buttons(&self, _buttons: window::WindowButtons) {} + + #[inline] + pub fn enabled_buttons(&self) -> window::WindowButtons { + window::WindowButtons::all() + } + + #[inline] + pub fn theme(&self) -> Option { + None + } + + #[inline] + pub fn set_theme(&self, _theme: Option) {} +} + +impl Drop for Window { + fn drop(&mut self) { + { + let mut destroys = self.destroys.lock().unwrap(); + destroys.push_back(self.id()); + } + + self.wake_socket.wake().unwrap(); + } +} diff --git a/src/window.rs b/src/window.rs index b646ddd0..3c7f4797 100644 --- a/src/window.rs +++ b/src/window.rs @@ -368,7 +368,7 @@ impl WindowBuilder { /// - **Wayland:** This control only CSD. You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme. /// Possible values for env variable are: "dark" and light". /// - **x11:** Build window with `_GTK_THEME_VARIANT` hint set to `dark` or `light`. - /// - **iOS / Android / Web / x11:** Ignored. + /// - **iOS / Android / Web / x11 / Orbital:** Ignored. #[inline] pub fn with_theme(mut self, theme: Option) -> Self { self.window.preferred_theme = theme; @@ -394,7 +394,7 @@ impl WindowBuilder { /// /// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely /// prevent all apps from reading the window content, for instance, QuickTime. - /// - **iOS / Android / Web / x11:** Ignored. + /// - **iOS / Android / Web / x11 / Orbital:** Ignored. /// /// [`NSWindowSharingNone`]: https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone #[inline] @@ -667,7 +667,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web:** Unsupported. + /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn set_min_inner_size>(&self, min_size: Option) { self.window.set_min_inner_size(min_size.map(|s| s.into())) @@ -690,7 +690,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web:** Unsupported. + /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn set_max_inner_size>(&self, max_size: Option) { self.window.set_max_inner_size(max_size.map(|s| s.into())) @@ -700,7 +700,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Wayland / Windows:** Always returns [`None`]. + /// - **iOS / Android / Web / Wayland / Windows / Orbital:** Always returns [`None`]. #[inline] pub fn resize_increments(&self) -> Option> { self.window.resize_increments() @@ -715,7 +715,7 @@ impl Window { /// /// - **macOS:** Increments are converted to logical size and then macOS rounds them to whole numbers. /// - **Wayland / Windows:** Not implemented. - /// - **iOS / Android / Web:** Unsupported. + /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn set_resize_increments>(&self, increments: Option) { self.window @@ -795,7 +795,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **Wayland / X11:** Not implemented. + /// - **Wayland / X11 / Orbital:** Not implemented. /// - **Web / iOS / Android:** Unsupported. pub fn set_enabled_buttons(&self, buttons: WindowButtons) { self.window.set_enabled_buttons(buttons) @@ -805,7 +805,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **Wayland / X11:** Not implemented. Always returns [`WindowButtons::all`]. + /// - **Wayland / X11 / Orbital:** Not implemented. Always returns [`WindowButtons::all`]. /// - **Web / iOS / Android:** Unsupported. Always returns [`WindowButtons::all`]. pub fn enabled_buttons(&self) -> WindowButtons { self.window.enabled_buttons() @@ -815,7 +815,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web:** Unsupported. + /// - **iOS / Android / Web / Orbital:** Unsupported. /// - **Wayland:** Un-minimize is unsupported. #[inline] pub fn set_minimized(&self, minimized: bool) { @@ -826,7 +826,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web:** Unsupported. + /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn set_maximized(&self, maximized: bool) { self.window.set_maximized(maximized) @@ -836,7 +836,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web:** Unsupported. + /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn is_maximized(&self) -> bool { self.window.is_maximized() @@ -861,7 +861,7 @@ 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. - /// - **Android:** Unsupported. + /// - **Android / Orbital:** Unsupported. #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { self.window.set_fullscreen(fullscreen.map(|f| f.into())) @@ -872,7 +872,7 @@ impl Window { /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. - /// - **Android:** Will always return `None`. + /// - **Android / Orbital:** Will always return `None`. /// - **Wayland:** Can return `Borderless(None)` when there are no monitors. #[inline] pub fn fullscreen(&self) -> Option { @@ -916,7 +916,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Wayland / macOS:** Unsupported. + /// - **iOS / Android / Web / Wayland / macOS / Orbital:** Unsupported. /// /// - **Windows:** 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. @@ -952,7 +952,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web:** Unsupported. + /// - **iOS / Android / Web / Orbital:** 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 @@ -978,7 +978,7 @@ impl Window { /// ## Platform-specific /// /// - **macOS:** IME must be enabled to receive text-input where dead-key sequences are combined. - /// - **iOS / Android / Web:** Unsupported. + /// - **iOS / Android / Web / Orbital:** Unsupported. /// /// [`Ime`]: crate::event::WindowEvent::Ime /// [`KeyboardInput`]: crate::event::WindowEvent::KeyboardInput @@ -997,7 +997,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Wayland:** Unsupported. + /// - **iOS / Android / Web / Wayland / Orbital:** Unsupported. #[inline] pub fn focus_window(&self) { self.window.focus_window() @@ -1012,7 +1012,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web:** Unsupported. + /// - **iOS / Android / Web / Orbital:** 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. @@ -1029,7 +1029,7 @@ impl Window { /// - **Wayland:** You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme. /// Possible values for env variable are: "dark" and light". When unspecified, a theme is automatically selected. /// -**x11:** Sets `_GTK_THEME_VARIANT` hint to `dark` or `light` and if `None` is used, it will default to [`Theme::Dark`]. - /// - **iOS / Android / Web / x11:** Unsupported. + /// - **iOS / Android / Web / x11 / Orbital:** Unsupported. #[inline] pub fn set_theme(&self, theme: Option) { self.window.set_theme(theme) @@ -1040,7 +1040,7 @@ impl Window { /// ## Platform-specific /// /// - **macOS:** This is an app-wide setting. - /// - **iOS / Android / Web / Wayland / x11:** Unsupported. + /// - **iOS / Android / Web / Wayland / x11 / Orbital:** Unsupported. #[inline] pub fn theme(&self) -> Option { self.window.theme() @@ -1052,7 +1052,7 @@ impl Window { /// /// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely /// prevent all apps from reading the window content, for instance, QuickTime. - /// - **iOS / Android / x11 / Wayland / Web:** Unsupported. + /// - **iOS / Android / x11 / Wayland / Web / Orbital:** Unsupported. /// /// [`NSWindowSharingNone`]: https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone pub fn set_content_protected(&self, _protected: bool) { @@ -1077,7 +1077,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android:** Unsupported. + /// - **iOS / Android / Orbital:** Unsupported. #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { self.window.set_cursor_icon(cursor); @@ -1100,7 +1100,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Wayland:** Always returns an [`ExternalError::NotSupported`]. + /// - **iOS / Android / Web / Wayland / Orbital:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_position>(&self, position: P) -> Result<(), ExternalError> { self.window.set_cursor_position(position.into()) @@ -1137,7 +1137,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. + /// - **iOS / Android / Orbital:** Unsupported. #[inline] pub fn set_cursor_visible(&self, visible: bool) { self.window.set_cursor_visible(visible) @@ -1153,7 +1153,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`]. + /// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { self.window.drag_window() @@ -1166,7 +1166,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / X11:** Always returns an [`ExternalError::NotSupported`]. + /// - **iOS / Android / Web / X11 / Orbital:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { self.window.set_cursor_hittest(hittest) @@ -1273,7 +1273,7 @@ pub enum CursorGrabMode { /// ## Platform-specific /// /// - **macOS:** Not implemented. Always returns [`ExternalError::NotSupported`] for now. - /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. + /// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`]. Confined, /// The cursor is locked inside the window area to the certain position. @@ -1284,7 +1284,7 @@ pub enum CursorGrabMode { /// ## Platform-specific /// /// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for now. - /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. + /// - **iOS / Android / Orbital:** Always returns an [`ExternalError::NotSupported`]. Locked, }