Fix or promote leftover TODOs and GPUI APIs (#12514)

Mikayla Maki created

fixes https://github.com/zed-industries/zed/issues/11966

Release Notes:

- N/A

Change summary

Cargo.toml                                           |   5 
assets/keymaps/default-linux.json                    |   1 
crates/gpui/Cargo.toml                               |   1 
crates/gpui/src/app.rs                               |  14 
crates/gpui/src/platform.rs                          |  18 
crates/gpui/src/platform/app_menu.rs                 |  79 ++++
crates/gpui/src/platform/blade/blade_renderer.rs     |   2 
crates/gpui/src/platform/linux.rs                    |   3 
crates/gpui/src/platform/linux/dispatcher.rs         |   8 
crates/gpui/src/platform/linux/headless/client.rs    |  41 -
crates/gpui/src/platform/linux/platform.rs           |  44 +
crates/gpui/src/platform/linux/wayland/client.rs     | 269 +++++++++----
crates/gpui/src/platform/linux/wayland/display.rs    |  37 +
crates/gpui/src/platform/linux/wayland/serial.rs     |  45 --
crates/gpui/src/platform/linux/wayland/window.rs     | 125 +++--
crates/gpui/src/platform/linux/x11/client.rs         | 118 +++--
crates/gpui/src/platform/linux/x11/window.rs         |  72 +--
crates/gpui/src/platform/linux/x11/xim_handler.rs    |  34 
crates/gpui/src/platform/linux/xdg_desktop_portal.rs |   2 
crates/gpui/src/platform/mac/platform.rs             |   3 
crates/gpui/src/platform/mac/window.rs               |   4 
crates/gpui/src/platform/test.rs                     |   1 
crates/gpui/src/platform/test/text_system.rs         |  50 --
crates/gpui/src/platform/test/window.rs              |   4 
crates/gpui/src/platform/windows/window.rs           |   4 
crates/gpui/src/window.rs                            |  13 
crates/zed/src/zed.rs                                |   3 
27 files changed, 570 insertions(+), 430 deletions(-)

Detailed changes

Cargo.toml 🔗

@@ -494,10 +494,5 @@ non_canonical_partial_ord_impl = "allow"
 reversed_empty_ranges = "allow"
 type_complexity = "allow"
 
-[workspace.lints.rust]
-unexpected_cfgs = { level = "warn", check-cfg = [
-    'cfg(gles)', # used in gpui
-] }
-
 [workspace.metadata.cargo-machete]
 ignored = ["bindgen", "cbindgen", "prost_build", "serde"]

assets/keymaps/default-linux.json 🔗

@@ -28,7 +28,6 @@
       "ctrl-0": "zed::ResetBufferFontSize",
       "ctrl-,": "zed::OpenSettings",
       "ctrl-q": "zed::Quit",
-      "alt-f9": "zed::Hide",
       "f11": "zed::ToggleFullScreen"
     }
   },

crates/gpui/Cargo.toml 🔗

@@ -99,7 +99,6 @@ objc = "0.2"
 
 [target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies]
 flume = "0.11"
-#TODO: use these on all platforms
 blade-graphics.workspace = true
 blade-macros.workspace = true
 blade-util.workspace = true

crates/gpui/src/app.rs 🔗

@@ -29,10 +29,11 @@ use crate::{
     current_platform, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle,
     AppMetadata, AssetCache, AssetSource, BackgroundExecutor, ClipboardItem, Context,
     DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap,
-    Keystroke, LayoutId, Menu, MenuItem, PathPromptOptions, Pixels, Platform, PlatformDisplay,
-    Point, PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation,
-    SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext,
-    Window, WindowAppearance, WindowContext, WindowHandle, WindowId,
+    Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
+    PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render,
+    RenderablePromptHandle, Reservation, SharedString, SubscriberSet, Subscription, SvgRenderer,
+    Task, TextSystem, View, ViewContext, Window, WindowAppearance, WindowContext, WindowHandle,
+    WindowId,
 };
 
 mod async_context;
@@ -1167,6 +1168,11 @@ impl AppContext {
         self.platform.set_menus(menus, &self.keymap.borrow());
     }
 
+    /// Sets the menu bar for this application. This will replace any existing menu bar.
+    pub fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
+        self.platform.get_menus()
+    }
+
     /// Sets the right click menu for the app icon in the dock
     pub fn set_dock_menu(&mut self, menus: Vec<MenuItem>) {
         self.platform.set_dock_menu(menus, &self.keymap.borrow());

crates/gpui/src/platform.rs 🔗

@@ -1,5 +1,3 @@
-// todo(linux): remove
-#![cfg_attr(target_os = "linux", allow(dead_code))]
 // todo(windows): remove
 #![cfg_attr(windows, allow(dead_code))]
 
@@ -135,6 +133,10 @@ pub(crate) trait Platform: 'static {
     fn on_reopen(&self, callback: Box<dyn FnMut()>);
 
     fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
+    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
+        None
+    }
+
     fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
     fn add_recent_document(&self, _path: &Path) {}
     fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
@@ -203,7 +205,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
     fn content_size(&self) -> Size<Pixels>;
     fn scale_factor(&self) -> f32;
     fn appearance(&self) -> WindowAppearance;
-    fn display(&self) -> Rc<dyn PlatformDisplay>;
+    fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
     fn mouse_position(&self) -> Point<Pixels>;
     fn modifiers(&self) -> Modifiers;
     fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
@@ -413,6 +415,7 @@ impl PlatformInputHandler {
             .flatten()
     }
 
+    #[cfg_attr(target_os = "linux", allow(dead_code))]
     fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
         self.cx
             .update(|cx| self.handler.text_for_range(range_utf16, cx))
@@ -573,13 +576,17 @@ pub(crate) struct WindowParams {
     pub titlebar: Option<TitlebarOptions>,
 
     /// The kind of window to create
+    #[cfg_attr(target_os = "linux", allow(dead_code))]
     pub kind: WindowKind,
 
     /// Whether the window should be movable by the user
+    #[cfg_attr(target_os = "linux", allow(dead_code))]
     pub is_movable: bool,
 
+    #[cfg_attr(target_os = "linux", allow(dead_code))]
     pub focus: bool,
 
+    #[cfg_attr(target_os = "linux", allow(dead_code))]
     pub show: bool,
 
     pub display_id: Option<DisplayId>,
@@ -797,10 +804,6 @@ pub enum CursorStyle {
     /// corresponds to the CSS curosr value `row-resize`
     ResizeRow,
 
-    /// A cursor indicating that something will disappear if moved here
-    /// Does not correspond to a CSS cursor value
-    DisappearingItem,
-
     /// A text input cursor for vertical layout
     /// corresponds to the CSS cursor value `vertical-text`
     IBeamCursorForVerticalLayout,
@@ -865,6 +868,7 @@ impl ClipboardItem {
             .and_then(|m| serde_json::from_str(m).ok())
     }
 
+    #[cfg_attr(target_os = "linux", allow(dead_code))]
     pub(crate) fn text_hash(text: &str) -> u64 {
         let mut hasher = SeaHasher::new();
         text.hash(&mut hasher);

crates/gpui/src/platform/app_menu.rs 🔗

@@ -1,3 +1,5 @@
+use std::borrow::Cow;
+
 use crate::{Action, AppContext, Platform};
 use util::ResultExt;
 
@@ -10,6 +12,16 @@ pub struct Menu<'a> {
     pub items: Vec<MenuItem<'a>>,
 }
 
+impl<'a> Menu<'a> {
+    /// Create an OwnedMenu from this Menu
+    pub fn owned(self) -> OwnedMenu {
+        OwnedMenu {
+            name: self.name.to_string().into(),
+            items: self.items.into_iter().map(|item| item.owned()).collect(),
+        }
+    }
+}
+
 /// The different kinds of items that can be in a menu
 pub enum MenuItem<'a> {
     /// A separator between items
@@ -60,6 +72,73 @@ impl<'a> MenuItem<'a> {
             os_action: Some(os_action),
         }
     }
+
+    /// Create an OwnedMenuItem from this MenuItem
+    pub fn owned(self) -> OwnedMenuItem {
+        match self {
+            MenuItem::Separator => OwnedMenuItem::Separator,
+            MenuItem::Submenu(submenu) => OwnedMenuItem::Submenu(submenu.owned()),
+            MenuItem::Action {
+                name,
+                action,
+                os_action,
+            } => OwnedMenuItem::Action {
+                name: name.into(),
+                action,
+                os_action,
+            },
+        }
+    }
+}
+
+/// A menu of the application, either a main menu or a submenu
+#[derive(Clone)]
+pub struct OwnedMenu {
+    /// The name of the menu
+    pub name: Cow<'static, str>,
+
+    /// The items in the menu
+    pub items: Vec<OwnedMenuItem>,
+}
+
+/// The different kinds of items that can be in a menu
+pub enum OwnedMenuItem {
+    /// A separator between items
+    Separator,
+
+    /// A submenu
+    Submenu(OwnedMenu),
+
+    /// An action that can be performed
+    Action {
+        /// The name of this menu item
+        name: String,
+
+        /// the action to perform when this menu item is selected
+        action: Box<dyn Action>,
+
+        /// The OS Action that corresponds to this action, if any
+        /// See [`OsAction`] for more information
+        os_action: Option<OsAction>,
+    },
+}
+
+impl Clone for OwnedMenuItem {
+    fn clone(&self) -> Self {
+        match self {
+            OwnedMenuItem::Separator => OwnedMenuItem::Separator,
+            OwnedMenuItem::Submenu(submenu) => OwnedMenuItem::Submenu(submenu.clone()),
+            OwnedMenuItem::Action {
+                name,
+                action,
+                os_action,
+            } => OwnedMenuItem::Action {
+                name: name.clone(),
+                action: action.boxed_clone(),
+                os_action: *os_action,
+            },
+        }
+    }
 }
 
 // TODO: As part of the global selections refactor, these should

crates/gpui/src/platform/blade/blade_renderer.rs 🔗

@@ -20,7 +20,9 @@ use std::{mem, sync::Arc};
 
 const MAX_FRAME_TIME_MS: u32 = 1000;
 
+#[cfg(target_os = "macos")]
 pub type Context = ();
+#[cfg(target_os = "macos")]
 pub type Renderer = BladeRenderer;
 
 #[cfg(target_os = "macos")]

crates/gpui/src/platform/linux/dispatcher.rs 🔗

@@ -1,9 +1,3 @@
-#![allow(non_upper_case_globals)]
-#![allow(non_camel_case_types)]
-#![allow(non_snake_case)]
-// todo(linux): remove
-#![allow(unused_variables)]
-
 use crate::{PlatformDispatcher, TaskLabel};
 use async_task::Runnable;
 use calloop::{
@@ -63,7 +57,7 @@ impl LinuxDispatcher {
                         timer_handle
                             .insert_source(
                                 calloop::timer::Timer::from_duration(timer.duration),
-                                move |e, _, _| {
+                                move |_, _, _| {
                                     if let Some(runnable) = runnable.take() {
                                         runnable.run();
                                     }

crates/gpui/src/platform/linux/headless/client.rs 🔗

@@ -1,27 +1,16 @@
 use std::cell::RefCell;
-use std::ops::Deref;
 use std::rc::Rc;
-use std::time::{Duration, Instant};
 
 use calloop::{EventLoop, LoopHandle};
-use collections::HashMap;
 
 use util::ResultExt;
 
 use crate::platform::linux::LinuxClient;
 use crate::platform::{LinuxCommon, PlatformWindow};
-use crate::{
-    px, AnyWindowHandle, Bounds, CursorStyle, DisplayId, Modifiers, ModifiersChangedEvent, Pixels,
-    PlatformDisplay, PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowParams,
-};
-
-use calloop::{
-    generic::{FdWrapper, Generic},
-    RegistrationToken,
-};
+use crate::{AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, WindowParams};
 
 pub struct HeadlessClientState {
-    pub(crate) loop_handle: LoopHandle<'static, HeadlessClient>,
+    pub(crate) _loop_handle: LoopHandle<'static, HeadlessClient>,
     pub(crate) event_loop: Option<calloop::EventLoop<'static, HeadlessClient>>,
     pub(crate) common: LinuxCommon,
 }
@@ -37,15 +26,17 @@ impl HeadlessClient {
 
         let handle = event_loop.handle();
 
-        handle.insert_source(main_receiver, |event, _, _: &mut HeadlessClient| {
-            if let calloop::channel::Event::Msg(runnable) = event {
-                runnable.run();
-            }
-        });
+        handle
+            .insert_source(main_receiver, |event, _, _: &mut HeadlessClient| {
+                if let calloop::channel::Event::Msg(runnable) = event {
+                    runnable.run();
+                }
+            })
+            .ok();
 
         HeadlessClient(Rc::new(RefCell::new(HeadlessClientState {
             event_loop: Some(event_loop),
-            loop_handle: handle,
+            _loop_handle: handle,
             common,
         })))
     }
@@ -64,7 +55,7 @@ impl LinuxClient for HeadlessClient {
         None
     }
 
-    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
+    fn display(&self, _id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
         None
     }
 
@@ -72,10 +63,14 @@ impl LinuxClient for HeadlessClient {
         return Err(anyhow::anyhow!("neither DISPLAY, nor WAYLAND_DISPLAY found. You can still run zed for remote development with --dev-server-token."));
     }
 
+    fn active_window(&self) -> Option<AnyWindowHandle> {
+        None
+    }
+
     fn open_window(
         &self,
         _handle: AnyWindowHandle,
-        params: WindowParams,
+        _params: WindowParams,
     ) -> Box<dyn PlatformWindow> {
         unimplemented!()
     }
@@ -84,9 +79,9 @@ impl LinuxClient for HeadlessClient {
 
     fn open_uri(&self, _uri: &str) {}
 
-    fn write_to_primary(&self, item: crate::ClipboardItem) {}
+    fn write_to_primary(&self, _item: crate::ClipboardItem) {}
 
-    fn write_to_clipboard(&self, item: crate::ClipboardItem) {}
+    fn write_to_clipboard(&self, _item: crate::ClipboardItem) {}
 
     fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
         None

crates/gpui/src/platform/linux/platform.rs 🔗

@@ -38,9 +38,9 @@ use crate::platform::linux::xdg_desktop_portal::{should_auto_hide_scrollbars, wi
 use crate::{
     px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CosmicTextSystem, CursorStyle,
     DisplayId, ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers,
-    PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler, PlatformTextSystem,
-    PlatformWindow, Point, PromptLevel, Result, SemanticVersion, Size, Task, WindowAppearance,
-    WindowOptions, WindowParams,
+    OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler,
+    PlatformTextSystem, PlatformWindow, Point, PromptLevel, Result, SemanticVersion, Size, Task,
+    WindowAppearance, WindowOptions, WindowParams,
 };
 
 use super::x11::X11Client;
@@ -72,6 +72,7 @@ pub trait LinuxClient {
     fn write_to_clipboard(&self, item: ClipboardItem);
     fn read_from_primary(&self) -> Option<ClipboardItem>;
     fn read_from_clipboard(&self) -> Option<ClipboardItem>;
+    fn active_window(&self) -> Option<AnyWindowHandle>;
     fn run(&self);
 }
 
@@ -93,6 +94,7 @@ pub(crate) struct LinuxCommon {
     pub(crate) auto_hide_scrollbars: bool,
     pub(crate) callbacks: PlatformHandlers,
     pub(crate) signal: LoopSignal,
+    pub(crate) menus: Vec<OwnedMenu>,
 }
 
 impl LinuxCommon {
@@ -118,6 +120,7 @@ impl LinuxCommon {
             auto_hide_scrollbars,
             callbacks,
             signal,
+            menus: Vec::new(),
         };
 
         (common, main_receiver)
@@ -210,18 +213,21 @@ impl<P: LinuxClient + 'static> Platform for P {
         }
     }
 
-    // todo(linux)
-    fn activate(&self, ignoring_other_apps: bool) {}
+    fn activate(&self, ignoring_other_apps: bool) {
+        log::info!("activate is not implemented on Linux, ignoring the call")
+    }
 
-    // todo(linux)
-    fn hide(&self) {}
+    fn hide(&self) {
+        log::info!("hide is not implemented on Linux, ignoring the call")
+    }
 
     fn hide_other_apps(&self) {
-        log::warn!("hide_other_apps is not implemented on Linux, ignoring the call")
+        log::info!("hide_other_apps is not implemented on Linux, ignoring the call")
     }
 
-    // todo(linux)
-    fn unhide_other_apps(&self) {}
+    fn unhide_other_apps(&self) {
+        log::info!("unhide_other_apps is not implemented on Linux, ignoring the call")
+    }
 
     fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
         self.primary_display()
@@ -231,9 +237,8 @@ impl<P: LinuxClient + 'static> Platform for P {
         self.displays()
     }
 
-    // todo(linux)
     fn active_window(&self) -> Option<AnyWindowHandle> {
-        None
+        self.active_window()
     }
 
     fn open_window(
@@ -387,15 +392,22 @@ impl<P: LinuxClient + 'static> Platform for P {
         Ok(exe_path)
     }
 
-    // todo(linux)
-    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {}
+    fn set_menus(&self, menus: Vec<Menu>, _keymap: &Keymap) {
+        self.with_common(|common| {
+            common.menus = menus.into_iter().map(|menu| menu.owned()).collect();
+        })
+    }
+
+    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
+        self.with_common(|common| Some(common.menus.clone()))
+    }
+
     fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap) {}
 
     fn local_timezone(&self) -> UtcOffset {
         UtcOffset::UTC
     }
 
-    //todo(linux)
     fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
         Err(anyhow::Error::msg(
             "Platform<LinuxPlatform>::path_for_auxiliary_executable is not implemented yet",
@@ -549,7 +561,6 @@ impl CursorStyle {
             CursorStyle::ResizeUpDown => Shape::NsResize,
             CursorStyle::ResizeColumn => Shape::ColResize,
             CursorStyle::ResizeRow => Shape::RowResize,
-            CursorStyle::DisappearingItem => Shape::Grabbing, // todo(linux) - couldn't find equivalent icon in linux
             CursorStyle::IBeamCursorForVerticalLayout => Shape::VerticalText,
             CursorStyle::OperationNotAllowed => Shape::NotAllowed,
             CursorStyle::DragLink => Shape::Alias,
@@ -577,7 +588,6 @@ impl CursorStyle {
             CursorStyle::ResizeUpDown => "ns-resize",
             CursorStyle::ResizeColumn => "col-resize",
             CursorStyle::ResizeRow => "row-resize",
-            CursorStyle::DisappearingItem => "grabbing", // todo(linux) - couldn't find equivalent icon in linux
             CursorStyle::IBeamCursorForVerticalLayout => "vertical-text",
             CursorStyle::OperationNotAllowed => "not-allowed",
             CursorStyle::DragLink => "alias",

crates/gpui/src/platform/linux/wayland/client.rs 🔗

@@ -1,14 +1,11 @@
-use core::hash;
 use std::cell::{RefCell, RefMut};
 use std::ffi::OsString;
-use std::ops::{Deref, DerefMut};
+use std::hash::Hash;
 use std::os::fd::{AsRawFd, BorrowedFd};
 use std::path::PathBuf;
 use std::rc::{Rc, Weak};
-use std::sync::Arc;
 use std::time::{Duration, Instant};
 
-use async_task::Runnable;
 use calloop::timer::{TimeoutAction, Timer};
 use calloop::{EventLoop, LoopHandle};
 use calloop_wayland_source::WaylandSource;
@@ -16,7 +13,7 @@ use collections::HashMap;
 use copypasta::wayland_clipboard::{create_clipboards_from_external, Clipboard, Primary};
 use copypasta::ClipboardProvider;
 use filedescriptor::Pipe;
-use parking_lot::Mutex;
+
 use smallvec::SmallVec;
 use util::ResultExt;
 use wayland_backend::client::ObjectId;
@@ -26,9 +23,8 @@ use wayland_client::globals::{registry_queue_init, GlobalList, GlobalListContent
 use wayland_client::protocol::wl_callback::{self, WlCallback};
 use wayland_client::protocol::wl_data_device_manager::DndAction;
 use wayland_client::protocol::wl_pointer::AxisSource;
-use wayland_client::protocol::wl_seat::WlSeat;
 use wayland_client::protocol::{
-    wl_data_device, wl_data_device_manager, wl_data_offer, wl_data_source, wl_output, wl_region,
+    wl_data_device, wl_data_device_manager, wl_data_offer, wl_output, wl_region,
 };
 use wayland_client::{
     delegate_noop,
@@ -38,7 +34,6 @@ use wayland_client::{
     },
     Connection, Dispatch, Proxy, QueueHandle,
 };
-use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape;
 use wayland_protocols::wp::cursor_shape::v1::client::{
     wp_cursor_shape_device_v1, wp_cursor_shape_manager_v1,
 };
@@ -62,7 +57,8 @@ use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
 use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
 
 use super::super::{open_uri_internal, read_fd, DOUBLE_CLICK_INTERVAL};
-use super::window::{ImeInput, WaylandWindowState, WaylandWindowStatePtr};
+use super::display::WaylandDisplay;
+use super::window::{ImeInput, WaylandWindowStatePtr};
 use crate::platform::linux::is_within_click_distance;
 use crate::platform::linux::wayland::cursor::Cursor;
 use crate::platform::linux::wayland::serial::{SerialKind, SerialTracker};
@@ -71,7 +67,7 @@ use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSour
 use crate::platform::linux::LinuxClient;
 use crate::platform::PlatformWindow;
 use crate::{
-    point, px, Bounds, FileDropEvent, ForegroundExecutor, MouseExitEvent, WindowAppearance,
+    point, px, size, Bounds, DevicePixels, FileDropEvent, ForegroundExecutor, MouseExitEvent, Size,
     SCROLL_LINES,
 };
 use crate::{
@@ -143,11 +139,49 @@ impl Globals {
     }
 }
 
+#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct InProgressOutput {
+    scale: Option<i32>,
+    position: Option<Point<DevicePixels>>,
+    size: Option<Size<DevicePixels>>,
+}
+
+impl InProgressOutput {
+    fn complete(&self) -> Option<Output> {
+        if let Some((position, size)) = self.position.zip(self.size) {
+            let scale = self.scale.unwrap_or(1);
+            Some(Output {
+                scale,
+                bounds: Bounds::new(position, size),
+            })
+        } else {
+            None
+        }
+    }
+}
+
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+pub struct Output {
+    pub scale: i32,
+    pub bounds: Bounds<DevicePixels>,
+}
+
+impl Hash for Output {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        state.write_i32(self.scale);
+        state.write_i32(self.bounds.origin.x.0);
+        state.write_i32(self.bounds.origin.y.0);
+        state.write_i32(self.bounds.size.width.0);
+        state.write_i32(self.bounds.size.height.0);
+    }
+}
+
 pub(crate) struct WaylandClientState {
     serial_tracker: SerialTracker,
     globals: Globals,
-    wl_seat: wl_seat::WlSeat, // todo(linux): multi-seat support
+    wl_seat: wl_seat::WlSeat, // TODO: Multi seat support
     wl_pointer: Option<wl_pointer::WlPointer>,
+    wl_keyboard: Option<wl_keyboard::WlKeyboard>,
     cursor_shape_device: Option<wp_cursor_shape_device_v1::WpCursorShapeDeviceV1>,
     data_device: Option<wl_data_device::WlDataDevice>,
     text_input: Option<zwp_text_input_v3::ZwpTextInputV3>,
@@ -156,15 +190,16 @@ pub(crate) struct WaylandClientState {
     // Surface to Window mapping
     windows: HashMap<ObjectId, WaylandWindowStatePtr>,
     // Output to scale mapping
-    output_scales: HashMap<ObjectId, i32>,
+    outputs: HashMap<ObjectId, Output>,
+    in_progress_outputs: HashMap<ObjectId, InProgressOutput>,
     keymap_state: Option<xkb::State>,
     compose_state: Option<xkb::compose::State>,
     drag: DragState,
     click: ClickState,
     repeat: KeyRepeat,
-    modifiers: Modifiers,
+    pub modifiers: Modifiers,
     axis_source: AxisSource,
-    mouse_location: Option<Point<Pixels>>,
+    pub mouse_location: Option<Point<Pixels>>,
     continuous_scroll_delta: Option<Point<Pixels>>,
     discrete_scroll_delta: Option<Point<f32>>,
     vertical_modifier: f32,
@@ -210,7 +245,7 @@ pub(crate) struct KeyRepeat {
 pub struct WaylandClientStatePtr(Weak<RefCell<WaylandClientState>>);
 
 impl WaylandClientStatePtr {
-    fn get_client(&self) -> Rc<RefCell<WaylandClientState>> {
+    pub fn get_client(&self) -> Rc<RefCell<WaylandClientState>> {
         self.0
             .upgrade()
             .expect("The pointer should always be valid when dispatching in wayland")
@@ -328,7 +363,7 @@ impl WaylandClient {
         let qh = event_queue.handle();
 
         let mut seat: Option<wl_seat::WlSeat> = None;
-        let mut outputs = HashMap::default();
+        let mut in_progress_outputs = HashMap::default();
         globals.contents().with_list(|list| {
             for global in list {
                 match &global.interface[..] {
@@ -347,7 +382,7 @@ impl WaylandClient {
                             &qh,
                             (),
                         );
-                        outputs.insert(output.id(), 1);
+                        in_progress_outputs.insert(output.id(), InProgressOutput::default());
                     }
                     _ => {}
                 }
@@ -361,11 +396,13 @@ impl WaylandClient {
         let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
 
         let handle = event_loop.handle();
-        handle.insert_source(main_receiver, |event, _, _: &mut WaylandClientStatePtr| {
-            if let calloop::channel::Event::Msg(runnable) = event {
-                runnable.run();
-            }
-        });
+        handle
+            .insert_source(main_receiver, |event, _, _: &mut WaylandClientStatePtr| {
+                if let calloop::channel::Event::Msg(runnable) = event {
+                    runnable.run();
+                }
+            })
+            .unwrap();
 
         let seat = seat.unwrap();
         let globals = Globals::new(
@@ -384,33 +421,37 @@ impl WaylandClient {
 
         let cursor = Cursor::new(&conn, &globals, 24);
 
-        handle.insert_source(XDPEventSource::new(&common.background_executor), {
-            move |event, _, client| match event {
-                XDPEvent::WindowAppearance(appearance) => {
-                    if let Some(client) = client.0.upgrade() {
-                        let mut client = client.borrow_mut();
+        handle
+            .insert_source(XDPEventSource::new(&common.background_executor), {
+                move |event, _, client| match event {
+                    XDPEvent::WindowAppearance(appearance) => {
+                        if let Some(client) = client.0.upgrade() {
+                            let mut client = client.borrow_mut();
 
-                        client.common.appearance = appearance;
+                            client.common.appearance = appearance;
 
-                        for (_, window) in &mut client.windows {
-                            window.set_appearance(appearance);
+                            for (_, window) in &mut client.windows {
+                                window.set_appearance(appearance);
+                            }
                         }
                     }
                 }
-            }
-        });
+            })
+            .unwrap();
 
         let mut state = Rc::new(RefCell::new(WaylandClientState {
             serial_tracker: SerialTracker::new(),
             globals,
             wl_seat: seat,
             wl_pointer: None,
+            wl_keyboard: None,
             cursor_shape_device: None,
             data_device,
             text_input: None,
             pre_edit_text: None,
             composing: false,
-            output_scales: outputs,
+            outputs: HashMap::default(),
+            in_progress_outputs,
             windows: HashMap::default(),
             common,
             keymap_state: None,
@@ -459,7 +500,9 @@ impl WaylandClient {
             pending_open_uri: None,
         }));
 
-        WaylandSource::new(conn, event_queue).insert(handle);
+        WaylandSource::new(conn, event_queue)
+            .insert(handle)
+            .unwrap();
 
         Self(state)
     }
@@ -467,11 +510,32 @@ impl WaylandClient {
 
 impl LinuxClient for WaylandClient {
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
-        Vec::new()
+        self.0
+            .borrow()
+            .outputs
+            .iter()
+            .map(|(id, output)| {
+                Rc::new(WaylandDisplay {
+                    id: id.clone(),
+                    bounds: output.bounds,
+                }) as Rc<dyn PlatformDisplay>
+            })
+            .collect()
     }
 
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
-        unimplemented!()
+        self.0
+            .borrow()
+            .outputs
+            .iter()
+            .find_map(|(object_id, output)| {
+                (object_id.protocol_id() == id.0).then(|| {
+                    Rc::new(WaylandDisplay {
+                        id: object_id.clone(),
+                        bounds: output.bounds,
+                    }) as Rc<dyn PlatformDisplay>
+                })
+            })
     }
 
     fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
@@ -486,6 +550,7 @@ impl LinuxClient for WaylandClient {
         let mut state = self.0.borrow_mut();
 
         let (window, surface_id) = WaylandWindow::new(
+            handle,
             state.globals.clone(),
             WaylandClientStatePtr(Rc::downgrade(&self.0)),
             params,
@@ -566,7 +631,8 @@ impl LinuxClient for WaylandClient {
             .primary
             .as_mut()
             .unwrap()
-            .set_contents(item.text);
+            .set_contents(item.text)
+            .ok();
     }
 
     fn write_to_clipboard(&self, item: crate::ClipboardItem) {
@@ -575,7 +641,8 @@ impl LinuxClient for WaylandClient {
             .clipboard
             .as_mut()
             .unwrap()
-            .set_contents(item.text);
+            .set_contents(item.text)
+            .ok();
     }
 
     fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
@@ -605,6 +672,14 @@ impl LinuxClient for WaylandClient {
                 metadata: None,
             })
     }
+
+    fn active_window(&self) -> Option<AnyWindowHandle> {
+        self.0
+            .borrow_mut()
+            .keyboard_focused_window
+            .as_ref()
+            .map(|window| window.handle())
+    }
 }
 
 impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStatePtr {
@@ -626,18 +701,33 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
                 version,
             } => match &interface[..] {
                 "wl_seat" => {
-                    state.wl_pointer = None;
-                    registry.bind::<wl_seat::WlSeat, _, _>(name, wl_seat_version(version), qh, ());
+                    if let Some(wl_pointer) = state.wl_pointer.take() {
+                        wl_pointer.release();
+                    }
+                    if let Some(wl_keyboard) = state.wl_keyboard.take() {
+                        wl_keyboard.release();
+                    }
+                    state.wl_seat.release();
+                    state.wl_seat = registry.bind::<wl_seat::WlSeat, _, _>(
+                        name,
+                        wl_seat_version(version),
+                        qh,
+                        (),
+                    );
                 }
                 "wl_output" => {
                     let output =
                         registry.bind::<wl_output::WlOutput, _, _>(name, WL_OUTPUT_VERSION, qh, ());
 
-                    state.output_scales.insert(output.id(), 1);
+                    state
+                        .in_progress_outputs
+                        .insert(output.id(), InProgressOutput::default());
                 }
                 _ => {}
             },
-            wl_registry::Event::GlobalRemove { name: _ } => {}
+            wl_registry::Event::GlobalRemove { name: _ } => {
+                // TODO: handle global removal
+            }
             _ => {}
         }
     }
@@ -667,7 +757,7 @@ impl Dispatch<WlCallback, ObjectId> for WaylandClientStatePtr {
         event: wl_callback::Event,
         surface_id: &ObjectId,
         _: &Connection,
-        qh: &QueueHandle<Self>,
+        _: &QueueHandle<Self>,
     ) {
         let client = state.get_client();
         let mut state = client.borrow_mut();
@@ -677,7 +767,7 @@ impl Dispatch<WlCallback, ObjectId> for WaylandClientStatePtr {
         drop(state);
 
         match event {
-            wl_callback::Event::Done { callback_data } => {
+            wl_callback::Event::Done { .. } => {
                 window.frame(true);
             }
             _ => {}
@@ -707,10 +797,10 @@ impl Dispatch<wl_surface::WlSurface, ()> for WaylandClientStatePtr {
         let Some(window) = get_window(&mut state, &surface.id()) else {
             return;
         };
-        let scales = state.output_scales.clone();
+        let outputs = state.outputs.clone();
         drop(state);
 
-        window.handle_surface_event(event, scales);
+        window.handle_surface_event(event, outputs);
     }
 }
 
@@ -726,13 +816,25 @@ impl Dispatch<wl_output::WlOutput, ()> for WaylandClientStatePtr {
         let mut client = this.get_client();
         let mut state = client.borrow_mut();
 
-        let Some(mut output_scale) = state.output_scales.get_mut(&output.id()) else {
+        let Some(mut in_progress_output) = state.in_progress_outputs.get_mut(&output.id()) else {
             return;
         };
 
         match event {
             wl_output::Event::Scale { factor } => {
-                *output_scale = factor;
+                in_progress_output.scale = Some(factor);
+            }
+            wl_output::Event::Geometry { x, y, .. } => {
+                in_progress_output.position = Some(point(DevicePixels(x), DevicePixels(y)))
+            }
+            wl_output::Event::Mode { width, height, .. } => {
+                in_progress_output.size = Some(size(DevicePixels(width), DevicePixels(height)))
+            }
+            wl_output::Event::Done => {
+                if let Some(complete) = in_progress_output.complete() {
+                    state.outputs.insert(output.id(), complete);
+                }
+                state.in_progress_outputs.remove(&output.id());
             }
             _ => {}
         }
@@ -742,7 +844,7 @@ impl Dispatch<wl_output::WlOutput, ()> for WaylandClientStatePtr {
 impl Dispatch<xdg_surface::XdgSurface, ObjectId> for WaylandClientStatePtr {
     fn event(
         state: &mut Self,
-        xdg_surface: &xdg_surface::XdgSurface,
+        _: &xdg_surface::XdgSurface,
         event: xdg_surface::Event,
         surface_id: &ObjectId,
         _: &Connection,
@@ -761,7 +863,7 @@ impl Dispatch<xdg_surface::XdgSurface, ObjectId> for WaylandClientStatePtr {
 impl Dispatch<xdg_toplevel::XdgToplevel, ObjectId> for WaylandClientStatePtr {
     fn event(
         this: &mut Self,
-        xdg_toplevel: &xdg_toplevel::XdgToplevel,
+        _: &xdg_toplevel::XdgToplevel,
         event: <xdg_toplevel::XdgToplevel as Proxy>::Event,
         surface_id: &ObjectId,
         _: &Connection,
@@ -824,8 +926,8 @@ impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr {
         state: &mut Self,
         seat: &wl_seat::WlSeat,
         event: wl_seat::Event,
-        data: &(),
-        conn: &Connection,
+        _: &(),
+        _: &Connection,
         qh: &QueueHandle<Self>,
     ) {
         if let wl_seat::Event::Capabilities {
@@ -835,12 +937,19 @@ impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr {
             let client = state.get_client();
             let mut state = client.borrow_mut();
             if capabilities.contains(wl_seat::Capability::Keyboard) {
-                seat.get_keyboard(qh, ());
+                let keyboard = seat.get_keyboard(qh, ());
+
                 state.text_input = state
                     .globals
                     .text_input_manager
                     .as_ref()
                     .map(|text_input_manager| text_input_manager.get_text_input(&seat, qh, ()));
+
+                if let Some(wl_keyboard) = &state.wl_keyboard {
+                    wl_keyboard.release();
+                }
+
+                state.wl_keyboard = Some(keyboard);
             }
             if capabilities.contains(wl_seat::Capability::Pointer) {
                 let pointer = seat.get_pointer(qh, ());
@@ -849,6 +958,11 @@ impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr {
                     .cursor_shape_manager
                     .as_ref()
                     .map(|cursor_shape_manager| cursor_shape_manager.get_pointer(&pointer, qh, ()));
+
+                if let Some(wl_pointer) = &state.wl_pointer {
+                    wl_pointer.release();
+                }
+
                 state.wl_pointer = Some(pointer);
             }
         }
@@ -858,11 +972,11 @@ impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr {
 impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
     fn event(
         this: &mut Self,
-        keyboard: &wl_keyboard::WlKeyboard,
+        _: &wl_keyboard::WlKeyboard,
         event: wl_keyboard::Event,
-        data: &(),
-        conn: &Connection,
-        qh: &QueueHandle<Self>,
+        _: &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
     ) {
         let mut client = this.get_client();
         let mut state = client.borrow_mut();
@@ -1018,8 +1132,8 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
                             state.compose_state = Some(compose);
                         }
                         let input = PlatformInput::KeyDown(KeyDownEvent {
-                            keystroke: keystroke,
-                            is_held: false, // todo(linux)
+                            keystroke: keystroke.clone(),
+                            is_held: false,
                         });
 
                         state.repeat.current_id += 1;
@@ -1030,8 +1144,11 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
                         state
                             .loop_handle
                             .insert_source(Timer::from_duration(state.repeat.delay), {
-                                let input = input.clone();
-                                move |event, _metadata, this| {
+                                let input = PlatformInput::KeyDown(KeyDownEvent {
+                                    keystroke,
+                                    is_held: true,
+                                });
+                                move |_event, _metadata, this| {
                                     let mut client = this.get_client();
                                     let mut state = client.borrow_mut();
                                     let is_repeating = id == state.repeat.current_id
@@ -1080,18 +1197,18 @@ impl Dispatch<zwp_text_input_v3::ZwpTextInputV3, ()> for WaylandClientStatePtr {
         this: &mut Self,
         text_input: &zwp_text_input_v3::ZwpTextInputV3,
         event: <zwp_text_input_v3::ZwpTextInputV3 as Proxy>::Event,
-        data: &(),
-        conn: &Connection,
-        qhandle: &QueueHandle<Self>,
+        _: &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
     ) {
         let client = this.get_client();
         let mut state = client.borrow_mut();
         match event {
-            zwp_text_input_v3::Event::Enter { surface } => {
+            zwp_text_input_v3::Event::Enter { .. } => {
                 drop(state);
                 this.enable_ime();
             }
-            zwp_text_input_v3::Event::Leave { surface } => {
+            zwp_text_input_v3::Event::Leave { .. } => {
                 drop(state);
                 this.disable_ime();
             }
@@ -1119,11 +1236,7 @@ impl Dispatch<zwp_text_input_v3::ZwpTextInputV3, ()> for WaylandClientStatePtr {
                     }
                 }
             }
-            zwp_text_input_v3::Event::PreeditString {
-                text,
-                cursor_begin,
-                cursor_end,
-            } => {
+            zwp_text_input_v3::Event::PreeditString { text, .. } => {
                 state.composing = true;
                 state.pre_edit_text = text;
             }
@@ -1183,9 +1296,9 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
         this: &mut Self,
         wl_pointer: &wl_pointer::WlPointer,
         event: wl_pointer::Event,
-        data: &(),
-        conn: &Connection,
-        qh: &QueueHandle<Self>,
+        _: &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
     ) {
         let mut client = this.get_client();
         let mut state = client.borrow_mut();
@@ -1220,7 +1333,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
                     window.set_focused(true);
                 }
             }
-            wl_pointer::Event::Leave { surface, .. } => {
+            wl_pointer::Event::Leave { .. } => {
                 if let Some(focused_window) = state.mouse_focused_window.clone() {
                     let input = PlatformInput::MouseExited(MouseExitEvent {
                         position: state.mouse_location.unwrap(),
@@ -1237,7 +1350,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
                 }
             }
             wl_pointer::Event::Motion {
-                time,
                 surface_x,
                 surface_y,
                 ..
@@ -1280,7 +1392,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
                     wl_pointer::ButtonState::Pressed => {
                         if let Some(window) = state.keyboard_focused_window.clone() {
                             if state.composing && state.text_input.is_some() {
-                                let text_input = state.text_input.as_ref().unwrap();
                                 drop(state);
                                 // text_input_v3 don't have something like a reset function
                                 this.disable_ime();
@@ -1351,7 +1462,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
                 state.axis_source = axis_source;
             }
             wl_pointer::Event::Axis {
-                time,
                 axis: WEnum::Value(axis),
                 value,
                 ..
@@ -1364,13 +1474,10 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
                     wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
                     _ => 1.0,
                 };
-                let supports_relative_direction =
-                    wl_pointer.version() >= wl_pointer::EVT_AXIS_RELATIVE_DIRECTION_SINCE;
                 state.scroll_event_received = true;
                 let scroll_delta = state
                     .continuous_scroll_delta
                     .get_or_insert(point(px(0.0), px(0.0)));
-                // TODO: Make nice feeling kinetic scrolling that integrates with the platform's scroll settings
                 let modifier = 3.0;
                 match axis {
                     wl_pointer::Axis::VerticalScroll => {

crates/gpui/src/platform/linux/wayland/display.rs 🔗

@@ -1,31 +1,36 @@
-use std::fmt::Debug;
+use std::{
+    fmt::Debug,
+    hash::{Hash, Hasher},
+};
 
 use uuid::Uuid;
+use wayland_backend::client::ObjectId;
 
-use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Size};
+use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay};
 
-#[derive(Debug)]
-pub(crate) struct WaylandDisplay {}
+#[derive(Debug, Clone)]
+pub(crate) struct WaylandDisplay {
+    /// The ID of the wl_output object
+    pub id: ObjectId,
+    pub bounds: Bounds<DevicePixels>,
+}
+
+impl Hash for WaylandDisplay {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.id.hash(state);
+    }
+}
 
 impl PlatformDisplay for WaylandDisplay {
-    // todo(linux)
     fn id(&self) -> DisplayId {
-        DisplayId(123) // return some fake data so it doesn't panic
+        DisplayId(self.id.protocol_id())
     }
 
-    // todo(linux)
     fn uuid(&self) -> anyhow::Result<Uuid> {
-        Ok(Uuid::from_bytes([0; 16])) // return some fake data so it doesn't panic
+        Err(anyhow::anyhow!("Display UUID is not supported on Wayland"))
     }
 
-    // todo(linux)
     fn bounds(&self) -> Bounds<DevicePixels> {
-        Bounds {
-            origin: Default::default(),
-            size: Size {
-                width: DevicePixels(1000),
-                height: DevicePixels(500),
-            },
-        } // return some fake data so it doesn't panic
+        self.bounds
     }
 }

crates/gpui/src/platform/linux/wayland/serial.rs 🔗

@@ -1,5 +1,3 @@
-use std::time::Instant;
-
 use collections::HashMap;
 
 #[derive(Debug, Hash, PartialEq, Eq)]
@@ -14,15 +12,11 @@ pub(crate) enum SerialKind {
 #[derive(Debug)]
 struct SerialData {
     serial: u32,
-    time: Instant,
 }
 
 impl SerialData {
     fn new(value: u32) -> Self {
-        Self {
-            serial: value,
-            time: Instant::now(),
-        }
+        Self { serial: value }
     }
 }
 
@@ -52,41 +46,4 @@ impl SerialTracker {
             .map(|serial_data| serial_data.serial)
             .unwrap_or(0)
     }
-
-    /// Returns the newest serial of any of the provided [`SerialKind`]
-    pub fn get_newest_of(&self, kinds: &[SerialKind]) -> u32 {
-        kinds
-            .iter()
-            .filter_map(|kind| self.serials.get(&kind))
-            .max_by_key(|serial_data| serial_data.time)
-            .map(|serial_data| serial_data.serial)
-            .unwrap_or(0)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_serial_tracker() {
-        let mut tracker = SerialTracker::new();
-
-        tracker.update(SerialKind::KeyPress, 100);
-        tracker.update(SerialKind::MousePress, 50);
-        tracker.update(SerialKind::MouseEnter, 300);
-
-        assert_eq!(
-            tracker.get_newest_of(&[SerialKind::KeyPress, SerialKind::MousePress]),
-            50
-        );
-        assert_eq!(tracker.get(SerialKind::DataDevice), 0);
-
-        tracker.update(SerialKind::KeyPress, 2000);
-        assert_eq!(tracker.get(SerialKind::KeyPress), 2000);
-        assert_eq!(
-            tracker.get_newest_of(&[SerialKind::KeyPress, SerialKind::MousePress]),
-            2000
-        );
-    }
 }

crates/gpui/src/platform/linux/wayland/window.rs 🔗

@@ -1,27 +1,24 @@
-use std::any::Any;
 use std::cell::{Ref, RefCell, RefMut};
 use std::ffi::c_void;
 use std::num::NonZeroU32;
-use std::ops::{Deref, Range};
 use std::ptr::NonNull;
-use std::rc::{Rc, Weak};
+use std::rc::Rc;
 use std::sync::Arc;
 
 use blade_graphics as gpu;
-use collections::{HashMap, HashSet};
+use collections::HashMap;
 use futures::channel::oneshot::Receiver;
-use parking_lot::Mutex;
+
 use raw_window_handle as rwh;
 use wayland_backend::client::ObjectId;
-use wayland_client::protocol::wl_region::WlRegion;
 use wayland_client::WEnum;
 use wayland_client::{protocol::wl_surface, Proxy};
 use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1;
 use wayland_protocols::wp::viewporter::client::wp_viewport;
 use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1;
 use wayland_protocols::xdg::shell::client::xdg_surface;
-use wayland_protocols::xdg::shell::client::xdg_toplevel::{self, WmCapabilities};
-use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blur_manager};
+use wayland_protocols::xdg::shell::client::xdg_toplevel::{self};
+use wayland_protocols_plasma::blur::client::org_kde_kwin_blur;
 
 use crate::platform::blade::{BladeRenderer, BladeSurfaceConfig};
 use crate::platform::linux::wayland::display::WaylandDisplay;
@@ -29,9 +26,9 @@ use crate::platform::linux::wayland::serial::SerialKind;
 use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
 use crate::scene::Scene;
 use crate::{
-    px, size, Bounds, DevicePixels, Globals, Modifiers, Pixels, PlatformDisplay, PlatformInput,
-    Point, PromptLevel, Size, WaylandClientStatePtr, WindowAppearance, WindowBackgroundAppearance,
-    WindowBounds, WindowParams,
+    px, size, AnyWindowHandle, Bounds, DevicePixels, Globals, Modifiers, Output, Pixels,
+    PlatformDisplay, PlatformInput, Point, PromptLevel, Size, WaylandClientStatePtr,
+    WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowParams,
 };
 
 #[derive(Default)]
@@ -75,7 +72,8 @@ pub struct WaylandWindowState {
     blur: Option<org_kde_kwin_blur::OrgKdeKwinBlur>,
     toplevel: xdg_toplevel::XdgToplevel,
     viewport: Option<wp_viewport::WpViewport>,
-    outputs: HashSet<ObjectId>,
+    outputs: HashMap<ObjectId, Output>,
+    display: Option<(ObjectId, Output)>,
     globals: Globals,
     renderer: BladeRenderer,
     bounds: Bounds<u32>,
@@ -86,7 +84,8 @@ pub struct WaylandWindowState {
     restore_bounds: Bounds<DevicePixels>,
     maximized: bool,
     client: WaylandClientStatePtr,
-    callbacks: Callbacks,
+    handle: AnyWindowHandle,
+    active: bool,
 }
 
 #[derive(Clone)]
@@ -98,6 +97,7 @@ pub struct WaylandWindowStatePtr {
 impl WaylandWindowState {
     #[allow(clippy::too_many_arguments)]
     pub(crate) fn new(
+        handle: AnyWindowHandle,
         surface: wl_surface::WlSurface,
         xdg_surface: xdg_surface::XdgSurface,
         toplevel: xdg_toplevel::XdgToplevel,
@@ -150,7 +150,8 @@ impl WaylandWindowState {
             toplevel,
             viewport,
             globals,
-            outputs: HashSet::default(),
+            outputs: HashMap::default(),
+            display: None,
             renderer: BladeRenderer::new(gpu, config),
             bounds,
             scale: 1.0,
@@ -159,9 +160,10 @@ impl WaylandWindowState {
             fullscreen: false,
             restore_bounds: Bounds::default(),
             maximized: false,
-            callbacks: Callbacks::default(),
             client,
             appearance,
+            handle,
+            active: false,
         }
     }
 }
@@ -217,6 +219,7 @@ impl WaylandWindow {
     }
 
     pub fn new(
+        handle: AnyWindowHandle,
         globals: Globals,
         client: WaylandClientStatePtr,
         params: WindowParams,
@@ -253,6 +256,7 @@ impl WaylandWindow {
 
         let this = Self(WaylandWindowStatePtr {
             state: Rc::new(RefCell::new(WaylandWindowState::new(
+                handle,
                 surface.clone(),
                 xdg_surface,
                 toplevel,
@@ -274,6 +278,10 @@ impl WaylandWindow {
 }
 
 impl WaylandWindowStatePtr {
+    pub fn handle(&self) -> AnyWindowHandle {
+        self.state.borrow().handle
+    }
+
     pub fn surface(&self) -> wl_surface::WlSurface {
         self.state.borrow().surface.clone()
     }
@@ -381,7 +389,7 @@ impl WaylandWindowStatePtr {
     pub fn handle_surface_event(
         &self,
         event: wl_surface::Event,
-        output_scales: HashMap<ObjectId, i32>,
+        outputs: HashMap<ObjectId, Output>,
     ) {
         let mut state = self.state.borrow_mut();
 
@@ -396,15 +404,15 @@ impl WaylandWindowStatePtr {
                 if state.surface.version() >= wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
                     return;
                 }
+                let id = output.id();
+
+                let Some(output) = outputs.get(&id) else {
+                    return;
+                };
 
-                state.outputs.insert(output.id());
+                state.outputs.insert(id, *output);
 
-                let mut scale = 1;
-                for output in state.outputs.iter() {
-                    if let Some(s) = output_scales.get(output) {
-                        scale = scale.max(*s)
-                    }
-                }
+                let scale = primary_output_scale(&mut state);
 
                 state.surface.set_buffer_scale(scale);
                 drop(state);
@@ -418,12 +426,7 @@ impl WaylandWindowStatePtr {
 
                 state.outputs.remove(&output.id());
 
-                let mut scale = 1;
-                for output in state.outputs.iter() {
-                    if let Some(s) = output_scales.get(output) {
-                        scale = scale.max(*s)
-                    }
-                }
+                let scale = primary_output_scale(&mut state);
 
                 state.surface.set_buffer_scale(scale);
                 drop(state);
@@ -577,6 +580,7 @@ impl WaylandWindowStatePtr {
     }
 
     pub fn set_focused(&self, focus: bool) {
+        self.state.borrow_mut().active = focus;
         if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
             fun(focus);
         }
@@ -592,6 +596,23 @@ impl WaylandWindowStatePtr {
     }
 }
 
+fn primary_output_scale(state: &mut RefMut<WaylandWindowState>) -> i32 {
+    let mut scale = 1;
+    let mut current_output = state.display.take();
+    for (id, output) in state.outputs.iter() {
+        if let Some((_, output_data)) = &current_output {
+            if output.scale > output_data.scale {
+                current_output = Some((id.clone(), *output));
+            }
+        } else {
+            current_output = Some((id.clone(), *output));
+        }
+        scale = scale.max(output.scale);
+    }
+    state.display = current_output;
+    scale
+}
+
 impl rwh::HasWindowHandle for WaylandWindow {
     fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
         unimplemented!()
@@ -612,8 +633,6 @@ impl PlatformWindow for WaylandWindow {
         self.borrow().maximized
     }
 
-    // todo(linux)
-    // check if it is right
     fn window_bounds(&self) -> WindowBounds {
         let state = self.borrow();
         if state.fullscreen {
@@ -641,19 +660,26 @@ impl PlatformWindow for WaylandWindow {
         self.borrow().appearance
     }
 
-    // todo(linux)
-    fn display(&self) -> Rc<dyn PlatformDisplay> {
-        Rc::new(WaylandDisplay {})
+    fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
+        self.borrow().display.as_ref().map(|(id, display)| {
+            Rc::new(WaylandDisplay {
+                id: id.clone(),
+                bounds: display.bounds,
+            }) as Rc<dyn PlatformDisplay>
+        })
     }
 
-    // todo(linux)
     fn mouse_position(&self) -> Point<Pixels> {
-        Point::default()
+        self.borrow()
+            .client
+            .get_client()
+            .borrow()
+            .mouse_location
+            .unwrap_or_default()
     }
 
-    // todo(linux)
     fn modifiers(&self) -> Modifiers {
-        crate::Modifiers::default()
+        self.borrow().client.get_client().borrow().modifiers
     }
 
     fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
@@ -666,21 +692,20 @@ impl PlatformWindow for WaylandWindow {
 
     fn prompt(
         &self,
-        level: PromptLevel,
-        msg: &str,
-        detail: Option<&str>,
-        answers: &[&str],
+        _level: PromptLevel,
+        _msg: &str,
+        _detail: Option<&str>,
+        _answers: &[&str],
     ) -> Option<Receiver<usize>> {
         None
     }
 
     fn activate(&self) {
-        // todo(linux)
+        log::info!("Wayland does not support this API");
     }
 
-    // todo(linux)
     fn is_active(&self) -> bool {
-        false
+        self.borrow().active
     }
 
     fn set_title(&mut self, title: &str) {
@@ -712,8 +737,8 @@ impl PlatformWindow for WaylandWindow {
         }
 
         if let Some(ref blur_manager) = state.globals.blur_manager {
-            if (background_appearance == WindowBackgroundAppearance::Blurred) {
-                if (state.blur.is_none()) {
+            if background_appearance == WindowBackgroundAppearance::Blurred {
+                if state.blur.is_none() {
                     let blur = blur_manager.create(&state.surface, &state.globals.qh, ());
                     blur.set_region(Some(&region));
                     state.blur = Some(blur);
@@ -731,12 +756,12 @@ impl PlatformWindow for WaylandWindow {
         region.destroy();
     }
 
-    fn set_edited(&mut self, edited: bool) {
-        // todo(linux)
+    fn set_edited(&mut self, _edited: bool) {
+        log::info!("ignoring macOS specific set_edited");
     }
 
     fn show_character_palette(&self) {
-        // todo(linux)
+        log::info!("ignoring macOS specific show_character_palette");
     }
 
     fn minimize(&self) {

crates/gpui/src/platform/linux/x11/client.rs 🔗

@@ -2,7 +2,6 @@ use std::cell::RefCell;
 use std::ffi::OsString;
 use std::ops::Deref;
 use std::rc::{Rc, Weak};
-use std::sync::OnceLock;
 use std::time::{Duration, Instant};
 
 use calloop::generic::{FdWrapper, Generic};
@@ -11,30 +10,29 @@ use calloop::{channel, EventLoop, LoopHandle, RegistrationToken};
 use collections::HashMap;
 use copypasta::x11_clipboard::{Clipboard, Primary, X11ClipboardContext};
 use copypasta::ClipboardProvider;
-use parking_lot::Mutex;
 
 use util::ResultExt;
 use x11rb::connection::{Connection, RequestConnection};
 use x11rb::cursor;
 use x11rb::errors::ConnectionError;
 use x11rb::protocol::randr::ConnectionExt as _;
-use x11rb::protocol::xinput::{ConnectionExt, ScrollClass};
+use x11rb::protocol::xinput::ConnectionExt;
 use x11rb::protocol::xkb::ConnectionExt as _;
 use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _};
 use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
 use x11rb::resource_manager::Database;
 use x11rb::xcb_ffi::XCBConnection;
 use xim::{x11rb::X11rbClient, Client};
-use xim::{AHashMap, AttributeName, ClientHandler, InputStyle};
+use xim::{AttributeName, InputStyle};
 use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
 use xkbcommon::xkb as xkbc;
 
 use crate::platform::linux::LinuxClient;
-use crate::platform::{LinuxCommon, PlatformWindow, WaylandClientState};
+use crate::platform::{LinuxCommon, PlatformWindow};
 use crate::{
     modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId,
-    ForegroundExecutor, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay,
-    PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowAppearance, WindowParams, X11Window,
+    Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point,
+    ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
 };
 
 use super::{
@@ -54,6 +52,12 @@ pub(crate) struct WindowRef {
     refresh_event_token: RegistrationToken,
 }
 
+impl WindowRef {
+    pub fn handle(&self) -> AnyWindowHandle {
+        self.window.state.borrow().handle
+    }
+}
+
 impl Deref for WindowRef {
     type Target = X11WindowStatePtr;
 
@@ -104,13 +108,14 @@ pub struct X11ClientState {
 
     pub(crate) xcb_connection: Rc<XCBConnection>,
     pub(crate) x_root_index: usize,
-    pub(crate) resource_database: Database,
+    pub(crate) _resource_database: Database,
     pub(crate) atoms: XcbAtoms,
     pub(crate) windows: HashMap<xproto::Window, WindowRef>,
     pub(crate) focused_window: Option<xproto::Window>,
     pub(crate) xkb: xkbc::State,
     pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>,
     pub(crate) xim_handler: Option<XimHandler>,
+    pub modifiers: Modifiers,
 
     pub(crate) compose_state: xkbc::compose::State,
     pub(crate) pre_edit_text: Option<String>,
@@ -159,11 +164,13 @@ impl X11Client {
 
         let handle = event_loop.handle();
 
-        handle.insert_source(main_receiver, |event, _, _: &mut X11Client| {
-            if let calloop::channel::Event::Msg(runnable) = event {
-                runnable.run();
-            }
-        });
+        handle
+            .insert_source(main_receiver, |event, _, _: &mut X11Client| {
+                if let calloop::channel::Event::Msg(runnable) = event {
+                    runnable.run();
+                }
+            })
+            .unwrap();
 
         let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap();
         xcb_connection
@@ -248,23 +255,7 @@ impl X11Client {
             xkbc::compose::State::new(&table, xkbc::compose::STATE_NO_FLAGS)
         };
 
-        let screen = xcb_connection.setup().roots.get(x_root_index).unwrap();
-
-        // Values from `Database::GET_RESOURCE_DATABASE`
-        let resource_manager = xcb_connection
-            .get_property(
-                false,
-                screen.root,
-                xproto::AtomEnum::RESOURCE_MANAGER,
-                xproto::AtomEnum::STRING,
-                0,
-                100_000_000,
-            )
-            .unwrap();
-        let resource_manager = resource_manager.reply().unwrap();
-
-        // todo(linux): read hostname
-        let resource_database = Database::new_from_default(&resource_manager, "HOSTNAME".into());
+        let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap();
 
         let scale_factor = resource_database
             .get_value("Xft.dpi", "Xft.dpi")
@@ -345,7 +336,7 @@ impl X11Client {
             .insert_source(xim_rx, {
                 move |chan_event, _, client| match chan_event {
                     channel::Event::Msg(xim_event) => {
-                        match (xim_event) {
+                        match xim_event {
                             XimCallbackEvent::XimXEvent(event) => {
                                 client.handle_event(event);
                             }
@@ -363,18 +354,21 @@ impl X11Client {
                 }
             })
             .expect("Failed to initialize XIM event source");
-        handle.insert_source(XDPEventSource::new(&common.background_executor), {
-            move |event, _, client| match event {
-                XDPEvent::WindowAppearance(appearance) => {
-                    client.with_common(|common| common.appearance = appearance);
-                    for (_, window) in &mut client.0.borrow_mut().windows {
-                        window.window.set_appearance(appearance);
+        handle
+            .insert_source(XDPEventSource::new(&common.background_executor), {
+                move |event, _, client| match event {
+                    XDPEvent::WindowAppearance(appearance) => {
+                        client.with_common(|common| common.appearance = appearance);
+                        for (_, window) in &mut client.0.borrow_mut().windows {
+                            window.window.set_appearance(appearance);
+                        }
                     }
                 }
-            }
-        });
+            })
+            .unwrap();
 
         X11Client(Rc::new(RefCell::new(X11ClientState {
+            modifiers: Modifiers::default(),
             event_loop: Some(event_loop),
             loop_handle: handle,
             common,
@@ -385,7 +379,7 @@ impl X11Client {
 
             xcb_connection,
             x_root_index,
-            resource_database,
+            _resource_database: resource_database,
             atoms,
             windows: HashMap::default(),
             focused_window: None,
@@ -446,7 +440,8 @@ impl X11Client {
                     });
             }
         }
-        ximc.create_ic(xim_handler.im_id, ic_attributes.build());
+        ximc.create_ic(xim_handler.im_id, ic_attributes.build())
+            .ok();
         state = self.0.borrow_mut();
         state.xim_handler = Some(xim_handler);
         state.ximc = Some(ximc);
@@ -457,7 +452,7 @@ impl X11Client {
         state.composing = false;
         if let Some(mut ximc) = state.ximc.take() {
             let xim_handler = state.xim_handler.as_ref().unwrap();
-            ximc.destroy_ic(xim_handler.im_id, xim_handler.ic_id);
+            ximc.destroy_ic(xim_handler.im_id, xim_handler.ic_id).ok();
             state.ximc = Some(ximc);
         }
     }
@@ -535,6 +530,7 @@ impl X11Client {
                 );
                 let modifiers = Modifiers::from_xkb(&state.xkb);
                 let focused_window_id = state.focused_window?;
+                state.modifiers = modifiers;
                 drop(state);
 
                 let focused_window = self.get_window(focused_window_id)?;
@@ -547,6 +543,8 @@ impl X11Client {
                 let mut state = self.0.borrow_mut();
 
                 let modifiers = modifiers_from_state(event.state);
+                state.modifiers = modifiers;
+
                 let keystroke = {
                     let code = event.detail.into();
                     let mut keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
@@ -600,6 +598,8 @@ impl X11Client {
                 let mut state = self.0.borrow_mut();
 
                 let modifiers = modifiers_from_state(event.state);
+                state.modifiers = modifiers;
+
                 let keystroke = {
                     let code = event.detail.into();
                     let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
@@ -618,6 +618,8 @@ impl X11Client {
                 let mut state = self.0.borrow_mut();
 
                 let modifiers = modifiers_from_xinput_info(event.mods);
+                state.modifiers = modifiers;
+
                 let position = point(
                     px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
                     px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
@@ -664,8 +666,10 @@ impl X11Client {
             }
             Event::XinputButtonRelease(event) => {
                 let window = self.get_window(event.event)?;
-                let state = self.0.borrow();
+                let mut state = self.0.borrow_mut();
                 let modifiers = modifiers_from_xinput_info(event.mods);
+                state.modifiers = modifiers;
+
                 let position = point(
                     px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
                     px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
@@ -683,14 +687,15 @@ impl X11Client {
             }
             Event::XinputMotion(event) => {
                 let window = self.get_window(event.event)?;
-                let state = self.0.borrow();
+                let mut state = self.0.borrow_mut();
                 let pressed_button = pressed_button_from_mask(event.button_mask[0]);
                 let position = point(
                     px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
                     px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
                 );
-                drop(state);
                 let modifiers = modifiers_from_xinput_info(event.mods);
+                state.modifiers = modifiers;
+                drop(state);
 
                 let axisvalues = event
                     .axisvalues
@@ -766,13 +771,14 @@ impl X11Client {
                 self.0.borrow_mut().scroll_y = None;
 
                 let window = self.get_window(event.event)?;
-                let state = self.0.borrow();
+                let mut state = self.0.borrow_mut();
                 let pressed_button = pressed_button_from_mask(event.buttons[0]);
                 let position = point(
                     px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
                     px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
                 );
                 let modifiers = modifiers_from_xinput_info(event.mods);
+                state.modifiers = modifiers;
                 drop(state);
 
                 window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
@@ -855,7 +861,8 @@ impl X11Client {
                     );
                 })
                 .build();
-            ximc.set_ic_values(xim_handler.im_id, xim_handler.ic_id, ic_attributes);
+            ximc.set_ic_values(xim_handler.im_id, xim_handler.ic_id, ic_attributes)
+                .ok();
         }
         let mut state = self.0.borrow_mut();
         state.ximc = Some(ximc);
@@ -904,13 +911,14 @@ impl LinuxClient for X11Client {
 
     fn open_window(
         &self,
-        _handle: AnyWindowHandle,
+        handle: AnyWindowHandle,
         params: WindowParams,
     ) -> Box<dyn PlatformWindow> {
         let mut state = self.0.borrow_mut();
         let x_window = state.xcb_connection.generate_id().unwrap();
 
         let window = X11Window::new(
+            handle,
             X11ClientStatePtr(Rc::downgrade(&self.0)),
             state.common.foreground_executor.clone(),
             params,
@@ -1034,11 +1042,11 @@ impl LinuxClient for X11Client {
     }
 
     fn write_to_primary(&self, item: crate::ClipboardItem) {
-        self.0.borrow_mut().primary.set_contents(item.text);
+        self.0.borrow_mut().primary.set_contents(item.text).ok();
     }
 
     fn write_to_clipboard(&self, item: crate::ClipboardItem) {
-        self.0.borrow_mut().clipboard.set_contents(item.text);
+        self.0.borrow_mut().clipboard.set_contents(item.text).ok();
     }
 
     fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
@@ -1075,6 +1083,16 @@ impl LinuxClient for X11Client {
 
         event_loop.run(None, &mut self.clone(), |_| {}).log_err();
     }
+
+    fn active_window(&self) -> Option<AnyWindowHandle> {
+        let state = self.0.borrow();
+        state.focused_window.and_then(|focused_window| {
+            state
+                .windows
+                .get(&focused_window)
+                .map(|window| window.handle())
+        })
+    }
 }
 
 // Adatpted from:

crates/gpui/src/platform/linux/x11/window.rs 🔗

@@ -1,40 +1,29 @@
-// todo(linux): remove
-#![allow(unused)]
-
 use crate::{
     platform::blade::{BladeRenderer, BladeSurfaceConfig},
-    size, Bounds, DevicePixels, ForegroundExecutor, Modifiers, Pixels, Platform, PlatformAtlas,
-    PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
-    Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowOptions,
-    WindowParams, X11Client, X11ClientState, X11ClientStatePtr,
+    size, AnyWindowHandle, Bounds, DevicePixels, ForegroundExecutor, Modifiers, Pixels,
+    PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
+    PromptLevel, Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
+    WindowParams, X11ClientStatePtr,
 };
 
 use blade_graphics as gpu;
-use parking_lot::Mutex;
 use raw_window_handle as rwh;
 use util::ResultExt;
 use x11rb::{
-    connection::{Connection as _, RequestConnection as _},
+    connection::Connection,
     protocol::{
-        render,
         xinput::{self, ConnectionExt as _},
         xproto::{
-            self, Atom, ClientMessageEvent, ConnectionExt as _, CreateWindowAux, EventMask,
-            TranslateCoordinatesReply,
+            self, ClientMessageEvent, ConnectionExt as _, EventMask, TranslateCoordinatesReply,
         },
     },
-    resource_manager::Database,
     wrapper::ConnectionExt as _,
     xcb_ffi::XCBConnection,
 };
 
-use std::ops::Deref;
-use std::rc::Weak;
 use std::{
-    cell::{Ref, RefCell, RefMut},
-    collections::HashMap,
+    cell::RefCell,
     ffi::c_void,
-    iter::Zip,
     mem,
     num::NonZeroU32,
     ops::Div,
@@ -165,29 +154,29 @@ pub struct Callbacks {
     appearance_changed: Option<Box<dyn FnMut()>>,
 }
 
-pub(crate) struct X11WindowState {
+pub struct X11WindowState {
     client: X11ClientStatePtr,
     executor: ForegroundExecutor,
     atoms: XcbAtoms,
     x_root_window: xproto::Window,
-    raw: RawWindow,
+    _raw: RawWindow,
     bounds: Bounds<i32>,
     scale_factor: f32,
     renderer: BladeRenderer,
     display: Rc<dyn PlatformDisplay>,
     input_handler: Option<PlatformInputHandler>,
     appearance: WindowAppearance,
+    pub handle: AnyWindowHandle,
 }
 
 #[derive(Clone)]
 pub(crate) struct X11WindowStatePtr {
-    pub(crate) state: Rc<RefCell<X11WindowState>>,
+    pub state: Rc<RefCell<X11WindowState>>,
     pub(crate) callbacks: Rc<RefCell<Callbacks>>,
     xcb_connection: Rc<XCBConnection>,
     x_window: xproto::Window,
 }
 
-// todo(linux): Remove other RawWindowHandle implementation
 impl rwh::HasWindowHandle for RawWindow {
     fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
         let non_zero = NonZeroU32::new(self.window_id).unwrap();
@@ -218,6 +207,7 @@ impl rwh::HasDisplayHandle for X11Window {
 impl X11WindowState {
     #[allow(clippy::too_many_arguments)]
     pub fn new(
+        handle: AnyWindowHandle,
         client: X11ClientStatePtr,
         executor: ForegroundExecutor,
         params: WindowParams,
@@ -372,7 +362,7 @@ impl X11WindowState {
             client,
             executor,
             display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()),
-            raw,
+            _raw: raw,
             x_root_window: visual_set.root,
             bounds: params.bounds.map(|v| v.0),
             scale_factor,
@@ -380,6 +370,7 @@ impl X11WindowState {
             atoms: *atoms,
             input_handler: None,
             appearance,
+            handle,
         }
     }
 
@@ -420,14 +411,15 @@ impl Drop for X11Window {
 }
 
 enum WmHintPropertyState {
-    Remove = 0,
-    Add = 1,
+    // Remove = 0,
+    // Add = 1,
     Toggle = 2,
 }
 
 impl X11Window {
     #[allow(clippy::too_many_arguments)]
     pub fn new(
+        handle: AnyWindowHandle,
         client: X11ClientStatePtr,
         executor: ForegroundExecutor,
         params: WindowParams,
@@ -440,6 +432,7 @@ impl X11Window {
     ) -> Self {
         Self(X11WindowStatePtr {
             state: Rc::new(RefCell::new(X11WindowState::new(
+                handle,
                 client,
                 executor,
                 params,
@@ -622,8 +615,6 @@ impl X11WindowStatePtr {
             let mut state = self.state.borrow_mut();
             let old_bounds = mem::replace(&mut state.bounds, bounds);
             do_move = old_bounds.origin != bounds.origin;
-            // todo(linux): use normal GPUI types here, refactor out the double
-            // viewport check and extra casts ( )
             let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
             if state.renderer.viewport_size() != gpu_size {
                 state
@@ -698,8 +689,8 @@ impl PlatformWindow for X11Window {
         self.0.state.borrow().appearance
     }
 
-    fn display(&self) -> Rc<dyn PlatformDisplay> {
-        self.0.state.borrow().display.clone()
+    fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
+        Some(self.0.state.borrow().display.clone())
     }
 
     fn mouse_position(&self) -> Point<Pixels> {
@@ -713,9 +704,15 @@ impl PlatformWindow for X11Window {
         Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
     }
 
-    // todo(linux)
     fn modifiers(&self) -> Modifiers {
-        Modifiers::default()
+        self.0
+            .state
+            .borrow()
+            .client
+            .0
+            .upgrade()
+            .map(|ref_cell| ref_cell.borrow().modifiers)
+            .unwrap_or_default()
     }
 
     fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
@@ -792,8 +789,9 @@ impl PlatformWindow for X11Window {
             .unwrap();
     }
 
-    // todo(linux)
-    fn set_edited(&mut self, edited: bool) {}
+    fn set_edited(&mut self, _edited: bool) {
+        log::info!("ignoring macOS specific set_edited");
+    }
 
     fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
         let mut inner = self.0.state.borrow_mut();
@@ -801,14 +799,8 @@ impl PlatformWindow for X11Window {
         inner.renderer.update_transparency(transparent);
     }
 
-    // todo(linux), this corresponds to `orderFrontCharacterPalette` on macOS,
-    // but it looks like the equivalent for Linux is GTK specific:
-    //
-    // https://docs.gtk.org/gtk3/signal.Entry.insert-emoji.html
-    //
-    // This API might need to change, or we might need to build an emoji picker into GPUI
     fn show_character_palette(&self) {
-        unimplemented!()
+        log::info!("ignoring macOS specific show_character_palette");
     }
 
     fn minimize(&self) {

crates/gpui/src/platform/linux/x11/xim_handler.rs 🔗

@@ -1,13 +1,9 @@
-use std::cell::RefCell;
 use std::default::Default;
-use std::rc::Rc;
 
 use calloop::channel;
 
 use x11rb::protocol::{xproto, Event};
-use xim::{AHashMap, AttributeName, Client, ClientError, ClientHandler, InputStyle, Point};
-
-use crate::{Keystroke, PlatformInput, X11ClientState};
+use xim::{AHashMap, AttributeName, Client, ClientError, ClientHandler, InputStyle};
 
 pub enum XimCallbackEvent {
     XimXEvent(x11rb::protocol::Event),
@@ -84,10 +80,12 @@ impl<C: Client<XEvent = xproto::KeyPressEvent>> ClientHandler<C> for XimHandler
         _input_context_id: u16,
         text: &str,
     ) -> Result<(), ClientError> {
-        self.xim_tx.send(XimCallbackEvent::XimCommitEvent(
-            self.window,
-            String::from(text),
-        ));
+        self.xim_tx
+            .send(XimCallbackEvent::XimCommitEvent(
+                self.window,
+                String::from(text),
+            ))
+            .ok();
         Ok(())
     }
 
@@ -99,14 +97,16 @@ impl<C: Client<XEvent = xproto::KeyPressEvent>> ClientHandler<C> for XimHandler
         _flag: xim::ForwardEventFlag,
         xev: C::XEvent,
     ) -> Result<(), ClientError> {
-        match (xev.response_type) {
+        match xev.response_type {
             x11rb::protocol::xproto::KEY_PRESS_EVENT => {
                 self.xim_tx
-                    .send(XimCallbackEvent::XimXEvent(Event::KeyPress(xev)));
+                    .send(XimCallbackEvent::XimXEvent(Event::KeyPress(xev)))
+                    .ok();
             }
             x11rb::protocol::xproto::KEY_RELEASE_EVENT => {
                 self.xim_tx
-                    .send(XimCallbackEvent::XimXEvent(Event::KeyRelease(xev)));
+                    .send(XimCallbackEvent::XimXEvent(Event::KeyRelease(xev)))
+                    .ok();
             }
             _ => {}
         }
@@ -145,10 +145,12 @@ impl<C: Client<XEvent = xproto::KeyPressEvent>> ClientHandler<C> for XimHandler
         // XIMPrimary, XIMHighlight, XIMSecondary, XIMTertiary are not specified,
         // but interchangeable as above
         // Currently there's no way to support these.
-        let mark_range = self.xim_tx.send(XimCallbackEvent::XimPreeditEvent(
-            self.window,
-            String::from(preedit_string),
-        ));
+        self.xim_tx
+            .send(XimCallbackEvent::XimPreeditEvent(
+                self.window,
+                String::from(preedit_string),
+            ))
+            .ok();
         Ok(())
     }
 }

crates/gpui/src/platform/linux/xdg_desktop_portal.rs 🔗

@@ -6,7 +6,6 @@ use std::future::Future;
 use ashpd::desktop::settings::{ColorScheme, Settings};
 use calloop::channel::{Channel, Sender};
 use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
-use parking_lot::Mutex;
 use smol::stream::StreamExt;
 use util::ResultExt;
 
@@ -115,6 +114,7 @@ impl WindowAppearance {
         }
     }
 
+    #[cfg_attr(target_os = "linux", allow(dead_code))]
     fn set_native(&mut self, cs: ColorScheme) {
         *self = Self::from_native(cs);
     }

crates/gpui/src/platform/mac/platform.rs 🔗

@@ -826,9 +826,6 @@ impl Platform for MacPlatform {
                 CursorStyle::ResizeDown => msg_send![class!(NSCursor), resizeDownCursor],
                 CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
                 CursorStyle::ResizeRow => msg_send![class!(NSCursor), resizeUpDownCursor],
-                CursorStyle::DisappearingItem => {
-                    msg_send![class!(NSCursor), disappearingItemCursor]
-                }
                 CursorStyle::IBeamCursorForVerticalLayout => {
                     msg_send![class!(NSCursor), IBeamCursorForVerticalLayout]
                 }

crates/gpui/src/platform/mac/window.rs 🔗

@@ -799,7 +799,7 @@ impl PlatformWindow for MacWindow {
         }
     }
 
-    fn display(&self) -> Rc<dyn PlatformDisplay> {
+    fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
         unsafe {
             let screen = self.0.lock().native_window.screen();
             let device_description: id = msg_send![screen, deviceDescription];
@@ -810,7 +810,7 @@ impl PlatformWindow for MacWindow {
 
             let screen_number: u32 = msg_send![screen_number, unsignedIntValue];
 
-            Rc::new(MacDisplay(screen_number))
+            Some(Rc::new(MacDisplay(screen_number)))
         }
     }
 

crates/gpui/src/platform/test/text_system.rs 🔗

@@ -1,50 +0,0 @@
-use crate::{
-    Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun, GlyphId, LineLayout, Pixels,
-    PlatformTextSystem, RenderGlyphParams, Size,
-};
-use anyhow::Result;
-use std::borrow::Cow;
-
-pub(crate) struct TestTextSystem {}
-
-// todo(linux)
-#[allow(unused)]
-impl PlatformTextSystem for TestTextSystem {
-    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
-        unimplemented!()
-    }
-    fn all_font_names(&self) -> Vec<String> {
-        unimplemented!()
-    }
-    fn all_font_families(&self) -> Vec<String> {
-        unimplemented!()
-    }
-    fn font_id(&self, descriptor: &Font) -> Result<FontId> {
-        unimplemented!()
-    }
-    fn font_metrics(&self, font_id: FontId) -> FontMetrics {
-        unimplemented!()
-    }
-    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
-        unimplemented!()
-    }
-    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
-        unimplemented!()
-    }
-    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
-        unimplemented!()
-    }
-    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
-        unimplemented!()
-    }
-    fn rasterize_glyph(
-        &self,
-        params: &RenderGlyphParams,
-        raster_bounds: Bounds<DevicePixels>,
-    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
-        unimplemented!()
-    }
-    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout {
-        unimplemented!()
-    }
-}

crates/gpui/src/platform/test/window.rs 🔗

@@ -132,8 +132,8 @@ impl PlatformWindow for TestWindow {
         WindowAppearance::Light
     }
 
-    fn display(&self) -> std::rc::Rc<dyn crate::PlatformDisplay> {
-        self.0.lock().display.clone()
+    fn display(&self) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
+        Some(self.0.lock().display.clone())
     }
 
     fn mouse_position(&self) -> Point<Pixels> {

crates/gpui/src/platform/windows/window.rs 🔗

@@ -379,8 +379,8 @@ impl PlatformWindow for WindowsWindow {
         WindowAppearance::Dark
     }
 
-    fn display(&self) -> Rc<dyn PlatformDisplay> {
-        Rc::new(self.0.state.borrow().display)
+    fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
+        Some(Rc::new(self.0.state.borrow().display))
     }
 
     fn mouse_position(&self) -> Point<Pixels> {

crates/gpui/src/window.rs 🔗

@@ -488,7 +488,7 @@ pub struct Window {
     pub(crate) handle: AnyWindowHandle,
     pub(crate) removed: bool,
     pub(crate) platform_window: Box<dyn PlatformWindow>,
-    display_id: DisplayId,
+    display_id: Option<DisplayId>,
     sprite_atlas: Arc<dyn PlatformAtlas>,
     text_system: Arc<WindowTextSystem>,
     rem_size: Pixels,
@@ -634,7 +634,7 @@ impl Window {
                 window_background,
             },
         );
-        let display_id = platform_window.display().id();
+        let display_id = platform_window.display().map(|display| display.id());
         let sprite_atlas = platform_window.sprite_atlas();
         let mouse_position = platform_window.mouse_position();
         let modifiers = platform_window.modifiers();
@@ -1099,7 +1099,12 @@ impl<'a> WindowContext<'a> {
     fn bounds_changed(&mut self) {
         self.window.scale_factor = self.window.platform_window.scale_factor();
         self.window.viewport_size = self.window.platform_window.content_size();
-        self.window.display_id = self.window.platform_window.display().id();
+        self.window.display_id = self
+            .window
+            .platform_window
+            .display()
+            .map(|display| display.id());
+
         self.refresh();
 
         self.window
@@ -1191,7 +1196,7 @@ impl<'a> WindowContext<'a> {
         self.platform
             .displays()
             .into_iter()
-            .find(|display| display.id() == self.window.display_id)
+            .find(|display| Some(display.id()) == self.window.display_id)
     }
 
     /// Show the platform character palette.

crates/zed/src/zed.rs 🔗

@@ -76,8 +76,11 @@ actions!(
 );
 
 pub fn init(cx: &mut AppContext) {
+    #[cfg(target_os = "macos")]
     cx.on_action(|_: &Hide, cx| cx.hide());
+    #[cfg(target_os = "macos")]
     cx.on_action(|_: &HideOthers, cx| cx.hide_other_apps());
+    #[cfg(target_os = "macos")]
     cx.on_action(|_: &ShowAll, cx| cx.unhide_other_apps());
     cx.on_action(quit);
 }