Add ability to get a screen share track for a window

Nathan Sobo created

And also list windows

Change summary

Cargo.lock                                                              |  1 
crates/capture/src/main.rs                                              | 20 
crates/live_kit/Cargo.toml                                              |  1 
crates/live_kit/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift | 16 
crates/live_kit/src/live_kit.rs                                         | 78 
5 files changed, 93 insertions(+), 23 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2941,6 +2941,7 @@ name = "live_kit"
 version = "0.1.0"
 dependencies = [
  "core-foundation",
+ "core-graphics",
  "futures",
  "serde",
  "serde_json",

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));
     });

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]

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<AnyObject>.fromOpaque(ptr).takeRetainedValue();
+}
+
 @_cdecl("LKRoomCreate")
 public func LKRoomCreate() -> UnsafeMutableRawPointer  {
     Unmanaged.passRetained(Room()).toOpaque()
 }
 
-@_cdecl("LKRoomDestroy")
-public func LKRoomDestroy(ptr: UnsafeRawPointer)  {
-    let _ = Unmanaged<Room>.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<Room>.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()
+}

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<Output = ()> {
@@ -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<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()
     }
 }