Update wayland-rs to 0.30.0

This update rewrites the winit's Wayland backend using new wayland-rs
0.30 API. This fixes long standing issue with the forward compatibility
of the wayland backend, meaning that future updates to the wayland
protocol won't break rust code anymore. like it was before when adding
new shm/enum variants into the protocol.

Fixes #2560.
Fixes #2164.
Fixes #2128.
Fixes #1760.
Fixes #725.
This commit is contained in:
Kirill Chibisov 2023-04-19 00:56:29 +03:00 committed by GitHub
parent 60e91b187a
commit 2496098890
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 3515 additions and 3458 deletions

View file

@ -75,6 +75,7 @@ jobs:
env:
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
PKG_CONFIG_ALLOW_CROSS: 1
RUSTFLAGS: "-C debuginfo=0 --deny warnings"
OPTIONS: ${{ matrix.platform.options }}
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
@ -97,9 +98,12 @@ jobs:
targets: ${{ matrix.platform.target }}
components: clippy
- name: Install Linux dependencies
if: (matrix.platform.os == 'ubuntu-latest')
run: sudo apt-get update && sudo apt-get install pkg-config libxkbcommon-dev
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
run: sudo apt-get update && sudo apt-get install gcc-multilib
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install g++-multilib gcc-multilib libxkbcommon-dev:i386
- name: Install cargo-apk
if: contains(matrix.platform.target, 'android')
run: cargo install cargo-apk

View file

@ -8,6 +8,14 @@ And please only add new entries to the top of this list, right below the `# Unre
# Unreleased
- On Wayland, fix maximized startup not taking full size on GNOME.
- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
- On Wayland, `Window::outer_size` now accounts for **client side** decorations.
- On Wayland, fix window not checking that it actually got initial configure event.
- On Wayland, fix maximized window creation and window geometry handling.
- On Wayland, fix forward compatibility issues.
- On Wayland, add `Window::drag_resize_window` method.
- On Wayland, drop `WINIT_WAYLAND_CSD_THEME` variable.
- Bump MSRV from `1.60` to `1.64`.
- On macOS, fixed potential panic when getting refresh rate.
- On macOS, fix crash when calling `Window::set_ime_position` from another thread.

View file

@ -37,8 +37,8 @@ rustdoc-args = ["--cfg", "docsrs"]
[features]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
x11 = ["x11-dl", "mio", "percent-encoding"]
wayland = ["wayland-client", "wayland-protocols", "sctk", "wayland-commons"]
wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv"]
wayland-dlopen = ["wayland-backend/dlopen"]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
@ -107,20 +107,19 @@ features = [
libc = "0.2.64"
mio = { version = "0.8", features = ["os-ext"], optional = true }
percent-encoding = { version = "2.0", optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.16.0", default_features = false, features = ["calloop"], optional = true }
sctk-adwaita = { version = "0.5.1", default_features = false, optional = true }
wayland-client = { version = "0.29.5", default_features = false, features = ["use_system_lib"], optional = true }
wayland-protocols = { version = "0.29.5", features = [ "staging_protocols"], optional = true }
wayland-commons = { version = "0.29.5", optional = true }
fnv = { version = "1.0.3", optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.17.0", optional = true }
sctk-adwaita = { version = "0.6.0", default_features = false, optional = true }
wayland-client = { version = "0.30.0", optional = true }
wayland-backend = { version = "0.1.0", default_features = false, features = ["client_system"], optional = true }
wayland-protocols = { version = "0.30.0", features = [ "staging"], optional = true }
calloop = "0.10.5"
x11-dl = { version = "2.18.5", optional = true }
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.42", default-features = false }
redox_syscall = "0.3"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.build-dependencies]
wayland-scanner = "0.29.5"
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
package = "web-sys"
version = "0.3.22"

View file

@ -1,35 +1,8 @@
use cfg_aliases::cfg_aliases;
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
),
feature = "wayland",
))]
mod wayland {
use std::env;
use std::path::PathBuf;
use wayland_scanner::Side;
pub fn main() {
let mut path = PathBuf::from(env::var("OUT_DIR").unwrap());
path.push("fractional_scale_v1.rs");
wayland_scanner::generate_code(
"wayland_protocols/fractional-scale-v1.xml",
&path,
Side::Client,
);
}
}
fn main() {
// The script doesn't depend on our code
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=wayland_protocols");
// Setup cfg aliases
cfg_aliases! {
@ -48,17 +21,4 @@ fn main() {
wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) },
orbital_platform: { redox },
}
// XXX aliases are not available for the build script itself.
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
),
feature = "wayland",
))]
wayland::main();
}

View file

@ -32,6 +32,9 @@ wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny
deny = []
skip = [
{ name = "nix" }, # differing version - as of 2023-03-02 whis can be solved with `cargo update && cargo update -p calloop --precise 0.10.2`
{ name = "memoffset"}, # due to different nix versions.
{ name = "syn" }, # https://github.com/rust-mobile/ndk/issues/392 and https://github.com/rustwasm/wasm-bindgen/issues/3390
{ name = "miniz_oxide"}, # https://github.com/rust-lang/flate2-rs/issues/340
{ name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46
]
skip-tree = []

View file

@ -1,5 +1,7 @@
use std::os::raw;
use sctk::reexports::client::Proxy;
use crate::{
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
monitor::MonitorHandle,
@ -39,7 +41,7 @@ impl<T> EventLoopWindowTargetExtWayland for EventLoopWindowTarget<T> {
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.p {
LinuxEventLoopWindowTarget::Wayland(ref p) => {
Some(p.display().get_display_ptr() as *mut _)
Some(p.connection.display().id().as_ptr() as *mut _)
}
#[cfg(x11_platform)]
_ => None,
@ -94,7 +96,7 @@ impl WindowExtWayland for Window {
#[inline]
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _),
LinuxWindow::Wayland(ref w) => Some(w.surface().id().as_ptr() as *mut _),
#[cfg(x11_platform)]
_ => None,
}
@ -103,7 +105,7 @@ impl WindowExtWayland for Window {
#[inline]
fn wayland_display(&self) -> Option<*mut raw::c_void> {
match self.window {
LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _),
LinuxWindow::Wayland(ref w) => Some(w.display().id().as_ptr() as *mut _),
#[cfg(x11_platform)]
_ => None,
}

View file

@ -1,183 +0,0 @@
//! 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::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::reexports::protocols::viewporter::client::wp_viewporter::WpViewporter;
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;
use crate::platform_impl::wayland::protocols::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
/// Set of extra features that are supported by the compositor.
#[derive(Debug, Clone, Copy)]
pub struct WindowingFeatures {
pointer_constraints: bool,
xdg_activation: bool,
}
impl WindowingFeatures {
/// Create `WindowingFeatures` based on the presented interfaces.
pub fn new(env: &Environment<WinitEnv>) -> Self {
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>().is_some();
let xdg_activation = env.get_global::<XdgActivationV1>().is_some();
Self {
pointer_constraints,
xdg_activation,
}
}
pub fn pointer_constraints(&self) -> bool {
self.pointer_constraints
}
pub fn xdg_activation(&self) -> bool {
self.xdg_activation
}
}
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,
XdgActivationV1 => xdg_activation,
WpFractionalScaleManagerV1 => fractional_scale_manager,
WpViewporter => viewporter,
],
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>,
xdg_activation: SimpleGlobal<XdgActivationV1>,
fractional_scale_manager: SimpleGlobal<WpFractionalScaleManagerV1>,
viewporter: SimpleGlobal<WpViewporter>,
}
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();
// Surface activation.
let xdg_activation = SimpleGlobal::new();
// Fractional surface scaling.
let fractional_scale_manager = SimpleGlobal::new();
// Surface resizing (used for fractional scaling).
let viewporter = SimpleGlobal::new();
Self {
seats,
outputs,
shm,
compositor,
subcompositor,
shell,
decoration_manager,
relative_pointer_manager,
pointer_constraints,
text_input_manager,
xdg_activation,
fractional_scale_manager,
viewporter,
}
}
}
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)
}
}

View file

@ -1,23 +1,20 @@
//! The event-loop routines.
use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error;
use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::mem;
use std::process;
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::time::{Duration, Instant};
use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle};
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 sctk::reexports::client::globals;
use sctk::reexports::client::{Connection, Proxy, QueueHandle, WaylandSource};
use crate::dpi::{LogicalSize, PhysicalSize};
use crate::event::{Event, StartCause, WindowEvent};
@ -25,135 +22,72 @@ use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindo
use crate::platform_impl::platform::sticky_exit_callback;
use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
use super::env::{WindowingFeatures, WinitEnv};
use super::output::OutputManager;
use super::seat::SeatManager;
use super::window::shim::{self, WindowCompositorUpdate, WindowUserRequest};
use super::{DeviceId, WindowId};
mod proxy;
mod sink;
mod state;
pub mod sink;
pub use proxy::EventLoopProxy;
pub use sink::EventSink;
pub use state::WinitState;
use sink::EventSink;
type WinitDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>;
use super::state::{WindowCompositorUpdate, WinitState};
use super::{DeviceId, WindowId};
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<'static, WinitState>,
/// Output manager.
pub output_manager: OutputManager,
/// State that we share across callbacks.
pub state: RefCell<WinitState>,
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WinitDispatcher,
/// 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 between all windows to avoid loading
/// multiple similar themes.
pub theme_manager: ThemeManager,
_marker: std::marker::PhantomData<T>,
}
impl<T> EventLoopWindowTarget<T> {
pub fn raw_display_handle(&self) -> RawDisplayHandle {
let mut display_handle = WaylandDisplayHandle::empty();
display_handle.display = self.display.get_display_ptr() as *mut _;
RawDisplayHandle::Wayland(display_handle)
}
}
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
/// The Wayland event loop.
pub struct EventLoop<T: 'static> {
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WinitDispatcher,
/// Event loop.
event_loop: calloop::EventLoop<'static, 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>,
/// Window target.
// XXX can't remove RefCell out of here, unless we can plumb generics into the `Window`, which
// we don't really want, since it'll break public API by a lot.
/// Pending events from the user.
pending_user_events: Rc<RefCell<Vec<T>>>,
/// The Wayland dispatcher to has raw access to the queue when needed, such as
/// when creating a new window.
wayland_dispatcher: WaylandDispatcher,
/// Connection to the wayland server.
connection: Connection,
/// Event loop window target.
window_target: RootEventLoopWindowTarget<T>,
/// Output manager.
_seat_manager: SeatManager,
// XXX drop after everything else, just to be safe.
/// Calloop's event loop.
event_loop: calloop::EventLoop<'static, WinitState>,
}
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());
let connection = Connection::connect_to_env()?;
// Setup environment.
let env = Environment::new(&display_proxy, &mut event_queue, WinitEnv::new())?;
let (globals, mut event_queue) = globals::registry_queue_init(&connection)?;
let queue_handle = event_queue.handle();
// Create event loop.
let event_loop = calloop::EventLoop::<'static, WinitState>::try_new()?;
// Build windowing features.
let windowing_features = WindowingFeatures::new(&env);
let event_loop = calloop::EventLoop::<WinitState>::try_new()?;
// 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);
let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle())?;
// Setup theme seat and output managers.
let seat_manager = SeatManager::new(&env, event_loop.handle(), theme_manager.clone());
let output_manager = OutputManager::new(&env);
// NOTE: do a roundtrip after binding the globals to prevent potential
// races with the server.
event_queue.roundtrip(&mut winit_state)?;
// A source of events that we plug into our event loop.
let wayland_source = WaylandSource::new(event_queue);
// Register Wayland source.
let wayland_source = WaylandSource::new(event_queue)?;
let wayland_dispatcher =
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| {
queue.dispatch_pending(winit_state, |event, object, _| {
panic!(
"[calloop] Encountered an orphan event: {}@{} : {}",
event.interface,
object.as_ref().id(),
event.name
);
})
queue.dispatch_pending(winit_state)
});
let _wayland_source_dispatcher = event_loop
event_loop
.handle()
.register_dispatcher(wayland_dispatcher.clone())?;
// A source of user events.
// Setup the user proxy.
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, _, _| {
@ -164,52 +98,30 @@ impl<T: 'static> EventLoop<T> {
// 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 |_, _, state| {
// Drain events here as well to account for application doing batch event processing
// on RedrawEventsCleared.
shim::handle_window_requests(state);
.insert_source(event_loop_awakener_source, move |_, _, _| {
// No extra handling is required, we just need to wake-up.
})?;
let event_loop_handle = event_loop.handle();
let window_map = HashMap::new();
let event_sink = EventSink::new();
let window_user_requests = HashMap::new();
let window_compositor_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_user_requests,
window_compositor_updates,
}),
event_loop_handle,
output_manager,
event_loop_awakener,
let window_target = EventLoopWindowTarget {
connection: connection.clone(),
wayland_dispatcher: wayland_dispatcher.clone(),
windowing_features,
theme_manager,
_marker: std::marker::PhantomData,
event_loop_awakener,
queue_handle,
state: RefCell::new(winit_state),
_marker: PhantomData,
};
// Create event loop itself.
let event_loop = Self {
event_loop,
display,
pending_user_events,
connection,
wayland_dispatcher,
_seat_manager: seat_manager,
user_events_sender,
pending_user_events,
event_loop,
window_target: RootEventLoopWindowTarget {
p: PlatformEventLoopWindowTarget::Wayland(event_loop_window_target),
_marker: std::marker::PhantomData,
p: PlatformEventLoopWindowTarget::Wayland(window_target),
_marker: PhantomData,
},
};
@ -229,7 +141,11 @@ impl<T: 'static> EventLoop<T> {
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{
let mut control_flow = ControlFlow::Poll;
let pending_user_events = self.pending_user_events.clone();
// XXX preallocate certian structures to avoid allocating on each loop iteration.
let mut window_ids = Vec::<WindowId>::new();
let mut compositor_updates = Vec::<WindowCompositorUpdate>::new();
let mut buffer_sink = EventSink::new();
callback(
Event::NewEvents(StartCause::Init),
@ -237,23 +153,19 @@ impl<T: 'static> EventLoop<T> {
&mut control_flow,
);
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
// XXX For consistency all platforms must emit a 'Resumed' event even though Wayland
// applications don't themselves have a formal suspend/resume lifecycle.
callback(Event::Resumed, &self.window_target, &mut control_flow);
let mut window_compositor_updates: Vec<(WindowId, WindowCompositorUpdate)> = Vec::new();
let mut window_user_requests: Vec<(WindowId, WindowUserRequest)> = Vec::new();
let mut event_sink_back_buffer = Vec::new();
// NOTE We break on errors from dispatches, since if we've got protocol error
// XXX 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.
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
let exit_code = loop {
// Send pending events to the server.
let _ = self.display.flush();
// Flush the connection.
let _ = self.connection.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
@ -273,9 +185,12 @@ impl<T: 'static> EventLoop<T> {
_ => unreachable!(),
};
match queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
match queue.dispatch_pending(state) {
Ok(dispatched) => dispatched > 0,
Err(error) => break error.raw_os_error().unwrap_or(1),
Err(error) => {
error!("Error dispatching wayland queue: {}", error);
break 1;
}
}
};
@ -283,7 +198,7 @@ impl<T: 'static> EventLoop<T> {
ControlFlow::ExitWithCode(code) => break code,
ControlFlow::Poll => {
// Non-blocking dispatch.
let timeout = Duration::from_millis(0);
let timeout = Duration::ZERO;
if let Err(error) = self.loop_dispatch(Some(timeout)) {
break error.raw_os_error().unwrap_or(1);
}
@ -296,7 +211,7 @@ impl<T: 'static> EventLoop<T> {
}
ControlFlow::Wait => {
let timeout = if instant_wakeup {
Some(Duration::from_millis(0))
Some(Duration::ZERO)
} else {
None
};
@ -321,7 +236,7 @@ impl<T: 'static> EventLoop<T> {
let duration = if deadline > start && !instant_wakeup {
deadline - start
} else {
Duration::from_millis(0)
Duration::ZERO
};
if let Err(error) = self.loop_dispatch(Some(duration)) {
@ -354,7 +269,7 @@ impl<T: 'static> EventLoop<T> {
// 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(..) {
for user_event in self.pending_user_events.borrow_mut().drain(..) {
sticky_exit_callback(
Event::UserEvent(user_event),
&self.window_target,
@ -363,35 +278,30 @@ impl<T: 'static> EventLoop<T> {
);
}
// Process 'new' pending updates from compositor.
// Drain the pending compositor updates.
self.with_state(|state| {
window_compositor_updates.clear();
window_compositor_updates.extend(
state
.window_compositor_updates
.iter_mut()
.map(|(wid, window_update)| (*wid, mem::take(window_update))),
);
compositor_updates.append(&mut state.window_compositor_updates)
});
for (window_id, window_compositor_update) in window_compositor_updates.iter_mut() {
if let Some(scale_factor) = window_compositor_update.scale_factor {
for mut compositor_update in compositor_updates.drain(..) {
let window_id = compositor_update.window_id;
if let Some(scale_factor) = compositor_update.scale_factor {
let mut physical_size = self.with_state(|state| {
let window_handle = state.window_map.get(window_id).unwrap();
*window_handle.scale_factor.lock().unwrap() = scale_factor;
let mut size = window_handle.size.lock().unwrap();
// Update the new logical size if it was changed.
let window_size = window_compositor_update.size.unwrap_or(*size);
*size = window_size;
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
// Set the new scale factor.
window.set_scale_factor(scale_factor);
let window_size = compositor_update.size.unwrap_or(window.inner_size());
logical_to_physical_rounded(window_size, scale_factor)
});
// Stash the old window size.
let old_physical_size = physical_size;
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(*window_id),
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size: &mut physical_size,
@ -402,69 +312,46 @@ impl<T: 'static> EventLoop<T> {
&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_compositor_update.size = Some(new_logical_size);
// Resize the window when user altered the size.
if old_physical_size != physical_size {
self.with_state(|state| {
let windows = state.windows.get_mut();
let mut window = windows.get(&window_id).unwrap().lock().unwrap();
window.resize(new_logical_size);
});
}
if let Some(size) = window_compositor_update.size.take() {
// Make it queue resize.
compositor_update.size = Some(new_logical_size);
}
if let Some(size) = compositor_update.size.take() {
let physical_size = self.with_state(|state| {
let window_handle = state.window_map.get_mut(window_id).unwrap();
let windows = state.windows.get_mut();
let window = windows.get(&window_id).unwrap().lock().unwrap();
if let Some(fs_state) = window_handle.fractional_scaling_state.as_ref() {
// If we have a viewport then we support fractional scaling. As per the
// protocol, we have to set the viewport size of the size prior scaling.
fs_state
.viewport
.set_destination(size.width as _, size.height as _);
}
let mut window_size = window_handle.size.lock().unwrap();
// Always issue resize event on scale factor change.
let physical_size = if window_compositor_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 = window_handle.scale_factor();
let scale_factor = window.scale_factor();
let physical_size = logical_to_physical_rounded(size, scale_factor);
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();
// TODO could probably bring back size reporting optimization.
// Update the opaque region.
window_handle.set_transparent(window_handle.transparent.get());
// Mark that refresh isn't required, since we've done it right now.
// Mark the window as needed a redraw.
state
.window_user_requests
.get_mut(window_id)
.window_requests
.get_mut()
.get_mut(&window_id)
.unwrap()
.refresh_frame = false;
// Queue redraw requested.
state
.window_user_requests
.get_mut(window_id)
.unwrap()
.redraw_requested = true;
.redraw_requested
.store(true, Ordering::Relaxed);
physical_size
});
if let Some(physical_size) = physical_size {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(*window_id),
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
@ -472,13 +359,11 @@ impl<T: 'static> EventLoop<T> {
&mut callback,
);
}
}
// If the close is requested, send it here.
if window_compositor_update.close_window {
if compositor_update.close_window {
sticky_exit_callback(
Event::WindowEvent {
window_id: crate::window::WindowId(*window_id),
window_id: crate::window::WindowId(window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
@ -488,18 +373,20 @@ impl<T: 'static> EventLoop<T> {
}
}
// 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.
// Push the events directly from the window.
self.with_state(|state| {
std::mem::swap(
&mut event_sink_back_buffer,
&mut state.event_sink.window_events,
)
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}
// Handle pending window events.
for event in event_sink_back_buffer.drain(..) {
// Handle non-synthetic events.
self.with_state(|state| {
buffer_sink.append(&mut state.events_sink);
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}
@ -512,42 +399,41 @@ impl<T: 'static> EventLoop<T> {
&mut callback,
);
// Apply user requests, so every event required resize and latter surface commit will
// be applied right before drawing. This will also ensure that every `RedrawRequested`
// event will be delivered in time.
// Collect the window ids
self.with_state(|state| {
shim::handle_window_requests(state);
window_ids.extend(state.window_requests.get_mut().keys());
});
// Process 'new' pending updates from compositor.
self.with_state(|state| {
window_user_requests.clear();
window_user_requests.extend(
state
.window_user_requests
.iter_mut()
.map(|(wid, window_request)| (*wid, mem::take(window_request))),
);
});
for window_id in window_ids.drain(..) {
let request_redraw = self.with_state(|state| {
let window_requests = state.window_requests.get_mut();
if window_requests.get(&window_id).unwrap().take_closed() {
mem::drop(window_requests.remove(&window_id));
mem::drop(state.windows.get_mut().remove(&window_id));
false
} else {
let mut redraw_requested = window_requests
.get(&window_id)
.unwrap()
.take_redraw_requested();
// Handle RedrawRequested events.
for (window_id, mut window_request) in window_user_requests.iter() {
// Handle refresh of the frame.
if window_request.refresh_frame {
self.with_state(|state| {
let window_handle = state.window_map.get_mut(window_id).unwrap();
window_handle.window.refresh();
});
// Redraw the frames while at it.
redraw_requested |= state
.windows
.get_mut()
.get_mut(&window_id)
.unwrap()
.lock()
.unwrap()
.refresh_frame();
// In general refreshing the frame requires surface commit, those force user
// to redraw.
window_request.redraw_requested = true;
redraw_requested
}
});
// Handle redraw request.
if window_request.redraw_requested {
if request_redraw {
sticky_exit_callback(
Event::RedrawRequested(crate::window::WindowId(*window_id)),
Event::RedrawRequested(crate::window::WindowId(window_id)),
&self.window_target,
&mut control_flow,
&mut callback,
@ -578,26 +464,55 @@ impl<T: 'static> EventLoop<T> {
&self.window_target
}
fn with_state<U, F: FnOnce(&mut WinitState) -> U>(&mut self, f: F) -> U {
fn with_state<'a, U: 'a, F: FnOnce(&'a mut WinitState) -> U>(&'a mut self, callback: F) -> U {
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(x11_platform)]
_ => unreachable!(),
};
f(state)
callback(state)
}
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> {
let state = match &mut self.window_target.p {
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
_ => unreachable!(),
};
self.event_loop
.dispatch(timeout, state)
.map_err(|error| error.into())
self.event_loop.dispatch(timeout, state).map_err(|error| {
error!("Error dispatching event loop: {}", error);
error.into()
})
}
}
pub struct EventLoopWindowTarget<T> {
/// The event loop wakeup source.
pub event_loop_awakener: calloop::ping::Ping,
/// The main queue used by the event loop.
pub queue_handle: QueueHandle<WinitState>,
// TODO remove that RefCell once we can pass `&mut` in `Window::new`.
/// Winit state.
pub state: RefCell<WinitState>,
/// Dispatcher of Wayland events.
pub wayland_dispatcher: WaylandDispatcher,
/// Connection to the wayland server.
pub connection: Connection,
_marker: std::marker::PhantomData<T>,
}
impl<T> EventLoopWindowTarget<T> {
pub fn raw_display_handle(&self) -> RawDisplayHandle {
let mut display_handle = WaylandDisplayHandle::empty();
display_handle.display = self.connection.display().id().as_ptr() as *mut _;
RawDisplayHandle::Wayland(display_handle)
}
}

View file

@ -1,5 +1,7 @@
//! An event loop's sink to deliver events from the Wayland event callbacks.
use std::vec::Drain;
use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent};
use crate::platform_impl::platform::DeviceId as PlatformDeviceId;
use crate::window::WindowId as RootWindowId;
@ -19,6 +21,7 @@ impl EventSink {
}
/// Add new device event to a queue.
#[inline]
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
self.window_events.push(Event::DeviceEvent {
event,
@ -27,10 +30,21 @@ impl EventSink {
}
/// Add new window event to a queue.
#[inline]
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
self.window_events.push(Event::WindowEvent {
event,
window_id: RootWindowId(window_id),
});
}
#[inline]
pub fn append(&mut self, other: &mut Self) {
self.window_events.append(&mut other.window_events);
}
#[inline]
pub fn drain(&mut self) -> Drain<'_, Event<'static, ()>> {
self.window_events.drain(..)
}
}

View file

@ -1,31 +0,0 @@
//! A state that we pass around in a dispatch.
use std::collections::HashMap;
use super::EventSink;
use crate::platform_impl::wayland::window::shim::{
WindowCompositorUpdate, WindowHandle, WindowUserRequest,
};
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 comming from the user requests. Those are separatelly dispatched right after
/// `MainEventsCleared`.
pub window_user_requests: HashMap<WindowId, WindowUserRequest>,
/// 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_compositor_updates: HashMap<WindowId, WindowCompositorUpdate>,
/// 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>,
}

View file

@ -1,19 +1,23 @@
#![cfg(wayland_platform)]
//! Winit's Wayland backend.
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy;
pub use crate::platform_impl::platform::WindowId;
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
pub use output::{MonitorHandle, VideoMode};
pub use window::Window;
mod env;
mod event_loop;
mod output;
mod protocols;
mod seat;
mod state;
mod types;
mod window;
/// Dummy device id, since Wayland doesn't have device events.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
@ -23,7 +27,8 @@ impl DeviceId {
}
}
/// Get the WindowId out of the surface.
#[inline]
fn make_wid(surface: &WlSurface) -> WindowId {
WindowId(surface.as_ref().c_ptr() as u64)
WindowId(surface.id().as_ptr() as u64)
}

View file

@ -1,98 +1,30 @@
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::Display;
use sctk::reexports::client::Proxy;
use sctk::environment::Environment;
use sctk::output::OutputStatusListener;
use sctk::output::OutputData;
use crate::dpi::{PhysicalPosition, PhysicalSize};
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<T> EventLoopWindowTarget<T> {
#[inline]
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
self.state
.borrow()
.output_state
.outputs()
.map(MonitorHandle::new)
.collect()
}
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()
#[inline]
pub fn primary_monitor(&self) -> Option<PlatformMonitorHandle> {
// There's no primary monitor on Wayland.
None
}
}
@ -101,6 +33,80 @@ pub struct MonitorHandle {
pub(crate) proxy: WlOutput,
}
impl MonitorHandle {
#[inline]
pub(crate) fn new(proxy: WlOutput) -> Self {
Self { proxy }
}
#[inline]
pub fn name(&self) -> Option<String> {
let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| info.name.clone())
}
#[inline]
pub fn native_identifier(&self) -> u32 {
let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| info.id)
}
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
let output_data = self.proxy.data::<OutputData>().unwrap();
let dimensions = output_data.with_output_info(|info| {
info.modes
.iter()
.find_map(|mode| mode.current.then_some(mode.dimensions))
});
match dimensions {
Some((width, height)) => (width as u32, height as u32),
_ => (0, 0),
}
.into()
}
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| info.location).into()
}
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.with_output_info(|info| {
info.modes
.iter()
.find_map(|mode| mode.current.then_some(mode.refresh_rate as u32))
})
}
#[inline]
pub fn scale_factor(&self) -> i32 {
let output_data = self.proxy.data::<OutputData>().unwrap();
output_data.scale_factor()
}
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = PlatformVideoMode> {
let output_data = self.proxy.data::<OutputData>().unwrap();
let modes = output_data.with_output_info(|info| info.modes.clone());
let monitor = self.clone();
modes.into_iter().map(move |mode| {
PlatformVideoMode::Wayland(VideoMode {
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
refresh_rate_millihertz: mode.refresh_rate as u32,
bit_depth: 32,
monitor: monitor.clone(),
})
})
}
}
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
self.native_identifier() == other.native_identifier()
@ -127,78 +133,6 @@ impl std::hash::Hash for MonitorHandle {
}
}
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 refresh_rate_millihertz(&self) -> Option<u32> {
sctk::output::with_output_info(&self.proxy, |info| {
info.modes
.iter()
.find_map(|mode| mode.is_current.then_some(mode.refresh_rate as u32))
})
.flatten()
}
#[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 = PlatformVideoMode> {
let modes = sctk::output::with_output_info(&self.proxy, |info| info.modes.clone())
.unwrap_or_default();
let monitor = self.clone();
modes.into_iter().map(move |mode| {
PlatformVideoMode::Wayland(VideoMode {
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
refresh_rate_millihertz: mode.refresh_rate as u32,
bit_depth: 32,
monitor: monitor.clone(),
})
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VideoMode {
pub(crate) size: PhysicalSize<u32>,
@ -227,21 +161,3 @@ impl VideoMode {
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<PlatformMonitorHandle> {
// There's no primary monitor on Wayland.
None
}
}

View file

@ -1,13 +0,0 @@
#![allow(dead_code, non_camel_case_types, unused_unsafe, unused_variables)]
#![allow(non_upper_case_globals, non_snake_case, unused_imports)]
#![allow(missing_docs, clippy::all)]
use wayland_client::protocol::wl_surface;
use wayland_client::sys;
use wayland_client::{AnonymousObject, Attached, Main, Proxy, ProxyMap};
use wayland_commons::map::{Object, ObjectMetadata};
use wayland_commons::smallvec;
use wayland_commons::wire::{Argument, ArgumentType, Message, MessageDesc};
use wayland_commons::{Interface, MessageGroup};
include!(concat!(env!("OUT_DIR"), "/fractional_scale_v1.rs"));

View file

@ -1,165 +0,0 @@
//! Handling of various keyboard events.
use std::sync::atomic::Ordering;
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);
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
window_handle.has_focus.store(true, Ordering::Relaxed);
// 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, .. } => {
// Reset the id.
inner.target_window_id = None;
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 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_handle.has_focus.store(false, Ordering::Relaxed);
// Window lost focus.
event_sink.push_window_event(WindowEvent::Focused(false), window_id);
}
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);
}
}
}
}

View file

@ -1,9 +1,9 @@
//! Convert Wayland keys to winit keys.
use sctk::seat::keyboard::keysyms;
use crate::event::VirtualKeyCode;
/// Convert xkb keysym into winit's 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),

View file

@ -1,85 +1,235 @@
//! Wayland keyboard handling.
//! The keyboard input handling.
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Mutex;
use sctk::reexports::client::delegate_dispatch;
use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::Attached;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::reexports::calloop::LoopHandle;
use sctk::seat::keyboard::{KeyEvent, KeyboardData, KeyboardDataExt, KeyboardHandler, Modifiers};
use sctk::seat::SeatState;
use sctk::seat::keyboard;
use crate::event::{ElementState, ModifiersState, WindowEvent};
use crate::event::ModifiersState;
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::WindowId;
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, DeviceId, WindowId};
mod handlers;
mod keymap;
pub(crate) struct Keyboard {
pub keyboard: WlKeyboard,
}
impl WinitState {
pub fn handle_key_input(
&mut self,
keyboard: &WlKeyboard,
event: KeyEvent,
state: ElementState,
) {
let window_id = match *keyboard.winit_data().window_id.lock().unwrap() {
Some(window_id) => window_id,
None => return,
};
impl Keyboard {
pub fn new(
seat: &Attached<WlSeat>,
loop_handle: LoopHandle<'static, WinitState>,
modifiers_state: Rc<RefCell<ModifiersState>>,
) -> Option<Self> {
let mut inner = KeyboardInner::new(modifiers_state);
let keyboard = 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 virtual_keycode = keymap::keysym_to_vkey(event.keysym);
let seat_state = self.seats.get(&keyboard.seat().id()).unwrap();
let modifiers = seat_state.modifiers;
self.events_sink.push_window_event(
#[allow(deprecated)]
WindowEvent::KeyboardInput {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
input: crate::event::KeyboardInput {
state,
scancode: event.raw_code,
virtual_keycode,
modifiers,
},
)
.ok()?;
is_synthetic: false,
},
window_id,
);
Some(Self { keyboard })
}
// Don't send utf8 chars on release.
if state == ElementState::Released {
return;
}
impl Drop for Keyboard {
fn drop(&mut self) {
if self.keyboard.as_ref().version() >= 3 {
self.keyboard.release();
if let Some(txt) = event.utf8 {
for ch in txt.chars() {
self.events_sink
.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
}
}
}
}
struct KeyboardInner {
/// Currently focused surface.
target_window_id: Option<WindowId>,
impl KeyboardHandler for WinitState {
fn enter(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
keyboard: &WlKeyboard,
surface: &WlSurface,
_serial: u32,
_raw: &[u32],
_keysyms: &[u32],
) {
let window_id = wayland::make_wid(surface);
/// 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>,
// Mark the window as focused.
match self.windows.get_mut().get(&window_id) {
Some(window) => window.lock().unwrap().set_has_focus(true),
None => return,
};
/// Current state of modifiers keys.
modifiers_state: Rc<RefCell<ModifiersState>>,
// Window gained focus.
self.events_sink
.push_window_event(WindowEvent::Focused(true), window_id);
*keyboard.winit_data().window_id.lock().unwrap() = Some(window_id);
// NOTE: GNOME still hasn't violates the specification wrt ordering of such
// events. See https://gitlab.gnome.org/GNOME/mutter/-/issues/2231.
let seat_state = self.seats.get_mut(&keyboard.seat().id()).unwrap();
if std::mem::take(&mut seat_state.modifiers_pending) {
self.events_sink.push_window_event(
WindowEvent::ModifiersChanged(seat_state.modifiers),
window_id,
);
}
}
impl KeyboardInner {
fn new(modifiers_state: Rc<RefCell<ModifiersState>>) -> Self {
fn leave(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
keyboard: &WlKeyboard,
surface: &WlSurface,
_serial: u32,
) {
let window_id = wayland::make_wid(surface);
// XXX The check whether the window exists is essential as we might get a nil surface...
match self.windows.get_mut().get(&window_id) {
Some(window) => window.lock().unwrap().set_has_focus(false),
None => return,
};
// Notify that no modifiers are being pressed.
self.events_sink.push_window_event(
WindowEvent::ModifiersChanged(ModifiersState::empty()),
window_id,
);
*keyboard.winit_data().window_id.lock().unwrap() = None;
// Window lost focus.
self.events_sink
.push_window_event(WindowEvent::Focused(false), window_id);
}
fn press_key(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
keyboard: &WlKeyboard,
_serial: u32,
event: KeyEvent,
) {
self.handle_key_input(keyboard, event, ElementState::Pressed);
}
fn release_key(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
keyboard: &WlKeyboard,
_serial: u32,
event: KeyEvent,
) {
self.handle_key_input(keyboard, event, ElementState::Released);
}
// FIXME(kchibisov): recent spec suggested that this event could be sent
// without any focus to indicate modifiers for the pointer, so update
// for will be required once https://github.com/rust-windowing/winit/issues/2768
// wrt winit design of the event is resolved.
fn update_modifiers(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
keyboard: &WlKeyboard,
_serial: u32,
modifiers: Modifiers,
) {
let modifiers = ModifiersState::from(modifiers);
let mut seat_state = self.seats.get_mut(&keyboard.seat().id()).unwrap();
seat_state.modifiers = modifiers;
// NOTE: part of the workaround from `fn enter`, see it above.
let window_id = match *keyboard.winit_data().window_id.lock().unwrap() {
Some(window_id) => window_id,
None => {
seat_state.modifiers_pending = true;
return;
}
};
self.events_sink
.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id);
}
}
/// The extension to KeyboardData used to store the `window_id`.
pub struct WinitKeyboardData {
/// The currently focused window surface. Could be `None` on bugged compositors, like mutter.
window_id: Mutex<Option<WindowId>>,
/// The original keyboard date.
keyboard_data: KeyboardData<WinitState>,
}
impl WinitKeyboardData {
pub fn new(seat: WlSeat) -> Self {
Self {
target_window_id: None,
pending_modifers_state: None,
modifiers_state,
window_id: Default::default(),
keyboard_data: KeyboardData::new(seat),
}
}
}
impl From<keyboard::ModifiersState> for ModifiersState {
fn from(mods: keyboard::ModifiersState) -> ModifiersState {
impl KeyboardDataExt for WinitKeyboardData {
type State = WinitState;
fn keyboard_data(&self) -> &KeyboardData<Self::State> {
&self.keyboard_data
}
fn keyboard_data_mut(&mut self) -> &mut KeyboardData<Self::State> {
&mut self.keyboard_data
}
}
pub trait WinitKeyboardDataExt {
fn winit_data(&self) -> &WinitKeyboardData;
fn seat(&self) -> &WlSeat {
self.winit_data().keyboard_data().seat()
}
}
impl WinitKeyboardDataExt for WlKeyboard {
fn winit_data(&self) -> &WinitKeyboardData {
self.data::<WinitKeyboardData>()
.expect("failed to get keyboard data.")
}
}
impl From<Modifiers> for ModifiersState {
fn from(mods: Modifiers) -> ModifiersState {
let mut wl_mods = ModifiersState::empty();
wl_mods.set(ModifiersState::SHIFT, mods.shift);
wl_mods.set(ModifiersState::CTRL, mods.ctrl);
@ -88,3 +238,5 @@ impl From<keyboard::ModifiersState> for ModifiersState {
wl_mods
}
}
delegate_dispatch!(WinitState: [ WlKeyboard: WinitKeyboardData] => SeatState);

View file

@ -1,208 +1,234 @@
//! Seat handling and managing.
//! Seat handling.
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
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 fnv::FnvHashMap;
use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::Attached;
use sctk::reexports::client::protocol::wl_touch::WlTouch;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::reexports::protocols::wp::relative_pointer::zv1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3;
use sctk::environment::Environment;
use sctk::reexports::calloop::LoopHandle;
use sctk::seat::pointer::ThemeManager;
use sctk::seat::{SeatData, SeatListener};
use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
use super::env::WinitEnv;
use super::event_loop::WinitState;
use crate::event::ModifiersState;
use crate::event::{ElementState, ModifiersState};
use crate::platform_impl::wayland::state::WinitState;
mod keyboard;
pub mod pointer;
pub mod text_input;
mod pointer;
mod text_input;
mod touch;
use keyboard::Keyboard;
use pointer::Pointers;
use text_input::TextInput;
use touch::Touch;
pub use pointer::relative_pointer::RelativePointerState;
pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt};
pub use text_input::{TextInputState, ZwpTextInputV3Ext};
pub struct SeatManager {
/// Listener for seats.
_seat_listener: SeatListener,
use keyboard::WinitKeyboardData;
use text_input::TextInputData;
use touch::TouchPoint;
#[derive(Debug)]
pub struct WinitSeatState {
/// The pointer bound on the seat.
pointer: Option<Arc<ThemedPointer<WinitPointerData>>>,
/// The touch bound on the seat.
touch: Option<WlTouch>,
/// The mapping from touched points to the surfaces they're present.
touch_map: FnvHashMap<i32, TouchPoint>,
/// The text input bound on the seat.
text_input: Option<Arc<ZwpTextInputV3>>,
/// The relative pointer bound on the seat.
relative_pointer: Option<ZwpRelativePointerV1>,
/// The keyboard bound on the seat.
keyboard_state: Option<KeyboardState>,
/// The current modifiers state on the seat.
modifiers: ModifiersState,
/// Wether we have pending modifiers.
modifiers_pending: bool,
}
impl SeatManager {
pub fn new(
env: &Environment<WinitEnv>,
loop_handle: LoopHandle<'static, 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);
});
impl WinitSeatState {
pub fn new() -> Self {
Self {
_seat_listener: seat_listener,
}
}
}
/// Inner state of the seat manager.
struct SeatManagerInner {
/// Currently observed seats.
seats: Vec<SeatInfo>,
/// Loop handle.
loop_handle: LoopHandle<'static, 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<'static, 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,
relative_pointer: None,
text_input: None,
modifiers_state: Rc::new(RefCell::new(ModifiersState::default())),
touch_map: Default::default(),
keyboard_state: None,
modifiers: ModifiersState::empty(),
modifiers_pending: false,
}
}
}
impl SeatHandler for WinitState {
fn seat_state(&mut self) -> &mut SeatState {
&mut self.seat_state
}
fn new_capability(
&mut self,
_: &Connection,
queue_handle: &QueueHandle<Self>,
seat: WlSeat,
capability: SeatCapability,
) {
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
match capability {
SeatCapability::Touch if seat_state.touch.is_none() => {
seat_state.touch = self.seat_state.get_touch(queue_handle, &seat).ok();
}
SeatCapability::Keyboard if seat_state.keyboard_state.is_none() => {
let keyboard = self
.seat_state
.get_keyboard_with_repeat_with_data(
queue_handle,
&seat,
WinitKeyboardData::new(seat.clone()),
self.loop_handle.clone(),
Box::new(|state, keyboard, event| {
state.handle_key_input(keyboard, event, ElementState::Pressed);
}),
)
.expect("failed to create keyboard with present capability.");
seat_state.keyboard_state = Some(KeyboardState { keyboard });
}
SeatCapability::Pointer if seat_state.pointer.is_none() => {
let surface = self.compositor_state.create_surface(queue_handle);
let surface_id = surface.id();
let pointer_data = WinitPointerData::new(seat.clone(), surface);
let themed_pointer = self
.seat_state
.get_pointer_with_theme_and_data(
queue_handle,
&seat,
ThemeSpec::System,
pointer_data,
)
.expect("failed to create pointer with present capability.");
seat_state.relative_pointer = self.relative_pointer.as_ref().map(|manager| {
manager.get_relative_pointer(
themed_pointer.pointer(),
queue_handle,
sctk::globals::GlobalData,
)
});
let themed_pointer = Arc::new(themed_pointer);
// Register cursor surface.
self.pointer_surfaces
.insert(surface_id, themed_pointer.clone());
seat_state.pointer = Some(themed_pointer);
}
_ => (),
}
if let Some(text_input_state) = seat_state
.text_input
.is_none()
.then_some(self.text_input_state.as_ref())
.flatten()
{
seat_state.text_input = Some(Arc::new(text_input_state.get_text_input(
&seat,
queue_handle,
TextInputData::default(),
)));
}
}
fn remove_capability(
&mut self,
_: &Connection,
_queue_handle: &QueueHandle<Self>,
seat: WlSeat,
capability: SeatCapability,
) {
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
match capability {
SeatCapability::Touch => {
if let Some(touch) = seat_state.touch.take() {
if touch.version() >= 3 {
touch.release();
}
}
}
SeatCapability::Pointer => {
if let Some(relative_pointer) = seat_state.relative_pointer.take() {
relative_pointer.destroy();
}
if let Some(pointer) = seat_state.pointer.take() {
let pointer_data = pointer.pointer().winit_data();
// Remove the cursor from the mapping.
let surface_id = pointer_data.cursor_surface().id();
let _ = self.pointer_surfaces.remove(&surface_id);
// Remove the inner locks/confines before dropping the pointer.
pointer_data.unlock_pointer();
pointer_data.unconfine_pointer();
if pointer.pointer().version() >= 3 {
pointer.pointer().release();
}
}
}
SeatCapability::Keyboard => {
if let Some(keyboard_state) = seat_state.keyboard_state.take() {
if keyboard_state.keyboard.version() >= 3 {
keyboard_state.keyboard.release();
}
}
}
_ => (),
}
if let Some(text_input) = seat_state.text_input.take() {
text_input.destroy();
}
}
fn new_seat(
&mut self,
_connection: &Connection,
_queue_handle: &QueueHandle<Self>,
seat: WlSeat,
) {
self.seats.insert(seat.id(), WinitSeatState::new());
}
fn remove_seat(
&mut self,
_connection: &Connection,
_queue_handle: &QueueHandle<Self>,
seat: WlSeat,
) {
let _ = self.seats.remove(&seat.id());
}
}
#[derive(Debug)]
pub struct KeyboardState {
/// The underlying WlKeyboard.
pub keyboard: WlKeyboard,
}
sctk::delegate_seat!(WinitState);

View file

@ -1,82 +0,0 @@
//! 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 sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
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>>>,
pub locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
/// Latest observed serial in pointer events.
pub latest_serial: Rc<Cell<u32>>,
/// Latest observed serial in pointer enter events.
pub latest_enter_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>>>,
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
modifiers_state: Rc<RefCell<ModifiersState>>,
) -> Self {
Self {
surface: None,
latest_serial: Rc::new(Cell::new(0)),
latest_enter_serial: Rc::new(Cell::new(0)),
confined_pointer,
locked_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,
}
}
}

View file

@ -1,327 +0,0 @@
//! 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::client::protocol::wl_seat::WlSeat;
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};
// These values are comming from <linux/input-event-codes.h>.
const BTN_LEFT: u32 = 0x110;
const BTN_RIGHT: u32 = 0x111;
const BTN_MIDDLE: u32 = 0x112;
#[inline]
pub(super) fn handle_pointer(
pointer: ThemedPointer,
event: PointerEvent,
pointer_data: &Rc<RefCell<PointerData>>,
winit_state: &mut WinitState,
seat: WlSeat,
) {
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);
pointer_data.latest_enter_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,
};
let scale_factor = window_handle.scale_factor();
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),
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
pointer_constraints: pointer_data.pointer_constraints.clone(),
latest_serial: pointer_data.latest_serial.clone(),
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
seat,
};
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),
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
pointer_constraints: pointer_data.pointer_constraints.clone(),
latest_serial: pointer_data.latest_serial.clone(),
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
seat,
};
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 window_handle = match winit_state.window_map.get(&window_id) {
Some(w) => w,
_ => return,
};
let scale_factor = window_handle.scale_factor();
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 {
BTN_LEFT => MouseButton::Left,
BTN_RIGHT => MouseButton::Right,
BTN_MIDDLE => MouseButton::Middle,
button => MouseButton::Other(button as u16),
};
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);
let window_handle = match winit_state.window_map.get(&window_id) {
Some(w) => w,
_ => return,
};
if pointer.as_ref().version() < 5 {
let (mut x, mut y) = (0.0, 0.0);
// Old seat compatibility.
match axis {
// Wayland 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 = window_handle.scale_factor();
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 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 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_handle = match winit_state.window_map.get(&window_id) {
Some(w) => w,
_ => return,
};
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 = window_handle.scale_factor();
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_unaccel,
dy_unaccel,
..
} = event
{
winit_state.event_sink.push_device_event(
DeviceEvent::MouseMotion {
delta: (dx_unaccel, dy_unaccel),
},
DeviceId,
)
}
}

View file

@ -1,343 +1,491 @@
//! All pointer related handling.
//! The pointer events.
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use std::ops::Deref;
use std::sync::{Arc, Mutex};
use sctk::reexports::client::delegate_dispatch;
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::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
use sctk::reexports::client::{Connection, Proxy, QueueHandle, Dispatch};
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1};
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::seat::pointer::{ThemeManager, ThemedPointer};
use sctk::window::Window;
use sctk::compositor::SurfaceData;
use sctk::globals::GlobalData;
use sctk::seat::pointer::{PointerData, PointerDataExt};
use sctk::seat::pointer::{PointerEvent, PointerEventKind, PointerHandler};
use sctk::seat::SeatState;
use sctk::shell::xdg::frame::FrameClick;
use crate::event::ModifiersState;
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::window::WinitFrame;
use crate::window::CursorIcon;
use crate::dpi::{LogicalPosition, PhysicalPosition};
use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent};
mod data;
mod handlers;
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, DeviceId, WindowId};
use data::PointerData;
pub mod relative_pointer;
/// A proxy to Wayland pointer, which serves requests from a `WindowHandle`.
pub struct WinitPointer {
pointer: ThemedPointer,
impl PointerHandler for WinitState {
fn pointer_frame(
&mut self,
connection: &Connection,
_: &QueueHandle<Self>,
pointer: &WlPointer,
events: &[PointerEvent],
) {
let seat = pointer.winit_data().seat();
let seat_state = self.seats.get(&seat.id()).unwrap();
let modifiers = seat_state.modifiers;
/// Create confined pointers.
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
/// Cursor to handle confine requests.
confined_pointer: Weak<RefCell<Option<ZwpConfinedPointerV1>>>,
for event in events {
let surface = &event.surface;
/// Cursor to handle locked requests.
locked_pointer: Weak<RefCell<Option<ZwpLockedPointerV1>>>,
/// Latest observed serial in pointer events.
/// used by Window::start_interactive_move()
latest_serial: Rc<Cell<u32>>,
/// Latest observed serial in pointer enter events.
/// used by Window::set_cursor()
latest_enter_serial: Rc<Cell<u32>>,
/// Seat.
seat: WlSeat,
}
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.
// WlPointer::set_cursor() expects the serial of the last *enter*
// event (compare to to start_interactive_move()).
(*self.pointer).set_cursor(self.latest_enter_serial.get(), None, 0, 0);
return;
}
// The parent surface.
let parent_surface = match event.surface.data::<SurfaceData>() {
Some(data) => data.parent_surface().unwrap_or(surface),
None => continue,
};
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 => &["hand2", "hand1"],
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"],
let window_id = wayland::make_wid(parent_surface);
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_fdiag"],
CursorIcon::NeswResize => &["fd_double_arrow", "size_bdiag"],
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"],
// Ensure that window exists.
let mut window = match self.windows.get_mut().get_mut(&window_id) {
Some(window) => window.lock().unwrap(),
None => continue,
};
let serial = Some(self.latest_enter_serial.get());
for cursor in cursors {
if self.pointer.set_cursor(cursor, serial).is_ok() {
return;
}
}
warn!("Failed to set cursor to {:?}", cursor_icon);
}
let scale_factor = window.scale_factor();
let position: PhysicalPosition<f64> =
LogicalPosition::new(event.position.0, event.position.1).to_physical(scale_factor);
/// 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,
};
match event.kind {
// Pointer movements on decorations.
PointerEventKind::Enter { .. } | PointerEventKind::Motion { .. }
if parent_surface != surface =>
{
if let Some(icon) =
window.frame_point_moved(surface, event.position.0, event.position.1)
{
if let Some(pointer) = seat_state.pointer.as_ref() {
let surface = pointer
.pointer()
.data::<WinitPointerData>()
.unwrap()
.cursor_surface();
let scale_factor =
surface.data::<SurfaceData>().unwrap().scale_factor();
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,
let _ = pointer.set_cursor(
connection,
icon,
self.shm.wl_shm(),
surface,
&self.pointer,
));
scale_factor,
);
}
/// 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,
}
}
PointerEventKind::Leave { .. } if parent_surface != surface => {
window.frame_point_left();
}
ref kind @ PointerEventKind::Press { button, serial, .. }
| ref kind @ PointerEventKind::Release { button, serial, .. }
if parent_surface != surface =>
{
let click = match wayland_button_to_winit(button) {
MouseButton::Left => FrameClick::Normal,
MouseButton::Right => FrameClick::Alternate,
_ => continue,
};
let pressed = matches!(kind, PointerEventKind::Press { .. });
let mut confined_pointer = confined_pointer.borrow_mut();
if let Some(confined_pointer) = confined_pointer.take() {
confined_pointer.destroy();
}
}
pub fn lock(&self, surface: &WlSurface) {
let pointer_constraints = match &self.pointer_constraints {
Some(pointer_constraints) => pointer_constraints,
None => return,
};
let locked_pointer = match self.locked_pointer.upgrade() {
Some(locked_pointer) => locked_pointer,
// A pointer is gone.
None => return,
};
*locked_pointer.borrow_mut() = Some(init_locked_pointer(
pointer_constraints,
surface,
&self.pointer,
));
}
pub fn unlock(&self) {
let locked_pointer = match self.locked_pointer.upgrade() {
Some(locked_pointer) => locked_pointer,
// A pointer is gone.
None => return,
};
let mut locked_pointer = locked_pointer.borrow_mut();
if let Some(locked_pointer) = locked_pointer.take() {
locked_pointer.destroy();
}
}
pub fn set_cursor_position(&self, surface_x: u32, surface_y: u32) {
let locked_pointer = match self.locked_pointer.upgrade() {
Some(locked_pointer) => locked_pointer,
// A pointer is gone.
None => return,
};
let locked_pointer = locked_pointer.borrow_mut();
if let Some(locked_pointer) = locked_pointer.as_ref() {
locked_pointer.set_cursor_position_hint(surface_x.into(), surface_y.into());
}
}
pub fn drag_window(&self, window: &Window<WinitFrame>) {
// WlPointer::setart_interactive_move() expects the last serial of *any*
// pointer event (compare to set_cursor()).
window.start_interactive_move(&self.seat, self.latest_serial.get());
}
}
/// 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>>>,
/// Locked pointer.
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
}
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 locked_pointer = Rc::new(RefCell::new(None));
let pointer_data = Rc::new(RefCell::new(PointerData::new(
confined_pointer.clone(),
locked_pointer.clone(),
pointer_constraints.clone(),
modifiers_state,
)));
let pointer_seat = seat.detach();
let pointer = theme_manager.theme_pointer_with_impl(
// Emulate click on the frame.
window.frame_click(
click,
pressed,
seat,
move |event, pointer, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_pointer(
pointer,
event,
&pointer_data,
winit_state,
pointer_seat.clone(),
serial,
window_id,
&mut self.window_compositor_updates,
);
}
// Regular events on the main surface.
PointerEventKind::Enter { .. } => {
self.events_sink
.push_window_event(WindowEvent::CursorEntered { device_id }, window_id);
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
window.pointer_entered(pointer);
}
// Set the currently focused surface.
pointer.winit_data().inner.lock().unwrap().surface = Some(window_id);
self.events_sink.push_window_event(
WindowEvent::CursorMoved {
device_id,
position,
modifiers,
},
window_id,
);
}
PointerEventKind::Leave { .. } => {
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
window.pointer_left(pointer);
}
// Setup relative_pointer if it's available.
let relative_pointer = relative_pointer_manager
.as_ref()
.map(|relative_pointer_manager| {
init_relative_pointer(relative_pointer_manager, &pointer)
});
// Remove the active surface.
pointer.winit_data().inner.lock().unwrap().surface = None;
self.events_sink
.push_window_event(WindowEvent::CursorLeft { device_id }, window_id);
}
PointerEventKind::Motion { .. } => {
self.events_sink.push_window_event(
WindowEvent::CursorMoved {
device_id,
position,
modifiers,
},
window_id,
);
}
ref kind @ PointerEventKind::Press { button, serial, .. }
| ref kind @ PointerEventKind::Release { button, serial, .. } => {
// Update the last button serial.
pointer
.winit_data()
.inner
.lock()
.unwrap()
.latest_button_serial = serial;
let button = wayland_button_to_winit(button);
let state = if matches!(kind, PointerEventKind::Press { .. }) {
ElementState::Pressed
} else {
ElementState::Released
};
self.events_sink.push_window_event(
WindowEvent::MouseInput {
device_id,
state,
button,
modifiers,
},
window_id,
);
}
PointerEventKind::Axis {
horizontal,
vertical,
..
} => {
// Get the current phase.
let mut pointer_data = pointer.winit_data().inner.lock().unwrap();
let has_discrete_scroll = horizontal.discrete != 0 || vertical.discrete != 0;
// Figure out what to do about start/ended phases here.
//
// Figure out how to deal with `Started`. Also the `Ended` is not guaranteed
// to be sent for mouse wheels.
let phase = if horizontal.stop || vertical.stop {
TouchPhase::Ended
} else {
match pointer_data.phase {
// Descrete scroll only results in moved events.
_ if has_discrete_scroll => TouchPhase::Moved,
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
_ => TouchPhase::Started,
}
};
// Update the phase.
pointer_data.phase = phase;
// Mice events have both pixel and discrete delta's at the same time. So prefer
// the descrite values if they are present.
let delta = if has_discrete_scroll {
// XXX Wayland sign convention is the inverse of winit.
MouseScrollDelta::LineDelta(
(-horizontal.discrete) as f32,
(-vertical.discrete) as f32,
)
} else {
// XXX Wayland sign convention is the inverse of winit.
MouseScrollDelta::PixelDelta(
LogicalPosition::new(-horizontal.absolute, -vertical.absolute)
.to_physical(scale_factor),
)
};
self.events_sink.push_window_event(
WindowEvent::MouseWheel {
device_id,
delta,
phase,
modifiers,
},
window_id,
)
}
}
}
}
}
#[derive(Debug)]
pub struct WinitPointerData {
/// The surface associated with this pointer, which is used for icons.
cursor_surface: WlSurface,
/// The inner winit data associated with the pointer.
inner: Mutex<WinitPointerDataInner>,
/// The data required by the sctk.
sctk_data: PointerData,
}
impl WinitPointerData {
pub fn new(seat: WlSeat, surface: WlSurface) -> Self {
Self {
cursor_surface: surface,
inner: Mutex::new(WinitPointerDataInner::default()),
sctk_data: PointerData::new(seat),
}
}
pub fn lock_pointer(
&self,
pointer_constraints: &PointerConstraintsState,
surface: &WlSurface,
pointer: &WlPointer,
queue_handle: &QueueHandle<WinitState>,
) {
let mut inner = self.inner.lock().unwrap();
if inner.locked_pointer.is_none() {
inner.locked_pointer = Some(pointer_constraints.lock_pointer(
surface,
pointer,
relative_pointer,
confined_pointer,
locked_pointer,
}
None,
Lifetime::Persistent,
queue_handle,
GlobalData,
));
}
}
impl Drop for Pointers {
fn drop(&mut self) {
// Drop relative pointer.
if let Some(relative_pointer) = self.relative_pointer.take() {
relative_pointer.destroy();
pub fn unlock_pointer(&self) {
let mut inner = self.inner.lock().unwrap();
if let Some(locked_pointer) = inner.locked_pointer.take() {
locked_pointer.destroy();
}
}
// Drop confined pointer.
if let Some(confined_pointer) = self.confined_pointer.borrow_mut().take() {
pub fn confine_pointer(
&self,
pointer_constraints: &PointerConstraintsState,
surface: &WlSurface,
pointer: &WlPointer,
queue_handle: &QueueHandle<WinitState>,
) {
self.inner.lock().unwrap().confined_pointer = Some(pointer_constraints.confine_pointer(
surface,
pointer,
None,
Lifetime::Persistent,
queue_handle,
GlobalData,
));
}
pub fn unconfine_pointer(&self) {
let inner = self.inner.lock().unwrap();
if let Some(confined_pointer) = inner.confined_pointer.as_ref() {
confined_pointer.destroy();
}
}
// Drop lock ponter.
if let Some(locked_pointer) = self.locked_pointer.borrow_mut().take() {
/// Seat associated with this pointer.
pub fn seat(&self) -> &WlSeat {
self.sctk_data.seat()
}
/// The WlSurface used to set cursor theme.
pub fn cursor_surface(&self) -> &WlSurface {
&self.cursor_surface
}
/// Active window.
pub fn focused_window(&self) -> Option<WindowId> {
self.inner.lock().unwrap().surface
}
/// Last button serial.
pub fn latest_button_serial(&self) -> u32 {
self.inner.lock().unwrap().latest_button_serial
}
/// Last enter serial.
pub fn latest_enter_serial(&self) -> u32 {
self.sctk_data.latest_enter_serial().unwrap_or_default()
}
pub fn set_locked_cursor_position(&self, surface_x: f64, surface_y: f64) {
let inner = self.inner.lock().unwrap();
if let Some(locked_pointer) = inner.locked_pointer.as_ref() {
locked_pointer.set_cursor_position_hint(surface_x, surface_y);
}
}
}
impl Drop for WinitPointerData {
fn drop(&mut self) {
self.cursor_surface.destroy();
}
}
impl PointerDataExt for WinitPointerData {
fn pointer_data(&self) -> &PointerData {
&self.sctk_data
}
}
#[derive(Debug)]
pub struct WinitPointerDataInner {
/// The associated locked pointer.
locked_pointer: Option<ZwpLockedPointerV1>,
/// The associated confined pointer.
confined_pointer: Option<ZwpConfinedPointerV1>,
/// Serial of the last button event.
latest_button_serial: u32,
/// Currently focused window.
surface: Option<WindowId>,
/// Current axis phase.
phase: TouchPhase,
}
impl Drop for WinitPointerDataInner {
fn drop(&mut self) {
if let Some(locked_pointer) = self.locked_pointer.take() {
locked_pointer.destroy();
}
// Drop the pointer itself in case it's possible.
if self.pointer.as_ref().version() >= 3 {
self.pointer.release();
if let Some(confined_pointer) = self.confined_pointer.take() {
confined_pointer.destroy();
}
}
}
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()
impl Default for WinitPointerDataInner {
fn default() -> Self {
Self {
surface: None,
locked_pointer: None,
confined_pointer: None,
latest_button_serial: 0,
phase: TouchPhase::Ended,
}
}
}
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);
/// Convert the Wayland button into winit.
fn wayland_button_to_winit(button: u32) -> MouseButton {
// These values are comming from <linux/input-event-codes.h>.
const BTN_LEFT: u32 = 0x110;
const BTN_RIGHT: u32 = 0x111;
const BTN_MIDDLE: u32 = 0x112;
confined_pointer.quick_assign(move |_, _, _| {});
confined_pointer.detach()
match button {
BTN_LEFT => MouseButton::Left,
BTN_RIGHT => MouseButton::Right,
BTN_MIDDLE => MouseButton::Middle,
button => MouseButton::Other(button as u16),
}
}
pub(super) fn init_locked_pointer(
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
surface: &WlSurface,
pointer: &WlPointer,
) -> ZwpLockedPointerV1 {
let locked_pointer =
pointer_constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent);
locked_pointer.quick_assign(move |_, _, _| {});
locked_pointer.detach()
pub trait WinitPointerDataExt {
fn winit_data(&self) -> &WinitPointerData;
}
impl WinitPointerDataExt for WlPointer {
fn winit_data(&self) -> &WinitPointerData {
self.data::<WinitPointerData>()
.expect("failed to get pointer data.")
}
}
pub struct PointerConstraintsState {
pointer_constraints: ZwpPointerConstraintsV1,
}
impl PointerConstraintsState {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let pointer_constraints = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self {
pointer_constraints,
})
}
}
impl Deref for PointerConstraintsState {
type Target = ZwpPointerConstraintsV1;
fn deref(&self) -> &Self::Target {
&self.pointer_constraints
}
}
impl Dispatch<ZwpPointerConstraintsV1, GlobalData, WinitState> for PointerConstraintsState {
fn event(
_state: &mut WinitState,
_proxy: &ZwpPointerConstraintsV1,
_event: <ZwpPointerConstraintsV1 as wayland_client::Proxy>::Event,
_data: &GlobalData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
}
}
impl Dispatch<ZwpLockedPointerV1, GlobalData, WinitState> for PointerConstraintsState {
fn event(
_state: &mut WinitState,
_proxy: &ZwpLockedPointerV1,
_event: <ZwpLockedPointerV1 as wayland_client::Proxy>::Event,
_data: &GlobalData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
}
}
impl Dispatch<ZwpConfinedPointerV1, GlobalData, WinitState> for PointerConstraintsState {
fn event(
_state: &mut WinitState,
_proxy: &ZwpConfinedPointerV1,
_event: <ZwpConfinedPointerV1 as wayland_client::Proxy>::Event,
_data: &GlobalData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
}
}
delegate_dispatch!(WinitState: [ WlPointer: WinitPointerData] => SeatState);
delegate_dispatch!(WinitState: [ZwpPointerConstraintsV1: GlobalData] => PointerConstraintsState);
delegate_dispatch!(WinitState: [ZwpLockedPointerV1: GlobalData] => PointerConstraintsState);
delegate_dispatch!(WinitState: [ZwpConfinedPointerV1: GlobalData] => PointerConstraintsState);

View file

@ -0,0 +1,80 @@
//! Relative pointer.
use std::ops::Deref;
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::{delegate_dispatch, Dispatch};
use sctk::reexports::client::{Connection, QueueHandle};
use sctk::reexports::protocols::wp::relative_pointer::zv1::{
client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
client::zwp_relative_pointer_v1::{self, ZwpRelativePointerV1},
};
use sctk::globals::GlobalData;
use crate::event::DeviceEvent;
use crate::platform_impl::wayland::state::WinitState;
/// Wrapper around the relative pointer.
pub struct RelativePointerState {
manager: ZwpRelativePointerManagerV1,
}
impl RelativePointerState {
/// Create new relative pointer manager.
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { manager })
}
}
impl Deref for RelativePointerState {
type Target = ZwpRelativePointerManagerV1;
fn deref(&self) -> &Self::Target {
&self.manager
}
}
impl Dispatch<ZwpRelativePointerManagerV1, GlobalData, WinitState> for RelativePointerState {
fn event(
_state: &mut WinitState,
_proxy: &ZwpRelativePointerManagerV1,
_event: <ZwpRelativePointerManagerV1 as wayland_client::Proxy>::Event,
_data: &GlobalData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
}
}
impl Dispatch<ZwpRelativePointerV1, GlobalData, WinitState> for RelativePointerState {
fn event(
state: &mut WinitState,
_proxy: &ZwpRelativePointerV1,
event: <ZwpRelativePointerV1 as wayland_client::Proxy>::Event,
_data: &GlobalData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
if let zwp_relative_pointer_v1::Event::RelativeMotion {
dx_unaccel,
dy_unaccel,
..
} = event
{
state.events_sink.push_device_event(
DeviceEvent::MouseMotion {
delta: (dx_unaccel, dy_unaccel),
},
super::DeviceId,
);
}
}
}
delegate_dispatch!(WinitState: [ZwpRelativePointerV1: GlobalData] => RelativePointerState);
delegate_dispatch!(WinitState: [ZwpRelativePointerManagerV1: GlobalData] => RelativePointerState);

View file

@ -1,122 +0,0 @@
//! 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::{Ime, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::event_loop::WinitState;
use super::{Preedit, TextInputHandler, TextInputInner, ZwpTextInputV3Ext};
#[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.
if window_handle.ime_allowed.get() {
text_input.enable();
text_input.set_content_type_by_purpose(window_handle.ime_purpose.get());
text_input.commit();
event_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
}
// 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);
event_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
}
TextInputEvent::PreeditString {
text,
cursor_begin,
cursor_end,
} => {
let text = text.unwrap_or_default();
let cursor_begin = usize::try_from(cursor_begin)
.ok()
.and_then(|idx| text.is_char_boundary(idx).then_some(idx));
let cursor_end = usize::try_from(cursor_end)
.ok()
.and_then(|idx| text.is_char_boundary(idx).then_some(idx));
inner.pending_preedit = Some(Preedit {
text,
cursor_begin,
cursor_end,
});
}
TextInputEvent::CommitString { text } => {
// Update currenly commited string and reset previous preedit.
inner.pending_preedit = None;
inner.pending_commit = Some(text.unwrap_or_default());
}
TextInputEvent::Done { .. } => {
let window_id = match inner.target_window_id {
Some(window_id) => window_id,
_ => return,
};
// Clear preedit at the start of `Done`.
event_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
window_id,
);
// Send `Commit`.
if let Some(text) = inner.pending_commit.take() {
event_sink.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
}
// Send preedit.
if let Some(preedit) = inner.pending_preedit.take() {
let cursor_range = preedit
.cursor_begin
.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
event_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)),
window_id,
);
}
}
_ => (),
}
}

View file

@ -1,23 +1,174 @@
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::{
use std::ops::Deref;
use sctk::globals::GlobalData;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::reexports::client::delegate_dispatch;
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Dispatch;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::Event as TextInputEvent;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
ContentHint, ContentPurpose, ZwpTextInputV3,
};
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::WindowId;
use crate::event::{Ime, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::state::WinitState;
use crate::window::ImePurpose;
mod handlers;
/// A handler for text input that we're advertising for `WindowHandle`.
#[derive(Eq, PartialEq)]
pub struct TextInputHandler {
text_input: ZwpTextInputV3,
pub struct TextInputState {
text_input_manager: ZwpTextInputManagerV3,
}
trait ZwpTextInputV3Ext {
impl TextInputState {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let text_input_manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { text_input_manager })
}
}
impl Deref for TextInputState {
type Target = ZwpTextInputManagerV3;
fn deref(&self) -> &Self::Target {
&self.text_input_manager
}
}
impl Dispatch<ZwpTextInputManagerV3, GlobalData, WinitState> for TextInputState {
fn event(
_state: &mut WinitState,
_proxy: &ZwpTextInputManagerV3,
_event: <ZwpTextInputManagerV3 as Proxy>::Event,
_data: &GlobalData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
}
}
impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
fn event(
state: &mut WinitState,
text_input: &ZwpTextInputV3,
event: <ZwpTextInputV3 as Proxy>::Event,
data: &TextInputData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
let windows = state.windows.get_mut();
let mut text_input_data = data.inner.lock().unwrap();
match event {
TextInputEvent::Enter { surface } => {
let window_id = wayland::make_wid(&surface);
text_input_data.surface = Some(surface);
let mut window = match windows.get(&window_id) {
Some(window) => window.lock().unwrap(),
None => return,
};
if window.ime_allowed() {
text_input.enable();
text_input.set_content_type_by_purpose(window.ime_purpose());
text_input.commit();
state
.events_sink
.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
}
window.text_input_entered(text_input);
}
TextInputEvent::Leave { surface } => {
text_input_data.surface = None;
// Always issue a disable.
text_input.disable();
text_input.commit();
let window_id = wayland::make_wid(&surface);
// XXX this check is essential, because `leave` could have a
// refence to nil surface...
let mut window = match windows.get(&window_id) {
Some(window) => window.lock().unwrap(),
None => return,
};
window.text_input_left(text_input);
state
.events_sink
.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
}
TextInputEvent::PreeditString {
text,
cursor_begin,
cursor_end,
} => {
let text = text.unwrap_or_default();
let cursor_begin = usize::try_from(cursor_begin)
.ok()
.and_then(|idx| text.is_char_boundary(idx).then_some(idx));
let cursor_end = usize::try_from(cursor_end)
.ok()
.and_then(|idx| text.is_char_boundary(idx).then_some(idx));
text_input_data.pending_preedit = Some(Preedit {
text,
cursor_begin,
cursor_end,
})
}
TextInputEvent::CommitString { text } => {
text_input_data.pending_preedit = None;
text_input_data.pending_commit = text;
}
TextInputEvent::Done { .. } => {
let window_id = match text_input_data.surface.as_ref() {
Some(surface) => wayland::make_wid(surface),
None => return,
};
// Clear preedit at the start of `Done`.
state.events_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
window_id,
);
// Send `Commit`.
if let Some(text) = text_input_data.pending_commit.take() {
state
.events_sink
.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
}
// Send preedit.
if let Some(preedit) = text_input_data.pending_preedit.take() {
let cursor_range = preedit
.cursor_begin
.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
state.events_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)),
window_id,
);
}
}
TextInputEvent::DeleteSurroundingText { .. } => {
// Not handled.
}
_ => {}
}
}
}
pub trait ZwpTextInputV3Ext {
fn set_content_type_by_purpose(&self, purpose: ImePurpose);
}
@ -32,81 +183,30 @@ impl ZwpTextInputV3Ext for 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();
/// The Data associated with the text input.
#[derive(Default)]
pub struct TextInputData {
inner: std::sync::Mutex<TextInputDataInner>,
}
#[inline]
pub fn set_content_type_by_purpose(&self, purpose: ImePurpose) {
self.text_input.set_content_type_by_purpose(purpose);
self.text_input.commit();
}
#[derive(Default)]
pub struct TextInputDataInner {
/// The `WlSurface` we're performing input to.
surface: Option<WlSurface>,
#[inline]
pub fn set_input_allowed(&self, allowed: Option<ImePurpose>) {
if let Some(purpose) = allowed {
self.text_input.set_content_type_by_purpose(purpose);
self.text_input.enable();
} else {
self.text_input.disable();
}
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 commit event which will be dispatched on `text_input_v3::Done`.
/// The commit to submit on `done`.
pending_commit: Option<String>,
/// Pending preedit event which will be dispatched on `text_input_v3::Done`.
/// The preedit to submit on `done`.
pending_preedit: Option<Preedit>,
}
/// The state of the preedit.
struct Preedit {
text: String,
cursor_begin: Option<usize>,
cursor_end: Option<usize>,
}
impl TextInputInner {
fn new() -> Self {
Self {
target_window_id: None,
pending_commit: None,
pending_preedit: None,
}
}
}
delegate_dispatch!(WinitState: [ZwpTextInputManagerV3: GlobalData] => TextInputState);
delegate_dispatch!(WinitState: [ZwpTextInputV3: TextInputData] => TextInputState);

View file

@ -1,144 +0,0 @@
//! 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);
let window_handle = match winit_state.window_map.get(&window_id) {
Some(w) => w,
_ => return,
};
let scale_factor = window_handle.scale_factor();
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,
);
// For `TouchEvent::Up` we don't receive a position, so we're tracking active
// touch points. Update either a known touch id or register a new one.
if let Some(i) = inner.touch_points.iter().position(|p| p.id == id) {
inner.touch_points[i].position = position;
} else {
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 window_id = wayland::make_wid(&touch_point.surface);
let window_handle = match winit_state.window_map.get(&window_id) {
Some(w) => w,
_ => return,
};
let scale_factor = window_handle.scale_factor();
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,
};
let window_id = wayland::make_wid(&touch_point.surface);
let window_handle = match winit_state.window_map.get(&window_id) {
Some(w) => w,
_ => return,
};
touch_point.position = LogicalPosition::new(x, y);
let scale_factor = window_handle.scale_factor();
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 window_id = wayland::make_wid(&touch_point.surface);
let window_handle = match winit_state.window_map.get(&window_id) {
Some(w) => w,
_ => return,
};
let scale_factor = window_handle.scale_factor();
let location = touch_point.position.to_physical(scale_factor);
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,
);
}
}
_ => (),
}
}

View file

@ -3,76 +3,197 @@
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 sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::seat::touch::{TouchData, TouchHandler};
use crate::dpi::LogicalPosition;
use crate::event::{Touch, TouchPhase, WindowEvent};
use crate::platform_impl::wayland::event_loop::WinitState;
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, DeviceId};
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.
impl TouchHandler for WinitState {
fn down(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
touch: &WlTouch,
_: u32,
_: u32,
surface: WlSurface,
/// Location of the touch point.
position: LogicalPosition<f64>,
/// Id.
id: i32,
position: (f64, f64),
) {
let window_id = wayland::make_wid(&surface);
let scale_factor = match self.windows.get_mut().get(&window_id) {
Some(window) => window.lock().unwrap().scale_factor(),
None => return,
};
let location = LogicalPosition::<f64>::from(position);
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
// Update the state of the point.
seat_state
.touch_map
.insert(id, TouchPoint { surface, location });
self.events_sink.push_window_event(
WindowEvent::Touch(Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Started,
location: location.to_physical(scale_factor),
force: None,
id: id as u64,
}),
window_id,
);
}
impl TouchPoint {
pub fn new(surface: WlSurface, position: LogicalPosition<f64>, id: i32) -> Self {
Self {
surface,
position,
id,
fn up(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
touch: &WlTouch,
_: u32,
_: u32,
id: i32,
) {
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
// Remove the touch point.
let touch_point = match seat_state.touch_map.remove(&id) {
Some(touch_point) => touch_point,
None => return,
};
let window_id = wayland::make_wid(&touch_point.surface);
let scale_factor = match self.windows.get_mut().get(&window_id) {
Some(window) => window.lock().unwrap().scale_factor(),
None => return,
};
self.events_sink.push_window_event(
WindowEvent::Touch(Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Ended,
location: touch_point.location.to_physical(scale_factor),
force: None,
id: id as u64,
}),
window_id,
);
}
fn motion(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
touch: &WlTouch,
_: u32,
id: i32,
position: (f64, f64),
) {
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
// Remove the touch point.
let mut touch_point = match seat_state.touch_map.get_mut(&id) {
Some(touch_point) => touch_point,
None => return,
};
let window_id = wayland::make_wid(&touch_point.surface);
let scale_factor = match self.windows.get_mut().get(&window_id) {
Some(window) => window.lock().unwrap().scale_factor(),
None => return,
};
touch_point.location = LogicalPosition::<f64>::from(position);
self.events_sink.push_window_event(
WindowEvent::Touch(Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Cancelled,
location: touch_point.location.to_physical(scale_factor),
force: None,
id: id as u64,
}),
window_id,
);
}
fn cancel(&mut self, _: &Connection, _: &QueueHandle<Self>, touch: &WlTouch) {
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
for (id, touch_point) in seat_state.touch_map.drain() {
let window_id = wayland::make_wid(&touch_point.surface);
let scale_factor = match self.windows.get_mut().get(&window_id) {
Some(window) => window.lock().unwrap().scale_factor(),
None => return,
};
let location = touch_point.location.to_physical(scale_factor);
self.events_sink.push_window_event(
WindowEvent::Touch(Touch {
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
DeviceId,
)),
phase: TouchPhase::Cancelled,
location,
force: None,
id: id as u64,
}),
window_id,
);
}
}
fn shape(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlTouch,
_: i32,
_: f64,
_: f64,
) {
// Blank.
}
fn orientation(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlTouch, _: i32, _: f64) {
// Blank.
}
}
/// The state of the touch point.
#[derive(Debug)]
pub struct TouchPoint {
/// The surface on which the point is present.
pub surface: WlSurface,
/// The location of the point on the surface.
pub location: LogicalPosition<f64>,
}
pub trait TouchDataExt {
fn seat(&self) -> &WlSeat;
}
impl TouchDataExt for WlTouch {
fn seat(&self) -> &WlSeat {
self.data::<TouchData>()
.expect("failed to get touch data.")
.seat()
}
}
sctk::delegate_touch!(WinitState);

View file

@ -0,0 +1,368 @@
use std::cell::RefCell;
use std::error::Error;
use std::sync::{Arc, Mutex};
use fnv::FnvHashMap;
use sctk::reexports::calloop::LoopHandle;
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::globals::GlobalList;
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::compositor::{CompositorHandler, CompositorState};
use sctk::output::{OutputHandler, OutputState};
use sctk::registry::{ProvidesRegistryState, RegistryState};
use sctk::seat::pointer::ThemedPointer;
use sctk::seat::SeatState;
use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler};
use sctk::shell::xdg::XdgShell;
use sctk::shell::WaylandSurface;
use sctk::shm::{Shm, ShmHandler};
use sctk::subcompositor::SubcompositorState;
use crate::dpi::LogicalSize;
use super::event_loop::sink::EventSink;
use super::output::MonitorHandle;
use super::seat::{
PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData,
WinitPointerDataExt, WinitSeatState,
};
use super::types::wp_fractional_scaling::FractionalScalingManager;
use super::types::wp_viewporter::ViewporterState;
use super::types::xdg_activation::XdgActivationState;
use super::window::{WindowRequests, WindowState};
use super::WindowId;
/// Winit's Wayland state.
pub struct WinitState {
/// The WlRegistry.
pub registry_state: RegistryState,
/// The state of the WlOutput handling.
pub output_state: OutputState,
/// The compositor state which is used to create new windows and regions.
pub compositor_state: Arc<CompositorState>,
/// The state of the subcompositor.
pub subcompositor_state: Arc<SubcompositorState>,
/// The seat state responsible for all sorts of input.
pub seat_state: SeatState,
/// The shm for software buffers, such as cursors.
pub shm: Shm,
/// The XDG shell that is used for widnows.
pub xdg_shell: XdgShell,
/// The currently present windows.
pub windows: RefCell<FnvHashMap<WindowId, Arc<Mutex<WindowState>>>>,
/// The requests from the `Window` to EventLoop, such as close operations and redraw requests.
pub window_requests: RefCell<FnvHashMap<WindowId, Arc<WindowRequests>>>,
/// The events that were generated directly from the window.
pub window_events_sink: Arc<Mutex<EventSink>>,
/// The update for the `windows` comming from the compositor.
pub window_compositor_updates: Vec<WindowCompositorUpdate>,
/// Currently handled seats.
pub seats: FnvHashMap<ObjectId, WinitSeatState>,
/// Currently present cursor surfaces.
pub pointer_surfaces: FnvHashMap<ObjectId, Arc<ThemedPointer<WinitPointerData>>>,
/// The state of the text input on the client.
pub text_input_state: Option<TextInputState>,
/// Observed monitors.
pub monitors: Arc<Mutex<Vec<MonitorHandle>>>,
/// Sink to accumulate window events from the compositor, which is latter dispatched in
/// event loop run.
pub events_sink: EventSink,
/// Xdg activation.
pub xdg_activation: Option<XdgActivationState>,
/// Relative pointer.
pub relative_pointer: Option<RelativePointerState>,
/// Pointer constraints to handle pointer locking and confining.
pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
/// Viewporter state on the given window.
pub viewporter_state: Option<ViewporterState>,
/// Fractional scaling manager.
pub fractional_scaling_manager: Option<FractionalScalingManager>,
/// Loop handle to re-register event sources, such as keyboard repeat.
pub loop_handle: LoopHandle<'static, Self>,
}
impl WinitState {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<Self>,
loop_handle: LoopHandle<'static, WinitState>,
) -> Result<Self, Box<dyn Error>> {
let registry_state = RegistryState::new(globals);
let compositor_state = CompositorState::bind(globals, queue_handle)?;
let subcompositor_state = SubcompositorState::bind(
compositor_state.wl_compositor().clone(),
globals,
queue_handle,
)?;
let output_state = OutputState::new(globals, queue_handle);
let monitors = output_state.outputs().map(MonitorHandle::new).collect();
let seat_state = SeatState::new(globals, queue_handle);
let mut seats = FnvHashMap::default();
for seat in seat_state.seats() {
seats.insert(seat.id(), WinitSeatState::new());
}
let (viewporter_state, fractional_scaling_manager) =
if let Ok(fsm) = FractionalScalingManager::new(globals, queue_handle) {
(ViewporterState::new(globals, queue_handle).ok(), Some(fsm))
} else {
(None, None)
};
Ok(Self {
registry_state,
compositor_state: Arc::new(compositor_state),
subcompositor_state: Arc::new(subcompositor_state),
output_state,
seat_state,
shm: Shm::bind(globals, queue_handle)?,
xdg_shell: XdgShell::bind(globals, queue_handle)?,
xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(),
windows: Default::default(),
window_requests: Default::default(),
window_compositor_updates: Vec::new(),
window_events_sink: Default::default(),
viewporter_state,
fractional_scaling_manager,
seats,
text_input_state: TextInputState::new(globals, queue_handle).ok(),
relative_pointer: RelativePointerState::new(globals, queue_handle).ok(),
pointer_constraints: PointerConstraintsState::new(globals, queue_handle)
.map(Arc::new)
.ok(),
pointer_surfaces: Default::default(),
monitors: Arc::new(Mutex::new(monitors)),
events_sink: EventSink::new(),
loop_handle,
})
}
pub fn scale_factor_changed(
&mut self,
surface: &WlSurface,
scale_factor: f64,
is_legacy: bool,
) {
// Check if the cursor surface.
let window_id = super::make_wid(surface);
if let Some(window) = self.windows.get_mut().get(&window_id) {
// Don't update the scaling factor, when legacy method is used.
if is_legacy && self.fractional_scaling_manager.is_some() {
return;
}
// The scale factor change is for the window.
let pos = if let Some(pos) = self
.window_compositor_updates
.iter()
.position(|update| update.window_id == window_id)
{
pos
} else {
self.window_compositor_updates
.push(WindowCompositorUpdate::new(window_id));
self.window_compositor_updates.len() - 1
};
// Update the scale factor right away.
window.lock().unwrap().set_scale_factor(scale_factor);
self.window_compositor_updates[pos].scale_factor = Some(scale_factor);
} else if let Some(pointer) = self.pointer_surfaces.get(&surface.id()) {
// Get the window, where the pointer resides right now.
let focused_window = match pointer.pointer().winit_data().focused_window() {
Some(focused_window) => focused_window,
None => return,
};
if let Some(window_state) = self.windows.get_mut().get(&focused_window) {
window_state.lock().unwrap().reload_cursor_style()
}
}
}
pub fn queue_close(updates: &mut Vec<WindowCompositorUpdate>, window_id: WindowId) {
let pos = if let Some(pos) = updates
.iter()
.position(|update| update.window_id == window_id)
{
pos
} else {
updates.push(WindowCompositorUpdate::new(window_id));
updates.len() - 1
};
updates[pos].close_window = true;
}
}
impl ShmHandler for WinitState {
fn shm_state(&mut self) -> &mut Shm {
&mut self.shm
}
}
impl WindowHandler for WinitState {
fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, window: &Window) {
let window_id = super::make_wid(window.wl_surface());
Self::queue_close(&mut self.window_compositor_updates, window_id);
}
fn configure(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
window: &Window,
configure: WindowConfigure,
_serial: u32,
) {
let window_id = super::make_wid(window.wl_surface());
let pos = if let Some(pos) = self
.window_compositor_updates
.iter()
.position(|update| update.window_id == window_id)
{
pos
} else {
self.window_compositor_updates
.push(WindowCompositorUpdate::new(window_id));
self.window_compositor_updates.len() - 1
};
// Populate the configure to the window.
//
// XXX the size on the window will be updated right before dispatching the size to the user.
let new_size = self
.windows
.get_mut()
.get_mut(&window_id)
.expect("got configure for dead window.")
.lock()
.unwrap()
.configure(configure, &self.shm, &self.subcompositor_state);
self.window_compositor_updates[pos].size = Some(new_size);
}
}
impl OutputHandler for WinitState {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, output: WlOutput) {
self.monitors
.lock()
.unwrap()
.push(MonitorHandle::new(output));
}
fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, updated: WlOutput) {
let mut monitors = self.monitors.lock().unwrap();
let updated = MonitorHandle::new(updated);
if let Some(pos) = monitors.iter().position(|output| output == &updated) {
monitors[pos] = updated
} else {
monitors.push(updated)
}
}
fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, removed: WlOutput) {
let mut monitors = self.monitors.lock().unwrap();
let removed = MonitorHandle::new(removed);
if let Some(pos) = monitors.iter().position(|output| output == &removed) {
monitors.remove(pos);
}
}
}
impl CompositorHandler for WinitState {
fn scale_factor_changed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
surface: &WlSurface,
scale_factor: i32,
) {
self.scale_factor_changed(surface, scale_factor as f64, true)
}
fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: u32) {}
}
impl ProvidesRegistryState for WinitState {
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
sctk::registry_handlers![OutputState, SeatState];
}
// The window update comming from the compositor.
#[derive(Debug, Clone, Copy)]
pub struct WindowCompositorUpdate {
/// The id of the window this updates belongs to.
pub window_id: WindowId,
/// New window size.
pub size: Option<LogicalSize<u32>>,
/// New scale factor.
pub scale_factor: Option<f64>,
/// Close the window.
pub close_window: bool,
}
impl WindowCompositorUpdate {
fn new(window_id: WindowId) -> Self {
Self {
window_id,
size: None,
scale_factor: None,
close_window: false,
}
}
}
sctk::delegate_subcompositor!(WinitState);
sctk::delegate_compositor!(WinitState);
sctk::delegate_output!(WinitState);
sctk::delegate_registry!(WinitState);
sctk::delegate_shm!(WinitState);
sctk::delegate_xdg_shell!(WinitState);
sctk::delegate_xdg_window!(WinitState);

View file

@ -0,0 +1,5 @@
//! Wayland protocol implementation boilerplate.
pub mod wp_fractional_scaling;
pub mod wp_viewporter;
pub mod xdg_activation;

View file

@ -0,0 +1,81 @@
//! Handling of the fractional scaling.
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Dispatch;
use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle};
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1;
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::Event as FractionalScalingEvent;
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
use sctk::globals::GlobalData;
use crate::platform_impl::wayland::state::WinitState;
/// The scaling factor denominator.
const SCALE_DENOMINATOR: f64 = 120.;
/// Fractional scaling manager.
#[derive(Debug)]
pub struct FractionalScalingManager {
manager: WpFractionalScaleManagerV1,
}
pub struct FractionalScaling {
/// The surface used for scaling.
surface: WlSurface,
}
impl FractionalScalingManager {
/// Create new viewporter.
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { manager })
}
pub fn fractional_scaling(
&self,
surface: &WlSurface,
queue_handle: &QueueHandle<WinitState>,
) -> WpFractionalScaleV1 {
let data = FractionalScaling {
surface: surface.clone(),
};
self.manager
.get_fractional_scale(surface, queue_handle, data)
}
}
impl Dispatch<WpFractionalScaleManagerV1, GlobalData, WinitState> for FractionalScalingManager {
fn event(
_: &mut WinitState,
_: &WpFractionalScaleManagerV1,
_: <WpFractionalScaleManagerV1 as Proxy>::Event,
_: &GlobalData,
_: &Connection,
_: &QueueHandle<WinitState>,
) {
// No events.
}
}
impl Dispatch<WpFractionalScaleV1, FractionalScaling, WinitState> for FractionalScalingManager {
fn event(
state: &mut WinitState,
_: &WpFractionalScaleV1,
event: <WpFractionalScaleV1 as Proxy>::Event,
data: &FractionalScaling,
_: &Connection,
_: &QueueHandle<WinitState>,
) {
if let FractionalScalingEvent::PreferredScale { scale } = event {
state.scale_factor_changed(&data.surface, scale as f64 / SCALE_DENOMINATOR, false);
}
}
}
delegate_dispatch!(WinitState: [WpFractionalScaleManagerV1: GlobalData] => FractionalScalingManager);
delegate_dispatch!(WinitState: [WpFractionalScaleV1: FractionalScaling] => FractionalScalingManager);

View file

@ -0,0 +1,67 @@
//! Handling of the wp-viewporter.
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Dispatch;
use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle};
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
use sctk::reexports::protocols::wp::viewporter::client::wp_viewporter::WpViewporter;
use sctk::globals::GlobalData;
use crate::platform_impl::wayland::state::WinitState;
/// Viewporter.
#[derive(Debug)]
pub struct ViewporterState {
viewporter: WpViewporter,
}
impl ViewporterState {
/// Create new viewporter.
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let viewporter = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { viewporter })
}
/// Get the viewport for the given object.
pub fn get_viewport(
&self,
surface: &WlSurface,
queue_handle: &QueueHandle<WinitState>,
) -> WpViewport {
self.viewporter
.get_viewport(surface, queue_handle, GlobalData)
}
}
impl Dispatch<WpViewporter, GlobalData, WinitState> for ViewporterState {
fn event(
_: &mut WinitState,
_: &WpViewporter,
_: <WpViewporter as Proxy>::Event,
_: &GlobalData,
_: &Connection,
_: &QueueHandle<WinitState>,
) {
// No events.
}
}
impl Dispatch<WpViewport, GlobalData, WinitState> for ViewporterState {
fn event(
_: &mut WinitState,
_: &WpViewport,
_: <WpViewport as Proxy>::Event,
_: &GlobalData,
_: &Connection,
_: &QueueHandle<WinitState>,
) {
// No events.
}
}
delegate_dispatch!(WinitState: [WpViewporter: GlobalData] => ViewporterState);
delegate_dispatch!(WinitState: [WpViewport: GlobalData] => ViewporterState);

View file

@ -0,0 +1,103 @@
//! Handling of xdg activation, which is used for user attention requests.
use std::sync::atomic::AtomicBool;
use std::sync::Weak;
use sctk::reexports::client::delegate_dispatch;
use sctk::reexports::client::globals::BindError;
use sctk::reexports::client::globals::GlobalList;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Dispatch;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_token_v1::{
Event as ActivationTokenEvent, XdgActivationTokenV1,
};
use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::globals::GlobalData;
use crate::platform_impl::wayland::state::WinitState;
pub struct XdgActivationState {
xdg_activation: XdgActivationV1,
}
impl XdgActivationState {
pub fn bind(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let xdg_activation = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { xdg_activation })
}
pub fn global(&self) -> &XdgActivationV1 {
&self.xdg_activation
}
}
impl Dispatch<XdgActivationV1, GlobalData, WinitState> for XdgActivationState {
fn event(
_state: &mut WinitState,
_proxy: &XdgActivationV1,
_event: <XdgActivationV1 as Proxy>::Event,
_data: &GlobalData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
}
}
impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgActivationState {
fn event(
state: &mut WinitState,
proxy: &XdgActivationTokenV1,
event: <XdgActivationTokenV1 as Proxy>::Event,
data: &XdgActivationTokenData,
_: &Connection,
_: &QueueHandle<WinitState>,
) {
let token = match event {
ActivationTokenEvent::Done { token } => token,
_ => return,
};
state
.xdg_activation
.as_ref()
.expect("got xdg_activation event without global.")
.global()
.activate(token, &data.surface);
// Mark that no request attention is in process.
if let Some(attention_requested) = data.attention_requested.upgrade() {
attention_requested.store(false, std::sync::atomic::Ordering::Relaxed);
}
proxy.destroy();
}
}
/// The data associated with the activation request.
pub struct XdgActivationTokenData {
/// The surface we're raising.
surface: WlSurface,
/// Flag to throttle attention requests.
attention_requested: Weak<AtomicBool>,
}
impl XdgActivationTokenData {
/// Create a new data.
///
/// The `attenteion_requested` is marked as `false` on complition.
pub fn new(surface: WlSurface, attention_requested: Weak<AtomicBool>) -> Self {
Self {
surface,
attention_requested,
}
}
}
delegate_dispatch!(WinitState: [ XdgActivationV1: GlobalData] => XdgActivationState);
delegate_dispatch!(WinitState: [ XdgActivationTokenV1: XdgActivationTokenData] => XdgActivationState);

File diff suppressed because it is too large Load diff

View file

@ -1,666 +0,0 @@
use std::cell::Cell;
use std::mem::ManuallyDrop;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::Attached;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_token_v1;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
use sctk::environment::Environment;
use sctk::window::{Decorations, Window};
use wayland_protocols::viewporter::client::wp_viewport::WpViewport;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{Ime, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::env::WinitEnv;
use crate::platform_impl::wayland::event_loop::{EventSink, WinitState};
use crate::platform_impl::wayland::protocols::wp_fractional_scale_v1::WpFractionalScaleV1;
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::{CursorGrabMode, CursorIcon, ImePurpose, Theme, UserAttentionType};
use super::WinitFrame;
/// 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),
/// Change cursor grabbing mode.
SetCursorGrabMode(CursorGrabMode),
/// Set cursor position.
SetLockedCursorPosition(LogicalPosition<u32>),
/// Drag window.
DragWindow,
/// 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>),
/// Enable IME on the given window.
AllowIme(bool),
/// Set the IME purpose.
ImePurpose(ImePurpose),
/// Mark the window as opaque.
Transparent(bool),
/// Request Attention.
///
/// `None` unsets the attention request.
Attention(Option<UserAttentionType>),
/// Passthrough mouse input to underlying windows.
PassthroughMouseInput(bool),
/// Redraw was requested.
Redraw,
/// Window should be closed.
Close,
/// Change window theme.
Theme(Option<Theme>),
}
// The window update comming from the compositor.
#[derive(Default, Debug, Clone, Copy)]
pub struct WindowCompositorUpdate {
/// New window size.
pub size: Option<LogicalSize<u32>>,
/// New scale factor.
pub scale_factor: Option<f64>,
/// Close the window.
pub close_window: bool,
}
impl WindowCompositorUpdate {
pub fn new() -> Self {
Default::default()
}
}
/// Pending update to a window requested by the user.
#[derive(Default, Debug, Clone, Copy)]
pub struct WindowUserRequest {
/// Whether `redraw` was requested.
pub redraw_requested: bool,
/// Wether the frame should be refreshed.
pub refresh_frame: bool,
}
impl WindowUserRequest {
pub fn new() -> Self {
Default::default()
}
}
/// A handle to perform operations on SCTK window
/// and react to events.
pub struct WindowHandle {
/// An actual window.
pub window: ManuallyDrop<Window<WinitFrame>>,
/// The state of the fractional scaling handlers for the window.
pub fractional_scaling_state: Option<FractionalScalingState>,
/// The scale factor of the window.
pub scale_factor: Arc<Mutex<f64>>,
/// 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>,
/// Whether the window is resizable.
pub is_resizable: Cell<bool>,
/// Whether the window has keyboard focus.
pub has_focus: Arc<AtomicBool>,
/// Allow IME events for that window.
pub ime_allowed: Cell<bool>,
/// IME purpose for that window.
pub ime_purpose: Cell<ImePurpose>,
/// Wether the window is transparent.
pub transparent: Cell<bool>,
/// Visible cursor or not.
cursor_visible: Cell<bool>,
/// Cursor confined to the surface.
cursor_grab_mode: Cell<CursorGrabMode>,
/// Pointers over the current surface.
pointers: Vec<WinitPointer>,
/// Text inputs on the current surface.
text_inputs: Vec<TextInputHandler>,
/// XdgActivation object.
xdg_activation: Option<Attached<XdgActivationV1>>,
/// Indicator whether user attention is requested.
attention_requested: Cell<bool>,
/// Compositor
compositor: Attached<WlCompositor>,
}
impl WindowHandle {
pub fn new(
env: &Environment<WinitEnv>,
window: Window<WinitFrame>,
size: Arc<Mutex<LogicalSize<u32>>>,
has_focus: Arc<AtomicBool>,
fractional_scaling_state: Option<FractionalScalingState>,
scale_factor: f64,
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
) -> Self {
let xdg_activation = env.get_global::<XdgActivationV1>();
// Unwrap is safe, since we can't create window without compositor anyway and won't be
// here.
let compositor = env.get_global::<WlCompositor>().unwrap();
Self {
window: ManuallyDrop::new(window),
fractional_scaling_state,
scale_factor: Arc::new(Mutex::new(scale_factor)),
size,
pending_window_requests,
cursor_icon: Cell::new(CursorIcon::Default),
is_resizable: Cell::new(true),
transparent: Cell::new(false),
cursor_grab_mode: Cell::new(CursorGrabMode::None),
cursor_visible: Cell::new(true),
pointers: Vec::new(),
text_inputs: Vec::new(),
xdg_activation,
attention_requested: Cell::new(false),
compositor,
ime_allowed: Cell::new(false),
ime_purpose: Cell::new(ImePurpose::default()),
has_focus,
}
}
pub fn scale_factor(&self) -> f64 {
*self.scale_factor.lock().unwrap()
}
pub fn set_cursor_grab(&self, mode: CursorGrabMode) {
// The new requested state matches the current confine status, return.
let old_mode = self.cursor_grab_mode.replace(mode);
if old_mode == mode {
return;
}
// Clear old pointer data.
match old_mode {
CursorGrabMode::None => (),
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.unconfine()),
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.unlock()),
}
let surface = self.window.surface();
match mode {
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.lock(surface)),
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.confine(surface)),
CursorGrabMode::None => {
// Current lock/confine was already removed.
}
}
}
pub fn set_locked_cursor_position(&self, position: LogicalPosition<u32>) {
// XXX the cursor locking is ensured inside `Window`.
self.pointers
.iter()
.for_each(|p| p.set_cursor_position(position.x, position.y));
}
pub fn set_user_attention(&self, request_type: Option<UserAttentionType>) {
let xdg_activation = match self.xdg_activation.as_ref() {
None => return,
Some(xdg_activation) => xdg_activation,
};
// Urgency is only removed by the compositor and there's no need to raise urgency when it
// was already raised.
if request_type.is_none() || self.attention_requested.get() {
return;
}
let xdg_activation_token = xdg_activation.get_activation_token();
let surface = self.window.surface();
let window_id = wayland::make_wid(surface);
let xdg_activation = xdg_activation.clone();
xdg_activation_token.quick_assign(move |xdg_token, event, mut dispatch_data| {
let token = match event {
xdg_activation_token_v1::Event::Done { token } => token,
_ => return,
};
let winit_state = dispatch_data.get::<WinitState>().unwrap();
let window_handle = match winit_state.window_map.get_mut(&window_id) {
Some(window_handle) => window_handle,
None => return,
};
let surface = window_handle.window.surface();
xdg_activation.activate(token, surface);
// Mark that attention request was done and drop the token.
window_handle.attention_requested.replace(false);
xdg_token.destroy();
});
xdg_activation_token.set_surface(surface);
xdg_activation_token.commit();
self.attention_requested.replace(true);
}
/// 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() {
let surface = self.window.surface();
match self.cursor_grab_mode.get() {
CursorGrabMode::None => (),
CursorGrabMode::Locked => pointer.lock(surface),
CursorGrabMode::Confined => 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 grabbing mode.
match self.cursor_grab_mode.get() {
CursorGrabMode::None => (),
CursorGrabMode::Locked => pointer.unlock(),
CursorGrabMode::Confined => pointer.unconfine(),
}
}
}
pub fn text_input_entered(&mut self, text_input: TextInputHandler) {
if !self.text_inputs.iter().any(|t| *t == text_input) {
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 passthrough_mouse_input(&self, passthrough_mouse_input: bool) {
if passthrough_mouse_input {
let region = self.compositor.create_region();
region.add(0, 0, 0, 0);
self.window
.surface()
.set_input_region(Some(&region.detach()));
region.destroy();
} else {
// Using `None` results in the entire window being clickable.
self.window.surface().set_input_region(None);
}
}
pub fn set_transparent(&self, transparent: bool) {
self.transparent.set(transparent);
let surface = self.window.surface();
if transparent {
surface.set_opaque_region(None);
} else {
let region = self.compositor.create_region();
region.add(0, 0, i32::MAX, i32::MAX);
surface.set_opaque_region(Some(&region.detach()));
region.destroy();
}
}
pub fn set_ime_allowed(&self, allowed: bool, event_sink: &mut EventSink) {
if self.ime_allowed.get() == allowed {
return;
}
self.ime_allowed.replace(allowed);
let window_id = wayland::make_wid(self.window.surface());
let purpose = allowed.then(|| self.ime_purpose.get());
for text_input in self.text_inputs.iter() {
text_input.set_input_allowed(purpose);
}
let event = if allowed {
WindowEvent::Ime(Ime::Enabled)
} else {
WindowEvent::Ime(Ime::Disabled)
};
event_sink.push_window_event(event, window_id);
}
pub fn set_ime_purpose(&self, purpose: ImePurpose) {
if self.ime_purpose.get() == purpose {
return;
}
self.ime_purpose.replace(purpose);
if self.ime_allowed.get() {
for text_input in self.text_inputs.iter() {
text_input.set_content_type_by_purpose(purpose);
}
}
}
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));
}
}
pub fn drag_window(&self) {
for pointer in self.pointers.iter() {
pointer.drag_window(&self.window);
}
}
}
#[inline]
pub fn handle_window_requests(winit_state: &mut WinitState) {
let window_map = &mut winit_state.window_map;
let window_user_requests = &mut winit_state.window_user_requests;
let window_compositor_updates = &mut winit_state.window_compositor_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();
let requests = requests.drain(..);
for request in requests {
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::AllowIme(allow) => {
let event_sink = &mut winit_state.event_sink;
window_handle.set_ime_allowed(allow, event_sink);
}
WindowRequest::ImePurpose(purpose) => {
window_handle.set_ime_purpose(purpose);
}
WindowRequest::SetCursorGrabMode(mode) => {
window_handle.set_cursor_grab(mode);
}
WindowRequest::SetLockedCursorPosition(position) => {
window_handle.set_locked_cursor_position(position);
}
WindowRequest::DragWindow => {
window_handle.drag_window();
}
WindowRequest::Maximize(maximize) => {
if maximize {
window_handle.window.set_maximized();
} else {
window_handle.window.unset_maximized();
}
}
WindowRequest::Minimize => {
window_handle.window.set_minimized();
}
WindowRequest::Transparent(transparent) => {
window_handle.set_transparent(transparent);
// This requires surface commit.
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.redraw_requested = true;
}
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_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::Resizeable(resizeable) => {
window_handle.window.set_resizable(resizeable);
// We should refresh the frame to update button state.
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::Title(title) => {
window_handle.window.set_title(title);
// We should refresh the frame to draw new title.
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::MinSize(size) => {
let size = size.map(|size| (size.width, size.height));
window_handle.window.set_min_size(size);
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::MaxSize(size) => {
let size = size.map(|size| (size.width, size.height));
window_handle.window.set_max_size(size);
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::FrameSize(size) => {
if !window_handle.is_resizable.get() {
// On Wayland non-resizable window is achieved by setting both min and max
// size of the window to the same value.
let size = Some((size.width, size.height));
window_handle.window.set_max_size(size);
window_handle.window.set_min_size(size);
}
window_handle.window.resize(size.width, size.height);
// We should refresh the frame after resize.
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::PassthroughMouseInput(passthrough) => {
window_handle.passthrough_mouse_input(passthrough);
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.refresh_frame = true;
}
WindowRequest::Attention(request_type) => {
window_handle.set_user_attention(request_type);
}
WindowRequest::Redraw => {
let window_request = window_user_requests.get_mut(window_id).unwrap();
window_request.redraw_requested = 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);
}
WindowRequest::Theme(_theme) => {
#[cfg(feature = "sctk-adwaita")]
{
window_handle.window.set_frame_config(match _theme {
Some(theme) => theme.into(),
None => sctk_adwaita::FrameConfig::auto(),
});
let window_requst = window_user_requests.get_mut(window_id).unwrap();
window_requst.refresh_frame = true;
}
}
};
}
}
// Close the windows.
for window in windows_to_close {
let _ = window_map.remove(&window);
let _ = window_user_requests.remove(&window);
let _ = window_compositor_updates.remove(&window);
}
}
impl Drop for WindowHandle {
fn drop(&mut self) {
// Drop the fractional scaling before the surface.
let _ = self.fractional_scaling_state.take();
unsafe {
let surface = self.window.surface().clone();
// The window must be destroyed before wl_surface.
ManuallyDrop::drop(&mut self.window);
surface.destroy();
}
}
}
/// Fractional scaling objects.
pub struct FractionalScalingState {
/// The wp-viewport of the window.
pub viewport: WpViewport,
/// The wp-fractional-scale of the window surface.
pub fractional_scale: WpFractionalScaleV1,
}
impl FractionalScalingState {
pub fn new(viewport: WpViewport, fractional_scale: WpFractionalScaleV1) -> Self {
Self {
viewport,
fractional_scale,
}
}
}
impl Drop for FractionalScalingState {
fn drop(&mut self) {
self.viewport.destroy();
self.fractional_scale.destroy();
}
}

View file

@ -0,0 +1,905 @@
//! The state of the window, which is shared with the event-loop.
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
use std::sync::{Arc, Weak};
use log::warn;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_shm::WlShm;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3;
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge;
use sctk::compositor::{CompositorState, Region, SurfaceData};
use sctk::seat::pointer::ThemedPointer;
use sctk::shell::xdg::frame::{DecorationsFrame, FrameAction, FrameClick};
use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure};
use sctk::shell::xdg::XdgSurface;
use sctk::shell::WaylandSurface;
use sctk::shm::Shm;
use sctk::subcompositor::SubcompositorState;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::error::{ExternalError, NotSupportedError};
use crate::platform_impl::WindowId;
use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme};
use crate::platform_impl::wayland::seat::{
PointerConstraintsState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext,
};
use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState};
#[cfg(feature = "sctk-adwaita")]
pub type WinitFrame = sctk_adwaita::AdwaitaFrame<WinitState>;
#[cfg(not(feature = "sctk-adwaita"))]
pub type WinitFrame = sctk::shell::xdg::frame::fallback_frame::FallbackFrame<WinitState>;
// Minimum window inner size.
const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
/// The state of the window which is being updated from the [`WinitState`].
pub struct WindowState {
/// The connection to Wayland server.
pub connection: Connection,
/// The underlying SCTK window.
pub window: ManuallyDrop<Window>,
/// The window frame, which is created from the configure request.
frame: Option<WinitFrame>,
/// The `Shm` to set cursor.
pub shm: WlShm,
/// The last received configure.
pub last_configure: Option<WindowConfigure>,
/// The pointers observed on the window.
pub pointers: Vec<Weak<ThemedPointer<WinitPointerData>>>,
/// Cursor icon.
pub cursor_icon: CursorIcon,
/// Wether the cursor is visible.
pub cursor_visible: bool,
/// Pointer constraints to lock/confine pointer.
pub pointer_constraints: Option<Arc<PointerConstraintsState>>,
/// Queue handle.
pub queue_handle: QueueHandle<WinitState>,
/// Theme varaint.
theme: Option<Theme>,
/// The current window title.
title: String,
/// Whether the frame is resizable.
resizable: bool,
/// Whether the window has focus.
has_focus: bool,
/// The scale factor of the window.
scale_factor: f64,
/// Whether the window is transparent.
transparent: bool,
/// The state of the compositor to create WlRegions.
compositor: Arc<CompositorState>,
/// The current cursor grabbing mode.
cursor_grab_mode: GrabState,
/// Whether the IME input is allowed for that window.
ime_allowed: bool,
/// The current IME purpose.
ime_purpose: ImePurpose,
/// The text inputs observed on the window.
text_inputs: Vec<ZwpTextInputV3>,
/// The inner size of the window, as in without client side decorations.
size: LogicalSize<u32>,
/// Whether the CSD fail to create, so we don't try to create them on each iteration.
csd_fails: bool,
/// Min size.
min_inner_size: LogicalSize<u32>,
max_inner_size: Option<LogicalSize<u32>>,
/// The size of the window when no states were applied to it. The primary use for it
/// is to fallback to original window size, before it was maximized, if the compositor
/// sends `None` for the new size in the configure.
stateless_size: LogicalSize<u32>,
viewport: Option<WpViewport>,
fractional_scale: Option<WpFractionalScaleV1>,
}
/// The state of the cursor grabs.
#[derive(Clone, Copy)]
struct GrabState {
/// The grab mode requested by the user.
user_grab_mode: CursorGrabMode,
/// The current grab mode.
current_grab_mode: CursorGrabMode,
}
impl GrabState {
fn new() -> Self {
Self {
user_grab_mode: CursorGrabMode::None,
current_grab_mode: CursorGrabMode::None,
}
}
}
impl WindowState {
/// Apply closure on the given pointer.
fn apply_on_poiner<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
&self,
callback: F,
) {
self.pointers
.iter()
.filter_map(Weak::upgrade)
.for_each(|pointer| {
let data = pointer.pointer().winit_data();
callback(pointer.as_ref(), data);
})
}
pub fn configure(
&mut self,
configure: WindowConfigure,
shm: &Shm,
subcompositor: &Arc<SubcompositorState>,
) -> LogicalSize<u32> {
if configure.decoration_mode == DecorationMode::Client
&& self.frame.is_none()
&& !self.csd_fails
{
match WinitFrame::new(
&*self.window,
shm,
subcompositor.clone(),
self.queue_handle.clone(),
#[cfg(feature = "sctk-adwaita")]
into_sctk_adwaita_config(self.theme),
) {
Ok(mut frame) => {
frame.set_title(&self.title);
// Ensure that the frame is not hidden.
frame.set_hidden(false);
self.frame = Some(frame);
}
Err(err) => {
warn!("Failed to create client side decorations frame: {err}");
self.csd_fails = true;
}
}
} else if configure.decoration_mode == DecorationMode::Server {
// Drop the frame for server side decorations to save resources.
self.frame = None;
}
let stateless = Self::is_stateless(&configure);
let new_size = if let Some(frame) = self.frame.as_mut() {
// Configure the window states.
frame.update_state(configure.state);
match configure.new_size {
(Some(width), Some(height)) => {
let (width, height) = frame.subtract_borders(width, height);
(
width.map(|w| w.get()).unwrap_or(1),
height.map(|h| h.get()).unwrap_or(1),
)
.into()
}
(_, _) if stateless => self.stateless_size,
_ => self.size,
}
} else {
match configure.new_size {
(Some(width), Some(height)) => (width.get(), height.get()).into(),
_ if stateless => self.stateless_size,
_ => self.size,
}
};
// XXX Set the configure before doing a resize.
self.last_configure = Some(configure);
// XXX Update the new size right away.
self.resize(new_size);
new_size
}
#[inline]
fn is_stateless(configure: &WindowConfigure) -> bool {
!(configure.is_maximized() || configure.is_fullscreen() || configure.is_tiled())
}
/// Start interacting drag resize.
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
let xdg_toplevel = self.window.xdg_toplevel();
// TODO(kchibisov) handle touch serials.
self.apply_on_poiner(|_, data| {
let serial = data.latest_button_serial();
let seat = data.seat();
xdg_toplevel.resize(seat, serial, direction.into());
});
Ok(())
}
/// Start the window drag.
pub fn drag_window(&self) -> Result<(), ExternalError> {
let xdg_toplevel = self.window.xdg_toplevel();
// TODO(kchibisov) handle touch serials.
self.apply_on_poiner(|_, data| {
let serial = data.latest_button_serial();
let seat = data.seat();
xdg_toplevel._move(seat, serial);
});
Ok(())
}
/// Tells whether the window should be closed.
pub fn frame_click(
&mut self,
click: FrameClick,
pressed: bool,
seat: &WlSeat,
serial: u32,
window_id: WindowId,
updates: &mut Vec<WindowCompositorUpdate>,
) -> Option<bool> {
match self.frame.as_mut()?.on_click(click, pressed)? {
FrameAction::Minimize => self.window.set_minimized(),
FrameAction::Maximize => self.window.set_maximized(),
FrameAction::UnMaximize => self.window.unset_maximized(),
FrameAction::Close => WinitState::queue_close(updates, window_id),
FrameAction::Move => self.window.move_(seat, serial),
FrameAction::Resize(edge) => self.window.resize(seat, serial, edge),
FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
};
Some(false)
}
pub fn frame_point_left(&mut self) {
if let Some(frame) = self.frame.as_mut() {
frame.click_point_left();
}
}
// Move the point over decorations.
pub fn frame_point_moved(&mut self, surface: &WlSurface, x: f64, y: f64) -> Option<&str> {
if let Some(frame) = self.frame.as_mut() {
frame.click_point_moved(surface, x, y)
} else {
None
}
}
/// Get the stored resizable state.
#[inline]
pub fn resizable(&self) -> bool {
self.resizable
}
/// Set the resizable state on the window.
#[inline]
pub fn set_resizable(&mut self, resizable: bool) {
if self.resizable == resizable {
return;
}
self.resizable = resizable;
if resizable {
// Restore min/max sizes of the window.
self.reload_min_max_hints();
} else {
self.set_min_inner_size(Some(self.size));
self.set_max_inner_size(Some(self.size));
}
// Reload the state on the frame as well.
if let Some(frame) = self.frame.as_mut() {
frame.set_resizable(resizable);
}
}
/// Whether the window is focused.
#[inline]
pub fn has_focus(&self) -> bool {
self.has_focus
}
/// Whether the IME is allowed.
#[inline]
pub fn ime_allowed(&self) -> bool {
self.ime_allowed
}
/// Get the size of the window.
#[inline]
pub fn inner_size(&self) -> LogicalSize<u32> {
self.size
}
/// Whether the window received initial configure event from the compositor.
#[inline]
pub fn is_configured(&self) -> bool {
self.last_configure.is_some()
}
#[inline]
pub fn is_decorated(&mut self) -> bool {
let csd = self
.last_configure
.as_ref()
.map(|configure| configure.decoration_mode == DecorationMode::Client)
.unwrap_or(false);
if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() {
frame.is_hidden()
} else {
// Server side decorations.
true
}
}
/// Create new window state.
pub fn new(
connection: Connection,
queue_handle: &QueueHandle<WinitState>,
winit_state: &WinitState,
size: LogicalSize<u32>,
window: Window,
theme: Option<Theme>,
) -> Self {
let compositor = winit_state.compositor_state.clone();
let pointer_constraints = winit_state.pointer_constraints.clone();
let viewport = winit_state
.viewporter_state
.as_ref()
.map(|state| state.get_viewport(window.wl_surface(), queue_handle));
let fractional_scale = winit_state
.fractional_scaling_manager
.as_ref()
.map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle));
Self {
compositor,
connection,
theme,
csd_fails: false,
cursor_grab_mode: GrabState::new(),
cursor_icon: CursorIcon::Default,
cursor_visible: true,
fractional_scale,
frame: None,
has_focus: false,
ime_allowed: false,
ime_purpose: ImePurpose::Normal,
last_configure: None,
max_inner_size: None,
min_inner_size: MIN_WINDOW_SIZE,
pointer_constraints,
pointers: Default::default(),
queue_handle: queue_handle.clone(),
scale_factor: 1.,
shm: winit_state.shm.wl_shm().clone(),
size,
stateless_size: size,
text_inputs: Vec::new(),
title: String::default(),
transparent: false,
resizable: true,
viewport,
window: ManuallyDrop::new(window),
}
}
/// Get the outer size of the window.
#[inline]
pub fn outer_size(&self) -> LogicalSize<u32> {
self.frame
.as_ref()
.map(|frame| frame.add_borders(self.size.width, self.size.height).into())
.unwrap_or(self.size)
}
/// Register pointer on the top-level.
pub fn pointer_entered(&mut self, added: Weak<ThemedPointer<WinitPointerData>>) {
self.pointers.push(added);
self.reload_cursor_style();
let mode = self.cursor_grab_mode.user_grab_mode;
let _ = self.set_cursor_grab_inner(mode);
}
/// Pointer has left the top-level.
pub fn pointer_left(&mut self, removed: Weak<ThemedPointer<WinitPointerData>>) {
let mut new_pointers = Vec::new();
for pointer in self.pointers.drain(..) {
if let Some(pointer) = pointer.upgrade() {
if pointer.pointer() != removed.upgrade().unwrap().pointer() {
new_pointers.push(Arc::downgrade(&pointer));
}
}
}
self.pointers = new_pointers;
}
/// Refresh the decorations frame if it's present returning whether the client should redraw.
pub fn refresh_frame(&mut self) -> bool {
if let Some(frame) = self.frame.as_mut() {
let dirty = frame.is_dirty();
if dirty {
frame.draw();
}
dirty
} else {
false
}
}
/// Reload the cursor style on the given window.
pub fn reload_cursor_style(&mut self) {
if self.cursor_visible {
self.set_cursor(self.cursor_icon);
} else {
self.set_cursor_visible(self.cursor_visible);
}
}
/// Reissue the transparency hint to the compositor.
pub fn reload_transparency_hint(&self) {
let surface = self.window.wl_surface();
if self.transparent {
surface.set_opaque_region(None);
} else if let Ok(region) = Region::new(&*self.compositor) {
region.add(0, 0, i32::MAX, i32::MAX);
surface.set_opaque_region(Some(region.wl_region()));
} else {
warn!("Failed to mark window opaque.");
}
}
/// Resize the window to the new inner size.
pub fn resize(&mut self, inner_size: LogicalSize<u32>) {
self.size = inner_size;
// Update the stateless size.
if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) {
self.stateless_size = inner_size;
}
// Update the inner frame.
let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() {
// Resize only visible frame.
if !frame.is_hidden() {
frame.resize(
NonZeroU32::new(self.size.width).unwrap(),
NonZeroU32::new(self.size.height).unwrap(),
);
}
(
frame.location(),
frame.add_borders(self.size.width, self.size.height).into(),
)
} else {
((0, 0), self.size)
};
// Reload the hint.
self.reload_transparency_hint();
// Set the window geometry.
self.window.xdg_surface().set_window_geometry(
x,
y,
outer_size.width as i32,
outer_size.height as i32,
);
// Update the target viewport, this is used if and only if fractional scaling is in use.
if let Some(viewport) = self.viewport.as_ref() {
// Set inner size without the borders.
viewport.set_destination(self.size.width as _, self.size.height as _);
}
}
/// Get the scale factor of the window.
#[inline]
pub fn scale_factor(&self) -> f64 {
self.scale_factor
}
/// Set the cursor icon.
///
/// Providing `None` will hide the cursor.
pub fn set_cursor(&mut self, cursor_icon: CursorIcon) {
self.cursor_icon = cursor_icon;
if !self.cursor_visible {
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 => &["hand2", "hand1"],
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_fdiag"],
CursorIcon::NeswResize => &["fd_double_arrow", "size_bdiag"],
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"],
};
self.apply_on_poiner(|pointer, data| {
let surface = data.cursor_surface();
let scale_factor = surface.data::<SurfaceData>().unwrap().scale_factor();
for cursor in cursors {
if pointer
.set_cursor(&self.connection, cursor, &self.shm, surface, scale_factor)
.is_ok()
{
return;
}
}
warn!("Failed to set cursor to {:?}", cursor_icon);
})
}
/// Set maximum inner window size.
pub fn set_min_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
// Ensure that the window has the right minimum size.
let mut size = size.unwrap_or(MIN_WINDOW_SIZE);
size.width = size.width.max(MIN_WINDOW_SIZE.width);
size.height = size.height.max(MIN_WINDOW_SIZE.height);
// Add the borders.
let size = self
.frame
.as_ref()
.map(|frame| frame.add_borders(size.width, size.height).into())
.unwrap_or(size);
self.min_inner_size = size;
self.window.set_min_size(Some(size.into()));
}
/// Set maximum inner window size.
pub fn set_max_inner_size(&mut self, size: Option<LogicalSize<u32>>) {
let size = size.map(|size| {
self.frame
.as_ref()
.map(|frame| frame.add_borders(size.width, size.height).into())
.unwrap_or(size)
});
self.max_inner_size = size;
self.window.set_max_size(size.map(Into::into));
}
/// Set the CSD theme.
pub fn set_theme(&mut self, theme: Option<Theme>) {
self.theme = theme;
#[cfg(feature = "sctk-adwaita")]
if let Some(frame) = self.frame.as_mut() {
frame.set_config(into_sctk_adwaita_config(theme))
}
}
/// The current theme for CSD decorations.
#[inline]
pub fn theme(&self) -> Option<Theme> {
self.theme
}
/// Set the cursor grabbing state on the top-level.
pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
// Replace the user grabbing mode.
self.cursor_grab_mode.user_grab_mode = mode;
self.set_cursor_grab_inner(mode)
}
/// Reload the hints for minimum and maximum sizes.
pub fn reload_min_max_hints(&mut self) {
self.set_min_inner_size(Some(self.min_inner_size));
self.set_max_inner_size(self.max_inner_size);
}
/// Set the grabbing state on the surface.
fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let pointer_constraints = match self.pointer_constraints.as_ref() {
Some(pointer_constraints) => pointer_constraints,
None if mode == CursorGrabMode::None => return Ok(()),
None => return Err(ExternalError::NotSupported(NotSupportedError::new())),
};
// Replace the current mode.
let old_mode = std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode);
match old_mode {
CursorGrabMode::None => (),
CursorGrabMode::Confined => self.apply_on_poiner(|_, data| {
data.unconfine_pointer();
}),
CursorGrabMode::Locked => {
self.apply_on_poiner(|_, data| data.unlock_pointer());
}
}
let surface = self.window.wl_surface();
match mode {
CursorGrabMode::Locked => self.apply_on_poiner(|pointer, data| {
let pointer = pointer.pointer();
data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
}),
CursorGrabMode::Confined => self.apply_on_poiner(|pointer, data| {
let pointer = pointer.pointer();
data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
}),
CursorGrabMode::None => {
// Current lock/confine was already removed.
}
}
Ok(())
}
/// Set the position of the cursor.
pub fn set_cursor_position(&self, position: LogicalPosition<f64>) -> Result<(), ExternalError> {
if self.pointer_constraints.is_none() {
return Err(ExternalError::NotSupported(NotSupportedError::new()));
}
// Positon can be set only for locked cursor.
if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked {
return Err(ExternalError::Os(os_error!(
crate::platform_impl::OsError::WaylandMisc(
"cursor position can be set only for locked cursor."
)
)));
}
self.apply_on_poiner(|_, data| {
data.set_locked_cursor_position(position.x, position.y);
});
Ok(())
}
/// Set the visibility state of the cursor.
pub fn set_cursor_visible(&mut self, cursor_visible: bool) {
self.cursor_visible = cursor_visible;
for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) {
let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial();
pointer
.pointer()
.set_cursor(latest_enter_serial, None, 0, 0);
}
}
/// Whether show or hide client side decorations.
#[inline]
pub fn set_decorate(&mut self, decorate: bool) {
if let Some(frame) = self.frame.as_mut() {
frame.set_hidden(!decorate);
// Force the resize.
self.resize(self.size);
}
}
/// Mark that the window has focus.
///
/// Should be used from routine that sends focused event.
#[inline]
pub fn set_has_focus(&mut self, has_focus: bool) {
self.has_focus = has_focus;
}
/// Returns `true` if the requested state was applied.
pub fn set_ime_allowed(&mut self, allowed: bool) -> bool {
self.ime_allowed = allowed;
let mut applied = false;
for text_input in &self.text_inputs {
applied = true;
if allowed {
text_input.enable();
text_input.set_content_type_by_purpose(self.ime_purpose);
} else {
text_input.disable();
}
text_input.commit();
}
applied
}
/// Set the IME 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_cursor_rectangle(x, y, 0, 0);
text_input.commit();
}
}
/// Set the IME purpose.
pub fn set_ime_purpose(&mut self, purpose: ImePurpose) {
self.ime_purpose = purpose;
for text_input in &self.text_inputs {
text_input.set_content_type_by_purpose(purpose);
text_input.commit();
}
}
/// Get the IME purpose.
pub fn ime_purpose(&self) -> ImePurpose {
self.ime_purpose
}
/// Set the scale factor for the given window.
#[inline]
pub fn set_scale_factor(&mut self, scale_factor: f64) {
self.scale_factor = scale_factor;
// XXX when fractional scaling is not used update the buffer scale.
if self.fractional_scale.is_none() {
let _ = self.window.set_buffer_scale(self.scale_factor as _);
}
}
/// Set the window title to a new value.
///
/// This will autmatically truncate the title to something meaningfull.
pub fn set_title(&mut self, mut title: String) {
// Truncate the title to at most 1024 bytes, so that it does not blow up the protocol
// messages
if title.len() > 1024 {
let mut new_len = 1024;
while !title.is_char_boundary(new_len) {
new_len -= 1;
}
title.truncate(new_len);
}
// Update the CSD title.
if let Some(frame) = self.frame.as_mut() {
frame.set_title(&title);
}
self.window.set_title(&title);
self.title = title;
}
/// Mark the window as transparent.
#[inline]
pub fn set_transparent(&mut self, transparent: bool) {
self.transparent = transparent;
self.reload_transparency_hint();
}
/// Register text input on the top-level.
#[inline]
pub fn text_input_entered(&mut self, text_input: &ZwpTextInputV3) {
if !self.text_inputs.iter().any(|t| t == text_input) {
self.text_inputs.push(text_input.clone());
}
}
/// The text input left the top-level.
#[inline]
pub fn text_input_left(&mut self, text_input: &ZwpTextInputV3) {
if let Some(position) = self.text_inputs.iter().position(|t| t == text_input) {
self.text_inputs.remove(position);
}
}
/// Get the cached title.
#[inline]
pub fn title(&self) -> &str {
&self.title
}
}
impl Drop for WindowState {
fn drop(&mut self) {
let surface = self.window.wl_surface().clone();
unsafe {
ManuallyDrop::drop(&mut self.window);
}
surface.destroy();
}
}
impl From<ResizeDirection> for ResizeEdge {
fn from(value: ResizeDirection) -> Self {
match value {
ResizeDirection::North => ResizeEdge::Top,
ResizeDirection::West => ResizeEdge::Left,
ResizeDirection::NorthWest => ResizeEdge::TopLeft,
ResizeDirection::NorthEast => ResizeEdge::TopRight,
ResizeDirection::East => ResizeEdge::Right,
ResizeDirection::SouthWest => ResizeEdge::BottomLeft,
ResizeDirection::SouthEast => ResizeEdge::BottomRight,
ResizeDirection::South => ResizeEdge::Bottom,
}
}
}
// XXX rust doesn't allow from `Option`.
#[cfg(feature = "sctk-adwaita")]
fn into_sctk_adwaita_config(theme: Option<Theme>) -> sctk_adwaita::FrameConfig {
match theme {
Some(Theme::Light) => sctk_adwaita::FrameConfig::light(),
Some(Theme::Dark) => sctk_adwaita::FrameConfig::dark(),
None => sctk_adwaita::FrameConfig::auto(),
}
}

View file

@ -1,102 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="fractional_scale_v1">
<copyright>
Copyright © 2022 Kenny Levinsen
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="Protocol for requesting fractional surface scales">
This protocol allows a compositor to suggest for surfaces to render at
fractional scales.
A client can submit scaled content by utilizing wp_viewport. This is done by
creating a wp_viewport object for the surface and setting the destination
rectangle to the surface size before the scale factor is applied.
The buffer size is calculated by multiplying the surface size by the
intended scale.
The wl_surface buffer scale should remain set to 1.
If a surface has a surface-local size of 100 px by 50 px and wishes to
submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should
be used and the wp_viewport destination rectangle should be 100 px by 50 px.
For toplevel surfaces, the size is rounded halfway away from zero. The
rounding algorithm for subsurface position and size is not defined.
</description>
<interface name="wp_fractional_scale_manager_v1" version="1">
<description summary="fractional surface scale information">
A global interface for requesting surfaces to use fractional scales.
</description>
<request name="destroy" type="destructor">
<description summary="unbind the fractional surface scale interface">
Informs the server that the client will not be using this protocol
object anymore. This does not affect any other objects,
wp_fractional_scale_v1 objects included.
</description>
</request>
<enum name="error">
<entry name="fractional_scale_exists" value="0"
summary="the surface already has a fractional_scale object associated"/>
</enum>
<request name="get_fractional_scale">
<description summary="extend surface interface for scale information">
Create an add-on object for the the wl_surface to let the compositor
request fractional scales. If the given wl_surface already has a
wp_fractional_scale_v1 object associated, the fractional_scale_exists
protocol error is raised.
</description>
<arg name="id" type="new_id" interface="wp_fractional_scale_v1"
summary="the new surface scale info interface id"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface"/>
</request>
</interface>
<interface name="wp_fractional_scale_v1" version="1">
<description summary="fractional scale interface to a wl_surface">
An additional interface to a wl_surface object which allows the compositor
to inform the client of the preferred scale.
</description>
<request name="destroy" type="destructor">
<description summary="remove surface scale information for surface">
Destroy the fractional scale object. When this object is destroyed,
preferred_scale events will no longer be sent.
</description>
</request>
<event name="preferred_scale">
<description summary="notify of new preferred scale">
Notification of a new preferred scale for this surface that the
compositor suggests that the client should use.
The sent scale is the numerator of a fraction with a denominator of 120.
</description>
<arg name="scale" type="uint" summary="the new preferred scale"/>
</event>
</interface>
</protocol>