windows: Make collab run on Windows (#23117)

I’ve also updated the documentation in
`development\local-collaboration.md` and
`docs\src\development\windows.md`.

Testing collab on my Windows machine:

![屏幕截图 2025-01-14
162021](https://github.com/user-attachments/assets/28b4a36a-e156-4012-981a-5d0a23dcc613)


Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
张小白 2025-01-17 15:39:13 +08:00 committed by GitHub
parent b1375ab946
commit 70db427fc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 176 additions and 197 deletions

View file

@ -19,6 +19,10 @@ rustflags = ["-C", "link-args=-Objc -all_load"]
[target.x86_64-apple-darwin] [target.x86_64-apple-darwin]
rustflags = ["-C", "link-args=-Objc -all_load"] rustflags = ["-C", "link-args=-Objc -all_load"]
# This cfg will reduce the size of `windows::core::Error` from 16 bytes to 4 bytes
[target.'cfg(target_os = "windows")'] [target.'cfg(target_os = "windows")']
rustflags = ["--cfg", "windows_slim_errors"] rustflags = [
"--cfg",
"windows_slim_errors", # This cfg will reduce the size of `windows::core::Error` from 16 bytes to 4 bytes
"-C",
"target-feature=+crt-static", # This fixes the linking issue when compiling livekit on Windows
]

View file

@ -1,5 +1,3 @@
#![cfg_attr(target_os = "windows", allow(unused))]
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use client::{proto, ParticipantIndex, User}; use client::{proto, ParticipantIndex, User};
use collections::HashMap; use collections::HashMap;
@ -8,7 +6,6 @@ use livekit_client::AudioStream;
use project::Project; use project::Project;
use std::sync::Arc; use std::sync::Arc;
#[cfg(not(target_os = "windows"))]
pub use livekit_client::id::TrackSid; pub use livekit_client::id::TrackSid;
pub use livekit_client::track::{RemoteAudioTrack, RemoteVideoTrack}; pub use livekit_client::track::{RemoteAudioTrack, RemoteVideoTrack};
@ -52,17 +49,12 @@ pub struct RemoteParticipant {
pub participant_index: ParticipantIndex, pub participant_index: ParticipantIndex,
pub muted: bool, pub muted: bool,
pub speaking: bool, pub speaking: bool,
#[cfg(not(target_os = "windows"))]
pub video_tracks: HashMap<TrackSid, RemoteVideoTrack>, pub video_tracks: HashMap<TrackSid, RemoteVideoTrack>,
#[cfg(not(target_os = "windows"))]
pub audio_tracks: HashMap<TrackSid, (RemoteAudioTrack, AudioStream)>, pub audio_tracks: HashMap<TrackSid, (RemoteAudioTrack, AudioStream)>,
} }
impl RemoteParticipant { impl RemoteParticipant {
pub fn has_video_tracks(&self) -> bool { pub fn has_video_tracks(&self) -> bool {
#[cfg(not(target_os = "windows"))] !self.video_tracks.is_empty()
return !self.video_tracks.is_empty();
#[cfg(target_os = "windows")]
return false;
} }
} }

View file

@ -1,5 +1,3 @@
#![cfg_attr(target_os = "windows", allow(unused))]
use crate::{ use crate::{
call_settings::CallSettings, call_settings::CallSettings,
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant}, participant::{LocalParticipant, ParticipantLocation, RemoteParticipant},
@ -17,7 +15,6 @@ use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
#[cfg(not(target_os = "windows"))]
use livekit::{ use livekit::{
capture_local_audio_track, capture_local_video_track, capture_local_audio_track, capture_local_video_track,
id::ParticipantIdentity, id::ParticipantIdentity,
@ -27,8 +24,6 @@ use livekit::{
track::{TrackKind, TrackSource}, track::{TrackKind, TrackSource},
RoomEvent, RoomOptions, RoomEvent, RoomOptions,
}; };
#[cfg(target_os = "windows")]
use livekit::{publication::LocalTrackPublication, RoomEvent};
use livekit_client as livekit; use livekit_client as livekit;
use postage::{sink::Sink, stream::Stream, watch}; use postage::{sink::Sink, stream::Stream, watch};
use project::Project; use project::Project;
@ -106,7 +101,7 @@ impl Room {
!self.shared_projects.is_empty() !self.shared_projects.is_empty()
} }
#[cfg(all(any(test, feature = "test-support"), not(target_os = "windows")))] #[cfg(any(test, feature = "test-support"))]
pub fn is_connected(&self) -> bool { pub fn is_connected(&self) -> bool {
if let Some(live_kit) = self.live_kit.as_ref() { if let Some(live_kit) = self.live_kit.as_ref() {
live_kit.room.connection_state() == livekit::ConnectionState::Connected live_kit.room.connection_state() == livekit::ConnectionState::Connected
@ -671,16 +666,6 @@ impl Room {
} }
} }
#[cfg(target_os = "windows")]
fn start_room_connection(
&self,
mut room: proto::Room,
cx: &mut ModelContext<Self>,
) -> Task<()> {
Task::ready(())
}
#[cfg(not(target_os = "windows"))]
fn start_room_connection( fn start_room_connection(
&self, &self,
mut room: proto::Room, mut room: proto::Room,
@ -837,7 +822,6 @@ impl Room {
muted: true, muted: true,
speaking: false, speaking: false,
video_tracks: Default::default(), video_tracks: Default::default(),
#[cfg(not(target_os = "windows"))]
audio_tracks: Default::default(), audio_tracks: Default::default(),
}, },
); );
@ -944,7 +928,6 @@ impl Room {
); );
match event { match event {
#[cfg(not(target_os = "windows"))]
RoomEvent::TrackSubscribed { RoomEvent::TrackSubscribed {
track, track,
participant, participant,
@ -979,7 +962,6 @@ impl Room {
} }
} }
#[cfg(not(target_os = "windows"))]
RoomEvent::TrackUnsubscribed { RoomEvent::TrackUnsubscribed {
track, participant, .. track, participant, ..
} => { } => {
@ -1007,7 +989,6 @@ impl Room {
} }
} }
#[cfg(not(target_os = "windows"))]
RoomEvent::ActiveSpeakersChanged { speakers } => { RoomEvent::ActiveSpeakersChanged { speakers } => {
let mut speaker_ids = speakers let mut speaker_ids = speakers
.into_iter() .into_iter()
@ -1024,7 +1005,6 @@ impl Room {
} }
} }
#[cfg(not(target_os = "windows"))]
RoomEvent::TrackMuted { RoomEvent::TrackMuted {
participant, participant,
publication, publication,
@ -1049,7 +1029,6 @@ impl Room {
} }
} }
#[cfg(not(target_os = "windows"))]
RoomEvent::LocalTrackUnpublished { publication, .. } => { RoomEvent::LocalTrackUnpublished { publication, .. } => {
log::info!("unpublished track {}", publication.sid()); log::info!("unpublished track {}", publication.sid());
if let Some(room) = &mut self.live_kit { if let Some(room) = &mut self.live_kit {
@ -1072,12 +1051,10 @@ impl Room {
} }
} }
#[cfg(not(target_os = "windows"))]
RoomEvent::LocalTrackPublished { publication, .. } => { RoomEvent::LocalTrackPublished { publication, .. } => {
log::info!("published track {:?}", publication.sid()); log::info!("published track {:?}", publication.sid());
} }
#[cfg(not(target_os = "windows"))]
RoomEvent::Disconnected { reason } => { RoomEvent::Disconnected { reason } => {
log::info!("disconnected from room: {reason:?}"); log::info!("disconnected from room: {reason:?}");
self.leave(cx).detach_and_log_err(cx); self.leave(cx).detach_and_log_err(cx);
@ -1307,13 +1284,6 @@ impl Room {
pub fn can_use_microphone(&self) -> bool { pub fn can_use_microphone(&self) -> bool {
use proto::ChannelRole::*; use proto::ChannelRole::*;
#[cfg(not(any(test, feature = "test-support")))]
{
if cfg!(target_os = "windows") {
return false;
}
}
match self.local_participant.role { match self.local_participant.role {
Admin | Member | Talker => true, Admin | Member | Talker => true,
Guest | Banned => false, Guest | Banned => false,
@ -1328,12 +1298,6 @@ impl Room {
} }
} }
#[cfg(target_os = "windows")]
pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
Task::ready(Err(anyhow!("Windows is not supported yet")))
}
#[cfg(not(target_os = "windows"))]
#[track_caller] #[track_caller]
pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
if self.status.is_offline() { if self.status.is_offline() {
@ -1411,12 +1375,6 @@ impl Room {
}) })
} }
#[cfg(target_os = "windows")]
pub fn share_screen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
Task::ready(Err(anyhow!("Windows is not supported yet")))
}
#[cfg(not(target_os = "windows"))]
pub fn share_screen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub fn share_screen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
if self.status.is_offline() { if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline"))); return Task::ready(Err(anyhow!("room is offline")));
@ -1564,15 +1522,13 @@ impl Room {
LocalTrack::Published { LocalTrack::Published {
track_publication, .. track_publication, ..
} => { } => {
#[cfg(not(target_os = "windows"))]
{
let local_participant = live_kit.room.local_participant(); let local_participant = live_kit.room.local_participant();
let sid = track_publication.sid(); let sid = track_publication.sid();
cx.background_executor() cx.background_executor()
.spawn(async move { local_participant.unpublish_track(&sid).await }) .spawn(async move { local_participant.unpublish_track(&sid).await })
.detach_and_log_err(cx); .detach_and_log_err(cx);
cx.notify(); cx.notify();
}
Audio::play_sound(Sound::StopScreenshare, cx); Audio::play_sound(Sound::StopScreenshare, cx);
Ok(()) Ok(())
} }
@ -1580,8 +1536,6 @@ impl Room {
} }
fn set_deafened(&mut self, deafened: bool, cx: &mut ModelContext<Self>) -> Option<()> { fn set_deafened(&mut self, deafened: bool, cx: &mut ModelContext<Self>) -> Option<()> {
#[cfg(not(target_os = "windows"))]
{
let live_kit = self.live_kit.as_mut()?; let live_kit = self.live_kit.as_mut()?;
cx.notify(); cx.notify();
for (_, participant) in live_kit.room.remote_participants() { for (_, participant) in live_kit.room.remote_participants() {
@ -1591,7 +1545,6 @@ impl Room {
} }
} }
} }
}
None None
} }
@ -1622,28 +1575,18 @@ impl Room {
LocalTrack::Published { LocalTrack::Published {
track_publication, .. track_publication, ..
} => { } => {
#[cfg(not(target_os = "windows"))]
{
if should_mute { if should_mute {
track_publication.mute() track_publication.mute()
} else { } else {
track_publication.unmute() track_publication.unmute()
} }
}
None None
} }
} }
} }
} }
#[cfg(target_os = "windows")]
fn spawn_room_connection(
livekit_connection_info: Option<proto::LiveKitConnectionInfo>,
cx: &mut ModelContext<'_, Room>,
) {
}
#[cfg(not(target_os = "windows"))]
fn spawn_room_connection( fn spawn_room_connection(
livekit_connection_info: Option<proto::LiveKitConnectionInfo>, livekit_connection_info: Option<proto::LiveKitConnectionInfo>,
cx: &mut ModelContext<'_, Room>, cx: &mut ModelContext<'_, Room>,
@ -1708,10 +1651,6 @@ struct LiveKitRoom {
} }
impl LiveKitRoom { impl LiveKitRoom {
#[cfg(target_os = "windows")]
fn stop_publishing(&mut self, _cx: &mut ModelContext<Room>) {}
#[cfg(not(target_os = "windows"))]
fn stop_publishing(&mut self, cx: &mut ModelContext<Room>) { fn stop_publishing(&mut self, cx: &mut ModelContext<Room>) {
let mut tracks_to_unpublish = Vec::new(); let mut tracks_to_unpublish = Vec::new();
if let LocalTrack::Published { if let LocalTrack::Published {

View file

@ -2,7 +2,11 @@ use collab::env::get_dotenv_vars;
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
for (key, value) in get_dotenv_vars(".")? { for (key, value) in get_dotenv_vars(".")? {
if option_env!("POWERSHELL").is_some() {
println!("$env:{}=\"{}\"", key, value);
} else {
println!("export {}=\"{}\"", key, value); println!("export {}=\"{}\"", key, value);
} }
}
Ok(()) Ok(())
} }

View file

@ -18,11 +18,7 @@ name = "test_app"
[features] [features]
no-webrtc = [] no-webrtc = []
test-support = [ test-support = ["collections/test-support", "gpui/test-support", "nanoid"]
"collections/test-support",
"gpui/test-support",
"nanoid",
]
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
@ -32,6 +28,7 @@ cpal = "0.15"
futures.workspace = true futures.workspace = true
gpui.workspace = true gpui.workspace = true
http_2 = { package = "http", version = "0.2.1" } http_2 = { package = "http", version = "0.2.1" }
livekit.workspace = true
livekit_server.workspace = true livekit_server.workspace = true
log.workspace = true log.workspace = true
media.workspace = true media.workspace = true
@ -43,9 +40,6 @@ http_client.workspace = true
smallvec.workspace = true smallvec.workspace = true
image.workspace = true image.workspace = true
[target.'cfg(not(target_os = "windows"))'.dependencies]
livekit.workspace = true
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
core-foundation.workspace = true core-foundation.workspace = true
coreaudio-rs = "0.12.1" coreaudio-rs = "0.12.1"

View file

@ -1,7 +1,5 @@
#![cfg_attr(target_os = "windows", allow(unused))]
mod remote_video_track_view; mod remote_video_track_view;
#[cfg(any(test, feature = "test-support", target_os = "windows"))] #[cfg(any(test, feature = "test-support"))]
pub mod test; pub mod test;
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
@ -13,7 +11,6 @@ use gpui::{
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{borrow::Cow, collections::VecDeque, future::Future, pin::Pin, sync::Arc, thread}; use std::{borrow::Cow, collections::VecDeque, future::Future, pin::Pin, sync::Arc, thread};
use util::{debug_panic, ResultExt as _}; use util::{debug_panic, ResultExt as _};
#[cfg(not(target_os = "windows"))]
use webrtc::{ use webrtc::{
audio_frame::AudioFrame, audio_frame::AudioFrame,
audio_source::{native::NativeAudioSource, AudioSourceOptions, RtcAudioSource}, audio_source::{native::NativeAudioSource, AudioSourceOptions, RtcAudioSource},
@ -23,13 +20,13 @@ use webrtc::{
video_stream::native::NativeVideoStream, video_stream::native::NativeVideoStream,
}; };
#[cfg(all(not(any(test, feature = "test-support")), not(target_os = "windows")))] #[cfg(not(any(test, feature = "test-support")))]
use livekit::track::RemoteAudioTrack; use livekit::track::RemoteAudioTrack;
#[cfg(all(not(any(test, feature = "test-support")), not(target_os = "windows")))] #[cfg(not(any(test, feature = "test-support")))]
pub use livekit::*; pub use livekit::*;
#[cfg(any(test, feature = "test-support", target_os = "windows"))] #[cfg(any(test, feature = "test-support"))]
use test::track::RemoteAudioTrack; use test::track::RemoteAudioTrack;
#[cfg(any(test, feature = "test-support", target_os = "windows"))] #[cfg(any(test, feature = "test-support"))]
pub use test::*; pub use test::*;
pub use remote_video_track_view::{RemoteVideoTrackView, RemoteVideoTrackViewEvent}; pub use remote_video_track_view::{RemoteVideoTrackView, RemoteVideoTrackViewEvent};
@ -46,7 +43,6 @@ pub enum AudioStream {
struct Dispatcher(Arc<dyn gpui::PlatformDispatcher>); struct Dispatcher(Arc<dyn gpui::PlatformDispatcher>);
#[cfg(not(target_os = "windows"))]
impl livekit::dispatcher::Dispatcher for Dispatcher { impl livekit::dispatcher::Dispatcher for Dispatcher {
fn dispatch(&self, runnable: livekit::dispatcher::Runnable) { fn dispatch(&self, runnable: livekit::dispatcher::Runnable) {
self.0.dispatch(runnable, None); self.0.dispatch(runnable, None);
@ -68,7 +64,6 @@ fn http_2_status(status: http_client::http::StatusCode) -> http_2::StatusCode {
.expect("valid status code to status code conversion") .expect("valid status code to status code conversion")
} }
#[cfg(not(target_os = "windows"))]
impl livekit::dispatcher::HttpClient for HttpClientAdapter { impl livekit::dispatcher::HttpClient for HttpClientAdapter {
fn get( fn get(
&self, &self,
@ -123,14 +118,6 @@ impl livekit::dispatcher::HttpClient for HttpClientAdapter {
} }
} }
#[cfg(target_os = "windows")]
pub fn init(
dispatcher: Arc<dyn gpui::PlatformDispatcher>,
http_client: Arc<dyn http_client::HttpClient>,
) {
}
#[cfg(not(target_os = "windows"))]
pub fn init( pub fn init(
dispatcher: Arc<dyn gpui::PlatformDispatcher>, dispatcher: Arc<dyn gpui::PlatformDispatcher>,
http_client: Arc<dyn http_client::HttpClient>, http_client: Arc<dyn http_client::HttpClient>,
@ -139,7 +126,6 @@ pub fn init(
livekit::dispatcher::set_http_client(HttpClientAdapter(http_client)); livekit::dispatcher::set_http_client(HttpClientAdapter(http_client));
} }
#[cfg(not(target_os = "windows"))]
pub async fn capture_local_video_track( pub async fn capture_local_video_track(
capture_source: &dyn ScreenCaptureSource, capture_source: &dyn ScreenCaptureSource,
) -> Result<(track::LocalVideoTrack, Box<dyn ScreenCaptureStream>)> { ) -> Result<(track::LocalVideoTrack, Box<dyn ScreenCaptureStream>)> {
@ -173,7 +159,6 @@ pub async fn capture_local_video_track(
)) ))
} }
#[cfg(not(target_os = "windows"))]
pub fn capture_local_audio_track( pub fn capture_local_audio_track(
background_executor: &BackgroundExecutor, background_executor: &BackgroundExecutor,
) -> Result<Task<(track::LocalAudioTrack, AudioStream)>> { ) -> Result<Task<(track::LocalAudioTrack, AudioStream)>> {
@ -265,7 +250,6 @@ pub fn capture_local_audio_track(
})) }))
} }
#[cfg(not(target_os = "windows"))]
pub fn play_remote_audio_track( pub fn play_remote_audio_track(
track: &RemoteAudioTrack, track: &RemoteAudioTrack,
background_executor: &BackgroundExecutor, background_executor: &BackgroundExecutor,
@ -336,7 +320,6 @@ fn default_device(input: bool) -> anyhow::Result<(cpal::Device, cpal::SupportedS
Ok((device, config)) Ok((device, config))
} }
#[cfg(not(target_os = "windows"))]
fn get_default_output() -> anyhow::Result<(cpal::Device, cpal::SupportedStreamConfig)> { fn get_default_output() -> anyhow::Result<(cpal::Device, cpal::SupportedStreamConfig)> {
let host = cpal::default_host(); let host = cpal::default_host();
let output_device = host let output_device = host
@ -346,7 +329,6 @@ fn get_default_output() -> anyhow::Result<(cpal::Device, cpal::SupportedStreamCo
Ok((output_device, output_config)) Ok((output_device, output_config))
} }
#[cfg(not(target_os = "windows"))]
fn start_output_stream( fn start_output_stream(
output_config: cpal::SupportedStreamConfig, output_config: cpal::SupportedStreamConfig,
output_device: cpal::Device, output_device: cpal::Device,
@ -431,14 +413,6 @@ fn start_output_stream(
(receive_task, thread) (receive_task, thread)
} }
#[cfg(target_os = "windows")]
pub fn play_remote_video_track(
track: &track::RemoteVideoTrack,
) -> impl Stream<Item = RemoteVideoFrame> {
futures::stream::empty()
}
#[cfg(not(target_os = "windows"))]
pub fn play_remote_video_track( pub fn play_remote_video_track(
track: &track::RemoteVideoTrack, track: &track::RemoteVideoTrack,
) -> impl Stream<Item = RemoteVideoFrame> { ) -> impl Stream<Item = RemoteVideoFrame> {
@ -466,7 +440,7 @@ fn video_frame_buffer_from_webrtc(buffer: Box<dyn VideoBuffer>) -> Option<Remote
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
pub type RemoteVideoFrame = Arc<gpui::RenderImage>; pub type RemoteVideoFrame = Arc<gpui::RenderImage>;
#[cfg(not(any(target_os = "macos", target_os = "windows")))] #[cfg(not(target_os = "macos"))]
fn video_frame_buffer_from_webrtc(buffer: Box<dyn VideoBuffer>) -> Option<RemoteVideoFrame> { fn video_frame_buffer_from_webrtc(buffer: Box<dyn VideoBuffer>) -> Option<RemoteVideoFrame> {
use gpui::RenderImage; use gpui::RenderImage;
use image::{Frame, RgbaImage}; use image::{Frame, RgbaImage};
@ -517,7 +491,7 @@ fn video_frame_buffer_to_webrtc(frame: ScreenCaptureFrame) -> Option<impl AsRef<
} }
} }
#[cfg(not(any(target_os = "macos", target_os = "windows")))] #[cfg(not(target_os = "macos"))]
fn video_frame_buffer_to_webrtc(_frame: ScreenCaptureFrame) -> Option<impl AsRef<dyn VideoBuffer>> { fn video_frame_buffer_to_webrtc(_frame: ScreenCaptureFrame) -> Option<impl AsRef<dyn VideoBuffer>> {
None as Option<Box<dyn VideoBuffer>> None as Option<Box<dyn VideoBuffer>>
} }

View file

@ -1,18 +1,14 @@
pub mod participant; pub mod participant;
pub mod publication; pub mod publication;
pub mod track; pub mod track;
#[cfg(not(windows))]
pub mod webrtc; pub mod webrtc;
#[cfg(not(windows))]
use self::id::*; use self::id::*;
use self::{participant::*, publication::*, track::*}; use self::{participant::*, publication::*, track::*};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use collections::{btree_map::Entry as BTreeEntry, hash_map::Entry, BTreeMap, HashMap, HashSet}; use collections::{btree_map::Entry as BTreeEntry, hash_map::Entry, BTreeMap, HashMap, HashSet};
use gpui::BackgroundExecutor; use gpui::BackgroundExecutor;
#[cfg(not(windows))]
use livekit::options::TrackPublishOptions; use livekit::options::TrackPublishOptions;
use livekit_server::{proto, token}; use livekit_server::{proto, token};
use parking_lot::Mutex; use parking_lot::Mutex;
@ -22,7 +18,6 @@ use std::sync::{
Arc, Weak, Arc, Weak,
}; };
#[cfg(not(windows))]
pub use livekit::{id, options, ConnectionState, DisconnectReason, RoomOptions}; pub use livekit::{id, options, ConnectionState, DisconnectReason, RoomOptions};
static SERVERS: Mutex<BTreeMap<String, Arc<TestServer>>> = Mutex::new(BTreeMap::new()); static SERVERS: Mutex<BTreeMap<String, Arc<TestServer>>> = Mutex::new(BTreeMap::new());
@ -31,12 +26,10 @@ pub struct TestServer {
pub url: String, pub url: String,
pub api_key: String, pub api_key: String,
pub secret_key: String, pub secret_key: String,
#[cfg(not(target_os = "windows"))]
rooms: Mutex<HashMap<String, TestServerRoom>>, rooms: Mutex<HashMap<String, TestServerRoom>>,
executor: BackgroundExecutor, executor: BackgroundExecutor,
} }
#[cfg(not(target_os = "windows"))]
impl TestServer { impl TestServer {
pub fn create( pub fn create(
url: String, url: String,
@ -534,7 +527,6 @@ impl TestServer {
} }
} }
#[cfg(not(target_os = "windows"))]
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct TestServerRoom { struct TestServerRoom {
client_rooms: HashMap<ParticipantIdentity, Room>, client_rooms: HashMap<ParticipantIdentity, Room>,
@ -543,7 +535,6 @@ struct TestServerRoom {
participant_permissions: HashMap<ParticipantIdentity, proto::ParticipantPermission>, participant_permissions: HashMap<ParticipantIdentity, proto::ParticipantPermission>,
} }
#[cfg(not(target_os = "windows"))]
#[derive(Debug)] #[derive(Debug)]
struct TestServerVideoTrack { struct TestServerVideoTrack {
sid: TrackSid, sid: TrackSid,
@ -551,7 +542,6 @@ struct TestServerVideoTrack {
// frames_rx: async_broadcast::Receiver<Frame>, // frames_rx: async_broadcast::Receiver<Frame>,
} }
#[cfg(not(target_os = "windows"))]
#[derive(Debug)] #[derive(Debug)]
struct TestServerAudioTrack { struct TestServerAudioTrack {
sid: TrackSid, sid: TrackSid,
@ -590,7 +580,6 @@ pub enum RoomEvent {
TrackSubscriptionFailed { TrackSubscriptionFailed {
participant: RemoteParticipant, participant: RemoteParticipant,
error: String, error: String,
#[cfg(not(target_os = "windows"))]
track_sid: TrackSid, track_sid: TrackSid,
}, },
TrackPublished { TrackPublished {
@ -626,12 +615,10 @@ pub enum RoomEvent {
ActiveSpeakersChanged { ActiveSpeakersChanged {
speakers: Vec<Participant>, speakers: Vec<Participant>,
}, },
#[cfg(not(target_os = "windows"))]
ConnectionStateChanged(ConnectionState), ConnectionStateChanged(ConnectionState),
Connected { Connected {
participants_with_tracks: Vec<(RemoteParticipant, Vec<RemoteTrackPublication>)>, participants_with_tracks: Vec<(RemoteParticipant, Vec<RemoteTrackPublication>)>,
}, },
#[cfg(not(target_os = "windows"))]
Disconnected { Disconnected {
reason: DisconnectReason, reason: DisconnectReason,
}, },
@ -639,7 +626,6 @@ pub enum RoomEvent {
Reconnected, Reconnected,
} }
#[cfg(not(target_os = "windows"))]
#[async_trait] #[async_trait]
impl livekit_server::api::Client for TestApiClient { impl livekit_server::api::Client for TestApiClient {
fn url(&self) -> &str { fn url(&self) -> &str {
@ -703,11 +689,8 @@ impl livekit_server::api::Client for TestApiClient {
struct RoomState { struct RoomState {
url: String, url: String,
token: String, token: String,
#[cfg(not(target_os = "windows"))]
local_identity: ParticipantIdentity, local_identity: ParticipantIdentity,
#[cfg(not(target_os = "windows"))]
connection_state: ConnectionState, connection_state: ConnectionState,
#[cfg(not(target_os = "windows"))]
paused_audio_tracks: HashSet<TrackSid>, paused_audio_tracks: HashSet<TrackSid>,
updates_tx: mpsc::Sender<RoomEvent>, updates_tx: mpsc::Sender<RoomEvent>,
} }
@ -718,7 +701,6 @@ pub struct Room(Arc<Mutex<RoomState>>);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct WeakRoom(Weak<Mutex<RoomState>>); pub(crate) struct WeakRoom(Weak<Mutex<RoomState>>);
#[cfg(not(target_os = "windows"))]
impl std::fmt::Debug for RoomState { impl std::fmt::Debug for RoomState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Room") f.debug_struct("Room")
@ -731,17 +713,6 @@ impl std::fmt::Debug for RoomState {
} }
} }
#[cfg(target_os = "windows")]
impl std::fmt::Debug for RoomState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Room")
.field("url", &self.url)
.field("token", &self.token)
.finish()
}
}
#[cfg(not(target_os = "windows"))]
impl Room { impl Room {
fn downgrade(&self) -> WeakRoom { fn downgrade(&self) -> WeakRoom {
WeakRoom(Arc::downgrade(&self.0)) WeakRoom(Arc::downgrade(&self.0))
@ -803,7 +774,6 @@ impl Room {
} }
} }
#[cfg(not(target_os = "windows"))]
impl Drop for RoomState { impl Drop for RoomState {
fn drop(&mut self) { fn drop(&mut self) {
if self.connection_state == ConnectionState::Connected { if self.connection_state == ConnectionState::Connected {

View file

@ -8,19 +8,16 @@ pub enum Participant {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct LocalParticipant { pub struct LocalParticipant {
#[cfg(not(target_os = "windows"))]
pub(super) identity: ParticipantIdentity, pub(super) identity: ParticipantIdentity,
pub(super) room: Room, pub(super) room: Room,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RemoteParticipant { pub struct RemoteParticipant {
#[cfg(not(target_os = "windows"))]
pub(super) identity: ParticipantIdentity, pub(super) identity: ParticipantIdentity,
pub(super) room: WeakRoom, pub(super) room: WeakRoom,
} }
#[cfg(not(target_os = "windows"))]
impl Participant { impl Participant {
pub fn identity(&self) -> ParticipantIdentity { pub fn identity(&self) -> ParticipantIdentity {
match self { match self {
@ -30,7 +27,6 @@ impl Participant {
} }
} }
#[cfg(not(target_os = "windows"))]
impl LocalParticipant { impl LocalParticipant {
pub async fn unpublish_track(&self, track: &TrackSid) -> Result<()> { pub async fn unpublish_track(&self, track: &TrackSid) -> Result<()> {
self.room self.room
@ -64,7 +60,6 @@ impl LocalParticipant {
} }
} }
#[cfg(not(target_os = "windows"))]
impl RemoteParticipant { impl RemoteParticipant {
pub fn track_publications(&self) -> HashMap<TrackSid, RemoteTrackPublication> { pub fn track_publications(&self) -> HashMap<TrackSid, RemoteTrackPublication> {
if let Some(room) = self.room.upgrade() { if let Some(room) = self.room.upgrade() {

View file

@ -8,20 +8,17 @@ pub enum TrackPublication {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct LocalTrackPublication { pub struct LocalTrackPublication {
#[cfg(not(target_os = "windows"))]
pub(crate) sid: TrackSid, pub(crate) sid: TrackSid,
pub(crate) room: WeakRoom, pub(crate) room: WeakRoom,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RemoteTrackPublication { pub struct RemoteTrackPublication {
#[cfg(not(target_os = "windows"))]
pub(crate) sid: TrackSid, pub(crate) sid: TrackSid,
pub(crate) room: WeakRoom, pub(crate) room: WeakRoom,
pub(crate) track: RemoteTrack, pub(crate) track: RemoteTrack,
} }
#[cfg(not(target_os = "windows"))]
impl TrackPublication { impl TrackPublication {
pub fn sid(&self) -> TrackSid { pub fn sid(&self) -> TrackSid {
match self { match self {
@ -38,7 +35,6 @@ impl TrackPublication {
} }
} }
#[cfg(not(target_os = "windows"))]
impl LocalTrackPublication { impl LocalTrackPublication {
pub fn sid(&self) -> TrackSid { pub fn sid(&self) -> TrackSid {
self.sid.clone() self.sid.clone()
@ -71,7 +67,6 @@ impl LocalTrackPublication {
} }
} }
#[cfg(not(target_os = "windows"))]
impl RemoteTrackPublication { impl RemoteTrackPublication {
pub fn sid(&self) -> TrackSid { pub fn sid(&self) -> TrackSid {
self.sid.clone() self.sid.clone()

View file

@ -1,8 +1,6 @@
use super::*; use super::*;
#[cfg(not(windows))]
use webrtc::{audio_source::RtcAudioSource, video_source::RtcVideoSource}; use webrtc::{audio_source::RtcAudioSource, video_source::RtcVideoSource};
#[cfg(not(windows))]
pub use livekit::track::{TrackKind, TrackSource}; pub use livekit::track::{TrackKind, TrackSource};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -25,14 +23,12 @@ pub struct LocalAudioTrack {}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RemoteVideoTrack { pub struct RemoteVideoTrack {
#[cfg(not(target_os = "windows"))]
pub(super) server_track: Arc<TestServerVideoTrack>, pub(super) server_track: Arc<TestServerVideoTrack>,
pub(super) _room: WeakRoom, pub(super) _room: WeakRoom,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RemoteAudioTrack { pub struct RemoteAudioTrack {
#[cfg(not(target_os = "windows"))]
pub(super) server_track: Arc<TestServerAudioTrack>, pub(super) server_track: Arc<TestServerAudioTrack>,
pub(super) room: WeakRoom, pub(super) room: WeakRoom,
} }
@ -43,17 +39,14 @@ pub enum RtcTrack {
} }
pub struct RtcAudioTrack { pub struct RtcAudioTrack {
#[cfg(not(target_os = "windows"))]
pub(super) server_track: Arc<TestServerAudioTrack>, pub(super) server_track: Arc<TestServerAudioTrack>,
pub(super) room: WeakRoom, pub(super) room: WeakRoom,
} }
pub struct RtcVideoTrack { pub struct RtcVideoTrack {
#[cfg(not(target_os = "windows"))]
pub(super) _server_track: Arc<TestServerVideoTrack>, pub(super) _server_track: Arc<TestServerVideoTrack>,
} }
#[cfg(not(target_os = "windows"))]
impl RemoteTrack { impl RemoteTrack {
pub fn sid(&self) -> TrackSid { pub fn sid(&self) -> TrackSid {
match self { match self {
@ -84,21 +77,18 @@ impl RemoteTrack {
} }
} }
#[cfg(not(windows))]
impl LocalVideoTrack { impl LocalVideoTrack {
pub fn create_video_track(_name: &str, _source: RtcVideoSource) -> Self { pub fn create_video_track(_name: &str, _source: RtcVideoSource) -> Self {
Self {} Self {}
} }
} }
#[cfg(not(windows))]
impl LocalAudioTrack { impl LocalAudioTrack {
pub fn create_audio_track(_name: &str, _source: RtcAudioSource) -> Self { pub fn create_audio_track(_name: &str, _source: RtcAudioSource) -> Self {
Self {} Self {}
} }
} }
#[cfg(not(target_os = "windows"))]
impl RemoteAudioTrack { impl RemoteAudioTrack {
pub fn sid(&self) -> TrackSid { pub fn sid(&self) -> TrackSid {
self.server_track.sid.clone() self.server_track.sid.clone()
@ -134,7 +124,6 @@ impl RemoteAudioTrack {
} }
} }
#[cfg(not(target_os = "windows"))]
impl RemoteVideoTrack { impl RemoteVideoTrack {
pub fn sid(&self) -> TrackSid { pub fn sid(&self) -> TrackSid {
self.server_track.sid.clone() self.server_track.sid.clone()
@ -151,7 +140,6 @@ impl RemoteVideoTrack {
} }
} }
#[cfg(not(target_os = "windows"))]
impl RtcTrack { impl RtcTrack {
pub fn enabled(&self) -> bool { pub fn enabled(&self) -> bool {
match self { match self {
@ -168,7 +156,6 @@ impl RtcTrack {
} }
} }
#[cfg(not(target_os = "windows"))]
impl RtcAudioTrack { impl RtcAudioTrack {
pub fn set_enabled(&self, enabled: bool) { pub fn set_enabled(&self, enabled: bool) {
if let Some(room) = self.room.upgrade() { if let Some(room) = self.room.upgrade() {

View file

@ -10,6 +10,8 @@ First, make sure you've installed Zed's backend dependencies for your platform:
Before you can run the `collab` server locally, you'll need to set up a `zed` Postgres database. Before you can run the `collab` server locally, you'll need to set up a `zed` Postgres database.
### On macOS and Linux
```sh ```sh
script/bootstrap script/bootstrap
``` ```
@ -31,8 +33,16 @@ To use a different set of admin users, you can create your own version of that j
} }
``` ```
### On Windows
```powershell
.\script\bootstrap.ps1
```
## Testing collaborative features locally ## Testing collaborative features locally
### On macOS and Linux
Ensure that Postgres is configured and running, then run Zed's collaboration server and the `livekit` dev server: Ensure that Postgres is configured and running, then run Zed's collaboration server and the `livekit` dev server:
```sh ```sh
@ -41,7 +51,7 @@ foreman start
cargo run -p collab -- serve all cargo run -p collab -- serve all
``` ```
In a second terminal, run two or more instances of Zed. In a new terminal, run two or more instances of Zed.
```sh ```sh
script/zed-local -2 script/zed-local -2
@ -49,6 +59,34 @@ script/zed-local -2
This script starts one to four instances of Zed, depending on the `-2`, `-3` or `-4` flags. Each instance will be connected to the local `collab` server, signed in as a different user from `.admins.json` or `.admins.default.json`. This script starts one to four instances of Zed, depending on the `-2`, `-3` or `-4` flags. Each instance will be connected to the local `collab` server, signed in as a different user from `.admins.json` or `.admins.default.json`.
### On Windows
Since `foreman` is not available on Windows, you can run the following commands in separate terminals:
```powershell
cargo run --package=collab -- serve all
```
If you have added the `livekit-server` binary to your `PATH`, you can run:
```powershell
livekit-server --dev
```
Otherwise,
```powershell
.\path\to\livekit-serve.exe --dev
```
In a new terminal, run two or more instances of Zed.
```powershell
node .\script\zed-local -2
```
Note that this requires `node.exe` to be in your `PATH`.
## Running a local collab server ## Running a local collab server
If you want to run your own version of the zed collaboration service, you can, but note that this is still under development, and there is no good support for authentication nor extensions. If you want to run your own version of the zed collaboration service, you can, but note that this is still under development, and there is no good support for authentication nor extensions.

View file

@ -12,7 +12,31 @@ Clone down the [Zed repository](https://github.com/zed-industries/zed).
- Install [Visual Studio](https://visualstudio.microsoft.com/downloads/) with the optional components `MSVC v*** - VS YYYY C++ x64/x86 build tools` and `MSVC v*** - VS YYYY C++ x64/x86 Spectre-mitigated libs (latest)` (`v***` is your VS version and `YYYY` is year when your VS was released. Pay attention to the architecture and change it to yours if needed.) - Install [Visual Studio](https://visualstudio.microsoft.com/downloads/) with the optional components `MSVC v*** - VS YYYY C++ x64/x86 build tools` and `MSVC v*** - VS YYYY C++ x64/x86 Spectre-mitigated libs (latest)` (`v***` is your VS version and `YYYY` is year when your VS was released. Pay attention to the architecture and change it to yours if needed.)
- Install Windows 11 or 10 SDK depending on your system, but ensure that at least `Windows 10 SDK version 2104 (10.0.20348.0)` is installed on your machine. You can download it from the [Windows SDK Archive](https://developer.microsoft.com/windows/downloads/windows-sdk/) - Install Windows 11 or 10 SDK depending on your system, but ensure that at least `Windows 10 SDK version 2104 (10.0.20348.0)` is installed on your machine. You can download it from the [Windows SDK Archive](https://developer.microsoft.com/windows/downloads/windows-sdk/)
- Install [CMake](https://cmake.org/download) (required by [a dependency](https://docs.rs/wasmtime-c-api-impl/latest/wasmtime_c_api/)) - Install [CMake](https://cmake.org/download) (required by [a dependency](https://docs.rs/wasmtime-c-api-impl/latest/wasmtime_c_api/)). Or you can install it through Visual Studio Installer, then manually add the `bin` directory to your `PATH`, for example: `C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin`.
If you can't compile Zed, make sure that you have at least the following components installed:
```json
{
"version": "1.0",
"components": [
"Microsoft.VisualStudio.Component.CoreEditor",
"Microsoft.VisualStudio.Workload.CoreEditor",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.CMake",
"Microsoft.VisualStudio.Component.VC.CMake.Project",
"Microsoft.VisualStudio.Component.Windows11SDK.26100",
"Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre"
],
"extensions": []
}
```
The list can be obtained as follows:
- Open the Visual Studio Installer
- Click on `More` in the `Installed` tab
- Click on `Export configuration`
## Backend dependencies ## Backend dependencies
@ -21,7 +45,7 @@ Clone down the [Zed repository](https://github.com/zed-industries/zed).
If you are developing collaborative features of Zed, you'll need to install the dependencies of zed's `collab` server: If you are developing collaborative features of Zed, you'll need to install the dependencies of zed's `collab` server:
- Install [Postgres](https://www.postgresql.org/download/windows/) - Install [Postgres](https://www.postgresql.org/download/windows/)
- Install [Livekit](https://github.com/livekit/livekit-cli) and [Foreman](https://theforeman.org/manuals/3.9/quickstart_guide.html) - Install [Livekit](https://github.com/livekit/livekit), optionally you can add the `livekit-server` binary to your `PATH`.
Alternatively, if you have [Docker](https://www.docker.com/) installed you can bring up all the `collab` dependencies using Docker Compose: Alternatively, if you have [Docker](https://www.docker.com/) installed you can bring up all the `collab` dependencies using Docker Compose:
@ -29,6 +53,26 @@ Alternatively, if you have [Docker](https://www.docker.com/) installed you can b
docker compose up -d docker compose up -d
``` ```
### Notes
You should modify the `pg_hba.conf` file in the `data` directory to use `trust` instead of `scram-sha-256` for the `host` method. Otherwise, the connection will fail with the error `password authentication failed`. The `pg_hba.conf` file typically locates at `C:\Program Files\PostgreSQL\17\data\pg_hba.conf`. After the modification, the file should look like this:
```conf
# IPv4 local connections:
host all all 127.0.0.1/32 trust
# IPv6 local connections:
host all all ::1/128 trust
```
Also, if you are using a non-latin Windows version, you must modify the`lc_messages` parameter in the `postgresql.conf` file in the `data` directory to `English_United States.1252` (or whatever UTF8-compatible encoding you have). Otherwise, the database will panic. The `postgresql.conf` file should look like this:
```conf
# lc_messages = 'Chinese (Simplified)_China.936' # locale for system error message strings
lc_messages = 'English_United States.1252'
```
After this, you should restart the `postgresql` service. Press the `win` key + `R` to launch the `Run` window. Type the `services.msc` and hit the `OK` button to open the Services Manager. Then, find the `postgresql-x64-XX` service, right-click on it, and select `Restart`.
## Building from source ## Building from source
Once you have the dependencies installed, you can build Zed using [Cargo](https://doc.rust-lang.org/cargo/). Once you have the dependencies installed, you can build Zed using [Cargo](https://doc.rust-lang.org/cargo/).

21
script/bootstrap.ps1 Normal file
View file

@ -0,0 +1,21 @@
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
$env:POWERSHELL = $true
if (!(Get-Command sqlx -ErrorAction SilentlyContinue) -or (sqlx --version) -notlike "sqlx-cli 0.7.2") {
Write-Output "sqlx-cli not found or not the required version, installing version 0.7.2..."
cargo install sqlx-cli --version 0.7.2
}
Set-Location .\crates\collab
# Export contents of .env.toml
$env = (cargo run --bin dotenv) -join "`n";
Invoke-Expression $env
Set-Location ../..
Write-Output "creating databases..."
sqlx database create --database-url "$env:DATABASE_URL"
sqlx database create --database-url "$env:LLM_DATABASE_URL"

View file

@ -112,9 +112,23 @@ if (platform === "darwin") {
console.log(err); console.log(err);
throw new Error("Could not get screen resolution"); throw new Error("Could not get screen resolution");
} }
} else if (platform === "win32") {
// windows
try {
const resolutionOutput = execSync(
`powershell -Command "& {Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea.Size}"`,
{ encoding: "utf8" }
).trim();
[screenWidth, screenHeight] = resolutionOutput.match(/\d+/g).map(Number);
} catch (err) {
console.log(err);
throw new Error("Could not get screen resolution on Windows");
}
} }
if (platform !== "win32") {
screenHeight -= titleBarHeight; screenHeight -= titleBarHeight;
}
if (isTop) { if (isTop) {
screenHeight = Math.floor(screenHeight / 2); screenHeight = Math.floor(screenHeight / 2);
@ -168,10 +182,18 @@ setTimeout(() => {
for (let i = 0; i < instanceCount; i++) { for (let i = 0; i < instanceCount; i++) {
const row = Math.floor(i / columns); const row = Math.floor(i / columns);
const column = i % columns; const column = i % columns;
const position = [ let position;
if (platform == "win32") {
position = [
column * instanceWidth,
row * instanceHeight,
].join(",");
} else {
position = [
column * instanceWidth, column * instanceWidth,
row * instanceHeight + titleBarHeight, row * instanceHeight + titleBarHeight,
].join(","); ].join(",");
}
const size = [instanceWidth, instanceHeight].join(","); const size = [instanceWidth, instanceHeight].join(",");
let binaryPath = zedBinary; let binaryPath = zedBinary;
if (i != 0 && othersOnStable) { if (i != 0 && othersOnStable) {
@ -186,8 +208,8 @@ setTimeout(() => {
ZED_WINDOW_POSITION: position, ZED_WINDOW_POSITION: position,
ZED_STATELESS: isStateful && i == 0 ? "" : "1", ZED_STATELESS: isStateful && i == 0 ? "" : "1",
ZED_ALWAYS_ACTIVE: "1", ZED_ALWAYS_ACTIVE: "1",
ZED_SERVER_URL: "http://localhost:3000", ZED_SERVER_URL: "http://127.0.0.1:3000", // On windows, the http_client could not parse localhost
ZED_RPC_URL: "http://localhost:8080/rpc", ZED_RPC_URL: "http://127.0.0.1:8080/rpc",
ZED_ADMIN_API_TOKEN: "secret", ZED_ADMIN_API_TOKEN: "secret",
ZED_WINDOW_SIZE: size, ZED_WINDOW_SIZE: size,
ZED_CLIENT_CHECKSUM_SEED: "development-checksum-seed", ZED_CLIENT_CHECKSUM_SEED: "development-checksum-seed",