mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2025-01-11 13:31:29 +11:00
Update SCTK to 0.11.0
* Update SCTK to 0.11.0 Updates smithay-client-toolkit to 0.11.0. The major highlight of that updated, is update of wayland-rs to 0.27.0. Switching to wayland-cursor, instead of using libwayland-cursor. It also fixes the following bugs: - Disabled repeat rate not being handled. - Decoration buttons not working after tty switch. - Scaling not being applied on output reenable. - Crash when `XCURSOR_SIZE` is `0`. - Pointer getting created in some cases without pointer capability. - On kwin, fix space between window and decorations on startup. - Incorrect size event when entering fullscreen when using client side decorations. - Client side decorations not being hided properly in fullscreen. - Size tracking between fullscreen/tiled state changes. - Repeat rate triggering multiple times from slow callback handler. - Resizable attribute not being applied properly on startup. - Not working IME Besides those fixes it also adds a bunch of missing virtual key codes, implements proper cursor grabbing, adds right click on decorations to open application menu, disabled maximize button for non-resizeable window, and fall back for cursor icon to similar ones, if the requested is missing. It also adds new methods to a `Theme` trait, such as: - `title_font(&self) -> Option<(String, f32)>` - The font for a title. - `title_color(&self, window_active: bool) -> [u8; 4]` - The color of the text in the title. Fixes #1680. Fixes #1678. Fixes #1676. Fixes #1646. Fixes #1614. Fixes #1601. Fixes #1533. Fixes #1509. Fixes #952. Fixes #947.
This commit is contained in:
parent
471b1e003a
commit
3d85af04be
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -44,6 +44,23 @@
|
|||
- On Web, `WindowEvent::Resized` is now emitted when `Window::set_inner_size` is called.
|
||||
- **Breaking:** `Fullscreen` enum now uses `Borderless(Option<MonitorHandle>)` instead of `Borderless(MonitorHandle)` to allow picking the current monitor.
|
||||
- On MacOS, fix `WindowEvent::Moved` ignoring the scale factor.
|
||||
- On Wayland, add missing virtual keycodes.
|
||||
- On Wayland, implement proper `set_cursor_grab`.
|
||||
- On Wayland, the cursor will use similar icons if the requested one isn't available.
|
||||
- On Wayland, right clicking on client side decorations will request application menu.
|
||||
- On Wayland, fix tracking of window size after state changes.
|
||||
- On Wayland, fix client side decorations not being hidden properly in fullscreen.
|
||||
- On Wayland, fix incorrect size event when entering fullscreen with client side decorations.
|
||||
- On Wayland, fix `resizable` attribute not being applied properly on startup.
|
||||
- On Wayland, fix disabled repeat rate not being handled.
|
||||
- On Wayland, fix decoration buttons not working after tty switch.
|
||||
- On Wayland, fix scaling not being applied on output re-enable.
|
||||
- On Wayland, fix crash when `XCURSOR_SIZE` is `0`.
|
||||
- On Wayland, fix pointer getting created in some cases without pointer capability.
|
||||
- On Wayland, on kwin, fix space between window and decorations on startup.
|
||||
- **Breaking:** On Wayland, `Theme` trait was reworked.
|
||||
- On Wayland, disable maximize button for non-resizable window.
|
||||
- On Wayland, added support for `set_ime_position`.
|
||||
|
||||
# 0.22.2 (2020-05-16)
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ default = ["x11", "wayland"]
|
|||
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
|
||||
stdweb = ["std_web", "instant/stdweb"]
|
||||
x11 = ["x11-dl"]
|
||||
wayland = ["wayland-client", "smithay-client-toolkit"]
|
||||
wayland = ["wayland-client", "sctk"]
|
||||
|
||||
[dependencies]
|
||||
instant = "0.1"
|
||||
|
@ -81,10 +81,10 @@ features = [
|
|||
]
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
|
||||
wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] , optional = true }
|
||||
wayland-client = { version = "0.27", features = [ "dlopen"] , optional = true }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.11", optional = true }
|
||||
mio = "0.6"
|
||||
mio-extras = "2.0"
|
||||
smithay-client-toolkit = { version = "^0.6.6", optional = true }
|
||||
x11-dl = { version = "2.18.5", optional = true }
|
||||
percent-encoding = "2.0"
|
||||
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "android",
|
||||
target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
|
||||
use crate::{
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
||||
#![cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
|
||||
use std::os::raw;
|
||||
#[cfg(feature = "x11")]
|
||||
use std::{ptr, sync::Arc};
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme};
|
||||
|
||||
use crate::{
|
||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
||||
monitor::MonitorHandle,
|
||||
|
@ -328,7 +331,7 @@ impl WindowExtUnix for Window {
|
|||
#[cfg(feature = "wayland")]
|
||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => Some(w.display().as_ref().c_ptr() as *mut _),
|
||||
LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _),
|
||||
#[cfg(feature = "x11")]
|
||||
_ => None,
|
||||
}
|
||||
|
@ -338,7 +341,7 @@ impl WindowExtUnix for Window {
|
|||
#[cfg(feature = "wayland")]
|
||||
fn set_wayland_theme<T: Theme>(&self, theme: T) {
|
||||
match self.window {
|
||||
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)),
|
||||
LinuxWindow::Wayland(ref w) => w.set_theme(theme),
|
||||
#[cfg(feature = "x11")]
|
||||
_ => {}
|
||||
}
|
||||
|
@ -466,83 +469,50 @@ impl MonitorHandleExtUnix for MonitorHandle {
|
|||
}
|
||||
}
|
||||
|
||||
/// Wrapper for implementing SCTK's theme trait.
|
||||
/// A theme for a Wayland's client side decorations.
|
||||
#[cfg(feature = "wayland")]
|
||||
struct WaylandTheme<T: Theme>(T);
|
||||
|
||||
pub trait Theme: Send + 'static {
|
||||
/// Primary color of the scheme.
|
||||
fn primary_color(&self, window_active: bool) -> [u8; 4];
|
||||
/// Title bar color.
|
||||
fn element_color(&self, element: Element, window_active: bool) -> ARGBColor;
|
||||
|
||||
/// Secondary color of the scheme.
|
||||
fn secondary_color(&self, window_active: bool) -> [u8; 4];
|
||||
/// Color for a given button part.
|
||||
fn button_color(
|
||||
&self,
|
||||
button: Button,
|
||||
state: ButtonState,
|
||||
foreground: bool,
|
||||
window_active: bool,
|
||||
) -> ARGBColor;
|
||||
|
||||
/// Color for the close button.
|
||||
fn close_button_color(&self, status: ButtonState) -> [u8; 4];
|
||||
|
||||
/// Icon color for the close button, defaults to the secondary color.
|
||||
#[allow(unused_variables)]
|
||||
fn close_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
||||
self.secondary_color(true)
|
||||
}
|
||||
|
||||
/// Background color for the maximize button.
|
||||
fn maximize_button_color(&self, status: ButtonState) -> [u8; 4];
|
||||
|
||||
/// Icon color for the maximize button, defaults to the secondary color.
|
||||
#[allow(unused_variables)]
|
||||
fn maximize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
||||
self.secondary_color(true)
|
||||
}
|
||||
|
||||
/// Background color for the minimize button.
|
||||
fn minimize_button_color(&self, status: ButtonState) -> [u8; 4];
|
||||
|
||||
/// Icon color for the minimize button, defaults to the secondary color.
|
||||
#[allow(unused_variables)]
|
||||
fn minimize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
||||
self.secondary_color(true)
|
||||
/// Font name and the size for the title bar.
|
||||
///
|
||||
/// By default the font is `sans-serif` at the size of 11.
|
||||
///
|
||||
/// Returning `None` means that title won't be drawn.
|
||||
fn font(&self) -> Option<(String, f32)> {
|
||||
// Not having any title isn't something desirable for the users, so setting it to
|
||||
// something generic.
|
||||
Some((String::from("sans-serif"), 11.))
|
||||
}
|
||||
}
|
||||
|
||||
/// A button on Wayland's client side decorations.
|
||||
#[cfg(feature = "wayland")]
|
||||
impl<T: Theme> SCTKTheme for WaylandTheme<T> {
|
||||
fn get_primary_color(&self, active: bool) -> [u8; 4] {
|
||||
self.0.primary_color(active)
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Button {
|
||||
/// Button that maximizes the window.
|
||||
Maximize,
|
||||
|
||||
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
|
||||
self.0.secondary_color(active)
|
||||
}
|
||||
/// Button that minimizes the window.
|
||||
Minimize,
|
||||
|
||||
fn get_close_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0.close_button_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_close_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0
|
||||
.close_button_icon_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_maximize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0.maximize_button_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_maximize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0
|
||||
.maximize_button_icon_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_minimize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0.minimize_button_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
|
||||
fn get_minimize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
||||
self.0
|
||||
.minimize_button_icon_color(ButtonState::from_sctk(status))
|
||||
}
|
||||
/// Button that closes the window.
|
||||
Close,
|
||||
}
|
||||
|
||||
/// A button state of the button on Wayland's client side decorations.
|
||||
#[cfg(feature = "wayland")]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ButtonState {
|
||||
/// Button is being hovered over by pointer.
|
||||
Hovered,
|
||||
|
@ -553,12 +523,23 @@ pub enum ButtonState {
|
|||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
impl ButtonState {
|
||||
fn from_sctk(button_state: SCTKButtonState) -> Self {
|
||||
match button_state {
|
||||
SCTKButtonState::Hovered => Self::Hovered,
|
||||
SCTKButtonState::Idle => Self::Idle,
|
||||
SCTKButtonState::Disabled => Self::Disabled,
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Element {
|
||||
/// Bar itself.
|
||||
Bar,
|
||||
|
||||
/// Separator between window and title bar.
|
||||
Separator,
|
||||
|
||||
/// Title bar text.
|
||||
Text,
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct ARGBColor {
|
||||
pub a: u8,
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#[cfg(all(not(feature = "x11"), not(feature = "wayland")))]
|
||||
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
use std::error::Error;
|
||||
use std::{collections::VecDeque, env, fmt};
|
||||
#[cfg(feature = "x11")]
|
||||
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc};
|
||||
|
@ -16,8 +18,6 @@ use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc};
|
|||
#[cfg(feature = "x11")]
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle::RawWindowHandle;
|
||||
#[cfg(feature = "wayland")]
|
||||
use smithay_client_toolkit::reexports::client::ConnectError;
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub use self::x11::XNotSupported;
|
||||
|
@ -108,6 +108,8 @@ pub enum OsError {
|
|||
XError(XError),
|
||||
#[cfg(feature = "x11")]
|
||||
XMisc(&'static str),
|
||||
#[cfg(feature = "wayland")]
|
||||
WaylandMisc(&'static str),
|
||||
}
|
||||
|
||||
impl fmt::Display for OsError {
|
||||
|
@ -117,6 +119,8 @@ impl fmt::Display for OsError {
|
|||
OsError::XError(ref e) => _f.pad(&e.description),
|
||||
#[cfg(feature = "x11")]
|
||||
OsError::XMisc(ref e) => _f.pad(e),
|
||||
#[cfg(feature = "wayland")]
|
||||
OsError::WaylandMisc(ref e) => _f.pad(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -410,13 +414,8 @@ impl Window {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_position(&self, _position: Position) {
|
||||
match self {
|
||||
#[cfg(feature = "x11")]
|
||||
&Window::X(ref w) => w.set_ime_position(_position),
|
||||
#[cfg(feature = "wayland")]
|
||||
_ => (),
|
||||
}
|
||||
pub fn set_ime_position(&self, position: Position) {
|
||||
x11_or_wayland!(match self; Window(w) => w.set_ime_position(position))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -597,14 +596,14 @@ impl<T: 'static> EventLoop<T> {
|
|||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn new_wayland() -> Result<EventLoop<T>, ConnectError> {
|
||||
pub fn new_wayland() -> Result<EventLoop<T>, Box<dyn Error>> {
|
||||
assert_is_main_thread("new_wayland_any_thread");
|
||||
|
||||
EventLoop::new_wayland_any_thread()
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn new_wayland_any_thread() -> Result<EventLoop<T>, ConnectError> {
|
||||
pub fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
|
||||
wayland::EventLoop::new().map(EventLoop::Wayland)
|
||||
}
|
||||
|
||||
|
|
149
src/platform_impl/linux/wayland/env.rs
Normal file
149
src/platform_impl/linux/wayland/env.rs
Normal file
|
@ -0,0 +1,149 @@
|
|||
//! SCTK environment setup.
|
||||
|
||||
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
use sctk::reexports::protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6::ZxdgShellV6;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
|
||||
use sctk::reexports::client::protocol::wl_shell::WlShell;
|
||||
use sctk::reexports::client::protocol::wl_subcompositor::WlSubcompositor;
|
||||
use sctk::reexports::client::{Attached, DispatchData};
|
||||
use sctk::reexports::client::protocol::wl_shm::WlShm;
|
||||
use sctk::reexports::protocols::xdg_shell::client::xdg_wm_base::XdgWmBase;
|
||||
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::environment::{Environment, SimpleGlobal};
|
||||
use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener};
|
||||
use sctk::seat::{SeatData, SeatHandler, SeatHandling, SeatListener};
|
||||
use sctk::shell::{Shell, ShellHandler, ShellHandling};
|
||||
use sctk::shm::ShmHandler;
|
||||
|
||||
/// Set of extra features that are supported by the compositor.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct WindowingFeatures {
|
||||
cursor_grab: bool,
|
||||
}
|
||||
|
||||
impl WindowingFeatures {
|
||||
/// Create `WindowingFeatures` based on the presented interfaces.
|
||||
pub fn new(env: &Environment<WinitEnv>) -> Self {
|
||||
let cursor_grab = env.get_global::<ZwpPointerConstraintsV1>().is_some();
|
||||
Self { cursor_grab }
|
||||
}
|
||||
|
||||
pub fn cursor_grab(&self) -> bool {
|
||||
self.cursor_grab
|
||||
}
|
||||
}
|
||||
|
||||
sctk::environment!(WinitEnv,
|
||||
singles = [
|
||||
WlShm => shm,
|
||||
WlCompositor => compositor,
|
||||
WlSubcompositor => subcompositor,
|
||||
WlShell => shell,
|
||||
XdgWmBase => shell,
|
||||
ZxdgShellV6 => shell,
|
||||
ZxdgDecorationManagerV1 => decoration_manager,
|
||||
ZwpRelativePointerManagerV1 => relative_pointer_manager,
|
||||
ZwpPointerConstraintsV1 => pointer_constraints,
|
||||
ZwpTextInputManagerV3 => text_input_manager,
|
||||
],
|
||||
multis = [
|
||||
WlSeat => seats,
|
||||
WlOutput => outputs,
|
||||
]
|
||||
);
|
||||
|
||||
/// The environment that we utilize.
|
||||
pub struct WinitEnv {
|
||||
seats: SeatHandler,
|
||||
|
||||
outputs: OutputHandler,
|
||||
|
||||
shm: ShmHandler,
|
||||
|
||||
compositor: SimpleGlobal<WlCompositor>,
|
||||
|
||||
subcompositor: SimpleGlobal<WlSubcompositor>,
|
||||
|
||||
shell: ShellHandler,
|
||||
|
||||
relative_pointer_manager: SimpleGlobal<ZwpRelativePointerManagerV1>,
|
||||
|
||||
pointer_constraints: SimpleGlobal<ZwpPointerConstraintsV1>,
|
||||
|
||||
text_input_manager: SimpleGlobal<ZwpTextInputManagerV3>,
|
||||
|
||||
decoration_manager: SimpleGlobal<ZxdgDecorationManagerV1>,
|
||||
}
|
||||
|
||||
impl WinitEnv {
|
||||
pub fn new() -> Self {
|
||||
// Output tracking for available_monitors, etc.
|
||||
let outputs = OutputHandler::new();
|
||||
|
||||
// Keyboard/Pointer/Touch input.
|
||||
let seats = SeatHandler::new();
|
||||
|
||||
// Essential globals.
|
||||
let shm = ShmHandler::new();
|
||||
let compositor = SimpleGlobal::new();
|
||||
let subcompositor = SimpleGlobal::new();
|
||||
|
||||
// Gracefully handle shell picking, since SCTK automatically supports multiple
|
||||
// backends.
|
||||
let shell = ShellHandler::new();
|
||||
|
||||
// Server side decorations.
|
||||
let decoration_manager = SimpleGlobal::new();
|
||||
|
||||
// Device events for pointer.
|
||||
let relative_pointer_manager = SimpleGlobal::new();
|
||||
|
||||
// Pointer grab functionality.
|
||||
let pointer_constraints = SimpleGlobal::new();
|
||||
|
||||
// IME handling.
|
||||
let text_input_manager = SimpleGlobal::new();
|
||||
|
||||
Self {
|
||||
seats,
|
||||
outputs,
|
||||
shm,
|
||||
compositor,
|
||||
subcompositor,
|
||||
shell,
|
||||
decoration_manager,
|
||||
relative_pointer_manager,
|
||||
pointer_constraints,
|
||||
text_input_manager,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellHandling for WinitEnv {
|
||||
fn get_shell(&self) -> Option<Shell> {
|
||||
self.shell.get_shell()
|
||||
}
|
||||
}
|
||||
|
||||
impl SeatHandling for WinitEnv {
|
||||
fn listen<F: FnMut(Attached<WlSeat>, &SeatData, DispatchData<'_>) + 'static>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> SeatListener {
|
||||
self.seats.listen(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputHandling for WinitEnv {
|
||||
fn listen<F: FnMut(WlOutput, &OutputInfo, DispatchData<'_>) + 'static>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> OutputStatusListener {
|
||||
self.outputs.listen(f)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
539
src/platform_impl/linux/wayland/event_loop/mod.rs
Normal file
539
src/platform_impl/linux/wayland/event_loop/mod.rs
Normal file
|
@ -0,0 +1,539 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::process;
|
||||
use std::rc::Rc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
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;
|
||||
|
||||
use crate::event::{Event, StartCause, WindowEvent};
|
||||
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
|
||||
use crate::platform_impl::platform::sticky_exit_callback;
|
||||
|
||||
use super::env::{WindowingFeatures, WinitEnv};
|
||||
use super::output::OutputManager;
|
||||
use super::seat::SeatManager;
|
||||
use super::window::shim::{self, WindowUpdate};
|
||||
use super::{DeviceId, WindowId};
|
||||
|
||||
mod proxy;
|
||||
mod sink;
|
||||
mod state;
|
||||
|
||||
pub use proxy::EventLoopProxy;
|
||||
pub use state::WinitState;
|
||||
|
||||
use sink::EventSink;
|
||||
|
||||
pub struct EventLoopWindowTarget<T> {
|
||||
/// Wayland display.
|
||||
pub display: Display,
|
||||
|
||||
/// Environment to handle object creation, etc.
|
||||
pub env: Environment<WinitEnv>,
|
||||
|
||||
/// Event loop handle.
|
||||
pub event_loop_handle: calloop::LoopHandle<WinitState>,
|
||||
|
||||
/// Output manager.
|
||||
pub output_manager: OutputManager,
|
||||
|
||||
/// State that we share across callbacks.
|
||||
pub state: RefCell<WinitState>,
|
||||
|
||||
/// Wayland source.
|
||||
pub wayland_source: Rc<calloop::Source<WaylandSource>>,
|
||||
|
||||
/// A proxy to wake up event loop.
|
||||
pub event_loop_awakener: calloop::ping::Ping,
|
||||
|
||||
/// The available windowing features.
|
||||
pub windowing_features: WindowingFeatures,
|
||||
|
||||
/// Theme manager to manage cursors.
|
||||
///
|
||||
/// It's being shared amoung all windows to avoid loading
|
||||
/// multiple similar themes.
|
||||
pub theme_manager: ThemeManager,
|
||||
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
/// Event loop.
|
||||
event_loop: calloop::EventLoop<WinitState>,
|
||||
|
||||
/// Wayland display.
|
||||
display: Display,
|
||||
|
||||
/// Pending user events.
|
||||
pending_user_events: Rc<RefCell<Vec<T>>>,
|
||||
|
||||
/// Sender of user events.
|
||||
user_events_sender: calloop::channel::Sender<T>,
|
||||
|
||||
/// Wayland source of events.
|
||||
wayland_source: Rc<calloop::Source<WaylandSource>>,
|
||||
|
||||
/// Window target.
|
||||
window_target: RootEventLoopWindowTarget<T>,
|
||||
|
||||
/// Output manager.
|
||||
_seat_manager: SeatManager,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn new() -> Result<EventLoop<T>, Box<dyn Error>> {
|
||||
// Connect to wayland server and setup event queue.
|
||||
let display = Display::connect_to_env()?;
|
||||
let mut event_queue = display.create_event_queue();
|
||||
let display_proxy = display.attach(event_queue.token());
|
||||
|
||||
// Setup environment.
|
||||
let env = Environment::init(&display_proxy, WinitEnv::new());
|
||||
|
||||
// Issue 2 sync roundtrips to initialize environment.
|
||||
event_queue.sync_roundtrip(&mut (), |_, _, _| unreachable!())?;
|
||||
event_queue.sync_roundtrip(&mut (), |_, _, _| unreachable!())?;
|
||||
|
||||
// Create event loop.
|
||||
let event_loop = calloop::EventLoop::<WinitState>::new()?;
|
||||
// Build windowing features.
|
||||
let windowing_features = WindowingFeatures::new(&env);
|
||||
|
||||
// Create a theme manager.
|
||||
let compositor = env.require_global::<WlCompositor>();
|
||||
let shm = env.require_global::<WlShm>();
|
||||
let theme_manager = ThemeManager::init(ThemeSpec::System, compositor, shm);
|
||||
|
||||
// Setup theme seat and output managers.
|
||||
let seat_manager = SeatManager::new(&env, event_loop.handle(), theme_manager.clone());
|
||||
let output_manager = OutputManager::new(&env);
|
||||
|
||||
// A source of events that we plug into our event loop.
|
||||
let wayland_source = WaylandSource::new(event_queue).quick_insert(event_loop.handle())?;
|
||||
let wayland_source = Rc::new(wayland_source);
|
||||
|
||||
// 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.
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(user_events_channel, move |event, _, _| {
|
||||
if let calloop::channel::Event::Msg(msg) = event {
|
||||
pending_user_events_clone.borrow_mut().push(msg);
|
||||
}
|
||||
})?;
|
||||
|
||||
// An event's loop awakener to wake up for window events from winit's windows.
|
||||
let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?;
|
||||
|
||||
// Handler of window requests.
|
||||
event_loop.handle().insert_source(
|
||||
event_loop_awakener_source,
|
||||
move |_, _, winit_state| {
|
||||
shim::handle_window_requests(winit_state);
|
||||
},
|
||||
)?;
|
||||
|
||||
let event_loop_handle = event_loop.handle();
|
||||
let window_map = HashMap::new();
|
||||
let event_sink = EventSink::new();
|
||||
let window_updates = HashMap::new();
|
||||
|
||||
// Create event loop window target.
|
||||
let event_loop_window_target = EventLoopWindowTarget {
|
||||
display: display.clone(),
|
||||
env,
|
||||
state: RefCell::new(WinitState {
|
||||
window_map,
|
||||
event_sink,
|
||||
window_updates,
|
||||
}),
|
||||
event_loop_handle,
|
||||
output_manager,
|
||||
event_loop_awakener,
|
||||
wayland_source: wayland_source.clone(),
|
||||
windowing_features,
|
||||
theme_manager,
|
||||
_marker: std::marker::PhantomData,
|
||||
};
|
||||
|
||||
// Create event loop itself.
|
||||
let event_loop = Self {
|
||||
event_loop,
|
||||
display,
|
||||
pending_user_events,
|
||||
wayland_source,
|
||||
_seat_manager: seat_manager,
|
||||
user_events_sender,
|
||||
window_target: RootEventLoopWindowTarget {
|
||||
p: crate::platform_impl::EventLoopWindowTarget::Wayland(event_loop_window_target),
|
||||
_marker: std::marker::PhantomData,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(event_loop)
|
||||
}
|
||||
|
||||
pub fn run<F>(mut self, callback: F) -> !
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static,
|
||||
{
|
||||
self.run_return(callback);
|
||||
process::exit(0)
|
||||
}
|
||||
|
||||
pub fn run_return<F>(&mut self, mut callback: F)
|
||||
where
|
||||
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
||||
{
|
||||
// Send pending events to the server.
|
||||
let _ = self.display.flush();
|
||||
|
||||
let mut control_flow = ControlFlow::default();
|
||||
|
||||
let pending_user_events = self.pending_user_events.clone();
|
||||
|
||||
callback(
|
||||
Event::NewEvents(StartCause::Init),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
|
||||
let mut window_updates: Vec<(WindowId, WindowUpdate)> = Vec::new();
|
||||
let mut event_sink_back_buffer = Vec::new();
|
||||
|
||||
// NOTE We break on errors from dispatches, since if we've got protocol error
|
||||
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
|
||||
// really an option. Instead we inform that the event loop got destroyed. We may
|
||||
// communicate an error that something was terminated, but winit doesn't provide us
|
||||
// with an API to do that via some event.
|
||||
loop {
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
|
||||
// Process 'new' pending updates.
|
||||
self.with_state(|state| {
|
||||
window_updates.clear();
|
||||
window_updates.extend(
|
||||
state
|
||||
.window_updates
|
||||
.iter_mut()
|
||||
.map(|(wid, window_update)| (*wid, window_update.take())),
|
||||
);
|
||||
});
|
||||
|
||||
for (window_id, window_update) in window_updates.iter_mut() {
|
||||
if let Some(scale_factor) = window_update.scale_factor.map(|f| f as f64) {
|
||||
let mut physical_size = self.with_state(|state| {
|
||||
let window_handle = state.window_map.get(&window_id).unwrap();
|
||||
let mut size = window_handle.size.lock().unwrap();
|
||||
|
||||
// Update the new logical size if it was changed.
|
||||
let window_size = window_update.size.unwrap_or(*size);
|
||||
*size = window_size;
|
||||
|
||||
window_size.to_physical(scale_factor)
|
||||
});
|
||||
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(
|
||||
crate::platform_impl::WindowId::Wayland(*window_id),
|
||||
),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size: &mut physical_size,
|
||||
},
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
// We don't update size on a window handle since we'll do that later
|
||||
// when handling size update.
|
||||
let new_logical_size = physical_size.to_logical(scale_factor);
|
||||
window_update.size = Some(new_logical_size);
|
||||
}
|
||||
|
||||
if let Some(size) = window_update.size.take() {
|
||||
let physical_size = self.with_state(|state| {
|
||||
let window_handle = state.window_map.get_mut(&window_id).unwrap();
|
||||
let mut window_size = window_handle.size.lock().unwrap();
|
||||
|
||||
// Always issue resize event on scale factor change.
|
||||
let physical_size =
|
||||
if window_update.scale_factor.is_none() && *window_size == size {
|
||||
// The size hasn't changed, don't inform downstream about that.
|
||||
None
|
||||
} else {
|
||||
*window_size = size;
|
||||
let scale_factor =
|
||||
sctk::get_surface_scale_factor(&window_handle.window.surface());
|
||||
let physical_size = size.to_physical(scale_factor as f64);
|
||||
Some(physical_size)
|
||||
};
|
||||
|
||||
// We still perform all of those resize related logic even if the size
|
||||
// hasn't changed, since GNOME relies on `set_geometry` calls after
|
||||
// configures.
|
||||
window_handle.window.resize(size.width, size.height);
|
||||
window_handle.window.refresh();
|
||||
|
||||
// Mark that refresh isn't required, since we've done it right now.
|
||||
window_update.refresh_frame = false;
|
||||
|
||||
physical_size
|
||||
});
|
||||
|
||||
if let Some(physical_size) = physical_size {
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(
|
||||
crate::platform_impl::WindowId::Wayland(*window_id),
|
||||
),
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
},
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if window_update.close_window {
|
||||
sticky_exit_callback(
|
||||
Event::WindowEvent {
|
||||
window_id: crate::window::WindowId(
|
||||
crate::platform_impl::WindowId::Wayland(*window_id),
|
||||
),
|
||||
event: WindowEvent::CloseRequested,
|
||||
},
|
||||
&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_state(|state| {
|
||||
std::mem::swap(
|
||||
&mut event_sink_back_buffer,
|
||||
&mut state.event_sink.window_events,
|
||||
)
|
||||
});
|
||||
|
||||
// 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,
|
||||
);
|
||||
|
||||
// Handle RedrawRequested events.
|
||||
for (window_id, window_update) in window_updates.iter() {
|
||||
// Handle refresh of the frame.
|
||||
if window_update.refresh_frame {
|
||||
self.with_state(|state| {
|
||||
let window_handle = state.window_map.get_mut(&window_id).unwrap();
|
||||
window_handle.window.refresh();
|
||||
if !window_update.redraw_requested {
|
||||
window_handle.window.surface().commit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle redraw request.
|
||||
if window_update.redraw_requested {
|
||||
sticky_exit_callback(
|
||||
Event::RedrawRequested(crate::window::WindowId(
|
||||
crate::platform_impl::WindowId::Wayland(*window_id),
|
||||
)),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Send RedrawEventCleared.
|
||||
sticky_exit_callback(
|
||||
Event::RedrawEventsCleared,
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
// Send pending events to the server.
|
||||
let _ = self.display.flush();
|
||||
|
||||
// During the run of the user callback, some other code monitoring and reading the
|
||||
// Wayland socket may have been run (mesa for example does this with vsync), if that
|
||||
// is the case, some events may have been enqueued in our event queue.
|
||||
//
|
||||
// If some messages are there, the event loop needs to behave as if it was instantly
|
||||
// woken up by messages arriving from the Wayland socket, to avoid delaying the
|
||||
// dispatch of these events until we're woken up again.
|
||||
let instant_wakeup = {
|
||||
let handle = self.event_loop.handle();
|
||||
let source = self.wayland_source.clone();
|
||||
let dispatched = handle.with_source(&source, |wayland_source| {
|
||||
let queue = wayland_source.queue();
|
||||
self.with_state(|state| {
|
||||
queue.dispatch_pending(state, |_, _, _| unimplemented!())
|
||||
})
|
||||
});
|
||||
|
||||
if let Ok(dispatched) = dispatched {
|
||||
dispatched > 0
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
match control_flow {
|
||||
ControlFlow::Exit => break,
|
||||
ControlFlow::Poll => {
|
||||
// Non-blocking dispatch.
|
||||
let timeout = Duration::from_millis(0);
|
||||
if self.loop_dispatch(Some(timeout)).is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
callback(
|
||||
Event::NewEvents(StartCause::Poll),
|
||||
&self.window_target,
|
||||
&mut control_flow,
|
||||
);
|
||||
}
|
||||
ControlFlow::Wait => {
|
||||
let timeout = if instant_wakeup {
|
||||
Some(Duration::from_millis(0))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if self.loop_dispatch(timeout).is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
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 && !instant_wakeup {
|
||||
deadline - start
|
||||
} else {
|
||||
Duration::from_millis(0)
|
||||
};
|
||||
|
||||
if self.loop_dispatch(Some(duration)).is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback(Event::LoopDestroyed, &self.window_target, &mut control_flow);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
EventLoopProxy::new(self.user_events_sender.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
|
||||
&self.window_target
|
||||
}
|
||||
|
||||
fn with_state<U, F: FnOnce(&mut WinitState) -> U>(&mut self, f: F) -> U {
|
||||
let state = match &mut self.window_target.p {
|
||||
crate::platform_impl::EventLoopWindowTarget::Wayland(ref mut window_target) => {
|
||||
window_target.state.get_mut()
|
||||
}
|
||||
#[cfg(feature = "x11")]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
f(state)
|
||||
}
|
||||
|
||||
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(
|
||||
&mut self,
|
||||
timeout: D,
|
||||
) -> std::io::Result<()> {
|
||||
let mut state = match &mut self.window_target.p {
|
||||
crate::platform_impl::EventLoopWindowTarget::Wayland(ref mut window_target) => {
|
||||
window_target.state.get_mut()
|
||||
}
|
||||
#[cfg(feature = "x11")]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
self.event_loop.dispatch(timeout, &mut state)
|
||||
}
|
||||
}
|
32
src/platform_impl/linux/wayland/event_loop/proxy.rs
Normal file
32
src/platform_impl/linux/wayland/event_loop/proxy.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
//! An event loop proxy.
|
||||
|
||||
use std::sync::mpsc::SendError;
|
||||
|
||||
use sctk::reexports::calloop::channel::Sender;
|
||||
|
||||
use crate::event_loop::EventLoopClosed;
|
||||
|
||||
/// A handle that can be sent across the threads and used to wake up the `EventLoop`.
|
||||
pub struct EventLoopProxy<T: 'static> {
|
||||
user_events_sender: Sender<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> Clone for EventLoopProxy<T> {
|
||||
fn clone(&self) -> Self {
|
||||
EventLoopProxy {
|
||||
user_events_sender: self.user_events_sender.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopProxy<T> {
|
||||
pub fn new(user_events_sender: Sender<T>) -> Self {
|
||||
Self { user_events_sender }
|
||||
}
|
||||
|
||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
||||
self.user_events_sender
|
||||
.send(event)
|
||||
.map_err(|SendError(error)| EventLoopClosed(error))
|
||||
}
|
||||
}
|
36
src/platform_impl/linux/wayland/event_loop/sink.rs
Normal file
36
src/platform_impl/linux/wayland/event_loop/sink.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
//! An event loop's sink to deliver events from the Wayland event callbacks.
|
||||
|
||||
use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent};
|
||||
use crate::platform_impl::platform::{DeviceId as PlatformDeviceId, WindowId as PlatformWindowId};
|
||||
use crate::window::WindowId as RootWindowId;
|
||||
|
||||
use super::{DeviceId, WindowId};
|
||||
|
||||
/// An event loop's sink to deliver events from the Wayland event callbacks
|
||||
/// to the winit's user.
|
||||
#[derive(Default)]
|
||||
pub struct EventSink {
|
||||
pub window_events: Vec<Event<'static, ()>>,
|
||||
}
|
||||
|
||||
impl EventSink {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Add new device event to a queue.
|
||||
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
|
||||
self.window_events.push(Event::DeviceEvent {
|
||||
event,
|
||||
device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)),
|
||||
});
|
||||
}
|
||||
|
||||
/// Add new window event to a queue.
|
||||
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
|
||||
self.window_events.push(Event::WindowEvent {
|
||||
event,
|
||||
window_id: RootWindowId(PlatformWindowId::Wayland(window_id)),
|
||||
});
|
||||
}
|
||||
}
|
25
src/platform_impl/linux/wayland/event_loop/state.rs
Normal file
25
src/platform_impl/linux/wayland/event_loop/state.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
//! A state that we pass around in a dispatch.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::EventSink;
|
||||
use crate::platform_impl::wayland::window::shim::{WindowHandle, WindowUpdate};
|
||||
use crate::platform_impl::wayland::WindowId;
|
||||
|
||||
/// Wrapper to carry winit's state.
|
||||
pub struct WinitState {
|
||||
/// A sink for window and device events that is being filled during dispatching
|
||||
/// event loop and forwarded downstream afterwards.
|
||||
pub event_sink: EventSink,
|
||||
|
||||
/// Window updates, which are coming from SCTK or the compositor, which require
|
||||
/// calling back to the winit's downstream. They are handled right in the event loop,
|
||||
/// unlike the ones coming from buffers on the `WindowHandle`'s.
|
||||
pub window_updates: HashMap<WindowId, WindowUpdate>,
|
||||
|
||||
/// Window map containing all SCTK windows. Since those windows aren't allowed
|
||||
/// to be sent to other threads, they live on the event loop's thread
|
||||
/// and requests from winit's windows are being forwarded to them either via
|
||||
/// `WindowUpdate` or buffer on the associated with it `WindowHandle`.
|
||||
pub window_map: HashMap<WindowId, WindowHandle>,
|
||||
}
|
|
@ -1,396 +0,0 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::{event_loop::EventsSink, make_wid, DeviceId};
|
||||
use smithay_client_toolkit::{
|
||||
keyboard::{
|
||||
self, map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatEvent, KeyRepeatKind,
|
||||
},
|
||||
reexports::client::protocol::{wl_keyboard, wl_seat},
|
||||
};
|
||||
|
||||
use crate::event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent};
|
||||
|
||||
pub fn init_keyboard(
|
||||
seat: &wl_seat::WlSeat,
|
||||
sink: EventsSink,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
) -> wl_keyboard::WlKeyboard {
|
||||
// { variables to be captured by the closures
|
||||
let target = Arc::new(Mutex::new(None));
|
||||
let my_sink = sink.clone();
|
||||
let repeat_sink = sink.clone();
|
||||
let repeat_target = target.clone();
|
||||
let my_modifiers = modifiers_tracker.clone();
|
||||
// }
|
||||
let ret = map_keyboard_auto_with_repeat(
|
||||
seat,
|
||||
KeyRepeatKind::System,
|
||||
move |evt: KbEvent<'_>, _| {
|
||||
match evt {
|
||||
KbEvent::Enter { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send_window_event(WindowEvent::Focused(true), wid);
|
||||
*target.lock().unwrap() = Some(wid);
|
||||
|
||||
let modifiers = *modifiers_tracker.lock().unwrap();
|
||||
|
||||
if !modifiers.is_empty() {
|
||||
my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid);
|
||||
}
|
||||
}
|
||||
KbEvent::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
let modifiers = *modifiers_tracker.lock().unwrap();
|
||||
|
||||
if !modifiers.is_empty() {
|
||||
my_sink.send_window_event(
|
||||
WindowEvent::ModifiersChanged(ModifiersState::empty()),
|
||||
wid,
|
||||
);
|
||||
}
|
||||
|
||||
my_sink.send_window_event(WindowEvent::Focused(false), wid);
|
||||
*target.lock().unwrap() = None;
|
||||
}
|
||||
KbEvent::Key {
|
||||
rawkey,
|
||||
keysym,
|
||||
state,
|
||||
utf8,
|
||||
..
|
||||
} => {
|
||||
if let Some(wid) = *target.lock().unwrap() {
|
||||
let state = match state {
|
||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let vkcode = key_to_vkey(rawkey, keysym);
|
||||
my_sink.send_window_event(
|
||||
#[allow(deprecated)]
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: rawkey,
|
||||
virtual_keycode: vkcode,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
wid,
|
||||
);
|
||||
// send char event only on key press, not release
|
||||
if let ElementState::Released = state {
|
||||
return;
|
||||
}
|
||||
if let Some(txt) = utf8 {
|
||||
for chr in txt.chars() {
|
||||
my_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
|
||||
KbEvent::Modifiers {
|
||||
modifiers: event_modifiers,
|
||||
} => {
|
||||
let modifiers = ModifiersState::from_wayland(event_modifiers);
|
||||
|
||||
*modifiers_tracker.lock().unwrap() = modifiers;
|
||||
|
||||
if let Some(wid) = *target.lock().unwrap() {
|
||||
my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
move |repeat_event: KeyRepeatEvent, _| {
|
||||
if let Some(wid) = *repeat_target.lock().unwrap() {
|
||||
let state = ElementState::Pressed;
|
||||
let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym);
|
||||
repeat_sink.send_window_event(
|
||||
#[allow(deprecated)]
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: repeat_event.rawkey,
|
||||
virtual_keycode: vkcode,
|
||||
modifiers: my_modifiers.lock().unwrap().clone(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
wid,
|
||||
);
|
||||
if let Some(txt) = repeat_event.utf8 {
|
||||
for chr in txt.chars() {
|
||||
repeat_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
match ret {
|
||||
Ok(keyboard) => keyboard,
|
||||
Err(_) => {
|
||||
// This is a fallback impl if libxkbcommon was not available
|
||||
// This case should probably never happen, as most wayland
|
||||
// compositors _need_ libxkbcommon anyway...
|
||||
//
|
||||
// In this case, we don't have the keymap information (it is
|
||||
// supposed to be serialized by the compositor using libxkbcommon)
|
||||
|
||||
seat.get_keyboard(|keyboard| {
|
||||
// { variables to be captured by the closure
|
||||
let mut target = None;
|
||||
let my_sink = sink;
|
||||
// }
|
||||
|
||||
keyboard.implement_closure(
|
||||
move |evt, _| match evt {
|
||||
wl_keyboard::Event::Enter { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send_window_event(WindowEvent::Focused(true), wid);
|
||||
target = Some(wid);
|
||||
}
|
||||
wl_keyboard::Event::Leave { surface, .. } => {
|
||||
let wid = make_wid(&surface);
|
||||
my_sink.send_window_event(WindowEvent::Focused(false), wid);
|
||||
target = None;
|
||||
}
|
||||
wl_keyboard::Event::Key { key, state, .. } => {
|
||||
if let Some(wid) = target {
|
||||
let state = match state {
|
||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
my_sink.send_window_event(
|
||||
#[allow(deprecated)]
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: key,
|
||||
virtual_keycode: None,
|
||||
modifiers: ModifiersState::default(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
(),
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_to_vkey(rawkey: u32, keysym: u32) -> Option<VirtualKeyCode> {
|
||||
match rawkey {
|
||||
1 => Some(VirtualKeyCode::Escape),
|
||||
2 => Some(VirtualKeyCode::Key1),
|
||||
3 => Some(VirtualKeyCode::Key2),
|
||||
4 => Some(VirtualKeyCode::Key3),
|
||||
5 => Some(VirtualKeyCode::Key4),
|
||||
6 => Some(VirtualKeyCode::Key5),
|
||||
7 => Some(VirtualKeyCode::Key6),
|
||||
8 => Some(VirtualKeyCode::Key7),
|
||||
9 => Some(VirtualKeyCode::Key8),
|
||||
10 => Some(VirtualKeyCode::Key9),
|
||||
11 => Some(VirtualKeyCode::Key0),
|
||||
_ => keysym_to_vkey(keysym),
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
||||
use smithay_client_toolkit::keyboard::keysyms;
|
||||
match keysym {
|
||||
// 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),
|
||||
// F--
|
||||
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),
|
||||
// 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),
|
||||
// => Some(VirtualKeyCode::At),
|
||||
// => Some(VirtualKeyCode::Ax),
|
||||
keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash),
|
||||
// => 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),
|
||||
// => Some(VirtualKeyCode::Grave),
|
||||
// => Some(VirtualKeyCode::Kana),
|
||||
// => Some(VirtualKeyCode::Kanji),
|
||||
keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt),
|
||||
// => Some(VirtualKeyCode::LBracket),
|
||||
keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl),
|
||||
keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift),
|
||||
// => Some(VirtualKeyCode::LWin),
|
||||
// => Some(VirtualKeyCode::Mail),
|
||||
// => Some(VirtualKeyCode::MediaSelect),
|
||||
// => Some(VirtualKeyCode::MediaStop),
|
||||
keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus),
|
||||
keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk),
|
||||
// => Some(VirtualKeyCode::Mute),
|
||||
// => Some(VirtualKeyCode::MyComputer),
|
||||
// => 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_Decimal => Some(VirtualKeyCode::NumpadDecimal),
|
||||
keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide),
|
||||
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),
|
||||
// => Some(VirtualKeyCode::OEM102),
|
||||
// => Some(VirtualKeyCode::Period),
|
||||
// => Some(VirtualKeyCode::Playpause),
|
||||
// => Some(VirtualKeyCode::Power),
|
||||
// => Some(VirtualKeyCode::Prevtrack),
|
||||
keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt),
|
||||
// => Some(VirtualKeyCode::RBracket),
|
||||
keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl),
|
||||
keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift),
|
||||
// => Some(VirtualKeyCode::RWin),
|
||||
keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon),
|
||||
keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash),
|
||||
// => 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),
|
||||
// => 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),
|
||||
// => 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,
|
||||
}
|
||||
}
|
||||
|
||||
impl ModifiersState {
|
||||
pub(crate) fn from_wayland(mods: keyboard::ModifiersState) -> ModifiersState {
|
||||
let mut m = ModifiersState::empty();
|
||||
m.set(ModifiersState::SHIFT, mods.shift);
|
||||
m.set(ModifiersState::CTRL, mods.ctrl);
|
||||
m.set(ModifiersState::ALT, mods.alt);
|
||||
m.set(ModifiersState::LOGO, mods.logo);
|
||||
m
|
||||
}
|
||||
}
|
|
@ -1,17 +1,21 @@
|
|||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd",
|
||||
target_os = "netbsd", target_os = "openbsd"))]
|
||||
#![cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
|
||||
pub use self::{
|
||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode},
|
||||
window::Window,
|
||||
};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
|
||||
use smithay_client_toolkit::reexports::client::protocol::wl_surface;
|
||||
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
||||
pub use output::{MonitorHandle, VideoMode};
|
||||
pub use window::Window;
|
||||
|
||||
mod env;
|
||||
mod event_loop;
|
||||
mod keyboard;
|
||||
mod pointer;
|
||||
mod touch;
|
||||
mod output;
|
||||
mod seat;
|
||||
mod window;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
@ -33,6 +37,6 @@ impl WindowId {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn make_wid(s: &wl_surface::WlSurface) -> WindowId {
|
||||
WindowId(s.as_ref().c_ptr() as usize)
|
||||
fn make_wid(surface: &WlSurface) -> WindowId {
|
||||
WindowId(surface.as_ref().c_ptr() as usize)
|
||||
}
|
||||
|
|
240
src/platform_impl/linux/wayland/output.rs
Normal file
240
src/platform_impl/linux/wayland/output.rs
Normal file
|
@ -0,0 +1,240 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
use sctk::reexports::client::Display;
|
||||
|
||||
use sctk::environment::Environment;
|
||||
use sctk::output::OutputStatusListener;
|
||||
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode};
|
||||
use crate::platform_impl::platform::{
|
||||
MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode,
|
||||
};
|
||||
|
||||
use super::env::WinitEnv;
|
||||
use super::event_loop::EventLoopWindowTarget;
|
||||
|
||||
/// Output manager.
|
||||
pub struct OutputManager {
|
||||
/// A handle that actually performs all operations on outputs.
|
||||
handle: OutputManagerHandle,
|
||||
|
||||
_output_listener: OutputStatusListener,
|
||||
}
|
||||
|
||||
impl OutputManager {
|
||||
pub fn new(env: &Environment<WinitEnv>) -> Self {
|
||||
let handle = OutputManagerHandle::new();
|
||||
|
||||
// Handle existing outputs.
|
||||
for output in env.get_all_outputs() {
|
||||
match sctk::output::with_output_info(&output, |info| info.obsolete) {
|
||||
Some(false) => (),
|
||||
// The output is obsolete or we've failed to access its data, skipping.
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
// The output is present and unusable, add it to the output manager manager.
|
||||
handle.add_output(output);
|
||||
}
|
||||
|
||||
let handle_for_listener = handle.clone();
|
||||
|
||||
let output_listener = env.listen_for_outputs(move |output, info, _| {
|
||||
if info.obsolete {
|
||||
handle_for_listener.remove_output(output)
|
||||
} else {
|
||||
handle_for_listener.add_output(output)
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
handle,
|
||||
_output_listener: output_listener,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> OutputManagerHandle {
|
||||
self.handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to output manager.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OutputManagerHandle {
|
||||
outputs: Arc<Mutex<VecDeque<MonitorHandle>>>,
|
||||
}
|
||||
|
||||
impl OutputManagerHandle {
|
||||
fn new() -> Self {
|
||||
let outputs = Arc::new(Mutex::new(VecDeque::new()));
|
||||
Self { outputs }
|
||||
}
|
||||
|
||||
/// Handle addition of the output.
|
||||
fn add_output(&self, output: WlOutput) {
|
||||
let mut outputs = self.outputs.lock().unwrap();
|
||||
let position = outputs.iter().position(|handle| handle.proxy == output);
|
||||
if position.is_none() {
|
||||
outputs.push_back(MonitorHandle::new(output));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle removal of the output.
|
||||
fn remove_output(&self, output: WlOutput) {
|
||||
let mut outputs = self.outputs.lock().unwrap();
|
||||
let position = outputs.iter().position(|handle| handle.proxy == output);
|
||||
if let Some(position) = position {
|
||||
outputs.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all observed outputs.
|
||||
pub fn available_outputs(&self) -> VecDeque<MonitorHandle> {
|
||||
self.outputs.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MonitorHandle {
|
||||
pub(crate) proxy: WlOutput,
|
||||
}
|
||||
|
||||
impl PartialEq for MonitorHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.native_identifier() == other.native_identifier()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for MonitorHandle {}
|
||||
|
||||
impl PartialOrd for MonitorHandle {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(&other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for MonitorHandle {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.native_identifier().cmp(&other.native_identifier())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for MonitorHandle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.native_identifier().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
#[inline]
|
||||
pub(crate) fn new(proxy: WlOutput) -> Self {
|
||||
Self { proxy }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<String> {
|
||||
sctk::output::with_output_info(&self.proxy, |info| {
|
||||
format!("{} ({})", info.model, info.make)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn native_identifier(&self) -> u32 {
|
||||
sctk::output::with_output_info(&self.proxy, |info| info.id).unwrap_or(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
match sctk::output::with_output_info(&self.proxy, |info| {
|
||||
info.modes
|
||||
.iter()
|
||||
.find(|mode| mode.is_current)
|
||||
.map(|mode| mode.dimensions)
|
||||
}) {
|
||||
Some(Some((w, h))) => (w as u32, h as u32),
|
||||
_ => (0, 0),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
sctk::output::with_output_info(&self.proxy, |info| info.location)
|
||||
.unwrap_or((0, 0))
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> i32 {
|
||||
sctk::output::with_output_info(&self.proxy, |info| info.scale_factor).unwrap_or(1)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||
let modes = sctk::output::with_output_info(&self.proxy, |info| info.modes.clone())
|
||||
.unwrap_or_else(Vec::new);
|
||||
|
||||
let monitor = self.clone();
|
||||
|
||||
modes.into_iter().map(move |mode| RootVideoMode {
|
||||
video_mode: PlatformVideoMode::Wayland(VideoMode {
|
||||
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
|
||||
refresh_rate: (mode.refresh_rate as f32 / 1000.0).round() as u16,
|
||||
bit_depth: 32,
|
||||
monitor: monitor.clone(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VideoMode {
|
||||
pub(crate) size: PhysicalSize<u32>,
|
||||
pub(crate) bit_depth: u16,
|
||||
pub(crate) refresh_rate: u16,
|
||||
pub(crate) monitor: MonitorHandle,
|
||||
}
|
||||
|
||||
impl VideoMode {
|
||||
#[inline]
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
self.size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bit_depth(&self) -> u16 {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn refresh_rate(&self) -> u16 {
|
||||
self.refresh_rate
|
||||
}
|
||||
|
||||
pub fn monitor(&self) -> RootMonitorHandle {
|
||||
RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(self.monitor.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
#[inline]
|
||||
pub fn display(&self) -> &Display {
|
||||
&self.display
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
self.output_manager.handle.available_outputs()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
||||
// There's no primary monitor on Wayland.
|
||||
None
|
||||
}
|
||||
}
|
|
@ -1,290 +0,0 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{
|
||||
DeviceEvent, ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
|
||||
WindowEvent,
|
||||
};
|
||||
|
||||
use super::{
|
||||
event_loop::{CursorManager, EventsSink},
|
||||
make_wid,
|
||||
window::WindowStore,
|
||||
DeviceId,
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::surface;
|
||||
|
||||
use smithay_client_toolkit::reexports::client::protocol::{
|
||||
wl_pointer::{self, Event as PtrEvent, WlPointer},
|
||||
wl_seat,
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1::client::{
|
||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, zwp_relative_pointer_v1::Event,
|
||||
zwp_relative_pointer_v1::ZwpRelativePointerV1,
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints::v1::client::{
|
||||
zwp_locked_pointer_v1::ZwpLockedPointerV1, zwp_pointer_constraints_v1::Lifetime,
|
||||
zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface;
|
||||
|
||||
pub fn implement_pointer(
|
||||
seat: &wl_seat::WlSeat,
|
||||
sink: EventsSink,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
||||
cursor_manager: Arc<Mutex<CursorManager>>,
|
||||
) -> WlPointer {
|
||||
seat.get_pointer(|pointer| {
|
||||
// Currently focused winit surface
|
||||
let mut mouse_focus = None;
|
||||
let mut axis_buffer = None;
|
||||
let mut axis_discrete_buffer = None;
|
||||
let mut axis_state = TouchPhase::Ended;
|
||||
|
||||
pointer.implement_closure(
|
||||
move |evt, pointer| {
|
||||
let store = store.lock().unwrap();
|
||||
let mut cursor_manager = cursor_manager.lock().unwrap();
|
||||
match evt {
|
||||
PtrEvent::Enter {
|
||||
surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
let wid = store.find_wid(&surface);
|
||||
|
||||
if let Some(wid) = wid {
|
||||
let scale_factor = surface::get_dpi_factor(&surface) as f64;
|
||||
mouse_focus = Some(surface);
|
||||
|
||||
// Reload cursor style only when we enter winit's surface. Calling
|
||||
// this function every time on `PtrEvent::Enter` could interfere with
|
||||
// SCTK CSD handling, since it changes cursor icons when you hover
|
||||
// cursor over the window borders.
|
||||
cursor_manager.reload_cursor_style();
|
||||
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorEntered {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
|
||||
let position = LogicalPosition::new(surface_x, surface_y)
|
||||
.to_physical(scale_factor);
|
||||
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
position,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Leave { surface, .. } => {
|
||||
mouse_focus = None;
|
||||
let wid = store.find_wid(&surface);
|
||||
if let Some(wid) = wid {
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorLeft {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Motion {
|
||||
surface_x,
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
if let Some(surface) = mouse_focus.as_ref() {
|
||||
let wid = make_wid(surface);
|
||||
|
||||
let scale_factor = surface::get_dpi_factor(&surface) as f64;
|
||||
let position = LogicalPosition::new(surface_x, surface_y)
|
||||
.to_physical(scale_factor);
|
||||
|
||||
sink.send_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
position,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Button { button, state, .. } => {
|
||||
if let Some(surface) = mouse_focus.as_ref() {
|
||||
let state = match state {
|
||||
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
|
||||
wl_pointer::ButtonState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let button = match button {
|
||||
0x110 => MouseButton::Left,
|
||||
0x111 => MouseButton::Right,
|
||||
0x112 => MouseButton::Middle,
|
||||
// TODO figure out the translation ?
|
||||
_ => return,
|
||||
};
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseInput {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
state,
|
||||
button,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
make_wid(surface),
|
||||
);
|
||||
}
|
||||
}
|
||||
PtrEvent::Axis { axis, value, .. } => {
|
||||
if let Some(surface) = mouse_focus.as_ref() {
|
||||
let wid = make_wid(surface);
|
||||
if pointer.as_ref().version() < 5 {
|
||||
let (mut x, mut y) = (0.0, 0.0);
|
||||
// old seat compatibility
|
||||
match axis {
|
||||
// wayland vertical sign convention is the inverse of winit
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let scale_factor = surface::get_dpi_factor(&surface) as f64;
|
||||
let delta = LogicalPosition::new(x as f64, y as f64)
|
||||
.to_physical(scale_factor);
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
delta: MouseScrollDelta::PixelDelta(delta),
|
||||
phase: TouchPhase::Moved,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
} else {
|
||||
let (mut x, mut y) = axis_buffer.unwrap_or((0.0, 0.0));
|
||||
match axis {
|
||||
// wayland vertical sign convention is the inverse of winit
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
axis_buffer = Some((x, y));
|
||||
axis_state = match axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PtrEvent::Frame => {
|
||||
let axis_buffer = axis_buffer.take();
|
||||
let axis_discrete_buffer = axis_discrete_buffer.take();
|
||||
if let Some(surface) = mouse_focus.as_ref() {
|
||||
let wid = make_wid(surface);
|
||||
if let Some((x, y)) = axis_discrete_buffer {
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
delta: MouseScrollDelta::LineDelta(x, y),
|
||||
phase: axis_state,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
} else if let Some((x, y)) = axis_buffer {
|
||||
let scale_factor = surface::get_dpi_factor(&surface) as f64;
|
||||
let delta = LogicalPosition::new(x, y).to_physical(scale_factor);
|
||||
sink.send_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
delta: MouseScrollDelta::PixelDelta(delta),
|
||||
phase: axis_state,
|
||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
||||
},
|
||||
wid,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
PtrEvent::AxisSource { .. } => (),
|
||||
PtrEvent::AxisStop { .. } => {
|
||||
axis_state = TouchPhase::Ended;
|
||||
}
|
||||
PtrEvent::AxisDiscrete { axis, discrete } => {
|
||||
let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0.0, 0.0));
|
||||
match axis {
|
||||
// wayland vertical sign convention is the inverse of winit
|
||||
wl_pointer::Axis::VerticalScroll => y -= discrete as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += discrete as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
axis_discrete_buffer = Some((x, y));
|
||||
axis_state = match axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
(),
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn implement_relative_pointer(
|
||||
sink: EventsSink,
|
||||
pointer: &WlPointer,
|
||||
manager: &ZwpRelativePointerManagerV1,
|
||||
) -> Result<ZwpRelativePointerV1, ()> {
|
||||
manager.get_relative_pointer(pointer, |rel_pointer| {
|
||||
rel_pointer.implement_closure(
|
||||
move |evt, _rel_pointer| match evt {
|
||||
Event::RelativeMotion { dx, dy, .. } => {
|
||||
sink.send_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn implement_locked_pointer(
|
||||
surface: &WlSurface,
|
||||
pointer: &WlPointer,
|
||||
constraints: &ZwpPointerConstraintsV1,
|
||||
) -> Result<ZwpLockedPointerV1, ()> {
|
||||
constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent.to_raw(), |c| {
|
||||
c.implement_closure(|_, _| (), ())
|
||||
})
|
||||
}
|
151
src/platform_impl/linux/wayland/seat/keyboard/handlers.rs
Normal file
151
src/platform_impl/linux/wayland/seat/keyboard/handlers.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
//! Handling of various keyboard events.
|
||||
|
||||
use sctk::reexports::client::protocol::wl_keyboard::KeyState;
|
||||
|
||||
use sctk::seat::keyboard::Event as KeyboardEvent;
|
||||
|
||||
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;
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_keyboard(
|
||||
event: KeyboardEvent<'_>,
|
||||
inner: &mut KeyboardInner,
|
||||
winit_state: &mut WinitState,
|
||||
) {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
match event {
|
||||
KeyboardEvent::Enter { surface, .. } => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
// Window gained focus.
|
||||
event_sink.push_window_event(WindowEvent::Focused(true), window_id);
|
||||
|
||||
// Dispatch modifers changes that we've received before getting `Enter` event.
|
||||
if let Some(modifiers) = inner.pending_modifers_state.take() {
|
||||
*inner.modifiers_state.borrow_mut() = modifiers;
|
||||
event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id);
|
||||
}
|
||||
|
||||
inner.target_window_id = Some(window_id);
|
||||
}
|
||||
KeyboardEvent::Leave { surface, .. } => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
// Notify that no modifiers are being pressed.
|
||||
if !inner.modifiers_state.borrow().is_empty() {
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::ModifiersChanged(ModifiersState::empty()),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
// Window lost focus.
|
||||
event_sink.push_window_event(WindowEvent::Focused(false), window_id);
|
||||
|
||||
// Reset the id.
|
||||
inner.target_window_id = None;
|
||||
}
|
||||
KeyboardEvent::Key {
|
||||
rawkey,
|
||||
keysym,
|
||||
state,
|
||||
utf8,
|
||||
..
|
||||
} => {
|
||||
let window_id = match inner.target_window_id {
|
||||
Some(window_id) => window_id,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let state = match state {
|
||||
KeyState::Pressed => ElementState::Pressed,
|
||||
KeyState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let virtual_keycode = keymap::keysym_to_vkey(keysym);
|
||||
|
||||
event_sink.push_window_event(
|
||||
#[allow(deprecated)]
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
input: KeyboardInput {
|
||||
state,
|
||||
scancode: rawkey,
|
||||
virtual_keycode,
|
||||
modifiers: *inner.modifiers_state.borrow(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
|
||||
// Send ReceivedCharacter event only on ElementState::Pressed.
|
||||
if ElementState::Released == state {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(txt) = utf8 {
|
||||
for ch in txt.chars() {
|
||||
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyboardEvent::Repeat {
|
||||
rawkey,
|
||||
keysym,
|
||||
utf8,
|
||||
..
|
||||
} => {
|
||||
let window_id = match inner.target_window_id {
|
||||
Some(window_id) => window_id,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let virtual_keycode = keymap::keysym_to_vkey(keysym);
|
||||
|
||||
event_sink.push_window_event(
|
||||
#[allow(deprecated)]
|
||||
WindowEvent::KeyboardInput {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
input: KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
scancode: rawkey,
|
||||
virtual_keycode,
|
||||
modifiers: *inner.modifiers_state.borrow(),
|
||||
},
|
||||
is_synthetic: false,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
|
||||
if let Some(txt) = utf8 {
|
||||
for ch in txt.chars() {
|
||||
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyboardEvent::Modifiers { modifiers } => {
|
||||
let modifiers = ModifiersState::from(modifiers);
|
||||
if let Some(window_id) = inner.target_window_id {
|
||||
*inner.modifiers_state.borrow_mut() = modifiers;
|
||||
|
||||
event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id);
|
||||
} else {
|
||||
// Compositor must send modifiers after wl_keyboard::enter, however certain
|
||||
// compositors are still sending it before, so stash such events and send
|
||||
// them on wl_keyboard::enter.
|
||||
inner.pending_modifers_state = Some(modifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
188
src/platform_impl/linux/wayland/seat/keyboard/keymap.rs
Normal file
188
src/platform_impl/linux/wayland/seat/keyboard/keymap.rs
Normal file
|
@ -0,0 +1,188 @@
|
|||
//! Convert Wayland keys to winit keys.
|
||||
|
||||
use crate::event::VirtualKeyCode;
|
||||
|
||||
pub fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
||||
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),
|
||||
// => 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,
|
||||
}
|
||||
}
|
105
src/platform_impl/linux/wayland/seat/keyboard/mod.rs
Normal file
105
src/platform_impl/linux/wayland/seat/keyboard/mod.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
//! Wayland keyboard handling.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::Attached;
|
||||
|
||||
use sctk::reexports::calloop::{LoopHandle, Source};
|
||||
|
||||
use sctk::seat::keyboard::{self, RepeatSource};
|
||||
|
||||
use crate::event::ModifiersState;
|
||||
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,
|
||||
|
||||
/// The source for repeat keys.
|
||||
pub repeat_source: Option<Source<RepeatSource>>,
|
||||
|
||||
/// LoopHandle to drop `RepeatSource`, when dropping the keyboard.
|
||||
pub loop_handle: LoopHandle<WinitState>,
|
||||
}
|
||||
|
||||
impl Keyboard {
|
||||
pub fn new(
|
||||
seat: &Attached<WlSeat>,
|
||||
loop_handle: LoopHandle<WinitState>,
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
) -> Option<Self> {
|
||||
let mut inner = KeyboardInner::new(modifiers_state);
|
||||
let keyboard_data = keyboard::map_keyboard_repeat(
|
||||
loop_handle.clone(),
|
||||
&seat,
|
||||
None,
|
||||
keyboard::RepeatKind::System,
|
||||
move |event, _, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_keyboard(event, &mut inner, winit_state);
|
||||
},
|
||||
);
|
||||
|
||||
let (keyboard, repeat_source) = keyboard_data.ok()?;
|
||||
|
||||
Some(Self {
|
||||
keyboard,
|
||||
loop_handle,
|
||||
repeat_source: Some(repeat_source),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Keyboard {
|
||||
fn drop(&mut self) {
|
||||
if self.keyboard.as_ref().version() >= 3 {
|
||||
self.keyboard.release();
|
||||
}
|
||||
|
||||
if let Some(repeat_source) = self.repeat_source.take() {
|
||||
self.loop_handle.remove(repeat_source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyboardInner {
|
||||
/// Currently focused surface.
|
||||
target_window_id: Option<WindowId>,
|
||||
|
||||
/// A pending state of modifiers.
|
||||
///
|
||||
/// This state is getting set if we've got a modifiers update
|
||||
/// before `Enter` event, which shouldn't happen in general, however
|
||||
/// some compositors are still doing so.
|
||||
pending_modifers_state: Option<ModifiersState>,
|
||||
|
||||
/// Current state of modifiers keys.
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
}
|
||||
|
||||
impl KeyboardInner {
|
||||
fn new(modifiers_state: Rc<RefCell<ModifiersState>>) -> Self {
|
||||
Self {
|
||||
target_window_id: None,
|
||||
pending_modifers_state: None,
|
||||
modifiers_state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<keyboard::ModifiersState> for ModifiersState {
|
||||
fn from(mods: keyboard::ModifiersState) -> ModifiersState {
|
||||
let mut wl_mods = ModifiersState::empty();
|
||||
wl_mods.set(ModifiersState::SHIFT, mods.shift);
|
||||
wl_mods.set(ModifiersState::CTRL, mods.ctrl);
|
||||
wl_mods.set(ModifiersState::ALT, mods.alt);
|
||||
wl_mods.set(ModifiersState::LOGO, mods.logo);
|
||||
wl_mods
|
||||
}
|
||||
}
|
208
src/platform_impl/linux/wayland/seat/mod.rs
Normal file
208
src/platform_impl/linux/wayland/seat/mod.rs
Normal file
|
@ -0,0 +1,208 @@
|
|||
//! Seat handling and managing.
|
||||
|
||||
use std::cell::RefCell;
|
||||
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::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};
|
||||
|
||||
use super::env::WinitEnv;
|
||||
use super::event_loop::WinitState;
|
||||
use crate::event::ModifiersState;
|
||||
|
||||
mod keyboard;
|
||||
pub mod pointer;
|
||||
pub mod text_input;
|
||||
mod touch;
|
||||
|
||||
use keyboard::Keyboard;
|
||||
use pointer::Pointers;
|
||||
use text_input::TextInput;
|
||||
use touch::Touch;
|
||||
|
||||
pub struct SeatManager {
|
||||
/// Listener for seats.
|
||||
_seat_listener: SeatListener,
|
||||
}
|
||||
|
||||
impl SeatManager {
|
||||
pub fn new(
|
||||
env: &Environment<WinitEnv>,
|
||||
loop_handle: LoopHandle<WinitState>,
|
||||
theme_manager: ThemeManager,
|
||||
) -> Self {
|
||||
let relative_pointer_manager = env.get_global::<ZwpRelativePointerManagerV1>();
|
||||
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>();
|
||||
let text_input_manager = env.get_global::<ZwpTextInputManagerV3>();
|
||||
|
||||
let mut inner = SeatManagerInner::new(
|
||||
theme_manager,
|
||||
relative_pointer_manager,
|
||||
pointer_constraints,
|
||||
text_input_manager,
|
||||
loop_handle,
|
||||
);
|
||||
|
||||
// Handle existing seats.
|
||||
for seat in env.get_all_seats() {
|
||||
let seat_data = match sctk::seat::clone_seat_data(&seat) {
|
||||
Some(seat_data) => seat_data,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
inner.process_seat_update(&seat, &seat_data);
|
||||
}
|
||||
|
||||
let seat_listener = env.listen_for_seats(move |seat, seat_data, _| {
|
||||
inner.process_seat_update(&seat, &seat_data);
|
||||
});
|
||||
|
||||
Self {
|
||||
_seat_listener: seat_listener,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inner state of the seat manager.
|
||||
struct SeatManagerInner {
|
||||
/// Currently observed seats.
|
||||
seats: Vec<SeatInfo>,
|
||||
|
||||
/// Loop handle.
|
||||
loop_handle: LoopHandle<WinitState>,
|
||||
|
||||
/// Relative pointer manager.
|
||||
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
|
||||
|
||||
/// Pointer constraints.
|
||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
|
||||
/// Text input manager.
|
||||
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
|
||||
|
||||
/// A theme manager.
|
||||
theme_manager: ThemeManager,
|
||||
}
|
||||
|
||||
impl SeatManagerInner {
|
||||
fn new(
|
||||
theme_manager: ThemeManager,
|
||||
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
|
||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
|
||||
loop_handle: LoopHandle<WinitState>,
|
||||
) -> Self {
|
||||
Self {
|
||||
seats: Vec::new(),
|
||||
loop_handle,
|
||||
relative_pointer_manager,
|
||||
pointer_constraints,
|
||||
text_input_manager,
|
||||
theme_manager,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle seats update from the `SeatListener`.
|
||||
pub fn process_seat_update(&mut self, seat: &Attached<WlSeat>, seat_data: &SeatData) {
|
||||
let detached_seat = seat.detach();
|
||||
|
||||
let position = self.seats.iter().position(|si| si.seat == detached_seat);
|
||||
let index = position.unwrap_or_else(|| {
|
||||
self.seats.push(SeatInfo::new(detached_seat));
|
||||
self.seats.len() - 1
|
||||
});
|
||||
|
||||
let seat_info = &mut self.seats[index];
|
||||
|
||||
// Pointer handling.
|
||||
if seat_data.has_pointer && !seat_data.defunct {
|
||||
if seat_info.pointer.is_none() {
|
||||
seat_info.pointer = Some(Pointers::new(
|
||||
&seat,
|
||||
&self.theme_manager,
|
||||
&self.relative_pointer_manager,
|
||||
&self.pointer_constraints,
|
||||
seat_info.modifiers_state.clone(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
seat_info.pointer = None;
|
||||
}
|
||||
|
||||
// Handle keyboard.
|
||||
if seat_data.has_keyboard && !seat_data.defunct {
|
||||
if seat_info.keyboard.is_none() {
|
||||
seat_info.keyboard = Keyboard::new(
|
||||
&seat,
|
||||
self.loop_handle.clone(),
|
||||
seat_info.modifiers_state.clone(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
seat_info.keyboard = None;
|
||||
}
|
||||
|
||||
// Handle touch.
|
||||
if seat_data.has_touch && !seat_data.defunct {
|
||||
if seat_info.touch.is_none() {
|
||||
seat_info.touch = Some(Touch::new(&seat));
|
||||
}
|
||||
} else {
|
||||
seat_info.touch = None;
|
||||
}
|
||||
|
||||
// Handle text input.
|
||||
if let Some(text_input_manager) = self.text_input_manager.as_ref() {
|
||||
if seat_data.defunct {
|
||||
seat_info.text_input = None;
|
||||
} else if seat_info.text_input.is_none() {
|
||||
seat_info.text_input = Some(TextInput::new(&seat, &text_input_manager));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resources associtated with a given seat.
|
||||
struct SeatInfo {
|
||||
/// Seat to which this `SeatInfo` belongs.
|
||||
seat: WlSeat,
|
||||
|
||||
/// A keyboard handle with its repeat rate handling.
|
||||
keyboard: Option<Keyboard>,
|
||||
|
||||
/// All pointers we're using on a seat.
|
||||
pointer: Option<Pointers>,
|
||||
|
||||
/// Touch handling.
|
||||
touch: Option<Touch>,
|
||||
|
||||
/// Text input handling aka IME.
|
||||
text_input: Option<TextInput>,
|
||||
|
||||
/// The current state of modifiers observed in keyboard handler.
|
||||
///
|
||||
/// We keep modifiers state on a seat, since it's being used by pointer events as well.
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
}
|
||||
|
||||
impl SeatInfo {
|
||||
pub fn new(seat: WlSeat) -> Self {
|
||||
Self {
|
||||
seat,
|
||||
keyboard: None,
|
||||
pointer: None,
|
||||
touch: None,
|
||||
text_input: None,
|
||||
modifiers_state: Rc::new(RefCell::new(ModifiersState::default())),
|
||||
}
|
||||
}
|
||||
}
|
74
src/platform_impl/linux/wayland/seat/pointer/data.rs
Normal file
74
src/platform_impl/linux/wayland/seat/pointer/data.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
//! Data which is used in pointer callbacks.
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Attached;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1};
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
||||
|
||||
use crate::event::{ModifiersState, TouchPhase};
|
||||
|
||||
/// A data being used by pointer handlers.
|
||||
pub(super) struct PointerData {
|
||||
/// Winit's surface the pointer is currently over.
|
||||
pub surface: Option<WlSurface>,
|
||||
|
||||
/// Current modifiers state.
|
||||
///
|
||||
/// This refers a state of modifiers from `WlKeyboard` on
|
||||
/// the given seat.
|
||||
pub modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
|
||||
/// Pointer constraints.
|
||||
pub pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
|
||||
pub confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
|
||||
/// A latest event serial.
|
||||
pub latest_serial: Rc<Cell<u32>>,
|
||||
|
||||
/// The currently accumulated axis data on a pointer.
|
||||
pub axis_data: AxisData,
|
||||
}
|
||||
|
||||
impl PointerData {
|
||||
pub fn new(
|
||||
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
surface: None,
|
||||
latest_serial: Rc::new(Cell::new(0)),
|
||||
confined_pointer,
|
||||
modifiers_state,
|
||||
pointer_constraints,
|
||||
axis_data: AxisData::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Axis data.
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct AxisData {
|
||||
/// Current state of the axis.
|
||||
pub axis_state: TouchPhase,
|
||||
|
||||
/// A buffer for `PixelDelta` event.
|
||||
pub axis_buffer: Option<(f32, f32)>,
|
||||
|
||||
/// A buffer for `LineDelta` event.
|
||||
pub axis_discrete_buffer: Option<(f32, f32)>,
|
||||
}
|
||||
|
||||
impl AxisData {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
axis_state: TouchPhase::Ended,
|
||||
axis_buffer: None,
|
||||
axis_discrete_buffer: None,
|
||||
}
|
||||
}
|
||||
}
|
297
src/platform_impl/linux/wayland/seat/pointer/handlers.rs
Normal file
297
src/platform_impl/linux/wayland/seat/pointer/handlers.rs
Normal file
|
@ -0,0 +1,297 @@
|
|||
//! Handlers for the pointers we're using.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use sctk::reexports::client::protocol::wl_pointer::{self, Event as PointerEvent};
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::Event as RelativePointerEvent;
|
||||
|
||||
use sctk::seat::pointer::ThemedPointer;
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{
|
||||
DeviceEvent, ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent,
|
||||
};
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::{self, DeviceId};
|
||||
|
||||
use super::{PointerData, WinitPointer};
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_pointer(
|
||||
pointer: ThemedPointer,
|
||||
event: PointerEvent,
|
||||
pointer_data: &Rc<RefCell<PointerData>>,
|
||||
winit_state: &mut WinitState,
|
||||
) {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
let mut pointer_data = pointer_data.borrow_mut();
|
||||
match event {
|
||||
PointerEvent::Enter {
|
||||
surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
serial,
|
||||
..
|
||||
} => {
|
||||
pointer_data.latest_serial.replace(serial);
|
||||
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
if !winit_state.window_map.contains_key(&window_id) {
|
||||
return;
|
||||
}
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
|
||||
pointer_data.surface = Some(surface);
|
||||
|
||||
// Notify window that pointer entered the surface.
|
||||
let winit_pointer = WinitPointer {
|
||||
pointer,
|
||||
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
|
||||
pointer_constraints: pointer_data.pointer_constraints.clone(),
|
||||
latest_serial: pointer_data.latest_serial.clone(),
|
||||
};
|
||||
window_handle.pointer_entered(winit_pointer);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::CursorEntered {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
|
||||
let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
position,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEvent::Leave { surface, serial } => {
|
||||
pointer_data.surface = None;
|
||||
pointer_data.latest_serial.replace(serial);
|
||||
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Notify a window that pointer is no longer observing it.
|
||||
let winit_pointer = WinitPointer {
|
||||
pointer,
|
||||
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
|
||||
pointer_constraints: pointer_data.pointer_constraints.clone(),
|
||||
latest_serial: pointer_data.latest_serial.clone(),
|
||||
};
|
||||
window_handle.pointer_left(winit_pointer);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::CursorLeft {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEvent::Motion {
|
||||
surface_x,
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
let surface = match pointer_data.surface.as_ref() {
|
||||
Some(surface) => surface,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let window_id = wayland::make_wid(surface);
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
|
||||
let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::CursorMoved {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
position,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEvent::Button {
|
||||
button,
|
||||
state,
|
||||
serial,
|
||||
..
|
||||
} => {
|
||||
pointer_data.latest_serial.replace(serial);
|
||||
let window_id = match pointer_data.surface.as_ref().map(wayland::make_wid) {
|
||||
Some(window_id) => window_id,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let state = match state {
|
||||
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
|
||||
wl_pointer::ButtonState::Released => ElementState::Released,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let button = match button {
|
||||
0x110 => MouseButton::Left,
|
||||
0x111 => MouseButton::Right,
|
||||
0x112 => MouseButton::Middle,
|
||||
// TODO - figure out the translation.
|
||||
_ => return,
|
||||
};
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::MouseInput {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
state,
|
||||
button,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
PointerEvent::Axis { axis, value, .. } => {
|
||||
let surface = match pointer_data.surface.as_ref() {
|
||||
Some(surface) => surface,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
if pointer.as_ref().version() < 5 {
|
||||
let (mut x, mut y) = (0.0, 0.0);
|
||||
|
||||
// Old seat compatibility.
|
||||
match axis {
|
||||
// Wayland vertical sign convention is the inverse of winit.
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
|
||||
let delta = LogicalPosition::new(x as f64, y as f64).to_physical(scale_factor);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
delta: MouseScrollDelta::PixelDelta(delta),
|
||||
phase: TouchPhase::Moved,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
} else {
|
||||
let (mut x, mut y) = pointer_data.axis_data.axis_buffer.unwrap_or((0.0, 0.0));
|
||||
match axis {
|
||||
// Wayland vertical sign convention is the inverse of winit.
|
||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += value as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
pointer_data.axis_data.axis_buffer = Some((x, y));
|
||||
|
||||
pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
}
|
||||
}
|
||||
PointerEvent::AxisDiscrete { axis, discrete } => {
|
||||
let (mut x, mut y) = pointer_data
|
||||
.axis_data
|
||||
.axis_discrete_buffer
|
||||
.unwrap_or((0., 0.));
|
||||
|
||||
match axis {
|
||||
// Wayland vertical sign convention is the inverse of winit.
|
||||
wl_pointer::Axis::VerticalScroll => y -= discrete as f32,
|
||||
wl_pointer::Axis::HorizontalScroll => x += discrete as f32,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
pointer_data.axis_data.axis_discrete_buffer = Some((x, y));
|
||||
|
||||
pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state {
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
}
|
||||
PointerEvent::AxisSource { .. } => (),
|
||||
PointerEvent::AxisStop { .. } => {
|
||||
pointer_data.axis_data.axis_state = TouchPhase::Ended;
|
||||
}
|
||||
PointerEvent::Frame => {
|
||||
let axis_buffer = pointer_data.axis_data.axis_buffer.take();
|
||||
let axis_discrete_buffer = pointer_data.axis_data.axis_discrete_buffer.take();
|
||||
|
||||
let surface = match pointer_data.surface.as_ref() {
|
||||
Some(surface) => surface,
|
||||
None => return,
|
||||
};
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
let window_event = if let Some((x, y)) = axis_discrete_buffer {
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
delta: MouseScrollDelta::LineDelta(x, y),
|
||||
phase: pointer_data.axis_data.axis_state,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
}
|
||||
} else if let Some((x, y)) = axis_buffer {
|
||||
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
|
||||
let delta = LogicalPosition::new(x, y).to_physical(scale_factor);
|
||||
|
||||
WindowEvent::MouseWheel {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
delta: MouseScrollDelta::PixelDelta(delta),
|
||||
phase: pointer_data.axis_data.axis_state,
|
||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
event_sink.push_window_event(window_event, window_id);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_relative_pointer(event: RelativePointerEvent, winit_state: &mut WinitState) {
|
||||
if let RelativePointerEvent::RelativeMotion { dx, dy, .. } = event {
|
||||
winit_state
|
||||
.event_sink
|
||||
.push_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId)
|
||||
}
|
||||
}
|
242
src/platform_impl/linux/wayland/seat/pointer/mod.rs
Normal file
242
src/platform_impl/linux/wayland/seat/pointer/mod.rs
Normal file
|
@ -0,0 +1,242 @@
|
|||
//! All pointer related handling.
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Attached;
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
|
||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1, Lifetime};
|
||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
||||
|
||||
use sctk::seat::pointer::{ThemeManager, ThemedPointer};
|
||||
|
||||
use crate::event::ModifiersState;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
mod data;
|
||||
mod handlers;
|
||||
|
||||
use data::PointerData;
|
||||
|
||||
/// A proxy to Wayland pointer, which serves requests from a `WindowHandle`.
|
||||
pub struct WinitPointer {
|
||||
pointer: ThemedPointer,
|
||||
|
||||
/// Create confined pointers.
|
||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
|
||||
/// Cursor to handle confine requests.
|
||||
confined_pointer: Weak<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
|
||||
/// Latest observed serial in pointer events.
|
||||
latest_serial: Rc<Cell<u32>>,
|
||||
}
|
||||
|
||||
impl PartialEq for WinitPointer {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
*self.pointer == *other.pointer
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for WinitPointer {}
|
||||
|
||||
impl WinitPointer {
|
||||
/// Set the cursor icon.
|
||||
///
|
||||
/// Providing `None` will hide the cursor.
|
||||
pub fn set_cursor(&self, cursor_icon: Option<CursorIcon>) {
|
||||
let cursor_icon = match cursor_icon {
|
||||
Some(cursor_icon) => cursor_icon,
|
||||
None => {
|
||||
// Hide the cursor.
|
||||
(*self.pointer).set_cursor(self.latest_serial.get(), None, 0, 0);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let cursors: &[&str] = match cursor_icon {
|
||||
CursorIcon::Alias => &["link"],
|
||||
CursorIcon::Arrow => &["arrow"],
|
||||
CursorIcon::Cell => &["plus"],
|
||||
CursorIcon::Copy => &["copy"],
|
||||
CursorIcon::Crosshair => &["crosshair"],
|
||||
CursorIcon::Default => &["left_ptr"],
|
||||
CursorIcon::Hand => &["hand"],
|
||||
CursorIcon::Help => &["question_arrow"],
|
||||
CursorIcon::Move => &["move"],
|
||||
CursorIcon::Grab => &["openhand", "grab"],
|
||||
CursorIcon::Grabbing => &["closedhand", "grabbing"],
|
||||
CursorIcon::Progress => &["progress"],
|
||||
CursorIcon::AllScroll => &["all-scroll"],
|
||||
CursorIcon::ContextMenu => &["context-menu"],
|
||||
|
||||
CursorIcon::NoDrop => &["no-drop", "circle"],
|
||||
CursorIcon::NotAllowed => &["crossed_circle"],
|
||||
|
||||
// Resize cursors
|
||||
CursorIcon::EResize => &["right_side"],
|
||||
CursorIcon::NResize => &["top_side"],
|
||||
CursorIcon::NeResize => &["top_right_corner"],
|
||||
CursorIcon::NwResize => &["top_left_corner"],
|
||||
CursorIcon::SResize => &["bottom_side"],
|
||||
CursorIcon::SeResize => &["bottom_right_corner"],
|
||||
CursorIcon::SwResize => &["bottom_left_corner"],
|
||||
CursorIcon::WResize => &["left_side"],
|
||||
CursorIcon::EwResize => &["h_double_arrow"],
|
||||
CursorIcon::NsResize => &["v_double_arrow"],
|
||||
CursorIcon::NwseResize => &["bd_double_arrow", "size_bdiag"],
|
||||
CursorIcon::NeswResize => &["fd_double_arrow", "size_fdiag"],
|
||||
CursorIcon::ColResize => &["split_h", "h_double_arrow"],
|
||||
CursorIcon::RowResize => &["split_v", "v_double_arrow"],
|
||||
CursorIcon::Text => &["text", "xterm"],
|
||||
CursorIcon::VerticalText => &["vertical-text"],
|
||||
|
||||
CursorIcon::Wait => &["watch"],
|
||||
|
||||
CursorIcon::ZoomIn => &["zoom-in"],
|
||||
CursorIcon::ZoomOut => &["zoom-out"],
|
||||
};
|
||||
|
||||
let serial = Some(self.latest_serial.get());
|
||||
for cursor in cursors {
|
||||
if self.pointer.set_cursor(cursor, serial).is_ok() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Confine the pointer to a surface.
|
||||
pub fn confine(&self, surface: &WlSurface) {
|
||||
let pointer_constraints = match &self.pointer_constraints {
|
||||
Some(pointer_constraints) => pointer_constraints,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let confined_pointer = match self.confined_pointer.upgrade() {
|
||||
Some(confined_pointer) => confined_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
*confined_pointer.borrow_mut() = Some(init_confined_pointer(
|
||||
&pointer_constraints,
|
||||
&surface,
|
||||
&*self.pointer,
|
||||
));
|
||||
}
|
||||
|
||||
/// Tries to unconfine the pointer if the current pointer is confined.
|
||||
pub fn unconfine(&self) {
|
||||
let confined_pointer = match self.confined_pointer.upgrade() {
|
||||
Some(confined_pointer) => confined_pointer,
|
||||
// A pointer is gone.
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut confined_pointer = confined_pointer.borrow_mut();
|
||||
|
||||
if let Some(confined_pointer) = confined_pointer.take() {
|
||||
confined_pointer.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A pointer wrapper for easy releasing and managing pointers.
|
||||
pub(super) struct Pointers {
|
||||
/// A pointer itself.
|
||||
pointer: ThemedPointer,
|
||||
|
||||
/// A relative pointer handler.
|
||||
relative_pointer: Option<ZwpRelativePointerV1>,
|
||||
|
||||
/// Confined pointer.
|
||||
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
||||
}
|
||||
|
||||
impl Pointers {
|
||||
pub(super) fn new(
|
||||
seat: &Attached<WlSeat>,
|
||||
theme_manager: &ThemeManager,
|
||||
relative_pointer_manager: &Option<Attached<ZwpRelativePointerManagerV1>>,
|
||||
pointer_constraints: &Option<Attached<ZwpPointerConstraintsV1>>,
|
||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
||||
) -> Self {
|
||||
let confined_pointer = Rc::new(RefCell::new(None));
|
||||
let pointer_data = Rc::new(RefCell::new(PointerData::new(
|
||||
confined_pointer.clone(),
|
||||
pointer_constraints.clone(),
|
||||
modifiers_state,
|
||||
)));
|
||||
let pointer = theme_manager.theme_pointer_with_impl(
|
||||
seat,
|
||||
move |event, pointer, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_pointer(pointer, event, &pointer_data, winit_state);
|
||||
},
|
||||
);
|
||||
|
||||
// Setup relative_pointer if it's available.
|
||||
let relative_pointer = match relative_pointer_manager.as_ref() {
|
||||
Some(relative_pointer_manager) => {
|
||||
Some(init_relative_pointer(&relative_pointer_manager, &*pointer))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
Self {
|
||||
pointer,
|
||||
relative_pointer,
|
||||
confined_pointer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Pointers {
|
||||
fn drop(&mut self) {
|
||||
// Drop relative pointer.
|
||||
if let Some(relative_pointer) = self.relative_pointer.take() {
|
||||
relative_pointer.destroy();
|
||||
}
|
||||
|
||||
// Drop confined pointer.
|
||||
if let Some(confined_pointer) = self.confined_pointer.borrow_mut().take() {
|
||||
confined_pointer.destroy();
|
||||
}
|
||||
|
||||
// Drop the pointer itself in case it's possible.
|
||||
if self.pointer.as_ref().version() >= 3 {
|
||||
self.pointer.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn init_relative_pointer(
|
||||
relative_pointer_manager: &ZwpRelativePointerManagerV1,
|
||||
pointer: &WlPointer,
|
||||
) -> ZwpRelativePointerV1 {
|
||||
let relative_pointer = relative_pointer_manager.get_relative_pointer(&*pointer);
|
||||
relative_pointer.quick_assign(move |_, event, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_relative_pointer(event, winit_state);
|
||||
});
|
||||
|
||||
relative_pointer.detach()
|
||||
}
|
||||
|
||||
pub(super) fn init_confined_pointer(
|
||||
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
|
||||
surface: &WlSurface,
|
||||
pointer: &WlPointer,
|
||||
) -> ZwpConfinedPointerV1 {
|
||||
let confined_pointer =
|
||||
pointer_constraints.confine_pointer(surface, pointer, None, Lifetime::Persistent.to_raw());
|
||||
|
||||
confined_pointer.quick_assign(move |_, _, _| {});
|
||||
|
||||
confined_pointer.detach()
|
||||
}
|
78
src/platform_impl/linux/wayland/seat/text_input/handlers.rs
Normal file
78
src/platform_impl/linux/wayland/seat/text_input/handlers.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
//! Handling of IME events.
|
||||
|
||||
use sctk::reexports::client::Main;
|
||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::{
|
||||
Event as TextInputEvent, ZwpTextInputV3,
|
||||
};
|
||||
|
||||
use crate::event::WindowEvent;
|
||||
use crate::platform_impl::wayland;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
|
||||
use super::{TextInputHandler, TextInputInner};
|
||||
|
||||
#[inline]
|
||||
pub(super) fn handle_text_input(
|
||||
text_input: Main<ZwpTextInputV3>,
|
||||
inner: &mut TextInputInner,
|
||||
event: TextInputEvent,
|
||||
winit_state: &mut WinitState,
|
||||
) {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
match event {
|
||||
TextInputEvent::Enter { surface } => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
inner.target_window_id = Some(window_id);
|
||||
|
||||
// Enable text input on that surface.
|
||||
text_input.enable();
|
||||
text_input.commit();
|
||||
|
||||
// Notify a window we're currently over about text input handler.
|
||||
let text_input_handler = TextInputHandler {
|
||||
text_input: text_input.detach(),
|
||||
};
|
||||
window_handle.text_input_entered(text_input_handler);
|
||||
}
|
||||
TextInputEvent::Leave { surface } => {
|
||||
// Always issue a disable.
|
||||
text_input.disable();
|
||||
text_input.commit();
|
||||
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
||||
Some(window_handle) => window_handle,
|
||||
None => return,
|
||||
};
|
||||
|
||||
inner.target_window_id = None;
|
||||
|
||||
// Remove text input handler from the window we're leaving.
|
||||
let text_input_handler = TextInputHandler {
|
||||
text_input: text_input.detach(),
|
||||
};
|
||||
window_handle.text_input_left(text_input_handler);
|
||||
}
|
||||
TextInputEvent::CommitString { text } => {
|
||||
// Update currenly commited string.
|
||||
inner.commit_string = text;
|
||||
}
|
||||
TextInputEvent::Done { .. } => {
|
||||
let (window_id, text) = match (inner.target_window_id, inner.commit_string.take()) {
|
||||
(Some(window_id), Some(text)) => (window_id, text),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
for ch in text.chars() {
|
||||
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
66
src/platform_impl/linux/wayland/seat/text_input/mod.rs
Normal file
66
src/platform_impl/linux/wayland/seat/text_input/mod.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::Attached;
|
||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
|
||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::ZwpTextInputV3;
|
||||
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::WindowId;
|
||||
|
||||
mod handlers;
|
||||
|
||||
/// A handler for text input that we're advertising for `WindowHandle`.
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct TextInputHandler {
|
||||
text_input: ZwpTextInputV3,
|
||||
}
|
||||
|
||||
impl TextInputHandler {
|
||||
#[inline]
|
||||
pub fn set_ime_position(&self, x: i32, y: i32) {
|
||||
self.text_input.set_cursor_rectangle(x, y, 0, 0);
|
||||
self.text_input.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around text input to automatically destroy the object on `Drop`.
|
||||
pub struct TextInput {
|
||||
text_input: Attached<ZwpTextInputV3>,
|
||||
}
|
||||
|
||||
impl TextInput {
|
||||
pub fn new(seat: &Attached<WlSeat>, text_input_manager: &ZwpTextInputManagerV3) -> Self {
|
||||
let text_input = text_input_manager.get_text_input(seat);
|
||||
let mut text_input_inner = TextInputInner::new();
|
||||
text_input.quick_assign(move |text_input, event, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_text_input(text_input, &mut text_input_inner, event, winit_state);
|
||||
});
|
||||
|
||||
let text_input: Attached<ZwpTextInputV3> = text_input.into();
|
||||
|
||||
Self { text_input }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TextInput {
|
||||
fn drop(&mut self) {
|
||||
self.text_input.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
struct TextInputInner {
|
||||
/// Currently focused surface.
|
||||
target_window_id: Option<WindowId>,
|
||||
|
||||
/// Pending string to commit.
|
||||
commit_string: Option<String>,
|
||||
}
|
||||
|
||||
impl TextInputInner {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
target_window_id: None,
|
||||
commit_string: None,
|
||||
}
|
||||
}
|
||||
}
|
122
src/platform_impl/linux/wayland/seat/touch/handlers.rs
Normal file
122
src/platform_impl/linux/wayland/seat/touch/handlers.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
//! Various handlers for touch events.
|
||||
|
||||
use sctk::reexports::client::protocol::wl_touch::Event as TouchEvent;
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{TouchPhase, WindowEvent};
|
||||
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::{self, DeviceId};
|
||||
|
||||
use super::{TouchInner, TouchPoint};
|
||||
|
||||
/// Handle WlTouch events.
|
||||
#[inline]
|
||||
pub(super) fn handle_touch(
|
||||
event: TouchEvent,
|
||||
inner: &mut TouchInner,
|
||||
winit_state: &mut WinitState,
|
||||
) {
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
|
||||
match event {
|
||||
TouchEvent::Down {
|
||||
surface, id, x, y, ..
|
||||
} => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
if !winit_state.window_map.contains_key(&window_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
|
||||
let position = LogicalPosition::new(x, y);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Started,
|
||||
location: position.to_physical(scale_factor),
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
|
||||
inner
|
||||
.touch_points
|
||||
.push(TouchPoint::new(surface, position, id));
|
||||
}
|
||||
TouchEvent::Up { id, .. } => {
|
||||
let touch_point = match inner.touch_points.iter().find(|p| p.id == id) {
|
||||
Some(touch_point) => touch_point,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
|
||||
let location = touch_point.position.to_physical(scale_factor);
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Ended,
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
TouchEvent::Motion { id, x, y, .. } => {
|
||||
let touch_point = match inner.touch_points.iter_mut().find(|p| p.id == id) {
|
||||
Some(touch_point) => touch_point,
|
||||
None => return,
|
||||
};
|
||||
|
||||
touch_point.position = LogicalPosition::new(x, y);
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
|
||||
let location = touch_point.position.to_physical(scale_factor);
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Moved,
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
TouchEvent::Frame => (),
|
||||
TouchEvent::Cancel => {
|
||||
for touch_point in inner.touch_points.drain(..) {
|
||||
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
|
||||
let location = touch_point.position.to_physical(scale_factor);
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
||||
DeviceId,
|
||||
)),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: touch_point.id as u64,
|
||||
}),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
78
src/platform_impl/linux/wayland/seat/touch/mod.rs
Normal file
78
src/platform_impl/linux/wayland/seat/touch/mod.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
//! Touch handling.
|
||||
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::protocol::wl_touch::WlTouch;
|
||||
use sctk::reexports::client::Attached;
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
|
||||
mod handlers;
|
||||
|
||||
/// Wrapper around touch to handle release.
|
||||
pub struct Touch {
|
||||
/// Proxy to touch.
|
||||
touch: WlTouch,
|
||||
}
|
||||
|
||||
impl Touch {
|
||||
pub fn new(seat: &Attached<WlSeat>) -> Self {
|
||||
let touch = seat.get_touch();
|
||||
let mut inner = TouchInner::new();
|
||||
|
||||
touch.quick_assign(move |_, event, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
handlers::handle_touch(event, &mut inner, winit_state);
|
||||
});
|
||||
|
||||
Self {
|
||||
touch: touch.detach(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Touch {
|
||||
fn drop(&mut self) {
|
||||
if self.touch.as_ref().version() >= 3 {
|
||||
self.touch.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The data used by touch handlers.
|
||||
pub(super) struct TouchInner {
|
||||
/// Current touch points.
|
||||
touch_points: Vec<TouchPoint>,
|
||||
}
|
||||
|
||||
impl TouchInner {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
touch_points: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Location of touch press.
|
||||
pub(super) struct TouchPoint {
|
||||
/// A surface where the touch point is located.
|
||||
surface: WlSurface,
|
||||
|
||||
/// Location of the touch point.
|
||||
position: LogicalPosition<f64>,
|
||||
|
||||
/// Id.
|
||||
id: i32,
|
||||
}
|
||||
|
||||
impl TouchPoint {
|
||||
pub fn new(surface: WlSurface, position: LogicalPosition<f64>, id: i32) -> Self {
|
||||
Self {
|
||||
surface,
|
||||
position,
|
||||
id,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::event::{TouchPhase, WindowEvent};
|
||||
|
||||
use super::{event_loop::EventsSink, make_wid, window::WindowStore, DeviceId};
|
||||
|
||||
use smithay_client_toolkit::surface;
|
||||
|
||||
use smithay_client_toolkit::reexports::client::protocol::{
|
||||
wl_seat,
|
||||
wl_surface::WlSurface,
|
||||
wl_touch::{Event as TouchEvent, WlTouch},
|
||||
};
|
||||
|
||||
// location is in logical coordinates.
|
||||
struct TouchPoint {
|
||||
surface: WlSurface,
|
||||
position: LogicalPosition<f64>,
|
||||
id: i32,
|
||||
}
|
||||
|
||||
pub(crate) fn implement_touch(
|
||||
seat: &wl_seat::WlSeat,
|
||||
sink: EventsSink,
|
||||
store: Arc<Mutex<WindowStore>>,
|
||||
) -> WlTouch {
|
||||
let mut pending_ids = Vec::new();
|
||||
seat.get_touch(|touch| {
|
||||
touch.implement_closure(
|
||||
move |evt, _| {
|
||||
let store = store.lock().unwrap();
|
||||
match evt {
|
||||
TouchEvent::Down {
|
||||
surface, id, x, y, ..
|
||||
} => {
|
||||
let wid = store.find_wid(&surface);
|
||||
if let Some(wid) = wid {
|
||||
let scale_factor = surface::get_dpi_factor(&surface) as f64;
|
||||
let position = LogicalPosition::new(x, y);
|
||||
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Started,
|
||||
location: position.to_physical(scale_factor),
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
wid,
|
||||
);
|
||||
pending_ids.push(TouchPoint {
|
||||
surface,
|
||||
position,
|
||||
id,
|
||||
});
|
||||
}
|
||||
}
|
||||
TouchEvent::Up { id, .. } => {
|
||||
let idx = pending_ids.iter().position(|p| p.id == id);
|
||||
if let Some(idx) = idx {
|
||||
let pt = pending_ids.remove(idx);
|
||||
|
||||
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
|
||||
let location = pt.position.to_physical(scale_factor);
|
||||
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Ended,
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
make_wid(&pt.surface),
|
||||
);
|
||||
}
|
||||
}
|
||||
TouchEvent::Motion { id, x, y, .. } => {
|
||||
let pt = pending_ids.iter_mut().find(|p| p.id == id);
|
||||
if let Some(pt) = pt {
|
||||
pt.position = LogicalPosition::new(x, y);
|
||||
|
||||
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
|
||||
let location = pt.position.to_physical(scale_factor);
|
||||
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Moved,
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: id as u64,
|
||||
}),
|
||||
make_wid(&pt.surface),
|
||||
);
|
||||
}
|
||||
}
|
||||
TouchEvent::Frame => (),
|
||||
TouchEvent::Cancel => {
|
||||
for pt in pending_ids.drain(..) {
|
||||
let scale_factor = surface::get_dpi_factor(&pt.surface) as f64;
|
||||
let location = pt.position.to_physical(scale_factor);
|
||||
|
||||
sink.send_window_event(
|
||||
WindowEvent::Touch(crate::event::Touch {
|
||||
device_id: crate::event::DeviceId(
|
||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
||||
),
|
||||
phase: TouchPhase::Cancelled,
|
||||
location,
|
||||
force: None, // TODO
|
||||
id: pt.id as u64,
|
||||
}),
|
||||
make_wid(&pt.surface),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
(),
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
|
@ -1,569 +0,0 @@
|
|||
use raw_window_handle::unix::WaylandHandle;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
mem::replace,
|
||||
sync::{Arc, Mutex, Weak},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
monitor::MonitorHandle as RootMonitorHandle,
|
||||
platform_impl::{
|
||||
platform::wayland::event_loop::available_monitors, MonitorHandle as PlatformMonitorHandle,
|
||||
PlatformSpecificWindowBuilderAttributes as PlAttributes,
|
||||
},
|
||||
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||
};
|
||||
|
||||
use smithay_client_toolkit::{
|
||||
output::OutputMgr,
|
||||
reexports::client::{
|
||||
protocol::{wl_seat, wl_surface},
|
||||
Display,
|
||||
},
|
||||
surface::{get_dpi_factor, get_outputs},
|
||||
window::{ConceptFrame, Event as WEvent, State as WState, Theme, Window as SWindow},
|
||||
};
|
||||
|
||||
use super::{event_loop::CursorManager, make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
|
||||
|
||||
pub struct Window {
|
||||
surface: wl_surface::WlSurface,
|
||||
frame: Arc<Mutex<SWindow<ConceptFrame>>>,
|
||||
cursor_manager: Arc<Mutex<CursorManager>>,
|
||||
outputs: OutputMgr, // Access to info for all monitors
|
||||
size: Arc<Mutex<(u32, u32)>>,
|
||||
kill_switch: (Arc<Mutex<bool>>, Arc<Mutex<bool>>),
|
||||
display: Arc<Display>,
|
||||
need_frame_refresh: Arc<Mutex<bool>>,
|
||||
need_refresh: Arc<Mutex<bool>>,
|
||||
fullscreen: Arc<Mutex<bool>>,
|
||||
cursor_grab_changed: Arc<Mutex<Option<bool>>>, // Update grab state
|
||||
decorated: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum DecorationsAction {
|
||||
Hide,
|
||||
Show,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new<T>(
|
||||
evlp: &EventLoopWindowTarget<T>,
|
||||
attributes: WindowAttributes,
|
||||
pl_attribs: PlAttributes,
|
||||
) -> Result<Window, RootOsError> {
|
||||
// Create the surface first to get initial DPI
|
||||
let window_store = evlp.store.clone();
|
||||
let cursor_manager = evlp.cursor_manager.clone();
|
||||
let surface = evlp.env.create_surface(move |scale_factor, surface| {
|
||||
window_store
|
||||
.lock()
|
||||
.unwrap()
|
||||
.scale_factor_change(&surface, scale_factor);
|
||||
surface.set_buffer_scale(scale_factor);
|
||||
});
|
||||
|
||||
// Always 1.
|
||||
let scale_factor = get_dpi_factor(&surface);
|
||||
|
||||
let (width, height) = attributes
|
||||
.inner_size
|
||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into())
|
||||
.unwrap_or((800, 600));
|
||||
|
||||
// Create the window
|
||||
let size = Arc::new(Mutex::new((width, height)));
|
||||
let fullscreen = Arc::new(Mutex::new(false));
|
||||
|
||||
let window_store = evlp.store.clone();
|
||||
|
||||
let decorated = Arc::new(Mutex::new(attributes.decorations));
|
||||
let pending_decorations_action = Arc::new(Mutex::new(None));
|
||||
|
||||
let my_surface = surface.clone();
|
||||
let mut frame = SWindow::<ConceptFrame>::init_from_env(
|
||||
&evlp.env,
|
||||
surface.clone(),
|
||||
(width, height),
|
||||
move |event| match event {
|
||||
WEvent::Configure { new_size, states } => {
|
||||
let mut store = window_store.lock().unwrap();
|
||||
let is_fullscreen = states.contains(&WState::Fullscreen);
|
||||
|
||||
for window in &mut store.windows {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
window.new_size = new_size;
|
||||
*(window.need_refresh.lock().unwrap()) = true;
|
||||
{
|
||||
// Get whether we're in fullscreen
|
||||
let mut fullscreen = window.fullscreen.lock().unwrap();
|
||||
// Fullscreen state was changed, so update decorations
|
||||
if *fullscreen != is_fullscreen {
|
||||
let decorated = { *window.decorated.lock().unwrap() };
|
||||
if decorated {
|
||||
*window.pending_decorations_action.lock().unwrap() =
|
||||
if is_fullscreen {
|
||||
Some(DecorationsAction::Hide)
|
||||
} else {
|
||||
Some(DecorationsAction::Show)
|
||||
};
|
||||
}
|
||||
}
|
||||
*fullscreen = is_fullscreen;
|
||||
}
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
WEvent::Refresh => {
|
||||
let store = window_store.lock().unwrap();
|
||||
for window in &store.windows {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
WEvent::Close => {
|
||||
let mut store = window_store.lock().unwrap();
|
||||
for window in &mut store.windows {
|
||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
||||
window.closed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(app_id) = pl_attribs.app_id {
|
||||
frame.set_app_id(app_id);
|
||||
}
|
||||
|
||||
for &(_, ref seat) in evlp.seats.lock().unwrap().iter() {
|
||||
frame.new_seat(seat);
|
||||
}
|
||||
|
||||
// Check for fullscreen requirements
|
||||
match attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
panic!("Wayland doesn't support exclusive fullscreen")
|
||||
}
|
||||
Some(Fullscreen::Borderless(monitor)) => {
|
||||
let monitor =
|
||||
monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor {
|
||||
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
|
||||
#[cfg(feature = "x11")]
|
||||
PlatformMonitorHandle::X(_) => None,
|
||||
});
|
||||
|
||||
frame.set_fullscreen(monitor.as_ref())
|
||||
}
|
||||
None => {
|
||||
if attributes.maximized {
|
||||
frame.set_maximized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frame.set_resizable(attributes.resizable);
|
||||
|
||||
// set decorations
|
||||
frame.set_decorate(attributes.decorations);
|
||||
|
||||
// set title
|
||||
frame.set_title(attributes.title);
|
||||
|
||||
// min-max dimensions
|
||||
frame.set_min_size(
|
||||
attributes
|
||||
.min_inner_size
|
||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into()),
|
||||
);
|
||||
frame.set_max_size(
|
||||
attributes
|
||||
.max_inner_size
|
||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into()),
|
||||
);
|
||||
|
||||
let kill_switch = Arc::new(Mutex::new(false));
|
||||
let need_frame_refresh = Arc::new(Mutex::new(true));
|
||||
let frame = Arc::new(Mutex::new(frame));
|
||||
let need_refresh = Arc::new(Mutex::new(true));
|
||||
let cursor_grab_changed = Arc::new(Mutex::new(None));
|
||||
|
||||
evlp.store.lock().unwrap().windows.push(InternalWindow {
|
||||
closed: false,
|
||||
new_size: None,
|
||||
size: size.clone(),
|
||||
need_refresh: need_refresh.clone(),
|
||||
fullscreen: fullscreen.clone(),
|
||||
cursor_grab_changed: cursor_grab_changed.clone(),
|
||||
need_frame_refresh: need_frame_refresh.clone(),
|
||||
surface: surface.clone(),
|
||||
kill_switch: kill_switch.clone(),
|
||||
frame: Arc::downgrade(&frame),
|
||||
current_scale_factor: scale_factor,
|
||||
new_scale_factor: None,
|
||||
decorated: decorated.clone(),
|
||||
pending_decorations_action: pending_decorations_action.clone(),
|
||||
});
|
||||
evlp.evq.borrow_mut().sync_roundtrip().unwrap();
|
||||
|
||||
Ok(Window {
|
||||
display: evlp.display.clone(),
|
||||
surface,
|
||||
frame,
|
||||
outputs: evlp.env.outputs.clone(),
|
||||
size,
|
||||
kill_switch: (kill_switch, evlp.cleanup_needed.clone()),
|
||||
need_frame_refresh,
|
||||
need_refresh,
|
||||
cursor_manager,
|
||||
fullscreen,
|
||||
cursor_grab_changed,
|
||||
decorated,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
make_wid(&self.surface)
|
||||
}
|
||||
|
||||
pub fn set_title(&self, title: &str) {
|
||||
self.frame.lock().unwrap().set_title(title.into());
|
||||
}
|
||||
|
||||
pub fn set_visible(&self, _visible: bool) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
Err(NotSupportedError::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
Err(NotSupportedError::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_outer_position(&self, _pos: Position) {
|
||||
// Not possible with wayland
|
||||
}
|
||||
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let size = LogicalSize::<f64>::from(*self.size.lock().unwrap());
|
||||
size.to_physical(scale_factor)
|
||||
}
|
||||
|
||||
pub fn request_redraw(&self) {
|
||||
*self.need_refresh.lock().unwrap() = true;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let (w, h) = self.size.lock().unwrap().clone();
|
||||
// let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
|
||||
let size = LogicalSize::<f64>::from((w, h));
|
||||
size.to_physical(scale_factor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
// NOTE: This will only resize the borders, the contents must be updated by the user
|
||||
pub fn set_inner_size(&self, size: Size) {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let (w, h) = size.to_logical::<u32>(scale_factor).into();
|
||||
self.frame.lock().unwrap().resize(w, h);
|
||||
*(self.size.lock().unwrap()) = (w, h);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
self.frame
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_min_size(dimensions.map(|dim| dim.to_logical::<f64>(scale_factor).into()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
self.frame
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_max_size(dimensions.map(|dim| dim.to_logical::<f64>(scale_factor).into()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
self.frame.lock().unwrap().set_resizable(resizable);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> i32 {
|
||||
get_dpi_factor(&self.surface)
|
||||
}
|
||||
|
||||
pub fn set_decorations(&self, decorate: bool) {
|
||||
*(self.decorated.lock().unwrap()) = decorate;
|
||||
self.frame.lock().unwrap().set_decorate(decorate);
|
||||
*(self.need_frame_refresh.lock().unwrap()) = true;
|
||||
}
|
||||
|
||||
pub fn set_minimized(&self, minimized: bool) {
|
||||
// An app cannot un-minimize itself on Wayland
|
||||
if minimized {
|
||||
self.frame.lock().unwrap().set_minimized();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
if maximized {
|
||||
self.frame.lock().unwrap().set_maximized();
|
||||
} else {
|
||||
self.frame.lock().unwrap().unset_maximized();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
if *(self.fullscreen.lock().unwrap()) {
|
||||
let current_monitor = self
|
||||
.current_monitor()
|
||||
.map(|current_monitor| RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(current_monitor),
|
||||
});
|
||||
|
||||
Some(Fullscreen::Borderless(current_monitor))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
match fullscreen {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
panic!("Wayland doesn't support exclusive fullscreen")
|
||||
}
|
||||
Some(Fullscreen::Borderless(monitor)) => {
|
||||
let monitor =
|
||||
monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor {
|
||||
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
|
||||
#[cfg(feature = "x11")]
|
||||
PlatformMonitorHandle::X(_) => None,
|
||||
});
|
||||
|
||||
self.frame.lock().unwrap().set_fullscreen(monitor.as_ref());
|
||||
}
|
||||
None => self.frame.lock().unwrap().unset_fullscreen(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_theme<T: Theme>(&self, theme: T) {
|
||||
self.frame.lock().unwrap().set_theme(theme)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
let mut cursor_manager = self.cursor_manager.lock().unwrap();
|
||||
cursor_manager.set_cursor_icon(cursor);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
let mut cursor_manager = self.cursor_manager.lock().unwrap();
|
||||
cursor_manager.set_cursor_visible(visible);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
|
||||
*self.cursor_grab_changed.lock().unwrap() = Some(grab);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _pos: Position) -> Result<(), ExternalError> {
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
pub fn display(&self) -> &Display {
|
||||
&*self.display
|
||||
}
|
||||
|
||||
pub fn surface(&self) -> &wl_surface::WlSurface {
|
||||
&self.surface
|
||||
}
|
||||
|
||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
||||
let output = get_outputs(&self.surface).last()?.clone();
|
||||
Some(MonitorHandle {
|
||||
proxy: output,
|
||||
mgr: self.outputs.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
available_monitors(&self.outputs)
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
||||
// Wayland doesn't have a notion of primary monitor.
|
||||
None
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> WaylandHandle {
|
||||
WaylandHandle {
|
||||
surface: self.surface().as_ref().c_ptr() as *mut _,
|
||||
display: self.display().as_ref().c_ptr() as *mut _,
|
||||
..WaylandHandle::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
*(self.kill_switch.0.lock().unwrap()) = true;
|
||||
*(self.kill_switch.1.lock().unwrap()) = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal store for windows
|
||||
*/
|
||||
|
||||
struct InternalWindow {
|
||||
surface: wl_surface::WlSurface,
|
||||
// TODO: CONVERT TO LogicalSize<u32>s
|
||||
new_size: Option<(u32, u32)>,
|
||||
size: Arc<Mutex<(u32, u32)>>,
|
||||
need_refresh: Arc<Mutex<bool>>,
|
||||
fullscreen: Arc<Mutex<bool>>,
|
||||
need_frame_refresh: Arc<Mutex<bool>>,
|
||||
cursor_grab_changed: Arc<Mutex<Option<bool>>>,
|
||||
closed: bool,
|
||||
kill_switch: Arc<Mutex<bool>>,
|
||||
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
|
||||
current_scale_factor: i32,
|
||||
new_scale_factor: Option<i32>,
|
||||
decorated: Arc<Mutex<bool>>,
|
||||
pending_decorations_action: Arc<Mutex<Option<DecorationsAction>>>,
|
||||
}
|
||||
|
||||
pub struct WindowStore {
|
||||
windows: Vec<InternalWindow>,
|
||||
}
|
||||
|
||||
pub struct WindowStoreForEach<'a> {
|
||||
pub new_size: Option<(u32, u32)>,
|
||||
pub size: &'a Mutex<(u32, u32)>,
|
||||
pub prev_scale_factor: i32,
|
||||
pub new_scale_factor: Option<i32>,
|
||||
pub closed: bool,
|
||||
pub grab_cursor: Option<bool>,
|
||||
pub surface: &'a wl_surface::WlSurface,
|
||||
pub wid: WindowId,
|
||||
pub frame: Option<Arc<Mutex<SWindow<ConceptFrame>>>>,
|
||||
pub decorations_action: Option<DecorationsAction>,
|
||||
}
|
||||
|
||||
impl WindowStore {
|
||||
pub fn new() -> WindowStore {
|
||||
WindowStore {
|
||||
windows: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_wid(&self, surface: &wl_surface::WlSurface) -> Option<WindowId> {
|
||||
for window in &self.windows {
|
||||
if surface.as_ref().equals(&window.surface.as_ref()) {
|
||||
return Some(make_wid(surface));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn cleanup(&mut self) -> Vec<WindowId> {
|
||||
let mut pruned = Vec::new();
|
||||
self.windows.retain(|w| {
|
||||
if *w.kill_switch.lock().unwrap() {
|
||||
// window is dead, cleanup
|
||||
pruned.push(make_wid(&w.surface));
|
||||
w.surface.destroy();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
pruned
|
||||
}
|
||||
|
||||
pub fn new_seat(&self, seat: &wl_seat::WlSeat) {
|
||||
for window in &self.windows {
|
||||
if let Some(w) = window.frame.upgrade() {
|
||||
w.lock().unwrap().new_seat(seat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scale_factor_change(&mut self, surface: &wl_surface::WlSurface, new: i32) {
|
||||
for window in &mut self.windows {
|
||||
if surface.as_ref().equals(&window.surface.as_ref()) {
|
||||
window.new_scale_factor = Some(new);
|
||||
*(window.need_refresh.lock().unwrap()) = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_each<F>(&mut self, mut f: F)
|
||||
where
|
||||
F: FnMut(WindowStoreForEach<'_>),
|
||||
{
|
||||
for window in &mut self.windows {
|
||||
let prev_scale_factor = window.current_scale_factor;
|
||||
if let Some(scale_factor) = window.new_scale_factor {
|
||||
window.current_scale_factor = scale_factor;
|
||||
}
|
||||
let frame = window.frame.upgrade();
|
||||
let decorations_action = { window.pending_decorations_action.lock().unwrap().take() };
|
||||
f(WindowStoreForEach {
|
||||
new_size: window.new_size.take(),
|
||||
size: &window.size,
|
||||
prev_scale_factor,
|
||||
new_scale_factor: window.new_scale_factor.take(),
|
||||
closed: window.closed,
|
||||
grab_cursor: window.cursor_grab_changed.lock().unwrap().take(),
|
||||
surface: &window.surface,
|
||||
wid: make_wid(&window.surface),
|
||||
frame,
|
||||
decorations_action,
|
||||
});
|
||||
// avoid re-spamming the event
|
||||
window.closed = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_each_redraw_trigger<F>(&mut self, mut f: F)
|
||||
where
|
||||
F: FnMut(bool, bool, WindowId, Option<Arc<Mutex<SWindow<ConceptFrame>>>>),
|
||||
{
|
||||
for window in &mut self.windows {
|
||||
let frame = window.frame.upgrade();
|
||||
f(
|
||||
replace(&mut *window.need_refresh.lock().unwrap(), false),
|
||||
replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
|
||||
make_wid(&window.surface),
|
||||
frame,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
656
src/platform_impl/linux/wayland/window/mod.rs
Normal file
656
src/platform_impl/linux/wayland/window/mod.rs
Normal file
|
@ -0,0 +1,656 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::Display;
|
||||
|
||||
use sctk::reexports::calloop;
|
||||
|
||||
use sctk::window::{
|
||||
ARGBColor, ButtonColorSpec, ColorSpec, ConceptConfig, ConceptFrame, Decorations,
|
||||
};
|
||||
|
||||
use raw_window_handle::unix::WaylandHandle;
|
||||
|
||||
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
|
||||
use crate::monitor::MonitorHandle as RootMonitorHandle;
|
||||
use crate::platform::unix::{ARGBColor as LocalARGBColor, Button, ButtonState, Element, Theme};
|
||||
use crate::platform_impl::{
|
||||
MonitorHandle as PlatformMonitorHandle, OsError,
|
||||
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
|
||||
};
|
||||
use crate::window::{CursorIcon, Fullscreen, WindowAttributes};
|
||||
|
||||
use super::env::WindowingFeatures;
|
||||
use super::event_loop::WinitState;
|
||||
use super::output::{MonitorHandle, OutputManagerHandle};
|
||||
use super::{EventLoopWindowTarget, WindowId};
|
||||
|
||||
pub mod shim;
|
||||
|
||||
use shim::{WindowHandle, WindowRequest, WindowUpdate};
|
||||
|
||||
pub struct Window {
|
||||
/// Window id.
|
||||
window_id: WindowId,
|
||||
|
||||
/// The Wayland display.
|
||||
display: Display,
|
||||
|
||||
/// The underlying wl_surface.
|
||||
surface: WlSurface,
|
||||
|
||||
/// The current window size.
|
||||
size: Arc<Mutex<LogicalSize<u32>>>,
|
||||
|
||||
/// A handle to output manager.
|
||||
output_manager_handle: OutputManagerHandle,
|
||||
|
||||
/// Event loop proxy to wake it up.
|
||||
event_loop_awakener: calloop::ping::Ping,
|
||||
|
||||
/// Fullscreen state.
|
||||
fullscreen: Arc<AtomicBool>,
|
||||
|
||||
/// Available windowing features.
|
||||
windowing_features: WindowingFeatures,
|
||||
|
||||
/// Requests that SCTK window should perform.
|
||||
window_requests: Arc<Mutex<Vec<WindowRequest>>>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new<T>(
|
||||
event_loop_window_target: &EventLoopWindowTarget<T>,
|
||||
attributes: WindowAttributes,
|
||||
platform_attributes: PlatformAttributes,
|
||||
) -> Result<Self, RootOsError> {
|
||||
let surface = event_loop_window_target
|
||||
.env
|
||||
.create_surface_with_scale_callback(move |scale, surface, mut dispatch_data| {
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
|
||||
// Get the window that receiced the event.
|
||||
let window_id = super::make_wid(&surface);
|
||||
let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap();
|
||||
|
||||
// Set pending scale factor.
|
||||
window_update.scale_factor = Some(scale);
|
||||
window_update.redraw_requested = true;
|
||||
|
||||
surface.set_buffer_scale(scale);
|
||||
})
|
||||
.detach();
|
||||
|
||||
let scale_factor = sctk::get_surface_scale_factor(&surface);
|
||||
|
||||
let window_id = super::make_wid(&surface);
|
||||
let fullscreen = Arc::new(AtomicBool::new(false));
|
||||
let fullscreen_clone = fullscreen.clone();
|
||||
|
||||
let (width, height) = attributes
|
||||
.inner_size
|
||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into())
|
||||
.unwrap_or((800, 600));
|
||||
|
||||
let theme_manager = event_loop_window_target.theme_manager.clone();
|
||||
let mut window = event_loop_window_target
|
||||
.env
|
||||
.create_window::<ConceptFrame, _>(
|
||||
surface.clone(),
|
||||
Some(theme_manager),
|
||||
(width, height),
|
||||
move |event, mut dispatch_data| {
|
||||
use sctk::window::{Event, State};
|
||||
|
||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
||||
let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap();
|
||||
|
||||
match event {
|
||||
Event::Refresh => {
|
||||
window_update.refresh_frame = true;
|
||||
}
|
||||
Event::Configure { new_size, states } => {
|
||||
let is_fullscreen = states.contains(&State::Fullscreen);
|
||||
fullscreen_clone.store(is_fullscreen, Ordering::Relaxed);
|
||||
|
||||
window_update.refresh_frame = true;
|
||||
window_update.redraw_requested = true;
|
||||
if let Some((w, h)) = new_size {
|
||||
window_update.size = Some(LogicalSize::new(w, h));
|
||||
}
|
||||
}
|
||||
Event::Close => {
|
||||
window_update.close_window = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.map_err(|_| os_error!(OsError::WaylandMisc("failed to create window.")))?;
|
||||
|
||||
// Set decorations.
|
||||
if attributes.decorations {
|
||||
window.set_decorate(Decorations::FollowServer);
|
||||
} else {
|
||||
window.set_decorate(Decorations::None);
|
||||
}
|
||||
|
||||
// Min dimensions.
|
||||
let min_size = attributes
|
||||
.min_inner_size
|
||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into());
|
||||
window.set_min_size(min_size);
|
||||
|
||||
// Max dimensions.
|
||||
let max_size = attributes
|
||||
.min_inner_size
|
||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into());
|
||||
window.set_max_size(max_size);
|
||||
|
||||
// Set Wayland specific window attributes.
|
||||
if let Some(app_id) = platform_attributes.app_id {
|
||||
window.set_app_id(app_id);
|
||||
}
|
||||
|
||||
// Set common window attributes.
|
||||
//
|
||||
// We set resizable after other attributes, since it touches min and max size under
|
||||
// the hood.
|
||||
window.set_resizable(attributes.resizable);
|
||||
window.set_title(attributes.title);
|
||||
|
||||
// Set fullscreen/maximized if so was requested.
|
||||
match attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
warn!("`Fullscreen::Exclusive` is ignored on Wayland")
|
||||
}
|
||||
Some(Fullscreen::Borderless(monitor)) => {
|
||||
let monitor =
|
||||
monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor {
|
||||
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
|
||||
#[cfg(feature = "x11")]
|
||||
PlatformMonitorHandle::X(_) => None,
|
||||
});
|
||||
|
||||
window.set_fullscreen(monitor.as_ref());
|
||||
}
|
||||
None => {
|
||||
if attributes.maximized {
|
||||
window.set_maximized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let size = Arc::new(Mutex::new(LogicalSize::new(width, height)));
|
||||
|
||||
// We should trigger redraw and commit the surface for the newly created window.
|
||||
let mut window_update = WindowUpdate::new();
|
||||
window_update.refresh_frame = true;
|
||||
window_update.redraw_requested = true;
|
||||
|
||||
let window_id = super::make_wid(&surface);
|
||||
let window_requests = Arc::new(Mutex::new(Vec::with_capacity(64)));
|
||||
|
||||
// Create a handle that performs all the requests on underlying sctk a window.
|
||||
let window_handle = WindowHandle::new(window, size.clone(), window_requests.clone());
|
||||
|
||||
let mut winit_state = event_loop_window_target.state.borrow_mut();
|
||||
|
||||
winit_state.window_map.insert(window_id, window_handle);
|
||||
|
||||
winit_state
|
||||
.window_updates
|
||||
.insert(window_id, WindowUpdate::new());
|
||||
|
||||
let windowing_features = event_loop_window_target.windowing_features;
|
||||
|
||||
// Send all updates to the server.
|
||||
let wayland_source = &event_loop_window_target.wayland_source;
|
||||
let event_loop_handle = &event_loop_window_target.event_loop_handle;
|
||||
|
||||
// To make our window usable for drawing right away we must `ack` a `configure`
|
||||
// from the server, the acking part here is done by SCTK window frame, so we just
|
||||
// need to sync with server so it'll be done automatically for us.
|
||||
event_loop_handle.with_source(&wayland_source, |event_queue| {
|
||||
let event_queue = event_queue.queue();
|
||||
let _ = event_queue.sync_roundtrip(&mut *winit_state, |_, _, _| unreachable!());
|
||||
});
|
||||
|
||||
// We all praise GNOME for these 3 lines of pure magic. If we don't do that,
|
||||
// GNOME will shrink our window a bit for the size of the decorations. I guess it
|
||||
// happens because we haven't committed them with buffers to the server.
|
||||
let window_handle = winit_state.window_map.get_mut(&window_id).unwrap();
|
||||
window_handle.window.refresh();
|
||||
|
||||
let output_manager_handle = event_loop_window_target.output_manager.handle();
|
||||
|
||||
let window = Self {
|
||||
window_id,
|
||||
surface,
|
||||
display: event_loop_window_target.display.clone(),
|
||||
output_manager_handle,
|
||||
size,
|
||||
window_requests,
|
||||
event_loop_awakener: event_loop_window_target.event_loop_awakener.clone(),
|
||||
fullscreen,
|
||||
windowing_features,
|
||||
};
|
||||
|
||||
Ok(window)
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId {
|
||||
self.window_id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_title(&self, title: &str) {
|
||||
let title_request = WindowRequest::Title(title.to_owned());
|
||||
self.window_requests.lock().unwrap().push(title_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_visible(&self, _visible: bool) {
|
||||
// Not possible on Wayland.
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
Err(NotSupportedError::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
||||
Err(NotSupportedError::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_outer_position(&self, _: Position) {
|
||||
// Not possible on Wayland.
|
||||
}
|
||||
|
||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
||||
self.size
|
||||
.lock()
|
||||
.unwrap()
|
||||
.to_physical(self.scale_factor() as f64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn request_redraw(&self) {
|
||||
let redraw_request = WindowRequest::Redraw;
|
||||
self.window_requests.lock().unwrap().push(redraw_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
self.size
|
||||
.lock()
|
||||
.unwrap()
|
||||
.to_physical(self.scale_factor() as f64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_inner_size(&self, size: Size) {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
|
||||
let size = size.to_logical::<u32>(scale_factor);
|
||||
*self.size.lock().unwrap() = size;
|
||||
|
||||
let frame_size_request = WindowRequest::FrameSize(size);
|
||||
self.window_requests
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(frame_size_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let size = dimensions.map(|size| size.to_logical::<u32>(scale_factor));
|
||||
|
||||
let min_size_request = WindowRequest::MinSize(size);
|
||||
self.window_requests.lock().unwrap().push(min_size_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let size = dimensions.map(|size| size.to_logical::<u32>(scale_factor));
|
||||
|
||||
let max_size_request = WindowRequest::MaxSize(size);
|
||||
self.window_requests.lock().unwrap().push(max_size_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
let resizeable_request = WindowRequest::Resizeable(resizable);
|
||||
self.window_requests
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(resizeable_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> u32 {
|
||||
// The scale factor from `get_surface_scale_factor` is always greater than zero, so
|
||||
// u32 conversion is safe.
|
||||
sctk::get_surface_scale_factor(&self.surface) as u32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_decorations(&self, decorate: bool) {
|
||||
let decorate_request = WindowRequest::Decorate(decorate);
|
||||
self.window_requests.lock().unwrap().push(decorate_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_minimized(&self, minimized: bool) {
|
||||
// You can't unminimize the window on Wayland.
|
||||
if !minimized {
|
||||
return;
|
||||
}
|
||||
|
||||
let minimize_request = WindowRequest::Minimize;
|
||||
self.window_requests.lock().unwrap().push(minimize_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_maximized(&self, maximized: bool) {
|
||||
let maximize_request = WindowRequest::Maximize(maximized);
|
||||
self.window_requests.lock().unwrap().push(maximize_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
if self.fullscreen.load(Ordering::Relaxed) {
|
||||
let current_monitor = self.current_monitor().map(|monitor| RootMonitorHandle {
|
||||
inner: PlatformMonitorHandle::Wayland(monitor),
|
||||
});
|
||||
|
||||
Some(Fullscreen::Borderless(current_monitor))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||
let fullscreen_request = match fullscreen {
|
||||
Some(Fullscreen::Exclusive(_)) => {
|
||||
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
|
||||
return;
|
||||
}
|
||||
Some(Fullscreen::Borderless(monitor)) => {
|
||||
let monitor =
|
||||
monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor {
|
||||
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
|
||||
#[cfg(feature = "x11")]
|
||||
PlatformMonitorHandle::X(_) => None,
|
||||
});
|
||||
|
||||
WindowRequest::Fullscreen(monitor)
|
||||
}
|
||||
None => WindowRequest::UnsetFullscreen,
|
||||
};
|
||||
|
||||
self.window_requests
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(fullscreen_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_theme<T: Theme>(&self, theme: T) {
|
||||
// First buttons is minimize, then maximize, and then close.
|
||||
let buttons: Vec<(ButtonColorSpec, ButtonColorSpec)> =
|
||||
[Button::Minimize, Button::Maximize, Button::Close]
|
||||
.iter()
|
||||
.map(|button| {
|
||||
let button = *button;
|
||||
let idle_active_bg = theme
|
||||
.button_color(button, ButtonState::Idle, false, true)
|
||||
.into();
|
||||
let idle_inactive_bg = theme
|
||||
.button_color(button, ButtonState::Idle, false, false)
|
||||
.into();
|
||||
let idle_active_icon = theme
|
||||
.button_color(button, ButtonState::Idle, true, true)
|
||||
.into();
|
||||
let idle_inactive_icon = theme
|
||||
.button_color(button, ButtonState::Idle, true, false)
|
||||
.into();
|
||||
let idle_bg = ColorSpec {
|
||||
active: idle_active_bg,
|
||||
inactive: idle_inactive_bg,
|
||||
};
|
||||
let idle_icon = ColorSpec {
|
||||
active: idle_active_icon,
|
||||
inactive: idle_inactive_icon,
|
||||
};
|
||||
|
||||
let hovered_active_bg = theme
|
||||
.button_color(button, ButtonState::Hovered, false, true)
|
||||
.into();
|
||||
let hovered_inactive_bg = theme
|
||||
.button_color(button, ButtonState::Hovered, false, false)
|
||||
.into();
|
||||
let hovered_active_icon = theme
|
||||
.button_color(button, ButtonState::Hovered, true, true)
|
||||
.into();
|
||||
let hovered_inactive_icon = theme
|
||||
.button_color(button, ButtonState::Hovered, true, false)
|
||||
.into();
|
||||
let hovered_bg = ColorSpec {
|
||||
active: hovered_active_bg,
|
||||
inactive: hovered_inactive_bg,
|
||||
};
|
||||
let hovered_icon = ColorSpec {
|
||||
active: hovered_active_icon,
|
||||
inactive: hovered_inactive_icon,
|
||||
};
|
||||
|
||||
let disabled_active_bg = theme
|
||||
.button_color(button, ButtonState::Disabled, false, true)
|
||||
.into();
|
||||
let disabled_inactive_bg = theme
|
||||
.button_color(button, ButtonState::Disabled, false, false)
|
||||
.into();
|
||||
let disabled_active_icon = theme
|
||||
.button_color(button, ButtonState::Disabled, true, true)
|
||||
.into();
|
||||
let disabled_inactive_icon = theme
|
||||
.button_color(button, ButtonState::Disabled, true, false)
|
||||
.into();
|
||||
let disabled_bg = ColorSpec {
|
||||
active: disabled_active_bg,
|
||||
inactive: disabled_inactive_bg,
|
||||
};
|
||||
let disabled_icon = ColorSpec {
|
||||
active: disabled_active_icon,
|
||||
inactive: disabled_inactive_icon,
|
||||
};
|
||||
|
||||
let button_bg = ButtonColorSpec {
|
||||
idle: idle_bg,
|
||||
hovered: hovered_bg,
|
||||
disabled: disabled_bg,
|
||||
};
|
||||
let button_icon = ButtonColorSpec {
|
||||
idle: idle_icon,
|
||||
hovered: hovered_icon,
|
||||
disabled: disabled_icon,
|
||||
};
|
||||
|
||||
(button_icon, button_bg)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let minimize_button = Some(buttons[0]);
|
||||
let maximize_button = Some(buttons[1]);
|
||||
let close_button = Some(buttons[2]);
|
||||
|
||||
// The first color is bar, then separator, and then text color.
|
||||
let titlebar_colors: Vec<ColorSpec> = [Element::Bar, Element::Separator, Element::Text]
|
||||
.iter()
|
||||
.map(|element| {
|
||||
let element = *element;
|
||||
let active = theme.element_color(element, true).into();
|
||||
let inactive = theme.element_color(element, false).into();
|
||||
|
||||
ColorSpec { active, inactive }
|
||||
})
|
||||
.collect();
|
||||
|
||||
let primary_color = titlebar_colors[0];
|
||||
let secondary_color = titlebar_colors[1];
|
||||
let title_color = titlebar_colors[2];
|
||||
|
||||
let title_font = theme.font();
|
||||
|
||||
let concept_config = ConceptConfig {
|
||||
primary_color,
|
||||
secondary_color,
|
||||
title_color,
|
||||
title_font,
|
||||
minimize_button,
|
||||
maximize_button,
|
||||
close_button,
|
||||
};
|
||||
|
||||
let theme_request = WindowRequest::Theme(concept_config);
|
||||
self.window_requests.lock().unwrap().push(theme_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
||||
let cursor_icon_request = WindowRequest::NewCursorIcon(cursor);
|
||||
self.window_requests
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(cursor_icon_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
let cursor_visible_request = WindowRequest::ShowCursor(visible);
|
||||
self.window_requests
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(cursor_visible_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
|
||||
if !self.windowing_features.cursor_grab() {
|
||||
return Err(ExternalError::NotSupported(NotSupportedError::new()));
|
||||
}
|
||||
|
||||
let cursor_grab_request = WindowRequest::GrabCursor(grab);
|
||||
self.window_requests
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(cursor_grab_request);
|
||||
self.event_loop_awakener.ping();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_cursor_position(&self, _: Position) -> Result<(), ExternalError> {
|
||||
// XXX This is possible if the locked pointer is being used. We don't have any
|
||||
// API for that right now, but it could be added in
|
||||
// https://github.com/rust-windowing/winit/issues/1677.
|
||||
//
|
||||
// This function is essential for the locked pointer API.
|
||||
//
|
||||
// See pointer-constraints-unstable-v1.xml.
|
||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ime_position(&self, position: Position) {
|
||||
let scale_factor = self.scale_factor() as f64;
|
||||
let position = position.to_logical(scale_factor);
|
||||
let ime_position_request = WindowRequest::IMEPosition(position);
|
||||
self.window_requests
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(ime_position_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn display(&self) -> &Display {
|
||||
&self.display
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn surface(&self) -> &WlSurface {
|
||||
&self.surface
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
||||
let output = sctk::get_surface_outputs(&self.surface).last()?.clone();
|
||||
Some(MonitorHandle::new(output))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
self.output_manager_handle.available_outputs()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn raw_window_handle(&self) -> WaylandHandle {
|
||||
let display = self.display.get_display_ptr() as *mut _;
|
||||
let surface = self.surface.as_ref().c_ptr() as *mut _;
|
||||
|
||||
WaylandHandle {
|
||||
display,
|
||||
surface,
|
||||
..WaylandHandle::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LocalARGBColor> for ARGBColor {
|
||||
fn from(color: LocalARGBColor) -> Self {
|
||||
let a = color.a;
|
||||
let r = color.r;
|
||||
let g = color.g;
|
||||
let b = color.b;
|
||||
Self { a, r, g, b }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
let close_request = WindowRequest::Close;
|
||||
self.window_requests.lock().unwrap().push(close_request);
|
||||
self.event_loop_awakener.ping();
|
||||
}
|
||||
}
|
388
src/platform_impl/linux/wayland/window/shim.rs
Normal file
388
src/platform_impl/linux/wayland/window/shim.rs
Normal file
|
@ -0,0 +1,388 @@
|
|||
use std::cell::Cell;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
||||
|
||||
use sctk::window::{ConceptConfig, ConceptFrame, Decorations, Window};
|
||||
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
|
||||
use crate::event::WindowEvent;
|
||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
||||
use crate::platform_impl::wayland::seat::pointer::WinitPointer;
|
||||
use crate::platform_impl::wayland::seat::text_input::TextInputHandler;
|
||||
use crate::platform_impl::wayland::WindowId;
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
/// A request to SCTK window from Winit window.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum WindowRequest {
|
||||
/// Set fullscreen.
|
||||
///
|
||||
/// Passing `None` will set it on the current monitor.
|
||||
Fullscreen(Option<WlOutput>),
|
||||
|
||||
/// Unset fullscreen.
|
||||
UnsetFullscreen,
|
||||
|
||||
/// Show cursor for the certain window or not.
|
||||
ShowCursor(bool),
|
||||
|
||||
/// Change the cursor icon.
|
||||
NewCursorIcon(CursorIcon),
|
||||
|
||||
/// Grab cursor.
|
||||
GrabCursor(bool),
|
||||
|
||||
/// Maximize the window.
|
||||
Maximize(bool),
|
||||
|
||||
/// Minimize the window.
|
||||
Minimize,
|
||||
|
||||
/// Request decorations change.
|
||||
Decorate(bool),
|
||||
|
||||
/// Make the window resizeable.
|
||||
Resizeable(bool),
|
||||
|
||||
/// Set the title for window.
|
||||
Title(String),
|
||||
|
||||
/// Min size.
|
||||
MinSize(Option<LogicalSize<u32>>),
|
||||
|
||||
/// Max size.
|
||||
MaxSize(Option<LogicalSize<u32>>),
|
||||
|
||||
/// New frame size.
|
||||
FrameSize(LogicalSize<u32>),
|
||||
|
||||
/// Set IME window position.
|
||||
IMEPosition(LogicalPosition<u32>),
|
||||
|
||||
/// Redraw was requested.
|
||||
Redraw,
|
||||
|
||||
/// A new theme for a concept frame was requested.
|
||||
Theme(ConceptConfig),
|
||||
|
||||
/// Window should be closed.
|
||||
Close,
|
||||
}
|
||||
|
||||
/// Pending update to a window from SCTK window.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct WindowUpdate {
|
||||
/// New window size.
|
||||
pub size: Option<LogicalSize<u32>>,
|
||||
|
||||
/// New scale factor.
|
||||
pub scale_factor: Option<i32>,
|
||||
|
||||
/// Whether `redraw` was requested.
|
||||
pub redraw_requested: bool,
|
||||
|
||||
/// Wether the frame should be refreshed.
|
||||
pub refresh_frame: bool,
|
||||
|
||||
/// Close the window.
|
||||
pub close_window: bool,
|
||||
}
|
||||
|
||||
impl WindowUpdate {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
size: None,
|
||||
scale_factor: None,
|
||||
redraw_requested: false,
|
||||
refresh_frame: false,
|
||||
close_window: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take(&mut self) -> Self {
|
||||
let size = self.size.take();
|
||||
let scale_factor = self.scale_factor.take();
|
||||
|
||||
let redraw_requested = self.redraw_requested;
|
||||
self.redraw_requested = false;
|
||||
|
||||
let refresh_frame = self.refresh_frame;
|
||||
self.refresh_frame = false;
|
||||
|
||||
let close_window = self.close_window;
|
||||
self.close_window = false;
|
||||
|
||||
Self {
|
||||
size,
|
||||
scale_factor,
|
||||
redraw_requested,
|
||||
refresh_frame,
|
||||
close_window,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to perform operations on SCTK window
|
||||
/// and react to events.
|
||||
pub struct WindowHandle {
|
||||
/// An actual window.
|
||||
pub window: Window<ConceptFrame>,
|
||||
|
||||
/// The current size of the window.
|
||||
pub size: Arc<Mutex<LogicalSize<u32>>>,
|
||||
|
||||
/// A pending requests to SCTK window.
|
||||
pub pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
|
||||
|
||||
/// Current cursor icon.
|
||||
pub cursor_icon: Cell<CursorIcon>,
|
||||
|
||||
/// Visible cursor or not.
|
||||
cursor_visible: Cell<bool>,
|
||||
|
||||
/// Cursor confined to the surface.
|
||||
confined: Cell<bool>,
|
||||
|
||||
/// Pointers over the current surface.
|
||||
pointers: Vec<WinitPointer>,
|
||||
|
||||
/// Text inputs on the current surface.
|
||||
text_inputs: Vec<TextInputHandler>,
|
||||
}
|
||||
|
||||
impl WindowHandle {
|
||||
pub fn new(
|
||||
window: Window<ConceptFrame>,
|
||||
size: Arc<Mutex<LogicalSize<u32>>>,
|
||||
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
window,
|
||||
size,
|
||||
pending_window_requests,
|
||||
cursor_icon: Cell::new(CursorIcon::Default),
|
||||
confined: Cell::new(false),
|
||||
cursor_visible: Cell::new(true),
|
||||
pointers: Vec::new(),
|
||||
text_inputs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_grab(&self, grab: bool) {
|
||||
// The new requested state matches the current confine status, return.
|
||||
if self.confined.get() == grab {
|
||||
return;
|
||||
}
|
||||
|
||||
self.confined.replace(grab);
|
||||
|
||||
for pointer in self.pointers.iter() {
|
||||
if self.confined.get() {
|
||||
let surface = self.window.surface();
|
||||
pointer.confine(&surface);
|
||||
} else {
|
||||
pointer.unconfine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pointer appeared over the window.
|
||||
pub fn pointer_entered(&mut self, pointer: WinitPointer) {
|
||||
let position = self.pointers.iter().position(|p| *p == pointer);
|
||||
|
||||
if position.is_none() {
|
||||
if self.confined.get() {
|
||||
let surface = self.window.surface();
|
||||
pointer.confine(&surface);
|
||||
}
|
||||
self.pointers.push(pointer);
|
||||
}
|
||||
|
||||
// Apply the current cursor style.
|
||||
self.set_cursor_visible(self.cursor_visible.get());
|
||||
}
|
||||
|
||||
/// Pointer left the window.
|
||||
pub fn pointer_left(&mut self, pointer: WinitPointer) {
|
||||
let position = self.pointers.iter().position(|p| *p == pointer);
|
||||
|
||||
if let Some(position) = position {
|
||||
let pointer = self.pointers.remove(position);
|
||||
|
||||
// Drop the confined pointer.
|
||||
if self.confined.get() {
|
||||
pointer.unconfine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_input_entered(&mut self, text_input: TextInputHandler) {
|
||||
if self
|
||||
.text_inputs
|
||||
.iter()
|
||||
.find(|t| *t == &text_input)
|
||||
.is_none()
|
||||
{
|
||||
self.text_inputs.push(text_input);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_input_left(&mut self, text_input: TextInputHandler) {
|
||||
if let Some(position) = self.text_inputs.iter().position(|t| *t == text_input) {
|
||||
self.text_inputs.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ime_position(&self, position: LogicalPosition<u32>) {
|
||||
// XXX This won't fly unless user will have a way to request IME window per seat, since
|
||||
// the ime windows will be overlapping, but winit doesn't expose API to specify for
|
||||
// which seat we're setting IME position.
|
||||
let (x, y) = (position.x as i32, position.y as i32);
|
||||
for text_input in self.text_inputs.iter() {
|
||||
text_input.set_ime_position(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_visible(&self, visible: bool) {
|
||||
self.cursor_visible.replace(visible);
|
||||
let cursor_icon = match visible {
|
||||
true => Some(self.cursor_icon.get()),
|
||||
false => None,
|
||||
};
|
||||
|
||||
for pointer in self.pointers.iter() {
|
||||
pointer.set_cursor(cursor_icon)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_icon(&self, cursor_icon: CursorIcon) {
|
||||
self.cursor_icon.replace(cursor_icon);
|
||||
|
||||
if !self.cursor_visible.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
for pointer in self.pointers.iter() {
|
||||
pointer.set_cursor(Some(cursor_icon));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn handle_window_requests(winit_state: &mut WinitState) {
|
||||
let window_map = &mut winit_state.window_map;
|
||||
let window_updates = &mut winit_state.window_updates;
|
||||
let mut windows_to_close: Vec<WindowId> = Vec::new();
|
||||
|
||||
// Process the rest of the events.
|
||||
for (window_id, window_handle) in window_map.iter_mut() {
|
||||
let mut requests = window_handle.pending_window_requests.lock().unwrap();
|
||||
for request in requests.drain(..) {
|
||||
match request {
|
||||
WindowRequest::Fullscreen(fullscreen) => {
|
||||
window_handle.window.set_fullscreen(fullscreen.as_ref());
|
||||
}
|
||||
WindowRequest::UnsetFullscreen => {
|
||||
window_handle.window.unset_fullscreen();
|
||||
}
|
||||
WindowRequest::ShowCursor(show_cursor) => {
|
||||
window_handle.set_cursor_visible(show_cursor);
|
||||
}
|
||||
WindowRequest::NewCursorIcon(cursor_icon) => {
|
||||
window_handle.set_cursor_icon(cursor_icon);
|
||||
}
|
||||
WindowRequest::IMEPosition(position) => {
|
||||
window_handle.set_ime_position(position);
|
||||
}
|
||||
WindowRequest::GrabCursor(grab) => {
|
||||
window_handle.set_cursor_grab(grab);
|
||||
}
|
||||
WindowRequest::Maximize(maximize) => {
|
||||
if maximize {
|
||||
window_handle.window.set_maximized();
|
||||
} else {
|
||||
window_handle.window.unset_maximized();
|
||||
}
|
||||
}
|
||||
WindowRequest::Minimize => {
|
||||
window_handle.window.set_minimized();
|
||||
}
|
||||
WindowRequest::Decorate(decorate) => {
|
||||
let decorations = match decorate {
|
||||
true => Decorations::FollowServer,
|
||||
false => Decorations::None,
|
||||
};
|
||||
|
||||
window_handle.window.set_decorate(decorations);
|
||||
|
||||
// We should refresh the frame to apply decorations change.
|
||||
let window_update = window_updates.get_mut(&window_id).unwrap();
|
||||
window_update.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::Resizeable(resizeable) => {
|
||||
window_handle.window.set_resizable(resizeable);
|
||||
|
||||
// We should refresh the frame to update button state.
|
||||
let window_update = window_updates.get_mut(&window_id).unwrap();
|
||||
window_update.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::Title(title) => {
|
||||
window_handle.window.set_title(title);
|
||||
|
||||
// We should refresh the frame to draw new title.
|
||||
let window_update = window_updates.get_mut(&window_id).unwrap();
|
||||
window_update.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::MinSize(size) => {
|
||||
let size = size.map(|size| (size.width, size.height));
|
||||
window_handle.window.set_min_size(size);
|
||||
|
||||
let window_update = window_updates.get_mut(&window_id).unwrap();
|
||||
window_update.redraw_requested = true;
|
||||
}
|
||||
WindowRequest::MaxSize(size) => {
|
||||
let size = size.map(|size| (size.width, size.height));
|
||||
window_handle.window.set_max_size(size);
|
||||
|
||||
let window_update = window_updates.get_mut(&window_id).unwrap();
|
||||
window_update.redraw_requested = true;
|
||||
}
|
||||
WindowRequest::FrameSize(size) => {
|
||||
// Set new size.
|
||||
window_handle.window.resize(size.width, size.height);
|
||||
|
||||
// We should refresh the frame after resize.
|
||||
let window_update = window_updates.get_mut(&window_id).unwrap();
|
||||
window_update.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::Redraw => {
|
||||
let window_update = window_updates.get_mut(&window_id).unwrap();
|
||||
window_update.redraw_requested = true;
|
||||
}
|
||||
WindowRequest::Theme(concept_config) => {
|
||||
window_handle.window.set_frame_config(concept_config);
|
||||
|
||||
// We should refresh the frame to apply new theme.
|
||||
let window_update = window_updates.get_mut(&window_id).unwrap();
|
||||
window_update.refresh_frame = true;
|
||||
}
|
||||
WindowRequest::Close => {
|
||||
// The window was requested to be closed.
|
||||
windows_to_close.push(*window_id);
|
||||
|
||||
// Send event that the window was destroyed.
|
||||
let event_sink = &mut winit_state.event_sink;
|
||||
event_sink.push_window_event(WindowEvent::Destroyed, *window_id);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Close the windows.
|
||||
for window in windows_to_close {
|
||||
let _ = window_map.remove(&window);
|
||||
let _ = window_updates.remove(&window);
|
||||
}
|
||||
}
|
|
@ -614,7 +614,7 @@ impl Window {
|
|||
///
|
||||
/// The dock and the menu bar are always disabled in fullscreen mode.
|
||||
/// - **iOS:** Can only be called on the main thread.
|
||||
/// - **Wayland:** Does not support exclusive fullscreen mode.
|
||||
/// - **Wayland:** Does not support exclusive fullscreen mode and will no-op a request.
|
||||
/// - **Windows:** Screen saver is disabled in fullscreen mode.
|
||||
/// - **Android:** Unsupported.
|
||||
#[inline]
|
||||
|
@ -677,7 +677,7 @@ impl Window {
|
|||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / Web / Wayland / Windows:** Unsupported.
|
||||
/// - **iOS / Android / Web / Windows:** Unsupported.
|
||||
#[inline]
|
||||
pub fn set_ime_position<P: Into<Position>>(&self, position: P) {
|
||||
self.window.set_ime_position(position.into())
|
||||
|
@ -708,9 +708,12 @@ impl Window {
|
|||
|
||||
/// Grabs the cursor, preventing it from leaving the window.
|
||||
///
|
||||
/// There's no guarantee that the cursor will be hidden. You should
|
||||
/// hide it by yourself if you want so.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **macOS / Wayland:** This locks the cursor in a fixed location, which looks visually awkward.
|
||||
/// - **macOS:** This locks the cursor in a fixed location, which looks visually awkward.
|
||||
/// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
|
||||
#[inline]
|
||||
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
|
||||
|
|
Loading…
Reference in a new issue