From 4ca1fd7681001f07ecd62c1e9e30e7ffc9d35057 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Tue, 29 Dec 2020 13:45:05 +0000 Subject: [PATCH 01/31] Save empty backend. --- druid-shell/Cargo.toml | 3 + druid-shell/examples/empty_window.rs | 115 ++++++++ druid-shell/src/backend/mod.rs | 13 +- .../src/backend/wayland/application.rs | 43 +++ druid-shell/src/backend/wayland/clipboard.rs | 56 ++++ druid-shell/src/backend/wayland/dialog.rs | 30 ++ druid-shell/src/backend/wayland/error.rs | 41 +++ druid-shell/src/backend/wayland/keycodes.rs | 29 ++ druid-shell/src/backend/wayland/menu.rs | 54 ++++ druid-shell/src/backend/wayland/mod.rs | 25 ++ druid-shell/src/backend/wayland/screen.rs | 22 ++ druid-shell/src/backend/wayland/util.rs | 13 + druid-shell/src/backend/wayland/window.rs | 272 ++++++++++++++++++ druid-shell/src/lib.rs | 1 + 14 files changed, 714 insertions(+), 3 deletions(-) create mode 100644 druid-shell/examples/empty_window.rs create mode 100644 druid-shell/src/backend/wayland/application.rs create mode 100644 druid-shell/src/backend/wayland/clipboard.rs create mode 100644 druid-shell/src/backend/wayland/dialog.rs create mode 100644 druid-shell/src/backend/wayland/error.rs create mode 100644 druid-shell/src/backend/wayland/keycodes.rs create mode 100644 druid-shell/src/backend/wayland/menu.rs create mode 100644 druid-shell/src/backend/wayland/mod.rs create mode 100644 druid-shell/src/backend/wayland/screen.rs create mode 100644 druid-shell/src/backend/wayland/util.rs create mode 100644 druid-shell/src/backend/wayland/window.rs diff --git a/druid-shell/Cargo.toml b/druid-shell/Cargo.toml index 79a795b60d..21cf16a92f 100755 --- a/druid-shell/Cargo.toml +++ b/druid-shell/Cargo.toml @@ -19,6 +19,7 @@ gtk = ["gdk-sys", "glib-sys", "gtk-sys", "gtk-rs"] x11 = ["x11rb", "nix", "cairo-sys-rs", "bindgen", "pkg-config"] # Implement HasRawWindowHandle for WindowHandle raw-win-handle = ["raw-window-handle"] +wayland = ["wayland-client", "nix", "cairo-sys-rs"] # passing on all the image features. AVIF is not supported because it does not # support decoding, and that's all we use `Image` for. @@ -86,6 +87,8 @@ glib-sys = { version = "0.14.0", optional = true } gtk-sys = { version = "0.14.0", optional = true } nix = { version = "0.18.0", optional = true } x11rb = { version = "0.8.0", features = ["allow-unsafe-code", "present", "render", "randr", "xfixes", "xkb", "resource_manager", "cursor"], optional = true } +# todo use dlopen eventually to gracefully fallback to X11 +wayland-client = { version = "0.28.2", optional = true, features = ["dlopen"] } [target.'cfg(target_arch="wasm32")'.dependencies] wasm-bindgen = "0.2.67" diff --git a/druid-shell/examples/empty_window.rs b/druid-shell/examples/empty_window.rs new file mode 100644 index 0000000000..f70885755b --- /dev/null +++ b/druid-shell/examples/empty_window.rs @@ -0,0 +1,115 @@ +// Copyright 2018 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// An example that is as simple as possible (just throw up an empty window). +use std::any::Any; + +use druid_shell::kurbo::{Line, Size}; +use druid_shell::piet::{Color, RenderContext}; + +use druid_shell::{ + Application, Cursor, FileDialogOptions, FileDialogToken, FileInfo, FileSpec, HotKey, KeyEvent, + Menu, MouseEvent, Region, SysMods, TimerToken, WinHandler, WindowBuilder, WindowHandle, +}; + +#[derive(Default)] +struct HelloState { + size: Size, + handle: WindowHandle, +} + +impl WinHandler for HelloState { + fn connect(&mut self, handle: &WindowHandle) { + self.handle = handle.clone(); + } + + fn prepare_paint(&mut self) {} + + fn paint(&mut self, _piet: &mut piet_common::Piet, _: &Region) {} + + fn command(&mut self, id: u32) { + println!("command id {}", id); + } + + fn open_file(&mut self, _token: FileDialogToken, file_info: Option) { + println!("open file result: {:?}", file_info); + } + + fn key_down(&mut self, event: KeyEvent) -> bool { + println!("keydown: {:?}", event); + false + } + + fn key_up(&mut self, event: KeyEvent) { + println!("keyup: {:?}", event); + } + + fn wheel(&mut self, event: &MouseEvent) { + println!("mouse_wheel {:?}", event); + } + + fn mouse_move(&mut self, event: &MouseEvent) { + self.handle.set_cursor(&Cursor::Arrow); + println!("mouse_move {:?}", event); + } + + fn mouse_down(&mut self, event: &MouseEvent) { + println!("mouse_down {:?}", event); + } + + fn mouse_up(&mut self, event: &MouseEvent) { + println!("mouse_up {:?}", event); + } + + fn timer(&mut self, id: TimerToken) { + println!("timer fired: {:?}", id); + } + + fn size(&mut self, size: Size) { + self.size = size; + } + + fn got_focus(&mut self) { + println!("Got focus"); + } + + fn lost_focus(&mut self) { + println!("Lost focus"); + } + + fn request_close(&mut self) { + self.handle.close(); + } + + fn destroy(&mut self) { + Application::global().quit() + } + + fn as_any(&mut self) -> &mut dyn Any { + self + } +} + +fn main() { + simple_logger::SimpleLogger::new().init().unwrap(); + let app = Application::new().unwrap(); + let mut builder = WindowBuilder::new(app.clone()); + builder.set_handler(Box::new(HelloState::default())); + builder.set_title("Hello example"); + + let window = builder.build().unwrap(); + window.show(); + + app.run(None); +} diff --git a/druid-shell/src/backend/mod.rs b/druid-shell/src/backend/mod.rs index f07150398d..a76b70429e 100644 --- a/druid-shell/src/backend/mod.rs +++ b/druid-shell/src/backend/mod.rs @@ -35,11 +35,18 @@ pub use x11::*; #[cfg(all(feature = "x11", any(target_os = "linux", target_os = "openbsd")))] pub(crate) mod shared; -#[cfg(all(not(feature = "x11"), any(target_os = "linux", target_os = "openbsd")))] +#[cfg(all(feature = "wayland", target_os = "linux"))] +mod wayland; +#[cfg(all(feature = "wayland", target_os = "linux"))] +pub use wayland::*; +#[cfg(all(feature = "wayland", target_os = "linux"))] +pub(crate) mod shared; + +#[cfg(all(not(feature = "x11"), not(feature = "wayland"), any(target_os = "linux", target_os = "openbsd")))] mod gtk; -#[cfg(all(not(feature = "x11"), any(target_os = "linux", target_os = "openbsd")))] +#[cfg(all(not(feature = "x11"), not(feature = "wayland"), any(target_os = "linux", target_os = "openbsd")))] pub use self::gtk::*; -#[cfg(all(not(feature = "x11"), any(target_os = "linux", target_os = "openbsd")))] +#[cfg(all(not(feature = "x11"), not(feature = "wayland"), any(target_os = "linux", target_os = "openbsd")))] pub(crate) mod shared; #[cfg(target_arch = "wasm32")] diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs new file mode 100644 index 0000000000..0c89df91ef --- /dev/null +++ b/druid-shell/src/backend/wayland/application.rs @@ -0,0 +1,43 @@ +// Copyright 2019 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::application::AppHandler; + +use super::clipboard::Clipboard; +use super::error::Error; + +#[derive(Clone)] +pub(crate) struct Application; + +impl Application { + pub fn new() -> Result { + todo!() + } + + pub fn run(self, _handler: Option>) { + todo!() + } + + pub fn quit(&self) { + todo!() + } + + pub fn clipboard(&self) -> Clipboard { + Clipboard + } + + pub fn get_locale() -> String { + todo!() + } +} diff --git a/druid-shell/src/backend/wayland/clipboard.rs b/druid-shell/src/backend/wayland/clipboard.rs new file mode 100644 index 0000000000..f73fb0a1f4 --- /dev/null +++ b/druid-shell/src/backend/wayland/clipboard.rs @@ -0,0 +1,56 @@ +// Copyright 2019 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Interactions with the system pasteboard on GTK+. + +use crate::clipboard::{ClipboardFormat, FormatId}; + +/// The system clipboard. +#[derive(Debug, Clone)] +pub struct Clipboard; + +impl Clipboard { + /// Put a string onto the system clipboard. + pub fn put_string(&mut self, s: impl AsRef) { + todo!() + } + + /// Put multi-format data on the system clipboard. + pub fn put_formats(&mut self, formats: &[ClipboardFormat]) { + todo!() + } + + /// Get a string from the system clipboard, if one is available. + pub fn get_string(&self) -> Option { + todo!() + } + + /// Given a list of supported clipboard types, returns the supported type which has + /// highest priority on the system clipboard, or `None` if no types are supported. + pub fn preferred_format(&self, formats: &[FormatId]) -> Option { + todo!() + } + + /// Return data in a given format, if available. + /// + /// It is recommended that the `fmt` argument be a format returned by + /// [`Clipboard::preferred_format`] + pub fn get_format(&self, format: FormatId) -> Option> { + todo!() + } + + pub fn available_type_names(&self) -> Vec { + todo!() + } +} diff --git a/druid-shell/src/backend/wayland/dialog.rs b/druid-shell/src/backend/wayland/dialog.rs new file mode 100644 index 0000000000..f8d1317c5d --- /dev/null +++ b/druid-shell/src/backend/wayland/dialog.rs @@ -0,0 +1,30 @@ +// Copyright 2019 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::ffi::OsString; + +use anyhow::anyhow; + +use crate::dialog::{FileDialogOptions, FileDialogType, FileSpec}; +use crate::Error; + +/* +pub(crate) fn get_file_dialog_path( + window: &Window, + ty: FileDialogType, + options: FileDialogOptions, +) -> Result { + todo!() +} +*/ diff --git a/druid-shell/src/backend/wayland/error.rs b/druid-shell/src/backend/wayland/error.rs new file mode 100644 index 0000000000..40b62f32c5 --- /dev/null +++ b/druid-shell/src/backend/wayland/error.rs @@ -0,0 +1,41 @@ +// Copyright 2019 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! GTK platform errors. + +use std::{fmt, sync::Arc}; +use wayland_client as wayland; + +#[derive(Debug, Clone)] +pub enum Error { + /// Error connecting to wayland server. + // wayland::ConnectError is not `Clone`. TODO do a PR to wayland_client to make it `Clone`. + Connect(Arc), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + Self::Connect(e) => fmt::Display::fmt(e, f), + } + } +} + +impl std::error::Error for Error {} + +impl From for Error { + fn from(err: wayland::ConnectError) -> Self { + Self::Connect(Arc::new(err)) + } +} diff --git a/druid-shell/src/backend/wayland/keycodes.rs b/druid-shell/src/backend/wayland/keycodes.rs new file mode 100644 index 0000000000..56582ad53d --- /dev/null +++ b/druid-shell/src/backend/wayland/keycodes.rs @@ -0,0 +1,29 @@ +// Copyright 2019 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::keyboard_types::{Key, Location}; + +pub type RawKey = (); + +pub fn raw_key_to_key(raw: RawKey) -> Option { + todo!() +} + +pub fn raw_key_to_location(raw: RawKey) -> Location { + todo!() +} + +pub fn key_to_raw_key(src: &Key) -> Option { + todo!() +} diff --git a/druid-shell/src/backend/wayland/menu.rs b/druid-shell/src/backend/wayland/menu.rs new file mode 100644 index 0000000000..0f1fea6e84 --- /dev/null +++ b/druid-shell/src/backend/wayland/menu.rs @@ -0,0 +1,54 @@ +// Copyright 2019 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::keycodes; +use super::window::WindowHandle; +use crate::common_util::strip_access_key; +use crate::hotkey::{HotKey, RawMods}; +use crate::keyboard::{KbKey, Modifiers}; + +#[derive(Default, Debug)] +pub struct Menu; + +#[derive(Debug)] +struct MenuItem; + +impl Menu { + pub fn new() -> Menu { + todo!() + } + + pub fn new_for_popup() -> Menu { + todo!() + } + + pub fn add_dropdown(&mut self, menu: Menu, text: &str, _enabled: bool) { + todo!() + } + + pub fn add_item( + &mut self, + id: u32, + text: &str, + key: Option<&HotKey>, + enabled: bool, + _selected: bool, + ) { + todo!() + } + + pub fn add_separator(&mut self) { + todo!() + } +} diff --git a/druid-shell/src/backend/wayland/mod.rs b/druid-shell/src/backend/wayland/mod.rs new file mode 100644 index 0000000000..dda73ed932 --- /dev/null +++ b/druid-shell/src/backend/wayland/mod.rs @@ -0,0 +1,25 @@ +// Copyright 2019 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! GTK-based platform support + +pub mod application; +pub mod clipboard; +pub mod dialog; +pub mod error; +pub mod keycodes; +pub mod menu; +pub mod screen; +pub mod util; +pub mod window; diff --git a/druid-shell/src/backend/wayland/screen.rs b/druid-shell/src/backend/wayland/screen.rs new file mode 100644 index 0000000000..33ba76ae9e --- /dev/null +++ b/druid-shell/src/backend/wayland/screen.rs @@ -0,0 +1,22 @@ +// Copyright 2020 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! GTK Monitors and Screen information. + +use crate::screen::Monitor; +use kurbo::{Point, Rect, Size}; + +pub(crate) fn get_monitors() -> Vec { + todo!() +} diff --git a/druid-shell/src/backend/wayland/util.rs b/druid-shell/src/backend/wayland/util.rs new file mode 100644 index 0000000000..c5f2147dd9 --- /dev/null +++ b/druid-shell/src/backend/wayland/util.rs @@ -0,0 +1,13 @@ +// Copyright 2019 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs new file mode 100644 index 0000000000..021c7d4f1b --- /dev/null +++ b/druid-shell/src/backend/wayland/window.rs @@ -0,0 +1,272 @@ +// Copyright 2019 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! GTK window creation and management. + +use std::any::Any; +use std::cell::{Cell, RefCell}; +use std::convert::{TryFrom, TryInto}; +use std::ffi::c_void; +use std::os::raw::{c_int, c_uint}; +use std::panic::Location; +use std::ptr; +use std::slice; +use std::sync::{Arc, Mutex, Weak}; +use std::time::Instant; + +use anyhow::anyhow; +use cairo::Surface; + +use crate::kurbo::{Point, Rect, Size, Vec2}; +use crate::piet::{Piet, PietText, RenderContext}; + +use crate::common_util::{ClickCounter, IdleCallback}; +use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; +use crate::error::Error as ShellError; +use crate::keyboard::{KbKey, KeyEvent, KeyState, Modifiers}; +use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}; +use crate::piet::ImageFormat; +use crate::region::Region; +use crate::scale::{Scalable, Scale, ScaledArea}; +use crate::window; +use crate::window::{FileDialogToken, IdleToken, TimerToken, WinHandler, WindowLevel}; + +use super::application::Application; +use super::dialog; +use super::keycodes; +use super::menu::Menu; +use super::util; + +#[derive(Clone, Default)] +pub struct WindowHandle; + +/// Builder abstraction for creating new windows +pub(crate) struct WindowBuilder { + app: Application, + handler: Option>, + title: String, + menu: Option, + position: Option, + level: Option, + state: Option, + size: Size, + min_size: Option, + resizable: bool, + show_titlebar: bool, +} + +#[derive(Clone)] +pub struct IdleHandle; + +#[derive(Clone, PartialEq)] +pub struct CustomCursor; + +impl WindowBuilder { + pub fn new(app: Application) -> WindowBuilder { + WindowBuilder { + app, + handler: None, + title: String::new(), + menu: None, + size: Size::new(500.0, 400.0), + position: None, + level: None, + state: None, + min_size: None, + resizable: true, + show_titlebar: true, + } + } + + pub fn set_handler(&mut self, handler: Box) { + self.handler = Some(handler); + } + + pub fn set_size(&mut self, size: Size) { + self.size = size; + } + + pub fn set_min_size(&mut self, size: Size) { + self.min_size = Some(size); + } + + pub fn resizable(&mut self, resizable: bool) { + self.resizable = resizable; + } + + pub fn show_titlebar(&mut self, show_titlebar: bool) { + self.show_titlebar = show_titlebar; + } + + pub fn set_position(&mut self, position: Point) { + self.position = Some(position); + } + + pub fn set_level(&mut self, level: WindowLevel) { + self.level = Some(level); + } + + pub fn set_window_state(&mut self, state: window::WindowState) { + self.state = Some(state); + } + + pub fn set_title(&mut self, title: impl Into) { + self.title = title.into(); + } + + pub fn set_menu(&mut self, menu: Menu) { + self.menu = Some(menu); + } + + pub fn build(self) -> Result { + todo!() + } +} + +impl WindowHandle { + pub fn show(&self) { + todo!() + } + + pub fn resizable(&self, resizable: bool) { + todo!() + } + + pub fn show_titlebar(&self, show_titlebar: bool) { + todo!() + } + + pub fn set_position(&self, position: Point) { + todo!() + } + + pub fn get_position(&self) -> Point { + todo!() + } + + pub fn set_level(&self, level: WindowLevel) { + todo!() + } + + pub fn set_size(&self, size: Size) { + todo!() + } + + pub fn get_size(&self) -> Size { + todo!() + } + + pub fn set_window_state(&mut self, size_state: window::WindowState) { + todo!() + } + + pub fn get_window_state(&self) -> window::WindowState { + todo!() + } + + pub fn handle_titlebar(&self, _val: bool) { + todo!() + } + + /// Close the window. + pub fn close(&self) { + todo!() + } + + /// Bring this window to the front of the window stack and give it focus. + pub fn bring_to_front_and_focus(&self) { + todo!() + } + + /// Request a new paint, but without invalidating anything. + pub fn request_anim_frame(&self) { + todo!() + } + + /// Request invalidation of the entire window contents. + pub fn invalidate(&self) { + todo!() + } + + /// Request invalidation of one rectangle, which is given in display points relative to the + /// drawing area. + pub fn invalidate_rect(&self, rect: Rect) { + todo!() + } + + pub fn text(&self) -> PietText { + todo!() + } + + pub fn request_timer(&self, deadline: Instant) -> TimerToken { + todo!() + } + + pub fn set_cursor(&mut self, cursor: &Cursor) { + todo!() + } + + pub fn make_cursor(&self, desc: &CursorDesc) -> Option { + todo!() + } + + pub fn open_file(&mut self, options: FileDialogOptions) -> Option { + todo!() + } + + pub fn save_as(&mut self, options: FileDialogOptions) -> Option { + todo!() + } + + /// Get a handle that can be used to schedule an idle task. + pub fn get_idle_handle(&self) -> Option { + todo!() + } + + /// Get the `Scale` of the window. + pub fn get_scale(&self) -> Result { + todo!() + } + + pub fn set_menu(&self, menu: Menu) { + todo!() + } + + pub fn show_context_menu(&self, menu: Menu, _pos: Point) { + todo!() + } + + pub fn set_title(&self, title: impl Into) { + todo!() + } +} + +impl IdleHandle { + /// Add an idle handler, which is called (once) when the message loop + /// is empty. The idle handler will be run from the main UI thread, and + /// won't be scheduled if the associated view has been dropped. + /// + /// Note: the name "idle" suggests that it will be scheduled with a lower + /// priority than other UI events, but that's not necessarily the case. + pub fn add_idle_callback(&self, callback: F) + where + F: FnOnce(&dyn Any) + Send + 'static, + { + todo!() + } + + pub fn add_idle_token(&self, token: IdleToken) { + todo!() + } +} diff --git a/druid-shell/src/lib.rs b/druid-shell/src/lib.rs index 22de7548f8..379914edb2 100644 --- a/druid-shell/src/lib.rs +++ b/druid-shell/src/lib.rs @@ -32,6 +32,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/linebender/druid/screenshots/images/doc_logo.png" )] +#![allow(unused_imports)] // TODO remove // Rename `gtk_rs` back to `gtk`. // This allows us to use `gtk` as the feature name. From fcfee87ccce83c1a8b2f83afa0f6eb627848caea Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Tue, 29 Dec 2020 23:22:39 +0000 Subject: [PATCH 02/31] implement druid-shell for wayland. --- druid-shell/Cargo.toml | 10 +- druid-shell/examples/empty_window.rs | 45 +- druid-shell/src/backend/shared/keyboard.rs | 1 + druid-shell/src/backend/shared/mod.rs | 6 + druid-shell/src/backend/shared/timer.rs | 44 + .../src/backend/wayland/application.rs | 741 ++++++++++++++- druid-shell/src/backend/wayland/buffer.rs | 693 ++++++++++++++ druid-shell/src/backend/wayland/error.rs | 61 +- druid-shell/src/backend/wayland/events.rs | 122 +++ druid-shell/src/backend/wayland/menu.rs | 10 +- druid-shell/src/backend/wayland/mod.rs | 27 +- druid-shell/src/backend/wayland/pointer.rs | 225 +++++ druid-shell/src/backend/wayland/window.rs | 896 +++++++++++++++--- druid-shell/src/backend/wayland/xkb.rs | 526 ++++++++++ druid-shell/src/backend/x11/util.rs | 37 - druid-shell/src/backend/x11/window.rs | 5 +- druid-shell/src/error.rs | 8 + druid-shell/src/lib.rs | 2 + druid-shell/src/util.rs | 14 + druid/Cargo.toml | 2 + 20 files changed, 3263 insertions(+), 212 deletions(-) create mode 100644 druid-shell/src/backend/shared/timer.rs create mode 100644 druid-shell/src/backend/wayland/buffer.rs create mode 100644 druid-shell/src/backend/wayland/events.rs create mode 100644 druid-shell/src/backend/wayland/pointer.rs create mode 100644 druid-shell/src/backend/wayland/xkb.rs diff --git a/druid-shell/Cargo.toml b/druid-shell/Cargo.toml index 21cf16a92f..0b58c30991 100755 --- a/druid-shell/Cargo.toml +++ b/druid-shell/Cargo.toml @@ -19,7 +19,9 @@ gtk = ["gdk-sys", "glib-sys", "gtk-sys", "gtk-rs"] x11 = ["x11rb", "nix", "cairo-sys-rs", "bindgen", "pkg-config"] # Implement HasRawWindowHandle for WindowHandle raw-win-handle = ["raw-window-handle"] -wayland = ["wayland-client", "nix", "cairo-sys-rs"] +# **WARNING** not ready for the prime time. Many things don't work yet. +wayland = ["wayland-client", "wayland-protocols/client", "wayland-protocols/unstable_protocols", +"nix", "cairo-sys-rs", "rand", "xkbcommon-sys", "calloop", "wayland-cursor", "log"] # passing on all the image features. AVIF is not supported because it does not # support decoding, and that's all we use `Image` for. @@ -89,6 +91,12 @@ nix = { version = "0.18.0", optional = true } x11rb = { version = "0.8.0", features = ["allow-unsafe-code", "present", "render", "randr", "xfixes", "xkb", "resource_manager", "cursor"], optional = true } # todo use dlopen eventually to gracefully fallback to X11 wayland-client = { version = "0.28.2", optional = true, features = ["dlopen"] } +wayland-protocols = { version = "0.28.2", optional = true } +wayland-cursor = { version = "0.28.3", optional = true } +rand = { version = "0.8.0", optional = true } +xkbcommon-sys = { version = "0.7.4", optional = true } +calloop = { version = "0.7.1", optional = true } +log = { version = "0.4.14", optional = true } [target.'cfg(target_arch="wasm32")'.dependencies] wasm-bindgen = "0.2.67" diff --git a/druid-shell/examples/empty_window.rs b/druid-shell/examples/empty_window.rs index f70885755b..a6e96cdce4 100644 --- a/druid-shell/examples/empty_window.rs +++ b/druid-shell/examples/empty_window.rs @@ -15,29 +15,50 @@ /// An example that is as simple as possible (just throw up an empty window). use std::any::Any; -use druid_shell::kurbo::{Line, Size}; -use druid_shell::piet::{Color, RenderContext}; +use druid_shell::kurbo::{Point, Rect, Size}; +use druid_shell::piet::{Color, FixedLinearGradient, GradientStop, RenderContext}; use druid_shell::{ - Application, Cursor, FileDialogOptions, FileDialogToken, FileInfo, FileSpec, HotKey, KeyEvent, - Menu, MouseEvent, Region, SysMods, TimerToken, WinHandler, WindowBuilder, WindowHandle, + Application, Cursor, FileDialogToken, FileInfo, KeyEvent, MouseEvent, Region, TimerToken, + WinHandler, WindowBuilder, WindowHandle, }; #[derive(Default)] struct HelloState { size: Size, - handle: WindowHandle, + handle: Option, } impl WinHandler for HelloState { fn connect(&mut self, handle: &WindowHandle) { - self.handle = handle.clone(); + self.handle = Some(handle.clone()); + } + + fn prepare_paint(&mut self) { + self.handle.as_mut().unwrap().invalidate(); + } + + fn paint(&mut self, piet: &mut piet_common::Piet, _: &Region) { + // draw a gradient so we can see what's going on. + let brush = piet + .gradient(FixedLinearGradient { + start: Point::ZERO, + end: self.size.to_vec2().to_point(), + stops: vec![ + GradientStop { + pos: 0.0, + color: Color::RED, + }, + GradientStop { + pos: 1.0, + color: Color::BLUE, + }, + ], + }) + .unwrap(); + piet.fill(Rect::ZERO.with_size(self.size), &brush); } - fn prepare_paint(&mut self) {} - - fn paint(&mut self, _piet: &mut piet_common::Piet, _: &Region) {} - fn command(&mut self, id: u32) { println!("command id {}", id); } @@ -60,7 +81,7 @@ impl WinHandler for HelloState { } fn mouse_move(&mut self, event: &MouseEvent) { - self.handle.set_cursor(&Cursor::Arrow); + self.handle.as_mut().unwrap().set_cursor(&Cursor::Arrow); println!("mouse_move {:?}", event); } @@ -89,7 +110,7 @@ impl WinHandler for HelloState { } fn request_close(&mut self) { - self.handle.close(); + self.handle.as_ref().unwrap().close(); } fn destroy(&mut self) { diff --git a/druid-shell/src/backend/shared/keyboard.rs b/druid-shell/src/backend/shared/keyboard.rs index 2d9ac9a999..b7f73e24b9 100644 --- a/druid-shell/src/backend/shared/keyboard.rs +++ b/druid-shell/src/backend/shared/keyboard.rs @@ -19,6 +19,7 @@ use keyboard_types::{Code, Location}; #[cfg(any( all(feature = "x11", any(target_os = "linux", target_os = "openbsd")), + all(feature = "wayland", target_os = "linux"), target_os = "macos" ))] /// Map key code to location. diff --git a/druid-shell/src/backend/shared/mod.rs b/druid-shell/src/backend/shared/mod.rs index da805c19ab..cfa9bc0c5a 100644 --- a/druid-shell/src/backend/shared/mod.rs +++ b/druid-shell/src/backend/shared/mod.rs @@ -20,3 +20,9 @@ cfg_if::cfg_if! { pub use keyboard::*; } } +cfg_if::cfg_if! { + if #[cfg(any(feature = "x11", feature = "wayland"))] { + mod timer; + pub use timer::*; + } +} diff --git a/druid-shell/src/backend/shared/timer.rs b/druid-shell/src/backend/shared/timer.rs new file mode 100644 index 0000000000..0d22589e6c --- /dev/null +++ b/druid-shell/src/backend/shared/timer.rs @@ -0,0 +1,44 @@ +use crate::TimerToken; +use std::{cmp::Ordering, time::Instant}; + +/// A timer is a deadline (`std::Time::Instant`) and a `TimerToken`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct Timer { + deadline: Instant, + token: TimerToken, + pub data: T, +} + +impl Timer { + pub(crate) fn new(deadline: Instant, data: T) -> Self { + let token = TimerToken::next(); + Self { + deadline, + token, + data, + } + } + + pub(crate) fn deadline(&self) -> Instant { + self.deadline + } + + pub(crate) fn token(&self) -> TimerToken { + self.token + } +} + +impl Ord for Timer { + /// Ordering is so that earliest deadline sorts first + // "Earliest deadline first" that a std::collections::BinaryHeap will have the earliest timer + // at its head, which is just what is needed for timer management. + fn cmp(&self, other: &Self) -> Ordering { + self.deadline.cmp(&other.deadline).reverse() + } +} + +impl PartialOrd for Timer { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index 0c89df91ef..0810f25933 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -12,25 +12,355 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::application::AppHandler; +#![allow(clippy::single_match)] -use super::clipboard::Clipboard; -use super::error::Error; +use super::{ + buffer::Mmap, + clipboard::Clipboard, + error::Error, + events::WaylandSource, + pointer::{MouseEvtKind, Pointer, PointerEvent}, + window::{WindowData, WindowHandle}, + xkb, +}; +use crate::{ + application::AppHandler, keyboard_types::KeyState, kurbo::Point, backend::shared::Timer, + window::WinHandler, TimerToken, +}; + +use calloop::{ + timer::{Timer as CalloopTimer, TimerHandle}, + EventLoop, +}; +use std::{ + cell::{Cell, RefCell}, + collections::{BTreeMap, BinaryHeap}, + convert::TryInto, + num::NonZeroI32, + rc::Rc, + time::{Duration, Instant}, +}; +use wayland_client::{ + self as wl, event_enum, + protocol::{ + wl_compositor::WlCompositor, + wl_keyboard::{self, WlKeyboard}, + wl_output::{self, Subpixel, Transform, WlOutput}, + wl_pointer::{self, WlPointer}, + wl_seat::{self, WlSeat}, + wl_shm::{self, WlShm}, + }, + Proxy, +}; +use wayland_cursor::CursorTheme; +use wayland_protocols::{ + unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1, + xdg_shell::client::xdg_wm_base::{self, XdgWmBase}, +}; #[derive(Clone)] -pub(crate) struct Application; +pub struct Application { + pub(crate) data: Rc, +} + +pub(crate) struct ApplicationData { + pub(crate) xkb_context: xkb::Context, + pub(crate) xkb_keymap: RefCell>, + // TODO should this be per-surface?? + pub(crate) xkb_state: RefCell>, + pub(crate) wl_server: wl::Display, + pub(crate) event_queue: Rc>, + // Wayland globals + pub(crate) globals: wl::GlobalManager, + pub(crate) xdg_base: wl::Main, + pub(crate) zxdg_decoration_manager_v1: wl::Main, + pub(crate) wl_compositor: wl::Main, + pub(crate) wl_shm: wl::Main, + pub(crate) cursor_theme: RefCell, + /// A map of wayland object IDs to outputs. + /// + /// Wayland will update this if the output change. Keep a record of the `Instant` you last + /// observed a change, and use `Output::changed` to see if there are any newer changes. + /// + /// It's a BTreeMap so the ordering is consistent when enumerating outputs (not sure if this is + /// necessary, but it negligable cost). + pub(crate) outputs: Rc>>, + pub(crate) seats: Rc>>>>, + /// Handles to any surfaces that have been created. + /// + /// This is where the window data is owned. Other handles should be weak. + pub(crate) surfaces: RefCell>>, + + /// Available pixel formats + pub(crate) formats: RefCell>, + /// Close flag + pub(crate) shutdown: Cell, + /// The currently active surface, if any (by wayland object ID) + pub(crate) active_surface_id: Cell>, + // Stuff for timers + /// A calloop event source for timers. We always set it to fire at the next set timer, if any. + pub(crate) timer_handle: TimerHandle, + /// We stuff this here until the event loop, then `take` it and use it. + timer_source: RefCell>>, + /// Currently pending timers + /// + /// The extra data is the surface this timer is for. + pub(crate) timers: RefCell>>, +} impl Application { - pub fn new() -> Result { - todo!() + pub fn new() -> Result { + // connect to the server. Internally an `Arc`, so cloning is cheap. Must be kept alive for + // the duration of the app. + let wl_server = wl::Display::connect_to_env()?; + + // create an event queue (required for receiving events from the server) + let mut event_queue = wl_server.create_event_queue(); + + // Tell wayland to use our event queue for creating new objects (applies recursively). + let attached_server = (*wl_server).clone().attach(event_queue.token()); + + // Global objects that can come and go (so we must handle them dynamically). + // + // They have to be behind a shared pointer because wayland may need to add or remove them + // for the life of the application. Use weak rcs inside the callbacks to avoid leaking + // memory. + let outputs: Rc>> = Rc::new(RefCell::new(BTreeMap::new())); + let seats: Rc>>>> = + Rc::new(RefCell::new(BTreeMap::new())); + // This object will create a container for the global wayland objects, and request that + // it is populated by the server. Doesn't take ownership of the registry, we are + // responsible for keeping it alive. + let weak_outputs = Rc::downgrade(&outputs); + let weak_seats = Rc::downgrade(&seats); + let globals = wl::GlobalManager::new_with_cb( + &attached_server, + move |event, registry, _| match event { + wl::GlobalEvent::New { + id, + interface, + version, + } => { + //println!("{}@{} - {}", interface, version, id); + if interface.as_str() == "wl_output" && version >= 3 { + let output = registry.bind::(3, id); + + let output = Output::new(output); + let output_id = output.id(); + output.wl_output.quick_assign(with_cloned!(weak_outputs; move |_, event, _| { + weak_outputs + .upgrade() + .unwrap() + .borrow_mut() + .get_mut(&output_id) + .expect( + "internal: wayland sent an event for an output that doesn't exist", + ) + .process_event(event) + })); + let prev_output = weak_outputs + .upgrade() + .unwrap() + .borrow_mut() + .insert(output_id, output); + assert!( + prev_output.is_none(), + "internal: wayland should always use new IDs" + ); + } else if interface.as_str() == "wl_seat" && version >= 7 { + let new_seat = registry.bind::(7, id); + let prev_seat = weak_seats + .upgrade() + .unwrap() + .borrow_mut() + .insert(id, Rc::new(RefCell::new(Seat::new(new_seat)))); + assert!( + prev_seat.is_none(), + "internal: wayland should always use new IDs" + ); + // Defer setting up the pointer/keyboard event handling until we've + // finished constructing the `Application`. That way we can pass it as a + // parameter. + } + } + wl::GlobalEvent::Removed { id, interface } if interface.as_str() == "wl_output" => { + let removed = weak_outputs + .upgrade() + .unwrap() + .borrow_mut() + .remove(&id) + .expect("internal: wayland removed an output that doesn't exist"); + removed.wl_output.release(); + } + _ => (), // ignore other interfaces + }, + ); + + // do a round trip to make sure we have all the globals + event_queue + .sync_roundtrip(&mut (), |_, _, _| unreachable!()) + .map_err(Error::fatal)?; + + let mut globals_list = globals.list(); + globals_list.sort_by(|(_, name1, version1), (_, name2, version2)| { + name1.cmp(name2).then(version1.cmp(version2)) + }); + for (id, name, version) in globals_list.into_iter() { + //println!("{}@{} - {}", name, version, id); + } + + let xdg_base = globals + .instantiate_exact::(2) + .map_err(|e| Error::global("xdg_wm_base", 2, e))?; + let zxdg_decoration_manager_v1 = globals + .instantiate_exact::(1) + .map_err(|e| Error::global("zxdg_decoration_manager_v1", 1, e))?; + let wl_compositor = globals + .instantiate_exact::(4) + .map_err(|e| Error::global("wl_compositor", 4, e))?; + let wl_shm = globals + .instantiate_exact::(1) + .map_err(|e| Error::global("wl_shm", 1, e))?; + + // We do this to make sure wayland knows we're still responsive. + // + // NOTE: This means that clients mustn't hold up the event loop, or else wayland might kill + // your app's connection. Move *everything* to another thread, including e.g. file i/o, + // computation, network, ... This is good practice for all back-ends: it will improve + // responsiveness. + xdg_base.quick_assign(|xdg_base, event, _| match event { + xdg_wm_base::Event::Ping { serial } => xdg_base.pong(serial), + _ => (), + }); + + let xkb_context = xkb::Context::new(); + xkb_context.set_log_level(log::Level::Trace); + + let timer_source = CalloopTimer::new().unwrap(); + let timer_handle = timer_source.handle(); + + // TODO the choice of size needs more refinement, it should probably be the size needed to + // draw sharp cursors on the largest scaled monitor. + let cursor_theme = CursorTheme::load(64, &wl_shm); + + // We need to have keyboard events set up for our seats before the next roundtrip. + let app_data = Rc::new(ApplicationData { + xkb_context, + xkb_keymap: RefCell::new(None), + xkb_state: RefCell::new(None), + wl_server, + event_queue: Rc::new(RefCell::new(event_queue)), + globals, + xdg_base, + zxdg_decoration_manager_v1, + wl_compositor, + wl_shm: wl_shm.clone(), + cursor_theme: RefCell::new(cursor_theme), + outputs, + seats, + surfaces: RefCell::new(BTreeMap::new()), + formats: RefCell::new(vec![]), + shutdown: Cell::new(false), + active_surface_id: Cell::new(None), + timer_handle, + timer_source: RefCell::new(Some(timer_source)), + timers: RefCell::new(BinaryHeap::new()), + }); + + // Collect the supported image formats. + wl_shm.quick_assign(with_cloned!(app_data; move |_, event, _| { + match event { + wl_shm::Event::Format { format } => app_data.formats.borrow_mut().push(format), + _ => (), // ignore other messages + } + })); + + //println!("{:?}", app_data.seats.borrow()); + + // Setup seat event listeners with our application + for (id, seat) in app_data.seats.borrow().iter() { + let id = *id; // move into closure. + let wl_seat = seat.borrow().wl_seat.clone(); + wl_seat.quick_assign(with_cloned!(seat, app_data; move |_, event, _| { + let mut seat = seat.borrow_mut(); + match event { + wl_seat::Event::Capabilities { capabilities } => { + seat.capabilities = capabilities; + if capabilities.contains(wl_seat::Capability::Keyboard) + && seat.keyboard.is_none() + { + let keyboard = seat.wl_seat.get_keyboard(); + let app = app_data.clone(); + keyboard.quick_assign(move |_, event, _| { + app.handle_keyboard_event(id, event); + }); + seat.keyboard = Some(keyboard); + } + if capabilities.contains(wl_seat::Capability::Pointer) + && seat.pointer.is_none() + { + let pointer = seat.wl_seat.get_pointer(); + let app = app_data.clone(); + let pointer_clone = pointer.detach(); + pointer.quick_assign(move |_, event, _| { + let pointer_clone = pointer_clone.clone(); + app.handle_pointer_event(pointer_clone, event); + }); + seat.pointer = Some(pointer); + } + // Dont worry if they go away - we will just stop receiving events. If the + // capability comes back we will start getting events again. + seat.last_update = Instant::now(); + } + wl_seat::Event::Name { name } => { + seat.name = name; + seat.last_update = Instant::now(); + } + _ => (), // ignore future events + } + })); + } + /* + new_seat.quick_assign(move |_, event, _| { + }); + */ + + // Let wayland finish setup before we allow the client to start creating windows etc. + app_data.sync()?; + + Ok(Application { data: app_data }) } pub fn run(self, _handler: Option>) { - todo!() + // NOTE if we want to call this function more than once, we will need to put the timer + // source back. + let timer_source = self.data.timer_source.borrow_mut().take().unwrap(); + // flush pending events (otherwise anything we submitted since sync will never be sent) + self.data.wl_server.flush().unwrap(); + // Use calloop so we can epoll both wayland events and others (e.g. timers) + let mut event_loop = EventLoop::try_new().expect("failed to initialize calloop event loop"); + let handle = event_loop.handle(); + let wayland_dispatcher = + WaylandSource::new(self.data.event_queue.clone()).into_dispatcher(); + handle.register_dispatcher(wayland_dispatcher).unwrap(); + let app_data = self.data.clone(); + handle + .insert_source(timer_source, move |token, handle, &mut ()| { + app_data.handle_timer_event(token); + }) + .unwrap(); + let signal = event_loop.get_signal(); + event_loop + .run(Duration::from_millis(20), &mut (), move |&mut ()| { + if self.data.shutdown.get() { + signal.stop(); + } + }) + .unwrap(); } pub fn quit(&self) { - todo!() + self.data.shutdown.set(true); } pub fn clipboard(&self) -> Clipboard { @@ -38,6 +368,399 @@ impl Application { } pub fn get_locale() -> String { - todo!() + //TODO + "en_US".into() + } +} + +impl ApplicationData { + /// Send all pending messages and process all received messages. + /// + /// Don't use this once the event loop has started. + pub(crate) fn sync(&self) -> Result<(), Error> { + self.event_queue + .borrow_mut() + .sync_roundtrip(&mut (), |evt, _, _| { + panic!("unexpected wayland event: {:?}", evt) + }) + .map_err(Error::fatal)?; + Ok(()) + } + + fn handle_keyboard_event(&self, seat_id: u32, event: wl_keyboard::Event) { + use wl_keyboard::{Event, KeyState as WlKeyState, KeymapFormat}; + // TODO need to keep the serial around for certain requests. + match event { + Event::Keymap { format, fd, size } => { + if !matches!(format, KeymapFormat::XkbV1) { + panic!("only xkb keymap supported for now"); + } + // TODO to test memory ownership we copy the memory. That way we can deallocate it + // and see if we get a segfault. + let keymap_data = unsafe { + Mmap::from_raw_private( + fd, + size.try_into().unwrap(), + 0, + size.try_into().unwrap(), + ) + .unwrap() + .as_ref() + .to_vec() + }; + // keymap data is '\0' terminated. + let keymap = self.xkb_context.keymap_from_slice(&keymap_data); + let state = keymap.state(); + *self.xkb_keymap.borrow_mut() = Some(keymap); + *self.xkb_state.borrow_mut() = Some(state); + } + Event::Enter { + serial, + surface, + keys, + } => { + let data = self + .find_surface(Proxy::from(surface).id()) + .expect("received a pointer event for a non-existant surface"); + data.keyboard_focus.set(true); + // (re-entrancy) call user code + data.handler.borrow_mut().got_focus(); + data.check_for_scheduled_paint(); + } + Event::Leave { serial, surface } => { + let data = self + .find_surface(Proxy::from(surface).id()) + .expect("received a pointer event for a non-existant surface"); + data.keyboard_focus.set(false); + // (re-entrancy) call user code + data.handler.borrow_mut().lost_focus(); + data.check_for_scheduled_paint(); + } + Event::Key { + serial, + time, + key, + state, + } => { + let event = self.xkb_state.borrow().as_ref().unwrap().key_event( + key, + match state { + WlKeyState::Released => KeyState::Up, + WlKeyState::Pressed => KeyState::Down, + _ => panic!("unrecognised key event"), + }, + ); + for (_, data) in self.surfaces.borrow().iter() { + if data.keyboard_focus.get() { + match event.state { + KeyState::Down => { + // TODO what do I do if the key event is handled? Do I not update + // the xkb state? + data.handler.borrow_mut().key_down(event.clone()); + } + KeyState::Up => data.handler.borrow_mut().key_up(event.clone()), + } + } + data.check_for_scheduled_paint(); + } + } + Event::Modifiers { + serial, + mods_depressed, + mods_latched, + mods_locked, + group, + } => { + // Ignore this event for now and handle modifiers in user code. This might be + // suboptimal and need revisiting in the future. + //surface.check_for_scheduled_paint(); + } + Event::RepeatInfo { rate, delay } => { + // TODO actually store/use this info + println!("Requested repeat rate={} delay={}", rate, delay); + } + evt => { + log::warn!("Unhandled keybaord event: {:?}", evt); + } + } + } + + /// `seat_id` is the object ID for the seat. + fn handle_pointer_event(&self, wl_pointer: WlPointer, event: wl_pointer::Event) { + use wl_pointer::Event; + match event { + Event::Enter { + serial, + surface, + surface_x, + surface_y, + } => { + let data = self + .find_surface(Proxy::from(surface).id()) + .expect("received a pointer event for a non-existant surface"); + // TODO some investigation will be needed to deduce the space `surface_x` and + // `surface_y` are relative to. + data.init_pointer(wl_pointer, serial); + // cannot fail (we just set it to Some) + let mut _pointer = data.pointer.borrow_mut(); + let pointer = _pointer.as_mut().unwrap(); + // No mouse enter event, but we know the position so we can issue a mouse move. + let pos = Point::new(surface_x, surface_y); + pointer.push(PointerEvent::Motion(pos)); + } + Event::Leave { serial, surface } => { + let data = self + .find_surface(Proxy::from(surface).id()) + .expect("received a pointer event for a non-existant surface"); + if let Some(pointer) = data.pointer.borrow_mut().as_mut() { + pointer.push(PointerEvent::Leave); + }; + } + Event::Motion { + time, + surface_x, + surface_y, + } => { + let pos = Point::new(surface_x, surface_y); + for (_, data) in self.surfaces.borrow().iter() { + if let Some(pointer) = data.pointer.borrow_mut().as_mut() { + pointer.push(PointerEvent::Motion(pos)); + } + } + } + Event::Button { + serial, + time, + button, + state, + } => { + for (_, data) in self.surfaces.borrow().iter() { + if let Some(pointer) = data.pointer.borrow_mut().as_mut() { + pointer.push(PointerEvent::Button { button, state }); + } + } + } + Event::Axis { time, axis, value } => { + for (_, data) in self.surfaces.borrow().iter() { + if let Some(pointer) = data.pointer.borrow_mut().as_mut() { + pointer.push(PointerEvent::Axis { axis, value }); + } + } + } + Event::Frame => { + for (_, data) in self.surfaces.borrow().iter() { + // Wait until we're outside the loop, then drop the pointer state. + let mut have_left = false; + while let Some(event) = data.pop_pointer_event() { + // (re-entrancy) + match event { + MouseEvtKind::Move(evt) => data.handler.borrow_mut().mouse_move(&evt), + MouseEvtKind::Up(evt) => data.handler.borrow_mut().mouse_up(&evt), + MouseEvtKind::Down(evt) => data.handler.borrow_mut().mouse_down(&evt), + MouseEvtKind::Leave => { + have_left = true; + data.handler.borrow_mut().mouse_leave(); + } + MouseEvtKind::Wheel(evt) => data.handler.borrow_mut().wheel(&evt), + } + } + if have_left { + *data.pointer.borrow_mut() = None; + } + data.check_for_scheduled_paint(); + } + } + evt => { + //log::warn!("Unhandled pointer event: {:?}", evt); + } + } + } + + fn handle_timer_event(&self, _token: TimerToken) { + // Shouldn't be necessary. + self.timer_handle.cancel_all_timeouts(); + // Don't borrow the timers in case the callbacks want to add more. + // TODO make this in the stack (smallvec) + let mut expired_timers = Vec::with_capacity(1); + let mut timers = self.timers.borrow_mut(); + let now = Instant::now(); + while matches!(timers.peek(), Some(timer) if timer.deadline() < now) { + // timer has passed + expired_timers.push(timers.pop().unwrap()); + } + drop(timers); + for timer in expired_timers { + let surface = match self.surfaces.borrow().get(&timer.data).cloned() { + Some(s) => s, + None => { + // NOTE this might be expected + log::warn!("Received event for surface that doesn't exist any more"); + continue; + } + }; + surface.handler.borrow_mut().timer(timer.token()); + } + for (_, surface) in self.surfaces.borrow().iter() { + surface.check_for_scheduled_paint(); + } + // Get the deadline soonest and queue it. + if let Some(timer) = self.timers.borrow().peek() { + self.timer_handle + .add_timeout(timer.deadline() - now, timer.token()); + } + // Now flush so the events actually get sent (we don't do this automatically because we + // aren't in a wayland callback. + self.wl_server.flush().unwrap(); + } + + fn find_surface(&self, id: u32) -> Option> { + self.surfaces.borrow().get(&id).cloned() + } +} + +#[derive(Debug, Clone)] +pub struct Output { + wl_output: wl::Main, + pub x: i32, + pub y: i32, + pub physical_width: i32, + pub physical_height: i32, + pub subpixel: Subpixel, + pub make: String, + pub model: String, + pub transform: Transform, + pub scale: i32, + pub current_mode: Option, + pub preferred_mode: Option, + /// Whether we have received some update events but not the `done` event. + update_in_progress: bool, + /// Lets us work out if things have changed since we last observed the output. + last_update: Instant, +} + +impl Output { + // All the stuff before `current_mode` will be filled out immediately after creation, so these + // dummy values will never be observed. + fn new(wl_output: wl::Main) -> Self { + Output { + wl_output, + x: 0, + y: 0, + physical_width: 0, + physical_height: 0, + subpixel: Subpixel::Unknown, + make: "".into(), + model: "".into(), + transform: Transform::Normal, + + current_mode: None, + preferred_mode: None, + scale: 1, // the spec says if there is no scale event, assume 1. + update_in_progress: true, + last_update: Instant::now(), + } + } + + /// Get the wayland object ID for this output. This is how we key outputs in our global + /// registry. + pub fn id(&self) -> u32 { + Proxy::from(self.wl_output.detach()).id() + } + + /// Incorporate update data from the server for this output. + fn process_event(&mut self, evt: wl_output::Event) { + match evt { + wl_output::Event::Geometry { + x, + y, + physical_width, + physical_height, + subpixel, + make, + model, + transform, + } => { + self.x = x; + self.y = y; + self.physical_width = physical_width; + self.physical_height = physical_height; + self.subpixel = subpixel; + self.make = make; + self.model = model; + self.transform = transform; + + self.update_in_progress = true; + } + wl_output::Event::Mode { + flags, + width, + height, + refresh, + } => { + if flags.contains(wl_output::Mode::Current) { + self.current_mode = Some(Mode { + width, + height, + refresh, + }); + } + if flags.contains(wl_output::Mode::Preferred) { + self.preferred_mode = Some(Mode { + width, + height, + refresh, + }); + } + self.update_in_progress = true; + } + wl_output::Event::Done => { + self.update_in_progress = false; + self.last_update = Instant::now(); + } + wl_output::Event::Scale { factor } => { + self.scale = factor; + self.update_in_progress = true; + } + _ => (), // ignore possible future events + } + } + + /// Whether the output has changed since `since`. + /// + /// Will return `false` if an update is in progress, as updates should be handled atomically. + fn changed(&self, since: Instant) -> bool { + !self.update_in_progress && since < self.last_update + } +} + +#[derive(Debug, Clone)] +pub struct Mode { + width: i32, + height: i32, + refresh: i32, +} + +#[derive(Debug, Clone)] +pub struct Seat { + wl_seat: wl::Main, + name: String, + capabilities: wl_seat::Capability, + keyboard: Option>, + pointer: Option>, + // TODO touch + /// Lets us work out if things have changed since we last observed the output. + last_update: Instant, +} + +impl Seat { + fn new(wl_seat: wl::Main) -> Self { + Self { + wl_seat, + name: "".into(), + capabilities: wl_seat::Capability::empty(), + last_update: Instant::now(), + keyboard: None, + pointer: None, + } } } diff --git a/druid-shell/src/backend/wayland/buffer.rs b/druid-shell/src/backend/wayland/buffer.rs new file mode 100644 index 0000000000..f9375ac82f --- /dev/null +++ b/druid-shell/src/backend/wayland/buffer.rs @@ -0,0 +1,693 @@ +use kurbo::{Rect, Size}; +use nix::{ + errno::Errno, + fcntl::OFlag, + sys::{ + mman::{mmap, munmap, shm_open, MapFlags, ProtFlags}, + stat::Mode, + }, + unistd::{close, ftruncate}, +}; +use std::{ + cell::{Cell, RefCell}, + convert::{TryFrom, TryInto}, + fmt, + mem::{self, MaybeUninit}, + ops::{Deref, DerefMut}, + os::{raw::c_void, unix::prelude::RawFd}, + ptr::{self, NonNull}, + rc::{Rc, Weak as WeakRc}, + slice, +}; +use wayland_client::{ + self as wl, + protocol::{ + wl_buffer::{self, WlBuffer}, + wl_callback, + wl_keyboard::{self, WlKeyboard}, + wl_output::WlOutput, + wl_pointer::{self, WlPointer}, + wl_shm::{self, WlShm}, + wl_shm_pool::WlShmPool, + wl_surface::{self, WlSurface}, + }, +}; +use wayland_cursor::CursorImageBuffer; +use wayland_protocols::{ + unstable::xdg_decoration::v1::client::zxdg_toplevel_decoration_v1::{ + Event as ZxdgToplevelDecorationV1Event, Mode as DecorationMode, ZxdgToplevelDecorationV1, + }, + xdg_shell::client::{ + xdg_surface::{Event as XdgSurfaceEvent, XdgSurface}, + xdg_toplevel::{Event as XdgTopLevelEvent, XdgToplevel}, + xdg_wm_base::XdgWmBase, + }, +}; + +use super::{window::WindowData, NUM_FRAMES, PIXEL_WIDTH}; + +/// A collection of buffers that can change size. +/// +/// This object knows nothing about scaling or events. It just provides buffers to draw into. +pub struct Buffers { + /// The actual buffer objects. + buffers: Cell>, + /// Which buffer is the next to present. Iterates through to `N-1` then wraps. Draw to this + /// buffer + pending: Cell, + /// The physical size of the buffers. + /// + /// This will be different from the buffers' actual size if `recrate_buffers` is true. + // NOTE: This really should support fractional scaling, use unstable protocol. + size: Cell, + /// Do we need to rebuild the framebuffers (size changed). + recreate_buffers: Cell, + /// A paint call could not be completed because no buffers were available. Once a buffer + /// becomes free, a frame should be painted. + deferred_paint: Cell, + /// This flag allows us to check that we only hand out a mutable ref to the buffer data once. + /// Otherwise providing mutable access to the data would be unsafe. + pending_buffer_borrowed: Cell, + /// A handle to the `WindowData`, so we can run the paint method. + /// + /// Weak handle because logically we are owned by the `WindowData`. If ownership went in both + /// directions we would leak memory. + window: WeakRc, + /// Shared memory to allocate buffers in + shm: RefCell, +} + +impl Buffers { + /// Create a new `Buffers` object. + /// + /// The initial size should have non-zero area. + pub fn new(wl_shm: wl::Main, size: RawSize) -> Rc { + assert!(N >= 2, "must be at least 2 buffers"); + assert!(!size.is_empty(), "window size must not be empty"); + Rc::new(Self { + buffers: Cell::new(None), + pending: Cell::new(0), + size: Cell::new(size), + recreate_buffers: Cell::new(true), + deferred_paint: Cell::new(false), + pending_buffer_borrowed: Cell::new(false), + window: WeakRc::new(), + shm: RefCell::new(Shm::new(wl_shm).expect("error allocating shared memory")), + }) + } + + pub fn set_window_data(&mut self, data: WeakRc) { + self.window = data; + } + + /// Get the physical size of the buffer. + pub fn size(&self) -> RawSize { + self.size.get() + } + + /// Request that the size of the buffer is changed. + pub fn set_size(&self, new_size: RawSize) { + assert!(!new_size.is_empty(), "window size must not be empty"); + if self.size.get() != new_size { + self.size.set(new_size); + self.recreate_buffers.set(true); + } + } + + /// Request painting the next frame. + /// + /// This calls into user code. To avoid re-entrancy, ensure that we are not already in user + /// code (defer this call if necessary). + /// + /// We will call into `WindowData` to paint the frame, and present it. If no buffers are + /// available we will set a flag, so that when one becomes available we immediately paint and + /// present. This includes if we need to resize. + pub fn request_paint(self: &Rc) { + //println!("request paint"); + if self.pending_buffer_borrowed.get() { + panic!("called request_paint during painting"); + } + // Ok so complicated here: + // - If we need to recreate the buffers, we must wait until *all* buffers are + // released, so we can re-use the memory. + // - If we are just waiting for the next frame, we can check if the *pending* + // buffer is free. + if self.recreate_buffers.get() { + // If all buffers are free, destroy and recreate them + if self.all_buffers_released() { + log::debug!("all buffers released, recreating"); + self.deferred_paint.set(false); + self.recreate_buffers_unchecked(); + self.paint_unchecked(); + } else { + self.deferred_paint.set(true); + } + } else { + // If the next buffer is free, draw & present. If buffers have not been initialized it + // is a bug in this code. + if self.pending_buffer_released() { + log::debug!("next frame has been released: draw and present"); + self.deferred_paint.set(false); + self.paint_unchecked(); + } else { + self.deferred_paint.set(true); + } + } + } + + /// Paint the next frame, without checking if the buffer is free. + /// + /// TODO call `handler.prepare_paint` before setting up cairo & invalidating regions. + fn paint_unchecked(self: &Rc) { + //println!("paint unchecked"); + debug_assert!(!self.size.get().is_empty()); + let mut buf_data = self.pending_buffer_data().unwrap(); + debug_assert!( + self.pending_buffer_released(), + "buffer in use/not initialized" + ); + if let Some(data) = self.window.upgrade() { + data.paint(self.size.get(), &mut *buf_data, self.recreate_buffers.get()); + } + self.recreate_buffers.set(false); + } + + /// Destroy the current buffers, resize the shared memory pool if necessary, and create new + /// buffers. Does not check if all buffers are free. + fn recreate_buffers_unchecked(&self) { + //println!("recreate buffers unchecked"); + debug_assert!( + self.all_buffers_released(), + "recreate buffers: some buffer still in use" + ); + debug_assert!(!self.pending_buffer_borrowed.get()); + self.with_buffers(|buffers| { + if let Some(buffers) = buffers.as_ref() { + buffers[0].destroy(); + buffers[1].destroy(); + } + }); + let new_buffer_size = self.size.get().buffer_size(N.try_into().unwrap()); + // This is probably OOM if it fails, but we unwrap to report the underlying error. + self.shm.borrow_mut().extend(new_buffer_size).unwrap(); + + let pool = self.shm.borrow_mut().create_pool(); + self.buffers.set({ + let mut buffers = vec![]; + + let size = self.size.get(); + for i in 0..N { + buffers.push(Buffer::create( + &pool, + self.window.clone(), + i, + size.width, + size.height, + )); + } + Some(buffers.try_into().unwrap()) + }); + pool.destroy(); + // Don't unset `recreate_buffers` here. We immediately call paint_unchecked, and need to + // know if buffers were recreated (to invalidate the whole window). + } + + fn with_buffers(&self, f: impl FnOnce(&Option<[Buffer; N]>) -> T) -> T { + let buffers = self.buffers.replace(None); + let out = f(&buffers); + self.buffers.set(buffers); + out + } + + /// Get a ref to the next buffer to draw to. + fn with_pending_buffer(&self, f: impl FnOnce(Option<&Buffer>) -> T) -> T { + self.with_buffers(|buffers| f(buffers.as_ref().map(|buffers| &buffers[self.pending.get()]))) + } + + /// For checking whether all buffers are free. + fn all_buffers_released(&self) -> bool { + self.with_buffers(|buffers| { + buffers + .as_ref() + .map(|buffers| buffers.iter().all(|buf| !buf.in_use.get())) + .unwrap_or(true) + }) + } + + /// For checking whether the next buffer is free. + fn pending_buffer_released(&self) -> bool { + self.with_pending_buffer(|buf| buf.map(|buf| !buf.in_use.get()).unwrap_or(false)) + } + + /// Get the raw buffer data of the next buffer to draw to. + /// + /// Will return `None` if buffer already borrowed. + fn pending_buffer_data<'a>(self: &'a Rc) -> Option + 'a> { + if self.pending_buffer_borrowed.get() { + None + } else { + self.pending_buffer_borrowed.set(true); + let frame_len = self.frame_len(); + // Safety: we make sure the data is only loaned out once. + unsafe { + Some(BufferData { + buffers: Rc::downgrade(self), + mmap: self + .shm + .borrow() + .mmap(frame_len * self.pending.get(), frame_len), + }) + } + } + } + + /// Signal to wayland that the pending buffer is ready to be presented, and switch the next + /// buffer to be the pending one. + pub(crate) fn attach(&self) { + if let Some(data) = self.window.upgrade() { + self.with_pending_buffer(|buf| buf.unwrap().attach(&data.wl_surface)); + self.pending.set((self.pending.get() + 1) % N); + } + } + + fn frame_len(&self) -> usize { + let size = self.size.get(); + (PIXEL_WIDTH * size.width * size.height) + .try_into() + .expect("integer overflow") + } +} + +/// A wrapper round `WlBuffer` that tracks whether the buffer is released. +/// +/// No allocations on `clone`. +#[derive(Debug, Clone)] +pub struct Buffer { + inner: wl::Main, + in_use: Rc>, +} + +impl Buffer { + /// Create a new buffer using the given backing storage. It is the responsibility of the caller + /// to ensure buffers don't overlap, and the backing storage has enough space. + // Window handle is needed for the callback. + pub fn create( + pool: &wl::Main, + window: WeakRc, + idx: usize, + width: i32, + height: i32, + ) -> Self { + // TODO overflow + let offset = i32::try_from(idx).unwrap() * width * height * PIXEL_WIDTH; + let stride = width * PIXEL_WIDTH; + let inner = pool.create_buffer(offset, width, height, stride, wl_shm::Format::Argb8888); + let in_use = Rc::new(Cell::new(false)); + + inner.quick_assign(with_cloned!(in_use; move |_, event, _| match event { + wl_buffer::Event::Release => { + in_use.set(false); + // TODO look at this. We should only paint if it has been requested. + if let Some(data) = window.upgrade() { + data.buffers.request_paint(); + } + } + _ => (), // panic!("release is the only event"), + })); + + Buffer { inner, in_use } + } + + pub fn attach(&self, wl_surface: &wl::Main) { + if self.in_use.get() { + panic!("attaching an already in-use surface"); + } + self.in_use.set(true); + wl_surface.attach(Some(&self.inner), 0, 0); + } + + pub fn destroy(&self) { + if self.in_use.get() { + panic!("Destroying a buffer while it is in use"); + } + self.inner.destroy(); + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + self.destroy(); + } +} + +pub struct BufferData { + buffers: WeakRc>, + mmap: Mmap, +} + +impl Deref for BufferData { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + self.mmap.deref() + } +} + +impl DerefMut for BufferData { + fn deref_mut(&mut self) -> &mut [u8] { + self.mmap.deref_mut() + } +} + +impl Drop for BufferData { + fn drop(&mut self) { + if let Some(buffers) = self.buffers.upgrade() { + buffers.pending_buffer_borrowed.set(false); + } + } +} + +/// RAII wrapper for shm_open (file descriptors for mmap'd shared memory) +/// +/// Designed to work like a vec: to manage extending when necessary. +pub struct Shm { + inner: RawFd, + size: usize, + // a handle on the wayland structure. + wl_shm: wl::Main, +} + +impl Shm { + /// Create a new shared memory object. Will be empty until resized. + pub fn new(wl_shm: wl::Main) -> Result { + // TODO is this a good way to choose a filename? What should our retry strategy be? + let name = format!("/druid-wl-{}", rand::random::()); + // Open the file we will use for shared memory. + let fd = shm_open( + name.as_str(), + OFlag::O_RDWR | OFlag::O_EXCL | OFlag::O_CREAT, + Mode::S_IRUSR | Mode::S_IWUSR, + )?; + + // The memory is 0-sized until we resize it with `ftruncate`. + let shm = Shm { + inner: fd, + size: 0, + wl_shm, + }; + Ok(shm) + } + + /// Resizes the shared memory pool. + /// + /// This is almost certainly unsafe if the server is using the memory TODO use locking + /// (provided by wayland I think). + pub fn resize(&mut self, new_size: i32) -> Result<(), nix::Error> { + let new_size: usize = new_size.try_into().unwrap(); + if self.size == new_size { + return Ok(()); + } + + // allocate the space (retry on interrupt) + loop { + match ftruncate(self.inner, new_size.try_into().unwrap()) { + Ok(()) => { + self.size = new_size; + return Ok(()); + } + Err(e) if matches!(e.as_errno(), Some(Errno::EINTR)) => { + // continue (try again) + } + Err(e) => { + return Err(e); + } + } + } + } + + /// Like `resize`, but doesn't shrink. + pub fn extend(&mut self, new_size: i32) -> Result<(), nix::Error> { + if self.size < new_size.try_into().unwrap() { + self.resize(new_size) + } else { + Ok(()) + } + } + + pub fn size(&self) -> usize { + self.size + } + + /// Create a `WlShmPool` backed by our memory that will be mmap'd by the server. + pub fn create_pool(&self) -> wl::Main { + self.wl_shm + .create_pool(self.inner, self.size.try_into().unwrap()) + } + + /// A method to make all the data `1` (white). Useful for debugging. + /// + /// Safe only when no frames are in use. + #[allow(unused)] + pub fn fill_white(&mut self) { + unsafe { + let mut buf = self.mmap(0, self.size); + for byte in buf.as_mut() { + *byte = 0xff; + } + } + } + + /// Get access to the shared memory for the given frame. + /// + /// # Safety + /// + /// It's not checked if any other process has access to the memory. Data races may occur if + /// they do. + pub unsafe fn mmap(&self, offset: usize, len: usize) -> Mmap { + Mmap::from_raw(self.inner, self.size, offset, len).unwrap() + } + + /// Closing with error checking + pub fn close(self) -> Result<(), nix::Error> { + close(self.inner) + } +} + +impl Drop for Shm { + fn drop(&mut self) { + // cannot handle errors in drop. + let _ = close(self.inner); + } +} + +pub struct Mmap { + ptr: NonNull, + size: usize, + offset: usize, + len: usize, +} + +impl Mmap { + /// `fd` and `size` are the whole memory you want to map. `offset` and `len` are there to + /// provide extra protection (only giving you access to that part). + /// + /// # Safety + /// + /// Concurrent use of the memory we map to isn't checked. + #[inline] + pub unsafe fn from_raw( + fd: RawFd, + size: usize, + offset: usize, + len: usize, + ) -> Result { + Self::from_raw_inner(fd, size, offset, len, false) + } + + #[inline] + pub unsafe fn from_raw_private( + fd: RawFd, + size: usize, + offset: usize, + len: usize, + ) -> Result { + Self::from_raw_inner(fd, size, offset, len, true) + } + + unsafe fn from_raw_inner( + fd: RawFd, + size: usize, + offset: usize, + len: usize, + private: bool, + ) -> Result { + assert!(offset + len <= size, "{0} + {1} <= {2}", offset, len, size,); + let map_flags = if private { + MapFlags::MAP_PRIVATE + } else { + MapFlags::MAP_SHARED + }; + let ptr = mmap( + ptr::null_mut(), + size, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + map_flags, + fd, + 0, + )?; + Ok(Mmap { + ptr: NonNull::new(ptr).unwrap(), + size, + offset, + len, + }) + } +} + +impl Deref for Mmap { + type Target = [u8]; + fn deref(&self) -> &[u8] { + unsafe { + let start = self.ptr.as_ptr().offset(self.offset.try_into().unwrap()); + slice::from_raw_parts(start as *const u8, self.len) + } + } +} + +impl DerefMut for Mmap { + fn deref_mut(&mut self) -> &mut [u8] { + unsafe { + let start = self.ptr.as_ptr().offset(self.offset.try_into().unwrap()); + slice::from_raw_parts_mut(start as *mut u8, self.len) + } + } +} + +impl Drop for Mmap { + fn drop(&mut self) { + unsafe { + if let Err(e) = munmap(self.ptr.as_ptr(), self.size) { + log::warn!("Error unmapping memory: {}", e); + } + } + } +} +#[derive(Copy, Clone, PartialEq)] +pub struct RawSize { + pub width: i32, + pub height: i32, +} + +impl RawSize { + pub const ZERO: Self = Self { + width: 0, + height: 0, + }; + + /// How many bytes do we need to store a frame of this size (in pixels) + pub fn frame_size(self) -> i32 { + // Check for overflow + assert!(self.width.checked_mul(self.height).unwrap() < i32::MAX / PIXEL_WIDTH); + self.width * self.height * PIXEL_WIDTH + } + + /// Helper function to get the total buffer size we will need for all the frames. + pub fn buffer_size(self, frames: i32) -> i32 { + // Check for overflow + assert!(self.width.checked_mul(self.height).unwrap() < i32::MAX / (PIXEL_WIDTH * frames)); + self.width * self.height * PIXEL_WIDTH * frames + } + + pub fn scale(self, scale: i32) -> Self { + // NOTE no overflow checking atm. + RawSize { + width: self.width * scale, + height: self.height * scale, + } + } + + pub fn to_rect(self) -> RawRect { + RawRect { + x0: 0, + y0: 0, + x1: self.width, + y1: self.height, + } + } + + pub fn area(self) -> i32 { + self.width * self.height + } + + pub fn is_empty(self) -> bool { + self.area() == 0 + } +} + +impl From for RawSize { + fn from(s: Size) -> Self { + let width = s.width as i32; + let height = s.height as i32; + + // Sanity check + assert!(width >= 0 && height >= 0); + + RawSize { width, height } + } +} + +impl From for Size { + fn from(s: RawSize) -> Self { + Size::new(s.width as f64, s.height as f64) + } +} + +impl fmt::Debug for RawSize { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}×{}", self.width, self.height) + } +} + +#[derive(Debug)] +pub struct RawRect { + pub x0: i32, + pub y0: i32, + pub x1: i32, + pub y1: i32, +} + +impl RawRect { + pub fn scale(self, scale: i32) -> Self { + // NOTE no overflow checking atm. + RawRect { + x0: self.x0 * scale, + y0: self.y0 * scale, + x1: self.x1 * scale, + y1: self.y1 * scale, + } + } +} + +impl From for RawRect { + fn from(r: Rect) -> Self { + let max = i32::MAX as f64; + assert!(r.x0.abs() < max && r.y0.abs() < max && r.x1.abs() < max && r.y1.abs() < max); + RawRect { + x0: r.x0 as i32, + y0: r.y0 as i32, + x1: r.x1 as i32, + y1: r.y1 as i32, + } + } +} + +impl From for Rect { + fn from(r: RawRect) -> Self { + Rect { + x0: r.x0 as f64, + y0: r.y0 as f64, + x1: r.x1 as f64, + y1: r.y1 as f64, + } + } +} diff --git a/druid-shell/src/backend/wayland/error.rs b/druid-shell/src/backend/wayland/error.rs index 40b62f32c5..045ce11176 100644 --- a/druid-shell/src/backend/wayland/error.rs +++ b/druid-shell/src/backend/wayland/error.rs @@ -14,28 +14,73 @@ //! GTK platform errors. -use std::{fmt, sync::Arc}; -use wayland_client as wayland; +use anyhow::Error as AnyError; +use std::{error::Error as StdError, fmt, sync::Arc}; +use wayland_client as wl; #[derive(Debug, Clone)] pub enum Error { /// Error connecting to wayland server. - // wayland::ConnectError is not `Clone`. TODO do a PR to wayland_client to make it `Clone`. - Connect(Arc), + Connect(Arc), + /// A wayland global either doesn't exist, or doesn't support the version we need. + Global { + name: String, + version: u32, + inner: Arc, + }, + /// An unexpected error occurred. It's not handled by druid-shell/wayland, so you should + /// terminate the app. + Fatal(Arc), +} + +impl Error { + pub fn fatal(e: impl StdError + 'static) -> Self { + Self::Fatal(Arc::new(e)) + } + + pub fn global(name: impl Into, version: u32, inner: wl::GlobalError) -> Self { + Error::Global { + name: name.into(), + version, + inner: Arc::new(inner), + } + } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self { - Self::Connect(e) => fmt::Display::fmt(e, f), + Self::Connect(e) => f.write_str("could not connect to the wayland server"), + Self::Global { + name, + version, + inner, + } => write!( + f, + "a required wayland global ({}@{}) was unavailable", + name, version + ), + Self::Fatal(e) => f.write_str("an unhandled error occurred"), } } } -impl std::error::Error for Error {} +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match self { + Self::Connect(e) => Some(&**e), + Self::Global { + name, + version, + inner, + } => Some(&**inner), + Self::Fatal(e) => Some(&**e), + } + } +} -impl From for Error { - fn from(err: wayland::ConnectError) -> Self { +impl From for Error { + fn from(err: wl::ConnectError) -> Self { Self::Connect(Arc::new(err)) } } diff --git a/druid-shell/src/backend/wayland/events.rs b/druid-shell/src/backend/wayland/events.rs new file mode 100644 index 0000000000..6d8c0dce2c --- /dev/null +++ b/druid-shell/src/backend/wayland/events.rs @@ -0,0 +1,122 @@ +//! Multiplexing events. +//! +//! Calloop is a wrapper around `epoll` essentially allowing us to *select* multiple file +//! descriptors. We use it here to select events from a timer and from wayland. +//! +//! Based on `client-toolkit/src/event_loop.rs` in `smithay-client-toolkit` (MIT Licensed). + +use calloop::{ + generic::{Fd, Generic}, + Dispatcher, EventSource, InsertError, Interest, LoopHandle, Mode, RegistrationToken, +}; +use std::{cell::RefCell, io, rc::Rc}; +use wayland_client::EventQueue; + +/// A wrapper around the wayland event queue that calloop knows how to select. +pub struct WaylandSource { + queue: Rc>, + fd: Generic, +} + +impl WaylandSource { + /// Wrap an `EventQueue` as a `WaylandSource`. + pub fn new(queue: Rc>) -> WaylandSource { + let fd = queue.borrow().display().get_connection_fd(); + WaylandSource { + queue, + fd: Generic::from_fd(fd, Interest::READ, Mode::Level), + } + } + + /// Get a dispatcher that we can insert into our event loop. + pub fn into_dispatcher( + self, + ) -> Dispatcher>, &mut ()) -> io::Result> + { + Dispatcher::new(self, |(), queue, &mut ()| { + queue + .borrow_mut() + .dispatch_pending(&mut (), |event, object, _| { + panic!( + "[druid-shell] Encountered an orphan event: {}@{} : {}\n\ + All events should be handled: please raise an issue", + event.interface, + object.as_ref().id(), + event.name + ); + }) + }) + } +} + +impl EventSource for WaylandSource { + type Event = (); + type Metadata = Rc>; + type Ret = io::Result; + + fn process_events( + &mut self, + _: calloop::Readiness, + _: calloop::Token, + mut callback: F, + ) -> std::io::Result<()> + where + F: FnMut((), &mut Rc>) -> Self::Ret, + { + // in case of readiness of the wayland socket we do the following in a loop, until nothing + // more can be read: + loop { + // 1. read events from the socket if any are available + if let Some(guard) = self.queue.borrow().prepare_read() { + // might be None if some other thread read events before us, concurently + if let Err(e) = guard.read_events() { + if e.kind() != io::ErrorKind::WouldBlock { + return Err(e); + } + } + } + // 2. dispatch any pending event in the queue + // propagate orphan events to the user + let ret = callback((), &mut self.queue); + match ret { + Ok(0) => { + // no events were dispatched even after reading the socket, + // nothing more to do, stop here + break; + } + Ok(_) => {} + Err(e) => { + // in case of error, forward it and fast-exit + return Err(e); + } + } + } + // 3. Once dispatching is finished, flush the responses to the compositor + if let Err(e) = self.queue.borrow().display().flush() { + if e.kind() != io::ErrorKind::WouldBlock { + // in case of error, forward it and fast-exit + return Err(e); + } + // WouldBlock error means the compositor could not process all our messages + // quickly. Either it is slowed down or we are a spammer. + // Should not really happen, if it does we do nothing and will flush again later + } + Ok(()) + } + + fn register(&mut self, poll: &mut calloop::Poll, token: calloop::Token) -> std::io::Result<()> { + self.fd.register(poll, token) + } + + fn reregister( + &mut self, + poll: &mut calloop::Poll, + token: calloop::Token, + ) -> std::io::Result<()> { + self.fd.reregister(poll, token) + } + + fn unregister(&mut self, poll: &mut calloop::Poll) -> std::io::Result<()> { + self.fd.unregister(poll) + } +} diff --git a/druid-shell/src/backend/wayland/menu.rs b/druid-shell/src/backend/wayland/menu.rs index 0f1fea6e84..f84f0782ce 100644 --- a/druid-shell/src/backend/wayland/menu.rs +++ b/druid-shell/src/backend/wayland/menu.rs @@ -26,15 +26,15 @@ struct MenuItem; impl Menu { pub fn new() -> Menu { - todo!() + Menu } pub fn new_for_popup() -> Menu { - todo!() + Menu } pub fn add_dropdown(&mut self, menu: Menu, text: &str, _enabled: bool) { - todo!() + () } pub fn add_item( @@ -45,10 +45,10 @@ impl Menu { enabled: bool, _selected: bool, ) { - todo!() + () } pub fn add_separator(&mut self) { - todo!() + () } } diff --git a/druid-shell/src/backend/wayland/mod.rs b/druid-shell/src/backend/wayland/mod.rs index dda73ed932..18ae7acbbe 100644 --- a/druid-shell/src/backend/wayland/mod.rs +++ b/druid-shell/src/backend/wayland/mod.rs @@ -12,14 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! GTK-based platform support - pub mod application; +mod buffer; pub mod clipboard; pub mod dialog; pub mod error; +mod events; pub mod keycodes; pub mod menu; +mod pointer; pub mod screen; pub mod util; pub mod window; +mod xkb; + +/// Number of bytes for a pixel (argb = 4) +const PIXEL_WIDTH: i32 = 4; +/// Number of frames we need (2 for double buffering) +const NUM_FRAMES: i32 = 2; + +/// Little enum to make it clearer what some return values mean. +#[derive(Copy, Clone)] +enum Changed { + Changed, + Unchanged, +} + +impl Changed { + fn is_changed(self) -> bool { + matches!(self, Changed::Changed) + } + fn is_unchanged(self) -> bool { + matches!(self, Changed::Unchanged) + } +} diff --git a/druid-shell/src/backend/wayland/pointer.rs b/druid-shell/src/backend/wayland/pointer.rs new file mode 100644 index 0000000000..8fe4090c38 --- /dev/null +++ b/druid-shell/src/backend/wayland/pointer.rs @@ -0,0 +1,225 @@ +use crate::{ + common_util::{ClickCounter, IdleCallback}, + dialog::{FileDialogOptions, FileDialogType, FileInfo}, + error::Error as ShellError, + keyboard::{KbKey, KeyEvent, KeyState, Modifiers}, + kurbo::{Insets, Point, Rect, Size, Vec2}, + mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}, + piet::ImageFormat, +}; +use std::collections::VecDeque; +use wayland_client::{ + self as wl, + protocol::{ + wl_buffer::{self, WlBuffer}, + wl_callback, + wl_keyboard::{self, WlKeyboard}, + wl_output::WlOutput, + wl_pointer::{self, WlPointer}, + wl_shm::{self, WlShm}, + wl_shm_pool::WlShmPool, + wl_surface::{self, WlSurface}, + }, +}; +use wayland_cursor::CursorImageBuffer; +use wayland_protocols::{ + unstable::xdg_decoration::v1::client::zxdg_toplevel_decoration_v1::{ + Event as ZxdgToplevelDecorationV1Event, Mode as DecorationMode, ZxdgToplevelDecorationV1, + }, + xdg_shell::client::{ + xdg_surface::{Event as XdgSurfaceEvent, XdgSurface}, + xdg_toplevel::{Event as XdgTopLevelEvent, XdgToplevel}, + xdg_wm_base::XdgWmBase, + }, +}; + +// Button constants (linux specific) +//const BTN_MOUSE: u32 = 0x110; +const BTN_LEFT: u32 = 0x110; +const BTN_RIGHT: u32 = 0x111; +const BTN_MIDDLE: u32 = 0x112; +//const BTN_SIDE: u32 = 0x113; +//const BTN_EXTRA: u32 = 0x114; +//const BTN_FORWARD: u32 = 0x115; +//const BTN_BACK: u32 = 0x116; +//const BTN_TASK: u32 = 0x117; + +/// Collect up mouse events then emit them together on a pointer frame. +pub(crate) struct Pointer { + /// The wayland pointer object + pub(crate) wl_pointer: WlPointer, + /// Currently pressed buttons + pub(crate) buttons: MouseButtons, + /// Current position + pub(crate) pos: Point, + /// Events that have occurred since the last frame. + pub(crate) queued_events: VecDeque, + /// The image surface which contains the cursor image. + pub(crate) cursor_surface: wl::Main, + /// The serial used when the `Enter` event was received + pub(crate) enter_serial: u32, + /// Cache the current cursor, so we can see if it changed + pub(crate) current_cursor: Option, +} + +/// Raw wayland pointer events. +pub(crate) enum PointerEvent { + /// Mouse moved/entered + Motion(Point), + /// Mouse button pressed/released + Button { + button: u32, + state: wl_pointer::ButtonState, + }, + /// Axis movement + Axis { axis: wl_pointer::Axis, value: f64 }, + /// Mouse left + Leave, +} + +/// An enum that we will convert into the different callbacks. +pub(crate) enum MouseEvtKind { + Move(MouseEvent), + Up(MouseEvent), + Down(MouseEvent), + Leave, + Wheel(MouseEvent), +} + +impl Pointer { + /// Create a new pointer + pub fn new(cursor: wl::Main, wl_pointer: WlPointer, serial: u32) -> Self { + Pointer { + wl_pointer, + buttons: MouseButtons::new(), + pos: Point::ZERO, // will get set before we emit any events + queued_events: VecDeque::with_capacity(3), // should be enough most of the time + cursor_surface: cursor, + enter_serial: serial, + current_cursor: None, + } + } + + #[inline] + pub fn push(&mut self, event: PointerEvent) { + self.queued_events.push_back(event); + } + + #[inline] + pub fn pop(&mut self) -> Option { + self.queued_events.pop_front() + } + + #[inline] + pub fn cursor(&self) -> &WlSurface { + &self.cursor_surface + } +} + +/// Gets any queued events. +impl Iterator for Pointer { + type Item = MouseEvtKind; + + fn next(&mut self) -> Option { + use wl_pointer::{Axis, ButtonState}; + // sometimes we need to ignore an event and move on + loop { + let event = self.queued_events.pop_front()?; + match event { + PointerEvent::Motion(point) => { + self.pos = point; + return Some(MouseEvtKind::Move(MouseEvent { + pos: self.pos, + buttons: self.buttons, + // TODO + mods: Modifiers::empty(), + count: 0, + focus: false, + button: MouseButton::None, + wheel_delta: Vec2::ZERO, + })); + } + PointerEvent::Button { button, state } => { + let button = match linux_to_mouse_button(button) { + Some(b) => b, + // Skip unsupported buttons. + None => continue, + }; + let evt = match state { + ButtonState::Pressed => { + self.buttons.insert(button); + MouseEvtKind::Down(MouseEvent { + pos: self.pos, + buttons: self.buttons, + // TODO + mods: Modifiers::empty(), + count: 1, + focus: false, + button, + wheel_delta: Vec2::ZERO, + }) + } + ButtonState::Released => { + self.buttons.remove(button); + MouseEvtKind::Up(MouseEvent { + pos: self.pos, + buttons: self.buttons, + // TODO + mods: Modifiers::empty(), + count: 0, + focus: false, + button, + wheel_delta: Vec2::ZERO, + }) + } + _ => { + log::error!("mouse button changed, but not pressed or released"); + continue; + } + }; + return Some(evt); + } + PointerEvent::Axis { axis, value } => { + let wheel_delta = match axis { + Axis::VerticalScroll => Vec2::new(0., value), + Axis::HorizontalScroll => Vec2::new(value, 0.), + _ => { + log::error!("axis direction not vertical or horizontal"); + continue; + } + }; + return Some(MouseEvtKind::Wheel(MouseEvent { + pos: self.pos, + buttons: self.buttons, + // TODO + mods: Modifiers::empty(), + count: 0, + focus: false, + button: MouseButton::None, + wheel_delta, + })); + } + PointerEvent::Leave => { + // The parent will remove us. + return Some(MouseEvtKind::Leave); + } + } + } + } +} + +impl Drop for Pointer { + fn drop(&mut self) { + self.cursor_surface.destroy(); + } +} + +#[inline] +fn linux_to_mouse_button(button: u32) -> Option { + match button { + BTN_LEFT => Some(MouseButton::Left), + BTN_RIGHT => Some(MouseButton::Right), + BTN_MIDDLE => Some(MouseButton::Middle), + _ => None, + } +} diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index 021c7d4f1b..30007d0530 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -1,4 +1,3 @@ -// Copyright 2019 The Druid Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,163 +11,182 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! GTK window creation and management. +#![allow(clippy::single_match)] -use std::any::Any; -use std::cell::{Cell, RefCell}; -use std::convert::{TryFrom, TryInto}; -use std::ffi::c_void; -use std::os::raw::{c_int, c_uint}; -use std::panic::Location; -use std::ptr; -use std::slice; -use std::sync::{Arc, Mutex, Weak}; -use std::time::Instant; - -use anyhow::anyhow; +use anyhow::{anyhow, format_err}; use cairo::Surface; - -use crate::kurbo::{Point, Rect, Size, Vec2}; -use crate::piet::{Piet, PietText, RenderContext}; - -use crate::common_util::{ClickCounter, IdleCallback}; -use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; -use crate::error::Error as ShellError; -use crate::keyboard::{KbKey, KeyEvent, KeyState, Modifiers}; -use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}; -use crate::piet::ImageFormat; -use crate::region::Region; -use crate::scale::{Scalable, Scale, ScaledArea}; -use crate::window; -use crate::window::{FileDialogToken, IdleToken, TimerToken, WinHandler, WindowLevel}; - -use super::application::Application; -use super::dialog; -use super::keycodes; -use super::menu::Menu; -use super::util; - -#[derive(Clone, Default)] -pub struct WindowHandle; - -/// Builder abstraction for creating new windows -pub(crate) struct WindowBuilder { - app: Application, - handler: Option>, - title: String, - menu: Option, - position: Option, - level: Option, - state: Option, - size: Size, - min_size: Option, - resizable: bool, - show_titlebar: bool, +use nix::{ + errno::Errno, + fcntl::OFlag, + sys::{ + mman::{mmap, munmap, shm_open, MapFlags, ProtFlags}, + stat::Mode, + }, + unistd::{close, ftruncate}, +}; +use std::{ + any::Any, + cell::{Cell, RefCell}, + collections::{BTreeMap, HashSet, VecDeque}, + convert::{TryFrom, TryInto}, + ffi::c_void, + fmt, + ops::Deref, + os::{ + raw::{c_int, c_uint}, + unix::io::RawFd, + }, + panic::Location, + ptr::{self, NonNull}, + rc::{Rc, Weak as WeakRc}, + slice, + sync::{Arc, Mutex, Weak}, + time::{Duration, Instant, SystemTime}, +}; +use wayland_client::{ + self as wl, + protocol::{ + wl_buffer::{self, WlBuffer}, + wl_callback, + wl_keyboard::{self, WlKeyboard}, + wl_output::WlOutput, + wl_pointer::{self, WlPointer}, + wl_shm::{self, WlShm}, + wl_shm_pool::WlShmPool, + wl_surface::{self, WlSurface}, + }, +}; +use wayland_cursor::CursorImageBuffer; +use wayland_protocols::{ + unstable::xdg_decoration::v1::client::zxdg_toplevel_decoration_v1::{ + Event as ZxdgToplevelDecorationV1Event, Mode as DecorationMode, ZxdgToplevelDecorationV1, + }, + xdg_shell::client::{ + xdg_surface::{Event as XdgSurfaceEvent, XdgSurface}, + xdg_toplevel::{Event as XdgTopLevelEvent, XdgToplevel}, + xdg_wm_base::XdgWmBase, + }, +}; + +use super::{ + application::{Application, ApplicationData, Output}, + buffer::{Buffer, Buffers, Mmap, RawRect, RawSize, Shm}, + dialog, keycodes, + menu::Menu, + pointer::{MouseEvtKind, Pointer}, + util, Changed, NUM_FRAMES, PIXEL_WIDTH, +}; +use crate::{ + backend::shared::Timer, + common_util::{ClickCounter, IdleCallback}, + dialog::{FileDialogOptions, FileDialogType, FileInfo}, + error::Error as ShellError, + keyboard::{KbKey, KeyEvent, KeyState, Modifiers}, + kurbo::{Insets, Point, Rect, Size, Vec2}, + mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}, + piet::ImageFormat, + piet::{Piet, PietText, RenderContext}, + region::Region, + scale::{Scalable, Scale, ScaledArea}, + text::Event, + window::{self, FileDialogToken, IdleToken, TimerToken, WinHandler, WindowLevel}, + TextFieldToken, +}; + +//TODO we flash the old window size before adjusting to the new size. This seems to leave some +//artifact on another monitor if the image would have spread across. The todo is investigate if we +//can avoid this. I think it might be something to do with telling the compositor about our new +//geometry? +// +// I think this bit of debug output might be useful (from GTK, which doesn't suffer from this +// problem). UPDATE I'm using the unstable window decoration protocol. GTK doesn't use this, since +// there is some arcane thing to do with `set_opaque_region` to make it backwards compatible. Let's +// ignore this for now. +//[2566306.298] xdg_toplevel@32.configure(580, 450, array) +//[2566306.344] xdg_surface@22.configure(74094) +//[2566306.359] -> wl_buffer@36.destroy() +//[2566306.366] -> wl_shm_pool@37.destroy() +//[2566306.728] -> wl_surface@26.set_buffer_scale(2) +//[2566306.752] -> xdg_surface@22.ack_configure(74094) +//[2566307.207] -> wl_shm@4.create_pool(new id wl_shm_pool@40, fd 31, 4176000) +//[2566307.229] -> wl_shm_pool@40.create_buffer(new id wl_buffer@41, 0, 1160, 900, 4640, 0) +//[2566315.088] -> wl_surface@26.attach(wl_buffer@41, 0, 0) +//[2566315.111] -> wl_surface@26.set_buffer_scale(2) +//[2566315.116] -> wl_surface@26.damage(0, 0, 580, 450) +//[2566315.137] -> xdg_toplevel@32.set_min_size(400, 427) +//[2566315.153] -> xdg_toplevel@32.set_max_size(0, 0) +//[2566315.160] -> xdg_surface@22.set_window_geometry(0, 0, 580, 450) +//[2566315.170] -> wl_compositor@6.create_region(new id wl_region@42) +//[2566315.176] -> wl_region@42.add(0, 0, 580, 450) +//[2566315.190] -> wl_surface@26.set_opaque_region(wl_region@42) +//[2566315.195] -> wl_region@42.destroy() +//[2566315.237] -> wl_surface@26.frame(new id wl_callback@43) +//[2566315.256] -> wl_surface@26.commit() + +// In cairo and Wayland, alpha is pre-multiplied. Yay. + +#[derive(Default, Clone)] +pub struct WindowHandle { + // holding a weak reference to the window is copied from the windows backend. + pub(crate) data: WeakRc, } -#[derive(Clone)] -pub struct IdleHandle; - -#[derive(Clone, PartialEq)] -pub struct CustomCursor; - -impl WindowBuilder { - pub fn new(app: Application) -> WindowBuilder { - WindowBuilder { - app, - handler: None, - title: String::new(), - menu: None, - size: Size::new(500.0, 400.0), - position: None, - level: None, - state: None, - min_size: None, - resizable: true, - show_titlebar: true, - } - } - - pub fn set_handler(&mut self, handler: Box) { - self.handler = Some(handler); - } - - pub fn set_size(&mut self, size: Size) { - self.size = size; - } - - pub fn set_min_size(&mut self, size: Size) { - self.min_size = Some(size); - } - - pub fn resizable(&mut self, resizable: bool) { - self.resizable = resizable; - } - - pub fn show_titlebar(&mut self, show_titlebar: bool) { - self.show_titlebar = show_titlebar; - } - - pub fn set_position(&mut self, position: Point) { - self.position = Some(position); - } - - pub fn set_level(&mut self, level: WindowLevel) { - self.level = Some(level); - } - - pub fn set_window_state(&mut self, state: window::WindowState) { - self.state = Some(state); - } - - pub fn set_title(&mut self, title: impl Into) { - self.title = title.into(); - } - - pub fn set_menu(&mut self, menu: Menu) { - self.menu = Some(menu); - } - - pub fn build(self) -> Result { - todo!() +impl PartialEq for WindowHandle { + fn eq(&self, other: &Self) -> bool { + self.data.ptr_eq(&other.data) } } impl WindowHandle { - pub fn show(&self) { - todo!() - } + pub fn show(&self) {} pub fn resizable(&self, resizable: bool) { - todo!() + //todo!() } pub fn show_titlebar(&self, show_titlebar: bool) { - todo!() + //todo!() } pub fn set_position(&self, position: Point) { - todo!() + //todo!() } pub fn get_position(&self) -> Point { todo!() } + pub fn content_insets(&self) -> Insets { + // TODO + Insets::from(0.) + } + pub fn set_level(&self, level: WindowLevel) { - todo!() + log::warn!("level is unsupported on wayland"); } pub fn set_size(&self, size: Size) { - todo!() + log::warn!("setting the size dynamically is unsupported on wayland"); } pub fn get_size(&self) -> Size { - todo!() + if let Some(data) = self.data.upgrade() { + // size in pixels, so we must apply scale + // TODO check the logic here. + let logical_size = data.logical_size.get(); + let scale = data.scale.get() as f64; + Size::new( + logical_size.width as f64 * scale, + logical_size.height as f64 * scale, + ) + } else { + // TODO panic? + Size::ZERO + } } pub fn set_window_state(&mut self, size_state: window::WindowState) { - todo!() + //todo!() } pub fn get_window_state(&self) -> window::WindowState { @@ -181,42 +199,116 @@ impl WindowHandle { /// Close the window. pub fn close(&self) { - todo!() + if let Some(data) = self.data.upgrade() { + // TODO destroy resources + if let Some(app_data) = data.app_data.upgrade() { + app_data.shutdown.set(true); + } + } } /// Bring this window to the front of the window stack and give it focus. pub fn bring_to_front_and_focus(&self) { - todo!() + //todo!() } /// Request a new paint, but without invalidating anything. pub fn request_anim_frame(&self) { - todo!() + if let Some(data) = self.data.upgrade() { + if !data.anim_frame_requested.get() { + let cb = data.wl_surface.frame(); + let handle = self.clone(); + cb.quick_assign(with_cloned!(data; move |_, event, _| match event { + wl_callback::Event::Done { callback_data } => { + data.anim_frame_requested.set(false); + data.request_paint(); + } + _ => panic!("done is the only event"), + })); + data.anim_frame_requested.set(true); + } + } } /// Request invalidation of the entire window contents. pub fn invalidate(&self) { - todo!() + // This is one of 2 methods the user can use to schedule a repaint, the other is + // `invalidate_rect`. + if let Some(data) = self.data.upgrade() { + data.invalidate() + } } /// Request invalidation of one rectangle, which is given in display points relative to the /// drawing area. pub fn invalidate_rect(&self, rect: Rect) { - todo!() + if let Some(data) = self.data.upgrade() { + data.invalidate_rect(rect) + } } pub fn text(&self) -> PietText { + PietText::new() + } + + pub fn add_text_field(&self) -> TextFieldToken { todo!() } - pub fn request_timer(&self, deadline: Instant) -> TimerToken { + pub fn remove_text_field(&self, token: TextFieldToken) { todo!() } - pub fn set_cursor(&mut self, cursor: &Cursor) { + pub fn set_focused_text_field(&self, active_field: Option) { + todo!() + } + + pub fn update_text_field(&self, token: TextFieldToken, update: Event) { todo!() } + pub fn request_timer(&self, deadline: Instant) -> TimerToken { + if let Some(data) = self.data.upgrade() { + if let Some(app_data) = data.app_data.upgrade() { + //println!("Timer requested"); + let now = instant::Instant::now(); + let mut timers = app_data.timers.borrow_mut(); + let sooner = timers + .peek() + .map(|timer| deadline < timer.deadline()) + .unwrap_or(true); + let timer = Timer::new(deadline, data.id()); + timers.push(timer); + // It is possible that the deadline has passed since it was set. + // TODO replace `Duration::new(0, 0)` with `Duration::ZERO` when it is stable. + let timeout = if deadline < now { + Duration::new(0, 0) + } else { + deadline - now + }; + if sooner { + app_data.timer_handle.cancel_all_timeouts(); + app_data.timer_handle.add_timeout(timeout, timer.token()); + } + return timer.token(); + } + } + panic!("requested timer on a window that was destroyed"); + } + + pub fn set_cursor(&mut self, cursor: &Cursor) { + if let Some(data) = self.data.upgrade() { + let mut _pointer = data.pointer.borrow_mut(); + let pointer = _pointer.as_mut().unwrap(); + // Setting a new cursor involves communicating with the server, so don't do it if we + // don't have to. + if matches!(&pointer.current_cursor, Some(c) if c == cursor) { + return; + } + pointer.current_cursor = Some(cursor.clone()); + } + } + pub fn make_cursor(&self, desc: &CursorDesc) -> Option { todo!() } @@ -231,12 +323,17 @@ impl WindowHandle { /// Get a handle that can be used to schedule an idle task. pub fn get_idle_handle(&self) -> Option { - todo!() + None } /// Get the `Scale` of the window. pub fn get_scale(&self) -> Result { - todo!() + if let Some(data) = self.data.upgrade() { + let scale = data.scale.get() as f64; + Ok(Scale::new(scale, scale)) + } else { + Err(ShellError::WindowDropped) + } } pub fn set_menu(&self, menu: Menu) { @@ -248,8 +345,535 @@ impl WindowHandle { } pub fn set_title(&self, title: impl Into) { + if let Some(data) = self.data.upgrade() { + data.xdg_toplevel.set_title(title.into()); + } + } +} + +pub struct WindowData { + pub(crate) app_data: WeakRc, + pub(crate) wl_surface: wl::Main, + pub(crate) xdg_surface: wl::Main, + pub(crate) xdg_toplevel: wl::Main, + pub(crate) zxdg_toplevel_decoration_v1: wl::Main, + /// The outputs that our surface is present on (we should get the first enter event early). + pub(crate) outputs: RefCell>, + /// Buffers in our shared memory. + // Buffers sometimes need to move references to themselves into closures, so must be behind a + // reference counter. + pub(crate) buffers: Rc>, + /// The logical size of the next frame. + pub(crate) logical_size: Cell, + /// The scale we are rendering to (defaults to 1) + pub(crate) scale: Cell, + /// Whether we've currently got keyboard focus. + pub(crate) keyboard_focus: Cell, + /// If we've currently got pointer focus, this object tracks pointer state. + pub(crate) pointer: RefCell>, + /// Whether we have requested an animation frame. This stops us requesting more than 1. + anim_frame_requested: Cell, + /// Track whether an event handler invalidated any regions. After the event handler has been + /// released, repaint if true. TODO refactor this into an enum of evens that might call in to + /// user code, and so need to be deferred. + paint_scheduled: Cell, + /// Contains the callbacks from user code. + pub(crate) handler: RefCell>, + /// Rects of the image that are damaged and need repainting in the logical coordinate space. + /// + /// This lives outside `data` because they can be borrowed concurrently without re-entrancy. + damaged_region: RefCell, +} + +impl WindowData { + /// Sets the physical size. + /// + /// Up to the caller to make sure `buffers.size`, `logical_size` and `scale` are consistent. + fn set_physical_size(&self, new_size: RawSize) { + self.buffers.set_size(new_size) + } + + /// Assert that the physical size = logical size * scale + #[allow(unused)] + fn assert_size(&self) { + assert_eq!( + self.buffers.size(), + RawSize::from(self.logical_size.get()).scale(self.scale.get()), + "phy {:?} == logic {:?} * {}", + self.buffers.size(), + self.logical_size.get(), + self.scale.get() + ); + } + + /// Recompute the scale to use (the maximum of all the scales for the different outputs this + /// screen is drawn to). + fn recompute_scale(&self) -> i32 { + if let Some(app_data) = self.app_data.upgrade() { + let mut scale = 0; + for id in self.outputs.borrow().iter() { + if let Some(output) = app_data.outputs.borrow().get(&id) { + scale = scale.max(output.scale); + } else { + log::warn!( + "we still have a reference to an output that's gone away. The output had id {}", + id + ); + } + } + if scale == 0 { + log::warn!("wayland never reported which output we are drawing to"); + 1 + } else { + scale + } + } else { + panic!("should never recompute scale of window that has been dropped"); + } + } + + fn set_cursor(&self, buf: &CursorImageBuffer) { + let (hotspot_x, hotspot_y) = buf.hotspot(); + let _pointer = self.pointer.borrow(); + let pointer = _pointer.as_ref().unwrap(); + pointer.wl_pointer.set_cursor( + pointer.enter_serial, + Some(&pointer.cursor_surface), + hotspot_x as i32, + hotspot_y as i32, + ); + pointer.cursor_surface.attach(Some(&*buf), 0, 0); + pointer + .cursor_surface + .damage_buffer(0, 0, i32::MAX, i32::MAX); + pointer.cursor_surface.commit(); + } + + /// Get the wayland object id for the `wl_surface` associated with this window. + /// + /// We use this as the key for the window. + pub(crate) fn id(&self) -> u32 { + wl::Proxy::from(self.wl_surface.detach()).id() + } + + /// Sets the scale + /// + /// Up to the caller to make sure `physical_size`, `logical_size` and `scale` are consistent. + fn set_scale(&self, new_scale: i32) -> Changed { + if self.scale.get() != new_scale { + self.scale.set(new_scale); + // (re-entrancy) Report change to client + let druid_scale = Scale::new(new_scale as f64, new_scale as f64); + self.handler.borrow_mut().scale(druid_scale); + Changed::Changed + } else { + Changed::Unchanged + } + } + + /// Schedule a paint (in response to invalidation). + pub(crate) fn schedule_paint(&self) { + self.paint_scheduled.set(true); + } + + /// If a repaint was scheduled, then execute it. + pub(crate) fn check_for_scheduled_paint(&self) { + if self.paint_scheduled.get() { + self.request_paint(); + } + } + + /// Request to `buffers` that the next frame be painted. + /// + /// If the next frame is ready, then it will be painted immediately, otherwise a paint will be + /// scheduled to take place when a frame is released. + /// + /// ```text + /// self.request_paint -> calls buffers.request_paint -> calls self.paint (possibly not immediately) + /// ``` + fn request_paint(&self) { + self.paint_scheduled.set(false); + self.buffers.request_paint(); + } + + /// Paint the next frame. + /// + /// The buffers object is responsible for calling this function after we called + /// `request_paint`. + /// + /// - `buf` is what we draw the frame into + /// - `size` is the physical size in pixels we are drawing. + /// - `force` means draw the whole frame, even if it wasn't all invalidated. + pub(crate) fn paint(&self, size: RawSize, buf: &mut [u8], force: bool) { + //log::trace!("Paint call"); + //self.data.borrow().assert_size(); + if force { + self.invalidate(); + } else { + let damaged_region = self.damaged_region.borrow_mut(); + for rect in damaged_region.rects() { + // Convert it to physical coordinate space. + let rect = RawRect::from(*rect).scale(self.scale.get()); + self.wl_surface.damage_buffer( + rect.x0, + rect.y0, + rect.x1 - rect.x0, + rect.y1 - rect.y0, + ); + } + if damaged_region.is_empty() { + // Nothing to draw, so we can finish here! + return; + } + } + + // create cairo context (safety: we must drop the buffer before we commit the frame) + // TODO: Cairo is native-endian while wayland is little-endian, which is a pain. Currently + // will give incorrect results on big-endian architectures. + // TODO cairo might use a different stride than the width of the format. Since we always + // use argb32 which is 32-bit aligned we should be ok, but strictly speaking cairo might + // choose a wider stride and read past the end of our buffer (UB). Fixing this would + // require a fair bit of effort. + unsafe { + let physical_size = self.buffers.size(); + // We're going to lie about the lifetime of our buffer here. This is (I think) ok, + // becuase the Rust wrapper for cairo is overly pessimistic: the buffer only has to + // last as long as the `ImageSurface` (which we know this buffer will). + let buf: &'static mut [u8] = &mut *(buf as *mut _); + let cairo_surface = cairo::ImageSurface::create_for_data( + buf, + cairo::Format::ARgb32, + physical_size.width, + physical_size.height, + physical_size.width * PIXEL_WIDTH, + ) + .unwrap(); + // we can't do much if we can't create cairo context + let ctx = cairo::Context::new(&cairo_surface).unwrap(); + // Apply scaling + let scale = self.scale.get() as f64; + ctx.scale(scale, scale); + // TODO we don't clip cairo stuff not in the damaged region. This might be a perf win? + let mut piet = Piet::new(&ctx); + // Actually paint the new frame + let region = self.damaged_region.borrow(); + // The handler must not be already borrowed. This may mean deferring this call. + self.handler.borrow_mut().paint(&mut piet, &*region); + } + // reset damage ready for next frame. + self.damaged_region.borrow_mut().clear(); + + self.buffers.attach(); + self.wl_surface.commit(); + } + + /// Request invalidation of the entire window contents. + fn invalidate(&self) { + // This is one of 2 methods the user can use to schedule a repaint, the other is + // `invalidate_rect`. + let window_rect = self.logical_size.get().to_rect(); + self.damaged_region.borrow_mut().add_rect(window_rect); + self.schedule_paint(); + } + + /// Request invalidation of one rectangle, which is given in display points relative to the + /// drawing area. + fn invalidate_rect(&self, rect: Rect) { + // Quick check to see if we can skip the rect entirely (if it is outside the visible + // screen). + if rect.intersect(self.logical_size.get().to_rect()).is_empty() { + return; + } + /* this would be useful for debugging over-keen invalidation by clients. + println!( + "{:?} {:?}", + rect, + self.with_data(|data| data.logical_size.to_rect()) + ); + */ + self.damaged_region.borrow_mut().add_rect(rect); + self.schedule_paint() + } + + /// If there are any pending pointer events, get the next one. + pub(crate) fn pop_pointer_event(&self) -> Option { + self.pointer.borrow_mut().as_mut()?.next() + } + + /// Initialize the pointer struct, and return the surface used for the cursor. + pub(crate) fn init_pointer(&self, pointer: WlPointer, serial: u32) { + if let Some(app_data) = self.app_data.upgrade() { + let cursor = app_data.wl_compositor.create_surface(); + // TODO for now we've hard-coded 2x scale. This should be dynamic. + //cursor.set_buffer_scale(2); + // ignore all events + cursor.quick_assign(|_, _, _| ()); + *self.pointer.borrow_mut() = Some(Pointer::new(cursor, pointer, serial)); + } + } + + fn set_system_cursor(&mut self, cursor: Cursor) { + if let Some(app_data) = self.app_data.upgrade() { + #[allow(deprecated)] + let cursor = match cursor { + // TODO check these are all correct + Cursor::Arrow => "left_ptr", + Cursor::IBeam => "xterm", + Cursor::Crosshair => "cross", + Cursor::OpenHand => "openhand", + Cursor::NotAllowed => "X_cursor", + Cursor::ResizeLeftRight => "row-resize", + Cursor::ResizeUpDown => "col-resize", + // TODO custom cursors + _ => "left_ptr", + }; + let mut theme = app_data.cursor_theme.borrow_mut(); + let cursor = theme.get_cursor(cursor).unwrap(); + // Just use the first image, people using animated cursors have already made bad life + // choices and shouldn't expect it to work. + let buf = &cursor[cursor.frame_and_duration(0).frame_index]; + self.set_cursor(buf); + } + } +} + +/// Builder abstraction for creating new windows +pub(crate) struct WindowBuilder { + app_data: WeakRc, + handler: Option>, + title: String, + menu: Option, + position: Option, + level: Option, + state: Option, + // pre-scaled + size: Size, + min_size: Option, + resizable: bool, + show_titlebar: bool, +} + +#[derive(Clone)] +pub struct IdleHandle; + +#[derive(Clone, PartialEq)] +pub struct CustomCursor; + +impl WindowBuilder { + pub fn new(app: Application) -> WindowBuilder { + WindowBuilder { + app_data: Rc::downgrade(&app.data), + handler: None, + title: String::new(), + menu: None, + size: Size::new(500.0, 400.0), + position: None, + level: None, + state: None, + min_size: None, + resizable: true, + show_titlebar: true, + } + } + + pub fn set_handler(&mut self, handler: Box) { + self.handler = Some(handler); + } + + pub fn set_size(&mut self, size: Size) { + self.size = size; + } + + pub fn set_min_size(&mut self, size: Size) { + self.min_size = Some(size); + } + + pub fn resizable(&mut self, resizable: bool) { + self.resizable = resizable; + } + + pub fn show_titlebar(&mut self, show_titlebar: bool) { + self.show_titlebar = show_titlebar; + } + + pub fn set_transparent(&mut self, transparent: bool) { todo!() } + + pub fn set_position(&mut self, position: Point) { + self.position = Some(position); + } + + pub fn set_level(&mut self, level: WindowLevel) { + self.level = Some(level); + } + + pub fn set_window_state(&mut self, state: window::WindowState) { + self.state = Some(state); + } + + pub fn set_title(&mut self, title: impl Into) { + self.title = title.into(); + } + + pub fn set_menu(&mut self, menu: Menu) { + self.menu = Some(menu); + } + + pub fn build(self) -> Result { + if matches!(self.menu, Some(_)) { + //panic!("menu unsupported"); + } + let app_data = match self.app_data.upgrade() { + Some(app_data) => app_data, + None => return Err(ShellError::ApplicationDropped), + }; + let handler = self.handler.expect("must set a window handler"); + + let wl_surface = app_data.wl_compositor.create_surface(); + + let xdg_surface = app_data.xdg_base.get_xdg_surface(&wl_surface); + let xdg_toplevel = xdg_surface.get_toplevel(); + let zxdg_toplevel_decoration_v1 = app_data + .zxdg_decoration_manager_v1 + .get_toplevel_decoration(&xdg_toplevel); + zxdg_toplevel_decoration_v1.set_mode(DecorationMode::ServerSide); + xdg_toplevel.set_title(self.title); + if let Some(size) = self.min_size { + // for sanity + assert!(size.width >= 0. && size.height >= 0.); + xdg_toplevel.set_min_size(size.width as i32, size.height as i32); + } + + let data = Rc::new(WindowData { + app_data: Rc::downgrade(&app_data), + wl_surface, + xdg_surface, + xdg_toplevel, + zxdg_toplevel_decoration_v1, + outputs: RefCell::new(HashSet::new()), + buffers: Buffers::new(app_data.wl_shm.clone(), self.size.into()), + logical_size: Cell::new(self.size), + scale: Cell::new(1), + keyboard_focus: Cell::new(false), + pointer: RefCell::new(None), + anim_frame_requested: Cell::new(false), + paint_scheduled: Cell::new(false), + handler: RefCell::new(handler), + damaged_region: RefCell::new(Region::EMPTY), + }); + + let weak_data = Rc::downgrade(&data); + // Hook up the child -> parent weak pointer. + unsafe { + // Safety: safe because no other references to the data are dereferenced for the life + // of the reference (the only refs are the Rc and the weak Rc we just created). + let buffers: &mut Buffers<{ NUM_FRAMES as usize }> = + &mut *(Rc::as_ptr(&data.buffers) as *mut _); + buffers.set_window_data(weak_data); + } + + // Insert a reference to us in the application. This is the main strong reference to the + // app. + if let Some(old_data) = app_data + .surfaces + .borrow_mut() + .insert(data.id(), data.clone()) + { + panic!("wayland should use unique object IDs"); + } + + // event handlers + data.xdg_toplevel.quick_assign(with_cloned!( + data; + move |xdg_toplevel, event, _| match event { + XdgTopLevelEvent::Configure { + width, + height, + states, + } => { + // Only change the size if the passed width/height is non-zero - otherwise we + // choose. + if width != 0 && height != 0 { + // The size here is the logical size + let scale = data.scale.get(); + let raw_logical_size = RawSize { width, height }; + let logical_size = Size::from(raw_logical_size); + if data.logical_size.get() != logical_size { + data.logical_size.set(logical_size); + data.buffers.set_size(raw_logical_size.scale(scale)); + // (re-entrancy) Report change to client + data.handler.borrow_mut().size(logical_size); + } + // Check if the client requested a repaint. + data.check_for_scheduled_paint(); + } + } + XdgTopLevelEvent::Close => { + data.handler.borrow_mut().request_close(); + } + _ => (), + } + )); + + data.zxdg_toplevel_decoration_v1.quick_assign(with_cloned!( + data; + move |zxdg_toplevel_decoration_v1, event, _| match event { + ZxdgToplevelDecorationV1Event::Configure { mode } => { + // do nothing for now + log::debug!("{:?}", mode); + } + _ => (), + } + )); + + data.xdg_surface.quick_assign( + with_cloned!(data; move |xdg_surface, event, _| match event { + XdgSurfaceEvent::Configure { serial } => { + xdg_surface.ack_configure(serial); + data.request_paint(); // will also rebuild buffers if needed. + } + _ => (), + }), + ); + + data.wl_surface + .quick_assign(with_cloned!(data; move |_, event, _| { + match event { + wl_surface::Event::Enter { output } => { + data + .outputs + .borrow_mut() + .insert(wl::Proxy::from(output).id()); + } + wl_surface::Event::Leave { output } => { + data + .outputs + .borrow_mut() + .remove(&wl::Proxy::from(output).id()); + } + _ => (), + } + let new_scale = data.recompute_scale(); + if data.set_scale(new_scale).is_changed() { + data.wl_surface.set_buffer_scale(new_scale); + // We also need to change the physical size to match the new scale + data.set_physical_size(RawSize::from(data.logical_size.get()).scale(new_scale)); + // always repaint, because the scale changed. + data.schedule_paint(); + } + })); + + // Notify wayland that we've finished setting up. + data.wl_surface.commit(); + + let handle = WindowHandle { + data: Rc::downgrade(&data), + }; + data.handler.borrow_mut().connect(&handle.clone().into()); + + Ok(handle) + } } impl IdleHandle { @@ -261,7 +885,7 @@ impl IdleHandle { /// priority than other UI events, but that's not necessarily the case. pub fn add_idle_callback(&self, callback: F) where - F: FnOnce(&dyn Any) + Send + 'static, + F: FnOnce(&mut dyn WinHandler) + Send + 'static, { todo!() } diff --git a/druid-shell/src/backend/wayland/xkb.rs b/druid-shell/src/backend/wayland/xkb.rs new file mode 100644 index 0000000000..c021686218 --- /dev/null +++ b/druid-shell/src/backend/wayland/xkb.rs @@ -0,0 +1,526 @@ +//! A minimal wrapper around Xkb for our use. + +#![allow(non_upper_case_globals)] + +use crate::{ + backend::shared::{code_to_location, hardware_keycode_to_code}, + KeyEvent, KeyState, Modifiers, +}; +use keyboard_types::{Code, Key}; +use std::{ + convert::{TryFrom, TryInto}, + ptr, +}; +use xkbcommon_sys::*; + +const MAX_KEY_LEN: usize = 32; + +/// A global xkb context object. +/// +/// Reference counted under the hood. +// Assume this isn't threadsafe unless proved otherwise. (e.g. don't implement Send/Sync) +// TODO do we need UnsafeCell? +pub struct Context { + inner: *mut xkb_context, +} + +impl Context { + /// Create a new xkb context. + /// + /// The returned object is lightweight and clones will point at the same context internally. + pub fn new() -> Self { + unsafe { + let inner = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + Context { inner } + } + } + + /// Create a keymap from some given data. + /// + /// Uses `xkb_keymap_new_from_buffer` under the hood. + pub fn keymap_from_slice(&self, buffer: &[u8]) -> Keymap { + // TODO we hope that the keymap doesn't borrow the underlying data. If it does' we need to + // use Rc. We'll find out soon enough if we get a segfault. + // TODO we hope that the keymap inc's the reference count of the context. + assert!( + buffer.iter().copied().any(|byte| byte == 0), + "`keymap_from_slice` expects a null-terminated string" + ); + unsafe { + let keymap = xkb_keymap_new_from_string( + self.inner, + buffer.as_ptr() as *const i8, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS, + ); + assert!(!keymap.is_null()); + Keymap::new(keymap) + } + } + + /// Set the log level using `log` levels. + /// + /// Becuase `xkb` has a `critical` error, each rust error maps to 1 above (e.g. error -> + /// critical, warn -> error etc.) + pub fn set_log_level(&self, level: log::Level) { + use log::Level; + let level = match level { + Level::Error => XKB_LOG_LEVEL_CRITICAL, + Level::Warn => XKB_LOG_LEVEL_ERROR, + Level::Info => XKB_LOG_LEVEL_WARNING, + Level::Debug => XKB_LOG_LEVEL_INFO, + Level::Trace => XKB_LOG_LEVEL_DEBUG, + }; + unsafe { + xkb_context_set_log_level(self.inner, level); + } + } +} + +impl Clone for Context { + fn clone(&self) -> Self { + unsafe { + xkb_context_ref(self.inner); + Context { inner: self.inner } + } + } +} + +impl Drop for Context { + fn drop(&mut self) { + unsafe { + xkb_context_unref(self.inner); + } + } +} + +pub struct Keymap { + inner: *mut xkb_keymap, +} + +impl Keymap { + /// Create a new keymap object. + /// + /// # Safety + /// + /// This function takes ownership of the `xkb_keymap`. It must be valid when this function is + /// called, and not deallocated elsewhere. + unsafe fn new(inner: *mut xkb_keymap) -> Self { + Keymap { inner } + } + + pub fn state(&self) -> State { + unsafe { State::new(xkb_state_new(self.inner)) } + } +} + +impl Clone for Keymap { + fn clone(&self) -> Self { + unsafe { + xkb_keymap_ref(self.inner); + Self { inner: self.inner } + } + } +} + +impl Drop for Keymap { + fn drop(&mut self) { + unsafe { + xkb_keymap_unref(self.inner); + } + } +} + +pub struct State { + inner: *mut xkb_state, +} + +impl State { + unsafe fn new(inner: *mut xkb_state) -> Self { + Self { inner } + } + + pub fn key_event(&self, scancode: u32, state: KeyState) -> KeyEvent { + // TODO make sure this adjustment is correct + let scancode = scancode + 8; + let code = u16::try_from(scancode) + .map(hardware_keycode_to_code) + .unwrap_or(Code::Unidentified); + let key = self.get_logical_key(scancode); + // TODO this is lazy - really should use xkb i.e. augment the get_logical_key method. + let location = code_to_location(code); + // TODO rest are unimplemented + let mods = Modifiers::empty(); + let repeat = false; + // TODO we get the information for this from a wayland event + let is_composing = false; + + // Update xkb's state (e.g. return capitals if we've pressed shift) + unsafe { + xkb_state_update_key( + self.inner, + scancode, + match state { + KeyState::Down => XKB_KEY_DOWN, + KeyState::Up => XKB_KEY_UP, + }, + ); + } + KeyEvent { + state, + key, + code, + location, + mods, + repeat, + is_composing, + } + } + + fn get_logical_key(&self, scancode: u32) -> Key { + let mut key = map_key(self.key_get_one_sym(scancode)); + if matches!(key, Key::Unidentified) { + if let Some(s) = self.key_get_utf8(scancode) { + key = Key::Character(s); + } + } + key + } + + fn key_get_one_sym(&self, scancode: u32) -> u32 { + unsafe { xkb_state_key_get_one_sym(self.inner, scancode) } + } + + /// Get the string representation of a key. + // TODO `keyboard_types` forces us to return a String, but it would be nicer if we could stay + // on the stack, especially since we expect most results to be pretty small. + fn key_get_utf8(&self, scancode: u32) -> Option { + unsafe { + // First get the size we will need + let len = xkb_state_key_get_utf8(self.inner, scancode, ptr::null_mut(), 0); + if len == 0 { + return None; + } + // add 1 because we will get a null-terminated string. + let len = usize::try_from(len).unwrap() + 1; + let mut buf: Vec = Vec::new(); + buf.resize(len, 0); + xkb_state_key_get_utf8(self.inner, scancode, buf.as_mut_ptr() as *mut i8, len); + debug_assert!(buf[buf.len() - 1] == 0); + buf.pop(); + Some(String::from_utf8(buf).unwrap()) + } + } +} + +impl Clone for State { + fn clone(&self) -> Self { + unsafe { + xkb_state_ref(self.inner); + Self { inner: self.inner } + } + } +} + +impl Drop for State { + fn drop(&mut self) { + unsafe { + xkb_state_unref(self.inner); + } + } +} + +/// Map from an xkb_common key code to a key, if possible. +// I couldn't find the commented out keys +fn map_key(keysym: u32) -> Key { + use Key::*; + match keysym { + XKB_KEY_Alt_L => Alt, + XKB_KEY_Alt_R => AltGraph, + XKB_KEY_Caps_Lock => CapsLock, + XKB_KEY_Control_L | XKB_KEY_Control_R => Control, + XKB_KEY_function => Fn, + // FnLock, - can't find in xkb + XKB_KEY_Meta_L | XKB_KEY_Meta_R => Meta, + XKB_KEY_Num_Lock => NumLock, + XKB_KEY_Scroll_Lock => ScrollLock, + XKB_KEY_Shift_L | XKB_KEY_Shift_R => Shift, + // Symbol, + // SymbolLock, + XKB_KEY_Hyper_L | XKB_KEY_Hyper_R => Hyper, + XKB_KEY_Super_L | XKB_KEY_Super_R => Super, + XKB_KEY_Return => Enter, + XKB_KEY_Tab => Tab, + XKB_KEY_Down => ArrowDown, + XKB_KEY_Left => ArrowLeft, + XKB_KEY_Right => ArrowRight, + XKB_KEY_Up => ArrowUp, + XKB_KEY_End => End, + XKB_KEY_Home => Home, + XKB_KEY_Page_Down => PageDown, + XKB_KEY_Page_Up => PageUp, + XKB_KEY_BackSpace => Backspace, + XKB_KEY_Clear => Clear, + // Copy, + XKB_KEY_3270_CursorSelect => CrSel, + //Cut, + //Delete, + //EraseEof, + //ExSel, + XKB_KEY_Insert => Insert, + //Paste, + XKB_KEY_Redo => Redo, + XKB_KEY_Undo => Undo, + /* todo carry on + Accept, + Again, + Attn, + Cancel, + ContextMenu, + Escape, + Execute, + Find, + Help, + Pause, + Play, + Props, + Select, + ZoomIn, + ZoomOut, + BrightnessDown, + BrightnessUp, + Eject, + LogOff, + Power, + PowerOff, + PrintScreen, + Hibernate, + Standby, + WakeUp, + AllCandidates, + Alphanumeric, + CodeInput, + Compose, + Convert, + Dead, + FinalMode, + GroupFirst, + GroupLast, + GroupNext, + GroupPrevious, + ModeChange, + NextCandidate, + NonConvert, + PreviousCandidate, + Process, + SingleCandidate, + HangulMode, + HanjaMode, + JunjaMode, + Eisu, + Hankaku, + Hiragana, + HiraganaKatakana, + KanaMode, + KanjiMode, + Katakana, + Romaji, + Zenkaku, + ZenkakuHankaku, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + Soft1, + Soft2, + Soft3, + Soft4, + ChannelDown, + ChannelUp, + Close, + MailForward, + MailReply, + MailSend, + MediaClose, + MediaFastForward, + MediaPause, + MediaPlay, + MediaPlayPause, + MediaRecord, + MediaRewind, + MediaStop, + MediaTrackNext, + MediaTrackPrevious, + New, + Open, + Print, + Save, + SpellCheck, + Key11, + Key12, + AudioBalanceLeft, + AudioBalanceRight, + AudioBassBoostDown, + AudioBassBoostToggle, + AudioBassBoostUp, + AudioFaderFront, + AudioFaderRear, + AudioSurroundModeNext, + AudioTrebleDown, + AudioTrebleUp, + AudioVolumeDown, + AudioVolumeUp, + AudioVolumeMute, + MicrophoneToggle, + MicrophoneVolumeDown, + MicrophoneVolumeUp, + MicrophoneVolumeMute, + SpeechCorrectionList, + SpeechInputToggle, + LaunchApplication1, + LaunchApplication2, + LaunchCalendar, + LaunchContacts, + LaunchMail, + LaunchMediaPlayer, + LaunchMusicPlayer, + LaunchPhone, + LaunchScreenSaver, + LaunchSpreadsheet, + LaunchWebBrowser, + LaunchWebCam, + LaunchWordProcessor, + BrowserBack, + BrowserFavorites, + BrowserForward, + BrowserHome, + BrowserRefresh, + BrowserSearch, + BrowserStop, + AppSwitch, + Call, + Camera, + CameraFocus, + EndCall, + GoBack, + GoHome, + HeadsetHook, + LastNumberRedial, + Notification, + MannerMode, + VoiceDial, + TV, + TV3DMode, + TVAntennaCable, + TVAudioDescription, + TVAudioDescriptionMixDown, + TVAudioDescriptionMixUp, + TVContentsMenu, + TVDataService, + TVInput, + TVInputComponent1, + TVInputComponent2, + TVInputComposite1, + TVInputComposite2, + TVInputHDMI1, + TVInputHDMI2, + TVInputHDMI3, + TVInputHDMI4, + TVInputVGA1, + TVMediaContext, + TVNetwork, + TVNumberEntry, + TVPower, + TVRadioService, + TVSatellite, + TVSatelliteBS, + TVSatelliteCS, + TVSatelliteToggle, + TVTerrestrialAnalog, + TVTerrestrialDigital, + TVTimer, + AVRInput, + AVRPower, + ColorF0Red, + ColorF1Green, + ColorF2Yellow, + ColorF3Blue, + ColorF4Grey, + ColorF5Brown, + ClosedCaptionToggle, + Dimmer, + DisplaySwap, + DVR, + Exit, + FavoriteClear0, + FavoriteClear1, + FavoriteClear2, + FavoriteClear3, + FavoriteRecall0, + FavoriteRecall1, + FavoriteRecall2, + FavoriteRecall3, + FavoriteStore0, + FavoriteStore1, + FavoriteStore2, + FavoriteStore3, + Guide, + GuideNextDay, + GuidePreviousDay, + Info, + InstantReplay, + Link, + ListProgram, + LiveContent, + Lock, + MediaApps, + MediaAudioTrack, + MediaLast, + MediaSkipBackward, + MediaSkipForward, + MediaStepBackward, + MediaStepForward, + MediaTopMenu, + NavigateIn, + NavigateNext, + NavigateOut, + NavigatePrevious, + NextFavoriteChannel, + NextUserProfile, + OnDemand, + Pairing, + PinPDown, + PinPMove, + PinPToggle, + PinPUp, + PlaySpeedDown, + PlaySpeedReset, + PlaySpeedUp, + RandomToggle, + RcLowBattery, + RecordSpeedNext, + RfBypass, + ScanChannelsToggle, + ScreenModeNext, + Settings, + SplitScreenToggle, + STBInput, + STBPower, + Subtitle, + Teletext, + VideoModeNext, + Wink, + ZoomToggle, + */ + // A catchall + _ => Unidentified, + } +} diff --git a/druid-shell/src/backend/x11/util.rs b/druid-shell/src/backend/x11/util.rs index fb85368a95..bfab46360f 100644 --- a/druid-shell/src/backend/x11/util.rs +++ b/druid-shell/src/backend/x11/util.rs @@ -153,40 +153,3 @@ macro_rules! log_x11 { } }; } - -/// A timer is a deadline (`std::Time::Instant`) and a `TimerToken`. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct Timer { - deadline: Instant, - token: TimerToken, -} - -impl Timer { - pub(crate) fn new(deadline: Instant) -> Self { - let token = TimerToken::next(); - Self { deadline, token } - } - - pub(crate) fn deadline(&self) -> Instant { - self.deadline - } - - pub(crate) fn token(&self) -> TimerToken { - self.token - } -} - -impl Ord for Timer { - /// Ordering is so that earliest deadline sorts first - // "Earliest deadline first" that a std::collections::BinaryHeap will have the earliest timer - // at its head, which is just what is needed for timer management. - fn cmp(&self, other: &Self) -> Ordering { - self.deadline.cmp(&other.deadline).reverse() - } -} - -impl PartialOrd for Timer { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} diff --git a/druid-shell/src/backend/x11/window.rs b/druid-shell/src/backend/x11/window.rs index d0ead09fcc..89bbeb5f45 100644 --- a/druid-shell/src/backend/x11/window.rs +++ b/druid-shell/src/backend/x11/window.rs @@ -51,6 +51,7 @@ use crate::keyboard::{KeyState, Modifiers}; use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}; use crate::piet::{Piet, PietText, RenderContext}; +use crate::backend::shared::Timer; use crate::region::Region; use crate::scale::Scale; use crate::text::{simulate_input, Event}; @@ -566,7 +567,7 @@ pub(crate) struct Window { /// The region that was invalidated since the last time we rendered. invalid: RefCell, /// Timers, sorted by "earliest deadline first" - timer_queue: Mutex>, + timer_queue: Mutex>>, idle_queue: Arc>>, // Writing to this wakes up the event loop, so that it can run idle handlers. idle_pipe: RawFd, @@ -1746,7 +1747,7 @@ impl WindowHandle { pub fn request_timer(&self, deadline: Instant) -> TimerToken { if let Some(w) = self.window.upgrade() { - let timer = Timer::new(deadline); + let timer = Timer::new(deadline, ()); w.timer_queue.lock().unwrap().push(timer); timer.token() } else { diff --git a/druid-shell/src/error.rs b/druid-shell/src/error.rs index 57093c5b3a..498eeadffc 100644 --- a/druid-shell/src/error.rs +++ b/druid-shell/src/error.rs @@ -24,6 +24,8 @@ use crate::backend::error as backend; pub enum Error { /// The Application instance has already been created. ApplicationAlreadyExists, + /// Tried to use the application after it had been dropped. + ApplicationDropped, /// The window has already been destroyed. WindowDropped, /// Platform specific error. @@ -38,6 +40,12 @@ impl fmt::Display for Error { Error::ApplicationAlreadyExists => { write!(f, "An application instance has already been created.") } + Error::ApplicationDropped => { + write!( + f, + "The application this operation requires has been dropped." + ) + } Error::Platform(err) => fmt::Display::fmt(err, f), Error::WindowDropped => write!(f, "The window has already been destroyed."), Error::Other(s) => write!(f, "{}", s), diff --git a/druid-shell/src/lib.rs b/druid-shell/src/lib.rs index 379914edb2..4a100eaa6f 100644 --- a/druid-shell/src/lib.rs +++ b/druid-shell/src/lib.rs @@ -33,6 +33,8 @@ html_logo_url = "https://raw.githubusercontent.com/linebender/druid/screenshots/images/doc_logo.png" )] #![allow(unused_imports)] // TODO remove +#![allow(unused_variables)] // TODO remove +#![allow(dead_code)] // TODO remove // Rename `gtk_rs` back to `gtk`. // This allows us to use `gtk` as the feature name. diff --git a/druid-shell/src/util.rs b/druid-shell/src/util.rs index c218bd67cf..873035f506 100644 --- a/druid-shell/src/util.rs +++ b/druid-shell/src/util.rs @@ -118,3 +118,17 @@ macro_rules! borrow_mut { }) }}; } + +/// This is a useful way to clone some values into a `move` closure. Currently only used in the +/// Wayland backend. +#[allow(unused_macros)] +macro_rules! with_cloned { + ($($val:ident),* ; $($rest:tt)*) => { + { + $( + let $val = $val.clone(); + )* + $($rest)* + } + }; +} diff --git a/druid/Cargo.toml b/druid/Cargo.toml index 3b0b7c276c..110c237fcc 100644 --- a/druid/Cargo.toml +++ b/druid/Cargo.toml @@ -26,6 +26,8 @@ gtk = ["druid-shell/gtk"] image = ["druid-shell/image"] svg = ["usvg"] x11 = ["druid-shell/x11"] +# **WARNING** not ready for the prime time. Many things don't work yet. +wayland = ["druid-shell/wayland"] crochet = [] serde = ["im/serde", "druid-shell/serde"] From 32d1e6c86b33f91b22f2fda67350d5e3c144ea28 Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Tue, 6 Apr 2021 21:34:53 +0100 Subject: [PATCH 03/31] Reduce the number of warnings. Also use `im` crate to solve reentrancy on windows collection (a group of windows, rather than the ubiquitous Microsoft product). --- druid-shell/Cargo.toml | 3 +- .../src/backend/wayland/application.rs | 55 ++++--- druid-shell/src/backend/wayland/buffer.rs | 24 +-- druid-shell/src/backend/wayland/dialog.rs | 11 +- druid-shell/src/backend/wayland/error.rs | 1 - druid-shell/src/backend/wayland/events.rs | 2 +- druid-shell/src/backend/wayland/menu.rs | 1 + druid-shell/src/backend/wayland/mod.rs | 3 - druid-shell/src/backend/wayland/pointer.rs | 29 +--- druid-shell/src/backend/wayland/screen.rs | 2 +- druid-shell/src/backend/wayland/window.rs | 149 +++++++----------- druid-shell/src/backend/wayland/xkb.rs | 7 +- druid-shell/src/backend/x11/window.rs | 3 +- druid-shell/src/lib.rs | 3 - 14 files changed, 115 insertions(+), 178 deletions(-) diff --git a/druid-shell/Cargo.toml b/druid-shell/Cargo.toml index 0b58c30991..e903030863 100755 --- a/druid-shell/Cargo.toml +++ b/druid-shell/Cargo.toml @@ -21,7 +21,7 @@ x11 = ["x11rb", "nix", "cairo-sys-rs", "bindgen", "pkg-config"] raw-win-handle = ["raw-window-handle"] # **WARNING** not ready for the prime time. Many things don't work yet. wayland = ["wayland-client", "wayland-protocols/client", "wayland-protocols/unstable_protocols", -"nix", "cairo-sys-rs", "rand", "xkbcommon-sys", "calloop", "wayland-cursor", "log"] +"nix", "cairo-sys-rs", "rand", "xkbcommon-sys", "calloop", "wayland-cursor", "log", "im"] # passing on all the image features. AVIF is not supported because it does not # support decoding, and that's all we use `Image` for. @@ -97,6 +97,7 @@ rand = { version = "0.8.0", optional = true } xkbcommon-sys = { version = "0.7.4", optional = true } calloop = { version = "0.7.1", optional = true } log = { version = "0.4.14", optional = true } +im = { version = "15.0.0", optional = true } [target.'cfg(target_arch="wasm32")'.dependencies] wasm-bindgen = "0.2.67" diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index 0810f25933..73e99c3213 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -19,13 +19,13 @@ use super::{ clipboard::Clipboard, error::Error, events::WaylandSource, - pointer::{MouseEvtKind, Pointer, PointerEvent}, - window::{WindowData, WindowHandle}, + pointer::{MouseEvtKind, PointerEvent}, + window::WindowData, xkb, }; use crate::{ - application::AppHandler, keyboard_types::KeyState, kurbo::Point, backend::shared::Timer, - window::WinHandler, TimerToken, + application::AppHandler, backend::shared::Timer, keyboard_types::KeyState, kurbo::Point, + TimerToken, WinHandler, }; use calloop::{ @@ -41,7 +41,7 @@ use std::{ time::{Duration, Instant}, }; use wayland_client::{ - self as wl, event_enum, + self as wl, protocol::{ wl_compositor::WlCompositor, wl_keyboard::{self, WlKeyboard}, @@ -89,7 +89,7 @@ pub(crate) struct ApplicationData { /// Handles to any surfaces that have been created. /// /// This is where the window data is owned. Other handles should be weak. - pub(crate) surfaces: RefCell>>, + pub(crate) surfaces: RefCell>>, /// Available pixel formats pub(crate) formats: RefCell>, @@ -234,7 +234,7 @@ impl Application { }); let xkb_context = xkb::Context::new(); - xkb_context.set_log_level(log::Level::Trace); + //xkb_context.set_log_level(log::Level::Trace); let timer_source = CalloopTimer::new().unwrap(); let timer_handle = timer_source.handle(); @@ -258,7 +258,7 @@ impl Application { cursor_theme: RefCell::new(cursor_theme), outputs, seats, - surfaces: RefCell::new(BTreeMap::new()), + surfaces: RefCell::new(im::OrdMap::new()), formats: RefCell::new(vec![]), shutdown: Cell::new(false), active_surface_id: Cell::new(None), @@ -425,7 +425,8 @@ impl ApplicationData { data.keyboard_focus.set(true); // (re-entrancy) call user code data.handler.borrow_mut().got_focus(); - data.check_for_scheduled_paint(); + + data.run_deferred_tasks(); } Event::Leave { serial, surface } => { let data = self @@ -434,7 +435,7 @@ impl ApplicationData { data.keyboard_focus.set(false); // (re-entrancy) call user code data.handler.borrow_mut().lost_focus(); - data.check_for_scheduled_paint(); + data.run_deferred_tasks(); } Event::Key { serial, @@ -450,7 +451,10 @@ impl ApplicationData { _ => panic!("unrecognised key event"), }, ); - for (_, data) in self.surfaces.borrow().iter() { + // This clone is necessary because user methods might add more surfaces, which + // would then be inserted here which would be 1 mut 1 immut borrows which is not + // allowed. + for (_, data) in self.surfaces_iter() { if data.keyboard_focus.get() { match event.state { KeyState::Down => { @@ -461,7 +465,7 @@ impl ApplicationData { KeyState::Up => data.handler.borrow_mut().key_up(event.clone()), } } - data.check_for_scheduled_paint(); + data.run_deferred_tasks(); } } Event::Modifiers { @@ -522,7 +526,7 @@ impl ApplicationData { surface_y, } => { let pos = Point::new(surface_x, surface_y); - for (_, data) in self.surfaces.borrow().iter() { + for (_, data) in self.surfaces_iter() { if let Some(pointer) = data.pointer.borrow_mut().as_mut() { pointer.push(PointerEvent::Motion(pos)); } @@ -534,21 +538,21 @@ impl ApplicationData { button, state, } => { - for (_, data) in self.surfaces.borrow().iter() { + for (_, data) in self.surfaces_iter() { if let Some(pointer) = data.pointer.borrow_mut().as_mut() { pointer.push(PointerEvent::Button { button, state }); } } } Event::Axis { time, axis, value } => { - for (_, data) in self.surfaces.borrow().iter() { + for (_, data) in self.surfaces_iter() { if let Some(pointer) = data.pointer.borrow_mut().as_mut() { pointer.push(PointerEvent::Axis { axis, value }); } } } Event::Frame => { - for (_, data) in self.surfaces.borrow().iter() { + for (_, data) in self.surfaces_iter() { // Wait until we're outside the loop, then drop the pointer state. let mut have_left = false; while let Some(event) = data.pop_pointer_event() { @@ -567,11 +571,11 @@ impl ApplicationData { if have_left { *data.pointer.borrow_mut() = None; } - data.check_for_scheduled_paint(); + data.run_deferred_tasks(); } } evt => { - //log::warn!("Unhandled pointer event: {:?}", evt); + log::warn!("Unhandled pointer event: {:?}", evt); } } } @@ -598,10 +602,11 @@ impl ApplicationData { continue; } }; + // re-entrancy surface.handler.borrow_mut().timer(timer.token()); } - for (_, surface) in self.surfaces.borrow().iter() { - surface.check_for_scheduled_paint(); + for (_, data) in self.surfaces_iter() { + data.run_deferred_tasks(); } // Get the deadline soonest and queue it. if let Some(timer) = self.timers.borrow().peek() { @@ -616,6 +621,16 @@ impl ApplicationData { fn find_surface(&self, id: u32) -> Option> { self.surfaces.borrow().get(&id).cloned() } + + /// Shallow clones surfaces so we can modify it during iteration. + fn surfaces_iter(&self) -> impl Iterator)> { + // make sure the borrow gets dropped. + let surfaces = { + let surfaces = self.surfaces.borrow(); + surfaces.clone() + }; + surfaces.into_iter() + } } #[derive(Debug, Clone)] diff --git a/druid-shell/src/backend/wayland/buffer.rs b/druid-shell/src/backend/wayland/buffer.rs index f9375ac82f..16185bc1db 100644 --- a/druid-shell/src/backend/wayland/buffer.rs +++ b/druid-shell/src/backend/wayland/buffer.rs @@ -12,7 +12,6 @@ use std::{ cell::{Cell, RefCell}, convert::{TryFrom, TryInto}, fmt, - mem::{self, MaybeUninit}, ops::{Deref, DerefMut}, os::{raw::c_void, unix::prelude::RawFd}, ptr::{self, NonNull}, @@ -23,28 +22,13 @@ use wayland_client::{ self as wl, protocol::{ wl_buffer::{self, WlBuffer}, - wl_callback, - wl_keyboard::{self, WlKeyboard}, - wl_output::WlOutput, - wl_pointer::{self, WlPointer}, wl_shm::{self, WlShm}, wl_shm_pool::WlShmPool, - wl_surface::{self, WlSurface}, - }, -}; -use wayland_cursor::CursorImageBuffer; -use wayland_protocols::{ - unstable::xdg_decoration::v1::client::zxdg_toplevel_decoration_v1::{ - Event as ZxdgToplevelDecorationV1Event, Mode as DecorationMode, ZxdgToplevelDecorationV1, - }, - xdg_shell::client::{ - xdg_surface::{Event as XdgSurfaceEvent, XdgSurface}, - xdg_toplevel::{Event as XdgTopLevelEvent, XdgToplevel}, - xdg_wm_base::XdgWmBase, + wl_surface::WlSurface, }, }; -use super::{window::WindowData, NUM_FRAMES, PIXEL_WIDTH}; +use super::{window::WindowData, PIXEL_WIDTH}; /// A collection of buffers that can change size. /// @@ -135,7 +119,7 @@ impl Buffers { if self.recreate_buffers.get() { // If all buffers are free, destroy and recreate them if self.all_buffers_released() { - log::debug!("all buffers released, recreating"); + //log::debug!("all buffers released, recreating"); self.deferred_paint.set(false); self.recreate_buffers_unchecked(); self.paint_unchecked(); @@ -146,7 +130,7 @@ impl Buffers { // If the next buffer is free, draw & present. If buffers have not been initialized it // is a bug in this code. if self.pending_buffer_released() { - log::debug!("next frame has been released: draw and present"); + //log::debug!("next frame has been released: draw and present"); self.deferred_paint.set(false); self.paint_unchecked(); } else { diff --git a/druid-shell/src/backend/wayland/dialog.rs b/druid-shell/src/backend/wayland/dialog.rs index f8d1317c5d..280ef21408 100644 --- a/druid-shell/src/backend/wayland/dialog.rs +++ b/druid-shell/src/backend/wayland/dialog.rs @@ -12,16 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +/* use std::ffi::OsString; -use anyhow::anyhow; - -use crate::dialog::{FileDialogOptions, FileDialogType, FileSpec}; use crate::Error; +use crate::{ + dialog::{FileDialogOptions, FileDialogType, FileSpec}, + window::WindowHandle, +}; -/* pub(crate) fn get_file_dialog_path( - window: &Window, + window: &WindowHandle, ty: FileDialogType, options: FileDialogOptions, ) -> Result { diff --git a/druid-shell/src/backend/wayland/error.rs b/druid-shell/src/backend/wayland/error.rs index 045ce11176..e7c68d891c 100644 --- a/druid-shell/src/backend/wayland/error.rs +++ b/druid-shell/src/backend/wayland/error.rs @@ -14,7 +14,6 @@ //! GTK platform errors. -use anyhow::Error as AnyError; use std::{error::Error as StdError, fmt, sync::Arc}; use wayland_client as wl; diff --git a/druid-shell/src/backend/wayland/events.rs b/druid-shell/src/backend/wayland/events.rs index 6d8c0dce2c..25b980fdf0 100644 --- a/druid-shell/src/backend/wayland/events.rs +++ b/druid-shell/src/backend/wayland/events.rs @@ -7,7 +7,7 @@ use calloop::{ generic::{Fd, Generic}, - Dispatcher, EventSource, InsertError, Interest, LoopHandle, Mode, RegistrationToken, + Dispatcher, EventSource, Interest, Mode, }; use std::{cell::RefCell, io, rc::Rc}; use wayland_client::EventQueue; diff --git a/druid-shell/src/backend/wayland/menu.rs b/druid-shell/src/backend/wayland/menu.rs index f84f0782ce..aec9b6141a 100644 --- a/druid-shell/src/backend/wayland/menu.rs +++ b/druid-shell/src/backend/wayland/menu.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(unused)] use super::keycodes; use super::window::WindowHandle; use crate::common_util::strip_access_key; diff --git a/druid-shell/src/backend/wayland/mod.rs b/druid-shell/src/backend/wayland/mod.rs index 18ae7acbbe..6d2d6ab6c9 100644 --- a/druid-shell/src/backend/wayland/mod.rs +++ b/druid-shell/src/backend/wayland/mod.rs @@ -42,7 +42,4 @@ impl Changed { fn is_changed(self) -> bool { matches!(self, Changed::Changed) } - fn is_unchanged(self) -> bool { - matches!(self, Changed::Unchanged) - } } diff --git a/druid-shell/src/backend/wayland/pointer.rs b/druid-shell/src/backend/wayland/pointer.rs index 8fe4090c38..d7acd785c6 100644 --- a/druid-shell/src/backend/wayland/pointer.rs +++ b/druid-shell/src/backend/wayland/pointer.rs @@ -1,35 +1,14 @@ use crate::{ - common_util::{ClickCounter, IdleCallback}, - dialog::{FileDialogOptions, FileDialogType, FileInfo}, - error::Error as ShellError, - keyboard::{KbKey, KeyEvent, KeyState, Modifiers}, - kurbo::{Insets, Point, Rect, Size, Vec2}, - mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}, - piet::ImageFormat, + keyboard::Modifiers, + kurbo::{Point, Vec2}, + mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}, }; use std::collections::VecDeque; use wayland_client::{ self as wl, protocol::{ - wl_buffer::{self, WlBuffer}, - wl_callback, - wl_keyboard::{self, WlKeyboard}, - wl_output::WlOutput, wl_pointer::{self, WlPointer}, - wl_shm::{self, WlShm}, - wl_shm_pool::WlShmPool, - wl_surface::{self, WlSurface}, - }, -}; -use wayland_cursor::CursorImageBuffer; -use wayland_protocols::{ - unstable::xdg_decoration::v1::client::zxdg_toplevel_decoration_v1::{ - Event as ZxdgToplevelDecorationV1Event, Mode as DecorationMode, ZxdgToplevelDecorationV1, - }, - xdg_shell::client::{ - xdg_surface::{Event as XdgSurfaceEvent, XdgSurface}, - xdg_toplevel::{Event as XdgTopLevelEvent, XdgToplevel}, - xdg_wm_base::XdgWmBase, + wl_surface::WlSurface, }, }; diff --git a/druid-shell/src/backend/wayland/screen.rs b/druid-shell/src/backend/wayland/screen.rs index 33ba76ae9e..676a344587 100644 --- a/druid-shell/src/backend/wayland/screen.rs +++ b/druid-shell/src/backend/wayland/screen.rs @@ -15,7 +15,7 @@ //! GTK Monitors and Screen information. use crate::screen::Monitor; -use kurbo::{Point, Rect, Size}; +//use kurbo::{Point, Rect, Size}; pub(crate) fn get_monitors() -> Vec { todo!() diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index 30007d0530..b7cd0aee19 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -13,46 +13,18 @@ #![allow(clippy::single_match)] -use anyhow::{anyhow, format_err}; -use cairo::Surface; -use nix::{ - errno::Errno, - fcntl::OFlag, - sys::{ - mman::{mmap, munmap, shm_open, MapFlags, ProtFlags}, - stat::Mode, - }, - unistd::{close, ftruncate}, -}; use std::{ any::Any, cell::{Cell, RefCell}, - collections::{BTreeMap, HashSet, VecDeque}, - convert::{TryFrom, TryInto}, - ffi::c_void, - fmt, - ops::Deref, - os::{ - raw::{c_int, c_uint}, - unix::io::RawFd, - }, - panic::Location, - ptr::{self, NonNull}, + collections::{HashSet, VecDeque}, rc::{Rc, Weak as WeakRc}, - slice, - sync::{Arc, Mutex, Weak}, - time::{Duration, Instant, SystemTime}, + time::{Duration, Instant}, }; use wayland_client::{ self as wl, protocol::{ - wl_buffer::{self, WlBuffer}, wl_callback, - wl_keyboard::{self, WlKeyboard}, - wl_output::WlOutput, - wl_pointer::{self, WlPointer}, - wl_shm::{self, WlShm}, - wl_shm_pool::WlShmPool, + wl_pointer::WlPointer, wl_surface::{self, WlSurface}, }, }; @@ -64,30 +36,25 @@ use wayland_protocols::{ xdg_shell::client::{ xdg_surface::{Event as XdgSurfaceEvent, XdgSurface}, xdg_toplevel::{Event as XdgTopLevelEvent, XdgToplevel}, - xdg_wm_base::XdgWmBase, }, }; use super::{ - application::{Application, ApplicationData, Output}, - buffer::{Buffer, Buffers, Mmap, RawRect, RawSize, Shm}, - dialog, keycodes, + application::{Application, ApplicationData}, + buffer::{Buffers, RawRect, RawSize}, menu::Menu, pointer::{MouseEvtKind, Pointer}, - util, Changed, NUM_FRAMES, PIXEL_WIDTH, + Changed, NUM_FRAMES, PIXEL_WIDTH, }; use crate::{ backend::shared::Timer, - common_util::{ClickCounter, IdleCallback}, - dialog::{FileDialogOptions, FileDialogType, FileInfo}, + dialog::FileDialogOptions, error::Error as ShellError, - keyboard::{KbKey, KeyEvent, KeyState, Modifiers}, - kurbo::{Insets, Point, Rect, Size, Vec2}, - mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}, - piet::ImageFormat, + kurbo::{Insets, Point, Rect, Size}, + mouse::{Cursor, CursorDesc}, piet::{Piet, PietText, RenderContext}, region::Region, - scale::{Scalable, Scale, ScaledArea}, + scale::Scale, text::Event, window::{self, FileDialogToken, IdleToken, TimerToken, WinHandler, WindowLevel}, TextFieldToken, @@ -221,7 +188,7 @@ impl WindowHandle { cb.quick_assign(with_cloned!(data; move |_, event, _| match event { wl_callback::Event::Done { callback_data } => { data.anim_frame_requested.set(false); - data.request_paint(); + data.buffers.request_paint(); } _ => panic!("done is the only event"), })); @@ -252,20 +219,16 @@ impl WindowHandle { } pub fn add_text_field(&self) -> TextFieldToken { - todo!() + // I think it makes sense to leave these until @forloveofcats' work on the pango + // integration is complete/merged. + TextFieldToken::next() } - pub fn remove_text_field(&self, token: TextFieldToken) { - todo!() - } + pub fn remove_text_field(&self, token: TextFieldToken) {} - pub fn set_focused_text_field(&self, active_field: Option) { - todo!() - } + pub fn set_focused_text_field(&self, active_field: Option) {} - pub fn update_text_field(&self, token: TextFieldToken, update: Event) { - todo!() - } + pub fn update_text_field(&self, token: TextFieldToken, update: Event) {} pub fn request_timer(&self, deadline: Instant) -> TimerToken { if let Some(data) = self.data.upgrade() { @@ -373,16 +336,21 @@ pub struct WindowData { pub(crate) pointer: RefCell>, /// Whether we have requested an animation frame. This stops us requesting more than 1. anim_frame_requested: Cell, - /// Track whether an event handler invalidated any regions. After the event handler has been - /// released, repaint if true. TODO refactor this into an enum of evens that might call in to - /// user code, and so need to be deferred. - paint_scheduled: Cell, /// Contains the callbacks from user code. pub(crate) handler: RefCell>, /// Rects of the image that are damaged and need repainting in the logical coordinate space. /// /// This lives outside `data` because they can be borrowed concurrently without re-entrancy. damaged_region: RefCell, + /// Tasks that were requested in user code. + /// + /// These call back into user code, and so should only be run after all user code has returned, + /// to avoid possible re-entrancy. + deferred_tasks: RefCell>, +} + +pub enum DeferredTask { + Paint, } impl WindowData { @@ -471,31 +439,6 @@ impl WindowData { } } - /// Schedule a paint (in response to invalidation). - pub(crate) fn schedule_paint(&self) { - self.paint_scheduled.set(true); - } - - /// If a repaint was scheduled, then execute it. - pub(crate) fn check_for_scheduled_paint(&self) { - if self.paint_scheduled.get() { - self.request_paint(); - } - } - - /// Request to `buffers` that the next frame be painted. - /// - /// If the next frame is ready, then it will be painted immediately, otherwise a paint will be - /// scheduled to take place when a frame is released. - /// - /// ```text - /// self.request_paint -> calls buffers.request_paint -> calls self.paint (possibly not immediately) - /// ``` - fn request_paint(&self) { - self.paint_scheduled.set(false); - self.buffers.request_paint(); - } - /// Paint the next frame. /// /// The buffers object is responsible for calling this function after we called @@ -573,7 +516,7 @@ impl WindowData { // `invalidate_rect`. let window_rect = self.logical_size.get().to_rect(); self.damaged_region.borrow_mut().add_rect(window_rect); - self.schedule_paint(); + self.schedule_deferred_task(DeferredTask::Paint); } /// Request invalidation of one rectangle, which is given in display points relative to the @@ -592,7 +535,7 @@ impl WindowData { ); */ self.damaged_region.borrow_mut().add_rect(rect); - self.schedule_paint() + self.schedule_deferred_task(DeferredTask::Paint); } /// If there are any pending pointer events, get the next one. @@ -635,6 +578,30 @@ impl WindowData { self.set_cursor(buf); } } + + // Deferred tasks + + pub fn schedule_deferred_task(&self, task: DeferredTask) { + self.deferred_tasks.borrow_mut().push_back(task); + } + + pub fn run_deferred_tasks(&self) { + while let Some(task) = self.next_deferred_task() { + self.run_deferred_task(task); + } + } + + fn next_deferred_task(&self) -> Option { + self.deferred_tasks.borrow_mut().pop_front() + } + + fn run_deferred_task(&self, task: DeferredTask) { + match task { + DeferredTask::Paint => { + self.buffers.request_paint(); + } + } + } } /// Builder abstraction for creating new windows @@ -758,16 +725,16 @@ impl WindowBuilder { keyboard_focus: Cell::new(false), pointer: RefCell::new(None), anim_frame_requested: Cell::new(false), - paint_scheduled: Cell::new(false), handler: RefCell::new(handler), damaged_region: RefCell::new(Region::EMPTY), + deferred_tasks: RefCell::new(VecDeque::new()), }); let weak_data = Rc::downgrade(&data); // Hook up the child -> parent weak pointer. unsafe { - // Safety: safe because no other references to the data are dereferenced for the life - // of the reference (the only refs are the Rc and the weak Rc we just created). + // Safety: No Rust references exist during the life of this reference (satisfies many + // read-only xor 1 mutable references). let buffers: &mut Buffers<{ NUM_FRAMES as usize }> = &mut *(Rc::as_ptr(&data.buffers) as *mut _); buffers.set_window_data(weak_data); @@ -806,7 +773,7 @@ impl WindowBuilder { data.handler.borrow_mut().size(logical_size); } // Check if the client requested a repaint. - data.check_for_scheduled_paint(); + data.run_deferred_tasks(); } } XdgTopLevelEvent::Close => { @@ -831,7 +798,7 @@ impl WindowBuilder { with_cloned!(data; move |xdg_surface, event, _| match event { XdgSurfaceEvent::Configure { serial } => { xdg_surface.ack_configure(serial); - data.request_paint(); // will also rebuild buffers if needed. + data.buffers.request_paint(); // will also rebuild buffers if needed. } _ => (), }), @@ -860,7 +827,7 @@ impl WindowBuilder { // We also need to change the physical size to match the new scale data.set_physical_size(RawSize::from(data.logical_size.get()).scale(new_scale)); // always repaint, because the scale changed. - data.schedule_paint(); + data.schedule_deferred_task(DeferredTask::Paint); } })); diff --git a/druid-shell/src/backend/wayland/xkb.rs b/druid-shell/src/backend/wayland/xkb.rs index c021686218..ffb5c31b13 100644 --- a/druid-shell/src/backend/wayland/xkb.rs +++ b/druid-shell/src/backend/wayland/xkb.rs @@ -7,13 +7,10 @@ use crate::{ KeyEvent, KeyState, Modifiers, }; use keyboard_types::{Code, Key}; -use std::{ - convert::{TryFrom, TryInto}, - ptr, -}; +use std::{convert::TryFrom, ptr}; use xkbcommon_sys::*; -const MAX_KEY_LEN: usize = 32; +//const MAX_KEY_LEN: usize = 32; /// A global xkb context object. /// diff --git a/druid-shell/src/backend/x11/window.rs b/druid-shell/src/backend/x11/window.rs index 89bbeb5f45..0201c6917c 100644 --- a/druid-shell/src/backend/x11/window.rs +++ b/druid-shell/src/backend/x11/window.rs @@ -44,6 +44,7 @@ use x11rb::xcb_ffi::XCBConnection; #[cfg(feature = "raw-win-handle")] use raw_window_handle::{unix::XcbHandle, HasRawWindowHandle, RawWindowHandle}; +use crate::backend::shared::Timer; use crate::common_util::IdleCallback; use crate::dialog::FileDialogOptions; use crate::error::Error as ShellError; @@ -51,7 +52,6 @@ use crate::keyboard::{KeyState, Modifiers}; use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}; use crate::piet::{Piet, PietText, RenderContext}; -use crate::backend::shared::Timer; use crate::region::Region; use crate::scale::Scale; use crate::text::{simulate_input, Event}; @@ -62,7 +62,6 @@ use crate::{window, KeyEvent, ScaledArea}; use super::application::Application; use super::menu::Menu; -use super::util::Timer; /// A version of XCB's `xcb_visualtype_t` struct. This was copied from the [example] in x11rb; it /// is used to interoperate with cairo. diff --git a/druid-shell/src/lib.rs b/druid-shell/src/lib.rs index 4a100eaa6f..22de7548f8 100644 --- a/druid-shell/src/lib.rs +++ b/druid-shell/src/lib.rs @@ -32,9 +32,6 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/linebender/druid/screenshots/images/doc_logo.png" )] -#![allow(unused_imports)] // TODO remove -#![allow(unused_variables)] // TODO remove -#![allow(dead_code)] // TODO remove // Rename `gtk_rs` back to `gtk`. // This allows us to use `gtk` as the feature name. From d8413e9fa04d8755ecd316f33a6998e44471701a Mon Sep 17 00:00:00 2001 From: Maan2003 Date: Tue, 17 Aug 2021 23:30:28 +0530 Subject: [PATCH 04/31] log -> tracing --- druid-shell/src/backend/wayland/application.rs | 6 +++--- druid-shell/src/backend/wayland/buffer.rs | 6 +++--- druid-shell/src/backend/wayland/pointer.rs | 4 ++-- druid-shell/src/backend/wayland/window.rs | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index 73e99c3213..58b4927ce6 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -484,7 +484,7 @@ impl ApplicationData { println!("Requested repeat rate={} delay={}", rate, delay); } evt => { - log::warn!("Unhandled keybaord event: {:?}", evt); + tracing::warn!("Unhandled keybaord event: {:?}", evt); } } } @@ -575,7 +575,7 @@ impl ApplicationData { } } evt => { - log::warn!("Unhandled pointer event: {:?}", evt); + tracing::warn!("Unhandled pointer event: {:?}", evt); } } } @@ -598,7 +598,7 @@ impl ApplicationData { Some(s) => s, None => { // NOTE this might be expected - log::warn!("Received event for surface that doesn't exist any more"); + tracing::warn!("Received event for surface that doesn't exist any more"); continue; } }; diff --git a/druid-shell/src/backend/wayland/buffer.rs b/druid-shell/src/backend/wayland/buffer.rs index 16185bc1db..aa6ee91b35 100644 --- a/druid-shell/src/backend/wayland/buffer.rs +++ b/druid-shell/src/backend/wayland/buffer.rs @@ -119,7 +119,7 @@ impl Buffers { if self.recreate_buffers.get() { // If all buffers are free, destroy and recreate them if self.all_buffers_released() { - //log::debug!("all buffers released, recreating"); + //tracing::debug!("all buffers released, recreating"); self.deferred_paint.set(false); self.recreate_buffers_unchecked(); self.paint_unchecked(); @@ -130,7 +130,7 @@ impl Buffers { // If the next buffer is free, draw & present. If buffers have not been initialized it // is a bug in this code. if self.pending_buffer_released() { - //log::debug!("next frame has been released: draw and present"); + //tracing::debug!("next frame has been released: draw and present"); self.deferred_paint.set(false); self.paint_unchecked(); } else { @@ -551,7 +551,7 @@ impl Drop for Mmap { fn drop(&mut self) { unsafe { if let Err(e) = munmap(self.ptr.as_ptr(), self.size) { - log::warn!("Error unmapping memory: {}", e); + tracing::warn!("Error unmapping memory: {}", e); } } } diff --git a/druid-shell/src/backend/wayland/pointer.rs b/druid-shell/src/backend/wayland/pointer.rs index d7acd785c6..a85e6a4298 100644 --- a/druid-shell/src/backend/wayland/pointer.rs +++ b/druid-shell/src/backend/wayland/pointer.rs @@ -152,7 +152,7 @@ impl Iterator for Pointer { }) } _ => { - log::error!("mouse button changed, but not pressed or released"); + tracing::error!("mouse button changed, but not pressed or released"); continue; } }; @@ -163,7 +163,7 @@ impl Iterator for Pointer { Axis::VerticalScroll => Vec2::new(0., value), Axis::HorizontalScroll => Vec2::new(value, 0.), _ => { - log::error!("axis direction not vertical or horizontal"); + tracing::error!("axis direction not vertical or horizontal"); continue; } }; diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index b7cd0aee19..44d8070495 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -129,11 +129,11 @@ impl WindowHandle { } pub fn set_level(&self, level: WindowLevel) { - log::warn!("level is unsupported on wayland"); + tracing::warn!("level is unsupported on wayland"); } pub fn set_size(&self, size: Size) { - log::warn!("setting the size dynamically is unsupported on wayland"); + tracing::warn!("setting the size dynamically is unsupported on wayland"); } pub fn get_size(&self) -> Size { @@ -383,14 +383,14 @@ impl WindowData { if let Some(output) = app_data.outputs.borrow().get(&id) { scale = scale.max(output.scale); } else { - log::warn!( + tracing::warn!( "we still have a reference to an output that's gone away. The output had id {}", id ); } } if scale == 0 { - log::warn!("wayland never reported which output we are drawing to"); + tracing::warn!("wayland never reported which output we are drawing to"); 1 } else { scale @@ -448,7 +448,7 @@ impl WindowData { /// - `size` is the physical size in pixels we are drawing. /// - `force` means draw the whole frame, even if it wasn't all invalidated. pub(crate) fn paint(&self, size: RawSize, buf: &mut [u8], force: bool) { - //log::trace!("Paint call"); + //tracing::trace!("Paint call"); //self.data.borrow().assert_size(); if force { self.invalidate(); @@ -788,7 +788,7 @@ impl WindowBuilder { move |zxdg_toplevel_decoration_v1, event, _| match event { ZxdgToplevelDecorationV1Event::Configure { mode } => { // do nothing for now - log::debug!("{:?}", mode); + tracing::debug!("{:?}", mode); } _ => (), } From 91b71a8832ab4a37cbff6bc38329c27e4068d6ba Mon Sep 17 00:00:00 2001 From: Maan2003 Date: Tue, 17 Aug 2021 17:59:51 +0530 Subject: [PATCH 05/31] simulate_input --- .../src/backend/wayland/application.rs | 7 +++++- druid-shell/src/backend/wayland/window.rs | 22 ++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index 58b4927ce6..fcc1abf361 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -23,6 +23,7 @@ use super::{ window::WindowData, xkb, }; +use crate::text::simulate_input; use crate::{ application::AppHandler, backend::shared::Timer, keyboard_types::KeyState, kurbo::Point, TimerToken, WinHandler, @@ -460,7 +461,11 @@ impl ApplicationData { KeyState::Down => { // TODO what do I do if the key event is handled? Do I not update // the xkb state? - data.handler.borrow_mut().key_down(event.clone()); + simulate_input( + &mut **data.handler.borrow_mut(), + data.active_text_field.get(), + event.clone(), + ); } KeyState::Up => data.handler.borrow_mut().key_up(event.clone()), } diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index 44d8070495..8445f923d7 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -219,16 +219,26 @@ impl WindowHandle { } pub fn add_text_field(&self) -> TextFieldToken { - // I think it makes sense to leave these until @forloveofcats' work on the pango - // integration is complete/merged. TextFieldToken::next() } - pub fn remove_text_field(&self, token: TextFieldToken) {} + pub fn remove_text_field(&self, token: TextFieldToken) { + if let Some(data) = self.data.upgrade() { + if data.active_text_field.get() == Some(token) { + data.active_text_field.set(None) + } + } + } - pub fn set_focused_text_field(&self, active_field: Option) {} + pub fn set_focused_text_field(&self, active_field: Option) { + if let Some(data) = self.data.upgrade() { + data.active_text_field.set(active_field); + } + } - pub fn update_text_field(&self, token: TextFieldToken, update: Event) {} + pub fn update_text_field(&self, _token: TextFieldToken, _update: Event) { + // noop until we get a real text input implementation + } pub fn request_timer(&self, deadline: Instant) -> TimerToken { if let Some(data) = self.data.upgrade() { @@ -347,6 +357,7 @@ pub struct WindowData { /// These call back into user code, and so should only be run after all user code has returned, /// to avoid possible re-entrancy. deferred_tasks: RefCell>, + pub(crate) active_text_field: Cell>, } pub enum DeferredTask { @@ -728,6 +739,7 @@ impl WindowBuilder { handler: RefCell::new(handler), damaged_region: RefCell::new(Region::EMPTY), deferred_tasks: RefCell::new(VecDeque::new()), + active_text_field: Cell::new(None), }); let weak_data = Rc::downgrade(&data); From 696ae7dfd4c4e5ce9c7b7c58c667d3dbde341d05 Mon Sep 17 00:00:00 2001 From: Maan2003 Date: Tue, 17 Aug 2021 22:43:14 +0530 Subject: [PATCH 06/31] prepare paint --- druid-shell/src/backend/wayland/window.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index 8445f923d7..bb0fac70a5 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -459,6 +459,7 @@ impl WindowData { /// - `size` is the physical size in pixels we are drawing. /// - `force` means draw the whole frame, even if it wasn't all invalidated. pub(crate) fn paint(&self, size: RawSize, buf: &mut [u8], force: bool) { + self.handler.borrow_mut().prepare_paint(); //tracing::trace!("Paint call"); //self.data.borrow().assert_size(); if force { From 81cf390cfd714a91dbe36c937b670ae8a21760cf Mon Sep 17 00:00:00 2001 From: Maan2003 Date: Mon, 27 Sep 2021 17:36:10 +0530 Subject: [PATCH 07/31] green CI --- druid-shell/examples/empty_window.rs | 2 +- druid-shell/src/backend/shared/mod.rs | 4 ++-- druid-shell/src/backend/x11/util.rs | 4 ---- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/druid-shell/examples/empty_window.rs b/druid-shell/examples/empty_window.rs index a6e96cdce4..c541000718 100644 --- a/druid-shell/examples/empty_window.rs +++ b/druid-shell/examples/empty_window.rs @@ -123,7 +123,7 @@ impl WinHandler for HelloState { } fn main() { - simple_logger::SimpleLogger::new().init().unwrap(); + tracing_subscriber::fmt::init(); let app = Application::new().unwrap(); let mut builder = WindowBuilder::new(app.clone()); builder.set_handler(Box::new(HelloState::default())); diff --git a/druid-shell/src/backend/shared/mod.rs b/druid-shell/src/backend/shared/mod.rs index cfa9bc0c5a..a1d8dd0bba 100644 --- a/druid-shell/src/backend/shared/mod.rs +++ b/druid-shell/src/backend/shared/mod.rs @@ -21,8 +21,8 @@ cfg_if::cfg_if! { } } cfg_if::cfg_if! { - if #[cfg(any(feature = "x11", feature = "wayland"))] { + if #[cfg(all(target_os = "linux", any(feature = "x11", feature = "wayland")))] { mod timer; - pub use timer::*; + pub(crate) use timer::*; } } diff --git a/druid-shell/src/backend/x11/util.rs b/druid-shell/src/backend/x11/util.rs index bfab46360f..714bc4d355 100644 --- a/druid-shell/src/backend/x11/util.rs +++ b/druid-shell/src/backend/x11/util.rs @@ -14,9 +14,7 @@ //! Miscellaneous utility functions for working with X11. -use std::cmp::Ordering; use std::rc::Rc; -use std::time::Instant; use anyhow::{anyhow, Error}; use x11rb::connection::RequestConnection; @@ -26,8 +24,6 @@ use x11rb::protocol::render::{self, ConnectionExt as _}; use x11rb::protocol::xproto::{Screen, Visualid, Visualtype, Window}; use x11rb::xcb_ffi::XCBConnection; -use crate::window::TimerToken; - // See: https://github.com/rtbo/rust-xcb/blob/master/examples/randr_screen_modes.rs pub fn refresh_rate(conn: &Rc, window_id: Window) -> Option { let try_refresh_rate = || -> Result { From 0662630b6984adf5f7edacdf23a34f89ebb2412a Mon Sep 17 00:00:00 2001 From: James Lawrence Date: Wed, 27 Oct 2021 19:03:53 -0400 Subject: [PATCH 08/31] wayland fixes and improvements while not 100% there implements significant improvements to the wayland backend. most (all?) examples seem to basic work. most outstanding work is integrating with the druid APIs cleanly, implement repeating keys, some point work, naturally performance fixes and finally there are some bugs when resuming from sleep. --- druid-shell/src/backend/wayland/.README.md | 3 + .../src/backend/wayland/application.rs | 631 ++++++------ druid-shell/src/backend/wayland/clipboard.rs | 3 +- druid-shell/src/backend/wayland/error.rs | 51 +- druid-shell/src/backend/wayland/events.rs | 63 +- druid-shell/src/backend/wayland/keyboard.rs | 192 ++++ druid-shell/src/backend/wayland/menu.rs | 1 - druid-shell/src/backend/wayland/mod.rs | 17 +- druid-shell/src/backend/wayland/pointers.rs | 393 +++++++ .../{buffer.rs => surfaces/buffers.rs} | 72 +- .../src/backend/wayland/surfaces/idle.rs | 55 + .../backend/wayland/surfaces/layershell.rs | 276 +++++ .../src/backend/wayland/surfaces/mod.rs | 194 ++++ .../src/backend/wayland/surfaces/popup.rs | 190 ++++ .../src/backend/wayland/surfaces/surface.rs | 644 ++++++++++++ .../src/backend/wayland/surfaces/toplevel.rs | 187 ++++ druid-shell/src/backend/wayland/window.rs | 967 ++++++------------ druid-shell/src/backend/wayland/xkb.rs | 66 +- 18 files changed, 2946 insertions(+), 1059 deletions(-) create mode 100644 druid-shell/src/backend/wayland/.README.md create mode 100644 druid-shell/src/backend/wayland/keyboard.rs create mode 100644 druid-shell/src/backend/wayland/pointers.rs rename druid-shell/src/backend/wayland/{buffer.rs => surfaces/buffers.rs} (91%) create mode 100644 druid-shell/src/backend/wayland/surfaces/idle.rs create mode 100644 druid-shell/src/backend/wayland/surfaces/layershell.rs create mode 100644 druid-shell/src/backend/wayland/surfaces/mod.rs create mode 100644 druid-shell/src/backend/wayland/surfaces/popup.rs create mode 100644 druid-shell/src/backend/wayland/surfaces/surface.rs create mode 100644 druid-shell/src/backend/wayland/surfaces/toplevel.rs diff --git a/druid-shell/src/backend/wayland/.README.md b/druid-shell/src/backend/wayland/.README.md new file mode 100644 index 0000000000..8cbb7baae1 --- /dev/null +++ b/druid-shell/src/backend/wayland/.README.md @@ -0,0 +1,3 @@ +### development notes +- setting `export WAYLAND_DEBUG=1` allows you to see the various API calls and their values sent to wayland. +- wlroots repository was a bunch of examples you can run as a reference to see the output of `WAYLAND_DEBUG`. \ No newline at end of file diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index fcc1abf361..4a32a4a0c1 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -15,69 +15,94 @@ #![allow(clippy::single_match)] use super::{ - buffer::Mmap, - clipboard::Clipboard, - error::Error, - events::WaylandSource, - pointer::{MouseEvtKind, PointerEvent}, - window::WindowData, - xkb, -}; -use crate::text::simulate_input; -use crate::{ - application::AppHandler, backend::shared::Timer, keyboard_types::KeyState, kurbo::Point, - TimerToken, WinHandler, + clipboard::Clipboard, error::Error, events::WaylandSource, keyboard, pointers, surfaces, + window::WindowHandle, }; -use calloop::{ - timer::{Timer as CalloopTimer, TimerHandle}, - EventLoop, -}; +use crate::{application::AppHandler, backend, kurbo, mouse, TimerToken}; + +use calloop; + use std::{ cell::{Cell, RefCell}, collections::{BTreeMap, BinaryHeap}, - convert::TryInto, - num::NonZeroI32, + num::NonZeroU32, rc::Rc, time::{Duration, Instant}, }; + +use wayland_client::protocol::wl_keyboard::WlKeyboard; use wayland_client::{ self as wl, protocol::{ wl_compositor::WlCompositor, - wl_keyboard::{self, WlKeyboard}, wl_output::{self, Subpixel, Transform, WlOutput}, - wl_pointer::{self, WlPointer}, + wl_pointer::WlPointer, wl_seat::{self, WlSeat}, wl_shm::{self, WlShm}, + wl_surface::WlSurface, }, Proxy, }; use wayland_cursor::CursorTheme; -use wayland_protocols::{ - unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1, - xdg_shell::client::xdg_wm_base::{self, XdgWmBase}, -}; +use wayland_protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1; +use wayland_protocols::wlr::unstable::layer_shell::v1::client::zwlr_layer_shell_v1::ZwlrLayerShellV1; +use wayland_protocols::xdg_shell::client::xdg_positioner::XdgPositioner; +use wayland_protocols::xdg_shell::client::xdg_surface; +use wayland_protocols::xdg_shell::client::xdg_wm_base::{self, XdgWmBase}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct Timer(backend::shared::Timer); + +impl Timer { + pub(crate) fn new(id: u32, deadline: Instant) -> Self { + Self(backend::shared::Timer::new(deadline, id)) + } + + pub(crate) fn id(self) -> u32 { + self.0.data + } + + pub(crate) fn deadline(&self) -> Instant { + self.0.deadline() + } + + pub fn token(&self) -> TimerToken { + self.0.token() + } +} + +impl std::cmp::Ord for Timer { + /// Ordering is so that earliest deadline sorts first + // "Earliest deadline first" that a std::collections::BinaryHeap will have the earliest timer + // at its head, which is just what is needed for timer management. + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.deadline().cmp(&other.0.deadline()).reverse() + } +} + +impl std::cmp::PartialOrd for Timer { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} #[derive(Clone)] pub struct Application { - pub(crate) data: Rc, + pub(super) data: std::sync::Arc, } +#[allow(dead_code)] pub(crate) struct ApplicationData { - pub(crate) xkb_context: xkb::Context, - pub(crate) xkb_keymap: RefCell>, - // TODO should this be per-surface?? - pub(crate) xkb_state: RefCell>, - pub(crate) wl_server: wl::Display, - pub(crate) event_queue: Rc>, + pub(super) wl_server: wl::Display, + pub(super) event_queue: Rc>, // Wayland globals - pub(crate) globals: wl::GlobalManager, - pub(crate) xdg_base: wl::Main, - pub(crate) zxdg_decoration_manager_v1: wl::Main, - pub(crate) wl_compositor: wl::Main, - pub(crate) wl_shm: wl::Main, - pub(crate) cursor_theme: RefCell, + pub(super) globals: wl::GlobalManager, + pub(super) xdg_base: wl::Main, + pub(super) zxdg_decoration_manager_v1: wl::Main, + pub(super) zwlr_layershell_v1: wl::Main, + pub(super) wl_compositor: wl::Main, + pub(super) wl_shm: wl::Main, /// A map of wayland object IDs to outputs. /// /// Wayland will update this if the output change. Keep a record of the `Instant` you last @@ -85,32 +110,46 @@ pub(crate) struct ApplicationData { /// /// It's a BTreeMap so the ordering is consistent when enumerating outputs (not sure if this is /// necessary, but it negligable cost). - pub(crate) outputs: Rc>>, - pub(crate) seats: Rc>>>>, + pub(super) outputs: Rc>>, + pub(super) seats: Rc>>>>, /// Handles to any surfaces that have been created. /// /// This is where the window data is owned. Other handles should be weak. - pub(crate) surfaces: RefCell>>, + // pub(super) surfaces: RefCell>>, + + /// Handles to any surfaces that have been created. + pub(super) handles: RefCell>, /// Available pixel formats - pub(crate) formats: RefCell>, + pub(super) formats: RefCell>, /// Close flag - pub(crate) shutdown: Cell, + pub(super) shutdown: Cell, /// The currently active surface, if any (by wayland object ID) - pub(crate) active_surface_id: Cell>, + pub(super) active_surface_id: RefCell>, // Stuff for timers /// A calloop event source for timers. We always set it to fire at the next set timer, if any. - pub(crate) timer_handle: TimerHandle, + pub(super) timer_handle: calloop::timer::TimerHandle, /// We stuff this here until the event loop, then `take` it and use it. - timer_source: RefCell>>, + timer_source: RefCell>>, /// Currently pending timers /// /// The extra data is the surface this timer is for. - pub(crate) timers: RefCell>>, + pub(super) timers: RefCell>, + + pub(super) roundtrip_requested: RefCell, + + /// track if the display was flushed during the event loop. + /// prevents double flushing unnecessarily. + pub(super) display_flushed: RefCell, + /// reference to the pointer events manager. + pub(super) pointer: pointers::Pointer, + /// reference to the keyboard events manager. + keyboard: keyboard::Manager, } impl Application { pub fn new() -> Result { + tracing::info!("wayland application initiated"); // connect to the server. Internally an `Arc`, so cloning is cheap. Must be kept alive for // the duration of the app. let wl_server = wl::Display::connect_to_env()?; @@ -129,6 +168,7 @@ impl Application { let outputs: Rc>> = Rc::new(RefCell::new(BTreeMap::new())); let seats: Rc>>>> = Rc::new(RefCell::new(BTreeMap::new())); + // This object will create a container for the global wayland objects, and request that // it is populated by the server. Doesn't take ownership of the registry, we are // responsible for keeping it alive. @@ -142,23 +182,21 @@ impl Application { interface, version, } => { - //println!("{}@{} - {}", interface, version, id); + tracing::trace!("invalidate_rect initiated"); if interface.as_str() == "wl_output" && version >= 3 { let output = registry.bind::(3, id); - let output = Output::new(output); let output_id = output.id(); output.wl_output.quick_assign(with_cloned!(weak_outputs; move |_, event, _| { - weak_outputs - .upgrade() - .unwrap() - .borrow_mut() - .get_mut(&output_id) - .expect( - "internal: wayland sent an event for an output that doesn't exist", - ) - .process_event(event) - })); + match weak_outputs.upgrade().unwrap().borrow_mut().get_mut(&output_id) { + Some(o) => o.process_event(event), + None => tracing::warn!( + "wayland sent an event for an output that doesn't exist {:?} {:?}", + &output_id, + &event, + ), + } + })); let prev_output = weak_outputs .upgrade() .unwrap() @@ -185,13 +223,12 @@ impl Application { } } wl::GlobalEvent::Removed { id, interface } if interface.as_str() == "wl_output" => { - let removed = weak_outputs - .upgrade() - .unwrap() - .borrow_mut() - .remove(&id) - .expect("internal: wayland removed an output that doesn't exist"); - removed.wl_output.release(); + match weak_outputs.upgrade().unwrap().borrow_mut().remove(&id) { + Some(removed) => removed.wl_output.release(), + None => tracing::warn!( + "wayland sent a remove event for an output that doesn't exist" + ), + } } _ => (), // ignore other interfaces }, @@ -206,8 +243,9 @@ impl Application { globals_list.sort_by(|(_, name1, version1), (_, name2, version2)| { name1.cmp(name2).then(version1.cmp(version2)) }); + for (id, name, version) in globals_list.into_iter() { - //println!("{}@{} - {}", name, version, id); + tracing::trace!("{:?}@{:?} - {:?}", name, version, id); } let xdg_base = globals @@ -216,6 +254,9 @@ impl Application { let zxdg_decoration_manager_v1 = globals .instantiate_exact::(1) .map_err(|e| Error::global("zxdg_decoration_manager_v1", 1, e))?; + let zwlr_layershell_v1 = globals + .instantiate_exact::(1) + .map_err(|e| Error::global("zwlr_layershell_v1", 1, e))?; let wl_compositor = globals .instantiate_exact::(4) .map_err(|e| Error::global("wl_compositor", 4, e))?; @@ -229,60 +270,64 @@ impl Application { // your app's connection. Move *everything* to another thread, including e.g. file i/o, // computation, network, ... This is good practice for all back-ends: it will improve // responsiveness. - xdg_base.quick_assign(|xdg_base, event, _| match event { - xdg_wm_base::Event::Ping { serial } => xdg_base.pong(serial), - _ => (), + xdg_base.quick_assign(|xdg_base, event, d3| { + tracing::info!("xdg_base events {:?} {:?} {:?}", xdg_base, event, d3); + match event { + xdg_wm_base::Event::Ping { serial } => xdg_base.pong(serial), + _ => (), + } }); - let xkb_context = xkb::Context::new(); - //xkb_context.set_log_level(log::Level::Trace); - - let timer_source = CalloopTimer::new().unwrap(); + let timer_source = calloop::timer::Timer::new().unwrap(); let timer_handle = timer_source.handle(); - // TODO the choice of size needs more refinement, it should probably be the size needed to + // TODO the cursor theme size needs more refinement, it should probably be the size needed to // draw sharp cursors on the largest scaled monitor. - let cursor_theme = CursorTheme::load(64, &wl_shm); + let pointer = pointers::Pointer::new( + CursorTheme::load(64, &wl_shm), + wl_compositor.create_surface(), + ); // We need to have keyboard events set up for our seats before the next roundtrip. - let app_data = Rc::new(ApplicationData { - xkb_context, - xkb_keymap: RefCell::new(None), - xkb_state: RefCell::new(None), + let app_data = std::sync::Arc::new(ApplicationData { wl_server, event_queue: Rc::new(RefCell::new(event_queue)), globals, xdg_base, zxdg_decoration_manager_v1, + zwlr_layershell_v1, wl_compositor, wl_shm: wl_shm.clone(), - cursor_theme: RefCell::new(cursor_theme), outputs, seats, - surfaces: RefCell::new(im::OrdMap::new()), + handles: RefCell::new(im::OrdMap::new()), formats: RefCell::new(vec![]), shutdown: Cell::new(false), - active_surface_id: Cell::new(None), + active_surface_id: RefCell::new(std::collections::VecDeque::with_capacity(20)), timer_handle, timer_source: RefCell::new(Some(timer_source)), timers: RefCell::new(BinaryHeap::new()), + display_flushed: RefCell::new(false), + pointer, + keyboard: keyboard::Manager::default(), + roundtrip_requested: RefCell::new(false), }); // Collect the supported image formats. - wl_shm.quick_assign(with_cloned!(app_data; move |_, event, _| { + wl_shm.quick_assign(with_cloned!(app_data; move |d1, event, d3| { + tracing::info!("shared memory events {:?} {:?} {:?}", d1, event, d3); match event { wl_shm::Event::Format { format } => app_data.formats.borrow_mut().push(format), _ => (), // ignore other messages } })); - //println!("{:?}", app_data.seats.borrow()); - // Setup seat event listeners with our application for (id, seat) in app_data.seats.borrow().iter() { let id = *id; // move into closure. let wl_seat = seat.borrow().wl_seat.clone(); - wl_seat.quick_assign(with_cloned!(seat, app_data; move |_, event, _| { + wl_seat.quick_assign(with_cloned!(seat, app_data; move |d1, event, d3| { + tracing::info!("seat events {:?} {:?} {:?}", d1, event, d3); let mut seat = seat.borrow_mut(); match event { wl_seat::Event::Capabilities { capabilities } => { @@ -290,41 +335,31 @@ impl Application { if capabilities.contains(wl_seat::Capability::Keyboard) && seat.keyboard.is_none() { - let keyboard = seat.wl_seat.get_keyboard(); - let app = app_data.clone(); - keyboard.quick_assign(move |_, event, _| { - app.handle_keyboard_event(id, event); - }); - seat.keyboard = Some(keyboard); + seat.keyboard = Some(app_data.keyboard.attach(app_data.clone(), id, seat.wl_seat.clone())); } if capabilities.contains(wl_seat::Capability::Pointer) && seat.pointer.is_none() { let pointer = seat.wl_seat.get_pointer(); - let app = app_data.clone(); - let pointer_clone = pointer.detach(); - pointer.quick_assign(move |_, event, _| { - let pointer_clone = pointer_clone.clone(); - app.handle_pointer_event(pointer_clone, event); + app_data.pointer.attach(pointer.detach()); + pointer.quick_assign({ + let app = app_data.clone(); + move |pointer, event, _| { + pointers::Pointer::consume(app.clone(), pointer.detach(), event); + } }); seat.pointer = Some(pointer); } // Dont worry if they go away - we will just stop receiving events. If the // capability comes back we will start getting events again. - seat.last_update = Instant::now(); } wl_seat::Event::Name { name } => { seat.name = name; - seat.last_update = Instant::now(); } - _ => (), // ignore future events + _ => tracing::info!("seat quick assign unknown event {:?}", event), // ignore future events } })); } - /* - new_seat.quick_assign(move |_, event, _| { - }); - */ // Let wayland finish setup before we allow the client to start creating windows etc. app_data.sync()?; @@ -333,30 +368,42 @@ impl Application { } pub fn run(self, _handler: Option>) { + tracing::info!("run initiated"); // NOTE if we want to call this function more than once, we will need to put the timer // source back. let timer_source = self.data.timer_source.borrow_mut().take().unwrap(); // flush pending events (otherwise anything we submitted since sync will never be sent) self.data.wl_server.flush().unwrap(); // Use calloop so we can epoll both wayland events and others (e.g. timers) - let mut event_loop = EventLoop::try_new().expect("failed to initialize calloop event loop"); + let mut event_loop = calloop::EventLoop::try_new().unwrap(); let handle = event_loop.handle(); - let wayland_dispatcher = - WaylandSource::new(self.data.event_queue.clone()).into_dispatcher(); + + let wayland_dispatcher = WaylandSource::new(self.data.clone()).into_dispatcher(); + handle.register_dispatcher(wayland_dispatcher).unwrap(); - let app_data = self.data.clone(); + handle - .insert_source(timer_source, move |token, handle, &mut ()| { - app_data.handle_timer_event(token); + .insert_source(timer_source, move |token, _metadata, appdata| { + tracing::trace!("timer source {:?}", token); + appdata.handle_timer_event(token); }) .unwrap(); + let signal = event_loop.get_signal(); + let handle = handle.clone(); + event_loop - .run(Duration::from_millis(20), &mut (), move |&mut ()| { - if self.data.shutdown.get() { - signal.stop(); - } - }) + .run( + Duration::from_millis(20), + &mut self.data.clone(), + move |appdata| { + if appdata.shutdown.get() { + signal.stop(); + } + + ApplicationData::idle_repaint(handle.clone()); + }, + ) .unwrap(); } @@ -369,12 +416,49 @@ impl Application { } pub fn get_locale() -> String { - //TODO + tracing::warn!("get_locale unimplemented"); "en_US".into() } } +impl surfaces::Compositor for ApplicationData { + fn output(&self, id: &u32) -> Option { + match self.outputs.borrow().get(id) { + None => None, + Some(o) => Some(o.clone()), + } + } + + fn create_surface(&self) -> wl::Main { + self.wl_compositor.create_surface() + } + + fn shared_mem(&self) -> wl::Main { + self.wl_shm.clone() + } + + fn get_xdg_positioner(&self) -> wl::Main { + self.xdg_base.create_positioner() + } + + fn get_xdg_surface(&self, s: &wl::Main) -> wl::Main { + self.xdg_base.get_xdg_surface(s) + } + + fn zxdg_decoration_manager_v1(&self) -> wl::Main { + self.zxdg_decoration_manager_v1.clone() + } + + fn zwlr_layershell_v1(&self) -> wl::Main { + self.zwlr_layershell_v1.clone() + } +} + impl ApplicationData { + pub(crate) fn set_cursor(&self, cursor: &mouse::Cursor) { + self.pointer.replace(&cursor); + } + /// Send all pending messages and process all received messages. /// /// Don't use this once the event loop has started. @@ -388,200 +472,48 @@ impl ApplicationData { Ok(()) } - fn handle_keyboard_event(&self, seat_id: u32, event: wl_keyboard::Event) { - use wl_keyboard::{Event, KeyState as WlKeyState, KeymapFormat}; - // TODO need to keep the serial around for certain requests. - match event { - Event::Keymap { format, fd, size } => { - if !matches!(format, KeymapFormat::XkbV1) { - panic!("only xkb keymap supported for now"); - } - // TODO to test memory ownership we copy the memory. That way we can deallocate it - // and see if we get a segfault. - let keymap_data = unsafe { - Mmap::from_raw_private( - fd, - size.try_into().unwrap(), - 0, - size.try_into().unwrap(), - ) - .unwrap() - .as_ref() - .to_vec() - }; - // keymap data is '\0' terminated. - let keymap = self.xkb_context.keymap_from_slice(&keymap_data); - let state = keymap.state(); - *self.xkb_keymap.borrow_mut() = Some(keymap); - *self.xkb_state.borrow_mut() = Some(state); - } - Event::Enter { - serial, - surface, - keys, - } => { - let data = self - .find_surface(Proxy::from(surface).id()) - .expect("received a pointer event for a non-existant surface"); - data.keyboard_focus.set(true); - // (re-entrancy) call user code - data.handler.borrow_mut().got_focus(); - - data.run_deferred_tasks(); - } - Event::Leave { serial, surface } => { - let data = self - .find_surface(Proxy::from(surface).id()) - .expect("received a pointer event for a non-existant surface"); - data.keyboard_focus.set(false); - // (re-entrancy) call user code - data.handler.borrow_mut().lost_focus(); - data.run_deferred_tasks(); - } - Event::Key { - serial, - time, - key, - state, - } => { - let event = self.xkb_state.borrow().as_ref().unwrap().key_event( - key, - match state { - WlKeyState::Released => KeyState::Up, - WlKeyState::Pressed => KeyState::Down, - _ => panic!("unrecognised key event"), - }, - ); - // This clone is necessary because user methods might add more surfaces, which - // would then be inserted here which would be 1 mut 1 immut borrows which is not - // allowed. - for (_, data) in self.surfaces_iter() { - if data.keyboard_focus.get() { - match event.state { - KeyState::Down => { - // TODO what do I do if the key event is handled? Do I not update - // the xkb state? - simulate_input( - &mut **data.handler.borrow_mut(), - data.active_text_field.get(), - event.clone(), - ); - } - KeyState::Up => data.handler.borrow_mut().key_up(event.clone()), - } - } - data.run_deferred_tasks(); - } - } - Event::Modifiers { - serial, - mods_depressed, - mods_latched, - mods_locked, - group, - } => { - // Ignore this event for now and handle modifiers in user code. This might be - // suboptimal and need revisiting in the future. - //surface.check_for_scheduled_paint(); - } - Event::RepeatInfo { rate, delay } => { - // TODO actually store/use this info - println!("Requested repeat rate={} delay={}", rate, delay); - } - evt => { - tracing::warn!("Unhandled keybaord event: {:?}", evt); - } + fn current_window_id(&self) -> u32 { + match self.active_surface_id.borrow().get(0) { + Some(u) => u.get(), + None => 0, } } - /// `seat_id` is the object ID for the seat. - fn handle_pointer_event(&self, wl_pointer: WlPointer, event: wl_pointer::Event) { - use wl_pointer::Event; - match event { - Event::Enter { - serial, - surface, - surface_x, - surface_y, - } => { - let data = self - .find_surface(Proxy::from(surface).id()) - .expect("received a pointer event for a non-existant surface"); - // TODO some investigation will be needed to deduce the space `surface_x` and - // `surface_y` are relative to. - data.init_pointer(wl_pointer, serial); - // cannot fail (we just set it to Some) - let mut _pointer = data.pointer.borrow_mut(); - let pointer = _pointer.as_mut().unwrap(); - // No mouse enter event, but we know the position so we can issue a mouse move. - let pos = Point::new(surface_x, surface_y); - pointer.push(PointerEvent::Motion(pos)); - } - Event::Leave { serial, surface } => { - let data = self - .find_surface(Proxy::from(surface).id()) - .expect("received a pointer event for a non-existant surface"); - if let Some(pointer) = data.pointer.borrow_mut().as_mut() { - pointer.push(PointerEvent::Leave); - }; - } - Event::Motion { - time, - surface_x, - surface_y, - } => { - let pos = Point::new(surface_x, surface_y); - for (_, data) in self.surfaces_iter() { - if let Some(pointer) = data.pointer.borrow_mut().as_mut() { - pointer.push(PointerEvent::Motion(pos)); - } - } - } - Event::Button { - serial, - time, - button, - state, - } => { - for (_, data) in self.surfaces_iter() { - if let Some(pointer) = data.pointer.borrow_mut().as_mut() { - pointer.push(PointerEvent::Button { button, state }); - } - } - } - Event::Axis { time, axis, value } => { - for (_, data) in self.surfaces_iter() { - if let Some(pointer) = data.pointer.borrow_mut().as_mut() { - pointer.push(PointerEvent::Axis { axis, value }); - } - } - } - Event::Frame => { - for (_, data) in self.surfaces_iter() { - // Wait until we're outside the loop, then drop the pointer state. - let mut have_left = false; - while let Some(event) = data.pop_pointer_event() { - // (re-entrancy) - match event { - MouseEvtKind::Move(evt) => data.handler.borrow_mut().mouse_move(&evt), - MouseEvtKind::Up(evt) => data.handler.borrow_mut().mouse_up(&evt), - MouseEvtKind::Down(evt) => data.handler.borrow_mut().mouse_down(&evt), - MouseEvtKind::Leave => { - have_left = true; - data.handler.borrow_mut().mouse_leave(); - } - MouseEvtKind::Wheel(evt) => data.handler.borrow_mut().wheel(&evt), - } - } - if have_left { - *data.pointer.borrow_mut() = None; - } - data.run_deferred_tasks(); - } - } - evt => { - tracing::warn!("Unhandled pointer event: {:?}", evt); - } + pub(super) fn initial_window_size(&self, defaults: kurbo::Size) -> kurbo::Size { + // compute the initial window size. + let initialwidth = if defaults.width == 0.0 { + f64::INFINITY + } else { + defaults.width + }; + let initialheight = if defaults.height == 0.0 { + f64::INFINITY + } else { + defaults.height + }; + return self.outputs.borrow().iter().fold( + kurbo::Size::from((initialwidth, initialheight)), + |computed, entry| match &entry.1.current_mode { + None => computed, + Some(mode) => kurbo::Size::new( + computed.width.min(mode.width.into()), + computed.height.min(mode.height.into()), + ), + }, + ); + } + + pub(super) fn acquire_current_window(&self) -> Option { + match self.handles.borrow().get(&self.current_window_id()) { + None => None, + Some(w) => Some(w.clone()), + } + } + + pub(super) fn popup<'a>(&self, surface: &'a surfaces::popup::Surface) -> Result<(), Error> { + match self.acquire_current_window() { + None => return Err(Error::string("parent window does not exist")), + Some(winhandle) => winhandle.popup(surface), } } @@ -598,21 +530,24 @@ impl ApplicationData { expired_timers.push(timers.pop().unwrap()); } drop(timers); - for timer in expired_timers { - let surface = match self.surfaces.borrow().get(&timer.data).cloned() { + for expired in expired_timers { + let win = match self.handles.borrow().get(&expired.id()).cloned() { Some(s) => s, None => { // NOTE this might be expected - tracing::warn!("Received event for surface that doesn't exist any more"); + log::warn!("Received event for surface that doesn't exist any more"); continue; } }; // re-entrancy - surface.handler.borrow_mut().timer(timer.token()); + win.data() + .map(|data| data.handler.borrow_mut().timer(expired.token())); } - for (_, data) in self.surfaces_iter() { - data.run_deferred_tasks(); + + for (_, win) in self.handles_iter() { + win.data().map(|data| data.run_deferred_tasks()); } + // Get the deadline soonest and queue it. if let Some(timer) = self.timers.borrow().peek() { self.timer_handle @@ -623,18 +558,55 @@ impl ApplicationData { self.wl_server.flush().unwrap(); } - fn find_surface(&self, id: u32) -> Option> { - self.surfaces.borrow().get(&id).cloned() - } - /// Shallow clones surfaces so we can modify it during iteration. - fn surfaces_iter(&self) -> impl Iterator)> { + fn handles_iter(&self) -> impl Iterator { + self.handles.borrow().clone().into_iter() // make sure the borrow gets dropped. - let surfaces = { - let surfaces = self.surfaces.borrow(); - surfaces.clone() - }; - surfaces.into_iter() + // let surfaces = { + // let surfaces = self.surfaces.borrow(); + // surfaces.clone() + // }; + // surfaces.into_iter() + } + + fn idle_repaint<'a>(loophandle: calloop::LoopHandle<'a, std::sync::Arc>) { + loophandle.insert_idle({ + move |appdata| { + match appdata.acquire_current_window() { + Some(winhandle) => { + tracing::trace!("idle processing initiated"); + winhandle.request_anim_frame(); + winhandle.run_idle(); + + // if we already flushed this cycle don't flush again. + if *appdata.display_flushed.borrow() { + tracing::trace!("idle repaint flushing display initiated"); + if let Err(cause) = appdata.event_queue.borrow().display().flush() { + tracing::warn!("unable to flush display: {:?}", cause); + } + } + tracing::trace!("idle processing completed"); + } + None => tracing::error!( + "unable to acquire current window, skipping idle processing" + ), + }; + } + }); + } +} + +impl From for surfaces::CompositorHandle { + fn from(app: Application) -> surfaces::CompositorHandle { + surfaces::CompositorHandle::from(app.data) + } +} + +impl From> for surfaces::CompositorHandle { + fn from(data: std::sync::Arc) -> surfaces::CompositorHandle { + surfaces::CompositorHandle::direct( + std::sync::Arc::downgrade(&data) as std::sync::Weak + ) } } @@ -658,6 +630,7 @@ pub struct Output { last_update: Instant, } +#[allow(unused)] impl Output { // All the stuff before `current_mode` will be filled out immediately after creation, so these // dummy values will never be observed. @@ -689,6 +662,7 @@ impl Output { /// Incorporate update data from the server for this output. fn process_event(&mut self, evt: wl_output::Event) { + tracing::trace!("processing wayland output event {:?}", evt); match evt { wl_output::Event::Geometry { x, @@ -708,7 +682,6 @@ impl Output { self.make = make; self.model = model; self.transform = transform; - self.update_in_progress = true; } wl_output::Event::Mode { @@ -755,9 +728,9 @@ impl Output { #[derive(Debug, Clone)] pub struct Mode { - width: i32, - height: i32, - refresh: i32, + pub width: i32, + pub height: i32, + pub refresh: i32, } #[derive(Debug, Clone)] @@ -767,9 +740,6 @@ pub struct Seat { capabilities: wl_seat::Capability, keyboard: Option>, pointer: Option>, - // TODO touch - /// Lets us work out if things have changed since we last observed the output. - last_update: Instant, } impl Seat { @@ -778,7 +748,6 @@ impl Seat { wl_seat, name: "".into(), capabilities: wl_seat::Capability::empty(), - last_update: Instant::now(), keyboard: None, pointer: None, } diff --git a/druid-shell/src/backend/wayland/clipboard.rs b/druid-shell/src/backend/wayland/clipboard.rs index f73fb0a1f4..a4c52feb10 100644 --- a/druid-shell/src/backend/wayland/clipboard.rs +++ b/druid-shell/src/backend/wayland/clipboard.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Interactions with the system pasteboard on GTK+. +//! Interactions with the system pasteboard on wayland compositors. use crate::clipboard::{ClipboardFormat, FormatId}; @@ -20,6 +20,7 @@ use crate::clipboard::{ClipboardFormat, FormatId}; #[derive(Debug, Clone)] pub struct Clipboard; +#[allow(unused)] impl Clipboard { /// Put a string onto the system clipboard. pub fn put_string(&mut self, s: impl AsRef) { diff --git a/druid-shell/src/backend/wayland/error.rs b/druid-shell/src/backend/wayland/error.rs index e7c68d891c..c2a0be9787 100644 --- a/druid-shell/src/backend/wayland/error.rs +++ b/druid-shell/src/backend/wayland/error.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! GTK platform errors. +//! wayland platform errors. use std::{error::Error as StdError, fmt, sync::Arc}; use wayland_client as wl; @@ -30,6 +30,8 @@ pub enum Error { /// An unexpected error occurred. It's not handled by druid-shell/wayland, so you should /// terminate the app. Fatal(Arc), + String(ErrorString), + InvalidParent(u32), } impl Error { @@ -44,22 +46,24 @@ impl Error { inner: Arc::new(inner), } } + + pub fn string(s: impl Into) -> Self { + Error::String(ErrorString::from(s)) + } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self { - Self::Connect(e) => f.write_str("could not connect to the wayland server"), - Self::Global { - name, - version, - inner, - } => write!( + Self::Connect(e) => write!(f, "could not connect to the wayland server: {:?}", e), + Self::Global { name, version, .. } => write!( f, "a required wayland global ({}@{}) was unavailable", name, version ), - Self::Fatal(e) => f.write_str("an unhandled error occurred"), + Self::Fatal(e) => write!(f, "an unhandled error occurred: {:?}", e), + Self::String(e) => e.fmt(f), + Self::InvalidParent(id) => write!(f, "invalid parent window for popup: {:?}", id), } } } @@ -68,12 +72,10 @@ impl std::error::Error for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { Self::Connect(e) => Some(&**e), - Self::Global { - name, - version, - inner, - } => Some(&**inner), + Self::Global { inner, .. } => Some(&**inner), Self::Fatal(e) => Some(&**e), + Self::String(e) => Some(e), + Self::InvalidParent(_) => None, } } } @@ -83,3 +85,26 @@ impl From for Error { Self::Connect(Arc::new(err)) } } + +#[derive(Debug, Clone)] +pub struct ErrorString { + details: String, +} + +impl ErrorString { + pub fn from(s: impl Into) -> Self { + Self { details: s.into() } + } +} + +impl std::fmt::Display for ErrorString { + fn fmt(&self, f: &mut std::fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.details) + } +} + +impl std::error::Error for ErrorString { + fn description(&self) -> &str { + &self.details + } +} diff --git a/druid-shell/src/backend/wayland/events.rs b/druid-shell/src/backend/wayland/events.rs index 25b980fdf0..7445b3399f 100644 --- a/druid-shell/src/backend/wayland/events.rs +++ b/druid-shell/src/backend/wayland/events.rs @@ -12,17 +12,22 @@ use calloop::{ use std::{cell::RefCell, io, rc::Rc}; use wayland_client::EventQueue; +use super::{application, window}; + /// A wrapper around the wayland event queue that calloop knows how to select. -pub struct WaylandSource { +pub(crate) struct WaylandSource { + appdata: std::sync::Arc, queue: Rc>, fd: Generic, } impl WaylandSource { /// Wrap an `EventQueue` as a `WaylandSource`. - pub fn new(queue: Rc>) -> WaylandSource { + pub fn new(appdata: std::sync::Arc) -> WaylandSource { + let queue = appdata.event_queue.clone(); let fd = queue.borrow().display().get_connection_fd(); WaylandSource { + appdata, queue, fd: Generic::from_fd(fd, Interest::READ, Mode::Level), } @@ -31,38 +36,56 @@ impl WaylandSource { /// Get a dispatcher that we can insert into our event loop. pub fn into_dispatcher( self, - ) -> Dispatcher>, &mut ()) -> io::Result> - { - Dispatcher::new(self, |(), queue, &mut ()| { + ) -> Dispatcher< + Self, + impl FnMut( + window::WindowHandle, + &mut Rc>, + &mut std::sync::Arc, + ) -> io::Result, + > { + Dispatcher::new(self, |_winhandle, queue, appdata| { queue .borrow_mut() - .dispatch_pending(&mut (), |event, object, _| { - panic!( - "[druid-shell] Encountered an orphan event: {}@{} : {}\n\ - All events should be handled: please raise an issue", + .dispatch_pending(appdata, |event, object, _| { + tracing::error!( + "[druid-shell] Encountered an orphan event: {}@{} : {}", event.interface, object.as_ref().id(), event.name ); + tracing::error!("all events should be handled: please raise an issue"); }) }) } } impl EventSource for WaylandSource { - type Event = (); + type Event = window::WindowHandle; type Metadata = Rc>; type Ret = io::Result; fn process_events( &mut self, - _: calloop::Readiness, - _: calloop::Token, + ready: calloop::Readiness, + token: calloop::Token, mut callback: F, ) -> std::io::Result<()> where - F: FnMut((), &mut Rc>) -> Self::Ret, + F: FnMut(window::WindowHandle, &mut Rc>) -> Self::Ret, { + tracing::trace!("processing events invoked {:?} {:?}", ready, token); + + self.appdata.display_flushed.replace(false); + + let winhandle = match self.appdata.acquire_current_window() { + Some(winhandle) => winhandle, + None => { + tracing::error!("unable to acquire current window"); + return Ok(()); + } + }; + // in case of readiness of the wayland socket we do the following in a loop, until nothing // more can be read: loop { @@ -75,9 +98,11 @@ impl EventSource for WaylandSource { } } } + tracing::trace!("processing events initiated"); // 2. dispatch any pending event in the queue // propagate orphan events to the user - let ret = callback((), &mut self.queue); + let ret = callback(winhandle.clone(), &mut self.queue); + tracing::trace!("processing events completed {:?}", ret); match ret { Ok(0) => { // no events were dispatched even after reading the socket, @@ -91,16 +116,24 @@ impl EventSource for WaylandSource { } } } + + tracing::trace!("dispatching completed, flushing"); // 3. Once dispatching is finished, flush the responses to the compositor if let Err(e) = self.queue.borrow().display().flush() { if e.kind() != io::ErrorKind::WouldBlock { // in case of error, forward it and fast-exit return Err(e); } + // WouldBlock error means the compositor could not process all our messages // quickly. Either it is slowed down or we are a spammer. - // Should not really happen, if it does we do nothing and will flush again later + // Should not really happen, if it does we do nothing and will flush again later. + tracing::warn!("unable to flush display: {:?}", e); + } else { + self.appdata.display_flushed.replace(true); } + + tracing::trace!("event queue completed"); Ok(()) } diff --git a/druid-shell/src/backend/wayland/keyboard.rs b/druid-shell/src/backend/wayland/keyboard.rs new file mode 100644 index 0000000000..bbb4a4f444 --- /dev/null +++ b/druid-shell/src/backend/wayland/keyboard.rs @@ -0,0 +1,192 @@ +use std::convert::TryInto; +use wayland_client as wlc; +use wayland_client::protocol::wl_keyboard; +use wayland_client::protocol::wl_seat; + +use crate::keyboard_types::KeyState; +use crate::text; +use crate::Modifiers; + +use super::application::ApplicationData; +use super::surfaces::buffers; +use super::xkb; + +pub(super) struct State { + /// Whether we've currently got keyboard focus. + focused: bool, + xkb_context: xkb::Context, + xkb_keymap: std::cell::RefCell>, + xkb_state: std::cell::RefCell>, + xkb_mods: std::cell::Cell, +} + +impl State { + fn focused(&mut self, updated: bool) { + self.focused = updated; + } +} + +impl Default for State { + fn default() -> Self { + Self { + focused: false, + xkb_context: xkb::Context::new(), + xkb_keymap: std::cell::RefCell::new(None), + xkb_state: std::cell::RefCell::new(None), + xkb_mods: std::cell::Cell::new(Modifiers::empty()), + } + } +} + +pub struct Manager { + inner: std::sync::Arc>, +} + +impl Default for Manager { + fn default() -> Self { + Self { + inner: std::sync::Arc::new(std::cell::RefCell::new(State::default())), + } + } +} + +impl Manager { + pub(super) fn attach( + &self, + appdata: std::sync::Arc, + id: u32, + seat: wlc::Main, + ) -> wlc::Main { + let keyboard = seat.get_keyboard(); + keyboard.quick_assign({ + let appdata = appdata.clone(); + let keyboardstate = self.inner.clone(); + move |_, event, _| Manager::consume(&keyboardstate, &appdata, id, event) + }); + + keyboard + } + + pub(super) fn consume( + keyboardstate: &std::sync::Arc>, + appdata: &std::sync::Arc, + seat: u32, + event: wl_keyboard::Event, + ) { + tracing::trace!("consume {:?} -> {:?}", seat, event); + match event { + wl_keyboard::Event::Keymap { format, fd, size } => { + if !matches!(format, wl_keyboard::KeymapFormat::XkbV1) { + panic!("only xkb keymap supported for now"); + } + + // TODO to test memory ownership we copy the memory. That way we can deallocate it + // and see if we get a segfault. + let keymap_data = unsafe { + buffers::Mmap::from_raw_private( + fd, + size.try_into().unwrap(), + 0, + size.try_into().unwrap(), + ) + .unwrap() + .as_ref() + .to_vec() + }; + + let state = keyboardstate.borrow_mut(); + + // keymap data is '\0' terminated. + let keymap = state.xkb_context.keymap_from_slice(&keymap_data); + let keymapstate = keymap.state(); + + state.xkb_keymap.replace(Some(keymap)); + state.xkb_state.replace(Some(keymapstate)); + } + wl_keyboard::Event::Enter { .. } => { + let winhandle = match appdata.acquire_current_window() { + Some(w) => w, + None => { + tracing::warn!("dropping keyboard events, no window available"); + return; + } + }; + + keyboardstate.borrow_mut().focused(true); + winhandle.data().map(|data| { + // (re-entrancy) call user code + data.handler.borrow_mut().got_focus(); + data.run_deferred_tasks(); + }); + } + wl_keyboard::Event::Leave { .. } => { + let winhandle = match appdata.acquire_current_window() { + Some(w) => w, + None => { + tracing::warn!("dropping keyboard events, no window available"); + return; + } + }; + + keyboardstate.borrow_mut().focused(false); + winhandle.data().map(|data| { + // (re-entrancy) call user code + data.handler.borrow_mut().lost_focus(); + data.run_deferred_tasks(); + }); + } + wl_keyboard::Event::Key { key, state, .. } => { + let event = keyboardstate + .borrow() + .xkb_state + .borrow() + .as_ref() + .unwrap() + .key_event( + key, + match state { + wl_keyboard::KeyState::Released => KeyState::Up, + wl_keyboard::KeyState::Pressed => KeyState::Down, + _ => panic!("unrecognised key event"), + }, + keyboardstate.borrow().xkb_mods.get(), + ); + + if let Some(winhandle) = appdata.acquire_current_window() { + winhandle.data().map(|windata| { + windata.with_handler({ + let windata = windata.clone(); + move |handler| match event.state { + KeyState::Up => { + handler.key_up(event); + } + KeyState::Down => { + let handled = text::simulate_input( + handler, + windata.active_text_input.get(), + event, + ); + tracing::trace!( + "key press event {:?} {:?} {:?}", + handled, + key, + windata.active_text_input.get() + ); + } + } + }); + }); + } + } + wl_keyboard::Event::Modifiers { .. } => { + keyboardstate + .borrow() + .xkb_mods + .replace(xkb::event_to_mods(event)); + } + evt => { + tracing::warn!("unimplemented keyboard event: {:?}", evt); + } + } + } +} diff --git a/druid-shell/src/backend/wayland/menu.rs b/druid-shell/src/backend/wayland/menu.rs index aec9b6141a..bf43f9c1b7 100644 --- a/druid-shell/src/backend/wayland/menu.rs +++ b/druid-shell/src/backend/wayland/menu.rs @@ -13,7 +13,6 @@ // limitations under the License. #![allow(unused)] -use super::keycodes; use super::window::WindowHandle; use crate::common_util::strip_access_key; use crate::hotkey::{HotKey, RawMods}; diff --git a/druid-shell/src/backend/wayland/mod.rs b/druid-shell/src/backend/wayland/mod.rs index 6d2d6ab6c9..1e3898705a 100644 --- a/druid-shell/src/backend/wayland/mod.rs +++ b/druid-shell/src/backend/wayland/mod.rs @@ -12,24 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! wayland platform support + pub mod application; -mod buffer; pub mod clipboard; pub mod dialog; pub mod error; -mod events; -pub mod keycodes; +pub mod events; +pub mod keyboard; pub mod menu; -mod pointer; +pub mod pointers; pub mod screen; +pub mod surfaces; pub mod util; pub mod window; -mod xkb; - -/// Number of bytes for a pixel (argb = 4) -const PIXEL_WIDTH: i32 = 4; -/// Number of frames we need (2 for double buffering) -const NUM_FRAMES: i32 = 2; +pub mod xkb; /// Little enum to make it clearer what some return values mean. #[derive(Copy, Clone)] diff --git a/druid-shell/src/backend/wayland/pointers.rs b/druid-shell/src/backend/wayland/pointers.rs new file mode 100644 index 0000000000..b53abd9456 --- /dev/null +++ b/druid-shell/src/backend/wayland/pointers.rs @@ -0,0 +1,393 @@ +use std::collections::VecDeque; +use wayland_client::protocol::wl_pointer; +use wayland_client::protocol::wl_surface::WlSurface; +use wayland_client::{self as wl}; +use wayland_cursor::CursorImageBuffer; +use wayland_cursor::CursorTheme; + +use crate::keyboard::Modifiers; +use crate::kurbo::{Point, Vec2}; +use crate::mouse; + +use super::application::ApplicationData; + +// Button constants (linux specific) +const BTN_LEFT: u32 = 0x110; +const BTN_RIGHT: u32 = 0x111; +const BTN_MIDDLE: u32 = 0x112; + +// used to keep track of click event counts. +#[derive(Debug, Clone)] +struct ClickDebouncer(std::time::Instant, u8, mouse::MouseButton); + +impl Default for ClickDebouncer { + fn default() -> Self { + Self(std::time::Instant::now(), 1, mouse::MouseButton::None) + } +} + +impl ClickDebouncer { + // this threshold was abritrarily chosen based on experimention. + // there is likely a better default based on research to use. + // during experimentation this allowed one to get to around 4 clicks. + // but likely heavily dependent on the machine. + const THRESHOLD: std::time::Duration = std::time::Duration::from_millis(500); + + fn reset(ts: std::time::Instant, btn: mouse::MouseButton) -> Self { + Self(ts, 1, btn) + } + + fn debounce(&mut self, current: MouseEvtKind) -> MouseEvtKind { + let ts = std::time::Instant::now(); + + // reset counting and button. + if self.0 + ClickDebouncer::THRESHOLD < ts { + *self = ClickDebouncer::default(); + } + + match current { + MouseEvtKind::Up(mut evt) if self.2 == evt.button => { + evt.count = self.1; + MouseEvtKind::Up(evt) + } + MouseEvtKind::Down(mut evt) if self.2 == evt.button => { + self.1 += 1; + evt.count = self.1; + MouseEvtKind::Down(evt) + } + MouseEvtKind::Down(evt) if self.2 != evt.button => { + *self = ClickDebouncer::reset(ts, evt.button); + MouseEvtKind::Down(evt) + } + MouseEvtKind::Leave => { + *self = ClickDebouncer::reset(ts, mouse::MouseButton::None); + current + } + _ => current, + } + } +} + +/// Collect up mouse events then emit them together on a pointer frame. +pub(crate) struct Pointer { + /// The image surface which contains the cursor image. + pub(crate) cursor_surface: wl::Main, + /// Events that have occurred since the last frame. + pub(crate) queued_events: std::cell::RefCell>, + /// Currently pressed buttons + buttons: std::cell::RefCell, + /// Current position + pos: std::cell::Cell, + wl_pointer: std::cell::RefCell>, + // used to keep track of the current clicking + clickevent: std::cell::RefCell, + /// cursor theme data. + theme: std::cell::RefCell, + /// Cache the current cursor, so we can see if it changed + current_cursor: std::cell::RefCell, +} + +/// Raw wayland pointer events. +#[derive(Debug)] +pub(crate) enum PointerEvent { + /// Mouse moved/entered + Motion { + pointer: wl_pointer::WlPointer, + point: Point, + }, + /// Mouse button pressed/released + Button { + button: u32, + state: wl_pointer::ButtonState, + }, + /// Axis movement + Axis { axis: wl_pointer::Axis, value: f64 }, + /// Mouse left + Leave, +} + +/// An enum that we will convert into the different callbacks. +pub(crate) enum MouseEvtKind { + Move(mouse::MouseEvent), + Up(mouse::MouseEvent), + Down(mouse::MouseEvent), + Leave, + Wheel(mouse::MouseEvent), +} + +#[allow(unused)] +impl Pointer { + /// Create a new pointer + pub fn new(theme: CursorTheme, cursor: wl::Main) -> Self { + // ignore all events + cursor.quick_assign(|a1, event, a2| { + tracing::trace!("pointer surface event {:?} {:?} {:?}", a1, event, a2); + }); + + Pointer { + theme: std::cell::RefCell::new(theme), + buttons: std::cell::RefCell::new(mouse::MouseButtons::new()), + pos: std::cell::Cell::new(Point::ZERO), // will get set before we emit any events + queued_events: std::cell::RefCell::new(VecDeque::with_capacity(3)), // should be enough most of the time + cursor_surface: cursor, + wl_pointer: std::cell::RefCell::new(None), + current_cursor: std::cell::RefCell::new(mouse::Cursor::Arrow), + clickevent: std::cell::RefCell::new(ClickDebouncer::default()), + } + } + + pub fn attach(&self, current: wl_pointer::WlPointer) { + tracing::trace!("attaching pointer reference {:?}", current); + self.wl_pointer.replace(Some(current)); + } + + #[inline] + pub fn push(&self, event: PointerEvent) { + self.queued_events.borrow_mut().push_back(event); + } + + #[inline] + pub fn pop(&self) -> Option { + self.queued_events.borrow_mut().pop_front() + } + + #[inline] + pub fn cursor(&self) -> &WlSurface { + &self.cursor_surface + } + + pub fn replace(&self, cursor: &mouse::Cursor) { + let current = self.current_cursor.borrow().clone(); + let cursor = cursor.clone(); + + // Setting a new cursor involves communicating with the server, so don't do it if we + // don't have to. + if current == cursor { + return; + } + + let b = self.wl_pointer.borrow_mut(); + let wl_pointer = match &*b { + None => return, + Some(p) => p, + }; + + tracing::trace!("replacing cursor {:?} -> {:?}", current, cursor); + let buffer = match self.get_cursor_buffer(&cursor) { + None => return, + Some(b) => b, + }; + + self.current_cursor.replace(cursor); + wl_pointer.set_cursor(0, Some(&self.cursor_surface), 0, 0); + self.cursor_surface.attach(Some(&*buffer), 0, 0); + self.cursor_surface.damage_buffer(0, 0, i32::MAX, i32::MAX); + self.cursor_surface.commit(); + } + + fn get_cursor_buffer(&self, cursor: &mouse::Cursor) -> Option { + #[allow(deprecated)] + return match cursor { + mouse::Cursor::Arrow => self.unpack_image_buffer("left_ptr"), + mouse::Cursor::IBeam => self.unpack_image_buffer("xterm"), + mouse::Cursor::Crosshair => self.unpack_image_buffer("cross"), + mouse::Cursor::OpenHand => self.unpack_image_buffer("openhand"), + mouse::Cursor::NotAllowed => self.unpack_image_buffer("X_cursor"), + mouse::Cursor::ResizeLeftRight => self.unpack_image_buffer("row-resize"), + mouse::Cursor::ResizeUpDown => self.unpack_image_buffer("col-resize"), + mouse::Cursor::Pointer => self.unpack_image_buffer("pointer"), + mouse::Cursor::Custom(_) => { + tracing::warn!("custom cursors not implemented"); + self.unpack_image_buffer("left_ptr") + } + }; + } + + // Just use the first image, people using animated cursors have already made bad life + // choices and shouldn't expect it to work. + fn unpack_image_buffer(&self, name: &str) -> Option { + match self.theme.borrow_mut().get_cursor(name) { + None => None, + Some(c) => Some(c[c.frame_and_duration(0).frame_index].clone()), + } + } + + pub(super) fn consume( + appdata: std::sync::Arc, + source: wl_pointer::WlPointer, + event: wl_pointer::Event, + ) { + match event { + wl_pointer::Event::Enter { + surface, + surface_x, + surface_y, + .. + } => { + appdata.pointer.push(PointerEvent::Motion { + point: Point::new(surface_x, surface_y), + pointer: source.clone(), + }); + } + wl_pointer::Event::Leave { surface, .. } => { + appdata.pointer.push(PointerEvent::Leave); + } + wl_pointer::Event::Motion { + surface_x, + surface_y, + .. + } => { + appdata.pointer.push(PointerEvent::Motion { + point: Point::new(surface_x, surface_y), + pointer: source.clone(), + }); + } + wl_pointer::Event::Button { button, state, .. } => { + appdata.pointer.push(PointerEvent::Button { button, state }); + } + wl_pointer::Event::Axis { axis, value, .. } => { + appdata.pointer.push(PointerEvent::Axis { axis, value }); + } + wl_pointer::Event::Frame => { + let winhandle = match appdata.acquire_current_window() { + Some(w) => w, + None => { + tracing::warn!("dropping mouse events, no window available"); + appdata.pointer.queued_events.borrow_mut().clear(); + return; + } + }; + let winhandle = match winhandle.data() { + Some(w) => w, + None => { + tracing::warn!("dropping mouse events, no window available"); + appdata.pointer.queued_events.borrow_mut().clear(); + return; + } + }; + let mut winhandle = winhandle.handler.borrow_mut(); + + // (re-entrancy) call user code + while let Some(event) = appdata.pointer.dequeue() { + match event { + MouseEvtKind::Move(evt) => winhandle.mouse_move(&evt), + MouseEvtKind::Up(evt) => winhandle.mouse_up(&evt), + MouseEvtKind::Down(evt) => winhandle.mouse_down(&evt), + MouseEvtKind::Wheel(evt) => winhandle.wheel(&evt), + MouseEvtKind::Leave => winhandle.mouse_leave(), + } + } + } + evt => { + log::warn!("Unhandled pointer event: {:?}", evt); + } + } + } + + fn dequeue(&self) -> Option { + use wl_pointer::{Axis, ButtonState}; + // sometimes we need to ignore an event and move on + loop { + let event = self.queued_events.borrow_mut().pop_front()?; + tracing::trace!("mouse event {:?}", event); + match event { + PointerEvent::Motion { pointer, point } => { + self.pos.replace(point); + return Some(MouseEvtKind::Move(mouse::MouseEvent { + pos: point, + buttons: *self.buttons.borrow(), + mods: Modifiers::empty(), + count: 0, + focus: false, + button: mouse::MouseButton::None, + wheel_delta: Vec2::ZERO, + })); + } + PointerEvent::Button { button, state } => { + let button = match linux_to_mouse_button(button) { + // Skip unsupported buttons. + None => { + tracing::debug!("unsupport button click {:?}", button); + continue; + } + Some(b) => b, + }; + let evt = match state { + ButtonState::Pressed => { + self.buttons.borrow_mut().insert(button); + self.clickevent.borrow_mut().debounce(MouseEvtKind::Down( + mouse::MouseEvent { + pos: self.pos.get(), + buttons: *self.buttons.borrow(), + mods: Modifiers::empty(), + count: 1, + focus: false, + button, + wheel_delta: Vec2::ZERO, + }, + )) + } + ButtonState::Released => { + let mut updated = self.buttons.borrow_mut().remove(button); + self.clickevent.borrow_mut().debounce(MouseEvtKind::Up( + mouse::MouseEvent { + pos: self.pos.get(), + buttons: *self.buttons.borrow(), + mods: Modifiers::empty(), + count: 0, + focus: false, + button, + wheel_delta: Vec2::ZERO, + }, + )) + } + _ => { + log::error!("mouse button changed, but not pressed or released"); + continue; + } + }; + return Some(evt); + } + PointerEvent::Axis { axis, value } => { + let wheel_delta = match axis { + Axis::VerticalScroll => Vec2::new(0., value), + Axis::HorizontalScroll => Vec2::new(value, 0.), + _ => { + log::error!("axis direction not vertical or horizontal"); + continue; + } + }; + return Some(MouseEvtKind::Wheel(mouse::MouseEvent { + pos: self.pos.get(), + buttons: *self.buttons.borrow(), + mods: Modifiers::empty(), + count: 0, + focus: false, + button: mouse::MouseButton::None, + wheel_delta, + })); + } + PointerEvent::Leave => { + // The parent will remove us. + return Some(MouseEvtKind::Leave); + } + } + } + } +} + +impl Drop for Pointer { + fn drop(&mut self) { + self.cursor_surface.destroy(); + } +} + +#[inline] +fn linux_to_mouse_button(button: u32) -> Option { + match button { + BTN_LEFT => Some(mouse::MouseButton::Left), + BTN_RIGHT => Some(mouse::MouseButton::Right), + BTN_MIDDLE => Some(mouse::MouseButton::Middle), + _ => None, + } +} diff --git a/druid-shell/src/backend/wayland/buffer.rs b/druid-shell/src/backend/wayland/surfaces/buffers.rs similarity index 91% rename from druid-shell/src/backend/wayland/buffer.rs rename to druid-shell/src/backend/wayland/surfaces/buffers.rs index aa6ee91b35..aed89cad2f 100644 --- a/druid-shell/src/backend/wayland/buffer.rs +++ b/druid-shell/src/backend/wayland/surfaces/buffers.rs @@ -28,7 +28,12 @@ use wayland_client::{ }, }; -use super::{window::WindowData, PIXEL_WIDTH}; +use super::surface; + +/// Number of bytes for a pixel (argb = 4) +pub(super) const PIXEL_WIDTH: i32 = 4; +/// Number of frames we need (2 for double buffering) +pub(super) const NUM_FRAMES: i32 = 2; /// A collection of buffers that can change size. /// @@ -41,7 +46,7 @@ pub struct Buffers { pending: Cell, /// The physical size of the buffers. /// - /// This will be different from the buffers' actual size if `recrate_buffers` is true. + /// This will be different from the buffers' actual size if `recreate_buffers` is true. // NOTE: This really should support fractional scaling, use unstable protocol. size: Cell, /// Do we need to rebuild the framebuffers (size changed). @@ -52,11 +57,12 @@ pub struct Buffers { /// This flag allows us to check that we only hand out a mutable ref to the buffer data once. /// Otherwise providing mutable access to the data would be unsafe. pending_buffer_borrowed: Cell, - /// A handle to the `WindowData`, so we can run the paint method. + + /// A handle to the `Surface`, so we can run the paint method. /// /// Weak handle because logically we are owned by the `WindowData`. If ownership went in both /// directions we would leak memory. - window: WeakRc, + window: std::sync::Weak, /// Shared memory to allocate buffers in shm: RefCell, } @@ -64,10 +70,8 @@ pub struct Buffers { impl Buffers { /// Create a new `Buffers` object. /// - /// The initial size should have non-zero area. pub fn new(wl_shm: wl::Main, size: RawSize) -> Rc { assert!(N >= 2, "must be at least 2 buffers"); - assert!(!size.is_empty(), "window size must not be empty"); Rc::new(Self { buffers: Cell::new(None), pending: Cell::new(0), @@ -75,12 +79,12 @@ impl Buffers { recreate_buffers: Cell::new(true), deferred_paint: Cell::new(false), pending_buffer_borrowed: Cell::new(false), - window: WeakRc::new(), + window: std::sync::Weak::new(), shm: RefCell::new(Shm::new(wl_shm).expect("error allocating shared memory")), }) } - pub fn set_window_data(&mut self, data: WeakRc) { + pub fn set_window_data(&mut self, data: std::sync::Weak) { self.window = data; } @@ -107,7 +111,13 @@ impl Buffers { /// available we will set a flag, so that when one becomes available we immediately paint and /// present. This includes if we need to resize. pub fn request_paint(self: &Rc) { - //println!("request paint"); + tracing::trace!("buffer.request_paint {:?}", self.size.get()); + + // if our size is empty there is nothing to do. + if self.size.get().is_empty() { + return; + } + if self.pending_buffer_borrowed.get() { panic!("called request_paint during painting"); } @@ -119,7 +129,7 @@ impl Buffers { if self.recreate_buffers.get() { // If all buffers are free, destroy and recreate them if self.all_buffers_released() { - //tracing::debug!("all buffers released, recreating"); + //log::debug!("all buffers released, recreating"); self.deferred_paint.set(false); self.recreate_buffers_unchecked(); self.paint_unchecked(); @@ -130,7 +140,7 @@ impl Buffers { // If the next buffer is free, draw & present. If buffers have not been initialized it // is a bug in this code. if self.pending_buffer_released() { - //tracing::debug!("next frame has been released: draw and present"); + //log::debug!("next frame has been released: draw and present"); self.deferred_paint.set(false); self.paint_unchecked(); } else { @@ -140,11 +150,8 @@ impl Buffers { } /// Paint the next frame, without checking if the buffer is free. - /// - /// TODO call `handler.prepare_paint` before setting up cairo & invalidating regions. fn paint_unchecked(self: &Rc) { - //println!("paint unchecked"); - debug_assert!(!self.size.get().is_empty()); + tracing::trace!("buffer.paint_unchecked"); let mut buf_data = self.pending_buffer_data().unwrap(); debug_assert!( self.pending_buffer_released(), @@ -153,13 +160,13 @@ impl Buffers { if let Some(data) = self.window.upgrade() { data.paint(self.size.get(), &mut *buf_data, self.recreate_buffers.get()); } + self.recreate_buffers.set(false); } /// Destroy the current buffers, resize the shared memory pool if necessary, and create new /// buffers. Does not check if all buffers are free. fn recreate_buffers_unchecked(&self) { - //println!("recreate buffers unchecked"); debug_assert!( self.all_buffers_released(), "recreate buffers: some buffer still in use" @@ -181,13 +188,7 @@ impl Buffers { let size = self.size.get(); for i in 0..N { - buffers.push(Buffer::create( - &pool, - self.window.clone(), - i, - size.width, - size.height, - )); + buffers.push(Buffer::create(&pool, i, size.width, size.height)); } Some(buffers.try_into().unwrap()) }); @@ -275,28 +276,20 @@ impl Buffer { /// Create a new buffer using the given backing storage. It is the responsibility of the caller /// to ensure buffers don't overlap, and the backing storage has enough space. // Window handle is needed for the callback. - pub fn create( - pool: &wl::Main, - window: WeakRc, - idx: usize, - width: i32, - height: i32, - ) -> Self { - // TODO overflow + pub fn create(pool: &wl::Main, idx: usize, width: i32, height: i32) -> Self { let offset = i32::try_from(idx).unwrap() * width * height * PIXEL_WIDTH; let stride = width * PIXEL_WIDTH; let inner = pool.create_buffer(offset, width, height, stride, wl_shm::Format::Argb8888); let in_use = Rc::new(Cell::new(false)); - inner.quick_assign(with_cloned!(in_use; move |_, event, _| match event { - wl_buffer::Event::Release => { - in_use.set(false); - // TODO look at this. We should only paint if it has been requested. - if let Some(data) = window.upgrade() { - data.buffers.request_paint(); + inner.quick_assign(with_cloned!(in_use; move |b, event, _dispatchdata| { + tracing::trace!("buffer event: {:?} {:?}", b, event); + match event { + wl_buffer::Event::Release => { + in_use.set(false); } + _ => tracing::warn!("unhandled wayland buffer event: {:?} {:?}", b, event), } - _ => (), // panic!("release is the only event"), })); Buffer { inner, in_use } @@ -361,6 +354,7 @@ pub struct Shm { wl_shm: wl::Main, } +#[allow(unused)] impl Shm { /// Create a new shared memory object. Will be empty until resized. pub fn new(wl_shm: wl::Main) -> Result { @@ -551,7 +545,7 @@ impl Drop for Mmap { fn drop(&mut self) { unsafe { if let Err(e) = munmap(self.ptr.as_ptr(), self.size) { - tracing::warn!("Error unmapping memory: {}", e); + log::warn!("Error unmapping memory: {}", e); } } } diff --git a/druid-shell/src/backend/wayland/surfaces/idle.rs b/druid-shell/src/backend/wayland/surfaces/idle.rs new file mode 100644 index 0000000000..13ba17ffeb --- /dev/null +++ b/druid-shell/src/backend/wayland/surfaces/idle.rs @@ -0,0 +1,55 @@ +use crate::common_util::IdleCallback; +use crate::window; + +/// This represents different Idle Callback Mechanism +pub(super) enum Kind { + Callback(Box), + Token(window::IdleToken), +} + +impl std::fmt::Debug for Kind { + fn fmt(&self, format: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Kind::Callback(_) => format.debug_struct("Idle(Callback)").finish(), + Kind::Token(_) => format.debug_struct("Idle(Token)").finish(), + } + } +} + +#[derive(Clone)] +pub struct Handle { + pub(super) queue: std::sync::Arc>>, +} + +impl Handle { + /// Add an idle handler, which is called (once) when the message loop + /// is empty. The idle handler will be run from the main UI thread, and + /// won't be scheduled if the associated view has been dropped. + /// + /// Note: the name "idle" suggests that it will be scheduled with a lower + /// priority than other UI events, but that's not necessarily the case. + pub fn add_idle_callback(&self, callback: F) + where + F: FnOnce(&mut dyn window::WinHandler) + Send + 'static, + { + tracing::trace!("add_idle_callback initiated"); + let mut queue = self.queue.lock().unwrap(); + queue.push(Kind::Callback(Box::new(callback))); + } + + pub fn add_idle_token(&self, token: window::IdleToken) { + tracing::trace!("add_idle_token initiated {:?}", token); + let mut queue = self.queue.lock().unwrap(); + queue.push(Kind::Token(token)); + } +} + +pub(crate) fn run(state: &Handle, winhandle: &mut dyn window::WinHandler) { + let queue: Vec<_> = std::mem::take(&mut state.queue.lock().unwrap()); + for item in queue { + match item { + Kind::Callback(it) => it.call(winhandle), + Kind::Token(it) => winhandle.idle(it), + } + } +} diff --git a/druid-shell/src/backend/wayland/surfaces/layershell.rs b/druid-shell/src/backend/wayland/surfaces/layershell.rs new file mode 100644 index 0000000000..6202ae1097 --- /dev/null +++ b/druid-shell/src/backend/wayland/surfaces/layershell.rs @@ -0,0 +1,276 @@ +use wayland_client as wlc; +use wayland_protocols::wlr::unstable::layer_shell::v1::client as layershell; + +use crate::kurbo; +use crate::window; + +use super::super::error; +use super::popup; +use super::surface; +use super::Compositor; +use super::CompositorHandle; +use super::Handle; +use super::Popup; +use super::PopupHandle; + +struct Inner { + wl_surface: surface::Handle, + ls_surface: wlc::Main, +} + +impl Drop for Inner { + fn drop(&mut self) { + self.ls_surface.destroy(); + } +} + +impl Popup for Inner { + fn popup_impl(&self, p: &popup::Surface) -> Result<(), error::Error> { + tracing::info!("layershell get popup initiated"); + self.ls_surface.get_popup(&p.get_xdg_popup()); + p.commit(); + Ok(()) + } +} + +impl From for u32 { + fn from(s: Inner) -> u32 { + u32::from(s.wl_surface.clone()) + } +} + +impl From for std::sync::Arc { + fn from(s: Inner) -> std::sync::Arc { + std::sync::Arc::::from(s.wl_surface.clone()) + } +} + +#[derive(Clone, Debug)] +pub struct Margin { + top: i32, + right: i32, + bottom: i32, + left: i32, +} + +impl Default for Margin { + fn default() -> Self { + Margin::from((0, 0, 0, 0)) + } +} + +impl Margin { + pub fn new(m: impl Into) -> Self { + m.into() + } + + pub fn uniform(m: i32) -> Self { + Margin::from((m, m, m, m)) + } +} + +impl From<(i32, i32, i32, i32)> for Margin { + fn from(margins: (i32, i32, i32, i32)) -> Self { + Self { + top: margins.0, + left: margins.1, + bottom: margins.2, + right: margins.3, + } + } +} + +impl From for Margin { + fn from(m: i32) -> Self { + Margin::from((m, m, m, m)) + } +} + +impl From<(i32, i32)> for Margin { + fn from(m: (i32, i32)) -> Self { + Margin::from((m.0, m.1, m.0, m.1)) + } +} + +#[derive(Clone)] +pub struct Config { + pub initial_size: kurbo::Size, + pub layer: layershell::zwlr_layer_shell_v1::Layer, + pub keyboard_interactivity: layershell::zwlr_layer_surface_v1::KeyboardInteractivity, + pub anchor: layershell::zwlr_layer_surface_v1::Anchor, + pub exclusive_zone: i32, + pub margin: Margin, + pub namespace: &'static str, + pub app_id: &'static str, +} + +impl Config { + pub fn keyboard_interactivity( + mut self, + mode: layershell::zwlr_layer_surface_v1::KeyboardInteractivity, + ) -> Self { + self.keyboard_interactivity = mode; + self + } + + pub fn layer(mut self, layer: layershell::zwlr_layer_shell_v1::Layer) -> Self { + self.layer = layer; + self + } + + pub fn anchor(mut self, anchor: layershell::zwlr_layer_surface_v1::Anchor) -> Self { + self.anchor = anchor; + self + } + + pub fn margin(mut self, m: impl Into) -> Self { + self.margin = m.into(); + self + } + + fn apply(self, surface: &Surface) { + surface.initialize_dimensions(self.initial_size); + surface + .inner + .ls_surface + .set_exclusive_zone(self.exclusive_zone); + surface.inner.ls_surface.set_anchor(self.anchor); + surface + .inner + .ls_surface + .set_keyboard_interactivity(self.keyboard_interactivity); + surface.inner.ls_surface.set_margin( + self.margin.top, + self.margin.right, + self.margin.bottom, + self.margin.left, + ); + } +} + +impl Default for Config { + fn default() -> Self { + Self { + layer: layershell::zwlr_layer_shell_v1::Layer::Overlay, + initial_size: kurbo::Size::ZERO, + keyboard_interactivity: layershell::zwlr_layer_surface_v1::KeyboardInteractivity::None, + anchor: layershell::zwlr_layer_surface_v1::Anchor::empty(), + exclusive_zone: 0, + margin: Margin::default(), + namespace: "druid", + app_id: "", + } + } +} + +#[derive(Clone)] +pub struct Surface { + inner: std::sync::Arc, +} + +impl Surface { + pub fn new( + c: impl Into, + handler: Box, + config: Config, + ) -> Self { + let compositor = CompositorHandle::new(c); + let wl_surface = surface::Handle::new(compositor.clone(), handler, kurbo::Size::ZERO); + let ls_surface = compositor.zwlr_layershell_v1().get_layer_surface( + &wl_surface.inner.wl_surface, + None, + config.layer, + config.namespace.to_string(), + ); + + let handle = Self { + inner: std::sync::Arc::new(Inner { + wl_surface, + ls_surface, + }), + }; + + handle.inner.wl_surface.set_popup_impl(PopupHandle { + inner: handle.inner.clone(), + }); + handle.inner.ls_surface.quick_assign({ + let handle = handle.clone(); + let mut dim = config.initial_size.clone(); + move |a1, event, a2| match event { + layershell::zwlr_layer_surface_v1::Event::Configure { + serial, + width, + height, + } => { + tracing::info!("event {:?} {:?} {:?}", a1, event, a2); + // compositor is deferring to the client for determining the size + // when values are zero. + if width != 0 && height != 0 { + dim = kurbo::Size::new(width as f64, height as f64); + } + + handle.inner.ls_surface.ack_configure(serial); + handle + .inner + .ls_surface + .set_size(dim.width as u32, dim.height as u32); + handle + .inner + .wl_surface + .update_dimensions(dim.width as u32, dim.height as u32); + handle.inner.wl_surface.inner.buffers.request_paint(); + } + _ => tracing::info!("unimplemented event {:?} {:?} {:?}", a1, event, a2), + } + }); + + config.apply(&handle); + handle.inner.wl_surface.commit(); + + handle + } + + pub(crate) fn with_handler T>( + &self, + f: F, + ) -> Option { + std::sync::Arc::::from(self).with_handler(f) + } + + fn initialize_dimensions(&self, dim: kurbo::Size) { + self.inner + .ls_surface + .set_size(dim.width as u32, dim.height as u32); + self.inner.wl_surface.inner.handler.borrow_mut().size(dim); + } +} + +impl From for u32 { + fn from(s: Surface) -> u32 { + u32::from(&s.inner.wl_surface) + } +} + +impl From<&Surface> for u32 { + fn from(s: &Surface) -> u32 { + u32::from(&s.inner.wl_surface) + } +} + +impl From<&Surface> for std::sync::Arc { + fn from(s: &Surface) -> std::sync::Arc { + std::sync::Arc::::from(s.inner.wl_surface.clone()) + } +} + +impl From for std::sync::Arc { + fn from(s: Surface) -> std::sync::Arc { + std::sync::Arc::::from(s.inner.wl_surface.clone()) + } +} + +impl From for Box { + fn from(s: Surface) -> Box { + Box::new(s.inner.wl_surface.clone()) as Box + } +} diff --git a/druid-shell/src/backend/wayland/surfaces/mod.rs b/druid-shell/src/backend/wayland/surfaces/mod.rs new file mode 100644 index 0000000000..7dd8279638 --- /dev/null +++ b/druid-shell/src/backend/wayland/surfaces/mod.rs @@ -0,0 +1,194 @@ +use wayland_client::protocol::wl_shm::WlShm; +use wayland_client::{self as wlc, protocol::wl_surface::WlSurface}; +use wayland_protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1; +use wayland_protocols::wlr::unstable::layer_shell::v1::client::zwlr_layer_shell_v1::ZwlrLayerShellV1; +use wayland_protocols::xdg_shell::client::xdg_positioner; +use wayland_protocols::xdg_shell::client::xdg_surface; + +use crate::kurbo; +use crate::Scale; +use crate::TextFieldToken; + +use super::application; +use super::error; + +pub mod buffers; +pub mod idle; +pub mod layershell; +pub mod popup; +pub mod surface; +pub mod toplevel; + +pub trait Compositor { + fn output(&self, id: &u32) -> Option; + fn create_surface(&self) -> wlc::Main; + fn shared_mem(&self) -> wlc::Main; + fn get_xdg_surface(&self, surface: &wlc::Main) + -> wlc::Main; + fn get_xdg_positioner(&self) -> wlc::Main; + fn zxdg_decoration_manager_v1(&self) -> wlc::Main; + fn zwlr_layershell_v1(&self) -> wlc::Main; +} + +pub trait Decor { + fn inner_set_title(&self, title: String); +} + +impl dyn Decor { + pub fn set_title(&self, title: impl Into) { + self.inner_set_title(title.into()) + } +} + +pub trait Popup { + fn popup_impl(&self, popup: &popup::Surface) -> Result<(), error::Error>; +} + +pub struct PopupHandle { + inner: std::sync::Arc, +} + +impl PopupHandle { + fn popup(&self, p: &popup::Surface) -> Result<(), error::Error> { + self.inner.popup_impl(p) + } +} + +// handle on given surface. +pub trait Handle { + fn wayland_surface_id(&self) -> u32; + fn get_size(&self) -> kurbo::Size; + fn set_size(&self, dim: kurbo::Size); + fn request_anim_frame(&self); + fn invalidate(&self); + fn invalidate_rect(&self, rect: kurbo::Rect); + fn remove_text_field(&self, token: TextFieldToken); + fn set_focused_text_field(&self, active_field: Option); + fn get_idle_handle(&self) -> idle::Handle; + fn get_scale(&self) -> Scale; + fn run_idle(&self); + fn popup(&self, popup: &popup::Surface) -> Result<(), error::Error>; + fn release(&self); + fn data(&self) -> Option>; +} + +#[derive(Clone)] +pub struct CompositorHandle { + inner: std::sync::Weak, +} + +impl CompositorHandle { + pub fn new(c: impl Into) -> Self { + c.into() + } + + pub fn direct(c: std::sync::Weak) -> Self { + Self { inner: c } + } + + fn create_surface(&self) -> Option> { + match self.inner.upgrade() { + Some(c) => Some(c.create_surface()), + None => None, + } + } + + /// Recompute the scale to use (the maximum of all the provided outputs). + fn recompute_scale<'a>(&self, outputs: &'a std::collections::HashSet) -> i32 { + let compositor = match self.inner.upgrade() { + Some(c) => c, + None => panic!("should never recompute scale of window that has been dropped"), + }; + + let scale = outputs.iter().fold(0, |scale, id| { + match compositor.output(id) { + None => { + tracing::warn!( + "we still have a reference to an output that's gone away. The output had id {}", + id, + ); + scale + }, + Some(output) => scale.max(output.scale), + } + }); + + match scale { + 0 => { + tracing::warn!("wayland never reported which output we are drawing to"); + 1 + } + scale => scale, + } + } +} + +impl Compositor for CompositorHandle { + fn output(&self, id: &u32) -> Option { + match self.inner.upgrade() { + None => None, + Some(c) => c.output(id), + } + } + + fn create_surface(&self) -> wlc::Main { + match self.inner.upgrade() { + None => panic!("unable to acquire underyling compositor to create a surface"), + Some(c) => c.create_surface(), + } + } + + fn shared_mem(&self) -> wlc::Main { + match self.inner.upgrade() { + None => panic!("unable to acquire underyling compositor to acquire shared memory"), + Some(c) => c.shared_mem(), + } + } + + fn get_xdg_positioner(&self) -> wlc::Main { + match self.inner.upgrade() { + None => panic!("unable to acquire underyling compositor to create an xdg positioner"), + Some(c) => c.get_xdg_positioner(), + } + } + + fn get_xdg_surface(&self, s: &wlc::Main) -> wlc::Main { + match self.inner.upgrade() { + None => panic!("unable to acquire underyling compositor to create an xdg surface"), + Some(c) => c.get_xdg_surface(s), + } + } + + // fn get_xdg_popup( + // &self, + // pos: &wlc::Main, + // parent: Option<&xdg_surface::XdgSurface>, + // ) -> wlc::Main { + // match self.inner.upgrade() { + // None => panic!("unable to acquire underyling compositor to acquire xdg popup surface"), + // Some(c) => c.get_xdg_popup(pos, parent), + // } + // } + + fn zxdg_decoration_manager_v1(&self) -> wlc::Main { + match self.inner.upgrade() { + None => { + panic!("unable to acquire underyling compositor to acquire the decoration manager") + } + Some(c) => c.zxdg_decoration_manager_v1(), + } + } + + fn zwlr_layershell_v1(&self) -> wlc::Main { + match self.inner.upgrade() { + None => { + panic!("unable to acquire underyling compositor to acquire the layershell manager") + } + Some(c) => c.zwlr_layershell_v1(), + } + } +} + +pub fn id(s: impl Into) -> u32 { + s.into() +} diff --git a/druid-shell/src/backend/wayland/surfaces/popup.rs b/druid-shell/src/backend/wayland/surfaces/popup.rs new file mode 100644 index 0000000000..38dc91130e --- /dev/null +++ b/druid-shell/src/backend/wayland/surfaces/popup.rs @@ -0,0 +1,190 @@ +use wayland_client as wlc; +use wayland_protocols::xdg_shell::client::xdg_popup; +use wayland_protocols::xdg_shell::client::xdg_positioner; +use wayland_protocols::xdg_shell::client::xdg_surface; + +use crate::kurbo; +use crate::window; + +use super::surface; +use super::Compositor; +use super::CompositorHandle; +use super::Handle; + +struct Inner { + wl_surface: surface::Handle, + wl_xdg_popup: wlc::Main, +} + +impl From for u32 { + fn from(s: Inner) -> u32 { + u32::from(s.wl_surface.clone()) + } +} + +impl From for std::sync::Arc { + fn from(s: Inner) -> std::sync::Arc { + std::sync::Arc::::from(s.wl_surface.clone()) + } +} + +#[derive(Clone, Debug)] +pub struct Config { + pub size: kurbo::Size, + pub offset: kurbo::Point, + pub anchor_rect: (kurbo::Point, kurbo::Size), + pub anchor: xdg_positioner::Anchor, + pub gravity: xdg_positioner::Gravity, + pub constraint_adjustment: xdg_positioner::ConstraintAdjustment, +} + +impl Config { + fn apply(self, c: &CompositorHandle) -> wlc::Main { + tracing::debug!("configuring popup {:?}", self); + let pos = c.get_xdg_positioner(); + + pos.set_size(self.size.width as i32, self.size.height as i32); + pos.set_offset(self.offset.x as i32, self.offset.y as i32); + pos.set_anchor_rect( + self.anchor_rect.0.x as i32, + self.anchor_rect.0.y as i32, + self.anchor_rect.1.width as i32, + self.anchor_rect.1.height as i32, + ); + pos.set_anchor(self.anchor); + pos.set_gravity(self.gravity); + pos.set_constraint_adjustment(self.constraint_adjustment.bits()); + + pos + } +} + +impl Default for Config { + fn default() -> Self { + Self { + size: kurbo::Size::new(1., 1.), + anchor: xdg_positioner::Anchor::None, + offset: kurbo::Point::ZERO, + anchor_rect: (kurbo::Point::ZERO, kurbo::Size::from((1., 1.))), + gravity: xdg_positioner::Gravity::None, + constraint_adjustment: xdg_positioner::ConstraintAdjustment::None, + } + } +} + +#[derive(Clone)] +pub struct Surface { + inner: std::sync::Arc, +} + +impl Surface { + pub fn new( + c: impl Into, + handler: Box, + config: Config, + parent: Option<&xdg_surface::XdgSurface>, + ) -> Self { + let compositor = CompositorHandle::new(c); + let wl_surface = surface::Handle::new(compositor.clone(), handler, kurbo::Size::ZERO); + let wl_xdg_surface = compositor.get_xdg_surface(&wl_surface.inner.wl_surface); + + // register to receive xdg_surface events. + wl_xdg_surface.quick_assign({ + let wl_surface = wl_surface.clone(); + move |xdg_surface, event, _| match event { + xdg_surface::Event::Configure { serial } => { + xdg_surface.ack_configure(serial); + let dim = wl_surface.inner.logical_size.get(); + wl_surface.inner.handler.borrow_mut().size(dim); + wl_surface.inner.buffers.request_paint(); + } + _ => tracing::warn!("unhandled xdg_surface event {:?}", event), + } + }); + + let pos = config.apply(&compositor); + + let wl_xdg_popup = wl_xdg_surface.get_popup(parent, &pos); + + pos.destroy(); + + wl_xdg_popup.quick_assign({ + let wl_surface = wl_surface.clone(); + move |_xdg_popup, event, _| { + match event { + xdg_popup::Event::Configure { + x, + y, + width, + height, + } => { + tracing::trace!( + "popup configuration ({:?},{:?}) {:?}x{:?}", + x, + y, + width, + height + ); + wl_surface.update_dimensions(width as u32, height as u32); + } + _ => tracing::warn!("unhandled xdg_popup event configure {:?}", event), + }; + } + }); + + let handle = Self { + inner: std::sync::Arc::new(Inner { + wl_surface, + wl_xdg_popup, + }), + }; + + handle + } + + pub(super) fn get_xdg_popup(&self) -> wlc::Main { + self.inner.wl_xdg_popup.clone() + } + + pub(super) fn commit(&self) { + let wl_surface = &self.inner.wl_surface; + wl_surface.commit(); + } + + pub(crate) fn with_handler T>( + &self, + f: F, + ) -> Option { + std::sync::Arc::::from(self).with_handler(f) + } +} + +impl From for u32 { + fn from(s: Surface) -> u32 { + u32::from(&s.inner.wl_surface) + } +} + +impl From<&Surface> for u32 { + fn from(s: &Surface) -> u32 { + u32::from(&s.inner.wl_surface) + } +} + +impl From<&Surface> for std::sync::Arc { + fn from(s: &Surface) -> std::sync::Arc { + std::sync::Arc::::from(s.inner.wl_surface.clone()) + } +} + +impl From for std::sync::Arc { + fn from(s: Surface) -> std::sync::Arc { + std::sync::Arc::::from(s.inner.wl_surface.clone()) + } +} + +impl From for Box { + fn from(s: Surface) -> Box { + Box::new(s.inner.wl_surface.clone()) as Box + } +} diff --git a/druid-shell/src/backend/wayland/surfaces/surface.rs b/druid-shell/src/backend/wayland/surfaces/surface.rs new file mode 100644 index 0000000000..dec7d6b2e3 --- /dev/null +++ b/druid-shell/src/backend/wayland/surfaces/surface.rs @@ -0,0 +1,644 @@ +use std::cell::{Cell, RefCell}; +use std::rc::Rc; +use wayland_client as wlc; +use wayland_client::protocol::wl_surface; + +use crate::kurbo; +use crate::window; +use crate::{piet::Piet, region::Region, scale::Scale, TextFieldToken}; + +use super::super::Changed; + +use super::buffers; +use super::error; +use super::idle; +use super::popup; +use super::{Compositor, CompositorHandle, Decor, Handle as SurfaceHandle, PopupHandle}; + +pub enum DeferredTask { + Paint, + AnimationClear, +} + +#[derive(Clone)] +pub struct Handle { + pub(super) inner: std::sync::Arc, +} + +impl Handle { + pub fn new( + c: impl Into, + handler: Box, + initial_size: kurbo::Size, + ) -> Self { + let compositor = CompositorHandle::new(c); + let wl_surface = match compositor.create_surface() { + None => panic!("unable to create surface"), + Some(v) => v, + }; + + let current = std::sync::Arc::new(Data { + compositor: compositor.clone(), + wl_surface, + outputs: RefCell::new(std::collections::HashSet::new()), + buffers: buffers::Buffers::new(compositor.shared_mem(), initial_size.into()), + logical_size: Cell::new(initial_size), + scale: Cell::new(1), + anim_frame_requested: Cell::new(false), + handler: RefCell::new(handler), + idle_queue: std::sync::Arc::new(std::sync::Mutex::new(vec![])), + active_text_input: Cell::new(None), + damaged_region: RefCell::new(Region::EMPTY), + deferred_tasks: RefCell::new(std::collections::VecDeque::new()), + popup_impl: RefCell::new(None), + }); + + // Hook up the child -> parent weak pointer. + unsafe { + // Safety: No Rust references exist during the life of this reference (satisfies many + // read-only xor 1 mutable references). + let bufs: &mut buffers::Buffers<{ buffers::NUM_FRAMES as usize }> = + &mut *(std::rc::Rc::as_ptr(¤t.buffers) as *mut _); + bufs.set_window_data(std::sync::Arc::downgrade(¤t)); + } + + // register to receive wl_surface events. + current.wl_surface.quick_assign({ + let current = current.clone(); + move |_, event, _| { + tracing::info!("wl_surface event {:?}", event); + match event { + wl_surface::Event::Enter { output } => { + current + .outputs + .borrow_mut() + .insert(wlc::Proxy::from(output).id()); + } + wl_surface::Event::Leave { output } => { + current + .outputs + .borrow_mut() + .remove(&wlc::Proxy::from(output).id()); + } + _ => tracing::warn!("unhandled wayland surface event {:?}", event), + } + + let new_scale = current.recompute_scale(); + if current.set_scale(new_scale).is_changed() { + current.wl_surface.set_buffer_scale(new_scale); + // We also need to change the physical size to match the new scale + current.buffers.set_size( + buffers::RawSize::from(current.logical_size.get()).scale(new_scale), + ); + // always repaint, because the scale changed. + current.schedule_deferred_task(DeferredTask::Paint); + } + } + }); + + return Self { inner: current }; + } + + pub(super) fn update_dimensions(&self, width: u32, height: u32) -> kurbo::Size { + self.inner.update_dimensions(width, height) + } + + pub(super) fn set_popup_impl(&self, imp: PopupHandle) { + self.inner.popup_impl.replace(Some(imp)); + } + + pub(super) fn commit(&self) { + self.inner.wl_surface.commit() + } +} + +impl SurfaceHandle for Handle { + fn wayland_surface_id(&self) -> u32 { + self.inner.wayland_surface_id() + } + + fn get_size(&self) -> kurbo::Size { + self.inner.get_size() + } + + fn set_size(&self, dim: kurbo::Size) { + self.inner.resize(dim); + } + + fn request_anim_frame(&self) { + self.inner.request_anim_frame() + } + + fn remove_text_field(&self, token: TextFieldToken) { + self.inner.remove_text_field(token) + } + + fn set_focused_text_field(&self, active_field: Option) { + self.inner.set_focused_text_field(active_field) + } + + fn get_idle_handle(&self) -> idle::Handle { + self.inner.get_idle_handle() + } + + fn get_scale(&self) -> Scale { + self.inner.get_scale() + } + + fn invalidate(&self) { + self.inner.invalidate() + } + + fn invalidate_rect(&self, rect: kurbo::Rect) { + self.inner.invalidate_rect(rect) + } + + fn run_idle(&self) { + self.inner.run_idle(); + } + + fn popup<'a>(&self, p: &'a popup::Surface) -> Result<(), error::Error> { + self.inner.popup(p) + } + + fn release(&self) { + self.inner.release() + } + + fn data(&self) -> Option> { + Some(Into::into(self)) + } +} + +impl From for u32 { + fn from(s: Handle) -> u32 { + use std::ops::Deref; + u32::from(s.inner.deref()) + } +} + +impl From<&Handle> for u32 { + fn from(s: &Handle) -> u32 { + use std::ops::Deref; + u32::from(s.inner.deref()) + } +} + +impl From for std::sync::Arc { + fn from(s: Handle) -> std::sync::Arc { + std::sync::Arc::::from(s.inner.clone()) + } +} + +impl From<&Handle> for std::sync::Arc { + fn from(s: &Handle) -> std::sync::Arc { + std::sync::Arc::::from(s.inner.clone()) + } +} + +pub struct Data { + pub(super) compositor: CompositorHandle, + pub(super) wl_surface: wlc::Main, + + /// The outputs that our surface is present on (we should get the first enter event early). + pub(super) outputs: RefCell>, + + /// Buffers in our shared memory. + // Buffers sometimes need to move references to themselves into closures, so must be behind a + // reference counter. + pub(super) buffers: Rc>, + /// The logical size of the next frame. + pub(crate) logical_size: Cell, + /// The scale we are rendering to (defaults to 1) + pub(crate) scale: Cell, + + /// Contains the callbacks from user code. + pub(crate) handler: RefCell>, + pub(crate) active_text_input: Cell>, + + /// Whether we have requested an animation frame. This stops us requesting more than 1. + anim_frame_requested: Cell, + /// Rects of the image that are damaged and need repainting in the logical coordinate space. + /// + /// This lives outside `data` because they can be borrowed concurrently without re-entrancy. + damaged_region: RefCell, + /// Tasks that were requested in user code. + /// + /// These call back into user code, and so should only be run after all user code has returned, + /// to avoid possible re-entrancy. + deferred_tasks: RefCell>, + + idle_queue: std::sync::Arc>>, + + popup_impl: RefCell>, +} + +impl Data { + #[track_caller] + pub(crate) fn with_handler T>( + &self, + f: F, + ) -> Option { + let ret = self.with_handler_and_dont_check_the_other_borrows(f); + self.run_deferred_tasks(); + ret + } + + #[track_caller] + fn with_handler_and_dont_check_the_other_borrows< + T, + F: FnOnce(&mut dyn window::WinHandler) -> T, + >( + &self, + f: F, + ) -> Option { + match self.handler.try_borrow_mut() { + Ok(mut h) => Some(f(&mut **h)), + Err(_) => { + tracing::error!( + "failed to borrow WinHandler at {}", + std::panic::Location::caller() + ); + None + } + } + } + + pub(super) fn update_dimensions(&self, width: u32, height: u32) -> kurbo::Size { + let dim = kurbo::Size::from((width as f64, height as f64)); + if self.logical_size.get() != self.resize(dim) { + match self.handler.try_borrow_mut() { + Ok(mut handler) => handler.size(dim), + Err(cause) => tracing::warn!("unhable to borrow handler {:?}", cause), + }; + } + + return dim; + } + + // client initiated resizing. + pub(super) fn resize(&self, dim: kurbo::Size) -> kurbo::Size { + // The size here is the logical size + let scale = self.scale.get(); + let raw_logical_size = buffers::RawSize { + width: dim.width as i32, + height: dim.height as i32, + }; + let previous_logical_size = self.logical_size.replace(dim); + + if previous_logical_size != dim { + self.buffers.set_size(raw_logical_size.scale(scale)); + } + + return dim; + } + + /// Assert that the physical size = logical size * scale + #[allow(unused)] + fn assert_size(&self) { + assert_eq!( + self.buffers.size(), + buffers::RawSize::from(self.logical_size.get()).scale(self.scale.get()), + "phy {:?} == logic {:?} * {}", + self.buffers.size(), + self.logical_size.get(), + self.scale.get() + ); + } + + /// Recompute the scale to use (the maximum of all the scales for the different outputs this + /// surface is drawn to). + fn recompute_scale(&self) -> i32 { + tracing::info!("recompute initiated"); + self.compositor.recompute_scale(&self.outputs.borrow()) + } + + /// Sets the scale + /// + /// Up to the caller to make sure `physical_size`, `logical_size` and `scale` are consistent. + fn set_scale(&self, new_scale: i32) -> Changed { + tracing::info!("set_scale initiated"); + if self.scale.get() != new_scale { + self.scale.set(new_scale); + // (re-entrancy) Report change to client + self.handler + .borrow_mut() + .scale(Scale::new(new_scale as f64, new_scale as f64)); + Changed::Changed + } else { + Changed::Unchanged + } + } + + /// Paint the next frame. + /// + /// The buffers object is responsible for calling this function after we called + /// `request_paint`. + /// + /// - `buf` is what we draw the frame into + /// - `size` is the physical size in pixels we are drawing. + /// - `force` means draw the whole frame, even if it wasn't all invalidated. + pub(super) fn paint(&self, size: buffers::RawSize, buf: &mut [u8], force: bool) { + tracing::trace!( + "paint initiated {:?} - {:?} {:?}", + self.get_size(), + size, + force + ); + + if force { + self.invalidate(); + } else { + let damaged_region = self.damaged_region.borrow_mut(); + for rect in damaged_region.rects() { + // Convert it to physical coordinate space. + let rect = buffers::RawRect::from(*rect).scale(self.scale.get()); + self.wl_surface.damage_buffer( + rect.x0, + rect.y0, + rect.x1 - rect.x0, + rect.y1 - rect.y0, + ); + } + if damaged_region.is_empty() { + // Nothing to draw, so we can finish here! + return; + } + } + + let physical_size = self.buffers.size(); + + // create cairo context (safety: we must drop the buffer before we commit the frame) + // TODO: Cairo is native-endian while wayland is little-endian, which is a pain. Currently + // will give incorrect results on big-endian architectures. + // TODO cairo might use a different stride than the width of the format. Since we always + // use argb32 which is 32-bit aligned we should be ok, but strictly speaking cairo might + // choose a wider stride and read past the end of our buffer (UB). Fixing this would + // require a fair bit of effort. + unsafe { + // We're going to lie about the lifetime of our buffer here. This is (I think) ok, + // becuase the Rust wrapper for cairo is overly pessimistic: the buffer only has to + // last as long as the `ImageSurface` (which we know this buffer will). + let buf: &'static mut [u8] = &mut *(buf as *mut _); + let cairo_surface = match cairo::ImageSurface::create_for_data( + buf, + cairo::Format::ARgb32, + physical_size.width, + physical_size.height, + physical_size.width * buffers::PIXEL_WIDTH, + ) { + Ok(s) => s, + Err(cause) => { + tracing::error!("unable to create cairo surface: {:?}", cause); + return; + } + }; + let ctx = match cairo::Context::new(&cairo_surface) { + Ok(ctx) => ctx, + Err(cause) => { + tracing::error!("unable to create cairo context: {:?}", cause); + return; + } + }; + // Apply scaling + let scale = self.scale.get() as f64; + ctx.scale(scale, scale); + + let mut piet = Piet::new(&ctx); + // Actually paint the new frame + let region = self.damaged_region.borrow(); + + // The handler must not be already borrowed. This may mean deferring this call. + self.handler.borrow_mut().paint(&mut piet, &*region); + } + + // reset damage ready for next frame. + self.damaged_region.borrow_mut().clear(); + + self.buffers.attach(); + self.wl_surface.commit(); + } + + /// Request invalidation of the entire window contents. + fn invalidate(&self) { + tracing::trace!("invalidate initiated"); + // This is one of 2 methods the user can use to schedule a repaint, the other is + // `invalidate_rect`. + let window_rect = self.logical_size.get().to_rect(); + self.damaged_region.borrow_mut().add_rect(window_rect); + self.schedule_deferred_task(DeferredTask::Paint); + } + + /// Request invalidation of one rectangle, which is given in display points relative to the + /// drawing area. + fn invalidate_rect(&self, rect: kurbo::Rect) { + tracing::trace!("invalidate_rect initiated {:?}", rect); + // Quick check to see if we can skip the rect entirely (if it is outside the visible + // screen). + if rect.intersect(self.logical_size.get().to_rect()).is_empty() { + return; + } + /* this would be useful for debugging over-keen invalidation by clients. + println!( + "{:?} {:?}", + rect, + self.with_data(|data| data.logical_size.to_rect()) + ); + */ + self.damaged_region.borrow_mut().add_rect(rect); + self.schedule_deferred_task(DeferredTask::Paint); + } + + pub fn schedule_deferred_task(&self, task: DeferredTask) { + tracing::trace!("scedule_deferred_task initiated"); + self.deferred_tasks.borrow_mut().push_back(task); + } + + pub fn run_deferred_tasks(&self) { + tracing::trace!("run_deferred_tasks initiated"); + while let Some(task) = self.next_deferred_task() { + self.run_deferred_task(task); + } + } + + fn next_deferred_task(&self) -> Option { + self.deferred_tasks.borrow_mut().pop_front() + } + + fn run_deferred_task(&self, task: DeferredTask) { + match task { + DeferredTask::Paint => { + self.buffers.request_paint(); + } + DeferredTask::AnimationClear => { + self.anim_frame_requested.set(false); + } + } + } + + pub(super) fn wayland_surface_id(&self) -> u32 { + u32::from(self) + } + + pub(super) fn get_size(&self) -> kurbo::Size { + // size in pixels, so we must apply scale. + let logical_size = self.logical_size.get(); + let scale = self.scale.get() as f64; + return kurbo::Size::new( + logical_size.width as f64 * scale, + logical_size.height as f64 * scale, + ); + } + + pub(super) fn request_anim_frame(&self) { + let idle = self.get_idle_handle(); + + if !self.anim_frame_requested.get() { + self.anim_frame_requested.set(true); + idle.add_idle_callback(move |winhandle| { + winhandle.prepare_paint(); + }); + self.schedule_deferred_task(DeferredTask::AnimationClear); + } + } + + pub(super) fn remove_text_field(&self, token: TextFieldToken) { + if self.active_text_input.get() == Some(token) { + self.active_text_input.set(None); + } + } + + pub(super) fn set_focused_text_field(&self, active_field: Option) { + self.active_text_input.set(active_field); + } + + pub(super) fn get_idle_handle(&self) -> idle::Handle { + idle::Handle { + queue: self.idle_queue.clone(), + } + } + + pub(super) fn get_scale(&self) -> Scale { + let scale = self.scale.get() as f64; + Scale::new(scale, scale) + } + + pub(super) fn run_idle(&self) { + self.with_handler(|winhandle| { + idle::run(&self.get_idle_handle(), winhandle); + }); + } + + pub(super) fn popup<'a>(&self, p: &'a popup::Surface) -> Result<(), error::Error> { + match &*self.popup_impl.borrow() { + Some(imp) => imp.popup(p), + None => Err(error::Error::string(format!( + "parent window doesn't support popups: {:?}", + self.wayland_surface_id() + ))), + } + } + + pub(super) fn release(&self) { + self.wl_surface.destroy() + } +} + +impl From for u32 { + fn from(s: Data) -> u32 { + wlc::Proxy::from(s.wl_surface.detach()).id() + } +} + +impl From<&Data> for u32 { + fn from(s: &Data) -> u32 { + wlc::Proxy::from(s.wl_surface.detach()).id() + } +} + +impl From> for Handle { + fn from(d: std::sync::Arc) -> Handle { + Handle { inner: d.clone() } + } +} + +pub struct Dead; + +impl Default for Dead { + fn default() -> Self { + Self {} + } +} + +impl From for Box { + fn from(d: Dead) -> Box { + Box::new(d) as Box + } +} + +impl Decor for Dead { + fn inner_set_title(&self, title: String) { + tracing::warn!("set_title not implemented for this surface: {:?}", title); + } +} + +impl SurfaceHandle for Dead { + fn wayland_surface_id(&self) -> u32 { + tracing::warn!("wayland_surface_id invoked on a dead surface"); + 0 + } + + fn get_size(&self) -> kurbo::Size { + kurbo::Size::ZERO + } + + fn set_size(&self, dim: kurbo::Size) { + tracing::warn!("set_size invoked on a dead surface {:?}", dim); + } + + fn request_anim_frame(&self) { + tracing::warn!("request_anim_frame invoked on a dead surface") + } + + fn remove_text_field(&self, _token: TextFieldToken) { + tracing::warn!("remove_text_field invoked on a dead surface") + } + + fn set_focused_text_field(&self, _active_field: Option) { + tracing::warn!("set_focused_text_field invoked on a dead surface") + } + + fn get_idle_handle(&self) -> idle::Handle { + panic!("get_idle_handle invoked on a dead surface") + } + + fn get_scale(&self) -> Scale { + Scale::new(1., 1.) + } + + fn invalidate(&self) { + tracing::warn!("invalidate invoked on a dead surface") + } + + fn invalidate_rect(&self, _rect: kurbo::Rect) { + tracing::warn!("invalidate_rect invoked on a dead surface") + } + + fn run_idle(&self) { + tracing::warn!("run_idle invoked on a dead surface") + } + + fn popup(&self, _: &popup::Surface) -> Result<(), error::Error> { + tracing::warn!("popup invoked on a dead surface"); + Err(error::Error::InvalidParent(0)) + } + + fn release(&self) { + tracing::warn!("release invoked on a dead surface"); + } + + fn data(&self) -> Option> { + tracing::warn!("data invoked on a dead surface"); + None + } +} diff --git a/druid-shell/src/backend/wayland/surfaces/toplevel.rs b/druid-shell/src/backend/wayland/surfaces/toplevel.rs new file mode 100644 index 0000000000..975c918c62 --- /dev/null +++ b/druid-shell/src/backend/wayland/surfaces/toplevel.rs @@ -0,0 +1,187 @@ +use wayland_client as wlc; +use wayland_protocols::unstable::xdg_decoration::v1::client::zxdg_toplevel_decoration_v1 as toplevel_decorations; +use wayland_protocols::xdg_shell::client::xdg_surface; +use wayland_protocols::xdg_shell::client::xdg_toplevel; + +use crate::kurbo; +use crate::window; + +use super::surface; +use super::Compositor; +use super::CompositorHandle; +use super::Decor; +use super::Handle; + +struct Inner { + wl_surface: surface::Handle, + #[allow(unused)] + pub(super) xdg_surface: wlc::Main, + pub(super) xdg_toplevel: wlc::Main, + #[allow(unused)] + pub(super) zxdg_toplevel_decoration_v1: + wlc::Main, +} + +impl From for u32 { + fn from(s: Inner) -> u32 { + u32::from(s.wl_surface) + } +} + +impl From for std::sync::Arc { + fn from(s: Inner) -> std::sync::Arc { + std::sync::Arc::::from(s.wl_surface) + } +} + +#[derive(Clone)] +pub struct Surface { + inner: std::sync::Arc, +} + +impl Surface { + pub fn new( + c: impl Into, + handler: Box, + initial_size: kurbo::Size, + min_size: Option, + ) -> Self { + let compositor = CompositorHandle::new(c); + let wl_surface = surface::Handle::new(compositor.clone(), handler, kurbo::Size::ZERO); + let xdg_surface = compositor.get_xdg_surface(&wl_surface.inner.wl_surface); + let xdg_toplevel = xdg_surface.get_toplevel(); + let zxdg_toplevel_decoration_v1 = compositor + .zxdg_decoration_manager_v1() + .get_toplevel_decoration(&xdg_toplevel); + + // register to receive xdg_surface events. + xdg_surface.quick_assign({ + let wl_surface = wl_surface.clone(); + move |xdg_surface, event, _| { + tracing::trace!("xdg_surface event configure {:?}", event); + match event { + xdg_surface::Event::Configure { serial } => { + xdg_surface.ack_configure(serial); + let dim = wl_surface.inner.logical_size.get(); + wl_surface.inner.handler.borrow_mut().size(dim); + wl_surface.inner.buffers.request_paint(); + } + _ => tracing::warn!("unhandled xdg_surface event {:?}", event), + } + } + }); + + xdg_toplevel.quick_assign({ + let wl_surface = wl_surface.clone(); + let mut dim = initial_size.clone(); + move |_xdg_toplevel, event, a3| match event { + xdg_toplevel::Event::Configure { + width, + height, + states, + } => { + tracing::trace!( + "configure event {:?} {:?} {:?} {:?}", + width, + height, + states, + a3 + ); + // compositor is deferring to the client for determining the size + // when values are zero. + if width != 0 && height != 0 { + dim = kurbo::Size::new(width as f64, height as f64); + } + wl_surface.update_dimensions(dim.width as u32, dim.height as u32); + } + xdg_toplevel::Event::Close => { + tracing::info!("xdg close event {:?}", event); + wl_surface.inner.handler.borrow_mut().request_close(); + } + _ => tracing::info!("unimplemented event {:?}", event), + } + }); + + zxdg_toplevel_decoration_v1.quick_assign(move |_zxdg_toplevel_decoration_v1, event, _| { + match event { + _ => tracing::info!("toplevel decoration unimplemented {:?}", event), + } + }); + + let inner = Inner { + wl_surface, + xdg_toplevel, + xdg_surface, + zxdg_toplevel_decoration_v1, + }; + + inner + .zxdg_toplevel_decoration_v1 + .set_mode(toplevel_decorations::Mode::ServerSide); + if let Some(size) = min_size { + inner + .xdg_toplevel + .set_min_size(size.width as i32, size.height as i32); + } + + let handle = Self { + inner: std::sync::Arc::new(inner), + }; + + handle.commit(); + handle + } + + pub(crate) fn with_handler T>( + &self, + f: F, + ) -> Option { + std::sync::Arc::::from(self).with_handler(f) + } + + pub(crate) fn commit(&self) { + self.inner.wl_surface.commit(); + } +} + +impl Decor for Surface { + fn inner_set_title(&self, title: String) { + self.inner.xdg_toplevel.set_title(title); + } +} + +impl From for u32 { + fn from(s: Surface) -> u32 { + u32::from(s.inner.wl_surface.clone()) + } +} + +impl From<&Surface> for u32 { + fn from(s: &Surface) -> u32 { + u32::from(s.inner.wl_surface.clone()) + } +} + +impl From<&Surface> for std::sync::Arc { + fn from(s: &Surface) -> std::sync::Arc { + std::sync::Arc::::from(s.inner.wl_surface.clone()) + } +} + +impl From for std::sync::Arc { + fn from(s: Surface) -> std::sync::Arc { + std::sync::Arc::::from(s.inner.wl_surface.clone()) + } +} + +impl From for Box { + fn from(s: Surface) -> Box { + Box::new(s.inner.wl_surface.clone()) as Box + } +} + +impl From for Box { + fn from(s: Surface) -> Box { + Box::new(s.clone()) as Box + } +} diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index bb0fac70a5..6482b26450 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -13,205 +13,157 @@ #![allow(clippy::single_match)] -use std::{ - any::Any, - cell::{Cell, RefCell}, - collections::{HashSet, VecDeque}, - rc::{Rc, Weak as WeakRc}, - time::{Duration, Instant}, -}; -use wayland_client::{ - self as wl, - protocol::{ - wl_callback, - wl_pointer::WlPointer, - wl_surface::{self, WlSurface}, - }, -}; -use wayland_cursor::CursorImageBuffer; -use wayland_protocols::{ - unstable::xdg_decoration::v1::client::zxdg_toplevel_decoration_v1::{ - Event as ZxdgToplevelDecorationV1Event, Mode as DecorationMode, ZxdgToplevelDecorationV1, - }, - xdg_shell::client::{ - xdg_surface::{Event as XdgSurfaceEvent, XdgSurface}, - xdg_toplevel::{Event as XdgTopLevelEvent, XdgToplevel}, - }, -}; +use tracing; use super::{ - application::{Application, ApplicationData}, - buffer::{Buffers, RawRect, RawSize}, + application::{Application, ApplicationData, Timer}, + error, menu::Menu, - pointer::{MouseEvtKind, Pointer}, - Changed, NUM_FRAMES, PIXEL_WIDTH, + surfaces, }; + use crate::{ - backend::shared::Timer, dialog::FileDialogOptions, error::Error as ShellError, kurbo::{Insets, Point, Rect, Size}, mouse::{Cursor, CursorDesc}, - piet::{Piet, PietText, RenderContext}, - region::Region, + piet::PietText, scale::Scale, text::Event, - window::{self, FileDialogToken, IdleToken, TimerToken, WinHandler, WindowLevel}, + window::{self, FileDialogToken, TimerToken, WinHandler, WindowLevel}, TextFieldToken, }; -//TODO we flash the old window size before adjusting to the new size. This seems to leave some -//artifact on another monitor if the image would have spread across. The todo is investigate if we -//can avoid this. I think it might be something to do with telling the compositor about our new -//geometry? -// -// I think this bit of debug output might be useful (from GTK, which doesn't suffer from this -// problem). UPDATE I'm using the unstable window decoration protocol. GTK doesn't use this, since -// there is some arcane thing to do with `set_opaque_region` to make it backwards compatible. Let's -// ignore this for now. -//[2566306.298] xdg_toplevel@32.configure(580, 450, array) -//[2566306.344] xdg_surface@22.configure(74094) -//[2566306.359] -> wl_buffer@36.destroy() -//[2566306.366] -> wl_shm_pool@37.destroy() -//[2566306.728] -> wl_surface@26.set_buffer_scale(2) -//[2566306.752] -> xdg_surface@22.ack_configure(74094) -//[2566307.207] -> wl_shm@4.create_pool(new id wl_shm_pool@40, fd 31, 4176000) -//[2566307.229] -> wl_shm_pool@40.create_buffer(new id wl_buffer@41, 0, 1160, 900, 4640, 0) -//[2566315.088] -> wl_surface@26.attach(wl_buffer@41, 0, 0) -//[2566315.111] -> wl_surface@26.set_buffer_scale(2) -//[2566315.116] -> wl_surface@26.damage(0, 0, 580, 450) -//[2566315.137] -> xdg_toplevel@32.set_min_size(400, 427) -//[2566315.153] -> xdg_toplevel@32.set_max_size(0, 0) -//[2566315.160] -> xdg_surface@22.set_window_geometry(0, 0, 580, 450) -//[2566315.170] -> wl_compositor@6.create_region(new id wl_region@42) -//[2566315.176] -> wl_region@42.add(0, 0, 580, 450) -//[2566315.190] -> wl_surface@26.set_opaque_region(wl_region@42) -//[2566315.195] -> wl_region@42.destroy() -//[2566315.237] -> wl_surface@26.frame(new id wl_callback@43) -//[2566315.256] -> wl_surface@26.commit() - -// In cairo and Wayland, alpha is pre-multiplied. Yay. - -#[derive(Default, Clone)] -pub struct WindowHandle { - // holding a weak reference to the window is copied from the windows backend. - pub(crate) data: WeakRc, +pub use surfaces::idle::Handle as IdleHandle; + +// holds references to the various components for a window implementation. +struct Inner { + pub(super) decor: Box, + pub(super) surface: Box, + pub(super) appdata: std::sync::Weak, } -impl PartialEq for WindowHandle { - fn eq(&self, other: &Self) -> bool { - self.data.ptr_eq(&other.data) - } +#[derive(Clone)] +pub struct WindowHandle { + inner: std::sync::Arc, } impl WindowHandle { - pub fn show(&self) {} + pub(super) fn new( + decor: impl Into>, + surface: impl Into>, + appdata: impl Into>, + ) -> Self { + Self { + inner: std::sync::Arc::new(Inner { + decor: decor.into(), + surface: surface.into(), + appdata: appdata.into(), + }), + } + } + + pub fn show(&self) { + tracing::info!("show initiated"); + } pub fn resizable(&self, resizable: bool) { - //todo!() + tracing::info!("resizable initiated {:?}", resizable); + todo!() } - pub fn show_titlebar(&self, show_titlebar: bool) { - //todo!() + pub fn show_titlebar(&self, _show_titlebar: bool) { + tracing::info!("show_titlebar initiated"); + todo!() } - pub fn set_position(&self, position: Point) { - //todo!() + pub fn set_position(&self, _position: Point) { + tracing::info!("set_position initiated"); + todo!() } pub fn get_position(&self) -> Point { - todo!() + tracing::info!("get_position initiated"); + Point::ZERO } pub fn content_insets(&self) -> Insets { - // TODO Insets::from(0.) } - pub fn set_level(&self, level: WindowLevel) { - tracing::warn!("level is unsupported on wayland"); + pub fn set_level(&self, _level: WindowLevel) { + log::warn!("level is unsupported on wayland"); } pub fn set_size(&self, size: Size) { - tracing::warn!("setting the size dynamically is unsupported on wayland"); + self.inner.surface.set_size(size); } pub fn get_size(&self) -> Size { - if let Some(data) = self.data.upgrade() { - // size in pixels, so we must apply scale - // TODO check the logic here. - let logical_size = data.logical_size.get(); - let scale = data.scale.get() as f64; - Size::new( - logical_size.width as f64 * scale, - logical_size.height as f64 * scale, - ) - } else { - // TODO panic? - Size::ZERO - } + return self.inner.surface.get_size(); } - pub fn set_window_state(&mut self, size_state: window::WindowState) { - //todo!() + pub fn set_window_state(&mut self, _current_state: window::WindowState) { + tracing::warn!("unimplemented set_window_state initiated"); + todo!(); } pub fn get_window_state(&self) -> window::WindowState { - todo!() + tracing::warn!("unimplemented get_window_state initiated"); + window::WindowState::Maximized } pub fn handle_titlebar(&self, _val: bool) { - todo!() + tracing::warn!("unimplemented handle_titlebar initiated"); + todo!(); } /// Close the window. pub fn close(&self) { - if let Some(data) = self.data.upgrade() { - // TODO destroy resources - if let Some(app_data) = data.app_data.upgrade() { - app_data.shutdown.set(true); - } + if let Some(appdata) = self.inner.appdata.upgrade() { + tracing::trace!( + "closing window initiated {:?}", + appdata.active_surface_id.borrow() + ); + appdata + .handles + .borrow_mut() + .remove(&self.inner.surface.wayland_surface_id()); + appdata.active_surface_id.borrow_mut().pop_front(); + self.inner.surface.release(); + tracing::trace!( + "closing window completed {:?}", + appdata.active_surface_id.borrow() + ); } } /// Bring this window to the front of the window stack and give it focus. pub fn bring_to_front_and_focus(&self) { - //todo!() + tracing::warn!("unimplemented bring_to_front_and_focus initiated"); + todo!() } /// Request a new paint, but without invalidating anything. pub fn request_anim_frame(&self) { - if let Some(data) = self.data.upgrade() { - if !data.anim_frame_requested.get() { - let cb = data.wl_surface.frame(); - let handle = self.clone(); - cb.quick_assign(with_cloned!(data; move |_, event, _| match event { - wl_callback::Event::Done { callback_data } => { - data.anim_frame_requested.set(false); - data.buffers.request_paint(); - } - _ => panic!("done is the only event"), - })); - data.anim_frame_requested.set(true); - } - } + tracing::trace!("request_anim_frame initiated"); + self.inner.surface.request_anim_frame(); + tracing::trace!("request_anim_frame completed"); } /// Request invalidation of the entire window contents. pub fn invalidate(&self) { - // This is one of 2 methods the user can use to schedule a repaint, the other is - // `invalidate_rect`. - if let Some(data) = self.data.upgrade() { - data.invalidate() - } + tracing::trace!("invalidate initiated"); + self.inner.surface.invalidate(); + tracing::trace!("invalidate completed"); } /// Request invalidation of one rectangle, which is given in display points relative to the /// drawing area. pub fn invalidate_rect(&self, rect: Rect) { - if let Some(data) = self.data.upgrade() { - data.invalidate_rect(rect) - } + tracing::trace!("invalidate_rect initiated"); + self.inner.surface.invalidate_rect(rect); + tracing::trace!("invalidate_rect completed"); } pub fn text(&self) -> PietText { @@ -223,402 +175,141 @@ impl WindowHandle { } pub fn remove_text_field(&self, token: TextFieldToken) { - if let Some(data) = self.data.upgrade() { - if data.active_text_field.get() == Some(token) { - data.active_text_field.set(None) - } - } + tracing::trace!("remove_text_field initiated"); + self.inner.surface.remove_text_field(token); + tracing::trace!("remove_text_field completed"); } pub fn set_focused_text_field(&self, active_field: Option) { - if let Some(data) = self.data.upgrade() { - data.active_text_field.set(active_field); - } + tracing::trace!("set_focused_text_field initiated"); + self.inner.surface.set_focused_text_field(active_field); + tracing::trace!("set_focused_text_field completed"); } pub fn update_text_field(&self, _token: TextFieldToken, _update: Event) { // noop until we get a real text input implementation } - pub fn request_timer(&self, deadline: Instant) -> TimerToken { - if let Some(data) = self.data.upgrade() { - if let Some(app_data) = data.app_data.upgrade() { - //println!("Timer requested"); - let now = instant::Instant::now(); - let mut timers = app_data.timers.borrow_mut(); - let sooner = timers - .peek() - .map(|timer| deadline < timer.deadline()) - .unwrap_or(true); - let timer = Timer::new(deadline, data.id()); - timers.push(timer); - // It is possible that the deadline has passed since it was set. - // TODO replace `Duration::new(0, 0)` with `Duration::ZERO` when it is stable. - let timeout = if deadline < now { - Duration::new(0, 0) - } else { - deadline - now - }; - if sooner { - app_data.timer_handle.cancel_all_timeouts(); - app_data.timer_handle.add_timeout(timeout, timer.token()); - } - return timer.token(); - } + pub fn request_timer(&self, deadline: std::time::Instant) -> TimerToken { + tracing::trace!("request_timer initiated"); + let appdata = match self.inner.appdata.upgrade() { + Some(d) => d, + None => panic!("requested timer on a window that was destroyed"), + }; + + let sid = self.inner.surface.wayland_surface_id(); + + let now = instant::Instant::now(); + let mut timers = appdata.timers.borrow_mut(); + let sooner = timers + .peek() + .map(|timer| deadline < timer.deadline()) + .unwrap_or(true); + + let timer = Timer::new(sid, deadline); + timers.push(timer); + + // It is possible that the deadline has passed since it was set. + let timeout = if deadline < now { + std::time::Duration::ZERO + } else { + deadline - now + }; + + if sooner { + appdata.timer_handle.cancel_all_timeouts(); + appdata.timer_handle.add_timeout(timeout, timer.token()); } - panic!("requested timer on a window that was destroyed"); + + return timer.token(); } pub fn set_cursor(&mut self, cursor: &Cursor) { - if let Some(data) = self.data.upgrade() { - let mut _pointer = data.pointer.borrow_mut(); - let pointer = _pointer.as_mut().unwrap(); - // Setting a new cursor involves communicating with the server, so don't do it if we - // don't have to. - if matches!(&pointer.current_cursor, Some(c) if c == cursor) { - return; - } - pointer.current_cursor = Some(cursor.clone()); + tracing::trace!("set_cursor initiated"); + if let Some(appdata) = self.inner.appdata.upgrade() { + appdata.set_cursor(cursor); } + tracing::trace!("set_cursor completed"); } - pub fn make_cursor(&self, desc: &CursorDesc) -> Option { - todo!() + pub fn make_cursor(&self, _desc: &CursorDesc) -> Option { + tracing::warn!("unimplemented make_cursor initiated"); + None } - pub fn open_file(&mut self, options: FileDialogOptions) -> Option { + pub fn open_file(&mut self, _options: FileDialogOptions) -> Option { + tracing::info!("open_file initiated"); todo!() } - pub fn save_as(&mut self, options: FileDialogOptions) -> Option { + pub fn save_as(&mut self, _options: FileDialogOptions) -> Option { + tracing::info!("save_as initiated"); todo!() } /// Get a handle that can be used to schedule an idle task. pub fn get_idle_handle(&self) -> Option { - None + tracing::trace!("get_idle_handle initiated"); + Some(self.inner.surface.get_idle_handle()) } /// Get the `Scale` of the window. pub fn get_scale(&self) -> Result { - if let Some(data) = self.data.upgrade() { - let scale = data.scale.get() as f64; - Ok(Scale::new(scale, scale)) - } else { - Err(ShellError::WindowDropped) - } + tracing::info!("get_scale initiated"); + Ok(self.inner.surface.get_scale()) } - pub fn set_menu(&self, menu: Menu) { + pub fn set_menu(&self, _menu: Menu) { + tracing::info!("set_menu initiated"); todo!() } - pub fn show_context_menu(&self, menu: Menu, _pos: Point) { + pub fn show_context_menu(&self, _menu: Menu, _pos: Point) { + tracing::info!("show_context_menu initiated"); todo!() } pub fn set_title(&self, title: impl Into) { - if let Some(data) = self.data.upgrade() { - data.xdg_toplevel.set_title(title.into()); - } + self.inner.decor.set_title(title); } -} - -pub struct WindowData { - pub(crate) app_data: WeakRc, - pub(crate) wl_surface: wl::Main, - pub(crate) xdg_surface: wl::Main, - pub(crate) xdg_toplevel: wl::Main, - pub(crate) zxdg_toplevel_decoration_v1: wl::Main, - /// The outputs that our surface is present on (we should get the first enter event early). - pub(crate) outputs: RefCell>, - /// Buffers in our shared memory. - // Buffers sometimes need to move references to themselves into closures, so must be behind a - // reference counter. - pub(crate) buffers: Rc>, - /// The logical size of the next frame. - pub(crate) logical_size: Cell, - /// The scale we are rendering to (defaults to 1) - pub(crate) scale: Cell, - /// Whether we've currently got keyboard focus. - pub(crate) keyboard_focus: Cell, - /// If we've currently got pointer focus, this object tracks pointer state. - pub(crate) pointer: RefCell>, - /// Whether we have requested an animation frame. This stops us requesting more than 1. - anim_frame_requested: Cell, - /// Contains the callbacks from user code. - pub(crate) handler: RefCell>, - /// Rects of the image that are damaged and need repainting in the logical coordinate space. - /// - /// This lives outside `data` because they can be borrowed concurrently without re-entrancy. - damaged_region: RefCell, - /// Tasks that were requested in user code. - /// - /// These call back into user code, and so should only be run after all user code has returned, - /// to avoid possible re-entrancy. - deferred_tasks: RefCell>, - pub(crate) active_text_field: Cell>, -} - -pub enum DeferredTask { - Paint, -} -impl WindowData { - /// Sets the physical size. - /// - /// Up to the caller to make sure `buffers.size`, `logical_size` and `scale` are consistent. - fn set_physical_size(&self, new_size: RawSize) { - self.buffers.set_size(new_size) - } - - /// Assert that the physical size = logical size * scale - #[allow(unused)] - fn assert_size(&self) { - assert_eq!( - self.buffers.size(), - RawSize::from(self.logical_size.get()).scale(self.scale.get()), - "phy {:?} == logic {:?} * {}", - self.buffers.size(), - self.logical_size.get(), - self.scale.get() - ); + pub(super) fn run_idle(&self) { + self.inner.surface.run_idle(); } - /// Recompute the scale to use (the maximum of all the scales for the different outputs this - /// screen is drawn to). - fn recompute_scale(&self) -> i32 { - if let Some(app_data) = self.app_data.upgrade() { - let mut scale = 0; - for id in self.outputs.borrow().iter() { - if let Some(output) = app_data.outputs.borrow().get(&id) { - scale = scale.max(output.scale); - } else { - tracing::warn!( - "we still have a reference to an output that's gone away. The output had id {}", - id - ); - } - } - if scale == 0 { - tracing::warn!("wayland never reported which output we are drawing to"); - 1 - } else { - scale - } - } else { - panic!("should never recompute scale of window that has been dropped"); - } + pub(super) fn popup<'a>(&self, s: &'a surfaces::popup::Surface) -> Result<(), error::Error> { + self.inner.surface.popup(s) } - fn set_cursor(&self, buf: &CursorImageBuffer) { - let (hotspot_x, hotspot_y) = buf.hotspot(); - let _pointer = self.pointer.borrow(); - let pointer = _pointer.as_ref().unwrap(); - pointer.wl_pointer.set_cursor( - pointer.enter_serial, - Some(&pointer.cursor_surface), - hotspot_x as i32, - hotspot_y as i32, - ); - pointer.cursor_surface.attach(Some(&*buf), 0, 0); - pointer - .cursor_surface - .damage_buffer(0, 0, i32::MAX, i32::MAX); - pointer.cursor_surface.commit(); - } - - /// Get the wayland object id for the `wl_surface` associated with this window. - /// - /// We use this as the key for the window. - pub(crate) fn id(&self) -> u32 { - wl::Proxy::from(self.wl_surface.detach()).id() - } - - /// Sets the scale - /// - /// Up to the caller to make sure `physical_size`, `logical_size` and `scale` are consistent. - fn set_scale(&self, new_scale: i32) -> Changed { - if self.scale.get() != new_scale { - self.scale.set(new_scale); - // (re-entrancy) Report change to client - let druid_scale = Scale::new(new_scale as f64, new_scale as f64); - self.handler.borrow_mut().scale(druid_scale); - Changed::Changed - } else { - Changed::Unchanged - } - } - - /// Paint the next frame. - /// - /// The buffers object is responsible for calling this function after we called - /// `request_paint`. - /// - /// - `buf` is what we draw the frame into - /// - `size` is the physical size in pixels we are drawing. - /// - `force` means draw the whole frame, even if it wasn't all invalidated. - pub(crate) fn paint(&self, size: RawSize, buf: &mut [u8], force: bool) { - self.handler.borrow_mut().prepare_paint(); - //tracing::trace!("Paint call"); - //self.data.borrow().assert_size(); - if force { - self.invalidate(); - } else { - let damaged_region = self.damaged_region.borrow_mut(); - for rect in damaged_region.rects() { - // Convert it to physical coordinate space. - let rect = RawRect::from(*rect).scale(self.scale.get()); - self.wl_surface.damage_buffer( - rect.x0, - rect.y0, - rect.x1 - rect.x0, - rect.y1 - rect.y0, - ); - } - if damaged_region.is_empty() { - // Nothing to draw, so we can finish here! - return; - } - } - - // create cairo context (safety: we must drop the buffer before we commit the frame) - // TODO: Cairo is native-endian while wayland is little-endian, which is a pain. Currently - // will give incorrect results on big-endian architectures. - // TODO cairo might use a different stride than the width of the format. Since we always - // use argb32 which is 32-bit aligned we should be ok, but strictly speaking cairo might - // choose a wider stride and read past the end of our buffer (UB). Fixing this would - // require a fair bit of effort. - unsafe { - let physical_size = self.buffers.size(); - // We're going to lie about the lifetime of our buffer here. This is (I think) ok, - // becuase the Rust wrapper for cairo is overly pessimistic: the buffer only has to - // last as long as the `ImageSurface` (which we know this buffer will). - let buf: &'static mut [u8] = &mut *(buf as *mut _); - let cairo_surface = cairo::ImageSurface::create_for_data( - buf, - cairo::Format::ARgb32, - physical_size.width, - physical_size.height, - physical_size.width * PIXEL_WIDTH, - ) - .unwrap(); - // we can't do much if we can't create cairo context - let ctx = cairo::Context::new(&cairo_surface).unwrap(); - // Apply scaling - let scale = self.scale.get() as f64; - ctx.scale(scale, scale); - // TODO we don't clip cairo stuff not in the damaged region. This might be a perf win? - let mut piet = Piet::new(&ctx); - // Actually paint the new frame - let region = self.damaged_region.borrow(); - // The handler must not be already borrowed. This may mean deferring this call. - self.handler.borrow_mut().paint(&mut piet, &*region); - } - // reset damage ready for next frame. - self.damaged_region.borrow_mut().clear(); - - self.buffers.attach(); - self.wl_surface.commit(); - } - - /// Request invalidation of the entire window contents. - fn invalidate(&self) { - // This is one of 2 methods the user can use to schedule a repaint, the other is - // `invalidate_rect`. - let window_rect = self.logical_size.get().to_rect(); - self.damaged_region.borrow_mut().add_rect(window_rect); - self.schedule_deferred_task(DeferredTask::Paint); - } - - /// Request invalidation of one rectangle, which is given in display points relative to the - /// drawing area. - fn invalidate_rect(&self, rect: Rect) { - // Quick check to see if we can skip the rect entirely (if it is outside the visible - // screen). - if rect.intersect(self.logical_size.get().to_rect()).is_empty() { - return; - } - /* this would be useful for debugging over-keen invalidation by clients. - println!( - "{:?} {:?}", - rect, - self.with_data(|data| data.logical_size.to_rect()) - ); - */ - self.damaged_region.borrow_mut().add_rect(rect); - self.schedule_deferred_task(DeferredTask::Paint); - } - - /// If there are any pending pointer events, get the next one. - pub(crate) fn pop_pointer_event(&self) -> Option { - self.pointer.borrow_mut().as_mut()?.next() - } - - /// Initialize the pointer struct, and return the surface used for the cursor. - pub(crate) fn init_pointer(&self, pointer: WlPointer, serial: u32) { - if let Some(app_data) = self.app_data.upgrade() { - let cursor = app_data.wl_compositor.create_surface(); - // TODO for now we've hard-coded 2x scale. This should be dynamic. - //cursor.set_buffer_scale(2); - // ignore all events - cursor.quick_assign(|_, _, _| ()); - *self.pointer.borrow_mut() = Some(Pointer::new(cursor, pointer, serial)); - } - } - - fn set_system_cursor(&mut self, cursor: Cursor) { - if let Some(app_data) = self.app_data.upgrade() { - #[allow(deprecated)] - let cursor = match cursor { - // TODO check these are all correct - Cursor::Arrow => "left_ptr", - Cursor::IBeam => "xterm", - Cursor::Crosshair => "cross", - Cursor::OpenHand => "openhand", - Cursor::NotAllowed => "X_cursor", - Cursor::ResizeLeftRight => "row-resize", - Cursor::ResizeUpDown => "col-resize", - // TODO custom cursors - _ => "left_ptr", - }; - let mut theme = app_data.cursor_theme.borrow_mut(); - let cursor = theme.get_cursor(cursor).unwrap(); - // Just use the first image, people using animated cursors have already made bad life - // choices and shouldn't expect it to work. - let buf = &cursor[cursor.frame_and_duration(0).frame_index]; - self.set_cursor(buf); - } - } - - // Deferred tasks - - pub fn schedule_deferred_task(&self, task: DeferredTask) { - self.deferred_tasks.borrow_mut().push_back(task); - } - - pub fn run_deferred_tasks(&self) { - while let Some(task) = self.next_deferred_task() { - self.run_deferred_task(task); - } + pub(super) fn data(&self) -> Option> { + self.inner.surface.data() } +} - fn next_deferred_task(&self) -> Option { - self.deferred_tasks.borrow_mut().pop_front() +impl std::cmp::PartialEq for WindowHandle { + fn eq(&self, _rhs: &Self) -> bool { + todo!() } +} - fn run_deferred_task(&self, task: DeferredTask) { - match task { - DeferredTask::Paint => { - self.buffers.request_paint(); - } +impl std::default::Default for WindowHandle { + fn default() -> WindowHandle { + WindowHandle { + inner: std::sync::Arc::new(Inner { + decor: Box::new(surfaces::surface::Dead::default()), + surface: Box::new(surfaces::surface::Dead::default()), + appdata: std::sync::Weak::new(), + }), } } } +#[derive(Clone, PartialEq)] +pub struct CustomCursor; + /// Builder abstraction for creating new windows pub(crate) struct WindowBuilder { - app_data: WeakRc, + app_data: std::sync::Weak, handler: Option>, title: String, menu: Option, @@ -632,20 +323,14 @@ pub(crate) struct WindowBuilder { show_titlebar: bool, } -#[derive(Clone)] -pub struct IdleHandle; - -#[derive(Clone, PartialEq)] -pub struct CustomCursor; - impl WindowBuilder { pub fn new(app: Application) -> WindowBuilder { WindowBuilder { - app_data: Rc::downgrade(&app.data), + app_data: std::sync::Arc::downgrade(&app.data), handler: None, title: String::new(), menu: None, - size: Size::new(500.0, 400.0), + size: Size::new(0.0, 0.0), position: None, level: None, state: None, @@ -675,8 +360,10 @@ impl WindowBuilder { self.show_titlebar = show_titlebar; } - pub fn set_transparent(&mut self, transparent: bool) { - todo!() + pub fn set_transparent(&mut self, _transparent: bool) { + tracing::warn!( + "set_transparent unimplemented for wayland, it allows transparency by default" + ); } pub fn set_position(&mut self, position: Point) { @@ -701,176 +388,204 @@ impl WindowBuilder { pub fn build(self) -> Result { if matches!(self.menu, Some(_)) { - //panic!("menu unsupported"); + tracing::error!("menus unimplemented"); } - let app_data = match self.app_data.upgrade() { - Some(app_data) => app_data, + + let appdata = match self.app_data.upgrade() { + Some(d) => d, None => return Err(ShellError::ApplicationDropped), }; let handler = self.handler.expect("must set a window handler"); + // compute the initial window size. + let initial_size = appdata.initial_window_size(self.size); - let wl_surface = app_data.wl_compositor.create_surface(); - - let xdg_surface = app_data.xdg_base.get_xdg_surface(&wl_surface); - let xdg_toplevel = xdg_surface.get_toplevel(); - let zxdg_toplevel_decoration_v1 = app_data - .zxdg_decoration_manager_v1 - .get_toplevel_decoration(&xdg_toplevel); - zxdg_toplevel_decoration_v1.set_mode(DecorationMode::ServerSide); - xdg_toplevel.set_title(self.title); - if let Some(size) = self.min_size { - // for sanity - assert!(size.width >= 0. && size.height >= 0.); - xdg_toplevel.set_min_size(size.width as i32, size.height as i32); - } + let surface = + surfaces::toplevel::Surface::new(appdata.clone(), handler, initial_size, self.min_size); - let data = Rc::new(WindowData { - app_data: Rc::downgrade(&app_data), - wl_surface, - xdg_surface, - xdg_toplevel, - zxdg_toplevel_decoration_v1, - outputs: RefCell::new(HashSet::new()), - buffers: Buffers::new(app_data.wl_shm.clone(), self.size.into()), - logical_size: Cell::new(self.size), - scale: Cell::new(1), - keyboard_focus: Cell::new(false), - pointer: RefCell::new(None), - anim_frame_requested: Cell::new(false), - handler: RefCell::new(handler), - damaged_region: RefCell::new(Region::EMPTY), - deferred_tasks: RefCell::new(VecDeque::new()), - active_text_field: Cell::new(None), - }); + (&surface as &dyn surfaces::Decor).set_title(self.title); - let weak_data = Rc::downgrade(&data); - // Hook up the child -> parent weak pointer. - unsafe { - // Safety: No Rust references exist during the life of this reference (satisfies many - // read-only xor 1 mutable references). - let buffers: &mut Buffers<{ NUM_FRAMES as usize }> = - &mut *(Rc::as_ptr(&data.buffers) as *mut _); - buffers.set_window_data(weak_data); - } + let sid = match std::num::NonZeroU32::new(surfaces::id(&surface)) { + Some(u) => u, + None => panic!("surface id must be non-zero"), + }; - // Insert a reference to us in the application. This is the main strong reference to the - // app. - if let Some(old_data) = app_data - .surfaces + let handle = WindowHandle::new(surface.clone(), surface.clone(), self.app_data.clone()); + + if let Some(_) = appdata + .handles .borrow_mut() - .insert(data.id(), data.clone()) + .insert(sid.get(), handle.clone()) { panic!("wayland should use unique object IDs"); } + appdata.active_surface_id.borrow_mut().push_front(sid); - // event handlers - data.xdg_toplevel.quick_assign(with_cloned!( - data; - move |xdg_toplevel, event, _| match event { - XdgTopLevelEvent::Configure { - width, - height, - states, - } => { - // Only change the size if the passed width/height is non-zero - otherwise we - // choose. - if width != 0 && height != 0 { - // The size here is the logical size - let scale = data.scale.get(); - let raw_logical_size = RawSize { width, height }; - let logical_size = Size::from(raw_logical_size); - if data.logical_size.get() != logical_size { - data.logical_size.set(logical_size); - data.buffers.set_size(raw_logical_size.scale(scale)); - // (re-entrancy) Report change to client - data.handler.borrow_mut().size(logical_size); - } - // Check if the client requested a repaint. - data.run_deferred_tasks(); - } - } - XdgTopLevelEvent::Close => { - data.handler.borrow_mut().request_close(); - } - _ => (), - } - )); - - data.zxdg_toplevel_decoration_v1.quick_assign(with_cloned!( - data; - move |zxdg_toplevel_decoration_v1, event, _| match event { - ZxdgToplevelDecorationV1Event::Configure { mode } => { - // do nothing for now - tracing::debug!("{:?}", mode); - } - _ => (), + surface.with_handler({ + let handle = handle.clone(); + move |winhandle| winhandle.connect(&handle.into()) + }); + + Ok(handle) + } +} + +pub mod layershell { + use crate::error::Error as ShellError; + use crate::window::WinHandler; + + use super::WindowHandle; + use crate::backend::wayland::application::{Application, ApplicationData}; + use crate::backend::wayland::error::Error; + use crate::backend::wayland::surfaces; + + /// Builder abstraction for creating new windows + pub(crate) struct Builder { + appdata: std::sync::Weak, + winhandle: Option>, + pub(crate) config: surfaces::layershell::Config, + } + + impl Builder { + pub fn new(app: Application) -> Builder { + Builder { + appdata: std::sync::Arc::downgrade(&app.data), + config: surfaces::layershell::Config::default(), + winhandle: None, } - )); + } - data.xdg_surface.quick_assign( - with_cloned!(data; move |xdg_surface, event, _| match event { - XdgSurfaceEvent::Configure { serial } => { - xdg_surface.ack_configure(serial); - data.buffers.request_paint(); // will also rebuild buffers if needed. - } - _ => (), - }), - ); + pub fn set_handler(&mut self, handler: Box) { + self.winhandle = Some(handler); + } - data.wl_surface - .quick_assign(with_cloned!(data; move |_, event, _| { - match event { - wl_surface::Event::Enter { output } => { - data - .outputs - .borrow_mut() - .insert(wl::Proxy::from(output).id()); - } - wl_surface::Event::Leave { output } => { - data - .outputs - .borrow_mut() - .remove(&wl::Proxy::from(output).id()); - } - _ => (), - } - let new_scale = data.recompute_scale(); - if data.set_scale(new_scale).is_changed() { - data.wl_surface.set_buffer_scale(new_scale); - // We also need to change the physical size to match the new scale - data.set_physical_size(RawSize::from(data.logical_size.get()).scale(new_scale)); - // always repaint, because the scale changed. - data.schedule_deferred_task(DeferredTask::Paint); + pub fn build(self) -> Result { + let appdata = match self.appdata.upgrade() { + Some(d) => d, + None => return Err(ShellError::ApplicationDropped), + }; + let winhandle = match self.winhandle { + Some(winhandle) => winhandle, + None => { + return Err(ShellError::Platform(Error::string( + "window handler required", + ))) } - })); + }; - // Notify wayland that we've finished setting up. - data.wl_surface.commit(); + // compute the initial window size. + let mut updated = self.config.clone(); + updated.initial_size = appdata.initial_window_size(self.config.initial_size); - let handle = WindowHandle { - data: Rc::downgrade(&data), - }; - data.handler.borrow_mut().connect(&handle.clone().into()); + let surface = surfaces::layershell::Surface::new(appdata.clone(), winhandle, updated); - Ok(handle) + let sid = match std::num::NonZeroU32::new(surfaces::id(&surface)) { + Some(u) => u, + None => panic!("surface id must be non-zero"), + }; + + let handle = WindowHandle::new( + surfaces::surface::Dead::default(), + surface.clone(), + self.appdata.clone(), + ); + + if let Some(_) = appdata + .handles + .borrow_mut() + .insert(sid.get(), handle.clone()) + { + panic!("wayland should use unique object IDs"); + } + appdata.active_surface_id.borrow_mut().push_front(sid); + + surface.with_handler({ + let handle = handle.clone(); + move |winhandle| winhandle.connect(&handle.into()) + }); + + Ok(handle) + } } } -impl IdleHandle { - /// Add an idle handler, which is called (once) when the message loop - /// is empty. The idle handler will be run from the main UI thread, and - /// won't be scheduled if the associated view has been dropped. - /// - /// Note: the name "idle" suggests that it will be scheduled with a lower - /// priority than other UI events, but that's not necessarily the case. - pub fn add_idle_callback(&self, callback: F) - where - F: FnOnce(&mut dyn WinHandler) + Send + 'static, - { - todo!() +pub mod popup { + use crate::error::Error as ShellError; + use crate::window::WinHandler; + + use super::WindowHandle; + use crate::backend::wayland::application::{Application, ApplicationData}; + use crate::backend::wayland::error::Error; + use crate::backend::wayland::surfaces; + + /// Builder abstraction for creating new windows + pub(crate) struct Builder { + appdata: std::sync::Weak, + winhandle: Option>, + pub(crate) config: surfaces::popup::Config, } - pub fn add_idle_token(&self, token: IdleToken) { - todo!() + impl Builder { + pub fn new(app: Application) -> Self { + Self { + appdata: std::sync::Arc::downgrade(&app.data), + config: surfaces::popup::Config::default(), + winhandle: None, + } + } + + pub fn set_handler(&mut self, handler: Box) { + self.winhandle = Some(handler); + } + + pub fn build(self) -> Result { + let appdata = match self.appdata.upgrade() { + Some(d) => d, + None => return Err(ShellError::ApplicationDropped), + }; + let winhandle = match self.winhandle { + Some(winhandle) => winhandle, + None => { + return Err(ShellError::Platform(Error::string( + "window handler required", + ))) + } + }; + + // compute the initial window size. + let updated = self.config.clone(); + + let surface = surfaces::popup::Surface::new(appdata.clone(), winhandle, updated, None); + + if let Err(cause) = appdata.popup(&surface) { + return Err(ShellError::Platform(cause)); + } + + let sid = match std::num::NonZeroU32::new(surfaces::id(&surface)) { + Some(u) => u, + None => panic!("surface id must be non-zero"), + }; + + let handle = WindowHandle::new( + surfaces::surface::Dead::default(), + surface.clone(), + self.appdata.clone(), + ); + + if let Some(_) = appdata + .handles + .borrow_mut() + .insert(sid.get(), handle.clone()) + { + panic!("wayland should use unique object IDs"); + } + appdata.active_surface_id.borrow_mut().push_front(sid); + + surface.with_handler({ + let handle = handle.clone(); + move |winhandle| winhandle.connect(&handle.into()) + }); + + Ok(handle) + } } } diff --git a/druid-shell/src/backend/wayland/xkb.rs b/druid-shell/src/backend/wayland/xkb.rs index ffb5c31b13..a96cd32948 100644 --- a/druid-shell/src/backend/wayland/xkb.rs +++ b/druid-shell/src/backend/wayland/xkb.rs @@ -8,8 +8,48 @@ use crate::{ }; use keyboard_types::{Code, Key}; use std::{convert::TryFrom, ptr}; +use wayland_client::protocol::wl_keyboard as wlkeyboard; use xkbcommon_sys::*; +struct ModMap(u32, Modifiers); + +impl ModMap { + fn merge(self, m: Modifiers, mods: u32, locked: u32) -> Modifiers { + if self.0 & mods == 0 && self.0 & locked == 0 { + return m; + } + + return m | self.1; + } +} + +const MOD_SHIFT: ModMap = ModMap(1, Modifiers::SHIFT); +const MOD_CAP_LOCK: ModMap = ModMap(2, Modifiers::CAPS_LOCK); +const MOD_CTRL: ModMap = ModMap(4, Modifiers::CONTROL); +const MOD_ALT: ModMap = ModMap(8, Modifiers::ALT); +const MOD_NUM_LOCK: ModMap = ModMap(16, Modifiers::NUM_LOCK); +const MOD_META: ModMap = ModMap(64, Modifiers::META); + +pub fn event_to_mods(event: wlkeyboard::Event) -> Modifiers { + match event { + wlkeyboard::Event::Modifiers { + mods_depressed, + mods_locked, + .. + } => { + let mods = Modifiers::empty(); + let mods = MOD_SHIFT.merge(mods, mods_depressed, mods_locked); + let mods = MOD_CAP_LOCK.merge(mods, mods_depressed, mods_locked); + let mods = MOD_CTRL.merge(mods, mods_depressed, mods_locked); + let mods = MOD_ALT.merge(mods, mods_depressed, mods_locked); + let mods = MOD_NUM_LOCK.merge(mods, mods_depressed, mods_locked); + let mods = MOD_META.merge(mods, mods_depressed, mods_locked); + return mods; + } + _ => return Modifiers::empty(), + } +} + //const MAX_KEY_LEN: usize = 32; /// A global xkb context object. @@ -54,24 +94,6 @@ impl Context { Keymap::new(keymap) } } - - /// Set the log level using `log` levels. - /// - /// Becuase `xkb` has a `critical` error, each rust error maps to 1 above (e.g. error -> - /// critical, warn -> error etc.) - pub fn set_log_level(&self, level: log::Level) { - use log::Level; - let level = match level { - Level::Error => XKB_LOG_LEVEL_CRITICAL, - Level::Warn => XKB_LOG_LEVEL_ERROR, - Level::Info => XKB_LOG_LEVEL_WARNING, - Level::Debug => XKB_LOG_LEVEL_INFO, - Level::Trace => XKB_LOG_LEVEL_DEBUG, - }; - unsafe { - xkb_context_set_log_level(self.inner, level); - } - } } impl Clone for Context { @@ -137,8 +159,7 @@ impl State { Self { inner } } - pub fn key_event(&self, scancode: u32, state: KeyState) -> KeyEvent { - // TODO make sure this adjustment is correct + pub fn key_event(&self, scancode: u32, state: KeyState, mods: Modifiers) -> KeyEvent { let scancode = scancode + 8; let code = u16::try_from(scancode) .map(hardware_keycode_to_code) @@ -147,7 +168,6 @@ impl State { // TODO this is lazy - really should use xkb i.e. augment the get_logical_key method. let location = code_to_location(code); // TODO rest are unimplemented - let mods = Modifiers::empty(); let repeat = false; // TODO we get the information for this from a wayland event let is_composing = false; @@ -228,7 +248,6 @@ impl Drop for State { } /// Map from an xkb_common key code to a key, if possible. -// I couldn't find the commented out keys fn map_key(keysym: u32) -> Key { use Key::*; match keysym { @@ -236,6 +255,7 @@ fn map_key(keysym: u32) -> Key { XKB_KEY_Alt_R => AltGraph, XKB_KEY_Caps_Lock => CapsLock, XKB_KEY_Control_L | XKB_KEY_Control_R => Control, + XKB_KEY_Escape => Escape, XKB_KEY_function => Fn, // FnLock, - can't find in xkb XKB_KEY_Meta_L | XKB_KEY_Meta_R => Meta, @@ -248,6 +268,7 @@ fn map_key(keysym: u32) -> Key { XKB_KEY_Super_L | XKB_KEY_Super_R => Super, XKB_KEY_Return => Enter, XKB_KEY_Tab => Tab, + XKB_KEY_ISO_Left_Tab => Tab, XKB_KEY_Down => ArrowDown, XKB_KEY_Left => ArrowLeft, XKB_KEY_Right => ArrowRight, @@ -274,7 +295,6 @@ fn map_key(keysym: u32) -> Key { Attn, Cancel, ContextMenu, - Escape, Execute, Find, Help, From 88cf58d7851f2ab405fb5c0e01567dcd589b6a87 Mon Sep 17 00:00:00 2001 From: Maan2003 Date: Wed, 18 Aug 2021 10:00:47 +0530 Subject: [PATCH 09/31] wayland: share xkb with x11 backend --- druid-shell/Cargo.toml | 5 +- druid-shell/build.rs | 6 +- druid-shell/src/backend/shared/mod.rs | 1 + .../backend/{x11 => shared/xkb}/keycodes.rs | 0 .../backend/{x11/xkb.rs => shared/xkb/mod.rs} | 38 +- .../{x11 => shared/xkb}/xkbcommon_sys.rs | 0 .../src/backend/wayland/application.rs | 6 +- druid-shell/src/backend/wayland/keyboard.rs | 56 +- druid-shell/src/backend/wayland/keycodes.rs | 29 - druid-shell/src/backend/wayland/mod.rs | 3 +- druid-shell/src/backend/wayland/xkb.rs | 543 ------------------ druid-shell/src/backend/x11/application.rs | 3 +- druid-shell/src/backend/x11/mod.rs | 3 - 13 files changed, 96 insertions(+), 597 deletions(-) rename druid-shell/src/backend/{x11 => shared/xkb}/keycodes.rs (100%) rename druid-shell/src/backend/{x11/xkb.rs => shared/xkb/mod.rs} (87%) rename druid-shell/src/backend/{x11 => shared/xkb}/xkbcommon_sys.rs (100%) delete mode 100644 druid-shell/src/backend/wayland/keycodes.rs delete mode 100644 druid-shell/src/backend/wayland/xkb.rs diff --git a/druid-shell/Cargo.toml b/druid-shell/Cargo.toml index e903030863..e93d9a5511 100755 --- a/druid-shell/Cargo.toml +++ b/druid-shell/Cargo.toml @@ -20,8 +20,8 @@ x11 = ["x11rb", "nix", "cairo-sys-rs", "bindgen", "pkg-config"] # Implement HasRawWindowHandle for WindowHandle raw-win-handle = ["raw-window-handle"] # **WARNING** not ready for the prime time. Many things don't work yet. -wayland = ["wayland-client", "wayland-protocols/client", "wayland-protocols/unstable_protocols", -"nix", "cairo-sys-rs", "rand", "xkbcommon-sys", "calloop", "wayland-cursor", "log", "im"] +wayland = ["wayland-client", "wayland-protocols/client", "wayland-protocols/unstable_protocols", +"nix", "cairo-sys-rs", "rand", "calloop", "wayland-cursor", "log", "im", "bindgen", "pkg-config"] # passing on all the image features. AVIF is not supported because it does not # support decoding, and that's all we use `Image` for. @@ -94,7 +94,6 @@ wayland-client = { version = "0.28.2", optional = true, features = ["dlopen"] } wayland-protocols = { version = "0.28.2", optional = true } wayland-cursor = { version = "0.28.3", optional = true } rand = { version = "0.8.0", optional = true } -xkbcommon-sys = { version = "0.7.4", optional = true } calloop = { version = "0.7.1", optional = true } log = { version = "0.4.14", optional = true } im = { version = "15.0.0", optional = true } diff --git a/druid-shell/build.rs b/druid-shell/build.rs index c35d720e84..2bc8d4b5e2 100644 --- a/druid-shell/build.rs +++ b/druid-shell/build.rs @@ -1,7 +1,7 @@ -#[cfg(not(feature = "x11"))] +#[cfg(not(any(feature = "x11", feature = "wayland")))] fn main() {} -#[cfg(feature = "x11")] +#[cfg(any(feature = "x11", feature = "wayland"))] fn main() { use pkg_config::probe_library; use std::env; @@ -14,6 +14,8 @@ fn main() { } probe_library("xkbcommon").unwrap(); + + #[cfg(feature = "x11")] probe_library("xkbcommon-x11").unwrap(); let bindings = bindgen::Builder::default() diff --git a/druid-shell/src/backend/shared/mod.rs b/druid-shell/src/backend/shared/mod.rs index a1d8dd0bba..568e5205a1 100644 --- a/druid-shell/src/backend/shared/mod.rs +++ b/druid-shell/src/backend/shared/mod.rs @@ -24,5 +24,6 @@ cfg_if::cfg_if! { if #[cfg(all(target_os = "linux", any(feature = "x11", feature = "wayland")))] { mod timer; pub(crate) use timer::*; + pub(crate) mod xkb; } } diff --git a/druid-shell/src/backend/x11/keycodes.rs b/druid-shell/src/backend/shared/xkb/keycodes.rs similarity index 100% rename from druid-shell/src/backend/x11/keycodes.rs rename to druid-shell/src/backend/shared/xkb/keycodes.rs diff --git a/druid-shell/src/backend/x11/xkb.rs b/druid-shell/src/backend/shared/xkb/mod.rs similarity index 87% rename from druid-shell/src/backend/x11/xkb.rs rename to druid-shell/src/backend/shared/xkb/mod.rs index 576d5fd3fa..5bbb499ac0 100644 --- a/druid-shell/src/backend/x11/xkb.rs +++ b/druid-shell/src/backend/shared/xkb/mod.rs @@ -14,19 +14,23 @@ //! A minimal wrapper around Xkb for our use. -use super::keycodes; -use super::xkbcommon_sys::*; +mod keycodes; +mod xkbcommon_sys; use crate::{ backend::shared::{code_to_location, hardware_keycode_to_code}, KeyEvent, KeyState, Modifiers, }; use keyboard_types::{Code, Key}; use std::convert::TryFrom; -use std::os::raw::{c_char, c_int}; +use std::os::raw::c_char; use std::ptr; +use xkbcommon_sys::*; + +#[cfg(feature = "x11")] use x11rb::xcb_ffi::XCBConnection; -pub struct DeviceId(c_int); +#[cfg(feature = "x11")] +pub struct DeviceId(std::os::raw::c_int); /// A global xkb context object. /// @@ -42,6 +46,7 @@ impl Context { unsafe { Self(xkb_context_new(XKB_CONTEXT_NO_FLAGS)) } } + #[cfg(feature = "x11")] pub fn core_keyboard_device_id(&self, conn: &XCBConnection) -> Option { let id = unsafe { xkb_x11_get_core_keyboard_device_id( @@ -55,6 +60,7 @@ impl Context { } } + #[cfg(feature = "x11")] pub fn keymap_from_device(&self, conn: &XCBConnection, device: DeviceId) -> Option { let key_map = unsafe { xkb_x11_keymap_new_from_device( @@ -70,6 +76,30 @@ impl Context { Some(Keymap(key_map)) } + /// Create a keymap from some given data. + /// + /// Uses `xkb_keymap_new_from_buffer` under the hood. + #[cfg(feature = "wayland")] + pub fn keymap_from_slice(&self, buffer: &[u8]) -> Keymap { + // TODO we hope that the keymap doesn't borrow the underlying data. If it does' we need to + // use Rc. We'll find out soon enough if we get a segfault. + // TODO we hope that the keymap inc's the reference count of the context. + assert!( + buffer.iter().copied().any(|byte| byte == 0), + "`keymap_from_slice` expects a null-terminated string" + ); + unsafe { + let keymap = xkb_keymap_new_from_string( + self.0, + buffer.as_ptr() as *const i8, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS, + ); + assert!(!keymap.is_null()); + Keymap(keymap) + } + } + /// Set the log level using `tracing` levels. /// /// Because `xkb` has a `critical` error, each rust error maps to 1 above (e.g. error -> diff --git a/druid-shell/src/backend/x11/xkbcommon_sys.rs b/druid-shell/src/backend/shared/xkb/xkbcommon_sys.rs similarity index 100% rename from druid-shell/src/backend/x11/xkbcommon_sys.rs rename to druid-shell/src/backend/shared/xkb/xkbcommon_sys.rs diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index 4a32a4a0c1..8472aff62b 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -16,10 +16,12 @@ use super::{ clipboard::Clipboard, error::Error, events::WaylandSource, keyboard, pointers, surfaces, - window::WindowHandle, + surfaces::buffers::Mmap, window::WindowHandle, }; -use crate::{application::AppHandler, backend, kurbo, mouse, TimerToken}; +use crate::{ + backend, backend::shared::xkb, keyboard_types::KeyState, kurbo, mouse, AppHandler, TimerToken, +}; use calloop; diff --git a/druid-shell/src/backend/wayland/keyboard.rs b/druid-shell/src/backend/wayland/keyboard.rs index bbb4a4f444..317a926df3 100644 --- a/druid-shell/src/backend/wayland/keyboard.rs +++ b/druid-shell/src/backend/wayland/keyboard.rs @@ -9,7 +9,7 @@ use crate::Modifiers; use super::application::ApplicationData; use super::surfaces::buffers; -use super::xkb; +use crate::backend::shared::xkb; pub(super) struct State { /// Whether we've currently got keyboard focus. @@ -38,6 +38,45 @@ impl Default for State { } } +struct ModMap(u32, Modifiers); + +impl ModMap { + fn merge(self, m: Modifiers, mods: u32, locked: u32) -> Modifiers { + if self.0 & mods == 0 && self.0 & locked == 0 { + return m; + } + + return m | self.1; + } +} + +const MOD_SHIFT: ModMap = ModMap(1, Modifiers::SHIFT); +const MOD_CAP_LOCK: ModMap = ModMap(2, Modifiers::CAPS_LOCK); +const MOD_CTRL: ModMap = ModMap(4, Modifiers::CONTROL); +const MOD_ALT: ModMap = ModMap(8, Modifiers::ALT); +const MOD_NUM_LOCK: ModMap = ModMap(16, Modifiers::NUM_LOCK); +const MOD_META: ModMap = ModMap(64, Modifiers::META); + +pub fn event_to_mods(event: wl_keyboard::Event) -> Modifiers { + match event { + wl_keyboard::Event::Modifiers { + mods_depressed, + mods_locked, + .. + } => { + let mods = Modifiers::empty(); + let mods = MOD_SHIFT.merge(mods, mods_depressed, mods_locked); + let mods = MOD_CAP_LOCK.merge(mods, mods_depressed, mods_locked); + let mods = MOD_CTRL.merge(mods, mods_depressed, mods_locked); + let mods = MOD_ALT.merge(mods, mods_depressed, mods_locked); + let mods = MOD_NUM_LOCK.merge(mods, mods_depressed, mods_locked); + let mods = MOD_META.merge(mods, mods_depressed, mods_locked); + return mods; + } + _ => return Modifiers::empty(), + } +} + pub struct Manager { inner: std::sync::Arc>, } @@ -136,22 +175,23 @@ impl Manager { }); } wl_keyboard::Event::Key { key, state, .. } => { - let event = keyboardstate - .borrow() + let mut event = keyboardstate + .borrow_mut() .xkb_state - .borrow() - .as_ref() + .borrow_mut() + .as_mut() .unwrap() .key_event( - key, + key + 8, match state { wl_keyboard::KeyState::Released => KeyState::Up, wl_keyboard::KeyState::Pressed => KeyState::Down, _ => panic!("unrecognised key event"), }, - keyboardstate.borrow().xkb_mods.get(), ); + event.mods = keyboardstate.borrow().xkb_mods.get(); + if let Some(winhandle) = appdata.acquire_current_window() { winhandle.data().map(|windata| { windata.with_handler({ @@ -182,7 +222,7 @@ impl Manager { keyboardstate .borrow() .xkb_mods - .replace(xkb::event_to_mods(event)); + .replace(event_to_mods(event)); } evt => { tracing::warn!("unimplemented keyboard event: {:?}", evt); diff --git a/druid-shell/src/backend/wayland/keycodes.rs b/druid-shell/src/backend/wayland/keycodes.rs deleted file mode 100644 index 56582ad53d..0000000000 --- a/druid-shell/src/backend/wayland/keycodes.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2019 The Druid Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::keyboard_types::{Key, Location}; - -pub type RawKey = (); - -pub fn raw_key_to_key(raw: RawKey) -> Option { - todo!() -} - -pub fn raw_key_to_location(raw: RawKey) -> Location { - todo!() -} - -pub fn key_to_raw_key(src: &Key) -> Option { - todo!() -} diff --git a/druid-shell/src/backend/wayland/mod.rs b/druid-shell/src/backend/wayland/mod.rs index 1e3898705a..32feb540e4 100644 --- a/druid-shell/src/backend/wayland/mod.rs +++ b/druid-shell/src/backend/wayland/mod.rs @@ -18,7 +18,7 @@ pub mod application; pub mod clipboard; pub mod dialog; pub mod error; -pub mod events; +mod events; pub mod keyboard; pub mod menu; pub mod pointers; @@ -26,7 +26,6 @@ pub mod screen; pub mod surfaces; pub mod util; pub mod window; -pub mod xkb; /// Little enum to make it clearer what some return values mean. #[derive(Copy, Clone)] diff --git a/druid-shell/src/backend/wayland/xkb.rs b/druid-shell/src/backend/wayland/xkb.rs deleted file mode 100644 index a96cd32948..0000000000 --- a/druid-shell/src/backend/wayland/xkb.rs +++ /dev/null @@ -1,543 +0,0 @@ -//! A minimal wrapper around Xkb for our use. - -#![allow(non_upper_case_globals)] - -use crate::{ - backend::shared::{code_to_location, hardware_keycode_to_code}, - KeyEvent, KeyState, Modifiers, -}; -use keyboard_types::{Code, Key}; -use std::{convert::TryFrom, ptr}; -use wayland_client::protocol::wl_keyboard as wlkeyboard; -use xkbcommon_sys::*; - -struct ModMap(u32, Modifiers); - -impl ModMap { - fn merge(self, m: Modifiers, mods: u32, locked: u32) -> Modifiers { - if self.0 & mods == 0 && self.0 & locked == 0 { - return m; - } - - return m | self.1; - } -} - -const MOD_SHIFT: ModMap = ModMap(1, Modifiers::SHIFT); -const MOD_CAP_LOCK: ModMap = ModMap(2, Modifiers::CAPS_LOCK); -const MOD_CTRL: ModMap = ModMap(4, Modifiers::CONTROL); -const MOD_ALT: ModMap = ModMap(8, Modifiers::ALT); -const MOD_NUM_LOCK: ModMap = ModMap(16, Modifiers::NUM_LOCK); -const MOD_META: ModMap = ModMap(64, Modifiers::META); - -pub fn event_to_mods(event: wlkeyboard::Event) -> Modifiers { - match event { - wlkeyboard::Event::Modifiers { - mods_depressed, - mods_locked, - .. - } => { - let mods = Modifiers::empty(); - let mods = MOD_SHIFT.merge(mods, mods_depressed, mods_locked); - let mods = MOD_CAP_LOCK.merge(mods, mods_depressed, mods_locked); - let mods = MOD_CTRL.merge(mods, mods_depressed, mods_locked); - let mods = MOD_ALT.merge(mods, mods_depressed, mods_locked); - let mods = MOD_NUM_LOCK.merge(mods, mods_depressed, mods_locked); - let mods = MOD_META.merge(mods, mods_depressed, mods_locked); - return mods; - } - _ => return Modifiers::empty(), - } -} - -//const MAX_KEY_LEN: usize = 32; - -/// A global xkb context object. -/// -/// Reference counted under the hood. -// Assume this isn't threadsafe unless proved otherwise. (e.g. don't implement Send/Sync) -// TODO do we need UnsafeCell? -pub struct Context { - inner: *mut xkb_context, -} - -impl Context { - /// Create a new xkb context. - /// - /// The returned object is lightweight and clones will point at the same context internally. - pub fn new() -> Self { - unsafe { - let inner = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - Context { inner } - } - } - - /// Create a keymap from some given data. - /// - /// Uses `xkb_keymap_new_from_buffer` under the hood. - pub fn keymap_from_slice(&self, buffer: &[u8]) -> Keymap { - // TODO we hope that the keymap doesn't borrow the underlying data. If it does' we need to - // use Rc. We'll find out soon enough if we get a segfault. - // TODO we hope that the keymap inc's the reference count of the context. - assert!( - buffer.iter().copied().any(|byte| byte == 0), - "`keymap_from_slice` expects a null-terminated string" - ); - unsafe { - let keymap = xkb_keymap_new_from_string( - self.inner, - buffer.as_ptr() as *const i8, - XKB_KEYMAP_FORMAT_TEXT_V1, - XKB_KEYMAP_COMPILE_NO_FLAGS, - ); - assert!(!keymap.is_null()); - Keymap::new(keymap) - } - } -} - -impl Clone for Context { - fn clone(&self) -> Self { - unsafe { - xkb_context_ref(self.inner); - Context { inner: self.inner } - } - } -} - -impl Drop for Context { - fn drop(&mut self) { - unsafe { - xkb_context_unref(self.inner); - } - } -} - -pub struct Keymap { - inner: *mut xkb_keymap, -} - -impl Keymap { - /// Create a new keymap object. - /// - /// # Safety - /// - /// This function takes ownership of the `xkb_keymap`. It must be valid when this function is - /// called, and not deallocated elsewhere. - unsafe fn new(inner: *mut xkb_keymap) -> Self { - Keymap { inner } - } - - pub fn state(&self) -> State { - unsafe { State::new(xkb_state_new(self.inner)) } - } -} - -impl Clone for Keymap { - fn clone(&self) -> Self { - unsafe { - xkb_keymap_ref(self.inner); - Self { inner: self.inner } - } - } -} - -impl Drop for Keymap { - fn drop(&mut self) { - unsafe { - xkb_keymap_unref(self.inner); - } - } -} - -pub struct State { - inner: *mut xkb_state, -} - -impl State { - unsafe fn new(inner: *mut xkb_state) -> Self { - Self { inner } - } - - pub fn key_event(&self, scancode: u32, state: KeyState, mods: Modifiers) -> KeyEvent { - let scancode = scancode + 8; - let code = u16::try_from(scancode) - .map(hardware_keycode_to_code) - .unwrap_or(Code::Unidentified); - let key = self.get_logical_key(scancode); - // TODO this is lazy - really should use xkb i.e. augment the get_logical_key method. - let location = code_to_location(code); - // TODO rest are unimplemented - let repeat = false; - // TODO we get the information for this from a wayland event - let is_composing = false; - - // Update xkb's state (e.g. return capitals if we've pressed shift) - unsafe { - xkb_state_update_key( - self.inner, - scancode, - match state { - KeyState::Down => XKB_KEY_DOWN, - KeyState::Up => XKB_KEY_UP, - }, - ); - } - KeyEvent { - state, - key, - code, - location, - mods, - repeat, - is_composing, - } - } - - fn get_logical_key(&self, scancode: u32) -> Key { - let mut key = map_key(self.key_get_one_sym(scancode)); - if matches!(key, Key::Unidentified) { - if let Some(s) = self.key_get_utf8(scancode) { - key = Key::Character(s); - } - } - key - } - - fn key_get_one_sym(&self, scancode: u32) -> u32 { - unsafe { xkb_state_key_get_one_sym(self.inner, scancode) } - } - - /// Get the string representation of a key. - // TODO `keyboard_types` forces us to return a String, but it would be nicer if we could stay - // on the stack, especially since we expect most results to be pretty small. - fn key_get_utf8(&self, scancode: u32) -> Option { - unsafe { - // First get the size we will need - let len = xkb_state_key_get_utf8(self.inner, scancode, ptr::null_mut(), 0); - if len == 0 { - return None; - } - // add 1 because we will get a null-terminated string. - let len = usize::try_from(len).unwrap() + 1; - let mut buf: Vec = Vec::new(); - buf.resize(len, 0); - xkb_state_key_get_utf8(self.inner, scancode, buf.as_mut_ptr() as *mut i8, len); - debug_assert!(buf[buf.len() - 1] == 0); - buf.pop(); - Some(String::from_utf8(buf).unwrap()) - } - } -} - -impl Clone for State { - fn clone(&self) -> Self { - unsafe { - xkb_state_ref(self.inner); - Self { inner: self.inner } - } - } -} - -impl Drop for State { - fn drop(&mut self) { - unsafe { - xkb_state_unref(self.inner); - } - } -} - -/// Map from an xkb_common key code to a key, if possible. -fn map_key(keysym: u32) -> Key { - use Key::*; - match keysym { - XKB_KEY_Alt_L => Alt, - XKB_KEY_Alt_R => AltGraph, - XKB_KEY_Caps_Lock => CapsLock, - XKB_KEY_Control_L | XKB_KEY_Control_R => Control, - XKB_KEY_Escape => Escape, - XKB_KEY_function => Fn, - // FnLock, - can't find in xkb - XKB_KEY_Meta_L | XKB_KEY_Meta_R => Meta, - XKB_KEY_Num_Lock => NumLock, - XKB_KEY_Scroll_Lock => ScrollLock, - XKB_KEY_Shift_L | XKB_KEY_Shift_R => Shift, - // Symbol, - // SymbolLock, - XKB_KEY_Hyper_L | XKB_KEY_Hyper_R => Hyper, - XKB_KEY_Super_L | XKB_KEY_Super_R => Super, - XKB_KEY_Return => Enter, - XKB_KEY_Tab => Tab, - XKB_KEY_ISO_Left_Tab => Tab, - XKB_KEY_Down => ArrowDown, - XKB_KEY_Left => ArrowLeft, - XKB_KEY_Right => ArrowRight, - XKB_KEY_Up => ArrowUp, - XKB_KEY_End => End, - XKB_KEY_Home => Home, - XKB_KEY_Page_Down => PageDown, - XKB_KEY_Page_Up => PageUp, - XKB_KEY_BackSpace => Backspace, - XKB_KEY_Clear => Clear, - // Copy, - XKB_KEY_3270_CursorSelect => CrSel, - //Cut, - //Delete, - //EraseEof, - //ExSel, - XKB_KEY_Insert => Insert, - //Paste, - XKB_KEY_Redo => Redo, - XKB_KEY_Undo => Undo, - /* todo carry on - Accept, - Again, - Attn, - Cancel, - ContextMenu, - Execute, - Find, - Help, - Pause, - Play, - Props, - Select, - ZoomIn, - ZoomOut, - BrightnessDown, - BrightnessUp, - Eject, - LogOff, - Power, - PowerOff, - PrintScreen, - Hibernate, - Standby, - WakeUp, - AllCandidates, - Alphanumeric, - CodeInput, - Compose, - Convert, - Dead, - FinalMode, - GroupFirst, - GroupLast, - GroupNext, - GroupPrevious, - ModeChange, - NextCandidate, - NonConvert, - PreviousCandidate, - Process, - SingleCandidate, - HangulMode, - HanjaMode, - JunjaMode, - Eisu, - Hankaku, - Hiragana, - HiraganaKatakana, - KanaMode, - KanjiMode, - Katakana, - Romaji, - Zenkaku, - ZenkakuHankaku, - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - Soft1, - Soft2, - Soft3, - Soft4, - ChannelDown, - ChannelUp, - Close, - MailForward, - MailReply, - MailSend, - MediaClose, - MediaFastForward, - MediaPause, - MediaPlay, - MediaPlayPause, - MediaRecord, - MediaRewind, - MediaStop, - MediaTrackNext, - MediaTrackPrevious, - New, - Open, - Print, - Save, - SpellCheck, - Key11, - Key12, - AudioBalanceLeft, - AudioBalanceRight, - AudioBassBoostDown, - AudioBassBoostToggle, - AudioBassBoostUp, - AudioFaderFront, - AudioFaderRear, - AudioSurroundModeNext, - AudioTrebleDown, - AudioTrebleUp, - AudioVolumeDown, - AudioVolumeUp, - AudioVolumeMute, - MicrophoneToggle, - MicrophoneVolumeDown, - MicrophoneVolumeUp, - MicrophoneVolumeMute, - SpeechCorrectionList, - SpeechInputToggle, - LaunchApplication1, - LaunchApplication2, - LaunchCalendar, - LaunchContacts, - LaunchMail, - LaunchMediaPlayer, - LaunchMusicPlayer, - LaunchPhone, - LaunchScreenSaver, - LaunchSpreadsheet, - LaunchWebBrowser, - LaunchWebCam, - LaunchWordProcessor, - BrowserBack, - BrowserFavorites, - BrowserForward, - BrowserHome, - BrowserRefresh, - BrowserSearch, - BrowserStop, - AppSwitch, - Call, - Camera, - CameraFocus, - EndCall, - GoBack, - GoHome, - HeadsetHook, - LastNumberRedial, - Notification, - MannerMode, - VoiceDial, - TV, - TV3DMode, - TVAntennaCable, - TVAudioDescription, - TVAudioDescriptionMixDown, - TVAudioDescriptionMixUp, - TVContentsMenu, - TVDataService, - TVInput, - TVInputComponent1, - TVInputComponent2, - TVInputComposite1, - TVInputComposite2, - TVInputHDMI1, - TVInputHDMI2, - TVInputHDMI3, - TVInputHDMI4, - TVInputVGA1, - TVMediaContext, - TVNetwork, - TVNumberEntry, - TVPower, - TVRadioService, - TVSatellite, - TVSatelliteBS, - TVSatelliteCS, - TVSatelliteToggle, - TVTerrestrialAnalog, - TVTerrestrialDigital, - TVTimer, - AVRInput, - AVRPower, - ColorF0Red, - ColorF1Green, - ColorF2Yellow, - ColorF3Blue, - ColorF4Grey, - ColorF5Brown, - ClosedCaptionToggle, - Dimmer, - DisplaySwap, - DVR, - Exit, - FavoriteClear0, - FavoriteClear1, - FavoriteClear2, - FavoriteClear3, - FavoriteRecall0, - FavoriteRecall1, - FavoriteRecall2, - FavoriteRecall3, - FavoriteStore0, - FavoriteStore1, - FavoriteStore2, - FavoriteStore3, - Guide, - GuideNextDay, - GuidePreviousDay, - Info, - InstantReplay, - Link, - ListProgram, - LiveContent, - Lock, - MediaApps, - MediaAudioTrack, - MediaLast, - MediaSkipBackward, - MediaSkipForward, - MediaStepBackward, - MediaStepForward, - MediaTopMenu, - NavigateIn, - NavigateNext, - NavigateOut, - NavigatePrevious, - NextFavoriteChannel, - NextUserProfile, - OnDemand, - Pairing, - PinPDown, - PinPMove, - PinPToggle, - PinPUp, - PlaySpeedDown, - PlaySpeedReset, - PlaySpeedUp, - RandomToggle, - RcLowBattery, - RecordSpeedNext, - RfBypass, - ScanChannelsToggle, - ScreenModeNext, - Settings, - SplitScreenToggle, - STBInput, - STBPower, - Subtitle, - Teletext, - VideoModeNext, - Wink, - ZoomToggle, - */ - // A catchall - _ => Unidentified, - } -} diff --git a/druid-shell/src/backend/x11/application.rs b/druid-shell/src/backend/x11/application.rs index b701195fc8..4378253747 100644 --- a/druid-shell/src/backend/x11/application.rs +++ b/druid-shell/src/backend/x11/application.rs @@ -36,8 +36,9 @@ use x11rb::xcb_ffi::XCBConnection; use crate::application::AppHandler; use super::clipboard::Clipboard; +use super::util; use super::window::Window; -use super::{util, xkb}; +use crate::backend::shared::xkb; // This creates a `struct WindowAtoms` containing the specified atoms as members (along with some // convenience methods to intern and query those atoms). We use the following atoms: diff --git a/druid-shell/src/backend/x11/mod.rs b/druid-shell/src/backend/x11/mod.rs index b51998b70e..6451f6ac52 100644 --- a/druid-shell/src/backend/x11/mod.rs +++ b/druid-shell/src/backend/x11/mod.rs @@ -32,13 +32,10 @@ #[macro_use] mod util; -mod xkb; pub mod application; pub mod clipboard; pub mod error; -mod keycodes; pub mod menu; pub mod screen; pub mod window; -mod xkbcommon_sys; From 1194bbedaa69a648df77f199065c2d16daa32d7d Mon Sep 17 00:00:00 2001 From: James Date: Tue, 9 Nov 2021 22:54:04 -0500 Subject: [PATCH 10/31] decouple buffers from surface. (#2032) removes the need to use some unsafe code. --- .../src/backend/wayland/application.rs | 6 +-- .../src/backend/wayland/surfaces/buffers.rs | 41 ++++++------------- .../backend/wayland/surfaces/layershell.rs | 7 +++- .../src/backend/wayland/surfaces/popup.rs | 2 +- .../src/backend/wayland/surfaces/surface.rs | 13 +----- .../src/backend/wayland/surfaces/toplevel.rs | 2 +- 6 files changed, 24 insertions(+), 47 deletions(-) diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index 8472aff62b..db3244ca01 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -16,12 +16,10 @@ use super::{ clipboard::Clipboard, error::Error, events::WaylandSource, keyboard, pointers, surfaces, - surfaces::buffers::Mmap, window::WindowHandle, + window::WindowHandle, }; -use crate::{ - backend, backend::shared::xkb, keyboard_types::KeyState, kurbo, mouse, AppHandler, TimerToken, -}; +use crate::{backend, kurbo, mouse, AppHandler, TimerToken}; use calloop; diff --git a/druid-shell/src/backend/wayland/surfaces/buffers.rs b/druid-shell/src/backend/wayland/surfaces/buffers.rs index aed89cad2f..5ea9335279 100644 --- a/druid-shell/src/backend/wayland/surfaces/buffers.rs +++ b/druid-shell/src/backend/wayland/surfaces/buffers.rs @@ -58,11 +58,6 @@ pub struct Buffers { /// Otherwise providing mutable access to the data would be unsafe. pending_buffer_borrowed: Cell, - /// A handle to the `Surface`, so we can run the paint method. - /// - /// Weak handle because logically we are owned by the `WindowData`. If ownership went in both - /// directions we would leak memory. - window: std::sync::Weak, /// Shared memory to allocate buffers in shm: RefCell, } @@ -79,27 +74,20 @@ impl Buffers { recreate_buffers: Cell::new(true), deferred_paint: Cell::new(false), pending_buffer_borrowed: Cell::new(false), - window: std::sync::Weak::new(), shm: RefCell::new(Shm::new(wl_shm).expect("error allocating shared memory")), }) } - pub fn set_window_data(&mut self, data: std::sync::Weak) { - self.window = data; - } - /// Get the physical size of the buffer. pub fn size(&self) -> RawSize { self.size.get() } /// Request that the size of the buffer is changed. - pub fn set_size(&self, new_size: RawSize) { - assert!(!new_size.is_empty(), "window size must not be empty"); - if self.size.get() != new_size { - self.size.set(new_size); - self.recreate_buffers.set(true); - } + pub fn set_size(&self, updated: RawSize) { + assert!(!updated.is_empty(), "window size must not be empty"); + let old = self.size.replace(updated); + self.recreate_buffers.set(old != updated); } /// Request painting the next frame. @@ -110,7 +98,7 @@ impl Buffers { /// We will call into `WindowData` to paint the frame, and present it. If no buffers are /// available we will set a flag, so that when one becomes available we immediately paint and /// present. This includes if we need to resize. - pub fn request_paint(self: &Rc) { + pub fn request_paint(self: &Rc, window: &surface::Data) { tracing::trace!("buffer.request_paint {:?}", self.size.get()); // if our size is empty there is nothing to do. @@ -132,7 +120,7 @@ impl Buffers { //log::debug!("all buffers released, recreating"); self.deferred_paint.set(false); self.recreate_buffers_unchecked(); - self.paint_unchecked(); + self.paint_unchecked(window); } else { self.deferred_paint.set(true); } @@ -142,7 +130,7 @@ impl Buffers { if self.pending_buffer_released() { //log::debug!("next frame has been released: draw and present"); self.deferred_paint.set(false); - self.paint_unchecked(); + self.paint_unchecked(window); } else { self.deferred_paint.set(true); } @@ -150,17 +138,14 @@ impl Buffers { } /// Paint the next frame, without checking if the buffer is free. - fn paint_unchecked(self: &Rc) { + fn paint_unchecked(self: &Rc, window: &surface::Data) { tracing::trace!("buffer.paint_unchecked"); let mut buf_data = self.pending_buffer_data().unwrap(); debug_assert!( self.pending_buffer_released(), "buffer in use/not initialized" ); - if let Some(data) = self.window.upgrade() { - data.paint(self.size.get(), &mut *buf_data, self.recreate_buffers.get()); - } - + window.paint(self.size.get(), &mut *buf_data, self.recreate_buffers.get()); self.recreate_buffers.set(false); } @@ -248,11 +233,9 @@ impl Buffers { /// Signal to wayland that the pending buffer is ready to be presented, and switch the next /// buffer to be the pending one. - pub(crate) fn attach(&self) { - if let Some(data) = self.window.upgrade() { - self.with_pending_buffer(|buf| buf.unwrap().attach(&data.wl_surface)); - self.pending.set((self.pending.get() + 1) % N); - } + pub(crate) fn attach(&self, window: &surface::Data) { + self.with_pending_buffer(|buf| buf.unwrap().attach(&window.wl_surface)); + self.pending.set((self.pending.get() + 1) % N); } fn frame_len(&self) -> usize { diff --git a/druid-shell/src/backend/wayland/surfaces/layershell.rs b/druid-shell/src/backend/wayland/surfaces/layershell.rs index 6202ae1097..85357466aa 100644 --- a/druid-shell/src/backend/wayland/surfaces/layershell.rs +++ b/druid-shell/src/backend/wayland/surfaces/layershell.rs @@ -218,7 +218,12 @@ impl Surface { .inner .wl_surface .update_dimensions(dim.width as u32, dim.height as u32); - handle.inner.wl_surface.inner.buffers.request_paint(); + handle + .inner + .wl_surface + .inner + .buffers + .request_paint(&handle.inner.wl_surface.inner); } _ => tracing::info!("unimplemented event {:?} {:?} {:?}", a1, event, a2), } diff --git a/druid-shell/src/backend/wayland/surfaces/popup.rs b/druid-shell/src/backend/wayland/surfaces/popup.rs index 38dc91130e..2cae463767 100644 --- a/druid-shell/src/backend/wayland/surfaces/popup.rs +++ b/druid-shell/src/backend/wayland/surfaces/popup.rs @@ -96,7 +96,7 @@ impl Surface { xdg_surface.ack_configure(serial); let dim = wl_surface.inner.logical_size.get(); wl_surface.inner.handler.borrow_mut().size(dim); - wl_surface.inner.buffers.request_paint(); + wl_surface.inner.buffers.request_paint(&wl_surface.inner); } _ => tracing::warn!("unhandled xdg_surface event {:?}", event), } diff --git a/druid-shell/src/backend/wayland/surfaces/surface.rs b/druid-shell/src/backend/wayland/surfaces/surface.rs index dec7d6b2e3..6d8514b452 100644 --- a/druid-shell/src/backend/wayland/surfaces/surface.rs +++ b/druid-shell/src/backend/wayland/surfaces/surface.rs @@ -53,15 +53,6 @@ impl Handle { popup_impl: RefCell::new(None), }); - // Hook up the child -> parent weak pointer. - unsafe { - // Safety: No Rust references exist during the life of this reference (satisfies many - // read-only xor 1 mutable references). - let bufs: &mut buffers::Buffers<{ buffers::NUM_FRAMES as usize }> = - &mut *(std::rc::Rc::as_ptr(¤t.buffers) as *mut _); - bufs.set_window_data(std::sync::Arc::downgrade(¤t)); - } - // register to receive wl_surface events. current.wl_surface.quick_assign({ let current = current.clone(); @@ -415,7 +406,7 @@ impl Data { // reset damage ready for next frame. self.damaged_region.borrow_mut().clear(); - self.buffers.attach(); + self.buffers.attach(&self); self.wl_surface.commit(); } @@ -468,7 +459,7 @@ impl Data { fn run_deferred_task(&self, task: DeferredTask) { match task { DeferredTask::Paint => { - self.buffers.request_paint(); + self.buffers.request_paint(&self); } DeferredTask::AnimationClear => { self.anim_frame_requested.set(false); diff --git a/druid-shell/src/backend/wayland/surfaces/toplevel.rs b/druid-shell/src/backend/wayland/surfaces/toplevel.rs index 975c918c62..c719724cdf 100644 --- a/druid-shell/src/backend/wayland/surfaces/toplevel.rs +++ b/druid-shell/src/backend/wayland/surfaces/toplevel.rs @@ -64,7 +64,7 @@ impl Surface { xdg_surface.ack_configure(serial); let dim = wl_surface.inner.logical_size.get(); wl_surface.inner.handler.borrow_mut().size(dim); - wl_surface.inner.buffers.request_paint(); + wl_surface.inner.buffers.request_paint(&wl_surface.inner); } _ => tracing::warn!("unhandled xdg_surface event {:?}", event), } From 3500e1e9ad4565af2b1b33641645be5fa268deb7 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 9 Nov 2021 22:57:32 -0500 Subject: [PATCH 11/31] decouple window handles from wayland id. (#2033) allows for us to replace inner surfaces dynamically. (necessary for layershells). --- druid-shell/src/backend/shared/xkb/mod.rs | 1 + .../src/backend/wayland/application.rs | 31 +++++------ .../src/backend/wayland/surfaces/mod.rs | 18 +----- .../src/backend/wayland/surfaces/surface.rs | 9 --- druid-shell/src/backend/wayland/window.rs | 55 +++++++++---------- 5 files changed, 43 insertions(+), 71 deletions(-) diff --git a/druid-shell/src/backend/shared/xkb/mod.rs b/druid-shell/src/backend/shared/xkb/mod.rs index 5bbb499ac0..fbddfd2b4c 100644 --- a/druid-shell/src/backend/shared/xkb/mod.rs +++ b/druid-shell/src/backend/shared/xkb/mod.rs @@ -104,6 +104,7 @@ impl Context { /// /// Because `xkb` has a `critical` error, each rust error maps to 1 above (e.g. error -> /// critical, warn -> error etc.) + #[allow(unused)] pub fn set_log_level(&self, level: tracing::Level) { use tracing::Level; let level = match level { diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index db3244ca01..7760e5ec64 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -26,7 +26,6 @@ use calloop; use std::{ cell::{Cell, RefCell}, collections::{BTreeMap, BinaryHeap}, - num::NonZeroU32, rc::Rc, time::{Duration, Instant}, }; @@ -52,14 +51,14 @@ use wayland_protocols::xdg_shell::client::xdg_surface; use wayland_protocols::xdg_shell::client::xdg_wm_base::{self, XdgWmBase}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct Timer(backend::shared::Timer); +pub(crate) struct Timer(backend::shared::Timer); impl Timer { - pub(crate) fn new(id: u32, deadline: Instant) -> Self { + pub(crate) fn new(id: u64, deadline: Instant) -> Self { Self(backend::shared::Timer::new(deadline, id)) } - pub(crate) fn id(self) -> u32 { + pub(crate) fn id(self) -> u64 { self.0.data } @@ -118,14 +117,14 @@ pub(crate) struct ApplicationData { // pub(super) surfaces: RefCell>>, /// Handles to any surfaces that have been created. - pub(super) handles: RefCell>, + pub(super) handles: RefCell>, /// Available pixel formats pub(super) formats: RefCell>, /// Close flag pub(super) shutdown: Cell, /// The currently active surface, if any (by wayland object ID) - pub(super) active_surface_id: RefCell>, + pub(super) active_surface_id: RefCell>, // Stuff for timers /// A calloop event source for timers. We always set it to fire at the next set timer, if any. pub(super) timer_handle: calloop::timer::TimerHandle, @@ -472,11 +471,13 @@ impl ApplicationData { Ok(()) } - fn current_window_id(&self) -> u32 { - match self.active_surface_id.borrow().get(0) { - Some(u) => u.get(), - None => 0, - } + fn current_window_id(&self) -> u64 { + static DEFAULT: u64 = 0 as u64; + self.active_surface_id + .borrow() + .get(0) + .unwrap_or_else(|| &DEFAULT) + .clone() } pub(super) fn initial_window_size(&self, defaults: kurbo::Size) -> kurbo::Size { @@ -559,14 +560,8 @@ impl ApplicationData { } /// Shallow clones surfaces so we can modify it during iteration. - fn handles_iter(&self) -> impl Iterator { + fn handles_iter(&self) -> impl Iterator { self.handles.borrow().clone().into_iter() - // make sure the borrow gets dropped. - // let surfaces = { - // let surfaces = self.surfaces.borrow(); - // surfaces.clone() - // }; - // surfaces.into_iter() } fn idle_repaint<'a>(loophandle: calloop::LoopHandle<'a, std::sync::Arc>) { diff --git a/druid-shell/src/backend/wayland/surfaces/mod.rs b/druid-shell/src/backend/wayland/surfaces/mod.rs index 7dd8279638..f02af220ba 100644 --- a/druid-shell/src/backend/wayland/surfaces/mod.rs +++ b/druid-shell/src/backend/wayland/surfaces/mod.rs @@ -19,6 +19,8 @@ pub mod popup; pub mod surface; pub mod toplevel; +pub const GLOBAL_ID: crate::Counter = crate::Counter::new(); + pub trait Compositor { fn output(&self, id: &u32) -> Option; fn create_surface(&self) -> wlc::Main; @@ -56,7 +58,6 @@ impl PopupHandle { // handle on given surface. pub trait Handle { - fn wayland_surface_id(&self) -> u32; fn get_size(&self) -> kurbo::Size; fn set_size(&self, dim: kurbo::Size); fn request_anim_frame(&self); @@ -159,17 +160,6 @@ impl Compositor for CompositorHandle { } } - // fn get_xdg_popup( - // &self, - // pos: &wlc::Main, - // parent: Option<&xdg_surface::XdgSurface>, - // ) -> wlc::Main { - // match self.inner.upgrade() { - // None => panic!("unable to acquire underyling compositor to acquire xdg popup surface"), - // Some(c) => c.get_xdg_popup(pos, parent), - // } - // } - fn zxdg_decoration_manager_v1(&self) -> wlc::Main { match self.inner.upgrade() { None => { @@ -188,7 +178,3 @@ impl Compositor for CompositorHandle { } } } - -pub fn id(s: impl Into) -> u32 { - s.into() -} diff --git a/druid-shell/src/backend/wayland/surfaces/surface.rs b/druid-shell/src/backend/wayland/surfaces/surface.rs index 6d8514b452..a05009cbb4 100644 --- a/druid-shell/src/backend/wayland/surfaces/surface.rs +++ b/druid-shell/src/backend/wayland/surfaces/surface.rs @@ -104,10 +104,6 @@ impl Handle { } impl SurfaceHandle for Handle { - fn wayland_surface_id(&self) -> u32 { - self.inner.wayland_surface_id() - } - fn get_size(&self) -> kurbo::Size { self.inner.get_size() } @@ -574,11 +570,6 @@ impl Decor for Dead { } impl SurfaceHandle for Dead { - fn wayland_surface_id(&self) -> u32 { - tracing::warn!("wayland_surface_id invoked on a dead surface"); - 0 - } - fn get_size(&self) -> kurbo::Size { kurbo::Size::ZERO } diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index 6482b26450..241aeea9d3 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -38,6 +38,7 @@ pub use surfaces::idle::Handle as IdleHandle; // holds references to the various components for a window implementation. struct Inner { + pub(super) id: u64, pub(super) decor: Box, pub(super) surface: Box, pub(super) appdata: std::sync::Weak, @@ -56,6 +57,7 @@ impl WindowHandle { ) -> Self { Self { inner: std::sync::Arc::new(Inner { + id: surfaces::GLOBAL_ID.next(), decor: decor.into(), surface: surface.into(), appdata: appdata.into(), @@ -63,6 +65,10 @@ impl WindowHandle { } } + pub fn id(&self) -> u64 { + self.inner.id + } + pub fn show(&self) { tracing::info!("show initiated"); } @@ -125,10 +131,7 @@ impl WindowHandle { "closing window initiated {:?}", appdata.active_surface_id.borrow() ); - appdata - .handles - .borrow_mut() - .remove(&self.inner.surface.wayland_surface_id()); + appdata.handles.borrow_mut().remove(&self.id()); appdata.active_surface_id.borrow_mut().pop_front(); self.inner.surface.release(); tracing::trace!( @@ -197,8 +200,6 @@ impl WindowHandle { None => panic!("requested timer on a window that was destroyed"), }; - let sid = self.inner.surface.wayland_surface_id(); - let now = instant::Instant::now(); let mut timers = appdata.timers.borrow_mut(); let sooner = timers @@ -206,7 +207,7 @@ impl WindowHandle { .map(|timer| deadline < timer.deadline()) .unwrap_or(true); - let timer = Timer::new(sid, deadline); + let timer = Timer::new(self.id(), deadline); timers.push(timer); // It is possible that the deadline has passed since it was set. @@ -277,6 +278,7 @@ impl WindowHandle { self.inner.surface.run_idle(); } + #[allow(unused)] pub(super) fn popup<'a>(&self, s: &'a surfaces::popup::Surface) -> Result<(), error::Error> { self.inner.surface.popup(s) } @@ -296,6 +298,7 @@ impl std::default::Default for WindowHandle { fn default() -> WindowHandle { WindowHandle { inner: std::sync::Arc::new(Inner { + id: surfaces::GLOBAL_ID.next(), decor: Box::new(surfaces::surface::Dead::default()), surface: Box::new(surfaces::surface::Dead::default()), appdata: std::sync::Weak::new(), @@ -404,21 +407,19 @@ impl WindowBuilder { (&surface as &dyn surfaces::Decor).set_title(self.title); - let sid = match std::num::NonZeroU32::new(surfaces::id(&surface)) { - Some(u) => u, - None => panic!("surface id must be non-zero"), - }; - let handle = WindowHandle::new(surface.clone(), surface.clone(), self.app_data.clone()); if let Some(_) = appdata .handles .borrow_mut() - .insert(sid.get(), handle.clone()) + .insert(handle.id(), handle.clone()) { panic!("wayland should use unique object IDs"); } - appdata.active_surface_id.borrow_mut().push_front(sid); + appdata + .active_surface_id + .borrow_mut() + .push_front(handle.id()); surface.with_handler({ let handle = handle.clone(); @@ -429,6 +430,7 @@ impl WindowBuilder { } } +#[allow(unused)] pub mod layershell { use crate::error::Error as ShellError; use crate::window::WinHandler; @@ -478,11 +480,6 @@ pub mod layershell { let surface = surfaces::layershell::Surface::new(appdata.clone(), winhandle, updated); - let sid = match std::num::NonZeroU32::new(surfaces::id(&surface)) { - Some(u) => u, - None => panic!("surface id must be non-zero"), - }; - let handle = WindowHandle::new( surfaces::surface::Dead::default(), surface.clone(), @@ -492,11 +489,14 @@ pub mod layershell { if let Some(_) = appdata .handles .borrow_mut() - .insert(sid.get(), handle.clone()) + .insert(handle.id(), handle.clone()) { panic!("wayland should use unique object IDs"); } - appdata.active_surface_id.borrow_mut().push_front(sid); + appdata + .active_surface_id + .borrow_mut() + .push_front(handle.id()); surface.with_handler({ let handle = handle.clone(); @@ -508,6 +508,7 @@ pub mod layershell { } } +#[allow(unused)] pub mod popup { use crate::error::Error as ShellError; use crate::window::WinHandler; @@ -560,11 +561,6 @@ pub mod popup { return Err(ShellError::Platform(cause)); } - let sid = match std::num::NonZeroU32::new(surfaces::id(&surface)) { - Some(u) => u, - None => panic!("surface id must be non-zero"), - }; - let handle = WindowHandle::new( surfaces::surface::Dead::default(), surface.clone(), @@ -574,11 +570,14 @@ pub mod popup { if let Some(_) = appdata .handles .borrow_mut() - .insert(sid.get(), handle.clone()) + .insert(handle.id(), handle.clone()) { panic!("wayland should use unique object IDs"); } - appdata.active_surface_id.borrow_mut().push_front(sid); + appdata + .active_surface_id + .borrow_mut() + .push_front(handle.id()); surface.with_handler({ let handle = handle.clone(); From 104579ffc7d8e3b36aebaf40687a022df670cfb4 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 19 Nov 2021 03:17:08 -0500 Subject: [PATCH 12/31] remove dead/unused code. (#2035) fix minor issue with global id --- druid-shell/src/backend/wayland/pointer.rs | 204 ------------------ .../src/backend/wayland/surfaces/mod.rs | 3 +- 2 files changed, 1 insertion(+), 206 deletions(-) delete mode 100644 druid-shell/src/backend/wayland/pointer.rs diff --git a/druid-shell/src/backend/wayland/pointer.rs b/druid-shell/src/backend/wayland/pointer.rs deleted file mode 100644 index a85e6a4298..0000000000 --- a/druid-shell/src/backend/wayland/pointer.rs +++ /dev/null @@ -1,204 +0,0 @@ -use crate::{ - keyboard::Modifiers, - kurbo::{Point, Vec2}, - mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}, -}; -use std::collections::VecDeque; -use wayland_client::{ - self as wl, - protocol::{ - wl_pointer::{self, WlPointer}, - wl_surface::WlSurface, - }, -}; - -// Button constants (linux specific) -//const BTN_MOUSE: u32 = 0x110; -const BTN_LEFT: u32 = 0x110; -const BTN_RIGHT: u32 = 0x111; -const BTN_MIDDLE: u32 = 0x112; -//const BTN_SIDE: u32 = 0x113; -//const BTN_EXTRA: u32 = 0x114; -//const BTN_FORWARD: u32 = 0x115; -//const BTN_BACK: u32 = 0x116; -//const BTN_TASK: u32 = 0x117; - -/// Collect up mouse events then emit them together on a pointer frame. -pub(crate) struct Pointer { - /// The wayland pointer object - pub(crate) wl_pointer: WlPointer, - /// Currently pressed buttons - pub(crate) buttons: MouseButtons, - /// Current position - pub(crate) pos: Point, - /// Events that have occurred since the last frame. - pub(crate) queued_events: VecDeque, - /// The image surface which contains the cursor image. - pub(crate) cursor_surface: wl::Main, - /// The serial used when the `Enter` event was received - pub(crate) enter_serial: u32, - /// Cache the current cursor, so we can see if it changed - pub(crate) current_cursor: Option, -} - -/// Raw wayland pointer events. -pub(crate) enum PointerEvent { - /// Mouse moved/entered - Motion(Point), - /// Mouse button pressed/released - Button { - button: u32, - state: wl_pointer::ButtonState, - }, - /// Axis movement - Axis { axis: wl_pointer::Axis, value: f64 }, - /// Mouse left - Leave, -} - -/// An enum that we will convert into the different callbacks. -pub(crate) enum MouseEvtKind { - Move(MouseEvent), - Up(MouseEvent), - Down(MouseEvent), - Leave, - Wheel(MouseEvent), -} - -impl Pointer { - /// Create a new pointer - pub fn new(cursor: wl::Main, wl_pointer: WlPointer, serial: u32) -> Self { - Pointer { - wl_pointer, - buttons: MouseButtons::new(), - pos: Point::ZERO, // will get set before we emit any events - queued_events: VecDeque::with_capacity(3), // should be enough most of the time - cursor_surface: cursor, - enter_serial: serial, - current_cursor: None, - } - } - - #[inline] - pub fn push(&mut self, event: PointerEvent) { - self.queued_events.push_back(event); - } - - #[inline] - pub fn pop(&mut self) -> Option { - self.queued_events.pop_front() - } - - #[inline] - pub fn cursor(&self) -> &WlSurface { - &self.cursor_surface - } -} - -/// Gets any queued events. -impl Iterator for Pointer { - type Item = MouseEvtKind; - - fn next(&mut self) -> Option { - use wl_pointer::{Axis, ButtonState}; - // sometimes we need to ignore an event and move on - loop { - let event = self.queued_events.pop_front()?; - match event { - PointerEvent::Motion(point) => { - self.pos = point; - return Some(MouseEvtKind::Move(MouseEvent { - pos: self.pos, - buttons: self.buttons, - // TODO - mods: Modifiers::empty(), - count: 0, - focus: false, - button: MouseButton::None, - wheel_delta: Vec2::ZERO, - })); - } - PointerEvent::Button { button, state } => { - let button = match linux_to_mouse_button(button) { - Some(b) => b, - // Skip unsupported buttons. - None => continue, - }; - let evt = match state { - ButtonState::Pressed => { - self.buttons.insert(button); - MouseEvtKind::Down(MouseEvent { - pos: self.pos, - buttons: self.buttons, - // TODO - mods: Modifiers::empty(), - count: 1, - focus: false, - button, - wheel_delta: Vec2::ZERO, - }) - } - ButtonState::Released => { - self.buttons.remove(button); - MouseEvtKind::Up(MouseEvent { - pos: self.pos, - buttons: self.buttons, - // TODO - mods: Modifiers::empty(), - count: 0, - focus: false, - button, - wheel_delta: Vec2::ZERO, - }) - } - _ => { - tracing::error!("mouse button changed, but not pressed or released"); - continue; - } - }; - return Some(evt); - } - PointerEvent::Axis { axis, value } => { - let wheel_delta = match axis { - Axis::VerticalScroll => Vec2::new(0., value), - Axis::HorizontalScroll => Vec2::new(value, 0.), - _ => { - tracing::error!("axis direction not vertical or horizontal"); - continue; - } - }; - return Some(MouseEvtKind::Wheel(MouseEvent { - pos: self.pos, - buttons: self.buttons, - // TODO - mods: Modifiers::empty(), - count: 0, - focus: false, - button: MouseButton::None, - wheel_delta, - })); - } - PointerEvent::Leave => { - // The parent will remove us. - return Some(MouseEvtKind::Leave); - } - } - } - } -} - -impl Drop for Pointer { - fn drop(&mut self) { - self.cursor_surface.destroy(); - } -} - -#[inline] -fn linux_to_mouse_button(button: u32) -> Option { - match button { - BTN_LEFT => Some(MouseButton::Left), - BTN_RIGHT => Some(MouseButton::Right), - BTN_MIDDLE => Some(MouseButton::Middle), - _ => None, - } -} diff --git a/druid-shell/src/backend/wayland/surfaces/mod.rs b/druid-shell/src/backend/wayland/surfaces/mod.rs index f02af220ba..a1671a5296 100644 --- a/druid-shell/src/backend/wayland/surfaces/mod.rs +++ b/druid-shell/src/backend/wayland/surfaces/mod.rs @@ -19,8 +19,7 @@ pub mod popup; pub mod surface; pub mod toplevel; -pub const GLOBAL_ID: crate::Counter = crate::Counter::new(); - +pub static GLOBAL_ID: crate::Counter = crate::Counter::new(); pub trait Compositor { fn output(&self, id: &u32) -> Option; fn create_surface(&self) -> wlc::Main; From ba5f3629de18fa1e32b6983baf7ed972edbcc00b Mon Sep 17 00:00:00 2001 From: James Lawrence Date: Sat, 20 Nov 2021 05:08:22 -0500 Subject: [PATCH 13/31] update wayland dependencies --- druid-shell/Cargo.toml | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/druid-shell/Cargo.toml b/druid-shell/Cargo.toml index e93d9a5511..cb6081bcaf 100755 --- a/druid-shell/Cargo.toml +++ b/druid-shell/Cargo.toml @@ -17,11 +17,22 @@ default-target = "x86_64-pc-windows-msvc" default = ["gtk"] gtk = ["gdk-sys", "glib-sys", "gtk-sys", "gtk-rs"] x11 = ["x11rb", "nix", "cairo-sys-rs", "bindgen", "pkg-config"] +wayland = [ + "wayland-client", + "wayland-protocols/client", + "wayland-protocols/unstable_protocols", + "nix", + "cairo-sys-rs", + "rand", + "calloop", + "wayland-cursor", + "log", + "im", + "bindgen", + "pkg-config", +] # Implement HasRawWindowHandle for WindowHandle raw-win-handle = ["raw-window-handle"] -# **WARNING** not ready for the prime time. Many things don't work yet. -wayland = ["wayland-client", "wayland-protocols/client", "wayland-protocols/unstable_protocols", -"nix", "cairo-sys-rs", "rand", "calloop", "wayland-cursor", "log", "im", "bindgen", "pkg-config"] # passing on all the image features. AVIF is not supported because it does not # support decoding, and that's all we use `Image` for. @@ -89,10 +100,9 @@ glib-sys = { version = "0.14.0", optional = true } gtk-sys = { version = "0.14.0", optional = true } nix = { version = "0.18.0", optional = true } x11rb = { version = "0.8.0", features = ["allow-unsafe-code", "present", "render", "randr", "xfixes", "xkb", "resource_manager", "cursor"], optional = true } -# todo use dlopen eventually to gracefully fallback to X11 -wayland-client = { version = "0.28.2", optional = true, features = ["dlopen"] } -wayland-protocols = { version = "0.28.2", optional = true } -wayland-cursor = { version = "0.28.3", optional = true } +wayland-client = { version = "0.29", optional = true } +wayland-protocols = { version = "0.29", optional = true } +wayland-cursor = { version = "0.29", optional = true } rand = { version = "0.8.0", optional = true } calloop = { version = "0.7.1", optional = true } log = { version = "0.4.14", optional = true } From 6a9b58deb91caa3481eb7ef7b2b48641bd6a2d68 Mon Sep 17 00:00:00 2001 From: James Lawrence Date: Sat, 20 Nov 2021 05:14:53 -0500 Subject: [PATCH 14/31] expose api for display reinitialization support. --- .../src/backend/wayland/application.rs | 30 ++- .../src/backend/wayland/surfaces/buffers.rs | 2 +- .../backend/wayland/surfaces/layershell.rs | 248 ++++++++++++------ .../src/backend/wayland/surfaces/mod.rs | 9 +- .../src/backend/wayland/surfaces/popup.rs | 29 +- .../src/backend/wayland/surfaces/surface.rs | 216 ++++++++------- .../src/backend/wayland/surfaces/toplevel.rs | 39 +-- druid-shell/src/backend/wayland/window.rs | 25 +- 8 files changed, 366 insertions(+), 232 deletions(-) diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index 7760e5ec64..e15bbbdc04 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -41,7 +41,6 @@ use wayland_client::{ wl_shm::{self, WlShm}, wl_surface::WlSurface, }, - Proxy, }; use wayland_cursor::CursorTheme; use wayland_protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1; @@ -184,7 +183,7 @@ impl Application { tracing::trace!("invalidate_rect initiated"); if interface.as_str() == "wl_output" && version >= 3 { let output = registry.bind::(3, id); - let output = Output::new(output); + let output = Output::new(id, output); let output_id = output.id(); output.wl_output.quick_assign(with_cloned!(weak_outputs; move |_, event, _| { match weak_outputs.upgrade().unwrap().borrow_mut().get_mut(&output_id) { @@ -608,6 +607,9 @@ impl From> for surfaces::CompositorHandle { #[derive(Debug, Clone)] pub struct Output { wl_output: wl::Main, + wl_proxy: wl::Proxy, + /// global id of surface. + pub gid: u32, pub x: i32, pub y: i32, pub physical_width: i32, @@ -629,9 +631,11 @@ pub struct Output { impl Output { // All the stuff before `current_mode` will be filled out immediately after creation, so these // dummy values will never be observed. - fn new(wl_output: wl::Main) -> Self { + fn new(id: u32, wl_output: wl::Main) -> Self { Output { - wl_output, + wl_output: wl_output.clone(), + wl_proxy: wl::Proxy::from(wl_output.detach()), + gid: id, x: 0, y: 0, physical_width: 0, @@ -640,7 +644,6 @@ impl Output { make: "".into(), model: "".into(), transform: Transform::Normal, - current_mode: None, preferred_mode: None, scale: 1, // the spec says if there is no scale event, assume 1. @@ -652,7 +655,7 @@ impl Output { /// Get the wayland object ID for this output. This is how we key outputs in our global /// registry. pub fn id(&self) -> u32 { - Proxy::from(self.wl_output.detach()).id() + self.wl_proxy.id() } /// Incorporate update data from the server for this output. @@ -671,13 +674,22 @@ impl Output { } => { self.x = x; self.y = y; - self.physical_width = physical_width; - self.physical_height = physical_height; self.subpixel = subpixel; self.make = make; self.model = model; self.transform = transform; self.update_in_progress = true; + + match transform { + wl_output::Transform::Flipped270 | wl_output::Transform::_270 => { + self.physical_width = physical_height; + self.physical_height = physical_width; + }, + _ => { + self.physical_width = physical_width; + self.physical_height = physical_height; + } + } } wl_output::Event::Mode { flags, @@ -709,7 +721,7 @@ impl Output { self.scale = factor; self.update_in_progress = true; } - _ => (), // ignore possible future events + _ => tracing::warn!("unknown output event {:?}", evt), // ignore possible future events } } diff --git a/druid-shell/src/backend/wayland/surfaces/buffers.rs b/druid-shell/src/backend/wayland/surfaces/buffers.rs index 5ea9335279..c8ba629ba8 100644 --- a/druid-shell/src/backend/wayland/surfaces/buffers.rs +++ b/druid-shell/src/backend/wayland/surfaces/buffers.rs @@ -234,7 +234,7 @@ impl Buffers { /// Signal to wayland that the pending buffer is ready to be presented, and switch the next /// buffer to be the pending one. pub(crate) fn attach(&self, window: &surface::Data) { - self.with_pending_buffer(|buf| buf.unwrap().attach(&window.wl_surface)); + self.with_pending_buffer(|buf| buf.unwrap().attach(&window.wl_surface.borrow())); self.pending.set((self.pending.get() + 1) % N); } diff --git a/druid-shell/src/backend/wayland/surfaces/layershell.rs b/druid-shell/src/backend/wayland/surfaces/layershell.rs index 85357466aa..2f8afb1ac2 100644 --- a/druid-shell/src/backend/wayland/surfaces/layershell.rs +++ b/druid-shell/src/backend/wayland/surfaces/layershell.rs @@ -5,6 +5,8 @@ use crate::kurbo; use crate::window; use super::super::error; +use super::super::application::{Output}; +use super::Outputs; use super::popup; use super::surface; use super::Compositor; @@ -14,34 +16,30 @@ use super::Popup; use super::PopupHandle; struct Inner { - wl_surface: surface::Handle, - ls_surface: wlc::Main, + config: Config, + wl_surface: std::cell::RefCell, + ls_surface: std::cell::RefCell>, + requires_initialization: std::cell::RefCell, + available: std::cell::RefCell, } impl Drop for Inner { fn drop(&mut self) { - self.ls_surface.destroy(); + self.ls_surface.borrow().destroy(); } } impl Popup for Inner { fn popup_impl(&self, p: &popup::Surface) -> Result<(), error::Error> { - tracing::info!("layershell get popup initiated"); - self.ls_surface.get_popup(&p.get_xdg_popup()); + self.ls_surface.borrow().get_popup(&p.get_xdg_popup()); p.commit(); Ok(()) } } -impl From for u32 { - fn from(s: Inner) -> u32 { - u32::from(s.wl_surface.clone()) - } -} - impl From for std::sync::Arc { fn from(s: Inner) -> std::sync::Arc { - std::sync::Arc::::from(s.wl_surface.clone()) + std::sync::Arc::::from(s.wl_surface.borrow().clone()) } } @@ -128,18 +126,13 @@ impl Config { self } - fn apply(self, surface: &Surface) { + fn apply(&self, surface: &Surface) { surface.initialize_dimensions(self.initial_size); - surface - .inner - .ls_surface - .set_exclusive_zone(self.exclusive_zone); - surface.inner.ls_surface.set_anchor(self.anchor); - surface - .inner - .ls_surface - .set_keyboard_interactivity(self.keyboard_interactivity); - surface.inner.ls_surface.set_margin( + let ls = surface.inner.ls_surface.borrow(); + ls.set_exclusive_zone(self.exclusive_zone); + ls.set_anchor(self.anchor); + ls.set_keyboard_interactivity(self.keyboard_interactivity); + ls.set_margin( self.margin.top, self.margin.right, self.margin.bottom, @@ -175,9 +168,9 @@ impl Surface { config: Config, ) -> Self { let compositor = CompositorHandle::new(c); - let wl_surface = surface::Handle::new(compositor.clone(), handler, kurbo::Size::ZERO); + let wl_surface = surface::Surface::new(compositor.clone(), handler, kurbo::Size::ZERO); let ls_surface = compositor.zwlr_layershell_v1().get_layer_surface( - &wl_surface.inner.wl_surface, + &wl_surface.inner.wl_surface.borrow(), None, config.layer, config.namespace.to_string(), @@ -185,53 +178,15 @@ impl Surface { let handle = Self { inner: std::sync::Arc::new(Inner { - wl_surface, - ls_surface, + config: config.clone(), + wl_surface: std::cell::RefCell::new(wl_surface), + ls_surface: std::cell::RefCell::new(ls_surface), + requires_initialization: std::cell::RefCell::new(true), + available: std::cell::RefCell::new(false), }), }; - handle.inner.wl_surface.set_popup_impl(PopupHandle { - inner: handle.inner.clone(), - }); - handle.inner.ls_surface.quick_assign({ - let handle = handle.clone(); - let mut dim = config.initial_size.clone(); - move |a1, event, a2| match event { - layershell::zwlr_layer_surface_v1::Event::Configure { - serial, - width, - height, - } => { - tracing::info!("event {:?} {:?} {:?}", a1, event, a2); - // compositor is deferring to the client for determining the size - // when values are zero. - if width != 0 && height != 0 { - dim = kurbo::Size::new(width as f64, height as f64); - } - - handle.inner.ls_surface.ack_configure(serial); - handle - .inner - .ls_surface - .set_size(dim.width as u32, dim.height as u32); - handle - .inner - .wl_surface - .update_dimensions(dim.width as u32, dim.height as u32); - handle - .inner - .wl_surface - .inner - .buffers - .request_paint(&handle.inner.wl_surface.inner); - } - _ => tracing::info!("unimplemented event {:?} {:?} {:?}", a1, event, a2), - } - }); - - config.apply(&handle); - handle.inner.wl_surface.commit(); - + Surface::initialize(&handle); handle } @@ -245,37 +200,172 @@ impl Surface { fn initialize_dimensions(&self, dim: kurbo::Size) { self.inner .ls_surface + .borrow() .set_size(dim.width as u32, dim.height as u32); - self.inner.wl_surface.inner.handler.borrow_mut().size(dim); + self.inner.wl_surface.borrow().inner.handler.borrow_mut().size(dim); + } + + fn initialize(handle: &Surface) { + if !handle.inner.requires_initialization.replace(false) { + return + } + + tracing::info!("attempting to initialize layershell"); + handle.inner.wl_surface.borrow().set_popup_impl(PopupHandle { + inner: handle.inner.clone(), + }); + + handle.inner.ls_surface.borrow().quick_assign({ + let handle = handle.clone(); + move |a1, event, a2| { + tracing::debug!("consuming event {:?} {:?} {:?}", a1, event, a2); + Surface::consume_layershell_event(&handle, &a1, &event, &a2); + } + }); + + handle.inner.config.apply(&handle); + handle.inner.wl_surface.borrow().commit(); + } + + fn consume_layershell_event(handle: &Surface, a1: &wlc::Main, event: &layershell::zwlr_layer_surface_v1::Event, data: &wlc::DispatchData) { + match *event { + layershell::zwlr_layer_surface_v1::Event::Configure { + serial, + width, + height, + } => { + let mut dim = handle.inner.config.initial_size.clone(); + // compositor is deferring to the client for determining the size + // when values are zero. + if width != 0 && height != 0 { + dim = kurbo::Size::new(width as f64, height as f64); + } + + let ls = handle.inner.ls_surface.borrow(); + ls.ack_configure(serial); + ls.set_size(dim.width as u32, dim.height as u32); + handle.inner.wl_surface.borrow().update_dimensions(dim); + handle.inner.wl_surface.borrow().request_paint(); + handle.inner.available.replace(true); + } + layershell::zwlr_layer_surface_v1::Event::Closed => { + handle.inner.ls_surface.borrow().destroy(); + handle.inner.available.replace(false); + handle.inner.requires_initialization.replace(true); + } + _ => tracing::warn!("unimplemented event {:?} {:?} {:?}", a1, event, data), + } } } -impl From for u32 { - fn from(s: Surface) -> u32 { - u32::from(&s.inner.wl_surface) +impl Outputs for Surface { + fn removed(&self, o: &Output) { + self.inner.wl_surface.borrow().removed(o); + } + + fn inserted(&self, o: &Output) { + if !self.inner.requires_initialization.borrow().clone() { + tracing::debug!("skipping reinitialization output for layershell {:?} {:?}", o.gid, o.id()); + return + } + + tracing::debug!("reinitializing output for layershell {:?} {:?} {:?}", o.gid, o.id(), o); + let sdata = self.inner.wl_surface.borrow().inner.clone(); + let replacedsurface = self.inner.wl_surface.replace(surface::Surface::replace(&sdata)); + let sdata = self.inner.wl_surface.borrow().inner.clone(); + let replacedlayershell = self.inner.ls_surface.replace(sdata.compositor.zwlr_layershell_v1().get_layer_surface( + &self.inner.wl_surface.borrow().inner.wl_surface.borrow(), + None, + self.inner.config.layer, + self.inner.config.namespace.to_string(), + )); + Surface::initialize(&self); + + tracing::debug!("replaced surface {:p}", &replacedsurface); + tracing::debug!("current surface {:p}", &self.inner.wl_surface.borrow()); + tracing::debug!("replaced layershell {:p}", &replacedlayershell); + tracing::debug!("current layershell {:p}", &self.inner.ls_surface.borrow()); } } -impl From<&Surface> for u32 { - fn from(s: &Surface) -> u32 { - u32::from(&s.inner.wl_surface) +impl Handle for Surface { + fn get_size(&self) -> kurbo::Size { + return self.inner.wl_surface.borrow().get_size(); + } + + fn set_size(&self, dim: kurbo::Size) { + return self.inner.wl_surface.borrow().set_size(dim); + } + + fn request_anim_frame(&self) { + if *self.inner.available.borrow() { + self.inner.wl_surface.borrow().request_anim_frame() + } + } + + fn invalidate(&self) { + return self.inner.wl_surface.borrow().invalidate(); + } + + fn invalidate_rect(&self, rect: kurbo::Rect) { + return self.inner.wl_surface.borrow().invalidate_rect(rect); + } + + fn remove_text_field(&self, token: crate::TextFieldToken) { + return self.inner.wl_surface.borrow().remove_text_field(token); + } + + fn set_focused_text_field(&self, active_field: Option) { + return self.inner.wl_surface.borrow().set_focused_text_field(active_field); + } + + fn get_idle_handle(&self) -> super::idle::Handle { + return self.inner.wl_surface.borrow().get_idle_handle(); + } + + fn get_scale(&self) -> crate::Scale { + return self.inner.wl_surface.borrow().get_scale(); + } + + fn run_idle(&self) { + if *self.inner.available.borrow() { + self.inner.wl_surface.borrow().run_idle(); + } + } + + fn popup(&self, popup: &popup::Surface) -> Result<(), error::Error> { + return self.inner.wl_surface.borrow().popup(popup); + } + + fn release(&self) { + return self.inner.wl_surface.borrow().release(); + } + + fn data(&self) -> Option> { + return self.inner.wl_surface.borrow().data(); } } impl From<&Surface> for std::sync::Arc { fn from(s: &Surface) -> std::sync::Arc { - std::sync::Arc::::from(s.inner.wl_surface.clone()) + std::sync::Arc::::from(s.inner.wl_surface.borrow().clone()) } } impl From for std::sync::Arc { fn from(s: Surface) -> std::sync::Arc { - std::sync::Arc::::from(s.inner.wl_surface.clone()) + std::sync::Arc::::from(s.inner.wl_surface.borrow().clone()) } } impl From for Box { fn from(s: Surface) -> Box { - Box::new(s.inner.wl_surface.clone()) as Box + Box::new(s) as Box } } + +impl From for Box { + fn from(s: Surface) -> Box { + Box::new(s) as Box + } +} \ No newline at end of file diff --git a/druid-shell/src/backend/wayland/surfaces/mod.rs b/druid-shell/src/backend/wayland/surfaces/mod.rs index a1671a5296..0ac6aec82b 100644 --- a/druid-shell/src/backend/wayland/surfaces/mod.rs +++ b/druid-shell/src/backend/wayland/surfaces/mod.rs @@ -20,6 +20,7 @@ pub mod surface; pub mod toplevel; pub static GLOBAL_ID: crate::Counter = crate::Counter::new(); + pub trait Compositor { fn output(&self, id: &u32) -> Option; fn create_surface(&self) -> wlc::Main; @@ -55,6 +56,11 @@ impl PopupHandle { } } +pub(super) trait Outputs { + fn removed(&self, o: &application::Output); + fn inserted(&self, o: &application::Output); +} + // handle on given surface. pub trait Handle { fn get_size(&self) -> kurbo::Size; @@ -99,8 +105,9 @@ impl CompositorHandle { Some(c) => c, None => panic!("should never recompute scale of window that has been dropped"), }; - + tracing::debug!("computing scale using {:?} outputs", outputs.len()); let scale = outputs.iter().fold(0, |scale, id| { + tracing::debug!("recomputing scale using output {:?}", id); match compositor.output(id) { None => { tracing::warn!( diff --git a/druid-shell/src/backend/wayland/surfaces/popup.rs b/druid-shell/src/backend/wayland/surfaces/popup.rs index 2cae463767..88d5bcaeaa 100644 --- a/druid-shell/src/backend/wayland/surfaces/popup.rs +++ b/druid-shell/src/backend/wayland/surfaces/popup.rs @@ -6,22 +6,17 @@ use wayland_protocols::xdg_shell::client::xdg_surface; use crate::kurbo; use crate::window; +use super::Outputs; use super::surface; use super::Compositor; use super::CompositorHandle; use super::Handle; struct Inner { - wl_surface: surface::Handle, + wl_surface: surface::Surface, wl_xdg_popup: wlc::Main, } -impl From for u32 { - fn from(s: Inner) -> u32 { - u32::from(s.wl_surface.clone()) - } -} - impl From for std::sync::Arc { fn from(s: Inner) -> std::sync::Arc { std::sync::Arc::::from(s.wl_surface.clone()) @@ -85,8 +80,8 @@ impl Surface { parent: Option<&xdg_surface::XdgSurface>, ) -> Self { let compositor = CompositorHandle::new(c); - let wl_surface = surface::Handle::new(compositor.clone(), handler, kurbo::Size::ZERO); - let wl_xdg_surface = compositor.get_xdg_surface(&wl_surface.inner.wl_surface); + let wl_surface = surface::Surface::new(compositor.clone(), handler, kurbo::Size::ZERO); + let wl_xdg_surface = compositor.get_xdg_surface(&wl_surface.inner.wl_surface.borrow()); // register to receive xdg_surface events. wl_xdg_surface.quick_assign({ @@ -96,7 +91,7 @@ impl Surface { xdg_surface.ack_configure(serial); let dim = wl_surface.inner.logical_size.get(); wl_surface.inner.handler.borrow_mut().size(dim); - wl_surface.inner.buffers.request_paint(&wl_surface.inner); + wl_surface.request_paint(); } _ => tracing::warn!("unhandled xdg_surface event {:?}", event), } @@ -125,7 +120,7 @@ impl Surface { width, height ); - wl_surface.update_dimensions(width as u32, height as u32); + wl_surface.update_dimensions((width as f64, height as f64)); } _ => tracing::warn!("unhandled xdg_popup event configure {:?}", event), }; @@ -159,15 +154,9 @@ impl Surface { } } -impl From for u32 { - fn from(s: Surface) -> u32 { - u32::from(&s.inner.wl_surface) - } -} - -impl From<&Surface> for u32 { - fn from(s: &Surface) -> u32 { - u32::from(&s.inner.wl_surface) +impl From for Box { + fn from(s: Surface) -> Box { + Box::new(s.inner.wl_surface.clone()) as Box } } diff --git a/druid-shell/src/backend/wayland/surfaces/surface.rs b/druid-shell/src/backend/wayland/surfaces/surface.rs index a05009cbb4..e7a5b246a5 100644 --- a/druid-shell/src/backend/wayland/surfaces/surface.rs +++ b/druid-shell/src/backend/wayland/surfaces/surface.rs @@ -3,6 +3,7 @@ use std::rc::Rc; use wayland_client as wlc; use wayland_client::protocol::wl_surface; +use crate::backend::application::{Output}; use crate::kurbo; use crate::window; use crate::{piet::Piet, region::Region, scale::Scale, TextFieldToken}; @@ -13,7 +14,7 @@ use super::buffers; use super::error; use super::idle; use super::popup; -use super::{Compositor, CompositorHandle, Decor, Handle as SurfaceHandle, PopupHandle}; +use super::{Compositor, CompositorHandle, Decor, Handle, PopupHandle, Outputs}; pub enum DeferredTask { Paint, @@ -21,11 +22,19 @@ pub enum DeferredTask { } #[derive(Clone)] -pub struct Handle { +pub struct Surface { pub(super) inner: std::sync::Arc, } -impl Handle { +impl From> for Surface { + fn from(d: std::sync::Arc) -> Self { + Self { + inner: d, + } + } +} + +impl Surface { pub fn new( c: impl Into, handler: Box, @@ -39,7 +48,7 @@ impl Handle { let current = std::sync::Arc::new(Data { compositor: compositor.clone(), - wl_surface, + wl_surface: RefCell::new(wl_surface), outputs: RefCell::new(std::collections::HashSet::new()), buffers: buffers::Buffers::new(compositor.shared_mem(), initial_size.into()), logical_size: Cell::new(initial_size), @@ -54,44 +63,21 @@ impl Handle { }); // register to receive wl_surface events. - current.wl_surface.quick_assign({ - let current = current.clone(); - move |_, event, _| { - tracing::info!("wl_surface event {:?}", event); - match event { - wl_surface::Event::Enter { output } => { - current - .outputs - .borrow_mut() - .insert(wlc::Proxy::from(output).id()); - } - wl_surface::Event::Leave { output } => { - current - .outputs - .borrow_mut() - .remove(&wlc::Proxy::from(output).id()); - } - _ => tracing::warn!("unhandled wayland surface event {:?}", event), - } - - let new_scale = current.recompute_scale(); - if current.set_scale(new_scale).is_changed() { - current.wl_surface.set_buffer_scale(new_scale); - // We also need to change the physical size to match the new scale - current.buffers.set_size( - buffers::RawSize::from(current.logical_size.get()).scale(new_scale), - ); - // always repaint, because the scale changed. - current.schedule_deferred_task(DeferredTask::Paint); - } - } - }); + Surface::initsurface(¤t); return Self { inner: current }; } - pub(super) fn update_dimensions(&self, width: u32, height: u32) -> kurbo::Size { - self.inner.update_dimensions(width, height) + pub(super) fn request_paint(&self) { + self.inner.buffers.request_paint(&self.inner); + } + + pub(super) fn update_dimensions(&self, dim: impl Into) -> kurbo::Size { + self.inner.update_dimensions(dim) + } + + pub(super) fn resize(&self, dim: kurbo::Size) -> kurbo::Size { + self.inner.resize(dim) } pub(super) fn set_popup_impl(&self, imp: PopupHandle) { @@ -99,11 +85,73 @@ impl Handle { } pub(super) fn commit(&self) { - self.inner.wl_surface.commit() + self.inner.wl_surface.borrow().commit() + } + + pub(super) fn replace(current: &std::sync::Arc) -> Surface { + current.wl_surface.replace(match current.compositor.create_surface() { + None => panic!("unable to create surface"), + Some(v) => v, + }); + Surface::initsurface(¤t); + Self { + inner: current.clone(), + } + } + + fn initsurface(current: &std::sync::Arc) { + current.wl_surface.borrow().quick_assign({ + let current = current.clone(); + move |a, event, b| { + tracing::info!("wl_surface event {:?} {:?} {:?}", a, event, b); + Surface::consume_surface_event(¤t, &a, &event, &b); + } + }); + } + + pub(super) fn consume_surface_event(current: &std::sync::Arc, surface: &wlc::Main, event: &wlc::protocol::wl_surface::Event, data: &wlc::DispatchData) { + tracing::info!("wl_surface event {:?} {:?} {:?}", surface, event, data); + match event { + wl_surface::Event::Enter { output } => { + let proxy = wlc::Proxy::from(output.clone()); + current + .outputs + .borrow_mut() + .insert(proxy.id()); + }, + wl_surface::Event::Leave { output } => { + current + .outputs + .borrow_mut() + .remove(&wlc::Proxy::from(output.clone()).id()); + } + _ => tracing::warn!("unhandled wayland surface event {:?}", event), + } + + let new_scale = current.recompute_scale(); + if current.set_scale(new_scale).is_changed() { + current.wl_surface.borrow().set_buffer_scale(new_scale); + // We also need to change the physical size to match the new scale + current.buffers.set_size( + buffers::RawSize::from(current.logical_size.get()).scale(new_scale), + ); + // always repaint, because the scale changed. + current.schedule_deferred_task(DeferredTask::Paint); + } } } -impl SurfaceHandle for Handle { +impl Outputs for Surface { + fn removed(&self, o: &Output) { + self.inner.outputs.borrow_mut().remove(&o.id()); + } + + fn inserted(&self, _: &Output) { + // nothing to do here. + } +} + +impl Handle for Surface { fn get_size(&self) -> kurbo::Size { self.inner.get_size() } @@ -157,35 +205,21 @@ impl SurfaceHandle for Handle { } } -impl From for u32 { - fn from(s: Handle) -> u32 { - use std::ops::Deref; - u32::from(s.inner.deref()) - } -} - -impl From<&Handle> for u32 { - fn from(s: &Handle) -> u32 { - use std::ops::Deref; - u32::from(s.inner.deref()) - } -} - -impl From for std::sync::Arc { - fn from(s: Handle) -> std::sync::Arc { +impl From for std::sync::Arc { + fn from(s: Surface) -> std::sync::Arc { std::sync::Arc::::from(s.inner.clone()) } } -impl From<&Handle> for std::sync::Arc { - fn from(s: &Handle) -> std::sync::Arc { +impl From<&Surface> for std::sync::Arc { + fn from(s: &Surface) -> std::sync::Arc { std::sync::Arc::::from(s.inner.clone()) } } pub struct Data { pub(super) compositor: CompositorHandle, - pub(super) wl_surface: wlc::Main, + pub(super) wl_surface: RefCell>, /// The outputs that our surface is present on (we should get the first enter event early). pub(super) outputs: RefCell>, @@ -251,8 +285,9 @@ impl Data { } } - pub(super) fn update_dimensions(&self, width: u32, height: u32) -> kurbo::Size { - let dim = kurbo::Size::from((width as f64, height as f64)); + pub(super) fn update_dimensions(&self, dim: impl Into) -> kurbo::Size { + // let dim = kurbo::Size::from((width as f64, height as f64)); + let dim = dim.into(); if self.logical_size.get() != self.resize(dim) { match self.handler.try_borrow_mut() { Ok(mut handler) => handler.size(dim), @@ -340,7 +375,7 @@ impl Data { for rect in damaged_region.rects() { // Convert it to physical coordinate space. let rect = buffers::RawRect::from(*rect).scale(self.scale.get()); - self.wl_surface.damage_buffer( + self.wl_surface.borrow().damage_buffer( rect.x0, rect.y0, rect.x1 - rect.x0, @@ -398,12 +433,11 @@ impl Data { // The handler must not be already borrowed. This may mean deferring this call. self.handler.borrow_mut().paint(&mut piet, &*region); } - // reset damage ready for next frame. self.damaged_region.borrow_mut().clear(); - self.buffers.attach(&self); - self.wl_surface.commit(); + self.buffers.attach(self); + self.wl_surface.borrow().commit(); } /// Request invalidation of the entire window contents. @@ -464,7 +498,7 @@ impl Data { } pub(super) fn wayland_surface_id(&self) -> u32 { - u32::from(self) + wlc::Proxy::from(self.wl_surface.borrow().detach()).id() } pub(super) fn get_size(&self) -> kurbo::Size { @@ -478,15 +512,15 @@ impl Data { } pub(super) fn request_anim_frame(&self) { - let idle = self.get_idle_handle(); - - if !self.anim_frame_requested.get() { - self.anim_frame_requested.set(true); - idle.add_idle_callback(move |winhandle| { - winhandle.prepare_paint(); - }); - self.schedule_deferred_task(DeferredTask::AnimationClear); + if self.anim_frame_requested.replace(true) { + return } + + let idle = self.get_idle_handle(); + idle.add_idle_callback(move |winhandle| { + winhandle.prepare_paint(); + }); + self.schedule_deferred_task(DeferredTask::AnimationClear); } pub(super) fn remove_text_field(&self, token: TextFieldToken) { @@ -527,25 +561,7 @@ impl Data { } pub(super) fn release(&self) { - self.wl_surface.destroy() - } -} - -impl From for u32 { - fn from(s: Data) -> u32 { - wlc::Proxy::from(s.wl_surface.detach()).id() - } -} - -impl From<&Data> for u32 { - fn from(s: &Data) -> u32 { - wlc::Proxy::from(s.wl_surface.detach()).id() - } -} - -impl From> for Handle { - fn from(d: std::sync::Arc) -> Handle { - Handle { inner: d.clone() } + self.wl_surface.borrow().destroy(); } } @@ -563,13 +579,25 @@ impl From for Box { } } +impl From for Box { + fn from(d: Dead) -> Box { + Box::new(d) as Box + } +} + impl Decor for Dead { fn inner_set_title(&self, title: String) { tracing::warn!("set_title not implemented for this surface: {:?}", title); } } -impl SurfaceHandle for Dead { +impl Outputs for Dead { + fn removed(&self, _: &Output) {} + + fn inserted(&self, _: &Output) {} +} + +impl Handle for Dead { fn get_size(&self) -> kurbo::Size { kurbo::Size::ZERO } diff --git a/druid-shell/src/backend/wayland/surfaces/toplevel.rs b/druid-shell/src/backend/wayland/surfaces/toplevel.rs index c719724cdf..026ca327ae 100644 --- a/druid-shell/src/backend/wayland/surfaces/toplevel.rs +++ b/druid-shell/src/backend/wayland/surfaces/toplevel.rs @@ -6,6 +6,7 @@ use wayland_protocols::xdg_shell::client::xdg_toplevel; use crate::kurbo; use crate::window; +use super::Outputs; use super::surface; use super::Compositor; use super::CompositorHandle; @@ -13,7 +14,7 @@ use super::Decor; use super::Handle; struct Inner { - wl_surface: surface::Handle, + wl_surface: surface::Surface, #[allow(unused)] pub(super) xdg_surface: wlc::Main, pub(super) xdg_toplevel: wlc::Main, @@ -22,12 +23,6 @@ struct Inner { wlc::Main, } -impl From for u32 { - fn from(s: Inner) -> u32 { - u32::from(s.wl_surface) - } -} - impl From for std::sync::Arc { fn from(s: Inner) -> std::sync::Arc { std::sync::Arc::::from(s.wl_surface) @@ -47,8 +42,8 @@ impl Surface { min_size: Option, ) -> Self { let compositor = CompositorHandle::new(c); - let wl_surface = surface::Handle::new(compositor.clone(), handler, kurbo::Size::ZERO); - let xdg_surface = compositor.get_xdg_surface(&wl_surface.inner.wl_surface); + let wl_surface = surface::Surface::new(compositor.clone(), handler, kurbo::Size::ZERO); + let xdg_surface = compositor.get_xdg_surface(&wl_surface.inner.wl_surface.borrow()); let xdg_toplevel = xdg_surface.get_toplevel(); let zxdg_toplevel_decoration_v1 = compositor .zxdg_decoration_manager_v1() @@ -62,15 +57,13 @@ impl Surface { match event { xdg_surface::Event::Configure { serial } => { xdg_surface.ack_configure(serial); - let dim = wl_surface.inner.logical_size.get(); - wl_surface.inner.handler.borrow_mut().size(dim); - wl_surface.inner.buffers.request_paint(&wl_surface.inner); + wl_surface.resize(wl_surface.get_size()); + wl_surface.request_paint(); } _ => tracing::warn!("unhandled xdg_surface event {:?}", event), } } }); - xdg_toplevel.quick_assign({ let wl_surface = wl_surface.clone(); let mut dim = initial_size.clone(); @@ -92,7 +85,7 @@ impl Surface { if width != 0 && height != 0 { dim = kurbo::Size::new(width as f64, height as f64); } - wl_surface.update_dimensions(dim.width as u32, dim.height as u32); + wl_surface.update_dimensions(dim); } xdg_toplevel::Event::Close => { tracing::info!("xdg close event {:?}", event); @@ -150,18 +143,6 @@ impl Decor for Surface { } } -impl From for u32 { - fn from(s: Surface) -> u32 { - u32::from(s.inner.wl_surface.clone()) - } -} - -impl From<&Surface> for u32 { - fn from(s: &Surface) -> u32 { - u32::from(s.inner.wl_surface.clone()) - } -} - impl From<&Surface> for std::sync::Arc { fn from(s: &Surface) -> std::sync::Arc { std::sync::Arc::::from(s.inner.wl_surface.clone()) @@ -185,3 +166,9 @@ impl From for Box { Box::new(s.clone()) as Box } } + +impl From for Box { + fn from(s: Surface) -> Box { + Box::new(s.inner.wl_surface.clone()) as Box + } +} \ No newline at end of file diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index 241aeea9d3..3269930260 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -16,7 +16,7 @@ use tracing; use super::{ - application::{Application, ApplicationData, Timer}, + application::{Application, ApplicationData, Timer, Output}, error, menu::Menu, surfaces, @@ -41,6 +41,7 @@ struct Inner { pub(super) id: u64, pub(super) decor: Box, pub(super) surface: Box, + pub(super) outputs: Box, pub(super) appdata: std::sync::Weak, } @@ -49,8 +50,19 @@ pub struct WindowHandle { inner: std::sync::Arc, } +impl surfaces::Outputs for WindowHandle { + fn removed(&self, o: &Output) { + self.inner.outputs.removed(o) + } + + fn inserted(&self, o: &Output) { + self.inner.outputs.inserted(o) + } +} + impl WindowHandle { pub(super) fn new( + outputs: impl Into>, decor: impl Into>, surface: impl Into>, appdata: impl Into>, @@ -58,6 +70,7 @@ impl WindowHandle { Self { inner: std::sync::Arc::new(Inner { id: surfaces::GLOBAL_ID.next(), + outputs: outputs.into(), decor: decor.into(), surface: surface.into(), appdata: appdata.into(), @@ -299,6 +312,7 @@ impl std::default::Default for WindowHandle { WindowHandle { inner: std::sync::Arc::new(Inner { id: surfaces::GLOBAL_ID.next(), + outputs: Box::new(surfaces::surface::Dead::default()), decor: Box::new(surfaces::surface::Dead::default()), surface: Box::new(surfaces::surface::Dead::default()), appdata: std::sync::Weak::new(), @@ -407,7 +421,12 @@ impl WindowBuilder { (&surface as &dyn surfaces::Decor).set_title(self.title); - let handle = WindowHandle::new(surface.clone(), surface.clone(), self.app_data.clone()); + let handle = WindowHandle::new( + surface.clone(), + surface.clone(), + surface.clone(), + self.app_data.clone(), + ); if let Some(_) = appdata .handles @@ -481,6 +500,7 @@ pub mod layershell { let surface = surfaces::layershell::Surface::new(appdata.clone(), winhandle, updated); let handle = WindowHandle::new( + surface.clone(), surfaces::surface::Dead::default(), surface.clone(), self.appdata.clone(), @@ -562,6 +582,7 @@ pub mod popup { } let handle = WindowHandle::new( + surface.clone(), surfaces::surface::Dead::default(), surface.clone(), self.appdata.clone(), From c800f00015c680e28ba51b4631b1beaa7468e709 Mon Sep 17 00:00:00 2001 From: James Lawrence Date: Sat, 20 Nov 2021 05:37:02 -0500 Subject: [PATCH 15/31] handle output added/removed events on eventqueue. --- .../src/backend/wayland/application.rs | 173 +++++++++++------- 1 file changed, 110 insertions(+), 63 deletions(-) diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index e15bbbdc04..cf7863f6ca 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -143,6 +143,9 @@ pub(crate) struct ApplicationData { pub(super) pointer: pointers::Pointer, /// reference to the keyboard events manager. keyboard: keyboard::Manager, + // wakeup events when outputs are added/removed. + outputs_removed: RefCell>>, + outputs_added: RefCell>>, } impl Application { @@ -166,69 +169,84 @@ impl Application { let outputs: Rc>> = Rc::new(RefCell::new(BTreeMap::new())); let seats: Rc>>>> = Rc::new(RefCell::new(BTreeMap::new())); - // This object will create a container for the global wayland objects, and request that // it is populated by the server. Doesn't take ownership of the registry, we are // responsible for keeping it alive. let weak_outputs = Rc::downgrade(&outputs); let weak_seats = Rc::downgrade(&seats); + + let (outputsremovedtx, outputsremovedrx) = calloop::channel::channel::(); + let (outputsaddedtx, outputsaddedrx) = calloop::channel::channel::(); + let globals = wl::GlobalManager::new_with_cb( - &attached_server, - move |event, registry, _| match event { - wl::GlobalEvent::New { - id, - interface, - version, - } => { - tracing::trace!("invalidate_rect initiated"); - if interface.as_str() == "wl_output" && version >= 3 { - let output = registry.bind::(3, id); - let output = Output::new(id, output); - let output_id = output.id(); - output.wl_output.quick_assign(with_cloned!(weak_outputs; move |_, event, _| { - match weak_outputs.upgrade().unwrap().borrow_mut().get_mut(&output_id) { - Some(o) => o.process_event(event), - None => tracing::warn!( - "wayland sent an event for an output that doesn't exist {:?} {:?}", - &output_id, - &event, - ), + &attached_server, { + move |event, registry, data| { + tracing::debug!("global manager event received {:?}\n{:?}\n{:?}", event, registry, data); + match event { + wl::GlobalEvent::New { + id, + interface, + version, + } => { + if interface.as_str() == "wl_output" && version >= 3 { + let output = registry.bind::(3, id); + let output = Output::new(id, output); + let oid = output.id(); + let gid = output.gid; + let previous = weak_outputs.upgrade().unwrap().borrow_mut().insert(oid, output.clone()); + assert!(previous.is_none(), "internal: wayland should always use new IDs"); + tracing::trace!("output added {:?} {:?}", gid, oid); + output.wl_output.quick_assign(with_cloned!(weak_outputs, gid, oid, outputsaddedtx; move |a, event, b| { + tracing::trace!("output event {:?} {:?} {:?}", a, event, b); + match weak_outputs.upgrade().unwrap().borrow_mut().get_mut(&oid) { + Some(o) => o.process_event(event, &outputsaddedtx), + None => tracing::warn!( + "wayland sent an event for an output that doesn't exist global({:?}) proxy({:?}) {:?}", + &gid, + &oid, + &event, + ), + } + })); + } else if interface.as_str() == "wl_seat" && version >= 7 { + let new_seat = registry.bind::(7, id); + let prev_seat = weak_seats + .upgrade() + .unwrap() + .borrow_mut() + .insert(id, Rc::new(RefCell::new(Seat::new(new_seat)))); + assert!( + prev_seat.is_none(), + "internal: wayland should always use new IDs" + ); + // Defer setting up the pointer/keyboard event handling until we've + // finished constructing the `Application`. That way we can pass it as a + // parameter. } - })); - let prev_output = weak_outputs - .upgrade() - .unwrap() - .borrow_mut() - .insert(output_id, output); - assert!( - prev_output.is_none(), - "internal: wayland should always use new IDs" - ); - } else if interface.as_str() == "wl_seat" && version >= 7 { - let new_seat = registry.bind::(7, id); - let prev_seat = weak_seats - .upgrade() - .unwrap() - .borrow_mut() - .insert(id, Rc::new(RefCell::new(Seat::new(new_seat)))); - assert!( - prev_seat.is_none(), - "internal: wayland should always use new IDs" - ); - // Defer setting up the pointer/keyboard event handling until we've - // finished constructing the `Application`. That way we can pass it as a - // parameter. - } - } - wl::GlobalEvent::Removed { id, interface } if interface.as_str() == "wl_output" => { - match weak_outputs.upgrade().unwrap().borrow_mut().remove(&id) { - Some(removed) => removed.wl_output.release(), - None => tracing::warn!( - "wayland sent a remove event for an output that doesn't exist" - ), + } + wl::GlobalEvent::Removed { id, interface } if interface.as_str() == "wl_output" => { + let boutputs = weak_outputs.upgrade().unwrap(); + let mut outputs = boutputs.borrow_mut(); + let removed = outputs.iter() + .find(|(_pid, o)| o.gid == id) + .map(|(pid, _)| pid.clone()) + .and_then(|id| outputs.remove(&id)); + + let result = match removed { + None => return, + Some(removed) => outputsremovedtx.send(removed), + }; + + match result { + Ok(_) => tracing::debug!("outputs remaining {:?}...", outputs.len()), + Err(cause) => tracing::error!("failed to remove output {:?}", cause), + } + } + _ => { + tracing::debug!("unhandled global manager event received {:?}", event); + }, } } - _ => (), // ignore other interfaces }, ); @@ -243,7 +261,7 @@ impl Application { }); for (id, name, version) in globals_list.into_iter() { - tracing::trace!("{:?}@{:?} - {:?}", name, version, id); + tracing::debug!("{:?}@{:?} - {:?}", name, version, id); } let xdg_base = globals @@ -309,6 +327,8 @@ impl Application { pointer, keyboard: keyboard::Manager::default(), roundtrip_requested: RefCell::new(false), + outputs_added: RefCell::new(Some(outputsaddedrx)), + outputs_removed: RefCell::new(Some(outputsremovedrx)), }); // Collect the supported image formats. @@ -373,12 +393,36 @@ impl Application { // flush pending events (otherwise anything we submitted since sync will never be sent) self.data.wl_server.flush().unwrap(); // Use calloop so we can epoll both wayland events and others (e.g. timers) - let mut event_loop = calloop::EventLoop::try_new().unwrap(); - let handle = event_loop.handle(); + let mut eventloop = calloop::EventLoop::try_new().unwrap(); + let handle = eventloop.handle(); let wayland_dispatcher = WaylandSource::new(self.data.clone()).into_dispatcher(); handle.register_dispatcher(wayland_dispatcher).unwrap(); + handle.insert_source(self.data.outputs_added.borrow_mut().take().unwrap(), { + move |evt, _ignored, appdata| { + match evt { + calloop::channel::Event::Closed => return, + calloop::channel::Event::Msg(output) => { + tracing::debug!("output added {:?} {:?}", output.gid, output.id()); + for (_, win) in appdata.handles_iter() { + surfaces::Outputs::inserted(&win, &output); + } + }, + } + } + }).unwrap(); + handle.insert_source(self.data.outputs_removed.borrow_mut().take().unwrap(), |evt, _ignored, appdata| { + match evt { + calloop::channel::Event::Closed => return, + calloop::channel::Event::Msg(output) => { + tracing::trace!("output removed {:?} {:?}", output.gid, output.id()); + for (_, win) in appdata.handles_iter() { + surfaces::Outputs::removed(&win, &output); + } + }, + } + }).unwrap(); handle .insert_source(timer_source, move |token, _metadata, appdata| { @@ -387,15 +431,15 @@ impl Application { }) .unwrap(); - let signal = event_loop.get_signal(); + let signal = eventloop.get_signal(); let handle = handle.clone(); - event_loop - .run( + eventloop.run( Duration::from_millis(20), &mut self.data.clone(), move |appdata| { if appdata.shutdown.get() { + tracing::debug!("shutting down"); signal.stop(); } @@ -535,7 +579,7 @@ impl ApplicationData { Some(s) => s, None => { // NOTE this might be expected - log::warn!("Received event for surface that doesn't exist any more"); + log::warn!("received event for surface that doesn't exist any more {:?} {:?}", expired, expired.id()); continue; } }; @@ -569,9 +613,9 @@ impl ApplicationData { match appdata.acquire_current_window() { Some(winhandle) => { tracing::trace!("idle processing initiated"); + winhandle.request_anim_frame(); winhandle.run_idle(); - // if we already flushed this cycle don't flush again. if *appdata.display_flushed.borrow() { tracing::trace!("idle repaint flushing display initiated"); @@ -659,7 +703,7 @@ impl Output { } /// Incorporate update data from the server for this output. - fn process_event(&mut self, evt: wl_output::Event) { + fn process_event(&mut self, evt: wl_output::Event, tx: &calloop::channel::Sender) { tracing::trace!("processing wayland output event {:?}", evt); match evt { wl_output::Event::Geometry { @@ -716,6 +760,9 @@ impl Output { wl_output::Event::Done => { self.update_in_progress = false; self.last_update = Instant::now(); + if let Err(cause) = tx.send(self.clone()) { + tracing::error!("unable to add output {:?} {:?}", self.gid, self.id()); + } } wl_output::Event::Scale { factor } => { self.scale = factor; From a90667e28586445cd31cb51c784237b98cfb4762 Mon Sep 17 00:00:00 2001 From: James Lawrence Date: Sat, 20 Nov 2021 05:42:45 -0500 Subject: [PATCH 16/31] formatting --- .../src/backend/wayland/application.rs | 169 ++++++++++-------- .../backend/wayland/surfaces/layershell.rs | 77 +++++--- .../src/backend/wayland/surfaces/popup.rs | 2 +- .../src/backend/wayland/surfaces/surface.rs | 40 +++-- .../src/backend/wayland/surfaces/toplevel.rs | 4 +- druid-shell/src/backend/wayland/window.rs | 2 +- 6 files changed, 176 insertions(+), 118 deletions(-) diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index cf7863f6ca..212463131d 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -178,25 +178,36 @@ impl Application { let (outputsremovedtx, outputsremovedrx) = calloop::channel::channel::(); let (outputsaddedtx, outputsaddedrx) = calloop::channel::channel::(); - let globals = wl::GlobalManager::new_with_cb( - &attached_server, { - move |event, registry, data| { - tracing::debug!("global manager event received {:?}\n{:?}\n{:?}", event, registry, data); - match event { - wl::GlobalEvent::New { - id, - interface, - version, - } => { - if interface.as_str() == "wl_output" && version >= 3 { - let output = registry.bind::(3, id); - let output = Output::new(id, output); - let oid = output.id(); - let gid = output.gid; - let previous = weak_outputs.upgrade().unwrap().borrow_mut().insert(oid, output.clone()); - assert!(previous.is_none(), "internal: wayland should always use new IDs"); - tracing::trace!("output added {:?} {:?}", gid, oid); - output.wl_output.quick_assign(with_cloned!(weak_outputs, gid, oid, outputsaddedtx; move |a, event, b| { + let globals = wl::GlobalManager::new_with_cb(&attached_server, { + move |event, registry, data| { + tracing::debug!( + "global manager event received {:?}\n{:?}\n{:?}", + event, + registry, + data + ); + match event { + wl::GlobalEvent::New { + id, + interface, + version, + } => { + if interface.as_str() == "wl_output" && version >= 3 { + let output = registry.bind::(3, id); + let output = Output::new(id, output); + let oid = output.id(); + let gid = output.gid; + let previous = weak_outputs + .upgrade() + .unwrap() + .borrow_mut() + .insert(oid, output.clone()); + assert!( + previous.is_none(), + "internal: wayland should always use new IDs" + ); + tracing::trace!("output added {:?} {:?}", gid, oid); + output.wl_output.quick_assign(with_cloned!(weak_outputs, gid, oid, outputsaddedtx; move |a, event, b| { tracing::trace!("output event {:?} {:?} {:?}", a, event, b); match weak_outputs.upgrade().unwrap().borrow_mut().get_mut(&oid) { Some(o) => o.process_event(event, &outputsaddedtx), @@ -208,47 +219,49 @@ impl Application { ), } })); - } else if interface.as_str() == "wl_seat" && version >= 7 { - let new_seat = registry.bind::(7, id); - let prev_seat = weak_seats - .upgrade() - .unwrap() - .borrow_mut() - .insert(id, Rc::new(RefCell::new(Seat::new(new_seat)))); - assert!( - prev_seat.is_none(), - "internal: wayland should always use new IDs" - ); - // Defer setting up the pointer/keyboard event handling until we've - // finished constructing the `Application`. That way we can pass it as a - // parameter. - } + } else if interface.as_str() == "wl_seat" && version >= 7 { + let new_seat = registry.bind::(7, id); + let prev_seat = weak_seats + .upgrade() + .unwrap() + .borrow_mut() + .insert(id, Rc::new(RefCell::new(Seat::new(new_seat)))); + assert!( + prev_seat.is_none(), + "internal: wayland should always use new IDs" + ); + // Defer setting up the pointer/keyboard event handling until we've + // finished constructing the `Application`. That way we can pass it as a + // parameter. } - wl::GlobalEvent::Removed { id, interface } if interface.as_str() == "wl_output" => { - let boutputs = weak_outputs.upgrade().unwrap(); - let mut outputs = boutputs.borrow_mut(); - let removed = outputs.iter() - .find(|(_pid, o)| o.gid == id) - .map(|(pid, _)| pid.clone()) - .and_then(|id| outputs.remove(&id)); - - let result = match removed { - None => return, - Some(removed) => outputsremovedtx.send(removed), - }; - - match result { - Ok(_) => tracing::debug!("outputs remaining {:?}...", outputs.len()), - Err(cause) => tracing::error!("failed to remove output {:?}", cause), - } + } + wl::GlobalEvent::Removed { id, interface } + if interface.as_str() == "wl_output" => + { + let boutputs = weak_outputs.upgrade().unwrap(); + let mut outputs = boutputs.borrow_mut(); + let removed = outputs + .iter() + .find(|(_pid, o)| o.gid == id) + .map(|(pid, _)| pid.clone()) + .and_then(|id| outputs.remove(&id)); + + let result = match removed { + None => return, + Some(removed) => outputsremovedtx.send(removed), + }; + + match result { + Ok(_) => tracing::debug!("outputs remaining {:?}...", outputs.len()), + Err(cause) => tracing::error!("failed to remove output {:?}", cause), } - _ => { - tracing::debug!("unhandled global manager event received {:?}", event); - }, + } + _ => { + tracing::debug!("unhandled global manager event received {:?}", event); } } - }, - ); + } + }); // do a round trip to make sure we have all the globals event_queue @@ -399,30 +412,33 @@ impl Application { let wayland_dispatcher = WaylandSource::new(self.data.clone()).into_dispatcher(); handle.register_dispatcher(wayland_dispatcher).unwrap(); - handle.insert_source(self.data.outputs_added.borrow_mut().take().unwrap(), { - move |evt, _ignored, appdata| { - match evt { + handle + .insert_source(self.data.outputs_added.borrow_mut().take().unwrap(), { + move |evt, _ignored, appdata| match evt { calloop::channel::Event::Closed => return, calloop::channel::Event::Msg(output) => { tracing::debug!("output added {:?} {:?}", output.gid, output.id()); for (_, win) in appdata.handles_iter() { surfaces::Outputs::inserted(&win, &output); } - }, + } } - } - }).unwrap(); - handle.insert_source(self.data.outputs_removed.borrow_mut().take().unwrap(), |evt, _ignored, appdata| { - match evt { - calloop::channel::Event::Closed => return, - calloop::channel::Event::Msg(output) => { - tracing::trace!("output removed {:?} {:?}", output.gid, output.id()); - for (_, win) in appdata.handles_iter() { - surfaces::Outputs::removed(&win, &output); + }) + .unwrap(); + handle + .insert_source( + self.data.outputs_removed.borrow_mut().take().unwrap(), + |evt, _ignored, appdata| match evt { + calloop::channel::Event::Closed => return, + calloop::channel::Event::Msg(output) => { + tracing::trace!("output removed {:?} {:?}", output.gid, output.id()); + for (_, win) in appdata.handles_iter() { + surfaces::Outputs::removed(&win, &output); + } } }, - } - }).unwrap(); + ) + .unwrap(); handle .insert_source(timer_source, move |token, _metadata, appdata| { @@ -434,7 +450,8 @@ impl Application { let signal = eventloop.get_signal(); let handle = handle.clone(); - eventloop.run( + eventloop + .run( Duration::from_millis(20), &mut self.data.clone(), move |appdata| { @@ -579,7 +596,11 @@ impl ApplicationData { Some(s) => s, None => { // NOTE this might be expected - log::warn!("received event for surface that doesn't exist any more {:?} {:?}", expired, expired.id()); + log::warn!( + "received event for surface that doesn't exist any more {:?} {:?}", + expired, + expired.id() + ); continue; } }; @@ -728,7 +749,7 @@ impl Output { wl_output::Transform::Flipped270 | wl_output::Transform::_270 => { self.physical_width = physical_height; self.physical_height = physical_width; - }, + } _ => { self.physical_width = physical_width; self.physical_height = physical_height; diff --git a/druid-shell/src/backend/wayland/surfaces/layershell.rs b/druid-shell/src/backend/wayland/surfaces/layershell.rs index 2f8afb1ac2..c3f7f142c8 100644 --- a/druid-shell/src/backend/wayland/surfaces/layershell.rs +++ b/druid-shell/src/backend/wayland/surfaces/layershell.rs @@ -4,21 +4,22 @@ use wayland_protocols::wlr::unstable::layer_shell::v1::client as layershell; use crate::kurbo; use crate::window; +use super::super::application::Output; use super::super::error; -use super::super::application::{Output}; -use super::Outputs; use super::popup; use super::surface; use super::Compositor; use super::CompositorHandle; use super::Handle; +use super::Outputs; use super::Popup; use super::PopupHandle; struct Inner { config: Config, wl_surface: std::cell::RefCell, - ls_surface: std::cell::RefCell>, + ls_surface: + std::cell::RefCell>, requires_initialization: std::cell::RefCell, available: std::cell::RefCell, } @@ -202,18 +203,28 @@ impl Surface { .ls_surface .borrow() .set_size(dim.width as u32, dim.height as u32); - self.inner.wl_surface.borrow().inner.handler.borrow_mut().size(dim); + self.inner + .wl_surface + .borrow() + .inner + .handler + .borrow_mut() + .size(dim); } fn initialize(handle: &Surface) { if !handle.inner.requires_initialization.replace(false) { - return + return; } tracing::info!("attempting to initialize layershell"); - handle.inner.wl_surface.borrow().set_popup_impl(PopupHandle { - inner: handle.inner.clone(), - }); + handle + .inner + .wl_surface + .borrow() + .set_popup_impl(PopupHandle { + inner: handle.inner.clone(), + }); handle.inner.ls_surface.borrow().quick_assign({ let handle = handle.clone(); @@ -227,7 +238,12 @@ impl Surface { handle.inner.wl_surface.borrow().commit(); } - fn consume_layershell_event(handle: &Surface, a1: &wlc::Main, event: &layershell::zwlr_layer_surface_v1::Event, data: &wlc::DispatchData) { + fn consume_layershell_event( + handle: &Surface, + a1: &wlc::Main, + event: &layershell::zwlr_layer_surface_v1::Event, + data: &wlc::DispatchData, + ) { match *event { layershell::zwlr_layer_surface_v1::Event::Configure { serial, @@ -265,20 +281,35 @@ impl Outputs for Surface { fn inserted(&self, o: &Output) { if !self.inner.requires_initialization.borrow().clone() { - tracing::debug!("skipping reinitialization output for layershell {:?} {:?}", o.gid, o.id()); - return + tracing::debug!( + "skipping reinitialization output for layershell {:?} {:?}", + o.gid, + o.id() + ); + return; } - tracing::debug!("reinitializing output for layershell {:?} {:?} {:?}", o.gid, o.id(), o); + tracing::debug!( + "reinitializing output for layershell {:?} {:?} {:?}", + o.gid, + o.id(), + o + ); let sdata = self.inner.wl_surface.borrow().inner.clone(); - let replacedsurface = self.inner.wl_surface.replace(surface::Surface::replace(&sdata)); + let replacedsurface = self + .inner + .wl_surface + .replace(surface::Surface::replace(&sdata)); let sdata = self.inner.wl_surface.borrow().inner.clone(); - let replacedlayershell = self.inner.ls_surface.replace(sdata.compositor.zwlr_layershell_v1().get_layer_surface( - &self.inner.wl_surface.borrow().inner.wl_surface.borrow(), - None, - self.inner.config.layer, - self.inner.config.namespace.to_string(), - )); + let replacedlayershell = + self.inner + .ls_surface + .replace(sdata.compositor.zwlr_layershell_v1().get_layer_surface( + &self.inner.wl_surface.borrow().inner.wl_surface.borrow(), + None, + self.inner.config.layer, + self.inner.config.namespace.to_string(), + )); Surface::initialize(&self); tracing::debug!("replaced surface {:p}", &replacedsurface); @@ -316,7 +347,11 @@ impl Handle for Surface { } fn set_focused_text_field(&self, active_field: Option) { - return self.inner.wl_surface.borrow().set_focused_text_field(active_field); + return self + .inner + .wl_surface + .borrow() + .set_focused_text_field(active_field); } fn get_idle_handle(&self) -> super::idle::Handle { @@ -368,4 +403,4 @@ impl From for Box { fn from(s: Surface) -> Box { Box::new(s) as Box } -} \ No newline at end of file +} diff --git a/druid-shell/src/backend/wayland/surfaces/popup.rs b/druid-shell/src/backend/wayland/surfaces/popup.rs index 88d5bcaeaa..894fa0c698 100644 --- a/druid-shell/src/backend/wayland/surfaces/popup.rs +++ b/druid-shell/src/backend/wayland/surfaces/popup.rs @@ -6,11 +6,11 @@ use wayland_protocols::xdg_shell::client::xdg_surface; use crate::kurbo; use crate::window; -use super::Outputs; use super::surface; use super::Compositor; use super::CompositorHandle; use super::Handle; +use super::Outputs; struct Inner { wl_surface: surface::Surface, diff --git a/druid-shell/src/backend/wayland/surfaces/surface.rs b/druid-shell/src/backend/wayland/surfaces/surface.rs index e7a5b246a5..eb00e5fd12 100644 --- a/druid-shell/src/backend/wayland/surfaces/surface.rs +++ b/druid-shell/src/backend/wayland/surfaces/surface.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use wayland_client as wlc; use wayland_client::protocol::wl_surface; -use crate::backend::application::{Output}; +use crate::backend::application::Output; use crate::kurbo; use crate::window; use crate::{piet::Piet, region::Region, scale::Scale, TextFieldToken}; @@ -14,7 +14,7 @@ use super::buffers; use super::error; use super::idle; use super::popup; -use super::{Compositor, CompositorHandle, Decor, Handle, PopupHandle, Outputs}; +use super::{Compositor, CompositorHandle, Decor, Handle, Outputs, PopupHandle}; pub enum DeferredTask { Paint, @@ -28,9 +28,7 @@ pub struct Surface { impl From> for Surface { fn from(d: std::sync::Arc) -> Self { - Self { - inner: d, - } + Self { inner: d } } } @@ -89,10 +87,12 @@ impl Surface { } pub(super) fn replace(current: &std::sync::Arc) -> Surface { - current.wl_surface.replace(match current.compositor.create_surface() { - None => panic!("unable to create surface"), - Some(v) => v, - }); + current + .wl_surface + .replace(match current.compositor.create_surface() { + None => panic!("unable to create surface"), + Some(v) => v, + }); Surface::initsurface(¤t); Self { inner: current.clone(), @@ -109,16 +109,18 @@ impl Surface { }); } - pub(super) fn consume_surface_event(current: &std::sync::Arc, surface: &wlc::Main, event: &wlc::protocol::wl_surface::Event, data: &wlc::DispatchData) { + pub(super) fn consume_surface_event( + current: &std::sync::Arc, + surface: &wlc::Main, + event: &wlc::protocol::wl_surface::Event, + data: &wlc::DispatchData, + ) { tracing::info!("wl_surface event {:?} {:?} {:?}", surface, event, data); match event { wl_surface::Event::Enter { output } => { let proxy = wlc::Proxy::from(output.clone()); - current - .outputs - .borrow_mut() - .insert(proxy.id()); - }, + current.outputs.borrow_mut().insert(proxy.id()); + } wl_surface::Event::Leave { output } => { current .outputs @@ -132,9 +134,9 @@ impl Surface { if current.set_scale(new_scale).is_changed() { current.wl_surface.borrow().set_buffer_scale(new_scale); // We also need to change the physical size to match the new scale - current.buffers.set_size( - buffers::RawSize::from(current.logical_size.get()).scale(new_scale), - ); + current + .buffers + .set_size(buffers::RawSize::from(current.logical_size.get()).scale(new_scale)); // always repaint, because the scale changed. current.schedule_deferred_task(DeferredTask::Paint); } @@ -513,7 +515,7 @@ impl Data { pub(super) fn request_anim_frame(&self) { if self.anim_frame_requested.replace(true) { - return + return; } let idle = self.get_idle_handle(); diff --git a/druid-shell/src/backend/wayland/surfaces/toplevel.rs b/druid-shell/src/backend/wayland/surfaces/toplevel.rs index 026ca327ae..09788e07e1 100644 --- a/druid-shell/src/backend/wayland/surfaces/toplevel.rs +++ b/druid-shell/src/backend/wayland/surfaces/toplevel.rs @@ -6,12 +6,12 @@ use wayland_protocols::xdg_shell::client::xdg_toplevel; use crate::kurbo; use crate::window; -use super::Outputs; use super::surface; use super::Compositor; use super::CompositorHandle; use super::Decor; use super::Handle; +use super::Outputs; struct Inner { wl_surface: surface::Surface, @@ -171,4 +171,4 @@ impl From for Box { fn from(s: Surface) -> Box { Box::new(s.inner.wl_surface.clone()) as Box } -} \ No newline at end of file +} diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index 3269930260..f57a68b084 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -16,7 +16,7 @@ use tracing; use super::{ - application::{Application, ApplicationData, Timer, Output}, + application::{Application, ApplicationData, Output, Timer}, error, menu::Menu, surfaces, From 2a3ca49e3239ed93361a98bf41b7bc9b9358f024 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 29 Nov 2021 22:55:34 -0500 Subject: [PATCH 17/31] implement locale detection for wayland. (#2059) --- druid-shell/src/backend/shared/linux/env.rs | 39 +++++++++++++++++++ druid-shell/src/backend/shared/linux/mod.rs | 2 + druid-shell/src/backend/shared/mod.rs | 1 + .../src/backend/wayland/application.rs | 4 +- druid-shell/src/backend/x11/application.rs | 39 +------------------ 5 files changed, 46 insertions(+), 39 deletions(-) create mode 100644 druid-shell/src/backend/shared/linux/env.rs create mode 100644 druid-shell/src/backend/shared/linux/mod.rs diff --git a/druid-shell/src/backend/shared/linux/env.rs b/druid-shell/src/backend/shared/linux/env.rs new file mode 100644 index 0000000000..21a1a3d34d --- /dev/null +++ b/druid-shell/src/backend/shared/linux/env.rs @@ -0,0 +1,39 @@ +pub fn locale() -> String { + fn locale_env_var(var: &str) -> Option { + match std::env::var(var) { + Ok(s) if s.is_empty() => { + tracing::debug!("locale: ignoring empty env var {}", var); + None + } + Ok(s) => { + tracing::debug!("locale: env var {} found: {:?}", var, &s); + Some(s) + } + Err(std::env::VarError::NotPresent) => { + tracing::debug!("locale: env var {} not found", var); + None + } + Err(std::env::VarError::NotUnicode(_)) => { + tracing::debug!("locale: ignoring invalid unicode env var {}", var); + None + } + } + } + + // from gettext manual + // https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html#Locale-Environment-Variables + let mut locale = locale_env_var("LANGUAGE") + // the LANGUAGE value is priority list seperated by : + // See: https://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html#The-LANGUAGE-variable + .and_then(|locale| locale.split(':').next().map(String::from)) + .or_else(|| locale_env_var("LC_ALL")) + .or_else(|| locale_env_var("LC_MESSAGES")) + .or_else(|| locale_env_var("LANG")) + .unwrap_or_else(|| "en-US".to_string()); + + // This is done because the locale parsing library we use expects an unicode locale, but these vars have an ISO locale + if let Some(idx) = locale.chars().position(|c| c == '.' || c == '@') { + locale.truncate(idx); + } + locale +} diff --git a/druid-shell/src/backend/shared/linux/mod.rs b/druid-shell/src/backend/shared/linux/mod.rs new file mode 100644 index 0000000000..d40b15d94a --- /dev/null +++ b/druid-shell/src/backend/shared/linux/mod.rs @@ -0,0 +1,2 @@ +// environment based utilities +pub mod env; diff --git a/druid-shell/src/backend/shared/mod.rs b/druid-shell/src/backend/shared/mod.rs index 568e5205a1..998b929f5b 100644 --- a/druid-shell/src/backend/shared/mod.rs +++ b/druid-shell/src/backend/shared/mod.rs @@ -25,5 +25,6 @@ cfg_if::cfg_if! { mod timer; pub(crate) use timer::*; pub(crate) mod xkb; + pub(crate) mod linux; } } diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index 212463131d..8cc46fe33f 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -30,6 +30,7 @@ use std::{ time::{Duration, Instant}, }; +use crate::backend::shared::linux; use wayland_client::protocol::wl_keyboard::WlKeyboard; use wayland_client::{ self as wl, @@ -475,8 +476,7 @@ impl Application { } pub fn get_locale() -> String { - tracing::warn!("get_locale unimplemented"); - "en_US".into() + linux::env::locale() } } diff --git a/druid-shell/src/backend/x11/application.rs b/druid-shell/src/backend/x11/application.rs index 4378253747..3c3997b135 100644 --- a/druid-shell/src/backend/x11/application.rs +++ b/druid-shell/src/backend/x11/application.rs @@ -38,6 +38,7 @@ use crate::application::AppHandler; use super::clipboard::Clipboard; use super::util; use super::window::Window; +use crate::backend::shared::linux; use crate::backend::shared::xkb; // This creates a `struct WindowAtoms` containing the specified atoms as members (along with some @@ -808,43 +809,7 @@ impl Application { } pub fn get_locale() -> String { - fn locale_env_var(var: &str) -> Option { - match std::env::var(var) { - Ok(s) if s.is_empty() => { - tracing::debug!("locale: ignoring empty env var {}", var); - None - } - Ok(s) => { - tracing::debug!("locale: env var {} found: {:?}", var, &s); - Some(s) - } - Err(std::env::VarError::NotPresent) => { - tracing::debug!("locale: env var {} not found", var); - None - } - Err(std::env::VarError::NotUnicode(_)) => { - tracing::debug!("locale: ignoring invalid unicode env var {}", var); - None - } - } - } - - // from gettext manual - // https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html#Locale-Environment-Variables - let mut locale = locale_env_var("LANGUAGE") - // the LANGUAGE value is priority list seperated by : - // See: https://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html#The-LANGUAGE-variable - .and_then(|locale| locale.split(':').next().map(String::from)) - .or_else(|| locale_env_var("LC_ALL")) - .or_else(|| locale_env_var("LC_MESSAGES")) - .or_else(|| locale_env_var("LANG")) - .unwrap_or_else(|| "en-US".to_string()); - - // This is done because the locale parsing library we use expects an unicode locale, but these vars have an ISO locale - if let Some(idx) = locale.chars().position(|c| c == '.' || c == '@') { - locale.truncate(idx); - } - locale + linux::env::locale() } pub(crate) fn idle_pipe(&self) -> RawFd { From 89412ba25547b08bacbf14f4700ff414dcbc6356 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 3 Dec 2021 10:32:23 -0500 Subject: [PATCH 18/31] enable buffer recreation to always be allowed. (#2070) - fixes a seg fault when resizing the current window. - fixes a bug where even if no seg fault happens we skip painting. --- .../src/backend/wayland/surfaces/buffers.rs | 117 ++++++++---------- 1 file changed, 55 insertions(+), 62 deletions(-) diff --git a/druid-shell/src/backend/wayland/surfaces/buffers.rs b/druid-shell/src/backend/wayland/surfaces/buffers.rs index c8ba629ba8..585747c794 100644 --- a/druid-shell/src/backend/wayland/surfaces/buffers.rs +++ b/druid-shell/src/backend/wayland/surfaces/buffers.rs @@ -39,6 +39,8 @@ pub(super) const NUM_FRAMES: i32 = 2; /// /// This object knows nothing about scaling or events. It just provides buffers to draw into. pub struct Buffers { + /// Release buffers which are just waiting to be freed. + released: Cell>, /// The actual buffer objects. buffers: Cell>, /// Which buffer is the next to present. Iterates through to `N-1` then wraps. Draw to this @@ -51,9 +53,6 @@ pub struct Buffers { size: Cell, /// Do we need to rebuild the framebuffers (size changed). recreate_buffers: Cell, - /// A paint call could not be completed because no buffers were available. Once a buffer - /// becomes free, a frame should be painted. - deferred_paint: Cell, /// This flag allows us to check that we only hand out a mutable ref to the buffer data once. /// Otherwise providing mutable access to the data would be unsafe. pending_buffer_borrowed: Cell, @@ -68,11 +67,11 @@ impl Buffers { pub fn new(wl_shm: wl::Main, size: RawSize) -> Rc { assert!(N >= 2, "must be at least 2 buffers"); Rc::new(Self { + released: Cell::new(Vec::new()), buffers: Cell::new(None), pending: Cell::new(0), size: Cell::new(size), recreate_buffers: Cell::new(true), - deferred_paint: Cell::new(false), pending_buffer_borrowed: Cell::new(false), shm: RefCell::new(Shm::new(wl_shm).expect("error allocating shared memory")), }) @@ -99,8 +98,11 @@ impl Buffers { /// available we will set a flag, so that when one becomes available we immediately paint and /// present. This includes if we need to resize. pub fn request_paint(self: &Rc, window: &surface::Data) { - tracing::trace!("buffer.request_paint {:?}", self.size.get()); - + tracing::trace!( + "request_paint {:?} {:?}", + self.size.get(), + window.get_size() + ); // if our size is empty there is nothing to do. if self.size.get().is_empty() { return; @@ -109,32 +111,17 @@ impl Buffers { if self.pending_buffer_borrowed.get() { panic!("called request_paint during painting"); } - // Ok so complicated here: - // - If we need to recreate the buffers, we must wait until *all* buffers are - // released, so we can re-use the memory. - // - If we are just waiting for the next frame, we can check if the *pending* - // buffer is free. - if self.recreate_buffers.get() { - // If all buffers are free, destroy and recreate them - if self.all_buffers_released() { - //log::debug!("all buffers released, recreating"); - self.deferred_paint.set(false); - self.recreate_buffers_unchecked(); - self.paint_unchecked(window); - } else { - self.deferred_paint.set(true); - } - } else { - // If the next buffer is free, draw & present. If buffers have not been initialized it - // is a bug in this code. - if self.pending_buffer_released() { - //log::debug!("next frame has been released: draw and present"); - self.deferred_paint.set(false); - self.paint_unchecked(window); - } else { - self.deferred_paint.set(true); - } + + // recreate if necessary + self.buffers_recreate(); + + // paint if we have a buffer available. + if self.pending_buffer_released() { + self.paint_unchecked(window); } + + // attempt to release any unused buffers. + self.buffers_drop_unused(); } /// Paint the next frame, without checking if the buffer is free. @@ -145,24 +132,47 @@ impl Buffers { self.pending_buffer_released(), "buffer in use/not initialized" ); - window.paint(self.size.get(), &mut *buf_data, self.recreate_buffers.get()); - self.recreate_buffers.set(false); - } - /// Destroy the current buffers, resize the shared memory pool if necessary, and create new - /// buffers. Does not check if all buffers are free. - fn recreate_buffers_unchecked(&self) { - debug_assert!( - self.all_buffers_released(), - "recreate buffers: some buffer still in use" + window.paint( + self.size.get(), + &mut *buf_data, + self.recreate_buffers.replace(false), ); - debug_assert!(!self.pending_buffer_borrowed.get()); - self.with_buffers(|buffers| { - if let Some(buffers) = buffers.as_ref() { - buffers[0].destroy(); - buffers[1].destroy(); + } + + // attempt to release unused buffers. + fn buffers_drop_unused(&self) { + let mut pool = self.released.take(); + pool.retain(|b| { + if b.in_use.get() { + return true; } + b.destroy(); + return false; }); + self.released.replace(pool); + } + + fn buffers_invalidate(&self) { + if let Some(buffers) = self.buffers.replace(None) { + let mut tmp = self.released.take(); + tmp.append(&mut buffers.to_vec()); + self.released.replace(tmp); + } + } + + /// Destroy the current buffers, resize the shared memory pool if necessary, and create new + /// buffers. + fn buffers_recreate(&self) { + if !self.recreate_buffers.get() { + return; + } + + debug_assert!(!self.pending_buffer_borrowed.get()); + + // move current buffers into the release queue to be cleaned up later. + self.buffers_invalidate(); + let new_buffer_size = self.size.get().buffer_size(N.try_into().unwrap()); // This is probably OOM if it fails, but we unwrap to report the underlying error. self.shm.borrow_mut().extend(new_buffer_size).unwrap(); @@ -170,7 +180,6 @@ impl Buffers { let pool = self.shm.borrow_mut().create_pool(); self.buffers.set({ let mut buffers = vec![]; - let size = self.size.get(); for i in 0..N { buffers.push(Buffer::create(&pool, i, size.width, size.height)); @@ -194,16 +203,6 @@ impl Buffers { self.with_buffers(|buffers| f(buffers.as_ref().map(|buffers| &buffers[self.pending.get()]))) } - /// For checking whether all buffers are free. - fn all_buffers_released(&self) -> bool { - self.with_buffers(|buffers| { - buffers - .as_ref() - .map(|buffers| buffers.iter().all(|buf| !buf.in_use.get())) - .unwrap_or(true) - }) - } - /// For checking whether the next buffer is free. fn pending_buffer_released(&self) -> bool { self.with_pending_buffer(|buf| buf.map(|buf| !buf.in_use.get()).unwrap_or(false)) @@ -294,12 +293,6 @@ impl Buffer { } } -impl Drop for Buffer { - fn drop(&mut self) { - self.destroy(); - } -} - pub struct BufferData { buffers: WeakRc>, mmap: Mmap, From 1186cffba77f54393d1fb154e95ebd32c56ef4ca Mon Sep 17 00:00:00 2001 From: James Date: Sat, 4 Dec 2021 00:26:53 -0500 Subject: [PATCH 19/31] implement repeating keys. (#2058) --- .../src/backend/wayland/application.rs | 4 +- druid-shell/src/backend/wayland/keyboard.rs | 438 ++++++++++++------ 2 files changed, 310 insertions(+), 132 deletions(-) diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index 8cc46fe33f..fe1a8ae26d 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -367,7 +367,7 @@ impl Application { if capabilities.contains(wl_seat::Capability::Keyboard) && seat.keyboard.is_none() { - seat.keyboard = Some(app_data.keyboard.attach(app_data.clone(), id, seat.wl_seat.clone())); + seat.keyboard = Some(app_data.keyboard.attach(id, seat.wl_seat.clone())) } if capabilities.contains(wl_seat::Capability::Pointer) && seat.pointer.is_none() @@ -412,6 +412,8 @@ impl Application { let wayland_dispatcher = WaylandSource::new(self.data.clone()).into_dispatcher(); + self.data.keyboard.events(&handle); + handle.register_dispatcher(wayland_dispatcher).unwrap(); handle .insert_source(self.data.outputs_added.borrow_mut().take().unwrap(), { diff --git a/druid-shell/src/backend/wayland/keyboard.rs b/druid-shell/src/backend/wayland/keyboard.rs index 317a926df3..02b9c928be 100644 --- a/druid-shell/src/backend/wayland/keyboard.rs +++ b/druid-shell/src/backend/wayland/keyboard.rs @@ -5,31 +5,64 @@ use wayland_client::protocol::wl_seat; use crate::keyboard_types::KeyState; use crate::text; +use crate::KeyEvent; use crate::Modifiers; use super::application::ApplicationData; use super::surfaces::buffers; use crate::backend::shared::xkb; -pub(super) struct State { +#[derive(Clone)] +struct CachedKeyPress { + seat: u32, + serial: u32, + timestamp: u32, + key: u32, + repeat: bool, + state: wayland_client::protocol::wl_keyboard::KeyState, + queue: calloop::channel::Sender, +} + +impl CachedKeyPress { + fn repeat(&self) -> Self { + let mut c = self.clone(); + c.repeat = true; + c + } +} + +#[derive(Debug, Clone)] +struct Repeat { + rate: std::time::Duration, + delay: std::time::Duration, +} + +impl Default for Repeat { + fn default() -> Self { + Self { + rate: std::time::Duration::from_millis(40), + delay: std::time::Duration::from_millis(600), + } + } +} + +struct Keyboard { /// Whether we've currently got keyboard focus. focused: bool, + repeat: Repeat, + last_key_press: Option, xkb_context: xkb::Context, xkb_keymap: std::cell::RefCell>, xkb_state: std::cell::RefCell>, xkb_mods: std::cell::Cell, } -impl State { - fn focused(&mut self, updated: bool) { - self.focused = updated; - } -} - -impl Default for State { +impl Default for Keyboard { fn default() -> Self { Self { focused: false, + repeat: Repeat::default(), + last_key_press: None, xkb_context: xkb::Context::new(), xkb_keymap: std::cell::RefCell::new(None), xkb_state: std::cell::RefCell::new(None), @@ -38,6 +71,217 @@ impl Default for State { } } +impl Keyboard { + fn focused(&mut self, updated: bool) { + self.focused = updated; + } + + fn repeat(&mut self, u: Repeat) { + self.repeat = u; + } + + fn replace_last_key_press(&mut self, u: Option) { + self.last_key_press = u; + } + + fn release_last_key_press(&self, current: &CachedKeyPress) -> Option { + match &self.last_key_press { + None => return None, // nothing to do. + Some(last) => { + if last.serial >= current.serial { + return Some(last.clone()); + } + if last.key != current.key { + return Some(last.clone()); + } + None + } + } + } + + fn keystroke<'a>(&'a mut self, keystroke: &'a CachedKeyPress) { + let keystate = match keystroke.state { + wl_keyboard::KeyState::Released => { + self.replace_last_key_press(self.release_last_key_press(keystroke)); + KeyState::Up + } + wl_keyboard::KeyState::Pressed => { + self.replace_last_key_press(Some(keystroke.repeat())); + KeyState::Down + } + _ => panic!("unrecognised key event"), + }; + + let mut event = self + .xkb_state + .borrow_mut() + .as_mut() + .unwrap() + .key_event(keystroke.key, keystate); + event.mods = self.xkb_mods.get(); + event.repeat = keystroke.repeat; + + if let Err(cause) = keystroke.queue.send(event) { + tracing::error!("failed to send druid key event: {:?}", cause); + } + } + + fn consume( + &mut self, + seat: u32, + event: wl_keyboard::Event, + keyqueue: calloop::channel::Sender, + ) { + tracing::trace!("consume {:?} -> {:?}", seat, event); + match event { + wl_keyboard::Event::Keymap { format, fd, size } => { + if !matches!(format, wl_keyboard::KeymapFormat::XkbV1) { + panic!("only xkb keymap supported for now"); + } + + // TODO to test memory ownership we copy the memory. That way we can deallocate it + // and see if we get a segfault. + let keymap_data = unsafe { + buffers::Mmap::from_raw_private( + fd, + size.try_into().unwrap(), + 0, + size.try_into().unwrap(), + ) + .unwrap() + .as_ref() + .to_vec() + }; + + // keymap data is '\0' terminated. + let keymap = self.xkb_context.keymap_from_slice(&keymap_data); + let keymapstate = keymap.state(); + + self.xkb_keymap.replace(Some(keymap)); + self.xkb_state.replace(Some(keymapstate)); + } + wl_keyboard::Event::Enter { .. } => { + self.focused(true); + } + wl_keyboard::Event::Leave { .. } => { + self.focused(false); + } + wl_keyboard::Event::Key { + serial, + time, + state, + key, + } => { + tracing::trace!( + "key stroke registered {:?} {:?} {:?} {:?}", + time, + serial, + key, + state + ); + self.keystroke(&CachedKeyPress { + repeat: false, + seat, + serial, + timestamp: time, + key: key + 8, // TODO: understand the magic 8. + state, + queue: keyqueue.clone(), + }) + } + wl_keyboard::Event::Modifiers { .. } => { + self.xkb_mods.replace(event_to_mods(event)); + } + wl_keyboard::Event::RepeatInfo { rate, delay } => { + tracing::trace!("keyboard repeat info received {:?} {:?}", rate, delay); + self.repeat(Repeat { + rate: std::time::Duration::from_millis((1000 / rate) as u64), + delay: std::time::Duration::from_millis(delay as u64), + }); + } + evt => { + tracing::warn!("unimplemented keyboard event: {:?}", evt); + } + } + } +} + +pub(super) struct State { + apptx: calloop::channel::Sender, + apprx: std::cell::RefCell>>, + tx: calloop::channel::Sender<(u32, wl_keyboard::Event, calloop::channel::Sender)>, +} + +impl Default for State { + fn default() -> Self { + let (apptx, apprx) = calloop::channel::channel::(); + let (tx, rx) = calloop::channel::channel::<( + u32, + wl_keyboard::Event, + calloop::channel::Sender, + )>(); + let state = Self { + apptx, + apprx: std::cell::RefCell::new(Some(apprx)), + tx, + }; + + std::thread::spawn(move || { + let mut eventloop: calloop::EventLoop<(calloop::LoopSignal, Keyboard)> = + calloop::EventLoop::try_new() + .expect("failed to initialize the keyboard event loop!"); + let signal = eventloop.get_signal(); + let handle = eventloop.handle(); + let repeat = calloop::timer::Timer::::new().unwrap(); + handle + .insert_source(rx, { + let repeater = repeat.handle(); + move |event, _ignored, state| { + let event = match event { + calloop::channel::Event::Closed => { + tracing::info!("keyboard event loop closed shutting down"); + state.0.stop(); + return; + } + calloop::channel::Event::Msg(keyevent) => keyevent, + }; + state.1.consume(event.0, event.1, event.2); + match &state.1.last_key_press { + None => repeater.cancel_all_timeouts(), + Some(cached) => { + repeater.cancel_all_timeouts(); + repeater.add_timeout(state.1.repeat.delay, cached.clone()); + } + }; + } + }) + .unwrap(); + + // generate repeat keypresses. + handle + .insert_source(repeat, |event, timer, state| { + timer.add_timeout(state.1.repeat.rate, event.clone()); + state.1.keystroke(&event); + }) + .unwrap(); + + tracing::debug!("keyboard event loop initiated"); + eventloop + .run( + std::time::Duration::from_secs(60), + &mut (signal, Keyboard::default()), + |_ignored| { + tracing::trace!("keyboard event loop idle"); + }, + ) + .expect("keyboard event processing failed"); + tracing::debug!("keyboard event loop completed"); + }); + + state + } +} + struct ModMap(u32, Modifiers); impl ModMap { @@ -78,13 +322,13 @@ pub fn event_to_mods(event: wl_keyboard::Event) -> Modifiers { } pub struct Manager { - inner: std::sync::Arc>, + inner: std::sync::Arc, } impl Default for Manager { fn default() -> Self { Self { - inner: std::sync::Arc::new(std::cell::RefCell::new(State::default())), + inner: std::sync::Arc::new(State::default()), } } } @@ -92,141 +336,73 @@ impl Default for Manager { impl Manager { pub(super) fn attach( &self, - appdata: std::sync::Arc, id: u32, seat: wlc::Main, ) -> wlc::Main { let keyboard = seat.get_keyboard(); keyboard.quick_assign({ - let appdata = appdata.clone(); - let keyboardstate = self.inner.clone(); - move |_, event, _| Manager::consume(&keyboardstate, &appdata, id, event) + let tx = self.inner.tx.clone(); + let queue = self.inner.apptx.clone(); + move |_, event, _| { + if let Err(cause) = tx.send((id, event, queue.clone())) { + tracing::error!("failed to transmit keyboard event {:?}", cause); + }; + } }); keyboard } - pub(super) fn consume( - keyboardstate: &std::sync::Arc>, - appdata: &std::sync::Arc, - seat: u32, - event: wl_keyboard::Event, + // TODO turn struct into a calloop event source. + pub(super) fn events<'a>( + &self, + handle: &'a calloop::LoopHandle>, ) { - tracing::trace!("consume {:?} -> {:?}", seat, event); - match event { - wl_keyboard::Event::Keymap { format, fd, size } => { - if !matches!(format, wl_keyboard::KeymapFormat::XkbV1) { - panic!("only xkb keymap supported for now"); - } + let rx = self.inner.apprx.borrow_mut().take().unwrap(); + handle + .insert_source(rx, { + move |evt, _ignored, appdata| { + let evt = match evt { + calloop::channel::Event::Msg(e) => e, + calloop::channel::Event::Closed => { + tracing::info!("keyboard events receiver closed"); + return; + } + }; - // TODO to test memory ownership we copy the memory. That way we can deallocate it - // and see if we get a segfault. - let keymap_data = unsafe { - buffers::Mmap::from_raw_private( - fd, - size.try_into().unwrap(), - 0, - size.try_into().unwrap(), - ) - .unwrap() - .as_ref() - .to_vec() - }; - - let state = keyboardstate.borrow_mut(); - - // keymap data is '\0' terminated. - let keymap = state.xkb_context.keymap_from_slice(&keymap_data); - let keymapstate = keymap.state(); - - state.xkb_keymap.replace(Some(keymap)); - state.xkb_state.replace(Some(keymapstate)); - } - wl_keyboard::Event::Enter { .. } => { - let winhandle = match appdata.acquire_current_window() { - Some(w) => w, - None => { - tracing::warn!("dropping keyboard events, no window available"); - return; - } - }; - - keyboardstate.borrow_mut().focused(true); - winhandle.data().map(|data| { - // (re-entrancy) call user code - data.handler.borrow_mut().got_focus(); - data.run_deferred_tasks(); - }); - } - wl_keyboard::Event::Leave { .. } => { - let winhandle = match appdata.acquire_current_window() { - Some(w) => w, - None => { - tracing::warn!("dropping keyboard events, no window available"); - return; - } - }; - - keyboardstate.borrow_mut().focused(false); - winhandle.data().map(|data| { - // (re-entrancy) call user code - data.handler.borrow_mut().lost_focus(); - data.run_deferred_tasks(); - }); - } - wl_keyboard::Event::Key { key, state, .. } => { - let mut event = keyboardstate - .borrow_mut() - .xkb_state - .borrow_mut() - .as_mut() - .unwrap() - .key_event( - key + 8, - match state { - wl_keyboard::KeyState::Released => KeyState::Up, - wl_keyboard::KeyState::Pressed => KeyState::Down, - _ => panic!("unrecognised key event"), - }, - ); - - event.mods = keyboardstate.borrow().xkb_mods.get(); - - if let Some(winhandle) = appdata.acquire_current_window() { - winhandle.data().map(|windata| { - windata.with_handler({ - let windata = windata.clone(); - move |handler| match event.state { - KeyState::Up => { - handler.key_up(event); - } - KeyState::Down => { - let handled = text::simulate_input( - handler, - windata.active_text_input.get(), - event, - ); - tracing::trace!( - "key press event {:?} {:?} {:?}", - handled, - key, - windata.active_text_input.get() - ); + if let Some(winhandle) = appdata.acquire_current_window() { + winhandle.data().map(|windata| { + windata.with_handler({ + let windata = windata.clone(); + let evt = evt.clone(); + move |handler| match evt.state { + KeyState::Up => { + handler.key_up(evt.clone()); + tracing::trace!( + "key press event up {:?} {:?}", + evt, + windata.active_text_input.get() + ); + } + KeyState::Down => { + let handled = text::simulate_input( + handler, + windata.active_text_input.get(), + evt.clone(), + ); + tracing::trace!( + "key press event down {:?} {:?} {:?}", + handled, + evt, + windata.active_text_input.get() + ); + } } - } + }); }); - }); + } } - } - wl_keyboard::Event::Modifiers { .. } => { - keyboardstate - .borrow() - .xkb_mods - .replace(event_to_mods(event)); - } - evt => { - tracing::warn!("unimplemented keyboard event: {:?}", evt); - } - } + }) + .unwrap(); } } From 7ad66e766a5aec18215f4feaaf07e0f189fe7efa Mon Sep 17 00:00:00 2001 From: James Date: Sat, 4 Dec 2021 00:47:18 -0500 Subject: [PATCH 20/31] attempt to create a working clipboard (#2057) while these changes don't fully implement a working copy/paste. I have confirm that when Clipboard::get_string is invoked the contents from the system clipboard are returned. --- .../src/backend/shared/xkb/keycodes.rs | 1 + .../src/backend/wayland/application.rs | 15 +- druid-shell/src/backend/wayland/clipboard.rs | 247 +++++++++++++++++- 3 files changed, 249 insertions(+), 14 deletions(-) diff --git a/druid-shell/src/backend/shared/xkb/keycodes.rs b/druid-shell/src/backend/shared/xkb/keycodes.rs index 3b5a0bb9ac..08adb56db2 100644 --- a/druid-shell/src/backend/shared/xkb/keycodes.rs +++ b/druid-shell/src/backend/shared/xkb/keycodes.rs @@ -8,6 +8,7 @@ use super::xkbcommon_sys::*; pub fn map_key(keysym: u32) -> Key { use Key::*; match keysym { + XKB_KEY_v => Key::Character("v".to_string()), XKB_KEY_BackSpace => Backspace, XKB_KEY_Tab | XKB_KEY_KP_Tab | XKB_KEY_ISO_Left_Tab => Tab, XKB_KEY_Clear | XKB_KEY_KP_Begin | XKB_KEY_XF86Clear => Clear, diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index fe1a8ae26d..d392aac84e 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -15,7 +15,7 @@ #![allow(clippy::single_match)] use super::{ - clipboard::Clipboard, error::Error, events::WaylandSource, keyboard, pointers, surfaces, + clipboard, error::Error, events::WaylandSource, keyboard, pointers, surfaces, window::WindowHandle, }; @@ -144,6 +144,7 @@ pub(crate) struct ApplicationData { pub(super) pointer: pointers::Pointer, /// reference to the keyboard events manager. keyboard: keyboard::Manager, + clipboard: clipboard::Manager, // wakeup events when outputs are added/removed. outputs_removed: RefCell>>, outputs_added: RefCell>>, @@ -320,9 +321,7 @@ impl Application { // We need to have keyboard events set up for our seats before the next roundtrip. let app_data = std::sync::Arc::new(ApplicationData { - wl_server, event_queue: Rc::new(RefCell::new(event_queue)), - globals, xdg_base, zxdg_decoration_manager_v1, zwlr_layershell_v1, @@ -340,9 +339,12 @@ impl Application { display_flushed: RefCell::new(false), pointer, keyboard: keyboard::Manager::default(), + clipboard: clipboard::Manager::new(&wl_server, &globals)?, roundtrip_requested: RefCell::new(false), outputs_added: RefCell::new(Some(outputsaddedrx)), outputs_removed: RefCell::new(Some(outputsremovedrx)), + globals, + wl_server, }); // Collect the supported image formats. @@ -361,6 +363,7 @@ impl Application { wl_seat.quick_assign(with_cloned!(seat, app_data; move |d1, event, d3| { tracing::info!("seat events {:?} {:?} {:?}", d1, event, d3); let mut seat = seat.borrow_mut(); + app_data.clipboard.attach(&mut seat); match event { wl_seat::Event::Capabilities { capabilities } => { seat.capabilities = capabilities; @@ -473,8 +476,8 @@ impl Application { self.data.shutdown.set(true); } - pub fn clipboard(&self) -> Clipboard { - Clipboard + pub fn clipboard(&self) -> clipboard::Clipboard { + clipboard::Clipboard::from(&self.data.clipboard) } pub fn get_locale() -> String { @@ -812,7 +815,7 @@ pub struct Mode { #[derive(Debug, Clone)] pub struct Seat { - wl_seat: wl::Main, + pub(super) wl_seat: wl::Main, name: String, capabilities: wl_seat::Capability, keyboard: Option>, diff --git a/druid-shell/src/backend/wayland/clipboard.rs b/druid-shell/src/backend/wayland/clipboard.rs index a4c52feb10..888e5baa9d 100644 --- a/druid-shell/src/backend/wayland/clipboard.rs +++ b/druid-shell/src/backend/wayland/clipboard.rs @@ -13,34 +13,264 @@ // limitations under the License. //! Interactions with the system pasteboard on wayland compositors. - +use super::application; +use super::error as waylanderr; use crate::clipboard::{ClipboardFormat, FormatId}; +use std::io::Read; +use wayland_client as wl; +use wayland_client::protocol::wl_data_device; +use wayland_client::protocol::wl_data_device_manager; +use wayland_client::protocol::wl_data_offer; +use wayland_client::protocol::wl_data_source; + +#[derive(Clone)] +struct Offer { + wobj: wl::Main, + mimetype: String, +} + +impl Offer { + fn new(d: wl::Main, mimetype: impl Into) -> Self { + Self { + wobj: d, + mimetype: mimetype.into(), + } + } +} + +struct Data { + pending: std::cell::RefCell>, + current: std::cell::RefCell>, +} + +impl Data { + fn receive<'a>(&'a self, mimetype: &'a String) -> Option { + for offer in self.current.borrow().iter() { + if !offer.mimetype.starts_with(mimetype) { + // tracing::debug!("compared {:?} {:?}", offer.mimetype, mimetype); + continue; + } + // tracing::debug!("retrieving {:?} {:?}", offer.mimetype, mimetype); + return Some(offer.clone()); + } + None + } +} + +impl std::fmt::Debug for Data { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Data") + .field("pending", &self.pending.borrow().len()) + .field("current", &self.current.borrow().len()) + .finish() + } +} + +impl Default for Data { + fn default() -> Self { + Self { + pending: Default::default(), + current: Default::default(), + } + } +} + +impl From> for Data { + fn from(current: Vec) -> Self { + Self { + current: std::cell::RefCell::new(current), + pending: Default::default(), + } + } +} + +struct Inner { + display: wl::Display, + wobj: wl::Main, + wdsobj: wl::Main, + devices: std::rc::Rc>, +} + +impl std::fmt::Debug for Inner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("") + .field("wobj", &self.wobj) + .field("wdsobj", &self.wdsobj) + .finish() + } +} + +#[derive(Debug, Clone)] +pub struct Manager { + inner: std::sync::Arc, +} + +impl Manager { + pub(super) fn new(display: &wl::Display, gm: &wl::GlobalManager) -> Result { + let m = gm + .instantiate_exact::(3) + .map_err(|e| waylanderr::Error::global("wl_data_device_manager", 1, e))?; + + m.quick_assign(|i, event, _ignored| { + tracing::info!("clipboard {:?} event {:?}", i, event); + }); + + let ds = m.create_data_source(); + ds.quick_assign(|i, event, _ignored| { + tracing::info!("clipboard {:?} event {:?}", i, event); + }); + + Ok(Self { + inner: std::sync::Arc::new(Inner { + wobj: m, + wdsobj: ds, + display: display.clone(), + devices: Default::default(), + }), + }) + } + + pub fn attach<'a>(&'a self, seat: &'a mut application::Seat) { + let device = self.inner.wobj.get_data_device(&seat.wl_seat); + device.quick_assign({ + let m = self.inner.clone(); + move |i, event, _ignored| match event { + wl_data_device::Event::DataOffer { id } => { + let offer = id; + offer.quick_assign({ + let m = m.clone(); + move |i, event, _ignored| match event { + wl_data_offer::Event::Offer { mime_type } => { + let data = m.devices.borrow_mut(); + let offer = Offer::new(i.clone(), mime_type); + data.pending.borrow_mut().push(offer); + } + _ => tracing::warn!("clipboard unhandled {:?} event {:?}", i, event), + } + }); + } + wl_data_device::Event::Selection { id } => { + if let Some(_) = id { + let data = m.devices.borrow(); + tracing::debug!( + "current data offers {:?} {:?}", + data.current.borrow().len(), + data.pending.borrow().len() + ); + let upd = Data::from(data.pending.take()); + drop(data); + tracing::debug!( + "updated data offers {:?} {:?}", + upd.current.borrow().len(), + upd.pending.borrow().len() + ); + m.devices.replace(upd); + } else { + let upd = Data::from(Vec::new()); + m.devices.replace(upd); + } + } + _ => tracing::warn!("clipboard unhandled {:?} event {:?}", i, event), + } + }); + } + + fn initiate(&self, o: Offer) -> Option> { + tracing::debug!("retrieving {:?} {:?}", o.wobj, o.mimetype); + let (fdread, fdwrite) = match nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC) { + Ok(pipe) => pipe, + Err(cause) => { + tracing::error!("clipboard failed to request data {:?}", cause); + return None; + } + }; + + o.wobj.receive(o.mimetype.to_string(), fdwrite); + if let Err(cause) = self.inner.display.flush() { + tracing::error!("clipboard failed to request data {:?}", cause); + return None; + } + + if let Err(cause) = nix::unistd::close(fdwrite) { + tracing::error!("clipboard failed to request data {:?}", cause); + return None; + } + + let mut data = Vec::new(); + let mut io: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(fdread) }; + let transfered = match io.read_to_end(&mut data) { + Err(cause) => { + tracing::error!("clipboard unable to retrieve pasted content {:?}", cause); + return None; + } + Ok(transfered) => transfered, + }; + + tracing::debug!("transfered {:?} bytes", transfered); + + match transfered { + 0 => None, + _ => Some(data), + } + } + + pub(super) fn receive(&self, mimetype: impl Into) -> Option> { + let mimetype: String = mimetype.into(); + if let Some(offer) = self.inner.devices.borrow().receive(&mimetype) { + return self.initiate(offer); + } + + return None; + } +} /// The system clipboard. #[derive(Debug, Clone)] -pub struct Clipboard; +pub struct Clipboard { + inner: Manager, +} + +impl From<&Manager> for Clipboard { + fn from<'a>(m: &'a Manager) -> Self { + Self { inner: m.clone() } + } +} #[allow(unused)] impl Clipboard { + const UTF8: &'static str = "text/plain;charset=utf-8"; + const TEXT: &'static str = "text/plain"; + const UTF8_STRING: &'static str = "UTF8_STRING"; + /// Put a string onto the system clipboard. pub fn put_string(&mut self, s: impl AsRef) { - todo!() + let s = s.as_ref().to_string(); + self.inner.inner.wdsobj.offer(Clipboard::UTF8.to_string()); } /// Put multi-format data on the system clipboard. pub fn put_formats(&mut self, formats: &[ClipboardFormat]) { - todo!() + tracing::warn!("clipboard copy not implemented"); } /// Get a string from the system clipboard, if one is available. pub fn get_string(&self) -> Option { - todo!() + vec![Clipboard::UTF8, Clipboard::TEXT, Clipboard::UTF8_STRING] + .iter() + .find_map(|mimetype| match std::str::from_utf8(&self.inner.receive(*mimetype)?) { + Ok(s) => Some(s.to_string()), + Err(cause) => { + tracing::error!("clipboard unable to retrieve utf8 content {:?}", cause); + None + }, + }) } /// Given a list of supported clipboard types, returns the supported type which has /// highest priority on the system clipboard, or `None` if no types are supported. pub fn preferred_format(&self, formats: &[FormatId]) -> Option { - todo!() + tracing::warn!("clipboard preferred_format not implemented"); + None } /// Return data in a given format, if available. @@ -48,10 +278,11 @@ impl Clipboard { /// It is recommended that the `fmt` argument be a format returned by /// [`Clipboard::preferred_format`] pub fn get_format(&self, format: FormatId) -> Option> { - todo!() + self.inner.receive(format) } pub fn available_type_names(&self) -> Vec { - todo!() + tracing::warn!("clipboard available_type_names not implemented"); + Vec::new() } } From 41471d71a0785219e242a945165403aef393dc00 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 5 Dec 2021 01:29:31 -0500 Subject: [PATCH 21/31] cleanup popup implementation. (#2071) - bunch of other misc cleanups --- .../src/backend/wayland/application.rs | 70 +++--- druid-shell/src/backend/wayland/clipboard.rs | 19 +- druid-shell/src/backend/wayland/mod.rs | 2 - druid-shell/src/backend/wayland/pointers.rs | 1 + .../backend/wayland/surfaces/layershell.rs | 60 +++-- .../src/backend/wayland/surfaces/mod.rs | 18 +- .../src/backend/wayland/surfaces/popup.rs | 137 ++++++++-- .../src/backend/wayland/surfaces/surface.rs | 66 ++--- .../src/backend/wayland/surfaces/toplevel.rs | 19 ++ druid-shell/src/backend/wayland/window.rs | 237 +++++++++--------- druid-shell/src/dialog.rs | 2 +- 11 files changed, 362 insertions(+), 269 deletions(-) diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index d392aac84e..6ffee152dd 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -181,13 +181,7 @@ impl Application { let (outputsaddedtx, outputsaddedrx) = calloop::channel::channel::(); let globals = wl::GlobalManager::new_with_cb(&attached_server, { - move |event, registry, data| { - tracing::debug!( - "global manager event received {:?}\n{:?}\n{:?}", - event, - registry, - data - ); + move |event, registry, _ctx| { match event { wl::GlobalEvent::New { id, @@ -270,15 +264,6 @@ impl Application { .sync_roundtrip(&mut (), |_, _, _| unreachable!()) .map_err(Error::fatal)?; - let mut globals_list = globals.list(); - globals_list.sort_by(|(_, name1, version1), (_, name2, version2)| { - name1.cmp(name2).then(version1.cmp(version2)) - }); - - for (id, name, version) in globals_list.into_iter() { - tracing::debug!("{:?}@{:?} - {:?}", name, version, id); - } - let xdg_base = globals .instantiate_exact::(2) .map_err(|e| Error::global("xdg_wm_base", 2, e))?; @@ -370,7 +355,7 @@ impl Application { if capabilities.contains(wl_seat::Capability::Keyboard) && seat.keyboard.is_none() { - seat.keyboard = Some(app_data.keyboard.attach(id, seat.wl_seat.clone())) + seat.keyboard = Some(app_data.keyboard.attach(id, seat.wl_seat.clone())); } if capabilities.contains(wl_seat::Capability::Pointer) && seat.pointer.is_none() @@ -403,7 +388,7 @@ impl Application { } pub fn run(self, _handler: Option>) { - tracing::info!("run initiated"); + tracing::info!("wayland event loop initiated"); // NOTE if we want to call this function more than once, we will need to put the timer // source back. let timer_source = self.data.timer_source.borrow_mut().take().unwrap(); @@ -431,6 +416,7 @@ impl Application { } }) .unwrap(); + handle .insert_source( self.data.outputs_removed.borrow_mut().take().unwrap(), @@ -456,20 +442,30 @@ impl Application { let signal = eventloop.get_signal(); let handle = handle.clone(); - eventloop - .run( - Duration::from_millis(20), - &mut self.data.clone(), - move |appdata| { - if appdata.shutdown.get() { - tracing::debug!("shutting down"); - signal.stop(); - } + let res = eventloop.run( + Duration::from_millis(20), + &mut self.data.clone(), + move |appdata| { + if appdata.shutdown.get() { + tracing::debug!("shutting down, requested"); + signal.stop(); + return; + } - ApplicationData::idle_repaint(handle.clone()); - }, - ) - .unwrap(); + if appdata.handles.borrow().len() == 0 { + tracing::debug!("shutting down, no window remaining"); + signal.stop(); + return; + } + + ApplicationData::idle_repaint(handle.clone()); + }, + ); + + match res { + Ok(_) => tracing::info!("wayland event loop completed"), + Err(cause) => tracing::error!("wayland event loop failed {:?}", cause), + } } pub fn quit(&self) { @@ -576,18 +572,8 @@ impl ApplicationData { } } - pub(super) fn popup<'a>(&self, surface: &'a surfaces::popup::Surface) -> Result<(), Error> { - match self.acquire_current_window() { - None => return Err(Error::string("parent window does not exist")), - Some(winhandle) => winhandle.popup(surface), - } - } - fn handle_timer_event(&self, _token: TimerToken) { - // Shouldn't be necessary. - self.timer_handle.cancel_all_timeouts(); // Don't borrow the timers in case the callbacks want to add more. - // TODO make this in the stack (smallvec) let mut expired_timers = Vec::with_capacity(1); let mut timers = self.timers.borrow_mut(); let now = Instant::now(); @@ -601,7 +587,7 @@ impl ApplicationData { Some(s) => s, None => { // NOTE this might be expected - log::warn!( + tracing::warn!( "received event for surface that doesn't exist any more {:?} {:?}", expired, expired.id() diff --git a/druid-shell/src/backend/wayland/clipboard.rs b/druid-shell/src/backend/wayland/clipboard.rs index 888e5baa9d..c3588fcfa7 100644 --- a/druid-shell/src/backend/wayland/clipboard.rs +++ b/druid-shell/src/backend/wayland/clipboard.rs @@ -106,7 +106,10 @@ pub struct Manager { } impl Manager { - pub(super) fn new(display: &wl::Display, gm: &wl::GlobalManager) -> Result { + pub(super) fn new( + display: &wl::Display, + gm: &wl::GlobalManager, + ) -> Result { let m = gm .instantiate_exact::(3) .map_err(|e| waylanderr::Error::global("wl_data_device_manager", 1, e))?; @@ -257,13 +260,15 @@ impl Clipboard { pub fn get_string(&self) -> Option { vec![Clipboard::UTF8, Clipboard::TEXT, Clipboard::UTF8_STRING] .iter() - .find_map(|mimetype| match std::str::from_utf8(&self.inner.receive(*mimetype)?) { - Ok(s) => Some(s.to_string()), - Err(cause) => { - tracing::error!("clipboard unable to retrieve utf8 content {:?}", cause); - None + .find_map( + |mimetype| match std::str::from_utf8(&self.inner.receive(*mimetype)?) { + Ok(s) => Some(s.to_string()), + Err(cause) => { + tracing::error!("clipboard unable to retrieve utf8 content {:?}", cause); + None + } }, - }) + ) } /// Given a list of supported clipboard types, returns the supported type which has diff --git a/druid-shell/src/backend/wayland/mod.rs b/druid-shell/src/backend/wayland/mod.rs index 32feb540e4..58eaf7b947 100644 --- a/druid-shell/src/backend/wayland/mod.rs +++ b/druid-shell/src/backend/wayland/mod.rs @@ -16,7 +16,6 @@ pub mod application; pub mod clipboard; -pub mod dialog; pub mod error; mod events; pub mod keyboard; @@ -24,7 +23,6 @@ pub mod menu; pub mod pointers; pub mod screen; pub mod surfaces; -pub mod util; pub mod window; /// Little enum to make it clearer what some return values mean. diff --git a/druid-shell/src/backend/wayland/pointers.rs b/druid-shell/src/backend/wayland/pointers.rs index b53abd9456..a8368d318f 100644 --- a/druid-shell/src/backend/wayland/pointers.rs +++ b/druid-shell/src/backend/wayland/pointers.rs @@ -107,6 +107,7 @@ pub(crate) enum PointerEvent { } /// An enum that we will convert into the different callbacks. +#[derive(Debug)] pub(crate) enum MouseEvtKind { Move(mouse::MouseEvent), Up(mouse::MouseEvent), diff --git a/druid-shell/src/backend/wayland/surfaces/layershell.rs b/druid-shell/src/backend/wayland/surfaces/layershell.rs index c3f7f142c8..203b62b37b 100644 --- a/druid-shell/src/backend/wayland/surfaces/layershell.rs +++ b/druid-shell/src/backend/wayland/surfaces/layershell.rs @@ -1,19 +1,18 @@ use wayland_client as wlc; use wayland_protocols::wlr::unstable::layer_shell::v1::client as layershell; +use wayland_protocols::xdg_shell::client::xdg_surface; use crate::kurbo; use crate::window; use super::super::application::Output; use super::super::error; -use super::popup; use super::surface; use super::Compositor; use super::CompositorHandle; use super::Handle; use super::Outputs; use super::Popup; -use super::PopupHandle; struct Inner { config: Config, @@ -24,17 +23,32 @@ struct Inner { available: std::cell::RefCell, } -impl Drop for Inner { - fn drop(&mut self) { - self.ls_surface.borrow().destroy(); +impl Inner { + fn popup<'a>( + &self, + surface: &'a wlc::Main, + pos: &'a wlc::Main, + ) -> wlc::Main { + let popup = surface.get_popup(None, pos); + self.ls_surface.borrow().get_popup(&popup); + return popup; } } impl Popup for Inner { - fn popup_impl(&self, p: &popup::Surface) -> Result<(), error::Error> { - self.ls_surface.borrow().get_popup(&p.get_xdg_popup()); - p.commit(); - Ok(()) + fn surface<'a>( + &self, + surface: &'a wlc::Main, + pos: &'a wlc::Main, + ) -> Result, error::Error> + { + return Ok(self.popup(surface, pos)); + } +} + +impl Drop for Inner { + fn drop(&mut self) { + self.ls_surface.borrow().destroy(); } } @@ -218,13 +232,6 @@ impl Surface { } tracing::info!("attempting to initialize layershell"); - handle - .inner - .wl_surface - .borrow() - .set_popup_impl(PopupHandle { - inner: handle.inner.clone(), - }); handle.inner.ls_surface.borrow().quick_assign({ let handle = handle.clone(); @@ -319,6 +326,17 @@ impl Outputs for Surface { } } +impl Popup for Surface { + fn surface<'a>( + &self, + popup: &'a wlc::Main, + pos: &'a wlc::Main, + ) -> Result, error::Error> + { + return Ok(self.inner.popup(popup, pos)); + } +} + impl Handle for Surface { fn get_size(&self) -> kurbo::Size { return self.inner.wl_surface.borrow().get_size(); @@ -368,10 +386,6 @@ impl Handle for Surface { } } - fn popup(&self, popup: &popup::Surface) -> Result<(), error::Error> { - return self.inner.wl_surface.borrow().popup(popup); - } - fn release(&self) { return self.inner.wl_surface.borrow().release(); } @@ -404,3 +418,9 @@ impl From for Box { Box::new(s) as Box } } + +impl From for Box { + fn from(s: Surface) -> Self { + Box::new(s) as Box + } +} diff --git a/druid-shell/src/backend/wayland/surfaces/mod.rs b/druid-shell/src/backend/wayland/surfaces/mod.rs index 0ac6aec82b..4972176fe3 100644 --- a/druid-shell/src/backend/wayland/surfaces/mod.rs +++ b/druid-shell/src/backend/wayland/surfaces/mod.rs @@ -2,6 +2,7 @@ use wayland_client::protocol::wl_shm::WlShm; use wayland_client::{self as wlc, protocol::wl_surface::WlSurface}; use wayland_protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1; use wayland_protocols::wlr::unstable::layer_shell::v1::client::zwlr_layer_shell_v1::ZwlrLayerShellV1; +use wayland_protocols::xdg_shell::client::xdg_popup; use wayland_protocols::xdg_shell::client::xdg_positioner; use wayland_protocols::xdg_shell::client::xdg_surface; @@ -43,17 +44,11 @@ impl dyn Decor { } pub trait Popup { - fn popup_impl(&self, popup: &popup::Surface) -> Result<(), error::Error>; -} - -pub struct PopupHandle { - inner: std::sync::Arc, -} - -impl PopupHandle { - fn popup(&self, p: &popup::Surface) -> Result<(), error::Error> { - self.inner.popup_impl(p) - } + fn surface<'a>( + &self, + popup: &'a wlc::Main, + pos: &'a wlc::Main, + ) -> Result, error::Error>; } pub(super) trait Outputs { @@ -73,7 +68,6 @@ pub trait Handle { fn get_idle_handle(&self) -> idle::Handle; fn get_scale(&self) -> Scale; fn run_idle(&self); - fn popup(&self, popup: &popup::Surface) -> Result<(), error::Error>; fn release(&self); fn data(&self) -> Option>; } diff --git a/druid-shell/src/backend/wayland/surfaces/popup.rs b/druid-shell/src/backend/wayland/surfaces/popup.rs index 894fa0c698..c96e22e626 100644 --- a/druid-shell/src/backend/wayland/surfaces/popup.rs +++ b/druid-shell/src/backend/wayland/surfaces/popup.rs @@ -6,15 +6,20 @@ use wayland_protocols::xdg_shell::client::xdg_surface; use crate::kurbo; use crate::window; +use super::error; use super::surface; use super::Compositor; use super::CompositorHandle; use super::Handle; use super::Outputs; +use super::Popup; +#[allow(unused)] struct Inner { wl_surface: surface::Surface, + wl_xdg_surface: wlc::Main, wl_xdg_popup: wlc::Main, + wl_xdg_pos: wlc::Main, } impl From for std::sync::Arc { @@ -49,20 +54,32 @@ impl Config { pos.set_anchor(self.anchor); pos.set_gravity(self.gravity); pos.set_constraint_adjustment(self.constraint_adjustment.bits()); + // requires version 3... + // pos.set_reactive(); pos } + + pub fn with_size(mut self, dim: kurbo::Size) -> Self { + self.size = dim; + self + } + + pub fn with_offset(mut self, p: kurbo::Point) -> Self { + self.offset = p; + self + } } impl Default for Config { fn default() -> Self { Self { size: kurbo::Size::new(1., 1.), - anchor: xdg_positioner::Anchor::None, + anchor: xdg_positioner::Anchor::Bottom, offset: kurbo::Point::ZERO, anchor_rect: (kurbo::Point::ZERO, kurbo::Size::from((1., 1.))), - gravity: xdg_positioner::Gravity::None, - constraint_adjustment: xdg_positioner::ConstraintAdjustment::None, + gravity: xdg_positioner::Gravity::BottomLeft, + constraint_adjustment: xdg_positioner::ConstraintAdjustment::all(), } } } @@ -77,8 +94,8 @@ impl Surface { c: impl Into, handler: Box, config: Config, - parent: Option<&xdg_surface::XdgSurface>, - ) -> Self { + parent: &dyn Popup, + ) -> Result { let compositor = CompositorHandle::new(c); let wl_surface = surface::Surface::new(compositor.clone(), handler, kurbo::Size::ZERO); let wl_xdg_surface = compositor.get_xdg_surface(&wl_surface.inner.wl_surface.borrow()); @@ -97,12 +114,15 @@ impl Surface { } }); - let pos = config.apply(&compositor); - - let wl_xdg_popup = wl_xdg_surface.get_popup(parent, &pos); - - pos.destroy(); + let wl_xdg_pos = config.apply(&compositor); + wl_xdg_pos.quick_assign(|obj, event, _| { + tracing::debug!("{:?} {:?}", obj, event); + }); + let wl_xdg_popup = match parent.surface(&wl_xdg_surface, &wl_xdg_pos) { + Ok(p) => p, + Err(cause) => return Err(cause), + }; wl_xdg_popup.quick_assign({ let wl_surface = wl_surface.clone(); move |_xdg_popup, event, _| { @@ -113,7 +133,7 @@ impl Surface { width, height, } => { - tracing::trace!( + tracing::debug!( "popup configuration ({:?},{:?}) {:?}x{:?}", x, y, @@ -122,6 +142,17 @@ impl Surface { ); wl_surface.update_dimensions((width as f64, height as f64)); } + xdg_popup::Event::PopupDone => { + tracing::debug!("popup done {:?}", event); + match wl_surface.data() { + None => tracing::warn!("missing surface data, cannot close popup"), + Some(data) => { + data.with_handler(|winhandle| { + winhandle.request_close(); + }); + } + }; + } _ => tracing::warn!("unhandled xdg_popup event configure {:?}", event), }; } @@ -130,15 +161,14 @@ impl Surface { let handle = Self { inner: std::sync::Arc::new(Inner { wl_surface, + wl_xdg_surface, wl_xdg_popup, + wl_xdg_pos, }), }; - handle - } - - pub(super) fn get_xdg_popup(&self) -> wlc::Main { - self.inner.wl_xdg_popup.clone() + handle.commit(); + Ok(handle) } pub(super) fn commit(&self) { @@ -154,9 +184,53 @@ impl Surface { } } -impl From for Box { - fn from(s: Surface) -> Box { - Box::new(s.inner.wl_surface.clone()) as Box +impl Handle for Surface { + fn get_size(&self) -> kurbo::Size { + return self.inner.wl_surface.get_size(); + } + + fn set_size(&self, dim: kurbo::Size) { + self.inner.wl_surface.set_size(dim); + } + + fn request_anim_frame(&self) { + self.inner.wl_surface.request_anim_frame() + } + + fn invalidate(&self) { + return self.inner.wl_surface.invalidate(); + } + + fn invalidate_rect(&self, rect: kurbo::Rect) { + return self.inner.wl_surface.invalidate_rect(rect); + } + + fn remove_text_field(&self, token: crate::TextFieldToken) { + return self.inner.wl_surface.remove_text_field(token); + } + + fn set_focused_text_field(&self, active_field: Option) { + return self.inner.wl_surface.set_focused_text_field(active_field); + } + + fn get_idle_handle(&self) -> super::idle::Handle { + return self.inner.wl_surface.get_idle_handle(); + } + + fn get_scale(&self) -> crate::Scale { + return self.inner.wl_surface.get_scale(); + } + + fn run_idle(&self) { + self.inner.wl_surface.run_idle(); + } + + fn release(&self) { + return self.inner.wl_surface.release(); + } + + fn data(&self) -> Option> { + return self.inner.wl_surface.data(); } } @@ -166,14 +240,31 @@ impl From<&Surface> for std::sync::Arc { } } -impl From for std::sync::Arc { - fn from(s: Surface) -> std::sync::Arc { - std::sync::Arc::::from(s.inner.wl_surface.clone()) +impl Popup for Surface { + fn surface<'a>( + &self, + popup: &'a wlc::Main, + pos: &'a wlc::Main, + ) -> Result, error::Error> + { + Ok(popup.get_popup(Some(&self.inner.wl_xdg_surface), pos)) + } +} + +impl From for Box { + fn from(s: Surface) -> Box { + Box::new(s.inner.wl_surface.clone()) as Box } } impl From for Box { fn from(s: Surface) -> Box { - Box::new(s.inner.wl_surface.clone()) as Box + Box::new(s.clone()) as Box + } +} + +impl From for Box { + fn from(s: Surface) -> Self { + Box::new(s) as Box } } diff --git a/druid-shell/src/backend/wayland/surfaces/surface.rs b/druid-shell/src/backend/wayland/surfaces/surface.rs index eb00e5fd12..3369d73634 100644 --- a/druid-shell/src/backend/wayland/surfaces/surface.rs +++ b/druid-shell/src/backend/wayland/surfaces/surface.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use wayland_client as wlc; use wayland_client::protocol::wl_surface; -use crate::backend::application::Output; +use crate::backend::wayland::application; use crate::kurbo; use crate::window; use crate::{piet::Piet, region::Region, scale::Scale, TextFieldToken}; @@ -13,8 +13,8 @@ use super::super::Changed; use super::buffers; use super::error; use super::idle; -use super::popup; -use super::{Compositor, CompositorHandle, Decor, Handle, Outputs, PopupHandle}; +use super::Popup; +use super::{Compositor, CompositorHandle, Decor, Handle, Outputs}; pub enum DeferredTask { Paint, @@ -57,7 +57,6 @@ impl Surface { active_text_input: Cell::new(None), damaged_region: RefCell::new(Region::EMPTY), deferred_tasks: RefCell::new(std::collections::VecDeque::new()), - popup_impl: RefCell::new(None), }); // register to receive wl_surface events. @@ -78,10 +77,6 @@ impl Surface { self.inner.resize(dim) } - pub(super) fn set_popup_impl(&self, imp: PopupHandle) { - self.inner.popup_impl.replace(Some(imp)); - } - pub(super) fn commit(&self) { self.inner.wl_surface.borrow().commit() } @@ -144,11 +139,11 @@ impl Surface { } impl Outputs for Surface { - fn removed(&self, o: &Output) { + fn removed(&self, o: &application::Output) { self.inner.outputs.borrow_mut().remove(&o.id()); } - fn inserted(&self, _: &Output) { + fn inserted(&self, _: &application::Output) { // nothing to do here. } } @@ -194,10 +189,6 @@ impl Handle for Surface { self.inner.run_idle(); } - fn popup<'a>(&self, p: &'a popup::Surface) -> Result<(), error::Error> { - self.inner.popup(p) - } - fn release(&self) { self.inner.release() } @@ -252,8 +243,6 @@ pub struct Data { deferred_tasks: RefCell>, idle_queue: std::sync::Arc>>, - - popup_impl: RefCell>, } impl Data { @@ -288,7 +277,6 @@ impl Data { } pub(super) fn update_dimensions(&self, dim: impl Into) -> kurbo::Size { - // let dim = kurbo::Size::from((width as f64, height as f64)); let dim = dim.into(); if self.logical_size.get() != self.resize(dim) { match self.handler.try_borrow_mut() { @@ -309,7 +297,6 @@ impl Data { height: dim.height as i32, }; let previous_logical_size = self.logical_size.replace(dim); - if previous_logical_size != dim { self.buffers.set_size(raw_logical_size.scale(scale)); } @@ -362,11 +349,11 @@ impl Data { /// - `buf` is what we draw the frame into /// - `size` is the physical size in pixels we are drawing. /// - `force` means draw the whole frame, even if it wasn't all invalidated. - pub(super) fn paint(&self, size: buffers::RawSize, buf: &mut [u8], force: bool) { + pub(super) fn paint(&self, physical_size: buffers::RawSize, buf: &mut [u8], force: bool) { tracing::trace!( "paint initiated {:?} - {:?} {:?}", self.get_size(), - size, + physical_size, force ); @@ -390,8 +377,6 @@ impl Data { } } - let physical_size = self.buffers.size(); - // create cairo context (safety: we must drop the buffer before we commit the frame) // TODO: Cairo is native-endian while wayland is little-endian, which is a pain. Currently // will give incorrect results on big-endian architectures. @@ -435,9 +420,9 @@ impl Data { // The handler must not be already borrowed. This may mean deferring this call. self.handler.borrow_mut().paint(&mut piet, &*region); } + // reset damage ready for next frame. self.damaged_region.borrow_mut().clear(); - self.buffers.attach(self); self.wl_surface.borrow().commit(); } @@ -499,10 +484,6 @@ impl Data { } } - pub(super) fn wayland_surface_id(&self) -> u32 { - wlc::Proxy::from(self.wl_surface.borrow().detach()).id() - } - pub(super) fn get_size(&self) -> kurbo::Size { // size in pixels, so we must apply scale. let logical_size = self.logical_size.get(); @@ -552,16 +533,6 @@ impl Data { }); } - pub(super) fn popup<'a>(&self, p: &'a popup::Surface) -> Result<(), error::Error> { - match &*self.popup_impl.borrow() { - Some(imp) => imp.popup(p), - None => Err(error::Error::string(format!( - "parent window doesn't support popups: {:?}", - self.wayland_surface_id() - ))), - } - } - pub(super) fn release(&self) { self.wl_surface.borrow().destroy(); } @@ -594,9 +565,21 @@ impl Decor for Dead { } impl Outputs for Dead { - fn removed(&self, _: &Output) {} + fn removed(&self, _: &application::Output) {} + + fn inserted(&self, _: &application::Output) {} +} - fn inserted(&self, _: &Output) {} +impl Popup for Dead { + fn surface<'a>( + &self, + _: &'a wlc::Main, + _: &'a wlc::Main, + ) -> Result, error::Error> + { + tracing::warn!("popup invoked on a dead surface"); + Err(error::Error::InvalidParent(0)) + } } impl Handle for Dead { @@ -640,11 +623,6 @@ impl Handle for Dead { tracing::warn!("run_idle invoked on a dead surface") } - fn popup(&self, _: &popup::Surface) -> Result<(), error::Error> { - tracing::warn!("popup invoked on a dead surface"); - Err(error::Error::InvalidParent(0)) - } - fn release(&self) { tracing::warn!("release invoked on a dead surface"); } diff --git a/druid-shell/src/backend/wayland/surfaces/toplevel.rs b/druid-shell/src/backend/wayland/surfaces/toplevel.rs index 09788e07e1..799d2ee800 100644 --- a/druid-shell/src/backend/wayland/surfaces/toplevel.rs +++ b/druid-shell/src/backend/wayland/surfaces/toplevel.rs @@ -6,12 +6,14 @@ use wayland_protocols::xdg_shell::client::xdg_toplevel; use crate::kurbo; use crate::window; +use super::error; use super::surface; use super::Compositor; use super::CompositorHandle; use super::Decor; use super::Handle; use super::Outputs; +use super::Popup; struct Inner { wl_surface: surface::Surface, @@ -137,6 +139,17 @@ impl Surface { } } +impl Popup for Surface { + fn surface<'a>( + &self, + popup: &'a wlc::Main, + pos: &'a wlc::Main, + ) -> Result, error::Error> + { + Ok(popup.get_popup(Some(&self.inner.xdg_surface), pos)) + } +} + impl Decor for Surface { fn inner_set_title(&self, title: String) { self.inner.xdg_toplevel.set_title(title); @@ -172,3 +185,9 @@ impl From for Box { Box::new(s.inner.wl_surface.clone()) as Box } } + +impl From for Box { + fn from(s: Surface) -> Self { + Box::new(s) as Box + } +} diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index f57a68b084..28fd95adfe 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -17,7 +17,7 @@ use tracing; use super::{ application::{Application, ApplicationData, Output, Timer}, - error, + error::Error, menu::Menu, surfaces, }; @@ -42,6 +42,7 @@ struct Inner { pub(super) decor: Box, pub(super) surface: Box, pub(super) outputs: Box, + pub(super) popup: Box, pub(super) appdata: std::sync::Weak, } @@ -60,11 +61,29 @@ impl surfaces::Outputs for WindowHandle { } } +impl surfaces::Popup for WindowHandle { + fn surface<'a>( + &self, + popup: &'a wayland_client::Main< + wayland_protocols::xdg_shell::client::xdg_surface::XdgSurface, + >, + pos: &'a wayland_client::Main< + wayland_protocols::xdg_shell::client::xdg_positioner::XdgPositioner, + >, + ) -> Result< + wayland_client::Main, + Error, + > { + self.inner.popup.surface(popup, pos) + } +} + impl WindowHandle { pub(super) fn new( outputs: impl Into>, decor: impl Into>, surface: impl Into>, + popup: impl Into>, appdata: impl Into>, ) -> Self { Self { @@ -73,6 +92,7 @@ impl WindowHandle { outputs: outputs.into(), decor: decor.into(), surface: surface.into(), + popup: popup.into(), appdata: appdata.into(), }), } @@ -86,23 +106,20 @@ impl WindowHandle { tracing::info!("show initiated"); } - pub fn resizable(&self, resizable: bool) { - tracing::info!("resizable initiated {:?}", resizable); - todo!() + pub fn resizable(&self, _resizable: bool) { + tracing::warn!("resizable is unimplemented on wayland"); } pub fn show_titlebar(&self, _show_titlebar: bool) { - tracing::info!("show_titlebar initiated"); - todo!() + tracing::warn!("show_titlebar is unimplemented on wayland"); } pub fn set_position(&self, _position: Point) { - tracing::info!("set_position initiated"); - todo!() + tracing::warn!("set_position is unimplemented on wayland"); } pub fn get_position(&self) -> Point { - tracing::info!("get_position initiated"); + tracing::warn!("get_position is unimplemented on wayland"); Point::ZERO } @@ -110,10 +127,6 @@ impl WindowHandle { Insets::from(0.) } - pub fn set_level(&self, _level: WindowLevel) { - log::warn!("level is unsupported on wayland"); - } - pub fn set_size(&self, size: Size) { self.inner.surface.set_size(size); } @@ -123,18 +136,16 @@ impl WindowHandle { } pub fn set_window_state(&mut self, _current_state: window::WindowState) { - tracing::warn!("unimplemented set_window_state initiated"); - todo!(); + tracing::warn!("set_window_state is unimplemented on wayland"); } pub fn get_window_state(&self) -> window::WindowState { - tracing::warn!("unimplemented get_window_state initiated"); + tracing::warn!("get_window_state is unimplemented on wayland"); window::WindowState::Maximized } pub fn handle_titlebar(&self, _val: bool) { - tracing::warn!("unimplemented handle_titlebar initiated"); - todo!(); + tracing::warn!("handle_titlebar is unimplemented on wayland"); } /// Close the window. @@ -157,29 +168,22 @@ impl WindowHandle { /// Bring this window to the front of the window stack and give it focus. pub fn bring_to_front_and_focus(&self) { tracing::warn!("unimplemented bring_to_front_and_focus initiated"); - todo!() } /// Request a new paint, but without invalidating anything. pub fn request_anim_frame(&self) { - tracing::trace!("request_anim_frame initiated"); self.inner.surface.request_anim_frame(); - tracing::trace!("request_anim_frame completed"); } /// Request invalidation of the entire window contents. pub fn invalidate(&self) { - tracing::trace!("invalidate initiated"); self.inner.surface.invalidate(); - tracing::trace!("invalidate completed"); } /// Request invalidation of one rectangle, which is given in display points relative to the /// drawing area. pub fn invalidate_rect(&self, rect: Rect) { - tracing::trace!("invalidate_rect initiated"); self.inner.surface.invalidate_rect(rect); - tracing::trace!("invalidate_rect completed"); } pub fn text(&self) -> PietText { @@ -191,15 +195,11 @@ impl WindowHandle { } pub fn remove_text_field(&self, token: TextFieldToken) { - tracing::trace!("remove_text_field initiated"); self.inner.surface.remove_text_field(token); - tracing::trace!("remove_text_field completed"); } pub fn set_focused_text_field(&self, active_field: Option) { - tracing::trace!("set_focused_text_field initiated"); self.inner.surface.set_focused_text_field(active_field); - tracing::trace!("set_focused_text_field completed"); } pub fn update_text_field(&self, _token: TextFieldToken, _update: Event) { @@ -207,7 +207,6 @@ impl WindowHandle { } pub fn request_timer(&self, deadline: std::time::Instant) -> TimerToken { - tracing::trace!("request_timer initiated"); let appdata = match self.inner.appdata.upgrade() { Some(d) => d, None => panic!("requested timer on a window that was destroyed"), @@ -239,11 +238,9 @@ impl WindowHandle { } pub fn set_cursor(&mut self, cursor: &Cursor) { - tracing::trace!("set_cursor initiated"); if let Some(appdata) = self.inner.appdata.upgrade() { appdata.set_cursor(cursor); } - tracing::trace!("set_cursor completed"); } pub fn make_cursor(&self, _desc: &CursorDesc) -> Option { @@ -252,35 +249,31 @@ impl WindowHandle { } pub fn open_file(&mut self, _options: FileDialogOptions) -> Option { - tracing::info!("open_file initiated"); - todo!() + tracing::warn!("unimplemented open_file"); + None } pub fn save_as(&mut self, _options: FileDialogOptions) -> Option { - tracing::info!("save_as initiated"); - todo!() + tracing::warn!("unimplemented save_as"); + None } /// Get a handle that can be used to schedule an idle task. pub fn get_idle_handle(&self) -> Option { - tracing::trace!("get_idle_handle initiated"); Some(self.inner.surface.get_idle_handle()) } /// Get the `Scale` of the window. pub fn get_scale(&self) -> Result { - tracing::info!("get_scale initiated"); Ok(self.inner.surface.get_scale()) } pub fn set_menu(&self, _menu: Menu) { - tracing::info!("set_menu initiated"); - todo!() + tracing::warn!("set_menu not implement for wayland"); } pub fn show_context_menu(&self, _menu: Menu, _pos: Point) { - tracing::info!("show_context_menu initiated"); - todo!() + tracing::warn!("show_context_menu not implement for wayland"); } pub fn set_title(&self, title: impl Into) { @@ -291,19 +284,14 @@ impl WindowHandle { self.inner.surface.run_idle(); } - #[allow(unused)] - pub(super) fn popup<'a>(&self, s: &'a surfaces::popup::Surface) -> Result<(), error::Error> { - self.inner.surface.popup(s) - } - pub(super) fn data(&self) -> Option> { self.inner.surface.data() } } impl std::cmp::PartialEq for WindowHandle { - fn eq(&self, _rhs: &Self) -> bool { - todo!() + fn eq(&self, rhs: &Self) -> bool { + self.id() == rhs.id() } } @@ -315,6 +303,7 @@ impl std::default::Default for WindowHandle { outputs: Box::new(surfaces::surface::Dead::default()), decor: Box::new(surfaces::surface::Dead::default()), surface: Box::new(surfaces::surface::Dead::default()), + popup: Box::new(surfaces::surface::Dead::default()), appdata: std::sync::Weak::new(), }), } @@ -326,12 +315,12 @@ pub struct CustomCursor; /// Builder abstraction for creating new windows pub(crate) struct WindowBuilder { - app_data: std::sync::Weak, + appdata: std::sync::Weak, handler: Option>, title: String, menu: Option, position: Option, - level: Option, + level: WindowLevel, state: Option, // pre-scaled size: Size, @@ -343,13 +332,13 @@ pub(crate) struct WindowBuilder { impl WindowBuilder { pub fn new(app: Application) -> WindowBuilder { WindowBuilder { - app_data: std::sync::Arc::downgrade(&app.data), + appdata: std::sync::Arc::downgrade(&app.data), handler: None, title: String::new(), menu: None, size: Size::new(0.0, 0.0), position: None, - level: None, + level: WindowLevel::AppWindow, state: None, min_size: None, resizable: true, @@ -388,7 +377,7 @@ impl WindowBuilder { } pub fn set_level(&mut self, level: WindowLevel) { - self.level = Some(level); + self.level = level; } pub fn set_window_state(&mut self, state: window::WindowState) { @@ -405,13 +394,33 @@ impl WindowBuilder { pub fn build(self) -> Result { if matches!(self.menu, Some(_)) { - tracing::error!("menus unimplemented"); + tracing::warn!("menus unimplemented for wayland"); } - let appdata = match self.app_data.upgrade() { + if let WindowLevel::DropDown(parent) = self.level { + let dim = self.min_size.unwrap_or_else(|| Size::ZERO); + let dim = Size::new(dim.width.max(1.), dim.height.max(1.)); + let dim = Size::new( + self.size.width.max(dim.width), + self.size.height.max(dim.height), + ); + + let config = surfaces::popup::Config::default() + .with_size(dim) + .with_offset(Into::into( + self.position.unwrap_or_else(|| Into::into((0., 0.))), + )); + + tracing::debug!("popup {:?}", config); + + return popup::create(&parent.0, &config, self.appdata, self.handler); + } + + let appdata = match self.appdata.upgrade() { Some(d) => d, None => return Err(ShellError::ApplicationDropped), }; + let handler = self.handler.expect("must set a window handler"); // compute the initial window size. let initial_size = appdata.initial_window_size(self.size); @@ -425,7 +434,8 @@ impl WindowBuilder { surface.clone(), surface.clone(), surface.clone(), - self.app_data.clone(), + surface.clone(), + self.appdata.clone(), ); if let Some(_) = appdata @@ -433,8 +443,11 @@ impl WindowBuilder { .borrow_mut() .insert(handle.id(), handle.clone()) { - panic!("wayland should use unique object IDs"); + return Err(ShellError::Platform(Error::string( + "wayland should use a unique id", + ))); } + appdata .active_surface_id .borrow_mut() @@ -484,6 +497,7 @@ pub mod layershell { Some(d) => d, None => return Err(ShellError::ApplicationDropped), }; + let winhandle = match self.winhandle { Some(winhandle) => winhandle, None => { @@ -503,6 +517,7 @@ pub mod layershell { surface.clone(), surfaces::surface::Dead::default(), surface.clone(), + surface.clone(), self.appdata.clone(), ); @@ -533,79 +548,65 @@ pub mod popup { use crate::error::Error as ShellError; use crate::window::WinHandler; + use super::WindowBuilder; use super::WindowHandle; use crate::backend::wayland::application::{Application, ApplicationData}; use crate::backend::wayland::error::Error; use crate::backend::wayland::surfaces; - /// Builder abstraction for creating new windows - pub(crate) struct Builder { - appdata: std::sync::Weak, + pub(super) fn create( + parent: &WindowHandle, + config: &surfaces::popup::Config, + wappdata: std::sync::Weak, winhandle: Option>, - pub(crate) config: surfaces::popup::Config, - } + ) -> Result { + let appdata = match wappdata.upgrade() { + Some(d) => d, + None => return Err(ShellError::ApplicationDropped), + }; - impl Builder { - pub fn new(app: Application) -> Self { - Self { - appdata: std::sync::Arc::downgrade(&app.data), - config: surfaces::popup::Config::default(), - winhandle: None, + let winhandle = match winhandle { + Some(winhandle) => winhandle, + None => { + return Err(ShellError::Platform(Error::string( + "window handler required", + ))) } - } - - pub fn set_handler(&mut self, handler: Box) { - self.winhandle = Some(handler); - } + }; - pub fn build(self) -> Result { - let appdata = match self.appdata.upgrade() { - Some(d) => d, - None => return Err(ShellError::ApplicationDropped), - }; - let winhandle = match self.winhandle { - Some(winhandle) => winhandle, - None => { - return Err(ShellError::Platform(Error::string( - "window handler required", - ))) - } + // compute the initial window size. + let updated = config.clone(); + let surface = + match surfaces::popup::Surface::new(appdata.clone(), winhandle, updated, parent) { + Err(cause) => return Err(ShellError::Platform(cause)), + Ok(s) => s, }; - // compute the initial window size. - let updated = self.config.clone(); - - let surface = surfaces::popup::Surface::new(appdata.clone(), winhandle, updated, None); - - if let Err(cause) = appdata.popup(&surface) { - return Err(ShellError::Platform(cause)); - } - - let handle = WindowHandle::new( - surface.clone(), - surfaces::surface::Dead::default(), - surface.clone(), - self.appdata.clone(), - ); + let handle = WindowHandle::new( + surface.clone(), + surfaces::surface::Dead::default(), + surface.clone(), + surface.clone(), + wappdata.clone(), + ); - if let Some(_) = appdata - .handles - .borrow_mut() - .insert(handle.id(), handle.clone()) - { - panic!("wayland should use unique object IDs"); - } - appdata - .active_surface_id - .borrow_mut() - .push_front(handle.id()); + if let Some(_) = appdata + .handles + .borrow_mut() + .insert(handle.id(), handle.clone()) + { + panic!("wayland should use unique object IDs"); + } + appdata + .active_surface_id + .borrow_mut() + .push_front(handle.id()); - surface.with_handler({ - let handle = handle.clone(); - move |winhandle| winhandle.connect(&handle.into()) - }); + surface.with_handler({ + let handle = handle.clone(); + move |winhandle| winhandle.connect(&handle.into()) + }); - Ok(handle) - } + Ok(handle) } } diff --git a/druid-shell/src/dialog.rs b/druid-shell/src/dialog.rs index 0d8352b429..e26eb9e9bc 100644 --- a/druid-shell/src/dialog.rs +++ b/druid-shell/src/dialog.rs @@ -37,7 +37,7 @@ pub struct FileInfo { } /// Type of file dialog. -#[cfg(not(all(any(target_os = "linux", target_os = "openbsd"), feature = "x11")))] +#[cfg(not(any(all(feature = "x11", any(target_os = "linux", target_os = "openbsd")), feature = "wayland")))] #[derive(Clone, Copy, PartialEq)] pub enum FileDialogType { /// File open dialog. From 804040ec29cda35fc848207215ed6f8d571980e4 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 6 Dec 2021 02:15:26 -0500 Subject: [PATCH 22/31] implement screen for wayland. (#2076) - refactors some of the wayland connection code out of the application startup. --- druid-shell/src/backend/mod.rs | 18 +- .../src/backend/wayland/application.rs | 224 ++++++++++-------- druid-shell/src/backend/wayland/dialog.rs | 31 --- druid-shell/src/backend/wayland/display.rs | 222 +++++++++++++++++ druid-shell/src/backend/wayland/error.rs | 8 + druid-shell/src/backend/wayland/events.rs | 2 +- druid-shell/src/backend/wayland/mod.rs | 2 + .../src/backend/wayland/outputs/mod.rs | 204 ++++++++++++++++ .../src/backend/wayland/outputs/wlr.rs | 149 ++++++++++++ .../src/backend/wayland/outputs/xdg.rs | 21 ++ druid-shell/src/backend/wayland/screen.rs | 35 ++- .../src/backend/wayland/surfaces/surface.rs | 8 +- druid-shell/src/backend/wayland/window.rs | 2 +- druid-shell/src/dialog.rs | 5 +- 14 files changed, 781 insertions(+), 150 deletions(-) delete mode 100644 druid-shell/src/backend/wayland/dialog.rs create mode 100644 druid-shell/src/backend/wayland/display.rs create mode 100644 druid-shell/src/backend/wayland/outputs/mod.rs create mode 100644 druid-shell/src/backend/wayland/outputs/wlr.rs create mode 100644 druid-shell/src/backend/wayland/outputs/xdg.rs diff --git a/druid-shell/src/backend/mod.rs b/druid-shell/src/backend/mod.rs index a76b70429e..977d4fa265 100644 --- a/druid-shell/src/backend/mod.rs +++ b/druid-shell/src/backend/mod.rs @@ -42,11 +42,23 @@ pub use wayland::*; #[cfg(all(feature = "wayland", target_os = "linux"))] pub(crate) mod shared; -#[cfg(all(not(feature = "x11"), not(feature = "wayland"), any(target_os = "linux", target_os = "openbsd")))] +#[cfg(all( + not(feature = "x11"), + not(feature = "wayland"), + any(target_os = "linux", target_os = "openbsd") +))] mod gtk; -#[cfg(all(not(feature = "x11"), not(feature = "wayland"), any(target_os = "linux", target_os = "openbsd")))] +#[cfg(all( + not(feature = "x11"), + not(feature = "wayland"), + any(target_os = "linux", target_os = "openbsd") +))] pub use self::gtk::*; -#[cfg(all(not(feature = "x11"), not(feature = "wayland"), any(target_os = "linux", target_os = "openbsd")))] +#[cfg(all( + not(feature = "x11"), + not(feature = "wayland"), + any(target_os = "linux", target_os = "openbsd") +))] pub(crate) mod shared; #[cfg(target_arch = "wasm32")] diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index 6ffee152dd..de4c721565 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -15,7 +15,7 @@ #![allow(clippy::single_match)] use super::{ - clipboard, error::Error, events::WaylandSource, keyboard, pointers, surfaces, + clipboard, display, error::Error, events::WaylandSource, keyboard, pointers, surfaces, window::WindowHandle, }; @@ -32,6 +32,7 @@ use std::{ use crate::backend::shared::linux; use wayland_client::protocol::wl_keyboard::WlKeyboard; +use wayland_client::protocol::wl_registry; use wayland_client::{ self as wl, protocol::{ @@ -48,7 +49,6 @@ use wayland_protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_man use wayland_protocols::wlr::unstable::layer_shell::v1::client::zwlr_layer_shell_v1::ZwlrLayerShellV1; use wayland_protocols::xdg_shell::client::xdg_positioner::XdgPositioner; use wayland_protocols::xdg_shell::client::xdg_surface; -use wayland_protocols::xdg_shell::client::xdg_wm_base::{self, XdgWmBase}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct Timer(backend::shared::Timer); @@ -93,11 +93,7 @@ pub struct Application { #[allow(dead_code)] pub(crate) struct ApplicationData { - pub(super) wl_server: wl::Display, - pub(super) event_queue: Rc>, - // Wayland globals - pub(super) globals: wl::GlobalManager, - pub(super) xdg_base: wl::Main, + pub(super) wayland: std::sync::Arc, pub(super) zxdg_decoration_manager_v1: wl::Main, pub(super) zwlr_layershell_v1: wl::Main, pub(super) wl_compositor: wl::Main, @@ -153,15 +149,6 @@ pub(crate) struct ApplicationData { impl Application { pub fn new() -> Result { tracing::info!("wayland application initiated"); - // connect to the server. Internally an `Arc`, so cloning is cheap. Must be kept alive for - // the duration of the app. - let wl_server = wl::Display::connect_to_env()?; - - // create an event queue (required for receiving events from the server) - let mut event_queue = wl_server.create_event_queue(); - - // Tell wayland to use our event queue for creating new objects (applies recursively). - let attached_server = (*wl_server).clone().attach(event_queue.token()); // Global objects that can come and go (so we must handle them dynamically). // @@ -180,60 +167,66 @@ impl Application { let (outputsremovedtx, outputsremovedrx) = calloop::channel::channel::(); let (outputsaddedtx, outputsaddedrx) = calloop::channel::channel::(); - let globals = wl::GlobalManager::new_with_cb(&attached_server, { - move |event, registry, _ctx| { + let dispatcher = display::Dispatcher::default(); + + display::GlobalEventDispatch::subscribe( + &dispatcher, + move |event: &'_ wl::GlobalEvent, + registry: &'_ wl::Attached, + _ctx: &'_ wl::DispatchData| { + // tracing::debug!("output detection consuming event {:?} {:?}", registry, event); match event { wl::GlobalEvent::New { id, interface, version, } => { - if interface.as_str() == "wl_output" && version >= 3 { - let output = registry.bind::(3, id); - let output = Output::new(id, output); - let oid = output.id(); - let gid = output.gid; - let previous = weak_outputs - .upgrade() - .unwrap() - .borrow_mut() - .insert(oid, output.clone()); - assert!( - previous.is_none(), - "internal: wayland should always use new IDs" - ); - tracing::trace!("output added {:?} {:?}", gid, oid); - output.wl_output.quick_assign(with_cloned!(weak_outputs, gid, oid, outputsaddedtx; move |a, event, b| { - tracing::trace!("output event {:?} {:?} {:?}", a, event, b); - match weak_outputs.upgrade().unwrap().borrow_mut().get_mut(&oid) { - Some(o) => o.process_event(event, &outputsaddedtx), - None => tracing::warn!( - "wayland sent an event for an output that doesn't exist global({:?}) proxy({:?}) {:?}", - &gid, - &oid, - &event, - ), - } - })); - } else if interface.as_str() == "wl_seat" && version >= 7 { - let new_seat = registry.bind::(7, id); - let prev_seat = weak_seats - .upgrade() - .unwrap() - .borrow_mut() - .insert(id, Rc::new(RefCell::new(Seat::new(new_seat)))); - assert!( - prev_seat.is_none(), - "internal: wayland should always use new IDs" - ); - // Defer setting up the pointer/keyboard event handling until we've - // finished constructing the `Application`. That way we can pass it as a - // parameter. + let id = id.clone(); + let version = version.clone(); + + if !(interface.as_str() == "wl_output" && version >= 3) { + return; } + tracing::info!("output added event {:?} {:?}", registry, interface); + let output = registry.bind::(3, id); + let output = Output::new(id, output); + let oid = output.id(); + let gid = output.gid; + let previous = weak_outputs + .upgrade() + .unwrap() + .borrow_mut() + .insert(oid, output.clone()); + assert!( + previous.is_none(), + "internal: wayland should always use new IDs" + ); + tracing::trace!("output added {:?} {:?}", gid, oid); + output.wl_output.quick_assign({ + let weak_outputs = weak_outputs.clone(); + let gid = gid; + let oid = oid; + let outputsaddedtx = outputsaddedtx.clone(); + move |a, event, b| { + tracing::trace!("output event {:?} {:?} {:?}", a, event, b); + match weak_outputs.upgrade().unwrap().borrow_mut().get_mut(&oid) { + Some(o) => o.process_event(event, &outputsaddedtx), + None => tracing::warn!( + "wayland sent an event for an output that doesn't exist global({:?}) proxy({:?}) {:?}", + &gid, + &oid, + &event, + ), + } + } + }); } - wl::GlobalEvent::Removed { id, interface } - if interface.as_str() == "wl_output" => - { + wl::GlobalEvent::Removed { id, interface } => { + let id = id.clone(); + if !(interface.as_str() == "wl_output") { + return; + } + tracing::info!("output removed event {:?} {:?}", registry, interface); let boutputs = weak_outputs.upgrade().unwrap(); let mut outputs = boutputs.borrow_mut(); let removed = outputs @@ -252,48 +245,70 @@ impl Application { Err(cause) => tracing::error!("failed to remove output {:?}", cause), } } - _ => { - tracing::debug!("unhandled global manager event received {:?}", event); + }; + }, + ); + + display::GlobalEventDispatch::subscribe( + &dispatcher, + move |event: &'_ wl::GlobalEvent, + registry: &'_ wl::Attached, + _ctx: &'_ wl::DispatchData| { + match event { + wl::GlobalEvent::New { + id, + interface, + version, + } => { + let id = id.clone(); + let version = version.clone(); + + if !(interface.as_str() == "wl_seat" && version >= 7) { + return; + } + tracing::debug!("seat detected {:?} {:?} {:?}", interface, id, version); + let new_seat = registry.bind::(7, id); + let prev_seat = weak_seats + .upgrade() + .unwrap() + .borrow_mut() + .insert(id, Rc::new(RefCell::new(Seat::new(new_seat)))); + assert!( + prev_seat.is_none(), + "internal: wayland should always use new IDs" + ); + // Defer setting up the pointer/keyboard event handling until we've + // finished constructing the `Application`. That way we can pass it as a + // parameter. } - } - } - }); + wl::GlobalEvent::Removed { .. } => { + // nothing to do. + } + }; + }, + ); - // do a round trip to make sure we have all the globals - event_queue - .sync_roundtrip(&mut (), |_, _, _| unreachable!()) - .map_err(Error::fatal)?; + let env = display::new(dispatcher)?; + + display::print(&env.registry); - let xdg_base = globals - .instantiate_exact::(2) - .map_err(|e| Error::global("xdg_wm_base", 2, e))?; - let zxdg_decoration_manager_v1 = globals + let zxdg_decoration_manager_v1 = env + .registry .instantiate_exact::(1) .map_err(|e| Error::global("zxdg_decoration_manager_v1", 1, e))?; - let zwlr_layershell_v1 = globals + let zwlr_layershell_v1 = env + .registry .instantiate_exact::(1) .map_err(|e| Error::global("zwlr_layershell_v1", 1, e))?; - let wl_compositor = globals + let wl_compositor = env + .registry .instantiate_exact::(4) .map_err(|e| Error::global("wl_compositor", 4, e))?; - let wl_shm = globals + let wl_shm = env + .registry .instantiate_exact::(1) .map_err(|e| Error::global("wl_shm", 1, e))?; - // We do this to make sure wayland knows we're still responsive. - // - // NOTE: This means that clients mustn't hold up the event loop, or else wayland might kill - // your app's connection. Move *everything* to another thread, including e.g. file i/o, - // computation, network, ... This is good practice for all back-ends: it will improve - // responsiveness. - xdg_base.quick_assign(|xdg_base, event, d3| { - tracing::info!("xdg_base events {:?} {:?} {:?}", xdg_base, event, d3); - match event { - xdg_wm_base::Event::Ping { serial } => xdg_base.pong(serial), - _ => (), - } - }); - let timer_source = calloop::timer::Timer::new().unwrap(); let timer_handle = timer_source.handle(); @@ -306,8 +321,6 @@ impl Application { // We need to have keyboard events set up for our seats before the next roundtrip. let app_data = std::sync::Arc::new(ApplicationData { - event_queue: Rc::new(RefCell::new(event_queue)), - xdg_base, zxdg_decoration_manager_v1, zwlr_layershell_v1, wl_compositor, @@ -324,17 +337,16 @@ impl Application { display_flushed: RefCell::new(false), pointer, keyboard: keyboard::Manager::default(), - clipboard: clipboard::Manager::new(&wl_server, &globals)?, + clipboard: clipboard::Manager::new(&env.display, &env.registry)?, roundtrip_requested: RefCell::new(false), outputs_added: RefCell::new(Some(outputsaddedrx)), outputs_removed: RefCell::new(Some(outputsremovedrx)), - globals, - wl_server, + wayland: env, }); // Collect the supported image formats. wl_shm.quick_assign(with_cloned!(app_data; move |d1, event, d3| { - tracing::info!("shared memory events {:?} {:?} {:?}", d1, event, d3); + tracing::debug!("shared memory events {:?} {:?} {:?}", d1, event, d3); match event { wl_shm::Event::Format { format } => app_data.formats.borrow_mut().push(format), _ => (), // ignore other messages @@ -346,7 +358,7 @@ impl Application { let id = *id; // move into closure. let wl_seat = seat.borrow().wl_seat.clone(); wl_seat.quick_assign(with_cloned!(seat, app_data; move |d1, event, d3| { - tracing::info!("seat events {:?} {:?} {:?}", d1, event, d3); + tracing::debug!("seat events {:?} {:?} {:?}", d1, event, d3); let mut seat = seat.borrow_mut(); app_data.clipboard.attach(&mut seat); match event { @@ -393,7 +405,8 @@ impl Application { // source back. let timer_source = self.data.timer_source.borrow_mut().take().unwrap(); // flush pending events (otherwise anything we submitted since sync will never be sent) - self.data.wl_server.flush().unwrap(); + self.data.wayland.display.flush().unwrap(); + // Use calloop so we can epoll both wayland events and others (e.g. timers) let mut eventloop = calloop::EventLoop::try_new().unwrap(); let handle = eventloop.handle(); @@ -498,11 +511,11 @@ impl surfaces::Compositor for ApplicationData { } fn get_xdg_positioner(&self) -> wl::Main { - self.xdg_base.create_positioner() + self.wayland.xdg_base.create_positioner() } fn get_xdg_surface(&self, s: &wl::Main) -> wl::Main { - self.xdg_base.get_xdg_surface(s) + self.wayland.xdg_base.get_xdg_surface(s) } fn zxdg_decoration_manager_v1(&self) -> wl::Main { @@ -523,7 +536,8 @@ impl ApplicationData { /// /// Don't use this once the event loop has started. pub(crate) fn sync(&self) -> Result<(), Error> { - self.event_queue + self.wayland + .queue .borrow_mut() .sync_roundtrip(&mut (), |evt, _, _| { panic!("unexpected wayland event: {:?}", evt) @@ -611,7 +625,7 @@ impl ApplicationData { } // Now flush so the events actually get sent (we don't do this automatically because we // aren't in a wayland callback. - self.wl_server.flush().unwrap(); + self.wayland.display.flush().unwrap(); } /// Shallow clones surfaces so we can modify it during iteration. @@ -631,7 +645,7 @@ impl ApplicationData { // if we already flushed this cycle don't flush again. if *appdata.display_flushed.borrow() { tracing::trace!("idle repaint flushing display initiated"); - if let Err(cause) = appdata.event_queue.borrow().display().flush() { + if let Err(cause) = appdata.wayland.queue.borrow().display().flush() { tracing::warn!("unable to flush display: {:?}", cause); } } diff --git a/druid-shell/src/backend/wayland/dialog.rs b/druid-shell/src/backend/wayland/dialog.rs deleted file mode 100644 index 280ef21408..0000000000 --- a/druid-shell/src/backend/wayland/dialog.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2019 The Druid Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/* -use std::ffi::OsString; - -use crate::Error; -use crate::{ - dialog::{FileDialogOptions, FileDialogType, FileSpec}, - window::WindowHandle, -}; - -pub(crate) fn get_file_dialog_path( - window: &WindowHandle, - ty: FileDialogType, - options: FileDialogOptions, -) -> Result { - todo!() -} -*/ diff --git a/druid-shell/src/backend/wayland/display.rs b/druid-shell/src/backend/wayland/display.rs new file mode 100644 index 0000000000..37f50f04a3 --- /dev/null +++ b/druid-shell/src/backend/wayland/display.rs @@ -0,0 +1,222 @@ +use super::error; +use std::collections::BTreeMap; +use wayland_client as wlc; +use wayland_client::protocol::wl_registry; +use wayland_protocols::xdg_shell::client::xdg_wm_base; + +#[derive(Clone)] +pub struct GlobalEventSubscription { + id: u64, + sub: std::sync::Arc, +} + +impl GlobalEventSubscription { + fn with_id(mut self, id: u64) -> Self { + self.id = id; + self + } +} + +impl GlobalEventConsumer for GlobalEventSubscription { + fn consume<'a>( + &self, + event: &'a wlc::GlobalEvent, + registry: &'a wlc::Attached, + ctx: &'a wlc::DispatchData, + ) { + self.sub.consume(event, registry, ctx) + } +} + +impl From for GlobalEventSubscription +where + X: Fn(&wlc::GlobalEvent, &wlc::Attached, &wlc::DispatchData) + + GlobalEventConsumer + + 'static, +{ + fn from(closure: X) -> Self { + Self { + id: 0, + sub: std::sync::Arc::new(closure), + } + } +} + +impl GlobalEventConsumer for X +where + X: Fn(&wlc::GlobalEvent, &wlc::Attached, &wlc::DispatchData) + 'static, +{ + fn consume<'a>( + &self, + event: &'a wlc::GlobalEvent, + registry: &'a wlc::Attached, + ctx: &'a wlc::DispatchData, + ) { + self(event, registry, ctx) + } +} + +pub trait GlobalEventDispatch { + fn subscribe(&self, sub: impl Into) -> GlobalEventSubscription; + fn release(&self, s: &GlobalEventSubscription); +} + +pub trait GlobalEventConsumer { + fn consume( + &self, + event: &wlc::GlobalEvent, + registry: &wlc::Attached, + ctx: &wlc::DispatchData, + ); +} + +pub(super) struct Dispatcher { + incr: crate::Counter, + subscriptions: std::cell::RefCell>, +} + +impl Default for Dispatcher { + fn default() -> Self { + Self { + incr: crate::Counter::new(), + subscriptions: std::cell::RefCell::new(BTreeMap::new()), + } + } +} + +impl GlobalEventConsumer for Dispatcher { + fn consume<'a>( + &self, + event: &'a wlc::GlobalEvent, + registry: &'a wlc::Attached, + ctx: &'a wlc::DispatchData, + ) { + // tracing::info!("global event initiated {:?} {:?}", registry, event); + for (_, sub) in self.subscriptions.borrow().iter() { + sub.consume(event, registry, ctx); + } + // tracing::info!("global event completed {:?} {:?}", registry, event); + } +} + +impl GlobalEventDispatch for Dispatcher { + fn subscribe(&self, sub: impl Into) -> GlobalEventSubscription { + let sub = sub.into().with_id(self.incr.next()); + self.subscriptions.borrow_mut().insert(sub.id, sub.clone()); + sub + } + + fn release(&self, s: &GlobalEventSubscription) { + self.subscriptions.borrow_mut().remove(&s.id); + } +} + +pub(super) struct Environment { + pub(super) display: wlc::Display, + pub(super) registry: wlc::GlobalManager, + pub(super) xdg_base: wlc::Main, + pub(super) queue: std::rc::Rc>, + dispatcher: std::sync::Arc, +} + +// because we have the global environment we need to mark these as safe/send. +// strictly speaking we should probably guard the access to the various fields +// behind a mutex, but in practice we are not actually accessing across threads. +unsafe impl Sync for Environment {} +unsafe impl Send for Environment {} + +impl GlobalEventDispatch for std::sync::Arc { + fn subscribe(&self, sub: impl Into) -> GlobalEventSubscription { + self.dispatcher.subscribe(sub) + } + + fn release(&self, s: &GlobalEventSubscription) { + self.dispatcher.release(s) + } +} + +pub(super) fn new(dispatcher: Dispatcher) -> Result, error::Error> { + let dispatcher = std::sync::Arc::new(dispatcher); + let d = wlc::Display::connect_to_env()?; + + let mut queue = d.create_event_queue(); + let handle = d.clone().attach(queue.token()); + let registry = wlc::GlobalManager::new_with_cb(&handle, { + let dispatcher = dispatcher.clone(); + move |event, registry, ctx| { + dispatcher.consume(&event, ®istry, &ctx); + } + }); + + // do a round trip to make sure we have all the globals + queue + .sync_roundtrip(&mut (), |_, _, _| unreachable!()) + .map_err(error::Error::fatal)?; + + let xdg_base = registry + .instantiate_exact::(2) + .map_err(|e| error::Error::global("xdg_wm_base", 2, e))?; + + // We do this to make sure wayland knows we're still responsive. + // + // NOTE: This means that clients mustn't hold up the event loop, or else wayland might kill + // your app's connection. Move *everything* to another thread, including e.g. file i/o, + // computation, network, ... This is good practice for all back-ends: it will improve + // responsiveness. + xdg_base.quick_assign(|xdg_base, event, ctx| { + tracing::info!( + "global xdg_base events {:?} {:?} {:?}", + xdg_base, + event, + ctx + ); + match event { + xdg_wm_base::Event::Ping { serial } => xdg_base.pong(serial), + _ => (), + } + }); + + let env = std::sync::Arc::new(Environment { + queue: std::rc::Rc::new(std::cell::RefCell::new(queue)), + display: d, + registry, + xdg_base, + dispatcher, + }); + + return Ok(env); +} + +pub(super) fn global() -> Result, error::Error> { + use lazy_static::lazy_static; + lazy_static! { + static ref _GLOBAL: std::sync::Mutex>> = + std::sync::Mutex::new(None,); + } + + let mut guard = _GLOBAL.lock().unwrap(); + if let Some(d) = &*guard { + return Ok(d.clone()); + } + + let env = new(Dispatcher::default())?; + guard.replace(env.clone()); + + return Ok(env); +} + +#[allow(unused)] +pub(super) fn print<'a>(reg: &'a wlc::GlobalManager) { + let mut globals_list = reg.list(); + globals_list.sort_by(|(_, name1, version1), (_, name2, version2)| { + name1.cmp(name2).then(version1.cmp(version2)) + }); + + for (id, name, version) in globals_list.into_iter() { + tracing::debug!("{:?}@{:?} - {:?}", name, version, id); + } +} + +pub(super) fn count<'a>(reg: &'a wlc::GlobalManager, i: &str) -> usize { + reg.list().iter().filter(|(_, name, _)| name == i).count() +} diff --git a/druid-shell/src/backend/wayland/error.rs b/druid-shell/src/backend/wayland/error.rs index c2a0be9787..db694b22e1 100644 --- a/druid-shell/src/backend/wayland/error.rs +++ b/druid-shell/src/backend/wayland/error.rs @@ -32,9 +32,15 @@ pub enum Error { Fatal(Arc), String(ErrorString), InvalidParent(u32), + /// general error. + Err(Arc), } impl Error { + pub fn error(e: impl StdError + 'static) -> Self { + Self::Err(Arc::new(e)) + } + pub fn fatal(e: impl StdError + 'static) -> Self { Self::Fatal(Arc::new(e)) } @@ -62,6 +68,7 @@ impl fmt::Display for Error { name, version ), Self::Fatal(e) => write!(f, "an unhandled error occurred: {:?}", e), + Self::Err(e) => write!(f, "an unhandled error occurred: {:?}", e), Self::String(e) => e.fmt(f), Self::InvalidParent(id) => write!(f, "invalid parent window for popup: {:?}", id), } @@ -74,6 +81,7 @@ impl std::error::Error for Error { Self::Connect(e) => Some(&**e), Self::Global { inner, .. } => Some(&**inner), Self::Fatal(e) => Some(&**e), + Self::Err(e) => Some(&**e), Self::String(e) => Some(e), Self::InvalidParent(_) => None, } diff --git a/druid-shell/src/backend/wayland/events.rs b/druid-shell/src/backend/wayland/events.rs index 7445b3399f..71fa3f7a7d 100644 --- a/druid-shell/src/backend/wayland/events.rs +++ b/druid-shell/src/backend/wayland/events.rs @@ -24,7 +24,7 @@ pub(crate) struct WaylandSource { impl WaylandSource { /// Wrap an `EventQueue` as a `WaylandSource`. pub fn new(appdata: std::sync::Arc) -> WaylandSource { - let queue = appdata.event_queue.clone(); + let queue = appdata.wayland.queue.clone(); let fd = queue.borrow().display().get_connection_fd(); WaylandSource { appdata, diff --git a/druid-shell/src/backend/wayland/mod.rs b/druid-shell/src/backend/wayland/mod.rs index 58eaf7b947..736cc4b608 100644 --- a/druid-shell/src/backend/wayland/mod.rs +++ b/druid-shell/src/backend/wayland/mod.rs @@ -16,10 +16,12 @@ pub mod application; pub mod clipboard; +mod display; pub mod error; mod events; pub mod keyboard; pub mod menu; +mod outputs; pub mod pointers; pub mod screen; pub mod surfaces; diff --git a/druid-shell/src/backend/wayland/outputs/mod.rs b/druid-shell/src/backend/wayland/outputs/mod.rs new file mode 100644 index 0000000000..2249978bdc --- /dev/null +++ b/druid-shell/src/backend/wayland/outputs/mod.rs @@ -0,0 +1,204 @@ +use wayland_client as wlc; +use wayland_client::protocol::wl_output; + +use super::display; +use super::error; +mod wlr; +mod xdg; + +#[derive(Debug, Clone)] +#[allow(unused)] +pub enum Event { + Located(Meta), + Removed(Meta), +} + +pub fn auto<'a>( + registry: &'a wlc::GlobalManager, +) -> Result, error::Error> { + tracing::debug!("detecting wlr outputs"); + match wlr::detect(registry) { + Ok(rx) => return Ok(rx), + Err(cause) => tracing::info!("unable to detect wlr outputs {:?}", cause), + } + + tracing::debug!("detecting xdg outputs"); + match xdg::detect(registry) { + Ok(rx) => return Ok(rx), + Err(cause) => tracing::info!("unable to detect xdg outputs {:?}", cause), + } + + Err(error::Error::string("unable to detect display outputs")) +} + +pub(super) fn current<'a>(env: &'a display::Environment) -> Result, error::Error> { + let rx = auto(&env.registry)?; + let mut cache = std::collections::BTreeMap::new(); + let mut eventloop: calloop::EventLoop<( + calloop::LoopSignal, + &mut std::collections::BTreeMap, + )> = calloop::EventLoop::try_new().expect("failed to initialize the displays event loop!"); + let signal = eventloop.get_signal(); + let handle = eventloop.handle(); + handle + .insert_source(rx, { + move |event, _ignored, (signal, cache)| { + let event = match event { + calloop::channel::Event::Msg(event) => event, + calloop::channel::Event::Closed => return signal.stop(), + }; + + match event { + Event::Located(meta) => { + cache.insert(meta.name.clone(), meta.clone()); + } + Event::Removed(meta) => { + cache.remove(&meta.name); + } + } + } + }) + .map_err(error::Error::error)?; + + // do a round trip to flush commands. + let mut queue = env.queue.try_borrow_mut().map_err(error::Error::error)?; + queue + .sync_roundtrip(&mut (), |_, _, _| unreachable!()) + .map_err(error::Error::error)?; + + let expected = display::count(&env.registry, "wl_output"); + let result: std::sync::Arc>> = + std::sync::Arc::new(std::cell::RefCell::new(Vec::new())); + eventloop + .run( + std::time::Duration::from_secs(1), + &mut (signal, &mut cache), + { + let result = result.clone(); + move |(signal, cache)| { + if expected <= cache.len() { + result.replace(cache.values().cloned().collect()); + signal.stop(); + return; + } + } + }, + ) + .map_err(error::Error::error)?; + Ok(result.take()) +} + +pub trait Wayland { + fn consume<'a>( + &'a mut self, + obj: &'a wlc::Main, + event: &'a wl_output::Event, + ); +} + +#[derive(Clone, Debug)] +pub struct Dimensions { + pub width: i32, + pub height: i32, +} + +impl Default for Dimensions { + fn default() -> Self { + Self { + width: 0, + height: 0, + } + } +} + +impl From<(i32, i32)> for Dimensions { + fn from(v: (i32, i32)) -> Self { + Self { + width: v.0, + height: v.1, + } + } +} + +#[derive(Clone, Debug)] +pub struct Position { + pub x: i32, + pub y: i32, +} + +impl Default for Position { + fn default() -> Self { + Self { x: 0, y: 0 } + } +} + +impl From<(i32, i32)> for Position { + fn from(v: (i32, i32)) -> Self { + Self { x: v.0, y: v.1 } + } +} + +#[derive(Debug, Clone)] +pub struct Mode { + pub logical: Dimensions, + pub refresh: i32, + pub preferred: bool, +} + +impl Default for Mode { + fn default() -> Self { + Self { + logical: Default::default(), + refresh: 0, + preferred: false, + } + } +} + +#[derive(Clone, Debug)] +pub struct Meta { + pub name: String, + pub description: String, + pub logical: Dimensions, + pub refresh: i32, + pub physical: Dimensions, + pub subpixel: wl_output::Subpixel, + pub transform: wl_output::Transform, + pub make: String, + pub model: String, + pub scale: f64, + pub enabled: bool, + pub position: Position, +} + +impl Meta { + pub fn normalize(mut self) -> Self { + match self.transform { + wl_output::Transform::Flipped270 | wl_output::Transform::_270 => { + self.logical = Dimensions::from((self.logical.height, self.logical.width)); + self.physical = Dimensions::from((self.physical.height, self.physical.width)); + } + _ => {} + } + self + } +} + +impl Default for Meta { + fn default() -> Self { + Self { + name: Default::default(), + description: Default::default(), + logical: Default::default(), + refresh: Default::default(), + physical: Default::default(), + position: Default::default(), + subpixel: wl_output::Subpixel::Unknown, + transform: wl_output::Transform::Normal, + make: Default::default(), + model: Default::default(), + scale: Default::default(), + enabled: Default::default(), + } + } +} diff --git a/druid-shell/src/backend/wayland/outputs/wlr.rs b/druid-shell/src/backend/wayland/outputs/wlr.rs new file mode 100644 index 0000000000..d4af5159ec --- /dev/null +++ b/druid-shell/src/backend/wayland/outputs/wlr.rs @@ -0,0 +1,149 @@ +use wayland_client as wlc; +use wayland_protocols::wlr::unstable::output_management::v1::client::zwlr_output_head_v1; +use wayland_protocols::wlr::unstable::output_management::v1::client::zwlr_output_manager_v1; +use wayland_protocols::wlr::unstable::output_management::v1::client::zwlr_output_mode_v1; + +use super::super::error; +use super::super::outputs; + +pub trait Consumer { + fn consume<'a>( + &'a self, + obj: &'a wlc::Main, + event: &'a zwlr_output_head_v1::Event, + ); +} + +struct Meta { + meta: outputs::Meta, + modes: Vec>>, +} + +impl Default for Meta { + fn default() -> Self { + Self { + meta: Default::default(), + modes: Default::default(), + } + } +} + +pub fn detect<'a>( + registry: &'a wlc::GlobalManager, +) -> Result, error::Error> { + let (outputsaddedtx, outputsaddedrx) = calloop::channel::channel::(); + let zwlr_output_manager = registry + .instantiate_exact::(2) + .map_err(|e| error::Error::global("zxdg_output_manager_v1", 2, e))?; + + zwlr_output_manager.quick_assign({ + let mut outputs = Vec::>>::new(); + move |m, event, ctx| { + tracing::debug!("global zwlr output manager {:?} {:?} {:?}", m, ctx, event); + match event { + zwlr_output_manager_v1::Event::Head { head } => { + tracing::debug!("zwlr_output_manager head event {:?} {:?}", m, head); + let current = std::sync::Arc::new(std::cell::RefCell::new(Meta::default())); + outputs.push(current.clone()); + head.quick_assign(move |obj, event, _| { + Consumer::consume(¤t, &obj, &event) + }); + } + zwlr_output_manager_v1::Event::Done { .. } => { + for m in &outputs { + let m = m.borrow().meta.clone().normalize(); + if let Err(cause) = outputsaddedtx.send(outputs::Event::Located(m)) { + tracing::error!("unable to deliver output event: {:?}", cause); + } + } + } + event => { + tracing::warn!("unhandled zwlr_output_manager event {:?} {:?}", m, event); + } + }; + } + }); + + zwlr_output_manager.create_configuration(0); + + Ok(outputsaddedrx) +} + +impl Consumer for std::sync::Arc> { + fn consume( + &self, + obj: &wlc::Main, + event: &zwlr_output_head_v1::Event, + ) { + match event { + zwlr_output_head_v1::Event::Name { name } => { + self.borrow_mut().meta.name = name.to_string(); + } + zwlr_output_head_v1::Event::Description { description } => { + self.borrow_mut().meta.description = description.to_string(); + } + zwlr_output_head_v1::Event::PhysicalSize { width, height } => { + self.borrow_mut().meta.physical = + outputs::Dimensions::from((width.clone(), height.clone())); + } + zwlr_output_head_v1::Event::Make { make } => { + self.borrow_mut().meta.make = make.to_string(); + } + zwlr_output_head_v1::Event::Model { model } => { + self.borrow_mut().meta.model = model.to_string(); + } + zwlr_output_head_v1::Event::SerialNumber { .. } => {} // ignored + zwlr_output_head_v1::Event::Enabled { enabled } => { + self.borrow_mut().meta.enabled = *enabled > 0; + } + zwlr_output_head_v1::Event::Position { x, y } => { + self.borrow_mut().meta.position = outputs::Position::from((*x, *y)); + } + zwlr_output_head_v1::Event::Scale { scale } => { + self.borrow_mut().meta.scale = scale.clone(); + } + zwlr_output_head_v1::Event::Transform { transform } => { + self.borrow_mut().meta.transform = transform.clone(); + } + zwlr_output_head_v1::Event::Mode { mode } => { + let current = + std::sync::Arc::new(std::cell::RefCell::new(outputs::Mode::default())); + self.borrow_mut().modes.push(current.clone()); + mode.quick_assign({ + move |m, event, _ctx| match event { + zwlr_output_mode_v1::Event::Size { width, height } => { + current.borrow_mut().logical = + outputs::Dimensions::from((width, height)); + } + zwlr_output_mode_v1::Event::Refresh { refresh } => { + current.borrow_mut().refresh = refresh; + } + zwlr_output_mode_v1::Event::Preferred => { + current.borrow_mut().preferred = true; + } + _ => tracing::debug!("unhandled mode event {:?} {:?}", m, event), + } + }); + } + zwlr_output_head_v1::Event::CurrentMode { mode: _ } => { + // BUG: api here is pretty brutal. doesn't seem to be + // a way to get a main object from the provided mode. + // or to compare within the current set of modes for a match. + // as a result we *incorrectly* just assign the preferred mode + // as the current. + let b = self.borrow(); + let mut modes = b.modes.iter(); + let mode = match modes.find(|m| m.borrow().preferred) { + Some(m) => m.borrow().clone(), + None => return, + }; + drop(modes); + drop(b); + + self.borrow_mut().meta.logical = mode.logical.clone(); + self.borrow_mut().meta.refresh = mode.refresh.clone(); + } + _ => tracing::warn!("unhandled {:?} {:?}", obj, event), + }; + } +} diff --git a/druid-shell/src/backend/wayland/outputs/xdg.rs b/druid-shell/src/backend/wayland/outputs/xdg.rs new file mode 100644 index 0000000000..15a8c98728 --- /dev/null +++ b/druid-shell/src/backend/wayland/outputs/xdg.rs @@ -0,0 +1,21 @@ +use wayland_client as wlc; +use wayland_protocols::unstable::xdg_output::v1::client::zxdg_output_manager_v1; + +use super::super::error; +use super::super::outputs; + +pub fn detect<'a>( + registry: &'a wlc::GlobalManager, +) -> Result, error::Error> { + tracing::warn!("output detection not implemented using the xdg protocol"); + let (_outputsaddedtx, outputsaddedrx) = calloop::channel::channel::(); + let zxdg_output_manager = registry + .instantiate_exact::(3) + .map_err(|e| error::Error::global("zxdg_output_manager_v1", 3, e))?; + + zxdg_output_manager.quick_assign(|m, event, ctx| { + tracing::info!("global zxdg output manager {:?} {:?} {:?}", m, ctx, event); + }); + + Ok(outputsaddedrx) +} diff --git a/druid-shell/src/backend/wayland/screen.rs b/druid-shell/src/backend/wayland/screen.rs index 676a344587..45a9524da6 100644 --- a/druid-shell/src/backend/wayland/screen.rs +++ b/druid-shell/src/backend/wayland/screen.rs @@ -12,11 +12,38 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! GTK Monitors and Screen information. - +//! wayland Monitors and Screen information. use crate::screen::Monitor; -//use kurbo::{Point, Rect, Size}; + +use super::display; +use super::error; +use super::outputs; + +fn _get_monitors() -> Result, error::Error> { + let env = display::global()?; + let metas = outputs::current(&env)?; + let monitors: Vec = metas + .iter() + .map(|m| { + let rect = kurbo::Rect::from_origin_size( + (m.position.x as f64, m.position.y as f64), + (m.logical.width as f64, m.logical.height as f64), + ); + Monitor::new(false, rect, rect) + }) + .collect(); + Ok(monitors) +} pub(crate) fn get_monitors() -> Vec { - todo!() + match _get_monitors() { + Ok(m) => m, + Err(cause) => { + tracing::error!( + "unable to detect monitors, failed to connect to wayland server {:?}", + cause + ); + Vec::new() + } + } } diff --git a/druid-shell/src/backend/wayland/surfaces/surface.rs b/druid-shell/src/backend/wayland/surfaces/surface.rs index 3369d73634..39ee4ab92f 100644 --- a/druid-shell/src/backend/wayland/surfaces/surface.rs +++ b/druid-shell/src/backend/wayland/surfaces/surface.rs @@ -98,7 +98,7 @@ impl Surface { current.wl_surface.borrow().quick_assign({ let current = current.clone(); move |a, event, b| { - tracing::info!("wl_surface event {:?} {:?} {:?}", a, event, b); + tracing::debug!("wl_surface event {:?} {:?} {:?}", a, event, b); Surface::consume_surface_event(¤t, &a, &event, &b); } }); @@ -110,7 +110,7 @@ impl Surface { event: &wlc::protocol::wl_surface::Event, data: &wlc::DispatchData, ) { - tracing::info!("wl_surface event {:?} {:?} {:?}", surface, event, data); + tracing::debug!("wl_surface event {:?} {:?} {:?}", surface, event, data); match event { wl_surface::Event::Enter { output } => { let proxy = wlc::Proxy::from(output.clone()); @@ -320,7 +320,7 @@ impl Data { /// Recompute the scale to use (the maximum of all the scales for the different outputs this /// surface is drawn to). fn recompute_scale(&self) -> i32 { - tracing::info!("recompute initiated"); + tracing::debug!("recompute initiated"); self.compositor.recompute_scale(&self.outputs.borrow()) } @@ -328,7 +328,7 @@ impl Data { /// /// Up to the caller to make sure `physical_size`, `logical_size` and `scale` are consistent. fn set_scale(&self, new_scale: i32) -> Changed { - tracing::info!("set_scale initiated"); + tracing::debug!("set_scale initiated"); if self.scale.get() != new_scale { self.scale.set(new_scale); // (re-entrancy) Report change to client diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index 28fd95adfe..8e33b6948f 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -103,7 +103,7 @@ impl WindowHandle { } pub fn show(&self) { - tracing::info!("show initiated"); + tracing::debug!("show initiated"); } pub fn resizable(&self, _resizable: bool) { diff --git a/druid-shell/src/dialog.rs b/druid-shell/src/dialog.rs index e26eb9e9bc..ba8d0d0fa4 100644 --- a/druid-shell/src/dialog.rs +++ b/druid-shell/src/dialog.rs @@ -37,7 +37,10 @@ pub struct FileInfo { } /// Type of file dialog. -#[cfg(not(any(all(feature = "x11", any(target_os = "linux", target_os = "openbsd")), feature = "wayland")))] +#[cfg(not(any( + all(feature = "x11", any(target_os = "linux", target_os = "openbsd")), + feature = "wayland" +)))] #[derive(Clone, Copy, PartialEq)] pub enum FileDialogType { /// File open dialog. From e7d7d31da197e786c58def6e5290f53d6b807744 Mon Sep 17 00:00:00 2001 From: Maan2003 Date: Tue, 7 Dec 2021 17:23:37 +0530 Subject: [PATCH 23/31] wayland: CI --- .github/workflows/ci.yml | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84ae7891a3..065102d1a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,6 +108,49 @@ jobs: args: --manifest-path=docs/book_examples/Cargo.toml if: contains(matrix.os, 'mac') + # we test the wayland backend as a separate job + test-stable-wayland: + runs-on: ubuntu-latest + name: cargo clippy+test (wayland) + steps: + - uses: actions/checkout@v2 + + - name: install wayland + run: | + sudo apt update + sudo apt install libwayland-dev libpango1.0-dev libxkbcommon-dev + + - name: install stable toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy + profile: minimal + override: true + + - name: restore cache + uses: Swatinem/rust-cache@v1 + + - name: cargo clippy druid-shell + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --manifest-path=druid-shell/Cargo.toml --all-targets --features wayland --no-default-features -- -D warnings + + - name: cargo test druid-shell + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path=druid-shell/Cargo.toml --features wayland --no-default-features + + # We use --all-targets to skip doc tests; there are no wayland-specific + # doctests in the main druid crate anyway + - name: cargo test druid + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path=druid/Cargo.toml --all-targets --features=svg,image,im --features wayland --no-default-features + # we test the gtk backend as a separate job because gtk install takes # a long time. test-stable-gtk: From cff899a996916e5ac034a9d632b5c8885e1ab4d7 Mon Sep 17 00:00:00 2001 From: Maan2003 Date: Tue, 7 Dec 2021 17:37:51 +0530 Subject: [PATCH 24/31] xkb: only include xkbcommon-x11.h if x11 feature is enabled --- druid-shell/build.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/druid-shell/build.rs b/druid-shell/build.rs index 2bc8d4b5e2..8bac82b53f 100644 --- a/druid-shell/build.rs +++ b/druid-shell/build.rs @@ -18,17 +18,21 @@ fn main() { #[cfg(feature = "x11")] probe_library("xkbcommon-x11").unwrap(); + let mut header = "\ +#include +#include +#include " + .to_string(); + + if cfg!(feature = "x11") { + header += " +#include "; + } + let bindings = bindgen::Builder::default() // The input header we would like to generate // bindings for. - .header_contents( - "wrapper.h", - "\ -#include -#include -#include -#include ", - ) + .header_contents("wrapper.h", &header) // Tell cargo to invalidate the built crate whenever any of the // included header files changed. .parse_callbacks(Box::new(bindgen::CargoCallbacks)) From 79a0c045394f7d1c62fa124ec59bd39d75a98088 Mon Sep 17 00:00:00 2001 From: Maan2003 Date: Mon, 6 Dec 2021 19:53:14 +0530 Subject: [PATCH 25/31] No ApplicationExt on wayland --- druid-shell/src/platform/linux.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/druid-shell/src/platform/linux.rs b/druid-shell/src/platform/linux.rs index d1f57b591d..55e2dda348 100644 --- a/druid-shell/src/platform/linux.rs +++ b/druid-shell/src/platform/linux.rs @@ -32,5 +32,7 @@ mod test { use super::*; use static_assertions as sa; + // TODO: impl ApplicationExt for wayland + #[cfg(not(feature = "wayland"))] sa::assert_impl_all!(Application: ApplicationExt); } From 1b51ace4932923e13002e22fe08c90d8c2cbfc97 Mon Sep 17 00:00:00 2001 From: Maan2003 Date: Tue, 7 Dec 2021 17:17:10 +0530 Subject: [PATCH 26/31] Clippy --- .../src/backend/wayland/application.rs | 86 +++++++++---------- druid-shell/src/backend/wayland/clipboard.rs | 27 ++---- druid-shell/src/backend/wayland/display.rs | 35 ++++---- druid-shell/src/backend/wayland/error.rs | 1 + druid-shell/src/backend/wayland/keyboard.rs | 18 ++-- druid-shell/src/backend/wayland/menu.rs | 9 +- .../src/backend/wayland/outputs/mod.rs | 40 ++------- .../src/backend/wayland/outputs/wlr.rs | 23 ++--- .../src/backend/wayland/outputs/xdg.rs | 4 +- druid-shell/src/backend/wayland/pointers.rs | 12 +-- .../src/backend/wayland/surfaces/buffers.rs | 4 +- .../backend/wayland/surfaces/layershell.rs | 16 ++-- .../src/backend/wayland/surfaces/mod.rs | 13 ++- .../src/backend/wayland/surfaces/popup.rs | 22 ++--- .../src/backend/wayland/surfaces/surface.rs | 25 +++--- .../src/backend/wayland/surfaces/toplevel.rs | 8 +- druid-shell/src/backend/wayland/window.rs | 17 ++-- 17 files changed, 149 insertions(+), 211 deletions(-) diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index de4c721565..d7f23b3766 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -181,8 +181,8 @@ impl Application { interface, version, } => { - let id = id.clone(); - let version = version.clone(); + let id = *id; + let version = *version; if !(interface.as_str() == "wl_output" && version >= 3) { return; @@ -222,8 +222,8 @@ impl Application { }); } wl::GlobalEvent::Removed { id, interface } => { - let id = id.clone(); - if !(interface.as_str() == "wl_output") { + let id = *id; + if interface.as_str() != "wl_output" { return; } tracing::info!("output removed event {:?} {:?}", registry, interface); @@ -232,7 +232,7 @@ impl Application { let removed = outputs .iter() .find(|(_pid, o)| o.gid == id) - .map(|(pid, _)| pid.clone()) + .map(|(pid, _)| *pid) .and_then(|id| outputs.remove(&id)); let result = match removed { @@ -260,8 +260,8 @@ impl Application { interface, version, } => { - let id = id.clone(); - let version = version.clone(); + let id = *id; + let version = *version; if !(interface.as_str() == "wl_seat" && version >= 7) { return; @@ -399,7 +399,7 @@ impl Application { Ok(Application { data: app_data }) } - pub fn run(self, _handler: Option>) { + pub fn run(mut self, _handler: Option>) { tracing::info!("wayland event loop initiated"); // NOTE if we want to call this function more than once, we will need to put the timer // source back. @@ -419,7 +419,7 @@ impl Application { handle .insert_source(self.data.outputs_added.borrow_mut().take().unwrap(), { move |evt, _ignored, appdata| match evt { - calloop::channel::Event::Closed => return, + calloop::channel::Event::Closed => {} calloop::channel::Event::Msg(output) => { tracing::debug!("output added {:?} {:?}", output.gid, output.id()); for (_, win) in appdata.handles_iter() { @@ -434,7 +434,7 @@ impl Application { .insert_source( self.data.outputs_removed.borrow_mut().take().unwrap(), |evt, _ignored, appdata| match evt { - calloop::channel::Event::Closed => return, + calloop::channel::Event::Closed => {} calloop::channel::Event::Msg(output) => { tracing::trace!("output removed {:?} {:?}", output.gid, output.id()); for (_, win) in appdata.handles_iter() { @@ -455,25 +455,21 @@ impl Application { let signal = eventloop.get_signal(); let handle = handle.clone(); - let res = eventloop.run( - Duration::from_millis(20), - &mut self.data.clone(), - move |appdata| { - if appdata.shutdown.get() { - tracing::debug!("shutting down, requested"); - signal.stop(); - return; - } + let res = eventloop.run(Duration::from_millis(20), &mut self.data, move |appdata| { + if appdata.shutdown.get() { + tracing::debug!("shutting down, requested"); + signal.stop(); + return; + } - if appdata.handles.borrow().len() == 0 { - tracing::debug!("shutting down, no window remaining"); - signal.stop(); - return; - } + if appdata.handles.borrow().len() == 0 { + tracing::debug!("shutting down, no window remaining"); + signal.stop(); + return; + } - ApplicationData::idle_repaint(handle.clone()); - }, - ); + ApplicationData::idle_repaint(handle.clone()); + }); match res { Ok(_) => tracing::info!("wayland event loop completed"), @@ -495,11 +491,8 @@ impl Application { } impl surfaces::Compositor for ApplicationData { - fn output(&self, id: &u32) -> Option { - match self.outputs.borrow().get(id) { - None => None, - Some(o) => Some(o.clone()), - } + fn output(&self, id: u32) -> Option { + self.outputs.borrow().get(&id).cloned() } fn create_surface(&self) -> wl::Main { @@ -529,7 +522,7 @@ impl surfaces::Compositor for ApplicationData { impl ApplicationData { pub(crate) fn set_cursor(&self, cursor: &mouse::Cursor) { - self.pointer.replace(&cursor); + self.pointer.replace(cursor); } /// Send all pending messages and process all received messages. @@ -547,12 +540,8 @@ impl ApplicationData { } fn current_window_id(&self) -> u64 { - static DEFAULT: u64 = 0 as u64; - self.active_surface_id - .borrow() - .get(0) - .unwrap_or_else(|| &DEFAULT) - .clone() + static DEFAULT: u64 = 0_u64; + *self.active_surface_id.borrow().get(0).unwrap_or(&DEFAULT) } pub(super) fn initial_window_size(&self, defaults: kurbo::Size) -> kurbo::Size { @@ -580,10 +569,10 @@ impl ApplicationData { } pub(super) fn acquire_current_window(&self) -> Option { - match self.handles.borrow().get(&self.current_window_id()) { - None => None, - Some(w) => Some(w.clone()), - } + self.handles + .borrow() + .get(&self.current_window_id()) + .cloned() } fn handle_timer_event(&self, _token: TimerToken) { @@ -610,12 +599,15 @@ impl ApplicationData { } }; // re-entrancy - win.data() - .map(|data| data.handler.borrow_mut().timer(expired.token())); + if let Some(data) = win.data() { + data.handler.borrow_mut().timer(expired.token()) + } } for (_, win) in self.handles_iter() { - win.data().map(|data| data.run_deferred_tasks()); + if let Some(data) = win.data() { + data.run_deferred_tasks() + } } // Get the deadline soonest and queue it. @@ -633,7 +625,7 @@ impl ApplicationData { self.handles.borrow().clone().into_iter() } - fn idle_repaint<'a>(loophandle: calloop::LoopHandle<'a, std::sync::Arc>) { + fn idle_repaint(loophandle: calloop::LoopHandle<'_, std::sync::Arc>) { loophandle.insert_idle({ move |appdata| { match appdata.acquire_current_window() { diff --git a/druid-shell/src/backend/wayland/clipboard.rs b/druid-shell/src/backend/wayland/clipboard.rs index c3588fcfa7..a23e09d996 100644 --- a/druid-shell/src/backend/wayland/clipboard.rs +++ b/druid-shell/src/backend/wayland/clipboard.rs @@ -38,13 +38,14 @@ impl Offer { } } +#[derive(Default)] struct Data { pending: std::cell::RefCell>, current: std::cell::RefCell>, } impl Data { - fn receive<'a>(&'a self, mimetype: &'a String) -> Option { + fn receive(&self, mimetype: &str) -> Option { for offer in self.current.borrow().iter() { if !offer.mimetype.starts_with(mimetype) { // tracing::debug!("compared {:?} {:?}", offer.mimetype, mimetype); @@ -66,15 +67,6 @@ impl std::fmt::Debug for Data { } } -impl Default for Data { - fn default() -> Self { - Self { - pending: Default::default(), - current: Default::default(), - } - } -} - impl From> for Data { fn from(current: Vec) -> Self { Self { @@ -145,7 +137,7 @@ impl Manager { move |i, event, _ignored| match event { wl_data_offer::Event::Offer { mime_type } => { let data = m.devices.borrow_mut(); - let offer = Offer::new(i.clone(), mime_type); + let offer = Offer::new(i, mime_type); data.pending.borrow_mut().push(offer); } _ => tracing::warn!("clipboard unhandled {:?} event {:?}", i, event), @@ -153,7 +145,7 @@ impl Manager { }); } wl_data_device::Event::Selection { id } => { - if let Some(_) = id { + if id.is_some() { let data = m.devices.borrow(); tracing::debug!( "current data offers {:?} {:?}", @@ -223,7 +215,7 @@ impl Manager { return self.initiate(offer); } - return None; + None } } @@ -234,12 +226,11 @@ pub struct Clipboard { } impl From<&Manager> for Clipboard { - fn from<'a>(m: &'a Manager) -> Self { + fn from(m: &Manager) -> Self { Self { inner: m.clone() } } } -#[allow(unused)] impl Clipboard { const UTF8: &'static str = "text/plain;charset=utf-8"; const TEXT: &'static str = "text/plain"; @@ -247,12 +238,12 @@ impl Clipboard { /// Put a string onto the system clipboard. pub fn put_string(&mut self, s: impl AsRef) { - let s = s.as_ref().to_string(); + let _s = s.as_ref().to_string(); self.inner.inner.wdsobj.offer(Clipboard::UTF8.to_string()); } /// Put multi-format data on the system clipboard. - pub fn put_formats(&mut self, formats: &[ClipboardFormat]) { + pub fn put_formats(&mut self, _formats: &[ClipboardFormat]) { tracing::warn!("clipboard copy not implemented"); } @@ -273,7 +264,7 @@ impl Clipboard { /// Given a list of supported clipboard types, returns the supported type which has /// highest priority on the system clipboard, or `None` if no types are supported. - pub fn preferred_format(&self, formats: &[FormatId]) -> Option { + pub fn preferred_format(&self, _formats: &[FormatId]) -> Option { tracing::warn!("clipboard preferred_format not implemented"); None } diff --git a/druid-shell/src/backend/wayland/display.rs b/druid-shell/src/backend/wayland/display.rs index 37f50f04a3..f9bd751dfb 100644 --- a/druid-shell/src/backend/wayland/display.rs +++ b/druid-shell/src/backend/wayland/display.rs @@ -1,3 +1,4 @@ +#![allow(clippy::single_match)] use super::error; use std::collections::BTreeMap; use wayland_client as wlc; @@ -18,11 +19,11 @@ impl GlobalEventSubscription { } impl GlobalEventConsumer for GlobalEventSubscription { - fn consume<'a>( + fn consume( &self, - event: &'a wlc::GlobalEvent, - registry: &'a wlc::Attached, - ctx: &'a wlc::DispatchData, + event: &wlc::GlobalEvent, + registry: &wlc::Attached, + ctx: &wlc::DispatchData, ) { self.sub.consume(event, registry, ctx) } @@ -46,11 +47,11 @@ impl GlobalEventConsumer for X where X: Fn(&wlc::GlobalEvent, &wlc::Attached, &wlc::DispatchData) + 'static, { - fn consume<'a>( + fn consume( &self, - event: &'a wlc::GlobalEvent, - registry: &'a wlc::Attached, - ctx: &'a wlc::DispatchData, + event: &wlc::GlobalEvent, + registry: &wlc::Attached, + ctx: &wlc::DispatchData, ) { self(event, registry, ctx) } @@ -85,11 +86,11 @@ impl Default for Dispatcher { } impl GlobalEventConsumer for Dispatcher { - fn consume<'a>( + fn consume( &self, - event: &'a wlc::GlobalEvent, - registry: &'a wlc::Attached, - ctx: &'a wlc::DispatchData, + event: &wlc::GlobalEvent, + registry: &wlc::Attached, + ctx: &wlc::DispatchData, ) { // tracing::info!("global event initiated {:?} {:?}", registry, event); for (_, sub) in self.subscriptions.borrow().iter() { @@ -140,7 +141,7 @@ pub(super) fn new(dispatcher: Dispatcher) -> Result, let d = wlc::Display::connect_to_env()?; let mut queue = d.create_event_queue(); - let handle = d.clone().attach(queue.token()); + let handle = d.attach(queue.token()); let registry = wlc::GlobalManager::new_with_cb(&handle, { let dispatcher = dispatcher.clone(); move |event, registry, ctx| { @@ -184,7 +185,7 @@ pub(super) fn new(dispatcher: Dispatcher) -> Result, dispatcher, }); - return Ok(env); + Ok(env) } pub(super) fn global() -> Result, error::Error> { @@ -202,11 +203,11 @@ pub(super) fn global() -> Result, error::Error> { let env = new(Dispatcher::default())?; guard.replace(env.clone()); - return Ok(env); + Ok(env) } #[allow(unused)] -pub(super) fn print<'a>(reg: &'a wlc::GlobalManager) { +pub(super) fn print(reg: &wlc::GlobalManager) { let mut globals_list = reg.list(); globals_list.sort_by(|(_, name1, version1), (_, name2, version2)| { name1.cmp(name2).then(version1.cmp(version2)) @@ -217,6 +218,6 @@ pub(super) fn print<'a>(reg: &'a wlc::GlobalManager) { } } -pub(super) fn count<'a>(reg: &'a wlc::GlobalManager, i: &str) -> usize { +pub(super) fn count(reg: &wlc::GlobalManager, i: &str) -> usize { reg.list().iter().filter(|(_, name, _)| name == i).count() } diff --git a/druid-shell/src/backend/wayland/error.rs b/druid-shell/src/backend/wayland/error.rs index db694b22e1..f28147ad63 100644 --- a/druid-shell/src/backend/wayland/error.rs +++ b/druid-shell/src/backend/wayland/error.rs @@ -37,6 +37,7 @@ pub enum Error { } impl Error { + #[allow(clippy::self_named_constructors)] pub fn error(e: impl StdError + 'static) -> Self { Self::Err(Arc::new(e)) } diff --git a/druid-shell/src/backend/wayland/keyboard.rs b/druid-shell/src/backend/wayland/keyboard.rs index 02b9c928be..2e0e77e007 100644 --- a/druid-shell/src/backend/wayland/keyboard.rs +++ b/druid-shell/src/backend/wayland/keyboard.rs @@ -86,7 +86,7 @@ impl Keyboard { fn release_last_key_press(&self, current: &CachedKeyPress) -> Option { match &self.last_key_press { - None => return None, // nothing to do. + None => None, // nothing to do. Some(last) => { if last.serial >= current.serial { return Some(last.clone()); @@ -186,7 +186,7 @@ impl Keyboard { timestamp: time, key: key + 8, // TODO: understand the magic 8. state, - queue: keyqueue.clone(), + queue: keyqueue, }) } wl_keyboard::Event::Modifiers { .. } => { @@ -290,7 +290,7 @@ impl ModMap { return m; } - return m | self.1; + m | self.1 } } @@ -314,10 +314,10 @@ pub fn event_to_mods(event: wl_keyboard::Event) -> Modifiers { let mods = MOD_CTRL.merge(mods, mods_depressed, mods_locked); let mods = MOD_ALT.merge(mods, mods_depressed, mods_locked); let mods = MOD_NUM_LOCK.merge(mods, mods_depressed, mods_locked); - let mods = MOD_META.merge(mods, mods_depressed, mods_locked); - return mods; + + MOD_META.merge(mods, mods_depressed, mods_locked) } - _ => return Modifiers::empty(), + _ => Modifiers::empty(), } } @@ -371,10 +371,10 @@ impl Manager { }; if let Some(winhandle) = appdata.acquire_current_window() { - winhandle.data().map(|windata| { + if let Some(windata) = winhandle.data() { windata.with_handler({ let windata = windata.clone(); - let evt = evt.clone(); + let evt = evt; move |handler| match evt.state { KeyState::Up => { handler.key_up(evt.clone()); @@ -399,7 +399,7 @@ impl Manager { } } }); - }); + } } } }) diff --git a/druid-shell/src/backend/wayland/menu.rs b/druid-shell/src/backend/wayland/menu.rs index bf43f9c1b7..b0c6b0a783 100644 --- a/druid-shell/src/backend/wayland/menu.rs +++ b/druid-shell/src/backend/wayland/menu.rs @@ -33,9 +33,7 @@ impl Menu { Menu } - pub fn add_dropdown(&mut self, menu: Menu, text: &str, _enabled: bool) { - () - } + pub fn add_dropdown(&mut self, menu: Menu, text: &str, _enabled: bool) {} pub fn add_item( &mut self, @@ -45,10 +43,7 @@ impl Menu { enabled: bool, _selected: bool, ) { - () } - pub fn add_separator(&mut self) { - () - } + pub fn add_separator(&mut self) {} } diff --git a/druid-shell/src/backend/wayland/outputs/mod.rs b/druid-shell/src/backend/wayland/outputs/mod.rs index 2249978bdc..f420b59407 100644 --- a/druid-shell/src/backend/wayland/outputs/mod.rs +++ b/druid-shell/src/backend/wayland/outputs/mod.rs @@ -13,8 +13,8 @@ pub enum Event { Removed(Meta), } -pub fn auto<'a>( - registry: &'a wlc::GlobalManager, +pub fn auto( + registry: &wlc::GlobalManager, ) -> Result, error::Error> { tracing::debug!("detecting wlr outputs"); match wlr::detect(registry) { @@ -31,7 +31,7 @@ pub fn auto<'a>( Err(error::Error::string("unable to detect display outputs")) } -pub(super) fn current<'a>(env: &'a display::Environment) -> Result, error::Error> { +pub(super) fn current(env: &display::Environment) -> Result, error::Error> { let rx = auto(&env.registry)?; let mut cache = std::collections::BTreeMap::new(); let mut eventloop: calloop::EventLoop<( @@ -50,7 +50,7 @@ pub(super) fn current<'a>(env: &'a display::Environment) -> Result, er match event { Event::Located(meta) => { - cache.insert(meta.name.clone(), meta.clone()); + cache.insert(meta.name.clone(), meta); } Event::Removed(meta) => { cache.remove(&meta.name); @@ -79,7 +79,6 @@ pub(super) fn current<'a>(env: &'a display::Environment) -> Result, er if expected <= cache.len() { result.replace(cache.values().cloned().collect()); signal.stop(); - return; } } }, @@ -96,21 +95,12 @@ pub trait Wayland { ); } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Dimensions { pub width: i32, pub height: i32, } -impl Default for Dimensions { - fn default() -> Self { - Self { - width: 0, - height: 0, - } - } -} - impl From<(i32, i32)> for Dimensions { fn from(v: (i32, i32)) -> Self { Self { @@ -120,41 +110,25 @@ impl From<(i32, i32)> for Dimensions { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Position { pub x: i32, pub y: i32, } -impl Default for Position { - fn default() -> Self { - Self { x: 0, y: 0 } - } -} - impl From<(i32, i32)> for Position { fn from(v: (i32, i32)) -> Self { Self { x: v.0, y: v.1 } } } -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] pub struct Mode { pub logical: Dimensions, pub refresh: i32, pub preferred: bool, } -impl Default for Mode { - fn default() -> Self { - Self { - logical: Default::default(), - refresh: 0, - preferred: false, - } - } -} - #[derive(Clone, Debug)] pub struct Meta { pub name: String, diff --git a/druid-shell/src/backend/wayland/outputs/wlr.rs b/druid-shell/src/backend/wayland/outputs/wlr.rs index d4af5159ec..edeb167468 100644 --- a/druid-shell/src/backend/wayland/outputs/wlr.rs +++ b/druid-shell/src/backend/wayland/outputs/wlr.rs @@ -14,22 +14,14 @@ pub trait Consumer { ); } +#[derive(Default)] struct Meta { meta: outputs::Meta, modes: Vec>>, } -impl Default for Meta { - fn default() -> Self { - Self { - meta: Default::default(), - modes: Default::default(), - } - } -} - -pub fn detect<'a>( - registry: &'a wlc::GlobalManager, +pub fn detect( + registry: &wlc::GlobalManager, ) -> Result, error::Error> { let (outputsaddedtx, outputsaddedrx) = calloop::channel::channel::(); let zwlr_output_manager = registry @@ -83,8 +75,7 @@ impl Consumer for std::sync::Arc> { self.borrow_mut().meta.description = description.to_string(); } zwlr_output_head_v1::Event::PhysicalSize { width, height } => { - self.borrow_mut().meta.physical = - outputs::Dimensions::from((width.clone(), height.clone())); + self.borrow_mut().meta.physical = outputs::Dimensions::from((*width, *height)); } zwlr_output_head_v1::Event::Make { make } => { self.borrow_mut().meta.make = make.to_string(); @@ -100,10 +91,10 @@ impl Consumer for std::sync::Arc> { self.borrow_mut().meta.position = outputs::Position::from((*x, *y)); } zwlr_output_head_v1::Event::Scale { scale } => { - self.borrow_mut().meta.scale = scale.clone(); + self.borrow_mut().meta.scale = *scale; } zwlr_output_head_v1::Event::Transform { transform } => { - self.borrow_mut().meta.transform = transform.clone(); + self.borrow_mut().meta.transform = *transform; } zwlr_output_head_v1::Event::Mode { mode } => { let current = @@ -141,7 +132,7 @@ impl Consumer for std::sync::Arc> { drop(b); self.borrow_mut().meta.logical = mode.logical.clone(); - self.borrow_mut().meta.refresh = mode.refresh.clone(); + self.borrow_mut().meta.refresh = mode.refresh; } _ => tracing::warn!("unhandled {:?} {:?}", obj, event), }; diff --git a/druid-shell/src/backend/wayland/outputs/xdg.rs b/druid-shell/src/backend/wayland/outputs/xdg.rs index 15a8c98728..a4fb4dea1c 100644 --- a/druid-shell/src/backend/wayland/outputs/xdg.rs +++ b/druid-shell/src/backend/wayland/outputs/xdg.rs @@ -4,8 +4,8 @@ use wayland_protocols::unstable::xdg_output::v1::client::zxdg_output_manager_v1; use super::super::error; use super::super::outputs; -pub fn detect<'a>( - registry: &'a wlc::GlobalManager, +pub fn detect( + registry: &wlc::GlobalManager, ) -> Result, error::Error> { tracing::warn!("output detection not implemented using the xdg protocol"); let (_outputsaddedtx, outputsaddedrx) = calloop::channel::channel::(); diff --git a/druid-shell/src/backend/wayland/pointers.rs b/druid-shell/src/backend/wayland/pointers.rs index a8368d318f..b3e9d3ddde 100644 --- a/druid-shell/src/backend/wayland/pointers.rs +++ b/druid-shell/src/backend/wayland/pointers.rs @@ -207,10 +207,10 @@ impl Pointer { // Just use the first image, people using animated cursors have already made bad life // choices and shouldn't expect it to work. fn unpack_image_buffer(&self, name: &str) -> Option { - match self.theme.borrow_mut().get_cursor(name) { - None => None, - Some(c) => Some(c[c.frame_and_duration(0).frame_index].clone()), - } + self.theme + .borrow_mut() + .get_cursor(name) + .map(|c| c[c.frame_and_duration(0).frame_index].clone()) } pub(super) fn consume( @@ -227,7 +227,7 @@ impl Pointer { } => { appdata.pointer.push(PointerEvent::Motion { point: Point::new(surface_x, surface_y), - pointer: source.clone(), + pointer: source, }); } wl_pointer::Event::Leave { surface, .. } => { @@ -240,7 +240,7 @@ impl Pointer { } => { appdata.pointer.push(PointerEvent::Motion { point: Point::new(surface_x, surface_y), - pointer: source.clone(), + pointer: source, }); } wl_pointer::Event::Button { button, state, .. } => { diff --git a/druid-shell/src/backend/wayland/surfaces/buffers.rs b/druid-shell/src/backend/wayland/surfaces/buffers.rs index 585747c794..95855d0102 100644 --- a/druid-shell/src/backend/wayland/surfaces/buffers.rs +++ b/druid-shell/src/backend/wayland/surfaces/buffers.rs @@ -148,7 +148,7 @@ impl Buffers { return true; } b.destroy(); - return false; + false }); self.released.replace(pool); } @@ -211,7 +211,7 @@ impl Buffers { /// Get the raw buffer data of the next buffer to draw to. /// /// Will return `None` if buffer already borrowed. - fn pending_buffer_data<'a>(self: &'a Rc) -> Option + 'a> { + fn pending_buffer_data(self: &Rc) -> Option> { if self.pending_buffer_borrowed.get() { None } else { diff --git a/druid-shell/src/backend/wayland/surfaces/layershell.rs b/druid-shell/src/backend/wayland/surfaces/layershell.rs index 203b62b37b..146ba475b1 100644 --- a/druid-shell/src/backend/wayland/surfaces/layershell.rs +++ b/druid-shell/src/backend/wayland/surfaces/layershell.rs @@ -31,7 +31,7 @@ impl Inner { ) -> wlc::Main { let popup = surface.get_popup(None, pos); self.ls_surface.borrow().get_popup(&popup); - return popup; + popup } } @@ -42,7 +42,7 @@ impl Popup for Inner { pos: &'a wlc::Main, ) -> Result, error::Error> { - return Ok(self.popup(surface, pos)); + Ok(self.popup(surface, pos)) } } @@ -193,7 +193,7 @@ impl Surface { let handle = Self { inner: std::sync::Arc::new(Inner { - config: config.clone(), + config, wl_surface: std::cell::RefCell::new(wl_surface), ls_surface: std::cell::RefCell::new(ls_surface), requires_initialization: std::cell::RefCell::new(true), @@ -241,7 +241,7 @@ impl Surface { } }); - handle.inner.config.apply(&handle); + handle.inner.config.apply(handle); handle.inner.wl_surface.borrow().commit(); } @@ -257,7 +257,7 @@ impl Surface { width, height, } => { - let mut dim = handle.inner.config.initial_size.clone(); + let mut dim = handle.inner.config.initial_size; // compositor is deferring to the client for determining the size // when values are zero. if width != 0 && height != 0 { @@ -287,7 +287,7 @@ impl Outputs for Surface { } fn inserted(&self, o: &Output) { - if !self.inner.requires_initialization.borrow().clone() { + if !*self.inner.requires_initialization.borrow() { tracing::debug!( "skipping reinitialization output for layershell {:?} {:?}", o.gid, @@ -317,7 +317,7 @@ impl Outputs for Surface { self.inner.config.layer, self.inner.config.namespace.to_string(), )); - Surface::initialize(&self); + Surface::initialize(self); tracing::debug!("replaced surface {:p}", &replacedsurface); tracing::debug!("current surface {:p}", &self.inner.wl_surface.borrow()); @@ -333,7 +333,7 @@ impl Popup for Surface { pos: &'a wlc::Main, ) -> Result, error::Error> { - return Ok(self.inner.popup(popup, pos)); + Ok(self.inner.popup(popup, pos)) } } diff --git a/druid-shell/src/backend/wayland/surfaces/mod.rs b/druid-shell/src/backend/wayland/surfaces/mod.rs index 4972176fe3..d2d9e0a0ef 100644 --- a/druid-shell/src/backend/wayland/surfaces/mod.rs +++ b/druid-shell/src/backend/wayland/surfaces/mod.rs @@ -23,7 +23,7 @@ pub mod toplevel; pub static GLOBAL_ID: crate::Counter = crate::Counter::new(); pub trait Compositor { - fn output(&self, id: &u32) -> Option; + fn output(&self, id: u32) -> Option; fn create_surface(&self) -> wlc::Main; fn shared_mem(&self) -> wlc::Main; fn get_xdg_surface(&self, surface: &wlc::Main) @@ -87,14 +87,11 @@ impl CompositorHandle { } fn create_surface(&self) -> Option> { - match self.inner.upgrade() { - Some(c) => Some(c.create_surface()), - None => None, - } + self.inner.upgrade().map(|c| c.create_surface()) } /// Recompute the scale to use (the maximum of all the provided outputs). - fn recompute_scale<'a>(&self, outputs: &'a std::collections::HashSet) -> i32 { + fn recompute_scale(&self, outputs: &std::collections::HashSet) -> i32 { let compositor = match self.inner.upgrade() { Some(c) => c, None => panic!("should never recompute scale of window that has been dropped"), @@ -102,7 +99,7 @@ impl CompositorHandle { tracing::debug!("computing scale using {:?} outputs", outputs.len()); let scale = outputs.iter().fold(0, |scale, id| { tracing::debug!("recomputing scale using output {:?}", id); - match compositor.output(id) { + match compositor.output(*id) { None => { tracing::warn!( "we still have a reference to an output that's gone away. The output had id {}", @@ -125,7 +122,7 @@ impl CompositorHandle { } impl Compositor for CompositorHandle { - fn output(&self, id: &u32) -> Option { + fn output(&self, id: u32) -> Option { match self.inner.upgrade() { None => None, Some(c) => c.output(id), diff --git a/druid-shell/src/backend/wayland/surfaces/popup.rs b/druid-shell/src/backend/wayland/surfaces/popup.rs index c96e22e626..05090af61e 100644 --- a/druid-shell/src/backend/wayland/surfaces/popup.rs +++ b/druid-shell/src/backend/wayland/surfaces/popup.rs @@ -24,7 +24,7 @@ struct Inner { impl From for std::sync::Arc { fn from(s: Inner) -> std::sync::Arc { - std::sync::Arc::::from(s.wl_surface.clone()) + std::sync::Arc::::from(s.wl_surface) } } @@ -186,7 +186,7 @@ impl Surface { impl Handle for Surface { fn get_size(&self) -> kurbo::Size { - return self.inner.wl_surface.get_size(); + self.inner.wl_surface.get_size() } fn set_size(&self, dim: kurbo::Size) { @@ -198,27 +198,27 @@ impl Handle for Surface { } fn invalidate(&self) { - return self.inner.wl_surface.invalidate(); + self.inner.wl_surface.invalidate() } fn invalidate_rect(&self, rect: kurbo::Rect) { - return self.inner.wl_surface.invalidate_rect(rect); + self.inner.wl_surface.invalidate_rect(rect) } fn remove_text_field(&self, token: crate::TextFieldToken) { - return self.inner.wl_surface.remove_text_field(token); + self.inner.wl_surface.remove_text_field(token) } fn set_focused_text_field(&self, active_field: Option) { - return self.inner.wl_surface.set_focused_text_field(active_field); + self.inner.wl_surface.set_focused_text_field(active_field) } fn get_idle_handle(&self) -> super::idle::Handle { - return self.inner.wl_surface.get_idle_handle(); + self.inner.wl_surface.get_idle_handle() } fn get_scale(&self) -> crate::Scale { - return self.inner.wl_surface.get_scale(); + self.inner.wl_surface.get_scale() } fn run_idle(&self) { @@ -226,11 +226,11 @@ impl Handle for Surface { } fn release(&self) { - return self.inner.wl_surface.release(); + self.inner.wl_surface.release() } fn data(&self) -> Option> { - return self.inner.wl_surface.data(); + self.inner.wl_surface.data() } } @@ -259,7 +259,7 @@ impl From for Box { impl From for Box { fn from(s: Surface) -> Box { - Box::new(s.clone()) as Box + Box::new(s) as Box } } diff --git a/druid-shell/src/backend/wayland/surfaces/surface.rs b/druid-shell/src/backend/wayland/surfaces/surface.rs index 39ee4ab92f..e886840159 100644 --- a/druid-shell/src/backend/wayland/surfaces/surface.rs +++ b/druid-shell/src/backend/wayland/surfaces/surface.rs @@ -62,7 +62,7 @@ impl Surface { // register to receive wl_surface events. Surface::initsurface(¤t); - return Self { inner: current }; + Self { inner: current } } pub(super) fn request_paint(&self) { @@ -88,7 +88,7 @@ impl Surface { None => panic!("unable to create surface"), Some(v) => v, }); - Surface::initsurface(¤t); + Surface::initsurface(current); Self { inner: current.clone(), } @@ -200,13 +200,13 @@ impl Handle for Surface { impl From for std::sync::Arc { fn from(s: Surface) -> std::sync::Arc { - std::sync::Arc::::from(s.inner.clone()) + s.inner } } impl From<&Surface> for std::sync::Arc { fn from(s: &Surface) -> std::sync::Arc { - std::sync::Arc::::from(s.inner.clone()) + s.inner.clone() } } @@ -285,7 +285,7 @@ impl Data { }; } - return dim; + dim } // client initiated resizing. @@ -301,7 +301,7 @@ impl Data { self.buffers.set_size(raw_logical_size.scale(scale)); } - return dim; + dim } /// Assert that the physical size = logical size * scale @@ -476,7 +476,7 @@ impl Data { fn run_deferred_task(&self, task: DeferredTask) { match task { DeferredTask::Paint => { - self.buffers.request_paint(&self); + self.buffers.request_paint(self); } DeferredTask::AnimationClear => { self.anim_frame_requested.set(false); @@ -488,10 +488,10 @@ impl Data { // size in pixels, so we must apply scale. let logical_size = self.logical_size.get(); let scale = self.scale.get() as f64; - return kurbo::Size::new( + kurbo::Size::new( logical_size.width as f64 * scale, logical_size.height as f64 * scale, - ); + ) } pub(super) fn request_anim_frame(&self) { @@ -538,14 +538,9 @@ impl Data { } } +#[derive(Default)] pub struct Dead; -impl Default for Dead { - fn default() -> Self { - Self {} - } -} - impl From for Box { fn from(d: Dead) -> Box { Box::new(d) as Box diff --git a/druid-shell/src/backend/wayland/surfaces/toplevel.rs b/druid-shell/src/backend/wayland/surfaces/toplevel.rs index 799d2ee800..c1ccd77781 100644 --- a/druid-shell/src/backend/wayland/surfaces/toplevel.rs +++ b/druid-shell/src/backend/wayland/surfaces/toplevel.rs @@ -68,7 +68,7 @@ impl Surface { }); xdg_toplevel.quick_assign({ let wl_surface = wl_surface.clone(); - let mut dim = initial_size.clone(); + let mut dim = initial_size; move |_xdg_toplevel, event, a3| match event { xdg_toplevel::Event::Configure { width, @@ -98,9 +98,7 @@ impl Surface { }); zxdg_toplevel_decoration_v1.quick_assign(move |_zxdg_toplevel_decoration_v1, event, _| { - match event { - _ => tracing::info!("toplevel decoration unimplemented {:?}", event), - } + tracing::info!("toplevel decoration unimplemented {:?}", event); }); let inner = Inner { @@ -176,7 +174,7 @@ impl From for Box { impl From for Box { fn from(s: Surface) -> Box { - Box::new(s.clone()) as Box + Box::new(s) as Box } } diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index 8e33b6948f..6e589a62b8 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -132,7 +132,7 @@ impl WindowHandle { } pub fn get_size(&self) -> Size { - return self.inner.surface.get_size(); + self.inner.surface.get_size() } pub fn set_window_state(&mut self, _current_state: window::WindowState) { @@ -234,7 +234,7 @@ impl WindowHandle { appdata.timer_handle.add_timeout(timeout, timer.token()); } - return timer.token(); + timer.token() } pub fn set_cursor(&mut self, cursor: &Cursor) { @@ -398,7 +398,7 @@ impl WindowBuilder { } if let WindowLevel::DropDown(parent) = self.level { - let dim = self.min_size.unwrap_or_else(|| Size::ZERO); + let dim = self.min_size.unwrap_or(Size::ZERO); let dim = Size::new(dim.width.max(1.), dim.height.max(1.)); let dim = Size::new( self.size.width.max(dim.width), @@ -438,10 +438,11 @@ impl WindowBuilder { self.appdata.clone(), ); - if let Some(_) = appdata + if appdata .handles .borrow_mut() .insert(handle.id(), handle.clone()) + .is_some() { return Err(ShellError::Platform(Error::string( "wayland should use a unique id", @@ -521,10 +522,11 @@ pub mod layershell { self.appdata.clone(), ); - if let Some(_) = appdata + if appdata .handles .borrow_mut() .insert(handle.id(), handle.clone()) + .is_some() { panic!("wayland should use unique object IDs"); } @@ -587,13 +589,14 @@ pub mod popup { surfaces::surface::Dead::default(), surface.clone(), surface.clone(), - wappdata.clone(), + wappdata, ); - if let Some(_) = appdata + if appdata .handles .borrow_mut() .insert(handle.id(), handle.clone()) + .is_some() { panic!("wayland should use unique object IDs"); } From 632c8c5daa4a7c66af7ec76e7685479fbb8ba06c Mon Sep 17 00:00:00 2001 From: James Lawrence Date: Tue, 7 Dec 2021 15:41:46 -0500 Subject: [PATCH 27/31] additional clippy fixes. --- druid-shell/src/backend/wayland/keyboard.rs | 1 + druid-shell/src/clipboard.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/druid-shell/src/backend/wayland/keyboard.rs b/druid-shell/src/backend/wayland/keyboard.rs index 2e0e77e007..8951811a79 100644 --- a/druid-shell/src/backend/wayland/keyboard.rs +++ b/druid-shell/src/backend/wayland/keyboard.rs @@ -12,6 +12,7 @@ use super::application::ApplicationData; use super::surfaces::buffers; use crate::backend::shared::xkb; +#[allow(unused)] #[derive(Clone)] struct CachedKeyPress { seat: u32, diff --git a/druid-shell/src/clipboard.rs b/druid-shell/src/clipboard.rs index 2d18dd8088..4f25a74ebd 100644 --- a/druid-shell/src/clipboard.rs +++ b/druid-shell/src/clipboard.rs @@ -179,6 +179,7 @@ pub type FormatId = &'static str; /// Data coupled with a type identifier. #[derive(Debug, Clone)] +#[cfg_attr(feature = "wayland", allow(dead_code))] #[cfg_attr(target_arch = "wasm32", allow(dead_code))] pub struct ClipboardFormat { pub(crate) identifier: FormatId, From c803df84ab98a00d24e78d893c39d59ebe4f2033 Mon Sep 17 00:00:00 2001 From: James Lawrence Date: Tue, 7 Dec 2021 16:19:37 -0500 Subject: [PATCH 28/31] test fixes --- druid-shell/src/backend/wayland/window.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index 6e589a62b8..7d05952ce7 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -209,7 +209,10 @@ impl WindowHandle { pub fn request_timer(&self, deadline: std::time::Instant) -> TimerToken { let appdata = match self.inner.appdata.upgrade() { Some(d) => d, - None => panic!("requested timer on a window that was destroyed"), + None => { + tracing::warn!("requested timer on a window that was destroyed"); + return Timer::new(self.id(), deadline).token(); + } }; let now = instant::Instant::now(); From 6e62f5e567bda25d72c2372fe5b7c749ba96cf02 Mon Sep 17 00:00:00 2001 From: James Lawrence Date: Wed, 8 Dec 2021 17:44:17 -0500 Subject: [PATCH 29/31] cleanup the output detect code. - remove wlr management api. - allow the application logic to make use of the code. --- .../src/backend/wayland/application.rs | 288 ++---------------- druid-shell/src/backend/wayland/display.rs | 30 +- .../src/backend/wayland/outputs/mod.rs | 60 ++-- .../src/backend/wayland/outputs/output.rs | 207 +++++++++++++ .../src/backend/wayland/outputs/wlr.rs | 140 --------- .../src/backend/wayland/outputs/xdg.rs | 21 -- druid-shell/src/backend/wayland/screen.rs | 4 +- .../backend/wayland/surfaces/layershell.rs | 13 +- .../src/backend/wayland/surfaces/mod.rs | 12 +- .../src/backend/wayland/surfaces/surface.rs | 13 +- druid-shell/src/backend/wayland/window.rs | 7 +- 11 files changed, 305 insertions(+), 490 deletions(-) create mode 100644 druid-shell/src/backend/wayland/outputs/output.rs delete mode 100644 druid-shell/src/backend/wayland/outputs/wlr.rs delete mode 100644 druid-shell/src/backend/wayland/outputs/xdg.rs diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index d7f23b3766..59e096b1c4 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -15,7 +15,7 @@ #![allow(clippy::single_match)] use super::{ - clipboard, display, error::Error, events::WaylandSource, keyboard, pointers, surfaces, + clipboard, display, outputs, error::Error, events::WaylandSource, keyboard, pointers, surfaces, window::WindowHandle, }; @@ -37,7 +37,6 @@ use wayland_client::{ self as wl, protocol::{ wl_compositor::WlCompositor, - wl_output::{self, Subpixel, Transform, WlOutput}, wl_pointer::WlPointer, wl_seat::{self, WlSeat}, wl_shm::{self, WlShm}, @@ -105,7 +104,7 @@ pub(crate) struct ApplicationData { /// /// It's a BTreeMap so the ordering is consistent when enumerating outputs (not sure if this is /// necessary, but it negligable cost). - pub(super) outputs: Rc>>, + pub(super) outputs: Rc>>, pub(super) seats: Rc>>>>, /// Handles to any surfaces that have been created. /// @@ -142,8 +141,7 @@ pub(crate) struct ApplicationData { keyboard: keyboard::Manager, clipboard: clipboard::Manager, // wakeup events when outputs are added/removed. - outputs_removed: RefCell>>, - outputs_added: RefCell>>, + outputsqueue: RefCell>>, } impl Application { @@ -155,100 +153,16 @@ impl Application { // They have to be behind a shared pointer because wayland may need to add or remove them // for the life of the application. Use weak rcs inside the callbacks to avoid leaking // memory. - let outputs: Rc>> = Rc::new(RefCell::new(BTreeMap::new())); + let dispatcher = display::Dispatcher::default(); + let outputqueue = outputs::auto(&dispatcher)?; + let seats: Rc>>>> = Rc::new(RefCell::new(BTreeMap::new())); // This object will create a container for the global wayland objects, and request that // it is populated by the server. Doesn't take ownership of the registry, we are // responsible for keeping it alive. - let weak_outputs = Rc::downgrade(&outputs); let weak_seats = Rc::downgrade(&seats); - let (outputsremovedtx, outputsremovedrx) = calloop::channel::channel::(); - let (outputsaddedtx, outputsaddedrx) = calloop::channel::channel::(); - - let dispatcher = display::Dispatcher::default(); - - display::GlobalEventDispatch::subscribe( - &dispatcher, - move |event: &'_ wl::GlobalEvent, - registry: &'_ wl::Attached, - _ctx: &'_ wl::DispatchData| { - // tracing::debug!("output detection consuming event {:?} {:?}", registry, event); - match event { - wl::GlobalEvent::New { - id, - interface, - version, - } => { - let id = *id; - let version = *version; - - if !(interface.as_str() == "wl_output" && version >= 3) { - return; - } - tracing::info!("output added event {:?} {:?}", registry, interface); - let output = registry.bind::(3, id); - let output = Output::new(id, output); - let oid = output.id(); - let gid = output.gid; - let previous = weak_outputs - .upgrade() - .unwrap() - .borrow_mut() - .insert(oid, output.clone()); - assert!( - previous.is_none(), - "internal: wayland should always use new IDs" - ); - tracing::trace!("output added {:?} {:?}", gid, oid); - output.wl_output.quick_assign({ - let weak_outputs = weak_outputs.clone(); - let gid = gid; - let oid = oid; - let outputsaddedtx = outputsaddedtx.clone(); - move |a, event, b| { - tracing::trace!("output event {:?} {:?} {:?}", a, event, b); - match weak_outputs.upgrade().unwrap().borrow_mut().get_mut(&oid) { - Some(o) => o.process_event(event, &outputsaddedtx), - None => tracing::warn!( - "wayland sent an event for an output that doesn't exist global({:?}) proxy({:?}) {:?}", - &gid, - &oid, - &event, - ), - } - } - }); - } - wl::GlobalEvent::Removed { id, interface } => { - let id = *id; - if interface.as_str() != "wl_output" { - return; - } - tracing::info!("output removed event {:?} {:?}", registry, interface); - let boutputs = weak_outputs.upgrade().unwrap(); - let mut outputs = boutputs.borrow_mut(); - let removed = outputs - .iter() - .find(|(_pid, o)| o.gid == id) - .map(|(pid, _)| *pid) - .and_then(|id| outputs.remove(&id)); - - let result = match removed { - None => return, - Some(removed) => outputsremovedtx.send(removed), - }; - - match result { - Ok(_) => tracing::debug!("outputs remaining {:?}...", outputs.len()), - Err(cause) => tracing::error!("failed to remove output {:?}", cause), - } - } - }; - }, - ); - display::GlobalEventDispatch::subscribe( &dispatcher, move |event: &'_ wl::GlobalEvent, @@ -325,7 +239,7 @@ impl Application { zwlr_layershell_v1, wl_compositor, wl_shm: wl_shm.clone(), - outputs, + outputs: Rc::new(RefCell::new(BTreeMap::new())), seats, handles: RefCell::new(im::OrdMap::new()), formats: RefCell::new(vec![]), @@ -339,8 +253,7 @@ impl Application { keyboard: keyboard::Manager::default(), clipboard: clipboard::Manager::new(&env.display, &env.registry)?, roundtrip_requested: RefCell::new(false), - outputs_added: RefCell::new(Some(outputsaddedrx)), - outputs_removed: RefCell::new(Some(outputsremovedrx)), + outputsqueue: RefCell::new(Some(outputqueue)), wayland: env, }); @@ -417,34 +330,29 @@ impl Application { handle.register_dispatcher(wayland_dispatcher).unwrap(); handle - .insert_source(self.data.outputs_added.borrow_mut().take().unwrap(), { + .insert_source(self.data.outputsqueue.take().unwrap(), { move |evt, _ignored, appdata| match evt { calloop::channel::Event::Closed => {} calloop::channel::Event::Msg(output) => { - tracing::debug!("output added {:?} {:?}", output.gid, output.id()); - for (_, win) in appdata.handles_iter() { - surfaces::Outputs::inserted(&win, &output); + match output { + outputs::Event::Located(output) => { + appdata.outputs.borrow_mut().insert(output.id(), output.clone()); + for (_, win) in appdata.handles_iter() { + surfaces::Outputs::inserted(&win, &output); + } + }, + outputs::Event::Removed(output) => { + appdata.outputs.borrow_mut().remove(&output.id()); + for (_, win) in appdata.handles_iter() { + surfaces::Outputs::removed(&win, &output); + } + }, } } } }) .unwrap(); - handle - .insert_source( - self.data.outputs_removed.borrow_mut().take().unwrap(), - |evt, _ignored, appdata| match evt { - calloop::channel::Event::Closed => {} - calloop::channel::Event::Msg(output) => { - tracing::trace!("output removed {:?} {:?}", output.gid, output.id()); - for (_, win) in appdata.handles_iter() { - surfaces::Outputs::removed(&win, &output); - } - } - }, - ) - .unwrap(); - handle .insert_source(timer_source, move |token, _metadata, appdata| { tracing::trace!("timer source {:?}", token); @@ -491,7 +399,7 @@ impl Application { } impl surfaces::Compositor for ApplicationData { - fn output(&self, id: u32) -> Option { + fn output(&self, id: u32) -> Option { self.outputs.borrow().get(&id).cloned() } @@ -558,12 +466,11 @@ impl ApplicationData { }; return self.outputs.borrow().iter().fold( kurbo::Size::from((initialwidth, initialheight)), - |computed, entry| match &entry.1.current_mode { - None => computed, - Some(mode) => kurbo::Size::new( - computed.width.min(mode.width.into()), - computed.height.min(mode.height.into()), - ), + |computed, entry| { + kurbo::Size::new( + computed.width.min(entry.1.logical.width.into()), + computed.height.min(entry.1.logical.height.into()), + ) }, ); } @@ -666,145 +573,6 @@ impl From> for surfaces::CompositorHandle { } } -#[derive(Debug, Clone)] -pub struct Output { - wl_output: wl::Main, - wl_proxy: wl::Proxy, - /// global id of surface. - pub gid: u32, - pub x: i32, - pub y: i32, - pub physical_width: i32, - pub physical_height: i32, - pub subpixel: Subpixel, - pub make: String, - pub model: String, - pub transform: Transform, - pub scale: i32, - pub current_mode: Option, - pub preferred_mode: Option, - /// Whether we have received some update events but not the `done` event. - update_in_progress: bool, - /// Lets us work out if things have changed since we last observed the output. - last_update: Instant, -} - -#[allow(unused)] -impl Output { - // All the stuff before `current_mode` will be filled out immediately after creation, so these - // dummy values will never be observed. - fn new(id: u32, wl_output: wl::Main) -> Self { - Output { - wl_output: wl_output.clone(), - wl_proxy: wl::Proxy::from(wl_output.detach()), - gid: id, - x: 0, - y: 0, - physical_width: 0, - physical_height: 0, - subpixel: Subpixel::Unknown, - make: "".into(), - model: "".into(), - transform: Transform::Normal, - current_mode: None, - preferred_mode: None, - scale: 1, // the spec says if there is no scale event, assume 1. - update_in_progress: true, - last_update: Instant::now(), - } - } - - /// Get the wayland object ID for this output. This is how we key outputs in our global - /// registry. - pub fn id(&self) -> u32 { - self.wl_proxy.id() - } - - /// Incorporate update data from the server for this output. - fn process_event(&mut self, evt: wl_output::Event, tx: &calloop::channel::Sender) { - tracing::trace!("processing wayland output event {:?}", evt); - match evt { - wl_output::Event::Geometry { - x, - y, - physical_width, - physical_height, - subpixel, - make, - model, - transform, - } => { - self.x = x; - self.y = y; - self.subpixel = subpixel; - self.make = make; - self.model = model; - self.transform = transform; - self.update_in_progress = true; - - match transform { - wl_output::Transform::Flipped270 | wl_output::Transform::_270 => { - self.physical_width = physical_height; - self.physical_height = physical_width; - } - _ => { - self.physical_width = physical_width; - self.physical_height = physical_height; - } - } - } - wl_output::Event::Mode { - flags, - width, - height, - refresh, - } => { - if flags.contains(wl_output::Mode::Current) { - self.current_mode = Some(Mode { - width, - height, - refresh, - }); - } - if flags.contains(wl_output::Mode::Preferred) { - self.preferred_mode = Some(Mode { - width, - height, - refresh, - }); - } - self.update_in_progress = true; - } - wl_output::Event::Done => { - self.update_in_progress = false; - self.last_update = Instant::now(); - if let Err(cause) = tx.send(self.clone()) { - tracing::error!("unable to add output {:?} {:?}", self.gid, self.id()); - } - } - wl_output::Event::Scale { factor } => { - self.scale = factor; - self.update_in_progress = true; - } - _ => tracing::warn!("unknown output event {:?}", evt), // ignore possible future events - } - } - - /// Whether the output has changed since `since`. - /// - /// Will return `false` if an update is in progress, as updates should be handled atomically. - fn changed(&self, since: Instant) -> bool { - !self.update_in_progress && since < self.last_update - } -} - -#[derive(Debug, Clone)] -pub struct Mode { - pub width: i32, - pub height: i32, - pub refresh: i32, -} - #[derive(Debug, Clone)] pub struct Seat { pub(super) wl_seat: wl::Main, diff --git a/druid-shell/src/backend/wayland/display.rs b/druid-shell/src/backend/wayland/display.rs index f9bd751dfb..e686830b44 100644 --- a/druid-shell/src/backend/wayland/display.rs +++ b/druid-shell/src/backend/wayland/display.rs @@ -126,6 +126,16 @@ pub(super) struct Environment { unsafe impl Sync for Environment {} unsafe impl Send for Environment {} +impl GlobalEventDispatch for Environment { + fn subscribe(&self, sub: impl Into) -> GlobalEventSubscription { + self.dispatcher.subscribe(sub) + } + + fn release(&self, s: &GlobalEventSubscription) { + self.dispatcher.release(s) + } +} + impl GlobalEventDispatch for std::sync::Arc { fn subscribe(&self, sub: impl Into) -> GlobalEventSubscription { self.dispatcher.subscribe(sub) @@ -188,24 +198,6 @@ pub(super) fn new(dispatcher: Dispatcher) -> Result, Ok(env) } -pub(super) fn global() -> Result, error::Error> { - use lazy_static::lazy_static; - lazy_static! { - static ref _GLOBAL: std::sync::Mutex>> = - std::sync::Mutex::new(None,); - } - - let mut guard = _GLOBAL.lock().unwrap(); - if let Some(d) = &*guard { - return Ok(d.clone()); - } - - let env = new(Dispatcher::default())?; - guard.replace(env.clone()); - - Ok(env) -} - #[allow(unused)] pub(super) fn print(reg: &wlc::GlobalManager) { let mut globals_list = reg.list(); @@ -220,4 +212,4 @@ pub(super) fn print(reg: &wlc::GlobalManager) { pub(super) fn count(reg: &wlc::GlobalManager, i: &str) -> usize { reg.list().iter().filter(|(_, name, _)| name == i).count() -} +} \ No newline at end of file diff --git a/druid-shell/src/backend/wayland/outputs/mod.rs b/druid-shell/src/backend/wayland/outputs/mod.rs index f420b59407..4a91364879 100644 --- a/druid-shell/src/backend/wayland/outputs/mod.rs +++ b/druid-shell/src/backend/wayland/outputs/mod.rs @@ -3,8 +3,7 @@ use wayland_client::protocol::wl_output; use super::display; use super::error; -mod wlr; -mod xdg; +pub mod output; #[derive(Debug, Clone)] #[allow(unused)] @@ -14,16 +13,10 @@ pub enum Event { } pub fn auto( - registry: &wlc::GlobalManager, + env: & impl display::GlobalEventDispatch, ) -> Result, error::Error> { - tracing::debug!("detecting wlr outputs"); - match wlr::detect(registry) { - Ok(rx) => return Ok(rx), - Err(cause) => tracing::info!("unable to detect wlr outputs {:?}", cause), - } - tracing::debug!("detecting xdg outputs"); - match xdg::detect(registry) { + match output::detect(env) { Ok(rx) => return Ok(rx), Err(cause) => tracing::info!("unable to detect xdg outputs {:?}", cause), } @@ -31,8 +24,10 @@ pub fn auto( Err(error::Error::string("unable to detect display outputs")) } -pub(super) fn current(env: &display::Environment) -> Result, error::Error> { - let rx = auto(&env.registry)?; +pub(super) fn current() -> Result, error::Error> { + let dispatcher = display::Dispatcher::default(); + let rx = auto(&dispatcher)?; + let env = display::new(dispatcher)?; let mut cache = std::collections::BTreeMap::new(); let mut eventloop: calloop::EventLoop<( calloop::LoopSignal, @@ -80,6 +75,15 @@ pub(super) fn current(env: &display::Environment) -> Result, error::Er result.replace(cache.values().cloned().collect()); signal.stop(); } + + let res = queue + .sync_roundtrip(&mut (), |_, _, _| unreachable!()) + .map_err(error::Error::error); + + if let Err(cause) = res { + tracing::error!("wayland sync failed {:?}", cause); + signal.stop(); + } } }, ) @@ -131,6 +135,7 @@ pub struct Mode { #[derive(Clone, Debug)] pub struct Meta { + pub gid: u32, pub name: String, pub description: String, pub logical: Dimensions, @@ -145,22 +150,10 @@ pub struct Meta { pub position: Position, } -impl Meta { - pub fn normalize(mut self) -> Self { - match self.transform { - wl_output::Transform::Flipped270 | wl_output::Transform::_270 => { - self.logical = Dimensions::from((self.logical.height, self.logical.width)); - self.physical = Dimensions::from((self.physical.height, self.physical.width)); - } - _ => {} - } - self - } -} - impl Default for Meta { fn default() -> Self { Self { + gid: Default::default(), name: Default::default(), description: Default::default(), logical: Default::default(), @@ -176,3 +169,20 @@ impl Default for Meta { } } } + +impl Meta { + pub fn normalize(mut self) -> Self { + match self.transform { + wl_output::Transform::Flipped270 | wl_output::Transform::_270 => { + self.logical = Dimensions::from((self.logical.height, self.logical.width)); + self.physical = Dimensions::from((self.physical.height, self.physical.width)); + } + _ => {} + } + self + } + + pub fn id(&self) -> u32 { + self.gid + } +} diff --git a/druid-shell/src/backend/wayland/outputs/output.rs b/druid-shell/src/backend/wayland/outputs/output.rs new file mode 100644 index 0000000000..d69589a4a4 --- /dev/null +++ b/druid-shell/src/backend/wayland/outputs/output.rs @@ -0,0 +1,207 @@ +use wayland_client as wlc; +use wayland_client::protocol::wl_registry; +use wayland_client::protocol::wl_output; +use wayland_protocols::unstable::xdg_output::v1::client::zxdg_output_manager_v1; +use wayland_protocols::unstable::xdg_output::v1::client::zxdg_output_v1; +use super::super::display; +use super::super::error; +use super::super::outputs; + + +pub trait Consumer { + fn consume( + &mut self, + obj: &wlc::Main, + event: &wl_output::Event, + ) -> Option; +} + +pub fn detect( + env: & impl display::GlobalEventDispatch, +) -> Result, error::Error> { + let (outputstx, outputsrx) = calloop::channel::channel::(); + let xdg_output_manager_id: std::cell::RefCell> = std::cell::RefCell::new(None); + display::GlobalEventDispatch::subscribe( + env, { + let outputstx = outputstx.clone(); + move |event: &'_ wlc::GlobalEvent, registry: &'_ wlc::Attached, _ctx: &'_ wlc::DispatchData| { + match event { + wlc::GlobalEvent::New { + id, + interface, + version, + } => { + let id = *id; + let version = *version; + + if interface.as_str() == "zxdg_output_manager_v1" && version == 3 { + xdg_output_manager_id.replace(Some(id)); + return; + } + + if !(interface.as_str() == "wl_output" && version >= 3) { + return; + } + + let output = registry.bind::(3, id); + let xdgm = match *xdg_output_manager_id.borrow() { + Some(xdgm_id) => Some(registry.bind::(3, xdgm_id)), + None => None, + }; + + let mut meta = Meta::default(); + let mut xdgmeta = XdgMeta::new(); + output.quick_assign({ + let outputstx = outputstx.clone(); + let xdgm = xdgm.clone(); + move |output, event, _ctx| { + let mut m = match meta.consume(&output, &event) { + Some(m) => m, + None => return, + }; + + if !xdgmeta.set_xdg_handled() { + if let Some(xdgm) = &xdgm { + let xdg_output = xdgm.get_xdg_output(&output); + xdg_output.quick_assign({ + let mut xdgmeta = xdgmeta.clone(); + move |xdg_output, event, _ctx| { + xdgmeta.consume(&xdg_output, &event); + } + }); + return + } + } + + xdgmeta.modify(&mut m); + + if let Err(cause) = outputstx.send(outputs::Event::Located(m)) { + tracing::warn!("unable to transmit output {:?}", cause); + } + } + }); + } + wlc::GlobalEvent::Removed { interface, .. } => { + if interface.as_str() != "wl_output" { + return; + } + tracing::info!("output removed event {:?} {:?}", registry, interface); + } + }; + } + }); + + Ok(outputsrx) +} + +#[derive(Debug, Default)] +struct XdgState { + name: String, + description: String, + position: outputs::Position, + logical: outputs::Dimensions, +} + +#[derive(Clone, Debug)] +struct XdgMeta { + handled: bool, + state: std::sync::Arc>, +} + +impl XdgMeta { + fn new() -> Self { + Self { + handled: false, + state: std::sync::Arc::new(std::cell::RefCell::new(XdgState::default())), + } + } + + fn set_xdg_handled(&mut self) -> bool { + let tmp = self.handled; + self.handled = true; + tmp + } + + fn consume(&mut self, output: &wlc::Main, evt: &zxdg_output_v1::Event) { + match evt { + zxdg_output_v1::Event::Name { name } => { + self.state.borrow_mut().name = name.clone(); + }, + zxdg_output_v1::Event::Description { description } => { + self.state.borrow_mut().description = description.clone(); + }, + zxdg_output_v1::Event::LogicalPosition { x, y } => { + self.state.borrow_mut().position = outputs::Position::from((*x, *y)); + }, + zxdg_output_v1::Event::LogicalSize { width, height } => { + self.state.borrow_mut().logical = outputs::Dimensions::from((*width, *height)); + }, + _ => tracing::warn!("unused xdg_output_v1 event {:?} {:?}", output, evt), + }; + } + + fn modify(&self, meta: &mut outputs::Meta) { + let state = self.state.borrow(); + meta.name = state.name.clone(); + meta.description = state.description.clone(); + meta.position = state.position.clone(); + meta.logical = state.logical.clone(); + } +} + +#[derive(Default)] +struct Meta { + meta: outputs::Meta, +} + +impl Consumer for Meta { + /// Incorporate update data from the server for this output. + fn consume(&mut self, output: &wlc::Main, evt: &wl_output::Event,) -> Option { + match evt { + wl_output::Event::Geometry { + x, + y, + physical_width, + physical_height, + subpixel, + make, + model, + transform, + } => { + self.meta.position = outputs::Position::from((*x, *y)); + self.meta.physical = outputs::Dimensions::from((*physical_width, *physical_height)); + self.meta.subpixel = *subpixel; + self.meta.make = make.clone(); + self.meta.model = model.clone(); + self.meta.transform = *transform; + None + } + wl_output::Event::Mode { + flags, + width, + height, + refresh, + } => { + if flags.contains(wl_output::Mode::Current) { + self.meta.logical = outputs::Dimensions::from((*width, *height)); + self.meta.refresh = *refresh; + } + + None + } + wl_output::Event::Done => { + self.meta.gid = wlc::Proxy::from(output.detach()).id(); + self.meta.enabled = true; + Some(self.meta.clone()) + } + wl_output::Event::Scale { factor } => { + self.meta.scale = (*factor).into(); + None + } + _ => { + tracing::warn!("unknown output event {:?}", evt); // ignore possible future events + None + }, + } + } +} \ No newline at end of file diff --git a/druid-shell/src/backend/wayland/outputs/wlr.rs b/druid-shell/src/backend/wayland/outputs/wlr.rs deleted file mode 100644 index edeb167468..0000000000 --- a/druid-shell/src/backend/wayland/outputs/wlr.rs +++ /dev/null @@ -1,140 +0,0 @@ -use wayland_client as wlc; -use wayland_protocols::wlr::unstable::output_management::v1::client::zwlr_output_head_v1; -use wayland_protocols::wlr::unstable::output_management::v1::client::zwlr_output_manager_v1; -use wayland_protocols::wlr::unstable::output_management::v1::client::zwlr_output_mode_v1; - -use super::super::error; -use super::super::outputs; - -pub trait Consumer { - fn consume<'a>( - &'a self, - obj: &'a wlc::Main, - event: &'a zwlr_output_head_v1::Event, - ); -} - -#[derive(Default)] -struct Meta { - meta: outputs::Meta, - modes: Vec>>, -} - -pub fn detect( - registry: &wlc::GlobalManager, -) -> Result, error::Error> { - let (outputsaddedtx, outputsaddedrx) = calloop::channel::channel::(); - let zwlr_output_manager = registry - .instantiate_exact::(2) - .map_err(|e| error::Error::global("zxdg_output_manager_v1", 2, e))?; - - zwlr_output_manager.quick_assign({ - let mut outputs = Vec::>>::new(); - move |m, event, ctx| { - tracing::debug!("global zwlr output manager {:?} {:?} {:?}", m, ctx, event); - match event { - zwlr_output_manager_v1::Event::Head { head } => { - tracing::debug!("zwlr_output_manager head event {:?} {:?}", m, head); - let current = std::sync::Arc::new(std::cell::RefCell::new(Meta::default())); - outputs.push(current.clone()); - head.quick_assign(move |obj, event, _| { - Consumer::consume(¤t, &obj, &event) - }); - } - zwlr_output_manager_v1::Event::Done { .. } => { - for m in &outputs { - let m = m.borrow().meta.clone().normalize(); - if let Err(cause) = outputsaddedtx.send(outputs::Event::Located(m)) { - tracing::error!("unable to deliver output event: {:?}", cause); - } - } - } - event => { - tracing::warn!("unhandled zwlr_output_manager event {:?} {:?}", m, event); - } - }; - } - }); - - zwlr_output_manager.create_configuration(0); - - Ok(outputsaddedrx) -} - -impl Consumer for std::sync::Arc> { - fn consume( - &self, - obj: &wlc::Main, - event: &zwlr_output_head_v1::Event, - ) { - match event { - zwlr_output_head_v1::Event::Name { name } => { - self.borrow_mut().meta.name = name.to_string(); - } - zwlr_output_head_v1::Event::Description { description } => { - self.borrow_mut().meta.description = description.to_string(); - } - zwlr_output_head_v1::Event::PhysicalSize { width, height } => { - self.borrow_mut().meta.physical = outputs::Dimensions::from((*width, *height)); - } - zwlr_output_head_v1::Event::Make { make } => { - self.borrow_mut().meta.make = make.to_string(); - } - zwlr_output_head_v1::Event::Model { model } => { - self.borrow_mut().meta.model = model.to_string(); - } - zwlr_output_head_v1::Event::SerialNumber { .. } => {} // ignored - zwlr_output_head_v1::Event::Enabled { enabled } => { - self.borrow_mut().meta.enabled = *enabled > 0; - } - zwlr_output_head_v1::Event::Position { x, y } => { - self.borrow_mut().meta.position = outputs::Position::from((*x, *y)); - } - zwlr_output_head_v1::Event::Scale { scale } => { - self.borrow_mut().meta.scale = *scale; - } - zwlr_output_head_v1::Event::Transform { transform } => { - self.borrow_mut().meta.transform = *transform; - } - zwlr_output_head_v1::Event::Mode { mode } => { - let current = - std::sync::Arc::new(std::cell::RefCell::new(outputs::Mode::default())); - self.borrow_mut().modes.push(current.clone()); - mode.quick_assign({ - move |m, event, _ctx| match event { - zwlr_output_mode_v1::Event::Size { width, height } => { - current.borrow_mut().logical = - outputs::Dimensions::from((width, height)); - } - zwlr_output_mode_v1::Event::Refresh { refresh } => { - current.borrow_mut().refresh = refresh; - } - zwlr_output_mode_v1::Event::Preferred => { - current.borrow_mut().preferred = true; - } - _ => tracing::debug!("unhandled mode event {:?} {:?}", m, event), - } - }); - } - zwlr_output_head_v1::Event::CurrentMode { mode: _ } => { - // BUG: api here is pretty brutal. doesn't seem to be - // a way to get a main object from the provided mode. - // or to compare within the current set of modes for a match. - // as a result we *incorrectly* just assign the preferred mode - // as the current. - let b = self.borrow(); - let mut modes = b.modes.iter(); - let mode = match modes.find(|m| m.borrow().preferred) { - Some(m) => m.borrow().clone(), - None => return, - }; - drop(modes); - drop(b); - - self.borrow_mut().meta.logical = mode.logical.clone(); - self.borrow_mut().meta.refresh = mode.refresh; - } - _ => tracing::warn!("unhandled {:?} {:?}", obj, event), - }; - } -} diff --git a/druid-shell/src/backend/wayland/outputs/xdg.rs b/druid-shell/src/backend/wayland/outputs/xdg.rs deleted file mode 100644 index a4fb4dea1c..0000000000 --- a/druid-shell/src/backend/wayland/outputs/xdg.rs +++ /dev/null @@ -1,21 +0,0 @@ -use wayland_client as wlc; -use wayland_protocols::unstable::xdg_output::v1::client::zxdg_output_manager_v1; - -use super::super::error; -use super::super::outputs; - -pub fn detect( - registry: &wlc::GlobalManager, -) -> Result, error::Error> { - tracing::warn!("output detection not implemented using the xdg protocol"); - let (_outputsaddedtx, outputsaddedrx) = calloop::channel::channel::(); - let zxdg_output_manager = registry - .instantiate_exact::(3) - .map_err(|e| error::Error::global("zxdg_output_manager_v1", 3, e))?; - - zxdg_output_manager.quick_assign(|m, event, ctx| { - tracing::info!("global zxdg output manager {:?} {:?} {:?}", m, ctx, event); - }); - - Ok(outputsaddedrx) -} diff --git a/druid-shell/src/backend/wayland/screen.rs b/druid-shell/src/backend/wayland/screen.rs index 45a9524da6..93a06ab05c 100644 --- a/druid-shell/src/backend/wayland/screen.rs +++ b/druid-shell/src/backend/wayland/screen.rs @@ -15,13 +15,11 @@ //! wayland Monitors and Screen information. use crate::screen::Monitor; -use super::display; use super::error; use super::outputs; fn _get_monitors() -> Result, error::Error> { - let env = display::global()?; - let metas = outputs::current(&env)?; + let metas = outputs::current()?; let monitors: Vec = metas .iter() .map(|m| { diff --git a/druid-shell/src/backend/wayland/surfaces/layershell.rs b/druid-shell/src/backend/wayland/surfaces/layershell.rs index 146ba475b1..8f9257115e 100644 --- a/druid-shell/src/backend/wayland/surfaces/layershell.rs +++ b/druid-shell/src/backend/wayland/surfaces/layershell.rs @@ -5,7 +5,7 @@ use wayland_protocols::xdg_shell::client::xdg_surface; use crate::kurbo; use crate::window; -use super::super::application::Output; +use super::super::outputs; use super::super::error; use super::surface; use super::Compositor; @@ -282,23 +282,22 @@ impl Surface { } impl Outputs for Surface { - fn removed(&self, o: &Output) { + fn removed(&self, o: &outputs::Meta) { self.inner.wl_surface.borrow().removed(o); } - fn inserted(&self, o: &Output) { + fn inserted(&self, o: &outputs::Meta) { if !*self.inner.requires_initialization.borrow() { tracing::debug!( "skipping reinitialization output for layershell {:?} {:?}", - o.gid, - o.id() + o.id(), + o ); return; } tracing::debug!( - "reinitializing output for layershell {:?} {:?} {:?}", - o.gid, + "reinitializing output for layershell {:?} {:?}", o.id(), o ); diff --git a/druid-shell/src/backend/wayland/surfaces/mod.rs b/druid-shell/src/backend/wayland/surfaces/mod.rs index d2d9e0a0ef..a21daf65e3 100644 --- a/druid-shell/src/backend/wayland/surfaces/mod.rs +++ b/druid-shell/src/backend/wayland/surfaces/mod.rs @@ -10,7 +10,7 @@ use crate::kurbo; use crate::Scale; use crate::TextFieldToken; -use super::application; +use super::outputs; use super::error; pub mod buffers; @@ -23,7 +23,7 @@ pub mod toplevel; pub static GLOBAL_ID: crate::Counter = crate::Counter::new(); pub trait Compositor { - fn output(&self, id: u32) -> Option; + fn output(&self, id: u32) -> Option; fn create_surface(&self) -> wlc::Main; fn shared_mem(&self) -> wlc::Main; fn get_xdg_surface(&self, surface: &wlc::Main) @@ -52,8 +52,8 @@ pub trait Popup { } pub(super) trait Outputs { - fn removed(&self, o: &application::Output); - fn inserted(&self, o: &application::Output); + fn removed(&self, o: &outputs::Meta); + fn inserted(&self, o: &outputs::Meta); } // handle on given surface. @@ -107,7 +107,7 @@ impl CompositorHandle { ); scale }, - Some(output) => scale.max(output.scale), + Some(output) => scale.max(output.scale as i32), } }); @@ -122,7 +122,7 @@ impl CompositorHandle { } impl Compositor for CompositorHandle { - fn output(&self, id: u32) -> Option { + fn output(&self, id: u32) -> Option { match self.inner.upgrade() { None => None, Some(c) => c.output(id), diff --git a/druid-shell/src/backend/wayland/surfaces/surface.rs b/druid-shell/src/backend/wayland/surfaces/surface.rs index e886840159..b972f2bf9c 100644 --- a/druid-shell/src/backend/wayland/surfaces/surface.rs +++ b/druid-shell/src/backend/wayland/surfaces/surface.rs @@ -3,13 +3,13 @@ use std::rc::Rc; use wayland_client as wlc; use wayland_client::protocol::wl_surface; -use crate::backend::wayland::application; use crate::kurbo; use crate::window; use crate::{piet::Piet, region::Region, scale::Scale, TextFieldToken}; use super::super::Changed; +use super::super::outputs; use super::buffers; use super::error; use super::idle; @@ -117,10 +117,11 @@ impl Surface { current.outputs.borrow_mut().insert(proxy.id()); } wl_surface::Event::Leave { output } => { + let proxy = wlc::Proxy::from(output.clone()); current .outputs .borrow_mut() - .remove(&wlc::Proxy::from(output.clone()).id()); + .remove(&proxy.id()); } _ => tracing::warn!("unhandled wayland surface event {:?}", event), } @@ -139,11 +140,11 @@ impl Surface { } impl Outputs for Surface { - fn removed(&self, o: &application::Output) { + fn removed(&self, o: &outputs::Meta) { self.inner.outputs.borrow_mut().remove(&o.id()); } - fn inserted(&self, _: &application::Output) { + fn inserted(&self, _: &outputs::Meta) { // nothing to do here. } } @@ -560,9 +561,9 @@ impl Decor for Dead { } impl Outputs for Dead { - fn removed(&self, _: &application::Output) {} + fn removed(&self, _: &outputs::Meta) {} - fn inserted(&self, _: &application::Output) {} + fn inserted(&self, _: &outputs::Meta) {} } impl Popup for Dead { diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index 7d05952ce7..0ffe636709 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -16,10 +16,11 @@ use tracing; use super::{ - application::{Application, ApplicationData, Output, Timer}, + application::{Application, ApplicationData, Timer}, error::Error, menu::Menu, surfaces, + outputs, }; use crate::{ @@ -52,11 +53,11 @@ pub struct WindowHandle { } impl surfaces::Outputs for WindowHandle { - fn removed(&self, o: &Output) { + fn removed(&self, o: &outputs::Meta) { self.inner.outputs.removed(o) } - fn inserted(&self, o: &Output) { + fn inserted(&self, o: &outputs::Meta) { self.inner.outputs.inserted(o) } } From 2451da9b1769d3f47d186b298d9d930aa76e5702 Mon Sep 17 00:00:00 2001 From: James Lawrence Date: Sat, 11 Dec 2021 10:17:11 -0500 Subject: [PATCH 30/31] cargo fmt --- .../src/backend/wayland/application.rs | 33 ++++++------ druid-shell/src/backend/wayland/display.rs | 2 +- .../src/backend/wayland/outputs/mod.rs | 6 +-- .../src/backend/wayland/outputs/output.rs | 53 ++++++++++--------- .../backend/wayland/surfaces/layershell.rs | 8 +-- .../src/backend/wayland/surfaces/mod.rs | 2 +- .../src/backend/wayland/surfaces/surface.rs | 5 +- druid-shell/src/backend/wayland/window.rs | 3 +- 8 files changed, 55 insertions(+), 57 deletions(-) diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index 59e096b1c4..9263ea4e28 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -15,7 +15,7 @@ #![allow(clippy::single_match)] use super::{ - clipboard, display, outputs, error::Error, events::WaylandSource, keyboard, pointers, surfaces, + clipboard, display, error::Error, events::WaylandSource, keyboard, outputs, pointers, surfaces, window::WindowHandle, }; @@ -333,22 +333,23 @@ impl Application { .insert_source(self.data.outputsqueue.take().unwrap(), { move |evt, _ignored, appdata| match evt { calloop::channel::Event::Closed => {} - calloop::channel::Event::Msg(output) => { - match output { - outputs::Event::Located(output) => { - appdata.outputs.borrow_mut().insert(output.id(), output.clone()); - for (_, win) in appdata.handles_iter() { - surfaces::Outputs::inserted(&win, &output); - } - }, - outputs::Event::Removed(output) => { - appdata.outputs.borrow_mut().remove(&output.id()); - for (_, win) in appdata.handles_iter() { - surfaces::Outputs::removed(&win, &output); - } - }, + calloop::channel::Event::Msg(output) => match output { + outputs::Event::Located(output) => { + appdata + .outputs + .borrow_mut() + .insert(output.id(), output.clone()); + for (_, win) in appdata.handles_iter() { + surfaces::Outputs::inserted(&win, &output); + } } - } + outputs::Event::Removed(output) => { + appdata.outputs.borrow_mut().remove(&output.id()); + for (_, win) in appdata.handles_iter() { + surfaces::Outputs::removed(&win, &output); + } + } + }, } }) .unwrap(); diff --git a/druid-shell/src/backend/wayland/display.rs b/druid-shell/src/backend/wayland/display.rs index e686830b44..613fcbb8c0 100644 --- a/druid-shell/src/backend/wayland/display.rs +++ b/druid-shell/src/backend/wayland/display.rs @@ -212,4 +212,4 @@ pub(super) fn print(reg: &wlc::GlobalManager) { pub(super) fn count(reg: &wlc::GlobalManager, i: &str) -> usize { reg.list().iter().filter(|(_, name, _)| name == i).count() -} \ No newline at end of file +} diff --git a/druid-shell/src/backend/wayland/outputs/mod.rs b/druid-shell/src/backend/wayland/outputs/mod.rs index 4a91364879..871c972de8 100644 --- a/druid-shell/src/backend/wayland/outputs/mod.rs +++ b/druid-shell/src/backend/wayland/outputs/mod.rs @@ -13,7 +13,7 @@ pub enum Event { } pub fn auto( - env: & impl display::GlobalEventDispatch, + env: &impl display::GlobalEventDispatch, ) -> Result, error::Error> { tracing::debug!("detecting xdg outputs"); match output::detect(env) { @@ -77,8 +77,8 @@ pub(super) fn current() -> Result, error::Error> { } let res = queue - .sync_roundtrip(&mut (), |_, _, _| unreachable!()) - .map_err(error::Error::error); + .sync_roundtrip(&mut (), |_, _, _| unreachable!()) + .map_err(error::Error::error); if let Err(cause) = res { tracing::error!("wayland sync failed {:?}", cause); diff --git a/druid-shell/src/backend/wayland/outputs/output.rs b/druid-shell/src/backend/wayland/outputs/output.rs index d69589a4a4..8061331e8c 100644 --- a/druid-shell/src/backend/wayland/outputs/output.rs +++ b/druid-shell/src/backend/wayland/outputs/output.rs @@ -1,12 +1,11 @@ +use super::super::display; +use super::super::error; +use super::super::outputs; use wayland_client as wlc; -use wayland_client::protocol::wl_registry; use wayland_client::protocol::wl_output; +use wayland_client::protocol::wl_registry; use wayland_protocols::unstable::xdg_output::v1::client::zxdg_output_manager_v1; use wayland_protocols::unstable::xdg_output::v1::client::zxdg_output_v1; -use super::super::display; -use super::super::error; -use super::super::outputs; - pub trait Consumer { fn consume( @@ -17,14 +16,14 @@ pub trait Consumer { } pub fn detect( - env: & impl display::GlobalEventDispatch, + env: &impl display::GlobalEventDispatch, ) -> Result, error::Error> { let (outputstx, outputsrx) = calloop::channel::channel::(); let xdg_output_manager_id: std::cell::RefCell> = std::cell::RefCell::new(None); - display::GlobalEventDispatch::subscribe( - env, { - let outputstx = outputstx.clone(); - move |event: &'_ wlc::GlobalEvent, registry: &'_ wlc::Attached, _ctx: &'_ wlc::DispatchData| { + display::GlobalEventDispatch::subscribe(env, { + move |event: &'_ wlc::GlobalEvent, + registry: &'_ wlc::Attached, + _ctx: &'_ wlc::DispatchData| { match event { wlc::GlobalEvent::New { id, @@ -44,16 +43,14 @@ pub fn detect( } let output = registry.bind::(3, id); - let xdgm = match *xdg_output_manager_id.borrow() { - Some(xdgm_id) => Some(registry.bind::(3, xdgm_id)), - None => None, - }; + let xdgm = (*xdg_output_manager_id.borrow()).map(|xdgm_id| { + registry.bind::(3, xdgm_id) + }); let mut meta = Meta::default(); let mut xdgmeta = XdgMeta::new(); output.quick_assign({ let outputstx = outputstx.clone(); - let xdgm = xdgm.clone(); move |output, event, _ctx| { let mut m = match meta.consume(&output, &event) { Some(m) => m, @@ -69,7 +66,7 @@ pub fn detect( xdgmeta.consume(&xdg_output, &event); } }); - return + return; } } @@ -122,20 +119,24 @@ impl XdgMeta { tmp } - fn consume(&mut self, output: &wlc::Main, evt: &zxdg_output_v1::Event) { + fn consume( + &mut self, + output: &wlc::Main, + evt: &zxdg_output_v1::Event, + ) { match evt { zxdg_output_v1::Event::Name { name } => { self.state.borrow_mut().name = name.clone(); - }, + } zxdg_output_v1::Event::Description { description } => { self.state.borrow_mut().description = description.clone(); - }, + } zxdg_output_v1::Event::LogicalPosition { x, y } => { self.state.borrow_mut().position = outputs::Position::from((*x, *y)); - }, + } zxdg_output_v1::Event::LogicalSize { width, height } => { self.state.borrow_mut().logical = outputs::Dimensions::from((*width, *height)); - }, + } _ => tracing::warn!("unused xdg_output_v1 event {:?} {:?}", output, evt), }; } @@ -156,7 +157,11 @@ struct Meta { impl Consumer for Meta { /// Incorporate update data from the server for this output. - fn consume(&mut self, output: &wlc::Main, evt: &wl_output::Event,) -> Option { + fn consume( + &mut self, + output: &wlc::Main, + evt: &wl_output::Event, + ) -> Option { match evt { wl_output::Event::Geometry { x, @@ -201,7 +206,7 @@ impl Consumer for Meta { _ => { tracing::warn!("unknown output event {:?}", evt); // ignore possible future events None - }, + } } } -} \ No newline at end of file +} diff --git a/druid-shell/src/backend/wayland/surfaces/layershell.rs b/druid-shell/src/backend/wayland/surfaces/layershell.rs index 8f9257115e..3dbc4eb973 100644 --- a/druid-shell/src/backend/wayland/surfaces/layershell.rs +++ b/druid-shell/src/backend/wayland/surfaces/layershell.rs @@ -5,8 +5,8 @@ use wayland_protocols::xdg_shell::client::xdg_surface; use crate::kurbo; use crate::window; -use super::super::outputs; use super::super::error; +use super::super::outputs; use super::surface; use super::Compositor; use super::CompositorHandle; @@ -296,11 +296,7 @@ impl Outputs for Surface { return; } - tracing::debug!( - "reinitializing output for layershell {:?} {:?}", - o.id(), - o - ); + tracing::debug!("reinitializing output for layershell {:?} {:?}", o.id(), o); let sdata = self.inner.wl_surface.borrow().inner.clone(); let replacedsurface = self .inner diff --git a/druid-shell/src/backend/wayland/surfaces/mod.rs b/druid-shell/src/backend/wayland/surfaces/mod.rs index a21daf65e3..1df1bdf3a9 100644 --- a/druid-shell/src/backend/wayland/surfaces/mod.rs +++ b/druid-shell/src/backend/wayland/surfaces/mod.rs @@ -10,8 +10,8 @@ use crate::kurbo; use crate::Scale; use crate::TextFieldToken; -use super::outputs; use super::error; +use super::outputs; pub mod buffers; pub mod idle; diff --git a/druid-shell/src/backend/wayland/surfaces/surface.rs b/druid-shell/src/backend/wayland/surfaces/surface.rs index b972f2bf9c..cb69e8f5d4 100644 --- a/druid-shell/src/backend/wayland/surfaces/surface.rs +++ b/druid-shell/src/backend/wayland/surfaces/surface.rs @@ -118,10 +118,7 @@ impl Surface { } wl_surface::Event::Leave { output } => { let proxy = wlc::Proxy::from(output.clone()); - current - .outputs - .borrow_mut() - .remove(&proxy.id()); + current.outputs.borrow_mut().remove(&proxy.id()); } _ => tracing::warn!("unhandled wayland surface event {:?}", event), } diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index 0ffe636709..e9ff13a83c 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -19,8 +19,7 @@ use super::{ application::{Application, ApplicationData, Timer}, error::Error, menu::Menu, - surfaces, - outputs, + outputs, surfaces, }; use crate::{ From bdf6792f3fcbfb58da91f114afb75973220365c0 Mon Sep 17 00:00:00 2001 From: James Lawrence Date: Fri, 31 Dec 2021 07:47:21 -0500 Subject: [PATCH 31/31] peer review --- druid-shell/examples/empty_window.rs | 136 ------------------ druid-shell/src/backend/mod.rs | 6 +- druid-shell/src/backend/shared/keyboard.rs | 6 +- .../src/backend/wayland/application.rs | 8 +- druid-shell/src/backend/wayland/clipboard.rs | 2 - druid-shell/src/backend/wayland/display.rs | 46 ++---- .../src/backend/wayland/outputs/output.rs | 10 +- druid-shell/src/backend/wayland/pointers.rs | 42 +++--- .../src/backend/wayland/surfaces/buffers.rs | 1 - .../src/backend/wayland/surfaces/idle.rs | 5 +- .../src/backend/wayland/surfaces/surface.rs | 10 +- druid-shell/src/backend/wayland/util.rs | 13 -- druid-shell/src/backend/wayland/window.rs | 19 +-- 13 files changed, 60 insertions(+), 244 deletions(-) delete mode 100644 druid-shell/examples/empty_window.rs delete mode 100644 druid-shell/src/backend/wayland/util.rs diff --git a/druid-shell/examples/empty_window.rs b/druid-shell/examples/empty_window.rs deleted file mode 100644 index c541000718..0000000000 --- a/druid-shell/examples/empty_window.rs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2018 The Druid Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/// An example that is as simple as possible (just throw up an empty window). -use std::any::Any; - -use druid_shell::kurbo::{Point, Rect, Size}; -use druid_shell::piet::{Color, FixedLinearGradient, GradientStop, RenderContext}; - -use druid_shell::{ - Application, Cursor, FileDialogToken, FileInfo, KeyEvent, MouseEvent, Region, TimerToken, - WinHandler, WindowBuilder, WindowHandle, -}; - -#[derive(Default)] -struct HelloState { - size: Size, - handle: Option, -} - -impl WinHandler for HelloState { - fn connect(&mut self, handle: &WindowHandle) { - self.handle = Some(handle.clone()); - } - - fn prepare_paint(&mut self) { - self.handle.as_mut().unwrap().invalidate(); - } - - fn paint(&mut self, piet: &mut piet_common::Piet, _: &Region) { - // draw a gradient so we can see what's going on. - let brush = piet - .gradient(FixedLinearGradient { - start: Point::ZERO, - end: self.size.to_vec2().to_point(), - stops: vec![ - GradientStop { - pos: 0.0, - color: Color::RED, - }, - GradientStop { - pos: 1.0, - color: Color::BLUE, - }, - ], - }) - .unwrap(); - piet.fill(Rect::ZERO.with_size(self.size), &brush); - } - - fn command(&mut self, id: u32) { - println!("command id {}", id); - } - - fn open_file(&mut self, _token: FileDialogToken, file_info: Option) { - println!("open file result: {:?}", file_info); - } - - fn key_down(&mut self, event: KeyEvent) -> bool { - println!("keydown: {:?}", event); - false - } - - fn key_up(&mut self, event: KeyEvent) { - println!("keyup: {:?}", event); - } - - fn wheel(&mut self, event: &MouseEvent) { - println!("mouse_wheel {:?}", event); - } - - fn mouse_move(&mut self, event: &MouseEvent) { - self.handle.as_mut().unwrap().set_cursor(&Cursor::Arrow); - println!("mouse_move {:?}", event); - } - - fn mouse_down(&mut self, event: &MouseEvent) { - println!("mouse_down {:?}", event); - } - - fn mouse_up(&mut self, event: &MouseEvent) { - println!("mouse_up {:?}", event); - } - - fn timer(&mut self, id: TimerToken) { - println!("timer fired: {:?}", id); - } - - fn size(&mut self, size: Size) { - self.size = size; - } - - fn got_focus(&mut self) { - println!("Got focus"); - } - - fn lost_focus(&mut self) { - println!("Lost focus"); - } - - fn request_close(&mut self) { - self.handle.as_ref().unwrap().close(); - } - - fn destroy(&mut self) { - Application::global().quit() - } - - fn as_any(&mut self) -> &mut dyn Any { - self - } -} - -fn main() { - tracing_subscriber::fmt::init(); - let app = Application::new().unwrap(); - let mut builder = WindowBuilder::new(app.clone()); - builder.set_handler(Box::new(HelloState::default())); - builder.set_title("Hello example"); - - let window = builder.build().unwrap(); - window.show(); - - app.run(None); -} diff --git a/druid-shell/src/backend/mod.rs b/druid-shell/src/backend/mod.rs index 977d4fa265..9f7585c8f5 100644 --- a/druid-shell/src/backend/mod.rs +++ b/druid-shell/src/backend/mod.rs @@ -35,11 +35,11 @@ pub use x11::*; #[cfg(all(feature = "x11", any(target_os = "linux", target_os = "openbsd")))] pub(crate) mod shared; -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[cfg(all(feature = "wayland", any(target_os = "linux", target_os = "openbsd")))] mod wayland; -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[cfg(all(feature = "wayland", any(target_os = "linux", target_os = "openbsd")))] pub use wayland::*; -#[cfg(all(feature = "wayland", target_os = "linux"))] +#[cfg(all(feature = "wayland", any(target_os = "linux", target_os = "openbsd")))] pub(crate) mod shared; #[cfg(all( diff --git a/druid-shell/src/backend/shared/keyboard.rs b/druid-shell/src/backend/shared/keyboard.rs index b7f73e24b9..db925b5f24 100644 --- a/druid-shell/src/backend/shared/keyboard.rs +++ b/druid-shell/src/backend/shared/keyboard.rs @@ -18,8 +18,10 @@ use keyboard_types::{Code, Location}; #[cfg(any( - all(feature = "x11", any(target_os = "linux", target_os = "openbsd")), - all(feature = "wayland", target_os = "linux"), + all( + any(feature = "x11", feature = "wayland"), + any(target_os = "linux", target_os = "openbsd") + ), target_os = "macos" ))] /// Map key code to location. diff --git a/druid-shell/src/backend/wayland/application.rs b/druid-shell/src/backend/wayland/application.rs index 9263ea4e28..963e466d43 100644 --- a/druid-shell/src/backend/wayland/application.rs +++ b/druid-shell/src/backend/wayland/application.rs @@ -106,10 +106,6 @@ pub(crate) struct ApplicationData { /// necessary, but it negligable cost). pub(super) outputs: Rc>>, pub(super) seats: Rc>>>>, - /// Handles to any surfaces that have been created. - /// - /// This is where the window data is owned. Other handles should be weak. - // pub(super) surfaces: RefCell>>, /// Handles to any surfaces that have been created. pub(super) handles: RefCell>, @@ -257,6 +253,10 @@ impl Application { wayland: env, }); + for m in outputs::current()? { + app_data.outputs.borrow_mut().insert(m.id(), m); + } + // Collect the supported image formats. wl_shm.quick_assign(with_cloned!(app_data; move |d1, event, d3| { tracing::debug!("shared memory events {:?} {:?} {:?}", d1, event, d3); diff --git a/druid-shell/src/backend/wayland/clipboard.rs b/druid-shell/src/backend/wayland/clipboard.rs index a23e09d996..a7e23a5daa 100644 --- a/druid-shell/src/backend/wayland/clipboard.rs +++ b/druid-shell/src/backend/wayland/clipboard.rs @@ -48,10 +48,8 @@ impl Data { fn receive(&self, mimetype: &str) -> Option { for offer in self.current.borrow().iter() { if !offer.mimetype.starts_with(mimetype) { - // tracing::debug!("compared {:?} {:?}", offer.mimetype, mimetype); continue; } - // tracing::debug!("retrieving {:?} {:?}", offer.mimetype, mimetype); return Some(offer.clone()); } None diff --git a/druid-shell/src/backend/wayland/display.rs b/druid-shell/src/backend/wayland/display.rs index 613fcbb8c0..d83fdca0bf 100644 --- a/druid-shell/src/backend/wayland/display.rs +++ b/druid-shell/src/backend/wayland/display.rs @@ -5,10 +5,13 @@ use wayland_client as wlc; use wayland_client::protocol::wl_registry; use wayland_protocols::xdg_shell::client::xdg_wm_base; +type GlobalEventConsumer = dyn Fn(&wlc::GlobalEvent, &wlc::Attached, &wlc::DispatchData) + + 'static; + #[derive(Clone)] pub struct GlobalEventSubscription { id: u64, - sub: std::sync::Arc, + sub: std::sync::Arc, } impl GlobalEventSubscription { @@ -18,22 +21,20 @@ impl GlobalEventSubscription { } } -impl GlobalEventConsumer for GlobalEventSubscription { +impl GlobalEventSubscription { fn consume( &self, event: &wlc::GlobalEvent, registry: &wlc::Attached, ctx: &wlc::DispatchData, ) { - self.sub.consume(event, registry, ctx) + (self.sub)(event, registry, ctx) } } impl From for GlobalEventSubscription where - X: Fn(&wlc::GlobalEvent, &wlc::Attached, &wlc::DispatchData) - + GlobalEventConsumer - + 'static, + X: Fn(&wlc::GlobalEvent, &wlc::Attached, &wlc::DispatchData) + 'static, { fn from(closure: X) -> Self { Self { @@ -43,34 +44,11 @@ where } } -impl GlobalEventConsumer for X -where - X: Fn(&wlc::GlobalEvent, &wlc::Attached, &wlc::DispatchData) + 'static, -{ - fn consume( - &self, - event: &wlc::GlobalEvent, - registry: &wlc::Attached, - ctx: &wlc::DispatchData, - ) { - self(event, registry, ctx) - } -} - pub trait GlobalEventDispatch { fn subscribe(&self, sub: impl Into) -> GlobalEventSubscription; fn release(&self, s: &GlobalEventSubscription); } -pub trait GlobalEventConsumer { - fn consume( - &self, - event: &wlc::GlobalEvent, - registry: &wlc::Attached, - ctx: &wlc::DispatchData, - ); -} - pub(super) struct Dispatcher { incr: crate::Counter, subscriptions: std::cell::RefCell>, @@ -85,18 +63,16 @@ impl Default for Dispatcher { } } -impl GlobalEventConsumer for Dispatcher { +impl Dispatcher { fn consume( &self, event: &wlc::GlobalEvent, registry: &wlc::Attached, ctx: &wlc::DispatchData, ) { - // tracing::info!("global event initiated {:?} {:?}", registry, event); for (_, sub) in self.subscriptions.borrow().iter() { sub.consume(event, registry, ctx); } - // tracing::info!("global event completed {:?} {:?}", registry, event); } } @@ -120,12 +96,6 @@ pub(super) struct Environment { dispatcher: std::sync::Arc, } -// because we have the global environment we need to mark these as safe/send. -// strictly speaking we should probably guard the access to the various fields -// behind a mutex, but in practice we are not actually accessing across threads. -unsafe impl Sync for Environment {} -unsafe impl Send for Environment {} - impl GlobalEventDispatch for Environment { fn subscribe(&self, sub: impl Into) -> GlobalEventSubscription { self.dispatcher.subscribe(sub) diff --git a/druid-shell/src/backend/wayland/outputs/output.rs b/druid-shell/src/backend/wayland/outputs/output.rs index 8061331e8c..41f08270f8 100644 --- a/druid-shell/src/backend/wayland/outputs/output.rs +++ b/druid-shell/src/backend/wayland/outputs/output.rs @@ -7,14 +7,6 @@ use wayland_client::protocol::wl_registry; use wayland_protocols::unstable::xdg_output::v1::client::zxdg_output_manager_v1; use wayland_protocols::unstable::xdg_output::v1::client::zxdg_output_v1; -pub trait Consumer { - fn consume( - &mut self, - obj: &wlc::Main, - event: &wl_output::Event, - ) -> Option; -} - pub fn detect( env: &impl display::GlobalEventDispatch, ) -> Result, error::Error> { @@ -155,7 +147,7 @@ struct Meta { meta: outputs::Meta, } -impl Consumer for Meta { +impl Meta { /// Incorporate update data from the server for this output. fn consume( &mut self, diff --git a/druid-shell/src/backend/wayland/pointers.rs b/druid-shell/src/backend/wayland/pointers.rs index b3e9d3ddde..7901caa924 100644 --- a/druid-shell/src/backend/wayland/pointers.rs +++ b/druid-shell/src/backend/wayland/pointers.rs @@ -18,11 +18,19 @@ const BTN_MIDDLE: u32 = 0x112; // used to keep track of click event counts. #[derive(Debug, Clone)] -struct ClickDebouncer(std::time::Instant, u8, mouse::MouseButton); +struct ClickDebouncer { + timestamp: std::time::Instant, + count: u8, + previous: mouse::MouseButton, +} impl Default for ClickDebouncer { fn default() -> Self { - Self(std::time::Instant::now(), 1, mouse::MouseButton::None) + Self { + timestamp: std::time::Instant::now(), + count: 1, + previous: mouse::MouseButton::None, + } } } @@ -34,28 +42,32 @@ impl ClickDebouncer { const THRESHOLD: std::time::Duration = std::time::Duration::from_millis(500); fn reset(ts: std::time::Instant, btn: mouse::MouseButton) -> Self { - Self(ts, 1, btn) + Self { + timestamp: ts, + count: 1, + previous: btn, + } } fn debounce(&mut self, current: MouseEvtKind) -> MouseEvtKind { let ts = std::time::Instant::now(); // reset counting and button. - if self.0 + ClickDebouncer::THRESHOLD < ts { + if self.timestamp + ClickDebouncer::THRESHOLD < ts { *self = ClickDebouncer::default(); } match current { - MouseEvtKind::Up(mut evt) if self.2 == evt.button => { - evt.count = self.1; + MouseEvtKind::Up(mut evt) if self.previous == evt.button => { + evt.count = self.count; MouseEvtKind::Up(evt) } - MouseEvtKind::Down(mut evt) if self.2 == evt.button => { - self.1 += 1; - evt.count = self.1; + MouseEvtKind::Down(mut evt) if self.previous == evt.button => { + self.count += 1; + evt.count = self.count; MouseEvtKind::Down(evt) } - MouseEvtKind::Down(evt) if self.2 != evt.button => { + MouseEvtKind::Down(evt) if self.previous != evt.button => { *self = ClickDebouncer::reset(ts, evt.button); MouseEvtKind::Down(evt) } @@ -250,15 +262,7 @@ impl Pointer { appdata.pointer.push(PointerEvent::Axis { axis, value }); } wl_pointer::Event::Frame => { - let winhandle = match appdata.acquire_current_window() { - Some(w) => w, - None => { - tracing::warn!("dropping mouse events, no window available"); - appdata.pointer.queued_events.borrow_mut().clear(); - return; - } - }; - let winhandle = match winhandle.data() { + let winhandle = match appdata.acquire_current_window().and_then(|w| w.data()) { Some(w) => w, None => { tracing::warn!("dropping mouse events, no window available"); diff --git a/druid-shell/src/backend/wayland/surfaces/buffers.rs b/druid-shell/src/backend/wayland/surfaces/buffers.rs index 95855d0102..9cf55039a8 100644 --- a/druid-shell/src/backend/wayland/surfaces/buffers.rs +++ b/druid-shell/src/backend/wayland/surfaces/buffers.rs @@ -582,7 +582,6 @@ impl From for RawSize { fn from(s: Size) -> Self { let width = s.width as i32; let height = s.height as i32; - // Sanity check assert!(width >= 0 && height >= 0); diff --git a/druid-shell/src/backend/wayland/surfaces/idle.rs b/druid-shell/src/backend/wayland/surfaces/idle.rs index 13ba17ffeb..5772df9a92 100644 --- a/druid-shell/src/backend/wayland/surfaces/idle.rs +++ b/druid-shell/src/backend/wayland/surfaces/idle.rs @@ -11,7 +11,10 @@ impl std::fmt::Debug for Kind { fn fmt(&self, format: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { match self { Kind::Callback(_) => format.debug_struct("Idle(Callback)").finish(), - Kind::Token(_) => format.debug_struct("Idle(Token)").finish(), + Kind::Token(token) => format + .debug_struct("Idle(Token)") + .field("token", &token) + .finish(), } } } diff --git a/druid-shell/src/backend/wayland/surfaces/surface.rs b/druid-shell/src/backend/wayland/surfaces/surface.rs index cb69e8f5d4..48ae8a1f3f 100644 --- a/druid-shell/src/backend/wayland/surfaces/surface.rs +++ b/druid-shell/src/backend/wayland/surfaces/surface.rs @@ -2,6 +2,9 @@ use std::cell::{Cell, RefCell}; use std::rc::Rc; use wayland_client as wlc; use wayland_client::protocol::wl_surface; +use wayland_protocols::xdg_shell::client::xdg_popup; +use wayland_protocols::xdg_shell::client::xdg_positioner; +use wayland_protocols::xdg_shell::client::xdg_surface; use crate::kurbo; use crate::window; @@ -566,10 +569,9 @@ impl Outputs for Dead { impl Popup for Dead { fn surface<'a>( &self, - _: &'a wlc::Main, - _: &'a wlc::Main, - ) -> Result, error::Error> - { + _: &'a wlc::Main, + _: &'a wlc::Main, + ) -> Result, error::Error> { tracing::warn!("popup invoked on a dead surface"); Err(error::Error::InvalidParent(0)) } diff --git a/druid-shell/src/backend/wayland/util.rs b/druid-shell/src/backend/wayland/util.rs deleted file mode 100644 index c5f2147dd9..0000000000 --- a/druid-shell/src/backend/wayland/util.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2019 The Druid Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index e9ff13a83c..26946a2b65 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -14,6 +14,9 @@ #![allow(clippy::single_match)] use tracing; +use wayland_protocols::xdg_shell::client::xdg_popup; +use wayland_protocols::xdg_shell::client::xdg_positioner; +use wayland_protocols::xdg_shell::client::xdg_surface; use super::{ application::{Application, ApplicationData, Timer}, @@ -62,18 +65,11 @@ impl surfaces::Outputs for WindowHandle { } impl surfaces::Popup for WindowHandle { - fn surface<'a>( + fn surface( &self, - popup: &'a wayland_client::Main< - wayland_protocols::xdg_shell::client::xdg_surface::XdgSurface, - >, - pos: &'a wayland_client::Main< - wayland_protocols::xdg_shell::client::xdg_positioner::XdgPositioner, - >, - ) -> Result< - wayland_client::Main, - Error, - > { + popup: &wayland_client::Main, + pos: &wayland_client::Main, + ) -> Result, Error> { self.inner.popup.surface(popup, pos) } } @@ -427,7 +423,6 @@ impl WindowBuilder { let handler = self.handler.expect("must set a window handler"); // compute the initial window size. let initial_size = appdata.initial_window_size(self.size); - let surface = surfaces::toplevel::Surface::new(appdata.clone(), handler, initial_size, self.min_size);