From 4effbeeb2a1764b20d5dd24b01720a674a2ca382 Mon Sep 17 00:00:00 2001 From: B Cohen <2946283+NOTtheMessiah@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:37:10 -0600 Subject: [PATCH 1/4] add glib to build.nix in buildInputs --- nix/build.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/build.nix b/nix/build.nix index 484049a421f8de..e32624a5f1cddf 100644 --- a/nix/build.nix +++ b/nix/build.nix @@ -24,6 +24,7 @@ fontconfig, freetype, git, + glib, libgit2, libglvnd, libxkbcommon, @@ -126,6 +127,7 @@ let curl fontconfig freetype + glib # TODO: need staticlib of this for linking the musl remote server. # should make it a separate derivation/flake output # see https://crane.dev/examples/cross-musl.html From 71ca6c4d47e3815f07fbc6d4c7557a36a4f9f1ae Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 20 Oct 2025 00:44:01 -0500 Subject: [PATCH 2/4] Use libwebrtc's DesktopCapturer for screen capture This adds support for screen sharing on Wayland (fixes #28754). libwebrtc replaces scap, which aims to be cross platform but was only used on Windows and X11. The X11 support relied on an out-of-tree branch on which the original author closed their own pull request (https://github.com/CapSoftware/scap/pull/124). This also replaces Zed's platform-specific code on macOS. Switching to a single cross platform library simplifies the code by removing the need for Zed to have its own traits for cross platform abstraction. --- .github/workflows/release.yml | 6 + .github/workflows/run_tests.yml | 21 + Cargo.lock | 486 +++++++----------- Cargo.toml | 11 +- crates/audio/Cargo.toml | 2 +- crates/call/Cargo.toml | 3 +- crates/call/src/call_impl/room.rs | 41 +- crates/collab/src/tests/following_tests.rs | 13 +- crates/collab/src/tests/integration_tests.rs | 25 +- crates/collab_ui/src/collab_panel.rs | 9 +- crates/gpui/Cargo.toml | 7 - crates/gpui/src/app.rs | 16 +- crates/gpui/src/app/test_context.rs | 11 +- crates/gpui/src/platform.rs | 107 +--- crates/gpui/src/platform/linux.rs | 5 - .../src/platform/linux/headless/client.rs | 18 - crates/gpui/src/platform/linux/platform.rs | 19 - .../gpui/src/platform/linux/wayland/client.rs | 23 - crates/gpui/src/platform/linux/x11/client.rs | 15 - crates/gpui/src/platform/mac.rs | 7 - crates/gpui/src/platform/mac/platform.rs | 13 - .../gpui/src/platform/mac/screen_capture.rs | 334 ------------ .../gpui/src/platform/scap_screen_capture.rs | 325 ------------ crates/gpui/src/platform/test.rs | 2 - crates/gpui/src/platform/test/platform.rs | 107 +--- crates/gpui/src/platform/windows.rs | 5 - crates/gpui/src/platform/windows/platform.rs | 12 - crates/livekit_client/Cargo.toml | 14 +- crates/livekit_client/examples/test_app.rs | 13 +- crates/livekit_client/src/lib.rs | 17 + crates/livekit_client/src/livekit_client.rs | 185 ++++++- .../src/livekit_client/playback.rs | 116 +---- crates/livekit_client/src/mock_client.rs | 5 + .../src/mock_client/participant.rs | 36 +- crates/title_bar/Cargo.toml | 3 +- crates/title_bar/src/collab.rs | 186 +++---- script/linux | 17 + .../xtask/src/tasks/workflows/run_tests.rs | 34 +- 38 files changed, 615 insertions(+), 1654 deletions(-) delete mode 100644 crates/gpui/src/platform/mac/screen_capture.rs delete mode 100644 crates/gpui/src/platform/scap_screen_capture.rs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7afac285b5a34d..fb56d25c983bc1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,6 +44,9 @@ jobs: run_tests_linux: if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') runs-on: namespace-profile-16x32-ubuntu-2204 + env: + CC: clang + CXX: clang++ steps: - name: steps::checkout_repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 @@ -123,6 +126,9 @@ jobs: check_scripts: if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') runs-on: namespace-profile-2x4-ubuntu-2404 + env: + CC: clang + CXX: clang++ steps: - name: steps::checkout_repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index ad228103e33bd1..82aa81669e9fb2 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -61,6 +61,9 @@ jobs: check_style: if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') runs-on: namespace-profile-4x8-ubuntu-2204 + env: + CC: clang + CXX: clang++ steps: - name: steps::checkout_repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 @@ -130,6 +133,9 @@ jobs: - orchestrate if: needs.orchestrate.outputs.run_tests == 'true' runs-on: namespace-profile-16x32-ubuntu-2204 + env: + CC: clang + CXX: clang++ steps: - name: steps::checkout_repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 @@ -213,6 +219,9 @@ jobs: - orchestrate if: needs.orchestrate.outputs.run_tests == 'true' runs-on: namespace-profile-16x32-ubuntu-2204 + env: + CC: clang + CXX: clang++ steps: - name: steps::checkout_repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 @@ -252,6 +261,9 @@ jobs: - orchestrate if: needs.orchestrate.outputs.run_tests == 'true' runs-on: namespace-profile-8x16-ubuntu-2204 + env: + CC: clang + CXX: clang++ steps: - name: steps::checkout_repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 @@ -292,6 +304,9 @@ jobs: - orchestrate if: needs.orchestrate.outputs.run_tests == 'true' runs-on: namespace-profile-2x4-ubuntu-2404 + env: + CC: clang + CXX: clang++ steps: - name: steps::checkout_repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 @@ -324,6 +339,9 @@ jobs: - orchestrate if: needs.orchestrate.outputs.run_docs == 'true' runs-on: namespace-profile-8x16-ubuntu-2204 + env: + CC: clang + CXX: clang++ steps: - name: steps::checkout_repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 @@ -394,6 +412,9 @@ jobs: - orchestrate if: needs.orchestrate.outputs.run_action_checks == 'true' runs-on: namespace-profile-2x4-ubuntu-2404 + env: + CC: clang + CXX: clang++ steps: - name: steps::checkout_repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 diff --git a/Cargo.lock b/Cargo.lock index 9de96dfe48ccd2..bf31741e304bc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1212,9 +1212,9 @@ dependencies = [ [[package]] name = "async-tungstenite" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee88b4c88ac8c9ea446ad43498955750a4bbe64c4392f21ccfe5d952865e318f" +checksum = "b6f89c129ab749940f95509d84950c62092c8b4bc6e386ddb162229037a6ec91" dependencies = [ "atomic-waker", "futures-core", @@ -1226,7 +1226,7 @@ dependencies = [ "rustls-pki-types", "tokio", "tokio-rustls 0.26.2", - "tungstenite 0.27.0", + "tungstenite 0.28.0", ] [[package]] @@ -2151,7 +2151,7 @@ dependencies = [ "ash-window", "bitflags 2.9.4", "bytemuck", - "codespan-reporting 0.12.0", + "codespan-reporting", "glow", "gpu-alloc", "gpu-alloc-ash", @@ -2259,6 +2259,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "bmrng" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54df9073108f1558f90ae6c5bf5ab9c917c4185f5527b280c87a993cbead0ac" +dependencies = [ + "futures-core", + "tokio", +] + [[package]] name = "borrow-or-share" version = "0.2.4" @@ -2533,6 +2543,7 @@ dependencies = [ "gpui_tokio", "http_client", "language", + "libwebrtc", "livekit_client", "log", "postage", @@ -2804,6 +2815,16 @@ dependencies = [ "target-lexicon 0.12.16", ] +[[package]] +name = "cfg-expr" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9acd0bdbbf4b2612d09f52ba61da432140cb10930354079d0d53fafc12968726" +dependencies = [ + "smallvec", + "target-lexicon 0.13.3", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -3209,17 +3230,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "codespan-reporting" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba7a06c0b31fff5ff2e1e7d37dbf940864e2a974b336e1a2938d10af6e8fb283" -dependencies = [ - "serde", - "termcolor", - "unicode-width", -] - [[package]] name = "codestral" version = "0.1.0" @@ -3708,19 +3718,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-graphics-helmer-fork" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5" -dependencies = [ - "bitflags 2.9.4", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "foreign-types 0.5.0", - "libc", -] - [[package]] name = "core-graphics-types" version = "0.1.3" @@ -4295,9 +4292,9 @@ checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" [[package]] name = "cxx" -version = "1.0.187" +version = "1.0.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8465678d499296e2cbf9d3acf14307458fd69b471a31b65b3c519efe8b5e187" +checksum = "4e9c4fe7f2f5dc5c62871a1b43992d197da6fa1394656a94276ac2894a90a6fe" dependencies = [ "cc", "cxx-build", @@ -4310,12 +4307,12 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.187" +version = "1.0.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d74b6bcf49ebbd91f1b1875b706ea46545032a14003b5557b7dfa4bbeba6766e" +checksum = "b5cf2909d37d80633ddd208676fc27c2608a7f035fff69c882421168038b26dd" dependencies = [ "cc", - "codespan-reporting 0.13.0", + "codespan-reporting", "indexmap", "proc-macro2", "quote", @@ -4325,12 +4322,12 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "1.0.187" +version = "1.0.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ca2ad69673c4b35585edfa379617ac364bccd0ba0adf319811ba3a74ffa48a" +checksum = "077f5ee3d3bfd8d27f83208fdaa96ddd50af7f096c77077cc4b94da10bfacefd" dependencies = [ "clap", - "codespan-reporting 0.13.0", + "codespan-reporting", "indexmap", "proc-macro2", "quote", @@ -4339,19 +4336,20 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.187" +version = "1.0.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29b52102aa395386d77d322b3a0522f2035e716171c2c60aa87cc5e9466e523" +checksum = "b0108748615125b9f2e915dfafdffcbdabbca9b15102834f6d7e9a768f2f2864" [[package]] name = "cxxbridge-macro" -version = "1.0.187" +version = "1.0.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8ebf0b6138325af3ec73324cb3a48b64d57721f17291b151206782e61f66cd" +checksum = "e6e896681ef9b8dc462cfa6961d61909704bde0984b30bcb4082fe102b478890" dependencies = [ "indexmap", "proc-macro2", "quote", + "rustversion", "syn 2.0.106", ] @@ -4608,7 +4606,7 @@ dependencies = [ "serde_json", "serde_json_lenient", "settings", - "sysinfo 0.37.2", + "sysinfo", "task", "tasks_ui", "telemetry", @@ -6949,6 +6947,19 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "gio-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171ed2f6dd927abbe108cfd9eebff2052c335013f5879d55bab0dc1dee19b706" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 7.0.7", + "windows-sys 0.61.2", +] + [[package]] name = "git" version = "0.1.0" @@ -7080,6 +7091,50 @@ dependencies = [ "ztracing", ] +[[package]] +name = "glib" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9dbecb1c33e483a98be4acfea2ab369e1c28f517c6eadb674537409c25c4b2" +dependencies = [ + "bitflags 2.9.4", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "smallvec", +] + +[[package]] +name = "glib-macros" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "880e524e0085f3546cfb38532b2c202c0d64741d9977a6e4aa24704bfc9f19fb" +dependencies = [ + "heck 0.5.0", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "glib-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d09d3d0fddf7239521674e57b0465dfbd844632fec54f059f7f56112e3f927e1" +dependencies = [ + "libc", + "system-deps 7.0.7", +] + [[package]] name = "glob" version = "0.3.3" @@ -7146,6 +7201,17 @@ dependencies = [ "workspace", ] +[[package]] +name = "gobject-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "538e41d8776173ec107e7b0f2aceced60abc368d7e1d81c1f0e2ecd35f59080d" +dependencies = [ + "glib-sys", + "libc", + "system-deps 7.0.7", +] + [[package]] name = "goblin" version = "0.8.2" @@ -7302,7 +7368,6 @@ dependencies = [ "x11rb", "xkbcommon", "zed-font-kit", - "zed-scap", "zed-xim", ] @@ -7584,6 +7649,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" + [[package]] name = "hexf-parse" version = "0.2.1" @@ -7918,7 +7989,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] @@ -9148,10 +9219,11 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.10" -source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=5f04705ac3f356350ae31534ffbc476abc9ea83d#5f04705ac3f356350ae31534ffbc476abc9ea83d" +version = "0.3.20" +source = "git+https://github.com/Be-ing/rust-sdks?branch=zed#03d3a397e85dba2e6acc0e53da81ed21369279a7" dependencies = [ "cxx", + "glib", "jni", "js-sys", "lazy_static", @@ -9233,9 +9305,11 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "livekit" -version = "0.7.8" -source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=5f04705ac3f356350ae31534ffbc476abc9ea83d#5f04705ac3f356350ae31534ffbc476abc9ea83d" +version = "0.7.25" +source = "git+https://github.com/Be-ing/rust-sdks?branch=zed#03d3a397e85dba2e6acc0e53da81ed21369279a7" dependencies = [ + "bmrng", + "bytes 1.10.1", "chrono", "futures-util", "lazy_static", @@ -9256,11 +9330,13 @@ dependencies = [ [[package]] name = "livekit-api" -version = "0.4.2" -source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=5f04705ac3f356350ae31534ffbc476abc9ea83d#5f04705ac3f356350ae31534ffbc476abc9ea83d" +version = "0.4.10" +source = "git+https://github.com/Be-ing/rust-sdks?branch=zed#03d3a397e85dba2e6acc0e53da81ed21369279a7" dependencies = [ + "base64 0.21.7", "futures-util", - "http 0.2.12", + "http 1.3.1", + "jsonwebtoken", "livekit-protocol", "livekit-runtime", "log", @@ -9268,20 +9344,21 @@ dependencies = [ "pbjson-types", "prost 0.12.6", "rand 0.9.2", - "reqwest 0.11.27", + "reqwest 0.12.24", "scopeguard", "serde", + "serde_json", "sha2", "thiserror 1.0.69", "tokio", - "tokio-tungstenite 0.26.2", + "tokio-tungstenite 0.28.0", "url", ] [[package]] name = "livekit-protocol" -version = "0.3.9" -source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=5f04705ac3f356350ae31534ffbc476abc9ea83d#5f04705ac3f356350ae31534ffbc476abc9ea83d" +version = "0.5.2" +source = "git+https://github.com/Be-ing/rust-sdks?branch=zed#03d3a397e85dba2e6acc0e53da81ed21369279a7" dependencies = [ "futures-util", "livekit-runtime", @@ -9289,7 +9366,6 @@ dependencies = [ "pbjson", "pbjson-types", "prost 0.12.6", - "prost-types 0.12.6", "serde", "thiserror 1.0.69", "tokio", @@ -9298,7 +9374,7 @@ dependencies = [ [[package]] name = "livekit-runtime" version = "0.4.0" -source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=5f04705ac3f356350ae31534ffbc476abc9ea83d#5f04705ac3f356350ae31534ffbc476abc9ea83d" +source = "git+https://github.com/Be-ing/rust-sdks?branch=zed#03d3a397e85dba2e6acc0e53da81ed21369279a7" dependencies = [ "tokio", "tokio-stream", @@ -9352,10 +9428,10 @@ dependencies = [ "sha2", "simplelog", "smallvec", - "tokio-tungstenite 0.26.2", + "tokio", + "tokio-tungstenite 0.28.0", "ui", "util", - "zed-scap", ] [[package]] @@ -10096,12 +10172,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "multimap" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" - [[package]] name = "naga" version = "25.0.1" @@ -10112,7 +10182,7 @@ dependencies = [ "bit-set", "bitflags 2.9.4", "cfg_aliases 0.2.1", - "codespan-reporting 0.12.0", + "codespan-reporting", "half", "hashbrown 0.15.5", "hexf-parse", @@ -10604,18 +10674,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", -] - -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", ] [[package]] @@ -10755,24 +10813,6 @@ dependencies = [ "objc2-quartz-core", ] -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - [[package]] name = "object" version = "0.36.7" @@ -12602,7 +12642,7 @@ dependencies = [ "itertools 0.10.5", "lazy_static", "log", - "multimap 0.8.3", + "multimap", "petgraph", "prost 0.9.0", "prost-types 0.9.0", @@ -12621,7 +12661,7 @@ dependencies = [ "heck 0.5.0", "itertools 0.12.1", "log", - "multimap 0.10.1", + "multimap", "once_cell", "petgraph", "prettyplease", @@ -12829,15 +12869,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" -[[package]] -name = "quick-xml" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" -dependencies = [ - "memchr", -] - [[package]] name = "quick-xml" version = "0.37.5" @@ -13045,7 +13076,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "simd_helpers", - "system-deps", + "system-deps 6.2.2", "thiserror 1.0.69", "v_frame", "wasm-bindgen", @@ -13416,7 +13447,7 @@ dependencies = [ "settings", "shellexpand 2.1.2", "smol", - "sysinfo 0.37.2", + "sysinfo", "task", "theme", "thiserror 2.0.17", @@ -13503,7 +13534,6 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.32", - "hyper-rustls 0.24.2", "hyper-tls", "ipnet", "js-sys", @@ -13513,8 +13543,6 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.12", - "rustls-native-certs 0.6.3", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -13523,7 +13551,6 @@ dependencies = [ "system-configuration 0.5.1", "tokio", "tokio-native-tls", - "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", @@ -13547,16 +13574,22 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "hyper 1.7.0", + "hyper-rustls 0.27.7", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", + "quinn", + "rustls 0.23.33", + "rustls-native-certs 0.8.2", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 1.0.2", "tokio", + "tokio-rustls 0.26.2", "tower 0.5.2", "tower-http 0.6.6", "tower-service", @@ -14268,29 +14301,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" -[[package]] -name = "screencapturekit" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e" -dependencies = [ - "screencapturekit-sys", -] - -[[package]] -name = "screencapturekit-sys" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60" -dependencies = [ - "block", - "dispatch", - "objc", - "objc-foundation", - "objc_id", - "once_cell", -] - [[package]] name = "scroll" version = "0.12.0" @@ -16067,20 +16077,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "sysinfo" -version = "0.31.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" -dependencies = [ - "core-foundation-sys", - "libc", - "memchr", - "ntapi", - "rayon", - "windows 0.57.0", -] - [[package]] name = "sysinfo" version = "0.37.2" @@ -16143,13 +16139,26 @@ version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ - "cfg-expr", + "cfg-expr 0.15.8", "heck 0.5.0", "pkg-config", "toml 0.8.23", "version-compare", ] +[[package]] +name = "system-deps" +version = "7.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" +dependencies = [ + "cfg-expr 0.20.4", + "heck 0.5.0", + "pkg-config", + "toml 0.9.8", + "version-compare", +] + [[package]] name = "system-interface" version = "0.27.3" @@ -16178,7 +16187,7 @@ dependencies = [ "release_channel", "semver", "serde", - "sysinfo 0.37.2", + "sysinfo", ] [[package]] @@ -16231,18 +16240,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb" -[[package]] -name = "tao-core-video-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "objc", -] - [[package]] name = "tap" version = "1.0.1" @@ -16382,7 +16379,7 @@ dependencies = [ "serde_json", "settings", "smol", - "sysinfo 0.37.2", + "sysinfo", "task", "theme", "thiserror 2.0.17", @@ -16755,6 +16752,7 @@ dependencies = [ "db", "gpui", "http_client", + "libwebrtc", "notifications", "pretty_assertions", "project", @@ -16894,9 +16892,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" dependencies = [ "futures-util", "log", @@ -16904,7 +16902,7 @@ dependencies = [ "rustls-pki-types", "tokio", "tokio-rustls 0.26.2", - "tungstenite 0.26.2", + "tungstenite 0.28.0", ] [[package]] @@ -17574,28 +17572,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" -dependencies = [ - "bytes 1.10.1", - "data-encoding", - "http 1.3.1", - "httparse", - "log", - "rand 0.9.2", - "rustls 0.23.33", - "rustls-pki-types", - "sha1", - "thiserror 2.0.17", - "utf-8", -] - -[[package]] -name = "tungstenite" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes 1.10.1", "data-encoding", @@ -18950,27 +18929,32 @@ dependencies = [ [[package]] name = "webrtc-sys" -version = "0.3.7" -source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=5f04705ac3f356350ae31534ffbc476abc9ea83d#5f04705ac3f356350ae31534ffbc476abc9ea83d" +version = "0.3.17" +source = "git+https://github.com/Be-ing/rust-sdks?branch=zed#03d3a397e85dba2e6acc0e53da81ed21369279a7" dependencies = [ "cc", "cxx", "cxx-build", "glob", "log", + "pkg-config", "webrtc-sys-build", ] [[package]] name = "webrtc-sys-build" -version = "0.3.6" -source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=5f04705ac3f356350ae31534ffbc476abc9ea83d#5f04705ac3f356350ae31534ffbc476abc9ea83d" +version = "0.3.11" +source = "git+https://github.com/Be-ing/rust-sdks?branch=zed#03d3a397e85dba2e6acc0e53da81ed21369279a7" dependencies = [ + "anyhow", "fs2", + "hex", + "hex-literal", "regex", - "reqwest 0.11.27", + "reqwest 0.12.24", "scratch", "semver", + "sha2", "zip 0.6.6", ] @@ -19115,16 +19099,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" -dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.58.0" @@ -19148,20 +19122,6 @@ dependencies = [ "windows-numerics", ] -[[package]] -name = "windows-capture" -version = "1.4.3" -source = "git+https://github.com/zed-industries/windows-capture.git?rev=f0d6c1b6691db75461b732f6d5ff56eed002eeb9#f0d6c1b6691db75461b732f6d5ff56eed002eeb9" -dependencies = [ - "clap", - "ctrlc", - "parking_lot", - "rayon", - "thiserror 2.0.17", - "windows 0.61.3", - "windows-future", -] - [[package]] name = "windows-collections" version = "0.2.0" @@ -19181,18 +19141,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" -dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.58.0" @@ -19219,19 +19167,6 @@ dependencies = [ "windows-strings 0.4.2", ] -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - [[package]] name = "windows-future" version = "0.2.1" @@ -19243,17 +19178,6 @@ dependencies = [ "windows-threading", ] -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "windows-implement" version = "0.58.0" @@ -19276,17 +19200,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "windows-interface" version = "0.58.0" @@ -20142,16 +20055,6 @@ dependencies = [ "tap", ] -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - [[package]] name = "x11-clipboard" version = "0.9.3" @@ -20201,18 +20104,6 @@ dependencies = [ "libc", ] -[[package]] -name = "xcb" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f07c123b796139bfe0603e654eaf08e132e52387ba95b252c78bad3640ba37ea" -dependencies = [ - "bitflags 1.3.2", - "libc", - "quick-xml 0.30.0", - "x11", -] - [[package]] name = "xcursor" version = "0.3.10" @@ -20582,7 +20473,7 @@ dependencies = [ "snippets_ui", "supermaven", "svg_preview", - "sysinfo 0.37.2", + "sysinfo", "system_specs", "tab_switcher", "task", @@ -20694,27 +20585,6 @@ dependencies = [ "windows-registry 0.4.0", ] -[[package]] -name = "zed-scap" -version = "0.0.8-zed" -source = "git+https://github.com/zed-industries/scap?rev=4afea48c3b002197176fb19cd0f9b180dd36eaac#4afea48c3b002197176fb19cd0f9b180dd36eaac" -dependencies = [ - "anyhow", - "cocoa 0.25.0", - "core-graphics-helmer-fork", - "log", - "objc", - "rand 0.8.5", - "screencapturekit", - "screencapturekit-sys", - "sysinfo 0.31.4", - "tao-core-video-sys", - "windows 0.61.3", - "windows-capture", - "x11", - "xcb", -] - [[package]] name = "zed-xim" version = "0.4.0-zed" diff --git a/Cargo.toml b/Cargo.toml index 0ad4d2b1452398..0cd1b7ae49c974 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -451,7 +451,7 @@ async-recursion = "1.0.0" async-tar = "0.5.1" async-task = "4.7" async-trait = "0.1" -async-tungstenite = "0.31.0" +async-tungstenite = "0.32.0" async_zip = { version = "0.0.18", features = ["deflate", "deflate64"] } aws-config = { version = "1.6.1", features = ["behavior-version-latest"] } aws-credential-types = { version = "1.2.2", features = [ @@ -529,6 +529,10 @@ jupyter-websocket-client = "0.15.0" libc = "0.2" libsqlite3-sys = { version = "0.30.1", features = ["bundled"] } linkify = "0.10.0" +libwebrtc = { branch = "zed", git = "https://github.com/Be-ing/rust-sdks" } +livekit = { branch = "zed", git = "https://github.com/Be-ing/rust-sdks", features = [ + "__rustls-tls" +] } log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] } lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "b71ab4eeb27d9758be8092020a46fe33fbca4e33" } mach2 = "0.5" @@ -613,8 +617,6 @@ rust-embed = { version = "8.4", features = ["include-exclude"] } rustc-hash = "2.1.0" rustls = { version = "0.23.26" } rustls-platform-verifier = "0.5.0" -# WARNING: If you change this, you must also publish a new version of zed-scap to crates.io -scap = { git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197176fb19cd0f9b180dd36eaac", default-features = false, package = "zed-scap", version = "0.0.8-zed" } schemars = { version = "1.0", features = ["indexmap2"] } semver = { version = "1.0", features = ["serde"] } serde = { version = "1.0.221", features = ["derive", "rc"] } @@ -656,7 +658,7 @@ time = { version = "0.3", features = [ ] } tiny_http = "0.8" tokio = { version = "1" } -tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] } +tokio-tungstenite = { version = "0.28", features = ["__rustls-tls"] } toml = "0.8" toml_edit = { version = "0.22", default-features = false, features = ["display", "parse", "serde"] } tower-http = "0.4.4" @@ -766,7 +768,6 @@ features = [ [patch.crates-io] notify = { git = "https://github.com/zed-industries/notify.git", rev = "b4588b2e5aee68f4c0e100f140e808cbce7b1419" } notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "b4588b2e5aee68f4c0e100f140e808cbce7b1419" } -windows-capture = { git = "https://github.com/zed-industries/windows-capture.git", rev = "f0d6c1b6691db75461b732f6d5ff56eed002eeb9" } calloop = { git = "https://github.com/zed-industries/calloop" } [profile.dev] diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index 2aee764007a791..0d78f08038dca2 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -29,4 +29,4 @@ thiserror.workspace = true util.workspace = true [target.'cfg(not(any(all(target_os = "windows", target_env = "gnu"), target_os = "freebsd")))'.dependencies] -libwebrtc = { rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d", git = "https://github.com/zed-industries/livekit-rust-sdks" } +libwebrtc = { workspace = true } diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index ff034f914b0be4..f0c3aef8e65c97 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -30,8 +30,9 @@ collections.workspace = true fs.workspace = true futures.workspace = true feature_flags.workspace = true -gpui = { workspace = true, features = ["screen-capture"] } +gpui.workspace = true language.workspace = true +libwebrtc.workspace = true log.workspace = true postage.workspace = true project.workspace = true diff --git a/crates/call/src/call_impl/room.rs b/crates/call/src/call_impl/room.rs index fc15b4e4395ae7..6c1d8bfb6a1937 100644 --- a/crates/call/src/call_impl/room.rs +++ b/crates/call/src/call_impl/room.rs @@ -13,13 +13,16 @@ use feature_flags::FeatureFlagAppExt; use fs::Fs; use futures::StreamExt; use gpui::{ - App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, FutureExt as _, - ScreenCaptureSource, ScreenCaptureStream, Task, Timeout, WeakEntity, + App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, FutureExt as _, Task, Timeout, + WeakEntity, }; use gpui_tokio::Tokio; use language::LanguageRegistry; +use libwebrtc::desktop_capturer::CaptureSource; use livekit::{LocalTrackPublication, ParticipantIdentity, RoomEvent}; -use livekit_client::{self as livekit, AudioStream, TrackSid}; +use livekit_client::{ + self as livekit, AudioStream, ScreenCaptureStreamHandle, TrackSid, screen_capture_sources, +}; use postage::{sink::Sink, stream::Stream, watch}; use project::Project; use settings::Settings as _; @@ -87,6 +90,7 @@ pub struct Room { pending_room_update: Option>, maintain_connection: Option>>, created: Instant, + available_screens: Vec, } impl EventEmitter for Room {} @@ -159,6 +163,7 @@ impl Room { room_update_completed_tx, room_update_completed_rx, created: cx.background_executor().now(), + available_screens: screen_capture_sources(), } } @@ -1277,9 +1282,7 @@ impl Room { pub fn shared_screen_id(&self) -> Option { self.live_kit.as_ref().and_then(|lk| match lk.screen_track { - LocalTrack::Published { ref _stream, .. } => { - _stream.metadata().ok().map(|meta| meta.id) - } + LocalTrack::Published { ref _stream, .. } => Some(_stream.screen_id), _ => None, }) } @@ -1386,7 +1389,7 @@ impl Room { } live_kit.microphone_track = LocalTrack::Published { track_publication: publication, - _stream: Box::new(stream), + _stream: stream, }; cx.notify(); } @@ -1406,9 +1409,10 @@ impl Room { }) } + /// Only tests should pass None for `source`. pub fn share_screen( &mut self, - source: Rc, + source: Option, cx: &mut Context, ) -> Task> { if self.status.is_offline() { @@ -1418,6 +1422,9 @@ impl Room { return Task::ready(Err(anyhow!("screen was already shared"))); } + #[cfg(not(feature = "test-support"))] + assert!(source.is_some()); + let (participant, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { let publish_id = post_inc(&mut live_kit.next_publish_id); live_kit.screen_track = LocalTrack::Pending { publish_id }; @@ -1428,9 +1435,12 @@ impl Room { }; cx.spawn(async move |this, cx| { - let publication = participant.publish_screenshare_track(&*source, cx).await; + let publication = participant.publish_screenshare_track(source, cx).await; this.update(cx, |this, cx| { + // If screens were added or removed since joining the room or unsharing the screen, + // let the user pick them from the screen selection menu. + this.available_screens = screen_capture_sources(); let live_kit = this .live_kit .as_mut() @@ -1521,6 +1531,9 @@ impl Room { pub fn unshare_screen(&mut self, play_sound: bool, cx: &mut Context) -> Result<()> { anyhow::ensure!(!self.status.is_offline(), "room is offline"); + // If screens were added or removed during the screen share, ensure they are available + // to share from the screen picker menu when re-enabling screen sharing. + self.available_screens = screen_capture_sources(); let live_kit = self .live_kit @@ -1602,6 +1615,10 @@ impl Room { } } } + + pub fn available_screens(&self) -> &Vec { + &self.available_screens + } } fn spawn_room_connection( @@ -1654,7 +1671,7 @@ fn spawn_room_connection( struct LiveKitRoom { room: Rc, - screen_track: LocalTrack, + screen_track: LocalTrack, microphone_track: LocalTrack, /// Tracks whether we're currently in a muted state due to auto-mute from deafening or manual mute performed by user. muted_by_user: bool, @@ -1694,7 +1711,7 @@ impl LiveKitRoom { } #[derive(Default)] -enum LocalTrack { +enum LocalTrack { #[default] None, Pending { @@ -1702,7 +1719,7 @@ enum LocalTrack { }, Published { track_publication: LocalTrackPublication, - _stream: Box, + _stream: Stream, }, } diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index ec654e06341b6f..13d71e016dab27 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -463,7 +463,6 @@ async fn test_basic_following( // #[cfg(all(not(target_os = "macos"), not(target_os = "windows")))] { use crate::rpc::RECONNECT_TIMEOUT; - use gpui::TestScreenCaptureSource; use workspace::{ dock::{DockPosition, test::TestPanel}, item::test::TestItem, @@ -471,25 +470,15 @@ async fn test_basic_following( }; // Client B activates an external window, which causes a new screen-sharing item to be added to the pane. - let display = TestScreenCaptureSource::new(); active_call_b .update(cx_b, |call, cx| call.set_location(None, cx)) .await .unwrap(); - cx_b.set_screen_capture_sources(vec![display]); - let source = cx_b - .read(|cx| cx.screen_capture_sources()) - .await - .unwrap() - .unwrap() - .into_iter() - .next() - .unwrap(); active_call_b .update(cx_b, |call, cx| { call.room() .unwrap() - .update(cx, |room, cx| room.share_screen(source, cx)) + .update(cx, |room, cx| room.share_screen(None, cx)) }) .await .unwrap(); diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 391e7355ea196d..add898229062d7 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -274,23 +274,13 @@ async fn test_basic_calls( ); // User A shares their screen - let display = gpui::TestScreenCaptureSource::new(); let events_b = active_call_events(cx_b); let events_c = active_call_events(cx_c); - cx_a.set_screen_capture_sources(vec![display]); - let screen_a = cx_a - .update(|cx| cx.screen_capture_sources()) - .await - .unwrap() - .unwrap() - .into_iter() - .next() - .unwrap(); active_call_a .update(cx_a, |call, cx| { call.room() .unwrap() - .update(cx, |room, cx| room.share_screen(screen_a, cx)) + .update(cx, |room, cx| room.share_screen(None, cx)) }) .await .unwrap(); @@ -6439,22 +6429,11 @@ async fn test_join_call_after_screen_was_shared( assert_eq!(call_b.calling_user.github_login, "user_a"); // User A shares their screen - let display = gpui::TestScreenCaptureSource::new(); - cx_a.set_screen_capture_sources(vec![display]); - let screen_a = cx_a - .update(|cx| cx.screen_capture_sources()) - .await - .unwrap() - .unwrap() - .into_iter() - .next() - .unwrap(); - active_call_a .update(cx_a, |call, cx| { call.room() .unwrap() - .update(cx, |room, cx| room.share_screen(screen_a, cx)) + .update(cx, |room, cx| room.share_screen(None, cx)) }) .await .unwrap(); diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 2f1e2842cbd2f5..1695a251d88887 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -164,13 +164,10 @@ pub fn init(cx: &mut App) { if room.is_sharing_screen() { room.unshare_screen(true, cx).ok(); } else { - let sources = cx.screen_capture_sources(); - + let first_screen = room.available_screens().first().map(|s| s.clone()); cx.spawn(async move |room, cx| { - let sources = sources.await??; - let first = sources.into_iter().next(); - if let Some(first) = first { - room.update(cx, |room, cx| room.share_screen(first, cx))? + if let Some(first) = first_screen { + room.update(cx, |room, cx| room.share_screen(Some(first), cx))? .await } else { Ok(()) diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 4985cc07383aac..18a1bb5493e100 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -73,10 +73,6 @@ x11 = [ "x11-clipboard", "filedescriptor", "open", - "scap?/x11", -] -screen-capture = [ - "scap", ] windows-manifest = [] @@ -164,9 +160,6 @@ metal.workspace = true [target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))'.dependencies] pathfinder_geometry = "0.5" -[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))'.dependencies] -scap = { workspace = true, optional = true } - [target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies] # Always used flume = "0.11" diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 2f4c7611dcf9d2..f4e8e6a22ee580 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -40,8 +40,8 @@ use crate::{ Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, PromptBuilder, PromptButton, PromptHandle, PromptLevel, Render, RenderImage, RenderablePromptHandle, - Reservation, ScreenCaptureSource, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, - TextSystem, Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator, + Reservation, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, + WindowAppearance, WindowHandle, WindowId, WindowInvalidator, colors::{Colors, GlobalColors}, current_platform, hash, init_app_menus, }; @@ -1054,18 +1054,6 @@ impl App { self.platform.primary_display() } - /// Returns whether `screen_capture_sources` may work. - pub fn is_screen_capture_supported(&self) -> bool { - self.platform.is_screen_capture_supported() - } - - /// Returns a list of available screen capture sources. - pub fn screen_capture_sources( - &self, - ) -> oneshot::Receiver>>> { - self.platform.screen_capture_sources() - } - /// Returns the display with the given ID, if one exists. pub fn find_display(&self, id: DisplayId) -> Option> { self.displays() diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 5be2e394e8edfd..9e9055055ddc10 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -3,9 +3,8 @@ use crate::{ BackgroundExecutor, BorrowAppContext, Bounds, Capslock, ClipboardItem, DrawPhase, Drawable, Element, Empty, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, - Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, - TestScreenCaptureSource, TestWindow, TextSystem, VisualContext, Window, WindowBounds, - WindowHandle, WindowOptions, app::GpuiMode, + Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, + TextSystem, VisualContext, Window, WindowBounds, WindowHandle, WindowOptions, app::GpuiMode, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt, channel::oneshot}; @@ -348,12 +347,6 @@ impl TestAppContext { rx } - /// Causes the given sources to be returned if the application queries for screen - /// capture sources. - pub fn set_screen_capture_sources(&self, sources: Vec) { - self.test_platform.set_screen_capture_sources(sources); - } - /// Returns all windows open in the test. pub fn windows(&self) -> Vec { self.app.borrow().windows() diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 370043fb6b8ec7..72e726e1c6262c 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -23,18 +23,6 @@ mod test; #[cfg(target_os = "windows")] mod windows; -#[cfg(all( - feature = "screen-capture", - any( - target_os = "windows", - all( - any(target_os = "linux", target_os = "freebsd"), - any(feature = "wayland", feature = "x11"), - ) - ) -))] -pub(crate) mod scap_screen_capture; - use crate::{ Action, AnyWindowHandle, App, AsyncWindowContext, BackgroundExecutor, Bounds, DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun, @@ -85,7 +73,7 @@ pub(crate) use windows::*; pub use linux::layer_shell; #[cfg(any(test, feature = "test-support"))] -pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream}; +pub use test::TestDispatcher; /// Returns a background executor for the current platform. pub fn background_executor() -> BackgroundExecutor { @@ -97,6 +85,18 @@ pub(crate) fn current_platform(headless: bool) -> Rc { Rc::new(MacPlatform::new(headless)) } +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +#[derive(strum::Display)] +/// The available display systems for Linux +pub enum LinuxCompositor { + /// Wayland + Wayland, + /// X11 + X11, + /// Headless (no display) + Headless, +} + #[cfg(any(target_os = "linux", target_os = "freebsd"))] pub(crate) fn current_platform(headless: bool) -> Rc { #[cfg(feature = "x11")] @@ -108,17 +108,18 @@ pub(crate) fn current_platform(headless: bool) -> Rc { match guess_compositor() { #[cfg(feature = "wayland")] - "Wayland" => Rc::new(WaylandClient::new()), - + LinuxCompositor::Wayland => Rc::new(WaylandClient::new()), + #[cfg(not(feature = "wayland"))] + LinuxCompositor::Wayland => panic!("Running on Wayland but built without Wayland support"), #[cfg(feature = "x11")] - "X11" => Rc::new( + LinuxCompositor::X11 => Rc::new( X11Client::new() .context("Failed to initialize X11 client.") .unwrap(), ), - - "Headless" => Rc::new(HeadlessClient::new()), - _ => unreachable!(), + #[cfg(not(feature = "x11"))] + LinuxCompositor::X11 => panic!("Running on X11 but built without X11 support"), + LinuxCompositor::Headless => Rc::new(HeadlessClient::new()), } } @@ -135,9 +136,9 @@ pub(crate) fn current_platform(_headless: bool) -> Rc { /// Does not attempt to connect to the given compositor #[cfg(any(target_os = "linux", target_os = "freebsd"))] #[inline] -pub fn guess_compositor() -> &'static str { +pub fn guess_compositor() -> LinuxCompositor { if std::env::var_os("ZED_HEADLESS").is_some() { - return "Headless"; + return LinuxCompositor::Headless; } #[cfg(feature = "wayland")] @@ -154,11 +155,11 @@ pub fn guess_compositor() -> &'static str { let use_x11 = x11_display.is_some_and(|display| !display.is_empty()); if use_wayland { - "Wayland" + LinuxCompositor::Wayland } else if use_x11 { - "X11" + LinuxCompositor::X11 } else { - "Headless" + LinuxCompositor::Headless } } @@ -182,28 +183,6 @@ pub(crate) trait Platform: 'static { None } - #[cfg(feature = "screen-capture")] - fn is_screen_capture_supported(&self) -> bool; - #[cfg(not(feature = "screen-capture"))] - fn is_screen_capture_supported(&self) -> bool { - false - } - #[cfg(feature = "screen-capture")] - fn screen_capture_sources(&self) - -> oneshot::Receiver>>>; - #[cfg(not(feature = "screen-capture"))] - fn screen_capture_sources( - &self, - ) -> oneshot::Receiver>>> { - let (sources_tx, sources_rx) = oneshot::channel(); - sources_tx - .send(Err(anyhow::anyhow!( - "gpui was compiled without the screen-capture feature" - ))) - .ok(); - sources_rx - } - fn open_window( &self, handle: AnyWindowHandle, @@ -301,42 +280,6 @@ pub trait PlatformDisplay: Send + Sync + Debug { } } -/// Metadata for a given [ScreenCaptureSource] -#[derive(Clone)] -pub struct SourceMetadata { - /// Opaque identifier of this screen. - pub id: u64, - /// Human-readable label for this source. - pub label: Option, - /// Whether this source is the main display. - pub is_main: Option, - /// Video resolution of this source. - pub resolution: Size, -} - -/// A source of on-screen video content that can be captured. -pub trait ScreenCaptureSource { - /// Returns metadata for this source. - fn metadata(&self) -> Result; - - /// Start capture video from this source, invoking the given callback - /// with each frame. - fn stream( - &self, - foreground_executor: &ForegroundExecutor, - frame_callback: Box, - ) -> oneshot::Receiver>>; -} - -/// A video stream captured from a screen. -pub trait ScreenCaptureStream { - /// Returns metadata for this source. - fn metadata(&self) -> Result; -} - -/// A frame of video captured from a screen. -pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame); - /// An opaque identifier for a hardware display #[derive(PartialEq, Eq, Hash, Copy, Clone)] pub struct DisplayId(pub(crate) u32); diff --git a/crates/gpui/src/platform/linux.rs b/crates/gpui/src/platform/linux.rs index f7d7ed0ebaa416..a7a5603735741c 100644 --- a/crates/gpui/src/platform/linux.rs +++ b/crates/gpui/src/platform/linux.rs @@ -23,10 +23,5 @@ pub(crate) use wayland::*; #[cfg(feature = "x11")] pub(crate) use x11::*; -#[cfg(all(feature = "screen-capture", any(feature = "wayland", feature = "x11")))] -pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame; -#[cfg(not(all(feature = "screen-capture", any(feature = "wayland", feature = "x11"))))] -pub(crate) type PlatformScreenCaptureFrame = (); - #[cfg(feature = "wayland")] pub use wayland::layer_shell; diff --git a/crates/gpui/src/platform/linux/headless/client.rs b/crates/gpui/src/platform/linux/headless/client.rs index 33f1bb17e3230d..4a39f6b1bf75b9 100644 --- a/crates/gpui/src/platform/linux/headless/client.rs +++ b/crates/gpui/src/platform/linux/headless/client.rs @@ -68,24 +68,6 @@ impl LinuxClient for HeadlessClient { None } - #[cfg(feature = "screen-capture")] - fn is_screen_capture_supported(&self) -> bool { - false - } - - #[cfg(feature = "screen-capture")] - fn screen_capture_sources( - &self, - ) -> futures::channel::oneshot::Receiver>>> - { - let (mut tx, rx) = futures::channel::oneshot::channel(); - tx.send(Err(anyhow::anyhow!( - "Headless mode does not support screen capture." - ))) - .ok(); - rx - } - fn active_window(&self) -> Option { None } diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 21468370c49905..9801987b40b3ba 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -51,13 +51,6 @@ pub trait LinuxClient { #[allow(unused)] fn display(&self, id: DisplayId) -> Option>; fn primary_display(&self) -> Option>; - #[cfg(feature = "screen-capture")] - fn is_screen_capture_supported(&self) -> bool; - #[cfg(feature = "screen-capture")] - fn screen_capture_sources( - &self, - ) -> oneshot::Receiver>>>; - fn open_window( &self, handle: AnyWindowHandle, @@ -252,18 +245,6 @@ impl Platform for P { self.displays() } - #[cfg(feature = "screen-capture")] - fn is_screen_capture_supported(&self) -> bool { - self.is_screen_capture_supported() - } - - #[cfg(feature = "screen-capture")] - fn screen_capture_sources( - &self, - ) -> oneshot::Receiver>>> { - self.screen_capture_sources() - } - fn active_window(&self) -> Option { self.active_window() } diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 2879925495e41f..fdda4b4fc67b17 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -698,29 +698,6 @@ impl LinuxClient for WaylandClient { None } - #[cfg(feature = "screen-capture")] - fn is_screen_capture_supported(&self) -> bool { - false - } - - #[cfg(feature = "screen-capture")] - fn screen_capture_sources( - &self, - ) -> futures::channel::oneshot::Receiver>>> - { - // TODO: Get screen capture working on wayland. Be sure to try window resizing as that may - // be tricky. - // - // start_scap_default_target_source() - let (sources_tx, sources_rx) = futures::channel::oneshot::channel(); - sources_tx - .send(Err(anyhow::anyhow!( - "Wayland screen capture not yet implemented." - ))) - .ok(); - sources_rx - } - fn open_window( &self, handle: AnyWindowHandle, diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 32f50cdf5d9d94..e717b8f8d2276f 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -1454,21 +1454,6 @@ impl LinuxClient for X11Client { )) } - #[cfg(feature = "screen-capture")] - fn is_screen_capture_supported(&self) -> bool { - true - } - - #[cfg(feature = "screen-capture")] - fn screen_capture_sources( - &self, - ) -> futures::channel::oneshot::Receiver>>> - { - crate::platform::scap_screen_capture::scap_screen_sources( - &self.0.borrow().common.foreground_executor, - ) - } - fn open_window( &self, handle: AnyWindowHandle, diff --git a/crates/gpui/src/platform/mac.rs b/crates/gpui/src/platform/mac.rs index 76d636b457517d..b278eaa2f0eac6 100644 --- a/crates/gpui/src/platform/mac.rs +++ b/crates/gpui/src/platform/mac.rs @@ -6,15 +6,11 @@ mod display_link; mod events; mod keyboard; -#[cfg(feature = "screen-capture")] -mod screen_capture; - #[cfg(not(feature = "macos-blade"))] mod metal_atlas; #[cfg(not(feature = "macos-blade"))] pub mod metal_renderer; -use core_video::image_buffer::CVImageBuffer; #[cfg(not(feature = "macos-blade"))] use metal_renderer as renderer; @@ -55,9 +51,6 @@ pub(crate) use window::*; #[cfg(feature = "font-kit")] pub(crate) use text_system::*; -/// A frame of video captured from a screen. -pub(crate) type PlatformScreenCaptureFrame = CVImageBuffer; - trait BoolExt { fn to_objc(self) -> BOOL; } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index c2363afe270f97..bf7510758ccd3f 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -606,19 +606,6 @@ impl Platform for MacPlatform { .collect() } - #[cfg(feature = "screen-capture")] - fn is_screen_capture_supported(&self) -> bool { - let min_version = cocoa::foundation::NSOperatingSystemVersion::new(12, 3, 0); - super::is_macos_version_at_least(min_version) - } - - #[cfg(feature = "screen-capture")] - fn screen_capture_sources( - &self, - ) -> oneshot::Receiver>>> { - super::screen_capture::get_sources() - } - fn active_window(&self) -> Option { MacWindow::active_window() } diff --git a/crates/gpui/src/platform/mac/screen_capture.rs b/crates/gpui/src/platform/mac/screen_capture.rs deleted file mode 100644 index 4d4ffa6896520e..00000000000000 --- a/crates/gpui/src/platform/mac/screen_capture.rs +++ /dev/null @@ -1,334 +0,0 @@ -use crate::{ - DevicePixels, ForegroundExecutor, SharedString, SourceMetadata, - platform::{ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream}, - size, -}; -use anyhow::{Result, anyhow}; -use block::ConcreteBlock; -use cocoa::{ - base::{YES, id, nil}, - foundation::{NSArray, NSString}, -}; -use collections::HashMap; -use core_foundation::base::TCFType; -use core_graphics::display::{ - CGDirectDisplayID, CGDisplayCopyDisplayMode, CGDisplayModeGetPixelHeight, - CGDisplayModeGetPixelWidth, CGDisplayModeRelease, -}; -use ctor::ctor; -use futures::channel::oneshot; -use media::core_media::{CMSampleBuffer, CMSampleBufferRef}; -use metal::NSInteger; -use objc::{ - class, - declare::ClassDecl, - msg_send, - runtime::{Class, Object, Sel}, - sel, sel_impl, -}; -use std::{cell::RefCell, ffi::c_void, mem, ptr, rc::Rc}; - -use super::NSStringExt; - -#[derive(Clone)] -pub struct MacScreenCaptureSource { - sc_display: id, - meta: Option, -} - -pub struct MacScreenCaptureStream { - sc_stream: id, - sc_stream_output: id, - meta: SourceMetadata, -} - -static mut DELEGATE_CLASS: *const Class = ptr::null(); -static mut OUTPUT_CLASS: *const Class = ptr::null(); -const FRAME_CALLBACK_IVAR: &str = "frame_callback"; - -#[allow(non_upper_case_globals)] -const SCStreamOutputTypeScreen: NSInteger = 0; - -impl ScreenCaptureSource for MacScreenCaptureSource { - fn metadata(&self) -> Result { - let (display_id, size) = unsafe { - let display_id: CGDirectDisplayID = msg_send![self.sc_display, displayID]; - let display_mode_ref = CGDisplayCopyDisplayMode(display_id); - let width = CGDisplayModeGetPixelWidth(display_mode_ref); - let height = CGDisplayModeGetPixelHeight(display_mode_ref); - CGDisplayModeRelease(display_mode_ref); - - ( - display_id, - size(DevicePixels(width as i32), DevicePixels(height as i32)), - ) - }; - let (label, is_main) = self - .meta - .clone() - .map(|meta| (meta.label, meta.is_main)) - .unzip(); - - Ok(SourceMetadata { - id: display_id as u64, - label, - is_main, - resolution: size, - }) - } - - fn stream( - &self, - _foreground_executor: &ForegroundExecutor, - frame_callback: Box, - ) -> oneshot::Receiver>> { - unsafe { - let stream: id = msg_send![class!(SCStream), alloc]; - let filter: id = msg_send![class!(SCContentFilter), alloc]; - let configuration: id = msg_send![class!(SCStreamConfiguration), alloc]; - let delegate: id = msg_send![DELEGATE_CLASS, alloc]; - let output: id = msg_send![OUTPUT_CLASS, alloc]; - - let excluded_windows = NSArray::array(nil); - let filter: id = msg_send![filter, initWithDisplay:self.sc_display excludingWindows:excluded_windows]; - let configuration: id = msg_send![configuration, init]; - let _: id = msg_send![configuration, setScalesToFit: true]; - let _: id = msg_send![configuration, setPixelFormat: 0x42475241]; - // let _: id = msg_send![configuration, setShowsCursor: false]; - // let _: id = msg_send![configuration, setCaptureResolution: 3]; - let delegate: id = msg_send![delegate, init]; - let output: id = msg_send![output, init]; - - output.as_mut().unwrap().set_ivar( - FRAME_CALLBACK_IVAR, - Box::into_raw(Box::new(frame_callback)) as *mut c_void, - ); - - let meta = self.metadata().unwrap(); - let _: id = msg_send![configuration, setWidth: meta.resolution.width.0 as i64]; - let _: id = msg_send![configuration, setHeight: meta.resolution.height.0 as i64]; - let stream: id = msg_send![stream, initWithFilter:filter configuration:configuration delegate:delegate]; - - let (mut tx, rx) = oneshot::channel(); - - let mut error: id = nil; - let _: () = msg_send![stream, addStreamOutput:output type:SCStreamOutputTypeScreen sampleHandlerQueue:0 error:&mut error as *mut id]; - if error != nil { - let message: id = msg_send![error, localizedDescription]; - tx.send(Err(anyhow!("failed to add stream output {message:?}"))) - .ok(); - return rx; - } - - let tx = Rc::new(RefCell::new(Some(tx))); - let handler = ConcreteBlock::new({ - move |error: id| { - let result = if error == nil { - let stream = MacScreenCaptureStream { - meta: meta.clone(), - sc_stream: stream, - sc_stream_output: output, - }; - Ok(Box::new(stream) as Box) - } else { - let message: id = msg_send![error, localizedDescription]; - Err(anyhow!("failed to stop screen capture stream {message:?}")) - }; - if let Some(tx) = tx.borrow_mut().take() { - tx.send(result).ok(); - } - } - }); - let handler = handler.copy(); - let _: () = msg_send![stream, startCaptureWithCompletionHandler:handler]; - rx - } - } -} - -impl Drop for MacScreenCaptureSource { - fn drop(&mut self) { - unsafe { - let _: () = msg_send![self.sc_display, release]; - } - } -} - -impl ScreenCaptureStream for MacScreenCaptureStream { - fn metadata(&self) -> Result { - Ok(self.meta.clone()) - } -} - -impl Drop for MacScreenCaptureStream { - fn drop(&mut self) { - unsafe { - let mut error: id = nil; - let _: () = msg_send![self.sc_stream, removeStreamOutput:self.sc_stream_output type:SCStreamOutputTypeScreen error:&mut error as *mut _]; - if error != nil { - let message: id = msg_send![error, localizedDescription]; - log::error!("failed to add stream output {message:?}"); - } - - let handler = ConcreteBlock::new(move |error: id| { - if error != nil { - let message: id = msg_send![error, localizedDescription]; - log::error!("failed to stop screen capture stream {message:?}"); - } - }); - let block = handler.copy(); - let _: () = msg_send![self.sc_stream, stopCaptureWithCompletionHandler:block]; - let _: () = msg_send![self.sc_stream, release]; - let _: () = msg_send![self.sc_stream_output, release]; - } - } -} - -#[derive(Clone)] -struct ScreenMeta { - label: SharedString, - // Is this the screen with menu bar? - is_main: bool, -} - -unsafe fn screen_id_to_human_label() -> HashMap { - let screens: id = msg_send![class!(NSScreen), screens]; - let count: usize = msg_send![screens, count]; - let mut map = HashMap::default(); - let screen_number_key = unsafe { NSString::alloc(nil).init_str("NSScreenNumber") }; - for i in 0..count { - let screen: id = msg_send![screens, objectAtIndex: i]; - let device_desc: id = msg_send![screen, deviceDescription]; - if device_desc == nil { - continue; - } - - let nsnumber: id = msg_send![device_desc, objectForKey: screen_number_key]; - if nsnumber == nil { - continue; - } - - let screen_id: u32 = msg_send![nsnumber, unsignedIntValue]; - - let name: id = msg_send![screen, localizedName]; - if name != nil { - let cstr: *const std::os::raw::c_char = msg_send![name, UTF8String]; - let rust_str = unsafe { - std::ffi::CStr::from_ptr(cstr) - .to_string_lossy() - .into_owned() - }; - map.insert( - screen_id, - ScreenMeta { - label: rust_str.into(), - is_main: i == 0, - }, - ); - } - } - map -} - -pub(crate) fn get_sources() -> oneshot::Receiver>>> { - unsafe { - let (mut tx, rx) = oneshot::channel(); - let tx = Rc::new(RefCell::new(Some(tx))); - let screen_id_to_label = screen_id_to_human_label(); - let block = ConcreteBlock::new(move |shareable_content: id, error: id| { - let Some(mut tx) = tx.borrow_mut().take() else { - return; - }; - - let result = if error == nil { - let displays: id = msg_send![shareable_content, displays]; - let mut result = Vec::new(); - for i in 0..displays.count() { - let display = displays.objectAtIndex(i); - let id: CGDirectDisplayID = msg_send![display, displayID]; - let meta = screen_id_to_label.get(&id).cloned(); - let source = MacScreenCaptureSource { - sc_display: msg_send![display, retain], - meta, - }; - result.push(Rc::new(source) as Rc); - } - Ok(result) - } else { - let msg: id = msg_send![error, localizedDescription]; - Err(anyhow!( - "Screen share failed: {:?}", - NSStringExt::to_str(&msg) - )) - }; - tx.send(result).ok(); - }); - let block = block.copy(); - - let _: () = msg_send![ - class!(SCShareableContent), - getShareableContentExcludingDesktopWindows:YES - onScreenWindowsOnly:YES - completionHandler:block]; - rx - } -} - -#[ctor] -unsafe fn build_classes() { - let mut decl = ClassDecl::new("GPUIStreamDelegate", class!(NSObject)).unwrap(); - unsafe { - decl.add_method( - sel!(outputVideoEffectDidStartForStream:), - output_video_effect_did_start_for_stream as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(outputVideoEffectDidStopForStream:), - output_video_effect_did_stop_for_stream as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(stream:didStopWithError:), - stream_did_stop_with_error as extern "C" fn(&Object, Sel, id, id), - ); - DELEGATE_CLASS = decl.register(); - - let mut decl = ClassDecl::new("GPUIStreamOutput", class!(NSObject)).unwrap(); - decl.add_method( - sel!(stream:didOutputSampleBuffer:ofType:), - stream_did_output_sample_buffer_of_type - as extern "C" fn(&Object, Sel, id, id, NSInteger), - ); - decl.add_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR); - - OUTPUT_CLASS = decl.register(); - } -} - -extern "C" fn output_video_effect_did_start_for_stream(_this: &Object, _: Sel, _stream: id) {} - -extern "C" fn output_video_effect_did_stop_for_stream(_this: &Object, _: Sel, _stream: id) {} - -extern "C" fn stream_did_stop_with_error(_this: &Object, _: Sel, _stream: id, _error: id) {} - -extern "C" fn stream_did_output_sample_buffer_of_type( - this: &Object, - _: Sel, - _stream: id, - sample_buffer: id, - buffer_type: NSInteger, -) { - if buffer_type != SCStreamOutputTypeScreen { - return; - } - - unsafe { - let sample_buffer = sample_buffer as CMSampleBufferRef; - let sample_buffer = CMSampleBuffer::wrap_under_get_rule(sample_buffer); - if let Some(buffer) = sample_buffer.image_buffer() { - let callback: Box> = - Box::from_raw(*this.get_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR) as *mut _); - callback(ScreenCaptureFrame(buffer)); - mem::forget(callback); - } - } -} diff --git a/crates/gpui/src/platform/scap_screen_capture.rs b/crates/gpui/src/platform/scap_screen_capture.rs deleted file mode 100644 index d6d19cd8102d58..00000000000000 --- a/crates/gpui/src/platform/scap_screen_capture.rs +++ /dev/null @@ -1,325 +0,0 @@ -//! Screen capture for Linux and Windows -use crate::{ - DevicePixels, ForegroundExecutor, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, - Size, SourceMetadata, size, -}; -use anyhow::{Context as _, Result, anyhow}; -use futures::channel::oneshot; -use scap::Target; -use std::rc::Rc; -use std::sync::Arc; -use std::sync::atomic::{self, AtomicBool}; - -/// Populates the receiver with the screens that can be captured. -/// -/// `scap_default_target_source` should be used instead on Wayland, since `scap_screen_sources` -/// won't return any results. -#[allow(dead_code)] -pub(crate) fn scap_screen_sources( - foreground_executor: &ForegroundExecutor, -) -> oneshot::Receiver>>> { - let (sources_tx, sources_rx) = oneshot::channel(); - get_screen_targets(sources_tx); - to_dyn_screen_capture_sources(sources_rx, foreground_executor) -} - -/// Starts screen capture for the default target, and populates the receiver with a single source -/// for it. The first frame of the screen capture is used to determine the size of the stream. -/// -/// On Wayland (Linux), prompts the user to select a target, and populates the receiver with a -/// single screen capture source for their selection. -#[allow(dead_code)] -pub(crate) fn start_scap_default_target_source( - foreground_executor: &ForegroundExecutor, -) -> oneshot::Receiver>>> { - let (sources_tx, sources_rx) = oneshot::channel(); - start_default_target_screen_capture(sources_tx); - to_dyn_screen_capture_sources(sources_rx, foreground_executor) -} - -struct ScapCaptureSource { - target: scap::Display, - size: Size, -} - -/// Populates the sender with the screens available for capture. -fn get_screen_targets(sources_tx: oneshot::Sender>>) { - // Due to use of blocking APIs, a new thread is used. - std::thread::spawn(|| { - let targets = match scap::get_all_targets() { - Ok(targets) => targets, - Err(err) => { - sources_tx.send(Err(err)).ok(); - return; - } - }; - let sources = targets - .into_iter() - .filter_map(|target| match target { - scap::Target::Display(display) => { - let size = Size { - width: DevicePixels(display.width as i32), - height: DevicePixels(display.height as i32), - }; - Some(ScapCaptureSource { - target: display, - size, - }) - } - scap::Target::Window(_) => None, - }) - .collect::>(); - sources_tx.send(Ok(sources)).ok(); - }); -} - -impl ScreenCaptureSource for ScapCaptureSource { - fn metadata(&self) -> Result { - Ok(SourceMetadata { - resolution: self.size, - label: Some(self.target.title.clone().into()), - is_main: None, - id: self.target.id as u64, - }) - } - - fn stream( - &self, - foreground_executor: &ForegroundExecutor, - frame_callback: Box, - ) -> oneshot::Receiver>> { - let (stream_tx, stream_rx) = oneshot::channel(); - let target = self.target.clone(); - - // Due to use of blocking APIs, a dedicated thread is used. - std::thread::spawn(move || { - match new_scap_capturer(Some(scap::Target::Display(target.clone()))) { - Ok(mut capturer) => { - capturer.start_capture(); - run_capture(capturer, target.clone(), frame_callback, stream_tx); - } - Err(e) => { - stream_tx.send(Err(e)).ok(); - } - } - }); - - to_dyn_screen_capture_stream(stream_rx, foreground_executor) - } -} - -struct ScapDefaultTargetCaptureSource { - // Sender populated by single call to `ScreenCaptureSource::stream`. - stream_call_tx: std::sync::mpsc::SyncSender<( - // Provides the result of `ScreenCaptureSource::stream`. - oneshot::Sender>, - // Callback for frames. - Box, - )>, - target: scap::Display, - size: Size, -} - -/// Starts screen capture on the default capture target, and populates the sender with the source. -fn start_default_target_screen_capture( - sources_tx: oneshot::Sender>>, -) { - // Due to use of blocking APIs, a dedicated thread is used. - std::thread::spawn(|| { - let start_result = util::maybe!({ - let mut capturer = new_scap_capturer(None)?; - capturer.start_capture(); - let first_frame = capturer - .get_next_frame() - .context("Failed to get first frame of screenshare to get the size.")?; - let size = frame_size(&first_frame); - let target = capturer - .target() - .context("Unable to determine the target display.")?; - let target = target.clone(); - Ok((capturer, size, target)) - }); - - match start_result { - Ok((capturer, size, Target::Display(display))) => { - let (stream_call_tx, stream_rx) = std::sync::mpsc::sync_channel(1); - sources_tx - .send(Ok(vec![ScapDefaultTargetCaptureSource { - stream_call_tx, - size, - target: display.clone(), - }])) - .ok(); - let Ok((stream_tx, frame_callback)) = stream_rx.recv() else { - return; - }; - run_capture(capturer, display, frame_callback, stream_tx); - } - Err(e) => { - sources_tx.send(Err(e)).ok(); - } - _ => { - sources_tx - .send(Err(anyhow!("The screen capture source is not a display"))) - .ok(); - } - } - }); -} - -impl ScreenCaptureSource for ScapDefaultTargetCaptureSource { - fn metadata(&self) -> Result { - Ok(SourceMetadata { - resolution: self.size, - label: None, - is_main: None, - id: self.target.id as u64, - }) - } - - fn stream( - &self, - foreground_executor: &ForegroundExecutor, - frame_callback: Box, - ) -> oneshot::Receiver>> { - let (tx, rx) = oneshot::channel(); - match self.stream_call_tx.try_send((tx, frame_callback)) { - Ok(()) => {} - Err(std::sync::mpsc::TrySendError::Full((tx, _))) - | Err(std::sync::mpsc::TrySendError::Disconnected((tx, _))) => { - // Note: support could be added for being called again after end of prior stream. - tx.send(Err(anyhow!( - "Can't call ScapDefaultTargetCaptureSource::stream multiple times." - ))) - .ok(); - } - } - to_dyn_screen_capture_stream(rx, foreground_executor) - } -} - -fn new_scap_capturer(target: Option) -> Result { - scap::capturer::Capturer::build(scap::capturer::Options { - fps: 60, - show_cursor: true, - show_highlight: true, - // Note that the actual frame output type may differ. - output_type: scap::frame::FrameType::YUVFrame, - output_resolution: scap::capturer::Resolution::Captured, - crop_area: None, - target, - excluded_targets: None, - }) -} - -fn run_capture( - mut capturer: scap::capturer::Capturer, - display: scap::Display, - frame_callback: Box, - stream_tx: oneshot::Sender>, -) { - let cancel_stream = Arc::new(AtomicBool::new(false)); - let size = Size { - width: DevicePixels(display.width as i32), - height: DevicePixels(display.height as i32), - }; - let stream_send_result = stream_tx.send(Ok(ScapStream { - cancel_stream: cancel_stream.clone(), - display, - size, - })); - if stream_send_result.is_err() { - return; - } - while !cancel_stream.load(std::sync::atomic::Ordering::SeqCst) { - match capturer.get_next_frame() { - Ok(frame) => frame_callback(ScreenCaptureFrame(frame)), - Err(err) => { - log::error!("Halting screen capture due to error: {err}"); - break; - } - } - } - capturer.stop_capture(); -} - -struct ScapStream { - cancel_stream: Arc, - display: scap::Display, - size: Size, -} - -impl ScreenCaptureStream for ScapStream { - fn metadata(&self) -> Result { - Ok(SourceMetadata { - resolution: self.size, - label: Some(self.display.title.clone().into()), - is_main: None, - id: self.display.id as u64, - }) - } -} - -impl Drop for ScapStream { - fn drop(&mut self) { - self.cancel_stream.store(true, atomic::Ordering::SeqCst); - } -} - -fn frame_size(frame: &scap::frame::Frame) -> Size { - let (width, height) = match frame { - scap::frame::Frame::YUVFrame(frame) => (frame.width, frame.height), - scap::frame::Frame::RGB(frame) => (frame.width, frame.height), - scap::frame::Frame::RGBx(frame) => (frame.width, frame.height), - scap::frame::Frame::XBGR(frame) => (frame.width, frame.height), - scap::frame::Frame::BGRx(frame) => (frame.width, frame.height), - scap::frame::Frame::BGR0(frame) => (frame.width, frame.height), - scap::frame::Frame::BGRA(frame) => (frame.width, frame.height), - }; - size(DevicePixels(width), DevicePixels(height)) -} - -/// This is used by `get_screen_targets` and `start_default_target_screen_capture` to turn their -/// results into `Rc`. They need to `Send` their capture source, and so -/// the capture source structs are used as `Rc` is not `Send`. -fn to_dyn_screen_capture_sources( - sources_rx: oneshot::Receiver>>, - foreground_executor: &ForegroundExecutor, -) -> oneshot::Receiver>>> { - let (dyn_sources_tx, dyn_sources_rx) = oneshot::channel(); - foreground_executor - .spawn(async move { - match sources_rx.await { - Ok(Ok(results)) => dyn_sources_tx - .send(Ok(results - .into_iter() - .map(|source| Rc::new(source) as Rc) - .collect::>())) - .ok(), - Ok(Err(err)) => dyn_sources_tx.send(Err(err)).ok(), - Err(oneshot::Canceled) => None, - } - }) - .detach(); - dyn_sources_rx -} - -/// Same motivation as `to_dyn_screen_capture_sources` above. -fn to_dyn_screen_capture_stream( - sources_rx: oneshot::Receiver>, - foreground_executor: &ForegroundExecutor, -) -> oneshot::Receiver>> { - let (dyn_sources_tx, dyn_sources_rx) = oneshot::channel(); - foreground_executor - .spawn(async move { - match sources_rx.await { - Ok(Ok(stream)) => dyn_sources_tx - .send(Ok(Box::new(stream) as Box)) - .ok(), - Ok(Err(err)) => dyn_sources_tx.send(Err(err)).ok(), - Err(oneshot::Canceled) => None, - } - }) - .detach(); - dyn_sources_rx -} diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 9227df5b63314b..a5a81f0a93ba05 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -7,5 +7,3 @@ pub use dispatcher::*; pub(crate) use display::*; pub(crate) use platform::*; pub(crate) use window::*; - -pub use platform::{TestScreenCaptureSource, TestScreenCaptureStream}; diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index dfada364667989..77c8c9f44e154c 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -1,9 +1,8 @@ use crate::{ - AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels, - DummyKeyboardMapper, ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, - PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PromptButton, - ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, SourceMetadata, Task, - TestDisplay, TestWindow, WindowAppearance, WindowParams, size, + AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DummyKeyboardMapper, + ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, PlatformKeyboardLayout, + PlatformKeyboardMapper, PlatformTextSystem, PromptButton, Task, TestDisplay, TestWindow, + WindowAppearance, WindowParams, }; use anyhow::Result; use collections::VecDeque; @@ -15,11 +14,6 @@ use std::{ rc::{Rc, Weak}, sync::Arc, }; -#[cfg(target_os = "windows")] -use windows::Win32::{ - Graphics::Imaging::{CLSID_WICImagingFactory, IWICImagingFactory}, - System::Com::{CLSCTX_INPROC_SERVER, CoCreateInstance}, -}; /// TestPlatform implements the Platform trait for use in tests. pub(crate) struct TestPlatform { @@ -33,51 +27,12 @@ pub(crate) struct TestPlatform { #[cfg(any(target_os = "linux", target_os = "freebsd"))] current_primary_item: Mutex>, pub(crate) prompts: RefCell, - screen_capture_sources: RefCell>, pub opened_url: RefCell>, pub text_system: Arc, pub expect_restart: RefCell>>>, - #[cfg(target_os = "windows")] - bitmap_factory: std::mem::ManuallyDrop, weak: Weak, } -#[derive(Clone)] -/// A fake screen capture source, used for testing. -pub struct TestScreenCaptureSource {} - -/// A fake screen capture stream, used for testing. -pub struct TestScreenCaptureStream {} - -impl ScreenCaptureSource for TestScreenCaptureSource { - fn metadata(&self) -> Result { - Ok(SourceMetadata { - id: 0, - is_main: None, - label: None, - resolution: size(DevicePixels(1), DevicePixels(1)), - }) - } - - fn stream( - &self, - _foreground_executor: &ForegroundExecutor, - _frame_callback: Box, - ) -> oneshot::Receiver>> { - let (mut tx, rx) = oneshot::channel(); - let stream = TestScreenCaptureStream {}; - tx.send(Ok(Box::new(stream) as Box)) - .ok(); - rx - } -} - -impl ScreenCaptureStream for TestScreenCaptureStream { - fn metadata(&self) -> Result { - TestScreenCaptureSource {}.metadata() - } -} - struct TestPrompt { msg: String, detail: Option, @@ -93,23 +48,12 @@ pub(crate) struct TestPrompts { impl TestPlatform { pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Rc { - #[cfg(target_os = "windows")] - let bitmap_factory = unsafe { - windows::Win32::System::Ole::OleInitialize(None) - .expect("unable to initialize Windows OLE"); - std::mem::ManuallyDrop::new( - CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_INPROC_SERVER) - .expect("Error creating bitmap factory."), - ) - }; - let text_system = Arc::new(NoopTextSystem); Rc::new_cyclic(|weak| TestPlatform { background_executor: executor, foreground_executor, prompts: Default::default(), - screen_capture_sources: Default::default(), active_cursor: Default::default(), active_display: Rc::new(TestDisplay::new()), active_window: Default::default(), @@ -119,8 +63,6 @@ impl TestPlatform { current_primary_item: Mutex::new(None), weak: weak.clone(), opened_url: Default::default(), - #[cfg(target_os = "windows")] - bitmap_factory, text_system, }) } @@ -170,10 +112,6 @@ impl TestPlatform { )) } - pub(crate) fn set_screen_capture_sources(&self, sources: Vec) { - *self.screen_capture_sources.borrow_mut() = sources; - } - pub(crate) fn prompt( &self, msg: &str, @@ -282,26 +220,6 @@ impl Platform for TestPlatform { Some(self.active_display.clone()) } - #[cfg(feature = "screen-capture")] - fn is_screen_capture_supported(&self) -> bool { - true - } - - #[cfg(feature = "screen-capture")] - fn screen_capture_sources( - &self, - ) -> oneshot::Receiver>>> { - let (mut tx, rx) = oneshot::channel(); - tx.send(Ok(self - .screen_capture_sources - .borrow() - .iter() - .map(|source| Rc::new(source.clone()) as Rc) - .collect())) - .ok(); - rx - } - fn active_window(&self) -> Option { self.active_window .borrow() @@ -437,23 +355,6 @@ impl Platform for TestPlatform { } } -impl TestScreenCaptureSource { - /// Create a fake screen capture source, for testing. - pub fn new() -> Self { - Self {} - } -} - -#[cfg(target_os = "windows")] -impl Drop for TestPlatform { - fn drop(&mut self) { - unsafe { - std::mem::ManuallyDrop::drop(&mut self.bitmap_factory); - windows::Win32::System::Ole::OleUninitialize(); - } - } -} - struct TestKeyboardLayout; impl PlatformKeyboardLayout for TestKeyboardLayout { diff --git a/crates/gpui/src/platform/windows.rs b/crates/gpui/src/platform/windows.rs index 9cd1a7d05f4bcc..6b325b5afa54d2 100644 --- a/crates/gpui/src/platform/windows.rs +++ b/crates/gpui/src/platform/windows.rs @@ -33,8 +33,3 @@ pub(crate) use window::*; pub(crate) use wrapper::*; pub(crate) use windows::Win32::Foundation::HWND; - -#[cfg(feature = "screen-capture")] -pub(crate) type PlatformScreenCaptureFrame = scap::frame::Frame; -#[cfg(not(feature = "screen-capture"))] -pub(crate) type PlatformScreenCaptureFrame = (); diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index af0cb89ecc94da..eb89dff70c5ead 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -421,18 +421,6 @@ impl Platform for WindowsPlatform { WindowsDisplay::primary_monitor().map(|display| Rc::new(display) as Rc) } - #[cfg(feature = "screen-capture")] - fn is_screen_capture_supported(&self) -> bool { - true - } - - #[cfg(feature = "screen-capture")] - fn screen_capture_sources( - &self, - ) -> oneshot::Receiver>>> { - crate::platform::scap_screen_capture::scap_screen_sources(&self.foreground_executor) - } - fn active_window(&self) -> Option { let active_window_hwnd = unsafe { GetActiveWindow() }; self.window_from_hwnd(active_window_hwnd) diff --git a/crates/livekit_client/Cargo.toml b/crates/livekit_client/Cargo.toml index a7766b5ba5b857..8861b95b07c813 100644 --- a/crates/livekit_client/Cargo.toml +++ b/crates/livekit_client/Cargo.toml @@ -26,10 +26,12 @@ audio.workspace = true collections.workspace = true cpal.workspace = true futures.workspace = true -gpui = { workspace = true, features = ["screen-capture", "x11", "wayland", "windows-manifest"] } +gpui = { workspace = true, features = ["x11", "wayland", "windows-manifest"] } gpui_tokio.workspace = true http_client_tls.workspace = true image.workspace = true +libwebrtc.workspace = true +livekit.workspace = true livekit_api.workspace = true log.workspace = true nanoid.workspace = true @@ -40,19 +42,11 @@ serde.workspace = true serde_urlencoded.workspace = true settings.workspace = true smallvec.workspace = true +tokio.workspace = true tokio-tungstenite.workspace = true ui.workspace = true util.workspace = true -[target.'cfg(not(any(all(target_os = "windows", target_env = "gnu"), target_os = "freebsd")))'.dependencies] -libwebrtc = { rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d", git = "https://github.com/zed-industries/livekit-rust-sdks" } -livekit = { rev = "5f04705ac3f356350ae31534ffbc476abc9ea83d", git = "https://github.com/zed-industries/livekit-rust-sdks", features = [ - "__rustls-tls" -] } - -[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))'.dependencies] -scap.workspace = true - [target.'cfg(target_os = "macos")'.dependencies] core-foundation.workspace = true core-video.workspace = true diff --git a/crates/livekit_client/examples/test_app.rs b/crates/livekit_client/examples/test_app.rs index a4d815aa9be6a8..d93f68cea641d0 100644 --- a/crates/livekit_client/examples/test_app.rs +++ b/crates/livekit_client/examples/test_app.rs @@ -3,15 +3,15 @@ use std::sync::Arc; use futures::StreamExt; use gpui::{ AppContext as _, AsyncApp, Bounds, Context, Entity, InteractiveElement, KeyBinding, Menu, - MenuItem, ParentElement, Pixels, Render, ScreenCaptureStream, SharedString, - StatefulInteractiveElement as _, Styled, Task, Window, WindowBounds, WindowHandle, - WindowOptions, actions, bounds, div, point, + MenuItem, ParentElement, Pixels, Render, SharedString, StatefulInteractiveElement as _, Styled, + Task, Window, WindowBounds, WindowHandle, WindowOptions, actions, bounds, div, point, prelude::{FluentBuilder as _, IntoElement}, px, rgb, size, }; use livekit_client::{ AudioStream, LocalTrackPublication, Participant, ParticipantIdentity, RemoteParticipant, RemoteTrackPublication, RemoteVideoTrack, RemoteVideoTrackView, Room, RoomEvent, + ScreenCaptureStreamHandle, screen_capture_sources, }; use livekit_api::token::{self, VideoGrant}; @@ -80,7 +80,7 @@ struct LivekitWindow { microphone_track: Option, screen_share_track: Option, microphone_stream: Option, - screen_share_stream: Option>, + screen_share_stream: Option, remote_participants: Vec<(ParticipantIdentity, ParticipantState)>, _events_task: Task<()>, } @@ -281,13 +281,12 @@ impl LivekitWindow { cx.notify(); } else { let participant = self.room.local_participant(); - let sources = cx.screen_capture_sources(); + let sources = screen_capture_sources(); cx.spawn_in(window, async move |this, cx| { - let sources = sources.await.unwrap()?; let source = sources.into_iter().next().unwrap(); let (publication, stream) = participant - .publish_screenshare_track(&*source, cx) + .publish_screenshare_track(Some(source), cx) .await .unwrap(); this.update(cx, |this, cx| { diff --git a/crates/livekit_client/src/lib.rs b/crates/livekit_client/src/lib.rs index 055aa3704e06f2..a2552b0bd96d2d 100644 --- a/crates/livekit_client/src/lib.rs +++ b/crates/livekit_client/src/lib.rs @@ -1,3 +1,8 @@ +use std::sync::{ + Arc, + atomic::{AtomicBool, Ordering}, +}; + use anyhow::Context as _; use collections::HashMap; @@ -180,6 +185,18 @@ pub enum RoomEvent { Reconnected, } +pub struct ScreenCaptureStreamHandle { + pub screen_id: u64, + stop_capture: Arc, + _spawn_handle: gpui::Task>, +} + +impl Drop for ScreenCaptureStreamHandle { + fn drop(&mut self) { + self.stop_capture.store(true, Ordering::Release); + } +} + pub(crate) fn default_device( input: bool, ) -> anyhow::Result<(cpal::Device, cpal::SupportedStreamConfig)> { diff --git a/crates/livekit_client/src/livekit_client.rs b/crates/livekit_client/src/livekit_client.rs index 5d31f802c81678..bfa080a259f938 100644 --- a/crates/livekit_client/src/livekit_client.rs +++ b/crates/livekit_client/src/livekit_client.rs @@ -1,13 +1,29 @@ -use std::sync::Arc; +use std::sync::{ + Arc, + atomic::{AtomicBool, Ordering}, +}; +use std::time::Duration; + +use crate::ScreenCaptureStreamHandle; use anyhow::{Context as _, Result, anyhow}; use audio::AudioSettings; use collections::HashMap; -use futures::{SinkExt, channel::mpsc}; -use gpui::{App, AsyncApp, ScreenCaptureSource, ScreenCaptureStream, Task}; +use futures::{SinkExt, channel::mpsc, stream::StreamExt}; +use gpui::{App, AsyncApp, Task}; use gpui_tokio::Tokio; +use livekit::options::{TrackPublishOptions, VideoCodec}; +use livekit::track::TrackSource; +use livekit::webrtc::desktop_capturer::{ + CaptureError, CaptureSource, DesktopCaptureSourceType, DesktopCapturer, DesktopCapturerOptions, + DesktopFrame, +}; +use livekit::webrtc::native::yuv_helper; +use livekit::webrtc::prelude::{ + I420Buffer, RtcVideoSource, VideoFrame, VideoResolution, VideoRotation, +}; +use livekit::webrtc::video_source::native::NativeVideoSource; use log::info; -use playback::capture_local_video_track; use settings::Settings; mod playback; @@ -29,6 +45,7 @@ pub struct RemoteTrackPublication(livekit::publication::RemoteTrackPublication); pub struct RemoteParticipant(livekit::participant::RemoteParticipant); #[derive(Clone, Debug)] +#[allow(unused)] pub struct LocalVideoTrack(livekit::track::LocalVideoTrack); #[derive(Clone, Debug)] pub struct LocalAudioTrack(livekit::track::LocalAudioTrack); @@ -37,6 +54,29 @@ pub struct LocalTrackPublication(livekit::publication::LocalTrackPublication); #[derive(Clone, Debug)] pub struct LocalParticipant(livekit::participant::LocalParticipant); +fn desktop_capturer_options() -> DesktopCapturerOptions { + // Picking either a screen or a window with one DesktopCapturer is only implemented + // in libwebrtc on Wayland and macOS. + #[allow(unused_variables)] + let source_type = DesktopCaptureSourceType::Screen; + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + let source_type = match gpui::guess_compositor() { + gpui::LinuxCompositor::Wayland => DesktopCaptureSourceType::Generic, + _ => DesktopCaptureSourceType::Screen, + }; + #[cfg(target_os = "macos")] + let source_type = DesktopCaptureSourceType::Generic; + + let mut options = DesktopCapturerOptions::new(source_type); + options.set_include_cursor(true); + options +} + +pub fn screen_capture_sources() -> Vec { + let capturer = DesktopCapturer::new(desktop_capturer_options()).unwrap(); + capturer.get_source_list() +} + pub struct Room { room: livekit::Room, _task: Task<()>, @@ -164,20 +204,137 @@ impl Room { impl LocalParticipant { pub async fn publish_screenshare_track( &self, - source: &dyn ScreenCaptureSource, + source: Option, cx: &mut AsyncApp, - ) -> Result<(LocalTrackPublication, Box)> { - let (track, stream) = capture_local_video_track(source, cx).await?; - let options = livekit::options::TrackPublishOptions { - source: livekit::track::TrackSource::Screenshare, - video_codec: livekit::options::VideoCodec::VP8, - ..Default::default() + ) -> Result<(LocalTrackPublication, ScreenCaptureStreamHandle)> { + let stop_capture = Arc::new(AtomicBool::new(false)); + let (mut video_source_sender, mut video_source_receiver) = mpsc::channel(0); + let callback = { + // These dimensions are arbitrary initial values. + // libwebrtc only exposes the resolution of the source in the DesktopFrame + // passed to the callback, so wait to publish the video track until + // the callback is called the first time. + let mut stream_width = 1920; + let mut stream_height = 1080; + + let mut video_frame = VideoFrame { + rotation: VideoRotation::VideoRotation0, + buffer: I420Buffer::new(stream_width, stream_height), + timestamp_us: 0, + }; + let mut video_source: Option = None; + let stop_capture = stop_capture.clone(); + move |result: Result| { + let frame = match result { + Ok(frame) => frame, + // This error is expected on Wayland while waiting for the user + // to pick a screen with the XDG Desktop Portal. + Err(CaptureError::Temporary) => { + log::debug!("Temporary error capturing screen"); + return; + } + Err(CaptureError::Permanent) => { + log::error!("Error capturing screen"); + stop_capture.store(true, Ordering::Release); + return; + } + }; + let height = frame.height().try_into().unwrap(); + let width = frame.width().try_into().unwrap(); + + if width != stream_width || height != stream_height { + stream_width = width; + stream_height = height; + video_frame.buffer = I420Buffer::new(width, height); + } + + let stride = frame.stride(); + let data = frame.data(); + + let (s_y, s_u, s_v) = video_frame.buffer.strides(); + let (y, u, v) = video_frame.buffer.data_mut(); + yuv_helper::argb_to_i420( + data, + stride, + y, + s_y, + u, + s_u, + v, + s_v, + frame.width(), + frame.height(), + ); + + if let Some(video_source) = &video_source { + video_source.capture_frame(&video_frame); + } else { + // This is the first time the callback has been called. + // Use the resolution from the DesktopFrame to create a video source + // and push it over a channel to be published from the async context. + let video_source_inner = NativeVideoSource::new(VideoResolution { + width: stream_width, + height: stream_height, + }); + + video_source_sender + .try_send(video_source_inner.clone()) + .unwrap(); + + video_source = Some(video_source_inner); + } + } }; + + // source should only be None in tests which have a different implementation + // of this function. + let source = source.unwrap(); + let screen_id = source.id(); + + let mut capturer = DesktopCapturer::new(desktop_capturer_options()) + .ok_or(anyhow!("Failed to create DesktopCapturer"))?; + capturer.start_capture(Some(source), callback); + log::debug!("Starting screen capture"); + + let spawn_handle = gpui_tokio::Tokio::spawn(cx, { + let stop_capture = stop_capture.clone(); + async move { + loop { + if stop_capture.load(Ordering::Acquire) { + log::debug!("Stopping screen capture"); + break; + } + capturer.capture_frame(); + tokio::time::sleep(Duration::from_secs_f32(1.0 / 60.0)).await; + } + } + })?; + + let video_source = video_source_receiver.next().await.ok_or(anyhow!( + "No NativeVideoSource received from DesktopCapturer" + ))?; + let track = livekit::track::LocalVideoTrack::create_video_track( + "screen_share", + RtcVideoSource::Native(video_source), + ); + let publication = self - .publish_track(livekit::track::LocalTrack::Video(track.0), options, cx) + .publish_track( + livekit::track::LocalTrack::Video(track), + TrackPublishOptions { + source: TrackSource::Screenshare, + video_codec: VideoCodec::VP8, + ..Default::default() + }, + cx, + ) .await?; - - Ok((publication, stream)) + let handle = ScreenCaptureStreamHandle { + screen_id, + stop_capture, + _spawn_handle: spawn_handle, + }; + Ok((publication, handle)) } async fn publish_track( diff --git a/crates/livekit_client/src/livekit_client/playback.rs b/crates/livekit_client/src/livekit_client/playback.rs index cdd766453c58ad..1ff29a04c11297 100644 --- a/crates/livekit_client/src/livekit_client/playback.rs +++ b/crates/livekit_client/src/livekit_client/playback.rs @@ -4,10 +4,7 @@ use audio::{AudioSettings, CHANNEL_COUNT, LEGACY_CHANNEL_COUNT, LEGACY_SAMPLE_RA use cpal::traits::{DeviceTrait, StreamTrait as _}; use futures::channel::mpsc::UnboundedSender; use futures::{Stream, StreamExt as _}; -use gpui::{ - AsyncApp, BackgroundExecutor, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, - Task, -}; +use gpui::{AsyncApp, BackgroundExecutor, Task}; use libwebrtc::native::{apm, audio_mixer, audio_resampler}; use livekit::track; @@ -15,8 +12,7 @@ use livekit::webrtc::{ audio_frame::AudioFrame, audio_source::{AudioSourceOptions, RtcAudioSource, native::NativeAudioSource}, audio_stream::native::NativeAudioStream, - video_frame::{VideoBuffer, VideoFrame, VideoRotation}, - video_source::{RtcVideoSource, VideoResolution, native::NativeVideoSource}, + video_frame::VideoBuffer, video_stream::native::NativeVideoStream, }; use log::info; @@ -456,50 +452,11 @@ fn send_to_livekit(frame_tx: UnboundedSender>, mut microphon } } -use super::LocalVideoTrack; - pub enum AudioStream { Input { _task: Task<()> }, Output { _drop: Box }, } -pub(crate) async fn capture_local_video_track( - capture_source: &dyn ScreenCaptureSource, - cx: &mut gpui::AsyncApp, -) -> Result<(crate::LocalVideoTrack, Box)> { - let metadata = capture_source.metadata()?; - let track_source = gpui_tokio::Tokio::spawn(cx, async move { - NativeVideoSource::new(VideoResolution { - width: metadata.resolution.width.0 as u32, - height: metadata.resolution.height.0 as u32, - }) - })? - .await?; - - let capture_stream = capture_source - .stream(cx.foreground_executor(), { - let track_source = track_source.clone(); - Box::new(move |frame| { - if let Some(buffer) = video_frame_buffer_to_webrtc(frame) { - track_source.capture_frame(&VideoFrame { - rotation: VideoRotation::VideoRotation0, - timestamp_us: 0, - buffer, - }); - } - }) - }) - .await??; - - Ok(( - LocalVideoTrack(track::LocalVideoTrack::create_video_track( - "screen share", - RtcVideoSource::Native(track_source), - )), - capture_stream, - )) -} - #[derive(Clone)] struct AudioMixerSource { ssrc: i32, @@ -727,75 +684,6 @@ fn video_frame_buffer_from_webrtc(buffer: Box) -> Option Option> { - use livekit::webrtc; - - let pixel_buffer = frame.0.as_concrete_TypeRef(); - std::mem::forget(frame.0); - unsafe { - Some(webrtc::video_frame::native::NativeBuffer::from_cv_pixel_buffer(pixel_buffer as _)) - } -} - -#[cfg(not(target_os = "macos"))] -fn video_frame_buffer_to_webrtc(frame: ScreenCaptureFrame) -> Option> { - use libwebrtc::native::yuv_helper::{abgr_to_nv12, argb_to_nv12}; - use livekit::webrtc::prelude::NV12Buffer; - match frame.0 { - scap::frame::Frame::BGRx(frame) => { - let mut buffer = NV12Buffer::new(frame.width as u32, frame.height as u32); - let (stride_y, stride_uv) = buffer.strides(); - let (data_y, data_uv) = buffer.data_mut(); - argb_to_nv12( - &frame.data, - frame.width as u32 * 4, - data_y, - stride_y, - data_uv, - stride_uv, - frame.width, - frame.height, - ); - Some(buffer) - } - scap::frame::Frame::RGBx(frame) => { - let mut buffer = NV12Buffer::new(frame.width as u32, frame.height as u32); - let (stride_y, stride_uv) = buffer.strides(); - let (data_y, data_uv) = buffer.data_mut(); - abgr_to_nv12( - &frame.data, - frame.width as u32 * 4, - data_y, - stride_y, - data_uv, - stride_uv, - frame.width, - frame.height, - ); - Some(buffer) - } - scap::frame::Frame::YUVFrame(yuvframe) => { - let mut buffer = NV12Buffer::with_strides( - yuvframe.width as u32, - yuvframe.height as u32, - yuvframe.luminance_stride as u32, - yuvframe.chrominance_stride as u32, - ); - let (luminance, chrominance) = buffer.data_mut(); - luminance.copy_from_slice(yuvframe.luminance_bytes.as_slice()); - chrominance.copy_from_slice(yuvframe.chrominance_bytes.as_slice()); - Some(buffer) - } - _ => { - log::error!( - "Expected BGRx or YUV frame from scap screen capture but got some other format." - ); - None - } - } -} - trait DeviceChangeListenerApi: Stream + Sized { fn new(input: bool) -> Result; } diff --git a/crates/livekit_client/src/mock_client.rs b/crates/livekit_client/src/mock_client.rs index 7d355749c01ac5..8b084a567946bc 100644 --- a/crates/livekit_client/src/mock_client.rs +++ b/crates/livekit_client/src/mock_client.rs @@ -1,4 +1,5 @@ use crate::test; +use libwebrtc::desktop_capturer::CaptureSource; pub(crate) mod participant; pub(crate) mod publication; @@ -36,3 +37,7 @@ pub(crate) fn play_remote_video_track( ) -> impl futures::Stream + use<> { futures::stream::pending() } + +pub fn screen_capture_sources() -> Vec { + Vec::new() +} diff --git a/crates/livekit_client/src/mock_client/participant.rs b/crates/livekit_client/src/mock_client/participant.rs index 033808cbb54189..84efea5c896f9e 100644 --- a/crates/livekit_client/src/mock_client/participant.rs +++ b/crates/livekit_client/src/mock_client/participant.rs @@ -1,13 +1,14 @@ +use std::sync::{Arc, atomic::AtomicBool}; + use crate::{ AudioStream, LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, Participant, - ParticipantIdentity, RemoteTrack, RemoteTrackPublication, TrackSid, + ParticipantIdentity, RemoteTrack, RemoteTrackPublication, ScreenCaptureStreamHandle, TrackSid, test::{Room, WeakRoom}, }; use anyhow::Result; use collections::HashMap; -use gpui::{ - AsyncApp, DevicePixels, ScreenCaptureSource, ScreenCaptureStream, SourceMetadata, size, -}; +use gpui::AsyncApp; +use livekit::webrtc::desktop_capturer::CaptureSource; #[derive(Clone, Debug)] pub struct LocalParticipant { @@ -59,20 +60,26 @@ impl LocalParticipant { pub async fn publish_screenshare_track( &self, - _source: &dyn ScreenCaptureSource, - _cx: &mut AsyncApp, - ) -> Result<(LocalTrackPublication, Box)> { + _source: Option, + cx: &mut AsyncApp, + ) -> Result<(LocalTrackPublication, ScreenCaptureStreamHandle)> { let this = self.clone(); let server = this.room.test_server(); let sid = server .publish_video_track(this.room.token(), LocalVideoTrack {}) .await?; + let spawn_handle = gpui_tokio::Tokio::spawn(cx, async {})?; + let handle = ScreenCaptureStreamHandle { + screen_id: 0, + stop_capture: Arc::new(AtomicBool::new(false)), + _spawn_handle: spawn_handle, + }; Ok(( LocalTrackPublication { room: self.room.downgrade(), sid, }, - Box::new(TestScreenCaptureStream {}), + handle, )) } } @@ -121,16 +128,3 @@ impl RemoteParticipant { self.identity.clone() } } - -struct TestScreenCaptureStream; - -impl ScreenCaptureStream for TestScreenCaptureStream { - fn metadata(&self) -> Result { - Ok(SourceMetadata { - id: 0, - is_main: None, - label: None, - resolution: size(DevicePixels(1), DevicePixels(1)), - }) - } -} diff --git a/crates/title_bar/Cargo.toml b/crates/title_bar/Cargo.toml index 6d5d0ce170e261..8c3159531c3488 100644 --- a/crates/title_bar/Cargo.toml +++ b/crates/title_bar/Cargo.toml @@ -35,7 +35,8 @@ chrono.workspace = true client.workspace = true cloud_llm_client.workspace = true db.workspace = true -gpui = { workspace = true, features = ["screen-capture"] } +gpui.workspace = true +libwebrtc.workspace = true notifications.workspace = true project.workspace = true remote.workspace = true diff --git a/crates/title_bar/src/collab.rs b/crates/title_bar/src/collab.rs index 8a2d23dd26f81d..192380e9e70ba0 100644 --- a/crates/title_bar/src/collab.rs +++ b/crates/title_bar/src/collab.rs @@ -4,11 +4,9 @@ use std::sync::Arc; use call::{ActiveCall, ParticipantLocation, Room}; use channel::ChannelStore; use client::{User, proto::PeerId}; -use gpui::{ - AnyElement, Hsla, IntoElement, MouseButton, Path, ScreenCaptureSource, Styled, WeakEntity, - canvas, point, -}; -use gpui::{App, Task, Window}; +use gpui::{AnyElement, Hsla, IntoElement, MouseButton, Path, Styled, WeakEntity, canvas, point}; +use gpui::{App, Task, Window, actions}; +use libwebrtc::desktop_capturer::CaptureSource; use project::WorktreeSettings; use rpc::proto::{self}; use settings::{Settings as _, SettingsLocation}; @@ -22,58 +20,59 @@ use workspace::notifications::DetachAndPromptErr; use crate::TitleBar; -pub fn toggle_screen_sharing( - screen: anyhow::Result>>, - window: &mut Window, - cx: &mut App, -) { +actions!( + collab, + [ + /// Toggles screen sharing on or off. + ToggleScreenSharing, + /// Toggles microphone mute. + ToggleMute, + /// Toggles deafen mode (mute both microphone and speakers). + ToggleDeafen + ] +); + +fn toggle_screen_sharing(screen: Option, window: &mut Window, cx: &mut App) { let call = ActiveCall::global(cx).read(cx); - let toggle_screen_sharing = match screen { - Ok(screen) => { - let Some(room) = call.room().cloned() else { - return; - }; + let toggle_screen_sharing = { + let Some(room) = call.room().cloned() else { + return; + }; - room.update(cx, |room, cx| { - let clicked_on_currently_shared_screen = - room.shared_screen_id().is_some_and(|screen_id| { - Some(screen_id) - == screen - .as_deref() - .and_then(|s| s.metadata().ok().map(|meta| meta.id)) - }); - let should_unshare_current_screen = room.is_sharing_screen(); - let unshared_current_screen = should_unshare_current_screen.then(|| { + room.update(cx, |room, cx| { + let clicked_on_currently_shared_screen = screen + .clone() + .is_some_and(|s| Some(s.id()) == room.shared_screen_id()); + let should_unshare_current_screen = room.is_sharing_screen(); + let unshared_current_screen = should_unshare_current_screen.then(|| { + telemetry::event!( + "Screen Share Disabled", + room_id = room.id(), + channel_id = room.channel_id(), + ); + room.unshare_screen(clicked_on_currently_shared_screen || screen.is_none(), cx) + }); + if let Some(screen) = screen { + if !should_unshare_current_screen { telemetry::event!( - "Screen Share Disabled", + "Screen Share Enabled", room_id = room.id(), channel_id = room.channel_id(), ); - room.unshare_screen(clicked_on_currently_shared_screen || screen.is_none(), cx) - }); - if let Some(screen) = screen { - if !should_unshare_current_screen { - telemetry::event!( - "Screen Share Enabled", - room_id = room.id(), - channel_id = room.channel_id(), - ); - } - cx.spawn(async move |room, cx| { - unshared_current_screen.transpose()?; - if !clicked_on_currently_shared_screen { - room.update(cx, |room, cx| room.share_screen(screen, cx))? - .await - } else { - Ok(()) - } - }) - } else { - Task::ready(Ok(())) } - }) - } - Err(e) => Task::ready(Err(e)), + cx.spawn(async move |room, cx| { + unshared_current_screen.transpose()?; + if !clicked_on_currently_shared_screen { + room.update(cx, |room, cx| room.share_screen(Some(screen), cx))? + .await + } else { + Ok(()) + } + }) + } else { + Task::ready(Ok(())) + } + }) }; toggle_screen_sharing.detach_and_prompt_err("Sharing Screen Failed", window, cx, |e, _, _| Some(format!("{:?}\n\nPlease check that you have given Zed permissions to record your screen in Settings.", e))); } @@ -344,7 +343,6 @@ impl TitleBar { let is_screen_sharing = room.is_sharing_screen(); let can_use_microphone = room.can_use_microphone(); let can_share_projects = room.can_share_projects(); - let screen_sharing_supported = cx.is_screen_capture_supported(); let channel_store = ChannelStore::global(cx); let channel = room @@ -487,7 +485,7 @@ impl TitleBar { .into_any_element(), ); - if can_use_microphone && screen_sharing_supported { + if can_use_microphone { let trigger = IconButton::new("screen-share", IconName::Screen) .style(ButtonStyle::Subtle) .icon_size(IconSize::Small) @@ -499,18 +497,13 @@ impl TitleBar { "Share Screen" })) .on_click(move |_, window, cx| { - let should_share = ActiveCall::global(cx) - .read(cx) - .room() - .is_some_and(|room| !room.read(cx).is_sharing_screen()); + let room = ActiveCall::global(cx).read(cx).room().unwrap().read(cx); + let first_screen = room.available_screens().first().cloned(); + let should_share = !room.is_sharing_screen(); window .spawn(cx, async move |cx| { - let screen = if should_share { - cx.update(|_, cx| pick_default_screen(cx))?.await - } else { - Ok(None) - }; + let screen = if should_share { first_screen } else { None }; cx.update(|window, cx| toggle_screen_sharing(screen, window, cx))?; Result::<_, anyhow::Error>::Ok(()) @@ -518,14 +511,18 @@ impl TitleBar { .detach(); }); - children.push( - SplitButton::new( - trigger.render(window, cx), - self.render_screen_list().into_any_element(), - ) - .style(SplitButtonStyle::Transparent) - .into_any_element(), - ); + match room.available_screens().len() { + 0 => (), + 1 => children.push(trigger.into_any_element()), + 2.. => children.push( + SplitButton::new( + trigger.render(window, cx), + self.render_screen_list().into_any_element(), + ) + .style(SplitButtonStyle::Transparent) + .into_any_element(), + ), + }; } children.push(div().pr_2().into_any_element()); @@ -548,28 +545,15 @@ impl TitleBar { .toggle_state(self.screen_share_popover_handle.is_deployed()), ) .menu(|window, cx| { - let screens = cx.screen_capture_sources(); Some(ContextMenu::build(window, cx, |context_menu, _, cx| { cx.spawn(async move |this: WeakEntity, cx| { - let screens = screens.await??; this.update(cx, |this, cx| { - let active_screenshare_id = ActiveCall::global(cx) - .read(cx) - .room() - .and_then(|room| room.read(cx).shared_screen_id()); + let room = ActiveCall::global(cx).read(cx).room().unwrap().read(cx); + let screens = room.available_screens().clone(); + let active_screenshare_id = room.shared_screen_id(); for screen in screens { - let Ok(meta) = screen.metadata() else { - continue; - }; - - let label = meta - .label - .clone() - .unwrap_or_else(|| SharedString::from("Unknown screen")); - let resolution = SharedString::from(format!( - "{} × {}", - meta.resolution.width.0, meta.resolution.height.0 - )); + let id = screen.id(); + let title = SharedString::from(screen.title().clone()); this.push_item(ContextMenuItem::CustomEntry { entry_render: Box::new(move |_, _| { h_flex() @@ -578,25 +562,20 @@ impl TitleBar { Icon::new(IconName::Screen) .size(IconSize::XSmall) .map(|this| { - if active_screenshare_id == Some(meta.id) { + if active_screenshare_id == Some(id) { this.color(Color::Accent) } else { this.color(Color::Muted) } }), ) - .child(Label::new(label.clone())) - .child( - Label::new(resolution.clone()) - .color(Color::Muted) - .size(LabelSize::Small), - ) + .child(Label::new(title.clone())) .into_any() }), selectable: true, documentation_aside: None, handler: Rc::new(move |_, window, cx| { - toggle_screen_sharing(Ok(Some(screen.clone())), window, cx); + toggle_screen_sharing(Some(screen.clone()), window, cx); }), }); } @@ -608,20 +587,3 @@ impl TitleBar { }) } } - -/// Picks the screen to share when clicking on the main screen sharing button. -fn pick_default_screen(cx: &App) -> Task>>> { - let source = cx.screen_capture_sources(); - cx.spawn(async move |_| { - let available_sources = source.await??; - Ok(available_sources - .iter() - .find(|it| { - it.as_ref() - .metadata() - .is_ok_and(|meta| meta.is_main.unwrap_or_default()) - }) - .or_else(|| available_sources.first()) - .cloned()) - }) -} diff --git a/script/linux b/script/linux index c5c4ea9ab38565..28a82c10f054c4 100755 --- a/script/linux +++ b/script/linux @@ -30,7 +30,16 @@ if [[ -n $apt ]]; then libwayland-dev libx11-xcb-dev libxkbcommon-x11-dev + libxfixes-dev + libxdamage-dev + libxrandr-dev + libxcomposite-dev + libxext-dev + libdrm-dev + libgbm-dev + libglib2.0-dev libssl-dev + libva-dev libzstd-dev libvulkan1 libgit2-dev @@ -81,6 +90,14 @@ if [[ -n $dnf ]] || [[ -n $yum ]]; then wayland-devel libxcb-devel libxkbcommon-x11-devel + libXcomposite-devel + libXdamage-devel + libXext-devel + libXrandr-devel + libdrm-devel + libva-devel + mesa-libgbm-devel + glib2-devel openssl-devel libzstd-devel vulkan-loader diff --git a/tooling/xtask/src/tasks/workflows/run_tests.rs b/tooling/xtask/src/tasks/workflows/run_tests.rs index e4443ad91313fd..9d072d7e3fe132 100644 --- a/tooling/xtask/src/tasks/workflows/run_tests.rs +++ b/tooling/xtask/src/tasks/workflows/run_tests.rs @@ -1,5 +1,5 @@ use gh_workflow::{ - Concurrency, Event, Expression, Job, PullRequest, Push, Run, Step, Use, Workflow, + Concurrency, Env, Event, Expression, Job, PullRequest, Push, Run, Step, Use, Workflow, }; use indexmap::IndexMap; @@ -15,6 +15,12 @@ use super::{ steps::{self, FluentBuilder, NamedJob, named, release_job}, }; +// Ubuntu 22.04's gcc is too old to compile libwebrtc's C++ headers +fn use_clang(job: Job) -> Job { + job.add_env(Env::new("CC", "clang")) + .add_env(Env::new("CXX", "clang++")) +} + pub(crate) fn run_tests() -> Workflow { // Specify anything which should potentially skip full test suite in this regex: // - docs/ @@ -230,7 +236,7 @@ fn check_style() -> NamedJob { ) // v1.40.0 .with(("config", "./typos.toml")) } - named::job( + named::job(use_clang( release_job(&[]) .runs_on(runners::LINUX_MEDIUM) .add_step(steps::checkout_repo()) @@ -241,7 +247,7 @@ fn check_style() -> NamedJob { .add_step(steps::script("./script/check-keymaps")) .add_step(check_for_typos()) .add_step(steps::cargo_fmt()), - ) + )) } fn check_dependencies() -> NamedJob { @@ -278,7 +284,7 @@ fn check_dependencies() -> NamedJob { .with(("license-check", false)) } - named::job( + named::job(use_clang( release_job(&[]) .runs_on(runners::LINUX_SMALL) .add_step(steps::checkout_repo()) @@ -287,11 +293,11 @@ fn check_dependencies() -> NamedJob { .add_step(run_cargo_machete()) .add_step(check_cargo_lock()) .add_step(check_vulnerable_dependencies()), - ) + )) } fn check_workspace_binaries() -> NamedJob { - named::job( + named::job(use_clang( release_job(&[]) .runs_on(runners::LINUX_LARGE) .add_step(steps::checkout_repo()) @@ -301,7 +307,7 @@ fn check_workspace_binaries() -> NamedJob { .add_step(steps::script("cargo build -p collab")) .add_step(steps::script("cargo build --workspace --bins --examples")) .add_step(steps::cleanup_cargo_config(Platform::Linux)), - ) + )) } pub(crate) fn run_platform_tests(platform: Platform) -> NamedJob { @@ -317,7 +323,7 @@ pub(crate) fn run_platform_tests(platform: Platform) -> NamedJob { .add_step(steps::checkout_repo()) .add_step(steps::setup_cargo_config(platform)) .when(platform == Platform::Linux, |this| { - this.add_step(steps::cache_rust_dependencies_namespace()) + use_clang(this.add_step(steps::cache_rust_dependencies_namespace())) }) .when( platform == Platform::Linux, @@ -384,7 +390,7 @@ fn doctests() -> NamedJob { .id("run_doctests") } - named::job( + named::job(use_clang( release_job(&[]) .runs_on(runners::LINUX_DEFAULT) .add_step(steps::checkout_repo()) @@ -393,7 +399,7 @@ fn doctests() -> NamedJob { .add_step(steps::setup_cargo_config(Platform::Linux)) .add_step(run_doctests()) .add_step(steps::cleanup_cargo_config(Platform::Linux)), - ) + )) } fn check_licenses() -> NamedJob { @@ -435,7 +441,7 @@ fn check_docs() -> NamedJob { "#}) } - named::job( + named::job(use_clang( release_job(&[]) .runs_on(runners::LINUX_LARGE) .add_step(steps::checkout_repo()) @@ -451,7 +457,7 @@ fn check_docs() -> NamedJob { .add_step( lychee_link_check("target/deploy/docs"), // check links in generated html ), - ) + )) } pub(crate) fn check_scripts() -> NamedJob { @@ -482,7 +488,7 @@ pub(crate) fn check_scripts() -> NamedJob { "#}) } - named::job( + named::job(use_clang( release_job(&[]) .runs_on(runners::LINUX_SMALL) .add_step(steps::checkout_repo()) @@ -490,5 +496,5 @@ pub(crate) fn check_scripts() -> NamedJob { .add_step(download_actionlint().id("get_actionlint")) .add_step(run_actionlint()) .add_step(check_xtask_workflows()), - ) + )) } From c80f2e5aeb352f8326de2284a494dc7aba3699f9 Mon Sep 17 00:00:00 2001 From: Josh Robson Chase Date: Mon, 12 Jan 2026 09:59:13 -0500 Subject: [PATCH 3/4] nix: vendor the in-flight update from nixpkgs --- nix/build.nix | 3 +- .../0001-shared-libraries.patch | 13 + nix/livekit-libwebrtc/README.md | 7 + nix/livekit-libwebrtc/chromium-129-rust.patch | 21 + nix/livekit-libwebrtc/mkSystemLibraries.nix | 64 +++ nix/livekit-libwebrtc/package.nix | 339 ++++++++++++++++ nix/livekit-libwebrtc/sources.json | 372 ++++++++++++++++++ nix/livekit-libwebrtc/update.sh | 33 ++ 8 files changed, 851 insertions(+), 1 deletion(-) create mode 100644 nix/livekit-libwebrtc/0001-shared-libraries.patch create mode 100644 nix/livekit-libwebrtc/README.md create mode 100644 nix/livekit-libwebrtc/chromium-129-rust.patch create mode 100644 nix/livekit-libwebrtc/mkSystemLibraries.nix create mode 100644 nix/livekit-libwebrtc/package.nix create mode 100644 nix/livekit-libwebrtc/sources.json create mode 100755 nix/livekit-libwebrtc/update.sh diff --git a/nix/build.nix b/nix/build.nix index e32624a5f1cddf..29653e53dc2b0f 100644 --- a/nix/build.nix +++ b/nix/build.nix @@ -1,6 +1,7 @@ { lib, stdenv, + pkgs, apple-sdk_15, darwin, @@ -178,7 +179,7 @@ let }; ZED_UPDATE_EXPLANATION = "Zed has been installed using Nix. Auto-updates have thus been disabled."; RELEASE_VERSION = version; - LK_CUSTOM_WEBRTC = livekit-libwebrtc; + LK_CUSTOM_WEBRTC = pkgs.callPackage ./livekit-libwebrtc/package.nix { }; PROTOC="${protobuf}/bin/protoc"; CARGO_PROFILE = profile; diff --git a/nix/livekit-libwebrtc/0001-shared-libraries.patch b/nix/livekit-libwebrtc/0001-shared-libraries.patch new file mode 100644 index 00000000000000..e0b8709a4d1607 --- /dev/null +++ b/nix/livekit-libwebrtc/0001-shared-libraries.patch @@ -0,0 +1,13 @@ +--- a/BUILD.gn 2026-01-10 19:22:47.201811909 -0500 ++++ b/BUILD.gn 2026-01-10 19:24:36.440918317 -0500 +@@ -143,8 +143,8 @@ + # target_defaults and direct_dependent_settings. + config("common_inherited_config") { + defines = [ "PROTOBUF_ENABLE_DEBUG_LOGGING_MAY_LEAK_PII=0" ] +- cflags = [] +- ldflags = [] ++ cflags = [ "-fvisibility=default" ] ++ ldflags = [ "-lavutil", "-lavformat", "-lavcodec" ] + + if (rtc_objc_prefix != "") { + defines += [ "RTC_OBJC_TYPE_PREFIX=${rtc_objc_prefix}" ] diff --git a/nix/livekit-libwebrtc/README.md b/nix/livekit-libwebrtc/README.md new file mode 100644 index 00000000000000..87d4fc5599fa0a --- /dev/null +++ b/nix/livekit-libwebrtc/README.md @@ -0,0 +1,7 @@ +# Vendored livekit-libwebrtc build + +The contents of this directory is vendored from [this nixpkgs +PR](https://github.com/NixOS/nixpkgs/pull/478907). + +It should be removed as soon as said PR is merged and the new version of libwebrtc hits +nixpkgs-unstable. diff --git a/nix/livekit-libwebrtc/chromium-129-rust.patch b/nix/livekit-libwebrtc/chromium-129-rust.patch new file mode 100644 index 00000000000000..1fe0c7f87324d8 --- /dev/null +++ b/nix/livekit-libwebrtc/chromium-129-rust.patch @@ -0,0 +1,21 @@ +diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn +index 45086d6838cac..81132ad8ecb31 100644 +--- a/build/config/compiler/BUILD.gn ++++ b/build/config/compiler/BUILD.gn +@@ -1727,16 +1727,6 @@ config("runtime_library") { + configs += [ "//build/config/c++:runtime_library" ] + } + +- # Rust and C++ both provide intrinsics for LLVM to call for math operations. We +- # want to use the C++ intrinsics, not the ones in the Rust compiler_builtins +- # library. The Rust symbols are marked as weak, so that they can be replaced by +- # the C++ symbols. This config ensures the C++ symbols exist and are strong in +- # order to cause that replacement to occur by explicitly linking in clang's +- # compiler-rt library. +- if (is_clang && !is_nacl && !is_cronet_build) { +- configs += [ "//build/config/clang:compiler_builtins" ] +- } +- + # TODO(crbug.com/40570904): Come up with a better name for is POSIX + Fuchsia + # configuration. + if (is_posix || is_fuchsia) { diff --git a/nix/livekit-libwebrtc/mkSystemLibraries.nix b/nix/livekit-libwebrtc/mkSystemLibraries.nix new file mode 100644 index 00000000000000..4293798faf9031 --- /dev/null +++ b/nix/livekit-libwebrtc/mkSystemLibraries.nix @@ -0,0 +1,64 @@ +{ + brotli, + fontconfig, + freetype, + harfbuzz, + icu, + jsoncpp, + libpng, + libwebp, + libxml2, + libxslt, + minizip, + ffmpeg_6, +}: +{ + "brotli" = { + package = brotli; + path = "third_party/brotli/BUILD.gn"; + }; + "fontconfig" = { + package = fontconfig; + path = "third_party/fontconfig/BUILD.gn"; + }; + "freetype" = { + package = freetype; + path = "build/config/freetype/freetype.gni"; + }; + "harfbuzz-ng" = { + package = harfbuzz; + path = "third_party/harfbuzz-ng/harfbuzz.gni"; + }; + "jsoncpp" = { + package = jsoncpp; + path = "third_party/jsoncpp/BUILD.gn"; + }; + "icu" = { + package = icu; + path = "third_party/icu/BUILD.gn"; + }; + "libpng" = { + package = libpng; + path = "third_party/libpng/BUILD.gn"; + }; + "libwebp" = { + package = libwebp; + path = "third_party/libwebp/BUILD.gn"; + }; + "libxml" = { + package = libxml2; + path = "third_party/libxml/BUILD.gn"; + }; + "libxslt" = { + package = libxslt; + path = "third_party/libxslt/BUILD.gn"; + }; + "zlib" = { + package = minizip; + path = "third_party/zlib/BUILD.gn"; + }; + "ffmpeg" = { + package = ffmpeg_6; + path = "third_party/ffmpeg/BUILD.gn"; + }; +} diff --git a/nix/livekit-libwebrtc/package.nix b/nix/livekit-libwebrtc/package.nix new file mode 100644 index 00000000000000..9a25e0c2558b36 --- /dev/null +++ b/nix/livekit-libwebrtc/package.nix @@ -0,0 +1,339 @@ +{ + stdenv, + clang, + gclient2nix, + lib, + gn, + fetchurl, + fetchpatch, + xcbuild, + python3, + ninja, + git, + cpio, + pkg-config, + glib, + alsa-lib, + pulseaudio, + nasm, + brotli, + fontconfig, + freetype, + harfbuzz, + icu, + jsoncpp, + libpng, + libwebp, + libxml2, + libxslt, + minizip, + ffmpeg_6, + libepoxy, + libgbm, + libGL, + libxcomposite, + libxdamage, + libxext, + libxfixes, + libxrandr, + libxtst, + pipewire, + xorg, +}: +let + platformMap = { + "x86_64" = "x64"; + "i686" = "x86"; + "arm" = "arm"; + "aarch64" = "arm64"; + }; + cpuName = stdenv.hostPlatform.parsed.cpu.name; + gnArch = platformMap."${cpuName}" or (throw "unsupported arch ${cpuName}"); + gnOs = + if stdenv.hostPlatform.isLinux then + "linux" + else if stdenv.hostPlatform.isDarwin then + "mac" + else + throw "unknown platform ${stdenv.hostPlatform.config}"; + boringSslSymbols = fetchurl { + url = "https://raw.githubusercontent.com/livekit/rust-sdks/refs/tags/webrtc-dac8015-6/webrtc-sys/libwebrtc/boringssl_prefix_symbols.txt"; + hash = "sha256-dAweArv8zjsFPENEKi9mNBQkt4y+hh3rCqG6QZjRC20="; + }; + gnSystemLibraries = import ./mkSystemLibraries.nix { + inherit + brotli + fontconfig + freetype + harfbuzz + icu + jsoncpp + libpng + libwebp + libxml2 + libxslt + minizip + ffmpeg_6 + ; + }; +in +stdenv.mkDerivation { + pname = "livekit-libwebrtc"; + version = "137-unstable-2025-11-24"; + + gclientDeps = gclient2nix.importGclientDeps ./sources.json; + sourceRoot = "src"; + + patches = [ + # Adds missing dependencies to generated LICENSE + (fetchpatch { + url = "https://raw.githubusercontent.com/livekit/rust-sdks/a4343fe9d88fcc96f8e88959c90d509abbd0307b/webrtc-sys/libwebrtc/patches/add_licenses.patch"; + hash = "sha256-9A4KyRW1K3eoQxsTbPX0vOnj66TCs2Fxjpsu5wO8mGI="; + }) + # Fixes the certificate chain, required for Let's Encrypt certs + (fetchpatch { + url = "https://raw.githubusercontent.com/livekit/rust-sdks/a4343fe9d88fcc96f8e88959c90d509abbd0307b/webrtc-sys/libwebrtc/patches/ssl_verify_callback_with_native_handle.patch"; + hash = "sha256-RBvRcJzoKItpEbqpe07YZe1D1ZVGS12EnDSISldGy+0="; + }) + # Adds dependencies and features required by livekit + (fetchpatch { + url = "https://raw.githubusercontent.com/livekit/rust-sdks/a4343fe9d88fcc96f8e88959c90d509abbd0307b/webrtc-sys/libwebrtc/patches/add_deps.patch"; + hash = "sha256-DwRtGdU5sppmiFsVuyhJoVCQrRl5JFmZJfxgUPhYXBg="; + }) + # Fix gcc-related errors + (fetchpatch { + url = "https://raw.githubusercontent.com/livekit/rust-sdks/a4343fe9d88fcc96f8e88959c90d509abbd0307b/webrtc-sys/libwebrtc/patches/force_gcc.patch"; + hash = "sha256-1d73Pi1HkbunjYvp1NskUNE4xXbCmnh++rC6NrCJHbY="; + stripLen = 1; + extraPrefix = "build/"; + }) + # fix a gcc-related dav1d compile option + (fetchpatch { + url = "https://raw.githubusercontent.com/livekit/rust-sdks/a4343fe9d88fcc96f8e88959c90d509abbd0307b/webrtc-sys/libwebrtc/patches/david_disable_gun_source_macro.patch"; + hash = "sha256-RCZpeeSQHaxkL3dY2oFFXDjYeU0KHw7idQFONGge8+0="; + stripLen = 1; + extraPrefix = "third_party/"; + }) + # Required for dynamically linking to ffmpeg libraries and exposing symbols + ./0001-shared-libraries.patch + # Borrow a patch from chromium to prevent a build failure due to missing libclang libraries + ./applications/networking/browsers/chromium/patches/chromium-129-rust.patch + ]; + + postPatch = '' + substituteInPlace .gn \ + --replace-fail "vpython3" "python3" + + substituteInPlace tools/generate_shim_headers/generate_shim_headers.py \ + --replace-fail "OFFICIAL_BUILD" "GOOGLE_CHROME_BUILD" + + substituteInPlace BUILD.gn \ + --replace-fail "rtc_static_library" "rtc_shared_library" \ + --replace-fail "complete_static_lib = true" "" + + substituteInPlace webrtc.gni \ + --replace-fail "!build_with_chromium && is_component_build" "false" + + substituteInPlace rtc_tools/BUILD.gn \ + --replace-fail "\":frame_analyzer\"," "" + + for lib in ${toString (builtins.attrNames gnSystemLibraries)}; do + if [ -d "third_party/$lib" ]; then + find "third_party/$lib" -type f \ + \! -path "third_party/$lib/chromium/*" \ + \! -path "third_party/$lib/google/*" \ + \! -path "third_party/harfbuzz-ng/utils/hb_scoped.h" \ + \! -regex '.*\.\(gn\|gni\|isolate\)' \ + \! -name 'LICENSE*' \ + \! -name 'COPYING*' \ + -delete + fi + done + + # Trick the update_rust.py script into thinking we have *this specfic* rust available. + # It isn't actually needed for the libwebrtc build, but GN will fail if it isn't there. + mkdir -p third_party/rust-toolchain + (python3 tools/rust/update_rust.py --print-package-version || true) \ + | head -n 1 \ + | sed 's/.* expected Rust version is \([^ ]*\) .*/rustc 1.0 1234 (\1 chromium)/' \ + > third_party/rust-toolchain/VERSION + '' + + lib.optionalString stdenv.hostPlatform.isLinux '' + mkdir -p buildtools/linux64 + ln -sf ${lib.getExe gn} buildtools/linux64/gn + substituteInPlace build/toolchain/linux/BUILD.gn \ + --replace 'toolprefix = "aarch64-linux-gnu-"' 'toolprefix = ""' + '' + + lib.optionalString stdenv.hostPlatform.isDarwin '' + mkdir -p buildtools/mac + ln -sf ${lib.getExe gn} buildtools/mac/gn + chmod +x build/toolchain/apple/linker_driver.py + patchShebangs build/toolchain/apple/linker_driver.py + substituteInPlace build/toolchain/apple/toolchain.gni --replace-fail "/bin/cp -Rc" "cp -a" + ''; + + outputs = [ + "dev" + "out" + ]; + + nativeBuildInputs = + (builtins.concatLists ( + lib.mapAttrsToList ( + _: library: if (library.package ? dev) then [ library.package.dev ] else [ ] + ) gnSystemLibraries + )) + ++ [ + gclient2nix.gclientUnpackHook + gn + (python3.withPackages (ps: [ ps.setuptools ])) + ninja + git + cpio + pkg-config + ] + ++ lib.optionals stdenv.hostPlatform.isDarwin [ xcbuild ]; + + buildInputs = [ + nasm + ] + ++ (lib.mapAttrsToList (_: library: library.package) gnSystemLibraries) + ++ (lib.optionals stdenv.hostPlatform.isLinux [ + glib + alsa-lib + pulseaudio + libepoxy + libgbm + libGL + libxcomposite + libxdamage + libxext + libxfixes + libxrandr + libxtst + pipewire + xorg.libX11 + xorg.libXi + ]); + + preConfigure = '' + echo "generate_location_tags = true" >> build/config/gclient_args.gni + echo "0" > build/util/LASTCHANGE.committime + + python build/linux/unbundle/replace_gn_files.py \ + --system-libraries ${toString (builtins.attrNames gnSystemLibraries)} + ''; + + gnFlags = [ + "is_debug=false" + "rtc_include_tests=false" + ''target_os="${gnOs}"'' + ''target_cpu="${gnArch}"'' + "treat_warnings_as_errors=false" + "rtc_enable_protobuf=false" + "rtc_include_tests=false" + "rtc_build_examples=false" + "rtc_build_tools=false" + "rtc_libvpx_build_vp9=true" + "enable_libaom=true" + "use_dummy_lastchange=true" + "is_component_build=true" + "enable_stripping=true" + "rtc_use_h264=true" + "rtc_use_h265=true" + "use_custom_libcxx=false" + "use_rtti=true" + ] + ++ (lib.optionals stdenv.hostPlatform.isLinux [ + "rtc_use_pipewire=true" + "symbol_level=0" + "enable_iterator_debugging=false" + "rtc_use_x11=true" + "use_sysroot=false" + "use_custom_libcxx_for_host=false" + "use_libcxx_modules=false" + "use_llvm_libatomic=false" + "is_clang=false" + ]) + ++ (lib.optionals stdenv.hostPlatform.isDarwin [ + ''mac_deployment_target="${stdenv.hostPlatform.darwinMinVersion}"'' + "rtc_enable_symbol_export=true" + "rtc_enable_objc_symbol_export=true" + "rtc_include_dav1d_in_internal_decoder_factory=true" + "clang_use_chrome_plugins=false" + "use_lld=false" + ''clang_base_path="${clang}"'' + ]); + + ninjaFlags = [ + ":default" + ] + ++ lib.optionals stdenv.hostPlatform.isDarwin [ + "api/audio_codecs:builtin_audio_decoder_factory" + "api/task_queue:default_task_queue_factory" + "sdk:native_api" + "sdk:default_codec_factory_objc" + "pc:peer_connection" + "sdk:videocapture_objc" + "sdk:mac_framework_objc" + "desktop_capture_objc" + ]; + + postBuild = + lib.optionalString stdenv.hostPlatform.isLinux '' + objcopy --redefine-syms="${boringSslSymbols}" "libwebrtc.so" + '' + + '' + # Generate licenses + python3 "../../tools_webrtc/libs/generate_licenses.py" \ + --target ${if stdenv.hostPlatform.isDarwin then ":webrtc" else ":default"} $PWD $PWD + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/lib + mkdir -p $dev/include + + install -m0644 obj/webrtc.ninja obj/modules/desktop_capture/desktop_capture.ninja args.gn LICENSE.md $dev + + pushd ../.. + find . -name "*.h" -print | cpio -pd $dev/include + find . -name "*.inc" -print | cpio -pd $dev/include + popd + '' + + lib.optionalString stdenv.hostPlatform.isLinux '' + install -m0644 libwebrtc.so libthird_party_boringssl.so $out/lib + '' + + lib.optionalString stdenv.hostPlatform.isDarwin '' + install -m0644 WebRTC.framework/Versions/A/WebRTC $out/lib/libwebrtc.dylib + install -m0644 libthird_party_boringssl.dylib $out/lib + '' + + '' + ln -s $out/lib $dev/lib + + runHook postInstall + ''; + + postFixup = lib.optionalString stdenv.hostPlatform.isDarwin '' + boringssl="$out/lib/libthird_party_boringssl.dylib" + webrtc="$out/lib/libwebrtc.dylib" + + install_name_tool -id "$boringssl" "$boringssl" + install_name_tool -id "$webrtc" "$webrtc" + install_name_tool -change @rpath/libthird_party_boringssl.dylib "$boringssl" "$webrtc" + ''; + + passthru.updateScript = ./update.sh; + + meta = { + description = "WebRTC library used by livekit"; + homepage = "https://github.com/livekit/rust-sdks/"; + license = lib.licenses.bsd3; + maintainers = with lib.maintainers; [ + WeetHet + niklaskorz + ]; + platforms = lib.platforms.linux ++ lib.platforms.darwin; + }; +} diff --git a/nix/livekit-libwebrtc/sources.json b/nix/livekit-libwebrtc/sources.json new file mode 100644 index 00000000000000..2db785a840f1db --- /dev/null +++ b/nix/livekit-libwebrtc/sources.json @@ -0,0 +1,372 @@ +{ + "src": { + "args": { + "hash": "sha256-+PgmOZD2Fi+SC66nguixhSwDsoXi4Sz693qOZZrLXm8=", + "owner": "webrtc-sdk", + "repo": "webrtc", + "rev": "624fa1dce239af785fc5fa9ca3b21b9250d3f835" + }, + "fetcher": "fetchFromGitHub" + }, + "src/base": { + "args": { + "hash": "sha256-MTG+pjMPY6/dqeEUy+xJVxPuICETtV98S+h/lFwGItg=", + "rev": "86c814633cf284bc8057a539bc722e2a672afe2f", + "url": "https://chromium.googlesource.com/chromium/src/base" + }, + "fetcher": "fetchFromGitiles" + }, + "src/build": { + "args": { + "hash": "sha256-qFZ12YFX4qxFEHU+VWOG+HDYYPXodgGz+iJ7WEc7cD8=", + "owner": "webrtc-sdk", + "repo": "build", + "rev": "01021e6c12636951a6b4e5342e16b2101b352367" + }, + "fetcher": "fetchFromGitHub" + }, + "src/buildtools": { + "args": { + "hash": "sha256-YWtmMKL1ydueNJ4XM/Pq+8OpqIFe5A6/vYyfZTv7/EI=", + "rev": "0f32cb9025766951122d4ed19aba87a94ded3f43", + "url": "https://chromium.googlesource.com/chromium/src/buildtools" + }, + "fetcher": "fetchFromGitiles" + }, + "src/testing": { + "args": { + "hash": "sha256-s65cABkyMo+FkAmilS67qM3VnrT7iYZg9scycrXzxyE=", + "rev": "a89c37d36bf80c05963727e28b9916835ae88d3a", + "url": "https://chromium.googlesource.com/chromium/src/testing" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party": { + "args": { + "hash": "sha256-q+xVOFlpC0vnLMSF9Z6ZRL7mb/cu8jBpsWjDNFFgiKM=", + "rev": "8062e0e102496ff14a8c58b586f014527424953d", + "url": "https://chromium.googlesource.com/chromium/src/third_party" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/boringssl/src": { + "args": { + "hash": "sha256-5Efqc8pLs4ZskXQGpFdTb5cw//v3+DR285m/DsrWSWA=", + "rev": "34492c89a8e381e0e856a686cc71b1eb5bd728db", + "url": "https://boringssl.googlesource.com/boringssl.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/breakpad/breakpad": { + "args": { + "hash": "sha256-0ynZuxIqBIpNkfD3Y9XdPFQr7HeQcsUO3lhnqvH+k8c=", + "rev": "232a723f5096ab02d53d87931efa485fa77d3b03", + "url": "https://chromium.googlesource.com/breakpad/breakpad.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/catapult": { + "args": { + "hash": "sha256-FIJZE1Qu1MLZA4qxB68k1NjhgSbFTjf57YF85JicVZw=", + "rev": "000f47cfa393d7f9557025a252862e2a61a60d44", + "url": "https://chromium.googlesource.com/catapult.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/ced/src": { + "args": { + "hash": "sha256-ySG74Rj2i2c/PltEgHVEDq+N8yd9gZmxNktc56zIUiY=", + "rev": "ba412eaaacd3186085babcd901679a48863c7dd5", + "url": "https://chromium.googlesource.com/external/github.com/google/compact_enc_det.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/clang-format/script": { + "args": { + "hash": "sha256-d9uweklBffiuCWEb03ti1eFLnMac2qRtvggzXY1n/RU=", + "rev": "37f6e68a107df43b7d7e044fd36a13cbae3413f2", + "url": "https://chromium.googlesource.com/external/github.com/llvm/llvm-project/clang/tools/clang-format.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/colorama/src": { + "args": { + "hash": "sha256-6ZTdPYSHdQOLYMSnE+Tp7PgsVTs3U2awGu9Qb4Rg/tk=", + "rev": "3de9f013df4b470069d03d250224062e8cf15c49", + "url": "https://chromium.googlesource.com/external/colorama.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/compiler-rt/src": { + "args": { + "hash": "sha256-yo7BFGgwJNScsXwnCAu8gFBdZVS8/HJplzUk2e73mVg=", + "rev": "57213f125d03209892fed26189feb3b736e96735", + "url": "https://chromium.googlesource.com/external/github.com/llvm/llvm-project/compiler-rt.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/crc32c/src": { + "args": { + "hash": "sha256-KBraGaO5LmmPP+p8RuDogGldbTWdNDK+WzF4Q09keuE=", + "rev": "d3d60ac6e0f16780bcfcc825385e1d338801a558", + "url": "https://chromium.googlesource.com/external/github.com/google/crc32c.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/dav1d/libdav1d": { + "args": { + "hash": "sha256-+DY4p41VuAlx7NvOfXjWzgEhvtpebjkjbFwSYOzSjv4=", + "rev": "8d956180934f16244bdb58b39175824775125e55", + "url": "https://chromium.googlesource.com/external/github.com/videolan/dav1d.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/depot_tools": { + "args": { + "hash": "sha256-DWQyYtpAAGiryeGJzIWlUwY5yn4cNwXY957vlPDUNak=", + "rev": "fa8fc854e1766b86f10c9a15902cf3cc23adaac2", + "url": "https://chromium.googlesource.com/chromium/tools/depot_tools.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/ffmpeg": { + "args": { + "hash": "sha256-hNzQZQxaa2Wtl7GWWF852cFmmXy4pc15Pp0d59TTfnI=", + "rev": "01f23648c6b84de6c0f717fa4e1816f53b9ee72e", + "url": "https://chromium.googlesource.com/chromium/third_party/ffmpeg.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/flatbuffers/src": { + "args": { + "hash": "sha256-tbc45o0MbMvK5XqRUJt5Eg8BU6+TJqlmwFgQhHq6wRM=", + "rev": "8db59321d9f02cdffa30126654059c7d02f70c32", + "url": "https://chromium.googlesource.com/external/github.com/google/flatbuffers.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/fontconfig/src": { + "args": { + "hash": "sha256-W5WIgC6A52kY4fNkbsDEa0o+dfd97Rl5NKfgnIRpI00=", + "rev": "14d466b30a8ab4a9d789977ed94f2c30e7209267", + "url": "https://chromium.googlesource.com/external/fontconfig.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/freetype/src": { + "args": { + "hash": "sha256-Vlin6Z+QisUyj6R+TclVOm8x6673YhUIWob9Ih6gzC8=", + "rev": "1da283b8ae6d6b94f34a5c4b8c1227adc9dbb1d8", + "url": "https://chromium.googlesource.com/chromium/src/third_party/freetype2.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/fuzztest/src": { + "args": { + "hash": "sha256-L2QG0pUmGjGdtdlivxYfxSqO9YaVHpIT6lvJwBMTxMw=", + "rev": "b10387fdbbca18192f85eaa5323a59f44bf9c468", + "url": "https://chromium.googlesource.com/external/github.com/google/fuzztest.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/google_benchmark/src": { + "args": { + "hash": "sha256-cH8s1gP6kCcojAAfTt5iQCVqiAaSooNk4BdaILujM3w=", + "rev": "761305ec3b33abf30e08d50eb829e19a802581cc", + "url": "https://chromium.googlesource.com/external/github.com/google/benchmark.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/googletest/src": { + "args": { + "hash": "sha256-QT9PQ9bF+eCPfRLkcHpH4jc0UZfGPc98fHf8QDV5bZg=", + "rev": "cd430b47a54841ec45d64d2377d7cabaf0eba610", + "url": "https://chromium.googlesource.com/external/github.com/google/googletest.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/grpc/src": { + "args": { + "hash": "sha256-xivmP36VCSbiMAV3PDUjzCrF+AJzFXJdMe5e2q9yW/k=", + "rev": "957c9f95224b1e1318c0ecb98d0e7584ea5ccff2", + "url": "https://chromium.googlesource.com/external/github.com/grpc/grpc.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/gtest-parallel": { + "args": { + "hash": "sha256-VUuk5tBTh+aU2dxVWUF1FePWlKUJaWSiGSXk/J5zgHw=", + "rev": "96f4f904922f9bf66689e749c40f314845baaac8", + "url": "https://chromium.googlesource.com/external/github.com/google/gtest-parallel" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/harfbuzz-ng/src": { + "args": { + "hash": "sha256-lNnCtgIegUy4DLhYaGZXcEaFw83KWAHoKpz69AEsWp4=", + "rev": "9f83bbbe64654b45ba5bb06927ff36c2e7588495", + "url": "https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/icu": { + "args": { + "hash": "sha256-eGI/6wk6IOUPvX7pRTm4VJk1CqkkxalTu84L36i/D6k=", + "rev": "4c8cc4b365a505ce35be1e0bd488476c5f79805d", + "url": "https://chromium.googlesource.com/chromium/deps/icu.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/instrumented_libs": { + "args": { + "hash": "sha256-8kokdsnn5jD9KgM/6g0NuITBbKkGXWEM4BMr1nCrfdU=", + "rev": "69015643b3f68dbd438c010439c59adc52cac808", + "url": "https://chromium.googlesource.com/chromium/third_party/instrumented_libraries.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/jsoncpp/source": { + "args": { + "hash": "sha256-bSLNcoYBz3QCt5VuTR056V9mU2PmBuYBa0W6hFg2m8Q=", + "rev": "42e892d96e47b1f6e29844cc705e148ec4856448", + "url": "https://chromium.googlesource.com/external/github.com/open-source-parsers/jsoncpp.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/libFuzzer/src": { + "args": { + "hash": "sha256-Lb+HczYax0T7qvC0/Nwhc5l2szQTUYDouWRMD/Qz7sA=", + "rev": "e31b99917861f891308269c36a32363b120126bb", + "url": "https://chromium.googlesource.com/external/github.com/llvm/llvm-project/compiler-rt/lib/fuzzer.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/libaom/source/libaom": { + "args": { + "hash": "sha256-ngVZ+xK0b+jKUmawteQ7VFAQzoebX4jqZ3hP9pW+Q0Q=", + "rev": "a23a4799ec2d7dd6e436c7b64a34553773014ed7", + "url": "https://aomedia.googlesource.com/aom.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/libc++/src": { + "args": { + "hash": "sha256-lqeuVUgeAKm1pxo+w1vyUbBkBXBzLCQ+Lfu44neKLPo=", + "rev": "917609c669e43edc850eeb192a342434a54e1dfd", + "url": "https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxx.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/libc++abi/src": { + "args": { + "hash": "sha256-X9cAbyd8ZPSwqOGhPYwIZ6b9E3tVwAuAYZKMgbZQxgk=", + "rev": "f2a7f2987f9dcdf8b04c2d8cd4dcb186641a7c3e", + "url": "https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxxabi.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/libjpeg_turbo": { + "args": { + "hash": "sha256-Ig+tmprZDvlf/M72/DTar2pbxat9ZElgSqdXdoM0lPs=", + "rev": "e14cbfaa85529d47f9f55b0f104a579c1061f9ad", + "url": "https://chromium.googlesource.com/chromium/deps/libjpeg_turbo.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/libsrtp": { + "args": { + "hash": "sha256-bkG1+ss+1a2rCHGwZjhvf5UaNVbPPZJt9HZSIPBKGwM=", + "rev": "a52756acb1c5e133089c798736dd171567df11f5", + "url": "https://chromium.googlesource.com/chromium/deps/libsrtp.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/libunwind/src": { + "args": { + "hash": "sha256-XdFKn+cGOxA0fHkVMG9UAhCmpML44ocoyHB7XnumX7o=", + "rev": "81e2cb40a70de2b6978e6d8658891ded9a77f7e3", + "url": "https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libunwind.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/libvpx/source/libvpx": { + "args": { + "hash": "sha256-NIGpzP6elcPScHJlZmnPHJdmXsuHcbuELT0C4Ha5PcA=", + "rev": "ff1d193f4b9dfa9b2ced51efbb6ec7a69e58e88c", + "url": "https://chromium.googlesource.com/webm/libvpx.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/libyuv": { + "args": { + "hash": "sha256-b/EYCWBQvsNoGhea31DPBKpG8eouf0OBi5TgdHDHs9A=", + "rev": "1e40e34573c3861480d107cd4a4ce290df79951f", + "url": "https://chromium.googlesource.com/libyuv/libyuv.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/llvm-libc/src": { + "args": { + "hash": "sha256-yNNx3gOGafMNvZ+aebDKHVj6QM8g0zt0d69PWlWLkyk=", + "rev": "912274164f0877ca917c06e8484ad3be1784833a", + "url": "https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libc.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/lss": { + "args": { + "hash": "sha256-rhp4EcZYdgSfu9cqn+zxxGx6v2IW8uX8V+iA0UfZhFY=", + "rev": "ed31caa60f20a4f6569883b2d752ef7522de51e0", + "url": "https://chromium.googlesource.com/linux-syscall-support.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/nasm": { + "args": { + "hash": "sha256-neYrS4kQ76ihUh22Q3uPR67Ld8+yerA922YSZU1KxJs=", + "rev": "9f916e90e6fc34ec302573f6ce147e43e33d68ca", + "url": "https://chromium.googlesource.com/chromium/deps/nasm.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/openh264/src": { + "args": { + "hash": "sha256-tf0lnxATCkoq+xRti6gK6J47HwioAYWnpEsLGSA5Xdg=", + "rev": "652bdb7719f30b52b08e506645a7322ff1b2cc6f", + "url": "https://chromium.googlesource.com/external/github.com/cisco/openh264" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/perfetto": { + "args": { + "hash": "sha256-I0qiAh3VliVop+3S2/tP6VwCAJOk0Vu7xy8vHJZ1w2A=", + "rev": "a54dd38d60593129ae56d400f1a72860670abea4", + "url": "https://chromium.googlesource.com/external/github.com/google/perfetto.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/protobuf-javascript/src": { + "args": { + "hash": "sha256-zq86SrDASl6aYPFPijRZp03hJqXUFz2Al/KkiNq7i0M=", + "rev": "eb785a9363664a402b6336dfe96aad27fb33ffa8", + "url": "https://chromium.googlesource.com/external/github.com/protocolbuffers/protobuf-javascript" + }, + "fetcher": "fetchFromGitiles" + }, + "src/third_party/re2/src": { + "args": { + "hash": "sha256-f/k2rloV2Nwb0KuJGUX4SijFxAx69EXcsXOG4vo+Kis=", + "rev": "c84a140c93352cdabbfb547c531be34515b12228", + "url": "https://chromium.googlesource.com/external/github.com/google/re2.git" + }, + "fetcher": "fetchFromGitiles" + }, + "src/tools": { + "args": { + "hash": "sha256-kZFZl8SC9nZIIOVtNl/5H4huw6BCBsBkJVJ4gaUmly4=", + "rev": "ffcbc837bbb14d80d09147c2af5302ff6bd4bd69", + "url": "https://chromium.googlesource.com/chromium/src/tools" + }, + "fetcher": "fetchFromGitiles" + } +} diff --git a/nix/livekit-libwebrtc/update.sh b/nix/livekit-libwebrtc/update.sh new file mode 100755 index 00000000000000..b28c405b300280 --- /dev/null +++ b/nix/livekit-libwebrtc/update.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i bash -p gitMinimal curl gojq gclient2nix + +set -eou pipefail +package="livekit-libwebrtc" +pkg_dir="$(dirname "$0")" +nixpkgs="$(git rev-parse --show-toplevel)" + +gh-curl () { + curl --silent ${GITHUB_TOKEN:+-u ":$GITHUB_TOKEN"} "$1" +} + +# Get the current version part before the "-unstable-" for the branch name. +# To manually update to a new major version, you can also invoke the script +# with the new major version, e.g., UPDATE_MAJOR_VERSION=137. +old_version="${UPDATE_NIX_OLD_VERSION:-$(nix-instantiate --eval -E "(import \"$nixpkgs\" { }).$package.version" | tr -d '"')}" +major_version="${UPDATE_MAJOR_VERSION:-${old_version%%-unstable-*}}" +branch="m${major_version}_release" + +# Fetch the current HEAD commit of the release branch +head="$(gh-curl "https://api.github.com/repos/webrtc-sdk/webrtc/git/refs/heads/$branch" | gojq '.object.sha' --raw-output)" +if gojq -e ".src.args.rev == \"$head\"" "$pkg_dir/sources.json"; then + echo "$package is already up-to-date: $head" + exit 0 +fi + +# Get the commit's date for the version field +date="$(gh-curl "https://api.github.com/repos/webrtc-sdk/webrtc/git/commits/$head" | gojq '.committer.date| split("T") | .[0]' --raw-output)" + +echo "Updating sources.json to $head" +gclient2nix generate --root src "https://github.com/webrtc-sdk/webrtc@$head" > "$pkg_dir/sources.json" + +sed -i "s|$old_version|$major_version-unstable-$date|g" "$pkg_dir/package.nix" From 25494d5b889f00fe4e70deb14e2974681b23952d Mon Sep 17 00:00:00 2001 From: Josh Robson Chase Date: Mon, 12 Jan 2026 10:11:53 -0500 Subject: [PATCH 4/4] add more buildInputs to zed --- nix/build.nix | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/nix/build.nix b/nix/build.nix index 29653e53dc2b0f..2ae335afdc8ed2 100644 --- a/nix/build.nix +++ b/nix/build.nix @@ -26,10 +26,17 @@ freetype, git, glib, + libdrm, + libgbm, libgit2, libglvnd, + libva, libxkbcommon, - livekit-libwebrtc, + libxcomposite, + libxdamage, + libxext, + libxfixes, + libxrandr, nodejs_22, openssl, perl, @@ -129,10 +136,18 @@ let fontconfig freetype glib + libdrm + libgbm + libva # TODO: need staticlib of this for linking the musl remote server. # should make it a separate derivation/flake output # see https://crane.dev/examples/cross-musl.html libgit2 + libxcomposite + libxdamage + libxext + libxfixes + libxrandr openssl sqlite zlib