gpui: Extract gpui_platform out of gpui (#49277)

Piotr Osiewicz and Eric Holk created

#2874 on steroids

Before you mark this PR as ready for review, make sure that you have:
- [ ] Added a solid test coverage and/or screenshots from doing manual
testing
- [ ] Done a self-review taking into account security and performance
aspects
- [ ] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)

Release Notes:

- N/A

---------

Co-authored-by: Eric Holk <eric@zed.dev>

Change summary

Cargo.lock                                                | 166 ++
Cargo.toml                                                |  12 
crates/component_preview/Cargo.toml                       |   5 
crates/component_preview/examples/component_preview.rs    | 151 ++
crates/component_preview/src/component_preview.rs         |   8 
crates/component_preview/src/component_preview_example.rs | 148 --
crates/edit_prediction_cli/Cargo.toml                     |   1 
crates/edit_prediction_cli/src/main.rs                    |   4 
crates/eval/Cargo.toml                                    |   1 
crates/eval/src/eval.rs                                   |   4 
crates/eval_utils/Cargo.toml                              |   2 
crates/eval_utils/src/eval_utils.rs                       |   2 
crates/extension_cli/Cargo.toml                           |   2 
crates/extension_cli/src/main.rs                          |   2 
crates/fs_benchmarks/Cargo.toml                           |   3 
crates/fs_benchmarks/src/main.rs                          |   6 
crates/gpui/Cargo.toml                                    | 106 -
crates/gpui/build.rs                                      | 465 -------
crates/gpui/examples/animation.rs                         |  39 
crates/gpui/examples/data_table.rs                        |   9 
crates/gpui/examples/drag_drop.rs                         |   7 
crates/gpui/examples/focus_visible.rs                     |   7 
crates/gpui/examples/gif_viewer.rs                        |   5 
crates/gpui/examples/gradient.rs                          |   7 
crates/gpui/examples/grid_layout.rs                       |   6 
crates/gpui/examples/hello_world.rs                       |   7 
crates/gpui/examples/image/image.rs                       |   9 
crates/gpui/examples/image_gallery.rs                     |   7 
crates/gpui/examples/image_loading.rs                     |  37 
crates/gpui/examples/input.rs                             |   9 
crates/gpui/examples/layer_shell.rs                       |   8 
crates/gpui/examples/mouse_pressure.rs                    |   7 
crates/gpui/examples/on_window_close_quit.rs              |   7 
crates/gpui/examples/opacity.rs                           |   7 
crates/gpui/examples/ownership_post.rs                    |   5 
crates/gpui/examples/painting.rs                          |   9 
crates/gpui/examples/paths_bench.rs                       |   9 
crates/gpui/examples/pattern.rs                           |   7 
crates/gpui/examples/popover.rs                           |   7 
crates/gpui/examples/scrollable.rs                        |   8 
crates/gpui/examples/set_menus.rs                         |   7 
crates/gpui/examples/shadow.rs                            |   7 
crates/gpui/examples/svg/svg.rs                           |   7 
crates/gpui/examples/tab_stop.rs                          |   7 
crates/gpui/examples/testing.rs                           |   5 
crates/gpui/examples/text.rs                              |   9 
crates/gpui/examples/text_layout.rs                       |   7 
crates/gpui/examples/text_wrapper.rs                      |   7 
crates/gpui/examples/tree.rs                              |   8 
crates/gpui/examples/uniform_list.rs                      |   7 
crates/gpui/examples/window.rs                            |   7 
crates/gpui/examples/window_positioning.rs                |   5 
crates/gpui/examples/window_shadow.rs                     |  11 
crates/gpui/src/_ownership_and_data_flow.rs               |  10 
crates/gpui/src/app.rs                                    |  23 
crates/gpui/src/app/visual_test_context.rs                | 126 -
crates/gpui/src/assets.rs                                 |   7 
crates/gpui/src/color.rs                                  |   2 
crates/gpui/src/geometry.rs                               |  16 
crates/gpui/src/gpui.rs                                   |  11 
crates/gpui/src/interactive.rs                            |   2 
crates/gpui/src/keymap/context.rs                         |   2 
crates/gpui/src/platform.rs                               | 239 +--
crates/gpui/src/platform/keystroke.rs                     |   3 
crates/gpui/src/platform/layer_shell.rs                   |  28 
crates/gpui/src/platform/linux.rs                         |  32 
crates/gpui/src/platform/linux/wayland.rs                 |  49 
crates/gpui/src/platform/mac/status_item.rs               | 387 ------
crates/gpui/src/platform/mac/window_appearance.rs         |  37 
crates/gpui/src/platform/scap_screen_capture.rs           |   2 
crates/gpui/src/platform/test/platform.rs                 |  31 
crates/gpui/src/platform/test/window.rs                   |   4 
crates/gpui/src/platform/visual_test.rs                   |  48 
crates/gpui/src/platform/wgpu.rs                          |   7 
crates/gpui/src/profiler.rs                               |  26 
crates/gpui/src/queue.rs                                  |  32 
crates/gpui/src/scene.rs                                  |  85 
crates/gpui/src/svg_renderer.rs                           |   7 
crates/gpui/src/text_system.rs                            |  60 
crates/gpui/src/text_system/line_layout.rs                |   5 
crates/gpui/src/window.rs                                 |   6 
crates/gpui_linux/Cargo.toml                              | 134 ++
crates/gpui_linux/LICENSE-APACHE                          |   1 
crates/gpui_linux/src/gpui_linux.rs                       |   4 
crates/gpui_linux/src/linux.rs                            |  57 
crates/gpui_linux/src/linux/dispatcher.rs                 |  21 
crates/gpui_linux/src/linux/headless.rs                   |   0 
crates/gpui_linux/src/linux/headless/client.rs            |  27 
crates/gpui_linux/src/linux/keyboard.rs                   |   2 
crates/gpui_linux/src/linux/platform.rs                   | 560 ++++----
crates/gpui_linux/src/linux/text_system.rs                | 120 -
crates/gpui_linux/src/linux/wayland.rs                    |  47 
crates/gpui_linux/src/linux/wayland/client.rs             | 154 +-
crates/gpui_linux/src/linux/wayland/clipboard.rs          |   8 
crates/gpui_linux/src/linux/wayland/cursor.rs             |  10 
crates/gpui_linux/src/linux/wayland/display.rs            |   4 
crates/gpui_linux/src/linux/wayland/layer_shell.rs        |  26 
crates/gpui_linux/src/linux/wayland/serial.rs             |   0 
crates/gpui_linux/src/linux/wayland/window.rs             |  99 
crates/gpui_linux/src/linux/x11.rs                        |   0 
crates/gpui_linux/src/linux/x11/client.rs                 | 116 +
crates/gpui_linux/src/linux/x11/clipboard.rs              |   2 
crates/gpui_linux/src/linux/x11/display.rs                |   4 
crates/gpui_linux/src/linux/x11/event.rs                  |   2 
crates/gpui_linux/src/linux/x11/window.rs                 |  68 
crates/gpui_linux/src/linux/x11/xim_handler.rs            |   0 
crates/gpui_linux/src/linux/xdg_desktop_portal.rs         |  31 
crates/gpui_macos/Cargo.toml                              |  62 
crates/gpui_macos/LICENSE-APACHE                          |   1 
crates/gpui_macos/build.rs                                | 209 +++
crates/gpui_macos/src/dispatch.h                          |   0 
crates/gpui_macos/src/dispatcher.rs                       |   2 
crates/gpui_macos/src/display.rs                          |   6 
crates/gpui_macos/src/display_link.rs                     |   2 
crates/gpui_macos/src/events.rs                           | 382 +++---
crates/gpui_macos/src/gpui_macos.rs                       |  36 
crates/gpui_macos/src/keyboard.rs                         |   4 
crates/gpui_macos/src/metal_atlas.rs                      |  54 
crates/gpui_macos/src/metal_renderer.rs                   |  24 
crates/gpui_macos/src/open_type.rs                        |   2 
crates/gpui_macos/src/pasteboard.rs                       |  10 
crates/gpui_macos/src/platform.rs                         |  50 
crates/gpui_macos/src/screen_capture.rs                   |  19 
crates/gpui_macos/src/shaders.metal                       |   0 
crates/gpui_macos/src/text_system.rs                      | 171 +-
crates/gpui_macos/src/window.rs                           |  97 
crates/gpui_macos/src/window_appearance.rs                |  35 
crates/gpui_platform/Cargo.toml                           |  32 
crates/gpui_platform/LICENSE-APACHE                       |   1 
crates/gpui_platform/src/gpui_platform.rs                 | 150 ++
crates/gpui_wgpu/Cargo.toml                               |  26 
crates/gpui_wgpu/LICENSE-APACHE                           |   1 
crates/gpui_wgpu/src/gpui_wgpu.rs                         |   8 
crates/gpui_wgpu/src/shaders.wgsl                         |   0 
crates/gpui_wgpu/src/wgpu_atlas.rs                        |  12 
crates/gpui_wgpu/src/wgpu_context.rs                      |   0 
crates/gpui_wgpu/src/wgpu_renderer.rs                     |   6 
crates/gpui_windows/Cargo.toml                            |  46 
crates/gpui_windows/LICENSE-APACHE                        |   1 
crates/gpui_windows/build.rs                              | 242 +++
crates/gpui_windows/src/alpha_correction.hlsl             |   0 
crates/gpui_windows/src/clipboard.rs                      |  27 
crates/gpui_windows/src/color_text_raster.hlsl            |   0 
crates/gpui_windows/src/destination_list.rs               |   2 
crates/gpui_windows/src/direct_write.rs                   |  72 
crates/gpui_windows/src/directx_atlas.rs                  |  34 
crates/gpui_windows/src/directx_devices.rs                |   0 
crates/gpui_windows/src/directx_renderer.rs               |  11 
crates/gpui_windows/src/dispatcher.rs                     |  24 
crates/gpui_windows/src/display.rs                        |  21 
crates/gpui_windows/src/events.rs                         |  27 
crates/gpui_windows/src/gpui_windows.rs                   |   9 
crates/gpui_windows/src/keyboard.rs                       |   5 
crates/gpui_windows/src/platform.rs                       |  22 
crates/gpui_windows/src/shaders.hlsl                      |   0 
crates/gpui_windows/src/system_settings.rs                |   0 
crates/gpui_windows/src/util.rs                           |  14 
crates/gpui_windows/src/vsync.rs                          |   0 
crates/gpui_windows/src/window.rs                         |  24 
crates/gpui_windows/src/wrapper.rs                        |   0 
crates/livekit_client/Cargo.toml                          |   3 
crates/livekit_client/examples/test_app.rs                |   2 
crates/markdown/Cargo.toml                                |   1 
crates/markdown/examples/markdown.rs                      |   4 
crates/markdown/examples/markdown_as_child.rs             |   4 
crates/project_benchmarks/Cargo.toml                      |   3 
crates/project_benchmarks/src/main.rs                     |   4 
crates/remote_server/Cargo.toml                           |   3 
crates/remote_server/src/server.rs                        |   2 
crates/storybook/Cargo.toml                               |   1 
crates/storybook/src/storybook.rs                         |  80 
crates/worktree_benchmarks/Cargo.toml                     |   2 
crates/worktree_benchmarks/src/main.rs                    |   3 
crates/zed/Cargo.toml                                     |   9 
crates/zed/src/main.rs                                    |   6 
crates/zed/src/visual_test_runner.rs                      |   5 
crates/zed/src/zed/visual_tests.rs                        |   6 
typos.toml                                                |  10 
178 files changed, 3,115 insertions(+), 3,205 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3438,6 +3438,7 @@ dependencies = [
  "db",
  "fs",
  "gpui",
+ "gpui_platform",
  "language",
  "log",
  "node_runtime",
@@ -5242,6 +5243,7 @@ dependencies = [
  "fs",
  "futures 0.3.31",
  "gpui",
+ "gpui_platform",
  "gpui_tokio",
  "http_client",
  "indoc",
@@ -5779,6 +5781,7 @@ dependencies = [
  "fs",
  "futures 0.3.31",
  "gpui",
+ "gpui_platform",
  "gpui_tokio",
  "handlebars 4.5.0",
  "language",
@@ -5814,7 +5817,7 @@ dependencies = [
 name = "eval_utils"
 version = "0.1.0"
 dependencies = [
- "gpui",
+ "gpui_platform",
  "serde",
  "smol",
 ]
@@ -5931,7 +5934,7 @@ dependencies = [
  "env_logger 0.11.8",
  "extension",
  "fs",
- "gpui",
+ "gpui_platform",
  "language",
  "log",
  "reqwest_client",
@@ -6498,6 +6501,7 @@ version = "0.1.0"
 dependencies = [
  "fs",
  "gpui",
+ "gpui_platform",
 ]
 
 [[package]]
@@ -7348,16 +7352,11 @@ name = "gpui"
 version = "0.2.2"
 dependencies = [
  "anyhow",
- "as-raw-xcb-connection",
- "ashpd",
  "async-task",
  "backtrace",
  "bindgen 0.71.1",
  "bitflags 2.10.0",
  "block",
- "bytemuck",
- "calloop",
- "calloop-wayland-source",
  "cbindgen",
  "chrono",
  "circular-buffer",
@@ -7369,21 +7368,19 @@ dependencies = [
  "core-graphics 0.24.0",
  "core-text",
  "core-video",
- "cosmic-text",
  "ctor",
  "derive_more 0.99.20",
  "embed-resource",
  "env_logger 0.11.8",
  "etagere",
- "filedescriptor",
  "foreign-types 0.5.0",
  "futures 0.3.31",
  "gpui_macros",
+ "gpui_platform",
  "http_client",
  "image",
  "inventory",
  "itertools 0.14.0",
- "libc",
  "log",
  "lyon",
  "mach2 0.5.0",
@@ -7394,8 +7391,6 @@ dependencies = [
  "objc",
  "objc2",
  "objc2-metal",
- "oo7",
- "open",
  "parking",
  "parking_lot",
  "pathfinder_geometry",
@@ -7411,7 +7406,6 @@ dependencies = [
  "scheduler",
  "schemars",
  "seahash",
- "semver",
  "serde",
  "serde_json",
  "slotmap",
@@ -7421,7 +7415,6 @@ dependencies = [
  "stacksafe",
  "strum 0.27.2",
  "sum_tree",
- "swash",
  "taffy",
  "thiserror 2.0.17",
  "unicode-segmentation",
@@ -7430,17 +7423,50 @@ dependencies = [
  "util_macros",
  "uuid",
  "waker-fn",
+ "windows 0.61.3",
+ "zed-font-kit",
+ "zed-scap",
+]
+
+[[package]]
+name = "gpui_linux"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "as-raw-xcb-connection",
+ "ashpd",
+ "bitflags 2.10.0",
+ "bytemuck",
+ "calloop",
+ "calloop-wayland-source",
+ "collections",
+ "cosmic-text",
+ "filedescriptor",
+ "futures 0.3.31",
+ "gpui",
+ "gpui_wgpu",
+ "http_client",
+ "itertools 0.14.0",
+ "libc",
+ "log",
+ "oo7",
+ "open",
+ "parking_lot",
+ "pathfinder_geometry",
+ "profiling",
+ "raw-window-handle",
+ "smallvec",
+ "smol",
+ "strum 0.27.2",
+ "swash",
+ "util",
+ "uuid",
  "wayland-backend",
  "wayland-client",
  "wayland-cursor",
  "wayland-protocols",
  "wayland-protocols-plasma",
  "wayland-protocols-wlr",
- "wgpu",
- "windows 0.61.3",
- "windows-core 0.61.2",
- "windows-numerics 0.2.0",
- "windows-registry 0.5.3",
  "x11-clipboard",
  "x11rb",
  "xkbcommon",
@@ -7449,6 +7475,47 @@ dependencies = [
  "zed-xim",
 ]
 
+[[package]]
+name = "gpui_macos"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-task",
+ "bindgen 0.71.1",
+ "block",
+ "cbindgen",
+ "cocoa 0.26.0",
+ "collections",
+ "core-foundation 0.10.0",
+ "core-foundation-sys",
+ "core-graphics 0.24.0",
+ "core-text",
+ "core-video",
+ "ctor",
+ "derive_more 0.99.20",
+ "etagere",
+ "foreign-types 0.5.0",
+ "futures 0.3.31",
+ "gpui",
+ "image",
+ "itertools 0.14.0",
+ "libc",
+ "log",
+ "mach2 0.5.0",
+ "media",
+ "metal 0.29.0",
+ "objc",
+ "parking_lot",
+ "pathfinder_geometry",
+ "raw-window-handle",
+ "semver",
+ "smallvec",
+ "strum 0.27.2",
+ "util",
+ "uuid",
+ "zed-font-kit",
+]
+
 [[package]]
 name = "gpui_macros"
 version = "0.1.0"
@@ -7460,6 +7527,16 @@ dependencies = [
  "syn 2.0.106",
 ]
 
+[[package]]
+name = "gpui_platform"
+version = "0.1.0"
+dependencies = [
+ "gpui",
+ "gpui_linux",
+ "gpui_macos",
+ "gpui_windows",
+]
+
 [[package]]
 name = "gpui_tokio"
 version = "0.1.0"
@@ -7470,6 +7547,49 @@ dependencies = [
  "util",
 ]
 
+[[package]]
+name = "gpui_wgpu"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "bytemuck",
+ "collections",
+ "etagere",
+ "gpui",
+ "log",
+ "parking_lot",
+ "profiling",
+ "raw-window-handle",
+ "smol",
+ "util",
+ "wgpu",
+]
+
+[[package]]
+name = "gpui_windows"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "collections",
+ "etagere",
+ "futures 0.3.31",
+ "gpui",
+ "image",
+ "itertools 0.14.0",
+ "log",
+ "parking_lot",
+ "rand 0.9.2",
+ "raw-window-handle",
+ "smallvec",
+ "util",
+ "uuid",
+ "windows 0.61.3",
+ "windows-core 0.61.2",
+ "windows-numerics 0.2.0",
+ "windows-registry 0.5.3",
+ "zed-scap",
+]
+
 [[package]]
 name = "grid"
 version = "0.18.0"
@@ -9534,6 +9654,7 @@ dependencies = [
  "cpal",
  "futures 0.3.31",
  "gpui",
+ "gpui_platform",
  "gpui_tokio",
  "http_client_tls",
  "image",
@@ -9786,6 +9907,7 @@ dependencies = [
  "fs",
  "futures 0.3.31",
  "gpui",
+ "gpui_platform",
  "language",
  "languages",
  "linkify",
@@ -12736,6 +12858,7 @@ dependencies = [
  "client",
  "futures 0.3.31",
  "gpui",
+ "gpui_platform",
  "http_client",
  "language",
  "node_runtime",
@@ -13702,6 +13825,7 @@ dependencies = [
  "git2",
  "git_hosting_providers",
  "gpui",
+ "gpui_platform",
  "gpui_tokio",
  "http_client",
  "image",
@@ -15911,6 +16035,7 @@ dependencies = [
  "editor",
  "fuzzy",
  "gpui",
+ "gpui_platform",
  "indoc",
  "language",
  "log",
@@ -20724,7 +20849,7 @@ name = "worktree_benchmarks"
 version = "0.1.0"
 dependencies = [
  "fs",
- "gpui",
+ "gpui_platform",
  "settings",
  "worktree",
 ]
@@ -21140,6 +21265,7 @@ dependencies = [
  "git_ui",
  "go_to_line",
  "gpui",
+ "gpui_platform",
  "gpui_tokio",
  "http_client",
  "image",

Cargo.toml 🔗

@@ -83,7 +83,12 @@ members = [
     "crates/go_to_line",
     "crates/google_ai",
     "crates/gpui",
+    "crates/gpui_linux",
+    "crates/gpui_macos",
     "crates/gpui_macros",
+    "crates/gpui_platform",
+    "crates/gpui_wgpu",
+    "crates/gpui_windows",
     "crates/gpui_tokio",
     "crates/html_to_markdown",
     "crates/http_client",
@@ -321,7 +326,12 @@ git_ui = { path = "crates/git_ui" }
 go_to_line = { path = "crates/go_to_line" }
 google_ai = { path = "crates/google_ai" }
 gpui = { path = "crates/gpui", default-features = false }
+gpui_linux = { path = "crates/gpui_linux", default-features = false }
+gpui_macos = { path = "crates/gpui_macos", default-features = false }
 gpui_macros = { path = "crates/gpui_macros" }
+gpui_platform = { path = "crates/gpui_platform", default-features = false }
+gpui_wgpu = { path = "crates/gpui_wgpu" }
+gpui_windows = { path = "crates/gpui_windows", default-features = false }
 gpui_tokio = { path = "crates/gpui_tokio" }
 html_to_markdown = { path = "crates/html_to_markdown" }
 http_client = { path = "crates/http_client" }
@@ -819,6 +829,8 @@ codegen-units = 16
 # (without this cargo will compile ~400 crates twice)
 [profile.dev.build-override]
 codegen-units = 16
+split-debuginfo = "unpacked"
+debug = true
 
 [profile.dev.package]
 # proc-macros start

crates/component_preview/Cargo.toml 🔗

@@ -13,7 +13,6 @@ path = "src/component_preview.rs"
 
 [features]
 default = []
-preview = []
 test-support = ["db/test-support"]
 
 [dependencies]
@@ -39,7 +38,9 @@ ui_input.workspace = true
 uuid.workspace = true
 workspace.workspace = true
 
+[dev-dependencies]
+gpui_platform = { workspace = true, features = ["screen-capture"] }
+
 [[example]]
 name = "component_preview"
 path = "examples/component_preview.rs"
-required-features = ["preview"]

crates/component_preview/examples/component_preview.rs 🔗

@@ -1,18 +1,143 @@
 //! Component Preview Example
 //!
-//! Run with: `cargo run -p component_preview --example component_preview --features="preview"`
-//!
-//! To use this in other projects, add the following to your `Cargo.toml`:
-//!
-//! ```toml
-//! [dependencies]
-//! component_preview = { path = "../component_preview", features = ["preview"] }
-//!
-//! [[example]]
-//! name = "component_preview"
-//! path = "examples/component_preview.rs"
-//! ```
+//! Run with: `cargo run -p component_preview --example component_preview"`
+use fs::RealFs;
+use gpui::{AppContext as _, Bounds, KeyBinding, WindowBounds, WindowOptions, actions, size};
+
+use client::{Client, UserStore};
+use language::LanguageRegistry;
+use node_runtime::NodeRuntime;
+use project::Project;
+use reqwest_client::ReqwestClient;
+use session::{AppSession, Session};
+use std::sync::Arc;
+use ui::{App, px};
+use workspace::{AppState, Workspace, WorkspaceStore};
+
+use component_preview::{ComponentPreview, init};
+
+actions!(zed, [Quit]);
+
+fn quit(_: &Quit, cx: &mut App) {
+    cx.quit();
+}
 
 fn main() {
-    component_preview::run_component_preview();
+    gpui_platform::application().run(|cx| {
+        component::init();
+
+        cx.on_action(quit);
+        cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
+        let version = release_channel::AppVersion::load(env!("CARGO_PKG_VERSION"), None, None);
+        release_channel::init(version, cx);
+
+        let http_client =
+            ReqwestClient::user_agent("component_preview").expect("Failed to create HTTP client");
+        cx.set_http_client(Arc::new(http_client));
+
+        let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
+        <dyn fs::Fs>::set_global(fs.clone(), cx);
+
+        settings::init(cx);
+        theme::init(theme::LoadThemes::JustBase, cx);
+
+        let languages = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
+        let client = Client::production(cx);
+        client::init(&client, cx);
+
+        let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
+        let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
+        let session_id = uuid::Uuid::new_v4().to_string();
+        let session = cx.foreground_executor().block_on(Session::new(session_id));
+        let session = cx.new(|cx| AppSession::new(session, cx));
+        let node_runtime = NodeRuntime::unavailable();
+
+        let app_state = Arc::new(AppState {
+            languages,
+            client,
+            user_store,
+            workspace_store,
+            fs,
+            build_window_options: |_, _| Default::default(),
+            node_runtime,
+            session,
+        });
+        AppState::set_global(Arc::downgrade(&app_state), cx);
+
+        workspace::init(app_state.clone(), cx);
+        init(app_state.clone(), cx);
+
+        let size = size(px(1200.), px(800.));
+        let bounds = Bounds::centered(None, size, cx);
+
+        cx.open_window(
+            WindowOptions {
+                window_bounds: Some(WindowBounds::Windowed(bounds)),
+                ..Default::default()
+            },
+            {
+                move |window, cx| {
+                    let app_state = app_state;
+                    theme::setup_ui_font(window, cx);
+
+                    let project = Project::local(
+                        app_state.client.clone(),
+                        app_state.node_runtime.clone(),
+                        app_state.user_store.clone(),
+                        app_state.languages.clone(),
+                        app_state.fs.clone(),
+                        None,
+                        project::LocalProjectFlags {
+                            init_worktree_trust: false,
+                            ..Default::default()
+                        },
+                        cx,
+                    );
+
+                    let workspace = cx.new(|cx| {
+                        Workspace::new(
+                            Default::default(),
+                            project.clone(),
+                            app_state.clone(),
+                            window,
+                            cx,
+                        )
+                    });
+
+                    workspace.update(cx, |workspace, cx| {
+                        let weak_workspace = cx.entity().downgrade();
+                        let language_registry = app_state.languages.clone();
+                        let user_store = app_state.user_store.clone();
+
+                        let component_preview = cx.new(|cx| {
+                            ComponentPreview::new(
+                                weak_workspace,
+                                project,
+                                language_registry,
+                                user_store,
+                                None,
+                                None,
+                                window,
+                                cx,
+                            )
+                            .expect("Failed to create component preview")
+                        });
+
+                        workspace.add_item_to_active_pane(
+                            Box::new(component_preview),
+                            None,
+                            true,
+                            window,
+                            cx,
+                        );
+                    });
+
+                    workspace
+                }
+            },
+        )
+        .expect("Failed to open component preview window");
+
+        cx.activate(true);
+    });
 }

crates/component_preview/src/component_preview.rs 🔗

@@ -1,4 +1,3 @@
-mod component_preview_example;
 mod persistence;
 
 use client::UserStore;
@@ -20,9 +19,6 @@ use workspace::{
     Item, ItemId, SerializableItem, Workspace, WorkspaceId, delete_unloaded_items, item::ItemEvent,
 };
 
-#[allow(unused_imports)]
-pub use component_preview_example::*;
-
 pub fn init(app_state: Arc<AppState>, cx: &mut App) {
     workspace::register_serializable_item::<ComponentPreview>(cx);
 
@@ -85,13 +81,13 @@ impl From<SharedString> for PreviewEntry {
 }
 
 #[derive(Default, Debug, Clone, PartialEq, Eq)]
-enum PreviewPage {
+pub enum PreviewPage {
     #[default]
     AllComponents,
     Component(ComponentId),
 }
 
-struct ComponentPreview {
+pub struct ComponentPreview {
     active_page: PreviewPage,
     reset_key: usize,
     component_list: ListState,

crates/component_preview/src/component_preview_example.rs 🔗

@@ -1,148 +0,0 @@
-/// Run the component preview application.
-///
-/// This initializes the application with minimal required infrastructure
-/// and opens a workspace with the ComponentPreview item.
-#[cfg(feature = "preview")]
-pub fn run_component_preview() {
-    use fs::RealFs;
-    use gpui::{
-        AppContext as _, Application, Bounds, KeyBinding, WindowBounds, WindowOptions, actions,
-        size,
-    };
-
-    use client::{Client, UserStore};
-    use language::LanguageRegistry;
-    use node_runtime::NodeRuntime;
-    use project::Project;
-    use reqwest_client::ReqwestClient;
-    use session::{AppSession, Session};
-    use std::sync::Arc;
-    use ui::{App, px};
-    use workspace::{AppState, Workspace, WorkspaceStore};
-
-    use crate::{ComponentPreview, init};
-
-    actions!(zed, [Quit]);
-
-    fn quit(_: &Quit, cx: &mut App) {
-        cx.quit();
-    }
-
-    Application::new().run(|cx| {
-        component::init();
-
-        cx.on_action(quit);
-        cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
-        let version = release_channel::AppVersion::load(env!("CARGO_PKG_VERSION"), None, None);
-        release_channel::init(version, cx);
-
-        let http_client =
-            ReqwestClient::user_agent("component_preview").expect("Failed to create HTTP client");
-        cx.set_http_client(Arc::new(http_client));
-
-        let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
-        <dyn fs::Fs>::set_global(fs.clone(), cx);
-
-        settings::init(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
-
-        let languages = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
-        let client = Client::production(cx);
-        client::init(&client, cx);
-
-        let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
-        let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
-        let session_id = uuid::Uuid::new_v4().to_string();
-        let session = cx.foreground_executor().block_on(Session::new(session_id));
-        let session = cx.new(|cx| AppSession::new(session, cx));
-        let node_runtime = NodeRuntime::unavailable();
-
-        let app_state = Arc::new(AppState {
-            languages,
-            client,
-            user_store,
-            workspace_store,
-            fs,
-            build_window_options: |_, _| Default::default(),
-            node_runtime,
-            session,
-        });
-        AppState::set_global(Arc::downgrade(&app_state), cx);
-
-        workspace::init(app_state.clone(), cx);
-        init(app_state.clone(), cx);
-
-        let size = size(px(1200.), px(800.));
-        let bounds = Bounds::centered(None, size, cx);
-
-        cx.open_window(
-            WindowOptions {
-                window_bounds: Some(WindowBounds::Windowed(bounds)),
-                ..Default::default()
-            },
-            {
-                move |window, cx| {
-                    let app_state = app_state;
-                    theme::setup_ui_font(window, cx);
-
-                    let project = Project::local(
-                        app_state.client.clone(),
-                        app_state.node_runtime.clone(),
-                        app_state.user_store.clone(),
-                        app_state.languages.clone(),
-                        app_state.fs.clone(),
-                        None,
-                        project::LocalProjectFlags {
-                            init_worktree_trust: false,
-                            ..Default::default()
-                        },
-                        cx,
-                    );
-
-                    let workspace = cx.new(|cx| {
-                        Workspace::new(
-                            Default::default(),
-                            project.clone(),
-                            app_state.clone(),
-                            window,
-                            cx,
-                        )
-                    });
-
-                    workspace.update(cx, |workspace, cx| {
-                        let weak_workspace = cx.entity().downgrade();
-                        let language_registry = app_state.languages.clone();
-                        let user_store = app_state.user_store.clone();
-
-                        let component_preview = cx.new(|cx| {
-                            ComponentPreview::new(
-                                weak_workspace,
-                                project,
-                                language_registry,
-                                user_store,
-                                None,
-                                None,
-                                window,
-                                cx,
-                            )
-                            .expect("Failed to create component preview")
-                        });
-
-                        workspace.add_item_to_active_pane(
-                            Box::new(component_preview),
-                            None,
-                            true,
-                            window,
-                            cx,
-                        );
-                    });
-
-                    workspace
-                }
-            },
-        )
-        .expect("Failed to open component preview window");
-
-        cx.activate(true);
-    });
-}

crates/edit_prediction_cli/Cargo.toml 🔗

@@ -27,6 +27,7 @@ extension.workspace = true
 fs.workspace = true
 futures.workspace = true
 gpui.workspace = true
+gpui_platform.workspace = true
 gpui_tokio.workspace = true
 indoc.workspace = true
 language.workspace = true

crates/edit_prediction_cli/src/main.rs 🔗

@@ -31,7 +31,7 @@ use collections::HashSet;
 use edit_prediction::EditPredictionStore;
 use futures::channel::mpsc;
 use futures::{SinkExt as _, StreamExt as _};
-use gpui::{AppContext as _, Application, BackgroundExecutor, Task};
+use gpui::{AppContext as _, BackgroundExecutor, Task};
 use zeta_prompt::ZetaFormat;
 
 use reqwest_client::ReqwestClient;
@@ -851,7 +851,7 @@ fn main() {
     }
 
     let http_client = Arc::new(ReqwestClient::new());
-    let app = Application::headless().with_http_client(http_client);
+    let app = gpui_platform::headless().with_http_client(http_client);
 
     app.run(move |cx| {
         let app_state = Arc::new(headless::init(cx));

crates/eval/Cargo.toml 🔗

@@ -38,6 +38,7 @@ extension.workspace = true
 fs.workspace = true
 futures.workspace = true
 gpui.workspace = true
+gpui_platform.workspace = true
 gpui_tokio.workspace = true
 handlebars.workspace = true
 language.workspace = true

crates/eval/src/eval.rs 🔗

@@ -18,7 +18,7 @@ use collections::{HashMap, HashSet};
 use extension::ExtensionHostProxy;
 use futures::future;
 use gpui::http_client::read_proxy_from_env;
-use gpui::{App, AppContext, Application, AsyncApp, Entity, UpdateGlobal};
+use gpui::{App, AppContext, AsyncApp, Entity, UpdateGlobal};
 use gpui_tokio::Tokio;
 use language::LanguageRegistry;
 use language_model::{ConfiguredModel, LanguageModel, LanguageModelRegistry, SelectedModel};
@@ -114,7 +114,7 @@ fn main() {
     let languages: HashSet<String> = args.languages.into_iter().collect();
 
     let http_client = Arc::new(ReqwestClient::new());
-    let app = Application::headless().with_http_client(http_client);
+    let app = gpui_platform::headless().with_http_client(http_client);
     let all_threads = examples::all(&examples_dir);
 
     app.run(move |cx| {

crates/eval_utils/Cargo.toml 🔗

@@ -13,6 +13,6 @@ path = "src/eval_utils.rs"
 doctest = false
 
 [dependencies]
-gpui.workspace = true
+gpui_platform.workspace = true
 serde.workspace = true
 smol.workspace = true

crates/eval_utils/src/eval_utils.rs 🔗

@@ -82,7 +82,7 @@ pub fn eval<P>(
 
     let (tx, rx) = mpsc::channel();
 
-    let executor = gpui::background_executor();
+    let executor = gpui_platform::background_executor();
     let semaphore = Arc::new(smol::lock::Semaphore::new(32));
     let evalf = Arc::new(evalf);
     // Warm the cache once

crates/extension_cli/Cargo.toml 🔗

@@ -19,7 +19,7 @@ cloud_api_types.workspace = true
 env_logger.workspace = true
 extension.workspace = true
 fs.workspace = true
-gpui.workspace = true
+gpui_platform.workspace = true
 language.workspace = true
 log.workspace = true
 reqwest_client.workspace = true

crates/extension_cli/src/main.rs 🔗

@@ -35,7 +35,7 @@ async fn main() -> Result<()> {
     env_logger::init();
 
     let args = Args::parse();
-    let fs = Arc::new(RealFs::new(None, gpui::background_executor()));
+    let fs = Arc::new(RealFs::new(None, gpui_platform::background_executor()));
     let engine = wasmtime::Engine::default();
     let mut wasm_store = WasmStore::new(&engine)?;
 

crates/fs_benchmarks/Cargo.toml 🔗

@@ -6,7 +6,8 @@ edition.workspace = true
 
 [dependencies]
 fs.workspace = true
-gpui = {workspace = true, features = ["windows-manifest"]}
+gpui.workspace = true
+gpui_platform.workspace = true
 
 [lints]
 workspace = true

crates/fs_benchmarks/src/main.rs 🔗

@@ -1,12 +1,14 @@
 use fs::Fs;
-use gpui::{AppContext, Application};
+use gpui::AppContext;
+use gpui_platform::headless;
+
 fn main() {
     let Some(path_to_read) = std::env::args().nth(1) else {
         println!("Expected path to read as 1st argument.");
         return;
     };
 
-    let _ = Application::headless().run(|cx| {
+    let _ = headless().run(|cx| {
         let fs = fs::RealFs::new(None, cx.background_executor().clone());
         cx.background_spawn(async move {
             let timer = std::time::Instant::now();

crates/gpui/Cargo.toml 🔗

@@ -31,37 +31,8 @@ leak-detection = ["backtrace"]
 runtime_shaders = []
 wayland = [
     "bitflags",
-    "wgpu",
-    "bytemuck",
-    "ashpd/wayland",
-    "cosmic-text",
-    "font-kit",
-    "calloop-wayland-source",
-    "wayland-backend",
-    "wayland-client",
-    "wayland-cursor",
-    "wayland-protocols",
-    "wayland-protocols-plasma",
-    "wayland-protocols-wlr",
-    "filedescriptor",
-    "xkbcommon",
-    "open",
-]
-x11 = [
-    "wgpu",
-    "bytemuck",
-    "ashpd",
-    "cosmic-text",
-    "font-kit",
-    "as-raw-xcb-connection",
-    "x11rb",
-    "xkbcommon",
-    "xim",
-    "x11-clipboard",
-    "filedescriptor",
-    "open",
-    "scap?/x11",
 ]
+x11 = []
 screen-capture = [
     "scap",
 ]
@@ -76,7 +47,7 @@ anyhow.workspace = true
 async-task = "4.7"
 backtrace = { workspace = true, optional = true }
 bitflags = { workspace = true, optional = true }
-bytemuck = { version = "1", optional = true }
+
 collections.workspace = true
 ctor.workspace = true
 derive_more.workspace = true
@@ -107,7 +78,6 @@ usvg = { version = "0.45.0", default-features = false }
 util_macros.workspace = true
 schemars.workspace = true
 seahash = "4.1"
-semver.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 slotmap.workspace = true
@@ -122,7 +92,6 @@ util.workspace = true
 uuid.workspace = true
 waker-fn = "1.2.0"
 lyon = "1.0"
-libc.workspace = true
 pin-project = "1.1.10"
 circular-buffer.workspace = true
 spin = "0.10.0"
@@ -154,76 +123,17 @@ pathfinder_geometry = "0.5"
 [target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))'.dependencies]
 scap = { workspace = true, optional = true }
 
-[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
-# Always used
-oo7 = { version = "0.5.0", default-features = false, features = [
-    "async-std",
-    "native_crypto",
-] }
 
-# Used in both windowing options
-ashpd = { workspace = true, optional = true }
-wgpu = { workspace = true, optional = true }
-cosmic-text = { version = "0.17.0", optional = true }
-swash = { version = "0.2.6" }
-# WARNING: If you change this, you must also publish a new version of zed-font-kit to crates.io
-font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "110523127440aefb11ce0cf280ae7c5071337ec5", package = "zed-font-kit", version = "0.14.1-zed", features = [
-    "source-fontconfig-dlopen",
-], optional = true }
-calloop = "0.14.3"
-filedescriptor = { version = "0.8.2", optional = true }
-open = { version = "5.2.0", optional = true }
-xkbcommon = { version = "0.8.0", features = ["wayland", "x11"], optional = true }
-
-# Wayland
-calloop-wayland-source = { version = "0.4.1", optional = true }
-wayland-backend = { version = "0.3.3", features = [
-    "client_system",
-    "dlopen",
-], optional = true }
-wayland-client = { version = "0.31.11", optional = true }
-wayland-cursor = { version = "0.31.11", optional = true }
-wayland-protocols = { version = "0.32.9", features = [
-    "client",
-    "staging",
-    "unstable",
-], optional = true }
-wayland-protocols-plasma = { version = "0.3.9", features = [
-    "client",
-], optional = true }
-wayland-protocols-wlr = { version = "0.3.9", features = [
-    "client",
-], optional = true }
-
-# X11
-as-raw-xcb-connection = { version = "1", optional = true }
-x11rb = { version = "0.13.1", features = [
-    "allow-unsafe-code",
-    "xkb",
-    "randr",
-    "xinput",
-    "cursor",
-    "resource_manager",
-    "sync",
-], optional = true }
-# WARNING: If you change this, you must also publish a new version of zed-xim to crates.io
-xim = { git = "https://github.com/zed-industries/xim-rs.git", rev = "16f35a2c881b815a2b6cdfd6687988e84f8447d8" , features = [
-    "x11rb-xcb",
-    "x11rb-client",
-], package = "zed-xim", version = "0.4.0-zed", optional = true }
-x11-clipboard = { version = "0.9.3", optional = true }
 
 [target.'cfg(target_os = "windows")'.dependencies]
-rand.workspace = true
-windows.workspace = true
-windows-core.workspace = true
-windows-numerics = "0.2"
-windows-registry = "0.5"
+windows = { version = "0.61", features = ["Win32_Foundation"] }
+
 
 [dev-dependencies]
 backtrace.workspace = true
 collections = { workspace = true, features = ["test-support"] }
 env_logger.workspace = true
+gpui_platform.workspace = true
 http_client = { workspace = true, features = ["test-support"] }
 lyon = { version = "1.0", features = ["extra"] }
 pretty_assertions.workspace = true
@@ -233,17 +143,17 @@ scheduler = { workspace = true, features = ["test-support"] }
 unicode-segmentation.workspace = true
 util = { workspace = true, features = ["test-support"] }
 
+
+
 [target.'cfg(target_os = "windows")'.build-dependencies]
 embed-resource = "3.0"
-windows-registry = "0.5"
 
 [target.'cfg(target_os = "macos")'.build-dependencies]
 bindgen = "0.71"
 cbindgen = { version = "0.28.0", default-features = false }
 naga.workspace = true
 
-[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.build-dependencies]
-naga.workspace = true
+
 
 
 [[example]]

crates/gpui/build.rs 🔗

@@ -1,463 +1,20 @@
 #![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
 #![cfg_attr(not(target_os = "macos"), allow(unused))]
 
-use std::env;
-
 fn main() {
-    let target = env::var("CARGO_CFG_TARGET_OS");
     println!("cargo::rustc-check-cfg=cfg(gles)");
 
-    match target.as_deref() {
-        Ok("macos") => {
-            #[cfg(target_os = "macos")]
-            macos::build();
-        }
-        Ok("windows") => {
-            #[cfg(target_os = "windows")]
-            windows::build();
-        }
-        _ => (),
-    };
+    #[cfg(all(target_os = "windows", feature = "windows-manifest"))]
+    embed_resource();
 }
-#[cfg(target_os = "macos")]
-mod macos {
-    use std::{
-        env,
-        path::{Path, PathBuf},
-    };
-
-    use cbindgen::Config;
-
-    pub(super) fn build() {
-        generate_dispatch_bindings();
-
-        let header_path = generate_shader_bindings();
-
-        #[cfg(feature = "runtime_shaders")]
-        emit_stitched_shaders(&header_path);
-        #[cfg(not(feature = "runtime_shaders"))]
-        compile_metal_shaders(&header_path);
-    }
-
-    fn generate_dispatch_bindings() {
-        println!("cargo:rustc-link-lib=framework=System");
-
-        let bindings = bindgen::Builder::default()
-            .header("src/platform/mac/dispatch.h")
-            .allowlist_var("_dispatch_main_q")
-            .allowlist_var("_dispatch_source_type_data_add")
-            .allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH")
-            .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
-            .allowlist_var("DISPATCH_QUEUE_PRIORITY_LOW")
-            .allowlist_var("DISPATCH_TIME_NOW")
-            .allowlist_function("dispatch_get_global_queue")
-            .allowlist_function("dispatch_async_f")
-            .allowlist_function("dispatch_after_f")
-            .allowlist_function("dispatch_time")
-            .allowlist_function("dispatch_source_merge_data")
-            .allowlist_function("dispatch_source_create")
-            .allowlist_function("dispatch_source_set_event_handler_f")
-            .allowlist_function("dispatch_resume")
-            .allowlist_function("dispatch_suspend")
-            .allowlist_function("dispatch_source_cancel")
-            .allowlist_function("dispatch_set_context")
-            .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
-            .layout_tests(false)
-            .generate()
-            .expect("unable to generate bindings");
-
-        let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
-        bindings
-            .write_to_file(out_path.join("dispatch_sys.rs"))
-            .expect("couldn't write dispatch bindings");
-    }
-
-    fn generate_shader_bindings() -> PathBuf {
-        let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
-        let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
-        let mut config = Config {
-            include_guard: Some("SCENE_H".into()),
-            language: cbindgen::Language::C,
-            no_includes: true,
-            ..Default::default()
-        };
-        config.export.include.extend([
-            "Bounds".into(),
-            "Corners".into(),
-            "Edges".into(),
-            "Size".into(),
-            "Pixels".into(),
-            "PointF".into(),
-            "Hsla".into(),
-            "ContentMask".into(),
-            "Uniforms".into(),
-            "AtlasTile".into(),
-            "PathRasterizationInputIndex".into(),
-            "PathVertex_ScaledPixels".into(),
-            "PathRasterizationVertex".into(),
-            "ShadowInputIndex".into(),
-            "Shadow".into(),
-            "QuadInputIndex".into(),
-            "Underline".into(),
-            "UnderlineInputIndex".into(),
-            "Quad".into(),
-            "BorderStyle".into(),
-            "SpriteInputIndex".into(),
-            "MonochromeSprite".into(),
-            "PolychromeSprite".into(),
-            "PathSprite".into(),
-            "SurfaceInputIndex".into(),
-            "SurfaceBounds".into(),
-            "TransformationMatrix".into(),
-        ]);
-        config.no_includes = true;
-        config.enumeration.prefix_with_name = true;
-
-        let mut builder = cbindgen::Builder::new();
-
-        let src_paths = [
-            crate_dir.join("src/scene.rs"),
-            crate_dir.join("src/geometry.rs"),
-            crate_dir.join("src/color.rs"),
-            crate_dir.join("src/window.rs"),
-            crate_dir.join("src/platform.rs"),
-            crate_dir.join("src/platform/mac/metal_renderer.rs"),
-        ];
-        for src_path in src_paths {
-            println!("cargo:rerun-if-changed={}", src_path.display());
-            builder = builder.with_src(src_path);
-        }
-
-        builder
-            .with_config(config)
-            .generate()
-            .expect("Unable to generate bindings")
-            .write_to_file(&output_path);
-
-        output_path
-    }
-
-    /// To enable runtime compilation, we need to "stitch" the shaders file with the generated header
-    /// so that it is self-contained.
-    #[cfg(feature = "runtime_shaders")]
-    fn emit_stitched_shaders(header_path: &Path) {
-        use std::str::FromStr;
-        fn stitch_header(header: &Path, shader_path: &Path) -> std::io::Result<PathBuf> {
-            let header_contents = std::fs::read_to_string(header)?;
-            let shader_contents = std::fs::read_to_string(shader_path)?;
-            let stitched_contents = format!("{header_contents}\n{shader_contents}");
-            let out_path =
-                PathBuf::from(env::var("OUT_DIR").unwrap()).join("stitched_shaders.metal");
-            std::fs::write(&out_path, stitched_contents)?;
-            Ok(out_path)
-        }
-        let shader_source_path = "./src/platform/mac/shaders.metal";
-        let shader_path = PathBuf::from_str(shader_source_path).unwrap();
-        stitch_header(header_path, &shader_path).unwrap();
-        println!("cargo:rerun-if-changed={}", &shader_source_path);
-    }
-
-    #[cfg(not(feature = "runtime_shaders"))]
-    fn compile_metal_shaders(header_path: &Path) {
-        use std::process::{self, Command};
-        let shader_path = "./src/platform/mac/shaders.metal";
-        let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
-        let metallib_output_path =
-            PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
-        println!("cargo:rerun-if-changed={}", shader_path);
-
-        let output = Command::new("xcrun")
-            .args([
-                "-sdk",
-                "macosx",
-                "metal",
-                "-gline-tables-only",
-                "-mmacosx-version-min=10.15.7",
-                "-MO",
-                "-c",
-                shader_path,
-                "-include",
-                (header_path.to_str().unwrap()),
-                "-o",
-            ])
-            .arg(&air_output_path)
-            .output()
-            .unwrap();
-
-        if !output.status.success() {
-            println!(
-                "cargo::error=metal shader compilation failed:\n{}",
-                String::from_utf8_lossy(&output.stderr)
-            );
-            process::exit(1);
-        }
-
-        let output = Command::new("xcrun")
-            .args(["-sdk", "macosx", "metallib"])
-            .arg(air_output_path)
-            .arg("-o")
-            .arg(metallib_output_path)
-            .output()
-            .unwrap();
-
-        if !output.status.success() {
-            println!(
-                "cargo::error=metallib compilation failed:\n{}",
-                String::from_utf8_lossy(&output.stderr)
-            );
-            process::exit(1);
-        }
-    }
-}
-
-#[cfg(target_os = "windows")]
-mod windows {
-    use std::{
-        ffi::OsString,
-        fs,
-        io::Write,
-        path::{Path, PathBuf},
-        process::{self, Command},
-    };
-
-    pub(super) fn build() {
-        // Compile HLSL shaders
-        #[cfg(not(debug_assertions))]
-        compile_shaders();
-
-        // Embed the Windows manifest and resource file
-        #[cfg(feature = "windows-manifest")]
-        embed_resource();
-    }
-
-    #[cfg(feature = "windows-manifest")]
-    fn embed_resource() {
-        let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml");
-        let rc_file = std::path::Path::new("resources/windows/gpui.rc");
-        println!("cargo:rerun-if-changed={}", manifest.display());
-        println!("cargo:rerun-if-changed={}", rc_file.display());
-        embed_resource::compile(rc_file, embed_resource::NONE)
-            .manifest_required()
-            .unwrap();
-    }
-
-    /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
-    fn compile_shaders() {
-        let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
-            .join("src/platform/windows/shaders.hlsl");
-        let out_dir = std::env::var("OUT_DIR").unwrap();
-
-        println!("cargo:rerun-if-changed={}", shader_path.display());
-
-        // Check if fxc.exe is available
-        let fxc_path = find_fxc_compiler();
-
-        // Define all modules
-        let modules = [
-            "quad",
-            "shadow",
-            "path_rasterization",
-            "path_sprite",
-            "underline",
-            "monochrome_sprite",
-            "subpixel_sprite",
-            "polychrome_sprite",
-        ];
-
-        let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir);
-        if Path::new(&rust_binding_path).exists() {
-            fs::remove_file(&rust_binding_path)
-                .expect("Failed to remove existing Rust binding file");
-        }
-        for module in modules {
-            compile_shader_for_module(
-                module,
-                &out_dir,
-                &fxc_path,
-                shader_path.to_str().unwrap(),
-                &rust_binding_path,
-            );
-        }
-
-        {
-            let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
-                .join("src/platform/windows/color_text_raster.hlsl");
-            compile_shader_for_module(
-                "emoji_rasterization",
-                &out_dir,
-                &fxc_path,
-                shader_path.to_str().unwrap(),
-                &rust_binding_path,
-            );
-        }
-    }
-
-    /// Locate `binary` in the newest installed Windows SDK.
-    pub fn find_latest_windows_sdk_binary(
-        binary: &str,
-    ) -> Result<Option<PathBuf>, Box<dyn std::error::Error>> {
-        let key = windows_registry::LOCAL_MACHINE
-            .open("SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0")?;
-
-        let install_folder: String = key.get_string("InstallationFolder")?; // "C:\Program Files (x86)\Windows Kits\10\"
-        let install_folder_bin = Path::new(&install_folder).join("bin");
-
-        let mut versions: Vec<_> = std::fs::read_dir(&install_folder_bin)?
-            .flatten()
-            .filter(|entry| entry.path().is_dir())
-            .filter_map(|entry| entry.file_name().into_string().ok())
-            .collect();
-
-        versions.sort_by_key(|s| {
-            s.split('.')
-                .filter_map(|p| p.parse().ok())
-                .collect::<Vec<u32>>()
-        });
-
-        let arch = match std::env::consts::ARCH {
-            "x86_64" => "x64",
-            "aarch64" => "arm64",
-            _ => Err(format!(
-                "Unsupported architecture: {}",
-                std::env::consts::ARCH
-            ))?,
-        };
-
-        if let Some(highest_version) = versions.last() {
-            return Ok(Some(
-                install_folder_bin
-                    .join(highest_version)
-                    .join(arch)
-                    .join(binary),
-            ));
-        }
-
-        Ok(None)
-    }
-
-    /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
-    fn find_fxc_compiler() -> String {
-        // Check environment variable
-        if let Ok(path) = std::env::var("GPUI_FXC_PATH")
-            && Path::new(&path).exists()
-        {
-            return path;
-        }
-
-        // Try to find in PATH
-        // NOTE: This has to be `where.exe` on Windows, not `where`, it must be ended with `.exe`
-        if let Ok(output) = std::process::Command::new("where.exe")
-            .arg("fxc.exe")
-            .output()
-            && output.status.success()
-        {
-            let path = String::from_utf8_lossy(&output.stdout);
-            return path.trim().to_string();
-        }
-
-        if let Ok(Some(path)) = find_latest_windows_sdk_binary("fxc.exe") {
-            return path.to_string_lossy().into_owned();
-        }
-
-        panic!("Failed to find fxc.exe");
-    }
-
-    fn compile_shader_for_module(
-        module: &str,
-        out_dir: &str,
-        fxc_path: &str,
-        shader_path: &str,
-        rust_binding_path: &str,
-    ) {
-        // Compile vertex shader
-        let output_file = format!("{}/{}_vs.h", out_dir, module);
-        let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
-        compile_shader_impl(
-            fxc_path,
-            &format!("{module}_vertex"),
-            &output_file,
-            &const_name,
-            shader_path,
-            "vs_4_1",
-        );
-        generate_rust_binding(&const_name, &output_file, rust_binding_path);
-
-        // Compile fragment shader
-        let output_file = format!("{}/{}_ps.h", out_dir, module);
-        let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
-        compile_shader_impl(
-            fxc_path,
-            &format!("{module}_fragment"),
-            &output_file,
-            &const_name,
-            shader_path,
-            "ps_4_1",
-        );
-        generate_rust_binding(&const_name, &output_file, rust_binding_path);
-    }
-
-    fn compile_shader_impl(
-        fxc_path: &str,
-        entry_point: &str,
-        output_path: &str,
-        var_name: &str,
-        shader_path: &str,
-        target: &str,
-    ) {
-        let output = Command::new(fxc_path)
-            .args([
-                "/T",
-                target,
-                "/E",
-                entry_point,
-                "/Fh",
-                output_path,
-                "/Vn",
-                var_name,
-                "/O3",
-                shader_path,
-            ])
-            .output();
-
-        match output {
-            Ok(result) => {
-                if result.status.success() {
-                    return;
-                }
-                println!(
-                    "cargo::error=Shader compilation failed for {}:\n{}",
-                    entry_point,
-                    String::from_utf8_lossy(&result.stderr)
-                );
-                process::exit(1);
-            }
-            Err(e) => {
-                println!("cargo::error=Failed to run fxc for {}: {}", entry_point, e);
-                process::exit(1);
-            }
-        }
-    }
 
-    fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) {
-        let header_content = fs::read_to_string(head_file).expect("Failed to read header file");
-        let const_definition = {
-            let global_var_start = header_content.find("const BYTE").unwrap();
-            let global_var = &header_content[global_var_start..];
-            let equal = global_var.find('=').unwrap();
-            global_var[equal + 1..].trim()
-        };
-        let rust_binding = format!(
-            "const {}: &[u8] = &{}\n",
-            const_name,
-            const_definition.replace('{', "[").replace('}', "]")
-        );
-        let mut options = fs::OpenOptions::new()
-            .create(true)
-            .append(true)
-            .open(output_path)
-            .expect("Failed to open Rust binding file");
-        options
-            .write_all(rust_binding.as_bytes())
-            .expect("Failed to write Rust binding file");
-    }
+#[cfg(all(target_os = "windows", feature = "windows-manifest"))]
+fn embed_resource() {
+    let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml");
+    let rc_file = std::path::Path::new("resources/windows/gpui.rc");
+    println!("cargo:rerun-if-changed={}", manifest.display());
+    println!("cargo:rerun-if-changed={}", rc_file.display());
+    embed_resource::compile(rc_file, embed_resource::NONE)
+        .manifest_required()
+        .unwrap();
 }

crates/gpui/examples/animation.rs 🔗

@@ -2,10 +2,11 @@ use std::time::Duration;
 
 use anyhow::Result;
 use gpui::{
-    Animation, AnimationExt as _, App, Application, AssetSource, Bounds, Context, SharedString,
-    Transformation, Window, WindowBounds, WindowOptions, bounce, div, ease_in_out, percentage,
-    prelude::*, px, size, svg,
+    Animation, AnimationExt as _, App, AssetSource, Bounds, Context, SharedString, Transformation,
+    Window, WindowBounds, WindowOptions, bounce, div, ease_in_out, percentage, prelude::*, px,
+    size, svg,
 };
+use gpui_platform::application;
 
 struct Assets {}
 
@@ -101,21 +102,19 @@ impl Render for AnimationExample {
 }
 
 fn main() {
-    Application::new()
-        .with_assets(Assets {})
-        .run(|cx: &mut App| {
-            let options = WindowOptions {
-                window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
-                    None,
-                    size(px(300.), px(300.)),
-                    cx,
-                ))),
-                ..Default::default()
-            };
-            cx.open_window(options, |_, cx| {
-                cx.activate(false);
-                cx.new(|_| AnimationExample {})
-            })
-            .unwrap();
-        });
+    application().with_assets(Assets {}).run(|cx: &mut App| {
+        let options = WindowOptions {
+            window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
+                None,
+                size(px(300.), px(300.)),
+                cx,
+            ))),
+            ..Default::default()
+        };
+        cx.open_window(options, |_, cx| {
+            cx.activate(false);
+            cx.new(|_| AnimationExample {})
+        })
+        .unwrap();
+    });
 }

crates/gpui/examples/data_table.rs 🔗

@@ -1,10 +1,11 @@
 use std::{ops::Range, rc::Rc, time::Duration};
 
 use gpui::{
-    App, Application, Bounds, Context, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point,
-    Render, SharedString, UniformListScrollHandle, Window, WindowBounds, WindowOptions, canvas,
-    div, point, prelude::*, px, rgb, size, uniform_list,
+    App, Bounds, Context, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Render,
+    SharedString, UniformListScrollHandle, Window, WindowBounds, WindowOptions, canvas, div, point,
+    prelude::*, px, rgb, size, uniform_list,
 };
+use gpui_platform::application;
 
 const TOTAL_ITEMS: usize = 10000;
 const SCROLLBAR_THUMB_WIDTH: Pixels = px(8.);
@@ -447,7 +448,7 @@ impl Render for DataTable {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         cx.open_window(
             WindowOptions {
                 focus: true,

crates/gpui/examples/drag_drop.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Bounds, Context, Half, Hsla, Pixels, Point, Window, WindowBounds,
-    WindowOptions, div, prelude::*, px, rgb, size,
+    App, Bounds, Context, Half, Hsla, Pixels, Point, Window, WindowBounds, WindowOptions, div,
+    prelude::*, px, rgb, size,
 };
+use gpui_platform::application;
 
 #[derive(Clone, Copy)]
 struct DragInfo {
@@ -121,7 +122,7 @@ impl Render for DragDrop {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(800.), px(600.0)), cx);
         cx.open_window(
             WindowOptions {

crates/gpui/examples/focus_visible.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString,
-    Stateful, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, size,
+    App, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString, Stateful, Window,
+    WindowBounds, WindowOptions, actions, div, prelude::*, px, size,
 };
+use gpui_platform::application;
 
 actions!(example, [Tab, TabPrev, Quit]);
 
@@ -192,7 +193,7 @@ impl Render for Example {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         cx.bind_keys([
             KeyBinding::new("tab", Tab, None),
             KeyBinding::new("shift-tab", TabPrev, None),

crates/gpui/examples/gif_viewer.rs 🔗

@@ -1,4 +1,5 @@
-use gpui::{App, Application, Context, Render, Window, WindowOptions, div, img, prelude::*};
+use gpui::{App, Context, Render, Window, WindowOptions, div, img, prelude::*};
+use gpui_platform::application;
 use std::path::PathBuf;
 
 struct GifViewer {
@@ -24,7 +25,7 @@ impl Render for GifViewer {
 
 fn main() {
     env_logger::init();
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let gif_path =
             PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/image/black-cat-typing.gif");
 

crates/gpui/examples/gradient.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Bounds, ColorSpace, Context, Half, Render, Window, WindowOptions, canvas,
-    div, linear_color_stop, linear_gradient, point, prelude::*, px, size,
+    App, Bounds, ColorSpace, Context, Half, Render, Window, WindowOptions, canvas, div,
+    linear_color_stop, linear_gradient, point, prelude::*, px, size,
 };
+use gpui_platform::application;
 
 struct GradientViewer {
     color_space: ColorSpace,
@@ -243,7 +244,7 @@ impl Render for GradientViewer {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         cx.open_window(
             WindowOptions {
                 focus: true,

crates/gpui/examples/grid_layout.rs 🔗

@@ -1,7 +1,7 @@
 use gpui::{
-    App, Application, Bounds, Context, Hsla, Window, WindowBounds, WindowOptions, div, prelude::*,
-    px, rgb, size,
+    App, Bounds, Context, Hsla, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size,
 };
+use gpui_platform::application;
 
 // https://en.wikipedia.org/wiki/Holy_grail_(web_design)
 struct HolyGrailExample {}
@@ -65,7 +65,7 @@ impl Render for HolyGrailExample {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
         cx.open_window(
             WindowOptions {

crates/gpui/examples/hello_world.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Bounds, Context, SharedString, Window, WindowBounds, WindowOptions, div,
-    prelude::*, px, rgb, size,
+    App, Bounds, Context, SharedString, Window, WindowBounds, WindowOptions, div, prelude::*, px,
+    rgb, size,
 };
+use gpui_platform::application;
 
 struct HelloWorld {
     text: SharedString,
@@ -87,7 +88,7 @@ impl Render for HelloWorld {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
         cx.open_window(
             WindowOptions {

crates/gpui/examples/image/image.rs 🔗

@@ -4,10 +4,11 @@ use std::sync::Arc;
 
 use anyhow::Result;
 use gpui::{
-    App, AppContext, Application, AssetSource, Bounds, Context, ImageSource, KeyBinding, Menu,
-    MenuItem, Point, SharedString, SharedUri, TitlebarOptions, Window, WindowBounds, WindowOptions,
-    actions, div, img, prelude::*, px, rgb, size,
+    App, AppContext, AssetSource, Bounds, Context, ImageSource, KeyBinding, Menu, MenuItem, Point,
+    SharedString, SharedUri, TitlebarOptions, Window, WindowBounds, WindowOptions, actions, div,
+    img, prelude::*, px, rgb, size,
 };
+use gpui_platform::application;
 use reqwest_client::ReqwestClient;
 
 struct Assets {
@@ -150,7 +151,7 @@ fn main() {
 
     let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
 
-    Application::new()
+    application()
         .with_assets(Assets {
             base: manifest_dir.join("examples"),
         })

crates/gpui/examples/image_gallery.rs 🔗

@@ -1,10 +1,11 @@
 use futures::FutureExt;
 use gpui::{
-    App, AppContext, Application, Asset as _, AssetLogger, Bounds, ClickEvent, Context, ElementId,
-    Entity, ImageAssetLoader, ImageCache, ImageCacheProvider, KeyBinding, Menu, MenuItem,
+    App, AppContext, Asset as _, AssetLogger, Bounds, ClickEvent, Context, ElementId, Entity,
+    ImageAssetLoader, ImageCache, ImageCacheProvider, KeyBinding, Menu, MenuItem,
     RetainAllImageCache, SharedString, TitlebarOptions, Window, WindowBounds, WindowOptions,
     actions, div, hash, image_cache, img, prelude::*, px, rgb, size,
 };
+use gpui_platform::application;
 use reqwest_client::ReqwestClient;
 use std::{collections::HashMap, sync::Arc};
 
@@ -247,7 +248,7 @@ actions!(image, [Quit]);
 fn main() {
     env_logger::init();
 
-    Application::new().run(move |cx: &mut App| {
+    application().run(move |cx: &mut App| {
         let http_client = ReqwestClient::user_agent("gpui example").unwrap();
         cx.set_http_client(Arc::new(http_client));
 

crates/gpui/examples/image_loading.rs 🔗

@@ -1,11 +1,12 @@
 use std::{path::Path, sync::Arc, time::Duration};
 
 use gpui::{
-    Animation, AnimationExt, App, Application, Asset, AssetLogger, AssetSource, Bounds, Context,
-    Hsla, ImageAssetLoader, ImageCacheError, ImgResourceLoader, LOADING_DELAY, Length, RenderImage,
+    Animation, AnimationExt, App, Asset, AssetLogger, AssetSource, Bounds, Context, Hsla,
+    ImageAssetLoader, ImageCacheError, ImgResourceLoader, LOADING_DELAY, Length, RenderImage,
     Resource, SharedString, Window, WindowBounds, WindowOptions, black, div, img, prelude::*,
     pulsating_between, px, red, size,
 };
+use gpui_platform::application;
 
 struct Assets {}
 
@@ -193,21 +194,19 @@ impl Render for ImageLoadingExample {
 
 fn main() {
     env_logger::init();
-    Application::new()
-        .with_assets(Assets {})
-        .run(|cx: &mut App| {
-            let options = WindowOptions {
-                window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
-                    None,
-                    size(px(300.), px(300.)),
-                    cx,
-                ))),
-                ..Default::default()
-            };
-            cx.open_window(options, |_, cx| {
-                cx.activate(false);
-                cx.new(|_| ImageLoadingExample {})
-            })
-            .unwrap();
-        });
+    application().with_assets(Assets {}).run(|cx: &mut App| {
+        let options = WindowOptions {
+            window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
+                None,
+                size(px(300.), px(300.)),
+                cx,
+            ))),
+            ..Default::default()
+        };
+        cx.open_window(options, |_, cx| {
+            cx.activate(false);
+            cx.new(|_| ImageLoadingExample {})
+        })
+        .unwrap();
+    });
 }

crates/gpui/examples/input.rs 🔗

@@ -1,13 +1,14 @@
 use std::ops::Range;
 
 use gpui::{
-    App, Application, Bounds, ClipboardItem, Context, CursorStyle, ElementId, ElementInputHandler,
-    Entity, EntityInputHandler, FocusHandle, Focusable, GlobalElementId, KeyBinding, Keystroke,
-    LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point,
+    App, Bounds, ClipboardItem, Context, CursorStyle, ElementId, ElementInputHandler, Entity,
+    EntityInputHandler, FocusHandle, Focusable, GlobalElementId, KeyBinding, Keystroke, LayoutId,
+    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point,
     ShapedLine, SharedString, Style, TextRun, UTF16Selection, UnderlineStyle, Window, WindowBounds,
     WindowOptions, actions, black, div, fill, hsla, opaque_grey, point, prelude::*, px, relative,
     rgb, rgba, size, white, yellow,
 };
+use gpui_platform::application;
 use unicode_segmentation::*;
 
 actions!(
@@ -682,7 +683,7 @@ impl Render for InputExample {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
         cx.bind_keys([
             KeyBinding::new("backspace", Backspace, None),

crates/gpui/examples/layer_shell.rs 🔗

@@ -11,10 +11,10 @@ mod example {
     use std::time::{Duration, SystemTime, UNIX_EPOCH};
 
     use gpui::{
-        App, Application, Bounds, Context, FontWeight, Size, Window, WindowBackgroundAppearance,
-        WindowBounds, WindowKind, WindowOptions, div, layer_shell::*, point, prelude::*, px, rems,
-        rgba, white,
+        App, Bounds, Context, FontWeight, Size, Window, WindowBackgroundAppearance, WindowBounds,
+        WindowKind, WindowOptions, div, layer_shell::*, point, prelude::*, px, rems, rgba, white,
     };
+    use gpui_platform::application;
 
     struct LayerShellExample;
 
@@ -60,7 +60,7 @@ mod example {
     }
 
     pub fn main() {
-        Application::new().run(|cx: &mut App| {
+        application().run(|cx: &mut App| {
             cx.open_window(
                 WindowOptions {
                     titlebar: None,

crates/gpui/examples/mouse_pressure.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Bounds, Context, MousePressureEvent, PressureStage, Window, WindowBounds,
-    WindowOptions, div, prelude::*, px, rgb, size,
+    App, Bounds, Context, MousePressureEvent, PressureStage, Window, WindowBounds, WindowOptions,
+    div, prelude::*, px, rgb, size,
 };
+use gpui_platform::application;
 
 struct MousePressureExample {
     pressure_stage: PressureStage,
@@ -44,7 +45,7 @@ impl MousePressureExample {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
 
         cx.open_window(

crates/gpui/examples/on_window_close_quit.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Bounds, Context, FocusHandle, KeyBinding, Window, WindowBounds,
-    WindowOptions, actions, div, prelude::*, px, rgb, size,
+    App, Bounds, Context, FocusHandle, KeyBinding, Window, WindowBounds, WindowOptions, actions,
+    div, prelude::*, px, rgb, size,
 };
+use gpui_platform::application;
 
 actions!(example, [CloseWindow]);
 
@@ -35,7 +36,7 @@ impl Render for ExampleWindow {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let mut bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
 
         cx.bind_keys([KeyBinding::new("cmd-w", CloseWindow, None)]);

crates/gpui/examples/opacity.rs 🔗

@@ -2,9 +2,10 @@ use std::{fs, path::PathBuf};
 
 use anyhow::Result;
 use gpui::{
-    App, Application, AssetSource, Bounds, BoxShadow, ClickEvent, Context, SharedString, Task,
-    Window, WindowBounds, WindowOptions, div, hsla, img, point, prelude::*, px, rgb, size, svg,
+    App, AssetSource, Bounds, BoxShadow, ClickEvent, Context, SharedString, Task, Window,
+    WindowBounds, WindowOptions, div, hsla, img, point, prelude::*, px, rgb, size, svg,
 };
+use gpui_platform::application;
 
 struct Assets {
     base: PathBuf,
@@ -156,7 +157,7 @@ impl Render for HelloWorld {
 }
 
 fn main() {
-    Application::new()
+    application()
         .with_assets(Assets {
             base: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples"),
         })

crates/gpui/examples/ownership_post.rs 🔗

@@ -1,4 +1,5 @@
-use gpui::{App, Application, Context, Entity, EventEmitter, prelude::*};
+use gpui::{App, Context, Entity, EventEmitter, prelude::*};
+use gpui_platform::application;
 
 struct Counter {
     count: usize,
@@ -11,7 +12,7 @@ struct Change {
 impl EventEmitter<Change> for Counter {}
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
         let subscriber = cx.new(|cx: &mut Context<Counter>| {
             cx.subscribe(&counter, |subscriber, _emitter, event, _cx| {

crates/gpui/examples/painting.rs 🔗

@@ -1,8 +1,9 @@
 use gpui::{
-    Application, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder,
-    PathStyle, Pixels, Point, Render, StrokeOptions, Window, WindowOptions, canvas, div,
-    linear_color_stop, linear_gradient, point, prelude::*, px, quad, rgb, size,
+    Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder, PathStyle, Pixels,
+    Point, Render, StrokeOptions, Window, WindowOptions, canvas, div, linear_color_stop,
+    linear_gradient, point, prelude::*, px, quad, rgb, size,
 };
+use gpui_platform::application;
 
 struct PaintingViewer {
     default_lines: Vec<(Path<Pixels>, Background)>,
@@ -445,7 +446,7 @@ impl Render for PaintingViewer {
 }
 
 fn main() {
-    Application::new().run(|cx| {
+    application().run(|cx| {
         cx.open_window(
             WindowOptions {
                 focus: true,

crates/gpui/examples/paths_bench.rs 🔗

@@ -1,8 +1,9 @@
 use gpui::{
-    Application, Background, Bounds, ColorSpace, Context, Path, PathBuilder, Pixels, Render,
-    TitlebarOptions, Window, WindowBounds, WindowOptions, canvas, div, linear_color_stop,
-    linear_gradient, point, prelude::*, px, rgb, size,
+    Background, Bounds, ColorSpace, Context, Path, PathBuilder, Pixels, Render, TitlebarOptions,
+    Window, WindowBounds, WindowOptions, canvas, div, linear_color_stop, linear_gradient, point,
+    prelude::*, px, rgb, size,
 };
+use gpui_platform::application;
 
 const DEFAULT_WINDOW_WIDTH: Pixels = px(1024.0);
 const DEFAULT_WINDOW_HEIGHT: Pixels = px(768.0);
@@ -69,7 +70,7 @@ impl Render for PaintingViewer {
 }
 
 fn main() {
-    Application::new().run(|cx| {
+    application().run(|cx| {
         cx.open_window(
             WindowOptions {
                 titlebar: Some(TitlebarOptions {

crates/gpui/examples/pattern.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, AppContext, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div,
-    linear_color_stop, linear_gradient, pattern_slash, prelude::*, px, rgb, size,
+    App, AppContext, Bounds, Context, Window, WindowBounds, WindowOptions, div, linear_color_stop,
+    linear_gradient, pattern_slash, prelude::*, px, rgb, size,
 };
+use gpui_platform::application;
 
 struct PatternExample;
 
@@ -99,7 +100,7 @@ impl Render for PatternExample {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
         cx.open_window(
             WindowOptions {

crates/gpui/examples/popover.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Context, Corner, Div, Hsla, Stateful, Window, WindowOptions, anchored,
-    deferred, div, prelude::*, px,
+    App, Context, Corner, Div, Hsla, Stateful, Window, WindowOptions, anchored, deferred, div,
+    prelude::*, px,
 };
+use gpui_platform::application;
 
 /// An example show use deferred to create a floating layers.
 struct HelloWorld {
@@ -161,7 +162,7 @@ impl Render for HelloWorld {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         cx.open_window(WindowOptions::default(), |_, cx| {
             cx.new(|_| HelloWorld {
                 open: false,

crates/gpui/examples/scrollable.rs 🔗

@@ -1,7 +1,5 @@
-use gpui::{
-    App, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px,
-    size,
-};
+use gpui::{App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, size};
+use gpui_platform::application;
 
 struct Scrollable {}
 
@@ -45,7 +43,7 @@ impl Render for Scrollable {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
         cx.open_window(
             WindowOptions {

crates/gpui/examples/set_menus.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Context, Global, Menu, MenuItem, SharedString, SystemMenuType, Window,
-    WindowOptions, actions, div, prelude::*, rgb,
+    App, Context, Global, Menu, MenuItem, SharedString, SystemMenuType, Window, WindowOptions,
+    actions, div, prelude::*, rgb,
 };
+use gpui_platform::application;
 
 struct SetMenus;
 
@@ -20,7 +21,7 @@ impl Render for SetMenus {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         cx.set_global(AppState::new());
 
         // Bring the menu bar to the foreground (so you can see the menu bar)

crates/gpui/examples/shadow.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Bounds, BoxShadow, Context, Div, SharedString, Window, WindowBounds,
-    WindowOptions, div, hsla, point, prelude::*, px, relative, rgb, size,
+    App, Bounds, BoxShadow, Context, Div, SharedString, Window, WindowBounds, WindowOptions, div,
+    hsla, point, prelude::*, px, relative, rgb, size,
 };
+use gpui_platform::application;
 
 struct Shadow {}
 
@@ -569,7 +570,7 @@ impl Render for Shadow {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(1000.0), px(800.0)), cx);
         cx.open_window(
             WindowOptions {

crates/gpui/examples/svg/svg.rs 🔗

@@ -3,9 +3,10 @@ use std::path::PathBuf;
 
 use anyhow::Result;
 use gpui::{
-    App, Application, AssetSource, Bounds, Context, SharedString, Window, WindowBounds,
-    WindowOptions, div, prelude::*, px, rgb, size, svg,
+    App, AssetSource, Bounds, Context, SharedString, Window, WindowBounds, WindowOptions, div,
+    prelude::*, px, rgb, size, svg,
 };
+use gpui_platform::application;
 
 struct Assets {
     base: PathBuf,
@@ -68,7 +69,7 @@ impl Render for SvgExample {
 }
 
 fn main() {
-    Application::new()
+    application()
         .with_assets(Assets {
             base: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples"),
         })

crates/gpui/examples/tab_stop.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString,
-    Stateful, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, size,
+    App, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString, Stateful, Window,
+    WindowBounds, WindowOptions, actions, div, prelude::*, px, size,
 };
+use gpui_platform::application;
 
 actions!(example, [Tab, TabPrev]);
 
@@ -178,7 +179,7 @@ impl Render for Example {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         cx.bind_keys([
             KeyBinding::new("tab", Tab, None),
             KeyBinding::new("shift-tab", TabPrev, None),

crates/gpui/examples/testing.rs 🔗

@@ -7,9 +7,10 @@
 //! Run tests:   cargo test -p gpui --example testing --features test-support
 
 use gpui::{
-    App, Application, Bounds, Context, FocusHandle, Focusable, Render, Task, Window, WindowBounds,
+    App, Bounds, Context, FocusHandle, Focusable, Render, Task, Window, WindowBounds,
     WindowOptions, actions, div, prelude::*, px, rgb, size,
 };
+use gpui_platform::application;
 
 actions!(counter, [Increment, Decrement]);
 
@@ -176,7 +177,7 @@ impl Render for Counter {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         cx.bind_keys([
             gpui::KeyBinding::new("up", Increment, Some("Counter")),
             gpui::KeyBinding::new("down", Decrement, Some("Counter")),

crates/gpui/examples/text.rs 🔗

@@ -4,10 +4,11 @@ use std::{
 };
 
 use gpui::{
-    AbsoluteLength, App, Application, Context, DefiniteLength, ElementId, Global, Hsla, Menu,
-    SharedString, TextStyle, TitlebarOptions, Window, WindowBounds, WindowOptions, bounds,
-    colors::DefaultColors, div, point, prelude::*, px, relative, rgb, size,
+    AbsoluteLength, App, Context, DefiniteLength, ElementId, Global, Hsla, Menu, SharedString,
+    TextStyle, TitlebarOptions, Window, WindowBounds, WindowOptions, bounds, colors::DefaultColors,
+    div, point, prelude::*, px, relative, rgb, size,
 };
+use gpui_platform::application;
 use std::iter;
 
 #[derive(Clone, Debug)]
@@ -298,7 +299,7 @@ impl Render for TextExample {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         cx.set_menus(vec![Menu {
             name: "GPUI Typography".into(),
             items: vec![],

crates/gpui/examples/text_layout.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Bounds, Context, FontStyle, FontWeight, StyledText, Window, WindowBounds,
-    WindowOptions, div, prelude::*, px, size,
+    App, Bounds, Context, FontStyle, FontWeight, StyledText, Window, WindowBounds, WindowOptions,
+    div, prelude::*, px, size,
 };
+use gpui_platform::application;
 
 struct HelloWorld {}
 
@@ -81,7 +82,7 @@ impl Render for HelloWorld {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx);
         cx.open_window(
             WindowOptions {

crates/gpui/examples/text_wrapper.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Bounds, Context, TextOverflow, Window, WindowBounds, WindowOptions, div,
-    prelude::*, px, size,
+    App, Bounds, Context, TextOverflow, Window, WindowBounds, WindowOptions, div, prelude::*, px,
+    size,
 };
+use gpui_platform::application;
 
 struct HelloWorld {}
 
@@ -108,7 +109,7 @@ impl Render for HelloWorld {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx);
         cx.open_window(
             WindowOptions {

crates/gpui/examples/tree.rs 🔗

@@ -2,10 +2,8 @@
 //! handle deep hierarchies (even though it cannot just yet!).
 use std::sync::LazyLock;
 
-use gpui::{
-    App, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px,
-    size,
-};
+use gpui::{App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, size};
+use gpui_platform::application;
 
 struct Tree {}
 
@@ -32,7 +30,7 @@ impl Render for Tree {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
         cx.open_window(
             WindowOptions {

crates/gpui/examples/uniform_list.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px,
-    rgb, size, uniform_list,
+    App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size,
+    uniform_list,
 };
+use gpui_platform::application;
 
 struct UniformListExample {}
 
@@ -36,7 +37,7 @@ impl Render for UniformListExample {
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
         cx.open_window(
             WindowOptions {

crates/gpui/examples/window.rs 🔗

@@ -1,7 +1,8 @@
 use gpui::{
-    App, Application, Bounds, Context, KeyBinding, PromptButton, PromptLevel, Window, WindowBounds,
-    WindowKind, WindowOptions, actions, div, prelude::*, px, rgb, size,
+    App, Bounds, Context, KeyBinding, PromptButton, PromptLevel, Window, WindowBounds, WindowKind,
+    WindowOptions, actions, div, prelude::*, px, rgb, size,
 };
+use gpui_platform::application;
 
 struct SubWindow {
     custom_titlebar: bool,
@@ -306,7 +307,7 @@ impl Render for WindowDemo {
 actions!(window, [Quit]);
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx);
 
         cx.open_window(

crates/gpui/examples/window_positioning.rs 🔗

@@ -1,8 +1,9 @@
 use gpui::{
-    App, Application, Bounds, Context, DisplayId, Hsla, Pixels, SharedString, Size, Window,
+    App, Bounds, Context, DisplayId, Hsla, Pixels, SharedString, Size, Window,
     WindowBackgroundAppearance, WindowBounds, WindowKind, WindowOptions, div, point, prelude::*,
     px, rgb,
 };
+use gpui_platform::application;
 
 struct WindowContent {
     text: SharedString,
@@ -68,7 +69,7 @@ fn build_window_options(display_id: DisplayId, bounds: Bounds<Pixels>) -> Window
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         // Create several new windows, positioned in the top right corner of each screen
         let size = Size {
             width: px(350.),

crates/gpui/examples/window_shadow.rs 🔗

@@ -1,9 +1,10 @@
 use gpui::{
-    App, Application, Bounds, Context, CursorStyle, Decorations, HitboxBehavior, Hsla, MouseButton,
-    Pixels, Point, ResizeEdge, Size, Window, WindowBackgroundAppearance, WindowBounds,
-    WindowDecorations, WindowOptions, black, canvas, div, green, point, prelude::*, px, rgb, size,
-    transparent_black, white,
+    App, Bounds, Context, CursorStyle, Decorations, HitboxBehavior, Hsla, MouseButton, Pixels,
+    Point, ResizeEdge, Size, Window, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
+    WindowOptions, black, canvas, div, green, point, prelude::*, px, rgb, size, transparent_black,
+    white,
 };
+use gpui_platform::application;
 
 struct WindowShadow {}
 
@@ -203,7 +204,7 @@ fn resize_edge(pos: Point<Pixels>, shadow_size: Pixels, size: Size<Pixels>) -> O
 }
 
 fn main() {
-    Application::new().run(|cx: &mut App| {
+    application().run(|cx: &mut App| {
         let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
         cx.open_window(
             WindowOptions {

crates/gpui/src/_ownership_and_data_flow.rs 🔗

@@ -7,7 +7,7 @@
 //! # struct Counter {
 //! #     count: usize,
 //! # }
-//! Application::new().run(|cx: &mut App| {
+//! gpui_platform::application().run(|cx: &mut App| {
 //!     let _counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
 //!     // ...
 //! });
@@ -22,7 +22,7 @@
 //! # struct Counter {
 //! #     count: usize,
 //! # }
-//! Application::new().run(|cx: &mut App| {
+//! gpui_platform::application().run(|cx: &mut App| {
 //!     let counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
 //!     // Call `update` to access the model's state.
 //!     counter.update(cx, |counter: &mut Counter, _cx: &mut Context<Counter>| {
@@ -42,7 +42,7 @@
 //! # struct Counter {
 //! #     count: usize,
 //! # }
-//! Application::new().run(|cx: &mut App| {
+//! gpui_platform::application().run(|cx: &mut App| {
 //!     let counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
 //!     counter.update(cx, |counter, cx| {
 //!         counter.count += 1;
@@ -60,7 +60,7 @@
 //!  # struct Counter {
 //!  #     count: usize,
 //!  # }
-//!  Application::new().run(|cx: &mut App| {
+//!  gpui_platform::application().run(|cx: &mut App| {
 //!      let first_counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
 //!
 //!      let second_counter = cx.new(|cx: &mut Context<Counter>| {
@@ -114,7 +114,7 @@
 //! #     increment: usize,
 //! # }
 //! # impl EventEmitter<CounterChangeEvent> for Counter {}
-//! Application::new().run(|cx: &mut App| {
+//! gpui_platform::application().run(|cx: &mut App| {
 //!     let first_counter: Entity<Counter> = cx.new(|_cx| Counter { count: 0 });
 //!
 //!     let second_counter = cx.new(|cx: &mut Context<Counter>| {

crates/gpui/src/app.rs 🔗

@@ -46,7 +46,7 @@ use crate::{
     SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextRenderingMode, TextSystem,
     ThermalState, Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator,
     colors::{Colors, GlobalColors},
-    current_platform, hash, init_app_menus,
+    hash, init_app_menus,
 };
 
 mod async_context;
@@ -132,25 +132,10 @@ pub struct Application(Rc<AppCell>);
 /// Represents an application before it is fully launched. Once your app is
 /// configured, you'll start the app with `App::run`.
 impl Application {
-    /// Builds an app with the given asset source.
-    #[allow(clippy::new_without_default)]
-    pub fn new() -> Self {
-        #[cfg(any(test, feature = "test-support"))]
-        log::info!("GPUI was compiled in test mode");
-
-        Self(App::new_app(
-            current_platform(false),
-            Arc::new(()),
-            Arc::new(NullHttpClient),
-        ))
-    }
-
-    /// Build an app in headless mode. This prevents opening windows,
-    /// but makes it possible to run an application in an context like
-    /// SSH, where GUI applications are not allowed.
-    pub fn headless() -> Self {
+    /// Builds an app with a caller-provided platform implementation.
+    pub fn with_platform(platform: Rc<dyn Platform>) -> Self {
         Self(App::new_app(
-            current_platform(true),
+            platform,
             Arc::new(()),
             Arc::new(NullHttpClient),
         ))

crates/gpui/src/app/visual_test_context.rs 🔗

@@ -42,15 +42,18 @@ impl VisualTestAppContext {
     ///
     /// Note: This uses a no-op asset source, so SVG icons won't render.
     /// Use `with_asset_source` to provide real assets for icon rendering.
-    pub fn new() -> Self {
-        Self::with_asset_source(Arc::new(()))
+    pub fn new(platform: Rc<dyn Platform>) -> Self {
+        Self::with_asset_source(platform, Arc::new(()))
     }
 
     /// Creates a new `VisualTestAppContext` with a custom asset source.
     ///
     /// Use this when you need SVG icons to render properly in visual tests.
     /// Pass the real `Assets` struct to enable icon rendering.
-    pub fn with_asset_source(asset_source: Arc<dyn AssetSource>) -> Self {
+    pub fn with_asset_source(
+        platform: Rc<dyn Platform>,
+        asset_source: Arc<dyn AssetSource>,
+    ) -> Self {
         // Use a seeded RNG for deterministic behavior
         let seed = std::env::var("SEED")
             .ok()
@@ -59,7 +62,7 @@ impl VisualTestAppContext {
 
         // Create a visual test platform that combines real Mac rendering
         // with controllable TestDispatcher for deterministic task scheduling
-        let platform = Rc::new(VisualTestPlatform::new(seed));
+        let platform = Rc::new(VisualTestPlatform::new(platform, seed));
 
         // Get the dispatcher and executors from the platform
         let dispatcher = platform.dispatcher().clone();
@@ -391,12 +394,6 @@ impl VisualTestAppContext {
     }
 }
 
-impl Default for VisualTestAppContext {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
 impl AppContext for VisualTestAppContext {
     fn new<T: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Entity<T> {
         let mut app = self.app.borrow_mut();
@@ -476,112 +473,3 @@ impl AppContext for VisualTestAppContext {
         callback(app.global::<G>(), &app)
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::Empty;
-    use std::cell::RefCell;
-
-    // Note: All VisualTestAppContext tests are ignored by default because they require
-    // the macOS main thread. Standard Rust tests run on worker threads, which causes
-    // SIGABRT when interacting with macOS AppKit/Cocoa APIs.
-    //
-    // To run these tests, use:
-    // cargo test -p gpui visual_test_context -- --ignored --test-threads=1
-
-    #[test]
-    #[ignore] // Requires macOS main thread
-    fn test_foreground_tasks_run_with_run_until_parked() {
-        let mut cx = VisualTestAppContext::new();
-
-        let task_ran = Rc::new(RefCell::new(false));
-
-        // Spawn a foreground task via the App's spawn method
-        // This should use our TestDispatcher, not the MacDispatcher
-        {
-            let task_ran = task_ran.clone();
-            cx.update(|cx| {
-                cx.spawn(async move |_| {
-                    *task_ran.borrow_mut() = true;
-                })
-                .detach();
-            });
-        }
-
-        // The task should not have run yet
-        assert!(!*task_ran.borrow());
-
-        // Run until parked should execute the foreground task
-        cx.run_until_parked();
-
-        // Now the task should have run
-        assert!(*task_ran.borrow());
-    }
-
-    #[test]
-    #[ignore] // Requires macOS main thread
-    fn test_advance_clock_triggers_delayed_tasks() {
-        let mut cx = VisualTestAppContext::new();
-
-        let task_ran = Rc::new(RefCell::new(false));
-
-        // Spawn a task that waits for a timer
-        {
-            let task_ran = task_ran.clone();
-            let executor = cx.background_executor.clone();
-            cx.update(|cx| {
-                cx.spawn(async move |_| {
-                    executor.timer(Duration::from_millis(500)).await;
-                    *task_ran.borrow_mut() = true;
-                })
-                .detach();
-            });
-        }
-
-        // Run until parked - the task should be waiting on the timer
-        cx.run_until_parked();
-        assert!(!*task_ran.borrow());
-
-        // Advance clock past the timer duration
-        cx.advance_clock(Duration::from_millis(600));
-
-        // Now the task should have completed
-        assert!(*task_ran.borrow());
-    }
-
-    #[test]
-    #[ignore] // Requires macOS main thread - window creation fails on test threads
-    fn test_window_spawn_uses_test_dispatcher() {
-        let mut cx = VisualTestAppContext::new();
-
-        let task_ran = Rc::new(RefCell::new(false));
-
-        let window = cx
-            .open_offscreen_window_default(|_, cx| cx.new(|_| Empty))
-            .expect("Failed to open window");
-
-        // Spawn a task via window.spawn - this is the critical test case
-        // for tooltip behavior, as tooltips use window.spawn for delayed show
-        {
-            let task_ran = task_ran.clone();
-            cx.update_window(window.into(), |_, window, cx| {
-                window
-                    .spawn(cx, async move |_| {
-                        *task_ran.borrow_mut() = true;
-                    })
-                    .detach();
-            })
-            .ok();
-        }
-
-        // The task should not have run yet
-        assert!(!*task_ran.borrow());
-
-        // Run until parked should execute the foreground task spawned via window
-        cx.run_until_parked();
-
-        // Now the task should have run
-        assert!(*task_ran.borrow());
-    }
-}

crates/gpui/src/assets.rs 🔗

@@ -33,9 +33,10 @@ impl AssetSource for () {
 pub struct ImageId(pub usize);
 
 #[derive(PartialEq, Eq, Hash, Clone)]
-pub(crate) struct RenderImageParams {
-    pub(crate) image_id: ImageId,
-    pub(crate) frame_index: usize,
+#[expect(missing_docs)]
+pub struct RenderImageParams {
+    pub image_id: ImageId,
+    pub frame_index: usize,
 }
 
 /// A cached and processed image, in BGRA format

crates/gpui/src/color.rs 🔗

@@ -23,7 +23,7 @@ pub fn rgba(hex: u32) -> Rgba {
 }
 
 /// Swap from RGBA with premultiplied alpha to BGRA
-pub(crate) fn swap_rgba_pa_to_bgra(color: &mut [u8]) {
+pub fn swap_rgba_pa_to_bgra(color: &mut [u8]) {
     color.swap(0, 2);
     if color[3] > 0 {
         let a = color[3] as f32 / 255.;

crates/gpui/src/geometry.rs 🔗

@@ -1592,7 +1592,7 @@ impl<T: Clone + Debug + Default + PartialEq + Display + Add<T, Output = T>> Disp
 
 impl Size<DevicePixels> {
     /// Converts the size from physical to logical pixels.
-    pub(crate) fn to_pixels(self, scale_factor: f32) -> Size<Pixels> {
+    pub fn to_pixels(self, scale_factor: f32) -> Size<Pixels> {
         size(
             px(self.width.0 as f32 / scale_factor),
             px(self.height.0 as f32 / scale_factor),
@@ -1602,7 +1602,7 @@ impl Size<DevicePixels> {
 
 impl Size<Pixels> {
     /// Converts the size from logical to physical pixels.
-    pub(crate) fn to_device_pixels(self, scale_factor: f32) -> Size<DevicePixels> {
+    pub fn to_device_pixels(self, scale_factor: f32) -> Size<DevicePixels> {
         size(
             DevicePixels((self.width.0 * scale_factor).round() as i32),
             DevicePixels((self.height.0 * scale_factor).round() as i32),
@@ -2683,6 +2683,11 @@ impl Pixels {
     /// The minimum value that can be represented by `Pixels`.
     pub const MIN: Pixels = Pixels(f32::MIN);
 
+    /// Returns the raw `f32` value of this `Pixels`.
+    pub fn as_f32(self) -> f32 {
+        self.0
+    }
+
     /// Floors the `Pixels` value to the nearest whole number.
     ///
     /// # Returns
@@ -2964,9 +2969,14 @@ impl From<usize> for DevicePixels {
 /// display resolutions.
 #[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, DivAssign, PartialEq)]
 #[repr(transparent)]
-pub struct ScaledPixels(pub(crate) f32);
+pub struct ScaledPixels(pub f32);
 
 impl ScaledPixels {
+    /// Returns the raw `f32` value of this `ScaledPixels`.
+    pub fn as_f32(self) -> f32 {
+        self.0
+    }
+
     /// Floors the `ScaledPixels` value to the nearest whole number.
     ///
     /// # Returns

crates/gpui/src/gpui.rs 🔗

@@ -5,7 +5,8 @@
 #![allow(unused_mut)] // False positives in platform specific code
 
 extern crate self as gpui;
-
+#[doc(hidden)]
+pub static GPUI_MANIFEST_DIR: &'static str = env!("CARGO_MANIFEST_DIR");
 #[macro_use]
 mod action;
 mod app;
@@ -32,9 +33,11 @@ mod keymap;
 mod path_builder;
 mod platform;
 pub mod prelude;
-mod profiler;
+/// Profiling utilities for task timing and thread performance tracking.
+pub mod profiler;
 #[cfg(any(target_os = "windows", target_os = "linux"))]
-mod queue;
+#[expect(missing_docs)]
+pub mod queue;
 mod scene;
 mod shared_string;
 mod shared_uri;
@@ -94,7 +97,7 @@ pub use path_builder::*;
 pub use platform::*;
 pub use profiler::*;
 #[cfg(any(target_os = "windows", target_os = "linux"))]
-pub(crate) use queue::{PriorityQueueReceiver, PriorityQueueSender};
+pub use queue::{PriorityQueueReceiver, PriorityQueueSender};
 pub use refineable::*;
 pub use scene::*;
 pub use shared_string::*;

crates/gpui/src/interactive.rs 🔗

@@ -557,7 +557,7 @@ impl Deref for MouseExitEvent {
 
 /// A collection of paths from the platform, such as from a file drop.
 #[derive(Debug, Clone, Default, Eq, PartialEq)]
-pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
+pub struct ExternalPaths(pub SmallVec<[PathBuf; 2]>);
 
 impl ExternalPaths {
     /// Convert this collection of paths into a slice.

crates/gpui/src/keymap/context.rs 🔗

@@ -262,7 +262,7 @@ impl KeyBindingContextPredicate {
 
     /// Eval a predicate against a set of contexts, arranged from lowest to highest.
     #[allow(unused)]
-    pub(crate) fn eval(&self, contexts: &[KeyContext]) -> bool {
+    pub fn eval(&self, contexts: &[KeyContext]) -> bool {
         self.eval_inner(contexts, contexts)
     }
 

crates/gpui/src/platform.rs 🔗

@@ -2,17 +2,9 @@ mod app_menu;
 mod keyboard;
 mod keystroke;
 
-#[cfg(any(target_os = "linux", target_os = "freebsd"))]
-mod linux;
-
-#[cfg(target_os = "macos")]
-mod mac;
-
-#[cfg(all(
-    any(target_os = "linux", target_os = "freebsd"),
-    any(feature = "wayland", feature = "x11")
-))]
-mod wgpu;
+#[cfg(all(target_os = "linux", feature = "wayland"))]
+#[expect(missing_docs)]
+pub mod layer_shell;
 
 #[cfg(any(test, feature = "test-support"))]
 mod test;
@@ -20,14 +12,21 @@ mod test;
 #[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
 mod visual_test;
 
-#[cfg(target_os = "windows")]
-mod windows;
-
 #[cfg(all(
     feature = "screen-capture",
     any(target_os = "windows", target_os = "linux", target_os = "freebsd",)
 ))]
-pub(crate) mod scap_screen_capture;
+pub mod scap_screen_capture;
+
+#[cfg(all(
+    any(target_os = "windows", target_os = "linux"),
+    feature = "screen-capture"
+))]
+pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
+#[cfg(not(feature = "screen-capture"))]
+pub(crate) type PlatformScreenCaptureFrame = ();
+#[cfg(all(target_os = "macos", feature = "screen-capture"))]
+pub(crate) type PlatformScreenCaptureFrame = core_video::image_buffer::CVImageBuffer;
 
 use crate::{
     Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds,
@@ -69,17 +68,8 @@ pub use app_menu::*;
 pub use keyboard::*;
 pub use keystroke::*;
 
-#[cfg(any(target_os = "linux", target_os = "freebsd"))]
-pub(crate) use linux::*;
-#[cfg(target_os = "macos")]
-pub(crate) use mac::*;
 #[cfg(any(test, feature = "test-support"))]
 pub(crate) use test::*;
-#[cfg(target_os = "windows")]
-pub(crate) use windows::*;
-
-#[cfg(all(target_os = "linux", feature = "wayland"))]
-pub use linux::layer_shell;
 
 #[cfg(any(test, feature = "test-support"))]
 pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream};
@@ -87,52 +77,8 @@ pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream}
 #[cfg(all(target_os = "macos", any(test, feature = "test-support")))]
 pub use visual_test::VisualTestPlatform;
 
-/// Returns a background executor for the current platform.
-pub fn background_executor() -> BackgroundExecutor {
-    current_platform(true).background_executor()
-}
-
-#[cfg(target_os = "macos")]
-pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
-    Rc::new(MacPlatform::new(headless))
-}
-
-#[cfg(any(target_os = "linux", target_os = "freebsd"))]
-pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
-    #[cfg(feature = "x11")]
-    use anyhow::Context as _;
-
-    if headless {
-        return Rc::new(HeadlessClient::new());
-    }
-
-    match guess_compositor() {
-        #[cfg(feature = "wayland")]
-        "Wayland" => Rc::new(WaylandClient::new()),
-
-        #[cfg(feature = "x11")]
-        "X11" => Rc::new(
-            X11Client::new()
-                .context("Failed to initialize X11 client.")
-                .unwrap(),
-        ),
-
-        "Headless" => Rc::new(HeadlessClient::new()),
-        _ => unreachable!(),
-    }
-}
-
-#[cfg(target_os = "windows")]
-pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
-    Rc::new(
-        WindowsPlatform::new(headless)
-            .inspect_err(|err| show_error("Failed to launch", err.to_string()))
-            .unwrap(),
-    )
-}
-
 /// Return which compositor we're guessing we'll use.
-/// Does not attempt to connect to the given compositor
+/// Does not attempt to connect to the given compositor.
 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 #[inline]
 pub fn guess_compositor() -> &'static str {
@@ -162,7 +108,8 @@ pub fn guess_compositor() -> &'static str {
     }
 }
 
-pub(crate) trait Platform: 'static {
+#[expect(missing_docs)]
+pub trait Platform: 'static {
     fn background_executor(&self) -> BackgroundExecutor;
     fn foreground_executor(&self) -> ForegroundExecutor;
     fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
@@ -182,16 +129,10 @@ pub(crate) trait Platform: 'static {
         None
     }
 
-    #[cfg(feature = "screen-capture")]
-    fn is_screen_capture_supported(&self) -> bool;
-    #[cfg(not(feature = "screen-capture"))]
     fn is_screen_capture_supported(&self) -> bool {
         false
     }
-    #[cfg(feature = "screen-capture")]
-    fn screen_capture_sources(&self)
-    -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>>;
-    #[cfg(not(feature = "screen-capture"))]
+
     fn screen_capture_sources(
         &self,
     ) -> oneshot::Receiver<anyhow::Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
@@ -370,6 +311,19 @@ pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
 #[derive(PartialEq, Eq, Hash, Copy, Clone)]
 pub struct DisplayId(pub(crate) u32);
 
+impl DisplayId {
+    /// Create a new `DisplayId` from a raw platform display identifier.
+    pub fn new(id: u32) -> Self {
+        Self(id)
+    }
+}
+
+impl From<u32> for DisplayId {
+    fn from(id: u32) -> Self {
+        Self(id)
+    }
+}
+
 impl From<DisplayId> for u32 {
     fn from(id: DisplayId) -> Self {
         id.0
@@ -482,13 +436,16 @@ impl Tiling {
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
-pub(crate) struct RequestFrameOptions {
-    pub(crate) require_presentation: bool,
-    /// Force refresh of all rendering states when true
-    pub(crate) force_render: bool,
+#[expect(missing_docs)]
+pub struct RequestFrameOptions {
+    /// Whether a presentation is required.
+    pub require_presentation: bool,
+    /// Force refresh of all rendering states when true.
+    pub force_render: bool,
 }
 
-pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
+#[expect(missing_docs)]
+pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
     fn bounds(&self) -> Bounds<Pixels>;
     fn is_maximized(&self) -> bool;
     fn window_bounds(&self) -> WindowBounds;
@@ -558,7 +515,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
     fn set_tabbing_identifier(&self, _identifier: Option<String>) {}
 
     #[cfg(target_os = "windows")]
-    fn get_raw_handle(&self) -> windows::HWND;
+    fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND;
 
     // Linux specific methods
     fn inner_window_bounds(&self) -> WindowBounds {
@@ -603,17 +560,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
 pub type RunnableVariant = Runnable<RunnableMeta>;
 
 #[doc(hidden)]
-pub struct TimerResolutionGuard {
-    cleanup: Option<Box<dyn FnOnce() + Send>>,
-}
-
-impl Drop for TimerResolutionGuard {
-    fn drop(&mut self) {
-        if let Some(cleanup) = self.cleanup.take() {
-            cleanup();
-        }
-    }
-}
+pub type TimerResolutionGuard = util::Deferred<Box<dyn FnOnce() + Send>>;
 
 /// This type is public so that our test macro can generate and use it, but it should not
 /// be considered part of our public API.
@@ -632,7 +579,7 @@ pub trait PlatformDispatcher: Send + Sync {
     }
 
     fn increase_timer_resolution(&self) -> TimerResolutionGuard {
-        TimerResolutionGuard { cleanup: None }
+        util::defer(Box::new(|| {}))
     }
 
     #[cfg(any(test, feature = "test-support"))]
@@ -641,7 +588,8 @@ pub trait PlatformDispatcher: Send + Sync {
     }
 }
 
-pub(crate) trait PlatformTextSystem: Send + Sync {
+#[expect(missing_docs)]
+pub trait PlatformTextSystem: Send + Sync {
     fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
     fn all_font_names(&self) -> Vec<String>;
     fn font_id(&self, descriptor: &Font) -> Result<FontId>;
@@ -660,8 +608,10 @@ pub(crate) trait PlatformTextSystem: Send + Sync {
     -> TextRenderingMode;
 }
 
-pub(crate) struct NoopTextSystem;
+#[expect(missing_docs)]
+pub struct NoopTextSystem;
 
+#[expect(missing_docs)]
 impl NoopTextSystem {
     #[allow(dead_code)]
     pub fn new() -> Self {
@@ -791,8 +741,9 @@ impl PlatformTextSystem for NoopTextSystem {
 // Adapted from https://github.com/microsoft/terminal/blob/1283c0f5b99a2961673249fa77c6b986efb5086c/src/renderer/atlas/dwrite.cpp
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT license.
+/// Compute gamma correction ratios for subpixel text rendering.
 #[allow(dead_code)]
-pub(crate) fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
+pub fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
     const GAMMA_INCORRECT_TARGET_RATIOS: [[f32; 4]; 13] = [
         [0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0, 0.0000 / 4.0], // gamma = 1.0
         [0.0166 / 4.0, -0.0807 / 4.0, 0.2227 / 4.0, -0.0751 / 4.0], // gamma = 1.1
@@ -824,7 +775,8 @@ pub(crate) fn get_gamma_correction_ratios(gamma: f32) -> [f32; 4] {
 }
 
 #[derive(PartialEq, Eq, Hash, Clone)]
-pub(crate) enum AtlasKey {
+#[expect(missing_docs)]
+pub enum AtlasKey {
     Glyph(RenderGlyphParams),
     Svg(RenderSvgParams),
     Image(RenderImageParams),
@@ -838,7 +790,8 @@ impl AtlasKey {
         ),
         allow(dead_code)
     )]
-    pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
+    /// Returns the texture kind for this atlas key.
+    pub fn texture_kind(&self) -> AtlasTextureKind {
         match self {
             AtlasKey::Glyph(params) => {
                 if params.is_emoji {
@@ -873,7 +826,8 @@ impl From<RenderImageParams> for AtlasKey {
     }
 }
 
-pub(crate) trait PlatformAtlas: Send + Sync {
+#[expect(missing_docs)]
+pub trait PlatformAtlas: Send + Sync {
     fn get_or_insert_with<'a>(
         &self,
         key: &AtlasKey,
@@ -882,9 +836,10 @@ pub(crate) trait PlatformAtlas: Send + Sync {
     fn remove(&self, key: &AtlasKey);
 }
 
-struct AtlasTextureList<T> {
-    textures: Vec<Option<T>>,
-    free_list: Vec<usize>,
+#[doc(hidden)]
+pub struct AtlasTextureList<T> {
+    pub textures: Vec<Option<T>>,
+    pub free_list: Vec<usize>,
 }
 
 impl<T> Default for AtlasTextureList<T> {
@@ -906,32 +861,40 @@ impl<T> ops::Index<usize> for AtlasTextureList<T> {
 
 impl<T> AtlasTextureList<T> {
     #[allow(unused)]
-    fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
+    pub fn drain(&mut self) -> std::vec::Drain<'_, Option<T>> {
         self.free_list.clear();
         self.textures.drain(..)
     }
 
     #[allow(dead_code)]
-    fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
+    pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
         self.textures.iter_mut().flatten()
     }
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 #[repr(C)]
-pub(crate) struct AtlasTile {
-    pub(crate) texture_id: AtlasTextureId,
-    pub(crate) tile_id: TileId,
-    pub(crate) padding: u32,
-    pub(crate) bounds: Bounds<DevicePixels>,
+#[expect(missing_docs)]
+pub struct AtlasTile {
+    /// The texture this tile belongs to.
+    pub texture_id: AtlasTextureId,
+    /// The unique ID of this tile within its texture.
+    pub tile_id: TileId,
+    /// Padding around the tile content in pixels.
+    pub padding: u32,
+    /// The bounds of this tile within the texture.
+    pub bounds: Bounds<DevicePixels>,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 #[repr(C)]
-pub(crate) struct AtlasTextureId {
+#[expect(missing_docs)]
+pub struct AtlasTextureId {
     // We use u32 instead of usize for Metal Shader Language compatibility
-    pub(crate) index: u32,
-    pub(crate) kind: AtlasTextureKind,
+    /// The index of this texture in the atlas.
+    pub index: u32,
+    /// The kind of content stored in this texture.
+    pub kind: AtlasTextureKind,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@@ -943,7 +906,8 @@ pub(crate) struct AtlasTextureId {
     ),
     allow(dead_code)
 )]
-pub(crate) enum AtlasTextureKind {
+#[expect(missing_docs)]
+pub enum AtlasTextureKind {
     Monochrome = 0,
     Polychrome = 1,
     Subpixel = 2,
@@ -951,7 +915,8 @@ pub(crate) enum AtlasTextureKind {
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 #[repr(C)]
-pub(crate) struct TileId(pub(crate) u32);
+#[expect(missing_docs)]
+pub struct TileId(pub u32);
 
 impl From<etagere::AllocId> for TileId {
     fn from(id: etagere::AllocId) -> Self {
@@ -965,11 +930,13 @@ impl From<TileId> for etagere::AllocId {
     }
 }
 
-pub(crate) struct PlatformInputHandler {
+#[expect(missing_docs)]
+pub struct PlatformInputHandler {
     cx: AsyncWindowContext,
     handler: Box<dyn InputHandler>,
 }
 
+#[expect(missing_docs)]
 #[cfg_attr(
     all(
         any(target_os = "linux", target_os = "freebsd"),
@@ -982,7 +949,7 @@ impl PlatformInputHandler {
         Self { cx, handler }
     }
 
-    fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
+    pub fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
         self.cx
             .update(|window, cx| {
                 self.handler
@@ -993,7 +960,7 @@ impl PlatformInputHandler {
     }
 
     #[cfg_attr(target_os = "windows", allow(dead_code))]
-    fn marked_text_range(&mut self) -> Option<Range<usize>> {
+    pub fn marked_text_range(&mut self) -> Option<Range<usize>> {
         self.cx
             .update(|window, cx| self.handler.marked_text_range(window, cx))
             .ok()
@@ -1004,7 +971,7 @@ impl PlatformInputHandler {
         any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
         allow(dead_code)
     )]
-    fn text_for_range(
+    pub fn text_for_range(
         &mut self,
         range_utf16: Range<usize>,
         adjusted: &mut Option<Range<usize>>,
@@ -1018,7 +985,7 @@ impl PlatformInputHandler {
             .flatten()
     }
 
-    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
+    pub fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
         self.cx
             .update(|window, cx| {
                 self.handler
@@ -1047,13 +1014,13 @@ impl PlatformInputHandler {
     }
 
     #[cfg_attr(target_os = "windows", allow(dead_code))]
-    fn unmark_text(&mut self) {
+    pub fn unmark_text(&mut self) {
         self.cx
             .update(|window, cx| self.handler.unmark_text(window, cx))
             .ok();
     }
 
-    fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
+    pub fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
         self.cx
             .update(|window, cx| self.handler.bounds_for_range(range_utf16, window, cx))
             .ok()
@@ -1061,11 +1028,11 @@ impl PlatformInputHandler {
     }
 
     #[allow(dead_code)]
-    fn apple_press_and_hold_enabled(&mut self) -> bool {
+    pub fn apple_press_and_hold_enabled(&mut self) -> bool {
         self.handler.apple_press_and_hold_enabled()
     }
 
-    pub(crate) fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
+    pub fn dispatch_input(&mut self, input: &str, window: &mut Window, cx: &mut App) {
         self.handler.replace_text_in_range(None, input, window, cx);
     }
 
@@ -1091,7 +1058,7 @@ impl PlatformInputHandler {
     }
 
     #[allow(dead_code)]
-    pub(crate) fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
+    pub fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
         self.handler.accepts_text_input(window, cx)
     }
 }
@@ -1268,7 +1235,8 @@ pub struct WindowOptions {
     ),
     allow(dead_code)
 )]
-pub(crate) struct WindowParams {
+#[expect(missing_docs)]
+pub struct WindowParams {
     pub bounds: Bounds<Pixels>,
 
     /// The titlebar configuration of the window
@@ -1523,8 +1491,9 @@ impl PromptButton {
         PromptButton::Cancel(label.into())
     }
 
+    /// Returns true if this button is a cancel button.
     #[allow(dead_code)]
-    pub(crate) fn is_cancel(&self) -> bool {
+    pub fn is_cancel(&self) -> bool {
         matches!(self, PromptButton::Cancel(_))
     }
 
@@ -1642,7 +1611,8 @@ pub enum CursorStyle {
 /// A clipboard item that should be copied to the clipboard
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct ClipboardItem {
-    entries: Vec<ClipboardEntry>,
+    /// The entries in this clipboard item.
+    pub entries: Vec<ClipboardEntry>,
 }
 
 /// Either a ClipboardString or a ClipboardImage
@@ -1842,7 +1812,7 @@ pub struct Image {
     /// The raw image bytes
     pub bytes: Vec<u8>,
     /// The unique ID for the image
-    id: u64,
+    pub id: u64,
 }
 
 impl Hash for Image {
@@ -1960,8 +1930,10 @@ impl Image {
 /// A clipboard item that should be copied to the clipboard
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct ClipboardString {
-    pub(crate) text: String,
-    pub(crate) metadata: Option<String>,
+    /// The text content.
+    pub text: String,
+    /// Optional metadata associated with this clipboard string.
+    pub metadata: Option<String>,
 }
 
 impl ClipboardString {
@@ -2001,7 +1973,8 @@ impl ClipboardString {
     }
 
     #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
-    pub(crate) fn text_hash(text: &str) -> u64 {
+    /// Compute a hash of the given text for clipboard change detection.
+    pub fn text_hash(text: &str) -> u64 {
         let mut hasher = SeaHasher::new();
         text.hash(&mut hasher);
         hasher.finish()

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

@@ -265,7 +265,8 @@ impl Keystroke {
 
 impl KeybindingKeystroke {
     #[cfg(target_os = "windows")]
-    pub(crate) fn new(inner: Keystroke, display_modifiers: Modifiers, display_key: String) -> Self {
+    #[expect(missing_docs)]
+    pub fn new(inner: Keystroke, display_modifiers: Modifiers, display_key: String) -> Self {
         KeybindingKeystroke {
             inner,
             display_modifiers,

crates/gpui/src/platform/linux/wayland/layer_shell.rs → crates/gpui/src/platform/layer_shell.rs 🔗

@@ -1,6 +1,5 @@
 use bitflags::bitflags;
 use thiserror::Error;
-use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
 
 use crate::Pixels;
 
@@ -22,17 +21,6 @@ pub enum Layer {
     Overlay,
 }
 
-impl From<Layer> for zwlr_layer_shell_v1::Layer {
-    fn from(layer: Layer) -> Self {
-        match layer {
-            Layer::Background => Self::Background,
-            Layer::Bottom => Self::Bottom,
-            Layer::Top => Self::Top,
-            Layer::Overlay => Self::Overlay,
-        }
-    }
-}
-
 bitflags! {
     /// Screen anchor point for layer_shell surfaces. These can be used in any combination, e.g.
     /// specifying `Anchor::LEFT | Anchor::RIGHT` will stretch the surface across the width of the
@@ -50,12 +38,6 @@ bitflags! {
     }
 }
 
-impl From<Anchor> for zwlr_layer_surface_v1::Anchor {
-    fn from(anchor: Anchor) -> Self {
-        Self::from_bits_truncate(anchor.bits())
-    }
-}
-
 /// Keyboard interactivity mode for the layer_shell surfaces.
 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
 pub enum KeyboardInteractivity {
@@ -72,16 +54,6 @@ pub enum KeyboardInteractivity {
     OnDemand,
 }
 
-impl From<KeyboardInteractivity> for zwlr_layer_surface_v1::KeyboardInteractivity {
-    fn from(value: KeyboardInteractivity) -> Self {
-        match value {
-            KeyboardInteractivity::None => Self::None,
-            KeyboardInteractivity::Exclusive => Self::Exclusive,
-            KeyboardInteractivity::OnDemand => Self::OnDemand,
-        }
-    }
-}
-
 /// Options for creating a layer_shell window.
 #[derive(Clone, Debug, Default, PartialEq, Eq)]
 pub struct LayerShellOptions {

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

@@ -1,32 +0,0 @@
-mod dispatcher;
-mod headless;
-mod keyboard;
-mod platform;
-#[cfg(any(feature = "wayland", feature = "x11"))]
-mod text_system;
-#[cfg(feature = "wayland")]
-mod wayland;
-#[cfg(feature = "x11")]
-mod x11;
-
-#[cfg(any(feature = "wayland", feature = "x11"))]
-mod xdg_desktop_portal;
-
-pub(crate) use dispatcher::*;
-pub(crate) use headless::*;
-pub(crate) use keyboard::*;
-pub(crate) use platform::*;
-#[cfg(any(feature = "wayland", feature = "x11"))]
-pub(crate) use text_system::*;
-#[cfg(feature = "wayland")]
-pub(crate) use wayland::*;
-#[cfg(feature = "x11")]
-pub(crate) use x11::*;
-
-#[cfg(all(feature = "screen-capture", any(feature = "wayland", feature = "x11")))]
-pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
-#[cfg(not(all(feature = "screen-capture", any(feature = "wayland", feature = "x11"))))]
-pub(crate) type PlatformScreenCaptureFrame = ();
-
-#[cfg(feature = "wayland")]
-pub use wayland::layer_shell;

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

@@ -1,49 +0,0 @@
-mod client;
-mod clipboard;
-mod cursor;
-mod display;
-mod serial;
-mod window;
-
-/// Contains Types for configuring layer_shell surfaces.
-pub mod layer_shell;
-
-pub(crate) use client::*;
-
-use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape;
-
-use crate::CursorStyle;
-
-impl CursorStyle {
-    pub(super) fn to_shape(self) -> Shape {
-        match self {
-            CursorStyle::Arrow => Shape::Default,
-            CursorStyle::IBeam => Shape::Text,
-            CursorStyle::Crosshair => Shape::Crosshair,
-            CursorStyle::ClosedHand => Shape::Grabbing,
-            CursorStyle::OpenHand => Shape::Grab,
-            CursorStyle::PointingHand => Shape::Pointer,
-            CursorStyle::ResizeLeft => Shape::WResize,
-            CursorStyle::ResizeRight => Shape::EResize,
-            CursorStyle::ResizeLeftRight => Shape::EwResize,
-            CursorStyle::ResizeUp => Shape::NResize,
-            CursorStyle::ResizeDown => Shape::SResize,
-            CursorStyle::ResizeUpDown => Shape::NsResize,
-            CursorStyle::ResizeUpLeftDownRight => Shape::NwseResize,
-            CursorStyle::ResizeUpRightDownLeft => Shape::NeswResize,
-            CursorStyle::ResizeColumn => Shape::ColResize,
-            CursorStyle::ResizeRow => Shape::RowResize,
-            CursorStyle::IBeamCursorForVerticalLayout => Shape::VerticalText,
-            CursorStyle::OperationNotAllowed => Shape::NotAllowed,
-            CursorStyle::DragLink => Shape::Alias,
-            CursorStyle::DragCopy => Shape::Copy,
-            CursorStyle::ContextualMenu => Shape::ContextMenu,
-            CursorStyle::None => {
-                #[cfg(debug_assertions)]
-                panic!("CursorStyle::None should be handled separately in the client");
-                #[cfg(not(debug_assertions))]
-                Shape::Default
-            }
-        }
-    }
-}

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

@@ -1,387 +0,0 @@
-use crate::{
-    Scene,
-    geometry::{
-        rect::RectF,
-        vector::{Vector2F, vec2f},
-    },
-    platform::{
-        self, Event, FontSystem, WindowBounds,
-        mac::{platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer},
-    },
-};
-use cocoa::{
-    appkit::{NSScreen, NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow},
-    base::{YES, id, nil},
-    foundation::{NSPoint, NSRect, NSSize},
-};
-use ctor::ctor;
-use foreign_types::ForeignTypeRef;
-use objc::{
-    class,
-    declare::ClassDecl,
-    msg_send,
-    rc::StrongPtr,
-    runtime::{Class, Object, Protocol, Sel},
-    sel, sel_impl,
-};
-use std::{
-    cell::RefCell,
-    ffi::c_void,
-    ptr,
-    rc::{Rc, Weak},
-    sync::Arc,
-};
-
-use super::screen::Screen;
-
-static mut VIEW_CLASS: *const Class = ptr::null();
-const STATE_IVAR: &str = "state";
-
-#[ctor]
-unsafe fn build_classes() {
-    VIEW_CLASS = {
-        let mut decl = ClassDecl::new("GPUIStatusItemView", class!(NSView)).unwrap();
-        decl.add_ivar::<*mut c_void>(STATE_IVAR);
-
-        decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
-
-        decl.add_method(
-            sel!(mouseDown:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(mouseUp:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(rightMouseDown:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(rightMouseUp:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(otherMouseDown:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(otherMouseUp:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(mouseMoved:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(mouseDragged:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(scrollWheel:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(flagsChanged:),
-            handle_view_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(makeBackingLayer),
-            make_backing_layer as extern "C" fn(&Object, Sel) -> id,
-        );
-        decl.add_method(
-            sel!(viewDidChangeEffectiveAppearance),
-            view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
-        );
-
-        decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
-        decl.add_method(
-            sel!(displayLayer:),
-            display_layer as extern "C" fn(&Object, Sel, id),
-        );
-
-        decl.register()
-    };
-}
-
-pub struct StatusItem(Rc<RefCell<StatusItemState>>);
-
-struct StatusItemState {
-    native_item: StrongPtr,
-    native_view: StrongPtr,
-    renderer: Renderer,
-    scene: Option<Scene>,
-    event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
-    appearance_changed_callback: Option<Box<dyn FnMut()>>,
-}
-
-impl StatusItem {
-    pub fn add(fonts: Arc<dyn FontSystem>) -> Self {
-        unsafe {
-            let renderer = Renderer::new(false, fonts);
-            let status_bar = NSStatusBar::systemStatusBar(nil);
-            let native_item =
-                StrongPtr::retain(status_bar.statusItemWithLength_(NSSquareStatusItemLength));
-
-            let button = native_item.button();
-            let _: () = msg_send![button, setHidden: YES];
-
-            let native_view = msg_send![VIEW_CLASS, alloc];
-            let state = Rc::new(RefCell::new(StatusItemState {
-                native_item,
-                native_view: StrongPtr::new(native_view),
-                renderer,
-                scene: None,
-                event_callback: None,
-                appearance_changed_callback: None,
-            }));
-
-            let parent_view = button.superview().superview();
-            NSView::initWithFrame_(
-                native_view,
-                NSRect::new(NSPoint::new(0., 0.), NSView::frame(parent_view).size),
-            );
-            (*native_view).set_ivar(
-                STATE_IVAR,
-                Weak::into_raw(Rc::downgrade(&state)) as *const c_void,
-            );
-            native_view.setWantsBestResolutionOpenGLSurface_(YES);
-            native_view.setWantsLayer(YES);
-            let _: () = msg_send![
-                native_view,
-                setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
-            ];
-
-            parent_view.addSubview_(native_view);
-
-            {
-                let state = state.borrow();
-                let layer = state.renderer.layer();
-                let scale_factor = state.scale_factor();
-                let size = state.content_size() * scale_factor;
-                layer.set_contents_scale(scale_factor.into());
-                layer.set_drawable_size(metal::CGSize::new(size.x().into(), size.y().into()));
-            }
-
-            Self(state)
-        }
-    }
-}
-
-impl platform::Window for StatusItem {
-    fn bounds(&self) -> WindowBounds {
-        self.0.borrow().bounds()
-    }
-
-    fn content_size(&self) -> Vector2F {
-        self.0.borrow().content_size()
-    }
-
-    fn scale_factor(&self) -> f32 {
-        self.0.borrow().scale_factor()
-    }
-
-    fn appearance(&self) -> platform::Appearance {
-        unsafe {
-            let appearance: id =
-                msg_send![self.0.borrow().native_item.button(), effectiveAppearance];
-            platform::Appearance::from_native(appearance)
-        }
-    }
-
-    fn screen(&self) -> Rc<dyn platform::Screen> {
-        unsafe {
-            Rc::new(Screen {
-                native_screen: self.0.borrow().native_window().screen(),
-            })
-        }
-    }
-
-    fn mouse_position(&self) -> Vector2F {
-        unimplemented!()
-    }
-
-    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
-        self
-    }
-
-    fn set_input_handler(&mut self, _: Box<dyn platform::InputHandler>) {}
-
-    fn prompt(
-        &self,
-        _: crate::platform::PromptLevel,
-        _: &str,
-        _: &[&str],
-    ) -> postage::oneshot::Receiver<usize> {
-        unimplemented!()
-    }
-
-    fn activate(&self) {
-        unimplemented!()
-    }
-
-    fn set_title(&mut self, _: &str) {
-        unimplemented!()
-    }
-
-    fn set_edited(&mut self, _: bool) {
-        unimplemented!()
-    }
-
-    fn show_character_palette(&self) {
-        unimplemented!()
-    }
-
-    fn minimize(&self) {
-        unimplemented!()
-    }
-
-    fn zoom(&self) {
-        unimplemented!()
-    }
-
-    fn present_scene(&mut self, scene: Scene) {
-        self.0.borrow_mut().scene = Some(scene);
-        unsafe {
-            let _: () = msg_send![*self.0.borrow().native_view, setNeedsDisplay: YES];
-        }
-    }
-
-    fn toggle_fullscreen(&self) {
-        unimplemented!()
-    }
-
-    fn on_event(&mut self, callback: Box<dyn FnMut(platform::Event) -> bool>) {
-        self.0.borrow_mut().event_callback = Some(callback);
-    }
-
-    fn on_active_status_change(&mut self, _: Box<dyn FnMut(bool)>) {}
-
-    fn on_resize(&mut self, _: Box<dyn FnMut()>) {}
-
-    fn on_fullscreen(&mut self, _: Box<dyn FnMut(bool)>) {}
-
-    fn on_moved(&mut self, _: Box<dyn FnMut()>) {}
-
-    fn on_should_close(&mut self, _: Box<dyn FnMut() -> bool>) {}
-
-    fn on_close(&mut self, _: Box<dyn FnOnce()>) {}
-
-    fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
-        self.0.borrow_mut().appearance_changed_callback = Some(callback);
-    }
-
-    fn is_topmost_for_position(&self, _: Vector2F) -> bool {
-        true
-    }
-}
-
-impl StatusItemState {
-    fn bounds(&self) -> WindowBounds {
-        unsafe {
-            let window: id = self.native_window();
-            let screen_frame = window.screen().visibleFrame();
-            let window_frame = NSWindow::frame(window);
-            let origin = vec2f(
-                window_frame.origin.x as f32,
-                (window_frame.origin.y - screen_frame.size.height - window_frame.size.height)
-                    as f32,
-            );
-            let size = vec2f(
-                window_frame.size.width as f32,
-                window_frame.size.height as f32,
-            );
-            WindowBounds::Fixed(RectF::new(origin, size))
-        }
-    }
-
-    fn content_size(&self) -> Vector2F {
-        unsafe {
-            let NSSize { width, height, .. } =
-                NSView::frame(self.native_item.button().superview().superview()).size;
-            vec2f(width as f32, height as f32)
-        }
-    }
-
-    fn scale_factor(&self) -> f32 {
-        unsafe {
-            let window: id = msg_send![self.native_item.button(), window];
-            NSScreen::backingScaleFactor(window.screen()) as f32
-        }
-    }
-
-    pub fn native_window(&self) -> id {
-        unsafe { msg_send![self.native_item.button(), window] }
-    }
-}
-
-extern "C" fn dealloc_view(this: &Object, _: Sel) {
-    unsafe {
-        drop_state(this);
-
-        let _: () = msg_send![super(this, class!(NSView)), dealloc];
-    }
-}
-
-extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
-    unsafe {
-        if let Some(state) = get_state(this).upgrade() {
-            let mut state_borrow = state.as_ref().borrow_mut();
-            if let Some(event) =
-                Event::from_native(native_event, Some(state_borrow.content_size().y()))
-            {
-                if let Some(mut callback) = state_borrow.event_callback.take() {
-                    drop(state_borrow);
-                    callback(event);
-                    state.borrow_mut().event_callback = Some(callback);
-                }
-            }
-        }
-    }
-}
-
-extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
-    if let Some(state) = unsafe { get_state(this).upgrade() } {
-        let state = state.borrow();
-        state.renderer.layer().as_ptr() as id
-    } else {
-        nil
-    }
-}
-
-extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
-    unsafe {
-        if let Some(state) = get_state(this).upgrade() {
-            let mut state = state.borrow_mut();
-            if let Some(scene) = state.scene.take() {
-                state.renderer.render(&scene);
-            }
-        }
-    }
-}
-
-extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
-    unsafe {
-        if let Some(state) = get_state(this).upgrade() {
-            let mut state_borrow = state.as_ref().borrow_mut();
-            if let Some(mut callback) = state_borrow.appearance_changed_callback.take() {
-                drop(state_borrow);
-                callback();
-                state.borrow_mut().appearance_changed_callback = Some(callback);
-            }
-        }
-    }
-}
-
-unsafe fn get_state(object: &Object) -> Weak<RefCell<StatusItemState>> {
-    let raw: *mut c_void = *object.get_ivar(STATE_IVAR);
-    let weak1 = Weak::from_raw(raw as *mut RefCell<StatusItemState>);
-    let weak2 = weak1.clone();
-    let _ = Weak::into_raw(weak1);
-    weak2
-}
-
-unsafe fn drop_state(object: &Object) {
-    let raw: *const c_void = *object.get_ivar(STATE_IVAR);
-    Weak::from_raw(raw as *const RefCell<StatusItemState>);
-}

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

@@ -1,37 +0,0 @@
-use crate::WindowAppearance;
-use cocoa::{
-    appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight},
-    base::id,
-    foundation::NSString,
-};
-use objc::{msg_send, sel, sel_impl};
-use std::ffi::CStr;
-
-impl WindowAppearance {
-    pub(crate) unsafe fn from_native(appearance: id) -> Self {
-        let name: id = msg_send![appearance, name];
-        unsafe {
-            if name == NSAppearanceNameVibrantLight {
-                Self::VibrantLight
-            } else if name == NSAppearanceNameVibrantDark {
-                Self::VibrantDark
-            } else if name == NSAppearanceNameAqua {
-                Self::Light
-            } else if name == NSAppearanceNameDarkAqua {
-                Self::Dark
-            } else {
-                println!(
-                    "unknown appearance: {:?}",
-                    CStr::from_ptr(name.UTF8String())
-                );
-                Self::Light
-            }
-        }
-    }
-}
-
-#[link(name = "AppKit", kind = "framework")]
-unsafe extern "C" {
-    pub static NSAppearanceNameAqua: id;
-    pub static NSAppearanceNameDarkAqua: id;
-}

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

@@ -15,7 +15,7 @@ use std::sync::atomic::{self, AtomicBool};
 /// `scap_default_target_source` should be used instead on Wayland, since `scap_screen_sources`
 /// won't return any results.
 #[allow(dead_code)]
-pub(crate) fn scap_screen_sources(
+pub fn scap_screen_sources(
     foreground_executor: &ForegroundExecutor,
 ) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
     let (sources_tx, sources_rx) = oneshot::channel();

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

@@ -15,11 +15,6 @@ use std::{
     rc::{Rc, Weak},
     sync::Arc,
 };
-#[cfg(target_os = "windows")]
-use windows::Win32::{
-    Graphics::Imaging::{CLSID_WICImagingFactory, IWICImagingFactory},
-    System::Com::{CLSCTX_INPROC_SERVER, CoCreateInstance},
-};
 
 /// TestPlatform implements the Platform trait for use in tests.
 pub(crate) struct TestPlatform {
@@ -39,8 +34,6 @@ pub(crate) struct TestPlatform {
     pub opened_url: RefCell<Option<String>>,
     pub text_system: Arc<dyn PlatformTextSystem>,
     pub expect_restart: RefCell<Option<oneshot::Sender<Option<PathBuf>>>>,
-    #[cfg(target_os = "windows")]
-    bitmap_factory: std::mem::ManuallyDrop<IWICImagingFactory>,
     weak: Weak<Self>,
 }
 
@@ -95,16 +88,6 @@ pub(crate) struct TestPrompts {
 
 impl TestPlatform {
     pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Rc<Self> {
-        #[cfg(target_os = "windows")]
-        let bitmap_factory = unsafe {
-            windows::Win32::System::Ole::OleInitialize(None)
-                .expect("unable to initialize Windows OLE");
-            std::mem::ManuallyDrop::new(
-                CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_INPROC_SERVER)
-                    .expect("Error creating bitmap factory."),
-            )
-        };
-
         let text_system = Arc::new(NoopTextSystem);
 
         Rc::new_cyclic(|weak| TestPlatform {
@@ -123,8 +106,6 @@ impl TestPlatform {
             current_find_pasteboard_item: Mutex::new(None),
             weak: weak.clone(),
             opened_url: Default::default(),
-            #[cfg(target_os = "windows")]
-            bitmap_factory,
             text_system,
         })
     }
@@ -288,12 +269,10 @@ impl Platform for TestPlatform {
         Some(self.active_display.clone())
     }
 
-    #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool {
         true
     }
 
-    #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
     ) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
@@ -458,16 +437,6 @@ impl TestScreenCaptureSource {
     }
 }
 
-#[cfg(target_os = "windows")]
-impl Drop for TestPlatform {
-    fn drop(&mut self) {
-        unsafe {
-            std::mem::ManuallyDrop::drop(&mut self.bitmap_factory);
-            windows::Win32::System::Ole::OleUninitialize();
-        }
-    }
-}
-
 struct TestKeyboardLayout;
 
 impl PlatformKeyboardLayout for TestKeyboardLayout {

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

@@ -32,7 +32,7 @@ pub(crate) struct TestWindowState {
 }
 
 #[derive(Clone)]
-pub(crate) struct TestWindow(pub(crate) Rc<Mutex<TestWindowState>>);
+pub struct TestWindow(pub(crate) Rc<Mutex<TestWindowState>>);
 
 impl HasWindowHandle for TestWindow {
     fn window_handle(
@@ -51,7 +51,7 @@ impl HasDisplayHandle for TestWindow {
 }
 
 impl TestWindow {
-    pub fn new(
+    pub(crate) fn new(
         handle: AnyWindowHandle,
         params: WindowParams,
         platform: Weak<TestPlatform>,

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

@@ -9,7 +9,7 @@
 use crate::ScreenCaptureSource;
 use crate::{
     AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, Keymap,
-    MacPlatform, Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform, PlatformDisplay,
+    Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform, PlatformDisplay,
     PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PlatformWindow, Task,
     TestDispatcher, WindowAppearance, WindowParams,
 };
@@ -33,7 +33,7 @@ pub struct VisualTestPlatform {
     dispatcher: TestDispatcher,
     background_executor: BackgroundExecutor,
     foreground_executor: ForegroundExecutor,
-    mac_platform: MacPlatform,
+    platform: Rc<dyn Platform>,
     clipboard: Mutex<Option<ClipboardItem>>,
     find_pasteboard: Mutex<Option<ClipboardItem>>,
 }
@@ -42,20 +42,18 @@ impl VisualTestPlatform {
     /// Creates a new VisualTestPlatform with the given random seed.
     ///
     /// The seed is used for deterministic random number generation in the TestDispatcher.
-    pub fn new(seed: u64) -> Self {
+    pub fn new(platform: Rc<dyn Platform>, seed: u64) -> Self {
         let dispatcher = TestDispatcher::new(seed);
         let arc_dispatcher = Arc::new(dispatcher.clone());
 
         let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
         let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
 
-        let mac_platform = MacPlatform::new(false);
-
         Self {
             dispatcher,
             background_executor,
             foreground_executor,
-            mac_platform,
+            platform,
             clipboard: Mutex::new(None),
             find_pasteboard: Mutex::new(None),
         }
@@ -77,7 +75,7 @@ impl Platform for VisualTestPlatform {
     }
 
     fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
-        self.mac_platform.text_system()
+        self.platform.text_system()
     }
 
     fn run(&self, _on_finish_launching: Box<dyn 'static + FnOnce()>) {
@@ -97,31 +95,29 @@ impl Platform for VisualTestPlatform {
     fn unhide_other_apps(&self) {}
 
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
-        self.mac_platform.displays()
+        self.platform.displays()
     }
 
     fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
-        self.mac_platform.primary_display()
+        self.platform.primary_display()
     }
 
     fn active_window(&self) -> Option<AnyWindowHandle> {
-        self.mac_platform.active_window()
+        self.platform.active_window()
     }
 
     fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
-        self.mac_platform.window_stack()
+        self.platform.window_stack()
     }
 
-    #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool {
-        self.mac_platform.is_screen_capture_supported()
+        self.platform.is_screen_capture_supported()
     }
 
-    #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
     ) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
-        self.mac_platform.screen_capture_sources()
+        self.platform.screen_capture_sources()
     }
 
     fn open_window(
@@ -129,15 +125,15 @@ impl Platform for VisualTestPlatform {
         handle: AnyWindowHandle,
         options: WindowParams,
     ) -> Result<Box<dyn PlatformWindow>> {
-        self.mac_platform.open_window(handle, options)
+        self.platform.open_window(handle, options)
     }
 
     fn window_appearance(&self) -> WindowAppearance {
-        self.mac_platform.window_appearance()
+        self.platform.window_appearance()
     }
 
     fn open_url(&self, url: &str) {
-        self.mac_platform.open_url(url)
+        self.platform.open_url(url)
     }
 
     fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {}
@@ -170,11 +166,11 @@ impl Platform for VisualTestPlatform {
     }
 
     fn reveal_path(&self, path: &Path) {
-        self.mac_platform.reveal_path(path)
+        self.platform.reveal_path(path)
     }
 
     fn open_with_system(&self, path: &Path) {
-        self.mac_platform.open_with_system(path)
+        self.platform.open_with_system(path)
     }
 
     fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
@@ -196,19 +192,19 @@ impl Platform for VisualTestPlatform {
     fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {}
 
     fn app_path(&self) -> Result<PathBuf> {
-        self.mac_platform.app_path()
+        self.platform.app_path()
     }
 
     fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
-        self.mac_platform.path_for_auxiliary_executable(name)
+        self.platform.path_for_auxiliary_executable(name)
     }
 
     fn set_cursor_style(&self, style: CursorStyle) {
-        self.mac_platform.set_cursor_style(style)
+        self.platform.set_cursor_style(style)
     }
 
     fn should_auto_hide_scrollbars(&self) -> bool {
-        self.mac_platform.should_auto_hide_scrollbars()
+        self.platform.should_auto_hide_scrollbars()
     }
 
     fn read_from_clipboard(&self) -> Option<ClipboardItem> {
@@ -242,11 +238,11 @@ impl Platform for VisualTestPlatform {
     }
 
     fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
-        self.mac_platform.keyboard_layout()
+        self.platform.keyboard_layout()
     }
 
     fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
-        self.mac_platform.keyboard_mapper()
+        self.platform.keyboard_mapper()
     }
 
     fn on_keyboard_layout_change(&self, _callback: Box<dyn FnMut()>) {}

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

@@ -1,7 +0,0 @@
-mod wgpu_atlas;
-mod wgpu_context;
-mod wgpu_renderer;
-
-pub(crate) use wgpu_atlas::*;
-pub(crate) use wgpu_context::*;
-pub(crate) use wgpu_renderer::*;

crates/gpui/src/profiler.rs 🔗

@@ -30,7 +30,8 @@ pub struct ThreadTaskTimings {
 }
 
 impl ThreadTaskTimings {
-    pub(crate) fn convert(timings: &[GlobalThreadTimings]) -> Vec<Self> {
+    /// Convert global thread timings into their structured format.
+    pub fn convert(timings: &[GlobalThreadTimings]) -> Vec<Self> {
         timings
             .iter()
             .filter_map(|t| match t.timings.upgrade() {
@@ -245,19 +246,24 @@ impl ProfilingCollector {
 // Allow 20mb of task timing entries
 const MAX_TASK_TIMINGS: usize = (20 * 1024 * 1024) / core::mem::size_of::<TaskTiming>();
 
-pub(crate) type TaskTimings = circular_buffer::CircularBuffer<MAX_TASK_TIMINGS, TaskTiming>;
-pub(crate) type GuardedTaskTimings = spin::Mutex<ThreadTimings>;
+#[doc(hidden)]
+pub type TaskTimings = circular_buffer::CircularBuffer<MAX_TASK_TIMINGS, TaskTiming>;
+#[doc(hidden)]
+pub type GuardedTaskTimings = spin::Mutex<ThreadTimings>;
 
-pub(crate) struct GlobalThreadTimings {
+#[doc(hidden)]
+pub struct GlobalThreadTimings {
     pub thread_id: ThreadId,
     pub timings: std::sync::Weak<GuardedTaskTimings>,
 }
 
-pub(crate) static GLOBAL_THREAD_TIMINGS: spin::Mutex<Vec<GlobalThreadTimings>> =
+#[doc(hidden)]
+pub static GLOBAL_THREAD_TIMINGS: spin::Mutex<Vec<GlobalThreadTimings>> =
     spin::Mutex::new(Vec::new());
 
 thread_local! {
-    pub(crate) static THREAD_TIMINGS: LazyCell<Arc<GuardedTaskTimings>> = LazyCell::new(|| {
+    #[doc(hidden)]
+    pub static THREAD_TIMINGS: LazyCell<Arc<GuardedTaskTimings>> = LazyCell::new(|| {
         let current_thread = std::thread::current();
         let thread_name = current_thread.name();
         let thread_id = current_thread.id();
@@ -277,7 +283,8 @@ thread_local! {
     });
 }
 
-pub(crate) struct ThreadTimings {
+#[doc(hidden)]
+pub struct ThreadTimings {
     pub thread_name: Option<String>,
     pub thread_id: ThreadId,
     pub timings: Box<TaskTimings>,
@@ -285,7 +292,7 @@ pub(crate) struct ThreadTimings {
 }
 
 impl ThreadTimings {
-    pub(crate) fn new(thread_name: Option<String>, thread_id: ThreadId) -> Self {
+    pub fn new(thread_name: Option<String>, thread_id: ThreadId) -> Self {
         ThreadTimings {
             thread_name,
             thread_id,
@@ -310,8 +317,9 @@ impl Drop for ThreadTimings {
     }
 }
 
+#[doc(hidden)]
 #[allow(dead_code)] // Used by Linux and Windows dispatchers, not macOS
-pub(crate) fn add_task_timing(timing: TaskTiming) {
+pub fn add_task_timing(timing: TaskTiming) {
     THREAD_TIMINGS.with(|timings| {
         let mut timings = timings.lock();
 

crates/gpui/src/queue.rs 🔗

@@ -86,7 +86,8 @@ impl<T> PriorityQueueState<T> {
     }
 }
 
-pub(crate) struct PriorityQueueSender<T> {
+#[doc(hidden)]
+pub struct PriorityQueueSender<T> {
     state: Arc<PriorityQueueState<T>>,
 }
 
@@ -95,7 +96,7 @@ impl<T> PriorityQueueSender<T> {
         Self { state }
     }
 
-    pub(crate) fn send(&self, priority: Priority, item: T) -> Result<(), SendError<T>> {
+    pub fn send(&self, priority: Priority, item: T) -> Result<(), SendError<T>> {
         self.state.send(priority, item)?;
         Ok(())
     }
@@ -109,7 +110,8 @@ impl<T> Drop for PriorityQueueSender<T> {
     }
 }
 
-pub(crate) struct PriorityQueueReceiver<T> {
+#[doc(hidden)]
+pub struct PriorityQueueReceiver<T> {
     state: Arc<PriorityQueueState<T>>,
     rand: SmallRng,
     disconnected: bool,
@@ -128,7 +130,8 @@ impl<T> Clone for PriorityQueueReceiver<T> {
     }
 }
 
-pub(crate) struct SendError<T>(T);
+#[doc(hidden)]
+pub struct SendError<T>(pub T);
 
 impl<T: fmt::Debug> fmt::Debug for SendError<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -137,11 +140,12 @@ impl<T: fmt::Debug> fmt::Debug for SendError<T> {
 }
 
 #[derive(Debug)]
-pub(crate) struct RecvError;
+#[doc(hidden)]
+pub struct RecvError;
 
 #[allow(dead_code)]
 impl<T> PriorityQueueReceiver<T> {
-    pub(crate) fn new() -> (PriorityQueueSender<T>, Self) {
+    pub fn new() -> (PriorityQueueSender<T>, Self) {
         let state = PriorityQueueState {
             queues: parking_lot::Mutex::new(PriorityQueues {
                 high_priority: VecDeque::new(),
@@ -175,7 +179,7 @@ impl<T> PriorityQueueReceiver<T> {
     /// # Errors
     ///
     /// If the sender was dropped
-    pub(crate) fn try_pop(&mut self) -> Result<Option<T>, RecvError> {
+    pub fn try_pop(&mut self) -> Result<Option<T>, RecvError> {
         self.pop_inner(false)
     }
 
@@ -187,13 +191,13 @@ impl<T> PriorityQueueReceiver<T> {
     /// # Errors
     ///
     /// If the sender was dropped
-    pub(crate) fn pop(&mut self) -> Result<T, RecvError> {
+    pub fn pop(&mut self) -> Result<T, RecvError> {
         self.pop_inner(true).map(|e| e.unwrap())
     }
 
     /// Returns an iterator over the elements of the queue
     /// this iterator will end when all elements have been consumed and will not wait for new ones.
-    pub(crate) fn try_iter(self) -> TryIter<T> {
+    pub fn try_iter(self) -> TryIter<T> {
         TryIter {
             receiver: self,
             ended: false,
@@ -202,7 +206,7 @@ impl<T> PriorityQueueReceiver<T> {
 
     /// Returns an iterator over the elements of the queue
     /// this iterator will wait for new elements if the queue is empty.
-    pub(crate) fn iter(self) -> Iter<T> {
+    pub fn iter(self) -> Iter<T> {
         Iter(self)
     }
 
@@ -261,8 +265,8 @@ impl<T> Drop for PriorityQueueReceiver<T> {
     }
 }
 
-/// If None is returned the sender disconnected
-pub(crate) struct Iter<T>(PriorityQueueReceiver<T>);
+#[doc(hidden)]
+pub struct Iter<T>(PriorityQueueReceiver<T>);
 impl<T> Iterator for Iter<T> {
     type Item = T;
 
@@ -272,8 +276,8 @@ impl<T> Iterator for Iter<T> {
 }
 impl<T> FusedIterator for Iter<T> {}
 
-/// If None is returned there are no more elements in the queue
-pub(crate) struct TryIter<T> {
+#[doc(hidden)]
+pub struct TryIter<T> {
     receiver: PriorityQueueReceiver<T>,
     ended: bool,
 }

crates/gpui/src/scene.rs 🔗

@@ -16,25 +16,29 @@ use std::{
 };
 
 #[allow(non_camel_case_types, unused)]
-pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
+#[expect(missing_docs)]
+pub type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
 
-pub(crate) type DrawOrder = u32;
+#[expect(missing_docs)]
+pub type DrawOrder = u32;
 
 #[derive(Default)]
-pub(crate) struct Scene {
+#[expect(missing_docs)]
+pub struct Scene {
     pub(crate) paint_operations: Vec<PaintOperation>,
     primitive_bounds: BoundsTree<ScaledPixels>,
     layer_stack: Vec<DrawOrder>,
-    pub(crate) shadows: Vec<Shadow>,
-    pub(crate) quads: Vec<Quad>,
-    pub(crate) paths: Vec<Path<ScaledPixels>>,
-    pub(crate) underlines: Vec<Underline>,
-    pub(crate) monochrome_sprites: Vec<MonochromeSprite>,
-    pub(crate) subpixel_sprites: Vec<SubpixelSprite>,
-    pub(crate) polychrome_sprites: Vec<PolychromeSprite>,
-    pub(crate) surfaces: Vec<PaintSurface>,
+    pub shadows: Vec<Shadow>,
+    pub quads: Vec<Quad>,
+    pub paths: Vec<Path<ScaledPixels>>,
+    pub underlines: Vec<Underline>,
+    pub monochrome_sprites: Vec<MonochromeSprite>,
+    pub subpixel_sprites: Vec<SubpixelSprite>,
+    pub polychrome_sprites: Vec<PolychromeSprite>,
+    pub surfaces: Vec<PaintSurface>,
 }
 
+#[expect(missing_docs)]
 impl Scene {
     pub fn clear(&mut self) {
         self.paint_operations.clear();
@@ -151,7 +155,7 @@ impl Scene {
         ),
         allow(dead_code)
     )]
-    pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ {
+    pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ {
         BatchIterator {
             shadows_start: 0,
             shadows_iter: self.shadows.iter().peekable(),
@@ -200,7 +204,8 @@ pub(crate) enum PaintOperation {
 }
 
 #[derive(Clone)]
-pub(crate) enum Primitive {
+#[expect(missing_docs)]
+pub enum Primitive {
     Shadow(Shadow),
     Quad(Quad),
     Path(Path<ScaledPixels>),
@@ -211,6 +216,7 @@ pub(crate) enum Primitive {
     Surface(PaintSurface),
 }
 
+#[expect(missing_docs)]
 impl Primitive {
     pub fn bounds(&self) -> &Bounds<ScaledPixels> {
         match self {
@@ -453,7 +459,8 @@ impl<'a> Iterator for BatchIterator<'a> {
     ),
     allow(dead_code)
 )]
-pub(crate) enum PrimitiveBatch {
+#[expect(missing_docs)]
+pub enum PrimitiveBatch {
     Shadows(Range<usize>),
     Quads(Range<usize>),
     Paths(Range<usize>),
@@ -476,7 +483,8 @@ pub(crate) enum PrimitiveBatch {
 
 #[derive(Default, Debug, Clone)]
 #[repr(C)]
-pub(crate) struct Quad {
+#[expect(missing_docs)]
+pub struct Quad {
     pub order: DrawOrder,
     pub border_style: BorderStyle,
     pub bounds: Bounds<ScaledPixels>,
@@ -495,7 +503,8 @@ impl From<Quad> for Primitive {
 
 #[derive(Debug, Clone)]
 #[repr(C)]
-pub(crate) struct Underline {
+#[expect(missing_docs)]
+pub struct Underline {
     pub order: DrawOrder,
     pub pad: u32, // align to 8 bytes
     pub bounds: Bounds<ScaledPixels>,
@@ -513,7 +522,8 @@ impl From<Underline> for Primitive {
 
 #[derive(Debug, Clone)]
 #[repr(C)]
-pub(crate) struct Shadow {
+#[expect(missing_docs)]
+pub struct Shadow {
     pub order: DrawOrder,
     pub blur_radius: ScaledPixels,
     pub bounds: Bounds<ScaledPixels>,
@@ -644,7 +654,8 @@ impl Default for TransformationMatrix {
 
 #[derive(Clone, Debug)]
 #[repr(C)]
-pub(crate) struct MonochromeSprite {
+#[expect(missing_docs)]
+pub struct MonochromeSprite {
     pub order: DrawOrder,
     pub pad: u32, // align to 8 bytes
     pub bounds: Bounds<ScaledPixels>,
@@ -662,7 +673,8 @@ impl From<MonochromeSprite> for Primitive {
 
 #[derive(Clone, Debug)]
 #[repr(C)]
-pub(crate) struct SubpixelSprite {
+#[expect(missing_docs)]
+pub struct SubpixelSprite {
     pub order: DrawOrder,
     pub pad: u32, // align to 8 bytes
     pub bounds: Bounds<ScaledPixels>,
@@ -680,7 +692,8 @@ impl From<SubpixelSprite> for Primitive {
 
 #[derive(Clone, Debug)]
 #[repr(C)]
-pub(crate) struct PolychromeSprite {
+#[expect(missing_docs)]
+pub struct PolychromeSprite {
     pub order: DrawOrder,
     pub pad: u32, // align to 8 bytes
     pub grayscale: bool,
@@ -698,7 +711,8 @@ impl From<PolychromeSprite> for Primitive {
 }
 
 #[derive(Clone, Debug)]
-pub(crate) struct PaintSurface {
+#[expect(missing_docs)]
+pub struct PaintSurface {
     pub order: DrawOrder,
     pub bounds: Bounds<ScaledPixels>,
     pub content_mask: ContentMask<ScaledPixels>,
@@ -713,17 +727,19 @@ impl From<PaintSurface> for Primitive {
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
-pub(crate) struct PathId(pub(crate) usize);
+#[expect(missing_docs)]
+pub struct PathId(pub usize);
 
 /// A line made up of a series of vertices and control points.
 #[derive(Clone, Debug)]
+#[expect(missing_docs)]
 pub struct Path<P: Clone + Debug + Default + PartialEq> {
-    pub(crate) id: PathId,
-    pub(crate) order: DrawOrder,
-    pub(crate) bounds: Bounds<P>,
-    pub(crate) content_mask: ContentMask<P>,
-    pub(crate) vertices: Vec<PathVertex<P>>,
-    pub(crate) color: Background,
+    pub id: PathId,
+    pub order: DrawOrder,
+    pub bounds: Bounds<P>,
+    pub content_mask: ContentMask<P>,
+    pub vertices: Vec<PathVertex<P>>,
+    pub color: Background,
     start: Point<P>,
     current: Point<P>,
     contour_count: usize,
@@ -847,7 +863,8 @@ where
     T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>,
 {
     #[allow(unused)]
-    pub(crate) fn clipped_bounds(&self) -> Bounds<T> {
+    #[expect(missing_docs)]
+    pub fn clipped_bounds(&self) -> Bounds<T> {
         self.bounds.intersect(&self.content_mask.bounds)
     }
 }
@@ -860,12 +877,14 @@ impl From<Path<ScaledPixels>> for Primitive {
 
 #[derive(Clone, Debug)]
 #[repr(C)]
-pub(crate) struct PathVertex<P: Clone + Debug + Default + PartialEq> {
-    pub(crate) xy_position: Point<P>,
-    pub(crate) st_position: Point<f32>,
-    pub(crate) content_mask: ContentMask<P>,
+#[expect(missing_docs)]
+pub struct PathVertex<P: Clone + Debug + Default + PartialEq> {
+    pub xy_position: Point<P>,
+    pub st_position: Point<f32>,
+    pub content_mask: ContentMask<P>,
 }
 
+#[expect(missing_docs)]
 impl PathVertex<Pixels> {
     pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
         PathVertex {

crates/gpui/src/svg_renderer.rs 🔗

@@ -14,9 +14,10 @@ use std::{
 pub const SMOOTH_SVG_SCALE_FACTOR: f32 = 2.;
 
 #[derive(Clone, PartialEq, Hash, Eq)]
-pub(crate) struct RenderSvgParams {
-    pub(crate) path: SharedString,
-    pub(crate) size: Size<DevicePixels>,
+#[expect(missing_docs)]
+pub struct RenderSvgParams {
+    pub path: SharedString,
+    pub size: Size<DevicePixels>,
 }
 
 #[derive(Clone)]

crates/gpui/src/text_system.rs 🔗

@@ -41,14 +41,15 @@ pub struct FontId(pub usize);
 #[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
 pub struct FontFamilyId(pub usize);
 
-pub(crate) const SUBPIXEL_VARIANTS_X: u8 = 4;
-
-pub(crate) const SUBPIXEL_VARIANTS_Y: u8 =
-    if cfg!(target_os = "windows") || cfg!(target_os = "linux") {
-        1
-    } else {
-        SUBPIXEL_VARIANTS_X
-    };
+/// Number of subpixel glyph variants along the X axis.
+pub const SUBPIXEL_VARIANTS_X: u8 = 4;
+
+/// Number of subpixel glyph variants along the Y axis.
+pub const SUBPIXEL_VARIANTS_Y: u8 = if cfg!(target_os = "windows") || cfg!(target_os = "linux") {
+    1
+} else {
+    SUBPIXEL_VARIANTS_X
+};
 
 /// The GPUI text rendering sub system.
 pub struct TextSystem {
@@ -799,17 +800,18 @@ impl TextRun {
 /// An identifier for a specific glyph, as returned by [`WindowTextSystem::layout_line`].
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 #[repr(C)]
-pub struct GlyphId(pub(crate) u32);
+pub struct GlyphId(pub u32);
 
 #[derive(Clone, Debug, PartialEq)]
-pub(crate) struct RenderGlyphParams {
-    pub(crate) font_id: FontId,
-    pub(crate) glyph_id: GlyphId,
-    pub(crate) font_size: Pixels,
-    pub(crate) subpixel_variant: Point<u8>,
-    pub(crate) scale_factor: f32,
-    pub(crate) is_emoji: bool,
-    pub(crate) subpixel_rendering: bool,
+#[expect(missing_docs)]
+pub struct RenderGlyphParams {
+    pub font_id: FontId,
+    pub glyph_id: GlyphId,
+    pub font_size: Pixels,
+    pub subpixel_variant: Point<u8>,
+    pub scale_factor: f32,
+    pub is_emoji: bool,
+    pub subpixel_rendering: bool,
 }
 
 impl Eq for RenderGlyphParams {}
@@ -884,32 +886,32 @@ impl Font {
 pub struct FontMetrics {
     /// The number of font units that make up the "em square",
     /// a scalable grid for determining the size of a typeface.
-    pub(crate) units_per_em: u32,
+    pub units_per_em: u32,
 
     /// The vertical distance from the baseline of the font to the top of the glyph covers.
-    pub(crate) ascent: f32,
+    pub ascent: f32,
 
     /// The vertical distance from the baseline of the font to the bottom of the glyph covers.
-    pub(crate) descent: f32,
+    pub descent: f32,
 
     /// The recommended additional space to add between lines of type.
-    pub(crate) line_gap: f32,
+    pub line_gap: f32,
 
     /// The suggested position of the underline.
-    pub(crate) underline_position: f32,
+    pub underline_position: f32,
 
     /// The suggested thickness of the underline.
-    pub(crate) underline_thickness: f32,
+    pub underline_thickness: f32,
 
     /// The height of a capital letter measured from the baseline of the font.
-    pub(crate) cap_height: f32,
+    pub cap_height: f32,
 
     /// The height of a lowercase x.
-    pub(crate) x_height: f32,
+    pub x_height: f32,
 
     /// The outer limits of the area that the font covers.
     /// Corresponds to the xMin / xMax / yMin / yMax values in the OpenType `head` table
-    pub(crate) bounding_box: Bounds<f32>,
+    pub bounding_box: Bounds<f32>,
 }
 
 impl FontMetrics {
@@ -954,8 +956,9 @@ impl FontMetrics {
     }
 }
 
+/// Maps well-known virtual font names to their concrete equivalents.
 #[allow(unused)]
-pub(crate) fn font_name_with_fallbacks<'a>(name: &'a str, system: &'a str) -> &'a str {
+pub fn font_name_with_fallbacks<'a>(name: &'a str, system: &'a str) -> &'a str {
     // Note: the "Zed Plex" fonts were deprecated as we are not allowed to use "Plex"
     // in a derived font name. They are essentially indistinguishable from IBM Plex/Lilex,
     // and so retained here for backward compatibility.
@@ -967,8 +970,9 @@ pub(crate) fn font_name_with_fallbacks<'a>(name: &'a str, system: &'a str) -> &'
     }
 }
 
+/// Like [`font_name_with_fallbacks`] but accepts and returns [`SharedString`] references.
 #[allow(unused)]
-pub(crate) fn font_name_with_fallbacks_shared<'a>(
+pub fn font_name_with_fallbacks_shared<'a>(
     name: &'a SharedString,
     system: &'a SharedString,
 ) -> &'a SharedString {

crates/gpui/src/text_system/line_layout.rs 🔗

@@ -594,9 +594,10 @@ impl LineLayoutCache {
 
 /// A run of text with a single font.
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+#[expect(missing_docs)]
 pub struct FontRun {
-    pub(crate) len: usize,
-    pub(crate) font_id: FontId,
+    pub len: usize,
+    pub font_id: FontId,
 }
 
 trait AsCacheKeyRef {

crates/gpui/src/window.rs 🔗

@@ -59,7 +59,8 @@ mod prompts;
 use crate::util::atomic_incr_if_not_zero;
 pub use prompts::*;
 
-pub(crate) const DEFAULT_WINDOW_SIZE: Size<Pixels> = size(px(1536.), px(864.));
+/// Default window size used when no explicit size is provided.
+pub const DEFAULT_WINDOW_SIZE: Size<Pixels> = size(px(1536.), px(864.));
 
 /// A 6:5 aspect ratio minimum window size to be used for functional,
 /// additional-to-main-Zed windows, like the settings and rules library windows.
@@ -1447,7 +1448,8 @@ impl Window {
 }
 
 #[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub(crate) struct DispatchEventResult {
+#[expect(missing_docs)]
+pub struct DispatchEventResult {
     pub propagate: bool,
     pub default_prevented: bool,
 }

crates/gpui_linux/Cargo.toml 🔗

@@ -0,0 +1,134 @@
+[package]
+name = "gpui_linux"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "Apache-2.0"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/gpui_linux.rs"
+
+[features]
+default = ["wayland", "x11"]
+test-support = ["gpui/test-support"]
+wayland = [
+    "bitflags",
+    "gpui_wgpu",
+    "ashpd/wayland",
+    "cosmic-text",
+    "font-kit",
+    "calloop-wayland-source",
+    "wayland-backend",
+    "wayland-client",
+    "wayland-cursor",
+    "wayland-protocols",
+    "wayland-protocols-plasma",
+    "wayland-protocols-wlr",
+    "filedescriptor",
+    "xkbcommon",
+    "open",
+]
+x11 = [
+    "gpui_wgpu",
+    "ashpd",
+    "cosmic-text",
+    "font-kit",
+    "as-raw-xcb-connection",
+    "x11rb",
+    "xkbcommon",
+    "xim",
+    "x11-clipboard",
+    "filedescriptor",
+    "open",
+    "scap?/x11",
+]
+screen-capture = [
+    "gpui/screen-capture",
+    "scap",
+]
+
+
+[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
+anyhow.workspace = true
+bytemuck = "1"
+collections.workspace = true
+futures.workspace = true
+gpui.workspace = true
+gpui_wgpu = { workspace = true, optional = true }
+http_client.workspace = true
+itertools.workspace = true
+libc.workspace = true
+log.workspace = true
+parking_lot.workspace = true
+pathfinder_geometry = "0.5"
+profiling.workspace = true
+smallvec.workspace = true
+smol.workspace = true
+strum.workspace = true
+util.workspace = true
+uuid.workspace = true
+
+# Always used
+oo7 = { version = "0.5.0", default-features = false, features = [
+    "async-std",
+    "native_crypto",
+] }
+calloop = "0.14.3"
+raw-window-handle = "0.6"
+
+# Used in both windowing options
+ashpd = { workspace = true, optional = true }
+cosmic-text = { version = "0.17.0", optional = true }
+swash = { version = "0.2.6" }
+# WARNING: If you change this, you must also publish a new version of zed-font-kit to crates.io
+font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "110523127440aefb11ce0cf280ae7c5071337ec5", package = "zed-font-kit", version = "0.14.1-zed", features = [
+    "source-fontconfig-dlopen",
+], optional = true }
+bitflags = { workspace = true, optional = true }
+filedescriptor = { version = "0.8.2", optional = true }
+open = { version = "5.2.0", optional = true }
+xkbcommon = { version = "0.8.0", features = ["wayland", "x11"], optional = true }
+
+# Screen capture
+scap = { workspace = true, optional = true }
+
+# Wayland
+calloop-wayland-source = { version = "0.4.1", optional = true }
+wayland-backend = { version = "0.3.3", features = [
+    "client_system",
+    "dlopen",
+], optional = true }
+wayland-client = { version = "0.31.11", optional = true }
+wayland-cursor = { version = "0.31.11", optional = true }
+wayland-protocols = { version = "0.32.9", features = [
+    "client",
+    "staging",
+    "unstable",
+], optional = true }
+wayland-protocols-plasma = { version = "0.3.9", features = [
+    "client",
+], optional = true }
+wayland-protocols-wlr = { version = "0.3.9", features = [
+    "client",
+], optional = true }
+
+# X11
+as-raw-xcb-connection = { version = "1", optional = true }
+x11rb = { version = "0.13.1", features = [
+    "allow-unsafe-code",
+    "xkb",
+    "randr",
+    "xinput",
+    "cursor",
+    "resource_manager",
+    "sync",
+], optional = true }
+# WARNING: If you change this, you must also publish a new version of zed-xim to crates.io
+xim = { git = "https://github.com/zed-industries/xim-rs.git", rev = "16f35a2c881b815a2b6cdfd6687988e84f8447d8", features = [
+    "x11rb-xcb",
+    "x11rb-client",
+], package = "zed-xim", version = "0.4.0-zed", optional = true }
+x11-clipboard = { version = "0.9.3", optional = true }

crates/gpui_linux/src/linux.rs 🔗

@@ -0,0 +1,57 @@
+mod dispatcher;
+mod headless;
+mod keyboard;
+mod platform;
+#[cfg(any(feature = "wayland", feature = "x11"))]
+mod text_system;
+#[cfg(feature = "wayland")]
+mod wayland;
+#[cfg(feature = "x11")]
+mod x11;
+
+#[cfg(any(feature = "wayland", feature = "x11"))]
+mod xdg_desktop_portal;
+
+pub use dispatcher::*;
+pub(crate) use headless::*;
+pub(crate) use keyboard::*;
+pub use platform::*;
+#[cfg(any(feature = "wayland", feature = "x11"))]
+pub(crate) use text_system::*;
+#[cfg(feature = "wayland")]
+pub(crate) use wayland::*;
+#[cfg(feature = "x11")]
+pub(crate) use x11::*;
+
+use std::rc::Rc;
+
+/// Returns the default platform implementation for the current OS.
+pub fn current_platform(headless: bool) -> Rc<dyn gpui::Platform> {
+    #[cfg(feature = "x11")]
+    use anyhow::Context as _;
+
+    if headless {
+        return Rc::new(LinuxPlatform {
+            inner: HeadlessClient::new(),
+        });
+    }
+
+    match gpui::guess_compositor() {
+        #[cfg(feature = "wayland")]
+        "Wayland" => Rc::new(LinuxPlatform {
+            inner: WaylandClient::new(),
+        }),
+
+        #[cfg(feature = "x11")]
+        "X11" => Rc::new(LinuxPlatform {
+            inner: X11Client::new()
+                .context("Failed to initialize X11 client.")
+                .unwrap(),
+        }),
+
+        "Headless" => Rc::new(LinuxPlatform {
+            inner: HeadlessClient::new(),
+        }),
+        _ => unreachable!(),
+    }
+}

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

@@ -11,7 +11,7 @@ use std::{
     time::{Duration, Instant},
 };
 
-use crate::{
+use gpui::{
     GLOBAL_THREAD_TIMINGS, PlatformDispatcher, Priority, PriorityQueueReceiver,
     PriorityQueueSender, RunnableVariant, THREAD_TIMINGS, TaskTiming, ThreadTaskTimings, profiler,
 };
@@ -39,8 +39,7 @@ impl LinuxDispatcher {
 
         let mut background_threads = (0..thread_count)
             .map(|i| {
-                let mut receiver: PriorityQueueReceiver<RunnableVariant> =
-                    background_receiver.clone();
+                let receiver: PriorityQueueReceiver<RunnableVariant> = background_receiver.clone();
                 std::thread::Builder::new()
                     .name(format!("Worker-{i}"))
                     .spawn(move || {
@@ -140,12 +139,12 @@ impl LinuxDispatcher {
 }
 
 impl PlatformDispatcher for LinuxDispatcher {
-    fn get_all_timings(&self) -> Vec<crate::ThreadTaskTimings> {
+    fn get_all_timings(&self) -> Vec<gpui::ThreadTaskTimings> {
         let global_timings = GLOBAL_THREAD_TIMINGS.lock();
         ThreadTaskTimings::convert(&global_timings)
     }
 
-    fn get_current_thread_timings(&self) -> crate::ThreadTaskTimings {
+    fn get_current_thread_timings(&self) -> gpui::ThreadTaskTimings {
         THREAD_TIMINGS.with(|timings| {
             let timings = timings.lock();
             let thread_name = timings.thread_name.clone();
@@ -158,7 +157,7 @@ impl PlatformDispatcher for LinuxDispatcher {
             vec.extend_from_slice(s1);
             vec.extend_from_slice(s2);
 
-            crate::ThreadTaskTimings {
+            gpui::ThreadTaskTimings {
                 thread_name,
                 thread_id: std::thread::current().id(),
                 timings: vec,
@@ -232,7 +231,7 @@ impl<T> PriorityQueueCalloopSender<T> {
         Self { sender: tx, ping }
     }
 
-    fn send(&self, priority: Priority, item: T) -> Result<(), crate::queue::SendError<T>> {
+    fn send(&self, priority: Priority, item: T) -> Result<(), gpui::queue::SendError<T>> {
         let res = self.sender.send(priority, item);
         if res.is_ok() {
             self.ping.ping();
@@ -312,7 +311,7 @@ impl<T> calloop::EventSource for PriorityQueueCalloopReceiver<T> {
             .process_events(readiness, token, |(), &mut ()| {
                 let mut is_empty = true;
 
-                let mut receiver = self.receiver.clone();
+                let receiver = self.receiver.clone();
                 for runnable in receiver.try_iter() {
                     match runnable {
                         Ok(r) => {
@@ -429,11 +428,11 @@ mod tests {
 }
 
 // running 1 test
-// test platform::linux::dispatcher::tests::tomato ... FAILED
+// test linux::dispatcher::tests::tomato ... FAILED
 
 // failures:
 
-// ---- platform::linux::dispatcher::tests::tomato stdout ----
+// ---- linux::dispatcher::tests::tomato stdout ----
 // [crates/gpui/src/platform/linux/dispatcher.rs:262:9]
 // returning 1 tasks to process
 // [crates/gpui/src/platform/linux/dispatcher.rs:480:75] evt = Msg(
@@ -441,6 +440,6 @@ mod tests {
 // )
 // returning 0 tasks to process
 
-// thread 'platform::linux::dispatcher::tests::tomato' (478301) panicked at crates/gpui/src/platform/linux/dispatcher.rs:515:9:
+// thread 'linux::dispatcher::tests::tomato' (478301) panicked at crates/gpui/src/platform/linux/dispatcher.rs:515:9:
 // assertion failed: data.got_closed
 // note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

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

@@ -4,11 +4,10 @@ use std::rc::Rc;
 use calloop::{EventLoop, LoopHandle};
 use util::ResultExt;
 
-use crate::platform::linux::LinuxClient;
-use crate::platform::{LinuxCommon, PlatformWindow};
-use crate::{
-    AnyWindowHandle, CursorStyle, DisplayId, LinuxKeyboardLayout, PlatformDisplay,
-    PlatformKeyboardLayout, WindowParams,
+use crate::linux::{LinuxClient, LinuxCommon, LinuxKeyboardLayout};
+use gpui::{
+    AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, PlatformKeyboardLayout,
+    PlatformWindow, WindowParams,
 };
 
 pub struct HeadlessClientState {
@@ -65,17 +64,11 @@ impl LinuxClient for HeadlessClient {
         None
     }
 
-    #[cfg(feature = "screen-capture")]
-    fn is_screen_capture_supported(&self) -> bool {
-        false
-    }
-
-    #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
-    ) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Rc<dyn crate::ScreenCaptureSource>>>>
+    ) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Rc<dyn gpui::ScreenCaptureSource>>>>
     {
-        let (mut tx, rx) = futures::channel::oneshot::channel();
+        let (tx, rx) = futures::channel::oneshot::channel();
         tx.send(Err(anyhow::anyhow!(
             "Headless mode does not support screen capture."
         )))
@@ -109,15 +102,15 @@ impl LinuxClient for HeadlessClient {
 
     fn reveal_path(&self, _path: std::path::PathBuf) {}
 
-    fn write_to_primary(&self, _item: crate::ClipboardItem) {}
+    fn write_to_primary(&self, _item: gpui::ClipboardItem) {}
 
-    fn write_to_clipboard(&self, _item: crate::ClipboardItem) {}
+    fn write_to_clipboard(&self, _item: gpui::ClipboardItem) {}
 
-    fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
+    fn read_from_primary(&self) -> Option<gpui::ClipboardItem> {
         None
     }
 
-    fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
+    fn read_from_clipboard(&self) -> Option<gpui::ClipboardItem> {
         None
     }
 

crates/gpui/src/platform/linux/keyboard.rs → crates/gpui_linux/src/linux/keyboard.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{PlatformKeyboardLayout, SharedString};
+use gpui::{PlatformKeyboardLayout, SharedString};
 
 #[derive(Clone)]
 pub(crate) struct LinuxKeyboardLayout {

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

@@ -21,15 +21,15 @@ use util::command::{new_command, new_std_command};
 #[cfg(any(feature = "wayland", feature = "x11"))]
 use xkbcommon::xkb::{self, Keycode, Keysym, State};
 
-use crate::{
+use crate::linux::{LinuxDispatcher, PriorityQueueCalloopReceiver};
+use gpui::{
     Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
-    ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions,
-    Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,
-    PlatformWindow, PriorityQueueCalloopReceiver, Result, RunnableVariant, Task, ThermalState,
-    WindowAppearance, WindowParams,
+    ForegroundExecutor, Keymap, Menu, MenuItem, OwnedMenu, PathPromptOptions, Platform,
+    PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,
+    PlatformWindow, Result, RunnableVariant, Task, ThermalState, WindowAppearance, WindowParams,
 };
 #[cfg(any(feature = "wayland", feature = "x11"))]
-use crate::{Pixels, Point, px};
+use gpui::{Pixels, Point, px};
 
 #[cfg(any(feature = "wayland", feature = "x11"))]
 pub(crate) const SCROLL_LINES: f32 = 3.0;
@@ -90,7 +90,7 @@ impl<T> ResultExt for anyhow::Result<T> {
     }
 }
 
-pub trait LinuxClient {
+pub(crate) trait LinuxClient {
     fn compositor_name(&self) -> &'static str;
     fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
     fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
@@ -99,11 +99,21 @@ pub trait LinuxClient {
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
     fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
     #[cfg(feature = "screen-capture")]
-    fn is_screen_capture_supported(&self) -> bool;
+    fn is_screen_capture_supported(&self) -> bool {
+        false
+    }
     #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
-    ) -> oneshot::Receiver<Result<Vec<Rc<dyn crate::ScreenCaptureSource>>>>;
+    ) -> oneshot::Receiver<Result<Vec<Rc<dyn gpui::ScreenCaptureSource>>>> {
+        let (sources_tx, sources_rx) = oneshot::channel();
+        sources_tx
+            .send(Err(anyhow::anyhow!(
+                "gpui_linux was compiled without the screen-capture feature"
+            )))
+            .ok();
+        sources_rx
+    }
 
     fn open_window(
         &self,
@@ -156,7 +166,7 @@ impl LinuxCommon {
         let (main_sender, main_receiver) = PriorityQueueCalloopReceiver::new();
 
         #[cfg(any(feature = "wayland", feature = "x11"))]
-        let text_system = Arc::new(crate::CosmicTextSystem::new());
+        let text_system = Arc::new(crate::linux::CosmicTextSystem::new());
         #[cfg(not(any(feature = "wayland", feature = "x11")))]
         let text_system = Arc::new(crate::NoopTextSystem::new());
 
@@ -181,29 +191,36 @@ impl LinuxCommon {
     }
 }
 
-impl<P: LinuxClient + 'static> Platform for P {
+pub(crate) struct LinuxPlatform<P> {
+    pub(crate) inner: P,
+}
+
+impl<P: LinuxClient + 'static> Platform for LinuxPlatform<P> {
     fn background_executor(&self) -> BackgroundExecutor {
-        self.with_common(|common| common.background_executor.clone())
+        self.inner
+            .with_common(|common| common.background_executor.clone())
     }
 
     fn foreground_executor(&self) -> ForegroundExecutor {
-        self.with_common(|common| common.foreground_executor.clone())
+        self.inner
+            .with_common(|common| common.foreground_executor.clone())
     }
 
     fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
-        self.with_common(|common| common.text_system.clone())
+        self.inner.with_common(|common| common.text_system.clone())
     }
 
     fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
-        self.keyboard_layout()
+        self.inner.keyboard_layout()
     }
 
     fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
-        Rc::new(crate::DummyKeyboardMapper)
+        Rc::new(gpui::DummyKeyboardMapper)
     }
 
     fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
-        self.with_common(|common| common.callbacks.keyboard_layout_change = Some(callback));
+        self.inner
+            .with_common(|common| common.callbacks.keyboard_layout_change = Some(callback));
     }
 
     fn on_thermal_state_change(&self, _callback: Box<dyn FnMut()>) {}
@@ -215,20 +232,22 @@ impl<P: LinuxClient + 'static> Platform for P {
     fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
         on_finish_launching();
 
-        LinuxClient::run(self);
+        LinuxClient::run(&self.inner);
 
-        let quit = self.with_common(|common| common.callbacks.quit.take());
+        let quit = self
+            .inner
+            .with_common(|common| common.callbacks.quit.take());
         if let Some(mut fun) = quit {
             fun();
         }
     }
 
     fn quit(&self) {
-        self.with_common(|common| common.signal.stop());
+        self.inner.with_common(|common| common.signal.stop());
     }
 
     fn compositor_name(&self) -> &'static str {
-        self.compositor_name()
+        self.inner.compositor_name()
     }
 
     fn restart(&self, binary_path: Option<PathBuf>) {
@@ -298,31 +317,31 @@ impl<P: LinuxClient + 'static> Platform for P {
     }
 
     fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
-        self.primary_display()
+        self.inner.primary_display()
     }
 
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
-        self.displays()
+        self.inner.displays()
     }
 
     #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool {
-        self.is_screen_capture_supported()
+        self.inner.is_screen_capture_supported()
     }
 
     #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
-    ) -> oneshot::Receiver<Result<Vec<Rc<dyn crate::ScreenCaptureSource>>>> {
-        self.screen_capture_sources()
+    ) -> oneshot::Receiver<Result<Vec<Rc<dyn gpui::ScreenCaptureSource>>>> {
+        self.inner.screen_capture_sources()
     }
 
     fn active_window(&self) -> Option<AnyWindowHandle> {
-        self.active_window()
+        self.inner.active_window()
     }
 
     fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
-        self.window_stack()
+        self.inner.window_stack()
     }
 
     fn open_window(
@@ -330,15 +349,16 @@ impl<P: LinuxClient + 'static> Platform for P {
         handle: AnyWindowHandle,
         options: WindowParams,
     ) -> anyhow::Result<Box<dyn PlatformWindow>> {
-        self.open_window(handle, options)
+        self.inner.open_window(handle, options)
     }
 
     fn open_url(&self, url: &str) {
-        self.open_uri(url);
+        self.inner.open_uri(url);
     }
 
     fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
-        self.with_common(|common| common.callbacks.open_urls = Some(callback));
+        self.inner
+            .with_common(|common| common.callbacks.open_urls = Some(callback));
     }
 
     fn prompt_for_paths(
@@ -351,7 +371,7 @@ impl<P: LinuxClient + 'static> Platform for P {
         let _ = (done_tx.send(Ok(None)), options);
 
         #[cfg(any(feature = "wayland", feature = "x11"))]
-        let identifier = self.window_identifier();
+        let identifier = self.inner.window_identifier();
 
         #[cfg(any(feature = "wayland", feature = "x11"))]
         self.foreground_executor()
@@ -366,7 +386,7 @@ impl<P: LinuxClient + 'static> Platform for P {
                     .identifier(identifier.await)
                     .modal(true)
                     .title(title)
-                    .accept_label(options.prompt.as_ref().map(crate::SharedString::as_str))
+                    .accept_label(options.prompt.as_ref().map(gpui::SharedString::as_str))
                     .multiple(options.multiple)
                     .directory(options.directories)
                     .send()
@@ -411,7 +431,7 @@ impl<P: LinuxClient + 'static> Platform for P {
         let _ = (done_tx.send(Ok(None)), directory, suggested_name);
 
         #[cfg(any(feature = "wayland", feature = "x11"))]
-        let identifier = self.window_identifier();
+        let identifier = self.inner.window_identifier();
 
         #[cfg(any(feature = "wayland", feature = "x11"))]
         self.foreground_executor()
@@ -468,7 +488,7 @@ impl<P: LinuxClient + 'static> Platform for P {
     }
 
     fn reveal_path(&self, path: &Path) {
-        self.reveal_path(path.to_owned());
+        self.inner.reveal_path(path.to_owned());
     }
 
     fn open_with_system(&self, path: &Path) {
@@ -489,31 +509,31 @@ impl<P: LinuxClient + 'static> Platform for P {
     }
 
     fn on_quit(&self, callback: Box<dyn FnMut()>) {
-        self.with_common(|common| {
+        self.inner.with_common(|common| {
             common.callbacks.quit = Some(callback);
         });
     }
 
     fn on_reopen(&self, callback: Box<dyn FnMut()>) {
-        self.with_common(|common| {
+        self.inner.with_common(|common| {
             common.callbacks.reopen = Some(callback);
         });
     }
 
     fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
-        self.with_common(|common| {
+        self.inner.with_common(|common| {
             common.callbacks.app_menu_action = Some(callback);
         });
     }
 
     fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
-        self.with_common(|common| {
+        self.inner.with_common(|common| {
             common.callbacks.will_open_app_menu = Some(callback);
         });
     }
 
     fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
-        self.with_common(|common| {
+        self.inner.with_common(|common| {
             common.callbacks.validate_app_menu_command = Some(callback);
         });
     }
@@ -525,13 +545,13 @@ impl<P: LinuxClient + 'static> Platform for P {
     }
 
     fn set_menus(&self, menus: Vec<Menu>, _keymap: &Keymap) {
-        self.with_common(|common| {
+        self.inner.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()))
+        self.inner.with_common(|common| Some(common.menus.clone()))
     }
 
     fn set_dock_menu(&self, _menu: Vec<MenuItem>, _keymap: &Keymap) {
@@ -545,11 +565,11 @@ impl<P: LinuxClient + 'static> Platform for P {
     }
 
     fn set_cursor_style(&self, style: CursorStyle) {
-        self.set_cursor_style(style)
+        self.inner.set_cursor_style(style)
     }
 
     fn should_auto_hide_scrollbars(&self) -> bool {
-        self.with_common(|common| common.auto_hide_scrollbars)
+        self.inner.with_common(|common| common.auto_hide_scrollbars)
     }
 
     fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
@@ -619,7 +639,7 @@ impl<P: LinuxClient + 'static> Platform for P {
     }
 
     fn window_appearance(&self) -> WindowAppearance {
-        self.with_common(|common| common.appearance)
+        self.inner.with_common(|common| common.appearance)
     }
 
     fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
@@ -627,19 +647,19 @@ impl<P: LinuxClient + 'static> Platform for P {
     }
 
     fn write_to_primary(&self, item: ClipboardItem) {
-        self.write_to_primary(item)
+        self.inner.write_to_primary(item)
     }
 
     fn write_to_clipboard(&self, item: ClipboardItem) {
-        self.write_to_clipboard(item)
+        self.inner.write_to_clipboard(item)
     }
 
     fn read_from_primary(&self) -> Option<ClipboardItem> {
-        self.read_from_primary()
+        self.inner.read_from_primary()
     }
 
     fn read_from_clipboard(&self) -> Option<ClipboardItem> {
-        self.read_from_clipboard()
+        self.inner.read_from_clipboard()
     }
 
     fn add_recent_document(&self, _path: &Path) {}
@@ -750,39 +770,37 @@ pub(super) unsafe fn read_fd(fd: filedescriptor::FileDescriptor) -> Result<Vec<u
 #[cfg(any(feature = "wayland", feature = "x11"))]
 pub(super) const DEFAULT_CURSOR_ICON_NAME: &str = "left_ptr";
 
-impl CursorStyle {
-    #[cfg(any(feature = "wayland", feature = "x11"))]
-    pub(super) fn to_icon_names(self) -> &'static [&'static str] {
-        // Based on cursor names from chromium:
-        // https://github.com/chromium/chromium/blob/d3069cf9c973dc3627fa75f64085c6a86c8f41bf/ui/base/cursor/cursor_factory.cc#L113
-        match self {
-            CursorStyle::Arrow => &[DEFAULT_CURSOR_ICON_NAME],
-            CursorStyle::IBeam => &["text", "xterm"],
-            CursorStyle::Crosshair => &["crosshair", "cross"],
-            CursorStyle::ClosedHand => &["closedhand", "grabbing", "hand2"],
-            CursorStyle::OpenHand => &["openhand", "grab", "hand1"],
-            CursorStyle::PointingHand => &["pointer", "hand", "hand2"],
-            CursorStyle::ResizeLeft => &["w-resize", "left_side"],
-            CursorStyle::ResizeRight => &["e-resize", "right_side"],
-            CursorStyle::ResizeLeftRight => &["ew-resize", "sb_h_double_arrow"],
-            CursorStyle::ResizeUp => &["n-resize", "top_side"],
-            CursorStyle::ResizeDown => &["s-resize", "bottom_side"],
-            CursorStyle::ResizeUpDown => &["sb_v_double_arrow", "ns-resize"],
-            CursorStyle::ResizeUpLeftDownRight => &["size_fdiag", "bd_double_arrow", "nwse-resize"],
-            CursorStyle::ResizeUpRightDownLeft => &["size_bdiag", "nesw-resize", "fd_double_arrow"],
-            CursorStyle::ResizeColumn => &["col-resize", "sb_h_double_arrow"],
-            CursorStyle::ResizeRow => &["row-resize", "sb_v_double_arrow"],
-            CursorStyle::IBeamCursorForVerticalLayout => &["vertical-text"],
-            CursorStyle::OperationNotAllowed => &["not-allowed", "crossed_circle"],
-            CursorStyle::DragLink => &["alias"],
-            CursorStyle::DragCopy => &["copy"],
-            CursorStyle::ContextualMenu => &["context-menu"],
-            CursorStyle::None => {
-                #[cfg(debug_assertions)]
-                panic!("CursorStyle::None should be handled separately in the client");
-                #[cfg(not(debug_assertions))]
-                &[DEFAULT_CURSOR_ICON_NAME]
-            }
+#[cfg(any(feature = "wayland", feature = "x11"))]
+pub(super) fn cursor_style_to_icon_names(style: CursorStyle) -> &'static [&'static str] {
+    // Based on cursor names from chromium:
+    // https://github.com/chromium/chromium/blob/d3069cf9c973dc3627fa75f64085c6a86c8f41bf/ui/base/cursor/cursor_factory.cc#L113
+    match style {
+        CursorStyle::Arrow => &[DEFAULT_CURSOR_ICON_NAME],
+        CursorStyle::IBeam => &["text", "xterm"],
+        CursorStyle::Crosshair => &["crosshair", "cross"],
+        CursorStyle::ClosedHand => &["closedhand", "grabbing", "hand2"],
+        CursorStyle::OpenHand => &["openhand", "grab", "hand1"],
+        CursorStyle::PointingHand => &["pointer", "hand", "hand2"],
+        CursorStyle::ResizeLeft => &["w-resize", "left_side"],
+        CursorStyle::ResizeRight => &["e-resize", "right_side"],
+        CursorStyle::ResizeLeftRight => &["ew-resize", "sb_h_double_arrow"],
+        CursorStyle::ResizeUp => &["n-resize", "top_side"],
+        CursorStyle::ResizeDown => &["s-resize", "bottom_side"],
+        CursorStyle::ResizeUpDown => &["sb_v_double_arrow", "ns-resize"],
+        CursorStyle::ResizeUpLeftDownRight => &["size_fdiag", "bd_double_arrow", "nwse-resize"],
+        CursorStyle::ResizeUpRightDownLeft => &["size_bdiag", "nesw-resize", "fd_double_arrow"],
+        CursorStyle::ResizeColumn => &["col-resize", "sb_h_double_arrow"],
+        CursorStyle::ResizeRow => &["row-resize", "sb_v_double_arrow"],
+        CursorStyle::IBeamCursorForVerticalLayout => &["vertical-text"],
+        CursorStyle::OperationNotAllowed => &["not-allowed", "crossed_circle"],
+        CursorStyle::DragLink => &["alias"],
+        CursorStyle::DragCopy => &["copy"],
+        CursorStyle::ContextualMenu => &["context-menu"],
+        CursorStyle::None => {
+            #[cfg(debug_assertions)]
+            panic!("CursorStyle::None should be handled separately in the client");
+            #[cfg(not(debug_assertions))]
+            &[DEFAULT_CURSOR_ICON_NAME]
         }
     }
 }
@@ -856,222 +874,214 @@ fn guess_ascii(keycode: Keycode, shift: bool) -> Option<char> {
 }
 
 #[cfg(any(feature = "wayland", feature = "x11"))]
-impl crate::Keystroke {
-    pub(super) fn from_xkb(
-        state: &State,
-        mut modifiers: crate::Modifiers,
-        keycode: Keycode,
-    ) -> Self {
-        let key_utf32 = state.key_get_utf32(keycode);
-        let key_utf8 = state.key_get_utf8(keycode);
-        let key_sym = state.key_get_one_sym(keycode);
-
-        let key = match key_sym {
-            Keysym::Return => "enter".to_owned(),
-            Keysym::Prior => "pageup".to_owned(),
-            Keysym::Next => "pagedown".to_owned(),
-            Keysym::ISO_Left_Tab => "tab".to_owned(),
-            Keysym::KP_Prior => "pageup".to_owned(),
-            Keysym::KP_Next => "pagedown".to_owned(),
-            Keysym::XF86_Back => "back".to_owned(),
-            Keysym::XF86_Forward => "forward".to_owned(),
-            Keysym::XF86_Cut => "cut".to_owned(),
-            Keysym::XF86_Copy => "copy".to_owned(),
-            Keysym::XF86_Paste => "paste".to_owned(),
-            Keysym::XF86_New => "new".to_owned(),
-            Keysym::XF86_Open => "open".to_owned(),
-            Keysym::XF86_Save => "save".to_owned(),
-
-            Keysym::comma => ",".to_owned(),
-            Keysym::period => ".".to_owned(),
-            Keysym::less => "<".to_owned(),
-            Keysym::greater => ">".to_owned(),
-            Keysym::slash => "/".to_owned(),
-            Keysym::question => "?".to_owned(),
-
-            Keysym::semicolon => ";".to_owned(),
-            Keysym::colon => ":".to_owned(),
-            Keysym::apostrophe => "'".to_owned(),
-            Keysym::quotedbl => "\"".to_owned(),
-
-            Keysym::bracketleft => "[".to_owned(),
-            Keysym::braceleft => "{".to_owned(),
-            Keysym::bracketright => "]".to_owned(),
-            Keysym::braceright => "}".to_owned(),
-            Keysym::backslash => "\\".to_owned(),
-            Keysym::bar => "|".to_owned(),
-
-            Keysym::grave => "`".to_owned(),
-            Keysym::asciitilde => "~".to_owned(),
-            Keysym::exclam => "!".to_owned(),
-            Keysym::at => "@".to_owned(),
-            Keysym::numbersign => "#".to_owned(),
-            Keysym::dollar => "$".to_owned(),
-            Keysym::percent => "%".to_owned(),
-            Keysym::asciicircum => "^".to_owned(),
-            Keysym::ampersand => "&".to_owned(),
-            Keysym::asterisk => "*".to_owned(),
-            Keysym::parenleft => "(".to_owned(),
-            Keysym::parenright => ")".to_owned(),
-            Keysym::minus => "-".to_owned(),
-            Keysym::underscore => "_".to_owned(),
-            Keysym::equal => "=".to_owned(),
-            Keysym::plus => "+".to_owned(),
-            Keysym::space => "space".to_owned(),
-            Keysym::BackSpace => "backspace".to_owned(),
-            Keysym::Tab => "tab".to_owned(),
-            Keysym::Delete => "delete".to_owned(),
-            Keysym::Escape => "escape".to_owned(),
-
-            Keysym::Left => "left".to_owned(),
-            Keysym::Right => "right".to_owned(),
-            Keysym::Up => "up".to_owned(),
-            Keysym::Down => "down".to_owned(),
-            Keysym::Home => "home".to_owned(),
-            Keysym::End => "end".to_owned(),
-            Keysym::Insert => "insert".to_owned(),
-
-            _ => {
-                let name = xkb::keysym_get_name(key_sym).to_lowercase();
-                if key_sym.is_keypad_key() {
-                    name.replace("kp_", "")
-                } else if let Some(key) = key_utf8.chars().next()
-                    && key_utf8.len() == 1
-                    && key.is_ascii()
+pub(super) fn keystroke_from_xkb(
+    state: &State,
+    mut modifiers: gpui::Modifiers,
+    keycode: Keycode,
+) -> gpui::Keystroke {
+    let key_utf32 = state.key_get_utf32(keycode);
+    let key_utf8 = state.key_get_utf8(keycode);
+    let key_sym = state.key_get_one_sym(keycode);
+
+    let key = match key_sym {
+        Keysym::Return => "enter".to_owned(),
+        Keysym::Prior => "pageup".to_owned(),
+        Keysym::Next => "pagedown".to_owned(),
+        Keysym::ISO_Left_Tab => "tab".to_owned(),
+        Keysym::KP_Prior => "pageup".to_owned(),
+        Keysym::KP_Next => "pagedown".to_owned(),
+        Keysym::XF86_Back => "back".to_owned(),
+        Keysym::XF86_Forward => "forward".to_owned(),
+        Keysym::XF86_Cut => "cut".to_owned(),
+        Keysym::XF86_Copy => "copy".to_owned(),
+        Keysym::XF86_Paste => "paste".to_owned(),
+        Keysym::XF86_New => "new".to_owned(),
+        Keysym::XF86_Open => "open".to_owned(),
+        Keysym::XF86_Save => "save".to_owned(),
+
+        Keysym::comma => ",".to_owned(),
+        Keysym::period => ".".to_owned(),
+        Keysym::less => "<".to_owned(),
+        Keysym::greater => ">".to_owned(),
+        Keysym::slash => "/".to_owned(),
+        Keysym::question => "?".to_owned(),
+
+        Keysym::semicolon => ";".to_owned(),
+        Keysym::colon => ":".to_owned(),
+        Keysym::apostrophe => "'".to_owned(),
+        Keysym::quotedbl => "\"".to_owned(),
+
+        Keysym::bracketleft => "[".to_owned(),
+        Keysym::braceleft => "{".to_owned(),
+        Keysym::bracketright => "]".to_owned(),
+        Keysym::braceright => "}".to_owned(),
+        Keysym::backslash => "\\".to_owned(),
+        Keysym::bar => "|".to_owned(),
+
+        Keysym::grave => "`".to_owned(),
+        Keysym::asciitilde => "~".to_owned(),
+        Keysym::exclam => "!".to_owned(),
+        Keysym::at => "@".to_owned(),
+        Keysym::numbersign => "#".to_owned(),
+        Keysym::dollar => "$".to_owned(),
+        Keysym::percent => "%".to_owned(),
+        Keysym::asciicircum => "^".to_owned(),
+        Keysym::ampersand => "&".to_owned(),
+        Keysym::asterisk => "*".to_owned(),
+        Keysym::parenleft => "(".to_owned(),
+        Keysym::parenright => ")".to_owned(),
+        Keysym::minus => "-".to_owned(),
+        Keysym::underscore => "_".to_owned(),
+        Keysym::equal => "=".to_owned(),
+        Keysym::plus => "+".to_owned(),
+        Keysym::space => "space".to_owned(),
+        Keysym::BackSpace => "backspace".to_owned(),
+        Keysym::Tab => "tab".to_owned(),
+        Keysym::Delete => "delete".to_owned(),
+        Keysym::Escape => "escape".to_owned(),
+
+        Keysym::Left => "left".to_owned(),
+        Keysym::Right => "right".to_owned(),
+        Keysym::Up => "up".to_owned(),
+        Keysym::Down => "down".to_owned(),
+        Keysym::Home => "home".to_owned(),
+        Keysym::End => "end".to_owned(),
+        Keysym::Insert => "insert".to_owned(),
+
+        _ => {
+            let name = xkb::keysym_get_name(key_sym).to_lowercase();
+            if key_sym.is_keypad_key() {
+                name.replace("kp_", "")
+            } else if let Some(key) = key_utf8.chars().next()
+                && key_utf8.len() == 1
+                && key.is_ascii()
+            {
+                if key.is_ascii_graphic() {
+                    key_utf8.to_lowercase()
+                // map ctrl-a to `a`
+                // ctrl-0..9 may emit control codes like ctrl-[, but
+                // we don't want to map them to `[`
+                } else if key_utf32 <= 0x1f
+                    && !name.chars().next().is_some_and(|c| c.is_ascii_digit())
                 {
-                    if key.is_ascii_graphic() {
-                        key_utf8.to_lowercase()
-                    // map ctrl-a to `a`
-                    // ctrl-0..9 may emit control codes like ctrl-[, but
-                    // we don't want to map them to `[`
-                    } else if key_utf32 <= 0x1f
-                        && !name.chars().next().is_some_and(|c| c.is_ascii_digit())
-                    {
-                        ((key_utf32 as u8 + 0x40) as char)
-                            .to_ascii_lowercase()
-                            .to_string()
-                    } else {
-                        name
-                    }
-                } else if let Some(key_en) = guess_ascii(keycode, modifiers.shift) {
-                    String::from(key_en)
+                    ((key_utf32 as u8 + 0x40) as char)
+                        .to_ascii_lowercase()
+                        .to_string()
                 } else {
                     name
                 }
-            }
-        };
-
-        if modifiers.shift {
-            // we only include the shift for upper-case letters by convention,
-            // so don't include for numbers and symbols, but do include for
-            // tab/enter, etc.
-            if key.chars().count() == 1 && key.to_lowercase() == key.to_uppercase() {
-                modifiers.shift = false;
+            } else if let Some(key_en) = guess_ascii(keycode, modifiers.shift) {
+                String::from(key_en)
+            } else {
+                name
             }
         }
+    };
 
-        // Ignore control characters (and DEL) for the purposes of key_char
-        let key_char =
-            (key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8);
-
-        Self {
-            modifiers,
-            key,
-            key_char,
+    if modifiers.shift {
+        // we only include the shift for upper-case letters by convention,
+        // so don't include for numbers and symbols, but do include for
+        // tab/enter, etc.
+        if key.chars().count() == 1 && key.to_lowercase() == key.to_uppercase() {
+            modifiers.shift = false;
         }
     }
 
-    /**
-     * Returns which symbol the dead key represents
-     * <https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#dead_keycodes_for_linux>
-     */
-    pub fn underlying_dead_key(keysym: Keysym) -> Option<String> {
-        match keysym {
-            Keysym::dead_grave => Some("`".to_owned()),
-            Keysym::dead_acute => Some("´".to_owned()),
-            Keysym::dead_circumflex => Some("^".to_owned()),
-            Keysym::dead_tilde => Some("~".to_owned()),
-            Keysym::dead_macron => Some("¯".to_owned()),
-            Keysym::dead_breve => Some("˘".to_owned()),
-            Keysym::dead_abovedot => Some("˙".to_owned()),
-            Keysym::dead_diaeresis => Some("¨".to_owned()),
-            Keysym::dead_abovering => Some("˚".to_owned()),
-            Keysym::dead_doubleacute => Some("˝".to_owned()),
-            Keysym::dead_caron => Some("ˇ".to_owned()),
-            Keysym::dead_cedilla => Some("¸".to_owned()),
-            Keysym::dead_ogonek => Some("˛".to_owned()),
-            Keysym::dead_iota => Some("ͅ".to_owned()),
-            Keysym::dead_voiced_sound => Some("゙".to_owned()),
-            Keysym::dead_semivoiced_sound => Some("゚".to_owned()),
-            Keysym::dead_belowdot => Some("̣̣".to_owned()),
-            Keysym::dead_hook => Some("̡".to_owned()),
-            Keysym::dead_horn => Some("̛".to_owned()),
-            Keysym::dead_stroke => Some("̶̶".to_owned()),
-            Keysym::dead_abovecomma => Some("̓̓".to_owned()),
-            Keysym::dead_abovereversedcomma => Some("ʽ".to_owned()),
-            Keysym::dead_doublegrave => Some("̏".to_owned()),
-            Keysym::dead_belowring => Some("˳".to_owned()),
-            Keysym::dead_belowmacron => Some("̱".to_owned()),
-            Keysym::dead_belowcircumflex => Some("ꞈ".to_owned()),
-            Keysym::dead_belowtilde => Some("̰".to_owned()),
-            Keysym::dead_belowbreve => Some("̮".to_owned()),
-            Keysym::dead_belowdiaeresis => Some("̤".to_owned()),
-            Keysym::dead_invertedbreve => Some("̯".to_owned()),
-            Keysym::dead_belowcomma => Some("̦".to_owned()),
-            Keysym::dead_currency => None,
-            Keysym::dead_lowline => None,
-            Keysym::dead_aboveverticalline => None,
-            Keysym::dead_belowverticalline => None,
-            Keysym::dead_longsolidusoverlay => None,
-            Keysym::dead_a => None,
-            Keysym::dead_A => None,
-            Keysym::dead_e => None,
-            Keysym::dead_E => None,
-            Keysym::dead_i => None,
-            Keysym::dead_I => None,
-            Keysym::dead_o => None,
-            Keysym::dead_O => None,
-            Keysym::dead_u => None,
-            Keysym::dead_U => None,
-            Keysym::dead_small_schwa => Some("ə".to_owned()),
-            Keysym::dead_capital_schwa => Some("Ə".to_owned()),
-            Keysym::dead_greek => None,
-            _ => None,
-        }
+    // Ignore control characters (and DEL) for the purposes of key_char
+    let key_char =
+        (key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8);
+
+    gpui::Keystroke {
+        modifiers,
+        key,
+        key_char,
     }
 }
 
+/**
+ * Returns which symbol the dead key represents
+ * <https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#dead_keycodes_for_linux>
+ */
 #[cfg(any(feature = "wayland", feature = "x11"))]
-impl crate::Modifiers {
-    pub(super) fn from_xkb(keymap_state: &State) -> Self {
-        let shift = keymap_state.mod_name_is_active(xkb::MOD_NAME_SHIFT, xkb::STATE_MODS_EFFECTIVE);
-        let alt = keymap_state.mod_name_is_active(xkb::MOD_NAME_ALT, xkb::STATE_MODS_EFFECTIVE);
-        let control =
-            keymap_state.mod_name_is_active(xkb::MOD_NAME_CTRL, xkb::STATE_MODS_EFFECTIVE);
-        let platform =
-            keymap_state.mod_name_is_active(xkb::MOD_NAME_LOGO, xkb::STATE_MODS_EFFECTIVE);
-        Self {
-            shift,
-            alt,
-            control,
-            platform,
-            function: false,
-        }
+pub fn keystroke_underlying_dead_key(keysym: Keysym) -> Option<String> {
+    match keysym {
+        Keysym::dead_grave => Some("`".to_owned()),
+        Keysym::dead_acute => Some("´".to_owned()),
+        Keysym::dead_circumflex => Some("^".to_owned()),
+        Keysym::dead_tilde => Some("~".to_owned()),
+        Keysym::dead_macron => Some("¯".to_owned()),
+        Keysym::dead_breve => Some("˘".to_owned()),
+        Keysym::dead_abovedot => Some("˙".to_owned()),
+        Keysym::dead_diaeresis => Some("¨".to_owned()),
+        Keysym::dead_abovering => Some("˚".to_owned()),
+        Keysym::dead_doubleacute => Some("˝".to_owned()),
+        Keysym::dead_caron => Some("ˇ".to_owned()),
+        Keysym::dead_cedilla => Some("¸".to_owned()),
+        Keysym::dead_ogonek => Some("˛".to_owned()),
+        Keysym::dead_iota => Some("ͅ".to_owned()),
+        Keysym::dead_voiced_sound => Some("゙".to_owned()),
+        Keysym::dead_semivoiced_sound => Some("゚".to_owned()),
+        Keysym::dead_belowdot => Some("̣̣".to_owned()),
+        Keysym::dead_hook => Some("̡".to_owned()),
+        Keysym::dead_horn => Some("̛".to_owned()),
+        Keysym::dead_stroke => Some("̶̶".to_owned()),
+        Keysym::dead_abovecomma => Some("̓̓".to_owned()),
+        Keysym::dead_abovereversedcomma => Some("ʽ".to_owned()),
+        Keysym::dead_doublegrave => Some("̏".to_owned()),
+        Keysym::dead_belowring => Some("˳".to_owned()),
+        Keysym::dead_belowmacron => Some("̱".to_owned()),
+        Keysym::dead_belowcircumflex => Some("ꞈ".to_owned()),
+        Keysym::dead_belowtilde => Some("̰".to_owned()),
+        Keysym::dead_belowbreve => Some("̮".to_owned()),
+        Keysym::dead_belowdiaeresis => Some("̤".to_owned()),
+        Keysym::dead_invertedbreve => Some("̯".to_owned()),
+        Keysym::dead_belowcomma => Some("̦".to_owned()),
+        Keysym::dead_currency => None,
+        Keysym::dead_lowline => None,
+        Keysym::dead_aboveverticalline => None,
+        Keysym::dead_belowverticalline => None,
+        Keysym::dead_longsolidusoverlay => None,
+        Keysym::dead_a => None,
+        Keysym::dead_A => None,
+        Keysym::dead_e => None,
+        Keysym::dead_E => None,
+        Keysym::dead_i => None,
+        Keysym::dead_I => None,
+        Keysym::dead_o => None,
+        Keysym::dead_O => None,
+        Keysym::dead_u => None,
+        Keysym::dead_U => None,
+        Keysym::dead_small_schwa => Some("ə".to_owned()),
+        Keysym::dead_capital_schwa => Some("Ə".to_owned()),
+        Keysym::dead_greek => None,
+        _ => None,
     }
 }
-
 #[cfg(any(feature = "wayland", feature = "x11"))]
-impl crate::Capslock {
-    pub(super) fn from_xkb(keymap_state: &State) -> Self {
-        let on = keymap_state.mod_name_is_active(xkb::MOD_NAME_CAPS, xkb::STATE_MODS_EFFECTIVE);
-        Self { on }
+pub(super) fn modifiers_from_xkb(keymap_state: &State) -> gpui::Modifiers {
+    let shift = keymap_state.mod_name_is_active(xkb::MOD_NAME_SHIFT, xkb::STATE_MODS_EFFECTIVE);
+    let alt = keymap_state.mod_name_is_active(xkb::MOD_NAME_ALT, xkb::STATE_MODS_EFFECTIVE);
+    let control = keymap_state.mod_name_is_active(xkb::MOD_NAME_CTRL, xkb::STATE_MODS_EFFECTIVE);
+    let platform = keymap_state.mod_name_is_active(xkb::MOD_NAME_LOGO, xkb::STATE_MODS_EFFECTIVE);
+    gpui::Modifiers {
+        shift,
+        alt,
+        control,
+        platform,
+        function: false,
     }
 }
 
+#[cfg(any(feature = "wayland", feature = "x11"))]
+pub(super) fn capslock_from_xkb(keymap_state: &State) -> gpui::Capslock {
+    let on = keymap_state.mod_name_is_active(xkb::MOD_NAME_CAPS, xkb::STATE_MODS_EFFECTIVE);
+    gpui::Capslock { on }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{Point, px};
+    use gpui::{Point, px};
 
     #[test]
     fn test_is_within_click_distance() {

crates/gpui/src/platform/linux/text_system.rs → crates/gpui_linux/src/linux/text_system.rs 🔗

@@ -1,22 +1,17 @@
-use crate::{
-    Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, FontStyle, FontWeight,
-    GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, SUBPIXEL_VARIANTS_X,
-    SUBPIXEL_VARIANTS_Y, ShapedGlyph, ShapedRun, SharedString, Size, TextRenderingMode, point,
-    size,
-};
 use anyhow::{Context as _, Ok, Result};
 use collections::HashMap;
 use cosmic_text::{
     Attrs, AttrsList, Family, Font as CosmicTextFont, FontFeatures as CosmicFontFeatures,
     FontSystem, ShapeBuffer, ShapeLine,
 };
+use gpui::{
+    Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, GlyphId, LineLayout,
+    Pixels, PlatformTextSystem, RenderGlyphParams, SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y,
+    ShapedGlyph, ShapedRun, SharedString, Size, TextRenderingMode, point, size,
+};
 
 use itertools::Itertools;
 use parking_lot::RwLock;
-use pathfinder_geometry::{
-    rect::{RectF, RectI},
-    vector::{Vector2F, Vector2I},
-};
 use smallvec::SmallVec;
 use std::{borrow::Cow, sync::Arc};
 use swash::{
@@ -58,7 +53,7 @@ struct LoadedFont {
 impl CosmicTextSystem {
     pub(crate) fn new() -> Self {
         // todo(linux) make font loading non-blocking
-        let mut font_system = FontSystem::new();
+        let font_system = FontSystem::new();
 
         Self(RwLock::new(CosmicTextSystemState {
             font_system,
@@ -227,7 +222,7 @@ impl CosmicTextSystemState {
         features: &FontFeatures,
     ) -> Result<SmallVec<[FontId; 4]>> {
         // TODO: Determine the proper system UI font.
-        let name = crate::text_system::font_name_with_fallbacks(name, "IBM Plex Sans");
+        let name = gpui::font_name_with_fallbacks(name, "IBM Plex Sans");
 
         let families = self
             .font_system
@@ -261,7 +256,7 @@ impl CosmicTextSystemState {
             loaded_font_ids.push(font_id);
             self.loaded_fonts.push(LoadedFont {
                 font,
-                features: features.try_into()?,
+                features: cosmic_font_features(features)?,
                 is_known_emoji_font: check_is_known_emoji_font(&postscript_name),
             });
         }
@@ -324,7 +319,7 @@ impl CosmicTextSystemState {
     ) -> Result<swash::scale::image::Image> {
         let loaded_font = &self.loaded_fonts[params.font_id.0];
         let font_ref = loaded_font.font.as_swash();
-        let pixel_size = params.font_size.0;
+        let pixel_size = f32::from(params.font_size);
 
         let subpixel_offset = Vector::new(
             params.subpixel_variant.x as f32 / SUBPIXEL_VARIANTS_X as f32 / params.scale_factor,
@@ -428,7 +423,7 @@ impl CosmicTextSystemState {
         let mut layout_lines = Vec::with_capacity(1);
         line.layout_to_buffer(
             &mut self.scratch,
-            font_size.0,
+            f32::from(font_size),
             None, // We do our own wrapping
             cosmic_text::Wrap::None,
             None,
@@ -484,93 +479,28 @@ impl CosmicTextSystemState {
     }
 }
 
-impl TryFrom<&FontFeatures> for CosmicFontFeatures {
-    type Error = anyhow::Error;
-
-    fn try_from(features: &FontFeatures) -> Result<Self> {
-        let mut result = CosmicFontFeatures::new();
-        for feature in features.0.iter() {
-            let name_bytes: [u8; 4] = feature
-                .0
-                .as_bytes()
-                .try_into()
-                .context("Incorrect feature flag format")?;
-
-            let tag = cosmic_text::FeatureTag::new(&name_bytes);
-
-            result.set(tag, feature.1);
-        }
-        Ok(result)
-    }
-}
-
-impl From<RectF> for Bounds<f32> {
-    fn from(rect: RectF) -> Self {
-        Bounds {
-            origin: point(rect.origin_x(), rect.origin_y()),
-            size: size(rect.width(), rect.height()),
-        }
-    }
-}
-
-impl From<RectI> for Bounds<DevicePixels> {
-    fn from(rect: RectI) -> Self {
-        Bounds {
-            origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())),
-            size: size(DevicePixels(rect.width()), DevicePixels(rect.height())),
-        }
-    }
-}
-
-impl From<Vector2I> for Size<DevicePixels> {
-    fn from(value: Vector2I) -> Self {
-        size(value.x().into(), value.y().into())
-    }
-}
-
-impl From<RectI> for Bounds<i32> {
-    fn from(rect: RectI) -> Self {
-        Bounds {
-            origin: point(rect.origin_x(), rect.origin_y()),
-            size: size(rect.width(), rect.height()),
-        }
-    }
-}
-
-impl From<Point<u32>> for Vector2I {
-    fn from(size: Point<u32>) -> Self {
-        Vector2I::new(size.x as i32, size.y as i32)
-    }
-}
-
-impl From<Vector2F> for Size<f32> {
-    fn from(vec: Vector2F) -> Self {
-        size(vec.x(), vec.y())
-    }
-}
+fn cosmic_font_features(features: &FontFeatures) -> Result<CosmicFontFeatures> {
+    let mut result = CosmicFontFeatures::new();
+    for feature in features.0.iter() {
+        let name_bytes: [u8; 4] = feature
+            .0
+            .as_bytes()
+            .try_into()
+            .context("Incorrect feature flag format")?;
 
-impl From<FontWeight> for cosmic_text::Weight {
-    fn from(value: FontWeight) -> Self {
-        cosmic_text::Weight(value.0 as u16)
-    }
-}
+        let tag = cosmic_text::FeatureTag::new(&name_bytes);
 
-impl From<FontStyle> for cosmic_text::Style {
-    fn from(style: FontStyle) -> Self {
-        match style {
-            FontStyle::Normal => cosmic_text::Style::Normal,
-            FontStyle::Italic => cosmic_text::Style::Italic,
-            FontStyle::Oblique => cosmic_text::Style::Oblique,
-        }
+        result.set(tag, feature.1);
     }
+    Ok(result)
 }
 
-fn font_into_properties(font: &crate::Font) -> font_kit::properties::Properties {
+fn font_into_properties(font: &gpui::Font) -> font_kit::properties::Properties {
     font_kit::properties::Properties {
         style: match font.style {
-            crate::FontStyle::Normal => font_kit::properties::Style::Normal,
-            crate::FontStyle::Italic => font_kit::properties::Style::Italic,
-            crate::FontStyle::Oblique => font_kit::properties::Style::Oblique,
+            gpui::FontStyle::Normal => font_kit::properties::Style::Normal,
+            gpui::FontStyle::Italic => font_kit::properties::Style::Italic,
+            gpui::FontStyle::Oblique => font_kit::properties::Style::Oblique,
         },
         weight: font_kit::properties::Weight(font.weight.0),
         stretch: Default::default(),

crates/gpui_linux/src/linux/wayland.rs 🔗

@@ -0,0 +1,47 @@
+mod client;
+mod clipboard;
+mod cursor;
+mod display;
+mod serial;
+mod window;
+
+/// Contains Types for configuring layer_shell surfaces.
+pub mod layer_shell;
+
+pub(crate) use client::*;
+
+use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape;
+
+use gpui::CursorStyle;
+
+pub(super) fn to_shape(style: CursorStyle) -> Shape {
+    match style {
+        CursorStyle::Arrow => Shape::Default,
+        CursorStyle::IBeam => Shape::Text,
+        CursorStyle::Crosshair => Shape::Crosshair,
+        CursorStyle::ClosedHand => Shape::Grabbing,
+        CursorStyle::OpenHand => Shape::Grab,
+        CursorStyle::PointingHand => Shape::Pointer,
+        CursorStyle::ResizeLeft => Shape::WResize,
+        CursorStyle::ResizeRight => Shape::EResize,
+        CursorStyle::ResizeLeftRight => Shape::EwResize,
+        CursorStyle::ResizeUp => Shape::NResize,
+        CursorStyle::ResizeDown => Shape::SResize,
+        CursorStyle::ResizeUpDown => Shape::NsResize,
+        CursorStyle::ResizeUpLeftDownRight => Shape::NwseResize,
+        CursorStyle::ResizeUpRightDownLeft => Shape::NeswResize,
+        CursorStyle::ResizeColumn => Shape::ColResize,
+        CursorStyle::ResizeRow => Shape::RowResize,
+        CursorStyle::IBeamCursorForVerticalLayout => Shape::VerticalText,
+        CursorStyle::OperationNotAllowed => Shape::NotAllowed,
+        CursorStyle::DragLink => Shape::Alias,
+        CursorStyle::DragCopy => Shape::Copy,
+        CursorStyle::ContextualMenu => Shape::ContextMenu,
+        CursorStyle::None => {
+            #[cfg(debug_assertions)]
+            panic!("CursorStyle::None should be handled separately in the client");
+            #[cfg(not(debug_assertions))]
+            Shape::Default
+        }
+    }
+}

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

@@ -73,32 +73,29 @@ use super::{
     window::{ImeInput, WaylandWindowStatePtr},
 };
 
-use crate::{
-    AnyWindowHandle, Bounds, Capslock, CursorStyle, DOUBLE_CLICK_INTERVAL, DevicePixels, DisplayId,
-    FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon,
-    LinuxKeyboardLayout, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
-    MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay,
-    PlatformInput, PlatformKeyboardLayout, Point, ResultExt as _, SCROLL_LINES, ScrollDelta,
-    ScrollWheelEvent, Size, TouchPhase, WindowParams, point, profiler, px, size,
-};
-use crate::{
-    SharedString,
-    platform::linux::{
-        LinuxClient, get_xkb_compose_state, is_within_click_distance, open_uri_internal, read_fd,
-        reveal_path_internal,
-        wayland::{
-            clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPES},
-            cursor::Cursor,
-            serial::{SerialKind, SerialTracker},
-            window::WaylandWindow,
-        },
-        xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
+use crate::linux::{
+    DOUBLE_CLICK_INTERVAL, LinuxClient, LinuxCommon, LinuxKeyboardLayout, ResultExt as _,
+    SCROLL_LINES, capslock_from_xkb, cursor_style_to_icon_names, get_xkb_compose_state,
+    is_within_click_distance, keystroke_from_xkb, keystroke_underlying_dead_key,
+    modifiers_from_xkb, open_uri_internal, read_fd, reveal_path_internal,
+    wayland::{
+        clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPES},
+        cursor::Cursor,
+        serial::{SerialKind, SerialTracker},
+        to_shape,
+        window::WaylandWindow,
     },
+    xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
 };
-use crate::{
-    TaskTiming,
-    platform::{PlatformWindow, wgpu::WgpuContext},
+use gpui::{
+    AnyWindowHandle, Bounds, Capslock, CursorStyle, DevicePixels, DisplayId, FileDropEvent,
+    ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
+    MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection,
+    Pixels, PlatformDisplay, PlatformInput, PlatformKeyboardLayout, PlatformWindow, Point,
+    ScrollDelta, ScrollWheelEvent, SharedString, Size, TaskTiming, TouchPhase, WindowParams, point,
+    profiler, px, size,
 };
+use gpui_wgpu::WgpuContext;
 
 /// Used to convert evdev scancode to xkb scancode
 const MIN_KEYCODE: u32 = 8;
@@ -303,7 +300,7 @@ impl WaylandClientStatePtr {
     pub fn enable_ime(&self) {
         let client = self.get_client();
         let mut state = client.borrow_mut();
-        let Some(mut text_input) = state.text_input.take() else {
+        let Some(text_input) = state.text_input.take() else {
             return;
         };
 
@@ -313,10 +310,10 @@ impl WaylandClientStatePtr {
             drop(state);
             if let Some(area) = window.get_ime_area() {
                 text_input.set_cursor_rectangle(
-                    area.origin.x.0 as i32,
-                    area.origin.y.0 as i32,
-                    area.size.width.0 as i32,
-                    area.size.height.0 as i32,
+                    f32::from(area.origin.x) as i32,
+                    f32::from(area.origin.y) as i32,
+                    f32::from(area.size.width) as i32,
+                    f32::from(area.size.height) as i32,
                 );
             }
             state = client.borrow_mut();
@@ -337,17 +334,17 @@ impl WaylandClientStatePtr {
 
     pub fn update_ime_position(&self, bounds: Bounds<Pixels>) {
         let client = self.get_client();
-        let mut state = client.borrow_mut();
+        let state = client.borrow_mut();
         if state.composing || state.text_input.is_none() || state.pre_edit_text.is_some() {
             return;
         }
 
         let text_input = state.text_input.as_ref().unwrap();
         text_input.set_cursor_rectangle(
-            bounds.origin.x.0 as i32,
-            bounds.origin.y.0 as i32,
-            bounds.size.width.0 as i32,
-            bounds.size.height.0 as i32,
+            bounds.origin.x.as_f32() as i32,
+            bounds.origin.y.as_f32() as i32,
+            bounds.size.width.as_f32() as i32,
+            bounds.size.height.as_f32() as i32,
         );
         text_input.commit();
     }
@@ -382,7 +379,7 @@ impl WaylandClientStatePtr {
     }
 
     pub fn drop_window(&self, surface_id: &ObjectId) {
-        let mut client = self.get_client();
+        let client = self.get_client();
         let mut state = client.borrow_mut();
         let closed_window = state.windows.remove(surface_id).unwrap();
         if let Some(window) = state.mouse_focused_window.take()
@@ -456,8 +453,7 @@ impl WaylandClient {
     pub(crate) fn new() -> Self {
         let conn = Connection::connect_to_env().unwrap();
 
-        let (globals, mut event_queue) =
-            registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap();
+        let (globals, event_queue) = registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap();
         let qh = event_queue.handle();
 
         let mut seat: Option<wl_seat::WlSeat> = None;
@@ -540,7 +536,7 @@ impl WaylandClient {
             .as_ref()
             .map(|primary_selection_manager| primary_selection_manager.get_device(&seat, &qh, ()));
 
-        let mut cursor = Cursor::new(&conn, &globals, 24);
+        let cursor = Cursor::new(&conn, &globals, 24);
 
         handle
             .insert_source(XDPEventSource::new(&common.background_executor), {
@@ -572,7 +568,7 @@ impl WaylandClient {
             })
             .unwrap();
 
-        let mut state = Rc::new(RefCell::new(WaylandClientState {
+        let state = Rc::new(RefCell::new(WaylandClientState {
             serial_tracker: SerialTracker::new(),
             globals,
             gpu_context,
@@ -673,7 +669,7 @@ impl LinuxClient for WaylandClient {
             .outputs
             .iter()
             .find_map(|(object_id, output)| {
-                (object_id.protocol_id() == id.0).then(|| {
+                (object_id.protocol_id() == u32::from(id)).then(|| {
                     Rc::new(WaylandDisplay {
                         id: object_id.clone(),
                         name: output.name.clone(),
@@ -695,7 +691,7 @@ impl LinuxClient for WaylandClient {
     #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
-    ) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Rc<dyn crate::ScreenCaptureSource>>>>
+    ) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Rc<dyn gpui::ScreenCaptureSource>>>>
     {
         // TODO: Get screen capture working on wayland. Be sure to try window resizing as that may
         // be tricky.
@@ -754,7 +750,7 @@ impl LinuxClient for WaylandClient {
                     .expect("window is focused by pointer");
                 wl_pointer.set_cursor(serial, None, 0, 0);
             } else if let Some(cursor_shape_device) = &state.cursor_shape_device {
-                cursor_shape_device.set_shape(serial, style.to_shape());
+                cursor_shape_device.set_shape(serial, to_shape(style));
             } else if let Some(focused_window) = &state.mouse_focused_window {
                 // cursor-shape-v1 isn't supported, set the cursor using a surface.
                 let wl_pointer = state
@@ -762,9 +758,12 @@ impl LinuxClient for WaylandClient {
                     .clone()
                     .expect("window is focused by pointer");
                 let scale = focused_window.primary_output_scale();
-                state
-                    .cursor
-                    .set_icon(&wl_pointer, serial, style.to_icon_names(), scale);
+                state.cursor.set_icon(
+                    &wl_pointer,
+                    serial,
+                    cursor_style_to_icon_names(style),
+                    scale,
+                );
             }
         }
     }
@@ -826,7 +825,7 @@ impl LinuxClient for WaylandClient {
             .log_err();
     }
 
-    fn write_to_primary(&self, item: crate::ClipboardItem) {
+    fn write_to_primary(&self, item: gpui::ClipboardItem) {
         let mut state = self.0.borrow_mut();
         let (Some(primary_selection_manager), Some(primary_selection)) = (
             state.globals.primary_selection_manager.clone(),
@@ -846,7 +845,7 @@ impl LinuxClient for WaylandClient {
         }
     }
 
-    fn write_to_clipboard(&self, item: crate::ClipboardItem) {
+    fn write_to_clipboard(&self, item: gpui::ClipboardItem) {
         let mut state = self.0.borrow_mut();
         let (Some(data_device_manager), Some(data_device)) = (
             state.globals.data_device_manager.clone(),
@@ -866,11 +865,11 @@ impl LinuxClient for WaylandClient {
         }
     }
 
-    fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
+    fn read_from_primary(&self) -> Option<gpui::ClipboardItem> {
         self.0.borrow_mut().clipboard.read_primary()
     }
 
-    fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
+    fn read_from_clipboard(&self) -> Option<gpui::ClipboardItem> {
         self.0.borrow_mut().clipboard.read()
     }
 
@@ -914,7 +913,7 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
         _: &Connection,
         qh: &QueueHandle<Self>,
     ) {
-        let mut client = this.get_client();
+        let client = this.get_client();
         let mut state = client.borrow_mut();
 
         match event {
@@ -1002,7 +1001,7 @@ impl Dispatch<WlCallback, ObjectId> for WaylandClientStatePtr {
 }
 
 pub(crate) fn get_window(
-    mut state: &mut RefMut<WaylandClientState>,
+    state: &mut RefMut<WaylandClientState>,
     surface_id: &ObjectId,
 ) -> Option<WaylandWindowStatePtr> {
     state.windows.get(surface_id).cloned()
@@ -1017,7 +1016,7 @@ impl Dispatch<wl_surface::WlSurface, ()> for WaylandClientStatePtr {
         _: &Connection,
         _: &QueueHandle<Self>,
     ) {
-        let mut client = this.get_client();
+        let client = this.get_client();
         let mut state = client.borrow_mut();
 
         let Some(window) = get_window(&mut state, &surface.id()) else {
@@ -1040,10 +1039,10 @@ impl Dispatch<wl_output::WlOutput, ()> for WaylandClientStatePtr {
         _: &Connection,
         _: &QueueHandle<Self>,
     ) {
-        let mut client = this.get_client();
+        let client = this.get_client();
         let mut state = client.borrow_mut();
 
-        let Some(mut in_progress_output) = state.in_progress_outputs.get_mut(&output.id()) else {
+        let Some(in_progress_output) = state.in_progress_outputs.get_mut(&output.id()) else {
             return;
         };
 
@@ -1257,7 +1256,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
         _: &Connection,
         _: &QueueHandle<Self>,
     ) {
-        let mut client = this.get_client();
+        let client = this.get_client();
         let mut state = client.borrow_mut();
         match event {
             wl_keyboard::Event::RepeatInfo { rate, delay } => {
@@ -1332,9 +1331,9 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
                 let old_layout =
                     keymap_state.serialize_layout(xkbcommon::xkb::STATE_LAYOUT_EFFECTIVE);
                 keymap_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
-                state.modifiers = Modifiers::from_xkb(keymap_state);
+                state.modifiers = modifiers_from_xkb(keymap_state);
                 let keymap_state = state.keymap_state.as_mut().unwrap();
-                state.capslock = Capslock::from_xkb(keymap_state);
+                state.capslock = capslock_from_xkb(keymap_state);
 
                 let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent {
                     modifiers: state.modifiers,
@@ -1370,14 +1369,14 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
                 match key_state {
                     wl_keyboard::KeyState::Pressed if !keysym.is_modifier_key() => {
                         let mut keystroke =
-                            Keystroke::from_xkb(keymap_state, state.modifiers, keycode);
+                            keystroke_from_xkb(keymap_state, state.modifiers, keycode);
                         if let Some(mut compose) = state.compose_state.take() {
                             compose.feed(keysym);
                             match compose.status() {
                                 xkb::Status::Composing => {
                                     keystroke.key_char = None;
                                     state.pre_edit_text =
-                                        compose.utf8().or(Keystroke::underlying_dead_key(keysym));
+                                        compose.utf8().or(keystroke_underlying_dead_key(keysym));
                                     let pre_edit =
                                         state.pre_edit_text.clone().unwrap_or(String::default());
                                     drop(state);
@@ -1394,7 +1393,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
                                 }
                                 xkb::Status::Cancelled => {
                                     let pre_edit = state.pre_edit_text.take();
-                                    let new_pre_edit = Keystroke::underlying_dead_key(keysym);
+                                    let new_pre_edit = keystroke_underlying_dead_key(keysym);
                                     state.pre_edit_text = new_pre_edit.clone();
                                     drop(state);
                                     if let Some(pre_edit) = pre_edit {
@@ -1432,8 +1431,8 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
                                     prefer_character_input: false,
                                 });
                                 move |event_timestamp, _metadata, this| {
-                                    let mut client = this.get_client();
-                                    let mut state = client.borrow_mut();
+                                    let client = this.get_client();
+                                    let state = client.borrow();
                                     let is_repeating = id == state.repeat.current_id
                                         && state.repeat.current_keycode.is_some()
                                         && state.keyboard_focused_window.is_some();
@@ -1459,7 +1458,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
                     }
                     wl_keyboard::KeyState::Released if !keysym.is_modifier_key() => {
                         let input = PlatformInput::KeyUp(KeyUpEvent {
-                            keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode),
+                            keystroke: keystroke_from_xkb(keymap_state, state.modifiers, keycode),
                         });
 
                         if state.repeat.current_keycode == Some(keycode) {
@@ -1538,10 +1537,10 @@ impl Dispatch<zwp_text_input_v3::ZwpTextInputV3, ()> for WaylandClientStatePtr {
                     window.handle_ime(ImeInput::SetMarkedText(text));
                     if let Some(area) = window.get_ime_area() {
                         text_input.set_cursor_rectangle(
-                            area.origin.x.0 as i32,
-                            area.origin.y.0 as i32,
-                            area.size.width.0 as i32,
-                            area.size.height.0 as i32,
+                            f32::from(area.origin.x) as i32,
+                            f32::from(area.origin.y) as i32,
+                            f32::from(area.size.width) as i32,
+                            f32::from(area.size.height) as i32,
                         );
                         if last_serial == serial {
                             text_input.commit();
@@ -1587,7 +1586,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
         _: &Connection,
         _: &QueueHandle<Self>,
     ) {
-        let mut client = this.get_client();
+        let client = this.get_client();
         let mut state = client.borrow_mut();
 
         match event {
@@ -1616,12 +1615,15 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
                                 .expect("window is focused by pointer");
                             wl_pointer.set_cursor(serial, None, 0, 0);
                         } else if let Some(cursor_shape_device) = &state.cursor_shape_device {
-                            cursor_shape_device.set_shape(serial, style.to_shape());
+                            cursor_shape_device.set_shape(serial, to_shape(style));
                         } else {
                             let scale = window.primary_output_scale();
-                            state
-                                .cursor
-                                .set_icon(wl_pointer, serial, style.to_icon_names(), scale);
+                            state.cursor.set_icon(
+                                wl_pointer,
+                                serial,
+                                cursor_style_to_icon_names(style),
+                                scale,
+                            );
                         }
                     }
                     drop(state);
@@ -1662,7 +1664,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
                             state.cursor_style = Some(default_style);
 
                             if let Some(cursor_shape_device) = &state.cursor_shape_device {
-                                cursor_shape_device.set_shape(serial, default_style.to_shape());
+                                cursor_shape_device.set_shape(serial, to_shape(default_style));
                             } else {
                                 // cursor-shape-v1 isn't supported, set the cursor using a surface.
                                 let wl_pointer = state
@@ -1673,7 +1675,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
                                 state.cursor.set_icon(
                                     &wl_pointer,
                                     serial,
-                                    default_style.to_icon_names(),
+                                    cursor_style_to_icon_names(default_style),
                                     scale,
                                 );
                             }
@@ -2043,7 +2045,7 @@ impl Dispatch<wl_data_device::WlDataDevice, ()> for WaylandClientStatePtr {
 
                             let input = PlatformInput::FileDrop(FileDropEvent::Entered {
                                 position,
-                                paths: crate::ExternalPaths(paths),
+                                paths: gpui::ExternalPaths(paths),
                             });
 
                             let client = this.get_client();
@@ -2151,7 +2153,7 @@ impl Dispatch<wl_data_source::WlDataSource, ()> for WaylandClientStatePtr {
         _: &QueueHandle<Self>,
     ) {
         let client = this.get_client();
-        let mut state = client.borrow_mut();
+        let state = client.borrow_mut();
 
         match event {
             wl_data_source::Event::Send { mime_type, fd } => {
@@ -2237,7 +2239,7 @@ impl Dispatch<zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, ()>
         _: &QueueHandle<Self>,
     ) {
         let client = this.get_client();
-        let mut state = client.borrow_mut();
+        let state = client.borrow_mut();
 
         match event {
             zwp_primary_selection_source_v1::Event::Send { mime_type, fd } => {

crates/gpui/src/platform/linux/wayland/clipboard.rs → crates/gpui_linux/src/linux/wayland/clipboard.rs 🔗

@@ -10,10 +10,8 @@ use strum::IntoEnumIterator;
 use wayland_client::{Connection, protocol::wl_data_offer::WlDataOffer};
 use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1;
 
-use crate::{
-    ClipboardEntry, ClipboardItem, Image, ImageFormat, WaylandClientStatePtr, hash,
-    platform::linux::platform::read_fd,
-};
+use crate::linux::{WaylandClientStatePtr, platform::read_fd};
+use gpui::{ClipboardEntry, ClipboardItem, Image, ImageFormat, hash};
 
 /// Text mime types that we'll offer to other programs.
 pub(crate) const TEXT_MIME_TYPES: [&str; 3] =
@@ -241,7 +239,7 @@ impl Clipboard {
                     calloop::Mode::Level,
                 ),
                 move |_, file, _| {
-                    let mut file = unsafe { file.get_mut() };
+                    let file = unsafe { file.get_mut() };
                     loop {
                         match file.write(&bytes[written..]) {
                             Ok(n) if written + n == bytes.len() => {

crates/gpui/src/platform/linux/wayland/cursor.rs → crates/gpui_linux/src/linux/wayland/cursor.rs 🔗

@@ -1,5 +1,5 @@
-use crate::Globals;
-use crate::platform::linux::{DEFAULT_CURSOR_ICON_NAME, log_cursor_icon_warning};
+use crate::linux::Globals;
+use crate::linux::{DEFAULT_CURSOR_ICON_NAME, log_cursor_icon_warning};
 use anyhow::{Context as _, anyhow};
 use util::ResultExt;
 
@@ -95,7 +95,7 @@ impl Cursor {
         &mut self,
         wl_pointer: &WlPointer,
         serial_id: u32,
-        mut cursor_icon_names: &[&str],
+        cursor_icon_names: &[&str],
         scale: i32,
     ) {
         self.set_scaled_size(self.size * scale as u32);
@@ -104,9 +104,9 @@ impl Cursor {
             log::warn!("Wayland: Unable to load cursor themes");
             return;
         };
-        let mut theme = &mut loaded_theme.theme;
+        let theme = &mut loaded_theme.theme;
 
-        let mut buffer: &CursorImageBuffer;
+        let buffer: &CursorImageBuffer;
         'outer: {
             for cursor_icon_name in cursor_icon_names {
                 if let Some(cursor) = theme.get_cursor(cursor_icon_name) {

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

@@ -7,7 +7,7 @@ use anyhow::Context as _;
 use uuid::Uuid;
 use wayland_backend::client::ObjectId;
 
-use crate::{Bounds, DisplayId, Pixels, PlatformDisplay};
+use gpui::{Bounds, DisplayId, Pixels, PlatformDisplay};
 
 #[derive(Debug, Clone)]
 pub(crate) struct WaylandDisplay {
@@ -25,7 +25,7 @@ impl Hash for WaylandDisplay {
 
 impl PlatformDisplay for WaylandDisplay {
     fn id(&self) -> DisplayId {
-        DisplayId(self.id.protocol_id())
+        DisplayId::new(self.id.protocol_id())
     }
 
     fn uuid(&self) -> anyhow::Result<Uuid> {

crates/gpui_linux/src/linux/wayland/layer_shell.rs 🔗

@@ -0,0 +1,26 @@
+pub use gpui::layer_shell::*;
+
+use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
+
+pub(crate) fn wayland_layer(layer: Layer) -> zwlr_layer_shell_v1::Layer {
+    match layer {
+        Layer::Background => zwlr_layer_shell_v1::Layer::Background,
+        Layer::Bottom => zwlr_layer_shell_v1::Layer::Bottom,
+        Layer::Top => zwlr_layer_shell_v1::Layer::Top,
+        Layer::Overlay => zwlr_layer_shell_v1::Layer::Overlay,
+    }
+}
+
+pub(crate) fn wayland_anchor(anchor: Anchor) -> zwlr_layer_surface_v1::Anchor {
+    zwlr_layer_surface_v1::Anchor::from_bits_truncate(anchor.bits())
+}
+
+pub(crate) fn wayland_keyboard_interactivity(
+    value: KeyboardInteractivity,
+) -> zwlr_layer_surface_v1::KeyboardInteractivity {
+    match value {
+        KeyboardInteractivity::None => zwlr_layer_surface_v1::KeyboardInteractivity::None,
+        KeyboardInteractivity::Exclusive => zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive,
+        KeyboardInteractivity::OnDemand => zwlr_layer_surface_v1::KeyboardInteractivity::OnDemand,
+    }
+}

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

@@ -24,27 +24,22 @@ use wayland_protocols::{
 use wayland_protocols_plasma::blur::client::org_kde_kwin_blur;
 use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1;
 
-use crate::{
-    AnyWindowHandle, Bounds, Decorations, DevicePixels, Globals, GpuSpecs, Modifiers, Output,
-    Pixels, PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions,
-    ResizeEdge, Size, Tiling, WaylandClientStatePtr, WindowAppearance, WindowBackgroundAppearance,
-    WindowBounds, WindowControlArea, WindowControls, WindowDecorations, WindowParams, get_window,
-    layer_shell::LayerShellNotSupportedError, px, size,
+use crate::linux::wayland::{display::WaylandDisplay, serial::SerialKind};
+use crate::linux::{Globals, Output, WaylandClientStatePtr, get_window};
+use gpui::{
+    AnyWindowHandle, Bounds, Capslock, Decorations, DevicePixels, GpuSpecs, Modifiers, Pixels,
+    PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
+    PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, Scene, Size, Tiling,
+    WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowControls,
+    WindowDecorations, WindowKind, WindowParams, layer_shell::LayerShellNotSupportedError, px,
+    size,
 };
-use crate::{
-    Capslock,
-    platform::{
-        PlatformAtlas, PlatformInputHandler, PlatformWindow,
-        linux::wayland::{display::WaylandDisplay, serial::SerialKind},
-        wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig},
-    },
-};
-use crate::{WindowKind, scene::Scene};
+use gpui_wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig};
 
 #[derive(Default)]
 pub(crate) struct Callbacks {
     request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
-    input: Option<Box<dyn FnMut(crate::PlatformInput) -> crate::DispatchEventResult>>,
+    input: Option<Box<dyn FnMut(gpui::PlatformInput) -> gpui::DispatchEventResult>>,
     active_status_change: Option<Box<dyn FnMut(bool)>>,
     hover_status_change: Option<Box<dyn FnMut(bool)>>,
     resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
@@ -144,34 +139,37 @@ impl WaylandSurfaceState {
             let layer_surface = layer_shell.get_layer_surface(
                 &surface,
                 None,
-                options.layer.into(),
+                super::layer_shell::wayland_layer(options.layer),
                 options.namespace.clone(),
                 &globals.qh,
                 surface.id(),
             );
 
-            let width = params.bounds.size.width.0;
-            let height = params.bounds.size.height.0;
+            let width = f32::from(params.bounds.size.width);
+            let height = f32::from(params.bounds.size.height);
             layer_surface.set_size(width as u32, height as u32);
 
-            layer_surface.set_anchor(options.anchor.into());
-            layer_surface.set_keyboard_interactivity(options.keyboard_interactivity.into());
+            layer_surface.set_anchor(super::layer_shell::wayland_anchor(options.anchor));
+            layer_surface.set_keyboard_interactivity(
+                super::layer_shell::wayland_keyboard_interactivity(options.keyboard_interactivity),
+            );
 
             if let Some(margin) = options.margin {
                 layer_surface.set_margin(
-                    margin.0.0 as i32,
-                    margin.1.0 as i32,
-                    margin.2.0 as i32,
-                    margin.3.0 as i32,
+                    f32::from(margin.0) as i32,
+                    f32::from(margin.1) as i32,
+                    f32::from(margin.2) as i32,
+                    f32::from(margin.3) as i32,
                 )
             }
 
             if let Some(exclusive_zone) = options.exclusive_zone {
-                layer_surface.set_exclusive_zone(exclusive_zone.0 as i32);
+                layer_surface.set_exclusive_zone(f32::from(exclusive_zone) as i32);
             }
 
             if let Some(exclusive_edge) = options.exclusive_edge {
-                layer_surface.set_exclusive_edge(exclusive_edge.into());
+                layer_surface
+                    .set_exclusive_edge(super::layer_shell::wayland_anchor(exclusive_edge));
             }
 
             return Ok(WaylandSurfaceState::LayerShell(WaylandLayerSurfaceState {
@@ -208,7 +206,7 @@ impl WaylandSurfaceState {
         };
 
         if let Some(size) = params.window_min_size {
-            toplevel.set_min_size(size.width.0 as i32, size.height.0 as i32);
+            toplevel.set_min_size(f32::from(size.width) as i32, f32::from(size.height) as i32);
         }
 
         // Attempt to set up window decorations based on the requested configuration
@@ -335,8 +333,8 @@ impl WaylandWindowState {
             };
             let config = WgpuSurfaceConfig {
                 size: Size {
-                    width: DevicePixels(options.bounds.size.width.0 as i32),
-                    height: DevicePixels(options.bounds.size.height.0 as i32),
+                    width: DevicePixels(f32::from(options.bounds.size.width) as i32),
+                    height: DevicePixels(f32::from(options.bounds.size.height) as i32),
                 },
                 transparent: true,
             };
@@ -618,7 +616,7 @@ impl WaylandWindowStatePtr {
                 state.inset(),
                 state.tiling,
             )
-            .map(|v| v.0 as i32)
+            .map(|v| f32::from(v) as i32)
             .map_size(|v| if v <= 0 { 1 } else { v });
 
             state.surface_state.set_geometry(
@@ -642,7 +640,7 @@ impl WaylandWindowStatePtr {
             match mode {
                 WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ServerSide) => {
                     self.state.borrow_mut().decorations = WindowDecorations::Server;
-                    if let Some(mut appearance_changed) =
+                    if let Some(appearance_changed) =
                         self.callbacks.borrow_mut().appearance_changed.as_mut()
                     {
                         appearance_changed();
@@ -651,7 +649,7 @@ impl WaylandWindowStatePtr {
                 WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ClientSide) => {
                     self.state.borrow_mut().decorations = WindowDecorations::Client;
                     // Update background to be transparent
-                    if let Some(mut appearance_changed) =
+                    if let Some(appearance_changed) =
                         self.callbacks.borrow_mut().appearance_changed.as_mut()
                     {
                         appearance_changed();
@@ -680,7 +678,7 @@ impl WaylandWindowStatePtr {
                 height,
                 states,
             } => {
-                let mut size = if width == 0 || height == 0 {
+                let size = if width == 0 || height == 0 {
                     None
                 } else {
                     Some(size(px(width as f32), px(height as f32)))
@@ -787,7 +785,7 @@ impl WaylandWindowStatePtr {
                 height,
                 serial,
             } => {
-                let mut size = if width == 0 || height == 0 {
+                let size = if width == 0 || height == 0 {
                     None
                 } else {
                     Some(size(px(width as f32), px(height as f32)))
@@ -933,7 +931,8 @@ impl WaylandWindowStatePtr {
         {
             let state = self.state.borrow();
             if let Some(viewport) = &state.viewport {
-                viewport.set_destination(size.width.0 as i32, size.height.0 as i32);
+                viewport
+                    .set_destination(f32::from(size.width) as i32, f32::from(size.height) as i32);
             }
         }
     }
@@ -1108,7 +1107,7 @@ impl PlatformWindow for WaylandWindow {
             state.inset(),
             state.tiling,
         )
-        .map(|v| v.0 as i32)
+        .map(|v| f32::from(v) as i32)
         .map_size(|v| if v <= 0 { 1 } else { v });
 
         state.surface_state.set_geometry(
@@ -1252,7 +1251,7 @@ impl PlatformWindow for WaylandWindow {
     }
 
     fn toggle_fullscreen(&self) {
-        let mut state = self.borrow();
+        let state = self.borrow();
         if let Some(toplevel) = state.surface_state.toplevel() {
             if !state.fullscreen {
                 toplevel.set_fullscreen(None);
@@ -1270,7 +1269,7 @@ impl PlatformWindow for WaylandWindow {
         self.0.callbacks.borrow_mut().request_frame = Some(callback);
     }
 
-    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
+    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> gpui::DispatchEventResult>) {
         self.0.callbacks.borrow_mut().input = Some(callback);
     }
 
@@ -1327,8 +1326,8 @@ impl PlatformWindow for WaylandWindow {
             toplevel.show_window_menu(
                 &state.globals.seat,
                 serial,
-                position.x.0 as i32,
-                position.y.0 as i32,
+                f32::from(position.x) as i32,
+                f32::from(position.y) as i32,
             );
         }
     }
@@ -1341,7 +1340,7 @@ impl PlatformWindow for WaylandWindow {
         }
     }
 
-    fn start_window_resize(&self, edge: crate::ResizeEdge) {
+    fn start_window_resize(&self, edge: gpui::ResizeEdge) {
         let state = self.borrow();
         if let Some(toplevel) = state.surface_state.toplevel() {
             toplevel.resize(
@@ -1408,8 +1407,8 @@ fn update_window(mut state: RefMut<WaylandWindowState>) {
     let opaque = !state.is_transparent();
 
     state.renderer.update_transparency(!opaque);
-    let mut opaque_area = state.window_bounds.map(|v| v.0 as i32);
-    opaque_area.inset(state.inset().0 as i32);
+    let opaque_area = state.window_bounds.map(|v| f32::from(v) as i32);
+    opaque_area.inset(f32::from(state.inset()) as i32);
 
     let region = state
         .globals
@@ -1454,7 +1453,11 @@ fn update_window(mut state: RefMut<WaylandWindowState>) {
     region.destroy();
 }
 
-impl WindowDecorations {
+pub(crate) trait WindowDecorationsExt {
+    fn to_xdg(self) -> zxdg_toplevel_decoration_v1::Mode;
+}
+
+impl WindowDecorationsExt for WindowDecorations {
     fn to_xdg(self) -> zxdg_toplevel_decoration_v1::Mode {
         match self {
             WindowDecorations::Client => zxdg_toplevel_decoration_v1::Mode::ClientSide,
@@ -1463,7 +1466,11 @@ impl WindowDecorations {
     }
 }
 
-impl ResizeEdge {
+pub(crate) trait ResizeEdgeWaylandExt {
+    fn to_xdg(self) -> xdg_toplevel::ResizeEdge;
+}
+
+impl ResizeEdgeWaylandExt for ResizeEdge {
     fn to_xdg(self) -> xdg_toplevel::ResizeEdge {
         match self {
             ResizeEdge::Top => xdg_toplevel::ResizeEdge::Top,

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

@@ -1,4 +1,3 @@
-use crate::{Capslock, ResultExt as _, TaskTiming, profiler, xcb_flush};
 use anyhow::{Context as _, anyhow};
 use ashpd::WindowIdentifier;
 use calloop::{
@@ -7,6 +6,7 @@ use calloop::{
 };
 use collections::HashMap;
 use core::str;
+use gpui::{Capslock, TaskTiming, profiler};
 use http_client::Url;
 use log::Level;
 use smallvec::SmallVec;
@@ -45,26 +45,27 @@ use super::{
     XimHandler, button_or_scroll_from_event_detail, check_reply,
     clipboard::{self, Clipboard},
     get_reply, get_valuator_axis_index, handle_connection_error, modifiers_from_state,
-    pressed_button_from_mask,
+    pressed_button_from_mask, xcb_flush,
 };
 
-use crate::platform::{
-    LinuxCommon, PlatformWindow,
-    linux::{
-        DEFAULT_CURSOR_ICON_NAME, LinuxClient, get_xkb_compose_state, is_within_click_distance,
-        log_cursor_icon_warning, open_uri_internal,
-        platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES},
-        reveal_path_internal,
-        xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
-    },
-    wgpu::WgpuContext,
+use crate::linux::{
+    DEFAULT_CURSOR_ICON_NAME, LinuxClient, ResultExt as _, capslock_from_xkb,
+    cursor_style_to_icon_names, get_xkb_compose_state, is_within_click_distance,
+    keystroke_from_xkb, keystroke_underlying_dead_key, log_cursor_icon_warning, modifiers_from_xkb,
+    open_uri_internal,
+    platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES},
+    reveal_path_internal,
+    xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
 };
-use crate::{
+use crate::linux::{LinuxCommon, LinuxKeyboardLayout, X11Window, modifiers_from_xinput_info};
+
+use gpui::{
     AnyWindowHandle, Bounds, ClipboardItem, CursorStyle, DisplayId, FileDropEvent, Keystroke,
-    LinuxKeyboardLayout, Modifiers, ModifiersChangedEvent, MouseButton, Pixels, Platform,
-    PlatformDisplay, PlatformInput, PlatformKeyboardLayout, Point, RequestFrameOptions,
-    ScrollDelta, Size, TouchPhase, WindowParams, X11Window, modifiers_from_xinput_info, point, px,
+    Modifiers, ModifiersChangedEvent, MouseButton, Pixels, PlatformDisplay, PlatformInput,
+    PlatformKeyboardLayout, PlatformWindow, Point, RequestFrameOptions, ScrollDelta, Size,
+    TouchPhase, WindowParams, point, px,
 };
+use gpui_wgpu::WgpuContext;
 
 /// Value for DeviceId parameters which selects all devices.
 pub(crate) const XINPUT_ALL_DEVICES: xinput::DeviceId = 0;
@@ -684,7 +685,7 @@ impl X11Client {
             return;
         }
 
-        let Some((mut ximc, mut xim_handler)) = state.take_xim() else {
+        let Some((mut ximc, xim_handler)) = state.take_xim() else {
             return;
         };
         let mut ic_attributes = ximc
@@ -815,7 +816,7 @@ impl X11Client {
                         state.xcb_connection.query_pointer(event.window),
                     ) {
                         state.xdnd_state.position =
-                            Point::new(Pixels(pos.win_x as f32), Pixels(pos.win_y as f32));
+                            Point::new(px(pos.win_x as f32), px(pos.win_y as f32));
                     }
                     if !state.xdnd_state.retrieved {
                         check_reply(
@@ -857,7 +858,7 @@ impl X11Client {
             }
             Event::SelectionNotify(event) => {
                 let window = self.get_window(event.requestor)?;
-                let mut state = self.0.borrow_mut();
+                let state = self.0.borrow_mut();
                 let reply = get_reply(
                     || "Failed to get XDND_DATA",
                     state.xcb_connection.get_property(
@@ -881,7 +882,7 @@ impl X11Client {
                         .collect();
                     let input = PlatformInput::FileDrop(FileDropEvent::Entered {
                         position: state.xdnd_state.position,
-                        paths: crate::ExternalPaths(paths),
+                        paths: gpui::ExternalPaths(paths),
                     });
                     drop(state);
                     window.handle_input(input);
@@ -969,8 +970,8 @@ impl X11Client {
                     event.latched_group as u32,
                     event.locked_group.into(),
                 );
-                let modifiers = Modifiers::from_xkb(&state.xkb);
-                let capslock = Capslock::from_xkb(&state.xkb);
+                let modifiers = modifiers_from_xkb(&state.xkb);
+                let capslock = capslock_from_xkb(&state.xkb);
                 if state.last_modifiers_changed_event == modifiers
                     && state.last_capslock_changed_event == capslock
                 {
@@ -1011,7 +1012,7 @@ impl X11Client {
 
                 let keystroke = {
                     let code = event.detail.into();
-                    let mut keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
+                    let mut keystroke = keystroke_from_xkb(&state.xkb, modifiers, code);
                     let keysym = state.xkb.key_get_one_sym(code);
 
                     if keysym.is_modifier_key() {
@@ -1035,7 +1036,7 @@ impl X11Client {
                                 keystroke.key_char = None;
                                 state.pre_edit_text = compose_state
                                     .utf8()
-                                    .or(crate::Keystroke::underlying_dead_key(keysym));
+                                    .or(keystroke_underlying_dead_key(keysym));
                                 let pre_edit =
                                     state.pre_edit_text.clone().unwrap_or(String::default());
                                 drop(state);
@@ -1048,7 +1049,7 @@ impl X11Client {
                                 if let Some(pre_edit) = pre_edit {
                                     window.handle_ime_commit(pre_edit);
                                 }
-                                if let Some(current_key) = Keystroke::underlying_dead_key(keysym) {
+                                if let Some(current_key) = keystroke_underlying_dead_key(keysym) {
                                     window.handle_ime_preedit(current_key);
                                 }
                                 state = self.0.borrow_mut();
@@ -1061,7 +1062,7 @@ impl X11Client {
                     keystroke
                 };
                 drop(state);
-                window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
+                window.handle_input(PlatformInput::KeyDown(gpui::KeyDownEvent {
                     keystroke,
                     is_held: false,
                     prefer_character_input: false,
@@ -1081,7 +1082,7 @@ impl X11Client {
 
                 let keystroke = {
                     let code = event.detail.into();
-                    let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
+                    let keystroke = keystroke_from_xkb(&state.xkb, modifiers, code);
                     let keysym = state.xkb.key_get_one_sym(code);
 
                     if keysym.is_modifier_key() {
@@ -1094,7 +1095,7 @@ impl X11Client {
                     keystroke
                 };
                 drop(state);
-                window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
+                window.handle_input(PlatformInput::KeyUp(gpui::KeyUpEvent { keystroke }));
             }
             Event::XinputButtonPress(event) => {
                 let window = self.get_window(event.event)?;
@@ -1141,7 +1142,7 @@ impl X11Client {
                         let current_count = state.current_count;
 
                         drop(state);
-                        window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
+                        window.handle_input(PlatformInput::MouseDown(gpui::MouseDownEvent {
                             button,
                             position,
                             modifiers,
@@ -1187,7 +1188,7 @@ impl X11Client {
                     Some(ButtonOrScroll::Button(button)) => {
                         let click_count = state.current_count;
                         drop(state);
-                        window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
+                        window.handle_input(PlatformInput::MouseUp(gpui::MouseUpEvent {
                             button,
                             position,
                             modifiers,
@@ -1238,7 +1239,7 @@ impl X11Client {
                 drop(state);
 
                 if event.valuator_mask[0] & 3 != 0 {
-                    window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
+                    window.handle_input(PlatformInput::MouseMove(gpui::MouseMoveEvent {
                         position,
                         pressed_button,
                         modifiers,
@@ -1246,7 +1247,7 @@ impl X11Client {
                 }
 
                 state = self.0.borrow_mut();
-                if let Some(mut pointer) = state.pointer_device_states.get_mut(&event.sourceid) {
+                if let Some(pointer) = state.pointer_device_states.get_mut(&event.sourceid) {
                     let scroll_delta = get_scroll_delta_and_update_state(pointer, &event);
                     drop(state);
                     if let Some(scroll_delta) = scroll_delta {
@@ -1280,7 +1281,7 @@ impl X11Client {
                 drop(state);
 
                 let window = self.get_window(event.event)?;
-                window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
+                window.handle_input(PlatformInput::MouseExited(gpui::MouseExitEvent {
                     pressed_button,
                     position,
                     modifiers,
@@ -1305,7 +1306,7 @@ impl X11Client {
             }
             Event::XinputDeviceChanged(event) => {
                 let mut state = self.0.borrow_mut();
-                if let Some(mut pointer) = state.pointer_device_states.get_mut(&event.sourceid) {
+                if let Some(pointer) = state.pointer_device_states.get_mut(&event.sourceid) {
                     reset_pointer_device_scroll_positions(pointer);
                 }
             }
@@ -1333,7 +1334,7 @@ impl X11Client {
         match event {
             Event::KeyPress(event) | Event::KeyRelease(event) => {
                 let mut state = self.0.borrow_mut();
-                state.pre_key_char_down = Some(Keystroke::from_xkb(
+                state.pre_key_char_down = Some(keystroke_from_xkb(
                     &state.xkb,
                     state.modifiers,
                     event.detail.into(),
@@ -1379,7 +1380,7 @@ impl X11Client {
         };
 
         let mut state = self.0.borrow_mut();
-        let (mut ximc, mut xim_handler) = state.take_xim()?;
+        let (mut ximc, xim_handler) = state.take_xim()?;
         state.composing = !text.is_empty();
         drop(state);
         window.handle_ime_preedit(text);
@@ -1473,7 +1474,12 @@ impl LinuxClient for X11Client {
         let state = self.0.borrow();
 
         Some(Rc::new(
-            X11Display::new(&state.xcb_connection, state.scale_factor, id.0 as usize).ok()?,
+            X11Display::new(
+                &state.xcb_connection,
+                state.scale_factor,
+                u32::from(id) as usize,
+            )
+            .ok()?,
         ))
     }
 
@@ -1485,11 +1491,9 @@ impl LinuxClient for X11Client {
     #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
-    ) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Rc<dyn crate::ScreenCaptureSource>>>>
+    ) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Rc<dyn gpui::ScreenCaptureSource>>>>
     {
-        crate::platform::scap_screen_capture::scap_screen_sources(
-            &self.0.borrow().common.foreground_executor,
-        )
+        gpui::scap_screen_capture::scap_screen_sources(&self.0.borrow().common.foreground_executor)
     }
 
     fn open_window(
@@ -1589,15 +1593,23 @@ impl LinuxClient for X11Client {
 
     fn open_uri(&self, uri: &str) {
         #[cfg(any(feature = "wayland", feature = "x11"))]
-        open_uri_internal(self.background_executor(), uri, None);
+        open_uri_internal(
+            self.with_common(|c| c.background_executor.clone()),
+            uri,
+            None,
+        );
     }
 
     fn reveal_path(&self, path: PathBuf) {
         #[cfg(any(feature = "x11", feature = "wayland"))]
-        reveal_path_internal(self.background_executor(), path, None);
+        reveal_path_internal(
+            self.with_common(|c| c.background_executor.clone()),
+            path,
+            None,
+        );
     }
 
-    fn write_to_primary(&self, item: crate::ClipboardItem) {
+    fn write_to_primary(&self, item: gpui::ClipboardItem) {
         let state = self.0.borrow_mut();
         state
             .clipboard
@@ -1610,7 +1622,7 @@ impl LinuxClient for X11Client {
             .log_with_level(log::Level::Debug);
     }
 
-    fn write_to_clipboard(&self, item: crate::ClipboardItem) {
+    fn write_to_clipboard(&self, item: gpui::ClipboardItem) {
         let mut state = self.0.borrow_mut();
         state
             .clipboard
@@ -1624,7 +1636,7 @@ impl LinuxClient for X11Client {
         state.clipboard_item.replace(item);
     }
 
-    fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
+    fn read_from_primary(&self) -> Option<gpui::ClipboardItem> {
         let state = self.0.borrow_mut();
         state
             .clipboard
@@ -1633,7 +1645,7 @@ impl LinuxClient for X11Client {
             .log_with_level(log::Level::Debug)
     }
 
-    fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
+    fn read_from_clipboard(&self) -> Option<gpui::ClipboardItem> {
         let state = self.0.borrow_mut();
         // if the last copy was from this app, return our cached item
         // which has metadata attached.
@@ -1876,7 +1888,7 @@ impl X11ClientState {
             return *cursor;
         }
 
-        let mut result;
+        let result;
         match style {
             CursorStyle::None => match create_invisible_cursor(&self.xcb_connection) {
                 Ok(loaded_cursor) => result = Ok(loaded_cursor),
@@ -1884,7 +1896,7 @@ impl X11ClientState {
             },
             _ => 'outer: {
                 let mut errors = String::new();
-                let cursor_icon_names = style.to_icon_names();
+                let cursor_icon_names = cursor_style_to_icon_names(style);
                 for cursor_icon_name in cursor_icon_names {
                     match self
                         .cursor_handle
@@ -2262,7 +2274,7 @@ fn make_scroll_wheel_event(
     position: Point<Pixels>,
     scroll_delta: Point<f32>,
     modifiers: Modifiers,
-) -> crate::ScrollWheelEvent {
+) -> gpui::ScrollWheelEvent {
     // When shift is held down, vertical scrolling turns into horizontal scrolling.
     let delta = if modifiers.shift {
         Point {
@@ -2272,7 +2284,7 @@ fn make_scroll_wheel_event(
     } else {
         scroll_delta
     };
-    crate::ScrollWheelEvent {
+    gpui::ScrollWheelEvent {
         position,
         delta: ScrollDelta::Lines(delta),
         modifiers,
@@ -2282,7 +2294,7 @@ fn make_scroll_wheel_event(
 
 fn create_invisible_cursor(
     connection: &XCBConnection,
-) -> anyhow::Result<crate::platform::linux::x11::client::xproto::Cursor> {
+) -> anyhow::Result<crate::linux::x11::client::xproto::Cursor> {
     let empty_pixmap = connection.generate_id()?;
     let root = connection.setup().roots[0].root;
     connection.create_pixmap(1, empty_pixmap, root, 1, 1)?;

crates/gpui/src/platform/linux/x11/clipboard.rs → crates/gpui_linux/src/linux/x11/clipboard.rs 🔗

@@ -47,7 +47,7 @@ use x11rb::{
     wrapper::ConnectionExt as _,
 };
 
-use crate::{ClipboardItem, Image, ImageFormat, hash};
+use gpui::{ClipboardItem, Image, ImageFormat, hash};
 
 type Result<T, E = Error> = std::result::Result<T, E>;
 

crates/gpui/src/platform/linux/x11/display.rs → crates/gpui_linux/src/linux/x11/display.rs 🔗

@@ -2,7 +2,7 @@ use anyhow::Context as _;
 use uuid::Uuid;
 use x11rb::{connection::Connection as _, xcb_ffi::XCBConnection};
 
-use crate::{Bounds, DisplayId, Pixels, PlatformDisplay, Size, px};
+use gpui::{Bounds, DisplayId, Pixels, PlatformDisplay, Size, px};
 
 #[derive(Debug)]
 pub(crate) struct X11Display {
@@ -38,7 +38,7 @@ impl X11Display {
 
 impl PlatformDisplay for X11Display {
     fn id(&self) -> DisplayId {
-        DisplayId(self.x_screen_index as u32)
+        DisplayId::new(self.x_screen_index as u32)
     }
 
     fn uuid(&self) -> anyhow::Result<Uuid> {

crates/gpui/src/platform/linux/x11/event.rs → crates/gpui_linux/src/linux/x11/event.rs 🔗

@@ -3,7 +3,7 @@ use x11rb::protocol::{
     xproto::{self, ModMask},
 };
 
-use crate::{Modifiers, MouseButton, NavigationDirection};
+use gpui::{Modifiers, MouseButton, NavigationDirection};
 
 pub(crate) enum ButtonOrScroll {
     Button(MouseButton),

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

@@ -1,14 +1,15 @@
 use anyhow::{Context as _, anyhow};
 use x11rb::connection::RequestConnection;
 
-use crate::platform::wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig};
-use crate::{
+use crate::linux::X11ClientStatePtr;
+use gpui::{
     AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs, Modifiers,
     Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
     Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,
     Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea,
-    WindowDecorations, WindowKind, WindowParams, X11ClientStatePtr, px,
+    WindowDecorations, WindowKind, WindowParams, px,
 };
+use gpui_wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig};
 
 use collections::FxHashSet;
 use raw_window_handle as rwh;
@@ -95,18 +96,16 @@ fn query_render_extent(
     })
 }
 
-impl ResizeEdge {
-    fn to_moveresize(self) -> u32 {
-        match self {
-            ResizeEdge::TopLeft => 0,
-            ResizeEdge::Top => 1,
-            ResizeEdge::TopRight => 2,
-            ResizeEdge::Right => 3,
-            ResizeEdge::BottomRight => 4,
-            ResizeEdge::Bottom => 5,
-            ResizeEdge::BottomLeft => 6,
-            ResizeEdge::Left => 7,
-        }
+fn resize_edge_to_moveresize(edge: ResizeEdge) -> u32 {
+    match edge {
+        ResizeEdge::TopLeft => 0,
+        ResizeEdge::Top => 1,
+        ResizeEdge::TopRight => 2,
+        ResizeEdge::Right => 3,
+        ResizeEdge::BottomRight => 4,
+        ResizeEdge::Bottom => 5,
+        ResizeEdge::BottomLeft => 6,
+        ResizeEdge::Left => 7,
     }
 }
 
@@ -242,7 +241,7 @@ unsafe impl Sync for RawWindow {}
 #[derive(Default)]
 pub struct Callbacks {
     request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
-    input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
+    input: Option<Box<dyn FnMut(PlatformInput) -> gpui::DispatchEventResult>>,
     active_status_change: Option<Box<dyn FnMut(bool)>>,
     hovered_status_change: Option<Box<dyn FnMut(bool)>>,
     resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
@@ -405,7 +404,7 @@ impl X11WindowState {
     ) -> anyhow::Result<Self> {
         let x_screen_index = params
             .display_id
-            .map_or(x_main_screen_index, |did| did.0 as usize);
+            .map_or(x_main_screen_index, |did| u32::from(did) as usize);
 
         let visual_set = find_visuals(xcb, x_screen_index);
 
@@ -500,7 +499,7 @@ impl X11WindowState {
 
             if let Some(size) = params.window_min_size {
                 let mut size_hints = WmSizeHints::new();
-                let min_size = (size.width.0 as i32, size.height.0 as i32);
+                let min_size = (f32::from(size.width) as i32, f32::from(size.height) as i32);
                 size_hints.min_size = Some(min_size);
                 check_reply(
                     || {
@@ -874,8 +873,8 @@ impl X11Window {
             self.0.xcb.translate_coordinates(
                 self.0.x_window,
                 state.x_root_window,
-                (position.x.0 * state.scale_factor) as i16,
-                (position.y.0 * state.scale_factor) as i16,
+                (f32::from(position.x) * state.scale_factor) as i16,
+                (f32::from(position.y) * state.scale_factor) as i16,
             ),
         )
     }
@@ -932,7 +931,7 @@ impl X11WindowStatePtr {
     }
 
     pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) -> anyhow::Result<()> {
-        let mut state = self.state.borrow_mut();
+        let state = self.state.borrow_mut();
         if event.atom == state.atoms._NET_WM_STATE {
             self.set_wm_properties(state)?;
         } else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS {
@@ -1247,10 +1246,10 @@ impl PlatformWindow for X11Window {
             let [left, right, top, bottom] = state.last_insets;
 
             let [left, right, top, bottom] = [
-                Pixels((left as f32) / state.scale_factor),
-                Pixels((right as f32) / state.scale_factor),
-                Pixels((top as f32) / state.scale_factor),
-                Pixels((bottom as f32) / state.scale_factor),
+                px((left as f32) / state.scale_factor),
+                px((right as f32) / state.scale_factor),
+                px((top as f32) / state.scale_factor),
+                px((bottom as f32) / state.scale_factor),
             ];
 
             bounds.origin.x += left;
@@ -1327,7 +1326,7 @@ impl PlatformWindow for X11Window {
             .unwrap_or_default()
     }
 
-    fn capslock(&self) -> crate::Capslock {
+    fn capslock(&self) -> gpui::Capslock {
         self.0
             .state
             .borrow()
@@ -1522,7 +1521,7 @@ impl PlatformWindow for X11Window {
         self.0.callbacks.borrow_mut().request_frame = Some(callback);
     }
 
-    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
+    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> gpui::DispatchEventResult>) {
         self.0.callbacks.borrow_mut().input = Some(callback);
     }
 
@@ -1609,10 +1608,11 @@ impl PlatformWindow for X11Window {
     }
 
     fn start_window_resize(&self, edge: ResizeEdge) {
-        self.send_moveresize(edge.to_moveresize()).log_err();
+        self.send_moveresize(resize_edge_to_moveresize(edge))
+            .log_err();
     }
 
-    fn window_decorations(&self) -> crate::Decorations {
+    fn window_decorations(&self) -> gpui::Decorations {
         let state = self.0.state.borrow();
 
         // Client window decorations require compositor support
@@ -1644,7 +1644,7 @@ impl PlatformWindow for X11Window {
     fn set_client_inset(&self, inset: Pixels) {
         let mut state = self.0.state.borrow_mut();
 
-        let dp = (inset.0 * state.scale_factor) as u32;
+        let dp = (f32::from(inset) * state.scale_factor) as u32;
 
         let insets = if state.fullscreen {
             [0, 0, 0, 0]
@@ -1688,16 +1688,16 @@ impl PlatformWindow for X11Window {
         }
     }
 
-    fn request_decorations(&self, mut decorations: crate::WindowDecorations) {
+    fn request_decorations(&self, mut decorations: gpui::WindowDecorations) {
         let mut state = self.0.state.borrow_mut();
 
-        if matches!(decorations, crate::WindowDecorations::Client)
+        if matches!(decorations, gpui::WindowDecorations::Client)
             && !state.client_side_decorations_supported
         {
             log::info!(
                 "x11: no compositor present, falling back to server-side window decorations"
             );
-            decorations = crate::WindowDecorations::Server;
+            decorations = gpui::WindowDecorations::Server;
         }
 
         // https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/x11/util/hint.rs#L53-L87
@@ -1745,7 +1745,7 @@ impl PlatformWindow for X11Window {
     }
 
     fn update_ime_position(&self, bounds: Bounds<Pixels>) {
-        let mut state = self.0.state.borrow_mut();
+        let state = self.0.state.borrow();
         let client = state.client.clone();
         drop(state);
         client.update_ime_position(bounds);

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

@@ -7,7 +7,7 @@ use calloop::channel::Channel;
 use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
 use smol::stream::StreamExt;
 
-use crate::{BackgroundExecutor, WindowAppearance};
+use gpui::{BackgroundExecutor, WindowAppearance};
 
 pub enum Event {
     WindowAppearance(WindowAppearance),
@@ -32,9 +32,9 @@ impl XDPEventSource {
                 let settings = Settings::new().await?;
 
                 if let Ok(initial_appearance) = settings.color_scheme().await {
-                    sender.send(Event::WindowAppearance(WindowAppearance::from_native(
-                        initial_appearance,
-                    )))?;
+                    sender.send(Event::WindowAppearance(
+                        window_appearance_from_color_scheme(initial_appearance),
+                    ))?;
                 }
                 if let Ok(initial_theme) = settings
                     .read::<String>("org.gnome.desktop.interface", "cursor-theme")
@@ -91,9 +91,9 @@ impl XDPEventSource {
 
                 let mut appearance_changed = settings.receive_color_scheme_changed().await?;
                 while let Some(scheme) = appearance_changed.next().await {
-                    sender.send(Event::WindowAppearance(WindowAppearance::from_native(
-                        scheme,
-                    )))?;
+                    sender.send(Event::WindowAppearance(
+                        window_appearance_from_color_scheme(scheme),
+                    ))?;
                 }
 
                 anyhow::Ok(())
@@ -155,17 +155,10 @@ impl EventSource for XDPEventSource {
     }
 }
 
-impl WindowAppearance {
-    fn from_native(cs: ColorScheme) -> WindowAppearance {
-        match cs {
-            ColorScheme::PreferDark => WindowAppearance::Dark,
-            ColorScheme::PreferLight => WindowAppearance::Light,
-            ColorScheme::NoPreference => WindowAppearance::Light,
-        }
-    }
-
-    #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
-    fn set_native(&mut self, cs: ColorScheme) {
-        *self = Self::from_native(cs);
+fn window_appearance_from_color_scheme(cs: ColorScheme) -> WindowAppearance {
+    match cs {
+        ColorScheme::PreferDark => WindowAppearance::Dark,
+        ColorScheme::PreferLight => WindowAppearance::Light,
+        ColorScheme::NoPreference => WindowAppearance::Light,
     }
 }

crates/gpui_macos/Cargo.toml 🔗

@@ -0,0 +1,62 @@
+[package]
+name = "gpui_macos"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "Apache-2.0"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/gpui_macos.rs"
+
+[features]
+default = ["gpui/default"]
+test-support = ["gpui/test-support"]
+runtime_shaders = []
+font-kit = ["dep:font-kit"]
+screen-capture = ["gpui/screen-capture"]
+
+[dependencies]
+gpui.workspace = true
+
+[target.'cfg(target_os = "macos")'.dependencies]
+anyhow.workspace = true
+async-task = "4.7"
+block = "0.1"
+cocoa.workspace = true
+collections.workspace = true
+core-foundation.workspace = true
+core-foundation-sys.workspace = true
+core-graphics = "0.24"
+core-text = "21"
+core-video.workspace = true
+ctor.workspace = true
+derive_more.workspace = true
+etagere = "0.2"
+# WARNING: If you change this, you must also publish a new version of zed-font-kit to crates.io
+font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "110523127440aefb11ce0cf280ae7c5071337ec5", package = "zed-font-kit", version = "0.14.1-zed", optional = true }
+foreign-types = "0.5"
+futures.workspace = true
+image.workspace = true
+itertools.workspace = true
+libc.workspace = true
+log.workspace = true
+mach2.workspace = true
+media.workspace = true
+metal.workspace = true
+objc.workspace = true
+parking_lot.workspace = true
+pathfinder_geometry = "0.5"
+raw-window-handle = "0.6"
+semver.workspace = true
+smallvec.workspace = true
+strum.workspace = true
+util.workspace = true
+uuid.workspace = true
+
+[target.'cfg(target_os = "macos")'.build-dependencies]
+bindgen = "0.71"
+cbindgen = { version = "0.28.0", default-features = false }
+gpui.workspace = true

crates/gpui_macos/build.rs 🔗

@@ -0,0 +1,209 @@
+#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
+
+fn main() {
+    #[cfg(target_os = "macos")]
+    macos_build::run();
+}
+
+#[cfg(target_os = "macos")]
+mod macos_build {
+    use std::{
+        env,
+        path::{Path, PathBuf},
+    };
+
+    use cbindgen::Config;
+
+    pub fn run() {
+        generate_dispatch_bindings();
+
+        let header_path = generate_shader_bindings();
+
+        #[cfg(feature = "runtime_shaders")]
+        emit_stitched_shaders(&header_path);
+        #[cfg(not(feature = "runtime_shaders"))]
+        compile_metal_shaders(&header_path);
+    }
+
+    fn generate_dispatch_bindings() {
+        println!("cargo:rustc-link-lib=framework=System");
+
+        let bindings = bindgen::Builder::default()
+            .header("src/dispatch.h")
+            .allowlist_var("_dispatch_main_q")
+            .allowlist_var("_dispatch_source_type_data_add")
+            .allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH")
+            .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
+            .allowlist_var("DISPATCH_QUEUE_PRIORITY_LOW")
+            .allowlist_var("DISPATCH_TIME_NOW")
+            .allowlist_function("dispatch_get_global_queue")
+            .allowlist_function("dispatch_async_f")
+            .allowlist_function("dispatch_after_f")
+            .allowlist_function("dispatch_time")
+            .allowlist_function("dispatch_source_merge_data")
+            .allowlist_function("dispatch_source_create")
+            .allowlist_function("dispatch_source_set_event_handler_f")
+            .allowlist_function("dispatch_resume")
+            .allowlist_function("dispatch_suspend")
+            .allowlist_function("dispatch_source_cancel")
+            .allowlist_function("dispatch_set_context")
+            .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
+            .layout_tests(false)
+            .generate()
+            .expect("unable to generate bindings");
+
+        let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+        bindings
+            .write_to_file(out_path.join("dispatch_sys.rs"))
+            .expect("couldn't write dispatch bindings");
+    }
+
+    fn generate_shader_bindings() -> PathBuf {
+        let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
+
+        let gpui_dir = find_gpui_crate_dir();
+
+        let mut config = Config {
+            include_guard: Some("SCENE_H".into()),
+            language: cbindgen::Language::C,
+            no_includes: true,
+            ..Default::default()
+        };
+        config.export.include.extend([
+            "Bounds".into(),
+            "Corners".into(),
+            "Edges".into(),
+            "Size".into(),
+            "Pixels".into(),
+            "PointF".into(),
+            "Hsla".into(),
+            "ContentMask".into(),
+            "Uniforms".into(),
+            "AtlasTile".into(),
+            "PathRasterizationInputIndex".into(),
+            "PathVertex_ScaledPixels".into(),
+            "PathRasterizationVertex".into(),
+            "ShadowInputIndex".into(),
+            "Shadow".into(),
+            "QuadInputIndex".into(),
+            "Underline".into(),
+            "UnderlineInputIndex".into(),
+            "Quad".into(),
+            "BorderStyle".into(),
+            "SpriteInputIndex".into(),
+            "MonochromeSprite".into(),
+            "PolychromeSprite".into(),
+            "PathSprite".into(),
+            "SurfaceInputIndex".into(),
+            "SurfaceBounds".into(),
+            "TransformationMatrix".into(),
+        ]);
+        config.no_includes = true;
+        config.enumeration.prefix_with_name = true;
+
+        let mut builder = cbindgen::Builder::new();
+
+        let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
+
+        // Source files from gpui that define types used in shaders
+        let gpui_src_paths = [
+            gpui_dir.join("src/scene.rs"),
+            gpui_dir.join("src/geometry.rs"),
+            gpui_dir.join("src/color.rs"),
+            gpui_dir.join("src/window.rs"),
+            gpui_dir.join("src/platform.rs"),
+        ];
+
+        // Source files from this crate
+        let local_src_paths = [crate_dir.join("src/metal_renderer.rs")];
+
+        for src_path in gpui_src_paths.iter().chain(local_src_paths.iter()) {
+            println!("cargo:rerun-if-changed={}", src_path.display());
+            builder = builder.with_src(src_path);
+        }
+
+        builder
+            .with_config(config)
+            .generate()
+            .expect("Unable to generate bindings")
+            .write_to_file(&output_path);
+
+        output_path
+    }
+
+    /// Locate the gpui crate directory relative to this crate.
+    fn find_gpui_crate_dir() -> PathBuf {
+        gpui::GPUI_MANIFEST_DIR.into()
+    }
+
+    /// To enable runtime compilation, we need to "stitch" the shaders file with the generated header
+    /// so that it is self-contained.
+    #[cfg(feature = "runtime_shaders")]
+    fn emit_stitched_shaders(header_path: &Path) {
+        fn stitch_header(header: &Path, shader_path: &Path) -> std::io::Result<PathBuf> {
+            let header_contents = std::fs::read_to_string(header)?;
+            let shader_contents = std::fs::read_to_string(shader_path)?;
+            let stitched_contents = format!("{header_contents}\n{shader_contents}");
+            let out_path =
+                PathBuf::from(env::var("OUT_DIR").unwrap()).join("stitched_shaders.metal");
+            std::fs::write(&out_path, stitched_contents)?;
+            Ok(out_path)
+        }
+        let shader_source_path = "./src/shaders.metal";
+        let shader_path = PathBuf::from(shader_source_path);
+        stitch_header(header_path, &shader_path).unwrap();
+        println!("cargo:rerun-if-changed={}", &shader_source_path);
+    }
+
+    #[cfg(not(feature = "runtime_shaders"))]
+    fn compile_metal_shaders(header_path: &Path) {
+        use std::process::{self, Command};
+        let shader_path = "./src/shaders.metal";
+        let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
+        let metallib_output_path =
+            PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
+        println!("cargo:rerun-if-changed={}", shader_path);
+
+        let output = Command::new("xcrun")
+            .args([
+                "-sdk",
+                "macosx",
+                "metal",
+                "-gline-tables-only",
+                "-mmacosx-version-min=10.15.7",
+                "-MO",
+                "-c",
+                shader_path,
+                "-include",
+                (header_path.to_str().unwrap()),
+                "-o",
+            ])
+            .arg(&air_output_path)
+            .output()
+            .unwrap();
+
+        if !output.status.success() {
+            println!(
+                "cargo::error=metal shader compilation failed:\n{}",
+                String::from_utf8_lossy(&output.stderr)
+            );
+            process::exit(1);
+        }
+
+        let output = Command::new("xcrun")
+            .args(["-sdk", "macosx", "metallib"])
+            .arg(air_output_path)
+            .arg("-o")
+            .arg(metallib_output_path)
+            .output()
+            .unwrap();
+
+        if !output.status.success() {
+            println!(
+                "cargo::error=metallib compilation failed:\n{}",
+                String::from_utf8_lossy(&output.stderr)
+            );
+            process::exit(1);
+        }
+    }
+}

crates/gpui/src/platform/mac/dispatcher.rs → crates/gpui_macos/src/dispatcher.rs 🔗

@@ -2,7 +2,7 @@
 #![allow(non_camel_case_types)]
 #![allow(non_snake_case)]
 
-use crate::{
+use gpui::{
     GLOBAL_THREAD_TIMINGS, PlatformDispatcher, Priority, RunnableMeta, RunnableVariant,
     THREAD_TIMINGS, TaskTiming, ThreadTaskTimings,
 };

crates/gpui/src/platform/mac/display.rs → crates/gpui_macos/src/display.rs 🔗

@@ -1,5 +1,4 @@
-use super::ns_string;
-use crate::{Bounds, DisplayId, Pixels, PlatformDisplay, point, px, size};
+use crate::ns_string;
 use anyhow::Result;
 use cocoa::{
     appkit::NSScreen,
@@ -8,6 +7,7 @@ use cocoa::{
 };
 use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
 use core_graphics::display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList};
+use gpui::{Bounds, DisplayId, Pixels, PlatformDisplay, point, px, size};
 use objc::{msg_send, sel, sel_impl};
 use uuid::Uuid;
 
@@ -72,7 +72,7 @@ unsafe extern "C" {
 
 impl PlatformDisplay for MacDisplay {
     fn id(&self) -> DisplayId {
-        DisplayId(self.0)
+        DisplayId::new(self.0)
     }
 
     fn uuid(&self) -> Result<Uuid> {

crates/gpui/src/platform/mac/display_link.rs → crates/gpui_macos/src/display_link.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     dispatch_get_main_queue,
-    dispatch_sys::{
+    dispatcher::dispatch_sys::{
         _dispatch_source_type_data_add, dispatch_resume, dispatch_set_context,
         dispatch_source_cancel, dispatch_source_create, dispatch_source_merge_data,
         dispatch_source_set_event_handler_f, dispatch_source_t, dispatch_suspend,

crates/gpui/src/platform/mac/events.rs → crates/gpui_macos/src/events.rs 🔗

@@ -1,13 +1,13 @@
-use crate::{
+use gpui::{
     Capslock, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
     MouseDownEvent, MouseExitEvent, MouseMoveEvent, MousePressureEvent, MouseUpEvent,
     NavigationDirection, Pixels, PlatformInput, PressureStage, ScrollDelta, ScrollWheelEvent,
-    TouchPhase,
-    platform::mac::{
-        LMGetKbdType, NSStringExt, TISCopyCurrentKeyboardLayoutInputSource,
-        TISGetInputSourceProperty, UCKeyTranslate, kTISPropertyUnicodeKeyLayoutData,
-    },
-    point, px,
+    TouchPhase, point, px,
+};
+
+use crate::{
+    LMGetKbdType, NSStringExt, TISCopyCurrentKeyboardLayoutInputSource, TISGetInputSourceProperty,
+    UCKeyTranslate, kTISPropertyUnicodeKeyLayoutData,
 };
 use cocoa::{
     appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
@@ -101,217 +101,215 @@ unsafe fn read_modifiers(native_event: id) -> Modifiers {
     }
 }
 
-impl PlatformInput {
-    pub(crate) unsafe fn from_native(
-        native_event: id,
-        window_height: Option<Pixels>,
-    ) -> Option<Self> {
-        unsafe {
-            let event_type = native_event.eventType();
+pub(crate) unsafe fn platform_input_from_native(
+    native_event: id,
+    window_height: Option<Pixels>,
+) -> Option<PlatformInput> {
+    unsafe {
+        let event_type = native_event.eventType();
 
-            // Filter out event types that aren't in the NSEventType enum.
-            // See https://github.com/servo/cocoa-rs/issues/155#issuecomment-323482792 for details.
-            match event_type as u64 {
-                0 | 21 | 32 | 33 | 35 | 36 | 37 => {
-                    return None;
-                }
-                _ => {}
+        // Filter out event types that aren't in the NSEventType enum.
+        // See https://github.com/servo/cocoa-rs/issues/155#issuecomment-323482792 for details.
+        match event_type as u64 {
+            0 | 21 | 32 | 33 | 35 | 36 | 37 => {
+                return None;
             }
+            _ => {}
+        }
 
-            match event_type {
-                NSEventType::NSFlagsChanged => {
-                    Some(Self::ModifiersChanged(ModifiersChangedEvent {
+        match event_type {
+            NSEventType::NSFlagsChanged => {
+                Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
+                    modifiers: read_modifiers(native_event),
+                    capslock: Capslock {
+                        on: native_event
+                            .modifierFlags()
+                            .contains(NSEventModifierFlags::NSAlphaShiftKeyMask),
+                    },
+                }))
+            }
+            NSEventType::NSKeyDown => Some(PlatformInput::KeyDown(KeyDownEvent {
+                keystroke: parse_keystroke(native_event),
+                is_held: native_event.isARepeat() == YES,
+                prefer_character_input: false,
+            })),
+            NSEventType::NSKeyUp => Some(PlatformInput::KeyUp(KeyUpEvent {
+                keystroke: parse_keystroke(native_event),
+            })),
+            NSEventType::NSLeftMouseDown
+            | NSEventType::NSRightMouseDown
+            | NSEventType::NSOtherMouseDown => {
+                let button = match native_event.buttonNumber() {
+                    0 => MouseButton::Left,
+                    1 => MouseButton::Right,
+                    2 => MouseButton::Middle,
+                    3 => MouseButton::Navigate(NavigationDirection::Back),
+                    4 => MouseButton::Navigate(NavigationDirection::Forward),
+                    // Other mouse buttons aren't tracked currently
+                    _ => return None,
+                };
+                window_height.map(|window_height| {
+                    PlatformInput::MouseDown(MouseDownEvent {
+                        button,
+                        position: point(
+                            px(native_event.locationInWindow().x as f32),
+                            // MacOS screen coordinates are relative to bottom left
+                            window_height - px(native_event.locationInWindow().y as f32),
+                        ),
                         modifiers: read_modifiers(native_event),
-                        capslock: Capslock {
-                            on: native_event
-                                .modifierFlags()
-                                .contains(NSEventModifierFlags::NSAlphaShiftKeyMask),
-                        },
-                    }))
-                }
-                NSEventType::NSKeyDown => Some(Self::KeyDown(KeyDownEvent {
-                    keystroke: parse_keystroke(native_event),
-                    is_held: native_event.isARepeat() == YES,
-                    prefer_character_input: false,
-                })),
-                NSEventType::NSKeyUp => Some(Self::KeyUp(KeyUpEvent {
-                    keystroke: parse_keystroke(native_event),
-                })),
-                NSEventType::NSLeftMouseDown
-                | NSEventType::NSRightMouseDown
-                | NSEventType::NSOtherMouseDown => {
-                    let button = match native_event.buttonNumber() {
-                        0 => MouseButton::Left,
-                        1 => MouseButton::Right,
-                        2 => MouseButton::Middle,
-                        3 => MouseButton::Navigate(NavigationDirection::Back),
-                        4 => MouseButton::Navigate(NavigationDirection::Forward),
-                        // Other mouse buttons aren't tracked currently
-                        _ => return None,
-                    };
-                    window_height.map(|window_height| {
-                        Self::MouseDown(MouseDownEvent {
-                            button,
-                            position: point(
-                                px(native_event.locationInWindow().x as f32),
-                                // MacOS screen coordinates are relative to bottom left
-                                window_height - px(native_event.locationInWindow().y as f32),
-                            ),
-                            modifiers: read_modifiers(native_event),
-                            click_count: native_event.clickCount() as usize,
-                            first_mouse: false,
-                        })
+                        click_count: native_event.clickCount() as usize,
+                        first_mouse: false,
                     })
-                }
-                NSEventType::NSLeftMouseUp
-                | NSEventType::NSRightMouseUp
-                | NSEventType::NSOtherMouseUp => {
-                    let button = match native_event.buttonNumber() {
-                        0 => MouseButton::Left,
-                        1 => MouseButton::Right,
-                        2 => MouseButton::Middle,
-                        3 => MouseButton::Navigate(NavigationDirection::Back),
-                        4 => MouseButton::Navigate(NavigationDirection::Forward),
-                        // Other mouse buttons aren't tracked currently
-                        _ => return None,
-                    };
-
-                    window_height.map(|window_height| {
-                        Self::MouseUp(MouseUpEvent {
-                            button,
-                            position: point(
-                                px(native_event.locationInWindow().x as f32),
-                                window_height - px(native_event.locationInWindow().y as f32),
-                            ),
-                            modifiers: read_modifiers(native_event),
-                            click_count: native_event.clickCount() as usize,
-                        })
-                    })
-                }
-                NSEventType::NSEventTypePressure => {
-                    let stage = native_event.stage();
-                    let pressure = native_event.pressure();
-
-                    window_height.map(|window_height| {
-                        Self::MousePressure(MousePressureEvent {
-                            stage: match stage {
-                                1 => PressureStage::Normal,
-                                2 => PressureStage::Force,
-                                _ => PressureStage::Zero,
-                            },
-                            pressure,
-                            modifiers: read_modifiers(native_event),
-                            position: point(
-                                px(native_event.locationInWindow().x as f32),
-                                window_height - px(native_event.locationInWindow().y as f32),
-                            ),
-                        })
+                })
+            }
+            NSEventType::NSLeftMouseUp
+            | NSEventType::NSRightMouseUp
+            | NSEventType::NSOtherMouseUp => {
+                let button = match native_event.buttonNumber() {
+                    0 => MouseButton::Left,
+                    1 => MouseButton::Right,
+                    2 => MouseButton::Middle,
+                    3 => MouseButton::Navigate(NavigationDirection::Back),
+                    4 => MouseButton::Navigate(NavigationDirection::Forward),
+                    // Other mouse buttons aren't tracked currently
+                    _ => return None,
+                };
+
+                window_height.map(|window_height| {
+                    PlatformInput::MouseUp(MouseUpEvent {
+                        button,
+                        position: point(
+                            px(native_event.locationInWindow().x as f32),
+                            window_height - px(native_event.locationInWindow().y as f32),
+                        ),
+                        modifiers: read_modifiers(native_event),
+                        click_count: native_event.clickCount() as usize,
                     })
-                }
-                // Some mice (like Logitech MX Master) send navigation buttons as swipe events
-                NSEventType::NSEventTypeSwipe => {
-                    let navigation_direction = match native_event.phase() {
-                        NSEventPhase::NSEventPhaseEnded => match native_event.deltaX() {
-                            x if x > 0.0 => Some(NavigationDirection::Back),
-                            x if x < 0.0 => Some(NavigationDirection::Forward),
-                            _ => return None,
+                })
+            }
+            NSEventType::NSEventTypePressure => {
+                let stage = native_event.stage();
+                let pressure = native_event.pressure();
+
+                window_height.map(|window_height| {
+                    PlatformInput::MousePressure(MousePressureEvent {
+                        stage: match stage {
+                            1 => PressureStage::Normal,
+                            2 => PressureStage::Force,
+                            _ => PressureStage::Zero,
                         },
-                        _ => return None,
-                    };
-
-                    match navigation_direction {
-                        Some(direction) => window_height.map(|window_height| {
-                            Self::MouseDown(MouseDownEvent {
-                                button: MouseButton::Navigate(direction),
-                                position: point(
-                                    px(native_event.locationInWindow().x as f32),
-                                    window_height - px(native_event.locationInWindow().y as f32),
-                                ),
-                                modifiers: read_modifiers(native_event),
-                                click_count: 1,
-                                first_mouse: false,
-                            })
-                        }),
-                        _ => None,
-                    }
-                }
-                NSEventType::NSScrollWheel => window_height.map(|window_height| {
-                    let phase = match native_event.phase() {
-                        NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
-                            TouchPhase::Started
-                        }
-                        NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
-                        _ => TouchPhase::Moved,
-                    };
-
-                    let raw_data = point(
-                        native_event.scrollingDeltaX() as f32,
-                        native_event.scrollingDeltaY() as f32,
-                    );
-
-                    let delta = if native_event.hasPreciseScrollingDeltas() == YES {
-                        ScrollDelta::Pixels(raw_data.map(px))
-                    } else {
-                        ScrollDelta::Lines(raw_data)
-                    };
-
-                    Self::ScrollWheel(ScrollWheelEvent {
+                        pressure,
+                        modifiers: read_modifiers(native_event),
                         position: point(
                             px(native_event.locationInWindow().x as f32),
                             window_height - px(native_event.locationInWindow().y as f32),
                         ),
-                        delta,
-                        touch_phase: phase,
-                        modifiers: read_modifiers(native_event),
                     })
-                }),
-                NSEventType::NSLeftMouseDragged
-                | NSEventType::NSRightMouseDragged
-                | NSEventType::NSOtherMouseDragged => {
-                    let pressed_button = match native_event.buttonNumber() {
-                        0 => MouseButton::Left,
-                        1 => MouseButton::Right,
-                        2 => MouseButton::Middle,
-                        3 => MouseButton::Navigate(NavigationDirection::Back),
-                        4 => MouseButton::Navigate(NavigationDirection::Forward),
-                        // Other mouse buttons aren't tracked currently
+                })
+            }
+            // Some mice (like Logitech MX Master) send navigation buttons as swipe events
+            NSEventType::NSEventTypeSwipe => {
+                let navigation_direction = match native_event.phase() {
+                    NSEventPhase::NSEventPhaseEnded => match native_event.deltaX() {
+                        x if x > 0.0 => Some(NavigationDirection::Back),
+                        x if x < 0.0 => Some(NavigationDirection::Forward),
                         _ => return None,
-                    };
-
-                    window_height.map(|window_height| {
-                        Self::MouseMove(MouseMoveEvent {
-                            pressed_button: Some(pressed_button),
+                    },
+                    _ => return None,
+                };
+
+                match navigation_direction {
+                    Some(direction) => window_height.map(|window_height| {
+                        PlatformInput::MouseDown(MouseDownEvent {
+                            button: MouseButton::Navigate(direction),
                             position: point(
                                 px(native_event.locationInWindow().x as f32),
                                 window_height - px(native_event.locationInWindow().y as f32),
                             ),
                             modifiers: read_modifiers(native_event),
+                            click_count: 1,
+                            first_mouse: false,
                         })
-                    })
+                    }),
+                    _ => None,
                 }
-                NSEventType::NSMouseMoved => window_height.map(|window_height| {
-                    Self::MouseMove(MouseMoveEvent {
-                        position: point(
-                            px(native_event.locationInWindow().x as f32),
-                            window_height - px(native_event.locationInWindow().y as f32),
-                        ),
-                        pressed_button: None,
-                        modifiers: read_modifiers(native_event),
-                    })
-                }),
-                NSEventType::NSMouseExited => window_height.map(|window_height| {
-                    Self::MouseExited(MouseExitEvent {
+            }
+            NSEventType::NSScrollWheel => window_height.map(|window_height| {
+                let phase = match native_event.phase() {
+                    NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
+                        TouchPhase::Started
+                    }
+                    NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
+                    _ => TouchPhase::Moved,
+                };
+
+                let raw_data = point(
+                    native_event.scrollingDeltaX() as f32,
+                    native_event.scrollingDeltaY() as f32,
+                );
+
+                let delta = if native_event.hasPreciseScrollingDeltas() == YES {
+                    ScrollDelta::Pixels(raw_data.map(px))
+                } else {
+                    ScrollDelta::Lines(raw_data)
+                };
+
+                PlatformInput::ScrollWheel(ScrollWheelEvent {
+                    position: point(
+                        px(native_event.locationInWindow().x as f32),
+                        window_height - px(native_event.locationInWindow().y as f32),
+                    ),
+                    delta,
+                    touch_phase: phase,
+                    modifiers: read_modifiers(native_event),
+                })
+            }),
+            NSEventType::NSLeftMouseDragged
+            | NSEventType::NSRightMouseDragged
+            | NSEventType::NSOtherMouseDragged => {
+                let pressed_button = match native_event.buttonNumber() {
+                    0 => MouseButton::Left,
+                    1 => MouseButton::Right,
+                    2 => MouseButton::Middle,
+                    3 => MouseButton::Navigate(NavigationDirection::Back),
+                    4 => MouseButton::Navigate(NavigationDirection::Forward),
+                    // Other mouse buttons aren't tracked currently
+                    _ => return None,
+                };
+
+                window_height.map(|window_height| {
+                    PlatformInput::MouseMove(MouseMoveEvent {
+                        pressed_button: Some(pressed_button),
                         position: point(
                             px(native_event.locationInWindow().x as f32),
                             window_height - px(native_event.locationInWindow().y as f32),
                         ),
-
-                        pressed_button: None,
                         modifiers: read_modifiers(native_event),
                     })
-                }),
-                _ => None,
+                })
             }
+            NSEventType::NSMouseMoved => window_height.map(|window_height| {
+                PlatformInput::MouseMove(MouseMoveEvent {
+                    position: point(
+                        px(native_event.locationInWindow().x as f32),
+                        window_height - px(native_event.locationInWindow().y as f32),
+                    ),
+                    pressed_button: None,
+                    modifiers: read_modifiers(native_event),
+                })
+            }),
+            NSEventType::NSMouseExited => window_height.map(|window_height| {
+                PlatformInput::MouseExited(MouseExitEvent {
+                    position: point(
+                        px(native_event.locationInWindow().x as f32),
+                        window_height - px(native_event.locationInWindow().y as f32),
+                    ),
+
+                    pressed_button: None,
+                    modifiers: read_modifiers(native_event),
+                })
+            }),
+            _ => None,
         }
     }
 }
@@ -320,7 +318,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
     unsafe {
         use cocoa::appkit::*;
 
-        let mut characters = native_event
+        let characters = native_event
             .charactersIgnoringModifiers()
             .to_str()
             .to_string();

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

@@ -1,5 +1,9 @@
-//! Macos screen have a y axis that goings up from the bottom of the screen and
+#![cfg(target_os = "macos")]
+//! macOS platform implementation for GPUI.
+//!
+//! macOS screens have a y axis that goes up from the bottom of the screen and
 //! an origin at the bottom left of the main display.
+
 mod dispatcher;
 mod display;
 mod display_link;
@@ -13,7 +17,6 @@ mod screen_capture;
 mod metal_atlas;
 pub mod metal_renderer;
 
-use core_video::image_buffer::CVImageBuffer;
 use metal_renderer as renderer;
 
 #[cfg(feature = "font-kit")]
@@ -26,10 +29,9 @@ mod platform;
 mod window;
 mod window_appearance;
 
-use crate::{DevicePixels, Pixels, Size, px, size};
 use cocoa::{
     base::{id, nil},
-    foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger},
+    foundation::{NSAutoreleasePool, NSNotFound, NSString, NSUInteger},
 };
 
 use objc::runtime::{BOOL, NO, YES};
@@ -48,8 +50,7 @@ pub(crate) use window::*;
 #[cfg(feature = "font-kit")]
 pub(crate) use text_system::*;
 
-/// A frame of video captured from a screen.
-pub(crate) type PlatformScreenCaptureFrame = CVImageBuffer;
+pub use platform::MacPlatform;
 
 trait BoolExt {
     fn to_objc(self) -> BOOL;
@@ -133,26 +134,3 @@ unsafe impl objc::Encode for NSRange {
 unsafe fn ns_string(string: &str) -> id {
     unsafe { NSString::alloc(nil).init_str(string).autorelease() }
 }
-
-impl From<NSSize> for Size<Pixels> {
-    fn from(value: NSSize) -> Self {
-        Size {
-            width: px(value.width as f32),
-            height: px(value.height as f32),
-        }
-    }
-}
-
-impl From<NSRect> for Size<Pixels> {
-    fn from(rect: NSRect) -> Self {
-        let NSSize { width, height } = rect.size;
-        size(width.into(), height.into())
-    }
-}
-
-impl From<NSRect> for Size<DevicePixels> {
-    fn from(rect: NSRect) -> Self {
-        let NSSize { width, height } = rect.size;
-        size(DevicePixels(width as i32), DevicePixels(height as i32))
-    }
-}

crates/gpui/src/platform/mac/keyboard.rs → crates/gpui_macos/src/keyboard.rs 🔗

@@ -3,9 +3,9 @@ use std::ffi::{CStr, c_void};
 
 use objc::{msg_send, runtime::Object, sel, sel_impl};
 
-use crate::{KeybindingKeystroke, Keystroke, PlatformKeyboardLayout, PlatformKeyboardMapper};
+use gpui::{KeybindingKeystroke, Keystroke, PlatformKeyboardLayout, PlatformKeyboardMapper};
 
-use super::{
+use crate::{
     TISCopyCurrentKeyboardLayoutInputSource, TISGetInputSourceProperty, kTISPropertyInputSourceID,
     kTISPropertyLocalizedName,
 };

crates/gpui/src/platform/mac/metal_atlas.rs → crates/gpui_macos/src/metal_atlas.rs 🔗

@@ -1,11 +1,11 @@
-use crate::{
-    AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
-    Point, Size, platform::AtlasTextureList,
-};
 use anyhow::{Context as _, Result};
 use collections::FxHashMap;
 use derive_more::{Deref, DerefMut};
 use etagere::BucketedAtlasAllocator;
+use gpui::{
+    AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTextureList, AtlasTile, Bounds, DevicePixels,
+    PlatformAtlas, Point, Size,
+};
 use metal::Device;
 use parking_lot::Mutex;
 use std::borrow::Cow;
@@ -164,7 +164,7 @@ impl MetalAtlasState {
                 index: index.unwrap_or(texture_list.textures.len()) as u32,
                 kind,
             },
-            allocator: etagere::BucketedAtlasAllocator::new(size.into()),
+            allocator: etagere::BucketedAtlasAllocator::new(size_to_etagere(size)),
             metal_texture: AssertSend(metal_texture),
             live_atlas_keys: 0,
         };
@@ -183,9 +183,9 @@ impl MetalAtlasState {
 
     fn texture(&self, id: AtlasTextureId) -> &MetalAtlasTexture {
         let textures = match id.kind {
-            crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
-            crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
-            crate::AtlasTextureKind::Subpixel => unreachable!(),
+            AtlasTextureKind::Monochrome => &self.monochrome_textures,
+            AtlasTextureKind::Polychrome => &self.polychrome_textures,
+            AtlasTextureKind::Subpixel => unreachable!(),
         };
         textures[id.index as usize].as_ref().unwrap()
     }
@@ -200,12 +200,12 @@ struct MetalAtlasTexture {
 
 impl MetalAtlasTexture {
     fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
-        let allocation = self.allocator.allocate(size.into())?;
+        let allocation = self.allocator.allocate(size_to_etagere(size))?;
         let tile = AtlasTile {
             texture_id: self.id,
             tile_id: allocation.id.into(),
             bounds: Bounds {
-                origin: allocation.rectangle.min.into(),
+                origin: point_from_etagere(allocation.rectangle.min),
                 size,
             },
             padding: 0,
@@ -247,36 +247,14 @@ impl MetalAtlasTexture {
     }
 }
 
-impl From<Size<DevicePixels>> for etagere::Size {
-    fn from(size: Size<DevicePixels>) -> Self {
-        etagere::Size::new(size.width.into(), size.height.into())
-    }
+fn size_to_etagere(size: Size<DevicePixels>) -> etagere::Size {
+    etagere::Size::new(size.width.into(), size.height.into())
 }
 
-impl From<etagere::Point> for Point<DevicePixels> {
-    fn from(value: etagere::Point) -> Self {
-        Point {
-            x: DevicePixels::from(value.x),
-            y: DevicePixels::from(value.y),
-        }
-    }
-}
-
-impl From<etagere::Size> for Size<DevicePixels> {
-    fn from(size: etagere::Size) -> Self {
-        Size {
-            width: DevicePixels::from(size.width),
-            height: DevicePixels::from(size.height),
-        }
-    }
-}
-
-impl From<etagere::Rectangle> for Bounds<DevicePixels> {
-    fn from(rectangle: etagere::Rectangle) -> Self {
-        Bounds {
-            origin: rectangle.min.into(),
-            size: rectangle.size().into(),
-        }
+fn point_from_etagere(value: etagere::Point) -> Point<DevicePixels> {
+    Point {
+        x: DevicePixels::from(value.x),
+        y: DevicePixels::from(value.y),
     }
 }
 

crates/gpui/src/platform/mac/metal_renderer.rs → crates/gpui_macos/src/metal_renderer.rs 🔗

@@ -1,9 +1,4 @@
-use super::metal_atlas::MetalAtlas;
-use crate::{
-    AtlasTextureId, Background, Bounds, ContentMask, DevicePixels, MonochromeSprite, PaintSurface,
-    Path, Point, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size,
-    Surface, Underline, point, size,
-};
+use crate::metal_atlas::MetalAtlas;
 use anyhow::Result;
 use block::ConcreteBlock;
 use cocoa::{
@@ -11,6 +6,11 @@ use cocoa::{
     foundation::{NSSize, NSUInteger},
     quartzcore::AutoresizingMask,
 };
+use gpui::{
+    AtlasTextureId, Background, Bounds, ContentMask, DevicePixels, MonochromeSprite, PaintSurface,
+    Path, Point, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size,
+    Surface, Underline, point, size,
+};
 #[cfg(any(test, feature = "test-support"))]
 use image::RgbaImage;
 
@@ -30,7 +30,7 @@ use parking_lot::Mutex;
 use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc};
 
 // Exported to metal
-pub(crate) type PointF = crate::Point<f32>;
+pub(crate) type PointF = gpui::Point<f32>;
 
 #[cfg(not(feature = "runtime_shaders"))]
 const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
@@ -40,14 +40,14 @@ const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitch
 // https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
 const PATH_SAMPLE_COUNT: u32 = 4;
 
-pub type Context = Arc<Mutex<InstanceBufferPool>>;
-pub type Renderer = MetalRenderer;
+pub(crate) type Context = Arc<Mutex<InstanceBufferPool>>;
+pub(crate) type Renderer = MetalRenderer;
 
-pub unsafe fn new_renderer(
+pub(crate) unsafe fn new_renderer(
     context: self::Context,
     _native_window: *mut c_void,
     _native_view: *mut c_void,
-    _bounds: crate::Size<f32>,
+    _bounds: gpui::Size<f32>,
     transparent: bool,
 ) -> Renderer {
     MetalRenderer::new(context, transparent)
@@ -349,7 +349,7 @@ impl MetalRenderer {
         self.path_intermediate_texture = Some(self.device.new_texture(&texture_descriptor));
 
         if self.path_sample_count > 1 {
-            let mut msaa_descriptor = texture_descriptor;
+            let msaa_descriptor = texture_descriptor;
             msaa_descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
             msaa_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
             msaa_descriptor.set_sample_count(self.path_sample_count as _);

crates/gpui/src/platform/mac/open_type.rs → crates/gpui_macos/src/open_type.rs 🔗

@@ -1,6 +1,5 @@
 #![allow(unused, non_upper_case_globals)]
 
-use crate::{FontFallbacks, FontFeatures};
 use cocoa::appkit::CGFloat;
 use core_foundation::{
     array::{
@@ -25,6 +24,7 @@ use core_text::{
     },
 };
 use font_kit::font::Font as FontKitFont;
+use gpui::{FontFallbacks, FontFeatures};
 use std::ptr;
 
 pub fn apply_features_and_fallbacks(

crates/gpui/src/platform/mac/pasteboard.rs → crates/gpui_macos/src/pasteboard.rs 🔗

@@ -9,10 +9,8 @@ use cocoa::{
 use objc::{msg_send, runtime::Object, sel, sel_impl};
 use strum::IntoEnumIterator as _;
 
-use crate::{
-    ClipboardEntry, ClipboardItem, ClipboardString, Image, ImageFormat, asset_cache::hash,
-    platform::mac::ns_string,
-};
+use crate::ns_string;
+use gpui::{ClipboardEntry, ClipboardItem, ClipboardString, Image, ImageFormat, hash};
 
 pub struct Pasteboard {
     inner: id,
@@ -77,7 +75,7 @@ impl Pasteboard {
     }
 
     fn read_image(&self, format: ImageFormat) -> Option<ClipboardItem> {
-        let mut ut_type: UTType = format.into();
+        let ut_type: UTType = format.into();
 
         unsafe {
             let types: id = self.inner.types();
@@ -304,7 +302,7 @@ impl UTType {
 mod tests {
     use cocoa::{appkit::NSPasteboardTypeString, foundation::NSData};
 
-    use crate::{ClipboardEntry, ClipboardItem, ClipboardString};
+    use gpui::{ClipboardEntry, ClipboardItem, ClipboardString};
 
     use super::*;
 

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

@@ -1,12 +1,6 @@
-use super::{
-    BoolExt, MacKeyboardLayout, MacKeyboardMapper, events::key_to_native, ns_string, renderer,
-};
 use crate::{
-    Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor,
-    KeyContext, Keymap, MacDispatcher, MacDisplay, MacWindow, Menu, MenuItem, OsMenu, OwnedMenu,
-    PathPromptOptions, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper,
-    PlatformTextSystem, PlatformWindow, Result, SystemMenuType, Task, ThermalState,
-    WindowAppearance, WindowParams, platform::mac::pasteboard::Pasteboard,
+    BoolExt, MacDispatcher, MacDisplay, MacKeyboardLayout, MacKeyboardMapper, MacWindow,
+    events::key_to_native, ns_string, pasteboard::Pasteboard, renderer,
 };
 use anyhow::{Context as _, anyhow};
 use block::ConcreteBlock;
@@ -31,6 +25,12 @@ use core_foundation::{
 };
 use ctor::ctor;
 use futures::channel::oneshot;
+use gpui::{
+    Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor,
+    KeyContext, Keymap, Menu, MenuItem, OsMenu, OwnedMenu, PathPromptOptions, Platform,
+    PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,
+    PlatformWindow, Result, SystemMenuType, Task, ThermalState, WindowAppearance, WindowParams,
+};
 use itertools::Itertools;
 use objc::{
     class,
@@ -154,7 +154,7 @@ unsafe fn build_classes() {
     }
 }
 
-pub(crate) struct MacPlatform(Mutex<MacPlatformState>);
+pub struct MacPlatform(Mutex<MacPlatformState>);
 
 pub(crate) struct MacPlatformState {
     background_executor: BackgroundExecutor,
@@ -180,14 +180,14 @@ pub(crate) struct MacPlatformState {
 }
 
 impl MacPlatform {
-    pub(crate) fn new(headless: bool) -> Self {
+    pub fn new(headless: bool) -> Self {
         let dispatcher = Arc::new(MacDispatcher::new());
 
         #[cfg(feature = "font-kit")]
         let text_system = Arc::new(crate::MacTextSystem::new());
 
         #[cfg(not(feature = "font-kit"))]
-        let text_system = Arc::new(crate::NoopTextSystem::new());
+        let text_system = Arc::new(gpui::NoopTextSystem::new());
 
         let keyboard_layout = MacKeyboardLayout::new();
         let keyboard_mapper = Rc::new(MacKeyboardMapper::new(keyboard_layout.id()));
@@ -321,14 +321,14 @@ impl MacPlatform {
                         .map(|binding| binding.keystrokes());
 
                     let selector = match os_action {
-                        Some(crate::OsAction::Cut) => selector("cut:"),
-                        Some(crate::OsAction::Copy) => selector("copy:"),
-                        Some(crate::OsAction::Paste) => selector("paste:"),
-                        Some(crate::OsAction::SelectAll) => selector("selectAll:"),
+                        Some(gpui::OsAction::Cut) => selector("cut:"),
+                        Some(gpui::OsAction::Copy) => selector("copy:"),
+                        Some(gpui::OsAction::Paste) => selector("paste:"),
+                        Some(gpui::OsAction::SelectAll) => selector("selectAll:"),
                         // "undo:" and "redo:" are always disabled in our case, as
                         // we don't have a NSTextView/NSTextField to enable them on.
-                        Some(crate::OsAction::Undo) => selector("handleGPUIMenuItem:"),
-                        Some(crate::OsAction::Redo) => selector("handleGPUIMenuItem:"),
+                        Some(gpui::OsAction::Undo) => selector("handleGPUIMenuItem:"),
+                        Some(gpui::OsAction::Redo) => selector("handleGPUIMenuItem:"),
                         None => selector("handleGPUIMenuItem:"),
                     };
 
@@ -448,7 +448,7 @@ impl Platform for MacPlatform {
         self.0.lock().background_executor.clone()
     }
 
-    fn foreground_executor(&self) -> crate::ForegroundExecutor {
+    fn foreground_executor(&self) -> gpui::ForegroundExecutor {
         self.0.lock().foreground_executor.clone()
     }
 
@@ -493,7 +493,7 @@ impl Platform for MacPlatform {
         // this, we make quitting the application asynchronous so that we aren't holding borrows to
         // the app state on the stack when we actually terminate the app.
 
-        use super::dispatcher::{dispatch_get_main_queue, dispatch_sys::dispatch_async_f};
+        use crate::dispatcher::{dispatch_get_main_queue, dispatch_sys::dispatch_async_f};
 
         unsafe {
             dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
@@ -587,14 +587,14 @@ impl Platform for MacPlatform {
     #[cfg(feature = "screen-capture")]
     fn is_screen_capture_supported(&self) -> bool {
         let min_version = cocoa::foundation::NSOperatingSystemVersion::new(12, 3, 0);
-        super::is_macos_version_at_least(min_version)
+        crate::is_macos_version_at_least(min_version)
     }
 
     #[cfg(feature = "screen-capture")]
     fn screen_capture_sources(
         &self,
-    ) -> oneshot::Receiver<Result<Vec<Rc<dyn crate::ScreenCaptureSource>>>> {
-        super::screen_capture::get_sources()
+    ) -> oneshot::Receiver<Result<Vec<Rc<dyn gpui::ScreenCaptureSource>>>> {
+        crate::screen_capture::get_sources()
     }
 
     fn active_window(&self) -> Option<AnyWindowHandle> {
@@ -626,7 +626,7 @@ impl Platform for MacPlatform {
         unsafe {
             let app = NSApplication::sharedApplication(nil);
             let appearance: id = msg_send![app, effectiveAppearance];
-            WindowAppearance::from_native(appearance)
+            crate::window_appearance::window_appearance_from_native(appearance)
         }
     }
 
@@ -1261,7 +1261,7 @@ extern "C" fn on_thermal_state_change(this: &mut Object, _: Sel, _: id) {
     // Defer to the next run loop iteration to avoid re-entrant borrows of the App RefCell,
     // as NSNotificationCenter delivers this notification synchronously and it may fire while
     // the App is already borrowed (same pattern as quit() above).
-    use super::dispatcher::{dispatch_get_main_queue, dispatch_sys::dispatch_async_f};
+    use crate::dispatcher::{dispatch_get_main_queue, dispatch_sys::dispatch_async_f};
 
     let platform = unsafe { get_mac_platform(this) };
     let platform_ptr = platform as *const MacPlatform as *mut c_void;
@@ -1367,7 +1367,7 @@ extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
 extern "C" fn handle_dock_menu(this: &mut Object, _: Sel, _: id) -> id {
     unsafe {
         let platform = get_mac_platform(this);
-        let mut state = platform.0.lock();
+        let state = platform.0.lock();
         if let Some(id) = state.dock_menu {
             id
         } else {

crates/gpui/src/platform/mac/screen_capture.rs → crates/gpui_macos/src/screen_capture.rs 🔗

@@ -1,9 +1,4 @@
-use super::ns_string;
-use crate::{
-    DevicePixels, ForegroundExecutor, SharedString, SourceMetadata,
-    platform::{ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream},
-    size,
-};
+use crate::ns_string;
 use anyhow::{Result, anyhow};
 use block::ConcreteBlock;
 use cocoa::{
@@ -18,6 +13,10 @@ use core_graphics::display::{
 };
 use ctor::ctor;
 use futures::channel::oneshot;
+use gpui::{
+    DevicePixels, ForegroundExecutor, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream,
+    SharedString, SourceMetadata, size,
+};
 use media::core_media::{CMSampleBuffer, CMSampleBufferRef};
 use metal::NSInteger;
 use objc::{
@@ -29,7 +28,7 @@ use objc::{
 };
 use std::{cell::RefCell, ffi::c_void, mem, ptr, rc::Rc};
 
-use super::NSStringExt;
+use crate::NSStringExt;
 
 #[derive(Clone)]
 pub struct MacScreenCaptureSource {
@@ -116,7 +115,7 @@ impl ScreenCaptureSource for MacScreenCaptureSource {
             let _: () = msg_send![configuration, release];
             let _: () = msg_send![delegate, release];
 
-            let (mut tx, rx) = oneshot::channel();
+            let (tx, rx) = oneshot::channel();
 
             let mut error: id = nil;
             let _: () = msg_send![stream, addStreamOutput:output type:SCStreamOutputTypeScreen sampleHandlerQueue:0 error:&mut error as *mut id];
@@ -243,11 +242,11 @@ unsafe fn screen_id_to_human_label() -> HashMap<CGDirectDisplayID, ScreenMeta> {
 
 pub(crate) fn get_sources() -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
     unsafe {
-        let (mut tx, rx) = oneshot::channel();
+        let (tx, rx) = oneshot::channel();
         let tx = Rc::new(RefCell::new(Some(tx)));
         let screen_id_to_label = screen_id_to_human_label();
         let block = ConcreteBlock::new(move |shareable_content: id, error: id| {
-            let Some(mut tx) = tx.borrow_mut().take() else {
+            let Some(tx) = tx.borrow_mut().take() else {
                 return;
             };
 

crates/gpui/src/platform/mac/text_system.rs → crates/gpui_macos/src/text_system.rs 🔗

@@ -1,9 +1,3 @@
-use crate::{
-    Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, FontId, FontMetrics, FontRun,
-    FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point,
-    RenderGlyphParams, Result, SUBPIXEL_VARIANTS_X, ShapedGlyph, ShapedRun, SharedString, Size,
-    TextRenderingMode, point, px, size, swap_rgba_pa_to_bgra,
-};
 use anyhow::anyhow;
 use cocoa::appkit::CGFloat;
 use collections::HashMap;
@@ -39,16 +33,22 @@ use font_kit::{
     source::SystemSource,
     sources::mem::MemSource,
 };
+use gpui::{
+    Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, FontId, FontMetrics, FontRun,
+    FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, RenderGlyphParams,
+    Result, SUBPIXEL_VARIANTS_X, ShapedGlyph, ShapedRun, SharedString, Size, TextRenderingMode,
+    point, px, size, swap_rgba_pa_to_bgra,
+};
 use parking_lot::{RwLock, RwLockUpgradableReadGuard};
 use pathfinder_geometry::{
     rect::{RectF, RectI},
     transform2d::Transform2F,
-    vector::{Vector2F, Vector2I},
+    vector::Vector2F,
 };
 use smallvec::SmallVec;
 use std::{borrow::Cow, char, convert::TryFrom, sync::Arc};
 
-use super::open_type::apply_features_and_fallbacks;
+use crate::open_type::apply_features_and_fallbacks;
 
 #[allow(non_upper_case_globals)]
 const kCGImageAlphaOnly: u32 = 7;
@@ -159,8 +159,8 @@ impl PlatformTextSystem for MacTextSystem {
             let ix = font_kit::matching::find_best_match(
                 &candidate_properties,
                 &font_kit::properties::Properties {
-                    style: font.style.into(),
-                    weight: font.weight.into(),
+                    style: fontkit_style(font.style),
+                    weight: fontkit_weight(font.weight),
                     stretch: Default::default(),
                 },
             )?;
@@ -172,13 +172,13 @@ impl PlatformTextSystem for MacTextSystem {
     }
 
     fn font_metrics(&self, font_id: FontId) -> FontMetrics {
-        self.0.read().fonts[font_id.0].metrics().into()
+        font_kit_metrics_to_metrics(self.0.read().fonts[font_id.0].metrics())
     }
 
     fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
-        Ok(self.0.read().fonts[font_id.0]
-            .typographic_bounds(glyph_id.0)?
-            .into())
+        Ok(bounds_from_rect(
+            self.0.read().fonts[font_id.0].typographic_bounds(glyph_id.0)?,
+        ))
     }
 
     fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
@@ -241,7 +241,7 @@ impl MacTextSystemState {
         features: &FontFeatures,
         fallbacks: Option<&FontFallbacks>,
     ) -> Result<SmallVec<[FontId; 4]>> {
-        let name = crate::text_system::font_name_with_fallbacks(name, ".AppleSystemUIFont");
+        let name = gpui::font_name_with_fallbacks(name, ".AppleSystemUIFont");
 
         let mut font_ids = SmallVec::new();
         let family = self
@@ -321,7 +321,9 @@ impl MacTextSystemState {
     }
 
     fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
-        Ok(self.fonts[font_id.0].advance(glyph_id.0)?.into())
+        Ok(size_from_vector2f(
+            self.fonts[font_id.0].advance(glyph_id.0)?,
+        ))
     }
 
     fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
@@ -357,15 +359,13 @@ impl MacTextSystemState {
     fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
         let font = &self.fonts[params.font_id.0];
         let scale = Transform2F::from_scale(params.scale_factor);
-        Ok(font
-            .raster_bounds(
-                params.glyph_id.0,
-                params.font_size.into(),
-                scale,
-                HintingOptions::None,
-                font_kit::canvas::RasterizationOptions::GrayscaleAa,
-            )?
-            .into())
+        Ok(bounds_from_rect_i(font.raster_bounds(
+            params.glyph_id.0,
+            params.font_size.into(),
+            scale,
+            HintingOptions::None,
+            font_kit::canvas::RasterizationOptions::GrayscaleAa,
+        )?))
     }
 
     fn rasterize_glyph(
@@ -480,12 +480,12 @@ impl MacTextSystemState {
                 let font = &self.fonts[run.font_id.0];
 
                 let font_metrics = font.metrics();
-                let font_scale = font_size.0 / font_metrics.units_per_em as f32;
+                let font_scale = f32::from(font_size) / font_metrics.units_per_em as f32;
                 max_ascent = max_ascent.max(font_metrics.ascent * font_scale);
                 max_descent = max_descent.max(-font_metrics.descent * font_scale);
 
                 let font_size = if break_ligature {
-                    px(font_size.0.next_up())
+                    px(f32::from(font_size).next_up())
                 } else {
                     font_size
                 };
@@ -514,7 +514,7 @@ impl MacTextSystemState {
             };
             let font_id = self.id_for_native_font(font);
 
-            let mut glyphs = match runs.last_mut() {
+            let glyphs = match runs.last_mut() {
                 Some(run) if run.font_id == font_id => &mut run.glyphs,
                 _ => {
                     runs.push(ShapedRun {
@@ -530,7 +530,7 @@ impl MacTextSystemState {
                 .zip(run.positions().iter())
                 .zip(run.string_indices().iter())
             {
-                let mut glyph_utf16_ix = usize::try_from(glyph_utf16_ix).unwrap();
+                let glyph_utf16_ix = usize::try_from(glyph_utf16_ix).unwrap();
                 if ix_converter.utf16_ix > glyph_utf16_ix {
                     // We cannot reuse current index converter, as it can only seek forward. Restart the search.
                     ix_converter = StringIndexConverter::new(text);
@@ -586,80 +586,68 @@ impl<'a> StringIndexConverter<'a> {
     }
 }
 
-impl From<Metrics> for FontMetrics {
-    fn from(metrics: Metrics) -> Self {
-        FontMetrics {
-            units_per_em: metrics.units_per_em,
-            ascent: metrics.ascent,
-            descent: metrics.descent,
-            line_gap: metrics.line_gap,
-            underline_position: metrics.underline_position,
-            underline_thickness: metrics.underline_thickness,
-            cap_height: metrics.cap_height,
-            x_height: metrics.x_height,
-            bounding_box: metrics.bounding_box.into(),
-        }
-    }
-}
-
-impl From<RectF> for Bounds<f32> {
-    fn from(rect: RectF) -> Self {
-        Bounds {
-            origin: point(rect.origin_x(), rect.origin_y()),
-            size: size(rect.width(), rect.height()),
-        }
-    }
-}
-
-impl From<RectI> for Bounds<DevicePixels> {
-    fn from(rect: RectI) -> Self {
-        Bounds {
-            origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())),
-            size: size(DevicePixels(rect.width()), DevicePixels(rect.height())),
-        }
-    }
-}
-
-impl From<Vector2I> for Size<DevicePixels> {
-    fn from(value: Vector2I) -> Self {
-        size(value.x().into(), value.y().into())
+fn font_kit_metrics_to_metrics(metrics: Metrics) -> FontMetrics {
+    FontMetrics {
+        units_per_em: metrics.units_per_em,
+        ascent: metrics.ascent,
+        descent: metrics.descent,
+        line_gap: metrics.line_gap,
+        underline_position: metrics.underline_position,
+        underline_thickness: metrics.underline_thickness,
+        cap_height: metrics.cap_height,
+        x_height: metrics.x_height,
+        bounding_box: bounds_from_rect(metrics.bounding_box),
     }
 }
 
-impl From<RectI> for Bounds<i32> {
-    fn from(rect: RectI) -> Self {
-        Bounds {
-            origin: point(rect.origin_x(), rect.origin_y()),
-            size: size(rect.width(), rect.height()),
-        }
+fn bounds_from_rect(rect: RectF) -> Bounds<f32> {
+    Bounds {
+        origin: point(rect.origin_x(), rect.origin_y()),
+        size: size(rect.width(), rect.height()),
     }
 }
 
-impl From<Point<u32>> for Vector2I {
-    fn from(size: Point<u32>) -> Self {
-        Vector2I::new(size.x as i32, size.y as i32)
+fn bounds_from_rect_i(rect: RectI) -> Bounds<DevicePixels> {
+    Bounds {
+        origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())),
+        size: size(DevicePixels(rect.width()), DevicePixels(rect.height())),
     }
 }
 
-impl From<Vector2F> for Size<f32> {
-    fn from(vec: Vector2F) -> Self {
-        size(vec.x(), vec.y())
-    }
+// impl From<Vector2I> for Size<DevicePixels> {
+//     fn from(value: Vector2I) -> Self {
+//         size(value.x().into(), value.y().into())
+//     }
+// }
+
+// impl From<RectI> for Bounds<i32> {
+//     fn from(rect: RectI) -> Self {
+//         Bounds {
+//             origin: point(rect.origin_x(), rect.origin_y()),
+//             size: size(rect.width(), rect.height()),
+//         }
+//     }
+// }
+
+// impl From<Point<u32>> for Vector2I {
+//     fn from(size: Point<u32>) -> Self {
+//         Vector2I::new(size.x as i32, size.y as i32)
+//     }
+// }
+
+fn size_from_vector2f(vec: Vector2F) -> Size<f32> {
+    size(vec.x(), vec.y())
 }
 
-impl From<FontWeight> for FontkitWeight {
-    fn from(value: FontWeight) -> Self {
-        FontkitWeight(value.0)
-    }
+fn fontkit_weight(value: FontWeight) -> FontkitWeight {
+    FontkitWeight(value.0)
 }
 
-impl From<FontStyle> for FontkitStyle {
-    fn from(style: FontStyle) -> Self {
-        match style {
-            FontStyle::Normal => FontkitStyle::Normal,
-            FontStyle::Italic => FontkitStyle::Italic,
-            FontStyle::Oblique => FontkitStyle::Oblique,
-        }
+fn fontkit_style(style: FontStyle) -> FontkitStyle {
+    match style {
+        FontStyle::Normal => FontkitStyle::Normal,
+        FontStyle::Italic => FontkitStyle::Italic,
+        FontStyle::Oblique => FontkitStyle::Oblique,
     }
 }
 
@@ -706,7 +694,8 @@ mod lenient_font_attributes {
 
 #[cfg(test)]
 mod tests {
-    use crate::{FontRun, GlyphId, MacTextSystem, PlatformTextSystem, font, px};
+    use crate::MacTextSystem;
+    use gpui::{FontRun, GlyphId, PlatformTextSystem, font, px};
 
     #[test]
     fn test_layout_line_bom_char() {

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

@@ -1,13 +1,7 @@
-use super::{BoolExt, MacDisplay, NSRange, NSStringExt, ns_string, renderer};
 use crate::{
-    AnyWindowHandle, BackgroundExecutor, Bounds, Capslock, DisplayLink, ExternalPaths,
-    FileDropEvent, ForegroundExecutor, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent,
-    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas,
-    PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptButton, PromptLevel,
-    RequestFrameOptions, SharedString, Size, SystemWindowTab, WindowAppearance,
-    WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowKind, WindowParams,
-    dispatch_get_main_queue, dispatch_sys::dispatch_async_f, platform::PlatformInputHandler, point,
-    px, size,
+    BoolExt, DisplayLink, MacDisplay, NSRange, NSStringExt, dispatch_get_main_queue,
+    dispatcher::dispatch_sys::dispatch_async_f, events::platform_input_from_native, ns_string,
+    renderer,
 };
 #[cfg(any(test, feature = "test-support"))]
 use anyhow::Result;
@@ -28,6 +22,15 @@ use cocoa::{
         NSUserDefaults,
     },
 };
+use gpui::{
+    AnyWindowHandle, BackgroundExecutor, Bounds, Capslock, ExternalPaths, FileDropEvent,
+    ForegroundExecutor, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
+    MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
+    PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptButton, PromptLevel,
+    RequestFrameOptions, SharedString, Size, SystemWindowTab, WindowAppearance,
+    WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowKind, WindowParams, point,
+    px, size,
+};
 #[cfg(any(test, feature = "test-support"))]
 use image::RgbaImage;
 
@@ -408,7 +411,7 @@ struct MacWindowState {
     display_link: Option<DisplayLink>,
     renderer: renderer::Renderer,
     request_frame_callback: Option<Box<dyn FnMut(RequestFrameOptions)>>,
-    event_callback: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
+    event_callback: Option<Box<dyn FnMut(PlatformInput) -> gpui::DispatchEventResult>>,
     activate_callback: Option<Box<dyn FnMut(bool)>>,
     resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
     moved_callback: Option<Box<dyn FnMut()>>,
@@ -514,9 +517,14 @@ impl MacWindowState {
     }
 
     fn is_maximized(&self) -> bool {
+        fn rect_to_size(rect: NSRect) -> Size<Pixels> {
+            let NSSize { width, height } = rect.size;
+            size(width.into(), height.into())
+        }
+
         unsafe {
             let bounds = self.bounds();
-            let screen_size = self.native_window.screen().visibleFrame().into();
+            let screen_size = rect_to_size(self.native_window.screen().visibleFrame());
             bounds.size == screen_size
         }
     }
@@ -532,7 +540,7 @@ impl MacWindowState {
         let mut window_frame = unsafe { NSWindow::frame(self.native_window) };
         let screen = unsafe { NSWindow::screen(self.native_window) };
         if screen == nil {
-            return Bounds::new(point(px(0.), px(0.)), crate::DEFAULT_WINDOW_SIZE);
+            return Bounds::new(point(px(0.), px(0.)), gpui::DEFAULT_WINDOW_SIZE);
         }
         let screen_frame = unsafe { NSScreen::frame(screen) };
 
@@ -674,11 +682,14 @@ impl MacWindow {
 
             let window_rect = NSRect::new(
                 NSPoint::new(
-                    screen_frame.origin.x + bounds.origin.x.0 as f64,
+                    screen_frame.origin.x + bounds.origin.x.as_f32() as f64,
                     screen_frame.origin.y
-                        + (display.bounds().size.height - bounds.origin.y).0 as f64,
+                        + (display.bounds().size.height - bounds.origin.y).as_f32() as f64,
+                ),
+                NSSize::new(
+                    bounds.size.width.as_f32() as f64,
+                    bounds.size.height.as_f32() as f64,
                 ),
-                NSSize::new(bounds.size.width.0 as f64, bounds.size.height.0 as f64),
             );
 
             let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
@@ -717,7 +728,7 @@ impl MacWindow {
                     renderer_context,
                     native_window as *mut _,
                     native_view as *mut _,
-                    bounds.size.map(|pixels| pixels.0),
+                    bounds.size.map(|pixels| pixels.as_f32()),
                     false,
                 ),
                 request_frame_callback: None,
@@ -1029,8 +1040,8 @@ impl PlatformWindow for MacWindow {
             .spawn(async move {
                 unsafe {
                     window.setContentSize_(NSSize {
-                        width: size.width.0 as f64,
-                        height: size.height.0 as f64,
+                        width: size.width.as_f32() as f64,
+                        height: size.height.as_f32() as f64,
                     });
                 }
             })
@@ -1103,7 +1114,7 @@ impl PlatformWindow for MacWindow {
     fn appearance(&self) -> WindowAppearance {
         unsafe {
             let appearance: id = msg_send![self.0.lock().native_window, effectiveAppearance];
-            WindowAppearance::from_native(appearance)
+            crate::window_appearance::window_appearance_from_native(appearance)
         }
     }
 
@@ -1227,7 +1238,7 @@ impl PlatformWindow for MacWindow {
 
                 if answer.is_cancel() {
                     // Bind Escape Key to Cancel Button
-                    if let Some(key) = std::char::from_u32(super::events::ESCAPE_KEY as u32) {
+                    if let Some(key) = std::char::from_u32(crate::events::ESCAPE_KEY as u32) {
                         let _: () =
                             msg_send![button, setKeyEquivalent: ns_string(&key.to_string())];
                     }
@@ -1443,7 +1454,7 @@ impl PlatformWindow for MacWindow {
         self.0.as_ref().lock().request_frame_callback = Some(callback);
     }
 
-    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
+    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> gpui::DispatchEventResult>) {
         self.0.as_ref().lock().event_callback = Some(callback);
     }
 
@@ -1532,7 +1543,7 @@ impl PlatformWindow for MacWindow {
         self.0.as_ref().lock().toggle_tab_bar_callback = Some(callback);
     }
 
-    fn draw(&self, scene: &crate::Scene) {
+    fn draw(&self, scene: &gpui::Scene) {
         let mut this = self.0.lock();
         this.renderer.draw(scene);
     }
@@ -1541,7 +1552,7 @@ impl PlatformWindow for MacWindow {
         self.0.lock().renderer.sprite_atlas().clone()
     }
 
-    fn gpu_specs(&self) -> Option<crate::GpuSpecs> {
+    fn gpu_specs(&self) -> Option<gpui::GpuSpecs> {
         None
     }
 
@@ -1613,13 +1624,13 @@ impl PlatformWindow for MacWindow {
 
         unsafe {
             let app = NSApplication::sharedApplication(nil);
-            let mut event: id = msg_send![app, currentEvent];
+            let event: id = msg_send![app, currentEvent];
             let _: () = msg_send![window, performWindowDragWithEvent: event];
         }
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    fn render_to_image(&self, scene: &crate::Scene) -> Result<RgbaImage> {
+    fn render_to_image(&self, scene: &gpui::Scene) -> Result<RgbaImage> {
         let mut this = self.0.lock();
         this.renderer.render_to_image(scene)
     }
@@ -1742,7 +1753,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
     let mut lock = window_state.as_ref().lock();
 
     let window_height = lock.content_size().height;
-    let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
+    let event = unsafe { platform_input_from_native(native_event, Some(window_height)) };
 
     let Some(event) = event else {
         return NO;
@@ -1760,7 +1771,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
     };
 
     match event {
-        PlatformInput::KeyDown(mut key_down_event) => {
+        PlatformInput::KeyDown(key_down_event) => {
             // For certain keystrokes, macOS will first dispatch a "key equivalent" event.
             // If that event isn't handled, it will then dispatch a "key down" event. GPUI
             // makes no distinction between these two types of events, so we need to ignore
@@ -1859,7 +1870,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
     let weak_window_state = Arc::downgrade(&window_state);
     let mut lock = window_state.as_ref().lock();
     let window_height = lock.content_size().height;
-    let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
+    let event = unsafe { platform_input_from_native(native_event, Some(window_height)) };
 
     if let Some(mut event) = event {
         match &mut event {
@@ -2023,7 +2034,7 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
 
 extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
     let window_state = unsafe { get_window_state(this) };
-    let mut lock = window_state.as_ref().lock();
+    let lock = window_state.as_ref().lock();
 
     let min_version = NSOperatingSystemVersion::new(15, 3, 0);
 
@@ -2082,7 +2093,7 @@ extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) {
 
 extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
     let window_state = unsafe { get_window_state(this) };
-    let mut lock = window_state.lock();
+    let lock = window_state.lock();
     let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
 
     // When opening a pop-up while the application isn't active, Cocoa sends a spurious
@@ -2188,13 +2199,20 @@ extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
 }
 
 extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
+    fn convert(value: NSSize) -> Size<Pixels> {
+        Size {
+            width: px(value.width as f32),
+            height: px(value.height as f32),
+        }
+    }
+
     let window_state = unsafe { get_window_state(this) };
     let mut lock = window_state.as_ref().lock();
 
-    let new_size = Size::<Pixels>::from(size);
+    let new_size = convert(size);
     let old_size = unsafe {
         let old_frame: NSRect = msg_send![this, frame];
-        Size::<Pixels>::from(old_frame.size)
+        convert(old_frame.size)
     };
 
     if old_size == new_size {
@@ -2289,12 +2307,15 @@ extern "C" fn first_rect_for_character_range(
         |bounds| {
             NSRect::new(
                 NSPoint::new(
-                    frame.origin.x + bounds.origin.x.0 as f64,
+                    frame.origin.x + bounds.origin.x.as_f32() as f64,
                     frame.origin.y + frame.size.height
-                        - bounds.origin.y.0 as f64
-                        - bounds.size.height.0 as f64,
+                        - bounds.origin.y.as_f32() as f64
+                        - bounds.size.height.as_f32() as f64,
+                ),
+                NSSize::new(
+                    bounds.size.width.as_f32() as f64,
+                    bounds.size.height.as_f32() as f64,
                 ),
-                NSSize::new(bounds.size.width.0 as f64, bounds.size.height.0 as f64),
             )
         },
     )
@@ -2397,7 +2418,7 @@ extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
     let mut event_callback = lock.event_callback.take();
     drop(lock);
 
-    if let Some((keystroke, mut callback)) = keystroke.zip(event_callback.as_mut()) {
+    if let Some((keystroke, callback)) = keystroke.zip(event_callback.as_mut()) {
         let handled = (callback)(PlatformInput::KeyDown(KeyDownEvent {
             keystroke,
             is_held: false,
@@ -2532,7 +2553,7 @@ fn send_file_drop_event(
 ) -> bool {
     let mut window_state = window_state.lock();
     let window_event_callback = window_state.event_callback.as_mut();
-    if let Some(mut callback) = window_event_callback {
+    if let Some(callback) = window_event_callback {
         let external_files_dragged = match file_drop_event {
             FileDropEvent::Entered { .. } => Some(true),
             FileDropEvent::Exited => Some(false),

crates/gpui_macos/src/window_appearance.rs 🔗

@@ -0,0 +1,35 @@
+use cocoa::{
+    appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight},
+    base::id,
+    foundation::NSString,
+};
+use gpui::WindowAppearance;
+use objc::{msg_send, sel, sel_impl};
+use std::ffi::CStr;
+
+pub(crate) unsafe fn window_appearance_from_native(appearance: id) -> WindowAppearance {
+    let name: id = msg_send![appearance, name];
+    unsafe {
+        if name == NSAppearanceNameVibrantLight {
+            WindowAppearance::VibrantLight
+        } else if name == NSAppearanceNameVibrantDark {
+            WindowAppearance::VibrantDark
+        } else if name == NSAppearanceNameAqua {
+            WindowAppearance::Light
+        } else if name == NSAppearanceNameDarkAqua {
+            WindowAppearance::Dark
+        } else {
+            println!(
+                "unknown appearance: {:?}",
+                CStr::from_ptr(name.UTF8String())
+            );
+            WindowAppearance::Light
+        }
+    }
+}
+
+#[link(name = "AppKit", kind = "framework")]
+unsafe extern "C" {
+    pub static NSAppearanceNameAqua: id;
+    pub static NSAppearanceNameDarkAqua: id;
+}

crates/gpui_platform/Cargo.toml 🔗

@@ -0,0 +1,32 @@
+[package]
+name = "gpui_platform"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "Apache-2.0"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/gpui_platform.rs"
+
+[features]
+default = []
+font-kit = ["gpui_macos/font-kit"]
+test-support = ["gpui/test-support"]
+screen-capture = ["gpui/screen-capture", "gpui_macos/screen-capture", "gpui_windows/screen-capture", "gpui_linux/screen-capture"]
+wayland = ["gpui_linux/wayland"]
+x11 = ["gpui_linux/x11"]
+
+[dependencies]
+gpui.workspace = true
+
+[target.'cfg(target_os = "macos")'.dependencies]
+gpui_macos.workspace = true
+
+[target.'cfg(target_os = "windows")'.dependencies]
+gpui_windows.workspace = true
+
+[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
+gpui_linux.workspace = true

crates/gpui_platform/src/gpui_platform.rs 🔗

@@ -0,0 +1,150 @@
+//! Convenience crate that re-exports GPUI's platform traits and the
+//! `current_platform` constructor so consumers don't need `#[cfg]` gating.
+
+pub use gpui::Platform;
+
+use std::rc::Rc;
+
+/// Returns a background executor for the current platform.
+pub fn background_executor() -> gpui::BackgroundExecutor {
+    current_platform(true).background_executor()
+}
+
+pub fn application() -> gpui::Application {
+    gpui::Application::with_platform(current_platform(false))
+}
+
+pub fn headless() -> gpui::Application {
+    gpui::Application::with_platform(current_platform(true))
+}
+
+/// Returns the default [`Platform`] for the current OS.
+pub fn current_platform(headless: bool) -> Rc<dyn Platform> {
+    #[cfg(target_os = "macos")]
+    {
+        Rc::new(gpui_macos::MacPlatform::new(headless))
+    }
+
+    #[cfg(target_os = "windows")]
+    {
+        Rc::new(
+            gpui_windows::WindowsPlatform::new(headless)
+                .expect("failed to initialize Windows platform"),
+        )
+    }
+
+    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
+    {
+        gpui_linux::current_platform(headless)
+    }
+}
+
+#[cfg(all(test, target_os = "macos"))]
+mod tests {
+    use super::*;
+    use gpui::{AppContext, Empty, VisualTestAppContext};
+    use std::cell::RefCell;
+    use std::time::Duration;
+
+    // Note: All VisualTestAppContext tests are ignored by default because they require
+    // the macOS main thread. Standard Rust tests run on worker threads, which causes
+    // SIGABRT when interacting with macOS AppKit/Cocoa APIs.
+    //
+    // To run these tests, use:
+    // cargo test -p gpui visual_test_context -- --ignored --test-threads=1
+
+    #[test]
+    #[ignore] // Requires macOS main thread
+    fn test_foreground_tasks_run_with_run_until_parked() {
+        let mut cx = VisualTestAppContext::new(current_platform(false));
+
+        let task_ran = Rc::new(RefCell::new(false));
+
+        // Spawn a foreground task via the App's spawn method
+        // This should use our TestDispatcher, not the MacDispatcher
+        {
+            let task_ran = task_ran.clone();
+            cx.update(|cx| {
+                cx.spawn(async move |_| {
+                    *task_ran.borrow_mut() = true;
+                })
+                .detach();
+            });
+        }
+
+        // The task should not have run yet
+        assert!(!*task_ran.borrow());
+
+        // Run until parked should execute the foreground task
+        cx.run_until_parked();
+
+        // Now the task should have run
+        assert!(*task_ran.borrow());
+    }
+
+    #[test]
+    #[ignore] // Requires macOS main thread
+    fn test_advance_clock_triggers_delayed_tasks() {
+        let mut cx = VisualTestAppContext::new(current_platform(false));
+
+        let task_ran = Rc::new(RefCell::new(false));
+
+        // Spawn a task that waits for a timer
+        {
+            let task_ran = task_ran.clone();
+            let executor = cx.background_executor.clone();
+            cx.update(|cx| {
+                cx.spawn(async move |_| {
+                    executor.timer(Duration::from_millis(500)).await;
+                    *task_ran.borrow_mut() = true;
+                })
+                .detach();
+            });
+        }
+
+        // Run until parked - the task should be waiting on the timer
+        cx.run_until_parked();
+        assert!(!*task_ran.borrow());
+
+        // Advance clock past the timer duration
+        cx.advance_clock(Duration::from_millis(600));
+
+        // Now the task should have completed
+        assert!(*task_ran.borrow());
+    }
+
+    #[test]
+    #[ignore] // Requires macOS main thread - window creation fails on test threads
+    fn test_window_spawn_uses_test_dispatcher() {
+        let mut cx = VisualTestAppContext::new(current_platform(false));
+
+        let task_ran = Rc::new(RefCell::new(false));
+
+        let window = cx
+            .open_offscreen_window_default(|_, cx| cx.new(|_| Empty))
+            .expect("Failed to open window");
+
+        // Spawn a task via window.spawn - this is the critical test case
+        // for tooltip behavior, as tooltips use window.spawn for delayed show
+        {
+            let task_ran = task_ran.clone();
+            cx.update_window(window.into(), |_, window, cx| {
+                window
+                    .spawn(cx, async move |_| {
+                        *task_ran.borrow_mut() = true;
+                    })
+                    .detach();
+            })
+            .ok();
+        }
+
+        // The task should not have run yet
+        assert!(!*task_ran.borrow());
+
+        // Run until parked should execute the foreground task spawned via window
+        cx.run_until_parked();
+
+        // Now the task should have run
+        assert!(*task_ran.borrow());
+    }
+}

crates/gpui_wgpu/Cargo.toml 🔗

@@ -0,0 +1,26 @@
+[package]
+name = "gpui_wgpu"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "Apache-2.0"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/gpui_wgpu.rs"
+
+[target.'cfg(not(target_os = "windows"))'.dependencies]
+gpui.workspace = true
+anyhow.workspace = true
+bytemuck = "1"
+collections.workspace = true
+etagere = "0.2"
+log.workspace = true
+parking_lot.workspace = true
+profiling.workspace = true
+raw-window-handle = "0.6"
+smol.workspace = true
+util.workspace = true
+wgpu.workspace = true

crates/gpui_wgpu/src/gpui_wgpu.rs 🔗

@@ -0,0 +1,8 @@
+#![cfg(not(target_os = "windows"))]
+mod wgpu_atlas;
+mod wgpu_context;
+mod wgpu_renderer;
+
+pub use wgpu_atlas::*;
+pub use wgpu_context::*;
+pub use wgpu_renderer::*;

crates/gpui/src/platform/wgpu/wgpu_atlas.rs → crates/gpui_wgpu/src/wgpu_atlas.rs 🔗

@@ -1,10 +1,10 @@
-use crate::{
-    AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
-    Point, Size, platform::AtlasTextureList,
-};
 use anyhow::Result;
 use collections::FxHashMap;
 use etagere::{BucketedAtlasAllocator, size2};
+use gpui::{
+    AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTextureList, AtlasTile, Bounds, DevicePixels,
+    PlatformAtlas, Point, Size,
+};
 use parking_lot::Mutex;
 use std::{borrow::Cow, ops, sync::Arc};
 
@@ -19,7 +19,7 @@ fn etagere_point_to_device(point: etagere::Point) -> Point<DevicePixels> {
     }
 }
 
-pub(crate) struct WgpuAtlas(Mutex<WgpuAtlasState>);
+pub struct WgpuAtlas(Mutex<WgpuAtlasState>);
 
 struct PendingUpload {
     id: AtlasTextureId,
@@ -40,7 +40,7 @@ pub struct WgpuTextureInfo {
 }
 
 impl WgpuAtlas {
-    pub(crate) fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>) -> Self {
+    pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>) -> Self {
         WgpuAtlas(Mutex::new(WgpuAtlasState {
             device,
             queue,

crates/gpui/src/platform/wgpu/wgpu_renderer.rs → crates/gpui_wgpu/src/wgpu_renderer.rs 🔗

@@ -1,10 +1,10 @@
-use super::{WgpuAtlas, WgpuContext};
-use crate::{
+use crate::{WgpuAtlas, WgpuContext};
+use bytemuck::{Pod, Zeroable};
+use gpui::{
     AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point,
     PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, SubpixelSprite,
     Underline, get_gamma_correction_ratios,
 };
-use bytemuck::{Pod, Zeroable};
 use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
 use std::num::NonZeroU64;
 use std::sync::Arc;

crates/gpui_windows/Cargo.toml 🔗

@@ -0,0 +1,46 @@
+[package]
+name = "gpui_windows"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "Apache-2.0"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/gpui_windows.rs"
+
+[features]
+default = ["gpui/default"]
+test-support = ["gpui/test-support"]
+screen-capture = ["gpui/screen-capture", "scap"]
+
+[dependencies]
+gpui.workspace = true
+
+[target.'cfg(target_os = "windows")'.dependencies]
+anyhow.workspace = true
+collections.workspace = true
+etagere = "0.2"
+futures.workspace = true
+image.workspace = true
+itertools.workspace = true
+log.workspace = true
+parking_lot.workspace = true
+rand.workspace = true
+raw-window-handle = "0.6"
+smallvec.workspace = true
+util.workspace = true
+uuid.workspace = true
+windows.workspace = true
+windows-core.workspace = true
+windows-numerics = "0.2"
+windows-registry = "0.5"
+
+[target.'cfg(target_os = "windows")'.dependencies.scap]
+workspace = true
+optional = true
+
+[target.'cfg(target_os = "windows")'.build-dependencies]
+windows-registry = "0.5"

crates/gpui_windows/build.rs 🔗

@@ -0,0 +1,242 @@
+#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
+
+fn main() {
+    #[cfg(target_os = "windows")]
+    {
+        // Compile HLSL shaders
+        #[cfg(not(debug_assertions))]
+        compile_shaders();
+    }
+}
+
+#[cfg(all(target_os = "windows", not(debug_assertions)))]
+mod shader_compilation {
+    use std::{
+        fs,
+        io::Write,
+        path::{Path, PathBuf},
+        process::{self, Command},
+    };
+
+    pub fn compile_shaders() {
+        let shader_path =
+            PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("src/shaders.hlsl");
+        let out_dir = std::env::var("OUT_DIR").unwrap();
+
+        println!("cargo:rerun-if-changed={}", shader_path.display());
+
+        // Check if fxc.exe is available
+        let fxc_path = find_fxc_compiler();
+
+        // Define all modules
+        let modules = [
+            "quad",
+            "shadow",
+            "path_rasterization",
+            "path_sprite",
+            "underline",
+            "monochrome_sprite",
+            "subpixel_sprite",
+            "polychrome_sprite",
+        ];
+
+        let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir);
+        if Path::new(&rust_binding_path).exists() {
+            fs::remove_file(&rust_binding_path)
+                .expect("Failed to remove existing Rust binding file");
+        }
+        for module in modules {
+            compile_shader_for_module(
+                module,
+                &out_dir,
+                &fxc_path,
+                shader_path.to_str().unwrap(),
+                &rust_binding_path,
+            );
+        }
+
+        {
+            let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
+                .join("src/color_text_raster.hlsl");
+            compile_shader_for_module(
+                "emoji_rasterization",
+                &out_dir,
+                &fxc_path,
+                shader_path.to_str().unwrap(),
+                &rust_binding_path,
+            );
+        }
+    }
+
+    /// Locate `binary` in the newest installed Windows SDK.
+    pub fn find_latest_windows_sdk_binary(
+        binary: &str,
+    ) -> Result<Option<PathBuf>, Box<dyn std::error::Error>> {
+        let key = windows_registry::LOCAL_MACHINE
+            .open("SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0")?;
+
+        let install_folder: String = key.get_string("InstallationFolder")?; // "C:\Program Files (x86)\Windows Kits\10\"
+        let install_folder_bin = Path::new(&install_folder).join("bin");
+
+        let mut versions: Vec<_> = std::fs::read_dir(&install_folder_bin)?
+            .flatten()
+            .filter(|entry| entry.path().is_dir())
+            .filter_map(|entry| entry.file_name().into_string().ok())
+            .collect();
+
+        versions.sort_by_key(|s| {
+            s.split('.')
+                .filter_map(|p| p.parse().ok())
+                .collect::<Vec<u32>>()
+        });
+
+        let arch = match std::env::consts::ARCH {
+            "x86_64" => "x64",
+            "aarch64" => "arm64",
+            _ => Err(format!(
+                "Unsupported architecture: {}",
+                std::env::consts::ARCH
+            ))?,
+        };
+
+        if let Some(highest_version) = versions.last() {
+            return Ok(Some(
+                install_folder_bin
+                    .join(highest_version)
+                    .join(arch)
+                    .join(binary),
+            ));
+        }
+
+        Ok(None)
+    }
+
+    /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
+    fn find_fxc_compiler() -> String {
+        // Check environment variable
+        if let Ok(path) = std::env::var("GPUI_FXC_PATH")
+            && Path::new(&path).exists()
+        {
+            return path;
+        }
+
+        // Try to find in PATH
+        // NOTE: This has to be `where.exe` on Windows, not `where`, it must be ended with `.exe`
+        if let Ok(output) = std::process::Command::new("where.exe")
+            .arg("fxc.exe")
+            .output()
+            && output.status.success()
+        {
+            let path = String::from_utf8_lossy(&output.stdout);
+            return path.trim().to_string();
+        }
+
+        if let Ok(Some(path)) = find_latest_windows_sdk_binary("fxc.exe") {
+            return path.to_string_lossy().into_owned();
+        }
+
+        panic!("Failed to find fxc.exe");
+    }
+
+    fn compile_shader_for_module(
+        module: &str,
+        out_dir: &str,
+        fxc_path: &str,
+        shader_path: &str,
+        rust_binding_path: &str,
+    ) {
+        // Compile vertex shader
+        let output_file = format!("{}/{}_vs.h", out_dir, module);
+        let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
+        compile_shader_impl(
+            fxc_path,
+            &format!("{module}_vertex"),
+            &output_file,
+            &const_name,
+            shader_path,
+            "vs_4_1",
+        );
+        generate_rust_binding(&const_name, &output_file, rust_binding_path);
+
+        // Compile fragment shader
+        let output_file = format!("{}/{}_ps.h", out_dir, module);
+        let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
+        compile_shader_impl(
+            fxc_path,
+            &format!("{module}_fragment"),
+            &output_file,
+            &const_name,
+            shader_path,
+            "ps_4_1",
+        );
+        generate_rust_binding(&const_name, &output_file, rust_binding_path);
+    }
+
+    fn compile_shader_impl(
+        fxc_path: &str,
+        entry_point: &str,
+        output_path: &str,
+        var_name: &str,
+        shader_path: &str,
+        target: &str,
+    ) {
+        let output = Command::new(fxc_path)
+            .args([
+                "/T",
+                target,
+                "/E",
+                entry_point,
+                "/Fh",
+                output_path,
+                "/Vn",
+                var_name,
+                "/O3",
+                shader_path,
+            ])
+            .output();
+
+        match output {
+            Ok(result) => {
+                if result.status.success() {
+                    return;
+                }
+                println!(
+                    "cargo::error=Shader compilation failed for {}:\n{}",
+                    entry_point,
+                    String::from_utf8_lossy(&result.stderr)
+                );
+                process::exit(1);
+            }
+            Err(e) => {
+                println!("cargo::error=Failed to run fxc for {}: {}", entry_point, e);
+                process::exit(1);
+            }
+        }
+    }
+
+    fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) {
+        let header_content = fs::read_to_string(head_file).expect("Failed to read header file");
+        let const_definition = {
+            let global_var_start = header_content.find("const BYTE").unwrap();
+            let global_var = &header_content[global_var_start..];
+            let equal = global_var.find('=').unwrap();
+            global_var[equal + 1..].trim()
+        };
+        let rust_binding = format!(
+            "const {}: &[u8] = &{}\n",
+            const_name,
+            const_definition.replace('{', "[").replace('}', "]")
+        );
+        let mut options = fs::OpenOptions::new()
+            .create(true)
+            .append(true)
+            .open(output_path)
+            .expect("Failed to open Rust binding file");
+        options
+            .write_all(rust_binding.as_bytes())
+            .expect("Failed to write Rust binding file");
+    }
+}
+
+#[cfg(all(target_os = "windows", not(debug_assertions)))]
+use shader_compilation::compile_shaders;

crates/gpui/src/platform/windows/clipboard.rs → crates/gpui_windows/src/clipboard.rs 🔗

@@ -18,7 +18,7 @@ use windows::Win32::{
 };
 use windows_core::PCWSTR;
 
-use crate::{
+use gpui::{
     ClipboardEntry, ClipboardItem, ClipboardString, ExternalPaths, Image, ImageFormat, hash,
 };
 
@@ -229,7 +229,8 @@ fn write_image_to_clipboard(item: &Image) -> Result<()> {
 }
 
 fn convert_image_to_png_format(bytes: &[u8], image_format: ImageFormat) -> Result<Vec<u8>> {
-    let image = image::load_from_memory_with_format(bytes, image_format.into())?;
+    let image =
+        image::load_from_memory_with_format(bytes, gpui_image_format_to_image(image_format))?;
     let mut output_buf = Vec::new();
     image.write_to(
         &mut std::io::Cursor::new(&mut output_buf),
@@ -440,17 +441,15 @@ where
     Some(result)
 }
 
-impl From<ImageFormat> for image::ImageFormat {
-    fn from(value: ImageFormat) -> Self {
-        match value {
-            ImageFormat::Png => image::ImageFormat::Png,
-            ImageFormat::Jpeg => image::ImageFormat::Jpeg,
-            ImageFormat::Webp => image::ImageFormat::WebP,
-            ImageFormat::Gif => image::ImageFormat::Gif,
-            // TODO: ImageFormat::Svg
-            ImageFormat::Bmp => image::ImageFormat::Bmp,
-            ImageFormat::Tiff => image::ImageFormat::Tiff,
-            _ => unreachable!(),
-        }
+fn gpui_image_format_to_image(value: ImageFormat) -> image::ImageFormat {
+    match value {
+        ImageFormat::Png => image::ImageFormat::Png,
+        ImageFormat::Jpeg => image::ImageFormat::Jpeg,
+        ImageFormat::Webp => image::ImageFormat::WebP,
+        ImageFormat::Gif => image::ImageFormat::Gif,
+        // TODO: ImageFormat::Svg
+        ImageFormat::Bmp => image::ImageFormat::Bmp,
+        ImageFormat::Tiff => image::ImageFormat::Tiff,
+        _ => unreachable!(),
     }
 }

crates/gpui/src/platform/windows/destination_list.rs → crates/gpui_windows/src/destination_list.rs 🔗

@@ -20,7 +20,7 @@ use windows::{
     core::{GUID, HSTRING, Interface},
 };
 
-use crate::{Action, MenuItem, SharedString};
+use gpui::{Action, MenuItem, SharedString};
 
 pub(crate) struct JumpList {
     pub(crate) dock_menus: Vec<DockMenuItem>,

crates/gpui/src/platform/windows/direct_write.rs → crates/gpui_windows/src/direct_write.rs 🔗

@@ -24,6 +24,7 @@ use windows::{
 use windows_numerics::Vector2;
 
 use crate::*;
+use gpui::*;
 
 #[derive(Debug)]
 struct FontInfo {
@@ -468,7 +469,7 @@ impl DirectWriteState {
         let family = if family == SYSTEM_UI_FONT_NAME {
             system_ui_font_name
         } else {
-            font_name_with_fallbacks_shared(&family, &system_ui_font_name)
+            gpui::font_name_with_fallbacks_shared(&family, &system_ui_font_name)
         };
         let fontset = unsafe { collection.GetFontSet().log_err()? };
         let font_family_h = HSTRING::from(family.as_str());
@@ -476,9 +477,9 @@ impl DirectWriteState {
             fontset
                 .GetMatchingFonts(
                     &font_family_h,
-                    weight.into(),
+                    font_weight_to_dwrite(weight),
                     DWRITE_FONT_STRETCH_NORMAL,
-                    style.into(),
+                    font_style_to_dwrite(style),
                 )
                 .log_err()?
         };
@@ -542,7 +543,7 @@ impl DirectWriteState {
                         font_info.font_face.GetWeight(),
                         font_info.font_face.GetStyle(),
                         DWRITE_FONT_STRETCH_NORMAL,
-                        font_size.0,
+                        font_size.as_f32(),
                         &components.locale,
                     )?
                     .cast()?;
@@ -569,7 +570,7 @@ impl DirectWriteState {
                 layout
             };
 
-            let (mut ascent, mut descent) = {
+            let (ascent, descent) = {
                 let mut first_metrics = [DWRITE_LINE_METRICS::default(); 4];
                 let mut line_count = 0u32;
                 text_layout.GetLineMetrics(Some(&mut first_metrics), &mut line_count)?;
@@ -594,9 +595,9 @@ impl DirectWriteState {
                 text_layout.SetFontCollection(collection, text_range)?;
                 text_layout.SetFontFamilyName(&font_info.font_family_h, text_range)?;
                 let font_size = if break_ligatures {
-                    font_size.0.next_up()
+                    font_size.as_f32().next_up()
                 } else {
-                    font_size.0
+                    font_size.as_f32()
                 };
                 text_layout.SetFontSize(font_size, text_range)?;
                 text_layout.SetFontStyle(font_info.font_face.GetStyle(), text_range)?;
@@ -673,7 +674,7 @@ impl DirectWriteState {
         let offset = [DWRITE_GLYPH_OFFSET::default()];
         let glyph_run = DWRITE_GLYPH_RUN {
             fontFace: ManuallyDrop::new(Some(unsafe { std::ptr::read(&***font.font_face) })),
-            fontEmSize: params.font_size.0,
+            fontEmSize: params.font_size.as_f32(),
             glyphCount: 1,
             glyphIndices: glyph_id.as_ptr(),
             glyphAdvances: advance.as_ptr(),
@@ -691,14 +692,15 @@ impl DirectWriteState {
         };
         let baseline_origin_x =
             params.subpixel_variant.x as f32 / SUBPIXEL_VARIANTS_X as f32 / params.scale_factor;
-        let baseline_origin_y =
-            params.subpixel_variant.y as f32 / SUBPIXEL_VARIANTS_Y as f32 / params.scale_factor;
+        let baseline_origin_y = params.subpixel_variant.y as f32
+            / gpui::SUBPIXEL_VARIANTS_Y as f32
+            / params.scale_factor;
 
         let mut rendering_mode = DWRITE_RENDERING_MODE1::default();
         let mut grid_fit_mode = DWRITE_GRID_FIT_MODE::default();
         unsafe {
             font.font_face.GetRecommendedRenderingMode(
-                params.font_size.0,
+                params.font_size.as_f32(),
                 // Using 96 as scale is applied by the transform
                 96.0,
                 96.0,
@@ -904,7 +906,7 @@ impl DirectWriteState {
         }];
         let glyph_run = DWRITE_GLYPH_RUN {
             fontFace: ManuallyDrop::new(Some(unsafe { std::ptr::read(&***font.font_face) })),
-            fontEmSize: params.font_size.0,
+            fontEmSize: params.font_size.as_f32(),
             glyphCount: 1,
             glyphIndices: glyph_id.as_ptr(),
             glyphAdvances: advance.as_ptr(),
@@ -1525,7 +1527,7 @@ impl IDWriteTextRenderer_Impl for TextRenderer_Impl {
         let cluster_map =
             unsafe { std::slice::from_raw_parts(desc.clusterMap, desc.stringLength as usize) };
 
-        let mut cluster_analyzer = ClusterAnalyzer::new(cluster_map, glyph_count);
+        let cluster_analyzer = ClusterAnalyzer::new(cluster_map, glyph_count);
         let mut utf16_idx = desc.textPosition as usize;
         let mut glyph_idx = 0;
         let mut glyphs = Vec::with_capacity(glyph_count);
@@ -1642,37 +1644,29 @@ impl<'a> StringIndexConverter<'a> {
     }
 }
 
-impl Into<DWRITE_FONT_STYLE> for FontStyle {
-    fn into(self) -> DWRITE_FONT_STYLE {
-        match self {
-            FontStyle::Normal => DWRITE_FONT_STYLE_NORMAL,
-            FontStyle::Italic => DWRITE_FONT_STYLE_ITALIC,
-            FontStyle::Oblique => DWRITE_FONT_STYLE_OBLIQUE,
-        }
+fn font_style_to_dwrite(style: FontStyle) -> DWRITE_FONT_STYLE {
+    match style {
+        FontStyle::Normal => DWRITE_FONT_STYLE_NORMAL,
+        FontStyle::Italic => DWRITE_FONT_STYLE_ITALIC,
+        FontStyle::Oblique => DWRITE_FONT_STYLE_OBLIQUE,
     }
 }
 
-impl From<DWRITE_FONT_STYLE> for FontStyle {
-    fn from(value: DWRITE_FONT_STYLE) -> Self {
-        match value.0 {
-            0 => FontStyle::Normal,
-            1 => FontStyle::Italic,
-            2 => FontStyle::Oblique,
-            _ => unreachable!(),
-        }
+fn font_style_from_dwrite(value: DWRITE_FONT_STYLE) -> FontStyle {
+    match value.0 {
+        0 => FontStyle::Normal,
+        1 => FontStyle::Italic,
+        2 => FontStyle::Oblique,
+        _ => unreachable!(),
     }
 }
 
-impl Into<DWRITE_FONT_WEIGHT> for FontWeight {
-    fn into(self) -> DWRITE_FONT_WEIGHT {
-        DWRITE_FONT_WEIGHT(self.0 as i32)
-    }
+fn font_weight_to_dwrite(weight: FontWeight) -> DWRITE_FONT_WEIGHT {
+    DWRITE_FONT_WEIGHT(weight.0 as i32)
 }
 
-impl From<DWRITE_FONT_WEIGHT> for FontWeight {
-    fn from(value: DWRITE_FONT_WEIGHT) -> Self {
-        FontWeight(value.0 as f32)
-    }
+fn font_weight_from_dwrite(value: DWRITE_FONT_WEIGHT) -> FontWeight {
+    FontWeight(value.0 as f32)
 }
 
 fn get_font_names_from_collection(
@@ -1707,8 +1701,8 @@ fn font_face_to_font(font_face: &IDWriteFontFace3, locale: &HSTRING) -> Option<F
     Some(Font {
         family: family_name.into(),
         features: FontFeatures::default(),
-        weight: weight.into(),
-        style: style.into(),
+        weight: font_weight_from_dwrite(weight),
+        style: font_style_from_dwrite(style),
         fallbacks: None,
     })
 }
@@ -1885,7 +1879,7 @@ const DEFAULT_LOCALE_NAME: PCWSTR = windows::core::w!("en-US");
 
 #[cfg(test)]
 mod tests {
-    use crate::platform::windows::direct_write::ClusterAnalyzer;
+    use crate::direct_write::ClusterAnalyzer;
 
     #[test]
     fn test_cluster_map() {

crates/gpui/src/platform/windows/directx_atlas.rs → crates/gpui_windows/src/directx_atlas.rs 🔗

@@ -9,9 +9,9 @@ use windows::Win32::Graphics::{
     Dxgi::Common::*,
 };
 
-use crate::{
-    AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
-    Point, Size, platform::AtlasTextureList,
+use gpui::{
+    AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTextureList, AtlasTile, Bounds, DevicePixels,
+    PlatformAtlas, Point, Size,
 };
 
 pub(crate) struct DirectXAtlas(Mutex<DirectXAtlasState>);
@@ -230,7 +230,7 @@ impl DirectXAtlasState {
                 kind,
             },
             bytes_per_pixel,
-            allocator: etagere::BucketedAtlasAllocator::new(size.into()),
+            allocator: etagere::BucketedAtlasAllocator::new(device_size_to_etagere(size)),
             texture,
             view,
             live_atlas_keys: 0,
@@ -246,13 +246,13 @@ impl DirectXAtlasState {
 
     fn texture(&self, id: AtlasTextureId) -> &DirectXAtlasTexture {
         match id.kind {
-            crate::AtlasTextureKind::Monochrome => &self.monochrome_textures[id.index as usize]
+            AtlasTextureKind::Monochrome => &self.monochrome_textures[id.index as usize]
                 .as_ref()
                 .unwrap(),
-            crate::AtlasTextureKind::Polychrome => &self.polychrome_textures[id.index as usize]
+            AtlasTextureKind::Polychrome => &self.polychrome_textures[id.index as usize]
                 .as_ref()
                 .unwrap(),
-            crate::AtlasTextureKind::Subpixel => {
+            AtlasTextureKind::Subpixel => {
                 &self.subpixel_textures[id.index as usize].as_ref().unwrap()
             }
         }
@@ -261,12 +261,12 @@ impl DirectXAtlasState {
 
 impl DirectXAtlasTexture {
     fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
-        let allocation = self.allocator.allocate(size.into())?;
+        let allocation = self.allocator.allocate(device_size_to_etagere(size))?;
         let tile = AtlasTile {
             texture_id: self.id,
             tile_id: allocation.id.into(),
             bounds: Bounds {
-                origin: allocation.rectangle.min.into(),
+                origin: etagere_point_to_device(allocation.rectangle.min),
                 size,
             },
             padding: 0,
@@ -309,17 +309,13 @@ impl DirectXAtlasTexture {
     }
 }
 
-impl From<Size<DevicePixels>> for etagere::Size {
-    fn from(size: Size<DevicePixels>) -> Self {
-        etagere::Size::new(size.width.into(), size.height.into())
-    }
+fn device_size_to_etagere(size: Size<DevicePixels>) -> etagere::Size {
+    etagere::Size::new(size.width.into(), size.height.into())
 }
 
-impl From<etagere::Point> for Point<DevicePixels> {
-    fn from(value: etagere::Point) -> Self {
-        Point {
-            x: DevicePixels::from(value.x),
-            y: DevicePixels::from(value.y),
-        }
+fn etagere_point_to_device(value: etagere::Point) -> Point<DevicePixels> {
+    Point {
+        x: DevicePixels::from(value.x),
+        y: DevicePixels::from(value.y),
     }
 }

crates/gpui/src/platform/windows/directx_renderer.rs → crates/gpui_windows/src/directx_renderer.rs 🔗

@@ -19,12 +19,9 @@ use windows::{
     core::Interface,
 };
 
-use crate::{
-    platform::windows::directx_renderer::shader_resources::{
-        RawShaderBytes, ShaderModule, ShaderTarget,
-    },
-    *,
-};
+use crate::directx_renderer::shader_resources::{RawShaderBytes, ShaderModule, ShaderTarget};
+use crate::*;
+use gpui::*;
 
 pub(crate) const DISABLE_DIRECT_COMPOSITION: &str = "GPUI_DISABLE_DIRECT_COMPOSITION";
 const RENDER_TARGET_FORMAT: DXGI_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM;
@@ -741,7 +738,7 @@ impl DirectXRenderer {
             let render_params: IDWriteRenderingParams1 =
                 factory.CreateRenderingParams().unwrap().cast().unwrap();
             FontInfo {
-                gamma_ratios: get_gamma_correction_ratios(render_params.GetGamma()),
+                gamma_ratios: gpui::get_gamma_correction_ratios(render_params.GetGamma()),
                 grayscale_enhanced_contrast: render_params.GetGrayscaleEnhancedContrast(),
                 subpixel_enhanced_contrast: render_params.GetEnhancedContrast(),
             }

crates/gpui/src/platform/windows/dispatcher.rs → crates/gpui_windows/src/dispatcher.rs 🔗

@@ -21,10 +21,10 @@ use windows::{
     },
 };
 
-use crate::{
-    GLOBAL_THREAD_TIMINGS, HWND, PlatformDispatcher, Priority, PriorityQueueSender,
-    RunnableVariant, SafeHwnd, THREAD_TIMINGS, TaskTiming, ThreadTaskTimings, TimerResolutionGuard,
-    WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD, profiler,
+use crate::{HWND, SafeHwnd, WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD};
+use gpui::{
+    GLOBAL_THREAD_TIMINGS, PlatformDispatcher, Priority, PriorityQueueSender, RunnableVariant,
+    THREAD_TIMINGS, TaskTiming, ThreadTaskTimings, TimerResolutionGuard,
 };
 
 pub(crate) struct WindowsDispatcher {
@@ -96,14 +96,14 @@ impl WindowsDispatcher {
             start,
             end: None,
         };
-        profiler::add_task_timing(timing);
+        gpui::profiler::add_task_timing(timing);
 
         runnable.run();
 
         let end = Instant::now();
         timing.end = Some(end);
 
-        profiler::add_task_timing(timing);
+        gpui::profiler::add_task_timing(timing);
     }
 }
 
@@ -113,7 +113,7 @@ impl PlatformDispatcher for WindowsDispatcher {
         ThreadTaskTimings::convert(&global_thread_timings)
     }
 
-    fn get_current_thread_timings(&self) -> crate::ThreadTaskTimings {
+    fn get_current_thread_timings(&self) -> gpui::ThreadTaskTimings {
         THREAD_TIMINGS.with(|timings| {
             let timings = timings.lock();
             let thread_name = timings.thread_name.clone();
@@ -126,7 +126,7 @@ impl PlatformDispatcher for WindowsDispatcher {
             vec.extend_from_slice(s1);
             vec.extend_from_slice(s2);
 
-            crate::ThreadTaskTimings {
+            gpui::ThreadTaskTimings {
                 thread_name,
                 thread_id: std::thread::current().id(),
                 timings: vec,
@@ -207,10 +207,8 @@ impl PlatformDispatcher for WindowsDispatcher {
         unsafe {
             timeBeginPeriod(1);
         }
-        TimerResolutionGuard {
-            cleanup: Some(Box::new(|| unsafe {
-                timeEndPeriod(1);
-            })),
-        }
+        util::defer(Box::new(|| unsafe {
+            timeEndPeriod(1);
+        }))
     }
 }

crates/gpui/src/platform/windows/display.rs → crates/gpui_windows/src/display.rs 🔗

@@ -15,7 +15,8 @@ use windows::{
     core::*,
 };
 
-use crate::{Bounds, DevicePixels, DisplayId, Pixels, PlatformDisplay, logical_point, point, size};
+use crate::logical_point;
+use gpui::{Bounds, DevicePixels, DisplayId, Pixels, PlatformDisplay, point, size};
 
 #[derive(Debug, Clone, Copy)]
 pub(crate) struct WindowsDisplay {
@@ -34,7 +35,9 @@ unsafe impl Sync for WindowsDisplay {}
 
 impl WindowsDisplay {
     pub(crate) fn new(display_id: DisplayId) -> Option<Self> {
-        let screen = available_monitors().into_iter().nth(display_id.0 as _)?;
+        let screen = available_monitors()
+            .into_iter()
+            .nth(u32::from(display_id) as _)?;
         let info = get_monitor_info(screen).log_err()?;
         let monitor_size = info.monitorInfo.rcMonitor;
         let work_area = info.monitorInfo.rcWork;
@@ -63,7 +66,7 @@ impl WindowsDisplay {
                     (work_area.right - work_area.left) as f32 / scale_factor,
                     (work_area.bottom - work_area.top) as f32 / scale_factor,
                 )
-                .map(crate::px),
+                .map(gpui::px),
             },
             physical_bounds: Bounds {
                 origin: point(monitor_size.left.into(), monitor_size.top.into()),
@@ -90,7 +93,7 @@ impl WindowsDisplay {
 
         Ok(WindowsDisplay {
             handle: monitor,
-            display_id: DisplayId(display_id as _),
+            display_id: DisplayId::new(display_id as _),
             scale_factor,
             bounds: Bounds {
                 origin: logical_point(
@@ -106,7 +109,7 @@ impl WindowsDisplay {
                     (work_area.right - work_area.left) as f32 / scale_factor,
                     (work_area.bottom - work_area.top) as f32 / scale_factor,
                 )
-                .map(crate::px),
+                .map(gpui::px),
             },
             physical_bounds: Bounds {
                 origin: point(monitor_size.left.into(), monitor_size.top.into()),
@@ -145,7 +148,7 @@ impl WindowsDisplay {
                     (work_area.right - work_area.left) as f32 / scale_factor,
                     (work_area.bottom - work_area.top) as f32 / scale_factor,
                 )
-                .map(crate::px),
+                .map(gpui::px),
             },
             physical_bounds: Bounds {
                 origin: point(monitor_size.left.into(), monitor_size.top.into()),
@@ -173,8 +176,8 @@ impl WindowsDisplay {
     pub fn check_given_bounds(&self, bounds: Bounds<Pixels>) -> bool {
         let center = bounds.center();
         let center = POINT {
-            x: (center.x.0 * self.scale_factor) as i32,
-            y: (center.y.0 * self.scale_factor) as i32,
+            x: (center.x.as_f32() * self.scale_factor) as i32,
+            y: (center.y.as_f32() * self.scale_factor) as i32,
         };
         let monitor = unsafe { MonitorFromPoint(center, MONITOR_DEFAULTTONULL) };
         if monitor.is_invalid() {
@@ -193,7 +196,7 @@ impl WindowsDisplay {
             .enumerate()
             .filter_map(|(id, handle)| {
                 Some(Rc::new(
-                    WindowsDisplay::new_with_handle_and_id(handle, DisplayId(id as _)).ok()?,
+                    WindowsDisplay::new_with_handle_and_id(handle, DisplayId::new(id as _)).ok()?,
                 ) as Rc<dyn PlatformDisplay>)
             })
             .collect()

crates/gpui/src/platform/windows/events.rs → crates/gpui_windows/src/events.rs 🔗

@@ -18,6 +18,7 @@ use windows::{
 };
 
 use crate::*;
+use gpui::*;
 
 pub(crate) const WM_GPUI_CURSOR_STYLE_CHANGED: u32 = WM_USER + 1;
 pub(crate) const WM_GPUI_CLOSE_ONE_WINDOW: u32 = WM_USER + 2;
@@ -127,13 +128,13 @@ impl WindowsWindowInner {
         );
         self.state.origin.set(origin);
         let size = self.state.logical_size.get();
-        let center_x = origin.x.0 + size.width.0 / 2.;
-        let center_y = origin.y.0 + size.height.0 / 2.;
+        let center_x = origin.x.as_f32() + size.width.as_f32() / 2.;
+        let center_y = origin.y.as_f32() + size.height.as_f32() / 2.;
         let monitor_bounds = self.state.display.get().bounds();
-        if center_x < monitor_bounds.left().0
-            || center_x > monitor_bounds.right().0
-            || center_y < monitor_bounds.top().0
-            || center_y > monitor_bounds.bottom().0
+        if center_x < monitor_bounds.left().as_f32()
+            || center_x > monitor_bounds.right().as_f32()
+            || center_y < monitor_bounds.top().as_f32()
+            || center_y > monitor_bounds.bottom().as_f32()
         {
             // center of the window may have moved to another monitor
             let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
@@ -160,10 +161,10 @@ impl WindowsWindowInner {
 
         unsafe {
             let minmax_info = &mut *(lparam.0 as *mut MINMAXINFO);
-            minmax_info.ptMinTrackSize.x =
-                min_size.width.scale(scale_factor).0 as i32 + boarder_offset.width_offset.get();
-            minmax_info.ptMinTrackSize.y =
-                min_size.height.scale(scale_factor).0 as i32 + boarder_offset.height_offset.get();
+            minmax_info.ptMinTrackSize.x = min_size.width.scale(scale_factor).as_f32() as i32
+                + boarder_offset.width_offset.get();
+            minmax_info.ptMinTrackSize.y = min_size.height.scale(scale_factor).as_f32() as i32
+                + boarder_offset.height_offset.get();
         }
         Some(0)
     }
@@ -577,9 +578,9 @@ impl WindowsWindowInner {
             let caret_position = input_handler.bounds_for_range(caret_range.range)?;
             Some(POINT {
                 // logical to physical
-                x: (caret_position.origin.x.0 * scale_factor) as i32,
-                y: (caret_position.origin.y.0 * scale_factor) as i32
-                    + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
+                x: (caret_position.origin.x.as_f32() * scale_factor) as i32,
+                y: (caret_position.origin.y.as_f32() * scale_factor) as i32
+                    + ((caret_position.size.height.as_f32() * scale_factor) as i32 / 2),
             })
         })
     }

crates/gpui/src/platform/windows.rs → crates/gpui_windows/src/gpui_windows.rs 🔗

@@ -1,3 +1,5 @@
+#![cfg(target_os = "windows")]
+
 mod clipboard;
 mod destination_list;
 mod direct_write;
@@ -32,9 +34,6 @@ pub(crate) use vsync::*;
 pub(crate) use window::*;
 pub(crate) use wrapper::*;
 
-pub(crate) use windows::Win32::Foundation::HWND;
+pub use platform::WindowsPlatform;
 
-#[cfg(feature = "screen-capture")]
-pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame;
-#[cfg(not(feature = "screen-capture"))]
-pub(crate) type PlatformScreenCaptureFrame = ();
+pub(crate) use windows::Win32::Foundation::HWND;

crates/gpui/src/platform/windows/keyboard.rs → crates/gpui_windows/src/keyboard.rs 🔗

@@ -10,7 +10,7 @@ use windows::Win32::UI::{
     WindowsAndMessaging::KL_NAMELENGTH,
 };
 
-use crate::{
+use gpui::{
     KeybindingKeystroke, Keystroke, Modifiers, PlatformKeyboardLayout, PlatformKeyboardMapper,
 };
 
@@ -316,7 +316,8 @@ const CANDIDATE_VKEYS: &[VIRTUAL_KEY] = &[
 
 #[cfg(test)]
 mod tests {
-    use crate::{Keystroke, Modifiers, PlatformKeyboardMapper, WindowsKeyboardMapper};
+    use crate::WindowsKeyboardMapper;
+    use gpui::{Keystroke, Modifiers, PlatformKeyboardMapper};
 
     #[test]
     fn test_keyboard_mapper() {

crates/gpui/src/platform/windows/platform.rs → crates/gpui_windows/src/platform.rs 🔗

@@ -28,8 +28,9 @@ use windows::{
 };
 
 use crate::*;
+use gpui::*;
 
-pub(crate) struct WindowsPlatform {
+pub struct WindowsPlatform {
     inner: Rc<WindowsPlatformInner>,
     raw_window_handles: Arc<RwLock<SmallVec<[SafeHwnd; 4]>>>,
     // The below members will never change throughout the entire lifecycle of the app.
@@ -93,7 +94,7 @@ impl WindowsPlatformState {
 }
 
 impl WindowsPlatform {
-    pub(crate) fn new(headless: bool) -> Result<Self> {
+    pub fn new(headless: bool) -> Result<Self> {
         unsafe {
             OleInitialize(None).context("unable to initialize Windows OLE")?;
         }
@@ -111,7 +112,7 @@ impl WindowsPlatform {
         } else {
             (
                 None,
-                Arc::new(crate::NoopTextSystem::new()) as Arc<dyn PlatformTextSystem>,
+                Arc::new(gpui::NoopTextSystem::new()) as Arc<dyn PlatformTextSystem>,
                 None,
             )
         };
@@ -195,7 +196,7 @@ impl WindowsPlatform {
         })
     }
 
-    pub fn window_from_hwnd(&self, hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {
+    pub(crate) fn window_from_hwnd(&self, hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {
         self.raw_window_handles
             .read()
             .iter()
@@ -452,7 +453,7 @@ impl Platform for WindowsPlatform {
                     reason = "We are restarting ourselves, using std command thus is fine"
                 )]
                 let restart_process =
-                    util::command::new_std_command(util::shell::get_windows_system_shell())
+                    ::util::command::new_std_command(::util::shell::get_windows_system_shell())
                         .arg("-command")
                         .arg(script)
                         .spawn();
@@ -496,7 +497,7 @@ impl Platform for WindowsPlatform {
     fn screen_capture_sources(
         &self,
     ) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
-        crate::platform::scap_screen_capture::scap_screen_sources(&self.foreground_executor)
+        gpui::scap_screen_capture::scap_screen_sources(&self.foreground_executor)
     }
 
     fn active_window(&self) -> Option<AnyWindowHandle> {
@@ -683,7 +684,7 @@ impl Platform for WindowsPlatform {
     }
 
     fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
-        let mut password = password.to_vec();
+        let password = password.to_vec();
         let mut username = username.encode_utf16().chain(Some(0)).collect_vec();
         let mut target_name = windows_credentials_target_name(url)
             .encode_utf16()
@@ -714,7 +715,7 @@ impl Platform for WindowsPlatform {
     }
 
     fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
-        let mut target_name = windows_credentials_target_name(url)
+        let target_name = windows_credentials_target_name(url)
             .encode_utf16()
             .chain(Some(0))
             .collect_vec();
@@ -756,7 +757,7 @@ impl Platform for WindowsPlatform {
     }
 
     fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
-        let mut target_name = windows_credentials_target_name(url)
+        let target_name = windows_credentials_target_name(url)
             .encode_utf16()
             .chain(Some(0))
             .collect_vec();
@@ -1340,7 +1341,8 @@ unsafe extern "system" fn window_procedure(
 
 #[cfg(test)]
 mod tests {
-    use crate::{ClipboardItem, read_from_clipboard, write_to_clipboard};
+    use crate::{read_from_clipboard, write_to_clipboard};
+    use gpui::ClipboardItem;
 
     #[test]
     fn test_clipboard() {

crates/gpui/src/platform/windows/util.rs → crates/gpui_windows/src/util.rs 🔗

@@ -11,10 +11,11 @@ use windows::{
         Foundation::*, Graphics::Dwm::*, System::LibraryLoader::LoadLibraryA,
         UI::WindowsAndMessaging::*,
     },
-    core::{BOOL, HSTRING, PCSTR},
+    core::{BOOL, PCSTR},
 };
 
 use crate::*;
+use gpui::*;
 
 pub(crate) trait HiLoWord {
     fn hiword(&self) -> u16;
@@ -174,17 +175,6 @@ fn is_color_light(color: &Color) -> bool {
     ((5 * color.G as u32) + (2 * color.R as u32) + color.B as u32) > (8 * 128)
 }
 
-pub(crate) fn show_error(title: &str, content: String) {
-    let _ = unsafe {
-        MessageBoxW(
-            None,
-            &HSTRING::from(content),
-            &HSTRING::from(title),
-            MB_ICONERROR | MB_SYSTEMMODAL,
-        )
-    };
-}
-
 pub(crate) fn with_dll_library<R, F>(dll_name: PCSTR, f: F) -> Result<R>
 where
     F: FnOnce(HMODULE) -> Result<R>,

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

@@ -27,6 +27,7 @@ use windows::{
 };
 
 use crate::*;
+use gpui::*;
 
 pub(crate) struct WindowsWindow(pub Rc<WindowsWindowInner>);
 
@@ -342,7 +343,7 @@ impl WindowsWindowInner {
 #[derive(Default)]
 pub(crate) struct Callbacks {
     pub(crate) request_frame: Cell<Option<Box<dyn FnMut(RequestFrameOptions)>>>,
-    pub(crate) input: Cell<Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>>,
+    pub(crate) input: Cell<Option<Box<dyn FnMut(PlatformInput) -> DispatchEventResult>>>,
     pub(crate) active_status_change: Cell<Option<Box<dyn FnMut(bool)>>>,
     pub(crate) hovered_status_change: Cell<Option<Box<dyn FnMut(bool)>>>,
     pub(crate) resize: Cell<Option<Box<dyn FnMut(Size<Pixels>, f32)>>>,
@@ -576,8 +577,7 @@ impl PlatformWindow for WindowsWindow {
 
     fn resize(&mut self, size: Size<Pixels>) {
         let hwnd = self.0.hwnd;
-        let bounds =
-            crate::bounds(self.bounds().origin, size).to_device_pixels(self.scale_factor());
+        let bounds = gpui::bounds(self.bounds().origin, size).to_device_pixels(self.scale_factor());
         let rect = calculate_window_rect(bounds, &self.state.border_offset);
 
         self.0
@@ -663,15 +663,15 @@ impl PlatformWindow for WindowsWindow {
                     let title;
                     let main_icon;
                     match level {
-                        crate::PromptLevel::Info => {
+                        PromptLevel::Info => {
                             title = windows::core::w!("Info");
                             main_icon = TD_INFORMATION_ICON;
                         }
-                        crate::PromptLevel::Warning => {
+                        PromptLevel::Warning => {
                             title = windows::core::w!("Warning");
                             main_icon = TD_WARNING_ICON;
                         }
-                        crate::PromptLevel::Critical => {
+                        PromptLevel::Critical => {
                             title = windows::core::w!("Critical");
                             main_icon = TD_ERROR_ICON;
                         }
@@ -935,9 +935,9 @@ impl PlatformWindow for WindowsWindow {
     fn update_ime_position(&self, bounds: Bounds<Pixels>) {
         let scale_factor = self.state.scale_factor.get();
         let caret_position = POINT {
-            x: (bounds.origin.x.0 * scale_factor) as i32,
-            y: (bounds.origin.y.0 * scale_factor) as i32
-                + ((bounds.size.height.0 * scale_factor) as i32 / 2),
+            x: (bounds.origin.x.as_f32() * scale_factor) as i32,
+            y: (bounds.origin.y.as_f32() * scale_factor) as i32
+                + ((bounds.size.height.as_f32() * scale_factor) as i32 / 2),
         };
 
         self.0.update_ime_position(self.0.hwnd, caret_position);
@@ -1476,12 +1476,12 @@ fn set_non_rude_hwnd(hwnd: HWND, non_rude: bool) {
 #[cfg(test)]
 mod tests {
     use super::ClickState;
-    use crate::{DevicePixels, MouseButton, point};
+    use gpui::{DevicePixels, MouseButton, point};
     use std::time::Duration;
 
     #[test]
     fn test_double_click_interval() {
-        let mut state = ClickState::new();
+        let state = ClickState::new();
         assert_eq!(
             state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
             1
@@ -1509,7 +1509,7 @@ mod tests {
 
     #[test]
     fn test_double_click_spatial_tolerance() {
-        let mut state = ClickState::new();
+        let state = ClickState::new();
         assert_eq!(
             state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))),
             1

crates/livekit_client/Cargo.toml 🔗

@@ -26,7 +26,7 @@ audio.workspace = true
 collections.workspace = true
 cpal.workspace = true
 futures.workspace = true
-gpui = { workspace = true, features = ["screen-capture", "x11", "wayland", "windows-manifest"] }
+gpui = { workspace = true, features = ["screen-capture", "x11", "wayland"] }
 gpui_tokio.workspace = true
 http_client_tls.workspace = true
 image.workspace = true
@@ -63,6 +63,7 @@ objc.workspace = true
 [dev-dependencies]
 collections = { workspace = true, features = ["test-support"] }
 gpui = { workspace = true, features = ["test-support"] }
+gpui_platform.workspace = true
 sha2.workspace = true
 simplelog.workspace = true
 

crates/livekit_client/examples/test_app.rs 🔗

@@ -23,7 +23,7 @@ actions!(livekit_client, [Quit]);
 fn main() {
     SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
 
-    gpui::Application::new().run(|cx| {
+    gpui_platform::application().run(|cx| {
         #[cfg(any(test, feature = "test-support"))]
         println!("USING TEST LIVEKIT");
 

crates/markdown/Cargo.toml 🔗

@@ -38,6 +38,7 @@ assets.workspace = true
 env_logger.workspace = true
 fs = {workspace = true, features = ["test-support"]}
 gpui = { workspace = true, features = ["test-support"] }
+gpui_platform.workspace = true
 language = { workspace = true, features = ["test-support"] }
 languages = { workspace = true, features = ["load-grammars"] }
 node_runtime.workspace = true

crates/markdown/examples/markdown.rs 🔗

@@ -1,5 +1,5 @@
 use assets::Assets;
-use gpui::{Application, Entity, KeyBinding, StyleRefinement, WindowOptions, prelude::*, rgb};
+use gpui::{Entity, KeyBinding, StyleRefinement, WindowOptions, prelude::*, rgb};
 use language::LanguageRegistry;
 use markdown::{Markdown, MarkdownElement, MarkdownStyle};
 use node_runtime::NodeRuntime;
@@ -35,7 +35,7 @@ Remember, markdown processors may have slight differences and extensions, so alw
 
 pub fn main() {
     env_logger::init();
-    Application::new().with_assets(Assets).run(|cx| {
+    gpui_platform::application().with_assets(Assets).run(|cx| {
         let store = SettingsStore::test(cx);
         cx.set_global(store);
         cx.bind_keys([KeyBinding::new("cmd-c", markdown::Copy, None)]);

crates/markdown/examples/markdown_as_child.rs 🔗

@@ -1,5 +1,5 @@
 use assets::Assets;
-use gpui::{Application, Entity, KeyBinding, Length, StyleRefinement, WindowOptions, rgb};
+use gpui::{Entity, KeyBinding, Length, StyleRefinement, WindowOptions, rgb};
 use language::LanguageRegistry;
 use markdown::{Markdown, MarkdownElement, MarkdownStyle};
 use node_runtime::NodeRuntime;
@@ -19,7 +19,7 @@ wow so cool
 pub fn main() {
     env_logger::init();
 
-    Application::new().with_assets(Assets).run(|cx| {
+    gpui_platform::application().with_assets(Assets).run(|cx| {
         let store = SettingsStore::test(cx);
         cx.set_global(store);
         cx.bind_keys([KeyBinding::new("cmd-c", markdown::Copy, None)]);

crates/project_benchmarks/Cargo.toml 🔗

@@ -10,7 +10,8 @@ askpass.workspace = true
 clap.workspace = true
 client.workspace = true
 futures.workspace = true
-gpui = { workspace = true, features = ["windows-manifest"] }
+gpui.workspace = true
+gpui_platform.workspace = true
 http_client = { workspace = true, features = ["test-support"]}
 language.workspace = true
 node_runtime.workspace = true

crates/project_benchmarks/src/main.rs 🔗

@@ -5,7 +5,7 @@ use askpass::EncryptedPassword;
 use clap::Parser;
 use client::{Client, UserStore};
 use futures::channel::oneshot;
-use gpui::{AppContext as _, Application};
+use gpui::AppContext as _;
 use http_client::FakeHttpClient;
 use language::LanguageRegistry;
 use node_runtime::NodeRuntime;
@@ -125,7 +125,7 @@ fn main() -> Result<(), anyhow::Error> {
             None,
         )
     }?;
-    Application::headless().run(|cx| {
+    gpui_platform::headless().run(|cx| {
         release_channel::init_test(semver::Version::new(0, 0, 0), ReleaseChannel::Dev, cx);
         settings::init(cx);
         let client = Client::production(cx);

crates/remote_server/Cargo.toml 🔗

@@ -38,7 +38,8 @@ futures.workspace = true
 git.workspace = true
 git_hosting_providers.workspace = true
 git2 = { workspace = true, features = ["vendored-libgit2"] }
-gpui = { workspace = true, features = ["windows-manifest"] }
+gpui.workspace = true
+gpui_platform.workspace = true
 gpui_tokio.workspace = true
 http_client.workspace = true
 image.workspace = true

crates/remote_server/src/server.rs 🔗

@@ -449,7 +449,7 @@ pub fn execute_run(
     init_paths()?;
 
     let startup_time = Instant::now();
-    let app = gpui::Application::headless();
+    let app = gpui_platform::headless();
     let pid = std::process::id();
     let id = pid.to_string();
     app.background_executor()

crates/storybook/Cargo.toml 🔗

@@ -20,6 +20,7 @@ dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
 editor.workspace = true
 fuzzy.workspace = true
 gpui = { workspace = true, default-features = true }
+gpui_platform.workspace = true
 indoc.workspace = true
 language.workspace = true
 log.workspace = true

crates/storybook/src/storybook.rs 🔗

@@ -65,45 +65,47 @@ fn main() {
     });
     let theme_name = args.theme.unwrap_or("One Dark".to_string());
 
-    gpui::Application::new().with_assets(Assets).run(move |cx| {
-        load_embedded_fonts(cx).unwrap();
-
-        cx.set_global(GlobalColors(Arc::new(Colors::default())));
-
-        let http_client = ReqwestClient::user_agent("zed_storybook").unwrap();
-        cx.set_http_client(Arc::new(http_client));
-
-        settings::init(cx);
-        theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
-
-        let selector = story_selector;
-
-        let mut theme_settings = ThemeSettings::get_global(cx).clone();
-        theme_settings.theme =
-            theme::ThemeSelection::Static(settings::ThemeName(theme_name.into()));
-        ThemeSettings::override_global(theme_settings, cx);
-
-        editor::init(cx);
-        init(cx);
-        load_storybook_keymap(cx);
-        cx.set_menus(app_menus());
-
-        let size = size(px(1500.), px(780.));
-        let bounds = Bounds::centered(None, size, cx);
-        let _window = cx.open_window(
-            WindowOptions {
-                window_bounds: Some(WindowBounds::Windowed(bounds)),
-                ..Default::default()
-            },
-            move |window, cx| {
-                theme::setup_ui_font(window, cx);
-
-                cx.new(|cx| StoryWrapper::new(selector.story(window, cx)))
-            },
-        );
-
-        cx.activate(true);
-    });
+    gpui_platform::application()
+        .with_assets(Assets)
+        .run(move |cx| {
+            load_embedded_fonts(cx).unwrap();
+
+            cx.set_global(GlobalColors(Arc::new(Colors::default())));
+
+            let http_client = ReqwestClient::user_agent("zed_storybook").unwrap();
+            cx.set_http_client(Arc::new(http_client));
+
+            settings::init(cx);
+            theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
+
+            let selector = story_selector;
+
+            let mut theme_settings = ThemeSettings::get_global(cx).clone();
+            theme_settings.theme =
+                theme::ThemeSelection::Static(settings::ThemeName(theme_name.into()));
+            ThemeSettings::override_global(theme_settings, cx);
+
+            editor::init(cx);
+            init(cx);
+            load_storybook_keymap(cx);
+            cx.set_menus(app_menus());
+
+            let size = size(px(1500.), px(780.));
+            let bounds = Bounds::centered(None, size, cx);
+            let _window = cx.open_window(
+                WindowOptions {
+                    window_bounds: Some(WindowBounds::Windowed(bounds)),
+                    ..Default::default()
+                },
+                move |window, cx| {
+                    theme::setup_ui_font(window, cx);
+
+                    cx.new(|cx| StoryWrapper::new(selector.story(window, cx)))
+                },
+            );
+
+            cx.activate(true);
+        });
 }
 
 #[derive(Clone)]

crates/worktree_benchmarks/Cargo.toml 🔗

@@ -6,7 +6,7 @@ edition.workspace = true
 
 [dependencies]
 fs.workspace = true
-gpui = { workspace = true, features = ["windows-manifest"] }
+gpui_platform.workspace = true
 settings.workspace = true
 worktree.workspace = true
 

crates/worktree_benchmarks/src/main.rs 🔗

@@ -4,7 +4,6 @@ use std::{
 };
 
 use fs::RealFs;
-use gpui::Application;
 use settings::WorktreeId;
 use worktree::Worktree;
 
@@ -15,7 +14,7 @@ fn main() {
         );
         return;
     };
-    let app = Application::headless();
+    let app = gpui_platform::headless();
 
     app.run(|cx| {
         settings::init(cx);

crates/zed/Cargo.toml 🔗

@@ -15,7 +15,7 @@ workspace = true
 tracy = ["ztracing/tracy"]
 test-support = [
     "gpui/test-support",
-    "gpui/screen-capture",
+    "gpui_platform/screen-capture",
     "dep:image",
     "dep:semver",
     "workspace/test-support",
@@ -29,7 +29,7 @@ test-support = [
 ]
 visual-tests = [
     "gpui/test-support",
-    "gpui/screen-capture",
+    "gpui_platform/screen-capture",
     "dep:image",
     "dep:semver",
     "dep:tempfile",
@@ -120,9 +120,8 @@ system_specs.workspace = true
 gpui = { workspace = true, features = [
     "wayland",
     "x11",
-    "font-kit",
-    "windows-manifest",
 ] }
+gpui_platform = {workspace = true, features=["screen-capture", "font-kit", "wayland", "x11"]}
 image = { workspace = true, optional = true }
 semver = { workspace = true, optional = true }
 tempfile = { workspace = true, optional = true }
@@ -241,7 +240,7 @@ ashpd.workspace = true
 call = { workspace = true, features = ["test-support"] }
 dap = { workspace = true, features = ["test-support"] }
 editor = { workspace = true, features = ["test-support"] }
-gpui = { workspace = true, features = ["test-support", "screen-capture"] }
+gpui = { workspace = true, features = ["test-support"] }
 image_viewer = { workspace = true, features = ["test-support"] }
 itertools.workspace = true
 language = { workspace = true, features = ["test-support"] }

crates/zed/src/main.rs 🔗

@@ -22,6 +22,7 @@ use futures::{StreamExt, channel::oneshot, future};
 use git::GitHostingProviderRegistry;
 use git_ui::clone::clone_and_open;
 use gpui::{App, AppContext, Application, AsyncApp, Focusable as _, QuitMode, UpdateGlobal as _};
+use gpui_platform;
 
 use gpui_tokio::Tokio;
 use language::LanguageRegistry;
@@ -98,7 +99,7 @@ fn files_not_created_on_launch(errors: HashMap<io::ErrorKind, Vec<&Path>>) {
         .collect::<Vec<_>>().join("\n\n");
 
     eprintln!("{message}: {error_details}");
-    Application::new()
+    Application::with_platform(gpui_platform::current_platform(false))
         .with_quit_mode(QuitMode::Explicit)
         .run(move |cx| {
             if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |_, cx| {
@@ -297,7 +298,8 @@ fn main() {
     #[cfg(windows)]
     check_for_conpty_dll();
 
-    let app = Application::new().with_assets(Assets);
+    let app =
+        Application::with_platform(gpui_platform::current_platform(false)).with_assets(Assets);
 
     let system_id = app.background_executor().spawn(system_id());
     let installation_id = app.background_executor().spawn(installation_id());

crates/zed/src/visual_test_runner.rs 🔗

@@ -150,7 +150,10 @@ fn main() {
 fn run_visual_tests(project_path: PathBuf, update_baseline: bool) -> Result<()> {
     // Create the visual test context with deterministic task scheduling
     // Use real Assets so that SVG icons render properly
-    let mut cx = VisualTestAppContext::with_asset_source(Arc::new(Assets));
+    let mut cx = VisualTestAppContext::with_asset_source(
+        gpui_platform::current_platform(false),
+        Arc::new(Assets),
+    );
 
     // Load embedded fonts (IBM Plex Sans, Lilex, etc.) so UI renders with correct fonts
     cx.update(|cx| {

crates/zed/src/zed/visual_tests.rs 🔗

@@ -426,7 +426,7 @@ mod tests {
     #[test]
     #[ignore]
     fn test_visual_test_smoke() {
-        let mut cx = VisualTestAppContext::new();
+        let mut cx = VisualTestAppContext::new(gpui_platform::current_platform(false));
 
         let _window = cx
             .open_offscreen_window_default(|_, cx| cx.new(|_| Empty))
@@ -438,7 +438,7 @@ mod tests {
     #[test]
     #[ignore]
     fn test_workspace_opens() {
-        let mut cx = VisualTestAppContext::new();
+        let mut cx = VisualTestAppContext::new(gpui_platform::current_platform(false));
         let app_state = init_visual_test(&mut cx);
 
         smol::block_on(async {
@@ -479,7 +479,7 @@ mod tests {
     #[test]
     #[ignore]
     fn test_workspace_screenshot() {
-        let mut cx = VisualTestAppContext::new();
+        let mut cx = VisualTestAppContext::new(gpui_platform::current_platform(false));
         let app_state = init_visual_test(&mut cx);
 
         smol::block_on(async {

typos.toml 🔗

@@ -36,10 +36,10 @@ extend-exclude = [
     "extensions/proto/extension.toml",
     "extensions/proto/src/language_servers/protols.rs",
     # Windows likes its abbreviations.
-    "crates/gpui/src/platform/windows/directx_renderer.rs",
-    "crates/gpui/src/platform/windows/events.rs",
-    "crates/gpui/src/platform/windows/direct_write.rs",
-    "crates/gpui/src/platform/windows/window.rs",
+    "crates/gpui_windows/src/directx_renderer.rs",
+    "crates/gpui_windows/src/events.rs",
+    "crates/gpui_windows/src/direct_write.rs",
+    "crates/gpui_windows/src/window.rs",
     # Some typos in the base mdBook CSS.
     "docs/theme/css/",
     # Spellcheck triggers on `|Fixe[sd]|` regex part.
@@ -57,7 +57,7 @@ extend-exclude = [
     # Some multibuffer test cases have word fragments that register as typos
     "crates/multi_buffer/src/multi_buffer_tests.rs",
     # Macos apis
-    "crates/gpui/src/platform/mac/dispatcher.rs",
+    "crates/gpui_macos/src/dispatcher.rs",
     # Tests contain partially incomplete words (by design)
     "crates/edit_prediction_cli/src/split_commit.rs",
     # Eval examples contain intentionally partial words (e.g. "secur" for "secure")