diff --git a/Cargo.lock b/Cargo.lock index bafbaaf93c..a9448065c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2941,6 +2941,7 @@ name = "live_kit" version = "0.1.0" dependencies = [ "core-foundation", + "core-graphics", "futures", "serde", "serde_json", diff --git a/crates/capture/src/main.rs b/crates/capture/src/main.rs index 83454ba77a..07d284b7b7 100644 --- a/crates/capture/src/main.rs +++ b/crates/capture/src/main.rs @@ -7,7 +7,7 @@ use gpui::{ platform::current::Surface, Menu, MenuItem, ViewContext, }; -use live_kit::Room; +use live_kit::{LocalVideoTrack, Room}; use log::LevelFilter; use media::core_video::CVImageBuffer; use simplelog::SimpleLogger; @@ -42,13 +42,17 @@ fn main() { .unwrap(); let room = live_kit::Room::new(); - cx.spawn(|cx| async move { - println!("connecting..."); - room.connect("wss://zed.livekit.cloud", &token).await; - println!("connected!"); - drop(room); - }) - .detach(); + cx.foreground() + .spawn(async move { + println!("connecting..."); + room.connect("wss://zed.livekit.cloud", &token).await; + let windows = live_kit::list_windows(); + println!("connected! {:?}", windows); + + let window_id = windows.iter().next().unwrap().id; + let track = LocalVideoTrack::screen_share_for_window(window_id); + }) + .detach(); // cx.add_window(Default::default(), |cx| ScreenCaptureView::new(cx)); }); diff --git a/crates/live_kit/Cargo.toml b/crates/live_kit/Cargo.toml index 8cb4df32cc..325753b7f9 100644 --- a/crates/live_kit/Cargo.toml +++ b/crates/live_kit/Cargo.toml @@ -10,6 +10,7 @@ doctest = false [dependencies] core-foundation = "0.9.3" +core-graphics = "0.22.3" futures = "0.3" [build-dependencies] diff --git a/crates/live_kit/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift index 6a935d7ec4..a5783d69b4 100644 --- a/crates/live_kit/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift +++ b/crates/live_kit/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift @@ -1,16 +1,16 @@ import Foundation import LiveKit +@_cdecl("LKRelease") +public func LKRelease(ptr: UnsafeRawPointer) { + let _ = Unmanaged.fromOpaque(ptr).takeRetainedValue(); +} + @_cdecl("LKRoomCreate") public func LKRoomCreate() -> UnsafeMutableRawPointer { Unmanaged.passRetained(Room()).toOpaque() } -@_cdecl("LKRoomDestroy") -public func LKRoomDestroy(ptr: UnsafeRawPointer) { - let _ = Unmanaged.fromOpaque(ptr).takeRetainedValue(); -} - @_cdecl("LKRoomConnect") public func LKRoomConnect(room: UnsafeRawPointer, url: CFString, token: CFString, callback: @escaping @convention(c) (UnsafeRawPointer) -> Void, callback_data: UnsafeRawPointer) { let room = Unmanaged.fromOpaque(room).takeUnretainedValue(); @@ -21,3 +21,9 @@ public func LKRoomConnect(room: UnsafeRawPointer, url: CFString, token: CFString print(error); }; } + +@_cdecl("LKCreateScreenShareTrackForWindow") +public func LKCreateScreenShareTrackForWindow(windowId: uint32) -> UnsafeMutableRawPointer { + let track = LocalVideoTrack.createMacOSScreenShareTrack(source: .window(id: windowId)); + return Unmanaged.passRetained(track).toOpaque() +} diff --git a/crates/live_kit/src/live_kit.rs b/crates/live_kit/src/live_kit.rs index 346ba2173b..7ea94298fd 100644 --- a/crates/live_kit/src/live_kit.rs +++ b/crates/live_kit/src/live_kit.rs @@ -1,13 +1,20 @@ use core_foundation::{ - base::TCFType, + 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::oneshot, Future}; use std::ffi::c_void; extern "C" { + fn LKRelease(object: *const c_void); fn LKRoomCreate() -> *const c_void; - fn LKRoomDestroy(room: *const c_void); fn LKRoomConnect( room: *const c_void, url: CFStringRef, @@ -15,17 +22,14 @@ extern "C" { callback: extern "C" fn(*mut c_void) -> (), callback_data: *mut c_void, ); + fn LKCreateScreenShareTrackForWindow(windowId: u32) -> *const c_void; } -pub struct Room { - native_room: *const c_void, -} +pub struct Room(*const c_void); impl Room { pub fn new() -> Self { - Self { - native_room: unsafe { LKRoomCreate() }, - } + Self(unsafe { LKRoomCreate() }) } pub fn connect(&self, url: &str, token: &str) -> impl Future { @@ -40,7 +44,7 @@ impl Room { unsafe { LKRoomConnect( - self.native_room, + self.0, url.as_concrete_TypeRef(), token.as_concrete_TypeRef(), did_connect, @@ -54,6 +58,60 @@ impl Room { impl Drop for Room { fn drop(&mut self) { - unsafe { LKRoomDestroy(self.native_room) } + unsafe { LKRelease(self.0) } + } +} + +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) } + } +} + +#[derive(Debug)] +pub struct WindowInfo { + pub id: u32, + pub owner_pid: i32, + pub owner_name: Option, +} + +pub fn list_windows() -> Vec { + unsafe { + let dicts = CFArray::::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() } }