From f62baeda64d1f1dd0c279f4fb953dfd26aba6221 Mon Sep 17 00:00:00 2001 From: Roman <40907255+witelokk@users.noreply.github.com> Date: Thu, 15 Feb 2024 01:50:11 +0300 Subject: [PATCH] gpui: Add Wayland support (#7664) This PR adds Wayland support to gpui using [wayland-rs](https://github.com/Smithay/wayland-rs). It is based on [#7598](https://github.com/zed-industries/zed/pull/7598). It detects Wayland support at runtime by checking the existence of the `WAYLAND_DISPLAY` environment variable. If it does not exist or is empty, the X11 backend will be used. To use the X11 backend in a Wayland session (for development purposes), you just need to unset WAYLAND_DISPLAY (`WAYLAND_DISPLAY= cargo run ...`). At the moment it only creates the window and renders the initial content provided by `BladeRenderer`, so it can run "Hello world" example. ![image](https://github.com/zed-industries/zed/assets/40907255/1655bc64-4d36-4178-9851-bfe42f03f716) Todo: - [x] Add basic Wayland support. - [x] Add window resizing. - [x] Add window closing. - [x] Add window updating. - [ ] Implement input handling, fractional scaling, and support other Wayland protocols. - [ ] Implement all unimplemented todo!(linux). - [ ] Add window decorations or use custom decorations (like on MacOS). - [ ] Address other missing functionality. Release Notes: - N/A --------- Co-authored-by: gabydd Co-authored-by: Mikayla Maki --- Cargo.lock | 88 ++++- crates/gpui/Cargo.toml | 3 + crates/gpui/src/app.rs | 61 +-- crates/gpui/src/platform/linux.rs | 4 + crates/gpui/src/platform/linux/platform.rs | 100 +++-- crates/gpui/src/platform/linux/wayland.rs | 7 + .../gpui/src/platform/linux/wayland/client.rs | 256 +++++++++++++ .../linux/wayland/client_dispatcher.rs | 30 ++ .../src/platform/linux/wayland/display.rs | 31 ++ .../gpui/src/platform/linux/wayland/window.rs | 350 ++++++++++++++++++ script/linux | 3 + typos.toml | 3 +- 12 files changed, 874 insertions(+), 62 deletions(-) create mode 100644 crates/gpui/src/platform/linux/wayland.rs create mode 100644 crates/gpui/src/platform/linux/wayland/client.rs create mode 100644 crates/gpui/src/platform/linux/wayland/client_dispatcher.rs create mode 100644 crates/gpui/src/platform/linux/wayland/display.rs create mode 100644 crates/gpui/src/platform/linux/wayland/window.rs diff --git a/Cargo.lock b/Cargo.lock index 61525fe52c..7297c76088 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2489,6 +2489,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + [[package]] name = "dwrote" version = "0.11.0" @@ -3518,6 +3524,9 @@ dependencies = [ "util", "uuid 1.4.1", "waker-fn", + "wayland-backend", + "wayland-client", + "wayland-protocols", "xcb", ] @@ -5922,7 +5931,7 @@ dependencies = [ "base64 0.21.4", "indexmap 1.9.3", "line-wrap", - "quick-xml", + "quick-xml 0.30.0", "serde", "time", ] @@ -6391,6 +6400,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", +] + [[package]] name = "quick_action_bar" version = "0.1.0" @@ -7245,6 +7263,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scoped_threadpool" version = "0.1.9" @@ -10320,6 +10344,66 @@ name = "wasmtime-wmemcheck" version = "16.0.0" source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" +[[package]] +name = "wayland-backend" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" +dependencies = [ + "cc", + "downcast-rs", + "rustix 0.38.30", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" +dependencies = [ + "bitflags 2.4.1", + "rustix 0.38.30", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +dependencies = [ + "bitflags 2.4.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283" +dependencies = [ + "proc-macro2", + "quick-xml 0.31.0", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.64" @@ -10753,7 +10837,7 @@ dependencies = [ "as-raw-xcb-connection", "bitflags 1.3.2", "libc", - "quick-xml", + "quick-xml 0.30.0", ] [[package]] diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 59c40cbaa2..f173ccb043 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -97,6 +97,9 @@ objc = "0.2" [target.'cfg(target_os = "linux")'.dependencies] flume = "0.11" xcb = { version = "1.3", features = ["as-raw-xcb-connection", "present", "randr"] } +wayland-client= { version = "0.31.2" } +wayland-protocols = { version = "0.31.2", features = ["client"] } +wayland-backend = { version = "0.3.3", features = ["client_system"] } as-raw-xcb-connection = "1" #TODO: use these on all platforms blade-graphics = { git = "https://github.com/kvark/blade", rev = "c4f951a88b345724cb952e920ad30e39851f7760" } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 6052f61358..333948f956 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1,33 +1,3 @@ -mod async_context; -mod entity_map; -mod model_context; -#[cfg(any(test, feature = "test-support"))] -mod test_context; - -pub use async_context::*; -use derive_more::{Deref, DerefMut}; -pub use entity_map::*; -pub use model_context::*; -use refineable::Refineable; -use smol::future::FutureExt; -#[cfg(any(test, feature = "test-support"))] -pub use test_context::*; -use time::UtcOffset; - -use crate::WindowAppearance; -use crate::{ - current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any, - AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, - DispatchPhase, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, - LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, - SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, - TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId, -}; -use anyhow::{anyhow, Result}; -use collections::{FxHashMap, FxHashSet, VecDeque}; -use futures::{channel::oneshot, future::LocalBoxFuture, Future}; - -use slotmap::SlotMap; use std::{ any::{type_name, TypeId}, cell::{Ref, RefCell, RefMut}, @@ -38,11 +8,42 @@ use std::{ sync::{atomic::Ordering::SeqCst, Arc}, time::Duration, }; + +use anyhow::{anyhow, Result}; +use derive_more::{Deref, DerefMut}; +use futures::{channel::oneshot, future::LocalBoxFuture, Future}; +use slotmap::SlotMap; +use smol::future::FutureExt; +use time::UtcOffset; + +pub use async_context::*; +use collections::{FxHashMap, FxHashSet, VecDeque}; +pub use entity_map::*; +pub use model_context::*; +use refineable::Refineable; +#[cfg(any(test, feature = "test-support"))] +pub use test_context::*; use util::{ http::{self, HttpClient}, ResultExt, }; +use crate::WindowAppearance; +use crate::{ + current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any, + AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, + DispatchPhase, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, + LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, + SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, + TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId, +}; + +mod async_context; +mod entity_map; +mod model_context; +#[cfg(any(test, feature = "test-support"))] +mod test_context; + /// The duration for which futures returned from [AppContext::on_app_context] or [ModelContext::on_app_quit] can run before the application fully quits. pub const SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(100); diff --git a/crates/gpui/src/platform/linux.rs b/crates/gpui/src/platform/linux.rs index 60caf428cd..1341b3da08 100644 --- a/crates/gpui/src/platform/linux.rs +++ b/crates/gpui/src/platform/linux.rs @@ -1,3 +1,6 @@ +//todo!(linux): remove this +#![allow(unused_variables)] + mod blade_atlas; mod blade_belt; mod blade_renderer; @@ -6,6 +9,7 @@ mod client_dispatcher; mod dispatcher; mod platform; mod text_system; +mod wayland; mod x11; pub(crate) use blade_atlas::*; diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index cd1bce6956..fb8e36c6ff 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -1,5 +1,6 @@ #![allow(unused)] +use std::env; use std::{ path::{Path, PathBuf}, rc::Rc, @@ -8,21 +9,21 @@ use std::{ }; use async_task::Runnable; +use flume::{Receiver, Sender}; use futures::channel::oneshot; use parking_lot::Mutex; use time::UtcOffset; - -use collections::{HashMap, HashSet}; +use wayland_client::Connection; use crate::platform::linux::client::Client; use crate::platform::linux::client_dispatcher::ClientDispatcher; +use crate::platform::linux::wayland::{WaylandClient, WaylandClientDispatcher}; use crate::platform::{X11Client, X11ClientDispatcher, XcbAtoms}; use crate::{ - Action, AnyWindowHandle, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DisplayId, + Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, Keymap, LinuxDispatcher, LinuxTextSystem, Menu, PathPromptOptions, - Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, PlatformWindow, Point, Result, - SemanticVersion, Size, Task, WindowAppearance, WindowOptions, X11Display, X11Window, - X11WindowState, + Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, PlatformWindow, Result, + SemanticVersion, Task, WindowOptions, }; #[derive(Default)] @@ -64,35 +65,78 @@ impl Default for LinuxPlatform { impl LinuxPlatform { pub(crate) fn new() -> Self { - let (xcb_connection, x_root_index) = - xcb::Connection::connect_with_extensions(None, &[xcb::Extension::Present], &[]) - .unwrap(); - let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap(); + let wayland_display = env::var_os("WAYLAND_DISPLAY"); + let use_wayland = wayland_display.is_some() && !wayland_display.unwrap().is_empty(); - let xcb_connection = Arc::new(xcb_connection); let (main_sender, main_receiver) = flume::unbounded::(); - let client_dispatcher: Arc = - Arc::new(X11ClientDispatcher::new(&xcb_connection, x_root_index)); - let dispatcher = LinuxDispatcher::new(main_sender, &client_dispatcher); - let dispatcher = Arc::new(dispatcher); + let text_system = Arc::new(LinuxTextSystem::new()); + let callbacks = Mutex::new(Callbacks::default()); + let state = Mutex::new(LinuxPlatformState { + quit_requested: false, + }); - let inner = LinuxPlatformInner { + if use_wayland { + Self::new_wayland(main_sender, main_receiver, text_system, callbacks, state) + } else { + Self::new_x11(main_sender, main_receiver, text_system, callbacks, state) + } + } + + fn new_wayland( + main_sender: Sender, + main_receiver: Receiver, + text_system: Arc, + callbacks: Mutex, + state: Mutex, + ) -> Self { + let conn = Arc::new(Connection::connect_to_env().unwrap()); + let client_dispatcher: Arc = + Arc::new(WaylandClientDispatcher::new(&conn)); + let dispatcher = Arc::new(LinuxDispatcher::new(main_sender, &client_dispatcher)); + let inner = Arc::new(LinuxPlatformInner { background_executor: BackgroundExecutor::new(dispatcher.clone()), foreground_executor: ForegroundExecutor::new(dispatcher.clone()), main_receiver, - text_system: Arc::new(LinuxTextSystem::new()), - callbacks: Mutex::new(Callbacks::default()), - state: Mutex::new(LinuxPlatformState { - quit_requested: false, - }), - }; - let inner = Arc::new(inner); - - let x11client = X11Client::new(Arc::clone(&inner), xcb_connection, x_root_index, atoms); - let x11client = Arc::new(x11client); - + text_system, + callbacks, + state, + }); + let client = Arc::new(WaylandClient::new(Arc::clone(&inner), Arc::clone(&conn))); Self { - client: x11client, + client, + inner: Arc::clone(&inner), + } + } + + fn new_x11( + main_sender: Sender, + main_receiver: Receiver, + text_system: Arc, + callbacks: Mutex, + state: Mutex, + ) -> Self { + let (xcb_connection, x_root_index) = xcb::Connection::connect(None).unwrap(); + let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap(); + let xcb_connection = Arc::new(xcb_connection); + let client_dispatcher: Arc = + Arc::new(X11ClientDispatcher::new(&xcb_connection, x_root_index)); + let dispatcher = Arc::new(LinuxDispatcher::new(main_sender, &client_dispatcher)); + let inner = Arc::new(LinuxPlatformInner { + background_executor: BackgroundExecutor::new(dispatcher.clone()), + foreground_executor: ForegroundExecutor::new(dispatcher.clone()), + main_receiver, + text_system, + callbacks, + state, + }); + let client = Arc::new(X11Client::new( + Arc::clone(&inner), + xcb_connection, + x_root_index, + atoms, + )); + Self { + client, inner: Arc::clone(&inner), } } diff --git a/crates/gpui/src/platform/linux/wayland.rs b/crates/gpui/src/platform/linux/wayland.rs new file mode 100644 index 0000000000..7621b5ee64 --- /dev/null +++ b/crates/gpui/src/platform/linux/wayland.rs @@ -0,0 +1,7 @@ +pub(crate) use client::*; +pub(crate) use client_dispatcher::*; + +mod client; +mod client_dispatcher; +mod display; +mod window; diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs new file mode 100644 index 0000000000..99680404ae --- /dev/null +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -0,0 +1,256 @@ +use std::rc::Rc; +use std::sync::Arc; + +use parking_lot::Mutex; +use wayland_client::protocol::wl_callback::WlCallback; +use wayland_client::{ + delegate_noop, + protocol::{ + wl_buffer, wl_callback, wl_compositor, wl_keyboard, wl_registry, wl_seat, wl_shm, + wl_shm_pool, + wl_surface::{self, WlSurface}, + }, + Connection, Dispatch, EventQueue, Proxy, QueueHandle, +}; +use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base}; + +use crate::platform::linux::client::Client; +use crate::platform::linux::wayland::window::WaylandWindow; +use crate::platform::{LinuxPlatformInner, PlatformWindow}; +use crate::{ + platform::linux::wayland::window::WaylandWindowState, AnyWindowHandle, DisplayId, + PlatformDisplay, WindowOptions, +}; + +pub(crate) struct WaylandClientState { + compositor: Option, + buffer: Option, + wm_base: Option, + windows: Vec<(xdg_surface::XdgSurface, Arc)>, + platform_inner: Arc, +} + +pub(crate) struct WaylandClient { + platform_inner: Arc, + conn: Arc, + state: Mutex, + event_queue: Mutex>, + qh: Arc>, +} + +impl WaylandClient { + pub(crate) fn new( + linux_platform_inner: Arc, + conn: Arc, + ) -> Self { + let state = WaylandClientState { + compositor: None, + buffer: None, + wm_base: None, + windows: Vec::new(), + platform_inner: Arc::clone(&linux_platform_inner), + }; + let event_queue: EventQueue = conn.new_event_queue(); + let qh = event_queue.handle(); + Self { + platform_inner: linux_platform_inner, + conn, + state: Mutex::new(state), + event_queue: Mutex::new(event_queue), + qh: Arc::new(qh), + } + } +} + +impl Client for WaylandClient { + fn run(&self, on_finish_launching: Box) { + let display = self.conn.display(); + let mut eq = self.event_queue.lock(); + let _registry = display.get_registry(&self.qh, ()); + + eq.roundtrip(&mut self.state.lock()).unwrap(); + + on_finish_launching(); + while !self.platform_inner.state.lock().quit_requested { + eq.flush().unwrap(); + eq.dispatch_pending(&mut self.state.lock()).unwrap(); + if let Some(guard) = self.conn.prepare_read() { + guard.read().unwrap(); + eq.dispatch_pending(&mut self.state.lock()).unwrap(); + } + if let Ok(runnable) = self.platform_inner.main_receiver.try_recv() { + runnable.run(); + } + } + } + + fn displays(&self) -> Vec> { + Vec::new() + } + + fn display(&self, id: DisplayId) -> Option> { + unimplemented!() + } + + fn open_window( + &self, + handle: AnyWindowHandle, + options: WindowOptions, + ) -> Box { + let mut state = self.state.lock(); + + let wm_base = state.wm_base.as_ref().unwrap(); + let compositor = state.compositor.as_ref().unwrap(); + let wl_surface = compositor.create_surface(&self.qh, ()); + let xdg_surface = wm_base.get_xdg_surface(&wl_surface, &self.qh, ()); + let toplevel = xdg_surface.get_toplevel(&self.qh, ()); + let wl_surface = Arc::new(wl_surface); + + wl_surface.frame(&self.qh, wl_surface.clone()); + wl_surface.commit(); + + let window_state: Arc = Arc::new(WaylandWindowState::new( + &self.conn, + wl_surface.clone(), + Arc::new(toplevel), + options, + )); + + state.windows.push((xdg_surface, Arc::clone(&window_state))); + Box::new(WaylandWindow(window_state)) + } +} + +impl Dispatch for WaylandClientState { + fn event( + state: &mut Self, + registry: &wl_registry::WlRegistry, + event: wl_registry::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + if let wl_registry::Event::Global { + name, interface, .. + } = event + { + match &interface[..] { + "wl_compositor" => { + let compositor = + registry.bind::(name, 1, qh, ()); + state.compositor = Some(compositor); + } + "xdg_wm_base" => { + let wm_base = registry.bind::(name, 1, qh, ()); + state.wm_base = Some(wm_base); + } + _ => {} + }; + } + } +} + +delegate_noop!(WaylandClientState: ignore wl_compositor::WlCompositor); +delegate_noop!(WaylandClientState: ignore wl_surface::WlSurface); +delegate_noop!(WaylandClientState: ignore wl_shm::WlShm); +delegate_noop!(WaylandClientState: ignore wl_shm_pool::WlShmPool); +delegate_noop!(WaylandClientState: ignore wl_buffer::WlBuffer); +delegate_noop!(WaylandClientState: ignore wl_seat::WlSeat); +delegate_noop!(WaylandClientState: ignore wl_keyboard::WlKeyboard); + +impl Dispatch> for WaylandClientState { + fn event( + state: &mut Self, + _: &WlCallback, + event: wl_callback::Event, + surf: &Arc, + _: &Connection, + qh: &QueueHandle, + ) { + if let wl_callback::Event::Done { .. } = event { + for window in &state.windows { + if window.1.surface.id() == surf.id() { + window.1.surface.frame(qh, surf.clone()); + window.1.update(); + window.1.surface.commit(); + } + } + } + } +} + +impl Dispatch for WaylandClientState { + fn event( + state: &mut Self, + xdg_surface: &xdg_surface::XdgSurface, + event: xdg_surface::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_surface::Event::Configure { serial, .. } = event { + xdg_surface.ack_configure(serial); + for window in &state.windows { + if &window.0 == xdg_surface { + window.1.update(); + window.1.surface.commit(); + return; + } + } + } + } +} + +impl Dispatch for WaylandClientState { + fn event( + state: &mut Self, + xdg_toplevel: &xdg_toplevel::XdgToplevel, + event: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_toplevel::Event::Configure { + width, + height, + states: _states, + } = event + { + if width == 0 || height == 0 { + return; + } + for window in &state.windows { + if window.1.toplevel.id() == xdg_toplevel.id() { + window.1.resize(width, height); + window.1.surface.commit(); + return; + } + } + } else if let xdg_toplevel::Event::Close = event { + state.windows.retain(|(_, window)| { + if window.toplevel.id() == xdg_toplevel.id() { + window.toplevel.destroy(); + false + } else { + true + } + }); + state.platform_inner.state.lock().quit_requested |= state.windows.is_empty(); + } + } +} + +impl Dispatch for WaylandClientState { + fn event( + _: &mut Self, + wm_base: &xdg_wm_base::XdgWmBase, + event: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_wm_base::Event::Ping { serial } = event { + wm_base.pong(serial); + } + } +} diff --git a/crates/gpui/src/platform/linux/wayland/client_dispatcher.rs b/crates/gpui/src/platform/linux/wayland/client_dispatcher.rs new file mode 100644 index 0000000000..c9154b2f6a --- /dev/null +++ b/crates/gpui/src/platform/linux/wayland/client_dispatcher.rs @@ -0,0 +1,30 @@ +use std::sync::Arc; + +use wayland_client::{Connection, EventQueue}; + +use crate::platform::linux::client_dispatcher::ClientDispatcher; + +pub(crate) struct WaylandClientDispatcher { + conn: Arc, + event_queue: Arc>, +} + +impl WaylandClientDispatcher { + pub(crate) fn new(conn: &Arc) -> Self { + let event_queue = conn.new_event_queue(); + Self { + conn: Arc::clone(conn), + event_queue: Arc::new(event_queue), + } + } +} + +impl Drop for WaylandClientDispatcher { + fn drop(&mut self) { + //todo!(linux) + } +} + +impl ClientDispatcher for WaylandClientDispatcher { + fn dispatch_on_main_thread(&self) {} +} diff --git a/crates/gpui/src/platform/linux/wayland/display.rs b/crates/gpui/src/platform/linux/wayland/display.rs new file mode 100644 index 0000000000..b0c7cd25e6 --- /dev/null +++ b/crates/gpui/src/platform/linux/wayland/display.rs @@ -0,0 +1,31 @@ +use std::fmt::Debug; + +use uuid::Uuid; + +use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size}; + +#[derive(Debug)] +pub(crate) struct WaylandDisplay {} + +impl PlatformDisplay for WaylandDisplay { + // todo!(linux) + fn id(&self) -> DisplayId { + return DisplayId(123); // return some fake data so it doesn't panic + } + + // todo!(linux) + fn uuid(&self) -> anyhow::Result { + return Ok(Uuid::from_bytes([0; 16])); // return some fake data so it doesn't panic + } + + // todo!(linux) + fn bounds(&self) -> Bounds { + Bounds { + origin: Default::default(), + size: Size { + width: GlobalPixels(1000f32), + height: GlobalPixels(500f32), + }, + } // return some fake data so it doesn't panic + } +} diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs new file mode 100644 index 0000000000..84c92d5e53 --- /dev/null +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -0,0 +1,350 @@ +use std::any::Any; +use std::ffi::c_void; +use std::rc::Rc; +use std::sync::Arc; + +use blade_graphics as gpu; +use blade_rwh::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle}; +use futures::channel::oneshot::Receiver; +use parking_lot::Mutex; +use raw_window_handle::{ + DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle, +}; +use wayland_client::{protocol::wl_surface, Proxy}; +use wayland_protocols::xdg::shell::client::xdg_toplevel; + +use crate::platform::linux::blade_renderer::BladeRenderer; +use crate::platform::linux::wayland::display::WaylandDisplay; +use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow}; +use crate::scene::Scene; +use crate::{ + px, Bounds, Modifiers, Pixels, PlatformDisplay, PlatformInput, Point, PromptLevel, Size, + WindowAppearance, WindowBounds, WindowOptions, +}; + +#[derive(Default)] +pub(crate) struct Callbacks { + request_frame: Option>, + input: Option bool>>, + active_status_change: Option>, + resize: Option, f32)>>, + fullscreen: Option>, + moved: Option>, + should_close: Option bool>>, + close: Option>, + appearance_changed: Option>, +} + +struct WaylandWindowInner { + renderer: BladeRenderer, + bounds: Bounds, +} + +struct RawWindow { + window: *mut c_void, + display: *mut c_void, +} + +unsafe impl HasRawWindowHandle for RawWindow { + fn raw_window_handle(&self) -> RawWindowHandle { + let mut wh = blade_rwh::WaylandWindowHandle::empty(); + wh.surface = self.window; + wh.into() + } +} + +unsafe impl HasRawDisplayHandle for RawWindow { + fn raw_display_handle(&self) -> RawDisplayHandle { + let mut dh = blade_rwh::WaylandDisplayHandle::empty(); + dh.display = self.display; + dh.into() + } +} + +impl WaylandWindowInner { + fn new( + conn: &Arc, + wl_surf: &Arc, + bounds: Bounds, + ) -> Self { + let raw = RawWindow { + window: wl_surf.id().as_ptr() as *mut _, + display: conn.backend().display_ptr() as *mut _, + }; + let gpu = Arc::new( + unsafe { + gpu::Context::init_windowed( + &raw, + gpu::ContextDesc { + validation: false, + capture: false, + }, + ) + } + .unwrap(), + ); + let extent = gpu::Extent { + width: bounds.size.width as u32, + height: bounds.size.height as u32, + depth: 1, + }; + Self { + renderer: BladeRenderer::new(gpu, extent), + bounds, + } + } +} + +pub(crate) struct WaylandWindowState { + conn: Arc, + inner: Mutex, + pub(crate) callbacks: Mutex, + pub(crate) surface: Arc, + pub(crate) toplevel: Arc, +} + +impl WaylandWindowState { + pub(crate) fn new( + conn: &Arc, + wl_surf: Arc, + toplevel: Arc, + options: WindowOptions, + ) -> Self { + if options.bounds == WindowBounds::Maximized { + toplevel.set_maximized(); + } else if options.bounds == WindowBounds::Fullscreen { + toplevel.set_fullscreen(None); + } + + let bounds: Bounds = match options.bounds { + WindowBounds::Fullscreen | WindowBounds::Maximized => Bounds { + origin: Point::default(), + size: Size { + width: 500, + height: 500, + }, //todo!(implement) + }, + WindowBounds::Fixed(bounds) => bounds.map(|p| p.0 as i32), + }; + + Self { + conn: Arc::clone(conn), + surface: Arc::clone(&wl_surf), + inner: Mutex::new(WaylandWindowInner::new(&Arc::clone(conn), &wl_surf, bounds)), + callbacks: Mutex::new(Callbacks::default()), + toplevel, + } + } + + pub fn update(&self) { + let mut cb = self.callbacks.lock(); + if let Some(mut fun) = cb.request_frame.take() { + drop(cb); + fun(); + self.callbacks.lock().request_frame = Some(fun); + } + } + + pub fn resize(&self, width: i32, height: i32) { + { + let mut inner = self.inner.lock(); + inner.bounds.size.width = width; + inner.bounds.size.height = height; + inner.renderer.resize(gpu::Extent { + width: width as u32, + height: height as u32, + depth: 1, + }); + } + let mut callbacks = self.callbacks.lock(); + if let Some(ref mut fun) = callbacks.resize { + fun( + Size { + width: px(width as f32), + height: px(height as f32), + }, + 1.0, + ); + } + if let Some(ref mut fun) = callbacks.moved { + fun() + } + } + + pub fn close(&self) { + let mut callbacks = self.callbacks.lock(); + if let Some(fun) = callbacks.close.take() { + fun() + } + self.toplevel.destroy(); + } +} + +#[derive(Clone)] +pub(crate) struct WaylandWindow(pub(crate) Arc); + +impl HasWindowHandle for WaylandWindow { + fn window_handle(&self) -> Result, HandleError> { + unimplemented!() + } +} + +impl HasDisplayHandle for WaylandWindow { + fn display_handle(&self) -> Result, HandleError> { + unimplemented!() + } +} + +impl PlatformWindow for WaylandWindow { + //todo!(linux) + fn bounds(&self) -> WindowBounds { + WindowBounds::Maximized + } + + // todo!(linux) + fn content_size(&self) -> Size { + let inner = self.0.inner.lock(); + Size { + width: Pixels(inner.bounds.size.width as f32), + height: Pixels(inner.bounds.size.height as f32), + } + } + + // todo!(linux) + fn scale_factor(&self) -> f32 { + return 1f32; + } + + //todo!(linux) + fn titlebar_height(&self) -> Pixels { + unimplemented!() + } + + // todo!(linux) + fn appearance(&self) -> WindowAppearance { + WindowAppearance::Light + } + + // todo!(linux) + fn display(&self) -> Rc { + Rc::new(WaylandDisplay {}) + } + + // todo!(linux) + fn mouse_position(&self) -> Point { + Point::default() + } + + //todo!(linux) + fn modifiers(&self) -> Modifiers { + crate::Modifiers::default() + } + + //todo!(linux) + fn as_any_mut(&mut self) -> &mut dyn Any { + unimplemented!() + } + + fn set_input_handler(&mut self, input_handler: PlatformInputHandler) { + //todo!(linux) + } + + //todo!(linux) + fn take_input_handler(&mut self) -> Option { + None + } + + //todo!(linux) + fn prompt( + &self, + level: PromptLevel, + msg: &str, + detail: Option<&str>, + answers: &[&str], + ) -> Receiver { + unimplemented!() + } + + fn activate(&self) { + //todo!(linux) + } + + fn set_title(&mut self, title: &str) { + self.0.toplevel.set_title(title.to_string()); + } + + fn set_edited(&mut self, edited: bool) { + //todo!(linux) + } + + fn show_character_palette(&self) { + //todo!(linux) + } + + fn minimize(&self) { + //todo!(linux) + } + + fn zoom(&self) { + //todo!(linux) + } + + fn toggle_full_screen(&self) { + //todo!(linux) + } + + fn on_request_frame(&self, callback: Box) { + self.0.callbacks.lock().request_frame = Some(callback); + } + + fn on_input(&self, callback: Box bool>) { + //todo!(linux) + } + + fn on_active_status_change(&self, callback: Box) { + //todo!(linux) + } + + fn on_resize(&self, callback: Box, f32)>) { + self.0.callbacks.lock().resize = Some(callback); + } + + fn on_fullscreen(&self, callback: Box) { + //todo!(linux) + } + + fn on_moved(&self, callback: Box) { + self.0.callbacks.lock().moved = Some(callback); + } + + fn on_should_close(&self, callback: Box bool>) { + self.0.callbacks.lock().should_close = Some(callback); + } + + fn on_close(&self, callback: Box) { + self.0.callbacks.lock().close = Some(callback); + } + + fn on_appearance_changed(&self, callback: Box) { + //todo!(linux) + } + + // todo!(linux) + fn is_topmost_for_position(&self, position: Point) -> bool { + false + } + + fn draw(&self, scene: &Scene) { + let mut inner = self.0.inner.lock(); + inner.renderer.draw(scene); + } + + fn sprite_atlas(&self) -> Arc { + let inner = self.0.inner.lock(); + inner.renderer.atlas().clone() + } + + fn set_graphics_profiler_enabled(&self, enabled: bool) { + //todo!(linux) + } +} diff --git a/script/linux b/script/linux index e2929133a1..b98f4b7d3e 100755 --- a/script/linux +++ b/script/linux @@ -11,6 +11,7 @@ if [[ -n $apt ]]; then libasound2-dev libfontconfig-dev vulkan-validationlayers* + libwayland-dev ) $maysudo "$apt" install -y "${deps[@]}" exit 0 @@ -24,6 +25,7 @@ if [[ -n $dnf ]]; then alsa-lib-devel fontconfig-devel vulkan-validation-layers + wayland-devel ) $maysudo "$dnf" install -y "${deps[@]}" exit 0 @@ -37,6 +39,7 @@ if [[ -n $pacman ]]; then alsa-lib fontconfig vulkan-validation-layers + wayland ) $maysudo "$pacman" -S --needed --noconfirm "${deps[@]}" exit 0 diff --git a/typos.toml b/typos.toml index 00e31d07b3..b05a1e1b83 100644 --- a/typos.toml +++ b/typos.toml @@ -21,7 +21,6 @@ extend-ignore-re = [ '"ba"', ":ba\\|z", # :/ crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql - "ADD COLUMN enviroment TEXT", - "ALTER TABLE rooms DROP COLUMN enviroment;", + "COLUMN enviroment", ] check-filename = true