Merge pull request #1630 from zed-industries/capture

Lay the groundwork for call-oriented collaboration
This commit is contained in:
Antonio Scandurra
2022-09-16 20:09:06 +02:00
committed by GitHub
46 changed files with 3114 additions and 382 deletions
Generated
+99 -2
View File
@@ -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",
+4
View File
@@ -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>

After

Width:  |  Height:  |  Size: 571 B

+32
View File
@@ -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"
+7
View File
@@ -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");
}
+71
View File
@@ -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)?)
}
+143
View File
@@ -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();
}
+32
View File
@@ -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"] }
@@ -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);
}
}
}
@@ -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());
}
}
}
}
+1 -1
View File
@@ -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);
+2 -2
View File
@@ -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.)),
+2
View File
@@ -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"
+100 -31
View File
@@ -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));
let status_item = this.cx.platform.add_status_item();
this.register_platform_window(window_id, status_item);
(window_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();
}
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,
}
}
+2 -2
View File
@@ -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))
+1 -1
View File
@@ -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(
+55 -18
View File
@@ -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,
}
}
}
+3
View File
@@ -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;
@@ -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;
}
+27 -18
View File
@@ -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();
+196 -31
View File
@@ -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,
+94 -91
View File
@@ -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;
@@ -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;
+367
View File
@@ -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>);
}
+248 -147
View File
@@ -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;
let mut style_mask;
if let Some(titlebar) = options.titlebar.as_ref() {
style_mask = NSWindowStyleMask::NSClosableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSTitledWindowMask;
if options.titlebar_appears_transparent {
style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
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,
+41 -26
View File
@@ -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 {
+22 -7
View File
@@ -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)
})
+17 -1
View File
@@ -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);
+22
View File
@@ -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"] }
+9
View File
@@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
@@ -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
}
@@ -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")]),
]
)
+3
View File
@@ -0,0 +1,3 @@
# LiveKitBridge
A description of this package.
@@ -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)
}
+148
View File
@@ -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())
}
}
+276
View File
@@ -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()
}
}
+20
View File
@@ -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"
+39
View File
@@ -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");
}
+5
View File
@@ -0,0 +1,5 @@
#import <CoreMedia/CMFormatDescription.h>
#import <CoreMedia/CMSampleBuffer.h>
#import <CoreVideo/CVPixelFormatDescription.h>
#import <CoreVideo/CVReturn.h>
#import <VideoToolbox/VTCompressionSession.h>
+8
View File
@@ -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"));
+534
View File
@@ -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;
}
}
+6
View File
@@ -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)]
+1
View File
@@ -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" }
+9 -4
View File
@@ -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,
}
}
+1
View File
@@ -5,6 +5,7 @@
"requires": true,
"packages": {
"": {
"name": "styles",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
+2
View File
@@ -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),
+8
View File
@@ -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),
}
}