From 2f7484714d0e6cef4c4c4110b41351f8e3825b6e Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Wed, 3 Jan 2024 20:22:07 -0500 Subject: [PATCH 01/55] Nuke Wayland --- Cargo.lock | 94 ++--- window/Cargo.toml | 8 +- window/src/os/wayland/connection.rs | 553 +--------------------------- window/src/os/wayland/mod.rs | 39 +- 4 files changed, 74 insertions(+), 620 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c5ac91e0c8..d8c56573628 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3492,18 +3492,6 @@ dependencies = [ "memoffset 0.6.5", ] -[[package]] -name = "nix" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", -] - [[package]] name = "nix" version = "0.25.1" @@ -5023,20 +5011,23 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "smithay-client-toolkit" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" +checksum = "e1476c3d89bb67079264b88aaf4f14358353318397e083b7c4e8c14517f55de7" dependencies = [ "bitflags 1.3.2", "dlib", "lazy_static", "log", "memmap2 0.5.10", - "nix 0.24.3", - "pkg-config", + "nix 0.26.4", + "thiserror", + "wayland-backend", "wayland-client", "wayland-cursor", "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", ] [[package]] @@ -6082,84 +6073,97 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] -name = "wayland-client" -version = "0.29.5" +name = "wayland-backend" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8" dependencies = [ - "bitflags 1.3.2", + "cc", "downcast-rs", - "libc", - "nix 0.24.3", + "io-lifetimes", + "nix 0.26.4", "scoped-tls", - "wayland-commons", - "wayland-scanner", + "smallvec", "wayland-sys", ] [[package]] -name = "wayland-commons" -version = "0.29.5" +name = "wayland-client" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +checksum = "489c9654770f674fc7e266b3c579f4053d7551df0ceb392f153adb1f9ed06ac8" dependencies = [ - "nix 0.24.3", - "once_cell", - "smallvec", - "wayland-sys", + "bitflags 1.3.2", + "nix 0.26.4", + "wayland-backend", + "wayland-scanner", ] [[package]] name = "wayland-cursor" -version = "0.29.5" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +checksum = "2d0c3a0d5b4b688b07b0442362d3ed6bf04724fcc16cd69ab6285b90dbc487aa" dependencies = [ - "nix 0.24.3", + "nix 0.26.4", "wayland-client", "xcursor", ] [[package]] name = "wayland-egl" -version = "0.29.5" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402de949f81a012926d821a2d659f930694257e76dd92b6e0042ceb27be4107d" +checksum = "1187695fe81c3153c3163f9d2953149f638c5d7dbc6fe988914ca3f4961e28ed" dependencies = [ - "wayland-client", + "wayland-backend", "wayland-sys", ] [[package]] name = "wayland-protocols" -version = "0.29.5" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b28101e5ca94f70461a6c2d610f76d85ad223d042dd76585ab23d3422dd9b4d" +dependencies = [ + "bitflags 1.3.2", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +checksum = "fce991093320e4a6a525876e6b629ab24da25f9baef0c2e0080ad173ec89588a" dependencies = [ "bitflags 1.3.2", + "wayland-backend", "wayland-client", - "wayland-commons", + "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-scanner" -version = "0.29.5" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +checksum = "b9b873b257fbc32ec909c0eb80dea312076a67014e65e245f5eb69a6b8ab330e" dependencies = [ "proc-macro2", + "quick-xml 0.28.2", "quote", - "xml-rs", ] [[package]] name = "wayland-sys" -version = "0.29.5" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" dependencies = [ "dlib", + "log", "pkg-config", ] diff --git a/window/Cargo.toml b/window/Cargo.toml index 6b6d9132bca..633ec7c2ae9 100644 --- a/window/Cargo.toml +++ b/window/Cargo.toml @@ -75,10 +75,10 @@ xcb = {version="1.2", features=["render", "randr", "dri2", "xkb", "xlib_xcb", "p xkbcommon = { version = "0.5.0", features = ["x11", "wayland"] } mio = {version="0.8", features=["os-ext"]} libc = "0.2" -smithay-client-toolkit = {version = "0.16.1", default-features=false, optional=true} -wayland-protocols = {version="0.29", optional=true} -wayland-client = {version="0.29", optional=true} -wayland-egl = {version="0.29", optional=true} +smithay-client-toolkit = {version = "0.17.0", default-features=false, optional=true} +wayland-protocols = {version="0.30", optional=true} +wayland-client = {version="0.30", optional=true} +wayland-egl = {version="0.30", optional=true} xcb-imdkit = { version="0.2", git="https://github.com/wez/xcb-imdkit-rs.git", branch="hangfix"} zbus = "3.13" zvariant = "3.14" diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index e94e6d0c3da..faed4ad765d 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -1,552 +1 @@ -#![allow(dead_code)] -use super::pointer::*; -use super::window::*; -use crate::connection::ConnectionOps; -use crate::os::wayland::inputhandler::InputHandler; -use crate::os::wayland::output::OutputHandler; -use crate::os::x11::keyboard::KeyboardWithFallback; -use crate::screen::{ScreenInfo, Screens}; -use crate::spawn::*; -use crate::{Appearance, Connection, ScreenRect, WindowEvent}; -use anyhow::{bail, Context}; -use mio::unix::SourceFd; -use mio::{Events, Interest, Poll, Token}; -use smithay_client_toolkit as toolkit; -use std::cell::RefCell; -use std::collections::HashMap; -use std::io::Read; -use std::os::unix::fs::FileExt; -use std::os::unix::io::FromRawFd; -use std::rc::Rc; -use std::sync::atomic::AtomicUsize; -use toolkit::environment::Environment; -use toolkit::reexports::client::Display; -use toolkit::seat::SeatListener; -use toolkit::shm::AutoMemPool; -use wayland_client::protocol::wl_keyboard::{Event as WlKeyboardEvent, KeymapFormat, WlKeyboard}; -use wayland_client::{EventQueue, Main}; - -toolkit::default_environment!(MyEnvironment, desktop, -fields=[ - output_handler: OutputHandler, - input_handler: InputHandler, -], -singles=[ - wayland_protocols::wlr::unstable::output_management::v1::client::zwlr_output_manager_v1::ZwlrOutputManagerV1 => output_handler, - wayland_protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3 => input_handler, -]); - -impl MyEnvironment { - pub fn input_handler(&mut self) -> &mut InputHandler { - &mut self.input_handler - } -} - -pub struct WaylandConnection { - should_terminate: RefCell, - pub(crate) next_window_id: AtomicUsize, - pub(crate) windows: RefCell>>>, - - // Take care with the destruction order: the underlying wayland - // libraries are not safe and require destruction in reverse - // creation order. This list of fields must reflect that otherwise - // we'll segfault on shutdown. - // Rust guarantees that struct fields are dropped in the order - // they appear in the struct, so the Display must be at the - // bottom of this list, and opengl, which depends on everything - // must be ahead of the rest. - pub(crate) gl_connection: RefCell>>, - pub(crate) pointer: RefCell, - pub(crate) keyboard_mapper: RefCell>, - pub(crate) keyboard_window_id: RefCell>, - pub(crate) surface_to_window_id: RefCell>, - pub(crate) active_surface_id: RefCell, - - /// Repeats per second - pub(crate) key_repeat_rate: RefCell, - - pub(crate) mem_pool: RefCell, - - /// Delay before repeating, in milliseconds - pub(crate) key_repeat_delay: RefCell, - pub(crate) last_serial: RefCell, - seat_listener: SeatListener, - pub(crate) environment: Environment, - event_q: RefCell, - pub(crate) display: RefCell, -} - -impl Drop for WaylandConnection { - fn drop(&mut self) { - self.environment - .with_inner(|env| env.input_handler.shutdown()); - } -} - -impl WaylandConnection { - pub fn create_new() -> anyhow::Result { - let (environment, display, event_q) = toolkit::new_default_environment!( - MyEnvironment, - desktop, - fields = [ - output_handler: OutputHandler::new(), - input_handler: InputHandler::new(), - ] - )?; - - let mut pointer = None; - let mut seat_keyboards = HashMap::new(); - - for seat in environment.get_all_seats() { - if let Some((has_kbd, has_ptr, name)) = - toolkit::seat::with_seat_data(&seat, |seat_data| { - ( - seat_data.has_keyboard && !seat_data.defunct, - seat_data.has_pointer && !seat_data.defunct, - seat_data.name.clone(), - ) - }) - { - if has_kbd { - let keyboard = seat.get_keyboard(); - keyboard.quick_assign(|keyboard, event, _| { - let conn = Connection::get().unwrap().wayland(); - if let Err(err) = conn.keyboard_event(keyboard, event) { - log::error!("keyboard_event: {:#}", err); - } - }); - environment.with_inner(|env| env.input_handler.advise_seat(&seat, &keyboard)); - seat_keyboards.insert(name, keyboard); - } - if has_ptr { - pointer.replace(PointerDispatcher::register( - &seat, - environment.require_global(), - environment.require_global(), - environment.require_global(), - environment.get_primary_selection_manager(), - )?); - } - } - } - let pointer = - pointer.ok_or_else(|| anyhow::anyhow!("no seats have an available pointer"))?; - - let seat_listener; - { - let env = environment.clone(); - seat_listener = environment.listen_for_seats(move |seat, seat_data, _| { - if seat_data.has_keyboard { - if !seat_data.defunct { - // We only want to assign a new keyboard object if we don't already have - // one for this seat. When a seat is being created or updated, the listener - // can receive the same seat multiple times: for example, when switching - // back from another virtual console, the same seat is usually seen four - // times with different data flags: - // - // has_pointer: true; has_keyboard: false - // has_pointer: false; has_keyboard: false - // has_pointer: false; has_keyboard: true - // has_pointer: true; has_keyboard: true - // - // This is essentially telling the client to re-assign its keyboard and - // pointer, but that means that this listener will fire twice with - // has_keyboard set to true. If we assign a handler both times, then we end - // up handling key events twice. - if !seat_keyboards.contains_key(&seat_data.name) { - let keyboard = seat.get_keyboard(); - - keyboard.quick_assign(|keyboard, event, _| { - let conn = Connection::get().unwrap().wayland(); - if let Err(err) = conn.keyboard_event(keyboard, event) { - log::error!("keyboard_event: {:#}", err); - } - }); - env.with_inner(|env| env.input_handler.advise_seat(&seat, &keyboard)); - seat_keyboards.insert(seat_data.name.clone(), keyboard); - } - } else { - env.with_inner(|env| env.input_handler.seat_defunct(&seat)); - } - } else { - // If we previously had a keyboard object on this seat, it's no longer valid if - // has_keyboard is false, so we remove the keyboard object we knew about and - // thereby ensure that we assign a new keyboard object next time the listener - // fires for this seat with has_keyboard = true. - seat_keyboards.remove(&seat_data.name); - } - if seat_data.has_pointer && !seat_data.defunct { - let conn = Connection::get().unwrap().wayland(); - conn.pointer.borrow_mut().seat_changed(&seat); - } - }); - } - - let mem_pool = environment.create_auto_pool()?; - - Ok(Self { - display: RefCell::new(display), - environment, - should_terminate: RefCell::new(false), - next_window_id: AtomicUsize::new(1), - windows: RefCell::new(HashMap::new()), - event_q: RefCell::new(event_q), - pointer: RefCell::new(pointer), - seat_listener, - mem_pool: RefCell::new(mem_pool), - gl_connection: RefCell::new(None), - keyboard_mapper: RefCell::new(None), - key_repeat_rate: RefCell::new(25), - key_repeat_delay: RefCell::new(400), - keyboard_window_id: RefCell::new(None), - last_serial: RefCell::new(0), - surface_to_window_id: RefCell::new(HashMap::new()), - active_surface_id: RefCell::new(0), - }) - } - - fn keyboard_event( - &self, - keyboard: Main, - event: WlKeyboardEvent, - ) -> anyhow::Result<()> { - match &event { - WlKeyboardEvent::Enter { - serial, surface, .. - } => { - // update global active surface id - *self.active_surface_id.borrow_mut() = surface.as_ref().id(); - - *self.last_serial.borrow_mut() = *serial; - if let Some(&window_id) = self - .surface_to_window_id - .borrow() - .get(&surface.as_ref().id()) - { - self.keyboard_window_id.borrow_mut().replace(window_id); - self.environment.with_inner(|env| { - if let Some(input) = - env.input_handler.get_text_input_for_keyboard(&keyboard) - { - input.enable(); - input.commit(); - } - env.input_handler.advise_surface(&surface, &keyboard); - }); - } else { - log::warn!("{:?}, no known surface", event); - } - } - WlKeyboardEvent::Leave { serial, .. } => { - if let Some(input) = self - .environment - .with_inner(|env| env.input_handler.get_text_input_for_keyboard(&keyboard)) - { - input.disable(); - input.commit(); - } - *self.last_serial.borrow_mut() = *serial; - } - WlKeyboardEvent::Key { serial, .. } | WlKeyboardEvent::Modifiers { serial, .. } => { - *self.last_serial.borrow_mut() = *serial; - } - WlKeyboardEvent::RepeatInfo { rate, delay } => { - *self.key_repeat_rate.borrow_mut() = *rate; - *self.key_repeat_delay.borrow_mut() = *delay; - } - WlKeyboardEvent::Keymap { format, fd, size } => { - let mut file = unsafe { std::fs::File::from_raw_fd(*fd) }; - match format { - KeymapFormat::XkbV1 => { - let mut data = vec![0u8; *size as usize]; - // If we weren't passed a pipe, be sure to explicitly - // read from the start of the file - match file.read_exact_at(&mut data, 0) { - Ok(_) => {} - Err(err) => { - // ideally: we check for: - // err.kind() == std::io::ErrorKind::NotSeekable - // but that is not yet in stable rust - if err.raw_os_error() == Some(libc::ESPIPE) { - // It's a pipe, which cannot be seeked, so we - // just try reading from the current pipe position - file.read(&mut data).context("read from Keymap fd/pipe")?; - } else { - return Err(err).context("read_exact_at from Keymap fd"); - } - } - } - // Dance around CString panicing on the NUL terminator - // in the xkbcommon crate - while let Some(0) = data.last() { - data.pop(); - } - let s = String::from_utf8(data)?; - match KeyboardWithFallback::new_from_string(s) { - Ok(k) => { - self.keyboard_mapper.replace(Some(k)); - } - Err(err) => { - log::error!("Error processing keymap change: {:#}", err); - } - } - } - _ => {} - } - } - _ => {} - } - if let Some(&window_id) = self.keyboard_window_id.borrow().as_ref() { - if let Some(win) = self.window_by_id(window_id) { - let mut inner = win.borrow_mut(); - inner.keyboard_event(event); - } - } - - Ok(()) - } - - pub(crate) fn dispatch_to_focused_window(&self, event: WindowEvent) { - if let Some(&window_id) = self.keyboard_window_id.borrow().as_ref() { - if let Some(win) = self.window_by_id(window_id) { - let mut inner = win.borrow_mut(); - inner.events.dispatch(event); - } - } - } - - pub(crate) fn next_window_id(&self) -> usize { - self.next_window_id - .fetch_add(1, ::std::sync::atomic::Ordering::Relaxed) - } - - fn flush(&self) -> anyhow::Result<()> { - if let Err(e) = self.display.borrow_mut().flush() { - if e.kind() != ::std::io::ErrorKind::WouldBlock { - bail!("Error while flushing display: {}", e); - } - } - Ok(()) - } - - pub(crate) fn window_by_id(&self, window_id: usize) -> Option>> { - self.windows.borrow().get(&window_id).map(Rc::clone) - } - - pub(crate) fn with_window_inner< - R, - F: FnOnce(&mut WaylandWindowInner) -> anyhow::Result + Send + 'static, - >( - window: usize, - f: F, - ) -> promise::Future - where - R: Send + 'static, - { - let mut prom = promise::Promise::new(); - let future = prom.get_future().unwrap(); - - promise::spawn::spawn_into_main_thread(async move { - if let Some(handle) = Connection::get().unwrap().wayland().window_by_id(window) { - let mut inner = handle.borrow_mut(); - prom.result(f(&mut inner)); - } - }) - .detach(); - - future - } - - fn run_message_loop_impl(&self) -> anyhow::Result<()> { - const TOK_WL: usize = 0xffff_fffc; - const TOK_SPAWN: usize = 0xffff_fffd; - let tok_wl = Token(TOK_WL); - let tok_spawn = Token(TOK_SPAWN); - - let mut poll = Poll::new()?; - let mut events = Events::with_capacity(8); - poll.registry().register( - &mut SourceFd(&self.display.borrow().get_connection_fd()), - tok_wl, - Interest::READABLE, - )?; - poll.registry().register( - &mut SourceFd(&SPAWN_QUEUE.raw_fd()), - tok_spawn, - Interest::READABLE, - )?; - - while !*self.should_terminate.borrow() { - // Check the spawn queue before we try to sleep; there may - // be work pending and we don't guarantee that there is a - // 1:1 wakeup to queued function, so we need to be assertive - // in order to avoid missing wakeups - let timeout = if SPAWN_QUEUE.run() { - // if we processed one, we don't want to sleep because - // there may be others to deal with - Some(std::time::Duration::from_secs(0)) - } else { - None - }; - - { - let mut event_q = self.event_q.borrow_mut(); - if let Err(err) = event_q.dispatch_pending(&mut (), |_, _, _| {}) { - return Err(err).with_context(|| { - format!( - "error during event_q.dispatch protocol_error={:?}", - self.display.borrow().protocol_error() - ) - }); - } - } - - self.flush()?; - if let Err(err) = poll.poll(&mut events, timeout) { - if err.kind() == std::io::ErrorKind::Interrupted { - continue; - } - bail!("polling for events: {:?}", err); - } - - for event in &events { - if event.token() == tok_wl { - let event_q = self.event_q.borrow(); - if let Some(guard) = event_q.prepare_read() { - if let Err(err) = guard.read_events() { - if err.kind() != std::io::ErrorKind::WouldBlock - && err.kind() != std::io::ErrorKind::Interrupted - { - return Err(err).with_context(|| { - format!( - "error during event_q.read_events protocol_error={:?}", - self.display.borrow().protocol_error() - ) - }); - } - } - } - } - } - } - Ok(()) - } - - pub(crate) fn advise_of_appearance_change(&self, appearance: crate::Appearance) { - for win in self.windows.borrow().values() { - win.borrow_mut().appearance_changed(appearance); - } - } -} - -impl ConnectionOps for WaylandConnection { - fn name(&self) -> String { - "Wayland".to_string() - } - - fn terminate_message_loop(&self) { - *self.should_terminate.borrow_mut() = true; - } - - fn get_appearance(&self) -> Appearance { - match promise::spawn::block_on(crate::os::xdg_desktop_portal::get_appearance()) { - Ok(Some(appearance)) => return appearance, - Ok(None) => {} - Err(err) => { - log::debug!("Unable to resolve appearance using xdg-desktop-portal: {err:#}"); - } - } - // fallback - Appearance::Light - } - - fn run_message_loop(&self) -> anyhow::Result<()> { - let res = self.run_message_loop_impl(); - // Ensure that we drop these eagerly, to avoid - // noisy errors wrt. global destructors unwinding - // in unexpected places - self.windows.borrow_mut().clear(); - res - } - - fn screens(&self) -> anyhow::Result { - if let Some(screens) = self - .environment - .with_inner(|env| env.output_handler.screens()) - { - return Ok(screens); - } - - let mut by_name = HashMap::new(); - let mut virtual_rect: ScreenRect = euclid::rect(0, 0, 0, 0); - let config = config::configuration(); - for output in self.environment.get_all_outputs() { - toolkit::output::with_output_info(&output, |info| { - let name = if info.name.is_empty() { - format!("{} {}", info.model, info.make) - } else { - info.name.clone() - }; - - let (width, height) = info - .modes - .iter() - .find(|mode| mode.is_current) - .map(|mode| mode.dimensions) - .unwrap_or((info.physical_size.0, info.physical_size.1)); - - let rect = euclid::rect( - info.location.0 as isize, - info.location.1 as isize, - width as isize, - height as isize, - ); - - let scale = info.scale_factor as f64; - - // FIXME: teach this how to resolve dpi_by_screen once - // dispatch_pending_event knows how to do the same - let effective_dpi = Some(config.dpi.unwrap_or(scale * crate::DEFAULT_DPI)); - - virtual_rect = virtual_rect.union(&rect); - by_name.insert( - name.clone(), - ScreenInfo { - name, - rect, - scale, - max_fps: None, - effective_dpi, - }, - ); - }); - } - - // The main screen is the one either at the origin of - // the virtual area, or if that doesn't exist for some weird - // reason, the screen closest to the origin. - let main = by_name - .values() - .min_by_key(|screen| { - screen - .rect - .origin - .to_f32() - .distance_to(euclid::Point2D::origin()) - .abs() as isize - }) - .ok_or_else(|| anyhow::anyhow!("no screens were found"))? - .clone(); - - // We don't yet know how to determine the active screen, - // so assume the main screen. - let active = main.clone(); - - Ok(Screens { - main, - active, - by_name, - virtual_rect, - }) - } -} +pub struct WaylandConnection {} diff --git a/window/src/os/wayland/mod.rs b/window/src/os/wayland/mod.rs index a904110a6fd..ee8defaae7c 100644 --- a/window/src/os/wayland/mod.rs +++ b/window/src/os/wayland/mod.rs @@ -1,26 +1,27 @@ #![cfg(all(unix, not(target_os = "macos")))] pub mod connection; -pub mod inputhandler; -pub mod output; -pub mod window; -pub use self::window::*; +// pub mod inputhandler; +// pub mod output; +// pub mod window; +// pub use self::window::*; pub use connection::*; -pub use output::*; -mod copy_and_paste; -mod drag_and_drop; -mod frame; -mod pointer; +// pub use output::*; +// mod copy_and_paste; +// mod drag_and_drop; +// mod frame; +// mod pointer; /// Returns the id of a wayland proxy object, suitable for using /// a key into hash maps -pub fn wl_id(obj: T) -> u32 -where - I: wayland_client::Interface, - T: AsRef>, - I: AsRef>, - I: From>, -{ - let proxy: &wayland_client::Proxy = obj.as_ref(); - proxy.id() -} +pub fn todo() {} +// pub fn wl_id(obj: T) -> u32 +// where +// I: wayland_client::Interface, +// T: AsRef>, +// I: AsRef>, +// I: From>, +// { +// let proxy: &wayland_client::Proxy = obj.as_ref(); +// proxy.id() +// } From a3190dbd169caea4bd398378be5e150d44507bc4 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Wed, 3 Jan 2024 20:28:32 -0500 Subject: [PATCH 02/55] Connections compile without complaining; time for window --- window/src/os/wayland/connection.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index faed4ad765d..93ab2e44db9 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -1 +1,25 @@ +use crate::ConnectionOps; + pub struct WaylandConnection {} + +impl WaylandConnection { + pub(crate) fn create_new() -> anyhow::Result { + Ok( WaylandConnection{} ) + } + + pub(crate) fn advise_of_appearance_change(&self, appearance: crate::Appearance) {} +} + +impl ConnectionOps for WaylandConnection { + fn name(&self) -> String { + todo!() + } + + fn terminate_message_loop(&self) { + todo!() + } + + fn run_message_loop(&self) -> anyhow::Result<()> { + todo!() + } +} From 610ca9ce137cfb8d33d175af701f4ee711b11dc4 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Wed, 3 Jan 2024 20:47:42 -0500 Subject: [PATCH 03/55] Compiles after nuking connection and window --- window/src/os/wayland/connection.rs | 2 + window/src/os/wayland/mod.rs | 4 +- window/src/os/wayland/window.rs | 1279 ++------------------------- 3 files changed, 71 insertions(+), 1214 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 93ab2e44db9..e61157f9544 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -1,3 +1,5 @@ +// TODO: change this +#![allow(dead_code, unused)] use crate::ConnectionOps; pub struct WaylandConnection {} diff --git a/window/src/os/wayland/mod.rs b/window/src/os/wayland/mod.rs index ee8defaae7c..b486ee13e29 100644 --- a/window/src/os/wayland/mod.rs +++ b/window/src/os/wayland/mod.rs @@ -3,8 +3,8 @@ pub mod connection; // pub mod inputhandler; // pub mod output; -// pub mod window; -// pub use self::window::*; +pub mod window; +pub use self::window::*; pub use connection::*; // pub use output::*; // mod copy_and_paste; diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 7b375b61574..a8f568c70d2 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -1,1267 +1,122 @@ -use super::copy_and_paste::*; -use super::frame::{ConceptConfig, ConceptFrame}; -use super::pointer::*; -use crate::connection::ConnectionOps; -use crate::os::wayland::connection::WaylandConnection; -use crate::os::wayland::wl_id; -use crate::os::x11::keyboard::KeyboardWithFallback; -use crate::{ - Appearance, Clipboard, Connection, Dimensions, MouseCursor, Point, Rect, - RequestedWindowGeometry, ResolvedGeometry, ScreenPoint, Window, WindowEvent, WindowEventSender, - WindowKeyEvent, WindowOps, WindowState, -}; -use anyhow::{anyhow, bail, Context}; -use async_io::Timer; -use async_trait::async_trait; -use config::ConfigHandle; -use filedescriptor::FileDescriptor; -use promise::{Future, Promise}; -use raw_window_handle::{ - HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, - WaylandDisplayHandle, WaylandWindowHandle, -}; -use smithay_client_toolkit as toolkit; +// TODO: change this +#![allow(dead_code, unused)] + use std::any::Any; -use std::cell::RefCell; -use std::convert::TryInto; -use std::io::Read; -use std::os::unix::io::AsRawFd; -use std::path::PathBuf; use std::rc::Rc; -use std::sync::{Arc, Mutex}; -use std::time::{Duration, Instant}; -use toolkit::get_surface_scale_factor; -use toolkit::reexports::client::protocol::wl_pointer::ButtonState; -use toolkit::reexports::client::protocol::wl_surface::WlSurface; -use toolkit::window::{Decorations, Event as SCTKWindowEvent, State}; -use wayland_client::protocol::wl_callback::WlCallback; -use wayland_client::protocol::wl_keyboard::{Event as WlKeyboardEvent, KeyState}; -use wayland_client::{Attached, Main}; -use wayland_egl::{is_available as egl_is_available, WlEglSurface}; -use wezterm_font::FontConfiguration; -use wezterm_input_types::*; - -#[derive(Debug)] -struct KeyRepeatState { - when: Instant, - event: WindowKeyEvent, -} - -impl KeyRepeatState { - fn schedule(state: Arc>, window_id: usize) { - promise::spawn::spawn_into_main_thread(async move { - let delay; - let gap; - { - let conn = WaylandConnection::get().unwrap().wayland(); - let rate = *conn.key_repeat_rate.borrow() as u64; - if rate == 0 { - return; - } - delay = Duration::from_millis(*conn.key_repeat_delay.borrow() as u64); - gap = Duration::from_millis(1000 / rate); - } - - let mut initial = true; - Timer::after(delay).await; - loop { - { - let handle = { - let conn = WaylandConnection::get().unwrap().wayland(); - match conn.window_by_id(window_id) { - Some(handle) => handle, - None => return, - } - }; - - let mut inner = handle.borrow_mut(); - - if inner.key_repeat.as_ref().map(|(_, k)| Arc::as_ptr(k)) - != Some(Arc::as_ptr(&state)) - { - // Key was released and/or some other key is doing - // its own repetition now - return; - } - - let mut st = state.lock().unwrap(); - - let mut repeat_count = 1; - - let mut elapsed = st.when.elapsed(); - if initial { - elapsed -= delay; - initial = false; - } - // If our scheduling interval is longer than the repeat - // gap, we need to inflate the repeat count to match - // the intended rate - while elapsed >= gap { - repeat_count += 1; - elapsed -= gap; - } - - let event = match st.event.clone() { - WindowKeyEvent::KeyEvent(mut key) => { - key.repeat_count = repeat_count; - WindowEvent::KeyEvent(key) - } - WindowKeyEvent::RawKeyEvent(mut raw) => { - raw.repeat_count = repeat_count; - WindowEvent::RawKeyEvent(raw) - } - }; - - inner.events.dispatch(event); - - st.when = Instant::now(); - } - - Timer::after(gap).await; - } - }) - .detach(); - } -} - -pub struct WaylandWindowInner { - window_id: usize, - pub(crate) events: WindowEventSender, - surface: Attached, - surface_factor: f64, - copy_and_paste: Arc>, - window: Option>, - dimensions: Dimensions, - resize_increments: Option<(u16, u16)>, - window_state: WindowState, - last_mouse_coords: Point, - mouse_buttons: MouseButtons, - hscroll_remainder: f64, - vscroll_remainder: f64, - modifiers: Modifiers, - leds: KeyboardLedStatus, - key_repeat: Option<(u32, Arc>)>, - pending_event: Arc>, - pending_mouse: Arc>, - pending_first_configure: Option>, - frame_callback: Option>, - invalidated: bool, - font_config: Rc, - text_cursor: Option, - appearance: Appearance, - config: ConfigHandle, - // cache the title for comparison to avoid spamming - // the compositor with updates that don't actually change it - title: Option, - // wegl_surface is listed before gl_state because it - // must be dropped before gl_state otherwise the underlying - // libraries will segfault on shutdown - wegl_surface: Option, - gl_state: Option>, -} +use config::ConfigHandle; +use promise::Future; +use raw_window_handle::HasRawDisplayHandle; +use raw_window_handle::HasRawWindowHandle; +use wezterm_font::FontConfiguration; -#[derive(Default, Clone, Debug)] -struct PendingEvent { - close: bool, - had_configure_event: bool, - refresh_decorations: bool, - configure: Option<(u32, u32)>, - dpi: Option, - window_state: Option, -} - -impl PendingEvent { - fn queue(&mut self, evt: SCTKWindowEvent) -> bool { - match evt { - SCTKWindowEvent::Close => { - if !self.close { - self.close = true; - true - } else { - false - } - } - SCTKWindowEvent::Refresh => { - if !self.refresh_decorations { - self.refresh_decorations = true; - true - } else { - false - } - } - SCTKWindowEvent::Configure { new_size, states } => { - let mut changed; - self.had_configure_event = true; - if let Some(new_size) = new_size { - changed = self.configure.is_none(); - self.configure.replace(new_size); - } else { - changed = true; - } - let mut state = WindowState::default(); - for s in &states { - match s { - State::Fullscreen => { - state |= WindowState::FULL_SCREEN; - } - State::Maximized - | State::TiledLeft - | State::TiledRight - | State::TiledTop - | State::TiledBottom => { - state |= WindowState::MAXIMIZED; - } - _ => {} - } - } - log::debug!( - "Config: self.window_state={:?}, states:{:?} {:?}", - self.window_state, - state, - states - ); - if self.window_state.is_none() && state != WindowState::default() { - changed = true; - } - // Always set it to avoid losing non-default -> default transitions - self.window_state.replace(state); - changed - } - } - } -} +use crate::Clipboard; +use crate::MouseCursor; +use crate::RequestedWindowGeometry; +use crate::Window; +use crate::WindowEvent; +use crate::WindowOps; #[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct WaylandWindow(usize); impl WaylandWindow { pub async fn new_window( - class_name: &str, - name: &str, - geometry: RequestedWindowGeometry, - config: Option<&ConfigHandle>, - font_config: Rc, - event_handler: F, + _class_name: &str, + _name: &str, + _geometry: RequestedWindowGeometry, + _config: Option<&ConfigHandle>, + _font_config: Rc, + _event_handler: F, ) -> anyhow::Result where F: 'static + FnMut(WindowEvent, &Window), { - let config = match config { - Some(c) => c.clone(), - None => config::configuration(), - }; - let conn = WaylandConnection::get() - .ok_or_else(|| { - anyhow!( - "new_window must be called on the gui thread after Connection::init has succeeded", - ) - })? - .wayland(); - - let window_id = conn.next_window_id(); - let pending_event = Arc::new(Mutex::new(PendingEvent::default())); - - let (pending_first_configure, wait_configure) = async_channel::bounded(1); - - let surface = conn.environment.create_surface_with_scale_callback({ - let pending_event = Arc::clone(&pending_event); - move |dpi, surface, _dispatch_data| { - pending_event.lock().unwrap().dpi.replace(dpi); - log::debug!( - "surface id={} dpi scale changed to {}", - surface.as_ref().id(), - dpi - ); - WaylandConnection::with_window_inner(window_id, move |inner| { - inner.dispatch_pending_event(); - Ok(()) - }); - } - }); - conn.surface_to_window_id - .borrow_mut() - .insert(surface.as_ref().id(), window_id); - - let ResolvedGeometry { - x: _, - y: _, - width, - height, - } = conn.resolve_geometry(geometry); - - let dimensions = Dimensions { - pixel_width: width, - pixel_height: height, - dpi: config.dpi.unwrap_or(crate::DEFAULT_DPI) as usize, - }; - - let theme_manager = None; - - let mut window = conn - .environment - .create_window::( - surface.clone().detach(), - theme_manager, - ( - dimensions.pixel_width as u32, - dimensions.pixel_height as u32, - ), - { - let pending_event = Arc::clone(&pending_event); - move |evt, mut _dispatch_data| { - if pending_event.lock().unwrap().queue(evt) { - WaylandConnection::with_window_inner(window_id, move |inner| { - inner.dispatch_pending_event(); - Ok(()) - }); - } - } - }, - ) - .context("Failed to create window")?; - - window.set_app_id(class_name.to_string()); - window.set_resizable(true); - window.set_title(name.to_string()); - let decorations = config.window_decorations; - - window.set_decorate(if decorations == WindowDecorations::NONE { - Decorations::None - } else if decorations == WindowDecorations::default() { - Decorations::FollowServer - } else { - // SCTK/Wayland don't allow more nuance than "decorations are hidden", - // so if we have a mixture of things, then we need to force our - // client side decoration rendering. - Decorations::ClientSide - }); - - window.set_frame_config(ConceptConfig { - font_config: Some(Rc::clone(&font_config)), - config: config.clone(), - }); - - window.set_min_size(Some((32, 32))); - - let copy_and_paste = CopyAndPaste::create(); - let pending_mouse = PendingMouse::create(window_id, ©_and_paste); - - conn.pointer.borrow().add_window(&surface, &pending_mouse); - - let inner = Rc::new(RefCell::new(WaylandWindowInner { - window_id, - font_config, - config, - key_repeat: None, - copy_and_paste, - events: WindowEventSender::new(event_handler), - surface, - surface_factor: 1.0, - invalidated: false, - window: Some(window), - dimensions, - resize_increments: None, - window_state: WindowState::default(), - last_mouse_coords: Point::new(0, 0), - mouse_buttons: MouseButtons::NONE, - hscroll_remainder: 0.0, - vscroll_remainder: 0.0, - modifiers: Modifiers::NONE, - leds: KeyboardLedStatus::empty(), - pending_event, - pending_mouse, - pending_first_configure: Some(pending_first_configure), - frame_callback: None, - title: None, - gl_state: None, - wegl_surface: None, - text_cursor: None, - appearance: Appearance::Light, - })); - - let window_handle = Window::Wayland(WaylandWindow(window_id)); - inner - .borrow_mut() - .events - .assign_window(window_handle.clone()); - - conn.windows.borrow_mut().insert(window_id, inner.clone()); - - wait_configure.recv().await?; - - Ok(window_handle) - } -} - -unsafe impl HasRawDisplayHandle for WaylandWindowInner { - fn raw_display_handle(&self) -> RawDisplayHandle { - let mut handle = WaylandDisplayHandle::empty(); - let conn = WaylandConnection::get().unwrap().wayland(); - handle.display = conn.display.borrow().c_ptr() as _; - RawDisplayHandle::Wayland(handle) + todo!("WaylandWindow::new_window") } } -unsafe impl HasRawWindowHandle for WaylandWindowInner { - fn raw_window_handle(&self) -> RawWindowHandle { - let mut handle = WaylandWindowHandle::empty(); - handle.surface = self.surface.as_ref().c_ptr() as *mut _; - RawWindowHandle::Wayland(handle) - } -} - -impl WaylandWindowInner { - pub(crate) fn appearance_changed(&mut self, appearance: Appearance) { - if appearance != self.appearance { - self.appearance = appearance; - self.events - .dispatch(WindowEvent::AppearanceChanged(appearance)); - } - } - - pub(crate) fn keyboard_event(&mut self, event: WlKeyboardEvent) { - let conn = WaylandConnection::get().unwrap().wayland(); - let mut mapper = conn.keyboard_mapper.borrow_mut(); - let mapper = mapper.as_mut().expect("no keymap"); - - match event { - WlKeyboardEvent::Enter { keys, .. } => { - // Keys is bytes, but is really u32 keysyms - let key_codes = keys - .chunks_exact(4) - .map(|c| u32::from_ne_bytes(c.try_into().unwrap())) - .collect::>(); - - log::trace!("keyboard event: Enter with keys: {:?}", key_codes); - - self.emit_focus(mapper, true); - } - WlKeyboardEvent::Leave { .. } => { - self.emit_focus(mapper, false); - } - WlKeyboardEvent::Key { key, state, .. } => { - if let Some(event) = - mapper.process_wayland_key(key, state == KeyState::Pressed, &mut self.events) - { - let rep = Arc::new(Mutex::new(KeyRepeatState { - when: Instant::now(), - event, - })); - self.key_repeat.replace((key, Arc::clone(&rep))); - KeyRepeatState::schedule(rep, self.window_id); - } else if let Some((cur_key, _)) = self.key_repeat.as_ref() { - // important to check that it's the same key, because the release of the previously - // repeated key can come right after the press of the newly held key - if *cur_key == key { - self.key_repeat.take(); - } - } - } - WlKeyboardEvent::Modifiers { - mods_depressed, - mods_latched, - mods_locked, - group, - .. - } => { - mapper.update_modifier_state(mods_depressed, mods_latched, mods_locked, group); - - let mods = mapper.get_key_modifiers(); - let leds = mapper.get_led_status(); - - let changed = (mods != self.modifiers) || (leds != self.leds); - - self.modifiers = mapper.get_key_modifiers(); - self.leds = mapper.get_led_status(); - - if changed { - self.events - .dispatch(WindowEvent::AdviseModifiersLedStatus(mods, leds)); - } - } - _ => {} - } - } - - fn emit_focus(&mut self, mapper: &mut KeyboardWithFallback, focused: bool) { - // Clear the modifiers when we change focus, otherwise weird - // things can happen. For instance, if we lost focus because - // CTRL+SHIFT+N was pressed to spawn a new window, we'd be - // left stuck with CTRL+SHIFT held down and the window would - // be left in a broken state. - - self.modifiers = Modifiers::NONE; - mapper.update_modifier_state(0, 0, 0, 0); - self.key_repeat.take(); - self.events.dispatch(WindowEvent::FocusChanged(focused)); - self.text_cursor.take(); - } - - pub(crate) fn dispatch_dropped_files(&mut self, paths: Vec) { - self.events.dispatch(WindowEvent::DroppedFile(paths)); - } - - pub(crate) fn dispatch_pending_mouse(&mut self) { - // Dancing around the borrow checker and the call to self.refresh_frame() - let pending_mouse = Arc::clone(&self.pending_mouse); - - if let Some((x, y)) = PendingMouse::coords(&pending_mouse) { - let coords = Point::new( - self.surface_to_pixels(x as i32) as isize, - self.surface_to_pixels(y as i32) as isize, - ); - self.last_mouse_coords = coords; - let event = MouseEvent { - kind: MouseEventKind::Move, - coords, - screen_coords: ScreenPoint::new( - coords.x + self.dimensions.pixel_width as isize, - coords.y + self.dimensions.pixel_height as isize, - ), - mouse_buttons: self.mouse_buttons, - modifiers: self.modifiers, - }; - self.events.dispatch(WindowEvent::MouseEvent(event)); - self.refresh_frame(); - } - - while let Some((button, state)) = PendingMouse::next_button(&pending_mouse) { - let button_mask = match button { - MousePress::Left => MouseButtons::LEFT, - MousePress::Right => MouseButtons::RIGHT, - MousePress::Middle => MouseButtons::MIDDLE, - }; - - if state == ButtonState::Pressed { - self.mouse_buttons |= button_mask; - } else { - self.mouse_buttons -= button_mask; - } - - let event = MouseEvent { - kind: match state { - ButtonState::Pressed => MouseEventKind::Press(button), - ButtonState::Released => MouseEventKind::Release(button), - _ => continue, - }, - coords: self.last_mouse_coords, - screen_coords: ScreenPoint::new( - self.last_mouse_coords.x + self.dimensions.pixel_width as isize, - self.last_mouse_coords.y + self.dimensions.pixel_height as isize, - ), - mouse_buttons: self.mouse_buttons, - modifiers: self.modifiers, - }; - self.events.dispatch(WindowEvent::MouseEvent(event)); - } - - if let Some((value_x, value_y)) = PendingMouse::scroll(&pending_mouse) { - let factor = self.get_dpi_factor() as f64; - - if value_x.signum() != self.hscroll_remainder.signum() { - // reset accumulator when changing scroll direction - self.hscroll_remainder = 0.0; - } - let scaled_x = (value_x * factor) + self.hscroll_remainder; - let discrete_x = scaled_x.trunc(); - self.hscroll_remainder = scaled_x - discrete_x; - if discrete_x != 0. { - let event = MouseEvent { - kind: MouseEventKind::HorzWheel(-discrete_x as i16), - coords: self.last_mouse_coords, - screen_coords: ScreenPoint::new( - self.last_mouse_coords.x + self.dimensions.pixel_width as isize, - self.last_mouse_coords.y + self.dimensions.pixel_height as isize, - ), - mouse_buttons: self.mouse_buttons, - modifiers: self.modifiers, - }; - self.events.dispatch(WindowEvent::MouseEvent(event)); - } - - if value_y.signum() != self.vscroll_remainder.signum() { - self.vscroll_remainder = 0.0; - } - let scaled_y = (value_y * factor) + self.vscroll_remainder; - let discrete_y = scaled_y.trunc(); - self.vscroll_remainder = scaled_y - discrete_y; - if discrete_y != 0. { - let event = MouseEvent { - kind: MouseEventKind::VertWheel(-discrete_y as i16), - coords: self.last_mouse_coords, - screen_coords: ScreenPoint::new( - self.last_mouse_coords.x + self.dimensions.pixel_width as isize, - self.last_mouse_coords.y + self.dimensions.pixel_height as isize, - ), - mouse_buttons: self.mouse_buttons, - modifiers: self.modifiers, - }; - self.events.dispatch(WindowEvent::MouseEvent(event)); - } - } - - if !PendingMouse::in_window(&pending_mouse) { - self.events.dispatch(WindowEvent::MouseLeave); - self.refresh_frame(); - } - } - - fn get_dpi_factor(&self) -> f64 { - self.dimensions.dpi as f64 / crate::DEFAULT_DPI as f64 - } - - fn surface_to_pixels(&self, surface: i32) -> i32 { - (surface as f64 * self.get_dpi_factor()).ceil() as i32 - } - - fn pixels_to_surface(&self, pixels: i32) -> i32 { - // Take care to round up, otherwise we can lose a pixel - // and that can effectively lose the final row of the - // terminal - ((pixels as f64) / self.get_dpi_factor()).ceil() as i32 - } - - fn dispatch_pending_event(&mut self) { - let mut pending; - { - let mut pending_events = self.pending_event.lock().unwrap(); - pending = pending_events.clone(); - *pending_events = PendingEvent::default(); - } - if pending.close { - self.events.dispatch(WindowEvent::CloseRequested); - } - - if let Some(window_state) = pending.window_state.take() { - log::debug!( - "dispatch_pending_event self.window_state={:?} pending:{:?}", - self.window_state, - window_state - ); - self.window_state = window_state; - } - - if pending.configure.is_none() { - if pending.dpi.is_some() { - // Synthesize a pending configure event for the dpi change - pending.configure.replace(( - self.pixels_to_surface(self.dimensions.pixel_width as i32) as u32, - self.pixels_to_surface(self.dimensions.pixel_height as i32) as u32, - )); - log::debug!("synthesize configure with {:?}", pending.configure); - } - } - - if let Some((mut w, mut h)) = pending.configure.take() { - if self.window.is_some() { - let factor = get_surface_scale_factor(&self.surface) as f64; - let old_dimensions = self.dimensions; - - // FIXME: teach this how to resolve dpi_by_screen - let dpi = self.config.dpi.unwrap_or(factor * crate::DEFAULT_DPI) as usize; - - // Do this early because this affects surface_to_pixels/pixels_to_surface below! - self.dimensions.dpi = dpi; - - let mut pixel_width = self.surface_to_pixels(w.try_into().unwrap()); - let mut pixel_height = self.surface_to_pixels(h.try_into().unwrap()); - - if self.window_state.can_resize() { - if let Some((x, y)) = self.resize_increments { - let desired_pixel_width = pixel_width - (pixel_width % x as i32); - let desired_pixel_height = pixel_height - (pixel_height % y as i32); - w = self.pixels_to_surface(desired_pixel_width) as u32; - h = self.pixels_to_surface(desired_pixel_height) as u32; - pixel_width = self.surface_to_pixels(w.try_into().unwrap()); - pixel_height = self.surface_to_pixels(h.try_into().unwrap()); - } - } - - // Update the window decoration size - self.window.as_mut().unwrap().resize(w, h); - - // Compute the new pixel dimensions - let new_dimensions = Dimensions { - pixel_width: pixel_width.try_into().unwrap(), - pixel_height: pixel_height.try_into().unwrap(), - dpi, - }; - - // Only trigger a resize if the new dimensions are different; - // this makes things more efficient and a little more smooth - if new_dimensions != old_dimensions { - self.dimensions = new_dimensions; - - self.events.dispatch(WindowEvent::Resized { - dimensions: self.dimensions, - window_state: self.window_state, - // We don't know if we're live resizing or not, so - // assume no. - live_resizing: false, - }); - // Avoid blurring by matching the scaling factor of the - // compositor; if it is going to double the size then - // we render at double the size anyway and tell it that - // the buffer is already doubled. - // Take care to detach the current buffer (managed by EGL), - // so that the compositor doesn't get annoyed by it not - // having dimensions that match the scale. - // The wegl_surface.resize won't take effect until - // we paint later on. - // We do this only if the scale has actually changed, - // otherwise interactive window resize will keep removing - // the window contents! - if let Some(wegl_surface) = self.wegl_surface.as_mut() { - wegl_surface.resize(pixel_width, pixel_height, 0, 0); - } - if self.surface_factor != factor { - let wayland_conn = Connection::get().unwrap().wayland(); - let mut pool = wayland_conn.mem_pool.borrow_mut(); - // Make a "fake" buffer with the right dimensions, as - // simply detaching the buffer can cause wlroots-derived - // compositors consider the window to be unconfigured. - if let Ok((_bytes, buffer)) = pool.buffer( - factor as i32, - factor as i32, - (factor * 4.0) as i32, - wayland_client::protocol::wl_shm::Format::Argb8888, - ) { - self.surface.attach(Some(&buffer), 0, 0); - self.surface.set_buffer_scale(factor as i32); - self.surface_factor = factor; - } - } - } - - self.refresh_frame(); - self.do_paint().unwrap(); - } - } - if pending.refresh_decorations && self.window.is_some() { - self.refresh_frame(); - } - if pending.had_configure_event && self.window.is_some() { - if let Some(notify) = self.pending_first_configure.take() { - // Allow window creation to complete - notify.try_send(()).ok(); - } - } - } - - fn refresh_frame(&mut self) { - if let Some(window) = self.window.as_mut() { - window.refresh(); - window.surface().commit(); - } - } - - fn enable_opengl(&mut self) -> anyhow::Result> { - let wayland_conn = Connection::get().unwrap().wayland(); - let mut wegl_surface = None; - - let gl_state = if !egl_is_available() { - Err(anyhow!("!egl_is_available")) - } else { - wegl_surface = Some(WlEglSurface::new( - &self.surface, - self.dimensions.pixel_width as i32, - self.dimensions.pixel_height as i32, - )); - - match wayland_conn.gl_connection.borrow().as_ref() { - Some(glconn) => crate::egl::GlState::create_wayland_with_existing_connection( - glconn, - wegl_surface.as_ref().unwrap(), - ), - None => crate::egl::GlState::create_wayland( - Some(wayland_conn.display.borrow().get_display_ptr() as *const _), - wegl_surface.as_ref().unwrap(), - ), - } - }; - let gl_state = gl_state.map(Rc::new).and_then(|state| unsafe { - wayland_conn - .gl_connection - .borrow_mut() - .replace(Rc::clone(state.get_connection())); - Ok(glium::backend::Context::new( - Rc::clone(&state), - true, - if cfg!(debug_assertions) { - glium::debug::DebugCallbackBehavior::DebugMessageOnError - } else { - glium::debug::DebugCallbackBehavior::Ignore - }, - )?) - })?; - - self.gl_state.replace(gl_state.clone()); - self.wegl_surface = wegl_surface; - - Ok(gl_state) - } - - fn next_frame_is_ready(&mut self) { - self.frame_callback.take(); - if self.invalidated { - self.do_paint().ok(); - } - } - - fn do_paint(&mut self) -> anyhow::Result<()> { - if self.frame_callback.is_some() { - // Painting now won't be productive, so skip it but - // remember that we need to be painted so that when - // the compositor is ready for us, we can paint then. - self.invalidated = true; - return Ok(()); - } - - self.invalidated = false; - - // Ask the compositor to wake us up when its time to paint the next frame, - // note that this only happens _after_ the next commit - let window_id = self.window_id; - let callback = self.surface.frame(); - callback.quick_assign(move |_source, _event, _data| { - WaylandConnection::with_window_inner(window_id, |inner| { - inner.next_frame_is_ready(); - Ok(()) - }); - }); - self.frame_callback.replace(callback); - - // The repaint has the side of effect of committing the surface, - // which is necessary for the frame callback to get triggered. - // Ordering the repaint after requesting the callback ensures that - // we will get woken at the appropriate time. - // - // - self.events.dispatch(WindowEvent::NeedRepaint); - - Ok(()) - } -} - -unsafe impl HasRawDisplayHandle for WaylandWindow { - fn raw_display_handle(&self) -> RawDisplayHandle { - let mut handle = WaylandDisplayHandle::empty(); - let conn = WaylandConnection::get().unwrap().wayland(); - handle.display = conn.display.borrow().c_ptr() as _; - RawDisplayHandle::Wayland(handle) - } -} - -unsafe impl HasRawWindowHandle for WaylandWindow { - fn raw_window_handle(&self) -> RawWindowHandle { - let conn = Connection::get().expect("raw_window_handle only callable on main thread"); - let handle = conn - .wayland() - .window_by_id(self.0) - .expect("window handle invalid!?"); - - let inner = handle.borrow(); - inner.raw_window_handle() - } -} - -#[async_trait(?Send)] impl WindowOps for WaylandWindow { - async fn enable_opengl(&self) -> anyhow::Result> { - let window = self.0; - promise::spawn::spawn(async move { - if let Some(handle) = Connection::get().unwrap().wayland().window_by_id(window) { - let mut inner = handle.borrow_mut(); - inner.enable_opengl() - } else { - anyhow::bail!("invalid window"); - } - }) - .await - } - - fn finish_frame(&self, frame: glium::Frame) -> anyhow::Result<()> { - frame.finish()?; - WaylandConnection::with_window_inner(self.0, |inner| { - inner.refresh_frame(); - Ok(()) - }); - Ok(()) + #[doc = r" Show a hidden window"] + fn show(&self) { + todo!() } fn notify(&self, t: T) where Self: Sized, { - WaylandConnection::with_window_inner(self.0, move |inner| { - inner - .events - .dispatch(WindowEvent::Notification(Box::new(t))); - Ok(()) - }); - } - - fn close(&self) { - WaylandConnection::with_window_inner(self.0, |inner| { - inner.close(); - Ok(()) - }); + todo!() + } + + #[doc = r" Setup opengl for rendering"] + #[must_use] + #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] + fn enable_opengl<'life0, 'async_trait>( + &'life0 self, + ) -> ::core::pin::Pin< + Box< + dyn ::core::future::Future>> + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + todo!() } + #[doc = r" Hide a visible window"] fn hide(&self) { - WaylandConnection::with_window_inner(self.0, |inner| { - inner.hide(); - Ok(()) - }); + todo!() } - fn toggle_fullscreen(&self) { - WaylandConnection::with_window_inner(self.0, |inner| { - inner.toggle_fullscreen(); - Ok(()) - }); - } - - fn config_did_change(&self, config: &ConfigHandle) { - let config = config.clone(); - WaylandConnection::with_window_inner(self.0, move |inner| { - inner.config_did_change(&config); - Ok(()) - }); - } - - fn focus(&self) { - WaylandConnection::with_window_inner(self.0, |inner| { - inner.focus(); - Ok(()) - }); - } - - fn show(&self) { - WaylandConnection::with_window_inner(self.0, |inner| { - inner.show(); - Ok(()) - }); + #[doc = r" Schedule the window to be closed"] + fn close(&self) { + todo!() } + #[doc = r" Change the cursor"] fn set_cursor(&self, cursor: Option) { - WaylandConnection::with_window_inner(self.0, move |inner| { - inner.set_cursor(cursor); - Ok(()) - }); + todo!() } + #[doc = r" Invalidate the window so that the entire client area will"] + #[doc = r" be repainted shortly"] fn invalidate(&self) { - WaylandConnection::with_window_inner(self.0, |inner| { - inner.invalidate(); - Ok(()) - }); - } - - fn set_text_cursor_position(&self, cursor: Rect) { - WaylandConnection::with_window_inner(self.0, move |inner| { - inner.set_text_cursor_position(cursor); - Ok(()) - }); + todo!() } + #[doc = r" Change the titlebar text for the window"] fn set_title(&self, title: &str) { - let title = title.to_owned(); - WaylandConnection::with_window_inner(self.0, move |inner| { - inner.set_title(title); - Ok(()) - }); - } - - fn maximize(&self) { - WaylandConnection::with_window_inner(self.0, move |inner| Ok(inner.maximize())); - } - - fn restore(&self) { - WaylandConnection::with_window_inner(self.0, move |inner| Ok(inner.restore())); + todo!() } + #[doc = r" Resize the inner or client area of the window"] fn set_inner_size(&self, width: usize, height: usize) { - WaylandConnection::with_window_inner(self.0, move |inner| { - Ok(inner.set_inner_size(width, height)) - }); - } - - fn request_drag_move(&self) { - WaylandConnection::with_window_inner(self.0, move |inner| { - inner.request_drag_move(); - Ok(()) - }); - } - - fn set_resize_increments(&self, x: u16, y: u16) { - WaylandConnection::with_window_inner(self.0, move |inner| { - Ok(inner.set_resize_increments(x, y)) - }); + todo!() } + #[doc = r" Initiate textual transfer from the clipboard"] fn get_clipboard(&self, clipboard: Clipboard) -> Future { - let mut promise = Promise::new(); - let future = promise.get_future().unwrap(); - let promise = Arc::new(Mutex::new(promise)); - WaylandConnection::with_window_inner(self.0, move |inner| { - let read = inner - .copy_and_paste - .lock() - .unwrap() - .get_clipboard_data(clipboard)?; - let promise = Arc::clone(&promise); - std::thread::spawn(move || { - let mut promise = promise.lock().unwrap(); - match read_pipe_with_timeout(read) { - Ok(result) => { - // Normalize the text to unix line endings, otherwise - // copying from eg: firefox inserts a lot of blank - // lines, and that is super annoying. - promise.ok(result.replace("\r\n", "\n")); - } - Err(e) => { - log::error!("while reading clipboard: {}", e); - promise.err(anyhow!("{}", e)); - } - }; - }); - Ok(()) - }); - future + todo!() } + #[doc = r" Set some text in the clipboard"] fn set_clipboard(&self, clipboard: Clipboard, text: String) { - WaylandConnection::with_window_inner(self.0, move |inner| { - inner - .copy_and_paste - .lock() - .unwrap() - .set_clipboard_data(clipboard, text); - Ok(()) - }); + todo!() } } -pub(crate) fn read_pipe_with_timeout(mut file: FileDescriptor) -> anyhow::Result { - let mut result = Vec::new(); - - file.set_non_blocking(true)?; - let mut pfd = libc::pollfd { - fd: file.as_raw_fd(), - events: libc::POLLIN, - revents: 0, - }; - - let mut buf = [0u8; 8192]; - - loop { - if unsafe { libc::poll(&mut pfd, 1, 3000) == 1 } { - match file.read(&mut buf) { - Ok(size) if size == 0 => { - break; - } - Ok(size) => { - result.extend_from_slice(&buf[..size]); - } - Err(e) => bail!("error reading from pipe: {}", e), - } - } else { - bail!("timed out reading from pipe"); - } +unsafe impl HasRawDisplayHandle for WaylandWindow { + fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle { + todo!() } - - Ok(String::from_utf8(result)?) } -impl WaylandWindowInner { - fn close(&mut self) { - self.events.dispatch(WindowEvent::Destroyed); - self.window.take(); - } - - fn hide(&mut self) { - if let Some(window) = self.window.as_ref() { - window.set_minimized(); - } - } - - fn toggle_fullscreen(&mut self) { - if let Some(window) = self.window.as_ref() { - if self.window_state.contains(WindowState::FULL_SCREEN) { - window.unset_fullscreen(); - } else { - window.set_fullscreen(None); - } - } - } - - fn focus(&mut self) { - log::debug!("Wayland doesn't support applications changing focus"); - } - - fn show(&mut self) { - if self.window.is_none() { - return; - } - // The window won't be visible until we've done our first paint, - // so we unconditionally queue a NeedRepaint event - self.do_paint().unwrap(); - } - - fn set_cursor(&mut self, cursor: Option) { - let names: &[&str] = match cursor { - Some(MouseCursor::Arrow) => &["arrow"], - Some(MouseCursor::Hand) => &["hand"], - Some(MouseCursor::SizeUpDown) => &["ns-resize"], - Some(MouseCursor::SizeLeftRight) => &["ew-resize"], - Some(MouseCursor::Text) => &["xterm"], - None => &[], - }; - let conn = Connection::get().unwrap().wayland(); - conn.pointer.borrow().set_cursor(names, None); - } - - fn invalidate(&mut self) { - if self.frame_callback.is_some() { - self.invalidated = true; - return; - } - self.do_paint().unwrap(); - } - - fn maximize(&mut self) { - if let Some(window) = self.window.as_mut() { - window.set_maximized(); - } - } - - fn restore(&mut self) { - if let Some(window) = self.window.as_mut() { - window.unset_maximized(); - } - } - - fn set_inner_size(&mut self, width: usize, height: usize) -> Dimensions { - let pixel_width = width as i32; - let pixel_height = height as i32; - let surface_width = self.pixels_to_surface(pixel_width) as u32; - let surface_height = self.pixels_to_surface(pixel_height) as u32; - // window.resize() doesn't generate a configure event, - // so we're going to fake one up, otherwise the window - // contents don't reflect the real size until eg: - // the focus is changed. - self.pending_event - .lock() - .unwrap() - .configure - .replace((surface_width, surface_height)); - // apply the synthetic configure event to the inner surfaces - self.dispatch_pending_event(); - - // and update the window decorations - if let Some(window) = self.window.as_mut() { - window.resize(surface_width, surface_height); - // The resize must be followed by a refresh call. - window.refresh(); - // In addition, resize doesn't take effect until - // the suface is commited - window.surface().commit(); - } - - let factor = get_surface_scale_factor(&self.surface); - Dimensions { - pixel_width: pixel_width as _, - pixel_height: pixel_height as _, - dpi: self - .config - .dpi - .unwrap_or(factor as f64 * crate::DEFAULT_DPI) as usize, - } - } - - fn request_drag_move(&self) { - if let Some(window) = self.window.as_ref() { - let conn = Connection::get().unwrap().wayland(); - let serial = *conn.last_serial.borrow(); - window.start_interactive_move(&conn.pointer.borrow().seat, serial); - } - } - - fn set_text_cursor_position(&mut self, rect: Rect) { - let surface_id = wl_id(&*self.surface); - let conn = Connection::get().unwrap().wayland(); - if surface_id == *conn.active_surface_id.borrow() { - if self.text_cursor.map(|prior| prior != rect).unwrap_or(true) { - self.text_cursor.replace(rect); - let factor = get_surface_scale_factor(&self.surface); - - conn.environment.with_inner(|env| { - if let Some(input) = env - .input_handler() - .get_text_input_for_surface(&self.surface) - { - input.set_cursor_rectangle( - rect.min_x() as i32 / factor, - rect.min_y() as i32 / factor, - rect.width() as i32 / factor, - rect.height() as i32 / factor, - ); - input.commit(); - } - }); - } - } - } - - /// Change the title for the window manager - fn set_title(&mut self, title: String) { - if let Some(last_title) = self.title.as_ref() { - if last_title == &title { - return; - } - } - if let Some(window) = self.window.as_ref() { - window.set_title(title.clone()); - } - self.refresh_frame(); - self.title = Some(title); - } - - fn set_resize_increments(&mut self, x: u16, y: u16) { - self.resize_increments = Some((x, y)); - } - - fn config_did_change(&mut self, config: &ConfigHandle) { - let dpi_changed = - self.config.dpi != config.dpi || self.config.dpi_by_screen != config.dpi_by_screen; - self.config = config.clone(); - if let Some(window) = self.window.as_mut() { - window.set_frame_config(ConceptConfig { - font_config: Some(Rc::clone(&self.font_config)), - config: config.clone(), - }); - - if dpi_changed { - // Synthesize a Resized event; we'll figure out the actual - // dpi to use there. - { - let mut pending = self.pending_event.lock().unwrap(); - if pending.configure.is_none() { - pending.configure.replace(( - self.dimensions.pixel_width as u32, - self.dimensions.pixel_height as u32, - )); - } - } - self.dispatch_pending_event(); - } - - // I tried re-applying the config to window.set_decorate - // here, but it crashed weston. I figure that users - // would prefer to manually close wezterm to change - // this setting! - } +unsafe impl HasRawWindowHandle for WaylandWindow { + fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { + todo!() } } From eca7bf4805b88486de351784e266f3d70dde4694 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Wed, 3 Jan 2024 21:57:10 -0500 Subject: [PATCH 04/55] Wow a message loop, but it does nothing... --- window/src/os/wayland/connection.rs | 54 +++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index e61157f9544..de965916c6f 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -1,17 +1,54 @@ // TODO: change this #![allow(dead_code, unused)] +use std::{borrow::BorrowMut, cell::RefCell}; + +use anyhow::Context; +use smithay_client_toolkit::{ + delegate_registry, + registry::{ProvidesRegistryState, RegistryState}, + registry_handlers, +}; +use wayland_client::{globals::registry_queue_init, Connection, EventQueue}; + use crate::ConnectionOps; -pub struct WaylandConnection {} +pub struct WaylandConnection { + event_queue: RefCell>, + wayland_state: RefCell, +} + +struct WaylandState { + registry_state: RegistryState, +} impl WaylandConnection { pub(crate) fn create_new() -> anyhow::Result { - Ok( WaylandConnection{} ) + let conn = Connection::connect_to_env()?; + let (globals, mut event_queue) = registry_queue_init::(&conn)?; + let qh = event_queue.handle(); + + let wayland_state = WaylandState { + registry_state: RegistryState::new(&globals), + }; + let wayland_connection = WaylandConnection { + event_queue: RefCell::new(event_queue), + wayland_state: RefCell::new(wayland_state), + }; + + Ok(wayland_connection) } pub(crate) fn advise_of_appearance_change(&self, appearance: crate::Appearance) {} } +impl ProvidesRegistryState for WaylandState { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + + registry_handlers!(); +} + impl ConnectionOps for WaylandConnection { fn name(&self) -> String { todo!() @@ -22,6 +59,17 @@ impl ConnectionOps for WaylandConnection { } fn run_message_loop(&self) -> anyhow::Result<()> { - todo!() + loop { + let mut event_q = self.event_queue.borrow_mut(); + let mut wayland_state = self.wayland_state.borrow_mut(); + if let Err(err) = event_q.dispatch_pending(&mut wayland_state) { + // TODO: show the protocol error in the display + return Err(err) + .with_context(|| format!("error during event_q.dispatch protcol_error")); + } + } + Ok(()) } } + +delegate_registry!(WaylandState); From e1fd45f1f642f89d824358841f6cc3bb9270bb8f Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Wed, 3 Jan 2024 22:35:05 -0500 Subject: [PATCH 05/55] No termination --- window/src/os/wayland/connection.rs | 19 +++++++++++++------ window/src/os/wayland/window.rs | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index de965916c6f..30990bab301 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -13,7 +13,9 @@ use wayland_client::{globals::registry_queue_init, Connection, EventQueue}; use crate::ConnectionOps; pub struct WaylandConnection { + should_terminate: RefCell, event_queue: RefCell>, + wayland_state: RefCell, } @@ -31,6 +33,7 @@ impl WaylandConnection { registry_state: RegistryState::new(&globals), }; let wayland_connection = WaylandConnection { + should_terminate: RefCell::new(false), event_queue: RefCell::new(event_queue), wayland_state: RefCell::new(wayland_state), }; @@ -59,14 +62,18 @@ impl ConnectionOps for WaylandConnection { } fn run_message_loop(&self) -> anyhow::Result<()> { - loop { + while !*self.should_terminate.borrow() { let mut event_q = self.event_queue.borrow_mut(); let mut wayland_state = self.wayland_state.borrow_mut(); - if let Err(err) = event_q.dispatch_pending(&mut wayland_state) { - // TODO: show the protocol error in the display - return Err(err) - .with_context(|| format!("error during event_q.dispatch protcol_error")); - } + // TODO + // Do dispatch pending later, just do blocking for now + // if let Err(err) = event_q.dispatch_pending(&mut wayland_state) { + // // TODO: show the protocol error in the display + // return Err(err) + // .with_context(|| format!("error during event_q.dispatch protcol_error")); + // } + // event_q.flush(); + event_q.blocking_dispatch(&mut wayland_state)?; } Ok(()) } diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index a8f568c70d2..fdf5b0b1c14 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -32,6 +32,7 @@ impl WaylandWindow { where F: 'static + FnMut(WindowEvent, &Window), { + log::debug!("Creating a window"); todo!("WaylandWindow::new_window") } } From 552d8dd5e82a20b31c1bd94ad553579fbc3db0b6 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Thu, 4 Jan 2024 00:53:19 -0500 Subject: [PATCH 06/55] Implement the epoll stuff --- window/src/os/wayland/connection.rs | 100 +++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 18 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 30990bab301..6dc7d542ae3 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -1,16 +1,17 @@ // TODO: change this #![allow(dead_code, unused)] -use std::{borrow::BorrowMut, cell::RefCell}; +use std::{borrow::BorrowMut, cell::RefCell, os::fd::AsRawFd}; -use anyhow::Context; +use anyhow::{Context, bail}; +use mio::{unix::SourceFd, Events, Interest, Poll, Token}; use smithay_client_toolkit::{ delegate_registry, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, }; -use wayland_client::{globals::registry_queue_init, Connection, EventQueue}; +use wayland_client::{globals::registry_queue_init, Connection, EventQueue, backend::{protocol::ProtocolError, WaylandError}}; -use crate::ConnectionOps; +use crate::{spawn::SPAWN_QUEUE, ConnectionOps}; pub struct WaylandConnection { should_terminate: RefCell, @@ -42,6 +43,81 @@ impl WaylandConnection { } pub(crate) fn advise_of_appearance_change(&self, appearance: crate::Appearance) {} + + fn run_message_loop_impl(&self) -> anyhow::Result<()> { + const TOK_WL: usize = 0xffff_fffc; + const TOK_SPAWN: usize = 0xffff_fffd; + let tok_wl = Token(TOK_WL); + let tok_spawn = Token(TOK_SPAWN); + + let mut poll = Poll::new()?; + let mut events = Events::with_capacity(8); + + let read_guard = self.event_queue.borrow().prepare_read()?; + let wl_fd = read_guard.connection_fd(); + + poll.registry().register( + &mut SourceFd(&wl_fd.as_raw_fd()), + tok_wl, + Interest::READABLE, + )?; + poll.registry().register( + &mut SourceFd(&SPAWN_QUEUE.raw_fd()), + tok_spawn, + Interest::READABLE, + )?; + + // JUST Realized that the reason we need the spawn executor is so we can have tasks + // scheduled (needed to open window) + while !*self.should_terminate.borrow() { + let timeout = if SPAWN_QUEUE.run() { + Some(std::time::Duration::from_secs(0)) + } else { + None + }; + + let mut event_q = self.event_queue.borrow_mut(); + { + let mut wayland_state = self.wayland_state.borrow_mut(); + if let Err(err) = event_q.dispatch_pending(&mut wayland_state) { + // TODO: show the protocol error in the display + return Err(err) + .with_context(|| format!("error during event_q.dispatch protcol_error")); + } + } + + event_q.flush()?; + if let Err(err) = poll.poll(&mut events, timeout) { + if err.kind() == std::io::ErrorKind::Interrupted { + continue; + } + bail!("polling for events: {:?}", err); + } + + for event in &events { + if event.token() != tok_wl { + continue; + } + + let a = event_q.prepare_read(); + if let Ok(guard) = event_q.prepare_read() { + if let Err(err) = guard.read() { + if let WaylandError::Protocol(perr) = err { + return Err(perr.into()); + // TODO + // return Err(perr).with_context(|| { + // format!("error during event_q.read protocol_error={:?}", + // perr) + // }) + } + } + } + } + + } + + Ok(()) + } } impl ProvidesRegistryState for WaylandState { @@ -62,20 +138,8 @@ impl ConnectionOps for WaylandConnection { } fn run_message_loop(&self) -> anyhow::Result<()> { - while !*self.should_terminate.borrow() { - let mut event_q = self.event_queue.borrow_mut(); - let mut wayland_state = self.wayland_state.borrow_mut(); - // TODO - // Do dispatch pending later, just do blocking for now - // if let Err(err) = event_q.dispatch_pending(&mut wayland_state) { - // // TODO: show the protocol error in the display - // return Err(err) - // .with_context(|| format!("error during event_q.dispatch protcol_error")); - // } - // event_q.flush(); - event_q.blocking_dispatch(&mut wayland_state)?; - } - Ok(()) + // TODO: match + self.run_message_loop_impl() } } From 966c3476aabb5bbbdae86347ca3302eaae2b5c86 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:13:16 -0500 Subject: [PATCH 07/55] Window progress: no WindowInner though --- window/src/os/wayland/connection.rs | 103 ++++++++++++++++++++++-- window/src/os/wayland/window.rs | 118 ++++++++++++++++++++++++++-- 2 files changed, 205 insertions(+), 16 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 6dc7d542ae3..322c076fa92 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -1,26 +1,29 @@ // TODO: change this #![allow(dead_code, unused)] -use std::{borrow::BorrowMut, cell::RefCell, os::fd::AsRawFd}; +use std::{borrow::BorrowMut, cell::RefCell, os::fd::AsRawFd, sync::atomic::AtomicUsize}; use anyhow::{Context, bail}; use mio::{unix::SourceFd, Events, Interest, Poll, Token}; use smithay_client_toolkit::{ delegate_registry, registry::{ProvidesRegistryState, RegistryState}, - registry_handlers, + registry_handlers, delegate_compositor, compositor::CompositorHandler, output::OutputHandler, delegate_xdg_shell, delegate_xdg_window, shell::xdg::window::WindowHandler, }; -use wayland_client::{globals::registry_queue_init, Connection, EventQueue, backend::{protocol::ProtocolError, WaylandError}}; +use wayland_client::{globals::{registry_queue_init, GlobalList}, Connection, EventQueue, backend::{protocol::ProtocolError, WaylandError}}; use crate::{spawn::SPAWN_QUEUE, ConnectionOps}; pub struct WaylandConnection { - should_terminate: RefCell, - event_queue: RefCell>, + pub(crate) should_terminate: RefCell, + pub(crate) next_window_id: AtomicUsize, - wayland_state: RefCell, + pub(crate) event_queue: RefCell>, + pub(crate) globals: RefCell, + + pub(crate) wayland_state: RefCell, } -struct WaylandState { +pub (crate) struct WaylandState { registry_state: RegistryState, } @@ -35,7 +38,11 @@ impl WaylandConnection { }; let wayland_connection = WaylandConnection { should_terminate: RefCell::new(false), + next_window_id: AtomicUsize::new(1), + event_queue: RefCell::new(event_queue), + globals: RefCell::new(globals), + wayland_state: RefCell::new(wayland_state), }; @@ -118,6 +125,11 @@ impl WaylandConnection { Ok(()) } + + pub(crate) fn next_window_id(&self) -> usize { + self.next_window_id + .fetch_add(1, ::std::sync::atomic::Ordering::Relaxed) + } } impl ProvidesRegistryState for WaylandState { @@ -128,9 +140,81 @@ impl ProvidesRegistryState for WaylandState { registry_handlers!(); } +impl CompositorHandler for WaylandState { + fn scale_factor_changed( + &mut self, + conn: &Connection, + qh: &wayland_client::QueueHandle, + surface: &wayland_client::protocol::wl_surface::WlSurface, + new_factor: i32, + ) { + todo!() + } + + fn frame( + &mut self, + conn: &Connection, + qh: &wayland_client::QueueHandle, + surface: &wayland_client::protocol::wl_surface::WlSurface, + time: u32, + ) { + todo!() + } +} + +impl OutputHandler for WaylandState { + fn output_state(&mut self) -> &mut smithay_client_toolkit::output::OutputState { + todo!() + } + + fn new_output( + &mut self, + conn: &Connection, + qh: &wayland_client::QueueHandle, + output: wayland_client::protocol::wl_output::WlOutput, + ) { + todo!() + } + + fn update_output( + &mut self, + conn: &Connection, + qh: &wayland_client::QueueHandle, + output: wayland_client::protocol::wl_output::WlOutput, + ) { + todo!() + } + + fn output_destroyed( + &mut self, + conn: &Connection, + qh: &wayland_client::QueueHandle, + output: wayland_client::protocol::wl_output::WlOutput, + ) { + todo!() + } +} + +impl WindowHandler for WaylandState { + fn request_close(&mut self, conn: &Connection, qh: &wayland_client::QueueHandle, window: &smithay_client_toolkit::shell::xdg::window::Window) { + todo!() + } + + fn configure( + &mut self, + conn: &Connection, + qh: &wayland_client::QueueHandle, + window: &smithay_client_toolkit::shell::xdg::window::Window, + configure: smithay_client_toolkit::shell::xdg::window::WindowConfigure, + serial: u32, + ) { + todo!() + } +} + impl ConnectionOps for WaylandConnection { fn name(&self) -> String { - todo!() + "Wayland".to_string() } fn terminate_message_loop(&self) { @@ -143,4 +227,7 @@ impl ConnectionOps for WaylandConnection { } } +delegate_xdg_shell!(WaylandState); +delegate_xdg_window!(WaylandState); +delegate_compositor!(WaylandState); delegate_registry!(WaylandState); diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index fdf5b0b1c14..3d767a3bd3d 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -3,14 +3,28 @@ use std::any::Any; use std::rc::Rc; +use std::sync::Arc; +use std::sync::Mutex; +use anyhow::anyhow; +use config::configuration; use config::ConfigHandle; use promise::Future; use raw_window_handle::HasRawDisplayHandle; use raw_window_handle::HasRawWindowHandle; +use smithay_client_toolkit::compositor::CompositorState; +use smithay_client_toolkit::registry::ProvidesRegistryState; +use smithay_client_toolkit::shell::WaylandSurface; +use smithay_client_toolkit::shell::xdg::XdgShell; +use smithay_client_toolkit::shell::xdg::window::DecorationMode; +use smithay_client_toolkit::shell::xdg::window::WindowDecorations as Decorations; +use wayland_client::globals::GlobalList; use wezterm_font::FontConfiguration; +use wezterm_input_types::WindowDecorations; +use crate::wayland::WaylandConnection; use crate::Clipboard; +use crate::ConnectionOps; use crate::MouseCursor; use crate::RequestedWindowGeometry; use crate::Window; @@ -22,18 +36,97 @@ pub struct WaylandWindow(usize); impl WaylandWindow { pub async fn new_window( - _class_name: &str, - _name: &str, - _geometry: RequestedWindowGeometry, - _config: Option<&ConfigHandle>, - _font_config: Rc, - _event_handler: F, + class_name: &str, + name: &str, + geometry: RequestedWindowGeometry, + config: Option<&ConfigHandle>, + font_config: Rc, + event_handler: F, ) -> anyhow::Result where F: 'static + FnMut(WindowEvent, &Window), { - log::debug!("Creating a window"); - todo!("WaylandWindow::new_window") + log::trace!("Creating a window"); + let config = match config { + Some(c) => c.clone(), + None => config::configuration(), + }; + + let conn = WaylandConnection::get() + .ok_or_else(|| { + anyhow!( + "new_window must be called on the gui thread after Connection:init has succeed", + ) + })? + .wayland(); + + let window_id = conn.next_window_id(); + // let pending_event = Arc::new(Mutex::new(PendingEvent::default())); + + // let (pending_first_configure, wait_configure) = async_channel::bounded(1); + + let qh = conn.event_queue.borrow().handle(); + let globals = conn.globals.borrow(); + + let compositor = CompositorState::bind(&globals, &qh)?; + let surface = compositor.create_surface(&qh); + + let xdg_shell = XdgShell::bind(&globals, &qh)?; + let window = xdg_shell.create_window( + surface, + Decorations::RequestServer, + &qh, + ); + + window.set_app_id(class_name.to_string()); + // TODO: investigate the resizable thing + // window.set_resizable(true); + window.set_title(name.to_string()); + let decorations = config.window_decorations; + + let decor_mode = if decorations == WindowDecorations::NONE { + None + } else if decorations == WindowDecorations::default() { + Some(DecorationMode::Server) + } else { + Some(DecorationMode::Client) + }; + window.request_decoration_mode(decor_mode); + + // TODO: I don't know anything about the frame thing + // window.set_frame_config(ConceptConfig { + + window.set_min_size(Some((32, 32))); + + // + // TODO: + // let copy_and_paste = CopyAndPaste::create(); + // let pending_mouse = PendingMouse::create(window_id, ©_and_paste); + + // conn.pointer.borrow().add_window(&surface, &pending_mouse); + + // TODO: WindowInner + + let window_handle = Window::Wayland(WaylandWindow(window_id)); + + // TODO: assign window inner + // + + + // window.set_decorate(if decorations == WindowDecorations::NONE { + // Decorations::None + // } else if decorations == WindowDecorations::default() { + // Decorations::FollowServer + // } else { + // // SCTK/Wayland don't allow more nuance than "decorations are hidden", + // // so if we have a mixture of things, then we need to force our + // // client side decoration rendering. + // Decorations::ClientSide + // }); + + window.commit(); + + Ok(window_handle) } } @@ -121,3 +214,12 @@ unsafe impl HasRawWindowHandle for WaylandWindow { todo!() } } + +#[derive(Default, Clone, Debug)] +struct PendingEvent { + close: bool, + had_configure_event: bool, + configure: Option<(u32, u32)>, + dpi: Option, + // window_state: Option, +} From 5d1ac893ff1a5b80696af0d848c07e53333099e8 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Thu, 4 Jan 2024 17:48:45 -0500 Subject: [PATCH 08/55] WaylandWindowInner --- window/src/os/wayland/connection.rs | 38 ++++++++--- window/src/os/wayland/window.rs | 101 ++++++++++++++++++++++++---- 2 files changed, 118 insertions(+), 21 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 322c076fa92..b60a7599ef1 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -1,21 +1,34 @@ // TODO: change this #![allow(dead_code, unused)] -use std::{borrow::BorrowMut, cell::RefCell, os::fd::AsRawFd, sync::atomic::AtomicUsize}; +use std::{ + borrow::BorrowMut, cell::RefCell, collections::HashMap, os::fd::AsRawFd, rc::Rc, + sync::atomic::AtomicUsize, +}; -use anyhow::{Context, bail}; +use anyhow::{bail, Context}; use mio::{unix::SourceFd, Events, Interest, Poll, Token}; use smithay_client_toolkit::{ - delegate_registry, + compositor::CompositorHandler, + delegate_compositor, delegate_registry, delegate_xdg_shell, delegate_xdg_window, + output::OutputHandler, registry::{ProvidesRegistryState, RegistryState}, - registry_handlers, delegate_compositor, compositor::CompositorHandler, output::OutputHandler, delegate_xdg_shell, delegate_xdg_window, shell::xdg::window::WindowHandler, + registry_handlers, + shell::xdg::window::WindowHandler, +}; +use wayland_client::{ + backend::{protocol::ProtocolError, WaylandError}, + globals::{registry_queue_init, GlobalList}, + Connection, EventQueue, }; -use wayland_client::{globals::{registry_queue_init, GlobalList}, Connection, EventQueue, backend::{protocol::ProtocolError, WaylandError}}; use crate::{spawn::SPAWN_QUEUE, ConnectionOps}; +use super::WaylandWindowInner; + pub struct WaylandConnection { pub(crate) should_terminate: RefCell, pub(crate) next_window_id: AtomicUsize, + pub(crate) windows: RefCell>>>, pub(crate) event_queue: RefCell>, pub(crate) globals: RefCell, @@ -23,7 +36,7 @@ pub struct WaylandConnection { pub(crate) wayland_state: RefCell, } -pub (crate) struct WaylandState { +pub(crate) struct WaylandState { registry_state: RegistryState, } @@ -39,6 +52,7 @@ impl WaylandConnection { let wayland_connection = WaylandConnection { should_terminate: RefCell::new(false), next_window_id: AtomicUsize::new(1), + windows: RefCell::new(HashMap::default()), event_queue: RefCell::new(event_queue), globals: RefCell::new(globals), @@ -120,7 +134,6 @@ impl WaylandConnection { } } } - } Ok(()) @@ -130,6 +143,10 @@ impl WaylandConnection { self.next_window_id .fetch_add(1, ::std::sync::atomic::Ordering::Relaxed) } + + pub(crate) fn window_by_id(&self, window_id: usize) -> Option>> { + self.windows.borrow().get(&window_id).map(Rc::clone) + } } impl ProvidesRegistryState for WaylandState { @@ -196,7 +213,12 @@ impl OutputHandler for WaylandState { } impl WindowHandler for WaylandState { - fn request_close(&mut self, conn: &Connection, qh: &wayland_client::QueueHandle, window: &smithay_client_toolkit::shell::xdg::window::Window) { + fn request_close( + &mut self, + conn: &Connection, + qh: &wayland_client::QueueHandle, + window: &smithay_client_toolkit::shell::xdg::window::Window, + ) { todo!() } diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 3d767a3bd3d..cafbfe83907 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -2,6 +2,7 @@ #![allow(dead_code, unused)] use std::any::Any; +use std::cell::RefCell; use std::rc::Rc; use std::sync::Arc; use std::sync::Mutex; @@ -12,23 +13,34 @@ use config::ConfigHandle; use promise::Future; use raw_window_handle::HasRawDisplayHandle; use raw_window_handle::HasRawWindowHandle; +use raw_window_handle::RawDisplayHandle; +use raw_window_handle::RawWindowHandle; +use raw_window_handle::WaylandWindowHandle; use smithay_client_toolkit::compositor::CompositorState; use smithay_client_toolkit::registry::ProvidesRegistryState; -use smithay_client_toolkit::shell::WaylandSurface; -use smithay_client_toolkit::shell::xdg::XdgShell; use smithay_client_toolkit::shell::xdg::window::DecorationMode; use smithay_client_toolkit::shell::xdg::window::WindowDecorations as Decorations; +use smithay_client_toolkit::shell::xdg::XdgShell; +use smithay_client_toolkit::shell::WaylandSurface; use wayland_client::globals::GlobalList; +use wayland_client::protocol::wl_surface::WlSurface; +use wayland_client::Proxy; +use wayland_egl::WlEglSurface; use wezterm_font::FontConfiguration; +use wezterm_input_types::KeyboardLedStatus; +use wezterm_input_types::Modifiers; +use wezterm_input_types::MouseButtons; use wezterm_input_types::WindowDecorations; use crate::wayland::WaylandConnection; use crate::Clipboard; +use crate::Connection; use crate::ConnectionOps; use crate::MouseCursor; use crate::RequestedWindowGeometry; use crate::Window; use crate::WindowEvent; +use crate::WindowEventSender; use crate::WindowOps; #[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] @@ -72,11 +84,7 @@ impl WaylandWindow { let surface = compositor.create_surface(&qh); let xdg_shell = XdgShell::bind(&globals, &qh)?; - let window = xdg_shell.create_window( - surface, - Decorations::RequestServer, - &qh, - ); + let window = xdg_shell.create_window(surface.clone(), Decorations::RequestServer, &qh); window.set_app_id(class_name.to_string()); // TODO: investigate the resizable thing @@ -98,7 +106,7 @@ impl WaylandWindow { window.set_min_size(Some((32, 32))); - // + // // TODO: // let copy_and_paste = CopyAndPaste::create(); // let pending_mouse = PendingMouse::create(window_id, ©_and_paste); @@ -106,12 +114,18 @@ impl WaylandWindow { // conn.pointer.borrow().add_window(&surface, &pending_mouse); // TODO: WindowInner + let inner = Rc::new(RefCell::new(WaylandWindowInner { + events: WindowEventSender::new(event_handler), + surface, + })); let window_handle = Window::Wayland(WaylandWindow(window_id)); // TODO: assign window inner - // - + inner + .borrow_mut() + .events + .assign_window(window_handle.clone()); // window.set_decorate(if decorations == WindowDecorations::NONE { // Decorations::None @@ -204,14 +218,21 @@ impl WindowOps for WaylandWindow { } unsafe impl HasRawDisplayHandle for WaylandWindow { - fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle { + fn raw_display_handle(&self) -> RawDisplayHandle { todo!() } } unsafe impl HasRawWindowHandle for WaylandWindow { - fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { - todo!() + fn raw_window_handle(&self) -> RawWindowHandle { + let conn = Connection::get().expect("raw_window_handle only callable on the main thread"); + let handle = conn + .wayland() + .window_by_id(self.0) + .expect("window handle invalid!?"); + + let inner = handle.borrow(); + inner.raw_window_handle() } } @@ -223,3 +244,57 @@ struct PendingEvent { dpi: Option, // window_state: Option, } + +pub struct WaylandWindowInner { + // window_id: usize, + pub(crate) events: WindowEventSender, + surface: WlSurface, + // surface_factor: f64, + // copy_and_paste: Arc>, + // window: Option>, + // dimensions: Dimensions, + // resize_increments: Option<(u16, u16)>, + // window_state: WindowState, + // last_mouse_coords: Point, + // mouse_buttons: MouseButtons, + // hscroll_remainder: f64, + // vscroll_remainder: f64, + // modifiers: Modifiers, + // leds: KeyboardLedStatus, + // key_repeat: Option<(u32, Arc>)>, + // pending_event: Arc>, + // pending_mouse: Arc>, + // pending_first_configure: Option>, + // frame_callback: Option>, + // invalidated: bool, + // font_config: Rc, + // text_cursor: Option, + // appearance: Appearance, + // config: ConfigHandle, + // // cache the title for comparison to avoid spamming + // // the compositor with updates that don't actually change it + // title: Option, + // // wegl_surface is listed before gl_state because it + // // must be dropped before gl_state otherwise the underlying + // // libraries will segfault on shutdown + // wegl_surface: Option, + // gl_state: Option>, +} + +unsafe impl HasRawDisplayHandle for WaylandWindowInner { + fn raw_display_handle(&self) -> RawDisplayHandle { + // let mut handle = WaylandDisplayHandle::empty(); + // let conn = WaylandConnection::get().unwrap().wayland(); + // handle.display = conn.display.borrow().c_ptr() as _; + // RawDisplayHandle::Wayland(handle) + todo!() + } +} + +unsafe impl HasRawWindowHandle for WaylandWindowInner { + fn raw_window_handle(&self) -> RawWindowHandle { + let mut handle = WaylandWindowHandle::empty(); + handle.surface = self.surface.id().as_ptr() as *mut _; + RawWindowHandle::Wayland(handle) + } +} From 4adb02fd38ed086133ab66d5369b9e831b080e4d Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Thu, 4 Jan 2024 19:46:38 -0500 Subject: [PATCH 09/55] No more todos! but I don't see a window --- window/src/os/wayland/connection.rs | 2 ++ window/src/os/wayland/window.rs | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index b60a7599ef1..d5ad6e5ffd1 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -30,6 +30,7 @@ pub struct WaylandConnection { pub(crate) next_window_id: AtomicUsize, pub(crate) windows: RefCell>>>, + pub(crate) connection: RefCell, pub(crate) event_queue: RefCell>, pub(crate) globals: RefCell, @@ -50,6 +51,7 @@ impl WaylandConnection { registry_state: RegistryState::new(&globals), }; let wayland_connection = WaylandConnection { + connection: RefCell::new(conn), should_terminate: RefCell::new(false), next_window_id: AtomicUsize::new(1), windows: RefCell::new(HashMap::default()), diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index cafbfe83907..dfb21b2be56 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -15,6 +15,7 @@ use raw_window_handle::HasRawDisplayHandle; use raw_window_handle::HasRawWindowHandle; use raw_window_handle::RawDisplayHandle; use raw_window_handle::RawWindowHandle; +use raw_window_handle::WaylandDisplayHandle; use raw_window_handle::WaylandWindowHandle; use smithay_client_toolkit::compositor::CompositorState; use smithay_client_toolkit::registry::ProvidesRegistryState; @@ -137,6 +138,7 @@ impl WaylandWindow { // // client side decoration rendering. // Decorations::ClientSide // }); + conn.windows.borrow_mut().insert(window_id, inner.clone()); window.commit(); @@ -219,13 +221,16 @@ impl WindowOps for WaylandWindow { unsafe impl HasRawDisplayHandle for WaylandWindow { fn raw_display_handle(&self) -> RawDisplayHandle { - todo!() + let mut handle = WaylandDisplayHandle::empty(); + let conn = WaylandConnection::get().unwrap().wayland(); + handle.display = conn.connection.borrow().display().id().as_ptr() as *mut _; + RawDisplayHandle::Wayland(handle) } } unsafe impl HasRawWindowHandle for WaylandWindow { fn raw_window_handle(&self) -> RawWindowHandle { - let conn = Connection::get().expect("raw_window_handle only callable on the main thread"); + let conn = Connection::get().expect("raw_window_handle only callable on main thread"); let handle = conn .wayland() .window_by_id(self.0) From 3f803ef07d4b224aebe095323ac3265f8414eff9 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Thu, 4 Jan 2024 22:33:07 -0500 Subject: [PATCH 10/55] Don't block ReadGuard after getting fd -> segfaulting webgpu --- window/src/os/wayland/connection.rs | 58 +++++++++++++++++++---------- window/src/os/wayland/window.rs | 3 +- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index d5ad6e5ffd1..7e82ebb1381 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -1,7 +1,7 @@ // TODO: change this #![allow(dead_code, unused)] use std::{ - borrow::BorrowMut, cell::RefCell, collections::HashMap, os::fd::AsRawFd, rc::Rc, + cell::RefCell, collections::HashMap, os::fd::AsRawFd, rc::Rc, sync::atomic::AtomicUsize, }; @@ -10,13 +10,13 @@ use mio::{unix::SourceFd, Events, Interest, Poll, Token}; use smithay_client_toolkit::{ compositor::CompositorHandler, delegate_compositor, delegate_registry, delegate_xdg_shell, delegate_xdg_window, - output::OutputHandler, + output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, - shell::xdg::window::WindowHandler, + shell::xdg::window::WindowHandler, delegate_output, }; use wayland_client::{ - backend::{protocol::ProtocolError, WaylandError}, + backend::WaylandError, globals::{registry_queue_init, GlobalList}, Connection, EventQueue, }; @@ -44,8 +44,8 @@ pub(crate) struct WaylandState { impl WaylandConnection { pub(crate) fn create_new() -> anyhow::Result { let conn = Connection::connect_to_env()?; - let (globals, mut event_queue) = registry_queue_init::(&conn)?; - let qh = event_queue.handle(); + let (globals, event_queue) = registry_queue_init::(&conn)?; + let _qh = event_queue.handle(); let wayland_state = WaylandState { registry_state: RegistryState::new(&globals), @@ -65,7 +65,7 @@ impl WaylandConnection { Ok(wayland_connection) } - pub(crate) fn advise_of_appearance_change(&self, appearance: crate::Appearance) {} + pub(crate) fn advise_of_appearance_change(&self, _appearance: crate::Appearance) {} fn run_message_loop_impl(&self) -> anyhow::Result<()> { const TOK_WL: usize = 0xffff_fffc; @@ -76,11 +76,13 @@ impl WaylandConnection { let mut poll = Poll::new()?; let mut events = Events::with_capacity(8); - let read_guard = self.event_queue.borrow().prepare_read()?; - let wl_fd = read_guard.connection_fd(); + let wl_fd = { + let read_guard = self.event_queue.borrow().prepare_read()?; + read_guard.connection_fd().as_raw_fd() + }; poll.registry().register( - &mut SourceFd(&wl_fd.as_raw_fd()), + &mut SourceFd(&wl_fd), tok_wl, Interest::READABLE, )?; @@ -122,9 +124,12 @@ impl WaylandConnection { continue; } + println!("READING WL EVENT"); + let a = event_q.prepare_read(); if let Ok(guard) = event_q.prepare_read() { if let Err(err) = guard.read() { + log::trace!("Event Q error: {:?}", err); if let WaylandError::Protocol(perr) = err { return Err(perr.into()); // TODO @@ -151,14 +156,6 @@ impl WaylandConnection { } } -impl ProvidesRegistryState for WaylandState { - fn registry(&mut self) -> &mut RegistryState { - &mut self.registry_state - } - - registry_handlers!(); -} - impl CompositorHandler for WaylandState { fn scale_factor_changed( &mut self, @@ -177,12 +174,14 @@ impl CompositorHandler for WaylandState { surface: &wayland_client::protocol::wl_surface::WlSurface, time: u32, ) { + log::trace!("frame: CompositorHandler"); todo!() } } impl OutputHandler for WaylandState { fn output_state(&mut self) -> &mut smithay_client_toolkit::output::OutputState { + log::trace!("output state: OutputHandler"); todo!() } @@ -192,6 +191,7 @@ impl OutputHandler for WaylandState { qh: &wayland_client::QueueHandle, output: wayland_client::protocol::wl_output::WlOutput, ) { + log::trace!("new output: OutputHandler"); todo!() } @@ -201,6 +201,7 @@ impl OutputHandler for WaylandState { qh: &wayland_client::QueueHandle, output: wayland_client::protocol::wl_output::WlOutput, ) { + log::trace!("update output: OutputHandler"); todo!() } @@ -210,6 +211,7 @@ impl OutputHandler for WaylandState { qh: &wayland_client::QueueHandle, output: wayland_client::protocol::wl_output::WlOutput, ) { + log::trace!("output destroyed: OutputHandler"); todo!() } } @@ -221,6 +223,7 @@ impl WindowHandler for WaylandState { qh: &wayland_client::QueueHandle, window: &smithay_client_toolkit::shell::xdg::window::Window, ) { + log::trace!("Request close on WindowHandler"); todo!() } @@ -232,6 +235,7 @@ impl WindowHandler for WaylandState { configure: smithay_client_toolkit::shell::xdg::window::WindowConfigure, serial: u32, ) { + log::trace!("Configuring Window Handler"); todo!() } } @@ -249,9 +253,25 @@ impl ConnectionOps for WaylandConnection { // TODO: match self.run_message_loop_impl() } + + fn screens(&self) -> anyhow::Result { + todo!("Screens is not implemented"); + } } +delegate_compositor!(WaylandState); +delegate_output!(WaylandState); + delegate_xdg_shell!(WaylandState); delegate_xdg_window!(WaylandState); -delegate_compositor!(WaylandState); + + delegate_registry!(WaylandState); + +impl ProvidesRegistryState for WaylandState { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } + + registry_handlers!(OutputState); +} diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index dfb21b2be56..29656b6337d 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -140,6 +140,7 @@ impl WaylandWindow { // }); conn.windows.borrow_mut().insert(window_id, inner.clone()); + log::trace!("About to commit the window"); window.commit(); Ok(window_handle) @@ -223,7 +224,7 @@ unsafe impl HasRawDisplayHandle for WaylandWindow { fn raw_display_handle(&self) -> RawDisplayHandle { let mut handle = WaylandDisplayHandle::empty(); let conn = WaylandConnection::get().unwrap().wayland(); - handle.display = conn.connection.borrow().display().id().as_ptr() as *mut _; + handle.display = conn.connection.borrow().backend().display_ptr() as *mut _; RawDisplayHandle::Wayland(handle) } } From 6f6c183ca8d3874bf3d62ecaa02303f37392dd9d Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Fri, 5 Jan 2024 00:37:29 -0500 Subject: [PATCH 11/55] Attempt on Implementing EGL for windows - wgpu gives segfault so move to EGL - implement terminating message loop to match prev implementation - still gives error but no segfault or panic right now --- window/src/os/wayland/connection.rs | 14 ++++- window/src/os/wayland/window.rs | 87 ++++++++++++++++++++++------- 2 files changed, 79 insertions(+), 22 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 7e82ebb1381..602d40ea531 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -30,6 +30,8 @@ pub struct WaylandConnection { pub(crate) next_window_id: AtomicUsize, pub(crate) windows: RefCell>>>, + pub(crate) gl_connection: RefCell>>, + pub(crate) connection: RefCell, pub(crate) event_queue: RefCell>, pub(crate) globals: RefCell, @@ -54,6 +56,7 @@ impl WaylandConnection { connection: RefCell::new(conn), should_terminate: RefCell::new(false), next_window_id: AtomicUsize::new(1), + gl_connection: RefCell::new(None), windows: RefCell::new(HashMap::default()), event_queue: RefCell::new(event_queue), @@ -246,12 +249,17 @@ impl ConnectionOps for WaylandConnection { } fn terminate_message_loop(&self) { - todo!() + log::trace!("Terminating Message Loop"); + *self.should_terminate.borrow_mut() = true; } fn run_message_loop(&self) -> anyhow::Result<()> { - // TODO: match - self.run_message_loop_impl() + let res = self.run_message_loop_impl(); + // Ensure that we drop these eagerly, to avoid + // noisy errors wrt. global destructors unwinding + // in unexpected places + self.windows.borrow_mut().clear(); + res } fn screens(&self) -> anyhow::Result { diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 29656b6337d..dc5e889eb14 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -8,6 +8,7 @@ use std::sync::Arc; use std::sync::Mutex; use anyhow::anyhow; +use async_trait::async_trait; use config::configuration; use config::ConfigHandle; use promise::Future; @@ -26,7 +27,7 @@ use smithay_client_toolkit::shell::WaylandSurface; use wayland_client::globals::GlobalList; use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::Proxy; -use wayland_egl::WlEglSurface; +use wayland_egl::{is_available as egl_is_available, WlEglSurface}; use wezterm_font::FontConfiguration; use wezterm_input_types::KeyboardLedStatus; use wezterm_input_types::Modifiers; @@ -118,6 +119,9 @@ impl WaylandWindow { let inner = Rc::new(RefCell::new(WaylandWindowInner { events: WindowEventSender::new(event_handler), surface, + + wegl_surface: None, + gl_state: None, })); let window_handle = Window::Wayland(WaylandWindow(window_id)); @@ -147,6 +151,7 @@ impl WaylandWindow { } } +#[async_trait(?Send)] impl WindowOps for WaylandWindow { #[doc = r" Show a hidden window"] fn show(&self) { @@ -160,22 +165,17 @@ impl WindowOps for WaylandWindow { todo!() } - #[doc = r" Setup opengl for rendering"] - #[must_use] - #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] - fn enable_opengl<'life0, 'async_trait>( - &'life0 self, - ) -> ::core::pin::Pin< - Box< - dyn ::core::future::Future>> - + 'async_trait, - >, - > - where - 'life0: 'async_trait, - Self: 'async_trait, - { - todo!() + async fn enable_opengl(&self) -> anyhow::Result> { + let window = self.0; + promise::spawn::spawn(async move { + if let Some(handle) = Connection::get().unwrap().wayland().window_by_id(window) { + let mut inner = handle.borrow_mut(); + inner.enable_opengl() + } else { + anyhow::bail!("invalid window"); + } + }) + .await } #[doc = r" Hide a visible window"] @@ -283,8 +283,57 @@ pub struct WaylandWindowInner { // // wegl_surface is listed before gl_state because it // // must be dropped before gl_state otherwise the underlying // // libraries will segfault on shutdown - // wegl_surface: Option, - // gl_state: Option>, + wegl_surface: Option, + gl_state: Option>, +} + +impl WaylandWindowInner { + fn enable_opengl(&mut self) -> anyhow::Result> { + let wayland_conn = Connection::get().unwrap().wayland(); + let mut wegl_surface = None; + + let gl_state = if !egl_is_available() { + Err(anyhow!("!egl_is_available")) + } else { + wegl_surface = Some(WlEglSurface::new( + self.surface.id(), + // TODO: remove the hardcoded stuff + 100, + 100, + )?); + + match wayland_conn.gl_connection.borrow().as_ref() { + Some(glconn) => crate::egl::GlState::create_wayland_with_existing_connection( + glconn, + wegl_surface.as_ref().unwrap(), + ), + None => crate::egl::GlState::create_wayland( + Some(wayland_conn.connection.borrow().backend().display_ptr() as *const _), + wegl_surface.as_ref().unwrap(), + ), + } + }; + let gl_state = gl_state.map(Rc::new).and_then(|state| unsafe { + wayland_conn + .gl_connection + .borrow_mut() + .replace(Rc::clone(state.get_connection())); + Ok(glium::backend::Context::new( + Rc::clone(&state), + true, + if cfg!(debug_assertions) { + glium::debug::DebugCallbackBehavior::DebugMessageOnError + } else { + glium::debug::DebugCallbackBehavior::Ignore + }, + )?) + })?; + + self.gl_state.replace(gl_state.clone()); + self.wegl_surface = wegl_surface; + + Ok(gl_state) + } } unsafe impl HasRawDisplayHandle for WaylandWindowInner { From 40252578782da9ba066a3d3da3ea0fd2a91c1295 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Fri, 5 Jan 2024 03:44:15 -0500 Subject: [PATCH 12/55] Trying to debug why WSurface has no good object id --- window/src/os/wayland/connection.rs | 9 +++++++++ window/src/os/wayland/window.rs | 13 ++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 602d40ea531..58a4af62ad7 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -122,6 +122,15 @@ impl WaylandConnection { bail!("polling for events: {:?}", err); } + { + // TODO: remove me + log::trace!("WINDOWS"); + for (k, v) in self.windows.borrow().iter() { + let obj_id = wayland_client::Proxy::id(&v.borrow().surface).as_ptr(); + log::trace!("{k} := inner <{:?}>", obj_id); + } + } + for event in &events { if event.token() != tok_wl { continue; diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index dc5e889eb14..c999408c148 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -84,6 +84,7 @@ impl WaylandWindow { let compositor = CompositorState::bind(&globals, &qh)?; let surface = compositor.create_surface(&qh); + log::trace!("SURFACE: {:?} - {:?} - {:?}", surface, surface.id(), surface.id().as_ptr()); let xdg_shell = XdgShell::bind(&globals, &qh)?; let window = xdg_shell.create_window(surface.clone(), Decorations::RequestServer, &qh); @@ -118,7 +119,7 @@ impl WaylandWindow { // TODO: WindowInner let inner = Rc::new(RefCell::new(WaylandWindowInner { events: WindowEventSender::new(event_handler), - surface, + surface: surface.clone(), wegl_surface: None, gl_state: None, @@ -144,7 +145,7 @@ impl WaylandWindow { // }); conn.windows.borrow_mut().insert(window_id, inner.clone()); - log::trace!("About to commit the window"); + log::trace!("About to commit window"); window.commit(); Ok(window_handle) @@ -254,7 +255,8 @@ struct PendingEvent { pub struct WaylandWindowInner { // window_id: usize, pub(crate) events: WindowEventSender, - surface: WlSurface, + // TODO: remove pub(crate) surface + pub(crate) surface: WlSurface, // surface_factor: f64, // copy_and_paste: Arc>, // window: Option>, @@ -295,12 +297,17 @@ impl WaylandWindowInner { let gl_state = if !egl_is_available() { Err(anyhow!("!egl_is_available")) } else { + let object_id = self.surface.id(); + log::trace!("EGL SURFACE: {:?} - {:?} - {:?}", self.surface, object_id, object_id.as_ptr()); + let ptr = object_id.as_ptr(); + // IMPORTANT!!!! - TODO: the wl_surface is null wegl_surface = Some(WlEglSurface::new( self.surface.id(), // TODO: remove the hardcoded stuff 100, 100, )?); + log::trace!("EGL SURFACE AFTER"); match wayland_conn.gl_connection.borrow().as_ref() { Some(glconn) => crate::egl::GlState::create_wayland_with_existing_connection( From f54bb3545f82cd4515f523a0b06642443b7c49c1 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Fri, 5 Jan 2024 12:35:20 -0500 Subject: [PATCH 13/55] Save the window: no more segfault + wgpu works too --- window/src/os/wayland/connection.rs | 9 --------- window/src/os/wayland/window.rs | 22 +++++++++------------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 58a4af62ad7..602d40ea531 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -122,15 +122,6 @@ impl WaylandConnection { bail!("polling for events: {:?}", err); } - { - // TODO: remove me - log::trace!("WINDOWS"); - for (k, v) in self.windows.borrow().iter() { - let obj_id = wayland_client::Proxy::id(&v.borrow().surface).as_ptr(); - log::trace!("{k} := inner <{:?}>", obj_id); - } - } - for event in &events { if event.token() != tok_wl { continue; diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index c999408c148..7855ee5fa88 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -21,6 +21,7 @@ use raw_window_handle::WaylandWindowHandle; use smithay_client_toolkit::compositor::CompositorState; use smithay_client_toolkit::registry::ProvidesRegistryState; use smithay_client_toolkit::shell::xdg::window::DecorationMode; +use smithay_client_toolkit::shell::xdg::window::Window as XdgWindow; use smithay_client_toolkit::shell::xdg::window::WindowDecorations as Decorations; use smithay_client_toolkit::shell::xdg::XdgShell; use smithay_client_toolkit::shell::WaylandSurface; @@ -84,7 +85,6 @@ impl WaylandWindow { let compositor = CompositorState::bind(&globals, &qh)?; let surface = compositor.create_surface(&qh); - log::trace!("SURFACE: {:?} - {:?} - {:?}", surface, surface.id(), surface.id().as_ptr()); let xdg_shell = XdgShell::bind(&globals, &qh)?; let window = xdg_shell.create_window(surface.clone(), Decorations::RequestServer, &qh); @@ -109,6 +109,7 @@ impl WaylandWindow { window.set_min_size(Some((32, 32))); + window.commit(); // // TODO: // let copy_and_paste = CopyAndPaste::create(); @@ -119,7 +120,7 @@ impl WaylandWindow { // TODO: WindowInner let inner = Rc::new(RefCell::new(WaylandWindowInner { events: WindowEventSender::new(event_handler), - surface: surface.clone(), + window, wegl_surface: None, gl_state: None, @@ -145,9 +146,6 @@ impl WaylandWindow { // }); conn.windows.borrow_mut().insert(window_id, inner.clone()); - log::trace!("About to commit window"); - window.commit(); - Ok(window_handle) } } @@ -256,7 +254,8 @@ pub struct WaylandWindowInner { // window_id: usize, pub(crate) events: WindowEventSender, // TODO: remove pub(crate) surface - pub(crate) surface: WlSurface, + pub (crate) window: XdgWindow, + // pub(crate) surface: WlSurface, // surface_factor: f64, // copy_and_paste: Arc>, // window: Option>, @@ -297,17 +296,14 @@ impl WaylandWindowInner { let gl_state = if !egl_is_available() { Err(anyhow!("!egl_is_available")) } else { - let object_id = self.surface.id(); - log::trace!("EGL SURFACE: {:?} - {:?} - {:?}", self.surface, object_id, object_id.as_ptr()); - let ptr = object_id.as_ptr(); - // IMPORTANT!!!! - TODO: the wl_surface is null + + let object_id = self.window.wl_surface().id(); wegl_surface = Some(WlEglSurface::new( - self.surface.id(), + object_id, // TODO: remove the hardcoded stuff 100, 100, )?); - log::trace!("EGL SURFACE AFTER"); match wayland_conn.gl_connection.borrow().as_ref() { Some(glconn) => crate::egl::GlState::create_wayland_with_existing_connection( @@ -356,7 +352,7 @@ unsafe impl HasRawDisplayHandle for WaylandWindowInner { unsafe impl HasRawWindowHandle for WaylandWindowInner { fn raw_window_handle(&self) -> RawWindowHandle { let mut handle = WaylandWindowHandle::empty(); - handle.surface = self.surface.id().as_ptr() as *mut _; + handle.surface = self.window.wl_surface().id().as_ptr() as *mut _; RawWindowHandle::Wayland(handle) } } From c3b78fde75b3d4d048d8b2a9c8520fe0e2cfd83c Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Fri, 5 Jan 2024 14:29:19 -0500 Subject: [PATCH 14/55] I give up :sob:, it's not worth my time --- window/src/os/wayland/connection.rs | 62 +++++++++++++++++++---------- window/src/os/wayland/window.rs | 41 +++++++++++++------ 2 files changed, 70 insertions(+), 33 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 602d40ea531..b292aa8aef5 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -1,27 +1,27 @@ // TODO: change this #![allow(dead_code, unused)] use std::{ - cell::RefCell, collections::HashMap, os::fd::AsRawFd, rc::Rc, - sync::atomic::AtomicUsize, + cell::RefCell, collections::HashMap, os::fd::AsRawFd, rc::Rc, sync::atomic::AtomicUsize, }; use anyhow::{bail, Context}; use mio::{unix::SourceFd, Events, Interest, Poll, Token}; use smithay_client_toolkit::{ compositor::CompositorHandler, - delegate_compositor, delegate_registry, delegate_xdg_shell, delegate_xdg_window, + delegate_compositor, delegate_output, delegate_registry, delegate_xdg_shell, + delegate_xdg_window, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, - shell::xdg::window::WindowHandler, delegate_output, + shell::xdg::window::WindowHandler, }; use wayland_client::{ backend::WaylandError, globals::{registry_queue_init, GlobalList}, - Connection, EventQueue, + Connection as WConnection, EventQueue, }; -use crate::{spawn::SPAWN_QUEUE, ConnectionOps}; +use crate::{spawn::SPAWN_QUEUE, ConnectionOps, Connection}; use super::WaylandWindowInner; @@ -32,7 +32,7 @@ pub struct WaylandConnection { pub(crate) gl_connection: RefCell>>, - pub(crate) connection: RefCell, + pub(crate) connection: RefCell, pub(crate) event_queue: RefCell>, pub(crate) globals: RefCell, @@ -45,7 +45,7 @@ pub(crate) struct WaylandState { impl WaylandConnection { pub(crate) fn create_new() -> anyhow::Result { - let conn = Connection::connect_to_env()?; + let conn = WConnection::connect_to_env()?; let (globals, event_queue) = registry_queue_init::(&conn)?; let _qh = event_queue.handle(); @@ -84,11 +84,8 @@ impl WaylandConnection { read_guard.connection_fd().as_raw_fd() }; - poll.registry().register( - &mut SourceFd(&wl_fd), - tok_wl, - Interest::READABLE, - )?; + poll.registry() + .register(&mut SourceFd(&wl_fd), tok_wl, Interest::READABLE)?; poll.registry().register( &mut SourceFd(&SPAWN_QUEUE.raw_fd()), tok_spawn, @@ -157,12 +154,36 @@ impl WaylandConnection { pub(crate) fn window_by_id(&self, window_id: usize) -> Option>> { self.windows.borrow().get(&window_id).map(Rc::clone) } + + pub(crate) fn with_window_inner< + R, + F: FnOnce(&mut WaylandWindowInner) -> anyhow::Result + Send + 'static, + >( + window: usize, + f: F, + ) -> promise::Future + where + R: Send + 'static, + { + let mut prom = promise::Promise::new(); + let future = prom.get_future().unwrap(); + + promise::spawn::spawn_into_main_thread(async move { + if let Some(handle) = Connection::get().unwrap().wayland().window_by_id(window) { + let mut inner = handle.borrow_mut(); + prom.result(f(&mut inner)); + } + }) + .detach(); + + future + } } impl CompositorHandler for WaylandState { fn scale_factor_changed( &mut self, - conn: &Connection, + conn: &WConnection, qh: &wayland_client::QueueHandle, surface: &wayland_client::protocol::wl_surface::WlSurface, new_factor: i32, @@ -172,7 +193,7 @@ impl CompositorHandler for WaylandState { fn frame( &mut self, - conn: &Connection, + conn: &WConnection, qh: &wayland_client::QueueHandle, surface: &wayland_client::protocol::wl_surface::WlSurface, time: u32, @@ -190,7 +211,7 @@ impl OutputHandler for WaylandState { fn new_output( &mut self, - conn: &Connection, + conn: &WConnection, qh: &wayland_client::QueueHandle, output: wayland_client::protocol::wl_output::WlOutput, ) { @@ -200,7 +221,7 @@ impl OutputHandler for WaylandState { fn update_output( &mut self, - conn: &Connection, + conn: &WConnection, qh: &wayland_client::QueueHandle, output: wayland_client::protocol::wl_output::WlOutput, ) { @@ -210,7 +231,7 @@ impl OutputHandler for WaylandState { fn output_destroyed( &mut self, - conn: &Connection, + conn: &WConnection, qh: &wayland_client::QueueHandle, output: wayland_client::protocol::wl_output::WlOutput, ) { @@ -222,7 +243,7 @@ impl OutputHandler for WaylandState { impl WindowHandler for WaylandState { fn request_close( &mut self, - conn: &Connection, + conn: &WConnection, qh: &wayland_client::QueueHandle, window: &smithay_client_toolkit::shell::xdg::window::Window, ) { @@ -232,7 +253,7 @@ impl WindowHandler for WaylandState { fn configure( &mut self, - conn: &Connection, + conn: &WConnection, qh: &wayland_client::QueueHandle, window: &smithay_client_toolkit::shell::xdg::window::Window, configure: smithay_client_toolkit::shell::xdg::window::WindowConfigure, @@ -273,7 +294,6 @@ delegate_output!(WaylandState); delegate_xdg_shell!(WaylandState); delegate_xdg_window!(WaylandState); - delegate_registry!(WaylandState); impl ProvidesRegistryState for WaylandState { diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 7855ee5fa88..2033b167c1d 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -120,7 +120,7 @@ impl WaylandWindow { // TODO: WindowInner let inner = Rc::new(RefCell::new(WaylandWindowInner { events: WindowEventSender::new(event_handler), - window, + window: Some(window), wegl_surface: None, gl_state: None, @@ -145,6 +145,7 @@ impl WaylandWindow { // Decorations::ClientSide // }); conn.windows.borrow_mut().insert(window_id, inner.clone()); + log::trace!("Return from commiting window"); Ok(window_handle) } @@ -154,7 +155,10 @@ impl WaylandWindow { impl WindowOps for WaylandWindow { #[doc = r" Show a hidden window"] fn show(&self) { - todo!() + WaylandConnection::with_window_inner(self.0, |inner| { + inner.show(); + Ok(()) + }); } fn notify(&self, t: T) @@ -253,12 +257,9 @@ struct PendingEvent { pub struct WaylandWindowInner { // window_id: usize, pub(crate) events: WindowEventSender, - // TODO: remove pub(crate) surface - pub (crate) window: XdgWindow, - // pub(crate) surface: WlSurface, // surface_factor: f64, // copy_and_paste: Arc>, - // window: Option>, + window: Option, // dimensions: Dimensions, // resize_increments: Option<(u16, u16)>, // window_state: WindowState, @@ -289,22 +290,33 @@ pub struct WaylandWindowInner { } impl WaylandWindowInner { + fn show(&mut self) { + // TODO: Need to implement show + if self.window.is_none() {} + } + fn enable_opengl(&mut self) -> anyhow::Result> { let wayland_conn = Connection::get().unwrap().wayland(); let mut wegl_surface = None; + log::trace!("Enable opengl"); + let gl_state = if !egl_is_available() { Err(anyhow!("!egl_is_available")) } else { + let window = self + .window + .as_ref() + .ok_or(anyhow!("Window does not exist"))?; + let object_id = window.wl_surface().id(); - let object_id = self.window.wl_surface().id(); wegl_surface = Some(WlEglSurface::new( - object_id, - // TODO: remove the hardcoded stuff - 100, - 100, + object_id, // TODO: remove the hardcoded stuff + 100, 100, )?); + log::trace!("WEGL Surface here {:?}", wegl_surface); + match wayland_conn.gl_connection.borrow().as_ref() { Some(glconn) => crate::egl::GlState::create_wayland_with_existing_connection( glconn, @@ -352,7 +364,12 @@ unsafe impl HasRawDisplayHandle for WaylandWindowInner { unsafe impl HasRawWindowHandle for WaylandWindowInner { fn raw_window_handle(&self) -> RawWindowHandle { let mut handle = WaylandWindowHandle::empty(); - handle.surface = self.window.wl_surface().id().as_ptr() as *mut _; + let surface = self + .window + .as_ref() + .expect("Window should exist") + .wl_surface(); + handle.surface = surface.id().as_ptr() as *mut _; RawWindowHandle::Wayland(handle) } } From e6bdfe464d3e8e645bc3e304825e74c11bdfeae6 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Sat, 6 Jan 2024 14:12:17 -0500 Subject: [PATCH 15/55] Have user data associated with a surface --- window/src/os/wayland/connection.rs | 13 +++++++++--- window/src/os/wayland/window.rs | 31 +++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index b292aa8aef5..0cdecbc7a18 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -7,7 +7,7 @@ use std::{ use anyhow::{bail, Context}; use mio::{unix::SourceFd, Events, Interest, Poll, Token}; use smithay_client_toolkit::{ - compositor::CompositorHandler, + compositor::{CompositorHandler, SurfaceData}, delegate_compositor, delegate_output, delegate_registry, delegate_xdg_shell, delegate_xdg_window, output::{OutputHandler, OutputState}, @@ -18,16 +18,18 @@ use smithay_client_toolkit::{ use wayland_client::{ backend::WaylandError, globals::{registry_queue_init, GlobalList}, + protocol::wl_surface::WlSurface, Connection as WConnection, EventQueue, }; -use crate::{spawn::SPAWN_QUEUE, ConnectionOps, Connection}; +use crate::{spawn::SPAWN_QUEUE, Connection, ConnectionOps}; -use super::WaylandWindowInner; +use super::{SurfaceUserData, WaylandWindowInner}; pub struct WaylandConnection { pub(crate) should_terminate: RefCell, pub(crate) next_window_id: AtomicUsize, + pub(crate) windows: RefCell>>>, pub(crate) gl_connection: RefCell>>, @@ -259,6 +261,7 @@ impl WindowHandler for WaylandState { configure: smithay_client_toolkit::shell::xdg::window::WindowConfigure, serial: u32, ) { + // How can i get the specific pending state for the window log::trace!("Configuring Window Handler"); todo!() } @@ -288,7 +291,11 @@ impl ConnectionOps for WaylandConnection { } } +// Undocumented in sctk 0.17: This is required to use have user data with a surface +// Will be just delegate_compositor!(WaylandState, surface: [SurfaceData, SurfaceUserData]) in 0.18 +wayland_client::delegate_dispatch!(WaylandState: [ WlSurface: SurfaceUserData] => WaylandState); delegate_compositor!(WaylandState); + delegate_output!(WaylandState); delegate_xdg_shell!(WaylandState); diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 2033b167c1d..c60e245fd49 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -19,6 +19,8 @@ use raw_window_handle::RawWindowHandle; use raw_window_handle::WaylandDisplayHandle; use raw_window_handle::WaylandWindowHandle; use smithay_client_toolkit::compositor::CompositorState; +use smithay_client_toolkit::compositor::SurfaceData; +use smithay_client_toolkit::compositor::SurfaceDataExt; use smithay_client_toolkit::registry::ProvidesRegistryState; use smithay_client_toolkit::shell::xdg::window::DecorationMode; use smithay_client_toolkit::shell::xdg::window::Window as XdgWindow; @@ -76,9 +78,9 @@ impl WaylandWindow { .wayland(); let window_id = conn.next_window_id(); - // let pending_event = Arc::new(Mutex::new(PendingEvent::default())); + let pending_event = Arc::new(Mutex::new(PendingEvent::default())); - // let (pending_first_configure, wait_configure) = async_channel::bounded(1); + let (pending_first_configure, wait_configure) = async_channel::bounded(1); let qh = conn.event_queue.borrow().handle(); let globals = conn.globals.borrow(); @@ -86,6 +88,13 @@ impl WaylandWindow { let compositor = CompositorState::bind(&globals, &qh)?; let surface = compositor.create_surface(&qh); + // We need user data so we can get the window_id => WaylandWindowInner during a handler + let surface_data = SurfaceUserData { + surface_data: SurfaceData::default(), + window_id, + }; + let surface = compositor.create_surface_with_data(&qh, surface_data); + let xdg_shell = XdgShell::bind(&globals, &qh)?; let window = xdg_shell.create_window(surface.clone(), Decorations::RequestServer, &qh); @@ -122,6 +131,8 @@ impl WaylandWindow { events: WindowEventSender::new(event_handler), window: Some(window), + pending_event, + wegl_surface: None, gl_state: None, })); @@ -145,7 +156,8 @@ impl WaylandWindow { // Decorations::ClientSide // }); conn.windows.borrow_mut().insert(window_id, inner.clone()); - log::trace!("Return from commiting window"); + + wait_configure.recv().await?; Ok(window_handle) } @@ -270,7 +282,7 @@ pub struct WaylandWindowInner { // modifiers: Modifiers, // leds: KeyboardLedStatus, // key_repeat: Option<(u32, Arc>)>, - // pending_event: Arc>, + pending_event: Arc>, // pending_mouse: Arc>, // pending_first_configure: Option>, // frame_callback: Option>, @@ -373,3 +385,14 @@ unsafe impl HasRawWindowHandle for WaylandWindowInner { RawWindowHandle::Wayland(handle) } } + +pub(crate) struct SurfaceUserData { + surface_data: SurfaceData, + window_id: usize, +} + +impl SurfaceDataExt for SurfaceUserData { + fn surface_data(&self) -> &smithay_client_toolkit::compositor::SurfaceData { + &self.surface_data + } +} From c4d64bbb8be7620d3b3c7644ff2b6b96c920d563 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Sat, 6 Jan 2024 20:52:09 -0500 Subject: [PATCH 16/55] Implement handling window configuration --- window/src/os/wayland/connection.rs | 82 +++++++++++++-- window/src/os/wayland/window.rs | 156 ++++++++++++++++++++++++++-- 2 files changed, 217 insertions(+), 21 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 0cdecbc7a18..b7c08fa63ce 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -1,7 +1,11 @@ // TODO: change this #![allow(dead_code, unused)] use std::{ - cell::RefCell, collections::HashMap, os::fd::AsRawFd, rc::Rc, sync::atomic::AtomicUsize, + cell::RefCell, + collections::HashMap, + os::fd::AsRawFd, + rc::Rc, + sync::{atomic::AtomicUsize, Arc}, }; use anyhow::{bail, Context}; @@ -13,16 +17,19 @@ use smithay_client_toolkit::{ output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, - shell::xdg::window::WindowHandler, + shell::{ + xdg::window::{WindowHandler, WindowState as SCTKWindowState}, + WaylandSurface, + }, }; use wayland_client::{ backend::WaylandError, globals::{registry_queue_init, GlobalList}, protocol::wl_surface::WlSurface, - Connection as WConnection, EventQueue, + Connection as WConnection, EventQueue, Proxy, }; -use crate::{spawn::SPAWN_QUEUE, Connection, ConnectionOps}; +use crate::{spawn::SPAWN_QUEUE, Connection, ConnectionOps, WindowState}; use super::{SurfaceUserData, WaylandWindowInner}; @@ -41,6 +48,7 @@ pub struct WaylandConnection { pub(crate) wayland_state: RefCell, } +// TODO: the SurfaceUserData should be something in WaylandConnection struct as a whole. I think? pub(crate) struct WaylandState { registry_state: RegistryState, } @@ -126,9 +134,6 @@ impl WaylandConnection { continue; } - println!("READING WL EVENT"); - - let a = event_q.prepare_read(); if let Ok(guard) = event_q.prepare_read() { if let Err(err) = guard.read() { log::trace!("Event Q error: {:?}", err); @@ -261,9 +266,66 @@ impl WindowHandler for WaylandState { configure: smithay_client_toolkit::shell::xdg::window::WindowConfigure, serial: u32, ) { - // How can i get the specific pending state for the window - log::trace!("Configuring Window Handler"); - todo!() + let surface_data = SurfaceUserData::from_wl(window.wl_surface()); + // TODO: XXX: should we grouping window data and connection + + let window_id = surface_data.window_id; + let wconn = WaylandConnection::get() + .expect("should be wayland connection") + .wayland(); + let window_inner = wconn + .window_by_id(window_id) + .expect("Inner Window should exist"); + + let p = window_inner.borrow().pending_event.clone(); + let mut pending_event = p.lock().unwrap(); + + // TODO: This should the new queue function + // p.queue_configure(&configure) + // + let changed = { + let mut changed; + pending_event.had_configure_event = true; + if let (Some(w), Some(h)) = configure.new_size { + changed = pending_event.configure.is_none(); + pending_event.configure.replace((w.get(), h.get())); + } else { + changed = true; + } + + let mut state = WindowState::default(); + if configure.state.contains(SCTKWindowState::FULLSCREEN) { + state |= WindowState::FULL_SCREEN; + } + let fs_bits = SCTKWindowState::MAXIMIZED + | SCTKWindowState::TILED_LEFT + | SCTKWindowState::TILED_RIGHT + | SCTKWindowState::TILED_TOP + | SCTKWindowState::TILED_BOTTOM; + if !((configure.state & fs_bits).is_empty()) { + state |= WindowState::MAXIMIZED; + } + + log::debug!( + "Config: self.window_state={:?}, states: {:?} {:?}", + pending_event.window_state, + state, + configure.state + ); + + if pending_event.window_state.is_none() && state != WindowState::default() { + changed = true; + } + + pending_event.window_state.replace(state); + changed + }; // function should return changed + if changed { + WaylandConnection::with_window_inner(window_id, move |inner| { + inner.dispatch_pending_event(); + Ok(()) + }); + } } } diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index c60e245fd49..130102906bc 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -1,4 +1,5 @@ // TODO: change this +// TODO: change a lot of the pubstruct crates #![allow(dead_code, unused)] use std::any::Any; @@ -47,6 +48,7 @@ use crate::Window; use crate::WindowEvent; use crate::WindowEventSender; use crate::WindowOps; +use crate::WindowState; #[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct WaylandWindow(usize); @@ -131,8 +133,12 @@ impl WaylandWindow { events: WindowEventSender::new(event_handler), window: Some(window), + window_state: WindowState::default(), + pending_event, + pending_first_configure: Some(pending_first_configure), + wegl_surface: None, gl_state: None, })); @@ -165,7 +171,6 @@ impl WaylandWindow { #[async_trait(?Send)] impl WindowOps for WaylandWindow { - #[doc = r" Show a hidden window"] fn show(&self) { WaylandConnection::with_window_inner(self.0, |inner| { inner.show(); @@ -258,12 +263,12 @@ unsafe impl HasRawWindowHandle for WaylandWindow { } #[derive(Default, Clone, Debug)] -struct PendingEvent { - close: bool, - had_configure_event: bool, - configure: Option<(u32, u32)>, - dpi: Option, - // window_state: Option, +pub(crate) struct PendingEvent { + pub(crate) close: bool, + pub(crate) had_configure_event: bool, + pub(crate) configure: Option<(u32, u32)>, + pub(crate) dpi: Option, + pub(crate) window_state: Option, } pub struct WaylandWindowInner { @@ -274,7 +279,7 @@ pub struct WaylandWindowInner { window: Option, // dimensions: Dimensions, // resize_increments: Option<(u16, u16)>, - // window_state: WindowState, + window_state: WindowState, // last_mouse_coords: Point, // mouse_buttons: MouseButtons, // hscroll_remainder: f64, @@ -282,9 +287,9 @@ pub struct WaylandWindowInner { // modifiers: Modifiers, // leds: KeyboardLedStatus, // key_repeat: Option<(u32, Arc>)>, - pending_event: Arc>, + pub(crate) pending_event: Arc>, // pending_mouse: Arc>, - // pending_first_configure: Option>, + pending_first_configure: Option>, // frame_callback: Option>, // invalidated: bool, // font_config: Rc, @@ -361,6 +366,128 @@ impl WaylandWindowInner { Ok(gl_state) } + + pub(crate) fn dispatch_pending_event(&mut self) { + let mut pending; + { + let mut pending_events = self.pending_event.lock().unwrap(); + pending = pending_events.clone(); + *pending_events = PendingEvent::default(); + } + + if pending.close { + self.events.dispatch(WindowEvent::CloseRequested); + } + + if let Some(window_state) = pending.window_state.take() { + log::debug!( + "dispatch_pending_event self.window_state={:?}, pending:{:?}", + window_state, + window_state + ); + self.window_state = window_state; + } + + if pending.configure.is_none() { + // TODO: Handle the DPI + } + + if let Some((mut w, mut h)) = pending.configure.take() { + log::trace!("Pending configure: w:{w}, h{h} -- {:?}", self.window); + if self.window.is_some() { + // TODO: here + // let factor = get_surface_scale_factor(&self.surface) as f64; + // let old_dimensions = self.dimensions; + + // FIXME: teach this how to resolve dpi_by_screen + // let dpi = self.config.dpi.unwrap_or(factor * crate::DEFAULT_DPI) as usize; + + // Do this early because this affects surface_to_pixels/pixels_to_surface + // self.dimensions.dpi = dpi; + + // let mut pixel_width = self.surface_to_pixels(w.try_into().unwrap()); + // let mut pixel_height = self.surface_to_pixels(h.try_into().unwrap()); + // + // if self.window_state.can_resize() { + // if let Some((x, y)) = self.resize_increments { + // let desired_pixel_width = pixel_width - (pixel_width % x as i32); + // let desired_pixel_height = pixel_height - (pixel_height % y as i32); + // w = self.pixels_to_surface(desired_pixel_width) as u32; + // h = self.pixels_to_surface(desired_pixel_height) as u32; + // pixel_width = self.surface_to_pixels(w.try_into().unwrap()); + // pixel_height = self.surface_to_pixels(h.try_into().unwrap()); + // } + // } + // + // // Update the window decoration size + // self.window.as_mut().unwrap().resize(w, h); + // + // // Compute the new pixel dimensions + // let new_dimensions = Dimensions { + // pixel_width: pixel_width.try_into().unwrap(), + // pixel_height: pixel_height.try_into().unwrap(), + // dpi, + // }; + // + // // Only trigger a resize if the new dimensions are different; + // // this makes things more efficient and a little more smooth + // if new_dimensions != old_dimensions { + // self.dimensions = new_dimensions; + // + // self.events.dispatch(WindowEvent::Resized { + // dimensions: self.dimensions, + // window_state: self.window_state, + // // We don't know if we're live resizing or not, so + // // assume no. + // live_resizing: false, + // }); + // // Avoid blurring by matching the scaling factor of the + // // compositor; if it is going to double the size then + // // we render at double the size anyway and tell it that + // // the buffer is already doubled. + // // Take care to detach the current buffer (managed by EGL), + // // so that the compositor doesn't get annoyed by it not + // // having dimensions that match the scale. + // // The wegl_surface.resize won't take effect until + // // we paint later on. + // // We do this only if the scale has actually changed, + // // otherwise interactive window resize will keep removing + // // the window contents! + // if let Some(wegl_surface) = self.wegl_surface.as_mut() { + // wegl_surface.resize(pixel_width, pixel_height, 0, 0); + // } + // if self.surface_factor != factor { + // let wayland_conn = Connection::get().unwrap().wayland(); + // let mut pool = wayland_conn.mem_pool.borrow_mut(); + // // Make a "fake" buffer with the right dimensions, as + // // simply detaching the buffer can cause wlroots-derived + // // compositors consider the window to be unconfigured. + // if let Ok((_bytes, buffer)) = pool.buffer( + // factor as i32, + // factor as i32, + // (factor * 4.0) as i32, + // wayland_client::protocol::wl_shm::Format::Argb8888, + // ) { + // self.surface.attach(Some(&buffer), 0, 0); + // self.surface.set_buffer_scale(factor as i32); + // self.surface_factor = factor; + // } + // } + // } + // self.refresh_frame(); + // self.do_paint().unwrap(); + } + } + // if pending.refresh_decorations && self.window.is_some() { + // self.refresh_frame(); + // } + if pending.had_configure_event && self.window.is_some() { + if let Some(notify) = self.pending_first_configure.take() { + // Allow window creation to complete + notify.try_send(()).ok(); + } + } + } } unsafe impl HasRawDisplayHandle for WaylandWindowInner { @@ -388,7 +515,14 @@ unsafe impl HasRawWindowHandle for WaylandWindowInner { pub(crate) struct SurfaceUserData { surface_data: SurfaceData, - window_id: usize, + pub(crate) window_id: usize, +} + +impl SurfaceUserData { + pub(crate) fn from_wl(wl: &WlSurface) -> &Self { + wl.data() + .expect("User data should be associated with WlSurface") + } } impl SurfaceDataExt for SurfaceUserData { From 1a2d3ea76f6536fc11857065508e46cca5facc56 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Sat, 6 Jan 2024 22:04:21 -0500 Subject: [PATCH 17/55] Handle panics after handling window configuration --- window/src/os/wayland/connection.rs | 1 - window/src/os/wayland/window.rs | 56 +++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index b7c08fa63ce..8a855d1d6a8 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -223,7 +223,6 @@ impl OutputHandler for WaylandState { output: wayland_client::protocol::wl_output::WlOutput, ) { log::trace!("new output: OutputHandler"); - todo!() } fn update_output( diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 130102906bc..f191ad6bfbd 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -29,6 +29,7 @@ use smithay_client_toolkit::shell::xdg::window::WindowDecorations as Decorations use smithay_client_toolkit::shell::xdg::XdgShell; use smithay_client_toolkit::shell::WaylandSurface; use wayland_client::globals::GlobalList; +use wayland_client::protocol::wl_callback::WlCallback; use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::Proxy; use wayland_egl::{is_available as egl_is_available, WlEglSurface}; @@ -131,6 +132,8 @@ impl WaylandWindow { // TODO: WindowInner let inner = Rc::new(RefCell::new(WaylandWindowInner { events: WindowEventSender::new(event_handler), + + invalidated: false, window: Some(window), window_state: WindowState::default(), @@ -138,6 +141,9 @@ impl WaylandWindow { pending_event, pending_first_configure: Some(pending_first_configure), + frame_callback: None, + + title: None, wegl_surface: None, gl_state: None, @@ -145,7 +151,6 @@ impl WaylandWindow { let window_handle = Window::Wayland(WaylandWindow(window_id)); - // TODO: assign window inner inner .borrow_mut() .events @@ -182,7 +187,12 @@ impl WindowOps for WaylandWindow { where Self: Sized, { - todo!() + WaylandConnection::with_window_inner(self.0, move |inner| { + inner + .events + .dispatch(WindowEvent::Notification(Box::new(t))); + Ok(()) + }); } async fn enable_opengl(&self) -> anyhow::Result> { @@ -213,15 +223,19 @@ impl WindowOps for WaylandWindow { todo!() } - #[doc = r" Invalidate the window so that the entire client area will"] - #[doc = r" be repainted shortly"] fn invalidate(&self) { - todo!() + WaylandConnection::with_window_inner(self.0, |inner| { + inner.invalidate(); + Ok(()) + }); } - #[doc = r" Change the titlebar text for the window"] fn set_title(&self, title: &str) { - todo!() + let title = title.to_owned(); + WaylandConnection::with_window_inner(self.0, |inner| { + inner.set_title(title); + Ok(()) + }); } #[doc = r" Resize the inner or client area of the window"] @@ -290,15 +304,15 @@ pub struct WaylandWindowInner { pub(crate) pending_event: Arc>, // pending_mouse: Arc>, pending_first_configure: Option>, - // frame_callback: Option>, - // invalidated: bool, + frame_callback: Option, + invalidated: bool, // font_config: Rc, // text_cursor: Option, // appearance: Appearance, // config: ConfigHandle, // // cache the title for comparison to avoid spamming // // the compositor with updates that don't actually change it - // title: Option, + title: Option, // // wegl_surface is listed before gl_state because it // // must be dropped before gl_state otherwise the underlying // // libraries will segfault on shutdown @@ -309,6 +323,7 @@ pub struct WaylandWindowInner { impl WaylandWindowInner { fn show(&mut self) { // TODO: Need to implement show + log::trace!("WaylandWindowInner show: {:?}", self.window); if self.window.is_none() {} } @@ -488,6 +503,27 @@ impl WaylandWindowInner { } } } + + fn invalidate(&mut self) { + if self.frame_callback.is_some() { + self.invalidated = true; + return; + } + // TODO: self.do_paint().unwrap(); + } + + fn set_title(&mut self, title: String) { + if let Some(last_title) = self.title.as_ref() { + if last_title == &title { + return; + } + } + if let Some(window) = self.window.as_ref() { + window.set_title(title.clone()); + } + // TODO: self.refresh_frame(); + self.title = Some(title); + } } unsafe impl HasRawDisplayHandle for WaylandWindowInner { From 671931afd775aa3516ae1030c008fcf819a02e0b Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Sun, 7 Jan 2024 00:33:10 -0500 Subject: [PATCH 18/55] Implement do_paint -> renders window and then panics at frame() --- window/src/os/wayland/window.rs | 62 +++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index f191ad6bfbd..d4314be799b 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -131,6 +131,7 @@ impl WaylandWindow { // TODO: WindowInner let inner = Rc::new(RefCell::new(WaylandWindowInner { + window_id, events: WindowEventSender::new(event_handler), invalidated: false, @@ -286,7 +287,7 @@ pub(crate) struct PendingEvent { } pub struct WaylandWindowInner { - // window_id: usize, + window_id: usize, pub(crate) events: WindowEventSender, // surface_factor: f64, // copy_and_paste: Arc>, @@ -324,7 +325,11 @@ impl WaylandWindowInner { fn show(&mut self) { // TODO: Need to implement show log::trace!("WaylandWindowInner show: {:?}", self.window); - if self.window.is_none() {} + if self.window.is_none() { + return; + } + + self.do_paint().unwrap(); } fn enable_opengl(&mut self) -> anyhow::Result> { @@ -524,6 +529,53 @@ impl WaylandWindowInner { // TODO: self.refresh_frame(); self.title = Some(title); } + + fn do_paint(&mut self) -> anyhow::Result<()> { + if self.frame_callback.is_some() { + // Painting now won't be productive, so skip it but + // remember that we need to be painted so that when + // the compositor is ready for us, we can paint then. + self.invalidated = true; + return Ok(()); + } + + self.invalidated = false; + + // Ask the compositor to wake us up when its time to paint the next frame, + // note that this only happens _after_ the next commit + let window_id = self.window_id; + + let conn = WaylandConnection::get().unwrap().wayland(); + let qh = conn.event_queue.borrow().handle(); + // TODO: is this the current udata to pass in?, just following examples + let callback = self.surface().frame(&qh, self.surface().clone()); + + log::trace!("do_paint - callback: {:?}", callback); + // callback.quick_assign(move |_source, _event, _data| { + // WaylandConnection::with_window_inner(window_id, |inner| { + // inner.next_frame_is_ready(); + // Ok(()) + // }); + // }); + self.frame_callback.replace(callback); + + // The repaint has the side of effect of committing the surface, + // which is necessary for the frame callback to get triggered. + // Ordering the repaint after requesting the callback ensures that + // we will get woken at the appropriate time. + // + // + self.events.dispatch(WindowEvent::NeedRepaint); + + Ok(()) + } + + fn surface(&self) -> &WlSurface { + self.window + .as_ref() + .expect("Window should exist") + .wl_surface() + } } unsafe impl HasRawDisplayHandle for WaylandWindowInner { @@ -539,11 +591,7 @@ unsafe impl HasRawDisplayHandle for WaylandWindowInner { unsafe impl HasRawWindowHandle for WaylandWindowInner { fn raw_window_handle(&self) -> RawWindowHandle { let mut handle = WaylandWindowHandle::empty(); - let surface = self - .window - .as_ref() - .expect("Window should exist") - .wl_surface(); + let surface = self.surface(); handle.surface = surface.id().as_ptr() as *mut _; RawWindowHandle::Wayland(handle) } From 4db59e69e3dea54c52dcfa078d78e1f3477f460c Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Sun, 7 Jan 2024 02:07:30 -0500 Subject: [PATCH 19/55] A window renders now --- window/src/os/wayland/connection.rs | 8 +++++++- window/src/os/wayland/window.rs | 15 ++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 8a855d1d6a8..e08f1af24ec 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -206,7 +206,13 @@ impl CompositorHandler for WaylandState { time: u32, ) { log::trace!("frame: CompositorHandler"); - todo!() + let surface_data = SurfaceUserData::from_wl(surface); + let window_id = surface_data.window_id; + + WaylandConnection::with_window_inner(window_id, |inner| { + inner.next_frame_is_ready(); + Ok(()) + }); } } diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index d4314be799b..a6873ee4032 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -547,16 +547,10 @@ impl WaylandWindowInner { let conn = WaylandConnection::get().unwrap().wayland(); let qh = conn.event_queue.borrow().handle(); - // TODO: is this the current udata to pass in?, just following examples + let callback = self.surface().frame(&qh, self.surface().clone()); log::trace!("do_paint - callback: {:?}", callback); - // callback.quick_assign(move |_source, _event, _data| { - // WaylandConnection::with_window_inner(window_id, |inner| { - // inner.next_frame_is_ready(); - // Ok(()) - // }); - // }); self.frame_callback.replace(callback); // The repaint has the side of effect of committing the surface, @@ -576,6 +570,13 @@ impl WaylandWindowInner { .expect("Window should exist") .wl_surface() } + + pub(crate) fn next_frame_is_ready(&mut self) { + self.frame_callback.take(); + if self.invalidated { + self.do_paint().ok(); + } + } } unsafe impl HasRawDisplayHandle for WaylandWindowInner { From f31274db8476aeff53200004423a9ab1008ca70c Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Sun, 7 Jan 2024 16:31:37 -0500 Subject: [PATCH 20/55] Need to look into window frames --- window/src/os/wayland/window.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index a6873ee4032..75aeda4b12b 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -157,16 +157,6 @@ impl WaylandWindow { .events .assign_window(window_handle.clone()); - // window.set_decorate(if decorations == WindowDecorations::NONE { - // Decorations::None - // } else if decorations == WindowDecorations::default() { - // Decorations::FollowServer - // } else { - // // SCTK/Wayland don't allow more nuance than "decorations are hidden", - // // so if we have a mixture of things, then we need to force our - // // client side decoration rendering. - // Decorations::ClientSide - // }); conn.windows.borrow_mut().insert(window_id, inner.clone()); wait_configure.recv().await?; @@ -323,7 +313,6 @@ pub struct WaylandWindowInner { impl WaylandWindowInner { fn show(&mut self) { - // TODO: Need to implement show log::trace!("WaylandWindowInner show: {:?}", self.window); if self.window.is_none() { return; @@ -332,6 +321,13 @@ impl WaylandWindowInner { self.do_paint().unwrap(); } + fn refresh_frame(&mut self) { + if let Some(window) = self.window.as_mut() { + // window.refresh(); + // window.surface().commit(); + } + } + fn enable_opengl(&mut self) -> anyhow::Result> { let wayland_conn = Connection::get().unwrap().wayland(); let mut wegl_surface = None; @@ -514,7 +510,7 @@ impl WaylandWindowInner { self.invalidated = true; return; } - // TODO: self.do_paint().unwrap(); + self.do_paint().unwrap(); } fn set_title(&mut self, title: String) { From 413f8c5f8cb85ca6f40e518a9a3bb3df17d5ac27 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Sun, 7 Jan 2024 21:57:52 -0500 Subject: [PATCH 21/55] Implement most of dispatch pending_event --- window/src/os/wayland/connection.rs | 25 +++- window/src/os/wayland/window.rs | 218 +++++++++++++++++----------- 2 files changed, 152 insertions(+), 91 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index e08f1af24ec..6a684be9ecc 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -1,7 +1,7 @@ // TODO: change this #![allow(dead_code, unused)] use std::{ - cell::RefCell, + cell::{RefCell, Ref}, collections::HashMap, os::fd::AsRawFd, rc::Rc, @@ -20,7 +20,7 @@ use smithay_client_toolkit::{ shell::{ xdg::window::{WindowHandler, WindowState as SCTKWindowState}, WaylandSurface, - }, + }, shm::{slot::SlotPool, Shm, ShmHandler}, delegate_shm, }; use wayland_client::{ backend::WaylandError, @@ -41,27 +41,34 @@ pub struct WaylandConnection { pub(crate) gl_connection: RefCell>>, + pub(crate) globals: RefCell, pub(crate) connection: RefCell, pub(crate) event_queue: RefCell>, - pub(crate) globals: RefCell, - pub(crate) wayland_state: RefCell, } // TODO: the SurfaceUserData should be something in WaylandConnection struct as a whole. I think? pub(crate) struct WaylandState { registry_state: RegistryState, + shm: Shm, + pub(crate) mem_pool: RefCell, } impl WaylandConnection { pub(crate) fn create_new() -> anyhow::Result { let conn = WConnection::connect_to_env()?; let (globals, event_queue) = registry_queue_init::(&conn)?; - let _qh = event_queue.handle(); + let qh = event_queue.handle(); + let shm = Shm::bind(&globals, &qh)?; + let mem_pool = SlotPool::new(1, &shm)?; let wayland_state = WaylandState { registry_state: RegistryState::new(&globals), + shm, + mem_pool: RefCell::new(mem_pool), }; + + let wayland_connection = WaylandConnection { connection: RefCell::new(conn), should_terminate: RefCell::new(false), @@ -334,6 +341,12 @@ impl WindowHandler for WaylandState { } } +impl ShmHandler for WaylandState { + fn shm_state(&mut self) -> &mut Shm { + &mut self.shm + } +} + impl ConnectionOps for WaylandConnection { fn name(&self) -> String { "Wayland".to_string() @@ -368,6 +381,8 @@ delegate_output!(WaylandState); delegate_xdg_shell!(WaylandState); delegate_xdg_window!(WaylandState); +delegate_shm!(WaylandState); + delegate_registry!(WaylandState); impl ProvidesRegistryState for WaylandState { diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 75aeda4b12b..7c1c30211c6 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -4,6 +4,7 @@ use std::any::Any; use std::cell::RefCell; +use std::convert::TryInto; use std::rc::Rc; use std::sync::Arc; use std::sync::Mutex; @@ -43,8 +44,10 @@ use crate::wayland::WaylandConnection; use crate::Clipboard; use crate::Connection; use crate::ConnectionOps; +use crate::Dimensions; use crate::MouseCursor; use crate::RequestedWindowGeometry; +use crate::ResolvedGeometry; use crate::Window; use crate::WindowEvent; use crate::WindowEventSender; @@ -98,6 +101,19 @@ impl WaylandWindow { }; let surface = compositor.create_surface_with_data(&qh, surface_data); + let ResolvedGeometry { + x: _, + y: _, + width, + height, + } = conn.resolve_geometry(geometry); + + let dimensions = Dimensions { + pixel_width: width, + pixel_height: height, + dpi: config.dpi.unwrap_or(crate::DEFAULT_DPI) as usize, + }; + let xdg_shell = XdgShell::bind(&globals, &qh)?; let window = xdg_shell.create_window(surface.clone(), Decorations::RequestServer, &qh); @@ -116,7 +132,8 @@ impl WaylandWindow { }; window.request_decoration_mode(decor_mode); - // TODO: I don't know anything about the frame thing + // TODO: I don't want to deal with CSD right now, since my current tiling window manager + // Hyprland doesn't support it // window.set_frame_config(ConceptConfig { window.set_min_size(Some((32, 32))); @@ -129,14 +146,15 @@ impl WaylandWindow { // conn.pointer.borrow().add_window(&surface, &pending_mouse); - // TODO: WindowInner let inner = Rc::new(RefCell::new(WaylandWindowInner { window_id, events: WindowEventSender::new(event_handler), + surface_factor: 1.0, invalidated: false, window: Some(window), - + dimensions, + resize_increments: None, window_state: WindowState::default(), pending_event, @@ -144,6 +162,8 @@ impl WaylandWindow { pending_first_configure: Some(pending_first_configure), frame_callback: None, + config, + title: None, wegl_surface: None, @@ -279,11 +299,11 @@ pub(crate) struct PendingEvent { pub struct WaylandWindowInner { window_id: usize, pub(crate) events: WindowEventSender, - // surface_factor: f64, + surface_factor: f64, // copy_and_paste: Arc>, window: Option, - // dimensions: Dimensions, - // resize_increments: Option<(u16, u16)>, + dimensions: Dimensions, + resize_increments: Option<(u16, u16)>, window_state: WindowState, // last_mouse_coords: Point, // mouse_buttons: MouseButtons, @@ -300,7 +320,7 @@ pub struct WaylandWindowInner { // font_config: Rc, // text_cursor: Option, // appearance: Appearance, - // config: ConfigHandle, + config: ConfigHandle, // // cache the title for comparison to avoid spamming // // the compositor with updates that don't actually change it title: Option, @@ -323,8 +343,9 @@ impl WaylandWindowInner { fn refresh_frame(&mut self) { if let Some(window) = self.window.as_mut() { + // TODO: refresh frame // window.refresh(); - // window.surface().commit(); + window.wl_surface().commit(); } } @@ -383,6 +404,21 @@ impl WaylandWindowInner { Ok(gl_state) } + fn get_dpi_factor(&self) -> f64 { + self.dimensions.dpi as f64 / crate::DEFAULT_DPI as f64 + } + + fn surface_to_pixels(&self, surface: i32) -> i32 { + (surface as f64 * self.get_dpi_factor()).ceil() as i32 + } + + fn pixels_to_surface(&self, pixels: i32) -> i32 { + // Take care to round up, otherwise we can lose a pixel + // and that can effectively lose the final row of the + // terminal + ((pixels as f64) / self.get_dpi_factor()).ceil() as i32 + } + pub(crate) fn dispatch_pending_event(&mut self) { let mut pending; { @@ -405,95 +441,105 @@ impl WaylandWindowInner { } if pending.configure.is_none() { - // TODO: Handle the DPI + if pending.dpi.is_some() { + // Synthesize a pending configure event for the dpi change + pending.configure.replace(( + self.pixels_to_surface(self.dimensions.pixel_width as i32) as u32, + self.pixels_to_surface(self.dimensions.pixel_height as i32) as u32, + )); + log::debug!("synthesize configure with {:?}", pending.configure); + } } if let Some((mut w, mut h)) = pending.configure.take() { log::trace!("Pending configure: w:{w}, h{h} -- {:?}", self.window); if self.window.is_some() { - // TODO: here - // let factor = get_surface_scale_factor(&self.surface) as f64; - // let old_dimensions = self.dimensions; + let surface_udata = SurfaceUserData::from_wl(self.surface()); + let factor = surface_udata.surface_data.scale_factor() as f64; + let old_dimensions = self.dimensions; // FIXME: teach this how to resolve dpi_by_screen - // let dpi = self.config.dpi.unwrap_or(factor * crate::DEFAULT_DPI) as usize; + let dpi = self.config.dpi.unwrap_or(factor * crate::DEFAULT_DPI) as usize; // Do this early because this affects surface_to_pixels/pixels_to_surface - // self.dimensions.dpi = dpi; - - // let mut pixel_width = self.surface_to_pixels(w.try_into().unwrap()); - // let mut pixel_height = self.surface_to_pixels(h.try_into().unwrap()); - // - // if self.window_state.can_resize() { - // if let Some((x, y)) = self.resize_increments { - // let desired_pixel_width = pixel_width - (pixel_width % x as i32); - // let desired_pixel_height = pixel_height - (pixel_height % y as i32); - // w = self.pixels_to_surface(desired_pixel_width) as u32; - // h = self.pixels_to_surface(desired_pixel_height) as u32; - // pixel_width = self.surface_to_pixels(w.try_into().unwrap()); - // pixel_height = self.surface_to_pixels(h.try_into().unwrap()); - // } - // } - // - // // Update the window decoration size + self.dimensions.dpi = dpi; + + let mut pixel_width = self.surface_to_pixels(w.try_into().unwrap()); + let mut pixel_height = self.surface_to_pixels(h.try_into().unwrap()); + + if self.window_state.can_resize() { + if let Some((x, y)) = self.resize_increments { + let desired_pixel_width = pixel_width - (pixel_width % x as i32); + let desired_pixel_height = pixel_height - (pixel_height % y as i32); + w = self.pixels_to_surface(desired_pixel_width) as u32; + h = self.pixels_to_surface(desired_pixel_height) as u32; + pixel_width = self.surface_to_pixels(w.try_into().unwrap()); + pixel_height = self.surface_to_pixels(h.try_into().unwrap()); + } + } + + // TODO: Update the window decoration size // self.window.as_mut().unwrap().resize(w, h); - // - // // Compute the new pixel dimensions - // let new_dimensions = Dimensions { - // pixel_width: pixel_width.try_into().unwrap(), - // pixel_height: pixel_height.try_into().unwrap(), - // dpi, - // }; - // - // // Only trigger a resize if the new dimensions are different; - // // this makes things more efficient and a little more smooth - // if new_dimensions != old_dimensions { - // self.dimensions = new_dimensions; - // - // self.events.dispatch(WindowEvent::Resized { - // dimensions: self.dimensions, - // window_state: self.window_state, - // // We don't know if we're live resizing or not, so - // // assume no. - // live_resizing: false, - // }); - // // Avoid blurring by matching the scaling factor of the - // // compositor; if it is going to double the size then - // // we render at double the size anyway and tell it that - // // the buffer is already doubled. - // // Take care to detach the current buffer (managed by EGL), - // // so that the compositor doesn't get annoyed by it not - // // having dimensions that match the scale. - // // The wegl_surface.resize won't take effect until - // // we paint later on. - // // We do this only if the scale has actually changed, - // // otherwise interactive window resize will keep removing - // // the window contents! - // if let Some(wegl_surface) = self.wegl_surface.as_mut() { - // wegl_surface.resize(pixel_width, pixel_height, 0, 0); - // } - // if self.surface_factor != factor { - // let wayland_conn = Connection::get().unwrap().wayland(); - // let mut pool = wayland_conn.mem_pool.borrow_mut(); - // // Make a "fake" buffer with the right dimensions, as - // // simply detaching the buffer can cause wlroots-derived - // // compositors consider the window to be unconfigured. - // if let Ok((_bytes, buffer)) = pool.buffer( - // factor as i32, - // factor as i32, - // (factor * 4.0) as i32, - // wayland_client::protocol::wl_shm::Format::Argb8888, - // ) { - // self.surface.attach(Some(&buffer), 0, 0); - // self.surface.set_buffer_scale(factor as i32); - // self.surface_factor = factor; - // } - // } - // } - // self.refresh_frame(); - // self.do_paint().unwrap(); + + // Compute the new pixel dimensions + let new_dimensions = Dimensions { + pixel_width: pixel_width.try_into().unwrap(), + pixel_height: pixel_height.try_into().unwrap(), + dpi, + }; + + // Only trigger a resize if the new dimensions are different; + // this makes things more efficient and a little more smooth + if new_dimensions != old_dimensions { + self.dimensions = new_dimensions; + + self.events.dispatch(WindowEvent::Resized { + dimensions: self.dimensions, + window_state: self.window_state, + // We don't know if we're live resizing or not, so + // assume no. + live_resizing: false, + }); + // Avoid blurring by matching the scaling factor of the + // compositor; if it is going to double the size then + // we render at double the size anyway and tell it that + // the buffer is already doubled. + // Take care to detach the current buffer (managed by EGL), + // so that the compositor doesn't get annoyed by it not + // having dimensions that match the scale. + // The wegl_surface.resize won't take effect until + // we paint later on. + // We do this only if the scale has actually changed, + // otherwise interactive window resize will keep removing + // the window contents! + if let Some(wegl_surface) = self.wegl_surface.as_mut() { + wegl_surface.resize(pixel_width, pixel_height, 0, 0); + } + if self.surface_factor != factor { + let wayland_conn = Connection::get().unwrap().wayland(); + let wayland_state = wayland_conn.wayland_state.borrow(); + let mut pool = wayland_state.mem_pool.borrow_mut(); + + // Make a "fake" buffer with the right dimensions, as + // simply detaching the buffer can cause wlroots-derived + // compositors consider the window to be unconfigured. + if let Ok((buffer, _bytes)) = pool.create_buffer( + factor as i32, + factor as i32, + (factor * 4.0) as i32, + wayland_client::protocol::wl_shm::Format::Argb8888, + ) { + self.surface().attach(Some(buffer.wl_buffer()), 0, 0); + self.surface().set_buffer_scale(factor as i32); + self.surface_factor = factor; + } + } + } + self.refresh_frame(); + self.do_paint().unwrap(); } } + // TODO: // if pending.refresh_decorations && self.window.is_some() { // self.refresh_frame(); // } @@ -522,7 +568,7 @@ impl WaylandWindowInner { if let Some(window) = self.window.as_ref() { window.set_title(title.clone()); } - // TODO: self.refresh_frame(); + self.refresh_frame(); self.title = Some(title); } From a24c1ff0a5be3e8acbbc9bb0a81c60b69b11859d Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Mon, 8 Jan 2024 01:55:55 -0500 Subject: [PATCH 22/55] Fix delegate dispatch for SurfaceUserData Without this change, the event dispatch would go on forever and cause a stack overflow --- window/src/os/wayland/connection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 6a684be9ecc..768da64280b 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -373,7 +373,7 @@ impl ConnectionOps for WaylandConnection { // Undocumented in sctk 0.17: This is required to use have user data with a surface // Will be just delegate_compositor!(WaylandState, surface: [SurfaceData, SurfaceUserData]) in 0.18 -wayland_client::delegate_dispatch!(WaylandState: [ WlSurface: SurfaceUserData] => WaylandState); +wayland_client::delegate_dispatch!(WaylandState: [ WlSurface: SurfaceUserData] => CompositorState); delegate_compositor!(WaylandState); delegate_output!(WaylandState); From 3a5f388d1c587fde850c14ede375e8ff7b039616 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Mon, 8 Jan 2024 02:01:42 -0500 Subject: [PATCH 23/55] Implement getting screens --- window/src/os/wayland/connection.rs | 102 +++++++++++++++++++++++++--- window/src/os/wayland/window.rs | 11 +-- 2 files changed, 100 insertions(+), 13 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 768da64280b..8b7308745f4 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -1,7 +1,7 @@ // TODO: change this #![allow(dead_code, unused)] use std::{ - cell::{RefCell, Ref}, + cell::{Ref, RefCell}, collections::HashMap, os::fd::AsRawFd, rc::Rc, @@ -11,8 +11,8 @@ use std::{ use anyhow::{bail, Context}; use mio::{unix::SourceFd, Events, Interest, Poll, Token}; use smithay_client_toolkit::{ - compositor::{CompositorHandler, SurfaceData}, - delegate_compositor, delegate_output, delegate_registry, delegate_xdg_shell, + compositor::{CompositorHandler, SurfaceData, CompositorState}, + delegate_compositor, delegate_output, delegate_registry, delegate_shm, delegate_xdg_shell, delegate_xdg_window, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, @@ -20,7 +20,8 @@ use smithay_client_toolkit::{ shell::{ xdg::window::{WindowHandler, WindowState as SCTKWindowState}, WaylandSurface, - }, shm::{slot::SlotPool, Shm, ShmHandler}, delegate_shm, + }, + shm::{slot::SlotPool, Shm, ShmHandler}, }; use wayland_client::{ backend::WaylandError, @@ -29,7 +30,9 @@ use wayland_client::{ Connection as WConnection, EventQueue, Proxy, }; -use crate::{spawn::SPAWN_QUEUE, Connection, ConnectionOps, WindowState}; +use crate::{ + screen::{ScreenInfo, Screens}, spawn::SPAWN_QUEUE, Connection, ConnectionOps, ScreenRect, WindowState, +}; use super::{SurfaceUserData, WaylandWindowInner}; @@ -50,6 +53,8 @@ pub struct WaylandConnection { // TODO: the SurfaceUserData should be something in WaylandConnection struct as a whole. I think? pub(crate) struct WaylandState { registry_state: RegistryState, + output_state: OutputState, + shm: Shm, pub(crate) mem_pool: RefCell, } @@ -64,11 +69,11 @@ impl WaylandConnection { let mem_pool = SlotPool::new(1, &shm)?; let wayland_state = WaylandState { registry_state: RegistryState::new(&globals), + output_state: OutputState::new(&globals, &qh), shm, mem_pool: RefCell::new(mem_pool), }; - let wayland_connection = WaylandConnection { connection: RefCell::new(conn), should_terminate: RefCell::new(false), @@ -226,7 +231,7 @@ impl CompositorHandler for WaylandState { impl OutputHandler for WaylandState { fn output_state(&mut self) -> &mut smithay_client_toolkit::output::OutputState { log::trace!("output state: OutputHandler"); - todo!() + &mut self.output_state } fn new_output( @@ -367,7 +372,88 @@ impl ConnectionOps for WaylandConnection { } fn screens(&self) -> anyhow::Result { - todo!("Screens is not implemented"); + // TODO: implement for the outputhandler + // if let Some(screens) = self + // .environment + // .with_inner(|env| env.output_handler.screens()) + // { + // return Ok(screens); + // } + // + + log::trace!("Getting screens for wayland connection"); + let mut by_name = HashMap::new(); + let mut virtual_rect: ScreenRect = euclid::rect(0, 0, 0, 0); + let config = config::configuration(); + + let output_state = &self.wayland_state.borrow().output_state; + + for output in output_state.outputs() { + let info = output_state.info(&output).unwrap(); + let name = match info.name { + Some(n) => n.clone(), + None => format!("{} {}", info.model, info.make), + }; + + let (width, height) = info + .modes + .iter() + .find(|mode| mode.current) + .map(|mode| mode.dimensions) + .unwrap_or((info.physical_size.0, info.physical_size.1)); + + let rect = euclid::rect( + info.location.0 as isize, + info.location.1 as isize, + width as isize, + height as isize, + ); + + let scale = info.scale_factor as f64; + + // FIXME: teach this how to resolve dpi_by_screen once + // dispatch_pending_event knows how to do the same + let effective_dpi = Some(config.dpi.unwrap_or(scale * crate::DEFAULT_DPI)); + + virtual_rect = virtual_rect.union(&rect); + by_name.insert( + name.clone(), + ScreenInfo { + name, + rect, + scale, + max_fps: None, + effective_dpi, + }, + ); + } + + // // The main screen is the one either at the origin of + // // the virtual area, or if that doesn't exist for some weird + // // reason, the screen closest to the origin. + let main = by_name + .values() + .min_by_key(|screen| { + screen + .rect + .origin + .to_f32() + .distance_to(euclid::Point2D::origin()) + .abs() as isize + }) + .ok_or_else(|| anyhow::anyhow!("no screens were found"))? + .clone(); + + // We don't yet know how to determine the active screen, + // so assume the main screen. + let active = main.clone(); + + Ok(Screens { + main, + active, + by_name, + virtual_rect, + }) } } diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 7c1c30211c6..b05923222d3 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -291,6 +291,7 @@ unsafe impl HasRawWindowHandle for WaylandWindow { pub(crate) struct PendingEvent { pub(crate) close: bool, pub(crate) had_configure_event: bool, + refresh_decorations: bool, pub(crate) configure: Option<(u32, u32)>, pub(crate) dpi: Option, pub(crate) window_state: Option, @@ -434,7 +435,7 @@ impl WaylandWindowInner { if let Some(window_state) = pending.window_state.take() { log::debug!( "dispatch_pending_event self.window_state={:?}, pending:{:?}", - window_state, + self.window_state, window_state ); self.window_state = window_state; @@ -539,11 +540,11 @@ impl WaylandWindowInner { self.do_paint().unwrap(); } } - // TODO: - // if pending.refresh_decorations && self.window.is_some() { - // self.refresh_frame(); - // } + if pending.refresh_decorations && self.window.is_some() { + self.refresh_frame(); + } if pending.had_configure_event && self.window.is_some() { + log::debug!("Had configured an event"); if let Some(notify) = self.pending_first_configure.take() { // Allow window creation to complete notify.try_send(()).ok(); From 4b7dd42bb9d9fca8ab03504978ad62ab17df16e3 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Mon, 8 Jan 2024 02:09:33 -0500 Subject: [PATCH 24/55] Handle scale factor changes: do nothing --- window/src/os/wayland/connection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 8b7308745f4..2e1423bf743 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -207,7 +207,7 @@ impl CompositorHandler for WaylandState { surface: &wayland_client::protocol::wl_surface::WlSurface, new_factor: i32, ) { - todo!() + // We do nothing, we get the scale_factor from surface_data } fn frame( From bf61ea60ef2aba7ace5e35d3aa9d33bd11374e59 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Mon, 8 Jan 2024 03:09:18 -0500 Subject: [PATCH 25/55] Handle window close -> no more crashes --- window/src/os/wayland/connection.rs | 173 ++++++++++++++++------------ window/src/os/wayland/window.rs | 11 +- 2 files changed, 107 insertions(+), 77 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 2e1423bf743..abd5b08ffd4 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -5,20 +5,20 @@ use std::{ collections::HashMap, os::fd::AsRawFd, rc::Rc, - sync::{atomic::AtomicUsize, Arc}, + sync::{atomic::AtomicUsize, Arc, Mutex}, }; use anyhow::{bail, Context}; use mio::{unix::SourceFd, Events, Interest, Poll, Token}; use smithay_client_toolkit::{ - compositor::{CompositorHandler, SurfaceData, CompositorState}, + compositor::{CompositorHandler, CompositorState, SurfaceData}, delegate_compositor, delegate_output, delegate_registry, delegate_shm, delegate_xdg_shell, delegate_xdg_window, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, shell::{ - xdg::window::{WindowHandler, WindowState as SCTKWindowState}, + xdg::window::{Window, WindowConfigure, WindowHandler, WindowState as SCTKWindowState}, WaylandSurface, }, shm::{slot::SlotPool, Shm, ShmHandler}, @@ -31,10 +31,12 @@ use wayland_client::{ }; use crate::{ - screen::{ScreenInfo, Screens}, spawn::SPAWN_QUEUE, Connection, ConnectionOps, ScreenRect, WindowState, + screen::{ScreenInfo, Screens}, + spawn::SPAWN_QUEUE, + Connection, ConnectionOps, ScreenRect, WindowState, }; -use super::{SurfaceUserData, WaylandWindowInner}; +use super::{PendingEvent, SurfaceUserData, WaylandWindowInner}; pub struct WaylandConnection { pub(crate) should_terminate: RefCell, @@ -199,6 +201,82 @@ impl WaylandConnection { } } +impl WaylandState { + fn handle_window_event(&self, window: &Window, event: WindowEvent) { + // TODO: XXX: should we grouping window data and connection + let surface_data = SurfaceUserData::from_wl(window.wl_surface()); + let window_id = surface_data.window_id; + let wconn = WaylandConnection::get() + .expect("should be wayland connection") + .wayland(); + let window_inner = wconn + .window_by_id(window_id) + .expect("Inner Window should exist"); + + let p = window_inner.borrow().pending_event.clone(); + let mut pending_event = p.lock().unwrap(); + + let changed = match event { + WindowEvent::Close => { + // TODO: This should the new queue function + // p.queue_close() + if !pending_event.close { + pending_event.close = true; + true + } else { + false + } + } + WindowEvent::Request(configure) => { + // TODO: This should the new queue function + // p.queue_configure(&configure) + // + let mut changed; + pending_event.had_configure_event = true; + if let (Some(w), Some(h)) = configure.new_size { + changed = pending_event.configure.is_none(); + pending_event.configure.replace((w.get(), h.get())); + } else { + changed = true; + } + + let mut state = WindowState::default(); + if configure.state.contains(SCTKWindowState::FULLSCREEN) { + state |= WindowState::FULL_SCREEN; + } + let fs_bits = SCTKWindowState::MAXIMIZED + | SCTKWindowState::TILED_LEFT + | SCTKWindowState::TILED_RIGHT + | SCTKWindowState::TILED_TOP + | SCTKWindowState::TILED_BOTTOM; + if !((configure.state & fs_bits).is_empty()) { + state |= WindowState::MAXIMIZED; + } + + log::debug!( + "Config: self.window_state={:?}, states: {:?} {:?}", + pending_event.window_state, + state, + configure.state + ); + + if pending_event.window_state.is_none() && state != WindowState::default() { + changed = true; + } + + pending_event.window_state.replace(state); + changed + } + }; + if changed { + WaylandConnection::with_window_inner(window_id, move |inner| { + inner.dispatch_pending_event(); + Ok(()) + }); + } + } +} + impl CompositorHandler for WaylandState { fn scale_factor_changed( &mut self, @@ -264,85 +342,30 @@ impl OutputHandler for WaylandState { } } +enum WindowEvent { + Close, + Request(WindowConfigure), +} + impl WindowHandler for WaylandState { fn request_close( &mut self, - conn: &WConnection, - qh: &wayland_client::QueueHandle, - window: &smithay_client_toolkit::shell::xdg::window::Window, + _conn: &WConnection, + _qh: &wayland_client::QueueHandle, + window: &Window, ) { - log::trace!("Request close on WindowHandler"); - todo!() + self.handle_window_event(window, WindowEvent::Close); } fn configure( &mut self, - conn: &WConnection, - qh: &wayland_client::QueueHandle, - window: &smithay_client_toolkit::shell::xdg::window::Window, - configure: smithay_client_toolkit::shell::xdg::window::WindowConfigure, - serial: u32, + _conn: &WConnection, + _qh: &wayland_client::QueueHandle, + window: &Window, + configure: WindowConfigure, + _serial: u32, ) { - let surface_data = SurfaceUserData::from_wl(window.wl_surface()); - // TODO: XXX: should we grouping window data and connection - - let window_id = surface_data.window_id; - let wconn = WaylandConnection::get() - .expect("should be wayland connection") - .wayland(); - let window_inner = wconn - .window_by_id(window_id) - .expect("Inner Window should exist"); - - let p = window_inner.borrow().pending_event.clone(); - let mut pending_event = p.lock().unwrap(); - - // TODO: This should the new queue function - // p.queue_configure(&configure) - // - let changed = { - let mut changed; - pending_event.had_configure_event = true; - if let (Some(w), Some(h)) = configure.new_size { - changed = pending_event.configure.is_none(); - pending_event.configure.replace((w.get(), h.get())); - } else { - changed = true; - } - - let mut state = WindowState::default(); - if configure.state.contains(SCTKWindowState::FULLSCREEN) { - state |= WindowState::FULL_SCREEN; - } - let fs_bits = SCTKWindowState::MAXIMIZED - | SCTKWindowState::TILED_LEFT - | SCTKWindowState::TILED_RIGHT - | SCTKWindowState::TILED_TOP - | SCTKWindowState::TILED_BOTTOM; - if !((configure.state & fs_bits).is_empty()) { - state |= WindowState::MAXIMIZED; - } - - log::debug!( - "Config: self.window_state={:?}, states: {:?} {:?}", - pending_event.window_state, - state, - configure.state - ); - - if pending_event.window_state.is_none() && state != WindowState::default() { - changed = true; - } - - pending_event.window_state.replace(state); - changed - }; // function should return changed - if changed { - WaylandConnection::with_window_inner(window_id, move |inner| { - inner.dispatch_pending_event(); - Ok(()) - }); - } + self.handle_window_event(window, WindowEvent::Request(configure)); } } diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index b05923222d3..28d77059d2e 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -224,9 +224,11 @@ impl WindowOps for WaylandWindow { todo!() } - #[doc = r" Schedule the window to be closed"] fn close(&self) { - todo!() + WaylandConnection::with_window_inner(self.0, |inner| { + inner.close(); + Ok(()) + }); } #[doc = r" Change the cursor"] @@ -333,6 +335,11 @@ pub struct WaylandWindowInner { } impl WaylandWindowInner { + fn close(&mut self) { + self.events.dispatch(WindowEvent::Destroyed); + self.window.take(); + } + fn show(&mut self) { log::trace!("WaylandWindowInner show: {:?}", self.window); if self.window.is_none() { From 6c0f7e9e6bd7678fd90e968eea0923f28c4b79c4 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Mon, 8 Jan 2024 15:37:03 -0500 Subject: [PATCH 26/55] Store OutputState, CompositorState, XdgShell in WaylandState instead of binding them during new_window --- window/src/os/wayland/connection.rs | 37 ++++++++++++++++------------- window/src/os/wayland/window.rs | 10 ++++---- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index abd5b08ffd4..62910ce87ed 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -18,7 +18,10 @@ use smithay_client_toolkit::{ registry::{ProvidesRegistryState, RegistryState}, registry_handlers, shell::{ - xdg::window::{Window, WindowConfigure, WindowHandler, WindowState as SCTKWindowState}, + xdg::{ + window::{Window, WindowConfigure, WindowHandler, WindowState as SCTKWindowState}, + XdgShell, + }, WaylandSurface, }, shm::{slot::SlotPool, Shm, ShmHandler}, @@ -46,16 +49,19 @@ pub struct WaylandConnection { pub(crate) gl_connection: RefCell>>, - pub(crate) globals: RefCell, - pub(crate) connection: RefCell, + pub(crate) connection: WConnection, pub(crate) event_queue: RefCell>, + pub(crate) wayland_state: RefCell, } -// TODO: the SurfaceUserData should be something in WaylandConnection struct as a whole. I think? +// We can't combine WaylandState and WaylandConnection together because +// the run_message_loop has &self(WaylandConnection) and needs to update WaylandState as mut pub(crate) struct WaylandState { - registry_state: RegistryState, - output_state: OutputState, + registry: RegistryState, + output: OutputState, + pub(crate) compositor: CompositorState, + pub(crate) xdg: XdgShell, shm: Shm, pub(crate) mem_pool: RefCell, @@ -70,21 +76,22 @@ impl WaylandConnection { let shm = Shm::bind(&globals, &qh)?; let mem_pool = SlotPool::new(1, &shm)?; let wayland_state = WaylandState { - registry_state: RegistryState::new(&globals), - output_state: OutputState::new(&globals, &qh), + registry: RegistryState::new(&globals), + output: OutputState::new(&globals, &qh), + compositor: CompositorState::bind(&globals, &qh)?, + xdg: XdgShell::bind(&globals, &qh)?, shm, mem_pool: RefCell::new(mem_pool), }; let wayland_connection = WaylandConnection { - connection: RefCell::new(conn), + connection: conn, should_terminate: RefCell::new(false), next_window_id: AtomicUsize::new(1), gl_connection: RefCell::new(None), windows: RefCell::new(HashMap::default()), event_queue: RefCell::new(event_queue), - globals: RefCell::new(globals), wayland_state: RefCell::new(wayland_state), }; @@ -116,8 +123,6 @@ impl WaylandConnection { Interest::READABLE, )?; - // JUST Realized that the reason we need the spawn executor is so we can have tasks - // scheduled (needed to open window) while !*self.should_terminate.borrow() { let timeout = if SPAWN_QUEUE.run() { Some(std::time::Duration::from_secs(0)) @@ -203,7 +208,6 @@ impl WaylandConnection { impl WaylandState { fn handle_window_event(&self, window: &Window, event: WindowEvent) { - // TODO: XXX: should we grouping window data and connection let surface_data = SurfaceUserData::from_wl(window.wl_surface()); let window_id = surface_data.window_id; let wconn = WaylandConnection::get() @@ -308,8 +312,7 @@ impl CompositorHandler for WaylandState { impl OutputHandler for WaylandState { fn output_state(&mut self) -> &mut smithay_client_toolkit::output::OutputState { - log::trace!("output state: OutputHandler"); - &mut self.output_state + &mut self.output } fn new_output( @@ -409,7 +412,7 @@ impl ConnectionOps for WaylandConnection { let mut virtual_rect: ScreenRect = euclid::rect(0, 0, 0, 0); let config = config::configuration(); - let output_state = &self.wayland_state.borrow().output_state; + let output_state = &self.wayland_state.borrow().output; for output in output_state.outputs() { let info = output_state.info(&output).unwrap(); @@ -496,7 +499,7 @@ delegate_registry!(WaylandState); impl ProvidesRegistryState for WaylandState { fn registry(&mut self) -> &mut RegistryState { - &mut self.registry_state + &mut self.registry } registry_handlers!(OutputState); diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 28d77059d2e..9dd4034cfef 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -69,7 +69,6 @@ impl WaylandWindow { where F: 'static + FnMut(WindowEvent, &Window), { - log::trace!("Creating a window"); let config = match config { Some(c) => c.clone(), None => config::configuration(), @@ -89,9 +88,8 @@ impl WaylandWindow { let (pending_first_configure, wait_configure) = async_channel::bounded(1); let qh = conn.event_queue.borrow().handle(); - let globals = conn.globals.borrow(); - let compositor = CompositorState::bind(&globals, &qh)?; + let compositor = &conn.wayland_state.borrow().compositor; let surface = compositor.create_surface(&qh); // We need user data so we can get the window_id => WaylandWindowInner during a handler @@ -114,7 +112,7 @@ impl WaylandWindow { dpi: config.dpi.unwrap_or(crate::DEFAULT_DPI) as usize, }; - let xdg_shell = XdgShell::bind(&globals, &qh)?; + let xdg_shell = &conn.wayland_state.borrow().xdg; let window = xdg_shell.create_window(surface.clone(), Decorations::RequestServer, &qh); window.set_app_id(class_name.to_string()); @@ -271,7 +269,7 @@ unsafe impl HasRawDisplayHandle for WaylandWindow { fn raw_display_handle(&self) -> RawDisplayHandle { let mut handle = WaylandDisplayHandle::empty(); let conn = WaylandConnection::get().unwrap().wayland(); - handle.display = conn.connection.borrow().backend().display_ptr() as *mut _; + handle.display = conn.connection.backend().display_ptr() as *mut _; RawDisplayHandle::Wayland(handle) } } @@ -385,7 +383,7 @@ impl WaylandWindowInner { wegl_surface.as_ref().unwrap(), ), None => crate::egl::GlState::create_wayland( - Some(wayland_conn.connection.borrow().backend().display_ptr() as *const _), + Some(wayland_conn.connection.backend().display_ptr() as *const _), wegl_surface.as_ref().unwrap(), ), } From 6dc69204c73a7b9cd0f7b01ba96f17e775c76b27 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Mon, 8 Jan 2024 22:23:19 -0500 Subject: [PATCH 27/55] Wayland refactor: Put handlers for windows in windows.rs, and separate WaylandState --- window/src/os/wayland/connection.rs | 291 +++------------------------- window/src/os/wayland/mod.rs | 1 + window/src/os/wayland/state.rs | 95 +++++++++ window/src/os/wayland/window.rs | 288 ++++++++++++++++++--------- 4 files changed, 315 insertions(+), 360 deletions(-) create mode 100644 window/src/os/wayland/state.rs diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 62910ce87ed..1ab5a067170 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -1,70 +1,31 @@ -// TODO: change this -#![allow(dead_code, unused)] -use std::{ - cell::{Ref, RefCell}, - collections::HashMap, - os::fd::AsRawFd, - rc::Rc, - sync::{atomic::AtomicUsize, Arc, Mutex}, -}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::os::fd::AsRawFd; +use std::rc::Rc; +use std::sync::atomic::AtomicUsize; use anyhow::{bail, Context}; -use mio::{unix::SourceFd, Events, Interest, Poll, Token}; -use smithay_client_toolkit::{ - compositor::{CompositorHandler, CompositorState, SurfaceData}, - delegate_compositor, delegate_output, delegate_registry, delegate_shm, delegate_xdg_shell, - delegate_xdg_window, - output::{OutputHandler, OutputState}, - registry::{ProvidesRegistryState, RegistryState}, - registry_handlers, - shell::{ - xdg::{ - window::{Window, WindowConfigure, WindowHandler, WindowState as SCTKWindowState}, - XdgShell, - }, - WaylandSurface, - }, - shm::{slot::SlotPool, Shm, ShmHandler}, -}; -use wayland_client::{ - backend::WaylandError, - globals::{registry_queue_init, GlobalList}, - protocol::wl_surface::WlSurface, - Connection as WConnection, EventQueue, Proxy, -}; - -use crate::{ - screen::{ScreenInfo, Screens}, - spawn::SPAWN_QUEUE, - Connection, ConnectionOps, ScreenRect, WindowState, -}; - -use super::{PendingEvent, SurfaceUserData, WaylandWindowInner}; +use mio::unix::SourceFd; +use mio::{Events, Interest, Poll, Token}; +use wayland_client::backend::WaylandError; +use wayland_client::globals::registry_queue_init; +use wayland_client::{Connection as WConnection, EventQueue}; + +use crate::screen::{ScreenInfo, Screens}; +use crate::spawn::SPAWN_QUEUE; +use crate::{Connection, ConnectionOps, ScreenRect}; + +use super::state::WaylandState; +use super::WaylandWindowInner; pub struct WaylandConnection { pub(crate) should_terminate: RefCell, pub(crate) next_window_id: AtomicUsize, - - pub(crate) windows: RefCell>>>, - - pub(crate) gl_connection: RefCell>>, - - pub(crate) connection: WConnection, - pub(crate) event_queue: RefCell>, - - pub(crate) wayland_state: RefCell, -} - -// We can't combine WaylandState and WaylandConnection together because -// the run_message_loop has &self(WaylandConnection) and needs to update WaylandState as mut -pub(crate) struct WaylandState { - registry: RegistryState, - output: OutputState, - pub(crate) compositor: CompositorState, - pub(crate) xdg: XdgShell, - - shm: Shm, - pub(crate) mem_pool: RefCell, + pub(super) windows: RefCell>>>, + pub(super) gl_connection: RefCell>>, + pub(super) connection: WConnection, + pub(super) event_queue: RefCell>, + pub(super) wayland_state: RefCell, } impl WaylandConnection { @@ -73,26 +34,14 @@ impl WaylandConnection { let (globals, event_queue) = registry_queue_init::(&conn)?; let qh = event_queue.handle(); - let shm = Shm::bind(&globals, &qh)?; - let mem_pool = SlotPool::new(1, &shm)?; - let wayland_state = WaylandState { - registry: RegistryState::new(&globals), - output: OutputState::new(&globals, &qh), - compositor: CompositorState::bind(&globals, &qh)?, - xdg: XdgShell::bind(&globals, &qh)?, - shm, - mem_pool: RefCell::new(mem_pool), - }; - + let wayland_state = WaylandState::new(&globals, &qh)?; let wayland_connection = WaylandConnection { connection: conn, should_terminate: RefCell::new(false), next_window_id: AtomicUsize::new(1), gl_connection: RefCell::new(None), windows: RefCell::new(HashMap::default()), - event_queue: RefCell::new(event_queue), - wayland_state: RefCell::new(wayland_state), }; @@ -206,178 +155,6 @@ impl WaylandConnection { } } -impl WaylandState { - fn handle_window_event(&self, window: &Window, event: WindowEvent) { - let surface_data = SurfaceUserData::from_wl(window.wl_surface()); - let window_id = surface_data.window_id; - let wconn = WaylandConnection::get() - .expect("should be wayland connection") - .wayland(); - let window_inner = wconn - .window_by_id(window_id) - .expect("Inner Window should exist"); - - let p = window_inner.borrow().pending_event.clone(); - let mut pending_event = p.lock().unwrap(); - - let changed = match event { - WindowEvent::Close => { - // TODO: This should the new queue function - // p.queue_close() - if !pending_event.close { - pending_event.close = true; - true - } else { - false - } - } - WindowEvent::Request(configure) => { - // TODO: This should the new queue function - // p.queue_configure(&configure) - // - let mut changed; - pending_event.had_configure_event = true; - if let (Some(w), Some(h)) = configure.new_size { - changed = pending_event.configure.is_none(); - pending_event.configure.replace((w.get(), h.get())); - } else { - changed = true; - } - - let mut state = WindowState::default(); - if configure.state.contains(SCTKWindowState::FULLSCREEN) { - state |= WindowState::FULL_SCREEN; - } - let fs_bits = SCTKWindowState::MAXIMIZED - | SCTKWindowState::TILED_LEFT - | SCTKWindowState::TILED_RIGHT - | SCTKWindowState::TILED_TOP - | SCTKWindowState::TILED_BOTTOM; - if !((configure.state & fs_bits).is_empty()) { - state |= WindowState::MAXIMIZED; - } - - log::debug!( - "Config: self.window_state={:?}, states: {:?} {:?}", - pending_event.window_state, - state, - configure.state - ); - - if pending_event.window_state.is_none() && state != WindowState::default() { - changed = true; - } - - pending_event.window_state.replace(state); - changed - } - }; - if changed { - WaylandConnection::with_window_inner(window_id, move |inner| { - inner.dispatch_pending_event(); - Ok(()) - }); - } - } -} - -impl CompositorHandler for WaylandState { - fn scale_factor_changed( - &mut self, - conn: &WConnection, - qh: &wayland_client::QueueHandle, - surface: &wayland_client::protocol::wl_surface::WlSurface, - new_factor: i32, - ) { - // We do nothing, we get the scale_factor from surface_data - } - - fn frame( - &mut self, - conn: &WConnection, - qh: &wayland_client::QueueHandle, - surface: &wayland_client::protocol::wl_surface::WlSurface, - time: u32, - ) { - log::trace!("frame: CompositorHandler"); - let surface_data = SurfaceUserData::from_wl(surface); - let window_id = surface_data.window_id; - - WaylandConnection::with_window_inner(window_id, |inner| { - inner.next_frame_is_ready(); - Ok(()) - }); - } -} - -impl OutputHandler for WaylandState { - fn output_state(&mut self) -> &mut smithay_client_toolkit::output::OutputState { - &mut self.output - } - - fn new_output( - &mut self, - conn: &WConnection, - qh: &wayland_client::QueueHandle, - output: wayland_client::protocol::wl_output::WlOutput, - ) { - log::trace!("new output: OutputHandler"); - } - - fn update_output( - &mut self, - conn: &WConnection, - qh: &wayland_client::QueueHandle, - output: wayland_client::protocol::wl_output::WlOutput, - ) { - log::trace!("update output: OutputHandler"); - todo!() - } - - fn output_destroyed( - &mut self, - conn: &WConnection, - qh: &wayland_client::QueueHandle, - output: wayland_client::protocol::wl_output::WlOutput, - ) { - log::trace!("output destroyed: OutputHandler"); - todo!() - } -} - -enum WindowEvent { - Close, - Request(WindowConfigure), -} - -impl WindowHandler for WaylandState { - fn request_close( - &mut self, - _conn: &WConnection, - _qh: &wayland_client::QueueHandle, - window: &Window, - ) { - self.handle_window_event(window, WindowEvent::Close); - } - - fn configure( - &mut self, - _conn: &WConnection, - _qh: &wayland_client::QueueHandle, - window: &Window, - configure: WindowConfigure, - _serial: u32, - ) { - self.handle_window_event(window, WindowEvent::Request(configure)); - } -} - -impl ShmHandler for WaylandState { - fn shm_state(&mut self) -> &mut Shm { - &mut self.shm - } -} - impl ConnectionOps for WaylandConnection { fn name(&self) -> String { "Wayland".to_string() @@ -482,25 +259,3 @@ impl ConnectionOps for WaylandConnection { }) } } - -// Undocumented in sctk 0.17: This is required to use have user data with a surface -// Will be just delegate_compositor!(WaylandState, surface: [SurfaceData, SurfaceUserData]) in 0.18 -wayland_client::delegate_dispatch!(WaylandState: [ WlSurface: SurfaceUserData] => CompositorState); -delegate_compositor!(WaylandState); - -delegate_output!(WaylandState); - -delegate_xdg_shell!(WaylandState); -delegate_xdg_window!(WaylandState); - -delegate_shm!(WaylandState); - -delegate_registry!(WaylandState); - -impl ProvidesRegistryState for WaylandState { - fn registry(&mut self) -> &mut RegistryState { - &mut self.registry - } - - registry_handlers!(OutputState); -} diff --git a/window/src/os/wayland/mod.rs b/window/src/os/wayland/mod.rs index b486ee13e29..8f09023a75d 100644 --- a/window/src/os/wayland/mod.rs +++ b/window/src/os/wayland/mod.rs @@ -11,6 +11,7 @@ pub use connection::*; // mod drag_and_drop; // mod frame; // mod pointer; +mod state; /// Returns the id of a wayland proxy object, suitable for using /// a key into hash maps diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs new file mode 100644 index 00000000000..13fcfee70b5 --- /dev/null +++ b/window/src/os/wayland/state.rs @@ -0,0 +1,95 @@ +use std::cell::RefCell; + +use smithay_client_toolkit::{ + compositor::CompositorState, + delegate_compositor, delegate_output, delegate_registry, delegate_shm, delegate_xdg_shell, + delegate_xdg_window, + output::{OutputHandler, OutputState}, + registry::{ProvidesRegistryState, RegistryState}, + registry_handlers, + shell::xdg::XdgShell, + shm::{slot::SlotPool, Shm, ShmHandler}, +}; +use wayland_client::{ + delegate_dispatch, + globals::GlobalList, + protocol::{wl_output::WlOutput, wl_surface::WlSurface}, + Connection, QueueHandle, +}; + +use super::SurfaceUserData; + +// We can't combine WaylandState and WaylandConnection together because +// the run_message_loop has &self(WaylandConnection) and needs to update WaylandState as mut +pub(super) struct WaylandState { + registry: RegistryState, + pub(super) output: OutputState, + pub(super) compositor: CompositorState, + pub(super) xdg: XdgShell, + + shm: Shm, + pub(super) mem_pool: RefCell, +} + +impl WaylandState { + pub(super) fn new(globals: &GlobalList, qh: &QueueHandle) -> anyhow::Result { + let shm = Shm::bind(&globals, qh)?; + let mem_pool = SlotPool::new(1, &shm)?; + let wayland_state = WaylandState { + registry: RegistryState::new(globals), + output: OutputState::new(globals, qh), + compositor: CompositorState::bind(globals, qh)?, + xdg: XdgShell::bind(globals, qh)?, + shm, + mem_pool: RefCell::new(mem_pool), + }; + Ok(wayland_state) + } +} + +impl ProvidesRegistryState for WaylandState { + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry + } + + registry_handlers!(OutputState); +} + +impl ShmHandler for WaylandState { + fn shm_state(&mut self) -> &mut Shm { + &mut self.shm + } +} + +impl OutputHandler for WaylandState { + fn output_state(&mut self) -> &mut OutputState { + &mut self.output + } + + fn new_output(&mut self, _conn: &Connection, _qh: &QueueHandle, _output: WlOutput) { + log::trace!("new output: OutputHandler"); + } + + fn update_output(&mut self, _conn: &Connection, _qh: &QueueHandle, _output: WlOutput) { + log::trace!("update output: OutputHandler"); + todo!() + } + + fn output_destroyed(&mut self, _conn: &Connection, _qh: &QueueHandle, _output: WlOutput) { + log::trace!("output destroyed: OutputHandler"); + todo!() + } +} +// Undocumented in sctk 0.17: This is required to use have user data with a surface +// Will be just delegate_compositor!(WaylandState, surface: [SurfaceData, SurfaceUserData]) in 0.18 +delegate_dispatch!(WaylandState: [ WlSurface: SurfaceUserData] => CompositorState); + +delegate_registry!(WaylandState); + +delegate_shm!(WaylandState); + +delegate_output!(WaylandState); +delegate_compositor!(WaylandState); + +delegate_xdg_shell!(WaylandState); +delegate_xdg_window!(WaylandState); diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 9dd4034cfef..da6b31c3f76 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -1,58 +1,42 @@ -// TODO: change this -// TODO: change a lot of the pubstruct crates -#![allow(dead_code, unused)] - use std::any::Any; use std::cell::RefCell; use std::convert::TryInto; use std::rc::Rc; -use std::sync::Arc; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use anyhow::anyhow; use async_trait::async_trait; -use config::configuration; use config::ConfigHandle; use promise::Future; -use raw_window_handle::HasRawDisplayHandle; -use raw_window_handle::HasRawWindowHandle; -use raw_window_handle::RawDisplayHandle; -use raw_window_handle::RawWindowHandle; -use raw_window_handle::WaylandDisplayHandle; -use raw_window_handle::WaylandWindowHandle; -use smithay_client_toolkit::compositor::CompositorState; -use smithay_client_toolkit::compositor::SurfaceData; -use smithay_client_toolkit::compositor::SurfaceDataExt; -use smithay_client_toolkit::registry::ProvidesRegistryState; -use smithay_client_toolkit::shell::xdg::window::DecorationMode; -use smithay_client_toolkit::shell::xdg::window::Window as XdgWindow; -use smithay_client_toolkit::shell::xdg::window::WindowDecorations as Decorations; -use smithay_client_toolkit::shell::xdg::XdgShell; +use raw_window_handle::{ + HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, + WaylandDisplayHandle, WaylandWindowHandle, +}; +use smithay_client_toolkit::compositor::{CompositorHandler, SurfaceData, SurfaceDataExt}; +use smithay_client_toolkit::shell::xdg::window::{ + DecorationMode, Window as XdgWindow, WindowConfigure, WindowDecorations as Decorations, + WindowHandler, WindowState as SCTKWindowState, +}; use smithay_client_toolkit::shell::WaylandSurface; -use wayland_client::globals::GlobalList; use wayland_client::protocol::wl_callback::WlCallback; use wayland_client::protocol::wl_surface::WlSurface; -use wayland_client::Proxy; +use wayland_client::{Connection as WConnection, Proxy}; use wayland_egl::{is_available as egl_is_available, WlEglSurface}; use wezterm_font::FontConfiguration; -use wezterm_input_types::KeyboardLedStatus; -use wezterm_input_types::Modifiers; -use wezterm_input_types::MouseButtons; use wezterm_input_types::WindowDecorations; use crate::wayland::WaylandConnection; -use crate::Clipboard; -use crate::Connection; -use crate::ConnectionOps; -use crate::Dimensions; -use crate::MouseCursor; -use crate::RequestedWindowGeometry; -use crate::ResolvedGeometry; -use crate::Window; -use crate::WindowEvent; -use crate::WindowEventSender; -use crate::WindowOps; -use crate::WindowState; +use crate::{ + Clipboard, Connection, ConnectionOps, Dimensions, MouseCursor, RequestedWindowGeometry, + ResolvedGeometry, Window, WindowEvent, WindowEventSender, WindowOps, WindowState, +}; + +use super::state::WaylandState; + +enum WaylandWindowEvent { + Close, + Request(WindowConfigure), +} #[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct WaylandWindow(usize); @@ -63,7 +47,7 @@ impl WaylandWindow { name: &str, geometry: RequestedWindowGeometry, config: Option<&ConfigHandle>, - font_config: Rc, + _font_config: Rc, event_handler: F, ) -> anyhow::Result where @@ -89,15 +73,15 @@ impl WaylandWindow { let qh = conn.event_queue.borrow().handle(); - let compositor = &conn.wayland_state.borrow().compositor; - let surface = compositor.create_surface(&qh); - // We need user data so we can get the window_id => WaylandWindowInner during a handler let surface_data = SurfaceUserData { surface_data: SurfaceData::default(), window_id, }; - let surface = compositor.create_surface_with_data(&qh, surface_data); + let surface = { + let compositor = &conn.wayland_state.borrow().compositor; + compositor.create_surface_with_data(&qh, surface_data) + }; let ResolvedGeometry { x: _, @@ -112,8 +96,10 @@ impl WaylandWindow { dpi: config.dpi.unwrap_or(crate::DEFAULT_DPI) as usize, }; - let xdg_shell = &conn.wayland_state.borrow().xdg; - let window = xdg_shell.create_window(surface.clone(), Decorations::RequestServer, &qh); + let window = { + let xdg_shell = &conn.wayland_state.borrow().xdg; + xdg_shell.create_window(surface.clone(), Decorations::RequestServer, &qh) + }; window.set_app_id(class_name.to_string()); // TODO: investigate the resizable thing @@ -145,7 +131,6 @@ impl WaylandWindow { // conn.pointer.borrow().add_window(&surface, &pending_mouse); let inner = Rc::new(RefCell::new(WaylandWindowInner { - window_id, events: WindowEventSender::new(event_handler), surface_factor: 1.0, @@ -217,7 +202,6 @@ impl WindowOps for WaylandWindow { .await } - #[doc = r" Hide a visible window"] fn hide(&self) { todo!() } @@ -229,8 +213,7 @@ impl WindowOps for WaylandWindow { }); } - #[doc = r" Change the cursor"] - fn set_cursor(&self, cursor: Option) { + fn set_cursor(&self, _cursor: Option) { todo!() } @@ -249,44 +232,19 @@ impl WindowOps for WaylandWindow { }); } - #[doc = r" Resize the inner or client area of the window"] - fn set_inner_size(&self, width: usize, height: usize) { + fn set_inner_size(&self, _width: usize, _height: usize) { todo!() } #[doc = r" Initiate textual transfer from the clipboard"] - fn get_clipboard(&self, clipboard: Clipboard) -> Future { + fn get_clipboard(&self, _clipboard: Clipboard) -> Future { todo!() } - #[doc = r" Set some text in the clipboard"] - fn set_clipboard(&self, clipboard: Clipboard, text: String) { + fn set_clipboard(&self, _clipboard: Clipboard, _text: String) { todo!() } } - -unsafe impl HasRawDisplayHandle for WaylandWindow { - fn raw_display_handle(&self) -> RawDisplayHandle { - let mut handle = WaylandDisplayHandle::empty(); - let conn = WaylandConnection::get().unwrap().wayland(); - handle.display = conn.connection.backend().display_ptr() as *mut _; - RawDisplayHandle::Wayland(handle) - } -} - -unsafe impl HasRawWindowHandle for WaylandWindow { - fn raw_window_handle(&self) -> RawWindowHandle { - let conn = Connection::get().expect("raw_window_handle only callable on main thread"); - let handle = conn - .wayland() - .window_by_id(self.0) - .expect("window handle invalid!?"); - - let inner = handle.borrow(); - inner.raw_window_handle() - } -} - #[derive(Default, Clone, Debug)] pub(crate) struct PendingEvent { pub(crate) close: bool, @@ -298,7 +256,7 @@ pub(crate) struct PendingEvent { } pub struct WaylandWindowInner { - window_id: usize, + // window_id: usize, pub(crate) events: WindowEventSender, surface_factor: f64, // copy_and_paste: Arc>, @@ -591,8 +549,6 @@ impl WaylandWindowInner { // Ask the compositor to wake us up when its time to paint the next frame, // note that this only happens _after_ the next commit - let window_id = self.window_id; - let conn = WaylandConnection::get().unwrap().wayland(); let qh = conn.event_queue.borrow().handle(); @@ -627,6 +583,150 @@ impl WaylandWindowInner { } } +impl WaylandState { + fn handle_window_event(&self, window: &XdgWindow, event: WaylandWindowEvent) { + let surface_data = SurfaceUserData::from_wl(window.wl_surface()); + let window_id = surface_data.window_id; + let wconn = WaylandConnection::get() + .expect("should be wayland connection") + .wayland(); + let window_inner = wconn + .window_by_id(window_id) + .expect("Inner Window should exist"); + + let p = window_inner.borrow().pending_event.clone(); + let mut pending_event = p.lock().unwrap(); + + let changed = match event { + WaylandWindowEvent::Close => { + // TODO: This should the new queue function + // p.queue_close() + if !pending_event.close { + pending_event.close = true; + true + } else { + false + } + } + WaylandWindowEvent::Request(configure) => { + // TODO: This should the new queue function + // p.queue_configure(&configure) + // + let mut changed; + pending_event.had_configure_event = true; + if let (Some(w), Some(h)) = configure.new_size { + changed = pending_event.configure.is_none(); + pending_event.configure.replace((w.get(), h.get())); + } else { + changed = true; + } + + let mut state = WindowState::default(); + if configure.state.contains(SCTKWindowState::FULLSCREEN) { + state |= WindowState::FULL_SCREEN; + } + let fs_bits = SCTKWindowState::MAXIMIZED + | SCTKWindowState::TILED_LEFT + | SCTKWindowState::TILED_RIGHT + | SCTKWindowState::TILED_TOP + | SCTKWindowState::TILED_BOTTOM; + if !((configure.state & fs_bits).is_empty()) { + state |= WindowState::MAXIMIZED; + } + + log::debug!( + "Config: self.window_state={:?}, states: {:?} {:?}", + pending_event.window_state, + state, + configure.state + ); + + if pending_event.window_state.is_none() && state != WindowState::default() { + changed = true; + } + + pending_event.window_state.replace(state); + changed + } + }; + if changed { + WaylandConnection::with_window_inner(window_id, move |inner| { + inner.dispatch_pending_event(); + Ok(()) + }); + } + } +} + +impl CompositorHandler for WaylandState { + fn scale_factor_changed( + &mut self, + _conn: &WConnection, + _qh: &wayland_client::QueueHandle, + _surface: &wayland_client::protocol::wl_surface::WlSurface, + _new_factor: i32, + ) { + // We do nothing, we get the scale_factor from surface_data + } + + fn frame( + &mut self, + _conn: &WConnection, + _qh: &wayland_client::QueueHandle, + surface: &wayland_client::protocol::wl_surface::WlSurface, + _time: u32, + ) { + log::trace!("frame: CompositorHandler"); + let surface_data = SurfaceUserData::from_wl(surface); + let window_id = surface_data.window_id; + + WaylandConnection::with_window_inner(window_id, |inner| { + inner.next_frame_is_ready(); + Ok(()) + }); + } +} + +impl WindowHandler for WaylandState { + fn request_close( + &mut self, + _conn: &WConnection, + _qh: &wayland_client::QueueHandle, + window: &XdgWindow, + ) { + self.handle_window_event(window, WaylandWindowEvent::Close); + } + + fn configure( + &mut self, + _conn: &WConnection, + _qh: &wayland_client::QueueHandle, + window: &XdgWindow, + configure: WindowConfigure, + _serial: u32, + ) { + self.handle_window_event(window, WaylandWindowEvent::Request(configure)); + } +} + +pub(super) struct SurfaceUserData { + surface_data: SurfaceData, + window_id: usize, +} + +impl SurfaceUserData { + pub(crate) fn from_wl(wl: &WlSurface) -> &Self { + wl.data() + .expect("User data should be associated with WlSurface") + } +} + +impl SurfaceDataExt for SurfaceUserData { + fn surface_data(&self) -> &SurfaceData { + &self.surface_data + } +} + unsafe impl HasRawDisplayHandle for WaylandWindowInner { fn raw_display_handle(&self) -> RawDisplayHandle { // let mut handle = WaylandDisplayHandle::empty(); @@ -646,20 +746,24 @@ unsafe impl HasRawWindowHandle for WaylandWindowInner { } } -pub(crate) struct SurfaceUserData { - surface_data: SurfaceData, - pub(crate) window_id: usize, -} - -impl SurfaceUserData { - pub(crate) fn from_wl(wl: &WlSurface) -> &Self { - wl.data() - .expect("User data should be associated with WlSurface") +unsafe impl HasRawDisplayHandle for WaylandWindow { + fn raw_display_handle(&self) -> RawDisplayHandle { + let mut handle = WaylandDisplayHandle::empty(); + let conn = WaylandConnection::get().unwrap().wayland(); + handle.display = conn.connection.backend().display_ptr() as *mut _; + RawDisplayHandle::Wayland(handle) } } -impl SurfaceDataExt for SurfaceUserData { - fn surface_data(&self) -> &smithay_client_toolkit::compositor::SurfaceData { - &self.surface_data +unsafe impl HasRawWindowHandle for WaylandWindow { + fn raw_window_handle(&self) -> RawWindowHandle { + let conn = Connection::get().expect("raw_window_handle only callable on main thread"); + let handle = conn + .wayland() + .window_by_id(self.0) + .expect("window handle invalid!?"); + + let inner = handle.borrow(); + inner.raw_window_handle() } } From c2c8d2483b7ba079b07def72386f5f1e8879d5e1 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Tue, 9 Jan 2024 23:19:50 -0500 Subject: [PATCH 28/55] Refactor windows to WaylandState --- window/src/os/wayland/connection.rs | 6 ++---- window/src/os/wayland/state.rs | 32 +++++++++++++++-------------- window/src/os/wayland/window.rs | 15 +++++++++----- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 1ab5a067170..33c5c5c16d3 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -21,7 +21,6 @@ use super::WaylandWindowInner; pub struct WaylandConnection { pub(crate) should_terminate: RefCell, pub(crate) next_window_id: AtomicUsize, - pub(super) windows: RefCell>>>, pub(super) gl_connection: RefCell>>, pub(super) connection: WConnection, pub(super) event_queue: RefCell>, @@ -40,7 +39,6 @@ impl WaylandConnection { should_terminate: RefCell::new(false), next_window_id: AtomicUsize::new(1), gl_connection: RefCell::new(None), - windows: RefCell::new(HashMap::default()), event_queue: RefCell::new(event_queue), wayland_state: RefCell::new(wayland_state), }; @@ -127,7 +125,7 @@ impl WaylandConnection { } pub(crate) fn window_by_id(&self, window_id: usize) -> Option>> { - self.windows.borrow().get(&window_id).map(Rc::clone) + self.wayland_state.borrow().window_by_id(window_id) } pub(crate) fn with_window_inner< @@ -170,7 +168,7 @@ impl ConnectionOps for WaylandConnection { // Ensure that we drop these eagerly, to avoid // noisy errors wrt. global destructors unwinding // in unexpected places - self.windows.borrow_mut().clear(); + self.wayland_state.borrow().windows.borrow_mut().clear(); res } diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index 13fcfee70b5..06a93b9623b 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -1,23 +1,23 @@ use std::cell::RefCell; - +use std::collections::HashMap; +use std::rc::Rc; + +use smithay_client_toolkit::compositor::CompositorState; +use smithay_client_toolkit::output::{OutputHandler, OutputState}; +use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState}; +use smithay_client_toolkit::shell::xdg::XdgShell; +use smithay_client_toolkit::shm::slot::SlotPool; +use smithay_client_toolkit::shm::{Shm, ShmHandler}; use smithay_client_toolkit::{ - compositor::CompositorState, delegate_compositor, delegate_output, delegate_registry, delegate_shm, delegate_xdg_shell, - delegate_xdg_window, - output::{OutputHandler, OutputState}, - registry::{ProvidesRegistryState, RegistryState}, - registry_handlers, - shell::xdg::XdgShell, - shm::{slot::SlotPool, Shm, ShmHandler}, -}; -use wayland_client::{ - delegate_dispatch, - globals::GlobalList, - protocol::{wl_output::WlOutput, wl_surface::WlSurface}, - Connection, QueueHandle, + delegate_xdg_window, registry_handlers, }; +use wayland_client::globals::GlobalList; +use wayland_client::protocol::wl_output::WlOutput; +use wayland_client::protocol::wl_surface::WlSurface; +use wayland_client::{delegate_dispatch, Connection, QueueHandle}; -use super::SurfaceUserData; +use super::{SurfaceUserData, WaylandWindowInner}; // We can't combine WaylandState and WaylandConnection together because // the run_message_loop has &self(WaylandConnection) and needs to update WaylandState as mut @@ -26,6 +26,7 @@ pub(super) struct WaylandState { pub(super) output: OutputState, pub(super) compositor: CompositorState, pub(super) xdg: XdgShell, + pub(super) windows: RefCell>>>, shm: Shm, pub(super) mem_pool: RefCell, @@ -39,6 +40,7 @@ impl WaylandState { registry: RegistryState::new(globals), output: OutputState::new(globals, qh), compositor: CompositorState::bind(globals, qh)?, + windows: RefCell::new(HashMap::new()), xdg: XdgShell::bind(globals, qh)?, shm, mem_pool: RefCell::new(mem_pool), diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index da6b31c3f76..06d181c0184 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -160,7 +160,10 @@ impl WaylandWindow { .events .assign_window(window_handle.clone()); - conn.windows.borrow_mut().insert(window_id, inner.clone()); + { + let windows = &conn.wayland_state.borrow().windows; + windows.borrow_mut().insert(window_id, inner.clone()); + }; wait_configure.recv().await?; @@ -584,13 +587,15 @@ impl WaylandWindowInner { } impl WaylandState { + pub(super) fn window_by_id(&self, window_id: usize) -> Option>> { + self.windows.borrow().get(&window_id).map(Rc::clone) + } + fn handle_window_event(&self, window: &XdgWindow, event: WaylandWindowEvent) { let surface_data = SurfaceUserData::from_wl(window.wl_surface()); let window_id = surface_data.window_id; - let wconn = WaylandConnection::get() - .expect("should be wayland connection") - .wayland(); - let window_inner = wconn + + let window_inner = self .window_by_id(window_id) .expect("Inner Window should exist"); From 85bb37b22b330ad8c0edf7ad49a1a09d87d370be Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Wed, 10 Jan 2024 03:47:18 -0500 Subject: [PATCH 29/55] Implement pressing a key --- Cargo.lock | 2 + window/Cargo.toml | 2 +- window/src/os/wayland/mod.rs | 1 + window/src/os/wayland/seat.rs | 196 ++++++++++++++++++++++++++++++++ window/src/os/wayland/state.rs | 31 ++++- window/src/os/wayland/window.rs | 137 ++++++++++++++++++++-- 6 files changed, 354 insertions(+), 15 deletions(-) create mode 100644 window/src/os/wayland/seat.rs diff --git a/Cargo.lock b/Cargo.lock index d8c56573628..5ada19ace1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5021,6 +5021,7 @@ dependencies = [ "log", "memmap2 0.5.10", "nix 0.26.4", + "pkg-config", "thiserror", "wayland-backend", "wayland-client", @@ -5028,6 +5029,7 @@ dependencies = [ "wayland-protocols", "wayland-protocols-wlr", "wayland-scanner", + "xkbcommon", ] [[package]] diff --git a/window/Cargo.toml b/window/Cargo.toml index 633ec7c2ae9..d864a3279dd 100644 --- a/window/Cargo.toml +++ b/window/Cargo.toml @@ -75,7 +75,7 @@ xcb = {version="1.2", features=["render", "randr", "dri2", "xkb", "xlib_xcb", "p xkbcommon = { version = "0.5.0", features = ["x11", "wayland"] } mio = {version="0.8", features=["os-ext"]} libc = "0.2" -smithay-client-toolkit = {version = "0.17.0", default-features=false, optional=true} +smithay-client-toolkit = {version = "0.17.0", features=["xkbcommon"], default-features=false, optional=true} wayland-protocols = {version="0.30", optional=true} wayland-client = {version="0.30", optional=true} wayland-egl = {version="0.30", optional=true} diff --git a/window/src/os/wayland/mod.rs b/window/src/os/wayland/mod.rs index 8f09023a75d..81f8c22f85e 100644 --- a/window/src/os/wayland/mod.rs +++ b/window/src/os/wayland/mod.rs @@ -12,6 +12,7 @@ pub use connection::*; // mod frame; // mod pointer; mod state; +mod seat; /// Returns the id of a wayland proxy object, suitable for using /// a key into hash maps diff --git a/window/src/os/wayland/seat.rs b/window/src/os/wayland/seat.rs new file mode 100644 index 00000000000..236d6027f3b --- /dev/null +++ b/window/src/os/wayland/seat.rs @@ -0,0 +1,196 @@ +use std::borrow::BorrowMut; +use std::sync::{Arc, Mutex}; +use std::time::Instant; + +use smithay_client_toolkit::seat::keyboard::{KeyEvent, KeyboardHandler}; +use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState}; +use wayland_client::protocol::wl_keyboard::WlKeyboard; +use wayland_client::protocol::wl_seat::WlSeat; +use wayland_client::protocol::wl_surface::WlSurface; +use wayland_client::{Connection, QueueHandle}; + +use super::state::WaylandState; +use super::KeyRepeatState; + +impl SeatHandler for WaylandState { + fn seat_state(&mut self) -> &mut SeatState { + &mut self.seat + } + + fn new_seat(&mut self, _conn: &Connection, _qh: &QueueHandle, _seat: WlSeat) { + todo!() + } + + fn new_capability( + &mut self, + _conn: &Connection, + qh: &QueueHandle, + seat: WlSeat, + capability: smithay_client_toolkit::seat::Capability, + ) { + if capability == Capability::Keyboard && self.keyboard.is_none() { + log::trace!("Setting keyboard capability"); + let keyboard = self + .seat + .get_keyboard(qh, &seat, None) + .expect("Failed to create keyboard"); + self.keyboard = Some(keyboard); + } + } + + fn remove_capability( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _seat: WlSeat, + _capability: smithay_client_toolkit::seat::Capability, + ) { + todo!() + } + + fn remove_seat(&mut self, _conn: &Connection, _qh: &QueueHandle, _seat: WlSeat) { + todo!() + } +} + +impl KeyboardHandler for WaylandState { + fn enter( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _keyboard: &WlKeyboard, + _surface: &WlSurface, + _serial: u32, + _raw: &[u32], + _keysyms: &[u32], + ) { + // *self.active_surface_id.borrow_mut() = Some(surface.id()); + // *self.last_serial.borrow_mut() = serial; + // if let Some(sud) = SurfaceUserData::try_from_wl(surface) { + // let window_id = sud.window_id; + // self.keyboard_window_id.borrow_mut().replace(window_id); + // // TODO: env with inner seems to IME stuff + // } else { + // log::warn!("{:?}, no known surface", "WlKeyboardEnter"); + // } + // + // if let Some(&window_id) = self.keyboard_window_id.borrow().as_ref() { + // // if let Some(win) = self. + // } + } + + fn leave( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _keyboard: &WlKeyboard, + _surface: &WlSurface, + serial: u32, + ) { + // TODO: inner input + *self.last_serial.borrow_mut() = serial; + } + + fn press_key( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _keyboard: &WlKeyboard, + serial: u32, + event: KeyEvent, + ) { + *self.last_serial.borrow_mut() = serial; + let Some(&window_id) = self.keyboard_window_id.borrow().as_ref() else { + return; + }; + let Some(mut win) = self.window_by_id(window_id) else { + return; + }; + let inner = win.borrow_mut(); + + let events = &mut inner.as_ref().borrow_mut().events; + let mapper = self.keyboard_mapper.borrow_mut(); + let mapper = mapper.as_mut().expect("no keymap"); + + // TODO: not sure if i should use keysym vs rawcode + let key = event.keysym; + let key_repeat = &mut inner.as_ref().borrow_mut().key_repeat; + if let Some(event) = mapper.process_wayland_key(key, true, events) { + let rep = Arc::new(Mutex::new(KeyRepeatState { + when: Instant::now(), + event, + })); + + let kp = &mut inner.as_ref().borrow_mut().key_repeat; + kp.replace((key, Arc::clone(&rep))); + + KeyRepeatState::schedule(rep, window_id); + } else if let Some((cur_key, _)) = key_repeat { + if *cur_key == key { + key_repeat.take(); + } + } + } + + fn release_key( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _keyboard: &WlKeyboard, + serial: u32, + event: KeyEvent, + ) { + // TODO: copy paste of press_key except process is false + *self.last_serial.borrow_mut() = serial; + let Some(&window_id) = self.keyboard_window_id.borrow().as_ref() else { + return; + }; + let Some(mut win) = self.window_by_id(window_id) else { + return; + }; + let inner = win.borrow_mut(); + + let events = &mut inner.as_ref().borrow_mut().events; + let mapper = self.keyboard_mapper.borrow_mut(); + let mapper = mapper.as_mut().expect("no keymap"); + + // TODO: not sure if i should use keysym vs rawcode + let key = event.keysym; + let key_repeat = &mut inner.as_ref().borrow_mut().key_repeat; + if let Some(event) = mapper.process_wayland_key(key, false, events) { + let rep = Arc::new(Mutex::new(KeyRepeatState { + when: Instant::now(), + event, + })); + + let kp = &mut inner.as_ref().borrow_mut().key_repeat; + kp.replace((key, Arc::clone(&rep))); + + KeyRepeatState::schedule(rep, window_id); + } else if let Some((cur_key, _)) = key_repeat { + if *cur_key == key { + key_repeat.take(); + } + } + } + + fn update_modifiers( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _keyboard: &WlKeyboard, + serial: u32, + _modifiers: smithay_client_toolkit::seat::keyboard::Modifiers, + ) { + *self.last_serial.borrow_mut() = serial; + } + + fn update_keymap( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard, + _keymap: smithay_client_toolkit::seat::keyboard::Keymap<'_>, + ) { + } +} diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index 06a93b9623b..5748214866b 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -5,18 +5,23 @@ use std::rc::Rc; use smithay_client_toolkit::compositor::CompositorState; use smithay_client_toolkit::output::{OutputHandler, OutputState}; use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState}; +use smithay_client_toolkit::seat::SeatState; use smithay_client_toolkit::shell::xdg::XdgShell; use smithay_client_toolkit::shm::slot::SlotPool; use smithay_client_toolkit::shm::{Shm, ShmHandler}; use smithay_client_toolkit::{ - delegate_compositor, delegate_output, delegate_registry, delegate_shm, delegate_xdg_shell, - delegate_xdg_window, registry_handlers, + delegate_compositor, delegate_keyboard, delegate_output, delegate_registry, delegate_seat, + delegate_shm, delegate_xdg_shell, delegate_xdg_window, registry_handlers, }; +use wayland_client::backend::ObjectId; use wayland_client::globals::GlobalList; +use wayland_client::protocol::wl_keyboard::WlKeyboard; use wayland_client::protocol::wl_output::WlOutput; use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::{delegate_dispatch, Connection, QueueHandle}; +use crate::x11::KeyboardWithFallback; + use super::{SurfaceUserData, WaylandWindowInner}; // We can't combine WaylandState and WaylandConnection together because @@ -25,9 +30,18 @@ pub(super) struct WaylandState { registry: RegistryState, pub(super) output: OutputState, pub(super) compositor: CompositorState, + pub(super) seat: SeatState, pub(super) xdg: XdgShell, pub(super) windows: RefCell>>>, + pub(super) active_surface_id: RefCell>, + pub(super) last_serial: RefCell, + pub(super) keyboard: Option, + pub(super) keyboard_mapper: Option, + pub(super) key_repeat_delay: i32, + pub(super) key_repeat_rate: i32, + pub(super) keyboard_window_id: RefCell>, + shm: Shm, pub(super) mem_pool: RefCell, } @@ -41,7 +55,15 @@ impl WaylandState { output: OutputState::new(globals, qh), compositor: CompositorState::bind(globals, qh)?, windows: RefCell::new(HashMap::new()), + seat: SeatState::new(globals, qh), xdg: XdgShell::bind(globals, qh)?, + active_surface_id: RefCell::new(None), + last_serial: RefCell::new(0), + keyboard: None, + keyboard_mapper: None, + key_repeat_rate: 25, + key_repeat_delay: 400, + keyboard_window_id: RefCell::new(None), shm, mem_pool: RefCell::new(mem_pool), }; @@ -54,7 +76,7 @@ impl ProvidesRegistryState for WaylandState { &mut self.registry } - registry_handlers!(OutputState); + registry_handlers![OutputState, SeatState]; } impl ShmHandler for WaylandState { @@ -93,5 +115,8 @@ delegate_shm!(WaylandState); delegate_output!(WaylandState); delegate_compositor!(WaylandState); +delegate_seat!(WaylandState); +delegate_keyboard!(WaylandState); + delegate_xdg_shell!(WaylandState); delegate_xdg_window!(WaylandState); diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 06d181c0184..1c423fc0d36 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -3,8 +3,10 @@ use std::cell::RefCell; use std::convert::TryInto; use std::rc::Rc; use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; use anyhow::anyhow; +use async_io::Timer; use async_trait::async_trait; use config::ConfigHandle; use promise::Future; @@ -23,16 +25,107 @@ use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::{Connection as WConnection, Proxy}; use wayland_egl::{is_available as egl_is_available, WlEglSurface}; use wezterm_font::FontConfiguration; -use wezterm_input_types::WindowDecorations; +use wezterm_input_types::{Modifiers, WindowDecorations}; use crate::wayland::WaylandConnection; use crate::{ - Clipboard, Connection, ConnectionOps, Dimensions, MouseCursor, RequestedWindowGeometry, - ResolvedGeometry, Window, WindowEvent, WindowEventSender, WindowOps, WindowState, + Clipboard, Connection, ConnectionOps, Dimensions, MouseCursor, Rect, RequestedWindowGeometry, + ResolvedGeometry, Window, WindowEvent, WindowEventSender, WindowKeyEvent, WindowOps, + WindowState, }; use super::state::WaylandState; +#[derive(Debug)] +pub(super) struct KeyRepeatState { + pub(super) when: Instant, + pub(super) event: WindowKeyEvent, +} + +impl KeyRepeatState { + pub(super) fn schedule(state: Arc>, window_id: usize) { + promise::spawn::spawn_into_main_thread(async move { + let delay; + let gap; + { + let conn = WaylandConnection::get().unwrap().wayland(); + let (rate, ddelay) = { + let wstate = conn.wayland_state.borrow(); + ( + wstate.key_repeat_rate as u64, + wstate.key_repeat_delay as u64, + ) + }; + if rate == 0 { + return; + } + delay = Duration::from_millis(ddelay); + gap = Duration::from_millis(1000 / rate); + } + + let mut initial = true; + Timer::after(delay).await; + loop { + { + let handle = { + let conn = WaylandConnection::get().unwrap().wayland(); + match conn.window_by_id(window_id) { + Some(handle) => handle, + None => return, + } + }; + + let mut inner = handle.borrow_mut(); + + if inner.key_repeat.as_ref().map(|(_, k)| Arc::as_ptr(k)) + != Some(Arc::as_ptr(&state)) + { + // Key was released and/or some other key is doing + // its own repetition now + return; + } + + let mut st = state.lock().unwrap(); + + let mut repeat_count = 1; + + let mut elapsed = st.when.elapsed(); + if initial { + elapsed -= delay; + initial = false; + } + + // If our scheduling interval is longer than the repeat + // gap, we need to inflate the repeat count to match + // the intended rate + while elapsed >= gap { + repeat_count += 1; + elapsed -= gap; + } + + let event = match st.event.clone() { + WindowKeyEvent::KeyEvent(mut key) => { + key.repeat_count = repeat_count; + WindowEvent::KeyEvent(key) + } + WindowKeyEvent::RawKeyEvent(mut raw) => { + raw.repeat_count = repeat_count; + WindowEvent::RawKeyEvent(raw) + } + }; + + inner.events.dispatch(event); + + st.when = Instant::now(); + } + + Timer::after(gap).await; + } + }) + .detach(); + } +} + enum WaylandWindowEvent { Close, Request(WindowConfigure), @@ -140,11 +233,16 @@ impl WaylandWindow { resize_increments: None, window_state: WindowState::default(), + _modifiers: Modifiers::NONE, + + key_repeat: None, pending_event, pending_first_configure: Some(pending_first_configure), frame_callback: None, + _text_cursor: None, + config, title: None, @@ -271,24 +369,24 @@ pub struct WaylandWindowInner { // mouse_buttons: MouseButtons, // hscroll_remainder: f64, // vscroll_remainder: f64, - // modifiers: Modifiers, + _modifiers: Modifiers, // leds: KeyboardLedStatus, - // key_repeat: Option<(u32, Arc>)>, + pub(super) key_repeat: Option<(u32, Arc>)>, pub(crate) pending_event: Arc>, // pending_mouse: Arc>, pending_first_configure: Option>, frame_callback: Option, invalidated: bool, // font_config: Rc, - // text_cursor: Option, + _text_cursor: Option, // appearance: Appearance, config: ConfigHandle, // // cache the title for comparison to avoid spamming // // the compositor with updates that don't actually change it title: Option, - // // wegl_surface is listed before gl_state because it - // // must be dropped before gl_state otherwise the underlying - // // libraries will segfault on shutdown + // wegl_surface is listed before gl_state because it + // must be dropped before gl_state otherwise the underlying + // libraries will segfault on shutdown wegl_surface: Option, gl_state: Option>, } @@ -584,6 +682,20 @@ impl WaylandWindowInner { self.do_paint().ok(); } } + + // pub(crate) fn emit_focus(&mut self, mapper: &mut KeyboardWithFallback, focused: bool) { + // // Clear the modifiers when we change focus, otherwise weird + // // things can happen. For instance, if we lost focus because + // // CTRL+SHIFT+N was pressed to spawn a new window, we'd be + // // left stuck with CTRL+SHIFT held down and the window would + // // be left in a broken state. + // + // self.modifiers = Modifiers::NONE; + // mapper.update_modifier_state(0, 0, 0, 0); + // self.key_repeat.take(); + // self.events.dispatch(WindowEvent::FocusChanged(focused)); + // self.text_cursor.take(); + // } } impl WaylandState { @@ -716,14 +828,17 @@ impl WindowHandler for WaylandState { pub(super) struct SurfaceUserData { surface_data: SurfaceData, - window_id: usize, + pub(super) window_id: usize, } impl SurfaceUserData { - pub(crate) fn from_wl(wl: &WlSurface) -> &Self { + pub(super) fn from_wl(wl: &WlSurface) -> &Self { wl.data() .expect("User data should be associated with WlSurface") } + pub(super) fn try_from_wl(wl: &WlSurface) -> Option<&SurfaceUserData> { + wl.data() + } } impl SurfaceDataExt for SurfaceUserData { From 3e55fe681da2dbfcb9b5e1c89bdab57ea847eced Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Wed, 10 Jan 2024 04:17:53 -0500 Subject: [PATCH 30/55] Implement enter + emit_focus -> need to impl keymap --- window/src/os/wayland/seat.rs | 53 ++++++++++++++++++++------------- window/src/os/wayland/state.rs | 4 +-- window/src/os/wayland/window.rs | 35 +++++++++++----------- 3 files changed, 53 insertions(+), 39 deletions(-) diff --git a/window/src/os/wayland/seat.rs b/window/src/os/wayland/seat.rs index 236d6027f3b..b36199a5451 100644 --- a/window/src/os/wayland/seat.rs +++ b/window/src/os/wayland/seat.rs @@ -7,10 +7,10 @@ use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState}; use wayland_client::protocol::wl_keyboard::WlKeyboard; use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::protocol::wl_surface::WlSurface; -use wayland_client::{Connection, QueueHandle}; +use wayland_client::{Connection, Proxy, QueueHandle}; use super::state::WaylandState; -use super::KeyRepeatState; +use super::{KeyRepeatState, SurfaceUserData}; impl SeatHandler for WaylandState { fn seat_state(&mut self) -> &mut SeatState { @@ -59,24 +59,36 @@ impl KeyboardHandler for WaylandState { _conn: &Connection, _qh: &QueueHandle, _keyboard: &WlKeyboard, - _surface: &WlSurface, - _serial: u32, + surface: &WlSurface, + serial: u32, _raw: &[u32], - _keysyms: &[u32], + keysyms: &[u32], ) { - // *self.active_surface_id.borrow_mut() = Some(surface.id()); - // *self.last_serial.borrow_mut() = serial; - // if let Some(sud) = SurfaceUserData::try_from_wl(surface) { - // let window_id = sud.window_id; - // self.keyboard_window_id.borrow_mut().replace(window_id); - // // TODO: env with inner seems to IME stuff - // } else { - // log::warn!("{:?}, no known surface", "WlKeyboardEnter"); - // } - // - // if let Some(&window_id) = self.keyboard_window_id.borrow().as_ref() { - // // if let Some(win) = self. - // } + *self.active_surface_id.borrow_mut() = Some(surface.id()); + *self.last_serial.borrow_mut() = serial; + if let Some(sud) = SurfaceUserData::try_from_wl(surface) { + let window_id = sud.window_id; + self.keyboard_window_id.borrow_mut().replace(window_id); + // TODO: env with inner seems to IME stuff + } else { + log::warn!("{:?}, no known surface", "WlKeyboardEnter"); + } + + let Some(&window_id) = self.keyboard_window_id.as_ref() else { + return; + }; + let Some(mut win) = self.window_by_id(window_id) else { + return; + }; + + // TODO: not sure if this is correct; is it keycodes? + log::trace!("keyboard event: Enter with keys: {:?}", keysyms); + + let inner = win.borrow_mut(); + let mapper = self.keyboard_mapper.borrow_mut(); + let mapper = mapper.as_mut().expect("no keymap"); + + inner.as_ref().borrow_mut().emit_focus(mapper, true); } fn leave( @@ -100,7 +112,7 @@ impl KeyboardHandler for WaylandState { event: KeyEvent, ) { *self.last_serial.borrow_mut() = serial; - let Some(&window_id) = self.keyboard_window_id.borrow().as_ref() else { + let Some(&window_id) = self.keyboard_window_id.as_ref() else { return; }; let Some(mut win) = self.window_by_id(window_id) else { @@ -142,7 +154,7 @@ impl KeyboardHandler for WaylandState { ) { // TODO: copy paste of press_key except process is false *self.last_serial.borrow_mut() = serial; - let Some(&window_id) = self.keyboard_window_id.borrow().as_ref() else { + let Some(&window_id) = self.keyboard_window_id.as_ref() else { return; }; let Some(mut win) = self.window_by_id(window_id) else { @@ -192,5 +204,6 @@ impl KeyboardHandler for WaylandState { _keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard, _keymap: smithay_client_toolkit::seat::keyboard::Keymap<'_>, ) { + todo!() } } diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index 5748214866b..b1c046457cf 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -40,7 +40,7 @@ pub(super) struct WaylandState { pub(super) keyboard_mapper: Option, pub(super) key_repeat_delay: i32, pub(super) key_repeat_rate: i32, - pub(super) keyboard_window_id: RefCell>, + pub(super) keyboard_window_id: Option, shm: Shm, pub(super) mem_pool: RefCell, @@ -63,7 +63,7 @@ impl WaylandState { keyboard_mapper: None, key_repeat_rate: 25, key_repeat_delay: 400, - keyboard_window_id: RefCell::new(None), + keyboard_window_id: None, shm, mem_pool: RefCell::new(mem_pool), }; diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 1c423fc0d36..e20d142043c 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -28,6 +28,7 @@ use wezterm_font::FontConfiguration; use wezterm_input_types::{Modifiers, WindowDecorations}; use crate::wayland::WaylandConnection; +use crate::x11::KeyboardWithFallback; use crate::{ Clipboard, Connection, ConnectionOps, Dimensions, MouseCursor, Rect, RequestedWindowGeometry, ResolvedGeometry, Window, WindowEvent, WindowEventSender, WindowKeyEvent, WindowOps, @@ -233,7 +234,7 @@ impl WaylandWindow { resize_increments: None, window_state: WindowState::default(), - _modifiers: Modifiers::NONE, + modifiers: Modifiers::NONE, key_repeat: None, pending_event, @@ -241,7 +242,7 @@ impl WaylandWindow { pending_first_configure: Some(pending_first_configure), frame_callback: None, - _text_cursor: None, + text_cursor: None, config, @@ -369,7 +370,7 @@ pub struct WaylandWindowInner { // mouse_buttons: MouseButtons, // hscroll_remainder: f64, // vscroll_remainder: f64, - _modifiers: Modifiers, + modifiers: Modifiers, // leds: KeyboardLedStatus, pub(super) key_repeat: Option<(u32, Arc>)>, pub(crate) pending_event: Arc>, @@ -378,7 +379,7 @@ pub struct WaylandWindowInner { frame_callback: Option, invalidated: bool, // font_config: Rc, - _text_cursor: Option, + text_cursor: Option, // appearance: Appearance, config: ConfigHandle, // // cache the title for comparison to avoid spamming @@ -683,19 +684,19 @@ impl WaylandWindowInner { } } - // pub(crate) fn emit_focus(&mut self, mapper: &mut KeyboardWithFallback, focused: bool) { - // // Clear the modifiers when we change focus, otherwise weird - // // things can happen. For instance, if we lost focus because - // // CTRL+SHIFT+N was pressed to spawn a new window, we'd be - // // left stuck with CTRL+SHIFT held down and the window would - // // be left in a broken state. - // - // self.modifiers = Modifiers::NONE; - // mapper.update_modifier_state(0, 0, 0, 0); - // self.key_repeat.take(); - // self.events.dispatch(WindowEvent::FocusChanged(focused)); - // self.text_cursor.take(); - // } + pub(crate) fn emit_focus(&mut self, mapper: &mut KeyboardWithFallback, focused: bool) { + // Clear the modifiers when we change focus, otherwise weird + // things can happen. For instance, if we lost focus because + // CTRL+SHIFT+N was pressed to spawn a new window, we'd be + // left stuck with CTRL+SHIFT held down and the window would + // be left in a broken state. + + self.modifiers = Modifiers::NONE; + mapper.update_modifier_state(0, 0, 0, 0); + self.key_repeat.take(); + self.events.dispatch(WindowEvent::FocusChanged(focused)); + self.text_cursor.take(); + } } impl WaylandState { From c278e4d4a34d3c93d705e7544047b10c127f33b7 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:06:23 -0500 Subject: [PATCH 31/55] Implement adding keymap --- window/src/os/wayland/seat.rs | 51 +++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/window/src/os/wayland/seat.rs b/window/src/os/wayland/seat.rs index b36199a5451..1965e28a086 100644 --- a/window/src/os/wayland/seat.rs +++ b/window/src/os/wayland/seat.rs @@ -1,14 +1,17 @@ use std::borrow::BorrowMut; +use std::cell::RefMut; use std::sync::{Arc, Mutex}; use std::time::Instant; -use smithay_client_toolkit::seat::keyboard::{KeyEvent, KeyboardHandler}; +use smithay_client_toolkit::seat::keyboard::{KeyEvent, KeyboardHandler, Keymap}; use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState}; use wayland_client::protocol::wl_keyboard::WlKeyboard; use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::{Connection, Proxy, QueueHandle}; +use crate::x11::KeyboardWithFallback; + use super::state::WaylandState; use super::{KeyRepeatState, SurfaceUserData}; @@ -115,29 +118,29 @@ impl KeyboardHandler for WaylandState { let Some(&window_id) = self.keyboard_window_id.as_ref() else { return; }; - let Some(mut win) = self.window_by_id(window_id) else { + let Some(win) = self.window_by_id(window_id) else { return; }; - let inner = win.borrow_mut(); - let events = &mut inner.as_ref().borrow_mut().events; + let inner = win.as_ref().borrow_mut(); + let (mut events, mut key_repeat) = + RefMut::map_split(inner, |w| (&mut w.events, &mut w.key_repeat)); + let mapper = self.keyboard_mapper.borrow_mut(); let mapper = mapper.as_mut().expect("no keymap"); // TODO: not sure if i should use keysym vs rawcode let key = event.keysym; - let key_repeat = &mut inner.as_ref().borrow_mut().key_repeat; - if let Some(event) = mapper.process_wayland_key(key, true, events) { + if let Some(event) = mapper.process_wayland_key(key, true, &mut events) { let rep = Arc::new(Mutex::new(KeyRepeatState { when: Instant::now(), event, })); - let kp = &mut inner.as_ref().borrow_mut().key_repeat; - kp.replace((key, Arc::clone(&rep))); + key_repeat.replace((key, Arc::clone(&rep))); KeyRepeatState::schedule(rep, window_id); - } else if let Some((cur_key, _)) = key_repeat { + } else if let Some((cur_key, _)) = key_repeat.as_mut() { if *cur_key == key { key_repeat.take(); } @@ -157,29 +160,29 @@ impl KeyboardHandler for WaylandState { let Some(&window_id) = self.keyboard_window_id.as_ref() else { return; }; - let Some(mut win) = self.window_by_id(window_id) else { + let Some(win) = self.window_by_id(window_id) else { return; }; - let inner = win.borrow_mut(); - let events = &mut inner.as_ref().borrow_mut().events; + let inner = win.as_ref().borrow_mut(); + let (mut events, mut key_repeat) = + RefMut::map_split(inner, |w| (&mut w.events, &mut w.key_repeat)); + let mapper = self.keyboard_mapper.borrow_mut(); let mapper = mapper.as_mut().expect("no keymap"); // TODO: not sure if i should use keysym vs rawcode let key = event.keysym; - let key_repeat = &mut inner.as_ref().borrow_mut().key_repeat; - if let Some(event) = mapper.process_wayland_key(key, false, events) { + if let Some(event) = mapper.process_wayland_key(key, false, &mut events) { let rep = Arc::new(Mutex::new(KeyRepeatState { when: Instant::now(), event, })); - let kp = &mut inner.as_ref().borrow_mut().key_repeat; - kp.replace((key, Arc::clone(&rep))); + key_repeat.replace((key, Arc::clone(&rep))); KeyRepeatState::schedule(rep, window_id); - } else if let Some((cur_key, _)) = key_repeat { + } else if let Some((cur_key, _)) = key_repeat.as_mut() { if *cur_key == key { key_repeat.take(); } @@ -201,9 +204,17 @@ impl KeyboardHandler for WaylandState { &mut self, _conn: &Connection, _qh: &QueueHandle, - _keyboard: &wayland_client::protocol::wl_keyboard::WlKeyboard, - _keymap: smithay_client_toolkit::seat::keyboard::Keymap<'_>, + _keyboard: &WlKeyboard, + keymap: Keymap<'_>, ) { - todo!() + let keymap_str = keymap.as_string(); + match KeyboardWithFallback::new_from_string(keymap_str) { + Ok(k) => { + self.keyboard_mapper.replace(k); + } + Err(err) => { + log::error!("Error processing keymap change: {:#}", err); + } + } } } From 3b693a9caed5040e014a61f504dd7909227131eb Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:15:34 -0500 Subject: [PATCH 32/55] Ignore cursor handling for now --- window/src/os/wayland/window.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index e20d142043c..a6ef3a55e0a 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -315,8 +315,11 @@ impl WindowOps for WaylandWindow { }); } - fn set_cursor(&self, _cursor: Option) { - todo!() + fn set_cursor(&self, cursor: Option) { + WaylandConnection::with_window_inner(self.0, move |inner| { + inner.set_cursor(cursor); + Ok(()) + }); } fn invalidate(&self) { @@ -617,6 +620,20 @@ impl WaylandWindowInner { } } + fn set_cursor(&mut self, cursor: Option) { + // TODO: Deal with cursors later + let _names: &[&str] = match cursor { + Some(MouseCursor::Arrow) => &["arrow"], + Some(MouseCursor::Hand) => &["hand"], + Some(MouseCursor::SizeUpDown) => &["ns-resize"], + Some(MouseCursor::SizeLeftRight) => &["ew-resize"], + Some(MouseCursor::Text) => &["xterm"], + None => &[], + }; + // let conn = Connection::get().unwrap().wayland(); + // conn.pointer.borrow().set_cursor(names, None); + } + fn invalidate(&mut self) { if self.frame_callback.is_some() { self.invalidated = true; From 96bda97ed22c5f1019c43c3b8ea1f4c19e1a9a27 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:23:28 -0500 Subject: [PATCH 33/55] Use raw_code for process_wayland_key instead of keysym --- window/src/os/wayland/seat.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/window/src/os/wayland/seat.rs b/window/src/os/wayland/seat.rs index 1965e28a086..3808ae90d8e 100644 --- a/window/src/os/wayland/seat.rs +++ b/window/src/os/wayland/seat.rs @@ -64,7 +64,7 @@ impl KeyboardHandler for WaylandState { _keyboard: &WlKeyboard, surface: &WlSurface, serial: u32, - _raw: &[u32], + raw: &[u32], keysyms: &[u32], ) { *self.active_surface_id.borrow_mut() = Some(surface.id()); @@ -85,7 +85,11 @@ impl KeyboardHandler for WaylandState { }; // TODO: not sure if this is correct; is it keycodes? - log::trace!("keyboard event: Enter with keys: {:?}", keysyms); + log::trace!( + "keyboard event: Enter with keysyms: {:?}, raw: {:?}", + keysyms, + raw + ); let inner = win.borrow_mut(); let mapper = self.keyboard_mapper.borrow_mut(); @@ -129,8 +133,7 @@ impl KeyboardHandler for WaylandState { let mapper = self.keyboard_mapper.borrow_mut(); let mapper = mapper.as_mut().expect("no keymap"); - // TODO: not sure if i should use keysym vs rawcode - let key = event.keysym; + let key = event.raw_code; if let Some(event) = mapper.process_wayland_key(key, true, &mut events) { let rep = Arc::new(Mutex::new(KeyRepeatState { when: Instant::now(), @@ -171,8 +174,7 @@ impl KeyboardHandler for WaylandState { let mapper = self.keyboard_mapper.borrow_mut(); let mapper = mapper.as_mut().expect("no keymap"); - // TODO: not sure if i should use keysym vs rawcode - let key = event.keysym; + let key = event.raw_code; if let Some(event) = mapper.process_wayland_key(key, false, &mut events) { let rep = Arc::new(Mutex::new(KeyRepeatState { when: Instant::now(), From 6f24a6cea8da9b29925750fe05c8cea52d3dc2b3 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:34:49 -0500 Subject: [PATCH 34/55] Don't use xkbcommon feature in smithay so we can use modifiers --- Cargo.lock | 2 - window/Cargo.toml | 2 +- window/src/os/wayland/keyboard.rs | 107 +++++++++++++++++ window/src/os/wayland/mod.rs | 3 +- window/src/os/wayland/seat.rs | 185 +----------------------------- window/src/os/wayland/state.rs | 5 +- window/src/os/wayland/window.rs | 76 +++++++++++- 7 files changed, 190 insertions(+), 190 deletions(-) create mode 100644 window/src/os/wayland/keyboard.rs diff --git a/Cargo.lock b/Cargo.lock index 5ada19ace1c..d8c56573628 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5021,7 +5021,6 @@ dependencies = [ "log", "memmap2 0.5.10", "nix 0.26.4", - "pkg-config", "thiserror", "wayland-backend", "wayland-client", @@ -5029,7 +5028,6 @@ dependencies = [ "wayland-protocols", "wayland-protocols-wlr", "wayland-scanner", - "xkbcommon", ] [[package]] diff --git a/window/Cargo.toml b/window/Cargo.toml index d864a3279dd..633ec7c2ae9 100644 --- a/window/Cargo.toml +++ b/window/Cargo.toml @@ -75,7 +75,7 @@ xcb = {version="1.2", features=["render", "randr", "dri2", "xkb", "xlib_xcb", "p xkbcommon = { version = "0.5.0", features = ["x11", "wayland"] } mio = {version="0.8", features=["os-ext"]} libc = "0.2" -smithay-client-toolkit = {version = "0.17.0", features=["xkbcommon"], default-features=false, optional=true} +smithay-client-toolkit = {version = "0.17.0", default-features=false, optional=true} wayland-protocols = {version="0.30", optional=true} wayland-client = {version="0.30", optional=true} wayland-egl = {version="0.30", optional=true} diff --git a/window/src/os/wayland/keyboard.rs b/window/src/os/wayland/keyboard.rs new file mode 100644 index 00000000000..e4324c2b032 --- /dev/null +++ b/window/src/os/wayland/keyboard.rs @@ -0,0 +1,107 @@ +use std::borrow::BorrowMut; +use std::io::Read; +use std::os::fd::{AsRawFd, FromRawFd}; +use std::os::unix::fs::FileExt; + +use wayland_client::protocol::wl_keyboard::{Event as WlKeyboardEvent, KeymapFormat, WlKeyboard}; +use wayland_client::{Dispatch, Proxy}; + +use crate::x11::KeyboardWithFallback; + +use super::state::WaylandState; +use super::SurfaceUserData; + +// We can't use the xkbcommon feature because it is too abstract for us +impl Dispatch for WaylandState { + fn event( + state: &mut WaylandState, + _proxy: &WlKeyboard, + event: ::Event, + _data: &KeyboardData, + _conn: &wayland_client::Connection, + _qhandle: &wayland_client::QueueHandle, + ) { + log::trace!("We reached an event here: {:?}???", event); + match &event { + WlKeyboardEvent::Enter { + serial, surface, .. + } => { + *state.active_surface_id.borrow_mut() = Some(surface.id()); + *state.last_serial.borrow_mut() = *serial; + if let Some(sud) = SurfaceUserData::try_from_wl(&surface) { + let window_id = sud.window_id; + state.keyboard_window_id.borrow_mut().replace(window_id); + } else { + log::warn!("{:?}, no known surface", event); + } + } + WlKeyboardEvent::Leave { serial, .. } => { + // TODO: I know nothing about the input handler currently + *state.last_serial.borrow_mut() = *serial; + } + WlKeyboardEvent::Key { serial, .. } | WlKeyboardEvent::Modifiers { serial, .. } => { + *state.last_serial.borrow_mut() = *serial; + } + WlKeyboardEvent::RepeatInfo { rate, delay } => { + *state.key_repeat_rate.borrow_mut() = *rate; + *state.key_repeat_delay.borrow_mut() = *delay; + } + WlKeyboardEvent::Keymap { format, fd, size } => { + let mut file = unsafe { std::fs::File::from_raw_fd(fd.as_raw_fd()) }; + match format.into_result().unwrap() { + KeymapFormat::XkbV1 => { + let mut data = vec![0u8; *size as usize]; + // If we weren't passed a pipe, be sure to explicitly + // read from the start of the file + match file.read_exact_at(&mut data, 0) { + Ok(_) => {} + Err(err) => { + // ideally: we check for: + // err.kind() == std::io::ErrorKind::NotSeekable + // but that is not yet in stable rust + if err.raw_os_error() == Some(libc::ESPIPE) { + // It's a pipe, which cannot be seeked, so we + // just try reading from the current pipe position + file.read(&mut data).expect("read from Keymap fd/pipe"); + } else { + return Err(err).expect("read_exact_at from Keymap fd"); + } + } + } + // Dance around CString panicing on the NUL terminator + // in the xkbcommon crate + while let Some(0) = data.last() { + data.pop(); + } + let s = String::from_utf8(data).expect("Failed to read string from data"); + match KeyboardWithFallback::new_from_string(s) { + Ok(k) => { + state.keyboard_mapper.replace(k); + } + Err(err) => { + log::error!("Error processing keymap change: {:#}", err); + } + } + } + _ => {} + } + } + _ => { + unimplemented!() + } + } + + let Some(&window_id) = state.keyboard_window_id.as_ref() else { + return; + }; + let Some(win) = state.window_by_id(window_id) else { + return; + }; + let mut inner = win.as_ref().borrow_mut(); + let mapper = state.keyboard_mapper.borrow_mut(); + let mapper = mapper.as_mut().expect("no keymap"); + inner.keyboard_event(mapper, event); + } +} + +pub(super) struct KeyboardData {} diff --git a/window/src/os/wayland/mod.rs b/window/src/os/wayland/mod.rs index 81f8c22f85e..ac2f720cd5a 100644 --- a/window/src/os/wayland/mod.rs +++ b/window/src/os/wayland/mod.rs @@ -11,8 +11,9 @@ pub use connection::*; // mod drag_and_drop; // mod frame; // mod pointer; -mod state; +mod keyboard; mod seat; +mod state; /// Returns the id of a wayland proxy object, suitable for using /// a key into hash maps diff --git a/window/src/os/wayland/seat.rs b/window/src/os/wayland/seat.rs index 3808ae90d8e..5df21176cf5 100644 --- a/window/src/os/wayland/seat.rs +++ b/window/src/os/wayland/seat.rs @@ -1,19 +1,10 @@ -use std::borrow::BorrowMut; -use std::cell::RefMut; -use std::sync::{Arc, Mutex}; -use std::time::Instant; - -use smithay_client_toolkit::seat::keyboard::{KeyEvent, KeyboardHandler, Keymap}; use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState}; -use wayland_client::protocol::wl_keyboard::WlKeyboard; use wayland_client::protocol::wl_seat::WlSeat; -use wayland_client::protocol::wl_surface::WlSurface; -use wayland_client::{Connection, Proxy, QueueHandle}; +use wayland_client::{Connection, QueueHandle}; -use crate::x11::KeyboardWithFallback; +use crate::wayland::keyboard::KeyboardData; use super::state::WaylandState; -use super::{KeyRepeatState, SurfaceUserData}; impl SeatHandler for WaylandState { fn seat_state(&mut self) -> &mut SeatState { @@ -33,11 +24,8 @@ impl SeatHandler for WaylandState { ) { if capability == Capability::Keyboard && self.keyboard.is_none() { log::trace!("Setting keyboard capability"); - let keyboard = self - .seat - .get_keyboard(qh, &seat, None) - .expect("Failed to create keyboard"); - self.keyboard = Some(keyboard); + let keyboard = seat.get_keyboard(qh, KeyboardData {}); + self.keyboard = Some(keyboard) } } @@ -55,168 +43,3 @@ impl SeatHandler for WaylandState { todo!() } } - -impl KeyboardHandler for WaylandState { - fn enter( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - _keyboard: &WlKeyboard, - surface: &WlSurface, - serial: u32, - raw: &[u32], - keysyms: &[u32], - ) { - *self.active_surface_id.borrow_mut() = Some(surface.id()); - *self.last_serial.borrow_mut() = serial; - if let Some(sud) = SurfaceUserData::try_from_wl(surface) { - let window_id = sud.window_id; - self.keyboard_window_id.borrow_mut().replace(window_id); - // TODO: env with inner seems to IME stuff - } else { - log::warn!("{:?}, no known surface", "WlKeyboardEnter"); - } - - let Some(&window_id) = self.keyboard_window_id.as_ref() else { - return; - }; - let Some(mut win) = self.window_by_id(window_id) else { - return; - }; - - // TODO: not sure if this is correct; is it keycodes? - log::trace!( - "keyboard event: Enter with keysyms: {:?}, raw: {:?}", - keysyms, - raw - ); - - let inner = win.borrow_mut(); - let mapper = self.keyboard_mapper.borrow_mut(); - let mapper = mapper.as_mut().expect("no keymap"); - - inner.as_ref().borrow_mut().emit_focus(mapper, true); - } - - fn leave( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - _keyboard: &WlKeyboard, - _surface: &WlSurface, - serial: u32, - ) { - // TODO: inner input - *self.last_serial.borrow_mut() = serial; - } - - fn press_key( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - _keyboard: &WlKeyboard, - serial: u32, - event: KeyEvent, - ) { - *self.last_serial.borrow_mut() = serial; - let Some(&window_id) = self.keyboard_window_id.as_ref() else { - return; - }; - let Some(win) = self.window_by_id(window_id) else { - return; - }; - - let inner = win.as_ref().borrow_mut(); - let (mut events, mut key_repeat) = - RefMut::map_split(inner, |w| (&mut w.events, &mut w.key_repeat)); - - let mapper = self.keyboard_mapper.borrow_mut(); - let mapper = mapper.as_mut().expect("no keymap"); - - let key = event.raw_code; - if let Some(event) = mapper.process_wayland_key(key, true, &mut events) { - let rep = Arc::new(Mutex::new(KeyRepeatState { - when: Instant::now(), - event, - })); - - key_repeat.replace((key, Arc::clone(&rep))); - - KeyRepeatState::schedule(rep, window_id); - } else if let Some((cur_key, _)) = key_repeat.as_mut() { - if *cur_key == key { - key_repeat.take(); - } - } - } - - fn release_key( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - _keyboard: &WlKeyboard, - serial: u32, - event: KeyEvent, - ) { - // TODO: copy paste of press_key except process is false - *self.last_serial.borrow_mut() = serial; - let Some(&window_id) = self.keyboard_window_id.as_ref() else { - return; - }; - let Some(win) = self.window_by_id(window_id) else { - return; - }; - - let inner = win.as_ref().borrow_mut(); - let (mut events, mut key_repeat) = - RefMut::map_split(inner, |w| (&mut w.events, &mut w.key_repeat)); - - let mapper = self.keyboard_mapper.borrow_mut(); - let mapper = mapper.as_mut().expect("no keymap"); - - let key = event.raw_code; - if let Some(event) = mapper.process_wayland_key(key, false, &mut events) { - let rep = Arc::new(Mutex::new(KeyRepeatState { - when: Instant::now(), - event, - })); - - key_repeat.replace((key, Arc::clone(&rep))); - - KeyRepeatState::schedule(rep, window_id); - } else if let Some((cur_key, _)) = key_repeat.as_mut() { - if *cur_key == key { - key_repeat.take(); - } - } - } - - fn update_modifiers( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - _keyboard: &WlKeyboard, - serial: u32, - _modifiers: smithay_client_toolkit::seat::keyboard::Modifiers, - ) { - *self.last_serial.borrow_mut() = serial; - } - - fn update_keymap( - &mut self, - _conn: &Connection, - _qh: &QueueHandle, - _keyboard: &WlKeyboard, - keymap: Keymap<'_>, - ) { - let keymap_str = keymap.as_string(); - match KeyboardWithFallback::new_from_string(keymap_str) { - Ok(k) => { - self.keyboard_mapper.replace(k); - } - Err(err) => { - log::error!("Error processing keymap change: {:#}", err); - } - } - } -} diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index b1c046457cf..c0876a7a745 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -10,8 +10,8 @@ use smithay_client_toolkit::shell::xdg::XdgShell; use smithay_client_toolkit::shm::slot::SlotPool; use smithay_client_toolkit::shm::{Shm, ShmHandler}; use smithay_client_toolkit::{ - delegate_compositor, delegate_keyboard, delegate_output, delegate_registry, delegate_seat, - delegate_shm, delegate_xdg_shell, delegate_xdg_window, registry_handlers, + delegate_compositor, delegate_output, delegate_registry, delegate_seat, delegate_shm, + delegate_xdg_shell, delegate_xdg_window, registry_handlers, }; use wayland_client::backend::ObjectId; use wayland_client::globals::GlobalList; @@ -116,7 +116,6 @@ delegate_output!(WaylandState); delegate_compositor!(WaylandState); delegate_seat!(WaylandState); -delegate_keyboard!(WaylandState); delegate_xdg_shell!(WaylandState); delegate_xdg_window!(WaylandState); diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index a6ef3a55e0a..380d8eadbd6 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -21,11 +21,12 @@ use smithay_client_toolkit::shell::xdg::window::{ }; use smithay_client_toolkit::shell::WaylandSurface; use wayland_client::protocol::wl_callback::WlCallback; +use wayland_client::protocol::wl_keyboard::{Event as WlKeyboardEvent, KeyState}; use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::{Connection as WConnection, Proxy}; use wayland_egl::{is_available as egl_is_available, WlEglSurface}; use wezterm_font::FontConfiguration; -use wezterm_input_types::{Modifiers, WindowDecorations}; +use wezterm_input_types::{KeyboardLedStatus, Modifiers, WindowDecorations}; use crate::wayland::WaylandConnection; use crate::x11::KeyboardWithFallback; @@ -235,6 +236,7 @@ impl WaylandWindow { window_state: WindowState::default(), modifiers: Modifiers::NONE, + leds: KeyboardLedStatus::empty(), key_repeat: None, pending_event, @@ -374,7 +376,7 @@ pub struct WaylandWindowInner { // hscroll_remainder: f64, // vscroll_remainder: f64, modifiers: Modifiers, - // leds: KeyboardLedStatus, + leds: KeyboardLedStatus, pub(super) key_repeat: Option<(u32, Arc>)>, pub(crate) pending_event: Arc>, // pending_mouse: Arc>, @@ -714,6 +716,76 @@ impl WaylandWindowInner { self.events.dispatch(WindowEvent::FocusChanged(focused)); self.text_cursor.take(); } + + pub(super) fn keyboard_event( + &mut self, + mapper: &mut KeyboardWithFallback, + event: WlKeyboardEvent, + ) { + match event { + WlKeyboardEvent::Enter { keys, .. } => { + let key_codes = keys + .chunks_exact(4) + .map(|c| u32::from_ne_bytes(c.try_into().unwrap())) + .collect::>(); + log::trace!("keyboard event: Enter with keys: {:?}", key_codes); + self.emit_focus(mapper, true); + } + WlKeyboardEvent::Leave { .. } => { + self.emit_focus(mapper, false); + } + WlKeyboardEvent::Key { key, state, .. } => { + if let Some(event) = mapper.process_wayland_key( + key, + state.into_result().unwrap() == KeyState::Pressed, + &mut self.events, + ) { + let rep = Arc::new(Mutex::new(KeyRepeatState { + when: Instant::now(), + event, + })); + self.key_repeat.replace((key, Arc::clone(&rep))); + let window_id = SurfaceUserData::from_wl( + self.window + .as_ref() + .expect("window should exist") + .wl_surface(), + ) + .window_id; + KeyRepeatState::schedule(rep, window_id); + } else if let Some((cur_key, _)) = self.key_repeat.as_ref() { + // important to check that it's the same key, because the release of the previously + // repeated key can come right after the press of the newly held key + if *cur_key == key { + self.key_repeat.take(); + } + } + } + WlKeyboardEvent::Modifiers { + mods_depressed, + mods_latched, + mods_locked, + group, + .. + } => { + mapper.update_modifier_state(mods_depressed, mods_latched, mods_locked, group); + + let mods = mapper.get_key_modifiers(); + let leds = mapper.get_led_status(); + + let changed = (mods != self.modifiers) || (leds != self.leds); + + self.modifiers = mapper.get_key_modifiers(); + self.leds = mapper.get_led_status(); + + if changed { + self.events + .dispatch(WindowEvent::AdviseModifiersLedStatus(mods, leds)); + } + } + _ => {} + } + } } impl WaylandState { From bbfefb3425d17252cf235eea3007539fdcefd71f Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Thu, 11 Jan 2024 20:17:00 -0500 Subject: [PATCH 35/55] Nuke pointer --- window/src/os/wayland/pointer.rs | 365 ------------------------------- 1 file changed, 365 deletions(-) diff --git a/window/src/os/wayland/pointer.rs b/window/src/os/wayland/pointer.rs index c9458ce3f38..139597f9cb0 100644 --- a/window/src/os/wayland/pointer.rs +++ b/window/src/os/wayland/pointer.rs @@ -1,367 +1,2 @@ -use super::copy_and_paste::*; -use super::drag_and_drop::*; -use crate::os::wayland::connection::WaylandConnection; -use crate::ConnectionOps; -use smithay_client_toolkit as toolkit; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; -use toolkit::primary_selection::{PrimarySelectionDevice, PrimarySelectionDeviceManager}; -use toolkit::reexports::client::protocol::wl_data_device::{ - Event as DataDeviceEvent, WlDataDevice, -}; -use toolkit::reexports::client::protocol::wl_data_offer::{Event as DataOfferEvent, WlDataOffer}; -use toolkit::reexports::client::protocol::wl_pointer::{Axis, ButtonState, Event as PointerEvent}; -use toolkit::reexports::client::protocol::wl_surface::WlSurface; -use toolkit::seat::pointer::{ThemeManager, ThemeSpec, ThemedPointer}; -use wayland_client::protocol::wl_compositor::WlCompositor; -use wayland_client::protocol::wl_data_device_manager::WlDataDeviceManager; -use wayland_client::protocol::wl_seat::WlSeat; -use wayland_client::protocol::wl_shm::WlShm; -use wayland_client::{Attached, Main}; -use wezterm_input_types::*; -#[derive(Default)] -struct Inner { - active_surface_id: u32, - surface_to_pending: HashMap>>, - drag_and_drop: DragAndDrop, - serial: u32, -} -impl Inner { - fn handle_event(&mut self, evt: PointerEvent) { - if let PointerEvent::Enter { surface, .. } = &evt { - let surface_id = surface.as_ref().id(); - // update global active surface id - let conn = WaylandConnection::get().unwrap().wayland(); - *conn.active_surface_id.borrow_mut() = surface_id; - // update pointer-specific active surface id - self.active_surface_id = surface_id; - } - if let Some(serial) = event_serial(&evt) { - self.serial = serial; - } - if let Some(pending) = self.surface_to_pending.get(&self.active_surface_id) { - let mut pending = pending.lock().unwrap(); - if pending.queue(evt) { - WaylandConnection::with_window_inner(pending.window_id, move |inner| { - inner.dispatch_pending_mouse(); - Ok(()) - }); - } - } - } - - fn resolve_copy_and_paste(&mut self) -> Option>> { - let conn = WaylandConnection::get().unwrap().wayland(); - let active_surface_id = conn.active_surface_id.borrow(); - if let Some(pending) = self.surface_to_pending.get(&active_surface_id) { - Some(Arc::clone(&pending.lock().unwrap().copy_and_paste)) - } else { - None - } - } - - fn route_data_offer(&mut self, event: DataOfferEvent, offer: WlDataOffer) { - if let Some(copy_and_paste) = self.resolve_copy_and_paste() { - copy_and_paste - .lock() - .unwrap() - .handle_data_offer(event, offer); - } - } - - fn handle_data_event(&mut self, event: DataDeviceEvent, inner: &Arc>) { - match event { - DataDeviceEvent::DataOffer { id } => { - id.quick_assign({ - let inner = Arc::clone(inner); - move |offer, event, _dispatch_data| { - let mut inner = inner.lock().unwrap(); - inner.route_data_offer(event, offer.detach()); - } - }); - } - - DataDeviceEvent::Enter { .. } - | DataDeviceEvent::Leave - | DataDeviceEvent::Motion { .. } - | DataDeviceEvent::Drop => { - self.drag_and_drop.handle_data_event(event); - } - - DataDeviceEvent::Selection { id } => { - if let Some(offer) = id { - if let Some(copy_and_paste) = self.resolve_copy_and_paste() { - copy_and_paste.lock().unwrap().confirm_selection(offer); - } - } - } - _ => {} - } - } -} - -pub struct PointerDispatcher { - inner: Arc>, - dev_mgr: Attached, - pub(crate) data_device: Main, - selection_manager: Option, - pub(crate) primary_selection_device: Option, - auto_pointer: ThemedPointer, - #[allow(dead_code)] - themer: ThemeManager, - pub(crate) seat: WlSeat, -} - -#[derive(Clone, Debug)] -pub struct PendingMouse { - window_id: usize, - copy_and_paste: Arc>, - surface_coords: Option<(f64, f64)>, - button: Vec<(MousePress, ButtonState)>, - scroll: Option<(f64, f64)>, - in_window: bool, -} - -impl PendingMouse { - pub fn create(window_id: usize, copy_and_paste: &Arc>) -> Arc> { - Arc::new(Mutex::new(Self { - window_id, - copy_and_paste: Arc::clone(copy_and_paste), - button: vec![], - scroll: None, - surface_coords: None, - in_window: false, - })) - } - - // Return true if we need to queue up a call to act on the event, - // false if we think there is already a pending event - pub fn queue(&mut self, evt: PointerEvent) -> bool { - match evt { - PointerEvent::Enter { serial, .. } => { - let conn = WaylandConnection::get().unwrap().wayland(); - *conn.last_serial.borrow_mut() = serial; - self.in_window = true; - false - } - PointerEvent::Leave { .. } => { - let changed = self.in_window; - self.surface_coords = None; - self.in_window = false; - changed - } - PointerEvent::Motion { - surface_x, - surface_y, - .. - } => { - let changed = self.surface_coords.is_none(); - self.surface_coords.replace((surface_x, surface_y)); - changed - } - PointerEvent::Button { - button, - state, - serial, - .. - } => { - let conn = WaylandConnection::get().unwrap().wayland(); - *conn.last_serial.borrow_mut() = serial; - fn linux_button(b: u32) -> Option { - // See BTN_LEFT and friends in - match b { - 0x110 => Some(MousePress::Left), - 0x111 => Some(MousePress::Right), - 0x112 => Some(MousePress::Middle), - _ => None, - } - } - let button = match linux_button(button) { - Some(button) => button, - None => return false, - }; - let changed = self.button.is_empty(); - self.button.push((button, state)); - changed - } - PointerEvent::Axis { - axis: Axis::VerticalScroll, - value, - .. - } => { - let changed = self.scroll.is_none(); - let (x, y) = self.scroll.take().unwrap_or((0., 0.)); - self.scroll.replace((x, y + value)); - changed - } - PointerEvent::Axis { - axis: Axis::HorizontalScroll, - value, - .. - } => { - let changed = self.scroll.is_none(); - let (x, y) = self.scroll.take().unwrap_or((0., 0.)); - self.scroll.replace((x + value, y)); - changed - } - _ => false, - } - } - - pub fn next_button(pending: &Arc>) -> Option<(MousePress, ButtonState)> { - let mut pending = pending.lock().unwrap(); - if pending.button.is_empty() { - None - } else { - Some(pending.button.remove(0)) - } - } - - pub fn coords(pending: &Arc>) -> Option<(f64, f64)> { - pending.lock().unwrap().surface_coords.take() - } - - pub fn scroll(pending: &Arc>) -> Option<(f64, f64)> { - pending.lock().unwrap().scroll.take() - } - - pub fn in_window(pending: &Arc>) -> bool { - pending.lock().unwrap().in_window - } -} - -pub fn make_theme_manager( - compositor: Attached, - shm: Attached, -) -> ThemeManager { - let config = config::configuration(); - let name = config - .xcursor_theme - .as_ref() - .map(|s| s.to_string()) - .or_else(|| std::env::var("XCURSOR_THEME").ok()) - .unwrap_or_else(|| "default".to_string()); - let size = match config.xcursor_size { - Some(size) => size, - None => match std::env::var("XCURSOR_SIZE").ok() { - Some(size_str) => size_str.parse().ok(), - None => None, - } - .unwrap_or(24), - }; - - let theme = ThemeSpec::Precise { name: &name, size }; - - ThemeManager::init(theme, compositor, shm) -} - -impl PointerDispatcher { - pub fn register( - seat: &WlSeat, - compositor: Attached, - shm: Attached, - dev_mgr: Attached, - selection_manager: Option, - ) -> anyhow::Result { - let inner = Arc::new(Mutex::new(Inner::default())); - let pointer = seat.get_pointer(); - pointer.quick_assign({ - let inner = Arc::clone(&inner); - move |_, evt, _| { - inner.lock().unwrap().handle_event(evt); - } - }); - - let themer = make_theme_manager(compositor, shm); - let auto_pointer = themer.theme_pointer(pointer.detach()); - - let data_device = dev_mgr.get_data_device(seat); - data_device.quick_assign({ - let inner = Arc::clone(&inner); - move |_device, event, _| { - inner.lock().unwrap().handle_data_event(event, &inner); - } - }); - - let primary_selection_device = selection_manager - .as_ref() - .map(|m| PrimarySelectionDevice::init_for_seat(&m, seat)); - - Ok(Self { - inner, - dev_mgr, - data_device, - selection_manager, - primary_selection_device, - themer, - auto_pointer, - seat: seat.clone(), - }) - } - - pub fn seat_changed(&mut self, seat: &WlSeat) { - let inner = Arc::clone(&self.inner); - - let pointer = seat.get_pointer(); - pointer.quick_assign({ - let inner = Arc::clone(&inner); - move |_, evt, _| { - inner.lock().unwrap().handle_event(evt); - } - }); - let data_device = self.dev_mgr.get_data_device(seat); - data_device.quick_assign({ - let inner = Arc::clone(&inner); - move |_device, event, _| { - inner.lock().unwrap().handle_data_event(event, &inner); - } - }); - - let primary_selection_device = self - .selection_manager - .as_ref() - .map(|m| PrimarySelectionDevice::init_for_seat(&m, seat)); - - self.data_device = data_device; - self.primary_selection_device = primary_selection_device; - self.seat = seat.clone(); - } - - pub fn add_window(&self, surface: &WlSurface, pending: &Arc>) { - let mut inner = self.inner.lock().unwrap(); - inner - .surface_to_pending - .insert(surface.as_ref().id(), Arc::clone(pending)); - } - - pub fn set_cursor(&self, names: &[&str], serial: Option) { - if names.is_empty() { - (*self.auto_pointer).set_cursor(0, None, 0, 0); - } else { - let mut errors = vec![]; - for name in names { - match self.auto_pointer.set_cursor(name, serial) { - Ok(_) => return, - Err(err) => errors.push(format!("Unable to set cursor to {name}: {err:#}")), - } - } - - if let Err(err) = self.auto_pointer.set_cursor("default", serial) { - errors.push(format!("Unable to set cursor to 'default': {err:#}")); - } - - log::error!("set_cursor: {}", errors.join(", ")); - } - } -} - -fn event_serial(event: &PointerEvent) -> Option { - Some(*match event { - PointerEvent::Enter { serial, .. } => serial, - PointerEvent::Leave { serial, .. } => serial, - PointerEvent::Button { serial, .. } => serial, - _ => return None, - }) -} From 20af0526c2bdd51c15fe1742fb71f6a33f006f2a Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Fri, 19 Jan 2024 13:03:00 -0800 Subject: [PATCH 36/55] Starting pointer implementation --- window/src/os/wayland/mod.rs | 2 +- window/src/os/wayland/pointer.rs | 53 ++++++++++++++++++++++++++++++++ window/src/os/wayland/seat.rs | 9 ++++++ window/src/os/wayland/state.rs | 9 ++++-- 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/window/src/os/wayland/mod.rs b/window/src/os/wayland/mod.rs index ac2f720cd5a..045c180ef9b 100644 --- a/window/src/os/wayland/mod.rs +++ b/window/src/os/wayland/mod.rs @@ -10,8 +10,8 @@ pub use connection::*; // mod copy_and_paste; // mod drag_and_drop; // mod frame; -// mod pointer; mod keyboard; +mod pointer; mod seat; mod state; diff --git a/window/src/os/wayland/pointer.rs b/window/src/os/wayland/pointer.rs index 139597f9cb0..67d208106e9 100644 --- a/window/src/os/wayland/pointer.rs +++ b/window/src/os/wayland/pointer.rs @@ -1,2 +1,55 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use smithay_client_toolkit::seat::pointer::{PointerEvent, PointerEventKind, PointerHandler}; +use wayland_client::protocol::wl_pointer::{ButtonState, WlPointer}; +use wayland_client::{Connection, Proxy, QueueHandle}; +use wezterm_input_types::MousePress; +use super::state::WaylandState; + +impl PointerHandler for WaylandState { + fn pointer_frame( + &mut self, + conn: &Connection, + qh: &QueueHandle, + pointer: &WlPointer, + events: &[PointerEvent], + ) { + for evt in events { + if let PointerEventKind::Enter { .. } = &evt.kind { + let surface_id = evt.surface.id(); + self.active_surface_id = RefCell::new(Some(surface_id)); + } + if let Some(serial) = event_serial(&evt) {} + } + } +} + +// pub(super) struct PointerData { +// active_surface_id: u32, +// surface_to_pending: HashMap>>, +// // TODO: drag_and_drop: DragAndDrop, +// serial: u32, +// } +// +// #[derive(Clone, Debug)] +// pub struct PendingMouse { +// window_id: usize, +// // TODO: copy_and_paste: Arc>, +// surface_coords: Option<(f64, f64)>, +// button: Vec<(MousePress, ButtonState)>, +// scroll: Option<(f64, f64)>, +// in_window: bool, +// } + +fn event_serial(event: &PointerEvent) -> Option { + Some(match event.kind { + PointerEventKind::Enter { serial, .. } => serial, + PointerEventKind::Leave { serial, .. } => serial, + PointerEventKind::Press { serial, .. } => serial, + PointerEventKind::Release { serial, .. } => serial, + _ => return None, + }) +} diff --git a/window/src/os/wayland/seat.rs b/window/src/os/wayland/seat.rs index 5df21176cf5..a1ec28b26dd 100644 --- a/window/src/os/wayland/seat.rs +++ b/window/src/os/wayland/seat.rs @@ -27,6 +27,15 @@ impl SeatHandler for WaylandState { let keyboard = seat.get_keyboard(qh, KeyboardData {}); self.keyboard = Some(keyboard) } + + if capability == Capability::Pointer && self.pointer.is_none() { + log::trace!("Setting pointer capability"); + let pointer = self + .seat_state() + .get_pointer(qh, &seat) + .expect("Failed to create pointer"); + self.pointer = Some(pointer); + } } fn remove_capability( diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index c0876a7a745..2867eefe6d6 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -10,13 +10,14 @@ use smithay_client_toolkit::shell::xdg::XdgShell; use smithay_client_toolkit::shm::slot::SlotPool; use smithay_client_toolkit::shm::{Shm, ShmHandler}; use smithay_client_toolkit::{ - delegate_compositor, delegate_output, delegate_registry, delegate_seat, delegate_shm, - delegate_xdg_shell, delegate_xdg_window, registry_handlers, + delegate_compositor, delegate_output, delegate_pointer, delegate_registry, delegate_seat, + delegate_shm, delegate_xdg_shell, delegate_xdg_window, registry_handlers, }; use wayland_client::backend::ObjectId; use wayland_client::globals::GlobalList; use wayland_client::protocol::wl_keyboard::WlKeyboard; use wayland_client::protocol::wl_output::WlOutput; +use wayland_client::protocol::wl_pointer::WlPointer; use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::{delegate_dispatch, Connection, QueueHandle}; @@ -42,6 +43,8 @@ pub(super) struct WaylandState { pub(super) key_repeat_rate: i32, pub(super) keyboard_window_id: Option, + pub(super) pointer: Option, + shm: Shm, pub(super) mem_pool: RefCell, } @@ -64,6 +67,7 @@ impl WaylandState { key_repeat_rate: 25, key_repeat_delay: 400, keyboard_window_id: None, + pointer: None, shm, mem_pool: RefCell::new(mem_pool), }; @@ -116,6 +120,7 @@ delegate_output!(WaylandState); delegate_compositor!(WaylandState); delegate_seat!(WaylandState); +delegate_pointer!(WaylandState); delegate_xdg_shell!(WaylandState); delegate_xdg_window!(WaylandState); From 73db71cf1c1ffa8534e267280392604e890354d4 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:27:32 -0800 Subject: [PATCH 37/55] Mouse seems to do something, but it is incorrect --- window/src/os/wayland/pointer.rs | 178 +++++++++++++++++++++++++++---- window/src/os/wayland/seat.rs | 3 +- window/src/os/wayland/state.rs | 13 ++- window/src/os/wayland/window.rs | 142 ++++++++++++++++++++++-- 4 files changed, 299 insertions(+), 37 deletions(-) diff --git a/window/src/os/wayland/pointer.rs b/window/src/os/wayland/pointer.rs index 67d208106e9..1f4ac2692cd 100644 --- a/window/src/os/wayland/pointer.rs +++ b/window/src/os/wayland/pointer.rs @@ -1,48 +1,182 @@ use std::cell::RefCell; -use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use smithay_client_toolkit::seat::pointer::{PointerEvent, PointerEventKind, PointerHandler}; +use smithay_client_toolkit::seat::pointer::{ + PointerData, PointerDataExt, PointerEvent, PointerEventKind, PointerHandler, +}; +use wayland_client::backend::ObjectId; use wayland_client::protocol::wl_pointer::{ButtonState, WlPointer}; +use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::{Connection, Proxy, QueueHandle}; use wezterm_input_types::MousePress; +use crate::ConnectionOps; + use super::state::WaylandState; +use super::WaylandConnection; impl PointerHandler for WaylandState { fn pointer_frame( &mut self, - conn: &Connection, - qh: &QueueHandle, + _conn: &Connection, + _qh: &QueueHandle, pointer: &WlPointer, events: &[PointerEvent], ) { + let mut pstate = pointer + .data::() + .unwrap() + .state + .lock() + .unwrap(); + for evt in events { if let PointerEventKind::Enter { .. } = &evt.kind { let surface_id = evt.surface.id(); - self.active_surface_id = RefCell::new(Some(surface_id)); + self.active_surface_id = RefCell::new(Some(surface_id.clone())); + pstate.active_surface_id = Some(surface_id); + } + if let Some(serial) = event_serial(&evt) { + *self.last_serial.borrow_mut() = serial; + pstate.serial = serial; } - if let Some(serial) = event_serial(&evt) {} + if let Some(pending) = self + .surface_to_pending + .get(&self.active_surface_id.borrow().as_ref().unwrap()) + { + let mut pending = pending.lock().unwrap(); + if pending.queue(evt) { + WaylandConnection::with_window_inner(pending.window_id, move |inner| { + inner.dispatch_pending_mouse(); + Ok(()) + }); + } + } + } + } +} + +#[derive(Debug)] +pub(super) struct PointerUserData { + pdata: PointerData, + state: Mutex, +} + +impl PointerUserData { + pub(super) fn new(seat: WlSeat) -> Self { + Self { + pdata: PointerData::new(seat), + state: Default::default(), } } } -// pub(super) struct PointerData { -// active_surface_id: u32, -// surface_to_pending: HashMap>>, -// // TODO: drag_and_drop: DragAndDrop, -// serial: u32, -// } -// -// #[derive(Clone, Debug)] -// pub struct PendingMouse { -// window_id: usize, -// // TODO: copy_and_paste: Arc>, -// surface_coords: Option<(f64, f64)>, -// button: Vec<(MousePress, ButtonState)>, -// scroll: Option<(f64, f64)>, -// in_window: bool, -// } +#[derive(Debug, Default)] +struct PointerState { + active_surface_id: Option, + // TODO: drag_and_drop: DragAndDrop, + serial: u32, +} + +impl PointerDataExt for PointerUserData { + fn pointer_data(&self) -> &PointerData { + &self.pdata + } +} + +#[derive(Clone, Debug)] +pub struct PendingMouse { + window_id: usize, + // TODO: copy_and_paste: Arc>, + surface_coords: Option<(f64, f64)>, + button: Vec<(MousePress, ButtonState)>, + scroll: Option<(f64, f64)>, + in_window: bool, +} + +impl PendingMouse { + // TODO: copy and paste + pub(super) fn create(window_id: usize) -> Arc> { + Arc::new(Mutex::new(Self { + window_id, + button: vec![], + scroll: None, + surface_coords: None, + in_window: false, + })) + } + + pub(super) fn queue(&mut self, evt: &PointerEvent) -> bool { + match evt.kind { + PointerEventKind::Enter { .. } => { + self.in_window = true; + false + } + PointerEventKind::Leave { .. } => { + let changed = self.in_window; + self.surface_coords = None; + self.in_window = false; + changed + } + PointerEventKind::Motion { .. } => { + let changed = self.surface_coords.is_none(); + self.surface_coords.replace(evt.position); + changed + } + PointerEventKind::Press { button, .. } => { + fn linux_button(b: u32) -> Option { + // See BTN_LEFT and friends in + match b { + 0x110 => Some(MousePress::Left), + 0x111 => Some(MousePress::Right), + 0x112 => Some(MousePress::Middle), + _ => None, + } + } + let button = match linux_button(button) { + Some(button) => button, + None => return false, + }; + let changed = self.button.is_empty(); + self.button.push((button, ButtonState::Pressed)); + changed + } + PointerEventKind::Axis { + horizontal, + vertical, + .. + } => { + let changed = self.scroll.is_none(); + let (x, y) = self.scroll.take().unwrap_or((0., 0.)); + self.scroll + .replace((x + horizontal.absolute, y + vertical.absolute)); + changed + } + _ => false, + } + } + + pub(super) fn next_button(pending: &Arc>) -> Option<(MousePress, ButtonState)> { + let mut pending = pending.lock().unwrap(); + if pending.button.is_empty() { + None + } else { + Some(pending.button.remove(0)) + } + } + + pub(super) fn coords(pending: &Arc>) -> Option<(f64, f64)> { + pending.lock().unwrap().surface_coords.take() + } + + pub(super) fn scroll(pending: &Arc>) -> Option<(f64, f64)> { + pending.lock().unwrap().scroll.take() + } + + pub(super) fn in_window(pending: &Arc>) -> bool { + pending.lock().unwrap().in_window + } +} fn event_serial(event: &PointerEvent) -> Option { Some(match event.kind { diff --git a/window/src/os/wayland/seat.rs b/window/src/os/wayland/seat.rs index a1ec28b26dd..b61853c711a 100644 --- a/window/src/os/wayland/seat.rs +++ b/window/src/os/wayland/seat.rs @@ -3,6 +3,7 @@ use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::{Connection, QueueHandle}; use crate::wayland::keyboard::KeyboardData; +use crate::wayland::pointer::PointerUserData; use super::state::WaylandState; @@ -32,7 +33,7 @@ impl SeatHandler for WaylandState { log::trace!("Setting pointer capability"); let pointer = self .seat_state() - .get_pointer(qh, &seat) + .get_pointer_with_data(qh, &seat, PointerUserData::new(seat.clone())) .expect("Failed to create pointer"); self.pointer = Some(pointer); } diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index 2867eefe6d6..d300394643a 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; +use std::sync::{Arc, Mutex}; use smithay_client_toolkit::compositor::CompositorState; use smithay_client_toolkit::output::{OutputHandler, OutputState}; @@ -10,8 +11,8 @@ use smithay_client_toolkit::shell::xdg::XdgShell; use smithay_client_toolkit::shm::slot::SlotPool; use smithay_client_toolkit::shm::{Shm, ShmHandler}; use smithay_client_toolkit::{ - delegate_compositor, delegate_output, delegate_pointer, delegate_registry, delegate_seat, - delegate_shm, delegate_xdg_shell, delegate_xdg_window, registry_handlers, + delegate_compositor, delegate_output, delegate_registry, delegate_seat, delegate_shm, + delegate_xdg_shell, delegate_xdg_window, registry_handlers, }; use wayland_client::backend::ObjectId; use wayland_client::globals::GlobalList; @@ -23,6 +24,7 @@ use wayland_client::{delegate_dispatch, Connection, QueueHandle}; use crate::x11::KeyboardWithFallback; +use super::pointer::{PendingMouse, PointerUserData}; use super::{SurfaceUserData, WaylandWindowInner}; // We can't combine WaylandState and WaylandConnection together because @@ -44,6 +46,7 @@ pub(super) struct WaylandState { pub(super) keyboard_window_id: Option, pub(super) pointer: Option, + pub(super) surface_to_pending: HashMap>>, shm: Shm, pub(super) mem_pool: RefCell, @@ -68,6 +71,7 @@ impl WaylandState { key_repeat_delay: 400, keyboard_window_id: None, pointer: None, + surface_to_pending: HashMap::new(), shm, mem_pool: RefCell::new(mem_pool), }; @@ -120,7 +124,10 @@ delegate_output!(WaylandState); delegate_compositor!(WaylandState); delegate_seat!(WaylandState); -delegate_pointer!(WaylandState); + +// Updating to 0.18 should have this be able to work +// delegate_pointer!(WaylandState, pointer: [PointerUserData]); +delegate_dispatch!(WaylandState: [WlPointer: PointerUserData] => SeatState); delegate_xdg_shell!(WaylandState); delegate_xdg_window!(WaylandState); diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 380d8eadbd6..1c99df2ba2d 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -22,20 +22,25 @@ use smithay_client_toolkit::shell::xdg::window::{ use smithay_client_toolkit::shell::WaylandSurface; use wayland_client::protocol::wl_callback::WlCallback; use wayland_client::protocol::wl_keyboard::{Event as WlKeyboardEvent, KeyState}; +use wayland_client::protocol::wl_pointer::ButtonState; use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::{Connection as WConnection, Proxy}; use wayland_egl::{is_available as egl_is_available, WlEglSurface}; use wezterm_font::FontConfiguration; -use wezterm_input_types::{KeyboardLedStatus, Modifiers, WindowDecorations}; +use wezterm_input_types::{ + KeyboardLedStatus, Modifiers, MouseButtons, MouseEvent, MouseEventKind, MousePress, + ScreenPoint, WindowDecorations, +}; use crate::wayland::WaylandConnection; use crate::x11::KeyboardWithFallback; use crate::{ - Clipboard, Connection, ConnectionOps, Dimensions, MouseCursor, Rect, RequestedWindowGeometry, - ResolvedGeometry, Window, WindowEvent, WindowEventSender, WindowKeyEvent, WindowOps, - WindowState, + Clipboard, Connection, ConnectionOps, Dimensions, MouseCursor, Point, Rect, + RequestedWindowGeometry, ResolvedGeometry, Window, WindowEvent, WindowEventSender, + WindowKeyEvent, WindowOps, WindowState, }; +use super::pointer::PendingMouse; use super::state::WaylandState; #[derive(Debug)] @@ -219,9 +224,15 @@ impl WaylandWindow { window.commit(); // - // TODO: + // TODO: copy and paste // let copy_and_paste = CopyAndPaste::create(); // let pending_mouse = PendingMouse::create(window_id, ©_and_paste); + let pending_mouse = PendingMouse::create(window_id); + + { + let surface_to_pending = &mut conn.wayland_state.borrow_mut().surface_to_pending; + surface_to_pending.insert(surface.id(), Arc::clone(&pending_mouse)); + } // conn.pointer.borrow().add_window(&surface, &pending_mouse); @@ -234,12 +245,17 @@ impl WaylandWindow { dimensions, resize_increments: None, window_state: WindowState::default(), + last_mouse_coords: Point::new(0, 0), + mouse_buttons: MouseButtons::NONE, + hscroll_remainder: 0.0, + vscroll_remainder: 0.0, modifiers: Modifiers::NONE, leds: KeyboardLedStatus::empty(), key_repeat: None, pending_event, + pending_mouse, pending_first_configure: Some(pending_first_configure), frame_callback: None, @@ -371,15 +387,15 @@ pub struct WaylandWindowInner { dimensions: Dimensions, resize_increments: Option<(u16, u16)>, window_state: WindowState, - // last_mouse_coords: Point, - // mouse_buttons: MouseButtons, - // hscroll_remainder: f64, - // vscroll_remainder: f64, + last_mouse_coords: Point, + mouse_buttons: MouseButtons, + hscroll_remainder: f64, + vscroll_remainder: f64, modifiers: Modifiers, leds: KeyboardLedStatus, pub(super) key_repeat: Option<(u32, Arc>)>, - pub(crate) pending_event: Arc>, - // pending_mouse: Arc>, + pub(super) pending_event: Arc>, + pub(super) pending_mouse: Arc>, pending_first_configure: Option>, frame_callback: Option, invalidated: bool, @@ -490,6 +506,110 @@ impl WaylandWindowInner { ((pixels as f64) / self.get_dpi_factor()).ceil() as i32 } + pub(crate) fn dispatch_pending_mouse(&mut self) { + let pending_mouse = Arc::clone(&self.pending_mouse); + + if let Some((x, y)) = PendingMouse::coords(&pending_mouse) { + let coords = Point::new( + self.surface_to_pixels(x as i32) as isize, + self.surface_to_pixels(y as i32) as isize, + ); + self.last_mouse_coords = coords; + let event = MouseEvent { + kind: MouseEventKind::Move, + coords, + screen_coords: ScreenPoint::new( + coords.x + self.dimensions.pixel_width as isize, + coords.y + self.dimensions.pixel_height as isize, + ), + mouse_buttons: self.mouse_buttons, + modifiers: self.modifiers, + }; + self.events.dispatch(WindowEvent::MouseEvent(event)); + self.refresh_frame(); + } + + while let Some((button, state)) = PendingMouse::next_button(&pending_mouse) { + let button_mask = match button { + MousePress::Left => MouseButtons::LEFT, + MousePress::Right => MouseButtons::RIGHT, + MousePress::Middle => MouseButtons::MIDDLE, + }; + + if state == ButtonState::Pressed { + self.mouse_buttons |= button_mask; + } else { + self.mouse_buttons -= button_mask; + } + + let event = MouseEvent { + kind: match state { + ButtonState::Pressed => MouseEventKind::Press(button), + ButtonState::Released => MouseEventKind::Release(button), + _ => continue, + }, + coords: self.last_mouse_coords, + screen_coords: ScreenPoint::new( + self.last_mouse_coords.x + self.dimensions.pixel_width as isize, + self.last_mouse_coords.y + self.dimensions.pixel_height as isize, + ), + mouse_buttons: self.mouse_buttons, + modifiers: self.modifiers, + }; + self.events.dispatch(WindowEvent::MouseEvent(event)); + } + + if let Some((value_x, value_y)) = PendingMouse::scroll(&pending_mouse) { + let factor = self.get_dpi_factor() as f64; + + if value_x.signum() != self.hscroll_remainder.signum() { + // reset accumulator when changing scroll direction + self.hscroll_remainder = 0.0; + } + let scaled_x = (value_x * factor) + self.hscroll_remainder; + let discrete_x = scaled_x.trunc(); + self.hscroll_remainder = scaled_x - discrete_x; + if discrete_x != 0. { + let event = MouseEvent { + kind: MouseEventKind::HorzWheel(-discrete_x as i16), + coords: self.last_mouse_coords, + screen_coords: ScreenPoint::new( + self.last_mouse_coords.x + self.dimensions.pixel_width as isize, + self.last_mouse_coords.y + self.dimensions.pixel_height as isize, + ), + mouse_buttons: self.mouse_buttons, + modifiers: self.modifiers, + }; + self.events.dispatch(WindowEvent::MouseEvent(event)); + } + + if value_y.signum() != self.vscroll_remainder.signum() { + self.vscroll_remainder = 0.0; + } + let scaled_y = (value_y * factor) + self.vscroll_remainder; + let discrete_y = scaled_y.trunc(); + self.vscroll_remainder = scaled_y - discrete_y; + if discrete_y != 0. { + let event = MouseEvent { + kind: MouseEventKind::VertWheel(-discrete_y as i16), + coords: self.last_mouse_coords, + screen_coords: ScreenPoint::new( + self.last_mouse_coords.x + self.dimensions.pixel_width as isize, + self.last_mouse_coords.y + self.dimensions.pixel_height as isize, + ), + mouse_buttons: self.mouse_buttons, + modifiers: self.modifiers, + }; + self.events.dispatch(WindowEvent::MouseEvent(event)); + } + } + + if !PendingMouse::in_window(&pending_mouse) { + self.events.dispatch(WindowEvent::MouseLeave); + self.refresh_frame(); + } + } + pub(crate) fn dispatch_pending_event(&mut self) { let mut pending; { From 943796c3b82b4dddfc67be49ee5a4dc365d1daba Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:47:49 -0800 Subject: [PATCH 38/55] Implement forgotten PointerEventKind::Release, mouse features seem to work now --- window/src/os/wayland/pointer.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/window/src/os/wayland/pointer.rs b/window/src/os/wayland/pointer.rs index 1f4ac2692cd..51a2308d468 100644 --- a/window/src/os/wayland/pointer.rs +++ b/window/src/os/wayland/pointer.rs @@ -10,8 +10,6 @@ use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::{Connection, Proxy, QueueHandle}; use wezterm_input_types::MousePress; -use crate::ConnectionOps; - use super::state::WaylandState; use super::WaylandConnection; @@ -123,7 +121,7 @@ impl PendingMouse { self.surface_coords.replace(evt.position); changed } - PointerEventKind::Press { button, .. } => { + PointerEventKind::Press { button, .. } | PointerEventKind::Release { button, .. } => { fn linux_button(b: u32) -> Option { // See BTN_LEFT and friends in match b { @@ -138,7 +136,12 @@ impl PendingMouse { None => return false, }; let changed = self.button.is_empty(); - self.button.push((button, ButtonState::Pressed)); + let button_state = match evt.kind { + PointerEventKind::Press { .. } => ButtonState::Pressed, + PointerEventKind::Release { .. } => ButtonState::Released, + _ => unreachable!(), + }; + self.button.push((button, button_state)); changed } PointerEventKind::Axis { @@ -152,7 +155,6 @@ impl PendingMouse { .replace((x + horizontal.absolute, y + vertical.absolute)); changed } - _ => false, } } From cef845d48d22501407181a69206d521556c926a9 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Fri, 19 Jan 2024 18:53:49 -0800 Subject: [PATCH 39/55] Theme the pointer --- window/src/os/wayland/seat.rs | 8 ++++++- window/src/os/wayland/state.rs | 5 ++-- window/src/os/wayland/window.rs | 42 +++++++++++++++++++++++---------- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/window/src/os/wayland/seat.rs b/window/src/os/wayland/seat.rs index b61853c711a..f86d3c0b7c9 100644 --- a/window/src/os/wayland/seat.rs +++ b/window/src/os/wayland/seat.rs @@ -1,3 +1,4 @@ +use smithay_client_toolkit::seat::pointer::ThemeSpec; use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState}; use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::{Connection, QueueHandle}; @@ -33,7 +34,12 @@ impl SeatHandler for WaylandState { log::trace!("Setting pointer capability"); let pointer = self .seat_state() - .get_pointer_with_data(qh, &seat, PointerUserData::new(seat.clone())) + .get_pointer_with_theme_and_data( + qh, + &seat, + ThemeSpec::System, + PointerUserData::new(seat.clone()), + ) .expect("Failed to create pointer"); self.pointer = Some(pointer); } diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index d300394643a..24947d242f8 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -6,6 +6,7 @@ use std::sync::{Arc, Mutex}; use smithay_client_toolkit::compositor::CompositorState; use smithay_client_toolkit::output::{OutputHandler, OutputState}; use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState}; +use smithay_client_toolkit::seat::pointer::ThemedPointer; use smithay_client_toolkit::seat::SeatState; use smithay_client_toolkit::shell::xdg::XdgShell; use smithay_client_toolkit::shm::slot::SlotPool; @@ -45,10 +46,10 @@ pub(super) struct WaylandState { pub(super) key_repeat_rate: i32, pub(super) keyboard_window_id: Option, - pub(super) pointer: Option, + pub(super) pointer: Option>, pub(super) surface_to_pending: HashMap>>, - shm: Shm, + pub(super) shm: Shm, pub(super) mem_pool: RefCell, } diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 1c99df2ba2d..903aba460d9 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -1,5 +1,5 @@ use std::any::Any; -use std::cell::RefCell; +use std::cell::{RefCell, RefMut}; use std::convert::TryInto; use std::rc::Rc; use std::sync::{Arc, Mutex}; @@ -183,6 +183,11 @@ impl WaylandWindow { compositor.create_surface_with_data(&qh, surface_data) }; + let pointer_surface = { + let compositor = &conn.wayland_state.borrow().compositor; + compositor.create_surface(&qh) + }; + let ResolvedGeometry { x: _, y: _, @@ -256,6 +261,7 @@ impl WaylandWindow { key_repeat: None, pending_event, pending_mouse, + pointer_surface, pending_first_configure: Some(pending_first_configure), frame_callback: None, @@ -387,6 +393,7 @@ pub struct WaylandWindowInner { dimensions: Dimensions, resize_increments: Option<(u16, u16)>, window_state: WindowState, + pointer_surface: WlSurface, last_mouse_coords: Point, mouse_buttons: MouseButtons, hscroll_remainder: f64, @@ -743,17 +750,28 @@ impl WaylandWindowInner { } fn set_cursor(&mut self, cursor: Option) { - // TODO: Deal with cursors later - let _names: &[&str] = match cursor { - Some(MouseCursor::Arrow) => &["arrow"], - Some(MouseCursor::Hand) => &["hand"], - Some(MouseCursor::SizeUpDown) => &["ns-resize"], - Some(MouseCursor::SizeLeftRight) => &["ew-resize"], - Some(MouseCursor::Text) => &["xterm"], - None => &[], - }; - // let conn = Connection::get().unwrap().wayland(); - // conn.pointer.borrow().set_cursor(names, None); + let name = cursor.map_or("none", |cursor| match cursor { + MouseCursor::Arrow => "arrow", + MouseCursor::Hand => "hand", + MouseCursor::SizeUpDown => "ns-resize", + MouseCursor::SizeLeftRight => "ew-resize", + MouseCursor::Text => "xterm", + }); + let conn = Connection::get().unwrap().wayland(); + let state = conn.wayland_state.borrow_mut(); + let (shm, pointer) = + RefMut::map_split(state, |s| (&mut s.shm, s.pointer.as_mut().unwrap())); + + // Much different API in 0.18 + if let Err(err) = pointer.set_cursor( + &conn.connection, + name, + shm.wl_shm(), + &self.pointer_surface, + 1, + ) { + log::error!("set_cursor: {}", err); + } } fn invalidate(&mut self) { From 92f0f59c49b26651d1c6dd177271afaf8440667a Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Sun, 21 Jan 2024 19:22:17 -0800 Subject: [PATCH 40/55] Implement copying, pasting does not work --- window/src/os/wayland/copy_and_paste.rs | 172 +++---------------- window/src/os/wayland/data_device.rs | 213 ++++++++++++++++++++++++ window/src/os/wayland/mod.rs | 3 +- window/src/os/wayland/pointer.rs | 10 +- window/src/os/wayland/seat.rs | 8 +- window/src/os/wayland/state.rs | 18 +- window/src/os/wayland/window.rs | 25 +-- 7 files changed, 284 insertions(+), 165 deletions(-) create mode 100644 window/src/os/wayland/data_device.rs diff --git a/window/src/os/wayland/copy_and_paste.rs b/window/src/os/wayland/copy_and_paste.rs index 4cacd204896..6b704ce71b1 100644 --- a/window/src/os/wayland/copy_and_paste.rs +++ b/window/src/os/wayland/copy_and_paste.rs @@ -1,17 +1,11 @@ -use anyhow::{anyhow, bail, Context, Error}; -use filedescriptor::{FileDescriptor, Pipe}; use smithay_client_toolkit as toolkit; -use std::io::Write; -use std::os::unix::io::AsRawFd; -use std::os::unix::prelude::{FromRawFd, IntoRawFd}; use std::sync::{Arc, Mutex}; -use toolkit::primary_selection::*; -use toolkit::reexports::client::protocol::wl_data_offer::{Event as DataOfferEvent, WlDataOffer}; -use wayland_client::protocol::wl_data_device_manager::WlDataDeviceManager; -use wayland_client::protocol::wl_data_source::Event as DataSourceEvent; +use toolkit::reexports::client::protocol::wl_data_offer::WlDataOffer; -use crate::connection::ConnectionOps; -use crate::Clipboard; +use crate::{Clipboard, ConnectionOps}; + +use super::data_device::TEXT_MIME_TYPE; +use super::state::WaylandState; #[derive(Default)] pub struct CopyAndPaste { @@ -26,156 +20,40 @@ impl std::fmt::Debug for CopyAndPaste { } } -pub const TEXT_MIME_TYPE: &str = "text/plain;charset=utf-8"; - impl CopyAndPaste { - pub fn create() -> Arc> { + pub(super) fn create() -> Arc> { Arc::new(Mutex::new(Default::default())) } - pub fn get_clipboard_data(&mut self, clipboard: Clipboard) -> anyhow::Result { - let conn = crate::Connection::get().unwrap().wayland(); - let pointer = conn.pointer.borrow(); - let primary_selection = if let Clipboard::PrimarySelection = clipboard { - pointer.primary_selection_device.as_ref() - } else { - None - }; - match primary_selection { - Some(device) => { - let pipe = device.with_selection(|offer| { - offer - .ok_or_else(|| anyhow!("no primary selection offer")) - .and_then(|o| { - o.receive(TEXT_MIME_TYPE.to_string()) - .with_context(|| "failed to open read pipe".to_string()) - }) - })?; - Ok(unsafe { FileDescriptor::from_raw_fd(pipe.into_raw_fd()) }) - } - None => { - let offer = self - .data_offer - .as_ref() - .ok_or_else(|| anyhow!("no data offer"))?; - let pipe = Pipe::new().map_err(Error::msg)?; - offer.receive(TEXT_MIME_TYPE.to_string(), pipe.write.as_raw_fd()); - Ok(pipe.read) - } - } - } + pub(super) fn set_clipboard_data(&mut self, _clipboard: Clipboard, data: String) { + // TODO: primary selection - pub fn set_clipboard_data(&mut self, clipboard: Clipboard, data: String) { let conn = crate::Connection::get().unwrap().wayland(); - let last_serial = *conn.last_serial.borrow(); - let pointer = conn.pointer.borrow(); - let primary_selection = if let Clipboard::PrimarySelection = clipboard { - conn.environment - .get_primary_selection_manager() - .zip(pointer.primary_selection_device.as_ref()) - } else { - None - }; - - match primary_selection { - Some((manager, device)) => { - let source = PrimarySelectionSource::new( - &manager, - &[TEXT_MIME_TYPE.to_string()], - move |event, _dispatch_data| match event { - PrimarySelectionSourceEvent::Cancelled => { - crate::Connection::get() - .unwrap() - .wayland() - .pointer - .borrow() - .data_device - .set_selection(None, 0); - } - PrimarySelectionSourceEvent::Send { pipe, .. } => { - let fd = unsafe { FileDescriptor::from_raw_fd(pipe.into_raw_fd()) }; - write_selection_to_pipe(fd, &data); - } - }, - ); - device.set_selection(&Some(source), last_serial) - } - None => { - let source = conn - .environment - .require_global::() - .create_data_source(); - source.quick_assign(move |_source, event, _dispatch_data| { - if let DataSourceEvent::Send { fd, .. } = event { - let fd = unsafe { FileDescriptor::from_raw_fd(fd) }; - write_selection_to_pipe(fd, &data); - } - }); - source.offer(TEXT_MIME_TYPE.to_string()); - conn.pointer - .borrow() - .data_device - .set_selection(Some(&source), last_serial); - } - } - } + let qh = conn.event_queue.borrow().handle(); + let mut wayland_state = conn.wayland_state.borrow_mut(); + let last_serial = *wayland_state.last_serial.borrow(); + let data_device = &wayland_state.data_device; - pub fn handle_data_offer(&mut self, event: DataOfferEvent, offer: WlDataOffer) { - match event { - DataOfferEvent::Offer { mime_type } => { - let conn = crate::Connection::get().unwrap().wayland(); - let last_serial = *conn.last_serial.borrow(); - if mime_type == TEXT_MIME_TYPE { - offer.accept(last_serial, Some(mime_type)); - self.data_offer.replace(offer); - } else { - // Refuse other mime types - offer.accept(last_serial, None); - } - } - DataOfferEvent::SourceActions { .. } | DataOfferEvent::Action { .. } => { - // ignore drag and drop events - } - _ => {} - } + let source = wayland_state + .data_device_manager_state + .create_copy_paste_source(&qh, vec![TEXT_MIME_TYPE]); + source.set_selection(data_device.as_ref().unwrap(), last_serial); + wayland_state.copy_paste_source.replace((source, data)); } - pub fn confirm_selection(&mut self, offer: WlDataOffer) { + pub(super) fn confirm_selection(&mut self, offer: WlDataOffer) { self.data_offer.replace(offer); } } -fn write_selection_to_pipe(fd: FileDescriptor, text: &str) { - if let Err(e) = write_pipe_with_timeout(fd, text.as_bytes()) { - log::error!("while sending primary selection to pipe: {}", e); - } -} - -fn write_pipe_with_timeout(mut file: FileDescriptor, data: &[u8]) -> anyhow::Result<()> { - file.set_non_blocking(true)?; - let mut pfd = libc::pollfd { - fd: file.as_raw_fd(), - events: libc::POLLOUT, - revents: 0, - }; - - let mut buf = data; - - while !buf.is_empty() { - if unsafe { libc::poll(&mut pfd, 1, 3000) == 1 } { - match file.write(buf) { - Ok(size) if size == 0 => { - bail!("zero byte write"); - } - Ok(size) => { - buf = &buf[size..]; - } - Err(e) => bail!("error writing to pipe: {}", e), - } +impl WaylandState { + pub(super) fn resolve_copy_and_paste(&mut self) -> Option>> { + let active_surface_id = self.active_surface_id.borrow(); + let active_surface_id = active_surface_id.as_ref().unwrap(); + if let Some(pending) = self.surface_to_pending.get(&active_surface_id) { + Some(Arc::clone(&pending.lock().unwrap().copy_and_paste)) } else { - bail!("timed out writing to pipe"); + None } } - - Ok(()) } diff --git a/window/src/os/wayland/data_device.rs b/window/src/os/wayland/data_device.rs new file mode 100644 index 00000000000..6c8e7d820b0 --- /dev/null +++ b/window/src/os/wayland/data_device.rs @@ -0,0 +1,213 @@ +use std::fs::File; +use std::io::Write; +use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd}; + +use anyhow::bail; +use filedescriptor::FileDescriptor; +use smithay_client_toolkit::data_device_manager::data_device::{ + DataDevice, DataDeviceDataExt, DataDeviceHandler, +}; +use smithay_client_toolkit::data_device_manager::data_offer::DataOfferHandler; +use smithay_client_toolkit::data_device_manager::data_source::DataSourceHandler; +use smithay_client_toolkit::data_device_manager::WritePipe; + +use super::state::WaylandState; + +pub(super) const TEXT_MIME_TYPE: &str = "text/plain;charset=utf-8"; + +impl DataDeviceHandler for WaylandState { + fn enter( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + _data_device: DataDevice, + ) { + todo!() + } + + fn leave( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + _data_device: DataDevice, + ) { + todo!() + } + + fn motion( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + _data_device: DataDevice, + ) { + todo!() + } + + fn selection( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + data_device: DataDevice, + ) { + if let Some(offer) = data_device.selection_offer() { + if let Some(copy_and_paste) = self.resolve_copy_and_paste() { + copy_and_paste + .lock() + .unwrap() + .confirm_selection(offer.inner().clone()); + } + } + } + + fn drop_performed( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + _data_device: DataDevice, + ) { + todo!() + } +} + +impl DataOfferHandler for WaylandState { + fn offer( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + offer: &mut smithay_client_toolkit::data_device_manager::data_offer::DataDeviceOffer, + mime_type: String, + ) { + log::trace!("Received offer with mime type: {mime_type}"); + if mime_type == TEXT_MIME_TYPE { + offer.accept_mime_type(*self.last_serial.borrow(), Some(mime_type)); + } else { + // Refuse other mime types + offer.accept_mime_type(*self.last_serial.borrow(), None); + } + } + + fn source_actions( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + _offer: &mut smithay_client_toolkit::data_device_manager::data_offer::DragOffer, + _actions: wayland_client::protocol::wl_data_device_manager::DndAction, + ) { + todo!() + } + + fn selected_action( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + _offer: &mut smithay_client_toolkit::data_device_manager::data_offer::DragOffer, + _actions: wayland_client::protocol::wl_data_device_manager::DndAction, + ) { + todo!() + } +} + +impl DataSourceHandler for WaylandState { + fn accept_mime( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + _source: &wayland_client::protocol::wl_data_source::WlDataSource, + _mime: Option, + ) { + todo!() + } + + fn send_request( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + source: &wayland_client::protocol::wl_data_source::WlDataSource, + mime: String, + fd: WritePipe, + ) { + if mime != TEXT_MIME_TYPE { + return; + } + + if let Some((cp_source, data)) = &self.copy_paste_source { + if cp_source.inner() != source { + return; + } + let fd = unsafe { FileDescriptor::from_raw_fd(fd.into_raw_fd()) }; + write_selection_to_pipe(fd, data); + } + } + + fn cancelled( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + _source: &wayland_client::protocol::wl_data_source::WlDataSource, + ) { + todo!() + } + + fn dnd_dropped( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + _source: &wayland_client::protocol::wl_data_source::WlDataSource, + ) { + todo!() + } + + fn dnd_finished( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + _source: &wayland_client::protocol::wl_data_source::WlDataSource, + ) { + todo!() + } + + fn action( + &mut self, + _conn: &wayland_client::Connection, + _qh: &wayland_client::QueueHandle, + _source: &wayland_client::protocol::wl_data_source::WlDataSource, + _action: wayland_client::protocol::wl_data_device_manager::DndAction, + ) { + todo!() + } +} + +fn write_selection_to_pipe(fd: FileDescriptor, text: &str) { + if let Err(e) = write_pipe_with_timeout(fd, text.as_bytes()) { + log::error!("while sending primary selection to pipe: {}", e); + } +} + +fn write_pipe_with_timeout(mut file: FileDescriptor, data: &[u8]) -> anyhow::Result<()> { + file.set_non_blocking(true)?; + let mut pfd = libc::pollfd { + fd: file.as_raw_fd(), + events: libc::POLLOUT, + revents: 0, + }; + + let mut buf = data; + + while !buf.is_empty() { + if unsafe { libc::poll(&mut pfd, 1, 3000) == 1 } { + match file.write(buf) { + Ok(size) if size == 0 => { + bail!("zero byte write"); + } + Ok(size) => { + buf = &buf[size..]; + } + Err(e) => bail!("error writing to pipe: {}", e), + } + } else { + bail!("timed out writing to pipe"); + } + } + + Ok(()) +} diff --git a/window/src/os/wayland/mod.rs b/window/src/os/wayland/mod.rs index 045c180ef9b..c5291db9f63 100644 --- a/window/src/os/wayland/mod.rs +++ b/window/src/os/wayland/mod.rs @@ -7,9 +7,10 @@ pub mod window; pub use self::window::*; pub use connection::*; // pub use output::*; -// mod copy_and_paste; +mod copy_and_paste; // mod drag_and_drop; // mod frame; +mod data_device; mod keyboard; mod pointer; mod seat; diff --git a/window/src/os/wayland/pointer.rs b/window/src/os/wayland/pointer.rs index 51a2308d468..10c18c16bf9 100644 --- a/window/src/os/wayland/pointer.rs +++ b/window/src/os/wayland/pointer.rs @@ -10,6 +10,7 @@ use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::{Connection, Proxy, QueueHandle}; use wezterm_input_types::MousePress; +use super::copy_and_paste::CopyAndPaste; use super::state::WaylandState; use super::WaylandConnection; @@ -85,7 +86,7 @@ impl PointerDataExt for PointerUserData { #[derive(Clone, Debug)] pub struct PendingMouse { window_id: usize, - // TODO: copy_and_paste: Arc>, + pub(super) copy_and_paste: Arc>, surface_coords: Option<(f64, f64)>, button: Vec<(MousePress, ButtonState)>, scroll: Option<(f64, f64)>, @@ -93,10 +94,13 @@ pub struct PendingMouse { } impl PendingMouse { - // TODO: copy and paste - pub(super) fn create(window_id: usize) -> Arc> { + pub(super) fn create( + window_id: usize, + copy_and_paste: &Arc>, + ) -> Arc> { Arc::new(Mutex::new(Self { window_id, + copy_and_paste: Arc::clone(copy_and_paste), button: vec![], scroll: None, surface_coords: None, diff --git a/window/src/os/wayland/seat.rs b/window/src/os/wayland/seat.rs index f86d3c0b7c9..0e3bac62312 100644 --- a/window/src/os/wayland/seat.rs +++ b/window/src/os/wayland/seat.rs @@ -27,13 +27,13 @@ impl SeatHandler for WaylandState { if capability == Capability::Keyboard && self.keyboard.is_none() { log::trace!("Setting keyboard capability"); let keyboard = seat.get_keyboard(qh, KeyboardData {}); - self.keyboard = Some(keyboard) + self.keyboard = Some(keyboard.clone()); } if capability == Capability::Pointer && self.pointer.is_none() { log::trace!("Setting pointer capability"); let pointer = self - .seat_state() + .seat .get_pointer_with_theme_and_data( qh, &seat, @@ -42,6 +42,10 @@ impl SeatHandler for WaylandState { ) .expect("Failed to create pointer"); self.pointer = Some(pointer); + + let data_device_manager = &self.data_device_manager_state; + let data_device = data_device_manager.get_data_device(qh, &seat); + self.data_device.replace(data_device); } } diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index 24947d242f8..5f62e77bff1 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -4,6 +4,9 @@ use std::rc::Rc; use std::sync::{Arc, Mutex}; use smithay_client_toolkit::compositor::CompositorState; +use smithay_client_toolkit::data_device_manager::data_device::DataDevice; +use smithay_client_toolkit::data_device_manager::data_source::CopyPasteSource; +use smithay_client_toolkit::data_device_manager::DataDeviceManagerState; use smithay_client_toolkit::output::{OutputHandler, OutputState}; use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState}; use smithay_client_toolkit::seat::pointer::ThemedPointer; @@ -12,7 +15,8 @@ use smithay_client_toolkit::shell::xdg::XdgShell; use smithay_client_toolkit::shm::slot::SlotPool; use smithay_client_toolkit::shm::{Shm, ShmHandler}; use smithay_client_toolkit::{ - delegate_compositor, delegate_output, delegate_registry, delegate_seat, delegate_shm, + delegate_compositor, delegate_data_device, delegate_data_device_manager, delegate_data_offer, + delegate_data_source, delegate_output, delegate_registry, delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, registry_handlers, }; use wayland_client::backend::ObjectId; @@ -20,6 +24,7 @@ use wayland_client::globals::GlobalList; use wayland_client::protocol::wl_keyboard::WlKeyboard; use wayland_client::protocol::wl_output::WlOutput; use wayland_client::protocol::wl_pointer::WlPointer; +use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::{delegate_dispatch, Connection, QueueHandle}; @@ -49,6 +54,9 @@ pub(super) struct WaylandState { pub(super) pointer: Option>, pub(super) surface_to_pending: HashMap>>, + pub(super) data_device_manager_state: DataDeviceManagerState, + pub(super) data_device: Option, + pub(super) copy_paste_source: Option<(CopyPasteSource, String)>, pub(super) shm: Shm, pub(super) mem_pool: RefCell, } @@ -73,6 +81,9 @@ impl WaylandState { keyboard_window_id: None, pointer: None, surface_to_pending: HashMap::new(), + data_device_manager_state: DataDeviceManagerState::bind(globals, qh)?, + data_device: None, + copy_paste_source: None, shm, mem_pool: RefCell::new(mem_pool), }; @@ -126,6 +137,11 @@ delegate_compositor!(WaylandState); delegate_seat!(WaylandState); +delegate_data_device_manager!(WaylandState); +delegate_data_device!(WaylandState); +delegate_data_source!(WaylandState); +delegate_data_offer!(WaylandState); + // Updating to 0.18 should have this be able to work // delegate_pointer!(WaylandState, pointer: [PointerUserData]); delegate_dispatch!(WaylandState: [WlPointer: PointerUserData] => SeatState); diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 903aba460d9..6ce8929ca1f 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -40,6 +40,7 @@ use crate::{ WindowKeyEvent, WindowOps, WindowState, }; +use super::copy_and_paste::CopyAndPaste; use super::pointer::PendingMouse; use super::state::WaylandState; @@ -229,22 +230,18 @@ impl WaylandWindow { window.commit(); // - // TODO: copy and paste - // let copy_and_paste = CopyAndPaste::create(); - // let pending_mouse = PendingMouse::create(window_id, ©_and_paste); - let pending_mouse = PendingMouse::create(window_id); + let copy_and_paste = CopyAndPaste::create(); + let pending_mouse = PendingMouse::create(window_id, ©_and_paste); { let surface_to_pending = &mut conn.wayland_state.borrow_mut().surface_to_pending; surface_to_pending.insert(surface.id(), Arc::clone(&pending_mouse)); } - // conn.pointer.borrow().add_window(&surface, &pending_mouse); - let inner = Rc::new(RefCell::new(WaylandWindowInner { events: WindowEventSender::new(event_handler), surface_factor: 1.0, - + copy_and_paste, invalidated: false, window: Some(window), dimensions, @@ -365,13 +362,19 @@ impl WindowOps for WaylandWindow { todo!() } - #[doc = r" Initiate textual transfer from the clipboard"] fn get_clipboard(&self, _clipboard: Clipboard) -> Future { todo!() } - fn set_clipboard(&self, _clipboard: Clipboard, _text: String) { - todo!() + fn set_clipboard(&self, clipboard: Clipboard, text: String) { + WaylandConnection::with_window_inner(self.0, move |inner| { + inner + .copy_and_paste + .lock() + .unwrap() + .set_clipboard_data(clipboard, text); + Ok(()) + }); } } #[derive(Default, Clone, Debug)] @@ -388,7 +391,7 @@ pub struct WaylandWindowInner { // window_id: usize, pub(crate) events: WindowEventSender, surface_factor: f64, - // copy_and_paste: Arc>, + copy_and_paste: Arc>, window: Option, dimensions: Dimensions, resize_increments: Option<(u16, u16)>, From 9327a5ec84b7fd931913e313d5a2c246a2bc55ed Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Sun, 21 Jan 2024 21:45:07 -0800 Subject: [PATCH 41/55] Implement paste --- window/src/os/wayland/copy_and_paste.rs | 17 ++++++ window/src/os/wayland/data_device.rs | 11 ++-- window/src/os/wayland/state.rs | 1 - window/src/os/wayland/window.rs | 69 +++++++++++++++++++++++-- 4 files changed, 87 insertions(+), 11 deletions(-) diff --git a/window/src/os/wayland/copy_and_paste.rs b/window/src/os/wayland/copy_and_paste.rs index 6b704ce71b1..81b00855cd2 100644 --- a/window/src/os/wayland/copy_and_paste.rs +++ b/window/src/os/wayland/copy_and_paste.rs @@ -1,4 +1,7 @@ +use anyhow::{anyhow, Error}; +use filedescriptor::{FileDescriptor, Pipe}; use smithay_client_toolkit as toolkit; +use std::os::fd::AsRawFd; use std::sync::{Arc, Mutex}; use toolkit::reexports::client::protocol::wl_data_offer::WlDataOffer; @@ -25,6 +28,20 @@ impl CopyAndPaste { Arc::new(Mutex::new(Default::default())) } + pub(super) fn get_clipboard_data( + &mut self, + _clipboard: Clipboard, + ) -> anyhow::Result { + // TODO; primary selection + let offer = self + .data_offer + .as_ref() + .ok_or_else(|| anyhow!("no data offer"))?; + let pipe = Pipe::new().map_err(Error::msg)?; + offer.receive(TEXT_MIME_TYPE.to_string(), pipe.write.as_raw_fd()); + Ok(pipe.read) + } + pub(super) fn set_clipboard_data(&mut self, _clipboard: Clipboard, data: String) { // TODO: primary selection diff --git a/window/src/os/wayland/data_device.rs b/window/src/os/wayland/data_device.rs index 6c8e7d820b0..c292b7aca62 100644 --- a/window/src/os/wayland/data_device.rs +++ b/window/src/os/wayland/data_device.rs @@ -1,6 +1,5 @@ -use std::fs::File; use std::io::Write; -use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd}; +use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd}; use anyhow::bail; use filedescriptor::FileDescriptor; @@ -86,6 +85,7 @@ impl DataOfferHandler for WaylandState { } } + // Ignore drag and drop events fn source_actions( &mut self, _conn: &wayland_client::Connection, @@ -93,7 +93,6 @@ impl DataOfferHandler for WaylandState { _offer: &mut smithay_client_toolkit::data_device_manager::data_offer::DragOffer, _actions: wayland_client::protocol::wl_data_device_manager::DndAction, ) { - todo!() } fn selected_action( @@ -103,7 +102,6 @@ impl DataOfferHandler for WaylandState { _offer: &mut smithay_client_toolkit::data_device_manager::data_offer::DragOffer, _actions: wayland_client::protocol::wl_data_device_manager::DndAction, ) { - todo!() } } @@ -143,9 +141,10 @@ impl DataSourceHandler for WaylandState { &mut self, _conn: &wayland_client::Connection, _qh: &wayland_client::QueueHandle, - _source: &wayland_client::protocol::wl_data_source::WlDataSource, + source: &wayland_client::protocol::wl_data_source::WlDataSource, ) { - todo!() + self.copy_paste_source.take(); + source.destroy(); } fn dnd_dropped( diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index 5f62e77bff1..419b506442e 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -24,7 +24,6 @@ use wayland_client::globals::GlobalList; use wayland_client::protocol::wl_keyboard::WlKeyboard; use wayland_client::protocol::wl_output::WlOutput; use wayland_client::protocol::wl_pointer::WlPointer; -use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::{delegate_dispatch, Connection, QueueHandle}; diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 6ce8929ca1f..661a4378126 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -1,15 +1,18 @@ use std::any::Any; use std::cell::{RefCell, RefMut}; use std::convert::TryInto; +use std::io::Read; +use std::os::fd::AsRawFd; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; -use anyhow::anyhow; +use anyhow::{anyhow, bail}; use async_io::Timer; use async_trait::async_trait; use config::ConfigHandle; -use promise::Future; +use filedescriptor::FileDescriptor; +use promise::{Future, Promise}; use raw_window_handle::{ HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, @@ -362,8 +365,35 @@ impl WindowOps for WaylandWindow { todo!() } - fn get_clipboard(&self, _clipboard: Clipboard) -> Future { - todo!() + fn get_clipboard(&self, clipboard: Clipboard) -> Future { + let mut promise = Promise::new(); + let future = promise.get_future().unwrap(); + let promise = Arc::new(Mutex::new(promise)); + WaylandConnection::with_window_inner(self.0, move |inner| { + let read = inner + .copy_and_paste + .lock() + .unwrap() + .get_clipboard_data(clipboard)?; + let promise = Arc::clone(&promise); + std::thread::spawn(move || { + let mut promise = promise.lock().unwrap(); + match read_pipe_with_timeout(read) { + Ok(result) => { + // Normalize the text to unix line endings, otherwise + // copying from eg: firefox inserts a lot of blank + // lines, and that is super annoying. + promise.ok(result.replace("\r\n", "\n")); + } + Err(e) => { + log::error!("while reading clipboard: {}", e); + promise.err(anyhow!("{}", e)); + } + }; + }); + Ok(()) + }); + future } fn set_clipboard(&self, clipboard: Clipboard, text: String) { @@ -387,6 +417,37 @@ pub(crate) struct PendingEvent { pub(crate) window_state: Option, } +pub(crate) fn read_pipe_with_timeout(mut file: FileDescriptor) -> anyhow::Result { + let mut result = Vec::new(); + + file.set_non_blocking(true)?; + let mut pfd = libc::pollfd { + fd: file.as_raw_fd(), + events: libc::POLLIN, + revents: 0, + }; + + let mut buf = [0u8; 8192]; + + loop { + if unsafe { libc::poll(&mut pfd, 1, 3000) == 1 } { + match file.read(&mut buf) { + Ok(size) if size == 0 => { + break; + } + Ok(size) => { + result.extend_from_slice(&buf[..size]); + } + Err(e) => bail!("error reading from pipe: {}", e), + } + } else { + bail!("timed out reading from pipe"); + } + } + + Ok(String::from_utf8(result)?) +} + pub struct WaylandWindowInner { // window_id: usize, pub(crate) events: WindowEventSender, From e356d5491694ff40e381e69c3f2eb7f79db0d2f6 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Mon, 22 Jan 2024 14:45:18 -0800 Subject: [PATCH 42/55] Implement dispatch_dropped_files --- window/src/os/wayland/data_device.rs | 73 +++++++++++++++++++++-- window/src/os/wayland/drag_and_drop.rs | 81 +++++++------------------- window/src/os/wayland/mod.rs | 2 +- window/src/os/wayland/pointer.rs | 10 ++-- window/src/os/wayland/window.rs | 5 ++ 5 files changed, 100 insertions(+), 71 deletions(-) diff --git a/window/src/os/wayland/data_device.rs b/window/src/os/wayland/data_device.rs index c292b7aca62..d7aae52a623 100644 --- a/window/src/os/wayland/data_device.rs +++ b/window/src/os/wayland/data_device.rs @@ -9,19 +9,56 @@ use smithay_client_toolkit::data_device_manager::data_device::{ use smithay_client_toolkit::data_device_manager::data_offer::DataOfferHandler; use smithay_client_toolkit::data_device_manager::data_source::DataSourceHandler; use smithay_client_toolkit::data_device_manager::WritePipe; +use wayland_client::protocol::wl_data_device_manager::DndAction; +use wayland_client::Proxy; +use crate::wayland::drag_and_drop::SurfaceAndOffer; +use crate::wayland::pointer::PointerUserData; +use crate::wayland::SurfaceUserData; + +use super::drag_and_drop::{DragAndDrop, SurfaceAndPipe}; use super::state::WaylandState; pub(super) const TEXT_MIME_TYPE: &str = "text/plain;charset=utf-8"; +pub(super) const URI_MIME_TYPE: &str = "text/uri-list"; impl DataDeviceHandler for WaylandState { fn enter( &mut self, _conn: &wayland_client::Connection, _qh: &wayland_client::QueueHandle, - _data_device: DataDevice, + data_device: DataDevice, ) { - todo!() + let mut drag_offer = data_device.drag_offer().unwrap(); + log::trace!( + "Data offer entered: {:?}, mime_types: {:?}", + drag_offer, + data_device.drag_mime_types() + ); + + if let Some(m) = data_device + .drag_mime_types() + .iter() + .find(|s| *s == URI_MIME_TYPE) + { + drag_offer.accept_mime_type(*self.last_serial.borrow(), Some(m.clone())); + } + + drag_offer.set_actions(DndAction::None | DndAction::Copy, DndAction::None); + + let pointer = self.pointer.as_mut().unwrap(); + let mut pstate = pointer + .pointer() + .data::() + .unwrap() + .state + .lock() + .unwrap(); + + let offer = drag_offer.inner().clone(); + let window_id = SurfaceUserData::from_wl(&drag_offer.surface).window_id; + + pstate.drag_and_drop.offer = Some(SurfaceAndOffer { window_id, offer }); } fn leave( @@ -30,7 +67,17 @@ impl DataDeviceHandler for WaylandState { _qh: &wayland_client::QueueHandle, _data_device: DataDevice, ) { - todo!() + let pointer = self.pointer.as_mut().unwrap(); + let mut pstate = pointer + .pointer() + .data::() + .unwrap() + .state + .lock() + .unwrap(); + if let Some(SurfaceAndOffer { offer, .. }) = pstate.drag_and_drop.offer.take() { + offer.destroy(); + } } fn motion( @@ -39,7 +86,6 @@ impl DataDeviceHandler for WaylandState { _qh: &wayland_client::QueueHandle, _data_device: DataDevice, ) { - todo!() } fn selection( @@ -48,6 +94,7 @@ impl DataDeviceHandler for WaylandState { _qh: &wayland_client::QueueHandle, data_device: DataDevice, ) { + // TODO: handle mime types if let Some(offer) = data_device.selection_offer() { if let Some(copy_and_paste) = self.resolve_copy_and_paste() { copy_and_paste @@ -64,7 +111,23 @@ impl DataDeviceHandler for WaylandState { _qh: &wayland_client::QueueHandle, _data_device: DataDevice, ) { - todo!() + let pointer = self.pointer.as_mut().unwrap(); + let mut pstate = pointer + .pointer() + .data::() + .unwrap() + .state + .lock() + .unwrap(); + let drag_and_drop = &mut pstate.drag_and_drop; + if let Some(SurfaceAndPipe { window_id, read }) = drag_and_drop.create_pipe_for_drop() { + std::thread::spawn(move || { + if let Some(paths) = DragAndDrop::read_paths_from_pipe(read) { + DragAndDrop::dispatch_dropped_files(window_id, paths); + } + }); + } + // if let Some(SurfaceAndOffer { offer, .. }) = pstate.drag_and_drop.offer.take() { } } diff --git a/window/src/os/wayland/drag_and_drop.rs b/window/src/os/wayland/drag_and_drop.rs index 2df5030f8b4..59dbfbe400d 100644 --- a/window/src/os/wayland/drag_and_drop.rs +++ b/window/src/os/wayland/drag_and_drop.rs @@ -1,46 +1,45 @@ -use crate::connection::ConnectionOps; -use crate::wayland::{read_pipe_with_timeout, WaylandConnection}; +use crate::wayland::read_pipe_with_timeout; +use crate::ConnectionOps; use filedescriptor::{FileDescriptor, Pipe}; use smithay_client_toolkit as toolkit; use std::os::unix::io::AsRawFd; use std::path::PathBuf; -use toolkit::reexports::client::protocol::wl_data_device::Event as DataDeviceEvent; use toolkit::reexports::client::protocol::wl_data_offer::WlDataOffer; use url::Url; -use wayland_client::protocol::wl_data_device_manager::DndAction; + +use super::data_device::URI_MIME_TYPE; +use super::WaylandConnection; #[derive(Default)] pub struct DragAndDrop { - offer: Option, + pub(super) offer: Option, } -struct SurfaceAndOffer { - surface_id: u32, - offer: WlDataOffer, +pub(super) struct SurfaceAndOffer { + pub(super) window_id: usize, + pub(super) offer: WlDataOffer, } -struct SurfaceAndPipe { - surface_id: u32, - read: FileDescriptor, +pub(super) struct SurfaceAndPipe { + pub(super) window_id: usize, + pub(super) read: FileDescriptor, } -pub const URI_MIME_TYPE: &str = "text/uri-list"; - impl DragAndDrop { /// Takes the current offer, if any, and initiates a receive into a pipe, /// returning that surface and pipe descriptor. - fn create_pipe_for_drop(&mut self) -> Option { - let SurfaceAndOffer { surface_id, offer } = self.offer.take()?; + pub(super) fn create_pipe_for_drop(&mut self) -> Option { + let SurfaceAndOffer { window_id, offer } = self.offer.take()?; let pipe = Pipe::new() .map_err(|err| log::error!("Unable to create pipe: {:#}", err)) .ok()?; offer.receive(URI_MIME_TYPE.to_string(), pipe.write.as_raw_fd()); let read = pipe.read; offer.finish(); - Some(SurfaceAndPipe { surface_id, read }) + Some(SurfaceAndPipe { window_id, read }) } - fn read_paths_from_pipe(read: FileDescriptor) -> Option> { + pub(super) fn read_paths_from_pipe(read: FileDescriptor) -> Option> { read_pipe_with_timeout(read) .map_err(|err| { log::error!("Error while reading pipe from drop result: {:#}", err); @@ -68,52 +67,14 @@ impl DragAndDrop { .into() } - fn dispatch_dropped_files(surface_id: u32, paths: Vec) { + pub(super) fn dispatch_dropped_files(window_id: usize, paths: Vec) { promise::spawn::spawn_into_main_thread(async move { let conn = WaylandConnection::get().unwrap().wayland(); - if let Some(&window_id) = conn.surface_to_window_id.borrow().get(&surface_id) { - if let Some(handle) = conn.window_by_id(window_id) { - let mut inner = handle.borrow_mut(); - inner.dispatch_dropped_files(paths); - } - }; + if let Some(handle) = conn.window_by_id(window_id) { + let mut inner = handle.borrow_mut(); + inner.dispatch_dropped_files(paths); + } }) .detach(); } - - pub fn handle_data_event(&mut self, event: DataDeviceEvent) { - match event { - DataDeviceEvent::Enter { - serial, - surface, - id, - .. - } => { - if let Some(offer) = id { - offer.accept(serial, Some(URI_MIME_TYPE.to_string())); - offer.set_actions(DndAction::None | DndAction::Copy, DndAction::None); - self.offer = Some(SurfaceAndOffer { - surface_id: surface.as_ref().id(), - offer, - }); - } - } - DataDeviceEvent::Leave => { - if let Some(SurfaceAndOffer { offer, .. }) = self.offer.take() { - offer.destroy(); - } - } - DataDeviceEvent::Motion { .. } => {} - DataDeviceEvent::Drop => { - if let Some(SurfaceAndPipe { surface_id, read }) = self.create_pipe_for_drop() { - std::thread::spawn(move || { - if let Some(paths) = Self::read_paths_from_pipe(read) { - Self::dispatch_dropped_files(surface_id, paths); - } - }); - } - } - _ => {} - } - } } diff --git a/window/src/os/wayland/mod.rs b/window/src/os/wayland/mod.rs index c5291db9f63..bf7cb43ba58 100644 --- a/window/src/os/wayland/mod.rs +++ b/window/src/os/wayland/mod.rs @@ -8,7 +8,7 @@ pub use self::window::*; pub use connection::*; // pub use output::*; mod copy_and_paste; -// mod drag_and_drop; +mod drag_and_drop; // mod frame; mod data_device; mod keyboard; diff --git a/window/src/os/wayland/pointer.rs b/window/src/os/wayland/pointer.rs index 10c18c16bf9..058e67517ec 100644 --- a/window/src/os/wayland/pointer.rs +++ b/window/src/os/wayland/pointer.rs @@ -11,6 +11,7 @@ use wayland_client::{Connection, Proxy, QueueHandle}; use wezterm_input_types::MousePress; use super::copy_and_paste::CopyAndPaste; +use super::drag_and_drop::DragAndDrop; use super::state::WaylandState; use super::WaylandConnection; @@ -55,10 +56,9 @@ impl PointerHandler for WaylandState { } } -#[derive(Debug)] pub(super) struct PointerUserData { pdata: PointerData, - state: Mutex, + pub(super) state: Mutex, } impl PointerUserData { @@ -70,10 +70,10 @@ impl PointerUserData { } } -#[derive(Debug, Default)] -struct PointerState { +#[derive(Default)] +pub(super) struct PointerState { active_surface_id: Option, - // TODO: drag_and_drop: DragAndDrop, + pub(super) drag_and_drop: DragAndDrop, serial: u32, } diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 661a4378126..ac3a110290c 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -3,6 +3,7 @@ use std::cell::{RefCell, RefMut}; use std::convert::TryInto; use std::io::Read; use std::os::fd::AsRawFd; +use std::path::PathBuf; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; @@ -577,6 +578,10 @@ impl WaylandWindowInner { ((pixels as f64) / self.get_dpi_factor()).ceil() as i32 } + pub(super) fn dispatch_dropped_files(&mut self, paths: Vec) { + self.events.dispatch(WindowEvent::DroppedFile(paths)); + } + pub(crate) fn dispatch_pending_mouse(&mut self) { let pending_mouse = Arc::clone(&self.pending_mouse); From 55b58eee255d95699816fdc7aa29af009c90c7b2 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:12:58 -0800 Subject: [PATCH 43/55] Ignore drag source handlers (we don't drag from wezterm) --- window/src/os/wayland/data_device.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/window/src/os/wayland/data_device.rs b/window/src/os/wayland/data_device.rs index d7aae52a623..719b69e8f75 100644 --- a/window/src/os/wayland/data_device.rs +++ b/window/src/os/wayland/data_device.rs @@ -94,7 +94,11 @@ impl DataDeviceHandler for WaylandState { _qh: &wayland_client::QueueHandle, data_device: DataDevice, ) { - // TODO: handle mime types + let mime_types = data_device.selection_mime_types(); + if !mime_types.iter().any(|s| s == TEXT_MIME_TYPE) { + return; + } + if let Some(offer) = data_device.selection_offer() { if let Some(copy_and_paste) = self.resolve_copy_and_paste() { copy_and_paste @@ -168,6 +172,7 @@ impl DataOfferHandler for WaylandState { } } +// We seem to to ignore all events other than sending_request and cancelled impl DataSourceHandler for WaylandState { fn accept_mime( &mut self, @@ -176,7 +181,6 @@ impl DataSourceHandler for WaylandState { _source: &wayland_client::protocol::wl_data_source::WlDataSource, _mime: Option, ) { - todo!() } fn send_request( @@ -216,7 +220,6 @@ impl DataSourceHandler for WaylandState { _qh: &wayland_client::QueueHandle, _source: &wayland_client::protocol::wl_data_source::WlDataSource, ) { - todo!() } fn dnd_finished( @@ -225,7 +228,6 @@ impl DataSourceHandler for WaylandState { _qh: &wayland_client::QueueHandle, _source: &wayland_client::protocol::wl_data_source::WlDataSource, ) { - todo!() } fn action( @@ -235,7 +237,6 @@ impl DataSourceHandler for WaylandState { _source: &wayland_client::protocol::wl_data_source::WlDataSource, _action: wayland_client::protocol::wl_data_device_manager::DndAction, ) { - todo!() } } From 41237cd25dc12642484094d5ebf181f992f15f13 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:08:34 -0800 Subject: [PATCH 44/55] Start IME support --- window/src/os/wayland/inputhandler.rs | 212 ++++---------------------- window/src/os/wayland/mod.rs | 2 +- window/src/os/wayland/state.rs | 7 + 3 files changed, 41 insertions(+), 180 deletions(-) diff --git a/window/src/os/wayland/inputhandler.rs b/window/src/os/wayland/inputhandler.rs index 5e280a6ebd5..2f82232282e 100644 --- a/window/src/os/wayland/inputhandler.rs +++ b/window/src/os/wayland/inputhandler.rs @@ -1,193 +1,47 @@ //! Implements zwp_text_input_v3 for handling IME -use crate::connection::ConnectionOps; -use crate::os::wayland::{wl_id, WaylandConnection}; -use crate::{DeadKeyStatus, KeyCode, KeyEvent, Modifiers, WindowEvent}; -use smithay_client_toolkit::environment::GlobalHandler; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; -use wayland_client::protocol::wl_keyboard::WlKeyboard; -use wayland_client::protocol::wl_registry::WlRegistry; -use wayland_client::protocol::wl_seat::WlSeat; -use wayland_client::protocol::wl_surface::WlSurface; -use wayland_client::{Attached, DispatchData, Main}; -use wayland_protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; -use wayland_protocols::unstable::text_input::v3::client::zwp_text_input_v3::{ - Event, ZwpTextInputV3, -}; -use wezterm_input_types::KeyboardLedStatus; +use std::sync::Mutex; -#[derive(Default, Debug)] -struct PendingState { - pre_edit: Option, - commit: Option, -} +use smithay_client_toolkit::globals::GlobalData; +use wayland_client::{Dispatch, Proxy, QueueHandle}; +use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; +use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; -#[derive(Debug, Default)] -struct Inner { - input_by_seat: HashMap>, - keyboard_to_seat: HashMap, - surface_to_keyboard: HashMap, - pending_state: HashMap, -} +use super::state::WaylandState; -impl Inner { - fn handle_event( - &mut self, - input: Main, - event: Event, - _ddata: DispatchData, - _inner: &Arc>, - ) { - log::trace!("{event:?}"); - let conn = WaylandConnection::get().unwrap().wayland(); - let pending_state = self.pending_state.entry(wl_id(&**input)).or_default(); - match event { - Event::PreeditString { - text, - cursor_begin: _, - cursor_end: _, - } => { - pending_state.pre_edit = text; - } - Event::CommitString { text } => { - pending_state.commit = text; - conn.dispatch_to_focused_window(WindowEvent::AdviseDeadKeyStatus( - DeadKeyStatus::None, - )); - } - Event::Done { serial } => { - *conn.last_serial.borrow_mut() = serial; - if let Some(text) = pending_state.commit.take() { - conn.dispatch_to_focused_window(WindowEvent::KeyEvent(KeyEvent { - key: KeyCode::composed(&text), - modifiers: Modifiers::NONE, - leds: KeyboardLedStatus::empty(), - repeat_count: 1, - key_is_down: true, - raw: None, - })); - } - let status = if let Some(text) = pending_state.pre_edit.take() { - DeadKeyStatus::Composing(text) - } else { - DeadKeyStatus::None - }; - conn.dispatch_to_focused_window(WindowEvent::AdviseDeadKeyStatus(status)); - } - _ => {} - } - } - - fn disable_all(&mut self) { - for input in self.input_by_seat.values() { - input.disable(); - input.commit(); - } - } +pub(super) struct TextInputState { + text_input_manager: ZwpTextInputManagerV3, } -pub struct InputHandler { - mgr: Option>, - inner: Arc>, +#[derive(Default)] +pub(super) struct TextInputData { + inner: Mutex, } -impl InputHandler { - pub fn new() -> Self { - Self { - mgr: None, - inner: Arc::new(Mutex::new(Inner::default())), - } - } - - pub fn get_text_input_for_keyboard( - &self, - keyboard: &WlKeyboard, - ) -> Option> { - let inner = self.inner.lock().unwrap(); - let keyboard_id = wl_id(keyboard); - let seat_id = inner.keyboard_to_seat.get(&keyboard_id)?; - inner.input_by_seat.get(&seat_id).cloned() - } - - pub fn get_text_input_for_surface( - &self, - surface: &WlSurface, - ) -> Option> { - let inner = self.inner.lock().unwrap(); - let surface_id = wl_id(surface); - let keyboard_id = inner.surface_to_keyboard.get(&surface_id)?; - let seat_id = inner.keyboard_to_seat.get(&keyboard_id)?; - inner.input_by_seat.get(&seat_id).cloned() - } - - pub fn get_text_input_for_seat(&self, seat: &WlSeat) -> Option> { - let mgr = self.mgr.as_ref()?; - let mut inner = self.inner.lock().unwrap(); - let seat_id = wl_id(seat); - let input = inner.input_by_seat.entry(seat_id).or_insert_with(|| { - let input = mgr.get_text_input(seat); - let inner = Arc::clone(&self.inner); - - input.quick_assign(move |input, event, ddat| { - inner - .lock() - .unwrap() - .handle_event(input, event, ddat, &inner); - }); - - input.into() - }); - Some(input.clone()) - } - - pub fn advise_surface(&self, surface: &WlSurface, keyboard: &WlKeyboard) { - let surface_id = wl_id(surface); - let keyboard_id = wl_id(keyboard); - self.inner - .lock() - .unwrap() - .surface_to_keyboard - .insert(surface_id, keyboard_id); - } - - pub fn advise_seat(&self, seat: &WlSeat, keyboard: &WlKeyboard) { - self.get_text_input_for_seat(seat); - let keyboard_id = wl_id(keyboard); - let seat_id = wl_id(seat); - self.inner - .lock() - .unwrap() - .keyboard_to_seat - .insert(keyboard_id, seat_id); - } - - /// Workaround for - /// If we make sure to disable things before we close the app, - /// mutter is less likely to get in a bad state - pub fn shutdown(&self) { - self.inner.lock().unwrap().disable_all(); - } - - pub fn seat_defunct(&self, seat: &WlSeat) { - let seat_id = wl_id(seat); - self.inner.lock().unwrap().input_by_seat.remove(&seat_id); +#[derive(Default)] +pub(super) struct TextInputDataInner {} + +impl Dispatch for TextInputState { + fn event( + _state: &mut WaylandState, + _proxy: &ZwpTextInputManagerV3, + _event: ::Event, + _data: &GlobalData, + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle, + ) { + todo!() } } -impl GlobalHandler for InputHandler { - fn created( - &mut self, - registry: Attached, - id: u32, - version: u32, - _ddata: DispatchData, +impl Dispatch for TextInputState { + fn event( + _state: &mut WaylandState, + _proxy: &ZwpTextInputV3, + _event: ::Event, + _data: &TextInputData, + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle, ) { - log::debug!("created ZwpTextInputV3 {id} {version}"); - let mgr = registry.bind::(1, id); - self.mgr.replace(mgr.into()); - } - - fn get(&self) -> std::option::Option> { - self.mgr.clone() + todo!() } } diff --git a/window/src/os/wayland/mod.rs b/window/src/os/wayland/mod.rs index bf7cb43ba58..7bfe7c39b7e 100644 --- a/window/src/os/wayland/mod.rs +++ b/window/src/os/wayland/mod.rs @@ -1,7 +1,7 @@ #![cfg(all(unix, not(target_os = "macos")))] pub mod connection; -// pub mod inputhandler; +pub mod inputhandler; // pub mod output; pub mod window; pub use self::window::*; diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index 419b506442e..67b49865d67 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -7,6 +7,7 @@ use smithay_client_toolkit::compositor::CompositorState; use smithay_client_toolkit::data_device_manager::data_device::DataDevice; use smithay_client_toolkit::data_device_manager::data_source::CopyPasteSource; use smithay_client_toolkit::data_device_manager::DataDeviceManagerState; +use smithay_client_toolkit::globals::GlobalData; use smithay_client_toolkit::output::{OutputHandler, OutputState}; use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState}; use smithay_client_toolkit::seat::pointer::ThemedPointer; @@ -26,9 +27,12 @@ use wayland_client::protocol::wl_output::WlOutput; use wayland_client::protocol::wl_pointer::WlPointer; use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::{delegate_dispatch, Connection, QueueHandle}; +use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; +use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; use crate::x11::KeyboardWithFallback; +use super::inputhandler::{TextInputData, TextInputState}; use super::pointer::{PendingMouse, PointerUserData}; use super::{SurfaceUserData, WaylandWindowInner}; @@ -147,3 +151,6 @@ delegate_dispatch!(WaylandState: [WlPointer: PointerUserData] => SeatState); delegate_xdg_shell!(WaylandState); delegate_xdg_window!(WaylandState); + +delegate_dispatch!(WaylandState: [ZwpTextInputManagerV3: GlobalData] => TextInputState); +delegate_dispatch!(WaylandState: [ZwpTextInputV3: TextInputData] => TextInputState); From 2652394aa8100f3d5aaba2bceba1e916d887c2b3 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Mon, 22 Jan 2024 23:21:28 -0800 Subject: [PATCH 45/55] Implement inputhandler --- window/src/os/wayland/inputhandler.rs | 172 ++++++++++++++++++++++++-- window/src/os/wayland/keyboard.rs | 12 +- window/src/os/wayland/seat.rs | 2 + window/src/os/wayland/state.rs | 2 + window/src/os/wayland/window.rs | 35 ++++++ 5 files changed, 214 insertions(+), 9 deletions(-) diff --git a/window/src/os/wayland/inputhandler.rs b/window/src/os/wayland/inputhandler.rs index 2f82232282e..315c8c9fa12 100644 --- a/window/src/os/wayland/inputhandler.rs +++ b/window/src/os/wayland/inputhandler.rs @@ -1,20 +1,117 @@ //! Implements zwp_text_input_v3 for handling IME +use std::borrow::Borrow; +use std::collections::HashMap; use std::sync::Mutex; use smithay_client_toolkit::globals::GlobalData; +use wayland_client::backend::ObjectId; +use wayland_client::globals::{BindError, GlobalList}; +use wayland_client::protocol::wl_keyboard::WlKeyboard; +use wayland_client::protocol::wl_seat::WlSeat; +use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::{Dispatch, Proxy, QueueHandle}; use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; -use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; +use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::{ + Event as TextInputEvent, ZwpTextInputV3, +}; +use wezterm_input_types::{KeyCode, KeyEvent, KeyboardLedStatus, Modifiers}; + +use crate::{DeadKeyStatus, WindowEvent}; use super::state::WaylandState; +#[derive(Clone, Default, Debug)] +struct PendingState { + pre_edit: Option, + commit: Option, +} + pub(super) struct TextInputState { text_input_manager: ZwpTextInputManagerV3, + inner: Mutex, +} + +#[derive(Debug, Default)] +struct Inner { + input_by_seat: HashMap, + keyboard_to_seat: HashMap, + surface_to_keyboard: HashMap, + pending_state: HashMap, +} + +impl TextInputState { + pub(super) fn bind( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + let text_input_manager = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { + text_input_manager, + inner: Mutex::new(Inner::default()), + }) + } + + pub fn get_text_input_for_keyboard(&self, keyboard: &WlKeyboard) -> Option { + let inner = self.inner.lock().unwrap(); + let keyboard_id = keyboard.id(); + let seat_id = inner.keyboard_to_seat.get(&keyboard_id)?; + inner.input_by_seat.get(&seat_id).cloned() + } + + pub(super) fn get_text_input_for_surface(&self, surface: &WlSurface) -> Option { + let inner = self.inner.lock().unwrap(); + let surface_id = surface.id(); + let keyboard_id = inner.surface_to_keyboard.get(&surface_id)?; + let seat_id = inner.keyboard_to_seat.get(&keyboard_id)?; + inner.input_by_seat.get(&seat_id).cloned() + } + + fn get_text_input_for_seat( + &self, + seat: &WlSeat, + qh: &QueueHandle, + ) -> Option { + let mgr = &self.text_input_manager; + let mut inner = self.inner.lock().unwrap(); + let seat_id = seat.id(); + let input = inner.input_by_seat.entry(seat_id).or_insert_with(|| { + let input = mgr.get_text_input(seat, &qh, TextInputData::default()); + input.into() + }); + Some(input.clone()) + } + + pub(super) fn advise_surface(&self, surface: &WlSurface, keyboard: &WlKeyboard) { + let surface_id = surface.id(); + let keyboard_id = keyboard.id(); + self.inner + .lock() + .unwrap() + .surface_to_keyboard + .insert(surface_id, keyboard_id); + } + + pub(super) fn advise_seat( + &self, + seat: &WlSeat, + keyboard: &WlKeyboard, + qh: &QueueHandle, + ) { + self.get_text_input_for_seat(seat, qh); + let keyboard_id = keyboard.id(); + let seat_id = seat.id(); + self.inner + .lock() + .unwrap() + .keyboard_to_seat + .insert(keyboard_id, seat_id); + } } #[derive(Default)] pub(super) struct TextInputData { - inner: Mutex, + // XXX: inner could probably be moved here + _inner: Mutex, } #[derive(Default)] @@ -29,19 +126,80 @@ impl Dispatch for TextInputStat _conn: &wayland_client::Connection, _qhandle: &QueueHandle, ) { - todo!() + // No events from ZwpTextInputMangerV3 + unreachable!(); } } impl Dispatch for TextInputState { fn event( - _state: &mut WaylandState, - _proxy: &ZwpTextInputV3, - _event: ::Event, + state: &mut WaylandState, + input: &ZwpTextInputV3, + event: ::Event, _data: &TextInputData, _conn: &wayland_client::Connection, _qhandle: &QueueHandle, ) { - todo!() + log::trace!("ZwpTextInputEvent: {event:?}"); + let mut pending_state = { + let text_input = &mut state.text_input; + let mut inner = text_input.inner.lock().unwrap(); + inner.pending_state.entry(input.id()).or_default().clone() + }; + + match event { + TextInputEvent::PreeditString { + text, + cursor_begin: _, + cursor_end: _, + } => { + pending_state.pre_edit = text; + } + TextInputEvent::CommitString { text } => { + pending_state.commit = text; + state.dispatch_to_focused_window(WindowEvent::AdviseDeadKeyStatus( + DeadKeyStatus::None, + )); + } + TextInputEvent::Done { serial } => { + *state.last_serial.borrow_mut() = serial; + if let Some(text) = pending_state.commit.take() { + state.dispatch_to_focused_window(WindowEvent::KeyEvent(KeyEvent { + key: KeyCode::composed(&text), + modifiers: Modifiers::NONE, + leds: KeyboardLedStatus::empty(), + repeat_count: 1, + key_is_down: true, + raw: None, + })); + } + let status = if let Some(text) = pending_state.pre_edit.take() { + DeadKeyStatus::Composing(text) + } else { + DeadKeyStatus::None + }; + state.dispatch_to_focused_window(WindowEvent::AdviseDeadKeyStatus(status)); + } + _ => {} + } + + state + .text_input + .inner + .lock() + .unwrap() + .pending_state + .insert(input.id(), pending_state); + } +} + +impl WaylandState { + fn dispatch_to_focused_window(&self, event: WindowEvent) { + if let Some(&window_id) = self.keyboard_window_id.borrow().as_ref() { + if let Some(win) = self.window_by_id(window_id) { + let mut inner = win.borrow_mut(); + inner.events.dispatch(event); + } + } } } diff --git a/window/src/os/wayland/keyboard.rs b/window/src/os/wayland/keyboard.rs index e4324c2b032..a831d29c316 100644 --- a/window/src/os/wayland/keyboard.rs +++ b/window/src/os/wayland/keyboard.rs @@ -15,7 +15,7 @@ use super::SurfaceUserData; impl Dispatch for WaylandState { fn event( state: &mut WaylandState, - _proxy: &WlKeyboard, + keyboard: &WlKeyboard, event: ::Event, _data: &KeyboardData, _conn: &wayland_client::Connection, @@ -31,13 +31,21 @@ impl Dispatch for WaylandState { if let Some(sud) = SurfaceUserData::try_from_wl(&surface) { let window_id = sud.window_id; state.keyboard_window_id.borrow_mut().replace(window_id); + if let Some(input) = state.text_input.get_text_input_for_keyboard(keyboard) { + input.enable(); + input.commit(); + } + state.text_input.advise_surface(surface, keyboard); } else { log::warn!("{:?}, no known surface", event); } } WlKeyboardEvent::Leave { serial, .. } => { - // TODO: I know nothing about the input handler currently *state.last_serial.borrow_mut() = *serial; + if let Some(input) = state.text_input.get_text_input_for_keyboard(keyboard) { + input.disable(); + input.commit(); + } } WlKeyboardEvent::Key { serial, .. } | WlKeyboardEvent::Modifiers { serial, .. } => { *state.last_serial.borrow_mut() = *serial; diff --git a/window/src/os/wayland/seat.rs b/window/src/os/wayland/seat.rs index 0e3bac62312..198a61600ee 100644 --- a/window/src/os/wayland/seat.rs +++ b/window/src/os/wayland/seat.rs @@ -28,6 +28,8 @@ impl SeatHandler for WaylandState { log::trace!("Setting keyboard capability"); let keyboard = seat.get_keyboard(qh, KeyboardData {}); self.keyboard = Some(keyboard.clone()); + + self.text_input.advise_seat(&seat, &keyboard, qh); } if capability == Capability::Pointer && self.pointer.is_none() { diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index 67b49865d67..c97fde2e5a3 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -42,6 +42,7 @@ pub(super) struct WaylandState { registry: RegistryState, pub(super) output: OutputState, pub(super) compositor: CompositorState, + pub(super) text_input: TextInputState, pub(super) seat: SeatState, pub(super) xdg: XdgShell, pub(super) windows: RefCell>>>, @@ -72,6 +73,7 @@ impl WaylandState { registry: RegistryState::new(globals), output: OutputState::new(globals, qh), compositor: CompositorState::bind(globals, qh)?, + text_input: TextInputState::bind(globals, qh)?, windows: RefCell::new(HashMap::new()), seat: SeatState::new(globals, qh), xdg: XdgShell::bind(globals, qh)?, diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index ac3a110290c..65cb72c60fb 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -354,6 +354,13 @@ impl WindowOps for WaylandWindow { }); } + fn set_text_cursor_position(&self, cursor: Rect) { + WaylandConnection::with_window_inner(self.0, move |inner| { + inner.set_text_cursor_position(cursor); + Ok(()) + }); + } + fn set_title(&self, title: &str) { let title = title.to_owned(); WaylandConnection::with_window_inner(self.0, |inner| { @@ -851,6 +858,34 @@ impl WaylandWindowInner { self.do_paint().unwrap(); } + fn set_text_cursor_position(&mut self, rect: Rect) { + let conn = WaylandConnection::get().unwrap().wayland(); + let state = &conn.wayland_state.borrow(); + let surface = self.surface().clone(); + + let surface_id = surface.id(); + let active_surface_id = state.active_surface_id.borrow().as_ref().unwrap().clone(); + + if surface_id == active_surface_id { + if self.text_cursor.map(|prior| prior != rect).unwrap_or(true) { + self.text_cursor.replace(rect); + + let surface_udata = SurfaceUserData::from_wl(&surface); + let factor = surface_udata.surface_data().scale_factor(); + + if let Some(input) = state.text_input.get_text_input_for_surface(&surface) { + input.set_cursor_rectangle( + rect.min_x() as i32 / factor, + rect.min_y() as i32 / factor, + rect.width() as i32 / factor, + rect.height() as i32 / factor, + ); + input.commit(); + } + } + } + } + fn set_title(&mut self, title: String) { if let Some(last_title) = self.title.as_ref() { if last_title == &title { From 312a52898b3d83eb6e5e4ea8e6e596f60eba48f5 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Tue, 23 Jan 2024 00:44:22 -0800 Subject: [PATCH 46/55] Implement shutdown for ime --- window/src/os/wayland/inputhandler.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/window/src/os/wayland/inputhandler.rs b/window/src/os/wayland/inputhandler.rs index 315c8c9fa12..be74fa8f6f0 100644 --- a/window/src/os/wayland/inputhandler.rs +++ b/window/src/os/wayland/inputhandler.rs @@ -106,6 +106,22 @@ impl TextInputState { .keyboard_to_seat .insert(keyboard_id, seat_id); } + + /// Workaround for + /// If we make sure to disable things before we close the app, + /// mutter is less likely to get in a bad state + pub fn shutdown(&self) { + self.inner.lock().unwrap().disable_all(); + } +} + +impl Inner { + fn disable_all(&mut self) { + for input in self.input_by_seat.values() { + input.disable(); + input.commit(); + } + } } #[derive(Default)] @@ -203,3 +219,9 @@ impl WaylandState { } } } + +impl Drop for WaylandState { + fn drop(&mut self) { + self.text_input.shutdown(); + } +} From 2a6e6ab5cc570f71c6638bc8a9d55a8532d1d8a0 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:15:58 -0800 Subject: [PATCH 47/55] Implement wlr-output-management-unstable --- window/src/os/wayland/connection.rs | 16 +- window/src/os/wayland/mod.rs | 18 +- window/src/os/wayland/output.rs | 355 +++++++++++++--------------- window/src/os/wayland/state.rs | 15 +- 4 files changed, 193 insertions(+), 211 deletions(-) diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs index 33c5c5c16d3..f917bdfcadd 100644 --- a/window/src/os/wayland/connection.rs +++ b/window/src/os/wayland/connection.rs @@ -173,16 +173,14 @@ impl ConnectionOps for WaylandConnection { } fn screens(&self) -> anyhow::Result { - // TODO: implement for the outputhandler - // if let Some(screens) = self - // .environment - // .with_inner(|env| env.output_handler.screens()) - // { - // return Ok(screens); - // } - // - log::trace!("Getting screens for wayland connection"); + + if let Some(output_manager) = &self.wayland_state.borrow().output_manager { + if let Some(screens) = output_manager.screens() { + return Ok(screens); + } + } + let mut by_name = HashMap::new(); let mut virtual_rect: ScreenRect = euclid::rect(0, 0, 0, 0); let config = config::configuration(); diff --git a/window/src/os/wayland/mod.rs b/window/src/os/wayland/mod.rs index 7bfe7c39b7e..0a88143c832 100644 --- a/window/src/os/wayland/mod.rs +++ b/window/src/os/wayland/mod.rs @@ -2,11 +2,11 @@ pub mod connection; pub mod inputhandler; -// pub mod output; +pub mod output; pub mod window; pub use self::window::*; pub use connection::*; -// pub use output::*; +pub use output::*; mod copy_and_paste; mod drag_and_drop; // mod frame; @@ -15,17 +15,3 @@ mod keyboard; mod pointer; mod seat; mod state; - -/// Returns the id of a wayland proxy object, suitable for using -/// a key into hash maps -pub fn todo() {} -// pub fn wl_id(obj: T) -> u32 -// where -// I: wayland_client::Interface, -// T: AsRef>, -// I: AsRef>, -// I: From>, -// { -// let proxy: &wayland_client::Proxy = obj.as_ref(); -// proxy.id() -// } diff --git a/window/src/os/wayland/output.rs b/window/src/os/wayland/output.rs index 9970d9af405..7928a8f2f01 100644 --- a/window/src/os/wayland/output.rs +++ b/window/src/os/wayland/output.rs @@ -1,27 +1,23 @@ //! Dealing with Wayland outputs -use crate::os::wayland::wl_id; use crate::screen::{ScreenInfo, Screens}; use crate::ScreenRect; -use smithay_client_toolkit::environment::GlobalHandler; +use smithay_client_toolkit::globals::GlobalData; +use smithay_client_toolkit::reexports::protocols_wlr::output_management::v1::client::zwlr_output_head_v1::{ZwlrOutputHeadV1, self, Event as ZwlrOutputHeadEvent}; +use smithay_client_toolkit::reexports::protocols_wlr::output_management::v1::client::zwlr_output_manager_v1::{ZwlrOutputManagerV1, self, Event as ZwlrOutputEvent}; +use smithay_client_toolkit::reexports::protocols_wlr::output_management::v1::client::zwlr_output_mode_v1::{ZwlrOutputModeV1, Event as ZwlrOutputModeEvent}; +use wayland_client::{Dispatch, event_created_child, Proxy}; +use wayland_client::globals::{GlobalList, BindError}; use std::collections::HashMap; -use std::sync::{Arc, Mutex}; +use std::sync::Mutex; +use wayland_client::backend::ObjectId; use wayland_client::protocol::wl_output::Transform; -use wayland_client::protocol::wl_registry::WlRegistry; -use wayland_client::{Attached, DispatchData, Main}; -use wayland_protocols::wlr::unstable::output_management::v1::client::zwlr_output_head_v1::{ - Event as ZwlrOutputHeadEvent, ZwlrOutputHeadV1, -}; -use wayland_protocols::wlr::unstable::output_management::v1::client::zwlr_output_manager_v1::{ - Event as ZwlrOutputEvent, ZwlrOutputManagerV1, -}; -use wayland_protocols::wlr::unstable::output_management::v1::client::zwlr_output_mode_v1::{ - Event as ZwlrOutputModeEvent, ZwlrOutputModeV1, -}; + +use super::state::WaylandState; #[derive(Debug, Default, Clone)] pub struct ModeInfo { - pub id: u32, + pub id: Option, pub width: i32, pub height: i32, pub refresh: i32, @@ -30,15 +26,15 @@ pub struct ModeInfo { #[derive(Debug, Default, Clone)] pub struct HeadInfo { - pub id: u32, + pub id: Option, pub name: String, pub description: String, pub physical_width: i32, pub physical_height: i32, /// List of ids that correspond to ModeInfo's - pub mode_ids: Vec, + pub mode_ids: Vec, pub enabled: bool, - pub current_mode_id: Option, + pub current_mode_id: Option, pub x: i32, pub y: i32, pub transform: Option, @@ -50,155 +46,27 @@ pub struct HeadInfo { #[derive(Default, Debug)] struct Inner { - zwlr_heads: HashMap>, - zwlr_modes: HashMap>, - zwlr_mode_info: HashMap, - zwlr_head_info: HashMap, -} - -impl Inner { - fn handle_zwlr_mode_event( - &mut self, - mode: Main, - event: ZwlrOutputModeEvent, - _ddata: DispatchData, - _inner: &Arc>, - ) { - log::debug!("handle_zwlr_mode_event {event:?}"); - let id = wl_id(mode.detach()); - let info = self.zwlr_mode_info.entry(id).or_insert_with(|| ModeInfo { - id, - ..ModeInfo::default() - }); - - match event { - ZwlrOutputModeEvent::Size { width, height } => { - info.width = width; - info.height = height; - } - ZwlrOutputModeEvent::Refresh { refresh } => { - info.refresh = refresh; - } - ZwlrOutputModeEvent::Preferred => { - info.preferred = true; - } - ZwlrOutputModeEvent::Finished => { - self.zwlr_mode_info.remove(&id); - self.zwlr_modes.remove(&id); - } - _ => {} - } - } - - fn handle_zwlr_head_event( - &mut self, - head: Main, - event: ZwlrOutputHeadEvent, - _ddata: DispatchData, - inner: &Arc>, - ) { - log::debug!("handle_zwlr_head_event {event:?}"); - let id = wl_id(head.detach()); - let info = self.zwlr_head_info.entry(id).or_insert_with(|| HeadInfo { - id, - ..HeadInfo::default() - }); - match event { - ZwlrOutputHeadEvent::Name { name } => { - info.name = name; - } - ZwlrOutputHeadEvent::Description { description } => { - info.description = description; - } - ZwlrOutputHeadEvent::PhysicalSize { width, height } => { - info.physical_width = width; - info.physical_height = height; - } - ZwlrOutputHeadEvent::Mode { mode } => { - let inner = Arc::clone(inner); - mode.quick_assign(move |mode, event, ddata| { - inner - .lock() - .unwrap() - .handle_zwlr_mode_event(mode, event, ddata, &inner); - }); - let mode_id = wl_id(mode.detach()); - info.mode_ids.push(mode_id); - self.zwlr_modes.insert(mode_id, mode.into()); - } - ZwlrOutputHeadEvent::Enabled { enabled } => { - info.enabled = enabled != 0; - } - ZwlrOutputHeadEvent::CurrentMode { mode } => { - let mode_id = wl_id(mode); - info.current_mode_id.replace(mode_id); - } - ZwlrOutputHeadEvent::Position { x, y } => { - info.x = x; - info.y = y; - } - ZwlrOutputHeadEvent::Transform { transform } => { - info.transform.replace(transform); - } - ZwlrOutputHeadEvent::Scale { scale } => { - info.scale = scale; - } - ZwlrOutputHeadEvent::Make { make } => { - info.make = make; - } - ZwlrOutputHeadEvent::Model { model } => { - info.model = model; - } - ZwlrOutputHeadEvent::SerialNumber { serial_number } => { - info.serial_number = serial_number; - } - ZwlrOutputHeadEvent::Finished => { - log::debug!("remove head with id {id}"); - self.zwlr_heads.remove(&id); - self.zwlr_head_info.remove(&id); - } - - _ => {} - } - } - - fn handle_zwlr_output_event( - &mut self, - _output: Main, - event: ZwlrOutputEvent, - _ddata: DispatchData, - inner: &Arc>, - ) { - log::debug!("handle_zwlr_output_event {event:?}"); - match event { - ZwlrOutputEvent::Head { head } => { - let inner = Arc::clone(inner); - head.quick_assign(move |output, event, ddata| { - inner - .lock() - .unwrap() - .handle_zwlr_head_event(output, event, ddata, &inner); - }); - self.zwlr_heads.insert(wl_id(head.detach()), head.into()); - } - ZwlrOutputEvent::Done { serial: _ } => {} - ZwlrOutputEvent::Finished => {} - _ => {} - } - } + zwlr_heads: HashMap, + zwlr_modes: HashMap, + zwlr_mode_info: HashMap, + zwlr_head_info: HashMap, } -pub struct OutputHandler { - zwlr: Option>, - inner: Arc>, +pub struct OutputManagerState { + _zwlr: ZwlrOutputManagerV1, + inner: Mutex, } -impl OutputHandler { - pub fn new() -> Self { - Self { - zwlr: None, - inner: Arc::new(Mutex::new(Inner::default())), - } +impl OutputManagerState { + pub(super) fn bind( + globals: &GlobalList, + queue_handle: &wayland_client::QueueHandle, + ) -> Result { + let _zwlr = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { + _zwlr, + inner: Mutex::new(Inner::default()), + }) } pub fn screens(&self) -> Option { @@ -212,7 +80,7 @@ impl OutputHandler { for head in inner.zwlr_head_info.values() { let name = head.name.clone(); - let (width, height) = match head.current_mode_id { + let (width, height) = match head.current_mode_id.clone() { Some(mode_id) => match inner.zwlr_mode_info.get(&mode_id) { Some(mode) => (mode.width, mode.height), None => continue, @@ -275,32 +143,149 @@ impl OutputHandler { } } -impl GlobalHandler for OutputHandler { - fn created( - &mut self, - registry: Attached, - id: u32, - version: u32, - _ddata: DispatchData, +#[derive(Default)] +pub(super) struct OutputManagerData {} + +impl Dispatch for OutputManagerState { + fn event( + state: &mut WaylandState, + _proxy: &ZwlrOutputManagerV1, + event: ::Event, + _data: &GlobalData, + _conn: &wayland_client::Connection, + _qhandle: &wayland_client::QueueHandle, ) { - if !config::configuration().enable_zwlr_output_manager { - return; + log::debug!("handle_zwlr_output_event {event:?}"); + let mut inner = state.output_manager.unwrap().inner.lock().unwrap(); + + match event { + ZwlrOutputEvent::Head { head } => { + inner.zwlr_heads.insert(head.id(), head); + } + _ => {} } - log::debug!("created ZwlrOutputManagerV1 {id} {version}"); - let zwlr = registry.bind::(2, id); + } - let inner = Arc::clone(&self.inner); - zwlr.quick_assign(move |output, event, ddata| { - inner - .lock() - .unwrap() - .handle_zwlr_output_event(output, event, ddata, &inner); - }); + event_created_child!(WaylandState, ZwlrOutputManagerV1, [ + zwlr_output_manager_v1::EVT_HEAD_OPCODE => (ZwlrOutputHeadV1, OutputManagerData::default()) + ]); +} + +impl Dispatch for OutputManagerState { + fn event( + state: &mut WaylandState, + head: &ZwlrOutputHeadV1, + event: ::Event, + _data: &OutputManagerData, + _conn: &wayland_client::Connection, + _qhandle: &wayland_client::QueueHandle, + ) { + log::debug!("handle_zwlr_head_event {event:?}"); + + let mut inner = state.output_manager.unwrap().inner.lock().unwrap(); + let id = head.id(); + let info = inner + .zwlr_head_info + .entry(id.clone()) + .or_insert_with(|| HeadInfo { + id: Some(id.clone()), + ..HeadInfo::default() + }); - self.zwlr.replace(zwlr.into()); + match event { + ZwlrOutputHeadEvent::Name { name } => { + info.name = name; + } + ZwlrOutputHeadEvent::Description { description } => { + info.description = description; + } + ZwlrOutputHeadEvent::PhysicalSize { width, height } => { + info.physical_width = width; + info.physical_height = height; + } + ZwlrOutputHeadEvent::Mode { mode } => { + let mode_id = mode.id(); + info.mode_ids.push(mode_id.clone().into()); + inner.zwlr_modes.insert(mode_id, mode.into()); + } + ZwlrOutputHeadEvent::Enabled { enabled } => { + info.enabled = enabled != 0; + } + ZwlrOutputHeadEvent::CurrentMode { mode } => { + let mode_id = mode.id(); + info.current_mode_id.replace(mode_id); + } + ZwlrOutputHeadEvent::Position { x, y } => { + info.x = x; + info.y = y; + } + ZwlrOutputHeadEvent::Transform { transform } => { + info.transform = transform.into_result().ok(); + } + ZwlrOutputHeadEvent::Scale { scale } => { + info.scale = scale; + } + ZwlrOutputHeadEvent::Make { make } => { + info.make = make; + } + ZwlrOutputHeadEvent::Model { model } => { + info.model = model; + } + ZwlrOutputHeadEvent::SerialNumber { serial_number } => { + info.serial_number = serial_number; + } + ZwlrOutputHeadEvent::Finished => { + log::debug!("remove head with id {id}"); + inner.zwlr_heads.remove(&id); + inner.zwlr_head_info.remove(&id); + } + _ => {} + } } - fn get(&self) -> std::option::Option> { - self.zwlr.clone() + event_created_child!(WaylandState, ZwlrOutputModeV1, [ + zwlr_output_head_v1::EVT_CURRENT_MODE_OPCODE => (ZwlrOutputModeV1, OutputManagerData::default()), + zwlr_output_head_v1::EVT_MODE_OPCODE => (ZwlrOutputModeV1, OutputManagerData::default()), + ]); +} + +impl Dispatch for OutputManagerState { + fn event( + state: &mut WaylandState, + mode: &ZwlrOutputModeV1, + event: ::Event, + _data: &OutputManagerData, + _conn: &wayland_client::Connection, + _qhandle: &wayland_client::QueueHandle, + ) { + log::debug!("handle_zwlr_mode_event {event:?}"); + let mut inner = state.output_manager.unwrap().inner.lock().unwrap(); + + let id = mode.id(); + let info = inner + .zwlr_mode_info + .entry(id.clone()) + .or_insert_with(|| ModeInfo { + id: Some(id.clone()), + ..ModeInfo::default() + }); + + match event { + ZwlrOutputModeEvent::Size { width, height } => { + info.width = width; + info.height = height; + } + ZwlrOutputModeEvent::Refresh { refresh } => { + info.refresh = refresh; + } + ZwlrOutputModeEvent::Preferred => { + info.preferred = true; + } + ZwlrOutputModeEvent::Finished => { + inner.zwlr_mode_info.remove(&id); + inner.zwlr_modes.remove(&id); + } + _ => {} + } } } diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index c97fde2e5a3..ae46eb5e55d 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -9,6 +9,9 @@ use smithay_client_toolkit::data_device_manager::data_source::CopyPasteSource; use smithay_client_toolkit::data_device_manager::DataDeviceManagerState; use smithay_client_toolkit::globals::GlobalData; use smithay_client_toolkit::output::{OutputHandler, OutputState}; +use smithay_client_toolkit::reexports::protocols_wlr::output_management::v1::client::zwlr_output_head_v1::ZwlrOutputHeadV1; +use smithay_client_toolkit::reexports::protocols_wlr::output_management::v1::client::zwlr_output_manager_v1::ZwlrOutputManagerV1; +use smithay_client_toolkit::reexports::protocols_wlr::output_management::v1::client::zwlr_output_mode_v1::ZwlrOutputModeV1; use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState}; use smithay_client_toolkit::seat::pointer::ThemedPointer; use smithay_client_toolkit::seat::SeatState; @@ -34,7 +37,7 @@ use crate::x11::KeyboardWithFallback; use super::inputhandler::{TextInputData, TextInputState}; use super::pointer::{PendingMouse, PointerUserData}; -use super::{SurfaceUserData, WaylandWindowInner}; +use super::{OutputManagerData, OutputManagerState, SurfaceUserData, WaylandWindowInner}; // We can't combine WaylandState and WaylandConnection together because // the run_message_loop has &self(WaylandConnection) and needs to update WaylandState as mut @@ -43,6 +46,7 @@ pub(super) struct WaylandState { pub(super) output: OutputState, pub(super) compositor: CompositorState, pub(super) text_input: TextInputState, + pub(super) output_manager: Option, pub(super) seat: SeatState, pub(super) xdg: XdgShell, pub(super) windows: RefCell>>>, @@ -74,6 +78,11 @@ impl WaylandState { output: OutputState::new(globals, qh), compositor: CompositorState::bind(globals, qh)?, text_input: TextInputState::bind(globals, qh)?, + output_manager: if config::configuration().enable_zwlr_output_manager { + Some(OutputManagerState::bind(globals, qh)?) + } else { + None + }, windows: RefCell::new(HashMap::new()), seat: SeatState::new(globals, qh), xdg: XdgShell::bind(globals, qh)?, @@ -156,3 +165,7 @@ delegate_xdg_window!(WaylandState); delegate_dispatch!(WaylandState: [ZwpTextInputManagerV3: GlobalData] => TextInputState); delegate_dispatch!(WaylandState: [ZwpTextInputV3: TextInputData] => TextInputState); + +delegate_dispatch!(WaylandState: [ZwlrOutputManagerV1: GlobalData] => OutputManagerState); +delegate_dispatch!(WaylandState: [ZwlrOutputHeadV1: OutputManagerData] => OutputManagerState); +delegate_dispatch!(WaylandState: [ZwlrOutputModeV1: OutputManagerData] => OutputManagerState); From 508169e72adabf554bedda56b960bf5a9a726248 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:43:35 -0800 Subject: [PATCH 48/55] Fix making wlr-output-management-unstable optional --- window/src/os/wayland/output.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/window/src/os/wayland/output.rs b/window/src/os/wayland/output.rs index 7928a8f2f01..0cb64f551ab 100644 --- a/window/src/os/wayland/output.rs +++ b/window/src/os/wayland/output.rs @@ -156,7 +156,7 @@ impl Dispatch for OutputManagerSt _qhandle: &wayland_client::QueueHandle, ) { log::debug!("handle_zwlr_output_event {event:?}"); - let mut inner = state.output_manager.unwrap().inner.lock().unwrap(); + let mut inner = state.output_manager.as_mut().unwrap().inner.lock().unwrap(); match event { ZwlrOutputEvent::Head { head } => { @@ -182,7 +182,7 @@ impl Dispatch for OutputManag ) { log::debug!("handle_zwlr_head_event {event:?}"); - let mut inner = state.output_manager.unwrap().inner.lock().unwrap(); + let mut inner = state.output_manager.as_mut().unwrap().inner.lock().unwrap(); let id = head.id(); let info = inner .zwlr_head_info @@ -259,7 +259,7 @@ impl Dispatch for OutputManag _qhandle: &wayland_client::QueueHandle, ) { log::debug!("handle_zwlr_mode_event {event:?}"); - let mut inner = state.output_manager.unwrap().inner.lock().unwrap(); + let mut inner = state.output_manager.as_mut().unwrap().inner.lock().unwrap(); let id = mode.id(); let info = inner From 660639b35df4334f9fedeb771a60a45a726a5777 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:54:32 -0800 Subject: [PATCH 49/55] Implement primary selection support --- window/src/os/wayland/copy_and_paste.rs | 270 ++++++++++++++++++++++-- window/src/os/wayland/data_device.rs | 40 +--- window/src/os/wayland/seat.rs | 7 + window/src/os/wayland/state.rs | 16 ++ 4 files changed, 275 insertions(+), 58 deletions(-) diff --git a/window/src/os/wayland/copy_and_paste.rs b/window/src/os/wayland/copy_and_paste.rs index 81b00855cd2..ad83e462b68 100644 --- a/window/src/os/wayland/copy_and_paste.rs +++ b/window/src/os/wayland/copy_and_paste.rs @@ -1,7 +1,15 @@ -use anyhow::{anyhow, Error}; +use anyhow::{anyhow, Error, bail}; use filedescriptor::{FileDescriptor, Pipe}; use smithay_client_toolkit as toolkit; -use std::os::fd::AsRawFd; +use toolkit::globals::GlobalData; +use wayland_client::{Dispatch, event_created_child}; +use wayland_client::globals::{GlobalList, BindError}; +use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1; +use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_device_v1::{ZwpPrimarySelectionDeviceV1, self, Event as PrimarySelectionDeviceEvent}; +use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::{ZwpPrimarySelectionOfferV1, Event as PrimarySelectionOfferEvent}; +use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_source_v1::{ZwpPrimarySelectionSourceV1, Event as PrimarySelectionSourceEvent}; +use std::io::Write; +use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd}; use std::sync::{Arc, Mutex}; use toolkit::reexports::client::protocol::wl_data_offer::WlDataOffer; @@ -30,32 +38,71 @@ impl CopyAndPaste { pub(super) fn get_clipboard_data( &mut self, - _clipboard: Clipboard, + clipboard: Clipboard, ) -> anyhow::Result { - // TODO; primary selection - let offer = self - .data_offer - .as_ref() - .ok_or_else(|| anyhow!("no data offer"))?; - let pipe = Pipe::new().map_err(Error::msg)?; - offer.receive(TEXT_MIME_TYPE.to_string(), pipe.write.as_raw_fd()); - Ok(pipe.read) - } + let conn = crate::Connection::get().unwrap().wayland(); + let wayland_state = conn.wayland_state.borrow(); + let primary_selection = if let Clipboard::PrimarySelection = clipboard { + wayland_state.primary_selection_manager.as_ref() + } else { + None + }; - pub(super) fn set_clipboard_data(&mut self, _clipboard: Clipboard, data: String) { - // TODO: primary selection + match primary_selection { + Some(primary_selection) => { + let inner = primary_selection.inner.lock().unwrap(); + let offer = inner + .offer + .as_ref() + .ok_or_else(|| anyhow!("no primary selection offer"))?; + let pipe = Pipe::new().map_err(Error::msg)?; + offer.receive(TEXT_MIME_TYPE.to_string(), pipe.write.as_raw_fd()); + Ok(pipe.read) + } + None => { + let offer = self + .data_offer + .as_ref() + .ok_or_else(|| anyhow!("no data offer"))?; + let pipe = Pipe::new().map_err(Error::msg)?; + offer.receive(TEXT_MIME_TYPE.to_string(), pipe.write.as_raw_fd()); + Ok(pipe.read) + } + } + } + pub(super) fn set_clipboard_data(&mut self, clipboard: Clipboard, data: String) { let conn = crate::Connection::get().unwrap().wayland(); let qh = conn.event_queue.borrow().handle(); let mut wayland_state = conn.wayland_state.borrow_mut(); let last_serial = *wayland_state.last_serial.borrow(); - let data_device = &wayland_state.data_device; - let source = wayland_state - .data_device_manager_state - .create_copy_paste_source(&qh, vec![TEXT_MIME_TYPE]); - source.set_selection(data_device.as_ref().unwrap(), last_serial); - wayland_state.copy_paste_source.replace((source, data)); + let primary_selection = if let Clipboard::PrimarySelection = clipboard { + wayland_state.primary_selection_manager.as_ref() + } else { + None + }; + + match primary_selection { + Some(primary_selection) => { + let manager = &primary_selection.manager; + let selection_device = wayland_state.primary_select_device.as_ref().unwrap(); + let source = manager.create_source(&qh, PrimarySelectionManagerData::default()); + source.offer(TEXT_MIME_TYPE.to_string()); + selection_device.set_selection(Some(&source), last_serial); + wayland_state + .primary_selection_source + .replace((source, data)); + } + None => { + let data_device = &wayland_state.data_device; + let source = wayland_state + .data_device_manager_state + .create_copy_paste_source(&qh, vec![TEXT_MIME_TYPE]); + source.set_selection(data_device.as_ref().unwrap(), last_serial); + wayland_state.copy_paste_source.replace((source, data)); + } + } } pub(super) fn confirm_selection(&mut self, offer: WlDataOffer) { @@ -74,3 +121,186 @@ impl WaylandState { } } } + +pub(super) fn write_selection_to_pipe(fd: FileDescriptor, text: &str) { + if let Err(e) = write_pipe_with_timeout(fd, text.as_bytes()) { + log::error!("while sending primary selection to pipe: {}", e); + } +} + +fn write_pipe_with_timeout(mut file: FileDescriptor, data: &[u8]) -> anyhow::Result<()> { + file.set_non_blocking(true)?; + let mut pfd = libc::pollfd { + fd: file.as_raw_fd(), + events: libc::POLLOUT, + revents: 0, + }; + + let mut buf = data; + + while !buf.is_empty() { + if unsafe { libc::poll(&mut pfd, 1, 3000) == 1 } { + match file.write(buf) { + Ok(size) if size == 0 => { + bail!("zero byte write"); + } + Ok(size) => { + buf = &buf[size..]; + } + Err(e) => bail!("error writing to pipe: {}", e), + } + } else { + bail!("timed out writing to pipe"); + } + } + + Ok(()) +} + +// Smithay has their own primary selection handler in 0.18 +// Some code borrowed from https://github.com/Smithay/client-toolkit/commit/4a5c4f59f640bc588a55277261bbed1bd2abea98 +pub(super) struct PrimarySelectionManagerState { + pub(super) manager: ZwpPrimarySelectionDeviceManagerV1, + inner: Mutex, +} + +#[derive(Default, Debug)] +struct PrimaryInner { + pending_offer: Option, + offer: Option, + valid_mime: bool, +} + +#[derive(Default)] +pub(super) struct PrimarySelectionManagerData {} + +impl PrimarySelectionManagerState { + pub(super) fn bind( + globals: &GlobalList, + queue_handle: &wayland_client::QueueHandle, + ) -> Result { + let manager = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { + manager, + inner: Mutex::new(PrimaryInner::default()), + }) + } +} + +impl Dispatch + for PrimarySelectionManagerState +{ + fn event( + _state: &mut WaylandState, + _proxy: &ZwpPrimarySelectionDeviceManagerV1, + _event: ::Event, + _data: &GlobalData, + _conn: &wayland_client::Connection, + _qhandle: &wayland_client::QueueHandle, + ) { + unreachable!("primary selection manager has no events"); + } +} + +impl Dispatch + for PrimarySelectionManagerState +{ + fn event( + state: &mut WaylandState, + source: &ZwpPrimarySelectionSourceV1, + event: ::Event, + _data: &PrimarySelectionManagerData, + _conn: &wayland_client::Connection, + _qhandle: &wayland_client::QueueHandle, + ) { + match event { + PrimarySelectionSourceEvent::Send { mime_type, fd } => { + if mime_type != TEXT_MIME_TYPE { + return; + }; + + if let Some((ps_source, data)) = &state.primary_selection_source { + if ps_source != source { + return; + } + let fd = unsafe { FileDescriptor::from_raw_fd(fd.into_raw_fd()) }; + write_selection_to_pipe(fd, data); + } + } + PrimarySelectionSourceEvent::Cancelled => { + state.primary_selection_source.take(); + source.destroy(); + } + _ => unreachable!(), + } + } +} + +impl Dispatch + for PrimarySelectionManagerState +{ + fn event( + state: &mut WaylandState, + _proxy: &ZwpPrimarySelectionOfferV1, + event: ::Event, + _data: &PrimarySelectionManagerData, + _conn: &wayland_client::Connection, + _qhandle: &wayland_client::QueueHandle, + ) { + match event { + PrimarySelectionOfferEvent::Offer { mime_type } => { + if mime_type == TEXT_MIME_TYPE { + let mgr = state.primary_selection_manager.as_ref().unwrap(); + let mut inner = mgr.inner.lock().unwrap(); + inner.valid_mime = true; + } + } + _ => unreachable!(), + } + } +} + +impl Dispatch + for PrimarySelectionManagerState +{ + event_created_child!(WaylandState, ZwpPrimarySelectionDeviceV1, [ + zwp_primary_selection_device_v1::EVT_DATA_OFFER_OPCODE => (ZwpPrimarySelectionOfferV1, PrimarySelectionManagerData::default()) + ]); + + fn event( + state: &mut WaylandState, + _primary_selection_device: &ZwpPrimarySelectionDeviceV1, + event: ::Event, + _data: &PrimarySelectionManagerData, + _conn: &wayland_client::Connection, + _qhandle: &wayland_client::QueueHandle, + ) { + let psm = state.primary_selection_manager.as_ref().unwrap(); + let mut inner = psm.inner.lock().unwrap(); + match event { + PrimarySelectionDeviceEvent::DataOffer { offer } => { + inner.pending_offer = Some(offer); + } + PrimarySelectionDeviceEvent::Selection { id } => { + if !inner.valid_mime { + return; + } + + if let Some(offer) = inner.offer.take() { + offer.destroy(); + } + if id == inner.pending_offer { + inner.offer = inner.pending_offer.take(); + } else { + // Remove the pending offer, assign the new delivered one. + if let Some(offer) = inner.pending_offer.take() { + offer.destroy() + } + + inner.offer = id; + } + } + _ => unreachable!(), + } + } +} diff --git a/window/src/os/wayland/data_device.rs b/window/src/os/wayland/data_device.rs index 719b69e8f75..d14857df1b0 100644 --- a/window/src/os/wayland/data_device.rs +++ b/window/src/os/wayland/data_device.rs @@ -1,7 +1,5 @@ -use std::io::Write; -use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd}; +use std::os::fd::{FromRawFd, IntoRawFd}; -use anyhow::bail; use filedescriptor::FileDescriptor; use smithay_client_toolkit::data_device_manager::data_device::{ DataDevice, DataDeviceDataExt, DataDeviceHandler, @@ -16,6 +14,7 @@ use crate::wayland::drag_and_drop::SurfaceAndOffer; use crate::wayland::pointer::PointerUserData; use crate::wayland::SurfaceUserData; +use super::copy_and_paste::write_selection_to_pipe; use super::drag_and_drop::{DragAndDrop, SurfaceAndPipe}; use super::state::WaylandState; @@ -239,38 +238,3 @@ impl DataSourceHandler for WaylandState { ) { } } - -fn write_selection_to_pipe(fd: FileDescriptor, text: &str) { - if let Err(e) = write_pipe_with_timeout(fd, text.as_bytes()) { - log::error!("while sending primary selection to pipe: {}", e); - } -} - -fn write_pipe_with_timeout(mut file: FileDescriptor, data: &[u8]) -> anyhow::Result<()> { - file.set_non_blocking(true)?; - let mut pfd = libc::pollfd { - fd: file.as_raw_fd(), - events: libc::POLLOUT, - revents: 0, - }; - - let mut buf = data; - - while !buf.is_empty() { - if unsafe { libc::poll(&mut pfd, 1, 3000) == 1 } { - match file.write(buf) { - Ok(size) if size == 0 => { - bail!("zero byte write"); - } - Ok(size) => { - buf = &buf[size..]; - } - Err(e) => bail!("error writing to pipe: {}", e), - } - } else { - bail!("timed out writing to pipe"); - } - } - - Ok(()) -} diff --git a/window/src/os/wayland/seat.rs b/window/src/os/wayland/seat.rs index 198a61600ee..d9e6b1bba24 100644 --- a/window/src/os/wayland/seat.rs +++ b/window/src/os/wayland/seat.rs @@ -3,6 +3,7 @@ use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState}; use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::{Connection, QueueHandle}; +use crate::wayland::copy_and_paste::PrimarySelectionManagerData; use crate::wayland::keyboard::KeyboardData; use crate::wayland::pointer::PointerUserData; @@ -48,6 +49,12 @@ impl SeatHandler for WaylandState { let data_device_manager = &self.data_device_manager_state; let data_device = data_device_manager.get_data_device(qh, &seat); self.data_device.replace(data_device); + + let primary_select_device = self.primary_selection_manager.as_ref().map(|m| { + m.manager + .get_device(&seat, qh, PrimarySelectionManagerData::default()) + }); + self.primary_select_device = primary_select_device; } } diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index ae46eb5e55d..44b85ed3624 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -30,11 +30,16 @@ use wayland_client::protocol::wl_output::WlOutput; use wayland_client::protocol::wl_pointer::WlPointer; use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::{delegate_dispatch, Connection, QueueHandle}; +use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1; +use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1; +use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1; +use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1; use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; use crate::x11::KeyboardWithFallback; +use super::copy_and_paste::{PrimarySelectionManagerData, PrimarySelectionManagerState}; use super::inputhandler::{TextInputData, TextInputState}; use super::pointer::{PendingMouse, PointerUserData}; use super::{OutputManagerData, OutputManagerState, SurfaceUserData, WaylandWindowInner}; @@ -65,6 +70,9 @@ pub(super) struct WaylandState { pub(super) data_device_manager_state: DataDeviceManagerState, pub(super) data_device: Option, pub(super) copy_paste_source: Option<(CopyPasteSource, String)>, + pub(super) primary_selection_manager: Option, + pub(super) primary_select_device: Option, + pub(super) primary_selection_source: Option<(ZwpPrimarySelectionSourceV1, String)>, pub(super) shm: Shm, pub(super) mem_pool: RefCell, } @@ -98,6 +106,9 @@ impl WaylandState { data_device_manager_state: DataDeviceManagerState::bind(globals, qh)?, data_device: None, copy_paste_source: None, + primary_selection_manager: PrimarySelectionManagerState::bind(globals, qh).ok(), + primary_select_device: None, + primary_selection_source: None, shm, mem_pool: RefCell::new(mem_pool), }; @@ -169,3 +180,8 @@ delegate_dispatch!(WaylandState: [ZwpTextInputV3: TextInputData] => TextInputSta delegate_dispatch!(WaylandState: [ZwlrOutputManagerV1: GlobalData] => OutputManagerState); delegate_dispatch!(WaylandState: [ZwlrOutputHeadV1: OutputManagerData] => OutputManagerState); delegate_dispatch!(WaylandState: [ZwlrOutputModeV1: OutputManagerData] => OutputManagerState); + +delegate_dispatch!(WaylandState: [ZwpPrimarySelectionDeviceManagerV1: GlobalData] => PrimarySelectionManagerState); +delegate_dispatch!(WaylandState: [ZwpPrimarySelectionDeviceV1: PrimarySelectionManagerData] => PrimarySelectionManagerState); +delegate_dispatch!(WaylandState: [ZwpPrimarySelectionSourceV1: PrimarySelectionManagerData] => PrimarySelectionManagerState); +delegate_dispatch!(WaylandState: [ZwpPrimarySelectionOfferV1: PrimarySelectionManagerData] => PrimarySelectionManagerState); From 7ffdab3ad4d5ab1698c68ff3ba61641169523cb3 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Wed, 24 Jan 2024 16:14:25 -0800 Subject: [PATCH 50/55] Handle crash when using software/opengl due to active_surface_id --- window/src/os/wayland/window.rs | 49 ++++++++++++++------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 65cb72c60fb..e4578e97d9f 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -860,27 +860,28 @@ impl WaylandWindowInner { fn set_text_cursor_position(&mut self, rect: Rect) { let conn = WaylandConnection::get().unwrap().wayland(); - let state = &conn.wayland_state.borrow(); + let state = conn.wayland_state.borrow(); let surface = self.surface().clone(); - + let active_surface_id = state.active_surface_id.borrow(); let surface_id = surface.id(); - let active_surface_id = state.active_surface_id.borrow().as_ref().unwrap().clone(); - - if surface_id == active_surface_id { - if self.text_cursor.map(|prior| prior != rect).unwrap_or(true) { - self.text_cursor.replace(rect); - - let surface_udata = SurfaceUserData::from_wl(&surface); - let factor = surface_udata.surface_data().scale_factor(); - - if let Some(input) = state.text_input.get_text_input_for_surface(&surface) { - input.set_cursor_rectangle( - rect.min_x() as i32 / factor, - rect.min_y() as i32 / factor, - rect.width() as i32 / factor, - rect.height() as i32 / factor, - ); - input.commit(); + + if let Some(active_surface_id) = active_surface_id.as_ref() { + if surface_id == active_surface_id.clone() { + if self.text_cursor.map(|prior| prior != rect).unwrap_or(true) { + self.text_cursor.replace(rect); + + let surface_udata = SurfaceUserData::from_wl(&surface); + let factor = surface_udata.surface_data().scale_factor(); + + if let Some(input) = state.text_input.get_text_input_for_surface(&surface) { + input.set_cursor_rectangle( + rect.min_x() as i32 / factor, + rect.min_y() as i32 / factor, + rect.width() as i32 / factor, + rect.height() as i32 / factor, + ); + input.commit(); + } } } } @@ -1179,16 +1180,6 @@ impl SurfaceDataExt for SurfaceUserData { } } -unsafe impl HasRawDisplayHandle for WaylandWindowInner { - fn raw_display_handle(&self) -> RawDisplayHandle { - // let mut handle = WaylandDisplayHandle::empty(); - // let conn = WaylandConnection::get().unwrap().wayland(); - // handle.display = conn.display.borrow().c_ptr() as _; - // RawDisplayHandle::Wayland(handle) - todo!() - } -} - unsafe impl HasRawWindowHandle for WaylandWindowInner { fn raw_window_handle(&self) -> RawWindowHandle { let mut handle = WaylandWindowHandle::empty(); From 29f1d08acd0015347fe628d4efa6a348fff90592 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:35:39 -0800 Subject: [PATCH 51/55] Make IME support optional since not all compositors support it --- window/src/os/wayland/inputhandler.rs | 8 ++++++-- window/src/os/wayland/keyboard.rs | 18 +++++++++++------- window/src/os/wayland/seat.rs | 4 +++- window/src/os/wayland/state.rs | 6 ++---- window/src/os/wayland/window.rs | 18 ++++++++++-------- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/window/src/os/wayland/inputhandler.rs b/window/src/os/wayland/inputhandler.rs index be74fa8f6f0..357d941f174 100644 --- a/window/src/os/wayland/inputhandler.rs +++ b/window/src/os/wayland/inputhandler.rs @@ -158,7 +158,7 @@ impl Dispatch for TextInputState { ) { log::trace!("ZwpTextInputEvent: {event:?}"); let mut pending_state = { - let text_input = &mut state.text_input; + let text_input = state.text_input.as_mut().unwrap(); let mut inner = text_input.inner.lock().unwrap(); inner.pending_state.entry(input.id()).or_default().clone() }; @@ -201,6 +201,8 @@ impl Dispatch for TextInputState { state .text_input + .as_ref() + .unwrap() .inner .lock() .unwrap() @@ -222,6 +224,8 @@ impl WaylandState { impl Drop for WaylandState { fn drop(&mut self) { - self.text_input.shutdown(); + if let Some(text_input) = self.text_input.as_mut() { + text_input.shutdown(); + } } } diff --git a/window/src/os/wayland/keyboard.rs b/window/src/os/wayland/keyboard.rs index a831d29c316..e9c03ddafb8 100644 --- a/window/src/os/wayland/keyboard.rs +++ b/window/src/os/wayland/keyboard.rs @@ -31,20 +31,24 @@ impl Dispatch for WaylandState { if let Some(sud) = SurfaceUserData::try_from_wl(&surface) { let window_id = sud.window_id; state.keyboard_window_id.borrow_mut().replace(window_id); - if let Some(input) = state.text_input.get_text_input_for_keyboard(keyboard) { - input.enable(); - input.commit(); + if let Some(text_input) = &state.text_input { + if let Some(input) = text_input.get_text_input_for_keyboard(keyboard) { + input.enable(); + input.commit(); + } + text_input.advise_surface(surface, keyboard); } - state.text_input.advise_surface(surface, keyboard); } else { log::warn!("{:?}, no known surface", event); } } WlKeyboardEvent::Leave { serial, .. } => { *state.last_serial.borrow_mut() = *serial; - if let Some(input) = state.text_input.get_text_input_for_keyboard(keyboard) { - input.disable(); - input.commit(); + if let Some(text_input) = &state.text_input { + if let Some(input) = text_input.get_text_input_for_keyboard(keyboard) { + input.disable(); + input.commit(); + } } } WlKeyboardEvent::Key { serial, .. } | WlKeyboardEvent::Modifiers { serial, .. } => { diff --git a/window/src/os/wayland/seat.rs b/window/src/os/wayland/seat.rs index d9e6b1bba24..3798f4259e5 100644 --- a/window/src/os/wayland/seat.rs +++ b/window/src/os/wayland/seat.rs @@ -30,7 +30,9 @@ impl SeatHandler for WaylandState { let keyboard = seat.get_keyboard(qh, KeyboardData {}); self.keyboard = Some(keyboard.clone()); - self.text_input.advise_seat(&seat, &keyboard, qh); + if let Some(text_input) = &self.text_input { + text_input.advise_seat(&seat, &keyboard, qh); + } } if capability == Capability::Pointer && self.pointer.is_none() { diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index 44b85ed3624..3e1ce47833c 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -50,7 +50,7 @@ pub(super) struct WaylandState { registry: RegistryState, pub(super) output: OutputState, pub(super) compositor: CompositorState, - pub(super) text_input: TextInputState, + pub(super) text_input: Option, pub(super) output_manager: Option, pub(super) seat: SeatState, pub(super) xdg: XdgShell, @@ -85,7 +85,7 @@ impl WaylandState { registry: RegistryState::new(globals), output: OutputState::new(globals, qh), compositor: CompositorState::bind(globals, qh)?, - text_input: TextInputState::bind(globals, qh)?, + text_input: TextInputState::bind(globals, qh).ok(), output_manager: if config::configuration().enable_zwlr_output_manager { Some(OutputManagerState::bind(globals, qh)?) } else { @@ -141,12 +141,10 @@ impl OutputHandler for WaylandState { fn update_output(&mut self, _conn: &Connection, _qh: &QueueHandle, _output: WlOutput) { log::trace!("update output: OutputHandler"); - todo!() } fn output_destroyed(&mut self, _conn: &Connection, _qh: &QueueHandle, _output: WlOutput) { log::trace!("output destroyed: OutputHandler"); - todo!() } } // Undocumented in sctk 0.17: This is required to use have user data with a surface diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index e4578e97d9f..4817480bae3 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -873,14 +873,16 @@ impl WaylandWindowInner { let surface_udata = SurfaceUserData::from_wl(&surface); let factor = surface_udata.surface_data().scale_factor(); - if let Some(input) = state.text_input.get_text_input_for_surface(&surface) { - input.set_cursor_rectangle( - rect.min_x() as i32 / factor, - rect.min_y() as i32 / factor, - rect.width() as i32 / factor, - rect.height() as i32 / factor, - ); - input.commit(); + if let Some(text_input) = &state.text_input { + if let Some(input) = text_input.get_text_input_for_surface(&surface) { + input.set_cursor_rectangle( + rect.min_x() as i32 / factor, + rect.min_y() as i32 / factor, + rect.width() as i32 / factor, + rect.height() as i32 / factor, + ); + input.commit(); + } } } } From cc6ae70faed39d1a8de17b6a8db543f9046923f0 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Fri, 26 Jan 2024 17:57:41 -0800 Subject: [PATCH 52/55] Initial window frame --- window/src/os/wayland/state.rs | 15 ++++++++--- window/src/os/wayland/window.rs | 47 ++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/window/src/os/wayland/state.rs b/window/src/os/wayland/state.rs index 3e1ce47833c..c410e681766 100644 --- a/window/src/os/wayland/state.rs +++ b/window/src/os/wayland/state.rs @@ -18,10 +18,9 @@ use smithay_client_toolkit::seat::SeatState; use smithay_client_toolkit::shell::xdg::XdgShell; use smithay_client_toolkit::shm::slot::SlotPool; use smithay_client_toolkit::shm::{Shm, ShmHandler}; +use smithay_client_toolkit::subcompositor::SubcompositorState; use smithay_client_toolkit::{ - delegate_compositor, delegate_data_device, delegate_data_device_manager, delegate_data_offer, - delegate_data_source, delegate_output, delegate_registry, delegate_seat, delegate_shm, - delegate_xdg_shell, delegate_xdg_window, registry_handlers, + delegate_compositor, delegate_data_device, delegate_data_device_manager, delegate_data_offer, delegate_data_source, delegate_output, delegate_registry, delegate_seat, delegate_shm, delegate_subcompositor, delegate_xdg_shell, delegate_xdg_window, registry_handlers }; use wayland_client::backend::ObjectId; use wayland_client::globals::GlobalList; @@ -50,6 +49,7 @@ pub(super) struct WaylandState { registry: RegistryState, pub(super) output: OutputState, pub(super) compositor: CompositorState, + pub(super) subcompositor: Arc, pub(super) text_input: Option, pub(super) output_manager: Option, pub(super) seat: SeatState, @@ -81,10 +81,16 @@ impl WaylandState { pub(super) fn new(globals: &GlobalList, qh: &QueueHandle) -> anyhow::Result { let shm = Shm::bind(&globals, qh)?; let mem_pool = SlotPool::new(1, &shm)?; + + let compositor = CompositorState::bind(globals, qh)?; + let subcompositor = + SubcompositorState::bind(compositor.wl_compositor().clone(), globals, qh)?; + let wayland_state = WaylandState { registry: RegistryState::new(globals), output: OutputState::new(globals, qh), - compositor: CompositorState::bind(globals, qh)?, + compositor, + subcompositor: Arc::new(subcompositor), text_input: TextInputState::bind(globals, qh).ok(), output_manager: if config::configuration().enable_zwlr_output_manager { Some(OutputManagerState::bind(globals, qh)?) @@ -157,6 +163,7 @@ delegate_shm!(WaylandState); delegate_output!(WaylandState); delegate_compositor!(WaylandState); +delegate_subcompositor!(WaylandState); delegate_seat!(WaylandState); diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 4817480bae3..25772e1f5fb 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -2,6 +2,7 @@ use std::any::Any; use std::cell::{RefCell, RefMut}; use std::convert::TryInto; use std::io::Read; +use std::num::NonZeroU32; use std::os::fd::AsRawFd; use std::path::PathBuf; use std::rc::Rc; @@ -19,6 +20,8 @@ use raw_window_handle::{ WaylandDisplayHandle, WaylandWindowHandle, }; use smithay_client_toolkit::compositor::{CompositorHandler, SurfaceData, SurfaceDataExt}; +use smithay_client_toolkit::shell::xdg::frame::fallback_frame::FallbackFrame; +use smithay_client_toolkit::shell::xdg::frame::DecorationsFrame; use smithay_client_toolkit::shell::xdg::window::{ DecorationMode, Window as XdgWindow, WindowConfigure, WindowDecorations as Decorations, WindowHandler, WindowState as SCTKWindowState, @@ -28,7 +31,7 @@ use wayland_client::protocol::wl_callback::WlCallback; use wayland_client::protocol::wl_keyboard::{Event as WlKeyboardEvent, KeyState}; use wayland_client::protocol::wl_pointer::ButtonState; use wayland_client::protocol::wl_surface::WlSurface; -use wayland_client::{Connection as WConnection, Proxy}; +use wayland_client::{Connection as WConnection, Proxy, QueueHandle}; use wayland_egl::{is_available as egl_is_available, WlEglSurface}; use wezterm_font::FontConfiguration; use wezterm_input_types::{ @@ -212,8 +215,6 @@ impl WaylandWindow { }; window.set_app_id(class_name.to_string()); - // TODO: investigate the resizable thing - // window.set_resizable(true); window.set_title(name.to_string()); let decorations = config.window_decorations; @@ -226,6 +227,27 @@ impl WaylandWindow { }; window.request_decoration_mode(decor_mode); + let mut window_frame = { + let wayland_state = &conn.wayland_state.borrow(); + let shm = &wayland_state.shm; + let subcompositor = wayland_state.subcompositor.clone(); + FallbackFrame::new(&window, shm, subcompositor, qh.clone()) + .expect("failed to create csd frame") + }; + let hidden = if let Some(decor) = decor_mode { + match decor { + DecorationMode::Client => false, + _ => true, + } + } else { + true + }; + window_frame.set_hidden(hidden); + window_frame.resize( + NonZeroU32::new(dimensions.pixel_width as u32).unwrap(), + NonZeroU32::new(dimensions.pixel_height as u32).unwrap(), + ); + // TODO: I don't want to deal with CSD right now, since my current tiling window manager // Hyprland doesn't support it // window.set_frame_config(ConceptConfig { @@ -248,6 +270,7 @@ impl WaylandWindow { copy_and_paste, invalidated: false, window: Some(window), + window_frame, dimensions, resize_increments: None, window_state: WindowState::default(), @@ -457,11 +480,11 @@ pub(crate) fn read_pipe_with_timeout(mut file: FileDescriptor) -> anyhow::Result } pub struct WaylandWindowInner { - // window_id: usize, pub(crate) events: WindowEventSender, surface_factor: f64, copy_and_paste: Arc>, window: Option, + window_frame: FallbackFrame, dimensions: Dimensions, resize_increments: Option<(u16, u16)>, window_state: WindowState, @@ -482,8 +505,8 @@ pub struct WaylandWindowInner { text_cursor: Option, // appearance: Appearance, config: ConfigHandle, - // // cache the title for comparison to avoid spamming - // // the compositor with updates that don't actually change it + // cache the title for comparison to avoid spamming + // the compositor with updates that don't actually change it title: Option, // wegl_surface is listed before gl_state because it // must be dropped before gl_state otherwise the underlying @@ -509,8 +532,9 @@ impl WaylandWindowInner { fn refresh_frame(&mut self) { if let Some(window) = self.window.as_mut() { - // TODO: refresh frame - // window.refresh(); + if self.window_frame.is_dirty() && !self.window_frame.is_hidden() { + self.window_frame.draw(); + } window.wl_surface().commit(); } } @@ -753,7 +777,12 @@ impl WaylandWindowInner { } // TODO: Update the window decoration size - // self.window.as_mut().unwrap().resize(w, h); + log::trace!("Resizing frame"); + self.window_frame.resize( + w.try_into().unwrap_or(NonZeroU32::new(1).unwrap()), + h.try_into().unwrap_or(NonZeroU32::new(1).unwrap()), + ); + self.window_frame.add_borders(w, h); // Compute the new pixel dimensions let new_dimensions = Dimensions { From 168af9b5374dada2d710e6cd846a4d72eb5913df Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Sat, 27 Jan 2024 13:45:48 -0800 Subject: [PATCH 53/55] Implement working frame actions --- window/src/os/wayland/pointer.rs | 59 ++++++++++++++++++++++++- window/src/os/wayland/window.rs | 76 +++++++++++++++++++++++++------- 2 files changed, 118 insertions(+), 17 deletions(-) diff --git a/window/src/os/wayland/pointer.rs b/window/src/os/wayland/pointer.rs index 058e67517ec..faefdce89f3 100644 --- a/window/src/os/wayland/pointer.rs +++ b/window/src/os/wayland/pointer.rs @@ -1,15 +1,19 @@ use std::cell::RefCell; use std::sync::{Arc, Mutex}; +use smithay_client_toolkit::compositor::SurfaceData; use smithay_client_toolkit::seat::pointer::{ PointerData, PointerDataExt, PointerEvent, PointerEventKind, PointerHandler, }; +use smithay_client_toolkit::shell::xdg::frame::{DecorationsFrame, FrameClick}; use wayland_client::backend::ObjectId; use wayland_client::protocol::wl_pointer::{ButtonState, WlPointer}; use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::{Connection, Proxy, QueueHandle}; use wezterm_input_types::MousePress; +use crate::wayland::SurfaceUserData; + use super::copy_and_paste::CopyAndPaste; use super::drag_and_drop::DragAndDrop; use super::state::WaylandState; @@ -53,11 +57,12 @@ impl PointerHandler for WaylandState { } } } + self.pointer_window_frame(pointer, events); } } pub(super) struct PointerUserData { - pdata: PointerData, + pub(super) pdata: PointerData, pub(super) state: Mutex, } @@ -193,3 +198,55 @@ fn event_serial(event: &PointerEvent) -> Option { _ => return None, }) } + +impl WaylandState { + fn pointer_window_frame(&mut self, pointer: &WlPointer, events: &[PointerEvent]) { + let windows = self.windows.borrow(); + + for evt in events { + let surface = &evt.surface; + if surface.id() == self.active_surface_id.borrow().as_ref().unwrap().clone() { + let (x, y) = evt.position; + let parent_surface = match evt.surface.data::() { + Some(data) => match data.parent_surface() { + Some(sd) => sd, + None => continue, + }, + None => continue, + }; + + let wid = SurfaceUserData::from_wl(parent_surface).window_id; + let mut inner = windows.get(&wid).unwrap().borrow_mut(); + + match evt.kind { + PointerEventKind::Enter { .. } => { + inner.window_frame.click_point_moved(&evt.surface, x, y); + } + PointerEventKind::Leave { .. } => { + inner.window_frame.click_point_left(); + } + PointerEventKind::Motion { .. } => { + inner.window_frame.click_point_moved(&evt.surface, x, y); + } + PointerEventKind::Press { button, serial, .. } + | PointerEventKind::Release { button, serial, .. } => { + let pressed = if matches!(evt.kind, PointerEventKind::Press { .. }) { + true + } else { + false + }; + let click = match button { + 0x110 => FrameClick::Normal, + 0x111 => FrameClick::Alternate, + _ => continue, + }; + if let Some(action) = inner.window_frame.on_click(click, pressed) { + inner.frame_action(pointer, serial, action); + } + } + _ => {} + } + } + } + } +} diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 25772e1f5fb..308f6679efd 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -21,17 +21,18 @@ use raw_window_handle::{ }; use smithay_client_toolkit::compositor::{CompositorHandler, SurfaceData, SurfaceDataExt}; use smithay_client_toolkit::shell::xdg::frame::fallback_frame::FallbackFrame; -use smithay_client_toolkit::shell::xdg::frame::DecorationsFrame; +use smithay_client_toolkit::shell::xdg::frame::{DecorationsFrame, FrameAction}; use smithay_client_toolkit::shell::xdg::window::{ DecorationMode, Window as XdgWindow, WindowConfigure, WindowDecorations as Decorations, WindowHandler, WindowState as SCTKWindowState, }; +use smithay_client_toolkit::shell::xdg::XdgSurface; use smithay_client_toolkit::shell::WaylandSurface; use wayland_client::protocol::wl_callback::WlCallback; use wayland_client::protocol::wl_keyboard::{Event as WlKeyboardEvent, KeyState}; -use wayland_client::protocol::wl_pointer::ButtonState; +use wayland_client::protocol::wl_pointer::{ButtonState, WlPointer}; use wayland_client::protocol::wl_surface::WlSurface; -use wayland_client::{Connection as WConnection, Proxy, QueueHandle}; +use wayland_client::{Connection as WConnection, Proxy}; use wayland_egl::{is_available as egl_is_available, WlEglSurface}; use wezterm_font::FontConfiguration; use wezterm_input_types::{ @@ -48,7 +49,7 @@ use crate::{ }; use super::copy_and_paste::CopyAndPaste; -use super::pointer::PendingMouse; +use super::pointer::{PendingMouse, PointerUserData}; use super::state::WaylandState; #[derive(Debug)] @@ -248,14 +249,17 @@ impl WaylandWindow { NonZeroU32::new(dimensions.pixel_height as u32).unwrap(), ); - // TODO: I don't want to deal with CSD right now, since my current tiling window manager - // Hyprland doesn't support it - // window.set_frame_config(ConceptConfig { - window.set_min_size(Some((32, 32))); - + let (w, h) = window_frame.add_borders( + dimensions.pixel_width as u32, + dimensions.pixel_height as u32, + ); + let (x, y) = window_frame.location(); + window + .xdg_surface() + .set_window_geometry(x, y, w as i32, h as i32); window.commit(); - // + let copy_and_paste = CopyAndPaste::create(); let pending_mouse = PendingMouse::create(window_id, ©_and_paste); @@ -443,7 +447,10 @@ pub(crate) struct PendingEvent { pub(crate) close: bool, pub(crate) had_configure_event: bool, refresh_decorations: bool, + // XXX: configure and window_configure could probably be combined, but right now configure only + // queues a new size, so it can be out of sync. Example would be maximizing and minimizing winodw pub(crate) configure: Option<(u32, u32)>, + pub(crate) window_configure: Option, pub(crate) dpi: Option, pub(crate) window_state: Option, } @@ -484,7 +491,7 @@ pub struct WaylandWindowInner { surface_factor: f64, copy_and_paste: Arc>, window: Option, - window_frame: FallbackFrame, + pub(super) window_frame: FallbackFrame, dimensions: Dimensions, resize_increments: Option<(u16, u16)>, window_state: WindowState, @@ -749,6 +756,12 @@ impl WaylandWindowInner { } } + if let Some(window_config) = pending.window_configure { + self.window_frame.update_state(window_config.state); + self.window_frame + .update_wm_capabilities(window_config.capabilities); + } + if let Some((mut w, mut h)) = pending.configure.take() { log::trace!("Pending configure: w:{w}, h{h} -- {:?}", self.window); if self.window.is_some() { @@ -778,11 +791,22 @@ impl WaylandWindowInner { // TODO: Update the window decoration size log::trace!("Resizing frame"); - self.window_frame.resize( - w.try_into().unwrap_or(NonZeroU32::new(1).unwrap()), - h.try_into().unwrap_or(NonZeroU32::new(1).unwrap()), - ); - self.window_frame.add_borders(w, h); + let (width, height) = self + .window_frame + .subtract_borders(NonZeroU32::new(w).unwrap(), NonZeroU32::new(h).unwrap()); + + // Clamp the size to at least one pixel. + let width = width.unwrap_or(NonZeroU32::new(1).unwrap()); + let height = height.unwrap_or(NonZeroU32::new(1).unwrap()); + + self.window_frame.resize(width, height); + let (x, y) = self.window_frame.location(); + let outer_size = self.window_frame.add_borders(width.get(), height.get()); + self.window + .as_mut() + .unwrap() + .xdg_surface() + .set_window_geometry(x, y, outer_size.0 as i32, outer_size.1 as i32); // Compute the new pixel dimensions let new_dimensions = Dimensions { @@ -1060,6 +1084,25 @@ impl WaylandWindowInner { _ => {} } } + + pub(super) fn frame_action(&mut self, pointer: &WlPointer, serial: u32, action: FrameAction) { + let pointer_data = pointer.data::().unwrap(); + let seat = pointer_data.pdata.seat(); + match action { + FrameAction::Close => self.events.dispatch(WindowEvent::CloseRequested), + FrameAction::Minimize => self.window.as_ref().unwrap().set_minimized(), + FrameAction::Maximize => self.window.as_ref().unwrap().set_maximized(), + FrameAction::UnMaximize => self.window.as_ref().unwrap().unset_maximized(), + FrameAction::ShowMenu(x, y) => { + self.window + .as_ref() + .unwrap() + .show_window_menu(seat, serial, (x, y)) + } + FrameAction::Resize(edge) => self.window.as_ref().unwrap().resize(seat, serial, edge), + FrameAction::Move => self.window.as_ref().unwrap().move_(seat, serial), + } + } } impl WaylandState { @@ -1090,6 +1133,7 @@ impl WaylandState { } } WaylandWindowEvent::Request(configure) => { + pending_event.window_configure.replace(configure.clone()); // TODO: This should the new queue function // p.queue_configure(&configure) // From 5609eaffce61ad5c536a5e46d55dd0a27e12bca9 Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Sat, 27 Jan 2024 14:46:39 -0800 Subject: [PATCH 54/55] Resize window frame correctly --- window/src/os/wayland/window.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index 308f6679efd..d5999398774 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -756,7 +756,7 @@ impl WaylandWindowInner { } } - if let Some(window_config) = pending.window_configure { + if let Some(ref window_config) = pending.window_configure { self.window_frame.update_state(window_config.state); self.window_frame .update_wm_capabilities(window_config.capabilities); @@ -789,16 +789,14 @@ impl WaylandWindowInner { } } - // TODO: Update the window decoration size log::trace!("Resizing frame"); - let (width, height) = self - .window_frame - .subtract_borders(NonZeroU32::new(w).unwrap(), NonZeroU32::new(h).unwrap()); - + let (width, height) = self.window_frame.subtract_borders( + NonZeroU32::new(pixel_width as u32).unwrap(), + NonZeroU32::new(pixel_height as u32).unwrap(), + ); // Clamp the size to at least one pixel. let width = width.unwrap_or(NonZeroU32::new(1).unwrap()); let height = height.unwrap_or(NonZeroU32::new(1).unwrap()); - self.window_frame.resize(width, height); let (x, y) = self.window_frame.location(); let outer_size = self.window_frame.add_borders(width.get(), height.get()); @@ -807,7 +805,7 @@ impl WaylandWindowInner { .unwrap() .xdg_surface() .set_window_geometry(x, y, outer_size.0 as i32, outer_size.1 as i32); - + // } // Compute the new pixel dimensions let new_dimensions = Dimensions { pixel_width: pixel_width.try_into().unwrap(), From 837aef4f42cdf5f6e8dd576a6193df49c797f6de Mon Sep 17 00:00:00 2001 From: Timmy Xiao <34635512+tzx@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:00:43 -0800 Subject: [PATCH 55/55] Don't hardcode egl sizes --- window/src/os/wayland/window.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs index d5999398774..9b12eb55be6 100644 --- a/window/src/os/wayland/window.rs +++ b/window/src/os/wayland/window.rs @@ -562,8 +562,9 @@ impl WaylandWindowInner { let object_id = window.wl_surface().id(); wegl_surface = Some(WlEglSurface::new( - object_id, // TODO: remove the hardcoded stuff - 100, 100, + object_id, + self.dimensions.pixel_width as i32, + self.dimensions.pixel_height as i32, )?); log::trace!("WEGL Surface here {:?}", wegl_surface);