Merge pull request #1630 from zed-industries/capture

Antonio Scandurra created

Lay the groundwork for call-oriented collaboration

Change summary

Cargo.lock                                                              | 101 
assets/icons/zed_22.svg                                                 |   4 
crates/capture/Cargo.toml                                               |  32 
crates/capture/build.rs                                                 |   7 
crates/capture/src/live_kit_token.rs                                    |  71 
crates/capture/src/main.rs                                              | 143 
crates/contacts_status_item/Cargo.toml                                  |  32 
crates/contacts_status_item/src/contacts_popover.rs                     |  94 
crates/contacts_status_item/src/contacts_status_item.rs                 |  94 
crates/diagnostics/src/diagnostics.rs                                   |   2 
crates/editor/src/element.rs                                            |   4 
crates/gpui/Cargo.toml                                                  |   2 
crates/gpui/src/app.rs                                                  | 131 
crates/gpui/src/elements/list.rs                                        |   4 
crates/gpui/src/elements/text.rs                                        |   2 
crates/gpui/src/platform.rs                                             |  73 
crates/gpui/src/platform/mac.rs                                         |   3 
crates/gpui/src/platform/mac/appearance.rs                              |  37 
crates/gpui/src/platform/mac/platform.rs                                |  45 
crates/gpui/src/platform/mac/renderer.rs                                | 227 
crates/gpui/src/platform/mac/shaders/shaders.h                          | 185 
crates/gpui/src/platform/mac/shaders/shaders.metal                      |  48 
crates/gpui/src/platform/mac/status_item.rs                             | 367 
crates/gpui/src/platform/mac/window.rs                                  | 397 
crates/gpui/src/platform/test.rs                                        |  67 
crates/gpui/src/presenter.rs                                            |  29 
crates/gpui/src/scene.rs                                                |  18 
crates/live_kit/Cargo.toml                                              |  22 
crates/live_kit/LiveKitBridge/.gitignore                                |   9 
crates/live_kit/LiveKitBridge/Package.resolved                          |  52 
crates/live_kit/LiveKitBridge/Package.swift                             |  27 
crates/live_kit/LiveKitBridge/README.md                                 |   3 
crates/live_kit/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift | 105 
crates/live_kit/build.rs                                                | 148 
crates/live_kit/src/live_kit.rs                                         | 276 
crates/media/Cargo.toml                                                 |  20 
crates/media/build.rs                                                   |  39 
crates/media/src/bindings.h                                             |   5 
crates/media/src/bindings.rs                                            |   8 
crates/media/src/media.rs                                               | 534 
crates/theme/src/theme.rs                                               |   6 
crates/zed/Cargo.toml                                                   |   1 
crates/zed/src/zed.rs                                                   |  13 
styles/package-lock.json                                                |   1 
styles/src/styleTree/app.ts                                             |   2 
styles/src/styleTree/contactsPopover.ts                                 |   8 
46 files changed, 3,115 insertions(+), 383 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -663,9 +663,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 
 [[package]]
 name = "bytes"
-version = "1.1.0"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
+checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
 
 [[package]]
 name = "bzip2-sys"
@@ -750,6 +750,34 @@ dependencies = [
  "winx",
 ]
 
+[[package]]
+name = "capture"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "bindgen",
+ "block",
+ "byteorder",
+ "bytes",
+ "cocoa",
+ "core-foundation",
+ "core-graphics",
+ "foreign-types",
+ "futures",
+ "gpui",
+ "hmac 0.12.1",
+ "jwt",
+ "live_kit",
+ "log",
+ "media",
+ "objc",
+ "parking_lot 0.11.2",
+ "postage",
+ "serde",
+ "sha2 0.10.2",
+ "simplelog",
+]
+
 [[package]]
 name = "castaway"
 version = "0.1.2"
@@ -1098,6 +1126,30 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "contacts_status_item"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "client",
+ "collections",
+ "editor",
+ "futures",
+ "fuzzy",
+ "gpui",
+ "language",
+ "log",
+ "menu",
+ "picker",
+ "postage",
+ "project",
+ "serde",
+ "settings",
+ "theme",
+ "util",
+ "workspace",
+]
+
 [[package]]
 name = "context_menu"
 version = "0.1.0"
@@ -2213,6 +2265,7 @@ dependencies = [
  "image",
  "lazy_static",
  "log",
+ "media",
  "metal",
  "num_cpus",
  "objc",
@@ -2722,6 +2775,21 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "41ee439ee368ba4a77ac70d04f14015415af8600d6c894dc1f11bd79758c57d5"
 
+[[package]]
+name = "jwt"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f"
+dependencies = [
+ "base64",
+ "crypto-common",
+ "digest 0.10.3",
+ "hmac 0.12.1",
+ "serde",
+ "serde_json",
+ "sha2 0.10.2",
+]
+
 [[package]]
 name = "kernel32-sys"
 version = "0.2.2"
@@ -2892,6 +2960,20 @@ dependencies = [
  "rand_chacha 0.3.1",
 ]
 
+[[package]]
+name = "live_kit"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "core-foundation",
+ "core-graphics",
+ "futures",
+ "media",
+ "parking_lot 0.11.2",
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "lock_api"
 version = "0.4.7"
@@ -3008,6 +3090,20 @@ dependencies = [
  "digest 0.10.3",
 ]
 
+[[package]]
+name = "media"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "bindgen",
+ "block",
+ "bytes",
+ "core-foundation",
+ "foreign-types",
+ "metal",
+ "objc",
+]
+
 [[package]]
 name = "memchr"
 version = "2.5.0"
@@ -7050,6 +7146,7 @@ dependencies = [
  "collections",
  "command_palette",
  "contacts_panel",
+ "contacts_status_item",
  "context_menu",
  "ctor",
  "diagnostics",

assets/icons/zed_22.svg 🔗

@@ -0,0 +1,4 @@
+<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M5 11C5 14.3137 7.68629 17 11 17C14.3137 17 17 14.3137 17 11C17 7.68629 14.3137 5 11 5C7.68629 5 5 7.68629 5 11ZM11 3C6.58172 3 3 6.58172 3 11C3 15.4183 6.58172 19 11 19C15.4183 19 19 15.4183 19 11C19 6.58172 15.4183 3 11 3Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8.09092 8.09088H14.6364L10.5511 12.4545H12.4546L13.9091 13.9091H7.36365L11.7273 9.54543H9.54547L8.09092 8.09088Z" fill="white"/>
+</svg>

crates/capture/Cargo.toml 🔗

@@ -0,0 +1,32 @@
+[package]
+name = "capture"
+version = "0.1.0"
+edition = "2021"
+description = "An example of screen capture"
+
+[dependencies]
+gpui = { path = "../gpui" }
+live_kit = { path = "../live_kit" }
+media = { path = "../media" }
+
+anyhow = "1.0.38"
+block = "0.1"
+bytes = "1.2"
+byteorder = "1.4"
+cocoa = "0.24"
+core-foundation = "0.9.3"
+core-graphics = "0.22.3"
+foreign-types = "0.3"
+futures = "0.3"
+hmac = "0.12"
+jwt = "0.16"
+log = { version = "0.4.16", features = ["kv_unstable_serde"] }
+objc = "0.2"
+parking_lot = "0.11.1"
+postage = { version = "0.4.1", features = ["futures-traits"] }
+serde = { version = "1.0", features = ["derive", "rc"] }
+sha2 = "0.10"
+simplelog = "0.9"
+
+[build-dependencies]
+bindgen = "0.59.2"

crates/capture/build.rs 🔗

@@ -0,0 +1,7 @@
+fn main() {
+    // Find WebRTC.framework as a sibling of the executable when running outside of an application bundle
+    println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
+
+    // Register exported Objective-C selectors, protocols, etc
+    println!("cargo:rustc-link-arg=-Wl,-ObjC");
+}

crates/capture/src/live_kit_token.rs 🔗

@@ -0,0 +1,71 @@
+use anyhow::Result;
+use hmac::{Hmac, Mac};
+use jwt::SignWithKey;
+use serde::Serialize;
+use sha2::Sha256;
+use std::{
+    ops::Add,
+    time::{Duration, SystemTime, UNIX_EPOCH},
+};
+
+static DEFAULT_TTL: Duration = Duration::from_secs(6 * 60 * 60); // 6 hours
+
+#[derive(Default, Serialize)]
+#[serde(rename_all = "camelCase")]
+struct ClaimGrants<'a> {
+    iss: &'a str,
+    sub: &'a str,
+    iat: u64,
+    exp: u64,
+    nbf: u64,
+    jwtid: &'a str,
+    video: VideoGrant<'a>,
+}
+
+#[derive(Default, Serialize)]
+#[serde(rename_all = "camelCase")]
+struct VideoGrant<'a> {
+    room_create: Option<bool>,
+    room_join: Option<bool>,
+    room_list: Option<bool>,
+    room_record: Option<bool>,
+    room_admin: Option<bool>,
+    room: Option<&'a str>,
+    can_publish: Option<bool>,
+    can_subscribe: Option<bool>,
+    can_publish_data: Option<bool>,
+    hidden: Option<bool>,
+    recorder: Option<bool>,
+}
+
+pub fn create_token(
+    api_key: &str,
+    secret_key: &str,
+    room_name: &str,
+    participant_name: &str,
+) -> Result<String> {
+    let secret_key: Hmac<Sha256> = Hmac::new_from_slice(secret_key.as_bytes())?;
+
+    let now = SystemTime::now();
+
+    let claims = ClaimGrants {
+        iss: api_key,
+        sub: participant_name,
+        iat: now.duration_since(UNIX_EPOCH).unwrap().as_secs(),
+        exp: now
+            .add(DEFAULT_TTL)
+            .duration_since(UNIX_EPOCH)
+            .unwrap()
+            .as_secs(),
+        nbf: 0,
+        jwtid: participant_name,
+        video: VideoGrant {
+            room: Some(room_name),
+            room_join: Some(true),
+            can_publish: Some(true),
+            can_subscribe: Some(true),
+            ..Default::default()
+        },
+    };
+    Ok(claims.sign_with_key(&secret_key)?)
+}

crates/capture/src/main.rs 🔗

@@ -0,0 +1,143 @@
+mod live_kit_token;
+
+use futures::StreamExt;
+use gpui::{
+    actions,
+    elements::{Canvas, *},
+    keymap::Binding,
+    platform::current::Surface,
+    Menu, MenuItem, ViewContext,
+};
+use live_kit::{LocalVideoTrack, Room};
+use log::LevelFilter;
+use media::core_video::CVImageBuffer;
+use postage::watch;
+use simplelog::SimpleLogger;
+use std::sync::Arc;
+
+actions!(capture, [Quit]);
+
+fn main() {
+    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
+
+    gpui::App::new(()).unwrap().run(|cx| {
+        cx.platform().activate(true);
+        cx.add_global_action(quit);
+
+        cx.add_bindings([Binding::new("cmd-q", Quit, None)]);
+        cx.set_menus(vec![Menu {
+            name: "Zed",
+            items: vec![MenuItem::Action {
+                name: "Quit",
+                action: Box::new(Quit),
+            }],
+        }]);
+
+        let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap();
+        let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap();
+        let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap();
+
+        cx.spawn(|mut cx| async move {
+            let user1_token = live_kit_token::create_token(
+                &live_kit_key,
+                &live_kit_secret,
+                "test-room",
+                "test-participant-1",
+            )
+            .unwrap();
+            let room1 = Room::new();
+            room1.connect(&live_kit_url, &user1_token).await.unwrap();
+
+            let user2_token = live_kit_token::create_token(
+                &live_kit_key,
+                &live_kit_secret,
+                "test-room",
+                "test-participant-2",
+            )
+            .unwrap();
+            let room2 = Room::new();
+            room2.connect(&live_kit_url, &user2_token).await.unwrap();
+            cx.add_window(Default::default(), |cx| ScreenCaptureView::new(room2, cx));
+
+            let windows = live_kit::list_windows();
+            let window = windows
+                .iter()
+                .find(|w| w.owner_name.as_deref() == Some("Safari"))
+                .unwrap();
+            let track = LocalVideoTrack::screen_share_for_window(window.id);
+            room1.publish_video_track(&track).await.unwrap();
+        })
+        .detach();
+    });
+}
+
+struct ScreenCaptureView {
+    image_buffer: Option<CVImageBuffer>,
+    _room: Arc<Room>,
+}
+
+impl gpui::Entity for ScreenCaptureView {
+    type Event = ();
+}
+
+impl ScreenCaptureView {
+    pub fn new(room: Arc<Room>, cx: &mut ViewContext<Self>) -> Self {
+        let mut remote_video_tracks = room.remote_video_tracks();
+        cx.spawn_weak(|this, mut cx| async move {
+            if let Some(video_track) = remote_video_tracks.next().await {
+                let (mut frames_tx, mut frames_rx) = watch::channel_with(None);
+                video_track.add_renderer(move |frame| *frames_tx.borrow_mut() = Some(frame));
+
+                while let Some(frame) = frames_rx.next().await {
+                    if let Some(this) = this.upgrade(&cx) {
+                        this.update(&mut cx, |this, cx| {
+                            this.image_buffer = frame;
+                            cx.notify();
+                        });
+                    } else {
+                        break;
+                    }
+                }
+            }
+        })
+        .detach();
+
+        Self {
+            image_buffer: None,
+            _room: room,
+        }
+    }
+}
+
+impl gpui::View for ScreenCaptureView {
+    fn ui_name() -> &'static str {
+        "View"
+    }
+
+    fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
+        let image_buffer = self.image_buffer.clone();
+        let canvas = Canvas::new(move |bounds, _, cx| {
+            if let Some(image_buffer) = image_buffer.clone() {
+                cx.scene.push_surface(Surface {
+                    bounds,
+                    image_buffer,
+                });
+            }
+        });
+
+        if let Some(image_buffer) = self.image_buffer.as_ref() {
+            canvas
+                .constrained()
+                .with_width(image_buffer.width() as f32)
+                .with_height(image_buffer.height() as f32)
+                .aligned()
+                .boxed()
+        } else {
+            canvas.boxed()
+        }
+    }
+}
+
+fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {
+    cx.platform().quit();
+}

crates/contacts_status_item/Cargo.toml 🔗

@@ -0,0 +1,32 @@
+[package]
+name = "contacts_status_item"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+path = "src/contacts_status_item.rs"
+doctest = false
+
+[dependencies]
+client = { path = "../client" }
+collections = { path = "../collections" }
+editor = { path = "../editor" }
+fuzzy = { path = "../fuzzy" }
+gpui = { path = "../gpui" }
+menu = { path = "../menu" }
+picker = { path = "../picker" }
+project = { path = "../project" }
+settings = { path = "../settings" }
+theme = { path = "../theme" }
+util = { path = "../util" }
+workspace = { path = "../workspace" }
+anyhow = "1.0"
+futures = "0.3"
+log = "0.4"
+postage = { version = "0.4.1", features = ["futures-traits"] }
+serde = { version = "1.0", features = ["derive", "rc"] }
+
+[dev-dependencies]
+language = { path = "../language", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }

crates/contacts_status_item/src/contacts_popover.rs 🔗

@@ -0,0 +1,94 @@
+use editor::Editor;
+use gpui::{elements::*, Entity, RenderContext, View, ViewContext, ViewHandle};
+use settings::Settings;
+
+pub enum Event {
+    Deactivated,
+}
+
+pub struct ContactsPopover {
+    filter_editor: ViewHandle<Editor>,
+}
+
+impl Entity for ContactsPopover {
+    type Event = Event;
+}
+
+impl View for ContactsPopover {
+    fn ui_name() -> &'static str {
+        "ContactsPopover"
+    }
+
+    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+        let theme = &cx.global::<Settings>().theme.contacts_popover;
+
+        Flex::row()
+            .with_child(
+                ChildView::new(self.filter_editor.clone())
+                    .contained()
+                    .with_style(
+                        cx.global::<Settings>()
+                            .theme
+                            .contacts_panel
+                            .user_query_editor
+                            .container,
+                    )
+                    .flex(1., true)
+                    .boxed(),
+            )
+            // .with_child(
+            //     MouseEventHandler::<AddContact>::new(0, cx, |_, _| {
+            //         Svg::new("icons/user_plus_16.svg")
+            //             .with_color(theme.add_contact_button.color)
+            //             .constrained()
+            //             .with_height(16.)
+            //             .contained()
+            //             .with_style(theme.add_contact_button.container)
+            //             .aligned()
+            //             .boxed()
+            //     })
+            //     .with_cursor_style(CursorStyle::PointingHand)
+            //     .on_click(MouseButton::Left, |_, cx| {
+            //         cx.dispatch_action(contact_finder::Toggle)
+            //     })
+            //     .boxed(),
+            // )
+            .constrained()
+            .with_height(
+                cx.global::<Settings>()
+                    .theme
+                    .contacts_panel
+                    .user_query_editor_height,
+            )
+            .aligned()
+            .top()
+            .contained()
+            .with_background_color(theme.background)
+            .with_uniform_padding(4.)
+            .boxed()
+    }
+}
+
+impl ContactsPopover {
+    pub fn new(cx: &mut ViewContext<Self>) -> Self {
+        cx.observe_window_activation(Self::window_activation_changed)
+            .detach();
+
+        let filter_editor = cx.add_view(|cx| {
+            let mut editor = Editor::single_line(
+                Some(|theme| theme.contacts_panel.user_query_editor.clone()),
+                cx,
+            );
+            editor.set_placeholder_text("Filter contacts", cx);
+            editor
+        });
+
+        Self { filter_editor }
+    }
+
+    fn window_activation_changed(&mut self, is_active: bool, cx: &mut ViewContext<Self>) {
+        if !is_active {
+            cx.emit(Event::Deactivated);
+        }
+    }
+}

crates/contacts_status_item/src/contacts_status_item.rs 🔗

@@ -0,0 +1,94 @@
+mod contacts_popover;
+
+use contacts_popover::ContactsPopover;
+use gpui::{
+    actions,
+    color::Color,
+    elements::*,
+    geometry::{rect::RectF, vector::vec2f},
+    Appearance, Entity, MouseButton, MutableAppContext, RenderContext, View, ViewContext,
+    ViewHandle, WindowKind,
+};
+
+actions!(contacts_status_item, [ToggleContactsPopover]);
+
+pub fn init(cx: &mut MutableAppContext) {
+    cx.add_action(ContactsStatusItem::toggle_contacts_popover);
+}
+
+pub struct ContactsStatusItem {
+    popover: Option<ViewHandle<ContactsPopover>>,
+}
+
+impl Entity for ContactsStatusItem {
+    type Event = ();
+}
+
+impl View for ContactsStatusItem {
+    fn ui_name() -> &'static str {
+        "ContactsStatusItem"
+    }
+
+    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+        let color = match cx.appearance {
+            Appearance::Light | Appearance::VibrantLight => Color::black(),
+            Appearance::Dark | Appearance::VibrantDark => Color::white(),
+        };
+        MouseEventHandler::<Self>::new(0, cx, |_, _| {
+            Svg::new("icons/zed_22.svg")
+                .with_color(color)
+                .aligned()
+                .boxed()
+        })
+        .on_click(MouseButton::Left, |_, cx| {
+            cx.dispatch_action(ToggleContactsPopover);
+        })
+        .boxed()
+    }
+}
+
+impl ContactsStatusItem {
+    pub fn new() -> Self {
+        Self { popover: None }
+    }
+
+    fn toggle_contacts_popover(&mut self, _: &ToggleContactsPopover, cx: &mut ViewContext<Self>) {
+        match self.popover.take() {
+            Some(popover) => {
+                cx.remove_window(popover.window_id());
+            }
+            None => {
+                let window_bounds = cx.window_bounds();
+                let size = vec2f(360., 460.);
+                let origin = window_bounds.lower_left()
+                    + vec2f(window_bounds.width() / 2. - size.x() / 2., 0.);
+                let (_, popover) = cx.add_window(
+                    gpui::WindowOptions {
+                        bounds: gpui::WindowBounds::Fixed(RectF::new(origin, size)),
+                        titlebar: None,
+                        center: false,
+                        kind: WindowKind::PopUp,
+                        is_movable: false,
+                    },
+                    |cx| ContactsPopover::new(cx),
+                );
+                cx.subscribe(&popover, Self::on_popover_event).detach();
+                self.popover = Some(popover);
+            }
+        }
+    }
+
+    fn on_popover_event(
+        &mut self,
+        popover: ViewHandle<ContactsPopover>,
+        event: &contacts_popover::Event,
+        cx: &mut ViewContext<Self>,
+    ) {
+        match event {
+            contacts_popover::Event::Deactivated => {
+                self.popover.take();
+                cx.remove_window(popover.window_id());
+            }
+        }
+    }
+}

crates/diagnostics/src/diagnostics.rs 🔗

@@ -1150,7 +1150,7 @@ mod tests {
         editor: &ViewHandle<Editor>,
         cx: &mut MutableAppContext,
     ) -> Vec<(u32, String)> {
-        let mut presenter = cx.build_presenter(editor.id(), 0.);
+        let mut presenter = cx.build_presenter(editor.id(), 0., Default::default());
         let mut cx = presenter.build_layout_context(Default::default(), false, cx);
         cx.render(editor, |editor, cx| {
             let snapshot = editor.snapshot(cx);

crates/editor/src/element.rs 🔗

@@ -2059,7 +2059,7 @@ mod tests {
 
         let layouts = editor.update(cx, |editor, cx| {
             let snapshot = editor.snapshot(cx);
-            let mut presenter = cx.build_presenter(window_id, 30.);
+            let mut presenter = cx.build_presenter(window_id, 30., Default::default());
             let layout_cx = presenter.build_layout_context(Vector2F::zero(), false, cx);
             element.layout_line_numbers(0..6, &Default::default(), &snapshot, &layout_cx)
         });
@@ -2098,7 +2098,7 @@ mod tests {
         );
 
         let mut scene = Scene::new(1.0);
-        let mut presenter = cx.build_presenter(window_id, 30.);
+        let mut presenter = cx.build_presenter(window_id, 30., Default::default());
         let mut layout_cx = presenter.build_layout_context(Vector2F::zero(), false, cx);
         let (size, mut state) = element.layout(
             SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),

crates/gpui/Cargo.toml 🔗

@@ -3,6 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
 edition = "2021"
 name = "gpui"
 version = "0.1.0"
+description = "A GPU-accelerated UI framework"
 
 [lib]
 path = "src/gpui.rs"
@@ -59,6 +60,7 @@ png = "0.16"
 simplelog = "0.9"
 
 [target.'cfg(target_os = "macos")'.dependencies]
+media = { path = "../media" }
 anyhow = "1"
 block = "0.1"
 cocoa = "0.24"

crates/gpui/src/app.rs 🔗

@@ -9,8 +9,8 @@ use crate::{
     platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions},
     presenter::Presenter,
     util::post_inc,
-    AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseButton, MouseRegionId,
-    PathPromptOptions, TextLayoutCache,
+    Appearance, AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseButton,
+    MouseRegionId, PathPromptOptions, TextLayoutCache,
 };
 pub use action::*;
 use anyhow::{anyhow, Context, Result};
@@ -579,6 +579,7 @@ impl TestAppContext {
                 hovered_region_ids: Default::default(),
                 clicked_region_ids: None,
                 refreshing: false,
+                appearance: Appearance::Light,
             };
             f(view, &mut render_cx)
         })
@@ -1243,6 +1244,10 @@ impl MutableAppContext {
             .map_or(false, |window| window.is_fullscreen)
     }
 
+    pub fn window_bounds(&self, window_id: usize) -> RectF {
+        self.presenters_and_platform_windows[&window_id].1.bounds()
+    }
+
     pub fn render_view(&mut self, params: RenderParams) -> Result<ElementBox> {
         let window_id = params.window_id;
         let view_id = params.view_id;
@@ -1260,6 +1265,7 @@ impl MutableAppContext {
         &mut self,
         window_id: usize,
         titlebar_height: f32,
+        appearance: Appearance,
     ) -> HashMap<usize, ElementBox> {
         self.start_frame();
         #[allow(clippy::needless_collect)]
@@ -1287,6 +1293,7 @@ impl MutableAppContext {
                         hovered_region_ids: Default::default(),
                         clicked_region_ids: None,
                         refreshing: false,
+                        appearance,
                     })
                     .unwrap(),
                 )
@@ -1920,42 +1927,56 @@ impl MutableAppContext {
                 },
             );
             root_view.update(this, |view, cx| view.on_focus_in(cx.handle().into(), cx));
-            this.open_platform_window(window_id, window_options);
+
+            let window =
+                this.cx
+                    .platform
+                    .open_window(window_id, window_options, this.foreground.clone());
+            this.register_platform_window(window_id, window);
 
             (window_id, root_view)
         })
     }
 
-    pub fn replace_root_view<T, F>(&mut self, window_id: usize, build_root_view: F) -> ViewHandle<T>
+    pub fn add_status_bar_item<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
     where
         T: View,
         F: FnOnce(&mut ViewContext<T>) -> T,
     {
         self.update(|this| {
+            let window_id = post_inc(&mut this.next_window_id);
             let root_view = this
                 .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx)))
                 .unwrap();
-            let window = this.cx.windows.get_mut(&window_id).unwrap();
-            window.root_view = root_view.clone().into();
-            window.focused_view_id = Some(root_view.id());
-            root_view
-        })
-    }
+            this.cx.windows.insert(
+                window_id,
+                Window {
+                    root_view: root_view.clone().into(),
+                    focused_view_id: Some(root_view.id()),
+                    is_active: false,
+                    invalidation: None,
+                    is_fullscreen: false,
+                },
+            );
+            root_view.update(this, |view, cx| view.on_focus_in(cx.handle().into(), cx));
 
-    pub fn remove_window(&mut self, window_id: usize) {
-        self.cx.windows.remove(&window_id);
-        self.presenters_and_platform_windows.remove(&window_id);
-        self.flush_effects();
+            let status_item = this.cx.platform.add_status_item();
+            this.register_platform_window(window_id, status_item);
+
+            (window_id, root_view)
+        })
     }
 
-    fn open_platform_window(&mut self, window_id: usize, window_options: WindowOptions) {
-        let mut window =
-            self.cx
-                .platform
-                .open_window(window_id, window_options, self.foreground.clone());
-        let presenter = Rc::new(RefCell::new(
-            self.build_presenter(window_id, window.titlebar_height()),
-        ));
+    fn register_platform_window(
+        &mut self,
+        window_id: usize,
+        mut window: Box<dyn platform::Window>,
+    ) {
+        let presenter = Rc::new(RefCell::new(self.build_presenter(
+            window_id,
+            window.titlebar_height(),
+            window.appearance(),
+        )));
 
         {
             let mut app = self.upgrade();
@@ -2006,24 +2027,59 @@ impl MutableAppContext {
             }));
         }
 
+        {
+            let mut app = self.upgrade();
+            window.on_appearance_changed(Box::new(move || app.update(|cx| cx.refresh_windows())));
+        }
+
         window.set_input_handler(Box::new(WindowInputHandler {
             app: self.upgrade().0,
             window_id,
         }));
 
-        let scene =
-            presenter
-                .borrow_mut()
-                .build_scene(window.size(), window.scale_factor(), false, self);
+        let scene = presenter.borrow_mut().build_scene(
+            window.content_size(),
+            window.scale_factor(),
+            false,
+            self,
+        );
         window.present_scene(scene);
         self.presenters_and_platform_windows
             .insert(window_id, (presenter.clone(), window));
     }
 
-    pub fn build_presenter(&mut self, window_id: usize, titlebar_height: f32) -> Presenter {
+    pub fn replace_root_view<T, F>(&mut self, window_id: usize, build_root_view: F) -> ViewHandle<T>
+    where
+        T: View,
+        F: FnOnce(&mut ViewContext<T>) -> T,
+    {
+        self.update(|this| {
+            let root_view = this
+                .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx)))
+                .unwrap();
+            let window = this.cx.windows.get_mut(&window_id).unwrap();
+            window.root_view = root_view.clone().into();
+            window.focused_view_id = Some(root_view.id());
+            root_view
+        })
+    }
+
+    pub fn remove_window(&mut self, window_id: usize) {
+        self.cx.windows.remove(&window_id);
+        self.presenters_and_platform_windows.remove(&window_id);
+        self.flush_effects();
+    }
+
+    pub fn build_presenter(
+        &mut self,
+        window_id: usize,
+        titlebar_height: f32,
+        appearance: Appearance,
+    ) -> Presenter {
         Presenter::new(
             window_id,
             titlebar_height,
+            appearance,
             self.cx.font_cache.clone(),
             TextLayoutCache::new(self.cx.platform.fonts()),
             self.assets.clone(),
@@ -2361,9 +2417,13 @@ impl MutableAppContext {
             {
                 {
                     let mut presenter = presenter.borrow_mut();
-                    presenter.invalidate(&mut invalidation, self);
-                    let scene =
-                        presenter.build_scene(window.size(), window.scale_factor(), false, self);
+                    presenter.invalidate(&mut invalidation, window.appearance(), self);
+                    let scene = presenter.build_scene(
+                        window.content_size(),
+                        window.scale_factor(),
+                        false,
+                        self,
+                    );
                     window.present_scene(scene);
                 }
                 self.presenters_and_platform_windows
@@ -2425,9 +2485,11 @@ impl MutableAppContext {
             let mut presenter = presenter.borrow_mut();
             presenter.refresh(
                 invalidation.as_mut().unwrap_or(&mut Default::default()),
+                window.appearance(),
                 self,
             );
-            let scene = presenter.build_scene(window.size(), window.scale_factor(), true, self);
+            let scene =
+                presenter.build_scene(window.content_size(), window.scale_factor(), true, self);
             window.present_scene(scene);
         }
         self.presenters_and_platform_windows = presenters;
@@ -3699,6 +3761,10 @@ impl<'a, T: View> ViewContext<'a, T> {
         self.app.toggle_window_full_screen(self.window_id)
     }
 
+    pub fn window_bounds(&self) -> RectF {
+        self.app.window_bounds(self.window_id)
+    }
+
     pub fn prompt(
         &self,
         level: PromptLevel,
@@ -4031,6 +4097,7 @@ pub struct RenderParams {
     pub hovered_region_ids: HashSet<MouseRegionId>,
     pub clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
     pub refreshing: bool,
+    pub appearance: Appearance,
 }
 
 pub struct RenderContext<'a, T: View> {
@@ -4041,6 +4108,7 @@ pub struct RenderContext<'a, T: View> {
     pub(crate) clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
     pub app: &'a mut MutableAppContext,
     pub titlebar_height: f32,
+    pub appearance: Appearance,
     pub refreshing: bool,
 }
 
@@ -4061,6 +4129,7 @@ impl<'a, V: View> RenderContext<'a, V> {
             hovered_region_ids: params.hovered_region_ids.clone(),
             clicked_region_ids: params.clicked_region_ids.clone(),
             refreshing: params.refreshing,
+            appearance: params.appearance,
         }
     }
 

crates/gpui/src/elements/list.rs 🔗

@@ -659,7 +659,7 @@ mod tests {
 
     #[crate::test(self)]
     fn test_layout(cx: &mut crate::MutableAppContext) {
-        let mut presenter = cx.build_presenter(0, 0.);
+        let mut presenter = cx.build_presenter(0, 0., Default::default());
         let (_, view) = cx.add_window(Default::default(), |_| TestView);
         let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
 
@@ -759,7 +759,7 @@ mod tests {
             .unwrap_or(10);
 
         let (_, view) = cx.add_window(Default::default(), |_| TestView);
-        let mut presenter = cx.build_presenter(0, 0.);
+        let mut presenter = cx.build_presenter(0, 0., Default::default());
         let mut next_id = 0;
         let elements = Rc::new(RefCell::new(
             (0..rng.gen_range(0..=20))

crates/gpui/src/elements/text.rs 🔗

@@ -291,7 +291,7 @@ mod tests {
     #[crate::test(self)]
     fn test_soft_wrapping_with_carriage_returns(cx: &mut MutableAppContext) {
         let (window_id, _) = cx.add_window(Default::default(), |_| TestView);
-        let mut presenter = cx.build_presenter(window_id, Default::default());
+        let mut presenter = cx.build_presenter(window_id, Default::default(), Default::default());
         fonts::with_font_cache(cx.font_cache().clone(), || {
             let mut text = Text::new("Hello\r\n".into(), Default::default()).with_soft_wrap(true);
             let (_, state) = text.layout(

crates/gpui/src/platform.rs 🔗

@@ -39,6 +39,11 @@ pub trait Platform: Send + Sync {
     fn fonts(&self) -> Arc<dyn FontSystem>;
 
     fn activate(&self, ignoring_other_apps: bool);
+    fn hide(&self);
+    fn hide_other_apps(&self);
+    fn unhide_other_apps(&self);
+    fn quit(&self);
+
     fn open_window(
         &self,
         id: usize,
@@ -46,10 +51,8 @@ pub trait Platform: Send + Sync {
         executor: Rc<executor::Foreground>,
     ) -> Box<dyn Window>;
     fn key_window_id(&self) -> Option<usize>;
-    fn hide(&self);
-    fn hide_other_apps(&self);
-    fn unhide_other_apps(&self);
-    fn quit(&self);
+
+    fn add_status_item(&self) -> Box<dyn Window>;
 
     fn write_to_clipboard(&self, item: ClipboardItem);
     fn read_from_clipboard(&self) -> Option<ClipboardItem>;
@@ -107,7 +110,7 @@ pub trait InputHandler {
     fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF>;
 }
 
-pub trait Window: WindowContext {
+pub trait Window {
     fn as_any_mut(&mut self) -> &mut dyn Any;
     fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
     fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>);
@@ -124,23 +127,52 @@ pub trait Window: WindowContext {
     fn minimize(&self);
     fn zoom(&self);
     fn toggle_full_screen(&self);
-}
 
-pub trait WindowContext {
-    fn size(&self) -> Vector2F;
+    fn bounds(&self) -> RectF;
+    fn content_size(&self) -> Vector2F;
     fn scale_factor(&self) -> f32;
     fn titlebar_height(&self) -> f32;
     fn present_scene(&mut self, scene: Scene);
+    fn appearance(&self) -> Appearance;
+    fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>);
 }
 
 #[derive(Debug)]
 pub struct WindowOptions<'a> {
     pub bounds: WindowBounds,
+    pub titlebar: Option<TitlebarOptions<'a>>,
+    pub center: bool,
+    pub kind: WindowKind,
+    pub is_movable: bool,
+}
+
+#[derive(Debug)]
+pub struct TitlebarOptions<'a> {
     pub title: Option<&'a str>,
-    pub titlebar_appears_transparent: bool,
+    pub appears_transparent: bool,
     pub traffic_light_position: Option<Vector2F>,
 }
 
+#[derive(Copy, Clone, Debug)]
+pub enum Appearance {
+    Light,
+    VibrantLight,
+    Dark,
+    VibrantDark,
+}
+
+impl Default for Appearance {
+    fn default() -> Self {
+        Self::Light
+    }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub enum WindowKind {
+    Normal,
+    PopUp,
+}
+
 #[derive(Debug)]
 pub enum WindowBounds {
     Maximized,
@@ -168,6 +200,12 @@ pub enum CursorStyle {
     IBeam,
 }
 
+impl Default for CursorStyle {
+    fn default() -> Self {
+        Self::Arrow
+    }
+}
+
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub struct AppVersion {
     major: usize,
@@ -175,12 +213,6 @@ pub struct AppVersion {
     patch: usize,
 }
 
-impl Default for CursorStyle {
-    fn default() -> Self {
-        Self::Arrow
-    }
-}
-
 impl FromStr for AppVersion {
     type Err = anyhow::Error;
 
@@ -247,9 +279,14 @@ impl<'a> Default for WindowOptions<'a> {
     fn default() -> Self {
         Self {
             bounds: WindowBounds::Maximized,
-            title: Default::default(),
-            titlebar_appears_transparent: Default::default(),
-            traffic_light_position: Default::default(),
+            titlebar: Some(TitlebarOptions {
+                title: Default::default(),
+                appears_transparent: Default::default(),
+                traffic_light_position: Default::default(),
+            }),
+            center: false,
+            kind: WindowKind::Normal,
+            is_movable: true,
         }
     }
 }

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

@@ -1,3 +1,4 @@
+mod appearance;
 mod atlas;
 mod dispatcher;
 mod event;
@@ -7,12 +8,14 @@ mod image_cache;
 mod platform;
 mod renderer;
 mod sprite_cache;
+mod status_item;
 mod window;
 
 use cocoa::base::{BOOL, NO, YES};
 pub use dispatcher::Dispatcher;
 pub use fonts::FontSystem;
 use platform::{MacForegroundPlatform, MacPlatform};
+pub use renderer::Surface;
 use std::{rc::Rc, sync::Arc};
 use window::Window;
 

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

@@ -0,0 +1,37 @@
+use std::ffi::CStr;
+
+use cocoa::{
+    appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight},
+    base::id,
+    foundation::NSString,
+};
+use objc::{msg_send, sel, sel_impl};
+
+use crate::Appearance;
+
+impl Appearance {
+    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/gpui/src/platform/mac/platform.rs 🔗

@@ -1,4 +1,6 @@
-use super::{event::key_to_native, BoolExt as _, Dispatcher, FontSystem, Window};
+use super::{
+    event::key_to_native, status_item::StatusItem, BoolExt as _, Dispatcher, FontSystem, Window,
+};
 use crate::{
     executor, keymap,
     platform::{self, CursorStyle},
@@ -50,6 +52,9 @@ use time::UtcOffset;
 #[allow(non_upper_case_globals)]
 const NSUTF8StringEncoding: NSUInteger = 4;
 
+#[allow(non_upper_case_globals)]
+pub const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
+
 const MAC_PLATFORM_IVAR: &str = "platform";
 static mut APP_CLASS: *const Class = ptr::null();
 static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
@@ -439,23 +444,6 @@ impl platform::Platform for MacPlatform {
         }
     }
 
-    fn open_window(
-        &self,
-        id: usize,
-        options: platform::WindowOptions,
-        executor: Rc<executor::Foreground>,
-    ) -> Box<dyn platform::Window> {
-        Box::new(Window::open(id, options, executor, self.fonts()))
-    }
-
-    fn key_window_id(&self) -> Option<usize> {
-        Window::key_window_id()
-    }
-
-    fn fonts(&self) -> Arc<dyn platform::FontSystem> {
-        self.fonts.clone()
-    }
-
     fn hide(&self) {
         unsafe {
             let app = NSApplication::sharedApplication(nil);
@@ -497,6 +485,27 @@ impl platform::Platform for MacPlatform {
         }
     }
 
+    fn open_window(
+        &self,
+        id: usize,
+        options: platform::WindowOptions,
+        executor: Rc<executor::Foreground>,
+    ) -> Box<dyn platform::Window> {
+        Box::new(Window::open(id, options, executor, self.fonts()))
+    }
+
+    fn key_window_id(&self) -> Option<usize> {
+        Window::key_window_id()
+    }
+
+    fn add_status_item(&self) -> Box<dyn platform::Window> {
+        Box::new(StatusItem::add(self.fonts()))
+    }
+
+    fn fonts(&self) -> Arc<dyn platform::FontSystem> {
+        self.fonts.clone()
+    }
+
     fn write_to_clipboard(&self, item: ClipboardItem) {
         unsafe {
             self.pasteboard.clearContents();

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

@@ -8,16 +8,26 @@ use crate::{
     platform,
     scene::{Glyph, Icon, Image, ImageGlyph, Layer, Quad, Scene, Shadow, Underline},
 };
-use cocoa::foundation::NSUInteger;
+use cocoa::{
+    base::{NO, YES},
+    foundation::NSUInteger,
+    quartzcore::AutoresizingMask,
+};
+use core_foundation::base::TCFType;
+use foreign_types::ForeignTypeRef;
 use log::warn;
-use metal::{MTLPixelFormat, MTLResourceOptions, NSRange};
+use media::core_video::{self, CVMetalTextureCache};
+use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
+use objc::{self, msg_send, sel, sel_impl};
 use shaders::ToFloat2 as _;
-use std::{collections::HashMap, ffi::c_void, iter::Peekable, mem, sync::Arc, vec};
+use std::{collections::HashMap, ffi::c_void, iter::Peekable, mem, ptr, sync::Arc, vec};
 
 const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
 const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
 
 pub struct Renderer {
+    layer: metal::MetalLayer,
+    command_queue: CommandQueue,
     sprite_cache: SpriteCache,
     image_cache: ImageCache,
     path_atlases: AtlasAllocator,
@@ -25,10 +35,12 @@ pub struct Renderer {
     shadow_pipeline_state: metal::RenderPipelineState,
     sprite_pipeline_state: metal::RenderPipelineState,
     image_pipeline_state: metal::RenderPipelineState,
+    surface_pipeline_state: metal::RenderPipelineState,
     path_atlas_pipeline_state: metal::RenderPipelineState,
     underline_pipeline_state: metal::RenderPipelineState,
     unit_vertices: metal::Buffer,
     instances: metal::Buffer,
+    cv_texture_cache: core_video::CVMetalTextureCache,
 }
 
 struct PathSprite {
@@ -37,13 +49,37 @@ struct PathSprite {
     shader_data: shaders::GPUISprite,
 }
 
+pub struct Surface {
+    pub bounds: RectF,
+    pub image_buffer: core_video::CVImageBuffer,
+}
+
 impl Renderer {
-    pub fn new(
-        device: metal::Device,
-        pixel_format: metal::MTLPixelFormat,
-        scale_factor: f32,
-        fonts: Arc<dyn platform::FontSystem>,
-    ) -> Self {
+    pub fn new(is_opaque: bool, fonts: Arc<dyn platform::FontSystem>) -> Self {
+        const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
+
+        let device: metal::Device = if let Some(device) = metal::Device::system_default() {
+            device
+        } else {
+            log::error!("unable to access a compatible graphics device");
+            std::process::exit(1);
+        };
+
+        let layer = metal::MetalLayer::new();
+        layer.set_device(&device);
+        layer.set_pixel_format(PIXEL_FORMAT);
+        layer.set_presents_with_transaction(true);
+        layer.set_opaque(is_opaque);
+        unsafe {
+            let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
+            let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
+            let _: () = msg_send![
+                &*layer,
+                setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
+                    | AutoresizingMask::HEIGHT_SIZABLE
+            ];
+        }
+
         let library = device
             .new_library_with_data(SHADERS_METALLIB)
             .expect("error building metal library");
@@ -66,13 +102,8 @@ impl Renderer {
             MTLResourceOptions::StorageModeManaged,
         );
 
-        let sprite_cache = SpriteCache::new(
-            device.clone(),
-            vec2i(1024, 768),
-            scale_factor,
-            fonts.clone(),
-        );
-        let image_cache = ImageCache::new(device.clone(), vec2i(1024, 768), scale_factor, fonts);
+        let sprite_cache = SpriteCache::new(device.clone(), vec2i(1024, 768), 1., fonts.clone());
+        let image_cache = ImageCache::new(device.clone(), vec2i(1024, 768), 1., fonts);
         let path_atlases =
             AtlasAllocator::new(device.clone(), build_path_atlas_texture_descriptor());
         let quad_pipeline_state = build_pipeline_state(
@@ -81,7 +112,7 @@ impl Renderer {
             "quad",
             "quad_vertex",
             "quad_fragment",
-            pixel_format,
+            PIXEL_FORMAT,
         );
         let shadow_pipeline_state = build_pipeline_state(
             &device,
@@ -89,7 +120,7 @@ impl Renderer {
             "shadow",
             "shadow_vertex",
             "shadow_fragment",
-            pixel_format,
+            PIXEL_FORMAT,
         );
         let sprite_pipeline_state = build_pipeline_state(
             &device,
@@ -97,7 +128,7 @@ impl Renderer {
             "sprite",
             "sprite_vertex",
             "sprite_fragment",
-            pixel_format,
+            PIXEL_FORMAT,
         );
         let image_pipeline_state = build_pipeline_state(
             &device,
@@ -105,7 +136,15 @@ impl Renderer {
             "image",
             "image_vertex",
             "image_fragment",
-            pixel_format,
+            PIXEL_FORMAT,
+        );
+        let surface_pipeline_state = build_pipeline_state(
+            &device,
+            &library,
+            "surface",
+            "surface_vertex",
+            "surface_fragment",
+            PIXEL_FORMAT,
         );
         let path_atlas_pipeline_state = build_path_atlas_pipeline_state(
             &device,
@@ -121,9 +160,12 @@ impl Renderer {
             "underline",
             "underline_vertex",
             "underline_fragment",
-            pixel_format,
+            PIXEL_FORMAT,
         );
+        let cv_texture_cache = CVMetalTextureCache::new(device.as_ptr()).unwrap();
         Self {
+            layer,
+            command_queue: device.new_command_queue(),
             sprite_cache,
             image_cache,
             path_atlases,
@@ -131,20 +173,26 @@ impl Renderer {
             shadow_pipeline_state,
             sprite_pipeline_state,
             image_pipeline_state,
+            surface_pipeline_state,
             path_atlas_pipeline_state,
             underline_pipeline_state,
             unit_vertices,
             instances,
+            cv_texture_cache,
         }
     }
 
-    pub fn render(
-        &mut self,
-        scene: &Scene,
-        drawable_size: Vector2F,
-        command_buffer: &metal::CommandBufferRef,
-        output: &metal::TextureRef,
-    ) {
+    pub fn layer(&self) -> &metal::MetalLayerRef {
+        &*self.layer
+    }
+
+    pub fn render(&mut self, scene: &Scene) {
+        let layer = self.layer.clone();
+        let drawable_size = layer.drawable_size();
+        let drawable = layer.next_drawable().unwrap();
+        let command_queue = self.command_queue.clone();
+        let command_buffer = command_queue.new_command_buffer();
+
         self.sprite_cache.set_scale_factor(scene.scale_factor());
         self.image_cache.set_scale_factor(scene.scale_factor());
 
@@ -155,15 +203,19 @@ impl Renderer {
             scene,
             path_sprites,
             &mut offset,
-            drawable_size,
+            vec2f(drawable_size.width as f32, drawable_size.height as f32),
             command_buffer,
-            output,
+            drawable.texture(),
         );
         self.instances.did_modify_range(NSRange {
             location: 0,
             length: offset as NSUInteger,
         });
         self.image_cache.finish_frame();
+
+        command_buffer.commit();
+        command_buffer.wait_until_completed();
+        drawable.present();
     }
 
     fn render_path_atlases(
@@ -312,7 +364,8 @@ impl Renderer {
         color_attachment.set_texture(Some(output));
         color_attachment.set_load_action(metal::MTLLoadAction::Clear);
         color_attachment.set_store_action(metal::MTLStoreAction::Store);
-        color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
+        let alpha = if self.layer.is_opaque() { 1. } else { 0. };
+        color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
         let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
 
         command_encoder.set_viewport(metal::MTLViewport {
@@ -372,6 +425,13 @@ impl Renderer {
                 drawable_size,
                 command_encoder,
             );
+            self.render_surfaces(
+                layer.surfaces(),
+                scale_factor,
+                offset,
+                drawable_size,
+                command_encoder,
+            );
         }
 
         command_encoder.end_encoding();
@@ -768,6 +828,111 @@ impl Renderer {
         }
     }
 
+    fn render_surfaces(
+        &mut self,
+        surfaces: &[Surface],
+        scale_factor: f32,
+        offset: &mut usize,
+        drawable_size: Vector2F,
+        command_encoder: &metal::RenderCommandEncoderRef,
+    ) {
+        if surfaces.is_empty() {
+            return;
+        }
+
+        command_encoder.set_render_pipeline_state(&self.surface_pipeline_state);
+        command_encoder.set_vertex_buffer(
+            shaders::GPUISurfaceVertexInputIndex_GPUISurfaceVertexInputIndexVertices as u64,
+            Some(&self.unit_vertices),
+            0,
+        );
+        command_encoder.set_vertex_bytes(
+            shaders::GPUISurfaceVertexInputIndex_GPUISurfaceVertexInputIndexViewportSize as u64,
+            mem::size_of::<shaders::vector_float2>() as u64,
+            [drawable_size.to_float2()].as_ptr() as *const c_void,
+        );
+
+        for surface in surfaces {
+            let origin = surface.bounds.origin() * scale_factor;
+            let source_size = vec2i(
+                surface.image_buffer.width() as i32,
+                surface.image_buffer.height() as i32,
+            );
+            let target_size = surface.bounds.size() * scale_factor;
+
+            assert_eq!(
+                surface.image_buffer.pixel_format_type(),
+                core_video::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
+            );
+
+            let y_texture = self
+                .cv_texture_cache
+                .create_texture_from_image(
+                    surface.image_buffer.as_concrete_TypeRef(),
+                    ptr::null(),
+                    MTLPixelFormat::R8Unorm,
+                    surface.image_buffer.plane_width(0),
+                    surface.image_buffer.plane_height(0),
+                    0,
+                )
+                .unwrap();
+            let cb_cr_texture = self
+                .cv_texture_cache
+                .create_texture_from_image(
+                    surface.image_buffer.as_concrete_TypeRef(),
+                    ptr::null(),
+                    MTLPixelFormat::RG8Unorm,
+                    surface.image_buffer.plane_width(1),
+                    surface.image_buffer.plane_height(1),
+                    1,
+                )
+                .unwrap();
+
+            align_offset(offset);
+            let next_offset = *offset + mem::size_of::<shaders::GPUISurface>();
+            assert!(
+                next_offset <= INSTANCE_BUFFER_SIZE,
+                "instance buffer exhausted"
+            );
+
+            command_encoder.set_vertex_buffer(
+                shaders::GPUISurfaceVertexInputIndex_GPUISurfaceVertexInputIndexSurfaces as u64,
+                Some(&self.instances),
+                *offset as u64,
+            );
+            command_encoder.set_vertex_bytes(
+                shaders::GPUISurfaceVertexInputIndex_GPUISurfaceVertexInputIndexAtlasSize as u64,
+                mem::size_of::<shaders::vector_float2>() as u64,
+                [source_size.to_float2()].as_ptr() as *const c_void,
+            );
+            command_encoder.set_fragment_texture(
+                shaders::GPUISurfaceFragmentInputIndex_GPUISurfaceFragmentInputIndexYAtlas as u64,
+                Some(y_texture.as_texture_ref()),
+            );
+            command_encoder.set_fragment_texture(
+                shaders::GPUISurfaceFragmentInputIndex_GPUISurfaceFragmentInputIndexCbCrAtlas
+                    as u64,
+                Some(cb_cr_texture.as_texture_ref()),
+            );
+
+            unsafe {
+                let buffer_contents = (self.instances.contents() as *mut u8).add(*offset)
+                    as *mut shaders::GPUISurface;
+                std::ptr::write(
+                    buffer_contents,
+                    shaders::GPUISurface {
+                        origin: origin.to_float2(),
+                        target_size: target_size.to_float2(),
+                        source_size: source_size.to_float2(),
+                    },
+                );
+            }
+
+            command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
+            *offset = next_offset;
+        }
+    }
+
     fn render_path_sprites(
         &mut self,
         layer_id: usize,

crates/gpui/src/platform/mac/shaders/shaders.h 🔗

@@ -1,122 +1,125 @@
 #include <simd/simd.h>
 
-typedef struct
-{
-    vector_float2 viewport_size;
+typedef struct {
+  vector_float2 viewport_size;
 } GPUIUniforms;
 
-typedef enum
-{
-    GPUIQuadInputIndexVertices = 0,
-    GPUIQuadInputIndexQuads = 1,
-    GPUIQuadInputIndexUniforms = 2,
+typedef enum {
+  GPUIQuadInputIndexVertices = 0,
+  GPUIQuadInputIndexQuads = 1,
+  GPUIQuadInputIndexUniforms = 2,
 } GPUIQuadInputIndex;
 
-typedef struct
-{
-    vector_float2 origin;
-    vector_float2 size;
-    vector_uchar4 background_color;
-    float border_top;
-    float border_right;
-    float border_bottom;
-    float border_left;
-    vector_uchar4 border_color;
-    float corner_radius;
+typedef struct {
+  vector_float2 origin;
+  vector_float2 size;
+  vector_uchar4 background_color;
+  float border_top;
+  float border_right;
+  float border_bottom;
+  float border_left;
+  vector_uchar4 border_color;
+  float corner_radius;
 } GPUIQuad;
 
-typedef enum
-{
-    GPUIShadowInputIndexVertices = 0,
-    GPUIShadowInputIndexShadows = 1,
-    GPUIShadowInputIndexUniforms = 2,
+typedef enum {
+  GPUIShadowInputIndexVertices = 0,
+  GPUIShadowInputIndexShadows = 1,
+  GPUIShadowInputIndexUniforms = 2,
 } GPUIShadowInputIndex;
 
-typedef struct
-{
-    vector_float2 origin;
-    vector_float2 size;
-    float corner_radius;
-    float sigma;
-    vector_uchar4 color;
+typedef struct {
+  vector_float2 origin;
+  vector_float2 size;
+  float corner_radius;
+  float sigma;
+  vector_uchar4 color;
 } GPUIShadow;
 
-typedef enum
-{
-    GPUISpriteVertexInputIndexVertices = 0,
-    GPUISpriteVertexInputIndexSprites = 1,
-    GPUISpriteVertexInputIndexViewportSize = 2,
-    GPUISpriteVertexInputIndexAtlasSize = 3,
+typedef enum {
+  GPUISpriteVertexInputIndexVertices = 0,
+  GPUISpriteVertexInputIndexSprites = 1,
+  GPUISpriteVertexInputIndexViewportSize = 2,
+  GPUISpriteVertexInputIndexAtlasSize = 3,
 } GPUISpriteVertexInputIndex;
 
-typedef enum
-{
-    GPUISpriteFragmentInputIndexAtlas = 0,
+typedef enum {
+  GPUISpriteFragmentInputIndexAtlas = 0,
 } GPUISpriteFragmentInputIndex;
 
-typedef struct
-{
-    vector_float2 origin;
-    vector_float2 target_size;
-    vector_float2 source_size;
-    vector_float2 atlas_origin;
-    vector_uchar4 color;
-    uint8_t compute_winding;
+typedef struct {
+  vector_float2 origin;
+  vector_float2 target_size;
+  vector_float2 source_size;
+  vector_float2 atlas_origin;
+  vector_uchar4 color;
+  uint8_t compute_winding;
 } GPUISprite;
 
-typedef enum
-{
-    GPUIPathAtlasVertexInputIndexVertices = 0,
-    GPUIPathAtlasVertexInputIndexAtlasSize = 1,
+typedef enum {
+  GPUIPathAtlasVertexInputIndexVertices = 0,
+  GPUIPathAtlasVertexInputIndexAtlasSize = 1,
 } GPUIPathAtlasVertexInputIndex;
 
-typedef struct
-{
-    vector_float2 xy_position;
-    vector_float2 st_position;
-    vector_float2 clip_rect_origin;
-    vector_float2 clip_rect_size;
+typedef struct {
+  vector_float2 xy_position;
+  vector_float2 st_position;
+  vector_float2 clip_rect_origin;
+  vector_float2 clip_rect_size;
 } GPUIPathVertex;
 
-typedef enum
-{
-    GPUIImageVertexInputIndexVertices = 0,
-    GPUIImageVertexInputIndexImages = 1,
-    GPUIImageVertexInputIndexViewportSize = 2,
-    GPUIImageVertexInputIndexAtlasSize = 3,
+typedef enum {
+  GPUIImageVertexInputIndexVertices = 0,
+  GPUIImageVertexInputIndexImages = 1,
+  GPUIImageVertexInputIndexViewportSize = 2,
+  GPUIImageVertexInputIndexAtlasSize = 3,
 } GPUIImageVertexInputIndex;
 
-typedef enum
-{
-    GPUIImageFragmentInputIndexAtlas = 0,
+typedef enum {
+  GPUIImageFragmentInputIndexAtlas = 0,
 } GPUIImageFragmentInputIndex;
 
-typedef struct
-{
-    vector_float2 origin;
-    vector_float2 target_size;
-    vector_float2 source_size;
-    vector_float2 atlas_origin;
-    float border_top;
-    float border_right;
-    float border_bottom;
-    float border_left;
-    vector_uchar4 border_color;
-    float corner_radius;
+typedef struct {
+  vector_float2 origin;
+  vector_float2 target_size;
+  vector_float2 source_size;
+  vector_float2 atlas_origin;
+  float border_top;
+  float border_right;
+  float border_bottom;
+  float border_left;
+  vector_uchar4 border_color;
+  float corner_radius;
 } GPUIImage;
 
-typedef enum
-{
-    GPUIUnderlineInputIndexVertices = 0,
-    GPUIUnderlineInputIndexUnderlines = 1,
-    GPUIUnderlineInputIndexUniforms = 2,
+typedef enum {
+  GPUISurfaceVertexInputIndexVertices = 0,
+  GPUISurfaceVertexInputIndexSurfaces = 1,
+  GPUISurfaceVertexInputIndexViewportSize = 2,
+  GPUISurfaceVertexInputIndexAtlasSize = 3,
+} GPUISurfaceVertexInputIndex;
+
+typedef enum {
+  GPUISurfaceFragmentInputIndexYAtlas = 0,
+  GPUISurfaceFragmentInputIndexCbCrAtlas = 1,
+} GPUISurfaceFragmentInputIndex;
+
+typedef struct {
+  vector_float2 origin;
+  vector_float2 target_size;
+  vector_float2 source_size;
+} GPUISurface;
+
+typedef enum {
+  GPUIUnderlineInputIndexVertices = 0,
+  GPUIUnderlineInputIndexUnderlines = 1,
+  GPUIUnderlineInputIndexUniforms = 2,
 } GPUIUnderlineInputIndex;
 
-typedef struct
-{
-    vector_float2 origin;
-    vector_float2 size;
-    float thickness;
-    vector_uchar4 color;
-    uint8_t squiggly;
+typedef struct {
+  vector_float2 origin;
+  vector_float2 size;
+  float thickness;
+  vector_uchar4 color;
+  uint8_t squiggly;
 } GPUIUnderline;

crates/gpui/src/platform/mac/shaders/shaders.metal 🔗

@@ -263,6 +263,54 @@ fragment float4 image_fragment(
     return quad_sdf(input);
 }
 
+vertex QuadFragmentInput surface_vertex(
+    uint unit_vertex_id [[vertex_id]],
+    uint image_id [[instance_id]],
+    constant float2 *unit_vertices [[buffer(GPUISurfaceVertexInputIndexVertices)]],
+    constant GPUISurface *images [[buffer(GPUISurfaceVertexInputIndexSurfaces)]],
+    constant float2 *viewport_size [[buffer(GPUISurfaceVertexInputIndexViewportSize)]],
+    constant float2 *atlas_size [[buffer(GPUISurfaceVertexInputIndexAtlasSize)]]
+) {
+    float2 unit_vertex = unit_vertices[unit_vertex_id];
+    GPUISurface image = images[image_id];
+    float2 position = unit_vertex * image.target_size + image.origin;
+    float4 device_position = to_device_position(position, *viewport_size);
+    float2 atlas_position = (unit_vertex * image.source_size) / *atlas_size;
+
+    return QuadFragmentInput {
+        device_position,
+        atlas_position,
+        image.origin,
+        image.target_size,
+        float4(0.),
+        0.,
+        0.,
+        0.,
+        0.,
+        float4(0.),
+        0.,
+    };
+}
+
+fragment float4 surface_fragment(
+    QuadFragmentInput input [[stage_in]],
+    texture2d<float> y_atlas [[ texture(GPUISurfaceFragmentInputIndexYAtlas) ]],
+    texture2d<float> cb_cr_atlas [[ texture(GPUISurfaceFragmentInputIndexCbCrAtlas) ]]
+) {
+    constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
+    const float4x4 ycbcrToRGBTransform = float4x4(
+        float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
+        float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
+        float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
+        float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f)
+    );
+    float4 ycbcr = float4(y_atlas.sample(atlas_sampler, input.atlas_position).r,
+                          cb_cr_atlas.sample(atlas_sampler, input.atlas_position).rg, 1.0);
+
+    input.background_color = ycbcrToRGBTransform * ycbcr;
+    return quad_sdf(input);
+}
+
 struct PathAtlasVertexOutput {
     float4 position [[position]];
     float2 st_position;

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

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

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

@@ -1,4 +1,3 @@
-use super::{geometry::RectFExt, renderer::Renderer};
 use crate::{
     executor,
     geometry::{
@@ -6,25 +5,30 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     keymap::Keystroke,
-    platform::{self, Event, WindowBounds, WindowContext},
+    mac::platform::NSViewLayerContentsRedrawDuringViewResize,
+    platform::{
+        self,
+        mac::{geometry::RectFExt, renderer::Renderer},
+        Event, WindowBounds,
+    },
     InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent,
-    MouseMovedEvent, Scene,
+    MouseMovedEvent, Scene, WindowKind,
 };
 use block::ConcreteBlock;
 use cocoa::{
     appkit::{
         CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
-        NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowStyleMask,
+        NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior,
+        NSWindowStyleMask,
     },
     base::{id, nil},
     foundation::{
         NSAutoreleasePool, NSInteger, NSNotFound, NSPoint, NSRect, NSSize, NSString, NSUInteger,
     },
-    quartzcore::AutoresizingMask,
 };
 use core_graphics::display::CGRect;
 use ctor::ctor;
-use foreign_types::ForeignType as _;
+use foreign_types::ForeignTypeRef;
 use objc::{
     class,
     declare::ClassDecl,
@@ -51,8 +55,25 @@ use std::{
 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 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;
+
 #[repr(C)]
 #[derive(Copy, Clone, Debug)]
 struct NSRange {
@@ -103,55 +124,10 @@ unsafe impl objc::Encode for NSRange {
     }
 }
 
-#[allow(non_upper_case_globals)]
-const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
-
 #[ctor]
 unsafe fn build_classes() {
-    WINDOW_CLASS = {
-        let mut decl = ClassDecl::new("GPUIWindow", class!(NSWindow)).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!(sendEvent:),
-            send_event as extern "C" fn(&Object, Sel, id),
-        );
-        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!(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()
-    };
-
+    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);
@@ -265,6 +241,10 @@ unsafe fn build_classes() {
             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(
@@ -276,6 +256,50 @@ unsafe fn build_classes() {
     };
 }
 
+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!(sendEvent:),
+        send_event as extern "C" fn(&Object, Sel, id),
+    );
+    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!(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()
+}
+
 pub struct Window(Rc<RefCell<WindowState>>);
 
 ///Used to track what the IME does when we send it a keystroke.
@@ -299,6 +323,7 @@ struct WindowState {
     fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
     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>)>,
     performed_key_equivalent: bool,
@@ -306,9 +331,7 @@ struct WindowState {
     executor: Rc<executor::Foreground>,
     scene_to_render: Option<Scene>,
     renderer: Renderer,
-    command_queue: metal::CommandQueue,
     last_fresh_keydown: Option<Keystroke>,
-    layer: id,
     traffic_light_position: Option<Vector2F>,
     previous_modifiers_changed_event: Option<Event>,
     //State tracking what the IME did after the last request
@@ -329,58 +352,59 @@ impl Window {
         executor: Rc<executor::Foreground>,
         fonts: Arc<dyn platform::FontSystem>,
     ) -> Self {
-        const PIXEL_FORMAT: metal::MTLPixelFormat = metal::MTLPixelFormat::BGRA8Unorm;
-
         unsafe {
             let pool = NSAutoreleasePool::new(nil);
 
-            let frame = match options.bounds {
-                WindowBounds::Maximized => RectF::new(Default::default(), vec2f(1024., 768.)),
-                WindowBounds::Fixed(rect) => rect,
-            }
-            .to_ns_rect();
-            let mut style_mask = NSWindowStyleMask::NSClosableWindowMask
-                | NSWindowStyleMask::NSMiniaturizableWindowMask
-                | NSWindowStyleMask::NSResizableWindowMask
-                | NSWindowStyleMask::NSTitledWindowMask;
-
-            if options.titlebar_appears_transparent {
-                style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
+            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 = msg_send![WINDOW_CLASS, alloc];
+            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_(
-                frame,
+                RectF::new(Default::default(), vec2f(1024., 768.)).to_ns_rect(),
                 style_mask,
                 NSBackingStoreBuffered,
                 NO,
             );
             assert!(!native_window.is_null());
 
-            if matches!(options.bounds, WindowBounds::Maximized) {
-                let screen = native_window.screen();
-                native_window.setFrame_display_(screen.visibleFrame(), YES);
+            let screen = native_window.screen();
+            match options.bounds {
+                WindowBounds::Maximized => {
+                    native_window.setFrame_display_(screen.visibleFrame(), YES);
+                }
+                WindowBounds::Fixed(top_left_bounds) => {
+                    let frame = screen.visibleFrame();
+                    let bottom_left_bounds = RectF::new(
+                        vec2f(
+                            top_left_bounds.origin_x(),
+                            frame.size.height as f32
+                                - top_left_bounds.origin_y()
+                                - top_left_bounds.height(),
+                        ),
+                        top_left_bounds.size(),
+                    );
+                    native_window.setFrame_display_(bottom_left_bounds.to_ns_rect(), YES);
+                }
             }
 
-            let device: metal::Device = if let Some(device) = metal::Device::system_default() {
-                device
-            } else {
-                log::error!("unable to access a compatible graphics device");
-                std::process::exit(1);
-            };
-
-            let layer: id = msg_send![class!(CAMetalLayer), layer];
-            let _: () = msg_send![layer, setDevice: device.as_ptr()];
-            let _: () = msg_send![layer, setPixelFormat: PIXEL_FORMAT];
-            let _: () = msg_send![layer, setAllowsNextDrawableTimeout: NO];
-            let _: () = msg_send![layer, setNeedsDisplayOnBoundsChange: YES];
-            let _: () = msg_send![layer, setPresentsWithTransaction: YES];
-            let _: () = msg_send![
-                layer,
-                setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
-                    | AutoresizingMask::HEIGHT_SIZABLE
-            ];
-
             let native_view: id = msg_send![VIEW_CLASS, alloc];
             let native_view = NSView::init(native_view);
             assert!(!native_view.is_null());
@@ -394,22 +418,19 @@ impl Window {
                 close_callback: None,
                 activate_callback: None,
                 fullscreen_callback: None,
+                appearance_changed_callback: None,
                 input_handler: None,
                 pending_key_down: None,
                 performed_key_equivalent: false,
                 synthetic_drag_counter: 0,
                 executor,
                 scene_to_render: Default::default(),
-                renderer: Renderer::new(
-                    device.clone(),
-                    PIXEL_FORMAT,
-                    get_scale_factor(native_window),
-                    fonts,
-                ),
-                command_queue: device.new_command_queue(),
+                renderer: Renderer::new(true, fonts),
                 last_fresh_keydown: None,
-                layer,
-                traffic_light_position: options.traffic_light_position,
+                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,
@@ -425,13 +446,28 @@ impl Window {
                 Rc::into_raw(window.0.clone()) as *const c_void,
             );
 
-            if let Some(title) = options.title.as_ref() {
+            if let Some(title) = options.titlebar.as_ref().and_then(|t| t.title) {
                 native_window.setTitle_(NSString::alloc(nil).init_str(title));
             }
-            if options.titlebar_appears_transparent {
+
+            native_window.setMovable_(options.is_movable as BOOL);
+
+            if options
+                .titlebar
+                .map_or(true, |titlebar| titlebar.appears_transparent)
+            {
                 native_window.setTitlebarAppearsTransparent_(YES);
             }
-            native_window.setAcceptsMouseMovedEvents_(YES);
+
+            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: NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect
+                owner: native_view
+                userInfo: nil
+            ];
+            let _: () = msg_send![native_view, addTrackingArea: tracking_area.autorelease()];
 
             native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
             native_view.setWantsBestResolutionOpenGLSurface_(YES);
@@ -450,7 +486,24 @@ impl Window {
             native_window.setContentView_(native_view.autorelease());
             native_window.makeFirstResponder_(native_view);
 
-            native_window.center();
+            if options.center {
+                native_window.center();
+            }
+
+            match options.kind {
+                WindowKind::Normal => native_window.setLevel_(NSNormalWindowLevel),
+                WindowKind::PopUp => {
+                    native_window.setLevel_(NSPopUpWindowLevel);
+                    let _: () = msg_send![
+                        native_window,
+                        setAnimationBehavior: NSWindowAnimationBehaviorUtilityWindow
+                    ];
+                    native_window.setCollectionBehavior_(
+                        NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces |
+                        NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary
+                    );
+                }
+            }
             native_window.makeKeyAndOrderFront_(nil);
 
             window.0.borrow().move_traffic_light();
@@ -637,11 +690,13 @@ impl platform::Window for Window {
             })
             .detach();
     }
-}
 
-impl platform::WindowContext for Window {
-    fn size(&self) -> Vector2F {
-        self.0.as_ref().borrow().size()
+    fn bounds(&self) -> RectF {
+        self.0.as_ref().borrow().bounds()
+    }
+
+    fn content_size(&self) -> Vector2F {
+        self.0.as_ref().borrow().content_size()
     }
 
     fn scale_factor(&self) -> f32 {
@@ -655,6 +710,17 @@ impl platform::WindowContext for Window {
     fn titlebar_height(&self) -> f32 {
         self.0.as_ref().borrow().titlebar_height()
     }
+
+    fn appearance(&self) -> crate::Appearance {
+        unsafe {
+            let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance];
+            crate::Appearance::from_native(appearance)
+        }
+    }
+
+    fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
+        self.0.borrow_mut().appearance_changed_callback = Some(callback);
+    }
 }
 
 impl WindowState {
@@ -701,10 +767,25 @@ impl WindowState {
             }
         }
     }
-}
 
-impl platform::WindowContext for WindowState {
-    fn size(&self) -> Vector2F {
+    fn bounds(&self) -> RectF {
+        unsafe {
+            let screen_frame = self.native_window.screen().visibleFrame();
+            let window_frame = NSWindow::frame(self.native_window);
+            let origin = vec2f(
+                window_frame.origin.x as f32,
+                (window_frame.origin.y - screen_frame.size.height - window_frame.size.height)
+                    as f32,
+            );
+            let size = vec2f(
+                window_frame.size.width as f32,
+                window_frame.size.height as f32,
+            );
+            RectF::new(origin, size)
+        }
+    }
+
+    fn content_size(&self) -> Vector2F {
         let NSSize { width, height, .. } =
             unsafe { NSView::frame(self.native_window.contentView()) }.size;
         vec2f(width as f32, height as f32)
@@ -781,7 +862,8 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
 
     let mut window_state_borrow = window_state.as_ref().borrow_mut();
 
-    let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
+    let event =
+        unsafe { Event::from_native(native_event, Some(window_state_borrow.content_size().y())) };
 
     if let Some(event) = event {
         if key_equivalent {
@@ -873,7 +955,8 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
     let weak_window_state = Rc::downgrade(&window_state);
     let mut window_state_borrow = window_state.as_ref().borrow_mut();
 
-    let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
+    let event =
+        unsafe { Event::from_native(native_event, Some(window_state_borrow.content_size().y())) };
     if let Some(event) = event {
         match &event {
             Event::MouseMoved(
@@ -992,16 +1075,30 @@ fn window_fullscreen_changed(this: &Object, is_fullscreen: bool) {
 }
 
 extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
-    let is_active = if selector == sel!(windowDidBecomeKey:) {
-        true
-    } else if selector == sel!(windowDidResignKey:) {
-        false
-    } else {
-        unreachable!();
-    };
-
     let window_state = unsafe { get_window_state(this) };
-    let executor = window_state.as_ref().borrow().executor.clone();
+    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();
@@ -1049,7 +1146,7 @@ extern "C" fn close_window(this: &Object, _: Sel) {
 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.layer
+    window_state.renderer.layer().as_ptr() as id
 }
 
 extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
@@ -1058,14 +1155,20 @@ extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
 
     unsafe {
         let scale_factor = window_state_borrow.scale_factor() as f64;
-        let size = window_state_borrow.size();
+        let size = window_state_borrow.content_size();
         let drawable_size: NSSize = NSSize {
             width: size.x() as f64 * scale_factor,
             height: size.y() as f64 * scale_factor,
         };
 
-        let _: () = msg_send![window_state_borrow.layer, setContentsScale: scale_factor];
-        let _: () = msg_send![window_state_borrow.layer, setDrawableSize: drawable_size];
+        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() {
@@ -1079,7 +1182,7 @@ 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.size() == vec2f(size.width as f32, size.height as f32) {
+    if window_state_borrow.content_size() == vec2f(size.width as f32, size.height as f32) {
         return;
     }
 
@@ -1094,7 +1197,10 @@ extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
     };
 
     unsafe {
-        let _: () = msg_send![window_state_borrow.layer, setDrawableSize: drawable_size];
+        let _: () = msg_send![
+            window_state_borrow.renderer.layer(),
+            setDrawableSize: drawable_size
+        ];
     }
 
     drop(window_state_borrow);
@@ -1110,25 +1216,8 @@ 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() {
-            let drawable: &metal::MetalDrawableRef = msg_send![window_state.layer, nextDrawable];
-            let command_queue = window_state.command_queue.clone();
-            let command_buffer = command_queue.new_command_buffer();
-
-            let size = window_state.size();
-            let scale_factor = window_state.scale_factor();
-
-            window_state.renderer.render(
-                &scene,
-                size * scale_factor,
-                command_buffer,
-                drawable.texture(),
-            );
-
-            command_buffer.commit();
-            command_buffer.wait_until_completed();
-            drawable.present();
+            window_state.renderer.render(&scene);
         };
     }
 }
@@ -1301,6 +1390,18 @@ extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
     }
 }
 
+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);
+        }
+    }
+}
+
 async fn synthetic_drag(
     window_state: Weak<RefCell<WindowState>>,
     drag_id: usize,

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

@@ -1,6 +1,9 @@
 use super::{AppVersion, CursorStyle, WindowBounds};
 use crate::{
-    geometry::vector::{vec2f, Vector2F},
+    geometry::{
+        rect::RectF,
+        vector::{vec2f, Vector2F},
+    },
     keymap, Action, ClipboardItem,
 };
 use anyhow::{anyhow, Result};
@@ -120,6 +123,14 @@ impl super::Platform for Platform {
 
     fn activate(&self, _ignoring_other_apps: bool) {}
 
+    fn hide(&self) {}
+
+    fn hide_other_apps(&self) {}
+
+    fn unhide_other_apps(&self) {}
+
+    fn quit(&self) {}
+
     fn open_window(
         &self,
         _: usize,
@@ -136,13 +147,9 @@ impl super::Platform for Platform {
         None
     }
 
-    fn hide(&self) {}
-
-    fn hide_other_apps(&self) {}
-
-    fn unhide_other_apps(&self) {}
-
-    fn quit(&self) {}
+    fn add_status_item(&self) -> Box<dyn crate::Window> {
+        Box::new(Window::new(vec2f(24., 24.)))
+    }
 
     fn write_to_clipboard(&self, item: ClipboardItem) {
         *self.current_clipboard_item.lock() = Some(item);
@@ -224,24 +231,6 @@ impl super::Dispatcher for Dispatcher {
     }
 }
 
-impl super::WindowContext for Window {
-    fn size(&self) -> Vector2F {
-        self.size
-    }
-
-    fn scale_factor(&self) -> f32 {
-        self.scale_factor
-    }
-
-    fn titlebar_height(&self) -> f32 {
-        24.
-    }
-
-    fn present_scene(&mut self, scene: crate::Scene) {
-        self.current_scene = Some(scene);
-    }
-}
-
 impl super::Window for Window {
     fn as_any_mut(&mut self) -> &mut dyn Any {
         self
@@ -296,6 +285,32 @@ impl super::Window for Window {
     fn zoom(&self) {}
 
     fn toggle_full_screen(&self) {}
+
+    fn bounds(&self) -> RectF {
+        RectF::new(Default::default(), self.size)
+    }
+
+    fn content_size(&self) -> Vector2F {
+        self.size
+    }
+
+    fn scale_factor(&self) -> f32 {
+        self.scale_factor
+    }
+
+    fn titlebar_height(&self) -> f32 {
+        24.
+    }
+
+    fn present_scene(&mut self, scene: crate::Scene) {
+        self.current_scene = Some(scene);
+    }
+
+    fn appearance(&self) -> crate::Appearance {
+        crate::Appearance::Light
+    }
+
+    fn on_appearance_changed(&mut self, _: Box<dyn FnMut()>) {}
 }
 
 pub fn platform() -> Platform {

crates/gpui/src/presenter.rs 🔗

@@ -12,10 +12,10 @@ use crate::{
         UpOutRegionEvent, UpRegionEvent,
     },
     text_layout::TextLayoutCache,
-    Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity,
-    FontSystem, ModelHandle, MouseButton, MouseMovedEvent, MouseRegion, MouseRegionId, ParentId,
-    ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle,
-    View, ViewHandle, WeakModelHandle, WeakViewHandle,
+    Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, Appearance, AssetCache, ElementBox,
+    Entity, FontSystem, ModelHandle, MouseButton, MouseMovedEvent, MouseRegion, MouseRegionId,
+    ParentId, ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle,
+    UpgradeViewHandle, View, ViewHandle, WeakModelHandle, WeakViewHandle,
 };
 use collections::{HashMap, HashSet};
 use pathfinder_geometry::vector::{vec2f, Vector2F};
@@ -41,12 +41,14 @@ pub struct Presenter {
     clicked_button: Option<MouseButton>,
     mouse_position: Vector2F,
     titlebar_height: f32,
+    appearance: Appearance,
 }
 
 impl Presenter {
     pub fn new(
         window_id: usize,
         titlebar_height: f32,
+        appearance: Appearance,
         font_cache: Arc<FontCache>,
         text_layout_cache: TextLayoutCache,
         asset_cache: Arc<AssetCache>,
@@ -54,7 +56,7 @@ impl Presenter {
     ) -> Self {
         Self {
             window_id,
-            rendered_views: cx.render_views(window_id, titlebar_height),
+            rendered_views: cx.render_views(window_id, titlebar_height, appearance),
             cursor_regions: Default::default(),
             mouse_regions: Default::default(),
             font_cache,
@@ -66,15 +68,18 @@ impl Presenter {
             clicked_button: None,
             mouse_position: vec2f(0., 0.),
             titlebar_height,
+            appearance,
         }
     }
 
     pub fn invalidate(
         &mut self,
         invalidation: &mut WindowInvalidation,
+        appearance: Appearance,
         cx: &mut MutableAppContext,
     ) {
         cx.start_frame();
+        self.appearance = appearance;
         for view_id in &invalidation.removed {
             invalidation.updated.remove(view_id);
             self.rendered_views.remove(view_id);
@@ -91,14 +96,20 @@ impl Presenter {
                         .clicked_button
                         .map(|button| (self.clicked_region_ids.clone(), button)),
                     refreshing: false,
+                    appearance,
                 })
                 .unwrap(),
             );
         }
     }
 
-    pub fn refresh(&mut self, invalidation: &mut WindowInvalidation, cx: &mut MutableAppContext) {
-        self.invalidate(invalidation, cx);
+    pub fn refresh(
+        &mut self,
+        invalidation: &mut WindowInvalidation,
+        appearance: Appearance,
+        cx: &mut MutableAppContext,
+    ) {
+        self.invalidate(invalidation, appearance, cx);
         for (view_id, view) in &mut self.rendered_views {
             if !invalidation.updated.contains(view_id) {
                 *view = cx
@@ -111,6 +122,7 @@ impl Presenter {
                             .clicked_button
                             .map(|button| (self.clicked_region_ids.clone(), button)),
                         refreshing: true,
+                        appearance,
                     })
                     .unwrap();
             }
@@ -177,6 +189,7 @@ impl Presenter {
                 .clicked_button
                 .map(|button| (self.clicked_region_ids.clone(), button)),
             titlebar_height: self.titlebar_height,
+            appearance: self.appearance,
             window_size,
             app: cx,
         }
@@ -550,6 +563,7 @@ pub struct LayoutContext<'a> {
     pub refreshing: bool,
     pub window_size: Vector2F,
     titlebar_height: f32,
+    appearance: Appearance,
     hovered_region_ids: HashSet<MouseRegionId>,
     clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
 }
@@ -624,6 +638,7 @@ impl<'a> LayoutContext<'a> {
                 hovered_region_ids: self.hovered_region_ids.clone(),
                 clicked_region_ids: self.clicked_region_ids.clone(),
                 refreshing: self.refreshing,
+                appearance: self.appearance,
             };
             f(view, &mut render_cx)
         })

crates/gpui/src/scene.rs 🔗

@@ -12,7 +12,7 @@ use crate::{
     fonts::{FontId, GlyphId},
     geometry::{rect::RectF, vector::Vector2F},
     json::ToJson,
-    platform::CursorStyle,
+    platform::{current::Surface, CursorStyle},
     ImageData,
 };
 pub use mouse_region::*;
@@ -38,6 +38,7 @@ pub struct Layer {
     quads: Vec<Quad>,
     underlines: Vec<Underline>,
     images: Vec<Image>,
+    surfaces: Vec<Surface>,
     shadows: Vec<Shadow>,
     glyphs: Vec<Glyph>,
     image_glyphs: Vec<ImageGlyph>,
@@ -272,6 +273,10 @@ impl Scene {
         self.active_layer().push_image(image)
     }
 
+    pub fn push_surface(&mut self, surface: Surface) {
+        self.active_layer().push_surface(surface)
+    }
+
     pub fn push_underline(&mut self, underline: Underline) {
         self.active_layer().push_underline(underline)
     }
@@ -352,6 +357,7 @@ impl Layer {
             quads: Default::default(),
             underlines: Default::default(),
             images: Default::default(),
+            surfaces: Default::default(),
             shadows: Default::default(),
             image_glyphs: Default::default(),
             glyphs: Default::default(),
@@ -420,6 +426,16 @@ impl Layer {
         self.images.as_slice()
     }
 
+    fn push_surface(&mut self, surface: Surface) {
+        if can_draw(surface.bounds) {
+            self.surfaces.push(surface);
+        }
+    }
+
+    pub fn surfaces(&self) -> &[Surface] {
+        self.surfaces.as_slice()
+    }
+
     fn push_shadow(&mut self, shadow: Shadow) {
         if can_draw(shadow.bounds) {
             self.shadows.push(shadow);

crates/live_kit/Cargo.toml 🔗

@@ -0,0 +1,22 @@
+[package]
+name = "live_kit"
+version = "0.1.0"
+edition = "2021"
+description = "Bindings to LiveKit Swift client SDK"
+
+[lib]
+path = "src/live_kit.rs"
+doctest = false
+
+[dependencies]
+media = { path = "../media" }
+
+anyhow = "1.0.38"
+core-foundation = "0.9.3"
+core-graphics = "0.22.3"
+futures = "0.3"
+parking_lot = "0.11.1"
+
+[build-dependencies]
+serde = { version = "1.0", features = ["derive", "rc"] }
+serde_json = { version = "1.0", features = ["preserve_order"] }

crates/live_kit/LiveKitBridge/.gitignore 🔗

@@ -0,0 +1,9 @@
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
+xcuserdata/
+DerivedData/
+.swiftpm/config/registries.json
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+.netrc

crates/live_kit/LiveKitBridge/Package.resolved 🔗

@@ -0,0 +1,52 @@
+{
+  "object": {
+    "pins": [
+      {
+        "package": "LiveKit",
+        "repositoryURL": "https://github.com/livekit/client-sdk-swift.git",
+        "state": {
+          "branch": null,
+          "revision": "5cc3c001779ab147199ce3ea0dce465b846368b4",
+          "version": null
+        }
+      },
+      {
+        "package": "Promises",
+        "repositoryURL": "https://github.com/google/promises.git",
+        "state": {
+          "branch": null,
+          "revision": "3e4e743631e86c8c70dbc6efdc7beaa6e90fd3bb",
+          "version": "2.1.1"
+        }
+      },
+      {
+        "package": "WebRTC",
+        "repositoryURL": "https://github.com/webrtc-sdk/Specs.git",
+        "state": {
+          "branch": null,
+          "revision": "5225f2de4b6d0098803b3a0e55b255a41f293dad",
+          "version": "104.5112.2"
+        }
+      },
+      {
+        "package": "swift-log",
+        "repositoryURL": "https://github.com/apple/swift-log.git",
+        "state": {
+          "branch": null,
+          "revision": "6fe203dc33195667ce1759bf0182975e4653ba1c",
+          "version": "1.4.4"
+        }
+      },
+      {
+        "package": "SwiftProtobuf",
+        "repositoryURL": "https://github.com/apple/swift-protobuf.git",
+        "state": {
+          "branch": null,
+          "revision": "b8230909dedc640294d7324d37f4c91ad3dcf177",
+          "version": "1.20.1"
+        }
+      }
+    ]
+  },
+  "version": 1
+}

crates/live_kit/LiveKitBridge/Package.swift 🔗

@@ -0,0 +1,27 @@
+// swift-tools-version: 5.5
+
+import PackageDescription
+
+let package = Package(
+    name: "LiveKitBridge",
+    platforms: [
+        .macOS(.v10_15)
+    ],
+    products: [
+        // Products define the executables and libraries a package produces, and make them visible to other packages.
+        .library(
+            name: "LiveKitBridge",
+            type: .static,
+            targets: ["LiveKitBridge"]),
+    ],
+    dependencies: [
+        .package(url: "https://github.com/livekit/client-sdk-swift.git", revision: "5cc3c001779ab147199ce3ea0dce465b846368b4"),
+    ],
+    targets: [
+        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
+        // Targets can depend on other targets in this package, and on products in packages this package depends on.
+        .target(
+            name: "LiveKitBridge",
+            dependencies: [.product(name: "LiveKit", package: "client-sdk-swift")]),
+    ]
+)

crates/live_kit/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift 🔗

@@ -0,0 +1,105 @@
+import Foundation
+import LiveKit
+import WebRTC
+
+class LKRoomDelegate: RoomDelegate {
+    var data: UnsafeRawPointer
+    var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, UnsafeRawPointer) -> Void
+    
+    init(data: UnsafeRawPointer, onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer) -> Void) {
+        self.data = data
+        self.onDidSubscribeToRemoteVideoTrack = onDidSubscribeToRemoteVideoTrack
+    }
+
+    func room(_ room: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track: Track) {
+        if track.kind == .video {
+            self.onDidSubscribeToRemoteVideoTrack(self.data, Unmanaged.passRetained(track).toOpaque())
+        }
+    }
+}
+
+class LKVideoRenderer: NSObject, VideoRenderer {
+    var data: UnsafeRawPointer
+    var onFrame: @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Void
+    var onDrop: @convention(c) (UnsafeRawPointer) -> Void
+    var adaptiveStreamIsEnabled: Bool = false
+    var adaptiveStreamSize: CGSize = .zero
+
+    init(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Void, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) {
+        self.data = data
+        self.onFrame = onFrame
+        self.onDrop = onDrop
+    }
+
+    deinit {
+        self.onDrop(self.data)
+    }
+
+    func setSize(_ size: CGSize) {
+        print("Called setSize", size);
+    }
+
+    func renderFrame(_ frame: RTCVideoFrame?) {
+        let buffer = frame?.buffer as? RTCCVPixelBuffer
+        if let pixelBuffer = buffer?.pixelBuffer {
+            self.onFrame(self.data, pixelBuffer)
+        }
+    }
+}
+
+@_cdecl("LKRelease")
+public func LKRelease(ptr: UnsafeRawPointer)  {
+    let _ = Unmanaged<AnyObject>.fromOpaque(ptr).takeRetainedValue()
+}
+
+@_cdecl("LKRoomDelegateCreate")
+public func LKRoomDelegateCreate(data: UnsafeRawPointer, onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer {
+    let delegate = LKRoomDelegate(data: data, onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack)
+    return Unmanaged.passRetained(delegate).toOpaque()
+}
+
+@_cdecl("LKRoomCreate")
+public func LKRoomCreate(delegate: UnsafeRawPointer) -> UnsafeMutableRawPointer  {
+    let delegate = Unmanaged<LKRoomDelegate>.fromOpaque(delegate).takeUnretainedValue()
+    return Unmanaged.passRetained(Room(delegate: delegate)).toOpaque()
+}
+
+@_cdecl("LKRoomConnect")
+public func LKRoomConnect(room: UnsafeRawPointer, url: CFString, token: CFString, callback: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, callback_data: UnsafeRawPointer) {
+    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
+
+    room.connect(url as String, token as String).then { _ in
+        callback(callback_data, UnsafeRawPointer(nil) as! CFString?)
+    }.catch { error in
+        callback(callback_data, error.localizedDescription as CFString)
+    }
+}
+
+@_cdecl("LKRoomPublishVideoTrack")
+public func LKRoomPublishVideoTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, callback_data: UnsafeRawPointer) {
+    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
+    let track = Unmanaged<LocalVideoTrack>.fromOpaque(track).takeUnretainedValue()
+    room.localParticipant?.publishVideoTrack(track: track).then { _ in
+        callback(callback_data, UnsafeRawPointer(nil) as! CFString?)
+    }.catch { error in
+        callback(callback_data, error.localizedDescription as CFString)
+    }
+}
+
+@_cdecl("LKCreateScreenShareTrackForWindow")
+public func LKCreateScreenShareTrackForWindow(windowId: uint32) -> UnsafeMutableRawPointer {
+    let track = LocalVideoTrack.createMacOSScreenShareTrack(source: .window(id: windowId))
+    return Unmanaged.passRetained(track).toOpaque()
+}
+
+@_cdecl("LKVideoRendererCreate")
+public func LKVideoRendererCreate(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Void, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer {
+    Unmanaged.passRetained(LKVideoRenderer(data: data, onFrame: onFrame, onDrop: onDrop)).toOpaque()
+}
+
+@_cdecl("LKVideoTrackAddRenderer")
+public func LKVideoTrackAddRenderer(track: UnsafeRawPointer, renderer: UnsafeRawPointer) {
+    let track = Unmanaged<Track>.fromOpaque(track).takeUnretainedValue() as! VideoTrack
+    let renderer = Unmanaged<LKVideoRenderer>.fromOpaque(renderer).takeRetainedValue()
+    track.add(videoRenderer: renderer)
+}

crates/live_kit/build.rs 🔗

@@ -0,0 +1,148 @@
+use serde::Deserialize;
+use std::{
+    env,
+    path::{Path, PathBuf},
+    process::Command,
+};
+
+const SWIFT_PACKAGE_NAME: &'static str = "LiveKitBridge";
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SwiftTargetInfo {
+    pub triple: String,
+    pub unversioned_triple: String,
+    pub module_triple: String,
+    pub swift_runtime_compatibility_version: String,
+    #[serde(rename = "librariesRequireRPath")]
+    pub libraries_require_rpath: bool,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SwiftPaths {
+    pub runtime_library_paths: Vec<String>,
+    pub runtime_library_import_paths: Vec<String>,
+    pub runtime_resource_path: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct SwiftTarget {
+    pub target: SwiftTargetInfo,
+    pub paths: SwiftPaths,
+}
+
+const MACOS_TARGET_VERSION: &str = "10.15";
+
+fn main() {
+    let swift_target = get_swift_target();
+
+    build_bridge(&swift_target);
+    link_swift_stdlib(&swift_target);
+    link_webrtc_framework(&swift_target);
+}
+
+fn build_bridge(swift_target: &SwiftTarget) {
+    println!("cargo:rerun-if-changed={}/Sources", SWIFT_PACKAGE_NAME);
+    println!(
+        "cargo:rerun-if-changed={}/Package.swift",
+        SWIFT_PACKAGE_NAME
+    );
+    println!(
+        "cargo:rerun-if-changed={}/Package.resolved",
+        SWIFT_PACKAGE_NAME
+    );
+    let swift_package_root = swift_package_root();
+    if !Command::new("swift")
+        .arg("build")
+        .args(&["--configuration", &env::var("PROFILE").unwrap()])
+        .args(&["--triple", &swift_target.target.triple])
+        .current_dir(&swift_package_root)
+        .status()
+        .unwrap()
+        .success()
+    {
+        panic!(
+            "Failed to compile swift package in {}",
+            swift_package_root.display()
+        );
+    }
+
+    println!(
+        "cargo:rustc-link-search=native={}",
+        swift_target.out_dir_path().display()
+    );
+    println!("cargo:rustc-link-lib=static={}", SWIFT_PACKAGE_NAME);
+}
+
+fn link_swift_stdlib(swift_target: &SwiftTarget) {
+    swift_target
+        .paths
+        .runtime_library_paths
+        .iter()
+        .for_each(|path| {
+            println!("cargo:rustc-link-search=native={}", path);
+        });
+}
+
+fn link_webrtc_framework(swift_target: &SwiftTarget) {
+    let swift_out_dir_path = swift_target.out_dir_path();
+    println!("cargo:rustc-link-lib=framework=WebRTC");
+    println!(
+        "cargo:rustc-link-search=framework={}",
+        swift_out_dir_path.display()
+    );
+    // Find WebRTC.framework as a sibling of the executable when running tests.
+    println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
+
+    let source_path = swift_out_dir_path.join("WebRTC.framework");
+    let deps_dir_path =
+        PathBuf::from(env::var("OUT_DIR").unwrap()).join("../../../deps/WebRTC.framework");
+    let target_dir_path =
+        PathBuf::from(env::var("OUT_DIR").unwrap()).join("../../../WebRTC.framework");
+    copy_dir(&source_path, &deps_dir_path);
+    copy_dir(&source_path, &target_dir_path);
+}
+
+fn get_swift_target() -> SwiftTarget {
+    let mut arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
+    if arch == "aarch64" {
+        arch = "arm64".into();
+    }
+    let target = format!("{}-apple-macosx{}", arch, MACOS_TARGET_VERSION);
+
+    let swift_target_info_str = Command::new("swift")
+        .args(&["-target", &target, "-print-target-info"])
+        .output()
+        .unwrap()
+        .stdout;
+
+    serde_json::from_slice(&swift_target_info_str).unwrap()
+}
+
+fn swift_package_root() -> PathBuf {
+    env::current_dir().unwrap().join(SWIFT_PACKAGE_NAME)
+}
+
+fn copy_dir(source: &Path, destination: &Path) {
+    assert!(
+        Command::new("cp")
+            .arg("-r")
+            .args(&[source, destination])
+            .status()
+            .unwrap()
+            .success(),
+        "could not copy {:?} to {:?}",
+        source,
+        destination
+    );
+}
+
+impl SwiftTarget {
+    fn out_dir_path(&self) -> PathBuf {
+        swift_package_root()
+            .join(".build")
+            .join(&self.target.unversioned_triple)
+            .join(env::var("PROFILE").unwrap())
+    }
+}

crates/live_kit/src/live_kit.rs 🔗

@@ -0,0 +1,276 @@
+use anyhow::{anyhow, Context, Result};
+use core_foundation::{
+    array::CFArray,
+    base::{TCFType, TCFTypeRef},
+    dictionary::CFDictionary,
+    number::CFNumber,
+    string::{CFString, CFStringRef},
+};
+use core_graphics::window::{
+    kCGNullWindowID, kCGWindowListOptionExcludeDesktopElements, kCGWindowListOptionOnScreenOnly,
+    kCGWindowNumber, kCGWindowOwnerName, kCGWindowOwnerPID, CGWindowListCopyWindowInfo,
+};
+use futures::{
+    channel::{mpsc, oneshot},
+    Future,
+};
+use media::core_video::{CVImageBuffer, CVImageBufferRef};
+use parking_lot::Mutex;
+use std::{
+    ffi::c_void,
+    sync::{Arc, Weak},
+};
+
+extern "C" {
+    fn LKRelease(object: *const c_void);
+
+    fn LKRoomDelegateCreate(
+        callback_data: *mut c_void,
+        on_did_subscribe_to_remote_video_track: extern "C" fn(
+            callback_data: *mut c_void,
+            remote_track: *const c_void,
+        ),
+    ) -> *const c_void;
+
+    fn LKRoomCreate(delegate: *const c_void) -> *const c_void;
+    fn LKRoomConnect(
+        room: *const c_void,
+        url: CFStringRef,
+        token: CFStringRef,
+        callback: extern "C" fn(*mut c_void, CFStringRef),
+        callback_data: *mut c_void,
+    );
+    fn LKRoomPublishVideoTrack(
+        room: *const c_void,
+        track: *const c_void,
+        callback: extern "C" fn(*mut c_void, CFStringRef),
+        callback_data: *mut c_void,
+    );
+
+    fn LKVideoRendererCreate(
+        callback_data: *mut c_void,
+        on_frame: extern "C" fn(callback_data: *mut c_void, frame: CVImageBufferRef),
+        on_drop: extern "C" fn(callback_data: *mut c_void),
+    ) -> *const c_void;
+
+    fn LKVideoTrackAddRenderer(track: *const c_void, renderer: *const c_void);
+
+    fn LKCreateScreenShareTrackForWindow(windowId: u32) -> *const c_void;
+}
+
+pub struct Room {
+    native_room: *const c_void,
+    remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<Arc<RemoteVideoTrack>>>>,
+    _delegate: RoomDelegate,
+}
+
+impl Room {
+    pub fn new() -> Arc<Self> {
+        Arc::new_cyclic(|weak_room| {
+            let delegate = RoomDelegate::new(weak_room.clone());
+            Self {
+                native_room: unsafe { LKRoomCreate(delegate.native_delegate) },
+                remote_video_track_subscribers: Default::default(),
+                _delegate: delegate,
+            }
+        })
+    }
+
+    pub fn connect(&self, url: &str, token: &str) -> impl Future<Output = Result<()>> {
+        let url = CFString::new(url);
+        let token = CFString::new(token);
+        let (did_connect, tx, rx) = Self::build_done_callback();
+        unsafe {
+            LKRoomConnect(
+                self.native_room,
+                url.as_concrete_TypeRef(),
+                token.as_concrete_TypeRef(),
+                did_connect,
+                tx,
+            )
+        }
+
+        async { rx.await.unwrap().context("error connecting to room") }
+    }
+
+    pub fn publish_video_track(&self, track: &LocalVideoTrack) -> impl Future<Output = Result<()>> {
+        let (did_publish, tx, rx) = Self::build_done_callback();
+        unsafe {
+            LKRoomPublishVideoTrack(self.native_room, track.0, did_publish, tx);
+        }
+        async { rx.await.unwrap().context("error publishing video track") }
+    }
+
+    pub fn remote_video_tracks(&self) -> mpsc::UnboundedReceiver<Arc<RemoteVideoTrack>> {
+        let (tx, rx) = mpsc::unbounded();
+        self.remote_video_track_subscribers.lock().push(tx);
+        rx
+    }
+
+    fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
+        let track = Arc::new(track);
+        self.remote_video_track_subscribers
+            .lock()
+            .retain(|tx| tx.unbounded_send(track.clone()).is_ok());
+    }
+
+    fn build_done_callback() -> (
+        extern "C" fn(*mut c_void, CFStringRef),
+        *mut c_void,
+        oneshot::Receiver<Result<()>>,
+    ) {
+        let (tx, rx) = oneshot::channel();
+        extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) {
+            let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<()>>) };
+            if error.is_null() {
+                let _ = tx.send(Ok(()));
+            } else {
+                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
+                let _ = tx.send(Err(anyhow!(error)));
+            }
+        }
+        (
+            done_callback,
+            Box::into_raw(Box::new(tx)) as *mut c_void,
+            rx,
+        )
+    }
+}
+
+impl Drop for Room {
+    fn drop(&mut self) {
+        unsafe { LKRelease(self.native_room) }
+    }
+}
+
+struct RoomDelegate {
+    native_delegate: *const c_void,
+    weak_room: *const Room,
+}
+
+impl RoomDelegate {
+    fn new(weak_room: Weak<Room>) -> Self {
+        let weak_room = Weak::into_raw(weak_room);
+        let native_delegate = unsafe {
+            LKRoomDelegateCreate(
+                weak_room as *mut c_void,
+                Self::on_did_subscribe_to_remote_video_track,
+            )
+        };
+        Self {
+            native_delegate,
+            weak_room,
+        }
+    }
+
+    extern "C" fn on_did_subscribe_to_remote_video_track(room: *mut c_void, track: *const c_void) {
+        let room = unsafe { Weak::from_raw(room as *mut Room) };
+        let track = RemoteVideoTrack(track);
+        if let Some(room) = room.upgrade() {
+            room.did_subscribe_to_remote_video_track(track);
+        }
+        let _ = Weak::into_raw(room);
+    }
+}
+
+impl Drop for RoomDelegate {
+    fn drop(&mut self) {
+        unsafe {
+            LKRelease(self.native_delegate);
+            let _ = Weak::from_raw(self.weak_room);
+        }
+    }
+}
+
+pub struct LocalVideoTrack(*const c_void);
+
+impl LocalVideoTrack {
+    pub fn screen_share_for_window(window_id: u32) -> Self {
+        Self(unsafe { LKCreateScreenShareTrackForWindow(window_id) })
+    }
+}
+
+impl Drop for LocalVideoTrack {
+    fn drop(&mut self) {
+        unsafe { LKRelease(self.0) }
+    }
+}
+
+pub struct RemoteVideoTrack(*const c_void);
+
+impl RemoteVideoTrack {
+    pub fn add_renderer<F>(&self, callback: F)
+    where
+        F: 'static + FnMut(CVImageBuffer),
+    {
+        extern "C" fn on_frame<F>(callback_data: *mut c_void, frame: CVImageBufferRef)
+        where
+            F: FnMut(CVImageBuffer),
+        {
+            unsafe {
+                let buffer = CVImageBuffer::wrap_under_get_rule(frame);
+                let callback = &mut *(callback_data as *mut F);
+                callback(buffer);
+            }
+        }
+
+        extern "C" fn on_drop<F>(callback_data: *mut c_void) {
+            unsafe {
+                let _ = Box::from_raw(callback_data as *mut F);
+            }
+        }
+
+        let callback_data = Box::into_raw(Box::new(callback));
+        unsafe {
+            let renderer =
+                LKVideoRendererCreate(callback_data as *mut c_void, on_frame::<F>, on_drop::<F>);
+            LKVideoTrackAddRenderer(self.0, renderer);
+        }
+    }
+}
+
+impl Drop for RemoteVideoTrack {
+    fn drop(&mut self) {
+        unsafe { LKRelease(self.0) }
+    }
+}
+
+#[derive(Debug)]
+pub struct WindowInfo {
+    pub id: u32,
+    pub owner_pid: i32,
+    pub owner_name: Option<String>,
+}
+
+pub fn list_windows() -> Vec<WindowInfo> {
+    unsafe {
+        let dicts = CFArray::<CFDictionary>::wrap_under_get_rule(CGWindowListCopyWindowInfo(
+            kCGWindowListOptionOnScreenOnly | kCGWindowListOptionExcludeDesktopElements,
+            kCGNullWindowID,
+        ));
+
+        dicts
+            .iter()
+            .map(|dict| {
+                let id =
+                    CFNumber::wrap_under_get_rule(*dict.get(kCGWindowNumber.as_void_ptr()) as _)
+                        .to_i64()
+                        .unwrap() as u32;
+
+                let owner_pid =
+                    CFNumber::wrap_under_get_rule(*dict.get(kCGWindowOwnerPID.as_void_ptr()) as _)
+                        .to_i32()
+                        .unwrap();
+
+                let owner_name = dict
+                    .find(kCGWindowOwnerName.as_void_ptr())
+                    .map(|name| CFString::wrap_under_get_rule(*name as _).to_string());
+                WindowInfo {
+                    id,
+                    owner_pid,
+                    owner_name,
+                }
+            })
+            .collect()
+    }
+}

crates/media/Cargo.toml 🔗

@@ -0,0 +1,20 @@
+[package]
+name = "media"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+path = "src/media.rs"
+doctest = false
+
+[dependencies]
+anyhow = "1.0"
+block = "0.1"
+bytes = "1.2"
+core-foundation = "0.9.3"
+foreign-types = "0.3"
+metal = "0.21.0"
+objc = "0.2"
+
+[build-dependencies]
+bindgen = "0.59.2"

crates/media/build.rs 🔗

@@ -0,0 +1,39 @@
+use std::{env, path::PathBuf, process::Command};
+
+fn main() {
+    let sdk_path = String::from_utf8(
+        Command::new("xcrun")
+            .args(&["--sdk", "macosx", "--show-sdk-path"])
+            .output()
+            .unwrap()
+            .stdout,
+    )
+    .unwrap();
+    let sdk_path = sdk_path.trim_end();
+
+    println!("cargo:rerun-if-changed=src/bindings.h");
+    let bindings = bindgen::Builder::default()
+        .header("src/bindings.h")
+        .clang_arg(format!("-isysroot{}", sdk_path))
+        .clang_arg("-xobjective-c")
+        .allowlist_type("CMItemIndex")
+        .allowlist_type("CMSampleTimingInfo")
+        .allowlist_type("CMVideoCodecType")
+        .allowlist_type("VTEncodeInfoFlags")
+        .allowlist_function("CMTimeMake")
+        .allowlist_var("kCVPixelFormatType_.*")
+        .allowlist_var("kCVReturn.*")
+        .allowlist_var("VTEncodeInfoFlags_.*")
+        .allowlist_var("kCMVideoCodecType_.*")
+        .allowlist_var("kCMTime.*")
+        .allowlist_var("kCMSampleAttachmentKey_.*")
+        .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("bindings.rs"))
+        .expect("couldn't write dispatch bindings");
+}

crates/media/src/bindings.h 🔗

@@ -0,0 +1,5 @@
+#import <CoreMedia/CMFormatDescription.h>
+#import <CoreMedia/CMSampleBuffer.h>
+#import <CoreVideo/CVPixelFormatDescription.h>
+#import <CoreVideo/CVReturn.h>
+#import <VideoToolbox/VTCompressionSession.h>

crates/media/src/bindings.rs 🔗

@@ -0,0 +1,8 @@
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(unused)]
+
+use objc::*;
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

crates/media/src/media.rs 🔗

@@ -0,0 +1,534 @@
+#![allow(non_snake_case)]
+#![allow(non_camel_case_types)]
+
+mod bindings;
+
+use core_foundation::{
+    base::{CFTypeID, TCFType},
+    declare_TCFType, impl_CFTypeDescription, impl_TCFType,
+};
+use std::ffi::c_void;
+
+pub mod io_surface {
+    use super::*;
+
+    #[repr(C)]
+    pub struct __IOSurface(c_void);
+    // The ref type must be a pointer to the underlying struct.
+    pub type IOSurfaceRef = *const __IOSurface;
+
+    declare_TCFType!(IOSurface, IOSurfaceRef);
+    impl_TCFType!(IOSurface, IOSurfaceRef, IOSurfaceGetTypeID);
+    impl_CFTypeDescription!(IOSurface);
+
+    #[link(name = "IOSurface", kind = "framework")]
+    extern "C" {
+        fn IOSurfaceGetTypeID() -> CFTypeID;
+    }
+}
+
+pub mod core_video {
+    #![allow(non_snake_case)]
+
+    use super::*;
+    pub use crate::bindings::{
+        kCVPixelFormatType_32BGRA, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
+        kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, kCVPixelFormatType_420YpCbCr8Planar,
+    };
+    use crate::bindings::{kCVReturnSuccess, CVReturn, OSType};
+    use anyhow::{anyhow, Result};
+    use core_foundation::{
+        base::kCFAllocatorDefault, dictionary::CFDictionaryRef, mach_port::CFAllocatorRef,
+    };
+    use foreign_types::ForeignTypeRef;
+    use io_surface::{IOSurface, IOSurfaceRef};
+    use metal::{MTLDevice, MTLPixelFormat};
+    use std::ptr;
+
+    #[repr(C)]
+    pub struct __CVImageBuffer(c_void);
+    // The ref type must be a pointer to the underlying struct.
+    pub type CVImageBufferRef = *const __CVImageBuffer;
+
+    declare_TCFType!(CVImageBuffer, CVImageBufferRef);
+    impl_TCFType!(CVImageBuffer, CVImageBufferRef, CVImageBufferGetTypeID);
+    impl_CFTypeDescription!(CVImageBuffer);
+
+    impl CVImageBuffer {
+        pub fn io_surface(&self) -> IOSurface {
+            unsafe {
+                IOSurface::wrap_under_get_rule(CVPixelBufferGetIOSurface(
+                    self.as_concrete_TypeRef(),
+                ))
+            }
+        }
+
+        pub fn width(&self) -> usize {
+            unsafe { CVPixelBufferGetWidth(self.as_concrete_TypeRef()) }
+        }
+
+        pub fn height(&self) -> usize {
+            unsafe { CVPixelBufferGetHeight(self.as_concrete_TypeRef()) }
+        }
+
+        pub fn plane_width(&self, plane: usize) -> usize {
+            unsafe { CVPixelBufferGetWidthOfPlane(self.as_concrete_TypeRef(), plane) }
+        }
+
+        pub fn plane_height(&self, plane: usize) -> usize {
+            unsafe { CVPixelBufferGetHeightOfPlane(self.as_concrete_TypeRef(), plane) }
+        }
+
+        pub fn pixel_format_type(&self) -> OSType {
+            unsafe { CVPixelBufferGetPixelFormatType(self.as_concrete_TypeRef()) }
+        }
+    }
+
+    #[link(name = "CoreVideo", kind = "framework")]
+    extern "C" {
+        fn CVImageBufferGetTypeID() -> CFTypeID;
+        fn CVPixelBufferGetIOSurface(buffer: CVImageBufferRef) -> IOSurfaceRef;
+        fn CVPixelBufferGetWidth(buffer: CVImageBufferRef) -> usize;
+        fn CVPixelBufferGetHeight(buffer: CVImageBufferRef) -> usize;
+        fn CVPixelBufferGetWidthOfPlane(buffer: CVImageBufferRef, plane: usize) -> usize;
+        fn CVPixelBufferGetHeightOfPlane(buffer: CVImageBufferRef, plane: usize) -> usize;
+        fn CVPixelBufferGetPixelFormatType(buffer: CVImageBufferRef) -> OSType;
+    }
+
+    #[repr(C)]
+    pub struct __CVMetalTextureCache(c_void);
+    pub type CVMetalTextureCacheRef = *const __CVMetalTextureCache;
+
+    declare_TCFType!(CVMetalTextureCache, CVMetalTextureCacheRef);
+    impl_TCFType!(
+        CVMetalTextureCache,
+        CVMetalTextureCacheRef,
+        CVMetalTextureCacheGetTypeID
+    );
+    impl_CFTypeDescription!(CVMetalTextureCache);
+
+    impl CVMetalTextureCache {
+        pub fn new(metal_device: *mut MTLDevice) -> Result<Self> {
+            unsafe {
+                let mut this = ptr::null();
+                let result = CVMetalTextureCacheCreate(
+                    kCFAllocatorDefault,
+                    ptr::null_mut(),
+                    metal_device,
+                    ptr::null_mut(),
+                    &mut this,
+                );
+                if result == kCVReturnSuccess {
+                    Ok(CVMetalTextureCache::wrap_under_create_rule(this))
+                } else {
+                    Err(anyhow!("could not create texture cache, code: {}", result))
+                }
+            }
+        }
+
+        pub fn create_texture_from_image(
+            &self,
+            source: CVImageBufferRef,
+            texture_attributes: CFDictionaryRef,
+            pixel_format: MTLPixelFormat,
+            width: usize,
+            height: usize,
+            plane_index: usize,
+        ) -> Result<CVMetalTexture> {
+            unsafe {
+                let mut this = ptr::null();
+                let result = CVMetalTextureCacheCreateTextureFromImage(
+                    kCFAllocatorDefault,
+                    self.as_concrete_TypeRef(),
+                    source,
+                    texture_attributes,
+                    pixel_format,
+                    width,
+                    height,
+                    plane_index,
+                    &mut this,
+                );
+                if result == kCVReturnSuccess {
+                    Ok(CVMetalTexture::wrap_under_create_rule(this))
+                } else {
+                    Err(anyhow!("could not create texture, code: {}", result))
+                }
+            }
+        }
+    }
+
+    #[link(name = "CoreVideo", kind = "framework")]
+    extern "C" {
+        fn CVMetalTextureCacheGetTypeID() -> CFTypeID;
+        fn CVMetalTextureCacheCreate(
+            allocator: CFAllocatorRef,
+            cache_attributes: CFDictionaryRef,
+            metal_device: *const MTLDevice,
+            texture_attributes: CFDictionaryRef,
+            cache_out: *mut CVMetalTextureCacheRef,
+        ) -> CVReturn;
+        fn CVMetalTextureCacheCreateTextureFromImage(
+            allocator: CFAllocatorRef,
+            texture_cache: CVMetalTextureCacheRef,
+            source_image: CVImageBufferRef,
+            texture_attributes: CFDictionaryRef,
+            pixel_format: MTLPixelFormat,
+            width: usize,
+            height: usize,
+            plane_index: usize,
+            texture_out: *mut CVMetalTextureRef,
+        ) -> CVReturn;
+    }
+
+    #[repr(C)]
+    pub struct __CVMetalTexture(c_void);
+    pub type CVMetalTextureRef = *const __CVMetalTexture;
+
+    declare_TCFType!(CVMetalTexture, CVMetalTextureRef);
+    impl_TCFType!(CVMetalTexture, CVMetalTextureRef, CVMetalTextureGetTypeID);
+    impl_CFTypeDescription!(CVMetalTexture);
+
+    impl CVMetalTexture {
+        pub fn as_texture_ref(&self) -> &metal::TextureRef {
+            unsafe {
+                let texture = CVMetalTextureGetTexture(self.as_concrete_TypeRef());
+                &metal::TextureRef::from_ptr(texture as *mut _)
+            }
+        }
+    }
+
+    #[link(name = "CoreVideo", kind = "framework")]
+    extern "C" {
+        fn CVMetalTextureGetTypeID() -> CFTypeID;
+        fn CVMetalTextureGetTexture(texture: CVMetalTextureRef) -> *mut c_void;
+    }
+}
+
+pub mod core_media {
+    #![allow(non_snake_case)]
+
+    pub use crate::bindings::{
+        kCMSampleAttachmentKey_NotSync, kCMTimeInvalid, kCMVideoCodecType_H264, CMItemIndex,
+        CMSampleTimingInfo, CMTime, CMTimeMake, CMVideoCodecType,
+    };
+    use crate::core_video::{CVImageBuffer, CVImageBufferRef};
+    use anyhow::{anyhow, Result};
+    use core_foundation::{
+        array::{CFArray, CFArrayRef},
+        base::{CFTypeID, OSStatus, TCFType},
+        declare_TCFType,
+        dictionary::CFDictionary,
+        impl_CFTypeDescription, impl_TCFType,
+        string::CFString,
+    };
+    use std::{ffi::c_void, ptr};
+
+    #[repr(C)]
+    pub struct __CMSampleBuffer(c_void);
+    // The ref type must be a pointer to the underlying struct.
+    pub type CMSampleBufferRef = *const __CMSampleBuffer;
+
+    declare_TCFType!(CMSampleBuffer, CMSampleBufferRef);
+    impl_TCFType!(CMSampleBuffer, CMSampleBufferRef, CMSampleBufferGetTypeID);
+    impl_CFTypeDescription!(CMSampleBuffer);
+
+    impl CMSampleBuffer {
+        pub fn attachments(&self) -> Vec<CFDictionary<CFString>> {
+            unsafe {
+                let attachments =
+                    CMSampleBufferGetSampleAttachmentsArray(self.as_concrete_TypeRef(), true);
+                CFArray::<CFDictionary>::wrap_under_get_rule(attachments)
+                    .into_iter()
+                    .map(|attachments| {
+                        CFDictionary::wrap_under_get_rule(attachments.as_concrete_TypeRef())
+                    })
+                    .collect()
+            }
+        }
+
+        pub fn image_buffer(&self) -> CVImageBuffer {
+            unsafe {
+                CVImageBuffer::wrap_under_get_rule(CMSampleBufferGetImageBuffer(
+                    self.as_concrete_TypeRef(),
+                ))
+            }
+        }
+
+        pub fn sample_timing_info(&self, index: usize) -> Result<CMSampleTimingInfo> {
+            unsafe {
+                let mut timing_info = CMSampleTimingInfo {
+                    duration: kCMTimeInvalid,
+                    presentationTimeStamp: kCMTimeInvalid,
+                    decodeTimeStamp: kCMTimeInvalid,
+                };
+                let result = CMSampleBufferGetSampleTimingInfo(
+                    self.as_concrete_TypeRef(),
+                    index as CMItemIndex,
+                    &mut timing_info,
+                );
+
+                if result == 0 {
+                    Ok(timing_info)
+                } else {
+                    Err(anyhow!("error getting sample timing info, code {}", result))
+                }
+            }
+        }
+
+        pub fn format_description(&self) -> CMFormatDescription {
+            unsafe {
+                CMFormatDescription::wrap_under_get_rule(CMSampleBufferGetFormatDescription(
+                    self.as_concrete_TypeRef(),
+                ))
+            }
+        }
+
+        pub fn data(&self) -> CMBlockBuffer {
+            unsafe {
+                CMBlockBuffer::wrap_under_get_rule(CMSampleBufferGetDataBuffer(
+                    self.as_concrete_TypeRef(),
+                ))
+            }
+        }
+    }
+
+    #[link(name = "CoreMedia", kind = "framework")]
+    extern "C" {
+        fn CMSampleBufferGetTypeID() -> CFTypeID;
+        fn CMSampleBufferGetSampleAttachmentsArray(
+            buffer: CMSampleBufferRef,
+            create_if_necessary: bool,
+        ) -> CFArrayRef;
+        fn CMSampleBufferGetImageBuffer(buffer: CMSampleBufferRef) -> CVImageBufferRef;
+        fn CMSampleBufferGetSampleTimingInfo(
+            buffer: CMSampleBufferRef,
+            index: CMItemIndex,
+            timing_info_out: *mut CMSampleTimingInfo,
+        ) -> OSStatus;
+        fn CMSampleBufferGetFormatDescription(buffer: CMSampleBufferRef) -> CMFormatDescriptionRef;
+        fn CMSampleBufferGetDataBuffer(sample_buffer: CMSampleBufferRef) -> CMBlockBufferRef;
+    }
+
+    #[repr(C)]
+    pub struct __CMFormatDescription(c_void);
+    pub type CMFormatDescriptionRef = *const __CMFormatDescription;
+
+    declare_TCFType!(CMFormatDescription, CMFormatDescriptionRef);
+    impl_TCFType!(
+        CMFormatDescription,
+        CMFormatDescriptionRef,
+        CMFormatDescriptionGetTypeID
+    );
+    impl_CFTypeDescription!(CMFormatDescription);
+
+    impl CMFormatDescription {
+        pub fn h264_parameter_set_count(&self) -> usize {
+            unsafe {
+                let mut count = 0;
+                let result = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
+                    self.as_concrete_TypeRef(),
+                    0,
+                    ptr::null_mut(),
+                    ptr::null_mut(),
+                    &mut count,
+                    ptr::null_mut(),
+                );
+                assert_eq!(result, 0);
+                count
+            }
+        }
+
+        pub fn h264_parameter_set_at_index(&self, index: usize) -> Result<&[u8]> {
+            unsafe {
+                let mut bytes = ptr::null();
+                let mut len = 0;
+                let result = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
+                    self.as_concrete_TypeRef(),
+                    index,
+                    &mut bytes,
+                    &mut len,
+                    ptr::null_mut(),
+                    ptr::null_mut(),
+                );
+                if result == 0 {
+                    Ok(std::slice::from_raw_parts(bytes, len))
+                } else {
+                    Err(anyhow!("error getting parameter set, code: {}", result))
+                }
+            }
+        }
+    }
+
+    #[link(name = "CoreMedia", kind = "framework")]
+    extern "C" {
+        fn CMFormatDescriptionGetTypeID() -> CFTypeID;
+        fn CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
+            video_desc: CMFormatDescriptionRef,
+            parameter_set_index: usize,
+            parameter_set_pointer_out: *mut *const u8,
+            parameter_set_size_out: *mut usize,
+            parameter_set_count_out: *mut usize,
+            NALUnitHeaderLengthOut: *mut isize,
+        ) -> OSStatus;
+    }
+
+    #[repr(C)]
+    pub struct __CMBlockBuffer(c_void);
+    pub type CMBlockBufferRef = *const __CMBlockBuffer;
+
+    declare_TCFType!(CMBlockBuffer, CMBlockBufferRef);
+    impl_TCFType!(CMBlockBuffer, CMBlockBufferRef, CMBlockBufferGetTypeID);
+    impl_CFTypeDescription!(CMBlockBuffer);
+
+    impl CMBlockBuffer {
+        pub fn bytes(&self) -> &[u8] {
+            unsafe {
+                let mut bytes = ptr::null();
+                let mut len = 0;
+                let result = CMBlockBufferGetDataPointer(
+                    self.as_concrete_TypeRef(),
+                    0,
+                    &mut 0,
+                    &mut len,
+                    &mut bytes,
+                );
+                assert!(result == 0, "could not get block buffer data");
+                std::slice::from_raw_parts(bytes, len)
+            }
+        }
+    }
+
+    #[link(name = "CoreMedia", kind = "framework")]
+    extern "C" {
+        fn CMBlockBufferGetTypeID() -> CFTypeID;
+        fn CMBlockBufferGetDataPointer(
+            buffer: CMBlockBufferRef,
+            offset: usize,
+            length_at_offset_out: *mut usize,
+            total_length_out: *mut usize,
+            data_pointer_out: *mut *const u8,
+        ) -> OSStatus;
+    }
+}
+
+pub mod video_toolbox {
+    #![allow(non_snake_case)]
+
+    use super::*;
+    use crate::{
+        core_media::{CMSampleBufferRef, CMTime, CMVideoCodecType},
+        core_video::CVImageBufferRef,
+    };
+    use anyhow::{anyhow, Result};
+    pub use bindings::VTEncodeInfoFlags;
+    use core_foundation::{base::OSStatus, dictionary::CFDictionaryRef, mach_port::CFAllocatorRef};
+    use std::ptr;
+
+    #[repr(C)]
+    pub struct __VTCompressionSession(c_void);
+    // The ref type must be a pointer to the underlying struct.
+    pub type VTCompressionSessionRef = *const __VTCompressionSession;
+
+    declare_TCFType!(VTCompressionSession, VTCompressionSessionRef);
+    impl_TCFType!(
+        VTCompressionSession,
+        VTCompressionSessionRef,
+        VTCompressionSessionGetTypeID
+    );
+    impl_CFTypeDescription!(VTCompressionSession);
+
+    impl VTCompressionSession {
+        pub fn new(
+            width: usize,
+            height: usize,
+            codec: CMVideoCodecType,
+            callback: VTCompressionOutputCallback,
+            callback_data: *const c_void,
+        ) -> Result<Self> {
+            unsafe {
+                let mut this = ptr::null();
+                let result = VTCompressionSessionCreate(
+                    ptr::null(),
+                    width as i32,
+                    height as i32,
+                    codec,
+                    ptr::null(),
+                    ptr::null(),
+                    ptr::null(),
+                    callback,
+                    callback_data,
+                    &mut this,
+                );
+
+                if result == 0 {
+                    Ok(Self::wrap_under_create_rule(this))
+                } else {
+                    Err(anyhow!(
+                        "error creating compression session, code {}",
+                        result
+                    ))
+                }
+            }
+        }
+
+        pub fn encode_frame(
+            &self,
+            buffer: CVImageBufferRef,
+            presentation_timestamp: CMTime,
+            duration: CMTime,
+        ) -> Result<()> {
+            unsafe {
+                let result = VTCompressionSessionEncodeFrame(
+                    self.as_concrete_TypeRef(),
+                    buffer,
+                    presentation_timestamp,
+                    duration,
+                    ptr::null(),
+                    ptr::null(),
+                    ptr::null_mut(),
+                );
+                if result == 0 {
+                    Ok(())
+                } else {
+                    Err(anyhow!("error encoding frame, code {}", result))
+                }
+            }
+        }
+    }
+
+    type VTCompressionOutputCallback = Option<
+        unsafe extern "C" fn(
+            outputCallbackRefCon: *mut c_void,
+            sourceFrameRefCon: *mut c_void,
+            status: OSStatus,
+            infoFlags: VTEncodeInfoFlags,
+            sampleBuffer: CMSampleBufferRef,
+        ),
+    >;
+
+    #[link(name = "VideoToolbox", kind = "framework")]
+    extern "C" {
+        fn VTCompressionSessionGetTypeID() -> CFTypeID;
+        fn VTCompressionSessionCreate(
+            allocator: CFAllocatorRef,
+            width: i32,
+            height: i32,
+            codec_type: CMVideoCodecType,
+            encoder_specification: CFDictionaryRef,
+            source_image_buffer_attributes: CFDictionaryRef,
+            compressed_data_allocator: CFAllocatorRef,
+            output_callback: VTCompressionOutputCallback,
+            output_callback_ref_con: *const c_void,
+            compression_session_out: *mut VTCompressionSessionRef,
+        ) -> OSStatus;
+        fn VTCompressionSessionEncodeFrame(
+            session: VTCompressionSessionRef,
+            image_buffer: CVImageBufferRef,
+            presentation_timestamp: CMTime,
+            duration: CMTime,
+            frame_properties: CFDictionaryRef,
+            source_frame_ref_con: *const c_void,
+            output_flags: *mut VTEncodeInfoFlags,
+        ) -> OSStatus;
+    }
+}

crates/theme/src/theme.rs 🔗

@@ -19,6 +19,7 @@ pub struct Theme {
     pub workspace: Workspace,
     pub context_menu: ContextMenu,
     pub chat_panel: ChatPanel,
+    pub contacts_popover: ContactsPopover,
     pub contacts_panel: ContactsPanel,
     pub contact_finder: ContactFinder,
     pub project_panel: ProjectPanel,
@@ -314,6 +315,11 @@ pub struct CommandPalette {
     pub keystroke_spacing: f32,
 }
 
+#[derive(Deserialize, Default)]
+pub struct ContactsPopover {
+    pub background: Color,
+}
+
 #[derive(Deserialize, Default)]
 pub struct ContactsPanel {
     #[serde(flatten)]

crates/zed/Cargo.toml 🔗

@@ -27,6 +27,7 @@ context_menu = { path = "../context_menu" }
 client = { path = "../client" }
 clock = { path = "../clock" }
 contacts_panel = { path = "../contacts_panel" }
+contacts_status_item = { path = "../contacts_status_item" }
 diagnostics = { path = "../diagnostics" }
 editor = { path = "../editor" }
 file_finder = { path = "../file_finder" }

crates/zed/src/zed.rs 🔗

@@ -20,7 +20,7 @@ use gpui::{
     geometry::vector::vec2f,
     impl_actions,
     platform::{WindowBounds, WindowOptions},
-    AssetSource, AsyncAppContext, ViewContext,
+    AssetSource, AsyncAppContext, TitlebarOptions, ViewContext, WindowKind,
 };
 use language::Rope;
 pub use lsp;
@@ -331,9 +331,14 @@ pub fn initialize_workspace(
 pub fn build_window_options() -> WindowOptions<'static> {
     WindowOptions {
         bounds: WindowBounds::Maximized,
-        title: None,
-        titlebar_appears_transparent: true,
-        traffic_light_position: Some(vec2f(8., 8.)),
+        titlebar: Some(TitlebarOptions {
+            title: None,
+            appears_transparent: true,
+            traffic_light_position: Some(vec2f(8., 8.)),
+        }),
+        center: false,
+        kind: WindowKind::Normal,
+        is_movable: true,
     }
 }
 

styles/package-lock.json 🔗

@@ -5,6 +5,7 @@
   "requires": true,
   "packages": {
     "": {
+      "name": "styles",
       "version": "1.0.0",
       "license": "ISC",
       "dependencies": {

styles/src/styleTree/app.ts 🔗

@@ -3,6 +3,7 @@ import chatPanel from "./chatPanel";
 import { text } from "./components";
 import contactFinder from "./contactFinder";
 import contactsPanel from "./contactsPanel";
+import contactsPopover from "./contactsPopover";
 import commandPalette from "./commandPalette";
 import editor from "./editor";
 import projectPanel from "./projectPanel";
@@ -34,6 +35,7 @@ export default function app(theme: Theme): Object {
     commandPalette: commandPalette(theme),
     projectPanel: projectPanel(theme),
     chatPanel: chatPanel(theme),
+    contactsPopover: contactsPopover(theme),
     contactsPanel: contactsPanel(theme),
     contactFinder: contactFinder(theme),
     search: search(theme),

styles/src/styleTree/contactsPopover.ts 🔗

@@ -0,0 +1,8 @@
+import Theme from "../themes/common/theme";
+import { backgroundColor } from "./components";
+
+export default function workspace(theme: Theme) {
+  return {
+    background: backgroundColor(theme, 300),
+  }
+}