Checkpoint

Nathan Sobo created

Change summary

Cargo.lock                                         |   42 
crates/gpui3/Cargo.toml                            |   79 
crates/gpui3/build.rs                              |   24 
crates/gpui3/src/app.rs                            |    4 
crates/gpui3/src/executor.rs                       | 1030 ++++++++++
crates/gpui3/src/fonts.rs                          |    6 
crates/gpui3/src/geometry.rs                       |   21 
crates/gpui3/src/gpui3.rs                          |   22 
crates/gpui3/src/platform.rs                       |  111 +
crates/gpui3/src/platform/events.rs                |  204 ++
crates/gpui3/src/platform/keystroke.rs             |  121 +
crates/gpui3/src/platform/mac.rs                   |  144 +
crates/gpui3/src/platform/mac/dispatch.h           |    1 
crates/gpui3/src/platform/mac/dispatcher.rs        |   42 
crates/gpui3/src/platform/mac/events.rs            |  354 +++
crates/gpui3/src/platform/mac/platform.rs          | 1102 ++++++++++
crates/gpui3/src/platform/mac/screen.rs            |  145 +
crates/gpui3/src/platform/mac/window.rs            | 1619 ++++++++++++++++
crates/gpui3/src/platform/mac/window_appearence.rs |   35 
crates/gpui3/src/platform/test.rs                  |    6 
crates/gpui3/src/style.rs                          |    2 
crates/gpui3/src/text.rs                           |   10 
crates/gpui3/src/util.rs                           |   49 
crates/gpui3/src/window.rs                         |    9 
24 files changed, 5,133 insertions(+), 49 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -3345,23 +3345,61 @@ name = "gpui3"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "async-task",
+ "backtrace",
+ "bindgen 0.65.1",
+ "block",
  "bytemuck",
+ "cocoa",
+ "collections",
+ "core-foundation",
+ "core-graphics",
+ "core-text",
+ "ctor",
  "derive_more",
+ "dhat",
+ "env_logger 0.9.3",
+ "etagere",
  "font-kit",
- "itertools 0.11.0",
+ "foreign-types 0.3.2",
+ "futures 0.3.28",
+ "gpui_macros",
+ "image",
+ "itertools 0.10.5",
+ "lazy_static",
  "log",
+ "media",
+ "num_cpus",
+ "objc",
+ "ordered-float",
+ "parking",
  "parking_lot 0.11.2",
  "plane-split",
+ "png",
+ "postage",
+ "rand 0.8.5",
  "raw-window-handle",
  "refineable",
- "rust-embed",
+ "resvg",
  "schemars",
+ "seahash",
  "serde",
+ "serde_derive",
+ "serde_json",
  "simplelog",
  "slotmap",
  "smallvec",
+ "smol",
+ "sqlez",
+ "sum_tree",
  "taffy",
+ "thiserror",
+ "time 0.3.27",
+ "tiny-skia",
+ "usvg",
  "util",
+ "uuid 1.4.1",
+ "waker-fn",
  "wgpu",
 ]
 

crates/gpui3/Cargo.toml πŸ”—

@@ -2,30 +2,83 @@
 name = "gpui3"
 version = "0.1.0"
 edition = "2021"
+authors = ["Nathan Sobo <nathansobo@gmail.com>"]
+description = "The next version of Zed's GPU-accelerated UI framework"
+publish = false
 
 [features]
-test = []
+test-support = ["backtrace", "dhat", "env_logger", "collections/test-support"]
 
 [lib]
 path = "src/gpui3.rs"
+doctest = false
 
 [dependencies]
-anyhow.workspace = true
-bytemuck = "1.14.0"
+collections = { path = "../collections" }
+gpui_macros = { path = "../gpui_macros" }
+util = { path = "../util" }
+sum_tree = { path = "../sum_tree" }
+sqlez = { path = "../sqlez" }
+async-task = "4.0.3"
+backtrace = { version = "0.3", optional = true }
+ctor.workspace = true
 derive_more.workspace = true
-font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" }
-itertools = "0.11.0"
+dhat = { version = "0.3", optional = true }
+env_logger = { version = "0.9", optional = true }
+etagere = "0.2"
+futures.workspace = true
+image = "0.23"
+itertools = "0.10"
+lazy_static.workspace = true
 log.workspace = true
+num_cpus = "1.13"
+ordered-float.workspace = true
+parking = "2.0.0"
 parking_lot.workspace = true
-plane-split = "0.18.0"
-raw-window-handle = "0.5.2"
-refineable = { path = "../refineable" }
-rust-embed.workspace = true
-schemars = "0.8"
+postage.workspace = true
+rand.workspace = true
+refineable.workspace = true
+resvg = "0.14"
+seahash = "4.1"
 serde.workspace = true
-simplelog = "0.9"
-slotmap = "1.0.6"
+serde_derive.workspace = true
+serde_json.workspace = true
 smallvec.workspace = true
+smol.workspace = true
 taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" }
-util = { path = "../util" }
+thiserror.workspace = true
+time.workspace = true
+tiny-skia = "0.5"
+usvg = { version = "0.14", features = [] }
+uuid = { version = "1.1.2", features = ["v4"] }
+waker-fn = "1.1.0"
+slotmap = "1.0.6"
+bytemuck = "1.14.0"
+schemars.workspace = true
+raw-window-handle = "0.5.2"
 wgpu = "0.17.0"
+plane-split = "0.18.0"
+
+[dev-dependencies]
+backtrace = "0.3"
+collections = { path = "../collections", features = ["test-support"] }
+dhat = "0.3"
+env_logger.workspace = true
+png = "0.16"
+simplelog = "0.9"
+
+[build-dependencies]
+bindgen = "0.65.1"
+
+[target.'cfg(target_os = "macos")'.dependencies]
+media = { path = "../media" }
+anyhow.workspace = true
+block = "0.1"
+cocoa = "0.24"
+core-foundation = { version = "0.9.3", features = ["with-uuid"] }
+core-graphics = "0.22.3"
+core-text = "19.2"
+font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" }
+foreign-types = "0.3"
+log.workspace = true
+objc = "0.2"

crates/gpui3/build.rs πŸ”—

@@ -0,0 +1,24 @@
+use std::{env, path::PathBuf};
+
+fn main() {
+    generate_dispatch_bindings();
+}
+
+fn generate_dispatch_bindings() {
+    println!("cargo:rustc-link-lib=framework=System");
+    println!("cargo:rerun-if-changed=src/platform/mac/dispatch.h");
+
+    let bindings = bindgen::Builder::default()
+        .header("src/platform/mac/dispatch.h")
+        .allowlist_var("_dispatch_main_q")
+        .allowlist_function("dispatch_async_f")
+        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
+        .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");
+}

crates/gpui3/src/app.rs πŸ”—

@@ -51,9 +51,9 @@ impl AppContext {
     ) -> WindowHandle<S> {
         let id = self.windows.insert(None);
         let handle = WindowHandle::new(id);
-        self.platform.open_window(handle.into(), options);
+        let platform_window = self.platform.open_window(handle.into(), options);
 
-        let mut window = Window::new(id);
+        let mut window = Window::new(id, platform_window);
         let root_view = build_root_view(&mut WindowContext::mutable(self, &mut window));
         window.root_view.replace(Box::new(root_view));
 

crates/gpui3/src/executor.rs πŸ”—

@@ -0,0 +1,1030 @@
+use crate::util;
+use anyhow::{anyhow, Result};
+use async_task::Runnable;
+use futures::channel::mpsc;
+use smol::{channel, prelude::*, Executor};
+use std::{
+    any::Any,
+    fmt::{self},
+    marker::PhantomData,
+    mem,
+    pin::Pin,
+    rc::Rc,
+    sync::Arc,
+    task::{Context, Poll},
+    thread,
+    time::Duration,
+};
+
+use crate::PlatformDispatcher;
+
+pub enum ForegroundExecutor {
+    Platform {
+        dispatcher: Arc<dyn PlatformDispatcher>,
+        _not_send_or_sync: PhantomData<Rc<()>>,
+    },
+    #[cfg(any(test, feature = "test"))]
+    Deterministic {
+        cx_id: usize,
+        executor: Arc<Deterministic>,
+    },
+}
+
+pub enum BackgroundExecutor {
+    #[cfg(any(test, feature = "test"))]
+    Deterministic { executor: Arc<Deterministic> },
+    Production {
+        executor: Arc<smol::Executor<'static>>,
+        _stop: channel::Sender<()>,
+    },
+}
+
+type AnyLocalFuture = Pin<Box<dyn 'static + Future<Output = Box<dyn Any + 'static>>>>;
+type AnyFuture = Pin<Box<dyn 'static + Send + Future<Output = Box<dyn Any + Send + 'static>>>>;
+type AnyTask = async_task::Task<Box<dyn Any + Send + 'static>>;
+type AnyLocalTask = async_task::Task<Box<dyn Any + 'static>>;
+
+#[must_use]
+pub enum Task<T> {
+    Ready(Option<T>),
+    Local {
+        any_task: AnyLocalTask,
+        result_type: PhantomData<T>,
+    },
+    Send {
+        any_task: AnyTask,
+        result_type: PhantomData<T>,
+    },
+}
+
+unsafe impl<T: Send> Send for Task<T> {}
+
+#[cfg(any(test, feature = "test"))]
+struct DeterministicState {
+    rng: rand::prelude::StdRng,
+    seed: u64,
+    scheduled_from_foreground: collections::HashMap<usize, Vec<ForegroundRunnable>>,
+    scheduled_from_background: Vec<BackgroundRunnable>,
+    forbid_parking: bool,
+    block_on_ticks: std::ops::RangeInclusive<usize>,
+    now: std::time::Instant,
+    next_timer_id: usize,
+    pending_timers: Vec<(usize, std::time::Instant, postage::barrier::Sender)>,
+    waiting_backtrace: Option<backtrace::Backtrace>,
+    next_runnable_id: usize,
+    poll_history: Vec<ExecutorEvent>,
+    previous_poll_history: Option<Vec<ExecutorEvent>>,
+    enable_runnable_backtraces: bool,
+    runnable_backtraces: collections::HashMap<usize, backtrace::Backtrace>,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum ExecutorEvent {
+    PollRunnable { id: usize },
+    EnqueuRunnable { id: usize },
+}
+
+#[cfg(any(test, feature = "test"))]
+struct ForegroundRunnable {
+    id: usize,
+    runnable: Runnable,
+    main: bool,
+}
+
+#[cfg(any(test, feature = "test"))]
+struct BackgroundRunnable {
+    id: usize,
+    runnable: Runnable,
+}
+
+#[cfg(any(test, feature = "test"))]
+pub struct Deterministic {
+    state: Arc<parking_lot::Mutex<DeterministicState>>,
+    parker: parking_lot::Mutex<parking::Parker>,
+}
+
+#[must_use]
+pub enum Timer {
+    Production(smol::Timer),
+    #[cfg(any(test, feature = "test"))]
+    Deterministic(DeterministicTimer),
+}
+
+#[cfg(any(test, feature = "test"))]
+pub struct DeterministicTimer {
+    rx: postage::barrier::Receiver,
+    id: usize,
+    state: Arc<parking_lot::Mutex<DeterministicState>>,
+}
+
+#[cfg(any(test, feature = "test"))]
+impl Deterministic {
+    pub fn new(seed: u64) -> Arc<Self> {
+        use rand::prelude::*;
+
+        Arc::new(Self {
+            state: Arc::new(parking_lot::Mutex::new(DeterministicState {
+                rng: StdRng::seed_from_u64(seed),
+                seed,
+                scheduled_from_foreground: Default::default(),
+                scheduled_from_background: Default::default(),
+                forbid_parking: false,
+                block_on_ticks: 0..=1000,
+                now: std::time::Instant::now(),
+                next_timer_id: Default::default(),
+                pending_timers: Default::default(),
+                waiting_backtrace: None,
+                next_runnable_id: 0,
+                poll_history: Default::default(),
+                previous_poll_history: Default::default(),
+                enable_runnable_backtraces: false,
+                runnable_backtraces: Default::default(),
+            })),
+            parker: Default::default(),
+        })
+    }
+
+    pub fn execution_history(&self) -> Vec<ExecutorEvent> {
+        self.state.lock().poll_history.clone()
+    }
+
+    pub fn set_previous_execution_history(&self, history: Option<Vec<ExecutorEvent>>) {
+        self.state.lock().previous_poll_history = history;
+    }
+
+    pub fn enable_runnable_backtrace(&self) {
+        self.state.lock().enable_runnable_backtraces = true;
+    }
+
+    pub fn runnable_backtrace(&self, runnable_id: usize) -> backtrace::Backtrace {
+        let mut backtrace = self.state.lock().runnable_backtraces[&runnable_id].clone();
+        backtrace.resolve();
+        backtrace
+    }
+
+    pub fn build_background(self: &Arc<Self>) -> Arc<BackgroundExecutor> {
+        Arc::new(BackgroundExecutor::Deterministic {
+            executor: self.clone(),
+        })
+    }
+
+    pub fn build_foreground(self: &Arc<Self>, id: usize) -> Rc<ForegroundExecutor> {
+        Rc::new(ForegroundExecutor::Deterministic {
+            cx_id: id,
+            executor: self.clone(),
+        })
+    }
+
+    fn spawn_from_foreground(
+        &self,
+        cx_id: usize,
+        future: AnyLocalFuture,
+        main: bool,
+    ) -> AnyLocalTask {
+        let state = self.state.clone();
+        let id;
+        {
+            let mut state = state.lock();
+            id = util::post_inc(&mut state.next_runnable_id);
+            if state.enable_runnable_backtraces {
+                state
+                    .runnable_backtraces
+                    .insert(id, backtrace::Backtrace::new_unresolved());
+            }
+        }
+
+        let unparker = self.parker.lock().unparker();
+        let (runnable, task) = async_task::spawn_local(future, move |runnable| {
+            let mut state = state.lock();
+            state.push_to_history(ExecutorEvent::EnqueuRunnable { id });
+            state
+                .scheduled_from_foreground
+                .entry(cx_id)
+                .or_default()
+                .push(ForegroundRunnable { id, runnable, main });
+            unparker.unpark();
+        });
+        runnable.schedule();
+        task
+    }
+
+    fn spawn(&self, future: AnyFuture) -> AnyTask {
+        let state = self.state.clone();
+        let id;
+        {
+            let mut state = state.lock();
+            id = util::post_inc(&mut state.next_runnable_id);
+            if state.enable_runnable_backtraces {
+                state
+                    .runnable_backtraces
+                    .insert(id, backtrace::Backtrace::new_unresolved());
+            }
+        }
+
+        let unparker = self.parker.lock().unparker();
+        let (runnable, task) = async_task::spawn(future, move |runnable| {
+            let mut state = state.lock();
+            state
+                .poll_history
+                .push(ExecutorEvent::EnqueuRunnable { id });
+            state
+                .scheduled_from_background
+                .push(BackgroundRunnable { id, runnable });
+            unparker.unpark();
+        });
+        runnable.schedule();
+        task
+    }
+
+    fn run<'a>(
+        &self,
+        cx_id: usize,
+        main_future: Pin<Box<dyn 'a + Future<Output = Box<dyn Any>>>>,
+    ) -> Box<dyn Any> {
+        use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
+
+        let woken = Arc::new(AtomicBool::new(false));
+
+        let state = self.state.clone();
+        let id;
+        {
+            let mut state = state.lock();
+            id = util::post_inc(&mut state.next_runnable_id);
+            if state.enable_runnable_backtraces {
+                state
+                    .runnable_backtraces
+                    .insert(id, backtrace::Backtrace::new_unresolved());
+            }
+        }
+
+        let unparker = self.parker.lock().unparker();
+        let (runnable, mut main_task) = unsafe {
+            async_task::spawn_unchecked(main_future, move |runnable| {
+                let state = &mut *state.lock();
+                state
+                    .scheduled_from_foreground
+                    .entry(cx_id)
+                    .or_default()
+                    .push(ForegroundRunnable {
+                        id: util::post_inc(&mut state.next_runnable_id),
+                        runnable,
+                        main: true,
+                    });
+                unparker.unpark();
+            })
+        };
+        runnable.schedule();
+
+        loop {
+            if let Some(result) = self.run_internal(woken.clone(), Some(&mut main_task)) {
+                return result;
+            }
+
+            if !woken.load(SeqCst) {
+                self.state.lock().will_park();
+            }
+
+            woken.store(false, SeqCst);
+            self.parker.lock().park();
+        }
+    }
+
+    pub fn run_until_parked(&self) {
+        use std::sync::atomic::AtomicBool;
+        let woken = Arc::new(AtomicBool::new(false));
+        self.run_internal(woken, None);
+    }
+
+    fn run_internal(
+        &self,
+        woken: Arc<std::sync::atomic::AtomicBool>,
+        mut main_task: Option<&mut AnyLocalTask>,
+    ) -> Option<Box<dyn Any>> {
+        use rand::prelude::*;
+        use std::sync::atomic::Ordering::SeqCst;
+
+        let unparker = self.parker.lock().unparker();
+        let waker = waker_fn::waker_fn(move || {
+            woken.store(true, SeqCst);
+            unparker.unpark();
+        });
+
+        let mut cx = Context::from_waker(&waker);
+        loop {
+            let mut state = self.state.lock();
+
+            if state.scheduled_from_foreground.is_empty()
+                && state.scheduled_from_background.is_empty()
+            {
+                if let Some(main_task) = main_task {
+                    if let Poll::Ready(result) = main_task.poll(&mut cx) {
+                        return Some(result);
+                    }
+                }
+
+                return None;
+            }
+
+            if !state.scheduled_from_background.is_empty() && state.rng.gen() {
+                let background_len = state.scheduled_from_background.len();
+                let ix = state.rng.gen_range(0..background_len);
+                let background_runnable = state.scheduled_from_background.remove(ix);
+                state.push_to_history(ExecutorEvent::PollRunnable {
+                    id: background_runnable.id,
+                });
+                drop(state);
+                background_runnable.runnable.run();
+            } else if !state.scheduled_from_foreground.is_empty() {
+                let available_cx_ids = state
+                    .scheduled_from_foreground
+                    .keys()
+                    .copied()
+                    .collect::<Vec<_>>();
+                let cx_id_to_run = *available_cx_ids.iter().choose(&mut state.rng).unwrap();
+                let scheduled_from_cx = state
+                    .scheduled_from_foreground
+                    .get_mut(&cx_id_to_run)
+                    .unwrap();
+                let foreground_runnable = scheduled_from_cx.remove(0);
+                if scheduled_from_cx.is_empty() {
+                    state.scheduled_from_foreground.remove(&cx_id_to_run);
+                }
+                state.push_to_history(ExecutorEvent::PollRunnable {
+                    id: foreground_runnable.id,
+                });
+
+                drop(state);
+
+                foreground_runnable.runnable.run();
+                if let Some(main_task) = main_task.as_mut() {
+                    if foreground_runnable.main {
+                        if let Poll::Ready(result) = main_task.poll(&mut cx) {
+                            return Some(result);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fn block<F, T>(&self, future: &mut F, max_ticks: usize) -> Option<T>
+    where
+        F: Unpin + Future<Output = T>,
+    {
+        use rand::prelude::*;
+
+        let unparker = self.parker.lock().unparker();
+        let waker = waker_fn::waker_fn(move || {
+            unparker.unpark();
+        });
+
+        let mut cx = Context::from_waker(&waker);
+        for _ in 0..max_ticks {
+            let mut state = self.state.lock();
+            let runnable_count = state.scheduled_from_background.len();
+            let ix = state.rng.gen_range(0..=runnable_count);
+            if ix < state.scheduled_from_background.len() {
+                let background_runnable = state.scheduled_from_background.remove(ix);
+                state.push_to_history(ExecutorEvent::PollRunnable {
+                    id: background_runnable.id,
+                });
+                drop(state);
+                background_runnable.runnable.run();
+            } else {
+                drop(state);
+                if let Poll::Ready(result) = future.poll(&mut cx) {
+                    return Some(result);
+                }
+                let mut state = self.state.lock();
+                if state.scheduled_from_background.is_empty() {
+                    state.will_park();
+                    drop(state);
+                    self.parker.lock().park();
+                }
+
+                continue;
+            }
+        }
+
+        None
+    }
+
+    pub fn timer(&self, duration: Duration) -> Timer {
+        let (tx, rx) = postage::barrier::channel();
+        let mut state = self.state.lock();
+        let wakeup_at = state.now + duration;
+        let id = util::post_inc(&mut state.next_timer_id);
+        match state
+            .pending_timers
+            .binary_search_by_key(&wakeup_at, |e| e.1)
+        {
+            Ok(ix) | Err(ix) => state.pending_timers.insert(ix, (id, wakeup_at, tx)),
+        }
+        let state = self.state.clone();
+        Timer::Deterministic(DeterministicTimer { rx, id, state })
+    }
+
+    pub fn now(&self) -> std::time::Instant {
+        let state = self.state.lock();
+        state.now
+    }
+
+    pub fn advance_clock(&self, duration: Duration) {
+        let new_now = self.state.lock().now + duration;
+        loop {
+            self.run_until_parked();
+            let mut state = self.state.lock();
+
+            if let Some((_, wakeup_time, _)) = state.pending_timers.first() {
+                let wakeup_time = *wakeup_time;
+                if wakeup_time <= new_now {
+                    let timer_count = state
+                        .pending_timers
+                        .iter()
+                        .take_while(|(_, t, _)| *t == wakeup_time)
+                        .count();
+                    state.now = wakeup_time;
+                    let timers_to_wake = state
+                        .pending_timers
+                        .drain(0..timer_count)
+                        .collect::<Vec<_>>();
+                    drop(state);
+                    drop(timers_to_wake);
+                    continue;
+                }
+            }
+
+            break;
+        }
+
+        self.state.lock().now = new_now;
+    }
+
+    pub fn start_waiting(&self) {
+        self.state.lock().waiting_backtrace = Some(backtrace::Backtrace::new_unresolved());
+    }
+
+    pub fn finish_waiting(&self) {
+        self.state.lock().waiting_backtrace.take();
+    }
+
+    pub fn forbid_parking(&self) {
+        use rand::prelude::*;
+
+        let mut state = self.state.lock();
+        state.forbid_parking = true;
+        state.rng = StdRng::seed_from_u64(state.seed);
+    }
+
+    pub fn allow_parking(&self) {
+        use rand::prelude::*;
+
+        let mut state = self.state.lock();
+        state.forbid_parking = false;
+        state.rng = StdRng::seed_from_u64(state.seed);
+    }
+
+    pub async fn simulate_random_delay(&self) {
+        use rand::prelude::*;
+        use smol::future::yield_now;
+        if self.state.lock().rng.gen_bool(0.2) {
+            let yields = self.state.lock().rng.gen_range(1..=10);
+            for _ in 0..yields {
+                yield_now().await;
+            }
+        }
+    }
+
+    pub fn record_backtrace(&self) {
+        let mut state = self.state.lock();
+        if state.enable_runnable_backtraces {
+            let current_id = state
+                .poll_history
+                .iter()
+                .rev()
+                .find_map(|event| match event {
+                    ExecutorEvent::PollRunnable { id } => Some(*id),
+                    _ => None,
+                });
+            if let Some(id) = current_id {
+                state
+                    .runnable_backtraces
+                    .insert(id, backtrace::Backtrace::new_unresolved());
+            }
+        }
+    }
+}
+
+impl Drop for Timer {
+    fn drop(&mut self) {
+        #[cfg(any(test, feature = "test"))]
+        if let Timer::Deterministic(DeterministicTimer { state, id, .. }) = self {
+            state
+                .lock()
+                .pending_timers
+                .retain(|(timer_id, _, _)| timer_id != id)
+        }
+    }
+}
+
+impl Future for Timer {
+    type Output = ();
+
+    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+        match &mut *self {
+            #[cfg(any(test, feature = "test"))]
+            Self::Deterministic(DeterministicTimer { rx, .. }) => {
+                use postage::stream::{PollRecv, Stream as _};
+                smol::pin!(rx);
+                match rx.poll_recv(&mut postage::Context::from_waker(cx.waker())) {
+                    PollRecv::Ready(()) | PollRecv::Closed => Poll::Ready(()),
+                    PollRecv::Pending => Poll::Pending,
+                }
+            }
+            Self::Production(timer) => {
+                smol::pin!(timer);
+                match timer.poll(cx) {
+                    Poll::Ready(_) => Poll::Ready(()),
+                    Poll::Pending => Poll::Pending,
+                }
+            }
+        }
+    }
+}
+
+#[cfg(any(test, feature = "test"))]
+impl DeterministicState {
+    fn push_to_history(&mut self, event: ExecutorEvent) {
+        use std::fmt::Write as _;
+
+        self.poll_history.push(event);
+        if let Some(prev_history) = &self.previous_poll_history {
+            let ix = self.poll_history.len() - 1;
+            let prev_event = prev_history[ix];
+            if event != prev_event {
+                let mut message = String::new();
+                writeln!(
+                    &mut message,
+                    "current runnable backtrace:\n{:?}",
+                    self.runnable_backtraces.get_mut(&event.id()).map(|trace| {
+                        trace.resolve();
+                        crate::util::CwdBacktrace(trace)
+                    })
+                )
+                .unwrap();
+                writeln!(
+                    &mut message,
+                    "previous runnable backtrace:\n{:?}",
+                    self.runnable_backtraces
+                        .get_mut(&prev_event.id())
+                        .map(|trace| {
+                            trace.resolve();
+                            util::CwdBacktrace(trace)
+                        })
+                )
+                .unwrap();
+                panic!("detected non-determinism after {ix}. {message}");
+            }
+        }
+    }
+
+    fn will_park(&mut self) {
+        if self.forbid_parking {
+            let mut backtrace_message = String::new();
+            #[cfg(any(test, feature = "test"))]
+            if let Some(backtrace) = self.waiting_backtrace.as_mut() {
+                backtrace.resolve();
+                backtrace_message = format!(
+                    "\nbacktrace of waiting future:\n{:?}",
+                    util::CwdBacktrace(backtrace)
+                );
+            }
+
+            panic!(
+                "deterministic executor parked after a call to forbid_parking{}",
+                backtrace_message
+            );
+        }
+    }
+}
+
+#[cfg(any(test, feature = "test"))]
+impl ExecutorEvent {
+    pub fn id(&self) -> usize {
+        match self {
+            ExecutorEvent::PollRunnable { id } => *id,
+            ExecutorEvent::EnqueuRunnable { id } => *id,
+        }
+    }
+}
+
+impl ForegroundExecutor {
+    pub fn platform(dispatcher: Arc<dyn PlatformDispatcher>) -> Result<Self> {
+        if dispatcher.is_main_thread() {
+            Ok(Self::Platform {
+                dispatcher,
+                _not_send_or_sync: PhantomData,
+            })
+        } else {
+            Err(anyhow!("must be constructed on main thread"))
+        }
+    }
+
+    pub fn spawn<T: 'static>(&self, future: impl Future<Output = T> + 'static) -> Task<T> {
+        let future = any_local_future(future);
+        let any_task = match self {
+            #[cfg(any(test, feature = "test"))]
+            Self::Deterministic { cx_id, executor } => {
+                executor.spawn_from_foreground(*cx_id, future, false)
+            }
+            Self::Platform { dispatcher, .. } => {
+                fn spawn_inner(
+                    future: AnyLocalFuture,
+                    dispatcher: &Arc<dyn PlatformDispatcher>,
+                ) -> AnyLocalTask {
+                    let dispatcher = dispatcher.clone();
+                    let schedule =
+                        move |runnable: Runnable| dispatcher.run_on_main_thread(runnable);
+                    let (runnable, task) = async_task::spawn_local(future, schedule);
+                    runnable.schedule();
+                    task
+                }
+                spawn_inner(future, dispatcher)
+            }
+        };
+        Task::local(any_task)
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn run<T: 'static>(&self, future: impl Future<Output = T>) -> T {
+        let future = async move { Box::new(future.await) as Box<dyn Any> }.boxed_local();
+        let result = match self {
+            Self::Deterministic { cx_id, executor } => executor.run(*cx_id, future),
+            Self::Platform { .. } => panic!("you can't call run on a platform foreground executor"),
+        };
+        *result.downcast().unwrap()
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn run_until_parked(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.run_until_parked(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn parking_forbidden(&self) -> bool {
+        match self {
+            Self::Deterministic { executor, .. } => executor.state.lock().forbid_parking,
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn start_waiting(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.start_waiting(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn finish_waiting(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.finish_waiting(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn forbid_parking(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.forbid_parking(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn allow_parking(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.allow_parking(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn advance_clock(&self, duration: Duration) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.advance_clock(duration),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.state.lock().block_on_ticks = range,
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+}
+
+impl BackgroundExecutor {
+    pub fn new() -> Self {
+        let executor = Arc::new(Executor::new());
+        let stop = channel::unbounded::<()>();
+
+        for i in 0..2 * num_cpus::get() {
+            let executor = executor.clone();
+            let stop = stop.1.clone();
+            thread::Builder::new()
+                .name(format!("background-executor-{}", i))
+                .spawn(move || smol::block_on(executor.run(stop.recv())))
+                .unwrap();
+        }
+
+        Self::Production {
+            executor,
+            _stop: stop.0,
+        }
+    }
+
+    pub fn num_cpus(&self) -> usize {
+        num_cpus::get()
+    }
+
+    pub fn spawn<T, F>(&self, future: F) -> Task<T>
+    where
+        T: 'static + Send,
+        F: Send + Future<Output = T> + 'static,
+    {
+        let future = any_future(future);
+        let any_task = match self {
+            Self::Production { executor, .. } => executor.spawn(future),
+            #[cfg(any(test, feature = "test"))]
+            Self::Deterministic { executor } => executor.spawn(future),
+        };
+        Task::send(any_task)
+    }
+
+    pub fn block<F, T>(&self, future: F) -> T
+    where
+        F: Future<Output = T>,
+    {
+        smol::pin!(future);
+        match self {
+            Self::Production { .. } => smol::block_on(&mut future),
+            #[cfg(any(test, feature = "test"))]
+            Self::Deterministic { executor, .. } => {
+                executor.block(&mut future, usize::MAX).unwrap()
+            }
+        }
+    }
+
+    pub fn block_with_timeout<F, T>(
+        &self,
+        timeout: Duration,
+        future: F,
+    ) -> Result<T, impl Future<Output = T>>
+    where
+        T: 'static,
+        F: 'static + Unpin + Future<Output = T>,
+    {
+        let mut future = any_local_future(future);
+        if !timeout.is_zero() {
+            let output = match self {
+                Self::Production { .. } => smol::block_on(util::timeout(timeout, &mut future)).ok(),
+                #[cfg(any(test, feature = "test"))]
+                Self::Deterministic { executor, .. } => {
+                    use rand::prelude::*;
+                    let max_ticks = {
+                        let mut state = executor.state.lock();
+                        let range = state.block_on_ticks.clone();
+                        state.rng.gen_range(range)
+                    };
+                    executor.block(&mut future, max_ticks)
+                }
+            };
+            if let Some(output) = output {
+                return Ok(*output.downcast().unwrap());
+            }
+        }
+        Err(async { *future.await.downcast().unwrap() })
+    }
+
+    pub async fn scoped<'scope, F>(self: &Arc<Self>, scheduler: F)
+    where
+        F: FnOnce(&mut Scope<'scope>),
+    {
+        let mut scope = Scope::new(self.clone());
+        (scheduler)(&mut scope);
+        let spawned = mem::take(&mut scope.futures)
+            .into_iter()
+            .map(|f| self.spawn(f))
+            .collect::<Vec<_>>();
+        for task in spawned {
+            task.await;
+        }
+    }
+
+    pub fn timer(&self, duration: Duration) -> Timer {
+        match self {
+            BackgroundExecutor::Production { .. } => {
+                Timer::Production(smol::Timer::after(duration))
+            }
+            #[cfg(any(test, feature = "test"))]
+            BackgroundExecutor::Deterministic { executor } => executor.timer(duration),
+        }
+    }
+
+    pub fn now(&self) -> std::time::Instant {
+        match self {
+            BackgroundExecutor::Production { .. } => std::time::Instant::now(),
+            #[cfg(any(test, feature = "test"))]
+            BackgroundExecutor::Deterministic { executor } => executor.now(),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn rng<'a>(&'a self) -> impl 'a + std::ops::DerefMut<Target = rand::prelude::StdRng> {
+        match self {
+            Self::Deterministic { executor, .. } => {
+                parking_lot::lock_api::MutexGuard::map(executor.state.lock(), |s| &mut s.rng)
+            }
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub async fn simulate_random_delay(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => {
+                executor.simulate_random_delay().await;
+            }
+            _ => {
+                panic!("this method can only be called on a deterministic executor")
+            }
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn record_backtrace(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.record_backtrace(),
+            _ => {
+                panic!("this method can only be called on a deterministic executor")
+            }
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn start_waiting(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.start_waiting(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+}
+
+impl Default for BackgroundExecutor {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+pub struct Scope<'a> {
+    executor: Arc<BackgroundExecutor>,
+    futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
+    tx: Option<mpsc::Sender<()>>,
+    rx: mpsc::Receiver<()>,
+    _phantom: PhantomData<&'a ()>,
+}
+
+impl<'a> Scope<'a> {
+    fn new(executor: Arc<BackgroundExecutor>) -> Self {
+        let (tx, rx) = mpsc::channel(1);
+        Self {
+            executor,
+            tx: Some(tx),
+            rx,
+            futures: Default::default(),
+            _phantom: PhantomData,
+        }
+    }
+
+    pub fn spawn<F>(&mut self, f: F)
+    where
+        F: Future<Output = ()> + Send + 'a,
+    {
+        let tx = self.tx.clone().unwrap();
+
+        // Safety: The 'a lifetime is guaranteed to outlive any of these futures because
+        // dropping this `Scope` blocks until all of the futures have resolved.
+        let f = unsafe {
+            mem::transmute::<
+                Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
+                Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
+            >(Box::pin(async move {
+                f.await;
+                drop(tx);
+            }))
+        };
+        self.futures.push(f);
+    }
+}
+
+impl<'a> Drop for Scope<'a> {
+    fn drop(&mut self) {
+        self.tx.take().unwrap();
+
+        // Wait until the channel is closed, which means that all of the spawned
+        // futures have resolved.
+        self.executor.block(self.rx.next());
+    }
+}
+
+impl<T> Task<T> {
+    pub fn ready(value: T) -> Self {
+        Self::Ready(Some(value))
+    }
+
+    fn local(any_task: AnyLocalTask) -> Self {
+        Self::Local {
+            any_task,
+            result_type: PhantomData,
+        }
+    }
+
+    pub fn detach(self) {
+        match self {
+            Task::Ready(_) => {}
+            Task::Local { any_task, .. } => any_task.detach(),
+            Task::Send { any_task, .. } => any_task.detach(),
+        }
+    }
+}
+
+// impl<T: 'static, E: 'static + Display> Task<Result<T, E>> {
+//     #[track_caller]
+//     pub fn detach_and_log_err(self, cx: &mut AppContext) {
+//         let caller = Location::caller();
+//         cx.spawn(|_| async move {
+//             if let Err(err) = self.await {
+//                 log::error!("{}:{}: {:#}", caller.file(), caller.line(), err);
+//             }
+//         })
+//         .detach();
+//     }
+// }
+
+impl<T: Send> Task<T> {
+    fn send(any_task: AnyTask) -> Self {
+        Self::Send {
+            any_task,
+            result_type: PhantomData,
+        }
+    }
+}
+
+impl<T: fmt::Debug> fmt::Debug for Task<T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Task::Ready(value) => value.fmt(f),
+            Task::Local { any_task, .. } => any_task.fmt(f),
+            Task::Send { any_task, .. } => any_task.fmt(f),
+        }
+    }
+}
+
+impl<T: 'static> Future for Task<T> {
+    type Output = T;
+
+    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+        match unsafe { self.get_unchecked_mut() } {
+            Task::Ready(value) => Poll::Ready(value.take().unwrap()),
+            Task::Local { any_task, .. } => {
+                any_task.poll(cx).map(|value| *value.downcast().unwrap())
+            }
+            Task::Send { any_task, .. } => {
+                any_task.poll(cx).map(|value| *value.downcast().unwrap())
+            }
+        }
+    }
+}
+
+fn any_future<T, F>(future: F) -> AnyFuture
+where
+    T: 'static + Send,
+    F: Future<Output = T> + Send + 'static,
+{
+    async { Box::new(future.await) as Box<dyn Any + Send> }.boxed()
+}
+
+fn any_local_future<T, F>(future: F) -> AnyLocalFuture
+where
+    T: 'static,
+    F: Future<Output = T> + 'static,
+{
+    async { Box::new(future.await) as Box<dyn Any> }.boxed_local()
+}

crates/gpui3/src/fonts.rs πŸ”—

@@ -1,4 +1,4 @@
-use crate::{px, Bounds, LineWrapper, Pixels, PlatformFontSystem, Result, Size};
+use crate::{px, Bounds, LineWrapper, Pixels, PlatformTextSystem, Result, Size};
 use anyhow::anyhow;
 pub use font_kit::properties::{
     Properties as FontProperties, Stretch as FontStretch, Style as FontStyle, Weight as FontWeight,
@@ -79,7 +79,7 @@ struct Family {
 pub struct FontCache(RwLock<FontCacheState>);
 
 pub struct FontCacheState {
-    font_system: Arc<dyn PlatformFontSystem>,
+    font_system: Arc<dyn PlatformTextSystem>,
     families: Vec<Family>,
     default_family: Option<FontFamilyId>,
     font_selections: HashMap<FontFamilyId, HashMap<(FontWeight, FontStyle), FontId>>,
@@ -90,7 +90,7 @@ pub struct FontCacheState {
 unsafe impl Send for FontCache {}
 
 impl FontCache {
-    pub fn new(fonts: Arc<dyn PlatformFontSystem>) -> Self {
+    pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
         Self(RwLock::new(FontCacheState {
             font_system: fonts,
             families: Default::default(),

crates/gpui3/src/geometry.rs πŸ”—

@@ -20,6 +20,13 @@ impl<T: Clone + Debug> Point<T> {
     pub fn new(x: T, y: T) -> Self {
         Self { x, y }
     }
+
+    pub fn map<U: Clone + Debug, F: Fn(T) -> U>(&self, f: F) -> Point<U> {
+        Point {
+            x: f(self.x.clone()),
+            y: f(self.y.clone()),
+        }
+    }
 }
 
 impl<T: Clone + Debug> Clone for Point<T> {
@@ -42,6 +49,10 @@ pub struct Size<T: Clone + Debug> {
     pub height: T,
 }
 
+pub fn size<T: Clone + Debug>(width: T, height: T) -> Size<T> {
+    Size { width, height }
+}
+
 impl Size<Length> {
     pub fn full() -> Self {
         Self {
@@ -157,6 +168,12 @@ impl Edges<Pixels> {
 #[repr(transparent)]
 pub struct Pixels(pub(crate) f32);
 
+impl From<Pixels> for f64 {
+    fn from(pixels: Pixels) -> Self {
+        pixels.0.into()
+    }
+}
+
 impl Mul<f32> for Pixels {
     type Output = Pixels;
 
@@ -326,11 +343,11 @@ pub fn relative<T: From<DefiniteLength>>(fraction: f32) -> T {
 }
 
 pub fn rems(rems: f32) -> Rems {
-    Rems(rems).into()
+    Rems(rems)
 }
 
 pub fn px(pixels: f32) -> Pixels {
-    Pixels(pixels).into()
+    Pixels(pixels)
 }
 
 pub fn auto() -> Length {

crates/gpui3/src/gpui3.rs πŸ”—

@@ -2,6 +2,7 @@ mod app;
 mod color;
 mod element;
 mod elements;
+mod executor;
 mod fonts;
 mod geometry;
 mod platform;
@@ -10,6 +11,7 @@ mod scene;
 mod style;
 mod taffy;
 mod text;
+mod util;
 mod window;
 
 use anyhow::Result;
@@ -17,10 +19,12 @@ pub use app::*;
 pub use color::*;
 pub use element::*;
 pub use elements::*;
+pub use executor::*;
 pub use fonts::*;
 pub use geometry::*;
 pub use platform::*;
 pub use scene::*;
+pub use smol::Timer;
 use std::ops::{Deref, DerefMut};
 pub use style::*;
 pub use taffy::LayoutId;
@@ -47,6 +51,12 @@ pub trait Context {
 #[derive(Clone, Eq, PartialEq)]
 pub struct SharedString(ArcCow<'static, str>);
 
+impl AsRef<str> for SharedString {
+    fn as_ref(&self) -> &str {
+        &self.0
+    }
+}
+
 impl std::fmt::Debug for SharedString {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         self.0.fmt(f)
@@ -98,7 +108,7 @@ mod tests {
         let workspace = cx.entity(|cx| Workspace {
             left_panel: collab_panel(cx).into_any(),
         });
-        view(workspace, |workspace, cx| {
+        view(workspace, |workspace, _cx| {
             div().child(workspace.left_panel.clone())
         })
     }
@@ -109,7 +119,7 @@ mod tests {
 
     fn collab_panel(cx: &mut WindowContext) -> View<CollabPanel> {
         let panel = cx.entity(|cx| CollabPanel::new(cx));
-        view(panel, |panel, cx| {
+        view(panel, |panel, _cx| {
             div().child(div()).child(
                 field(panel.filter_editor.clone()).placeholder_text("Search channels, contacts"),
             )
@@ -124,14 +134,6 @@ mod tests {
         }
     }
 
-    struct Editor {}
-
-    impl Editor {
-        pub fn new(cx: &mut ViewContext<Self>) -> Self {
-            Self {}
-        }
-    }
-
     #[test]
     fn test() {
         let mut cx = AppContext::test();

crates/gpui3/src/platform.rs πŸ”—

@@ -1,17 +1,30 @@
+mod events;
+mod keystroke;
+#[cfg(target_os = "macos")]
+mod mac;
 #[cfg(any(test, feature = "test"))]
 mod test;
+
 use crate::{
     AnyWindowHandle, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, GlyphId,
-    LineLayout, Pixels, Point, RunStyle, SharedString,
+    LineLayout, Pixels, Point, RunStyle, SharedString, Size,
 };
+use async_task::Runnable;
+use futures::channel::oneshot;
 use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
-use std::sync::Arc;
+use std::{any::Any, fmt::Debug, ops::Range, rc::Rc, sync::Arc};
+use uuid::Uuid;
 
+pub use events::*;
+pub use keystroke::*;
+#[cfg(target_os = "macos")]
+pub use mac::*;
 #[cfg(any(test, feature = "test"))]
 pub use test::*;
 
 pub trait Platform {
-    fn font_system(&self) -> Arc<dyn PlatformFontSystem>;
+    fn dispatcher(&self) -> Arc<dyn PlatformDispatcher>;
+    fn font_system(&self) -> Arc<dyn PlatformTextSystem>;
 
     fn open_window(
         &self,
@@ -20,9 +33,53 @@ pub trait Platform {
     ) -> Box<dyn PlatformWindow>;
 }
 
-pub trait PlatformWindow: HasRawWindowHandle + HasRawDisplayHandle {}
+pub trait PlatformScreen: Debug {
+    fn as_any(&self) -> &dyn Any;
+    fn bounds(&self) -> Bounds<Pixels>;
+    fn content_bounds(&self) -> Bounds<Pixels>;
+    fn display_uuid(&self) -> Option<Uuid>;
+}
+
+pub trait PlatformWindow: HasRawWindowHandle + HasRawDisplayHandle {
+    fn bounds(&self) -> WindowBounds;
+    fn content_size(&self) -> Size<Pixels>;
+    fn scale_factor(&self) -> f32;
+    fn titlebar_height(&self) -> Pixels;
+    fn appearance(&self) -> WindowAppearance;
+    fn screen(&self) -> Rc<dyn PlatformScreen>;
+    fn mouse_position(&self) -> Point<Pixels>;
+    fn as_any_mut(&mut self) -> &mut dyn Any;
+    fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
+    fn prompt(
+        &self,
+        level: WindowPromptLevel,
+        msg: &str,
+        answers: &[&str],
+    ) -> oneshot::Receiver<usize>;
+    fn activate(&self);
+    fn set_title(&mut self, title: &str);
+    fn set_edited(&mut self, edited: bool);
+    fn show_character_palette(&self);
+    fn minimize(&self);
+    fn zoom(&self);
+    fn toggle_full_screen(&self);
+    fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
+    fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>);
+    fn on_resize(&mut self, callback: Box<dyn FnMut()>);
+    fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>);
+    fn on_moved(&mut self, callback: Box<dyn FnMut()>);
+    fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>);
+    fn on_close(&mut self, callback: Box<dyn FnOnce()>);
+    fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>);
+    fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
+}
+
+pub trait PlatformDispatcher: Send + Sync {
+    fn is_main_thread(&self) -> bool;
+    fn run_on_main_thread(&self, task: Runnable);
+}
 
-pub trait PlatformFontSystem: Send + Sync {
+pub trait PlatformTextSystem: Send + Sync {
     fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()>;
     fn all_families(&self) -> Vec<String>;
     fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>>;
@@ -59,6 +116,21 @@ pub trait PlatformFontSystem: Send + Sync {
     ) -> Vec<usize>;
 }
 
+pub trait InputHandler {
+    fn selected_text_range(&self) -> Option<Range<usize>>;
+    fn marked_text_range(&self) -> Option<Range<usize>>;
+    fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
+    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
+    fn replace_and_mark_text_in_range(
+        &mut self,
+        range_utf16: Option<Range<usize>>,
+        new_text: &str,
+        new_selected_range: Option<Range<usize>>,
+    );
+    fn unmark_text(&mut self);
+    fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<f32>>;
+}
+
 #[derive(Copy, Clone, Debug)]
 pub enum RasterizationOptions {
     Alpha,
@@ -74,6 +146,7 @@ pub struct WindowOptions {
     pub show: bool,
     pub kind: WindowKind,
     pub is_movable: bool,
+    pub screen: Option<Rc<dyn PlatformScreen>>,
 }
 
 impl Default for WindowOptions {
@@ -90,16 +163,16 @@ impl Default for WindowOptions {
             show: true,
             kind: WindowKind::Normal,
             is_movable: true,
+            screen: None,
         }
     }
 }
 
-
 #[derive(Debug, Default)]
 pub struct TitlebarOptions {
     pub title: Option<SharedString>,
     pub appears_transparent: bool,
-    pub traffic_light_position: Option<Point<f32>>,
+    pub traffic_light_position: Option<Point<Pixels>>,
 }
 
 #[derive(Copy, Clone, Debug)]
@@ -127,5 +200,27 @@ pub enum WindowBounds {
     Fullscreen,
     #[default]
     Maximized,
-    Fixed(Bounds<f32>),
+    Fixed(Bounds<Pixels>),
+}
+
+#[derive(Copy, Clone, Debug)]
+pub enum WindowAppearance {
+    Light,
+    VibrantLight,
+    Dark,
+    VibrantDark,
+}
+
+impl Default for WindowAppearance {
+    fn default() -> Self {
+        Self::Light
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Default)]
+pub enum WindowPromptLevel {
+    #[default]
+    Info,
+    Warning,
+    Critical,
 }

crates/gpui3/src/platform/events.rs πŸ”—

@@ -0,0 +1,204 @@
+use crate::{point, Keystroke, Modifiers, Pixels, Point};
+use std::{any::Any, ops::Deref};
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct KeyDownEvent {
+    pub keystroke: Keystroke,
+    pub is_held: bool,
+}
+
+#[derive(Clone, Debug)]
+pub struct KeyUpEvent {
+    pub keystroke: Keystroke,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct ModifiersChangedEvent {
+    pub modifiers: Modifiers,
+}
+
+impl Deref for ModifiersChangedEvent {
+    type Target = Modifiers;
+
+    fn deref(&self) -> &Self::Target {
+        &self.modifiers
+    }
+}
+
+/// The phase of a touch motion event.
+/// Based on the winit enum of the same name,
+#[derive(Clone, Copy, Debug)]
+pub enum TouchPhase {
+    Started,
+    Moved,
+    Ended,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum ScrollDelta {
+    Pixels(Point<Pixels>),
+    Lines(Point<f32>),
+}
+
+impl Default for ScrollDelta {
+    fn default() -> Self {
+        Self::Lines(Default::default())
+    }
+}
+
+impl ScrollDelta {
+    pub fn precise(&self) -> bool {
+        match self {
+            ScrollDelta::Pixels(_) => true,
+            ScrollDelta::Lines(_) => false,
+        }
+    }
+
+    pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
+        match self {
+            ScrollDelta::Pixels(delta) => *delta,
+            ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
+        }
+    }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct ScrollWheelEvent {
+    pub position: Point<Pixels>,
+    pub delta: ScrollDelta,
+    pub modifiers: Modifiers,
+    /// If the platform supports returning the phase of a scroll wheel event, it will be stored here
+    pub phase: Option<TouchPhase>,
+}
+
+impl Deref for ScrollWheelEvent {
+    type Target = Modifiers;
+
+    fn deref(&self) -> &Self::Target {
+        &self.modifiers
+    }
+}
+
+#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
+pub enum NavigationDirection {
+    Back,
+    Forward,
+}
+
+impl Default for NavigationDirection {
+    fn default() -> Self {
+        Self::Back
+    }
+}
+
+#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
+pub enum MouseButton {
+    Left,
+    Right,
+    Middle,
+    Navigate(NavigationDirection),
+}
+
+impl MouseButton {
+    pub fn all() -> Vec<Self> {
+        vec![
+            MouseButton::Left,
+            MouseButton::Right,
+            MouseButton::Middle,
+            MouseButton::Navigate(NavigationDirection::Back),
+            MouseButton::Navigate(NavigationDirection::Forward),
+        ]
+    }
+}
+
+impl Default for MouseButton {
+    fn default() -> Self {
+        Self::Left
+    }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct MouseDownEvent {
+    pub button: MouseButton,
+    pub position: Point<Pixels>,
+    pub modifiers: Modifiers,
+    pub click_count: usize,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct MouseUpEvent {
+    pub button: MouseButton,
+    pub position: Point<Pixels>,
+    pub modifiers: Modifiers,
+    pub click_count: usize,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct MouseUp {
+    pub button: MouseButton,
+    pub position: Point<Pixels>,
+    pub modifiers: Modifiers,
+    pub click_count: usize,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct MouseMovedEvent {
+    pub position: Point<Pixels>,
+    pub pressed_button: Option<MouseButton>,
+    pub modifiers: Modifiers,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct MouseExitedEvent {
+    pub position: Point<Pixels>,
+    pub pressed_button: Option<MouseButton>,
+    pub modifiers: Modifiers,
+}
+
+impl Deref for MouseExitedEvent {
+    type Target = Modifiers;
+
+    fn deref(&self) -> &Self::Target {
+        &self.modifiers
+    }
+}
+
+#[derive(Clone, Debug)]
+pub enum Event {
+    KeyDown(KeyDownEvent),
+    KeyUp(KeyUpEvent),
+    ModifiersChanged(ModifiersChangedEvent),
+    MouseDown(MouseDownEvent),
+    MouseUp(MouseUpEvent),
+    MouseMoved(MouseMovedEvent),
+    MouseExited(MouseExitedEvent),
+    ScrollWheel(ScrollWheelEvent),
+}
+
+impl Event {
+    pub fn position(&self) -> Option<Point<Pixels>> {
+        match self {
+            Event::KeyDown { .. } => None,
+            Event::KeyUp { .. } => None,
+            Event::ModifiersChanged { .. } => None,
+            Event::MouseDown(event) => Some(event.position),
+            Event::MouseUp(event) => Some(event.position),
+            Event::MouseMoved(event) => Some(event.position),
+            Event::MouseExited(event) => Some(event.position),
+            Event::ScrollWheel(event) => Some(event.position),
+        }
+    }
+
+    pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
+        match self {
+            Event::KeyDown { .. } => None,
+            Event::KeyUp { .. } => None,
+            Event::ModifiersChanged { .. } => None,
+            Event::MouseDown(event) => Some(event),
+            Event::MouseUp(event) => Some(event),
+            Event::MouseMoved(event) => Some(event),
+            Event::MouseExited(event) => Some(event),
+            Event::ScrollWheel(event) => Some(event),
+        }
+    }
+}

crates/gpui3/src/platform/keystroke.rs πŸ”—

@@ -0,0 +1,121 @@
+use anyhow::anyhow;
+use serde::Deserialize;
+use std::fmt::Write;
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
+pub struct Modifiers {
+    pub control: bool,
+    pub alt: bool,
+    pub shift: bool,
+    pub command: bool,
+    pub function: bool,
+}
+
+impl Modifiers {
+    pub fn modified(&self) -> bool {
+        self.control || self.alt || self.shift || self.command || self.function
+    }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Hash)]
+pub struct Keystroke {
+    pub key: String,
+    pub modifiers: Modifiers,
+}
+
+impl Keystroke {
+    pub fn parse(source: &str) -> anyhow::Result<Self> {
+        let mut control = false;
+        let mut alt = false;
+        let mut shift = false;
+        let mut command = false;
+        let mut function = false;
+        let mut key = None;
+
+        let mut components = source.split('-').peekable();
+        while let Some(component) = components.next() {
+            match component {
+                "ctrl" => control = true,
+                "alt" => alt = true,
+                "shift" => shift = true,
+                "cmd" => command = true,
+                "fn" => function = true,
+                _ => {
+                    if let Some(component) = components.peek() {
+                        if component.is_empty() && source.ends_with('-') {
+                            key = Some(String::from("-"));
+                            break;
+                        } else {
+                            return Err(anyhow!("Invalid keystroke `{}`", source));
+                        }
+                    } else {
+                        key = Some(String::from(component));
+                    }
+                }
+            }
+        }
+
+        let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
+
+        Ok(Keystroke {
+            modifiers: Modifiers {
+                control,
+                alt,
+                shift,
+                command,
+                function,
+            },
+            key,
+        })
+    }
+
+    pub fn modified(&self) -> bool {
+        self.modifiers.modified()
+    }
+}
+
+impl std::fmt::Display for Keystroke {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let Modifiers {
+            control,
+            alt,
+            shift,
+            command,
+            function,
+        } = self.modifiers;
+
+        if control {
+            f.write_char('^')?;
+        }
+        if alt {
+            f.write_char('βŽ‡')?;
+        }
+        if command {
+            f.write_char('⌘')?;
+        }
+        if shift {
+            f.write_char('⇧')?;
+        }
+        if function {
+            f.write_char('𝙛')?;
+        }
+
+        let key = match self.key.as_str() {
+            "backspace" => '⌫',
+            "up" => '↑',
+            "down" => '↓',
+            "left" => '←',
+            "right" => 'β†’',
+            "tab" => 'β‡₯',
+            "escape" => 'βŽ‹',
+            key => {
+                if key.len() == 1 {
+                    key.chars().next().unwrap().to_ascii_uppercase()
+                } else {
+                    return f.write_str(key);
+                }
+            }
+        };
+        f.write_char(key)
+    }
+}

crates/gpui3/src/platform/mac.rs πŸ”—

@@ -0,0 +1,144 @@
+///! Macos screen have a y axis that goings up from the bottom of the screen and
+///! an origin at the bottom left of the main display.
+mod dispatcher;
+mod events;
+mod platform;
+mod screen;
+mod window;
+mod window_appearence;
+
+use std::{
+    ffi::{c_char, CStr, OsStr},
+    ops::Range,
+    os::unix::prelude::OsStrExt,
+    path::PathBuf,
+};
+
+use crate::{px, size, Pixels, Size};
+use anyhow::anyhow;
+use cocoa::{
+    base::{id, nil},
+    foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger, NSURL},
+};
+use objc::{
+    msg_send,
+    runtime::{BOOL, NO, YES},
+    sel, sel_impl,
+};
+pub use platform::*;
+pub use screen::*;
+pub use window::*;
+use window_appearence::*;
+
+trait BoolExt {
+    fn to_objc(self) -> BOOL;
+}
+
+impl BoolExt for bool {
+    fn to_objc(self) -> BOOL {
+        if self {
+            YES
+        } else {
+            NO
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+struct NSRange {
+    pub location: NSUInteger,
+    pub length: NSUInteger,
+}
+
+impl NSRange {
+    fn invalid() -> Self {
+        Self {
+            location: NSNotFound as NSUInteger,
+            length: 0,
+        }
+    }
+
+    fn is_valid(&self) -> bool {
+        self.location != NSNotFound as NSUInteger
+    }
+
+    fn to_range(self) -> Option<Range<usize>> {
+        if self.is_valid() {
+            let start = self.location as usize;
+            let end = start + self.length as usize;
+            Some(start..end)
+        } else {
+            None
+        }
+    }
+}
+
+impl From<Range<usize>> for NSRange {
+    fn from(range: Range<usize>) -> Self {
+        NSRange {
+            location: range.start as NSUInteger,
+            length: range.len() as NSUInteger,
+        }
+    }
+}
+
+unsafe impl objc::Encode for NSRange {
+    fn encode() -> objc::Encoding {
+        let encoding = format!(
+            "{{NSRange={}{}}}",
+            NSUInteger::encode().as_str(),
+            NSUInteger::encode().as_str()
+        );
+        unsafe { objc::Encoding::from_str(&encoding) }
+    }
+}
+
+unsafe fn ns_string(string: &str) -> id {
+    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),
+        }
+    }
+}
+
+pub trait NSRectExt {
+    fn size(&self) -> Size<Pixels>;
+    fn intersects(&self, other: Self) -> bool;
+}
+
+impl NSRectExt for NSRect {
+    fn size(&self) -> Size<Pixels> {
+        size(px(self.size.width as f32), px(self.size.height as f32))
+    }
+
+    fn intersects(&self, other: Self) -> bool {
+        self.size.width > 0.
+            && self.size.height > 0.
+            && other.size.width > 0.
+            && other.size.height > 0.
+            && self.origin.x <= other.origin.x + other.size.width
+            && self.origin.x + self.size.width >= other.origin.x
+            && self.origin.y <= other.origin.y + other.size.height
+            && self.origin.y + self.size.height >= other.origin.y
+    }
+}
+
+unsafe fn ns_url_to_path(url: id) -> crate::Result<PathBuf> {
+    let path: *mut c_char = msg_send![url, fileSystemRepresentation];
+    if path.is_null() {
+        Err(anyhow!(
+            "url is not a file path: {}",
+            CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
+        ))
+    } else {
+        Ok(PathBuf::from(OsStr::from_bytes(
+            CStr::from_ptr(path).to_bytes(),
+        )))
+    }
+}

crates/gpui3/src/platform/mac/dispatcher.rs πŸ”—

@@ -0,0 +1,42 @@
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+use crate::PlatformDispatcher;
+use async_task::Runnable;
+use objc::{
+    class, msg_send,
+    runtime::{BOOL, YES},
+    sel, sel_impl,
+};
+use std::ffi::c_void;
+
+include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
+
+pub fn dispatch_get_main_queue() -> dispatch_queue_t {
+    unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
+}
+
+pub struct MacDispatcher;
+
+impl PlatformDispatcher for MacDispatcher {
+    fn is_main_thread(&self) -> bool {
+        let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
+        is_main_thread == YES
+    }
+
+    fn run_on_main_thread(&self, runnable: Runnable) {
+        unsafe {
+            dispatch_async_f(
+                dispatch_get_main_queue(),
+                runnable.into_raw() as *mut c_void,
+                Some(trampoline),
+            );
+        }
+
+        extern "C" fn trampoline(runnable: *mut c_void) {
+            let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
+            task.run();
+        }
+    }
+}

crates/gpui3/src/platform/mac/events.rs πŸ”—

@@ -0,0 +1,354 @@
+use crate::{
+    point, px, Event, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
+    MouseButton, MouseDownEvent, MouseExitedEvent, MouseMovedEvent, MouseUpEvent,
+    NavigationDirection, Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase,
+};
+use cocoa::{
+    appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
+    base::{id, YES},
+    foundation::NSString as _,
+};
+use core_graphics::{
+    event::{CGEvent, CGEventFlags, CGKeyCode},
+    event_source::{CGEventSource, CGEventSourceStateID},
+};
+use ctor::ctor;
+use foreign_types::ForeignType;
+use objc::{class, msg_send, sel, sel_impl};
+use std::{borrow::Cow, ffi::CStr, mem, os::raw::c_char, ptr};
+
+const BACKSPACE_KEY: u16 = 0x7f;
+const SPACE_KEY: u16 = b' ' as u16;
+const ENTER_KEY: u16 = 0x0d;
+const NUMPAD_ENTER_KEY: u16 = 0x03;
+const ESCAPE_KEY: u16 = 0x1b;
+const TAB_KEY: u16 = 0x09;
+const SHIFT_TAB_KEY: u16 = 0x19;
+
+static mut EVENT_SOURCE: core_graphics::sys::CGEventSourceRef = ptr::null_mut();
+
+#[ctor]
+unsafe fn build_event_source() {
+    let source = CGEventSource::new(CGEventSourceStateID::Private).unwrap();
+    EVENT_SOURCE = source.as_ptr();
+    mem::forget(source);
+}
+
+pub fn key_to_native(key: &str) -> Cow<str> {
+    use cocoa::appkit::*;
+    let code = match key {
+        "space" => SPACE_KEY,
+        "backspace" => BACKSPACE_KEY,
+        "up" => NSUpArrowFunctionKey,
+        "down" => NSDownArrowFunctionKey,
+        "left" => NSLeftArrowFunctionKey,
+        "right" => NSRightArrowFunctionKey,
+        "pageup" => NSPageUpFunctionKey,
+        "pagedown" => NSPageDownFunctionKey,
+        "home" => NSHomeFunctionKey,
+        "end" => NSEndFunctionKey,
+        "delete" => NSDeleteFunctionKey,
+        "f1" => NSF1FunctionKey,
+        "f2" => NSF2FunctionKey,
+        "f3" => NSF3FunctionKey,
+        "f4" => NSF4FunctionKey,
+        "f5" => NSF5FunctionKey,
+        "f6" => NSF6FunctionKey,
+        "f7" => NSF7FunctionKey,
+        "f8" => NSF8FunctionKey,
+        "f9" => NSF9FunctionKey,
+        "f10" => NSF10FunctionKey,
+        "f11" => NSF11FunctionKey,
+        "f12" => NSF12FunctionKey,
+        _ => return Cow::Borrowed(key),
+    };
+    Cow::Owned(String::from_utf16(&[code]).unwrap())
+}
+
+unsafe fn read_modifiers(native_event: id) -> Modifiers {
+    let modifiers = native_event.modifierFlags();
+    let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
+    let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
+    let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
+    let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
+    let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
+
+    Modifiers {
+        control,
+        alt,
+        shift,
+        command,
+        function,
+    }
+}
+
+impl Event {
+    pub unsafe fn from_native(native_event: id, window_height: Option<Pixels>) -> Option<Self> {
+        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;
+            }
+            _ => {}
+        }
+
+        match event_type {
+            NSEventType::NSFlagsChanged => Some(Self::ModifiersChanged(ModifiersChangedEvent {
+                modifiers: read_modifiers(native_event),
+            })),
+            NSEventType::NSKeyDown => Some(Self::KeyDown(KeyDownEvent {
+                keystroke: parse_keystroke(native_event),
+                is_held: native_event.isARepeat() == YES,
+            })),
+            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,
+                    })
+                })
+            }
+            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::NSScrollWheel => window_height.map(|window_height| {
+                let phase = match native_event.phase() {
+                    NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
+                        Some(TouchPhase::Started)
+                    }
+                    NSEventPhase::NSEventPhaseEnded => Some(TouchPhase::Ended),
+                    _ => Some(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 {
+                    position: point(
+                        px(native_event.locationInWindow().x as f32),
+                        window_height - px(native_event.locationInWindow().y as f32),
+                    ),
+                    delta,
+                    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| {
+                    Self::MouseMoved(MouseMovedEvent {
+                        pressed_button: Some(pressed_button),
+                        position: point(
+                            px(native_event.locationInWindow().x as f32),
+                            window_height - px(native_event.locationInWindow().y as f32),
+                        ),
+                        modifiers: read_modifiers(native_event),
+                    })
+                })
+            }
+            NSEventType::NSMouseMoved => window_height.map(|window_height| {
+                Self::MouseMoved(MouseMovedEvent {
+                    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(MouseExitedEvent {
+                    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,
+        }
+    }
+}
+
+unsafe fn parse_keystroke(native_event: id) -> Keystroke {
+    use cocoa::appkit::*;
+
+    let mut chars_ignoring_modifiers =
+        CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char)
+            .to_str()
+            .unwrap()
+            .to_string();
+    let first_char = chars_ignoring_modifiers.chars().next().map(|ch| ch as u16);
+    let modifiers = native_event.modifierFlags();
+
+    let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
+    let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
+    let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
+    let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
+    let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask)
+        && first_char.map_or(true, |ch| {
+            !(NSUpArrowFunctionKey..=NSModeSwitchFunctionKey).contains(&ch)
+        });
+
+    #[allow(non_upper_case_globals)]
+    let key = match first_char {
+        Some(SPACE_KEY) => "space".to_string(),
+        Some(BACKSPACE_KEY) => "backspace".to_string(),
+        Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter".to_string(),
+        Some(ESCAPE_KEY) => "escape".to_string(),
+        Some(TAB_KEY) => "tab".to_string(),
+        Some(SHIFT_TAB_KEY) => "tab".to_string(),
+        Some(NSUpArrowFunctionKey) => "up".to_string(),
+        Some(NSDownArrowFunctionKey) => "down".to_string(),
+        Some(NSLeftArrowFunctionKey) => "left".to_string(),
+        Some(NSRightArrowFunctionKey) => "right".to_string(),
+        Some(NSPageUpFunctionKey) => "pageup".to_string(),
+        Some(NSPageDownFunctionKey) => "pagedown".to_string(),
+        Some(NSHomeFunctionKey) => "home".to_string(),
+        Some(NSEndFunctionKey) => "end".to_string(),
+        Some(NSDeleteFunctionKey) => "delete".to_string(),
+        Some(NSF1FunctionKey) => "f1".to_string(),
+        Some(NSF2FunctionKey) => "f2".to_string(),
+        Some(NSF3FunctionKey) => "f3".to_string(),
+        Some(NSF4FunctionKey) => "f4".to_string(),
+        Some(NSF5FunctionKey) => "f5".to_string(),
+        Some(NSF6FunctionKey) => "f6".to_string(),
+        Some(NSF7FunctionKey) => "f7".to_string(),
+        Some(NSF8FunctionKey) => "f8".to_string(),
+        Some(NSF9FunctionKey) => "f9".to_string(),
+        Some(NSF10FunctionKey) => "f10".to_string(),
+        Some(NSF11FunctionKey) => "f11".to_string(),
+        Some(NSF12FunctionKey) => "f12".to_string(),
+        _ => {
+            let mut chars_ignoring_modifiers_and_shift =
+                chars_for_modified_key(native_event.keyCode(), false, false);
+
+            // Honor ⌘ when Dvorak-QWERTY is used.
+            let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), true, false);
+            if command && chars_ignoring_modifiers_and_shift != chars_with_cmd {
+                chars_ignoring_modifiers =
+                    chars_for_modified_key(native_event.keyCode(), true, shift);
+                chars_ignoring_modifiers_and_shift = chars_with_cmd;
+            }
+
+            if shift {
+                if chars_ignoring_modifiers_and_shift
+                    == chars_ignoring_modifiers.to_ascii_lowercase()
+                {
+                    chars_ignoring_modifiers_and_shift
+                } else if chars_ignoring_modifiers_and_shift != chars_ignoring_modifiers {
+                    shift = false;
+                    chars_ignoring_modifiers
+                } else {
+                    chars_ignoring_modifiers
+                }
+            } else {
+                chars_ignoring_modifiers
+            }
+        }
+    };
+
+    Keystroke {
+        modifiers: Modifiers {
+            control,
+            alt,
+            shift,
+            command,
+            function,
+        },
+        key,
+    }
+}
+
+fn chars_for_modified_key(code: CGKeyCode, cmd: bool, shift: bool) -> String {
+    // Ideally, we would use `[NSEvent charactersByApplyingModifiers]` but that
+    // always returns an empty string with certain keyboards, e.g. Japanese. Synthesizing
+    // an event with the given flags instead lets us access `characters`, which always
+    // returns a valid string.
+    let source = unsafe { core_graphics::event_source::CGEventSource::from_ptr(EVENT_SOURCE) };
+    let event = CGEvent::new_keyboard_event(source.clone(), code, true).unwrap();
+    mem::forget(source);
+
+    let mut flags = CGEventFlags::empty();
+    if cmd {
+        flags |= CGEventFlags::CGEventFlagCommand;
+    }
+    if shift {
+        flags |= CGEventFlags::CGEventFlagShift;
+    }
+    event.set_flags(flags);
+
+    unsafe {
+        let event: id = msg_send![class!(NSEvent), eventWithCGEvent: &*event];
+        CStr::from_ptr(event.characters().UTF8String())
+            .to_str()
+            .unwrap()
+            .to_string()
+    }
+}

crates/gpui3/src/platform/mac/platform.rs πŸ”—

@@ -0,0 +1,1102 @@
+// use anyhow::{anyhow, Result};
+// use block::ConcreteBlock;
+// use cocoa::{
+//     appkit::{
+//         NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
+//         NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
+//         NSSavePanel, NSWindow,
+//     },
+//     base::{id, nil, selector, BOOL, YES},
+//     foundation::{NSArray, NSAutoreleasePool, NSData, NSInteger, NSString, NSUInteger, NSURL},
+// };
+// use core_foundation::{
+//     base::{CFTypeRef, OSStatus},
+//     dictionary::CFDictionaryRef,
+//     string::CFStringRef,
+// };
+// use ctor::ctor;
+// use objc::{
+//     class,
+//     declare::ClassDecl,
+//     msg_send,
+//     runtime::{Class, Object, Sel},
+//     sel, sel_impl,
+// };
+
+// use postage::oneshot;
+// use ptr::null_mut;
+// use std::{
+//     cell::{Cell, RefCell},
+//     convert::TryInto,
+//     ffi::{c_void, CStr, OsStr},
+//     os::{raw::c_char, unix::ffi::OsStrExt},
+//     path::{Path, PathBuf},
+//     process::Command,
+//     ptr,
+//     rc::Rc,
+//     slice, str,
+//     sync::Arc,
+// };
+
+// use crate::Event;
+
+// #[allow(non_upper_case_globals)]
+// const NSUTF8StringEncoding: NSUInteger = 4;
+
+// const MAC_PLATFORM_IVAR: &str = "platform";
+// static mut APP_CLASS: *const Class = ptr::null();
+// static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
+
+// #[ctor]
+// unsafe fn build_classes() {
+//     APP_CLASS = {
+//         let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
+//         decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
+//         decl.add_method(
+//             sel!(sendEvent:),
+//             send_event as extern "C" fn(&mut Object, Sel, id),
+//         );
+//         decl.register()
+//     };
+
+//     APP_DELEGATE_CLASS = {
+//         let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
+//         decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
+//         decl.add_method(
+//             sel!(applicationDidFinishLaunching:),
+//             did_finish_launching as extern "C" fn(&mut Object, Sel, id),
+//         );
+//         decl.add_method(
+//             sel!(applicationShouldHandleReopen:hasVisibleWindows:),
+//             should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool),
+//         );
+//         decl.add_method(
+//             sel!(applicationDidBecomeActive:),
+//             did_become_active as extern "C" fn(&mut Object, Sel, id),
+//         );
+//         decl.add_method(
+//             sel!(applicationDidResignActive:),
+//             did_resign_active as extern "C" fn(&mut Object, Sel, id),
+//         );
+//         decl.add_method(
+//             sel!(applicationWillTerminate:),
+//             will_terminate as extern "C" fn(&mut Object, Sel, id),
+//         );
+//         decl.add_method(
+//             sel!(handleGPUIMenuItem:),
+//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+//         );
+//         // Add menu item handlers so that OS save panels have the correct key commands
+//         decl.add_method(
+//             sel!(cut:),
+//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+//         );
+//         decl.add_method(
+//             sel!(copy:),
+//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+//         );
+//         decl.add_method(
+//             sel!(paste:),
+//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+//         );
+//         decl.add_method(
+//             sel!(selectAll:),
+//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+//         );
+//         decl.add_method(
+//             sel!(undo:),
+//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+//         );
+//         decl.add_method(
+//             sel!(redo:),
+//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+//         );
+//         decl.add_method(
+//             sel!(validateMenuItem:),
+//             validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,
+//         );
+//         decl.add_method(
+//             sel!(menuWillOpen:),
+//             menu_will_open as extern "C" fn(&mut Object, Sel, id),
+//         );
+//         decl.add_method(
+//             sel!(application:openURLs:),
+//             open_urls as extern "C" fn(&mut Object, Sel, id, id),
+//         );
+//         decl.register()
+//     }
+// }
+
+// pub struct MacForegroundPlatformState {
+//     dispatcher: Arc<MacDispatcher>,
+//     fonts: Arc<MacFontSystem>,
+//     pasteboard: id,
+//     text_hash_pasteboard_type: id,
+//     metadata_pasteboard_type: id,
+//     become_active: Option<Box<dyn FnMut()>>,
+//     resign_active: Option<Box<dyn FnMut()>>,
+//     reopen: Option<Box<dyn FnMut()>>,
+//     quit: Option<Box<dyn FnMut()>>,
+//     event: Option<Box<dyn FnMut(Event) -> bool>>,
+//     // menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
+//     validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
+//     will_open_menu: Option<Box<dyn FnMut()>>,
+//     open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
+//     finish_launching: Option<Box<dyn FnOnce()>>,
+//     // menu_actions: Vec<Box<dyn Action>>,
+//     // foreground: Rc<executor::Foreground>,
+// }
+
+// impl MacForegroundPlatform {
+//     pub fn new(foreground: Rc<executor::Foreground>) -> Self {
+//         Self(RefCell::new(MacForegroundPlatformState {
+//             become_active: None,
+//             resign_active: None,
+//             reopen: None,
+//             quit: None,
+//             event: None,
+//             // menu_command: None,
+//             validate_menu_command: None,
+//             will_open_menu: None,
+//             open_urls: None,
+//             finish_launching: None,
+//             menu_actions: Default::default(),
+//             foreground,
+//         }))
+//     }
+
+//     unsafe fn create_menu_bar(
+//         &self,
+//         menus: Vec<Menu>,
+//         delegate: id,
+//         actions: &mut Vec<Box<dyn Action>>,
+//         keystroke_matcher: &KeymapMatcher,
+//     ) -> id {
+//         let application_menu = NSMenu::new(nil).autorelease();
+//         application_menu.setDelegate_(delegate);
+
+//         for menu_config in menus {
+//             let menu = NSMenu::new(nil).autorelease();
+//             menu.setTitle_(ns_string(menu_config.name));
+//             menu.setDelegate_(delegate);
+
+//             for item_config in menu_config.items {
+//                 menu.addItem_(self.create_menu_item(
+//                     item_config,
+//                     delegate,
+//                     actions,
+//                     keystroke_matcher,
+//                 ));
+//             }
+
+//             let menu_item = NSMenuItem::new(nil).autorelease();
+//             menu_item.setSubmenu_(menu);
+//             application_menu.addItem_(menu_item);
+
+//             if menu_config.name == "Window" {
+//                 let app: id = msg_send![APP_CLASS, sharedApplication];
+//                 app.setWindowsMenu_(menu);
+//             }
+//         }
+
+//         application_menu
+//     }
+
+//     // unsafe fn create_menu_item(
+//     //     &self,
+//     //     item: MenuItem,
+//     //     delegate: id,
+//     //     actions: &mut Vec<Box<dyn Action>>,
+//     //     keystroke_matcher: &KeymapMatcher,
+//     // ) -> id {
+//     //     match item {
+//     //         MenuItem::Separator => NSMenuItem::separatorItem(nil),
+//     //         MenuItem::Action {
+//     //             name,
+//     //             action,
+//     //             os_action,
+//     //         } => {
+//     //             // TODO
+//     //             let keystrokes = keystroke_matcher
+//     //                 .bindings_for_action(action.id())
+//     //                 .find(|binding| binding.action().eq(action.as_ref()))
+//     //                 .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(crate::OsAction::Undo) => selector("undo:"),
+//     //                 Some(crate::OsAction::Redo) => selector("redo:"),
+//     //                 None => selector("handleGPUIMenuItem:"),
+//     //             };
+
+//     //             let item;
+//     //             if let Some(keystrokes) = keystrokes {
+//     //                 if keystrokes.len() == 1 {
+//     //                     let keystroke = &keystrokes[0];
+//     //                     let mut mask = NSEventModifierFlags::empty();
+//     //                     for (modifier, flag) in &[
+//     //                         (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
+//     //                         (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
+//     //                         (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
+//     //                         (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),
+//     //                     ] {
+//     //                         if *modifier {
+//     //                             mask |= *flag;
+//     //                         }
+//     //                     }
+
+//     //                     item = NSMenuItem::alloc(nil)
+//     //                         .initWithTitle_action_keyEquivalent_(
+//     //                             ns_string(name),
+//     //                             selector,
+//     //                             ns_string(key_to_native(&keystroke.key).as_ref()),
+//     //                         )
+//     //                         .autorelease();
+//     //                     item.setKeyEquivalentModifierMask_(mask);
+//     //                 }
+//     //                 // For multi-keystroke bindings, render the keystroke as part of the title.
+//     //                 else {
+//     //                     use std::fmt::Write;
+
+//     //                     let mut name = format!("{name} [");
+//     //                     for (i, keystroke) in keystrokes.iter().enumerate() {
+//     //                         if i > 0 {
+//     //                             name.push(' ');
+//     //                         }
+//     //                         write!(&mut name, "{}", keystroke).unwrap();
+//     //                     }
+//     //                     name.push(']');
+
+//     //                     item = NSMenuItem::alloc(nil)
+//     //                         .initWithTitle_action_keyEquivalent_(
+//     //                             ns_string(&name),
+//     //                             selector,
+//     //                             ns_string(""),
+//     //                         )
+//     //                         .autorelease();
+//     //                 }
+//     //             } else {
+//     //                 item = NSMenuItem::alloc(nil)
+//     //                     .initWithTitle_action_keyEquivalent_(
+//     //                         ns_string(name),
+//     //                         selector,
+//     //                         ns_string(""),
+//     //                     )
+//     //                     .autorelease();
+//     //             }
+
+//     //             let tag = actions.len() as NSInteger;
+//     //             let _: () = msg_send![item, setTag: tag];
+//     //             actions.push(action);
+//     //             item
+//     //         }
+//     //         MenuItem::Submenu(Menu { name, items }) => {
+//     //             let item = NSMenuItem::new(nil).autorelease();
+//     //             let submenu = NSMenu::new(nil).autorelease();
+//     //             submenu.setDelegate_(delegate);
+//     //             for item in items {
+//     //                 submenu.addItem_(self.create_menu_item(
+//     //                     item,
+//     //                     delegate,
+//     //                     actions,
+//     //                     keystroke_matcher,
+//     //                 ));
+//     //             }
+//     //             item.setSubmenu_(submenu);
+//     //             item.setTitle_(ns_string(name));
+//     //             item
+//     //         }
+//     //     }
+//     // }
+
+//     unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> {
+//         let data = self.pasteboard.dataForType(kind);
+//         if data == nil {
+//             None
+//         } else {
+//             Some(slice::from_raw_parts(
+//                 data.bytes() as *mut u8,
+//                 data.length() as usize,
+//             ))
+//         }
+//     }
+// }
+
+// // impl platform::ForegroundPlatform for MacForegroundPlatform {
+// //     fn on_become_active(&self, callback: Box<dyn FnMut()>) {
+// //         self.0.borrow_mut().become_active = Some(callback);
+// //     }
+
+// //     fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
+// //         self.0.borrow_mut().resign_active = Some(callback);
+// //     }
+
+// //     fn on_quit(&self, callback: Box<dyn FnMut()>) {
+// //         self.0.borrow_mut().quit = Some(callback);
+// //     }
+
+// //     fn on_reopen(&self, callback: Box<dyn FnMut()>) {
+// //         self.0.borrow_mut().reopen = Some(callback);
+// //     }
+
+// //     fn on_event(&self, callback: Box<dyn FnMut(platform::Event) -> bool>) {
+// //         self.0.borrow_mut().event = Some(callback);
+// //     }
+
+// //     fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
+// //         self.0.borrow_mut().open_urls = Some(callback);
+// //     }
+
+// //     fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
+// //         self.0.borrow_mut().finish_launching = Some(on_finish_launching);
+
+// //         unsafe {
+// //             let app: id = msg_send![APP_CLASS, sharedApplication];
+// //             let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
+// //             app.setDelegate_(app_delegate);
+
+// //             let self_ptr = self as *const Self as *const c_void;
+// //             (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
+// //             (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
+
+// //             let pool = NSAutoreleasePool::new(nil);
+// //             app.run();
+// //             pool.drain();
+
+// //             (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
+// //             (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
+// //         }
+// //     }
+
+// //     fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
+// //         self.0.borrow_mut().menu_command = Some(callback);
+// //     }
+
+// //     fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
+// //         self.0.borrow_mut().will_open_menu = Some(callback);
+// //     }
+
+// //     fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
+// //         self.0.borrow_mut().validate_menu_command = Some(callback);
+// //     }
+
+// //     fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
+// //         unsafe {
+// //             let app: id = msg_send![APP_CLASS, sharedApplication];
+// //             let mut state = self.0.borrow_mut();
+// //             let actions = &mut state.menu_actions;
+// //             app.setMainMenu_(self.create_menu_bar(
+// //                 menus,
+// //                 app.delegate(),
+// //                 actions,
+// //                 keystroke_matcher,
+// //             ));
+// //         }
+// //     }
+
+// //     fn prompt_for_paths(
+// //         &self,
+// //         options: platform::PathPromptOptions,
+// //     ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
+// //         unsafe {
+// //             let panel = NSOpenPanel::openPanel(nil);
+// //             panel.setCanChooseDirectories_(options.directories.to_objc());
+// //             panel.setCanChooseFiles_(options.files.to_objc());
+// //             panel.setAllowsMultipleSelection_(options.multiple.to_objc());
+// //             panel.setResolvesAliases_(false.to_objc());
+// //             let (done_tx, done_rx) = oneshot::channel();
+// //             let done_tx = Cell::new(Some(done_tx));
+// //             let block = ConcreteBlock::new(move |response: NSModalResponse| {
+// //                 let result = if response == NSModalResponse::NSModalResponseOk {
+// //                     let mut result = Vec::new();
+// //                     let urls = panel.URLs();
+// //                     for i in 0..urls.count() {
+// //                         let url = urls.objectAtIndex(i);
+// //                         if url.isFileURL() == YES {
+// //                             if let Ok(path) = ns_url_to_path(url) {
+// //                                 result.push(path)
+// //                             }
+// //                         }
+// //                     }
+// //                     Some(result)
+// //                 } else {
+// //                     None
+// //                 };
+
+// //                 if let Some(mut done_tx) = done_tx.take() {
+// //                     let _ = postage::sink::Sink::try_send(&mut done_tx, result);
+// //                 }
+// //             });
+// //             let block = block.copy();
+// //             let _: () = msg_send![panel, beginWithCompletionHandler: block];
+// //             done_rx
+// //         }
+// //     }
+
+// //     fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
+// //         unsafe {
+// //             let panel = NSSavePanel::savePanel(nil);
+// //             let path = ns_string(directory.to_string_lossy().as_ref());
+// //             let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
+// //             panel.setDirectoryURL(url);
+
+// //             let (done_tx, done_rx) = oneshot::channel();
+// //             let done_tx = Cell::new(Some(done_tx));
+// //             let block = ConcreteBlock::new(move |response: NSModalResponse| {
+// //                 let mut result = None;
+// //                 if response == NSModalResponse::NSModalResponseOk {
+// //                     let url = panel.URL();
+// //                     if url.isFileURL() == YES {
+// //                         result = ns_url_to_path(panel.URL()).ok()
+// //                     }
+// //                 }
+
+// //                 if let Some(mut done_tx) = done_tx.take() {
+// //                     let _ = postage::sink::Sink::try_send(&mut done_tx, result);
+// //                 }
+// //             });
+// //             let block = block.copy();
+// //             let _: () = msg_send![panel, beginWithCompletionHandler: block];
+// //             done_rx
+// //         }
+// //     }
+
+// //     fn reveal_path(&self, path: &Path) {
+// //         unsafe {
+// //             let path = path.to_path_buf();
+// //             self.0
+// //                 .borrow()
+// //                 .foreground
+// //                 .spawn(async move {
+// //                     let full_path = ns_string(path.to_str().unwrap_or(""));
+// //                     let root_full_path = ns_string("");
+// //                     let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
+// //                     let _: BOOL = msg_send![
+// //                         workspace,
+// //                         selectFile: full_path
+// //                         inFileViewerRootedAtPath: root_full_path
+// //                     ];
+// //                 })
+// //                 .detach();
+// //         }
+// //     }
+// // }
+
+// // impl Platform for MacPlatform {
+// //     fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
+// //         self.dispatcher.clone()
+// //     }
+
+// //     fn fonts(&self) -> Arc<dyn platform::FontSystem> {
+// //         self.fonts.clone()
+// //     }
+
+// //     fn activate(&self, ignoring_other_apps: bool) {
+// //         unsafe {
+// //             let app = NSApplication::sharedApplication(nil);
+// //             app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
+// //         }
+// //     }
+
+// //     fn hide(&self) {
+// //         unsafe {
+// //             let app = NSApplication::sharedApplication(nil);
+// //             let _: () = msg_send![app, hide: nil];
+// //         }
+// //     }
+
+// //     fn hide_other_apps(&self) {
+// //         unsafe {
+// //             let app = NSApplication::sharedApplication(nil);
+// //             let _: () = msg_send![app, hideOtherApplications: nil];
+// //         }
+// //     }
+
+// //     fn unhide_other_apps(&self) {
+// //         unsafe {
+// //             let app = NSApplication::sharedApplication(nil);
+// //             let _: () = msg_send![app, unhideAllApplications: nil];
+// //         }
+// //     }
+
+// //     fn quit(&self) {
+// //         // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
+// //         // synchronously before this method terminates. If we call `Platform::quit` while holding a
+// //         // borrow of the app state (which most of the time we will do), we will end up
+// //         // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
+// //         // 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_async_f, dispatch_get_main_queue};
+
+// //         unsafe {
+// //             dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
+// //         }
+
+// //         unsafe extern "C" fn quit(_: *mut c_void) {
+// //             let app = NSApplication::sharedApplication(nil);
+// //             let _: () = msg_send![app, terminate: nil];
+// //         }
+// //     }
+
+// //     fn screen_by_id(&self, id: uuid::Uuid) -> Option<Rc<dyn platform::Screen>> {
+// //         Screen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
+// //     }
+
+// //     fn screens(&self) -> Vec<Rc<dyn platform::Screen>> {
+// //         Screen::all()
+// //             .into_iter()
+// //             .map(|screen| Rc::new(screen) as Rc<_>)
+// //             .collect()
+// //     }
+
+// //     fn open_window(
+// //         &self,
+// //         handle: AnyWindowHandle,
+// //         options: platform::WindowOptions,
+// //         executor: Rc<executor::Foreground>,
+// //     ) -> Box<dyn platform::Window> {
+// //         Box::new(MacWindow::open(handle, options, executor, self.fonts()))
+// //     }
+
+// //     fn main_window(&self) -> Option<AnyWindowHandle> {
+// //         MacWindow::main_window()
+// //     }
+
+// //     fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
+// //         Box::new(StatusItem::add(self.fonts()))
+// //     }
+
+// //     fn write_to_clipboard(&self, item: ClipboardItem) {
+// //         unsafe {
+// //             self.pasteboard.clearContents();
+
+// //             let text_bytes = NSData::dataWithBytes_length_(
+// //                 nil,
+// //                 item.text.as_ptr() as *const c_void,
+// //                 item.text.len() as u64,
+// //             );
+// //             self.pasteboard
+// //                 .setData_forType(text_bytes, NSPasteboardTypeString);
+
+// //             if let Some(metadata) = item.metadata.as_ref() {
+// //                 let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes();
+// //                 let hash_bytes = NSData::dataWithBytes_length_(
+// //                     nil,
+// //                     hash_bytes.as_ptr() as *const c_void,
+// //                     hash_bytes.len() as u64,
+// //                 );
+// //                 self.pasteboard
+// //                     .setData_forType(hash_bytes, self.text_hash_pasteboard_type);
+
+// //                 let metadata_bytes = NSData::dataWithBytes_length_(
+// //                     nil,
+// //                     metadata.as_ptr() as *const c_void,
+// //                     metadata.len() as u64,
+// //                 );
+// //                 self.pasteboard
+// //                     .setData_forType(metadata_bytes, self.metadata_pasteboard_type);
+// //             }
+// //         }
+// //     }
+
+// //     fn read_from_clipboard(&self) -> Option<ClipboardItem> {
+// //         unsafe {
+// //             if let Some(text_bytes) = self.read_from_pasteboard(NSPasteboardTypeString) {
+// //                 let text = String::from_utf8_lossy(text_bytes).to_string();
+// //                 let hash_bytes = self
+// //                     .read_from_pasteboard(self.text_hash_pasteboard_type)
+// //                     .and_then(|bytes| bytes.try_into().ok())
+// //                     .map(u64::from_be_bytes);
+// //                 let metadata_bytes = self
+// //                     .read_from_pasteboard(self.metadata_pasteboard_type)
+// //                     .and_then(|bytes| String::from_utf8(bytes.to_vec()).ok());
+
+// //                 if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {
+// //                     if hash == ClipboardItem::text_hash(&text) {
+// //                         Some(ClipboardItem {
+// //                             text,
+// //                             metadata: Some(metadata),
+// //                         })
+// //                     } else {
+// //                         Some(ClipboardItem {
+// //                             text,
+// //                             metadata: None,
+// //                         })
+// //                     }
+// //                 } else {
+// //                     Some(ClipboardItem {
+// //                         text,
+// //                         metadata: None,
+// //                     })
+// //                 }
+// //             } else {
+// //                 None
+// //             }
+// //         }
+// //     }
+
+// //     fn open_url(&self, url: &str) {
+// //         unsafe {
+// //             let url = NSURL::alloc(nil)
+// //                 .initWithString_(ns_string(url))
+// //                 .autorelease();
+// //             let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
+// //             msg_send![workspace, openURL: url]
+// //         }
+// //     }
+
+// //     fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
+// //         let url = CFString::from(url);
+// //         let username = CFString::from(username);
+// //         let password = CFData::from_buffer(password);
+
+// //         unsafe {
+// //             use security::*;
+
+// //             // First, check if there are already credentials for the given server. If so, then
+// //             // update the username and password.
+// //             let mut verb = "updating";
+// //             let mut query_attrs = CFMutableDictionary::with_capacity(2);
+// //             query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
+// //             query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
+
+// //             let mut attrs = CFMutableDictionary::with_capacity(4);
+// //             attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
+// //             attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
+// //             attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef());
+// //             attrs.set(kSecValueData as *const _, password.as_CFTypeRef());
+
+// //             let mut status = SecItemUpdate(
+// //                 query_attrs.as_concrete_TypeRef(),
+// //                 attrs.as_concrete_TypeRef(),
+// //             );
+
+// //             // If there were no existing credentials for the given server, then create them.
+// //             if status == errSecItemNotFound {
+// //                 verb = "creating";
+// //                 status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut());
+// //             }
+
+// //             if status != errSecSuccess {
+// //                 return Err(anyhow!("{} password failed: {}", verb, status));
+// //             }
+// //         }
+// //         Ok(())
+// //     }
+
+// //     fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
+// //         let url = CFString::from(url);
+// //         let cf_true = CFBoolean::true_value().as_CFTypeRef();
+
+// //         unsafe {
+// //             use security::*;
+
+// //             // Find any credentials for the given server URL.
+// //             let mut attrs = CFMutableDictionary::with_capacity(5);
+// //             attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
+// //             attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
+// //             attrs.set(kSecReturnAttributes as *const _, cf_true);
+// //             attrs.set(kSecReturnData as *const _, cf_true);
+
+// //             let mut result = CFTypeRef::from(ptr::null());
+// //             let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result);
+// //             match status {
+// //                 security::errSecSuccess => {}
+// //                 security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None),
+// //                 _ => return Err(anyhow!("reading password failed: {}", status)),
+// //             }
+
+// //             let result = CFType::wrap_under_create_rule(result)
+// //                 .downcast::<CFDictionary>()
+// //                 .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?;
+// //             let username = result
+// //                 .find(kSecAttrAccount as *const _)
+// //                 .ok_or_else(|| anyhow!("account was missing from keychain item"))?;
+// //             let username = CFType::wrap_under_get_rule(*username)
+// //                 .downcast::<CFString>()
+// //                 .ok_or_else(|| anyhow!("account was not a string"))?;
+// //             let password = result
+// //                 .find(kSecValueData as *const _)
+// //                 .ok_or_else(|| anyhow!("password was missing from keychain item"))?;
+// //             let password = CFType::wrap_under_get_rule(*password)
+// //                 .downcast::<CFData>()
+// //                 .ok_or_else(|| anyhow!("password was not a string"))?;
+
+// //             Ok(Some((username.to_string(), password.bytes().to_vec())))
+// //         }
+// //     }
+
+// //     fn delete_credentials(&self, url: &str) -> Result<()> {
+// //         let url = CFString::from(url);
+
+// //         unsafe {
+// //             use security::*;
+
+// //             let mut query_attrs = CFMutableDictionary::with_capacity(2);
+// //             query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
+// //             query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
+
+// //             let status = SecItemDelete(query_attrs.as_concrete_TypeRef());
+
+// //             if status != errSecSuccess {
+// //                 return Err(anyhow!("delete password failed: {}", status));
+// //             }
+// //         }
+// //         Ok(())
+// //     }
+
+// //     fn set_cursor_style(&self, style: CursorStyle) {
+// //         unsafe {
+// //             let new_cursor: id = match style {
+// //                 CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
+// //                 CursorStyle::ResizeLeftRight => {
+// //                     msg_send![class!(NSCursor), resizeLeftRightCursor]
+// //                 }
+// //                 CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
+// //                 CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
+// //                 CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
+// //             };
+
+// //             let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
+// //             if new_cursor != old_cursor {
+// //                 let _: () = msg_send![new_cursor, set];
+// //             }
+// //         }
+// //     }
+
+// //     fn should_auto_hide_scrollbars(&self) -> bool {
+// //         #[allow(non_upper_case_globals)]
+// //         const NSScrollerStyleOverlay: NSInteger = 1;
+
+// //         unsafe {
+// //             let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
+// //             style == NSScrollerStyleOverlay
+// //         }
+// //     }
+
+// //     fn local_timezone(&self) -> UtcOffset {
+// //         unsafe {
+// //             let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
+// //             let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
+// //             UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
+// //         }
+// //     }
+
+// //     fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
+// //         unsafe {
+// //             let bundle: id = NSBundle::mainBundle();
+// //             if bundle.is_null() {
+// //                 Err(anyhow!("app is not running inside a bundle"))
+// //             } else {
+// //                 let name = ns_string(name);
+// //                 let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
+// //                 if url.is_null() {
+// //                     Err(anyhow!("resource not found"))
+// //                 } else {
+// //                     ns_url_to_path(url)
+// //                 }
+// //             }
+// //         }
+// //     }
+
+// //     fn app_path(&self) -> Result<PathBuf> {
+// //         unsafe {
+// //             let bundle: id = NSBundle::mainBundle();
+// //             if bundle.is_null() {
+// //                 Err(anyhow!("app is not running inside a bundle"))
+// //             } else {
+// //                 Ok(path_from_objc(msg_send![bundle, bundlePath]))
+// //             }
+// //         }
+// //     }
+
+// //     fn app_version(&self) -> Result<platform::AppVersion> {
+// //         unsafe {
+// //             let bundle: id = NSBundle::mainBundle();
+// //             if bundle.is_null() {
+// //                 Err(anyhow!("app is not running inside a bundle"))
+// //             } else {
+// //                 let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
+// //                 let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
+// //                 let bytes = version.UTF8String() as *const u8;
+// //                 let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
+// //                 version.parse()
+// //             }
+// //         }
+// //     }
+
+// //     fn os_name(&self) -> &'static str {
+// //         "macOS"
+// //     }
+
+// //     fn os_version(&self) -> Result<crate::platform::AppVersion> {
+// //         unsafe {
+// //             let process_info = NSProcessInfo::processInfo(nil);
+// //             let version = process_info.operatingSystemVersion();
+// //             Ok(AppVersion {
+// //                 major: version.majorVersion as usize,
+// //                 minor: version.minorVersion as usize,
+// //                 patch: version.patchVersion as usize,
+// //             })
+// //         }
+// //     }
+
+// //     fn restart(&self) {
+// //         use std::os::unix::process::CommandExt as _;
+
+// //         let app_pid = std::process::id().to_string();
+// //         let app_path = self
+// //             .app_path()
+// //             .ok()
+// //             // When the app is not bundled, `app_path` returns the
+// //             // directory containing the executable. Disregard this
+// //             // and get the path to the executable itself.
+// //             .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
+// //             .unwrap_or_else(|| std::env::current_exe().unwrap());
+
+// //         // Wait until this process has exited and then re-open this path.
+// //         let script = r#"
+// //             while kill -0 $0 2> /dev/null; do
+// //                 sleep 0.1
+// //             done
+// //             open "$1"
+// //         "#;
+
+// //         let restart_process = Command::new("/bin/bash")
+// //             .arg("-c")
+// //             .arg(script)
+// //             .arg(app_pid)
+// //             .arg(app_path)
+// //             .process_group(0)
+// //             .spawn();
+
+// //         match restart_process {
+// //             Ok(_) => self.quit(),
+// //             Err(e) => log::error!("failed to spawn restart script: {:?}", e),
+// //         }
+// //     }
+// // }
+
+// unsafe fn path_from_objc(path: id) -> PathBuf {
+//     let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
+//     let bytes = path.UTF8String() as *const u8;
+//     let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
+//     PathBuf::from(path)
+// }
+
+// unsafe fn get_foreground_platform(object: &mut Object) -> &MacForegroundPlatform {
+//     let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
+//     assert!(!platform_ptr.is_null());
+//     &*(platform_ptr as *const MacForegroundPlatform)
+// }
+
+// extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
+//     unsafe {
+//         if let Some(event) = Event::from_native(native_event, None) {
+//             let platform = get_foreground_platform(this);
+//             if let Some(callback) = platform.0.borrow_mut().event.as_mut() {
+//                 if callback(event) {
+//                     return;
+//                 }
+//             }
+//         }
+//         msg_send![super(this, class!(NSApplication)), sendEvent: native_event]
+//     }
+// }
+
+// extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
+//     unsafe {
+//         let app: id = msg_send![APP_CLASS, sharedApplication];
+//         app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
+
+//         let platform = get_foreground_platform(this);
+//         let callback = platform.0.borrow_mut().finish_launching.take();
+//         if let Some(callback) = callback {
+//             callback();
+//         }
+//     }
+// }
+
+// extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
+//     if !has_open_windows {
+//         let platform = unsafe { get_foreground_platform(this) };
+//         if let Some(callback) = platform.0.borrow_mut().reopen.as_mut() {
+//             callback();
+//         }
+//     }
+// }
+
+// extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
+//     let platform = unsafe { get_foreground_platform(this) };
+//     if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() {
+//         callback();
+//     }
+// }
+
+// extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
+//     let platform = unsafe { get_foreground_platform(this) };
+//     if let Some(callback) = platform.0.borrow_mut().resign_active.as_mut() {
+//         callback();
+//     }
+// }
+
+// extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
+//     let platform = unsafe { get_foreground_platform(this) };
+//     if let Some(callback) = platform.0.borrow_mut().quit.as_mut() {
+//         callback();
+//     }
+// }
+
+// extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {
+//     let urls = unsafe {
+//         (0..urls.count())
+//             .into_iter()
+//             .filter_map(|i| {
+//                 let url = urls.objectAtIndex(i);
+//                 match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() {
+//                     Ok(string) => Some(string.to_string()),
+//                     Err(err) => {
+//                         log::error!("error converting path to string: {}", err);
+//                         None
+//                     }
+//                 }
+//             })
+//             .collect::<Vec<_>>()
+//     };
+//     let platform = unsafe { get_foreground_platform(this) };
+//     if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() {
+//         callback(urls);
+//     }
+// }
+
+// extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
+//     unsafe {
+//         let platform = get_foreground_platform(this);
+//         let mut platform = platform.0.borrow_mut();
+//         if let Some(mut callback) = platform.menu_command.take() {
+//             let tag: NSInteger = msg_send![item, tag];
+//             let index = tag as usize;
+//             if let Some(action) = platform.menu_actions.get(index) {
+//                 callback(action.as_ref());
+//             }
+//             platform.menu_command = Some(callback);
+//         }
+//     }
+// }
+
+// extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {
+//     unsafe {
+//         let mut result = false;
+//         let platform = get_foreground_platform(this);
+//         let mut platform = platform.0.borrow_mut();
+//         if let Some(mut callback) = platform.validate_menu_command.take() {
+//             let tag: NSInteger = msg_send![item, tag];
+//             let index = tag as usize;
+//             if let Some(action) = platform.menu_actions.get(index) {
+//                 result = callback(action.as_ref());
+//             }
+//             platform.validate_menu_command = Some(callback);
+//         }
+//         result
+//     }
+// }
+
+// extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
+//     unsafe {
+//         let platform = get_foreground_platform(this);
+//         let mut platform = platform.0.borrow_mut();
+//         if let Some(mut callback) = platform.will_open_menu.take() {
+//             callback();
+//             platform.will_open_menu = Some(callback);
+//         }
+//     }
+// }
+
+// unsafe fn ns_string(string: &str) -> id {
+//     NSString::alloc(nil).init_str(string).autorelease()
+// }
+
+// unsafe fn ns_url_to_path(url: id) -> Result<PathBuf> {
+//     let path: *mut c_char = msg_send![url, fileSystemRepresentation];
+//     if path.is_null() {
+//         Err(anyhow!(
+//             "url is not a file path: {}",
+//             CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
+//         ))
+//     } else {
+//         Ok(PathBuf::from(OsStr::from_bytes(
+//             CStr::from_ptr(path).to_bytes(),
+//         )))
+//     }
+// }
+
+// mod security {
+//     #![allow(non_upper_case_globals)]
+//     use super::*;
+
+//     #[link(name = "Security", kind = "framework")]
+//     extern "C" {
+//         pub static kSecClass: CFStringRef;
+//         pub static kSecClassInternetPassword: CFStringRef;
+//         pub static kSecAttrServer: CFStringRef;
+//         pub static kSecAttrAccount: CFStringRef;
+//         pub static kSecValueData: CFStringRef;
+//         pub static kSecReturnAttributes: CFStringRef;
+//         pub static kSecReturnData: CFStringRef;
+
+//         pub fn SecItemAdd(attributes: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
+//         pub fn SecItemUpdate(query: CFDictionaryRef, attributes: CFDictionaryRef) -> OSStatus;
+//         pub fn SecItemDelete(query: CFDictionaryRef) -> OSStatus;
+//         pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
+//     }
+
+//     pub const errSecSuccess: OSStatus = 0;
+//     pub const errSecUserCanceled: OSStatus = -128;
+//     pub const errSecItemNotFound: OSStatus = -25300;
+// }
+
+// #[cfg(test)]
+// mod tests {
+//     use crate::platform::Platform;
+
+//     use super::*;
+
+//     #[test]
+//     fn test_clipboard() {
+//         let platform = build_platform();
+//         assert_eq!(platform.read_from_clipboard(), None);
+
+//         let item = ClipboardItem::new("1".to_string());
+//         platform.write_to_clipboard(item.clone());
+//         assert_eq!(platform.read_from_clipboard(), Some(item));
+
+//         let item = ClipboardItem::new("2".to_string()).with_metadata(vec![3, 4]);
+//         platform.write_to_clipboard(item.clone());
+//         assert_eq!(platform.read_from_clipboard(), Some(item));
+
+//         let text_from_other_app = "text from other app";
+//         unsafe {
+//             let bytes = NSData::dataWithBytes_length_(
+//                 nil,
+//                 text_from_other_app.as_ptr() as *const c_void,
+//                 text_from_other_app.len() as u64,
+//             );
+//             platform
+//                 .pasteboard
+//                 .setData_forType(bytes, NSPasteboardTypeString);
+//         }
+//         assert_eq!(
+//             platform.read_from_clipboard(),
+//             Some(ClipboardItem::new(text_from_other_app.to_string()))
+//         );
+//     }
+
+//     fn build_platform() -> MacPlatform {
+//         let mut platform = MacPlatform::new();
+//         platform.pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) };
+//         platform
+//     }
+// }

crates/gpui3/src/platform/mac/screen.rs πŸ”—

@@ -0,0 +1,145 @@
+use super::ns_string;
+use crate::{platform, point, px, size, Bounds, Pixels, PlatformScreen};
+use cocoa::{
+    appkit::NSScreen,
+    base::{id, nil},
+    foundation::{NSArray, NSDictionary, NSPoint, NSRect, NSSize},
+};
+use core_foundation::{
+    number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef},
+    uuid::{CFUUIDGetUUIDBytes, CFUUIDRef},
+};
+use core_graphics::display::CGDirectDisplayID;
+use std::{any::Any, ffi::c_void};
+use uuid::Uuid;
+
+#[link(name = "ApplicationServices", kind = "framework")]
+extern "C" {
+    pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
+}
+
+#[derive(Debug)]
+pub struct MacScreen {
+    pub(crate) native_screen: id,
+}
+
+impl MacScreen {
+    /// Get the screen with the given UUID.
+    pub fn find_by_id(uuid: Uuid) -> Option<Self> {
+        Self::all().find(|screen| platform::MacScreen::display_uuid(screen) == Some(uuid))
+    }
+
+    /// Get the primary screen - the one with the menu bar, and whose bottom left
+    /// corner is at the origin of the AppKit coordinate system.
+    fn primary() -> Self {
+        Self::all().next().unwrap()
+    }
+
+    pub fn all() -> impl Iterator<Item = Self> {
+        unsafe {
+            let native_screens = NSScreen::screens(nil);
+            (0..NSArray::count(native_screens)).map(move |ix| MacScreen {
+                native_screen: native_screens.objectAtIndex(ix),
+            })
+        }
+    }
+
+    /// Convert the given rectangle in screen coordinates from GPUI's
+    /// coordinate system to the AppKit coordinate system.
+    ///
+    /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
+    /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
+    /// bottom left of the primary screen, with the Y axis pointing upward.
+    pub(crate) fn screen_bounds_to_native(bounds: Bounds<Pixels>) -> NSRect {
+        let primary_screen_height =
+            px(unsafe { Self::primary().native_screen.frame().size.height } as f32);
+
+        NSRect::new(
+            NSPoint::new(
+                bounds.origin.x.into(),
+                (primary_screen_height - bounds.origin.y - bounds.size.height).into(),
+            ),
+            NSSize::new(bounds.size.width.into(), bounds.size.height.into()),
+        )
+    }
+
+    /// Convert the given rectangle in screen coordinates from the AppKit
+    /// coordinate system to GPUI's coordinate system.
+    ///
+    /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
+    /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
+    /// bottom left of the primary screen, with the Y axis pointing upward.
+    pub(crate) fn screen_bounds_from_native(rect: NSRect) -> Bounds<Pixels> {
+        let primary_screen_height = unsafe { Self::primary().native_screen.frame().size.height };
+        Bounds {
+            origin: point(
+                px(rect.origin.x as f32),
+                px((primary_screen_height - rect.origin.y - rect.size.height) as f32),
+            ),
+            size: size(px(rect.size.width as f32), px(rect.size.height as f32)),
+        }
+    }
+}
+
+impl PlatformScreen for MacScreen {
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn display_uuid(&self) -> Option<uuid::Uuid> {
+        unsafe {
+            // Screen ids are not stable. Further, the default device id is also unstable across restarts.
+            // CGDisplayCreateUUIDFromDisplayID is stable but not exposed in the bindings we use.
+            // This approach is similar to that which winit takes
+            // https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99
+            let device_description = self.native_screen.deviceDescription();
+
+            let key = ns_string("NSScreenNumber");
+            let device_id_obj = device_description.objectForKey_(key);
+            if device_id_obj.is_null() {
+                // Under some circumstances, especially display re-arrangements or display locking, we seem to get a null pointer
+                // to the device id. See: https://linear.app/zed-industries/issue/Z-257/lock-screen-crash-with-multiple-monitors
+                return None;
+            }
+
+            let mut device_id: u32 = 0;
+            CFNumberGetValue(
+                device_id_obj as CFNumberRef,
+                kCFNumberIntType,
+                (&mut device_id) as *mut _ as *mut c_void,
+            );
+            let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID);
+            if cfuuid.is_null() {
+                return None;
+            }
+
+            let bytes = CFUUIDGetUUIDBytes(cfuuid);
+            Some(Uuid::from_bytes([
+                bytes.byte0,
+                bytes.byte1,
+                bytes.byte2,
+                bytes.byte3,
+                bytes.byte4,
+                bytes.byte5,
+                bytes.byte6,
+                bytes.byte7,
+                bytes.byte8,
+                bytes.byte9,
+                bytes.byte10,
+                bytes.byte11,
+                bytes.byte12,
+                bytes.byte13,
+                bytes.byte14,
+                bytes.byte15,
+            ]))
+        }
+    }
+
+    fn bounds(&self) -> Bounds<Pixels> {
+        unsafe { Self::screen_bounds_from_native(self.native_screen.frame()) }
+    }
+
+    fn content_bounds(&self) -> Bounds<Pixels> {
+        unsafe { Self::screen_bounds_from_native(self.native_screen.visibleFrame()) }
+    }
+}

crates/gpui3/src/platform/mac/window.rs πŸ”—

@@ -0,0 +1,1619 @@
+use crate::{
+    point, px, size, AnyWindowHandle, Bounds, Event, ForegroundExecutor, InputHandler,
+    KeyDownEvent, Keystroke, MacScreen, Modifiers, ModifiersChangedEvent, MouseButton,
+    MouseDownEvent, MouseMovedEvent, MouseUpEvent, NSRectExt, Pixels, PlatformScreen,
+    PlatformTextSystem, PlatformWindow, Point, Size, Timer, WindowAppearance, WindowBounds,
+    WindowKind, WindowOptions, WindowPromptLevel,
+};
+use block::ConcreteBlock;
+use cocoa::{
+    appkit::{
+        CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
+        NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior,
+        NSWindowStyleMask, NSWindowTitleVisibility,
+    },
+    base::{id, nil},
+    foundation::{NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger},
+};
+use core_graphics::display::CGRect;
+use ctor::ctor;
+use futures::channel::oneshot;
+use objc::{
+    class,
+    declare::ClassDecl,
+    msg_send,
+    runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES},
+    sel, sel_impl,
+};
+use raw_window_handle::{
+    AppKitDisplayHandle, AppKitWindowHandle, HasRawDisplayHandle, HasRawWindowHandle,
+    RawDisplayHandle, RawWindowHandle,
+};
+use std::{
+    any::Any,
+    cell::{Cell, RefCell},
+    ffi::{c_void, CStr},
+    mem,
+    ops::Range,
+    os::raw::c_char,
+    ptr,
+    rc::{Rc, Weak},
+    sync::Arc,
+    time::Duration,
+};
+
+use super::{ns_string, NSRange};
+
+const WINDOW_STATE_IVAR: &str = "windowState";
+
+static mut WINDOW_CLASS: *const Class = ptr::null();
+static mut PANEL_CLASS: *const Class = ptr::null();
+static mut VIEW_CLASS: *const Class = ptr::null();
+
+#[allow(non_upper_case_globals)]
+const NSWindowStyleMaskNonactivatingPanel: NSWindowStyleMask =
+    unsafe { NSWindowStyleMask::from_bits_unchecked(1 << 7) };
+#[allow(non_upper_case_globals)]
+const NSNormalWindowLevel: NSInteger = 0;
+#[allow(non_upper_case_globals)]
+const NSPopUpWindowLevel: NSInteger = 101;
+#[allow(non_upper_case_globals)]
+const NSTrackingMouseEnteredAndExited: NSUInteger = 0x01;
+#[allow(non_upper_case_globals)]
+const NSTrackingMouseMoved: NSUInteger = 0x02;
+#[allow(non_upper_case_globals)]
+const NSTrackingActiveAlways: NSUInteger = 0x80;
+#[allow(non_upper_case_globals)]
+const NSTrackingInVisibleRect: NSUInteger = 0x200;
+#[allow(non_upper_case_globals)]
+const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4;
+#[allow(non_upper_case_globals)]
+const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
+
+#[ctor]
+unsafe fn build_classes() {
+    WINDOW_CLASS = build_window_class("GPUIWindow", class!(NSWindow));
+    PANEL_CLASS = build_window_class("GPUIPanel", class!(NSPanel));
+    VIEW_CLASS = {
+        let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap();
+        decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
+
+        decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
+
+        decl.add_method(
+            sel!(performKeyEquivalent:),
+            handle_key_equivalent as extern "C" fn(&Object, Sel, id) -> BOOL,
+        );
+        decl.add_method(
+            sel!(keyDown:),
+            handle_key_down as extern "C" fn(&Object, Sel, id),
+        );
+        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!(mouseExited:),
+            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!(cancelOperation:),
+            cancel_operation as extern "C" fn(&Object, Sel, id),
+        );
+
+        // decl.add_method(
+        //     sel!(makeBackingLayer),
+        //     make_backing_layer as extern "C" fn(&Object, Sel) -> id,
+        // );
+
+        decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
+        decl.add_method(
+            sel!(viewDidChangeBackingProperties),
+            view_did_change_backing_properties as extern "C" fn(&Object, Sel),
+        );
+        decl.add_method(
+            sel!(setFrameSize:),
+            set_frame_size as extern "C" fn(&Object, Sel, NSSize),
+        );
+        decl.add_method(
+            sel!(displayLayer:),
+            display_layer as extern "C" fn(&Object, Sel, id),
+        );
+
+        decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
+        decl.add_method(
+            sel!(validAttributesForMarkedText),
+            valid_attributes_for_marked_text as extern "C" fn(&Object, Sel) -> id,
+        );
+        decl.add_method(
+            sel!(hasMarkedText),
+            has_marked_text as extern "C" fn(&Object, Sel) -> BOOL,
+        );
+        decl.add_method(
+            sel!(markedRange),
+            marked_range as extern "C" fn(&Object, Sel) -> NSRange,
+        );
+        decl.add_method(
+            sel!(selectedRange),
+            selected_range as extern "C" fn(&Object, Sel) -> NSRange,
+        );
+        decl.add_method(
+            sel!(firstRectForCharacterRange:actualRange:),
+            first_rect_for_character_range as extern "C" fn(&Object, Sel, NSRange, id) -> NSRect,
+        );
+        decl.add_method(
+            sel!(insertText:replacementRange:),
+            insert_text as extern "C" fn(&Object, Sel, id, NSRange),
+        );
+        decl.add_method(
+            sel!(setMarkedText:selectedRange:replacementRange:),
+            set_marked_text as extern "C" fn(&Object, Sel, id, NSRange, NSRange),
+        );
+        decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel));
+        decl.add_method(
+            sel!(attributedSubstringForProposedRange:actualRange:),
+            attributed_substring_for_proposed_range
+                as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id,
+        );
+        decl.add_method(
+            sel!(viewDidChangeEffectiveAppearance),
+            view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
+        );
+
+        // Suppress beep on keystrokes with modifier keys.
+        decl.add_method(
+            sel!(doCommandBySelector:),
+            do_command_by_selector as extern "C" fn(&Object, Sel, Sel),
+        );
+
+        decl.add_method(
+            sel!(acceptsFirstMouse:),
+            accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
+        );
+
+        decl.register()
+    };
+}
+
+pub fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
+    point(
+        px(position.x as f32),
+        // MacOS screen coordinates are relative to bottom left
+        window_height - px(position.y as f32),
+    )
+}
+
+unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
+    let mut decl = ClassDecl::new(name, superclass).unwrap();
+    decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
+    decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
+    decl.add_method(
+        sel!(canBecomeMainWindow),
+        yes as extern "C" fn(&Object, Sel) -> BOOL,
+    );
+    decl.add_method(
+        sel!(canBecomeKeyWindow),
+        yes as extern "C" fn(&Object, Sel) -> BOOL,
+    );
+    decl.add_method(
+        sel!(windowDidResize:),
+        window_did_resize as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowWillEnterFullScreen:),
+        window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowWillExitFullScreen:),
+        window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowDidMove:),
+        window_did_move as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowDidBecomeKey:),
+        window_did_change_key_status as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowDidResignKey:),
+        window_did_change_key_status as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowShouldClose:),
+        window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
+    );
+    decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
+    decl.register()
+}
+
+///Used to track what the IME does when we send it a keystroke.
+///This is only used to handle the case where the IME mysteriously
+///swallows certain keys.
+///
+///Basically a direct copy of the approach that WezTerm uses in:
+///github.com/wez/wezterm : d5755f3e : window/src/os/macos/window.rs
+enum ImeState {
+    Continue,
+    Acted,
+    None,
+}
+
+struct InsertText {
+    replacement_range: Option<Range<usize>>,
+    text: String,
+}
+
+struct WindowState {
+    handle: AnyWindowHandle,
+    native_window: id,
+    kind: WindowKind,
+    event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
+    activate_callback: Option<Box<dyn FnMut(bool)>>,
+    resize_callback: Option<Box<dyn FnMut()>>,
+    fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
+    moved_callback: Option<Box<dyn FnMut()>>,
+    should_close_callback: Option<Box<dyn FnMut() -> bool>>,
+    close_callback: Option<Box<dyn FnOnce()>>,
+    appearance_changed_callback: Option<Box<dyn FnMut()>>,
+    input_handler: Option<Box<dyn InputHandler>>,
+    pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
+    last_key_equivalent: Option<KeyDownEvent>,
+    synthetic_drag_counter: usize,
+    executor: Rc<ForegroundExecutor>,
+    last_fresh_keydown: Option<Keystroke>,
+    traffic_light_position: Option<Point<Pixels>>,
+    previous_modifiers_changed_event: Option<Event>,
+    // State tracking what the IME did after the last request
+    ime_state: ImeState,
+    // Retains the last IME Text
+    ime_text: Option<String>,
+}
+
+impl WindowState {
+    fn move_traffic_light(&self) {
+        if let Some(traffic_light_position) = self.traffic_light_position {
+            let titlebar_height = self.titlebar_height();
+
+            unsafe {
+                let close_button: id = msg_send![
+                    self.native_window,
+                    standardWindowButton: NSWindowButton::NSWindowCloseButton
+                ];
+                let min_button: id = msg_send![
+                    self.native_window,
+                    standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton
+                ];
+                let zoom_button: id = msg_send![
+                    self.native_window,
+                    standardWindowButton: NSWindowButton::NSWindowZoomButton
+                ];
+
+                let mut close_button_frame: CGRect = msg_send![close_button, frame];
+                let mut min_button_frame: CGRect = msg_send![min_button, frame];
+                let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
+                let mut origin = point(
+                    traffic_light_position.x,
+                    titlebar_height
+                        - traffic_light_position.y
+                        - px(close_button_frame.size.height as f32),
+                );
+                let button_spacing =
+                    px((min_button_frame.origin.x - close_button_frame.origin.x) as f32);
+
+                close_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
+                let _: () = msg_send![close_button, setFrame: close_button_frame];
+                origin.x += button_spacing;
+
+                min_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
+                let _: () = msg_send![min_button, setFrame: min_button_frame];
+                origin.x += button_spacing;
+
+                zoom_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
+                let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
+                origin.x += button_spacing;
+            }
+        }
+    }
+
+    fn is_fullscreen(&self) -> bool {
+        unsafe {
+            let style_mask = self.native_window.styleMask();
+            style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask)
+        }
+    }
+
+    fn bounds(&self) -> WindowBounds {
+        unsafe {
+            if self.is_fullscreen() {
+                return WindowBounds::Fullscreen;
+            }
+
+            let frame = self.frame();
+            let screen_size = self.native_window.screen().visibleFrame().size();
+            if frame.size == screen_size {
+                WindowBounds::Maximized
+            } else {
+                WindowBounds::Fixed(frame)
+            }
+        }
+    }
+
+    fn frame(&self) -> Bounds<Pixels> {
+        unsafe {
+            let frame = NSWindow::frame(self.native_window);
+            MacScreen::screen_bounds_from_native(frame)
+        }
+    }
+
+    fn content_size(&self) -> Size<Pixels> {
+        let NSSize { width, height, .. } =
+            unsafe { NSView::frame(self.native_window.contentView()) }.size;
+        size(px(width as f32), px(height as f32))
+    }
+
+    fn scale_factor(&self) -> f32 {
+        get_scale_factor(self.native_window)
+    }
+
+    fn titlebar_height(&self) -> Pixels {
+        unsafe {
+            let frame = NSWindow::frame(self.native_window);
+            let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
+            px((frame.size.height - content_layout_rect.size.height) as f32)
+        }
+    }
+
+    fn to_screen_ns_point(&self, point: Point<Pixels>) -> NSPoint {
+        unsafe {
+            let point = NSPoint::new(
+                point.x.into(),
+                (self.content_size().height - point.y).into(),
+            );
+            msg_send![self.native_window, convertPointToScreen: point]
+        }
+    }
+}
+
+pub struct MacWindow(Rc<RefCell<WindowState>>);
+
+impl MacWindow {
+    pub fn open(
+        handle: AnyWindowHandle,
+        options: WindowOptions,
+        executor: Rc<ForegroundExecutor>,
+        text_system: Arc<dyn PlatformTextSystem>,
+    ) -> Self {
+        unsafe {
+            let pool = NSAutoreleasePool::new(nil);
+
+            let mut style_mask;
+            if let Some(titlebar) = options.titlebar.as_ref() {
+                style_mask = NSWindowStyleMask::NSClosableWindowMask
+                    | NSWindowStyleMask::NSMiniaturizableWindowMask
+                    | NSWindowStyleMask::NSResizableWindowMask
+                    | NSWindowStyleMask::NSTitledWindowMask;
+
+                if titlebar.appears_transparent {
+                    style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
+                }
+            } else {
+                style_mask = NSWindowStyleMask::NSTitledWindowMask
+                    | NSWindowStyleMask::NSFullSizeContentViewWindowMask;
+            }
+
+            let native_window: id = match options.kind {
+                WindowKind::Normal => msg_send![WINDOW_CLASS, alloc],
+                WindowKind::PopUp => {
+                    style_mask |= NSWindowStyleMaskNonactivatingPanel;
+                    msg_send![PANEL_CLASS, alloc]
+                }
+            };
+            let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
+                NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)),
+                style_mask,
+                NSBackingStoreBuffered,
+                NO,
+                options
+                    .screen
+                    .and_then(|screen| {
+                        Some(screen.as_any().downcast_ref::<MacScreen>()?.native_screen)
+                    })
+                    .unwrap_or(nil),
+            );
+            assert!(!native_window.is_null());
+
+            let screen = native_window.screen();
+            match options.bounds {
+                WindowBounds::Fullscreen => {
+                    native_window.toggleFullScreen_(nil);
+                }
+                WindowBounds::Maximized => {
+                    native_window.setFrame_display_(screen.visibleFrame(), YES);
+                }
+                WindowBounds::Fixed(bounds) => {
+                    let bounds = MacScreen::screen_bounds_to_native(bounds);
+                    let screen_bounds = screen.visibleFrame();
+                    if bounds.intersects(screen_bounds) {
+                        native_window.setFrame_display_(bounds, YES);
+                    } else {
+                        native_window.setFrame_display_(screen_bounds, YES);
+                    }
+                }
+            }
+
+            let native_view: id = msg_send![VIEW_CLASS, alloc];
+            let native_view = NSView::init(native_view);
+
+            assert!(!native_view.is_null());
+
+            let window = Self(Rc::new(RefCell::new(WindowState {
+                handle,
+                native_window,
+                kind: options.kind,
+                event_callback: None,
+                resize_callback: None,
+                should_close_callback: None,
+                close_callback: None,
+                activate_callback: None,
+                fullscreen_callback: None,
+                moved_callback: None,
+                appearance_changed_callback: None,
+                input_handler: None,
+                pending_key_down: None,
+                last_key_equivalent: None,
+                synthetic_drag_counter: 0,
+                executor,
+                last_fresh_keydown: None,
+                traffic_light_position: options
+                    .titlebar
+                    .as_ref()
+                    .and_then(|titlebar| titlebar.traffic_light_position),
+                previous_modifiers_changed_event: None,
+                ime_state: ImeState::None,
+                ime_text: None,
+            })));
+
+            (*native_window).set_ivar(
+                WINDOW_STATE_IVAR,
+                Rc::into_raw(window.0.clone()) as *const c_void,
+            );
+            native_window.setDelegate_(native_window);
+            (*native_view).set_ivar(
+                WINDOW_STATE_IVAR,
+                Rc::into_raw(window.0.clone()) as *const c_void,
+            );
+
+            if let Some(title) = options
+                .titlebar
+                .as_ref()
+                .and_then(|t| t.title.as_ref().map(AsRef::as_ref))
+            {
+                native_window.setTitle_(NSString::alloc(nil).init_str(title));
+            }
+
+            native_window.setMovable_(options.is_movable as BOOL);
+
+            if options
+                .titlebar
+                .map_or(true, |titlebar| titlebar.appears_transparent)
+            {
+                native_window.setTitlebarAppearsTransparent_(YES);
+                native_window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);
+            }
+
+            native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
+            native_view.setWantsBestResolutionOpenGLSurface_(YES);
+
+            // From winit crate: On Mojave, views automatically become layer-backed shortly after
+            // being added to a native_window. Changing the layer-backedness of a view breaks the
+            // association between the view and its associated OpenGL context. To work around this,
+            // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
+            // itself and break the association with its context.
+            native_view.setWantsLayer(YES);
+            let _: () = msg_send![
+                native_view,
+                setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
+            ];
+
+            native_window.setContentView_(native_view.autorelease());
+            native_window.makeFirstResponder_(native_view);
+
+            if options.center {
+                native_window.center();
+            }
+
+            match options.kind {
+                WindowKind::Normal => {
+                    native_window.setLevel_(NSNormalWindowLevel);
+                    native_window.setAcceptsMouseMovedEvents_(YES);
+                }
+                WindowKind::PopUp => {
+                    // Use a tracking area to allow receiving MouseMoved events even when
+                    // the window or application aren't active, which is often the case
+                    // e.g. for notification windows.
+                    let tracking_area: id = msg_send![class!(NSTrackingArea), alloc];
+                    let _: () = msg_send![
+                        tracking_area,
+                        initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.))
+                        options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect
+                        owner: native_view
+                        userInfo: nil
+                    ];
+                    let _: () =
+                        msg_send![native_view, addTrackingArea: tracking_area.autorelease()];
+
+                    native_window.setLevel_(NSPopUpWindowLevel);
+                    let _: () = msg_send![
+                        native_window,
+                        setAnimationBehavior: NSWindowAnimationBehaviorUtilityWindow
+                    ];
+                    native_window.setCollectionBehavior_(
+                        NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces |
+                        NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary
+                    );
+                }
+            }
+            if options.focus {
+                native_window.makeKeyAndOrderFront_(nil);
+            } else if options.show {
+                native_window.orderFront_(nil);
+            }
+
+            window.0.borrow().move_traffic_light();
+            pool.drain();
+
+            window
+        }
+    }
+
+    pub fn main_window() -> Option<AnyWindowHandle> {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let main_window: id = msg_send![app, mainWindow];
+            if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
+                let handle = get_window_state(&*main_window).borrow().handle;
+                Some(handle)
+            } else {
+                None
+            }
+        }
+    }
+}
+
+impl Drop for MacWindow {
+    fn drop(&mut self) {
+        let this = self.0.borrow();
+        let window = this.native_window;
+        this.executor
+            .spawn(async move {
+                unsafe {
+                    window.close();
+                }
+            })
+            .detach();
+    }
+}
+
+unsafe impl HasRawWindowHandle for MacWindow {
+    fn raw_window_handle(&self) -> RawWindowHandle {
+        let ns_window = self.0.borrow().native_window;
+        let ns_view = unsafe { ns_window.contentView() };
+        let mut handle = AppKitWindowHandle::empty();
+        handle.ns_window = ns_window as *mut c_void;
+        handle.ns_view = ns_view as *mut c_void;
+        handle.into()
+    }
+}
+
+unsafe impl HasRawDisplayHandle for MacWindow {
+    fn raw_display_handle(&self) -> RawDisplayHandle {
+        AppKitDisplayHandle::empty().into()
+    }
+}
+
+impl PlatformWindow for MacWindow {
+    fn bounds(&self) -> WindowBounds {
+        self.0.as_ref().borrow().bounds()
+    }
+
+    fn content_size(&self) -> Size<Pixels> {
+        self.0.as_ref().borrow().content_size().into()
+    }
+
+    fn scale_factor(&self) -> f32 {
+        self.0.as_ref().borrow().scale_factor()
+    }
+
+    fn titlebar_height(&self) -> Pixels {
+        self.0.as_ref().borrow().titlebar_height()
+    }
+
+    fn appearance(&self) -> WindowAppearance {
+        unsafe {
+            let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance];
+            WindowAppearance::from_native(appearance)
+        }
+    }
+
+    fn screen(&self) -> Rc<dyn PlatformScreen> {
+        unsafe {
+            Rc::new(MacScreen {
+                native_screen: self.0.as_ref().borrow().native_window.screen(),
+            })
+        }
+    }
+
+    fn mouse_position(&self) -> Point<Pixels> {
+        let position = unsafe {
+            self.0
+                .borrow()
+                .native_window
+                .mouseLocationOutsideOfEventStream()
+        };
+        convert_mouse_position(position, self.content_size().height)
+    }
+
+    fn as_any_mut(&mut self) -> &mut dyn Any {
+        self
+    }
+
+    fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>) {
+        self.0.as_ref().borrow_mut().input_handler = Some(input_handler);
+    }
+
+    fn prompt(
+        &self,
+        level: WindowPromptLevel,
+        msg: &str,
+        answers: &[&str],
+    ) -> oneshot::Receiver<usize> {
+        // macOs applies overrides to modal window buttons after they are added.
+        // Two most important for this logic are:
+        // * Buttons with "Cancel" title will be displayed as the last buttons in the modal
+        // * Last button added to the modal via `addButtonWithTitle` stays focused
+        // * Focused buttons react on "space"/" " keypresses
+        // * Usage of `keyEquivalent`, `makeFirstResponder` or `setInitialFirstResponder` does not change the focus
+        //
+        // See also https://developer.apple.com/documentation/appkit/nsalert/1524532-addbuttonwithtitle#discussion
+        // ```
+        // By default, the first button has a key equivalent of Return,
+        // any button with a title of β€œCancel” has a key equivalent of Escape,
+        // and any button with the title β€œDon’t Save” has a key equivalent of Command-D (but only if it’s not the first button).
+        // ```
+        //
+        // To avoid situations when the last element added is "Cancel" and it gets the focus
+        // (hence stealing both ESC and Space shortcuts), we find and add one non-Cancel button
+        // last, so it gets focus and a Space shortcut.
+        // This way, "Save this file? Yes/No/Cancel"-ish modals will get all three buttons mapped with a key.
+        let latest_non_cancel_label = answers
+            .iter()
+            .enumerate()
+            .rev()
+            .find(|(_, &label)| label != "Cancel")
+            .filter(|&(label_index, _)| label_index > 0);
+
+        unsafe {
+            let alert: id = msg_send![class!(NSAlert), alloc];
+            let alert: id = msg_send![alert, init];
+            let alert_style = match level {
+                WindowPromptLevel::Info => 1,
+                WindowPromptLevel::Warning => 0,
+                WindowPromptLevel::Critical => 2,
+            };
+            let _: () = msg_send![alert, setAlertStyle: alert_style];
+            let _: () = msg_send![alert, setMessageText: ns_string(msg)];
+
+            for (ix, answer) in answers
+                .iter()
+                .enumerate()
+                .filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix))
+            {
+                let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
+                let _: () = msg_send![button, setTag: ix as NSInteger];
+            }
+            if let Some((ix, answer)) = latest_non_cancel_label {
+                let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
+                let _: () = msg_send![button, setTag: ix as NSInteger];
+            }
+
+            let (done_tx, done_rx) = oneshot::channel();
+            let done_tx = Cell::new(Some(done_tx));
+            let block = ConcreteBlock::new(move |answer: NSInteger| {
+                if let Some(done_tx) = done_tx.take() {
+                    let _ = done_tx.send(answer.try_into().unwrap());
+                }
+            });
+            let block = block.copy();
+            let native_window = self.0.borrow().native_window;
+            self.0
+                .borrow()
+                .executor
+                .spawn(async move {
+                    let _: () = msg_send![
+                        alert,
+                        beginSheetModalForWindow: native_window
+                        completionHandler: block
+                    ];
+                })
+                .detach();
+
+            done_rx
+        }
+    }
+
+    fn activate(&self) {
+        let window = self.0.borrow().native_window;
+        self.0
+            .borrow()
+            .executor
+            .spawn(async move {
+                unsafe {
+                    let _: () = msg_send![window, makeKeyAndOrderFront: nil];
+                }
+            })
+            .detach();
+    }
+
+    fn set_title(&mut self, title: &str) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let window = self.0.borrow().native_window;
+            let title = ns_string(title);
+            let _: () = msg_send![app, changeWindowsItem:window title:title filename:false];
+            let _: () = msg_send![window, setTitle: title];
+            self.0.borrow().move_traffic_light();
+        }
+    }
+
+    fn set_edited(&mut self, edited: bool) {
+        unsafe {
+            let window = self.0.borrow().native_window;
+            msg_send![window, setDocumentEdited: edited as BOOL]
+        }
+
+        // Changing the document edited state resets the traffic light position,
+        // so we have to move it again.
+        self.0.borrow().move_traffic_light();
+    }
+
+    fn show_character_palette(&self) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let window = self.0.borrow().native_window;
+            let _: () = msg_send![app, orderFrontCharacterPalette: window];
+        }
+    }
+
+    fn minimize(&self) {
+        let window = self.0.borrow().native_window;
+        unsafe {
+            window.miniaturize_(nil);
+        }
+    }
+
+    fn zoom(&self) {
+        let this = self.0.borrow();
+        let window = this.native_window;
+        this.executor
+            .spawn(async move {
+                unsafe {
+                    window.zoom_(nil);
+                }
+            })
+            .detach();
+    }
+
+    fn toggle_full_screen(&self) {
+        let this = self.0.borrow();
+        let window = this.native_window;
+        this.executor
+            .spawn(async move {
+                unsafe {
+                    window.toggleFullScreen_(nil);
+                }
+            })
+            .detach();
+    }
+
+    fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>) {
+        self.0.as_ref().borrow_mut().event_callback = Some(callback);
+    }
+
+    fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>) {
+        self.0.as_ref().borrow_mut().activate_callback = Some(callback);
+    }
+
+    fn on_resize(&mut self, callback: Box<dyn FnMut()>) {
+        self.0.as_ref().borrow_mut().resize_callback = Some(callback);
+    }
+
+    fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>) {
+        self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback);
+    }
+
+    fn on_moved(&mut self, callback: Box<dyn FnMut()>) {
+        self.0.as_ref().borrow_mut().moved_callback = Some(callback);
+    }
+
+    fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>) {
+        self.0.as_ref().borrow_mut().should_close_callback = Some(callback);
+    }
+
+    fn on_close(&mut self, callback: Box<dyn FnOnce()>) {
+        self.0.as_ref().borrow_mut().close_callback = Some(callback);
+    }
+
+    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, position: Point<Pixels>) -> bool {
+        let self_borrow = self.0.borrow();
+        let self_handle = self_borrow.handle;
+
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+
+            // Convert back to screen coordinates
+            let screen_point = self_borrow.to_screen_ns_point(position);
+
+            let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0];
+            let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number];
+
+            let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
+            let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
+            if is_panel == YES || is_window == YES {
+                let topmost_window = get_window_state(&*top_most_window).borrow().handle;
+                topmost_window == self_handle
+            } else {
+                // Someone else's window is on top
+                false
+            }
+        }
+    }
+}
+
+fn get_scale_factor(native_window: id) -> f32 {
+    unsafe {
+        let screen: id = msg_send![native_window, screen];
+        NSScreen::backingScaleFactor(screen) as f32
+    }
+}
+
+unsafe fn get_window_state(object: &Object) -> Rc<RefCell<WindowState>> {
+    let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
+    let rc1 = Rc::from_raw(raw as *mut RefCell<WindowState>);
+    let rc2 = rc1.clone();
+    mem::forget(rc1);
+    rc2
+}
+
+unsafe fn drop_window_state(object: &Object) {
+    let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
+    Rc::from_raw(raw as *mut RefCell<WindowState>);
+}
+
+extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
+    YES
+}
+
+extern "C" fn dealloc_window(this: &Object, _: Sel) {
+    unsafe {
+        drop_window_state(this);
+        let _: () = msg_send![super(this, class!(NSWindow)), dealloc];
+    }
+}
+
+extern "C" fn dealloc_view(this: &Object, _: Sel) {
+    unsafe {
+        drop_window_state(this);
+        let _: () = msg_send![super(this, class!(NSView)), dealloc];
+    }
+}
+
+extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
+    handle_key_event(this, native_event, true)
+}
+
+extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
+    handle_key_event(this, native_event, false);
+}
+
+extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
+    let window_state = unsafe { get_window_state(this) };
+    let mut window_state_borrow = window_state.as_ref().borrow_mut();
+
+    let window_height = window_state_borrow.content_size().height;
+    let event = unsafe { Event::from_native(native_event, Some(window_height)) };
+
+    if let Some(Event::KeyDown(event)) = 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
+        // the "key down" event if we've already just processed its "key equivalent" version.
+        if key_equivalent {
+            window_state_borrow.last_key_equivalent = Some(event.clone());
+        } else if window_state_borrow.last_key_equivalent.take().as_ref() == Some(&event) {
+            return NO;
+        }
+
+        let keydown = event.keystroke.clone();
+        let fn_modifier = keydown.modifiers.function;
+        // Ignore events from held-down keys after some of the initially-pressed keys
+        // were released.
+        if event.is_held {
+            if window_state_borrow.last_fresh_keydown.as_ref() != Some(&keydown) {
+                return YES;
+            }
+        } else {
+            window_state_borrow.last_fresh_keydown = Some(keydown);
+        }
+        window_state_borrow.pending_key_down = Some((event, None));
+        drop(window_state_borrow);
+
+        // Send the event to the input context for IME handling, unless the `fn` modifier is
+        // being pressed.
+        if !fn_modifier {
+            unsafe {
+                let input_context: id = msg_send![this, inputContext];
+                let _: BOOL = msg_send![input_context, handleEvent: native_event];
+            }
+        }
+
+        let mut handled = false;
+        let mut window_state_borrow = window_state.borrow_mut();
+        let ime_text = window_state_borrow.ime_text.clone();
+        if let Some((event, insert_text)) = window_state_borrow.pending_key_down.take() {
+            let is_held = event.is_held;
+            if let Some(mut callback) = window_state_borrow.event_callback.take() {
+                drop(window_state_borrow);
+
+                let is_composing =
+                    with_input_handler(this, |input_handler| input_handler.marked_text_range())
+                        .flatten()
+                        .is_some();
+                if !is_composing {
+                    // if the IME has changed the key, we'll first emit an event with the character
+                    // generated by the IME system; then fallback to the keystroke if that is not
+                    // handled.
+                    // cases that we have working:
+                    // - " on a brazillian layout by typing <quote><space>
+                    // - ctrl-` on a brazillian layout by typing <ctrl-`>
+                    // - $ on a czech QWERTY layout by typing <alt-4>
+                    // - 4 on a czech QWERTY layout by typing <shift-4>
+                    // - ctrl-4 on a czech QWERTY layout by typing <ctrl-alt-4> (or <ctrl-shift-4>)
+                    if ime_text.is_some() && ime_text.as_ref() != Some(&event.keystroke.key) {
+                        let event_with_ime_text = KeyDownEvent {
+                            is_held: false,
+                            keystroke: Keystroke {
+                                // we match ctrl because some use-cases need it.
+                                // we don't match alt because it's often used to generate the optional character
+                                // we don't match shift because we're not here with letters (usually)
+                                // we don't match cmd/fn because they don't seem to use IME
+                                modifiers: Default::default(),
+                                key: ime_text.clone().unwrap(),
+                            },
+                        };
+                        handled = callback(Event::KeyDown(event_with_ime_text));
+                    }
+                    if !handled {
+                        // empty key happens when you type a deadkey in input composition.
+                        // (e.g. on a brazillian keyboard typing quote is a deadkey)
+                        if !event.keystroke.key.is_empty() {
+                            handled = callback(Event::KeyDown(event));
+                        }
+                    }
+                }
+
+                if !handled {
+                    if let Some(insert) = insert_text {
+                        handled = true;
+                        with_input_handler(this, |input_handler| {
+                            input_handler
+                                .replace_text_in_range(insert.replacement_range, &insert.text)
+                        });
+                    } else if !is_composing && is_held {
+                        if let Some(last_insert_text) = ime_text {
+                            //MacOS IME is a bit funky, and even when you've told it there's nothing to
+                            //inter it will still swallow certain keys (e.g. 'f', 'j') and not others
+                            //(e.g. 'n'). This is a problem for certain kinds of views, like the terminal
+                            with_input_handler(this, |input_handler| {
+                                if input_handler.selected_text_range().is_none() {
+                                    handled = true;
+                                    input_handler.replace_text_in_range(None, &last_insert_text)
+                                }
+                            });
+                        }
+                    }
+                }
+
+                window_state.borrow_mut().event_callback = Some(callback);
+            }
+        } else {
+            handled = true;
+        }
+
+        handled as BOOL
+    } else {
+        NO
+    }
+}
+
+extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
+    let window_state = unsafe { get_window_state(this) };
+    let weak_window_state = Rc::downgrade(&window_state);
+    let mut window_state_borrow = window_state.as_ref().borrow_mut();
+    let is_active = unsafe { window_state_borrow.native_window.isKeyWindow() == YES };
+
+    let window_height = window_state_borrow.content_size().height;
+    let event = unsafe { Event::from_native(native_event, Some(window_height)) };
+
+    if let Some(mut event) = event {
+        let synthesized_second_event = match &mut event {
+            Event::MouseDown(
+                event @ MouseDownEvent {
+                    button: MouseButton::Left,
+                    modifiers: Modifiers { control: true, .. },
+                    ..
+                },
+            ) => {
+                *event = MouseDownEvent {
+                    button: MouseButton::Right,
+                    modifiers: Modifiers {
+                        control: false,
+                        ..event.modifiers
+                    },
+                    click_count: 1,
+                    ..*event
+                };
+
+                Some(Event::MouseDown(MouseDownEvent {
+                    button: MouseButton::Right,
+                    ..*event
+                }))
+            }
+
+            // Because we map a ctrl-left_down to a right_down -> right_up let's ignore
+            // the ctrl-left_up to avoid having a mismatch in button down/up events if the
+            // user is still holding ctrl when releasing the left mouse button
+            Event::MouseUp(MouseUpEvent {
+                button: MouseButton::Left,
+                modifiers: Modifiers { control: true, .. },
+                ..
+            }) => {
+                window_state_borrow.synthetic_drag_counter += 1;
+                return;
+            }
+
+            _ => None,
+        };
+
+        match &event {
+            Event::MouseMoved(
+                event @ MouseMovedEvent {
+                    pressed_button: Some(_),
+                    ..
+                },
+            ) => {
+                window_state_borrow.synthetic_drag_counter += 1;
+                window_state_borrow
+                    .executor
+                    .spawn(synthetic_drag(
+                        weak_window_state,
+                        window_state_borrow.synthetic_drag_counter,
+                        event.clone(),
+                    ))
+                    .detach();
+            }
+
+            Event::MouseMoved(_)
+                if !(is_active || window_state_borrow.kind == WindowKind::PopUp) =>
+            {
+                return
+            }
+
+            Event::MouseUp(MouseUpEvent {
+                button: MouseButton::Left,
+                ..
+            }) => {
+                window_state_borrow.synthetic_drag_counter += 1;
+            }
+
+            Event::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
+                // Only raise modifiers changed event when they have actually changed
+                if let Some(Event::ModifiersChanged(ModifiersChangedEvent {
+                    modifiers: prev_modifiers,
+                })) = &window_state_borrow.previous_modifiers_changed_event
+                {
+                    if prev_modifiers == modifiers {
+                        return;
+                    }
+                }
+
+                window_state_borrow.previous_modifiers_changed_event = Some(event.clone());
+            }
+
+            _ => {}
+        }
+
+        if let Some(mut callback) = window_state_borrow.event_callback.take() {
+            drop(window_state_borrow);
+            callback(event);
+            if let Some(event) = synthesized_second_event {
+                callback(event);
+            }
+            window_state.borrow_mut().event_callback = Some(callback);
+        }
+    }
+}
+
+// Allows us to receive `cmd-.` (the shortcut for closing a dialog)
+// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
+extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
+    let window_state = unsafe { get_window_state(this) };
+    let mut window_state_borrow = window_state.as_ref().borrow_mut();
+
+    let keystroke = Keystroke {
+        modifiers: Default::default(),
+        key: ".".into(),
+    };
+    let event = Event::KeyDown(KeyDownEvent {
+        keystroke: keystroke.clone(),
+        is_held: false,
+    });
+
+    window_state_borrow.last_fresh_keydown = Some(keystroke);
+    if let Some(mut callback) = window_state_borrow.event_callback.take() {
+        drop(window_state_borrow);
+        callback(event);
+        window_state.borrow_mut().event_callback = Some(callback);
+    }
+}
+
+extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
+    let window_state = unsafe { get_window_state(this) };
+    window_state.as_ref().borrow().move_traffic_light();
+}
+
+extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
+    window_fullscreen_changed(this, true);
+}
+
+extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
+    window_fullscreen_changed(this, false);
+}
+
+fn window_fullscreen_changed(this: &Object, is_fullscreen: bool) {
+    let window_state = unsafe { get_window_state(this) };
+    let mut window_state_borrow = window_state.as_ref().borrow_mut();
+    if let Some(mut callback) = window_state_borrow.fullscreen_callback.take() {
+        drop(window_state_borrow);
+        callback(is_fullscreen);
+        window_state.borrow_mut().fullscreen_callback = Some(callback);
+    }
+}
+
+extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
+    let window_state = unsafe { get_window_state(this) };
+    let mut window_state_borrow = window_state.as_ref().borrow_mut();
+    if let Some(mut callback) = window_state_borrow.moved_callback.take() {
+        drop(window_state_borrow);
+        callback();
+        window_state.borrow_mut().moved_callback = Some(callback);
+    }
+}
+
+extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
+    let window_state = unsafe { get_window_state(this) };
+    let window_state_borrow = window_state.borrow();
+    let is_active = unsafe { window_state_borrow.native_window.isKeyWindow() == YES };
+
+    // When opening a pop-up while the application isn't active, Cocoa sends a spurious
+    // `windowDidBecomeKey` message to the previous key window even though that window
+    // isn't actually key. This causes a bug if the application is later activated while
+    // the pop-up is still open, making it impossible to activate the previous key window
+    // even if the pop-up gets closed. The only way to activate it again is to de-activate
+    // the app and re-activate it, which is a pretty bad UX.
+    // The following code detects the spurious event and invokes `resignKeyWindow`:
+    // in theory, we're not supposed to invoke this method manually but it balances out
+    // the spurious `becomeKeyWindow` event and helps us work around that bug.
+    if selector == sel!(windowDidBecomeKey:) {
+        if !is_active {
+            unsafe {
+                let _: () = msg_send![window_state_borrow.native_window, resignKeyWindow];
+                return;
+            }
+        }
+    }
+
+    let executor = window_state_borrow.executor.clone();
+    drop(window_state_borrow);
+    executor
+        .spawn(async move {
+            let mut window_state_borrow = window_state.as_ref().borrow_mut();
+            if let Some(mut callback) = window_state_borrow.activate_callback.take() {
+                drop(window_state_borrow);
+                callback(is_active);
+                window_state.borrow_mut().activate_callback = Some(callback);
+            };
+        })
+        .detach();
+}
+
+extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
+    let window_state = unsafe { get_window_state(this) };
+    let mut window_state_borrow = window_state.as_ref().borrow_mut();
+    if let Some(mut callback) = window_state_borrow.should_close_callback.take() {
+        drop(window_state_borrow);
+        let should_close = callback();
+        window_state.borrow_mut().should_close_callback = Some(callback);
+        should_close as BOOL
+    } else {
+        YES
+    }
+}
+
+extern "C" fn close_window(this: &Object, _: Sel) {
+    unsafe {
+        let close_callback = {
+            let window_state = get_window_state(this);
+            window_state
+                .as_ref()
+                .try_borrow_mut()
+                .ok()
+                .and_then(|mut window_state| window_state.close_callback.take())
+        };
+
+        if let Some(callback) = close_callback {
+            callback();
+        }
+
+        let _: () = msg_send![super(this, class!(NSWindow)), close];
+    }
+}
+
+// extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
+//     let window_state = unsafe { get_window_state(this) };
+//     let window_state = window_state.as_ref().borrow();
+//     window_state.renderer.layer().as_ptr() as id
+// }
+
+extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
+    let window_state = unsafe { get_window_state(this) };
+    let mut window_state_borrow = window_state.as_ref().borrow_mut();
+
+    // unsafe {
+    //     let scale_factor = window_state_borrow.scale_factor() as f64;
+    //     let size = window_state_borrow.content_size();
+    //     let drawable_size: NSSize = NSSize {
+    //         width: f64::from(size.width) * scale_factor,
+    //         height: f64::from(size.height) * scale_factor,
+    //     };
+
+    //     // let _: () = msg_send![
+    //     //     window_state_borrow.renderer.layer(),
+    //     //     setContentsScale: scale_factor
+    //     // ];
+    //     // let _: () = msg_send![
+    //     //     window_state_borrow.renderer.layer(),
+    //     //     setDrawableSize: drawable_size
+    //     // ];
+    // }
+
+    if let Some(mut callback) = window_state_borrow.resize_callback.take() {
+        drop(window_state_borrow);
+        callback();
+        window_state.as_ref().borrow_mut().resize_callback = Some(callback);
+    };
+}
+
+extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
+    let window_state = unsafe { get_window_state(this) };
+    let window_state_borrow = window_state.as_ref().borrow();
+
+    if window_state_borrow.content_size() == size.into() {
+        return;
+    }
+
+    unsafe {
+        let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
+    }
+
+    // let scale_factor = window_state_borrow.scale_factor() as f64;
+    // let drawable_size: NSSize = NSSize {
+    //     width: size.width * scale_factor,
+    //     height: size.height * scale_factor,
+    // };
+    //
+    // unsafe {
+    //     let _: () = msg_send![
+    //         window_state_borrow.renderer.layer(),
+    //         setDrawableSize: drawable_size
+    //     ];
+    // }
+
+    drop(window_state_borrow);
+    let mut window_state_borrow = window_state.borrow_mut();
+    if let Some(mut callback) = window_state_borrow.resize_callback.take() {
+        drop(window_state_borrow);
+        callback();
+        window_state.borrow_mut().resize_callback = Some(callback);
+    };
+}
+
+extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
+    unsafe {
+        // let window_state = get_window_state(this);
+        // let mut window_state = window_state.as_ref().borrow_mut();
+        // if let Some(scene) = window_state.scene_to_render.take() {
+        //     window_state.renderer.render(&scene);
+        // };
+    }
+}
+
+extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
+    unsafe { msg_send![class!(NSArray), array] }
+}
+
+extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
+    with_input_handler(this, |input_handler| input_handler.marked_text_range())
+        .flatten()
+        .is_some() as BOOL
+}
+
+extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
+    with_input_handler(this, |input_handler| input_handler.marked_text_range())
+        .flatten()
+        .map_or(NSRange::invalid(), |range| range.into())
+}
+
+extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
+    with_input_handler(this, |input_handler| input_handler.selected_text_range())
+        .flatten()
+        .map_or(NSRange::invalid(), |range| range.into())
+}
+
+extern "C" fn first_rect_for_character_range(
+    this: &Object,
+    _: Sel,
+    range: NSRange,
+    _: id,
+) -> NSRect {
+    let frame = unsafe {
+        let window = get_window_state(this).borrow().native_window;
+        NSView::frame(window)
+    };
+    with_input_handler(this, |input_handler| {
+        input_handler.bounds_for_range(range.to_range()?)
+    })
+    .flatten()
+    .map_or(
+        NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
+        |bounds| {
+            NSRect::new(
+                NSPoint::new(
+                    frame.origin.x + bounds.origin.x as f64,
+                    frame.origin.y + frame.size.height - bounds.origin.y as f64,
+                ),
+                NSSize::new(bounds.size.width as f64, bounds.size.height as f64),
+            )
+        },
+    )
+}
+
+extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
+    unsafe {
+        let window_state = get_window_state(this);
+        let mut window_state_borrow = window_state.borrow_mut();
+        let pending_key_down = window_state_borrow.pending_key_down.take();
+        drop(window_state_borrow);
+
+        let is_attributed_string: BOOL =
+            msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
+        let text: id = if is_attributed_string == YES {
+            msg_send![text, string]
+        } else {
+            text
+        };
+        let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
+            .to_str()
+            .unwrap();
+        let replacement_range = replacement_range.to_range();
+
+        window_state.borrow_mut().ime_text = Some(text.to_string());
+        window_state.borrow_mut().ime_state = ImeState::Acted;
+
+        let is_composing =
+            with_input_handler(this, |input_handler| input_handler.marked_text_range())
+                .flatten()
+                .is_some();
+
+        if is_composing || text.chars().count() > 1 || pending_key_down.is_none() {
+            with_input_handler(this, |input_handler| {
+                input_handler.replace_text_in_range(replacement_range, text)
+            });
+        } else {
+            let mut pending_key_down = pending_key_down.unwrap();
+            pending_key_down.1 = Some(InsertText {
+                replacement_range,
+                text: text.to_string(),
+            });
+            window_state.borrow_mut().pending_key_down = Some(pending_key_down);
+        }
+    }
+}
+
+extern "C" fn set_marked_text(
+    this: &Object,
+    _: Sel,
+    text: id,
+    selected_range: NSRange,
+    replacement_range: NSRange,
+) {
+    unsafe {
+        let window_state = get_window_state(this);
+        window_state.borrow_mut().pending_key_down.take();
+
+        let is_attributed_string: BOOL =
+            msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
+        let text: id = if is_attributed_string == YES {
+            msg_send![text, string]
+        } else {
+            text
+        };
+        let selected_range = selected_range.to_range();
+        let replacement_range = replacement_range.to_range();
+        let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
+            .to_str()
+            .unwrap();
+
+        window_state.borrow_mut().ime_state = ImeState::Acted;
+        window_state.borrow_mut().ime_text = Some(text.to_string());
+
+        with_input_handler(this, |input_handler| {
+            input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range);
+        });
+    }
+}
+
+extern "C" fn unmark_text(this: &Object, _: Sel) {
+    unsafe {
+        let state = get_window_state(this);
+        let mut borrow = state.borrow_mut();
+        borrow.ime_state = ImeState::Acted;
+        borrow.ime_text.take();
+    }
+
+    with_input_handler(this, |input_handler| input_handler.unmark_text());
+}
+
+extern "C" fn attributed_substring_for_proposed_range(
+    this: &Object,
+    _: Sel,
+    range: NSRange,
+    _actual_range: *mut c_void,
+) -> id {
+    with_input_handler(this, |input_handler| {
+        let range = range.to_range()?;
+        if range.is_empty() {
+            return None;
+        }
+
+        let selected_text = input_handler.text_for_range(range)?;
+        unsafe {
+            let string: id = msg_send![class!(NSAttributedString), alloc];
+            let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
+            Some(string)
+        }
+    })
+    .flatten()
+    .unwrap_or(nil)
+}
+
+extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
+    unsafe {
+        let state = get_window_state(this);
+        let mut borrow = state.borrow_mut();
+        borrow.ime_state = ImeState::Continue;
+        borrow.ime_text.take();
+    }
+}
+
+extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
+    unsafe {
+        let state = get_window_state(this);
+        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);
+        }
+    }
+}
+
+extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
+    unsafe {
+        let state = get_window_state(this);
+        let state_borrow = state.as_ref().borrow();
+        return if state_borrow.kind == WindowKind::PopUp {
+            YES
+        } else {
+            NO
+        };
+    }
+}
+
+async fn synthetic_drag(
+    window_state: Weak<RefCell<WindowState>>,
+    drag_id: usize,
+    event: MouseMovedEvent,
+) {
+    loop {
+        Timer::after(Duration::from_millis(16)).await;
+        if let Some(window_state) = window_state.upgrade() {
+            let mut window_state_borrow = window_state.borrow_mut();
+            if window_state_borrow.synthetic_drag_counter == drag_id {
+                if let Some(mut callback) = window_state_borrow.event_callback.take() {
+                    drop(window_state_borrow);
+                    callback(Event::MouseMoved(event.clone()));
+                    window_state.borrow_mut().event_callback = Some(callback);
+                }
+            } else {
+                break;
+            }
+        }
+    }
+}
+
+fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
+where
+    F: FnOnce(&mut dyn InputHandler) -> R,
+{
+    let window_state = unsafe { get_window_state(window) };
+    let mut window_state_borrow = window_state.as_ref().borrow_mut();
+    if let Some(mut input_handler) = window_state_borrow.input_handler.take() {
+        drop(window_state_borrow);
+        let result = f(input_handler.as_mut());
+        window_state.borrow_mut().input_handler = Some(input_handler);
+        Some(result)
+    } else {
+        None
+    }
+}

crates/gpui3/src/platform/mac/window_appearence.rs πŸ”—

@@ -0,0 +1,35 @@
+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 unsafe fn from_native(appearance: id) -> Self {
+        let name: id = msg_send![appearance, name];
+        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")]
+extern "C" {
+    pub static NSAppearanceNameAqua: id;
+    pub static NSAppearanceNameDarkAqua: id;
+}

crates/gpui3/src/platform/test.rs πŸ”—

@@ -9,7 +9,7 @@ impl TestPlatform {
 }
 
 impl Platform for TestPlatform {
-    fn font_system(&self) -> std::sync::Arc<dyn crate::PlatformFontSystem> {
+    fn font_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
         todo!()
     }
 
@@ -20,4 +20,8 @@ impl Platform for TestPlatform {
     ) -> Box<dyn crate::PlatformWindow> {
         todo!()
     }
+
+    fn dispatcher(&self) -> std::sync::Arc<dyn crate::PlatformDispatcher> {
+        todo!()
+    }
 }

crates/gpui3/src/style.rs πŸ”—

@@ -3,7 +3,7 @@ use super::{
     Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, RunStyle, SharedString, Size,
     SizeRefinement, ViewContext, WindowContext,
 };
-use crate::{FontCache};
+use crate::FontCache;
 use refineable::Refineable;
 pub use taffy::style::{
     AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,

crates/gpui3/src/text.rs πŸ”—

@@ -1,7 +1,7 @@
 use crate::{black, px};
 
 use super::{
-    point, Bounds, FontId, Glyph, Hsla, Pixels, PlatformFontSystem, Point, UnderlineStyle,
+    point, Bounds, FontId, Glyph, Hsla, Pixels, PlatformTextSystem, Point, UnderlineStyle,
     WindowContext,
 };
 use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
@@ -17,7 +17,7 @@ use std::{
 pub struct TextLayoutCache {
     prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
     curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
-    fonts: Arc<dyn PlatformFontSystem>,
+    fonts: Arc<dyn PlatformTextSystem>,
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -28,7 +28,7 @@ pub struct RunStyle {
 }
 
 impl TextLayoutCache {
-    pub fn new(fonts: Arc<dyn PlatformFontSystem>) -> Self {
+    pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
         Self {
             prev_frame: Mutex::new(HashMap::new()),
             curr_frame: RwLock::new(HashMap::new()),
@@ -520,7 +520,7 @@ impl Boundary {
 }
 
 pub struct LineWrapper {
-    font_system: Arc<dyn PlatformFontSystem>,
+    font_system: Arc<dyn PlatformTextSystem>,
     pub(crate) font_id: FontId,
     pub(crate) font_size: Pixels,
     cached_ascii_char_widths: [Option<Pixels>; 128],
@@ -533,7 +533,7 @@ impl LineWrapper {
     pub fn new(
         font_id: FontId,
         font_size: Pixels,
-        font_system: Arc<dyn PlatformFontSystem>,
+        font_system: Arc<dyn PlatformTextSystem>,
     ) -> Self {
         Self {
             font_system,

crates/gpui3/src/util.rs πŸ”—

@@ -0,0 +1,49 @@
+use smol::future::FutureExt;
+use std::{future::Future, time::Duration};
+pub use util::*;
+
+pub fn post_inc(value: &mut usize) -> usize {
+    let prev = *value;
+    *value += 1;
+    prev
+}
+
+pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
+where
+    F: Future<Output = T>,
+{
+    let timer = async {
+        smol::Timer::after(timeout).await;
+        Err(())
+    };
+    let future = async move { Ok(f.await) };
+    timer.race(future).await
+}
+
+#[cfg(any(test, feature = "test"))]
+pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
+
+#[cfg(any(test, feature = "test"))]
+impl<'a> std::fmt::Debug for CwdBacktrace<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        use backtrace::{BacktraceFmt, BytesOrWideString};
+
+        let cwd = std::env::current_dir().unwrap();
+        let cwd = cwd.parent().unwrap();
+        let mut print_path = |fmt: &mut std::fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
+            std::fmt::Display::fmt(&path, fmt)
+        };
+        let mut fmt = BacktraceFmt::new(f, backtrace::PrintFmt::Full, &mut print_path);
+        for frame in self.0.frames() {
+            let mut formatted_frame = fmt.frame();
+            if frame
+                .symbols()
+                .iter()
+                .any(|s| s.filename().map_or(false, |f| f.starts_with(&cwd)))
+            {
+                formatted_frame.backtrace_frame(frame)?;
+            }
+        }
+        fmt.finish()
+    }
+}

crates/gpui3/src/window.rs πŸ”—

@@ -1,3 +1,5 @@
+use crate::PlatformWindow;
+
 use super::{
     px, taffy::LayoutId, AppContext, Bounds, Context, EntityId, Handle, Pixels, Reference, Style,
     TaffyLayoutEngine,
@@ -13,17 +15,19 @@ pub struct AnyWindow {}
 
 pub struct Window {
     id: WindowId,
+    platform_window: Box<dyn PlatformWindow>,
     rem_size: Pixels,
     layout_engine: TaffyLayoutEngine,
     pub(crate) root_view: Option<Box<dyn Any>>,
 }
 
 impl Window {
-    pub fn new(id: WindowId) -> Window {
+    pub fn new(id: WindowId, platform_window: Box<dyn PlatformWindow>) -> Window {
         Window {
             id,
-            layout_engine: TaffyLayoutEngine::new(),
+            platform_window,
             rem_size: px(16.),
+            layout_engine: TaffyLayoutEngine::new(),
             root_view: None,
         }
     }
@@ -239,6 +243,7 @@ impl<S: 'static> Into<AnyWindowHandle> for WindowHandle<S> {
     }
 }
 
+#[derive(Copy, Clone, PartialEq, Eq)]
 pub struct AnyWindowHandle {
     id: WindowId,
     state_type: TypeId,