Compare commits

...

3 Commits

Author SHA1 Message Date
mgsloan@gmail.com
a03d59be7c Further progress on linux screen capture
In my testing this doesn't work on X11, frames aren't getting
captured. Might work on Wayland
2024-12-07 14:37:25 -07:00
mgsloan@gmail.com
42a125aa19 WIP use of scap for linux screencapture
Co-authored-by: Mikayla <mikayla@zed.dev>
2024-12-06 15:14:35 -07:00
mgsloan@gmail.com
3e0630e227 WIP plumbing preparing for Linux screen capture 2024-12-05 15:11:25 -08:00
20 changed files with 629 additions and 75 deletions

345
Cargo.lock generated
View File

@@ -184,6 +184,16 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "annotate-snippets"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e"
dependencies = [
"unicode-width",
"yansi-term",
]
[[package]]
name = "anstream"
version = "0.6.18"
@@ -1681,6 +1691,27 @@ dependencies = [
"serde",
]
[[package]]
name = "bindgen"
version = "0.69.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
dependencies = [
"annotate-snippets",
"bitflags 2.6.0",
"cexpr",
"clang-sys",
"itertools 0.12.1",
"lazy_static",
"lazycell",
"proc-macro2",
"quote",
"regex",
"rustc-hash 1.1.0",
"shlex",
"syn 2.0.87",
]
[[package]]
name = "bindgen"
version = "0.70.1"
@@ -2976,6 +3007,15 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "cookie-factory"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2"
dependencies = [
"futures 0.3.31",
]
[[package]]
name = "copilot"
version = "0.1.0"
@@ -3069,6 +3109,19 @@ 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.6.0",
"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"
@@ -3140,7 +3193,7 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ce857aa0b77d77287acc1ac3e37a05a8c95a2af3647d23b15f263bdaeb7562b"
dependencies = [
"bindgen",
"bindgen 0.70.1",
]
[[package]]
@@ -3480,9 +3533,9 @@ dependencies = [
[[package]]
name = "ctor"
version = "0.2.8"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
dependencies = [
"quote",
"syn 2.0.87",
@@ -3494,7 +3547,7 @@ version = "3.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
dependencies = [
"nix",
"nix 0.29.0",
"windows-sys 0.59.0",
]
@@ -3796,6 +3849,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "dispatch"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
[[package]]
name = "displaydoc"
version = "0.2.5"
@@ -4517,7 +4576,7 @@ dependencies = [
"serde_derive",
"serde_json",
"smol",
"sysinfo",
"sysinfo 0.31.4",
"ui",
"urlencoding",
"util",
@@ -5295,7 +5354,7 @@ dependencies = [
"ashpd",
"async-task",
"backtrace",
"bindgen",
"bindgen 0.70.1",
"blade-graphics",
"blade-macros",
"blade-util",
@@ -5342,6 +5401,7 @@ dependencies = [
"raw-window-handle",
"refineable",
"resvg",
"scap",
"schemars",
"seahash",
"semantic_version",
@@ -5906,7 +5966,7 @@ dependencies = [
"http 1.1.0",
"hyper 1.5.0",
"hyper-util",
"rustls 0.23.18",
"rustls 0.23.16",
"rustls-native-certs 0.8.0",
"rustls-pki-types",
"tokio",
@@ -6917,6 +6977,12 @@ dependencies = [
"spin",
]
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "leb128"
version = "0.2.5"
@@ -6931,9 +6997,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.162"
version = "0.2.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
[[package]]
name = "libdbus-sys"
@@ -7004,6 +7070,34 @@ dependencies = [
"redox_syscall 0.5.7",
]
[[package]]
name = "libspa"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65f3a4b81b2a2d8c7f300643676202debd1b7c929dbf5c9bb89402ea11d19810"
dependencies = [
"bitflags 2.6.0",
"cc",
"convert_case 0.6.0",
"cookie-factory",
"libc",
"libspa-sys",
"nix 0.27.1",
"nom",
"system-deps",
]
[[package]]
name = "libspa-sys"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f"
dependencies = [
"bindgen 0.69.5",
"cc",
"system-deps",
]
[[package]]
name = "libsqlite3-sys"
version = "0.30.1"
@@ -7203,6 +7297,7 @@ dependencies = [
"nanoid",
"parking_lot",
"postage",
"scap",
"serde",
"serde_json",
"sha2",
@@ -7531,7 +7626,7 @@ name = "media"
version = "0.1.0"
dependencies = [
"anyhow",
"bindgen",
"bindgen 0.70.1",
"core-foundation 0.9.4",
"ctor",
"foreign-types 0.5.0",
@@ -7822,6 +7917,17 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "nix"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
"libc",
]
[[package]]
name = "nix"
version = "0.29.0"
@@ -8148,6 +8254,36 @@ 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]]
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]]
@@ -9250,6 +9386,34 @@ dependencies = [
"futures-io",
]
[[package]]
name = "pipewire"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08e645ba5c45109106d56610b3ee60eb13a6f2beb8b74f8dc8186cf261788dda"
dependencies = [
"anyhow",
"bitflags 2.6.0",
"libc",
"libspa",
"libspa-sys",
"nix 0.27.1",
"once_cell",
"pipewire-sys",
"thiserror 1.0.69",
]
[[package]]
name = "pipewire-sys"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112"
dependencies = [
"bindgen 0.69.5",
"libspa-sys",
"system-deps",
]
[[package]]
name = "pkcs1"
version = "0.7.5"
@@ -9903,7 +10067,7 @@ dependencies = [
"quinn-proto",
"quinn-udp",
"rustc-hash 2.0.0",
"rustls 0.23.18",
"rustls 0.23.16",
"socket2 0.5.7",
"thiserror 2.0.3",
"tokio",
@@ -9921,7 +10085,7 @@ dependencies = [
"rand 0.8.5",
"ring",
"rustc-hash 2.0.0",
"rustls 0.23.18",
"rustls 0.23.16",
"rustls-pki-types",
"slab",
"thiserror 2.0.3",
@@ -10346,7 +10510,7 @@ dependencies = [
"settings",
"shellexpand 2.1.2",
"smol",
"sysinfo",
"sysinfo 0.31.4",
"telemetry_events",
"toml 0.8.19",
"util",
@@ -10480,7 +10644,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls 0.23.18",
"rustls 0.23.16",
"rustls-native-certs 0.8.0",
"rustls-pemfile 2.2.0",
"rustls-pki-types",
@@ -10868,9 +11032,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.18"
version = "0.23.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f"
checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e"
dependencies = [
"once_cell",
"ring",
@@ -11000,6 +11164,26 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scap"
version = "0.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14ca85dd7aa5f0e11e093c1c32b306666a52903db1bd89aa6442967a03cd8bd2"
dependencies = [
"cocoa 0.25.0",
"core-graphics-helmer-fork",
"dbus",
"objc",
"pipewire",
"rand 0.8.5",
"screencapturekit",
"screencapturekit-sys",
"sysinfo 0.30.13",
"tao-core-video-sys",
"windows 0.58.0",
"windows-capture",
]
[[package]]
name = "schannel"
version = "0.1.26"
@@ -11052,6 +11236,29 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
[[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 = "scrypt"
version = "0.11.0"
@@ -11359,9 +11566,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.132"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"indexmap 2.6.0",
"itoa",
@@ -11915,9 +12122,9 @@ dependencies = [
[[package]]
name = "sqlx"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcfa89bea9500db4a0d038513d7a060566bfc51d46d1c014847049a45cce85e8"
checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e"
dependencies = [
"sqlx-core",
"sqlx-macros",
@@ -11928,9 +12135,9 @@ dependencies = [
[[package]]
name = "sqlx-core"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d06e2f2bd861719b1f3f0c7dbe1d80c30bf59e76cf019f07d9014ed7eefb8e08"
checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e"
dependencies = [
"atoi",
"bigdecimal",
@@ -11956,7 +12163,7 @@ dependencies = [
"paste",
"percent-encoding",
"rust_decimal",
"rustls 0.23.18",
"rustls 0.23.16",
"rustls-pemfile 2.2.0",
"serde",
"serde_json",
@@ -11975,9 +12182,9 @@ dependencies = [
[[package]]
name = "sqlx-macros"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f998a9defdbd48ed005a89362bd40dd2117502f15294f61c8d47034107dbbdc"
checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657"
dependencies = [
"proc-macro2",
"quote",
@@ -11988,9 +12195,9 @@ dependencies = [
[[package]]
name = "sqlx-macros-core"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d100558134176a2629d46cec0c8891ba0be8910f7896abfdb75ef4ab6f4e7ce"
checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5"
dependencies = [
"dotenvy",
"either",
@@ -12014,9 +12221,9 @@ dependencies = [
[[package]]
name = "sqlx-mysql"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936cac0ab331b14cb3921c62156d913e4c15b74fb6ec0f3146bd4ef6e4fb3c12"
checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a"
dependencies = [
"atoi",
"base64 0.22.1",
@@ -12061,9 +12268,9 @@ dependencies = [
[[package]]
name = "sqlx-postgres"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9734dbce698c67ecf67c442f768a5e90a49b2a4d61a9f1d59f73874bd4cf0710"
checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8"
dependencies = [
"atoi",
"base64 0.22.1",
@@ -12105,9 +12312,9 @@ dependencies = [
[[package]]
name = "sqlx-sqlite"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75b419c3c1b1697833dd927bdc4c6545a620bc1bbafabd44e1efbe9afcd337e"
checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680"
dependencies = [
"atoi",
"chrono",
@@ -12490,6 +12697,21 @@ dependencies = [
"libc",
]
[[package]]
name = "sysinfo"
version = "0.30.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3"
dependencies = [
"cfg-if",
"core-foundation-sys",
"libc",
"ntapi",
"once_cell",
"rayon",
"windows 0.52.0",
]
[[package]]
name = "sysinfo"
version = "0.31.4"
@@ -12617,6 +12839,18 @@ 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"
@@ -12733,7 +12967,7 @@ dependencies = [
"serde_derive",
"settings",
"smol",
"sysinfo",
"sysinfo 0.31.4",
"task",
"theme",
"thiserror 1.0.69",
@@ -13181,7 +13415,7 @@ version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
"rustls 0.23.18",
"rustls 0.23.16",
"rustls-pki-types",
"tokio",
]
@@ -15041,6 +15275,16 @@ dependencies = [
"wasmtime-environ",
]
[[package]]
name = "windows"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
dependencies = [
"windows-core 0.52.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows"
version = "0.54.0"
@@ -15071,6 +15315,20 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-capture"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6001b777f61cafce437201de46a019ed7f4afed3b669f02e5ce4e0759164cb3e"
dependencies = [
"clap",
"ctrlc",
"parking_lot",
"rayon",
"thiserror 1.0.69",
"windows 0.58.0",
]
[[package]]
name = "windows-core"
version = "0.52.0"
@@ -15857,6 +16115,15 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "yansi-term"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
dependencies = [
"winapi",
]
[[package]]
name = "yazi"
version = "0.1.6"
@@ -15920,7 +16187,7 @@ dependencies = [
"futures-sink",
"futures-util",
"hex",
"nix",
"nix 0.29.0",
"ordered-stream",
"rand 0.8.5",
"serde",
@@ -15957,7 +16224,7 @@ dependencies = [
"futures-core",
"futures-util",
"hex",
"nix",
"nix 0.29.0",
"ordered-stream",
"serde",
"serde_repr",
@@ -16085,7 +16352,7 @@ dependencies = [
"markdown_preview",
"menu",
"mimalloc",
"nix",
"nix 0.29.0",
"node_runtime",
"notifications",
"outline",
@@ -16116,7 +16383,7 @@ dependencies = [
"snippet_provider",
"snippets_ui",
"supermaven",
"sysinfo",
"sysinfo 0.31.4",
"tab_switcher",
"task",
"tasks_ui",

View File

@@ -441,6 +441,7 @@ rustc-demangle = "0.1.23"
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustls = "0.21.12"
rustls-native-certs = "0.8.0"
scap = "0.0.7"
schemars = { version = "0.8", features = ["impl_json_schema"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] }

View File

@@ -40,6 +40,7 @@ wayland = [
"filedescriptor",
"xkbcommon",
"open",
"scap",
]
x11 = [
"blade-graphics",
@@ -56,6 +57,7 @@ x11 = [
"x11-clipboard",
"filedescriptor",
"open",
"scap",
]
@@ -160,6 +162,7 @@ font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "40391b7"
calloop = { version = "0.13.0" }
filedescriptor = { version = "0.8.2", optional = true }
open = { version = "5.2.0", optional = true }
scap = { workspace = true, optional = true }
# Wayland
calloop-wayland-source = { version = "0.3.0", optional = true }

View File

@@ -237,20 +237,20 @@ pub trait PlatformDisplay: Send + Sync + Debug {
}
/// A source of on-screen video content that can be captured.
pub trait ScreenCaptureSource {
pub trait ScreenCaptureSource: Send {
/// Returns the video resolution of this source.
fn resolution(&self) -> Result<Size<Pixels>>;
fn resolution(&self) -> Result<Size<DevicePixels>>;
/// Start capture video from this source, invoking the given callback
/// with each frame.
fn stream(
&self,
frame_callback: Box<dyn Fn(ScreenCaptureFrame)>,
frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>>;
}
/// A video stream captured from a screen.
pub trait ScreenCaptureStream {}
pub trait ScreenCaptureStream: Send {}
/// A frame of video captured from a screen.
pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);

View File

@@ -21,4 +21,4 @@ pub(crate) use wayland::*;
#[cfg(feature = "x11")]
pub(crate) use x11::*;
pub(crate) type PlatformScreenCaptureFrame = ();
pub type PlatformScreenCaptureFrame = platform::ScapFrame;

View File

@@ -3,11 +3,14 @@ use std::rc::Rc;
use calloop::{EventLoop, LoopHandle};
use futures::channel::oneshot;
use util::ResultExt;
use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, WindowParams};
use crate::{
AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, ScreenCaptureSource, WindowParams,
};
pub struct HeadlessClientState {
pub(crate) _loop_handle: LoopHandle<'static, HeadlessClient>,
@@ -59,6 +62,17 @@ impl LinuxClient for HeadlessClient {
None
}
fn screen_capture_sources(
&self,
) -> oneshot::Receiver<anyhow::Result<Vec<Box<dyn ScreenCaptureSource>>>> {
let (mut tx, rx) = oneshot::channel();
tx.send(Err(anyhow::anyhow!(
"headless client does not support screen capture."
)))
.ok();
rx
}
fn active_window(&self) -> Option<AnyWindowHandle> {
None
}

View File

@@ -10,6 +10,7 @@ use std::ops::{Deref, DerefMut};
use std::os::fd::{AsFd, AsRawFd, FromRawFd};
use std::panic::Location;
use std::rc::Weak;
use std::sync::atomic::AtomicBool;
use std::{
path::{Path, PathBuf},
process::Command,
@@ -32,11 +33,12 @@ use xkbcommon::xkb::{self, Keycode, Keysym, State};
use crate::platform::NoopTextSystem;
use crate::{
px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers, OwnedMenu,
PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler, PlatformTextSystem,
PlatformWindow, Point, PromptLevel, Result, ScreenCaptureSource, SemanticVersion, SharedString,
Size, Task, WindowAppearance, WindowOptions, WindowParams,
px, size, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle,
DevicePixels, DisplayId, ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu,
MenuItem, Modifiers, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay,
PlatformInputHandler, PlatformTextSystem, PlatformWindow, Point, PromptLevel, Result,
ScreenCaptureSource, ScreenCaptureStream, SemanticVersion, SharedString, Size, Task,
WindowAppearance, WindowOptions, WindowParams,
};
pub(crate) const SCROLL_LINES: f32 = 3.0;
@@ -56,6 +58,9 @@ pub trait LinuxClient {
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
fn screen_capture_sources(
&self,
) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>>;
fn open_window(
&self,
@@ -245,9 +250,7 @@ impl<P: LinuxClient + 'static> Platform for P {
fn screen_capture_sources(
&self,
) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>> {
let (mut tx, rx) = oneshot::channel();
tx.send(Err(anyhow!("screen capture not implemented"))).ok();
rx
self.screen_capture_sources()
}
fn active_window(&self) -> Option<AnyWindowHandle> {
@@ -843,6 +846,31 @@ impl Modifiers {
}
}
pub fn new_scap_capturer(target: Option<scap::Target>) -> anyhow::Result<scap::capturer::Capturer> {
Ok(scap::capturer::Capturer::build(scap::capturer::Options {
fps: 60,
show_cursor: true,
show_highlight: true,
output_type: scap::frame::FrameType::YUVFrame,
output_resolution: scap::capturer::Resolution::Captured,
crop_area: None,
target,
excluded_targets: None,
})?)
}
pub struct ScapFrame(pub scap::frame::Frame);
pub struct ScapStream(pub Arc<AtomicBool>);
impl ScreenCaptureStream for ScapStream {}
impl Drop for ScapStream {
fn drop(&mut self) {
self.0.store(true, std::sync::atomic::Ordering::SeqCst);
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -2,6 +2,7 @@ mod client;
mod clipboard;
mod cursor;
mod display;
mod screen_capture;
mod serial;
mod window;

View File

@@ -11,6 +11,8 @@ use calloop_wayland_source::WaylandSource;
use collections::HashMap;
use filedescriptor::Pipe;
use futures::channel::oneshot;
use http_client::Url;
use smallvec::SmallVec;
use util::ResultExt;
@@ -68,6 +70,7 @@ use crate::platform::linux::wayland::clipboard::{
Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE,
};
use crate::platform::linux::wayland::cursor::Cursor;
use crate::platform::linux::wayland::screen_capture::wayland_screen_capture_sources;
use crate::platform::linux::wayland::serial::{SerialKind, SerialTracker};
use crate::platform::linux::wayland::window::WaylandWindow;
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
@@ -78,16 +81,13 @@ use crate::platform::linux::{
};
use crate::platform::PlatformWindow;
use crate::{
point, px, size, Bounds, DevicePixels, FileDropEvent, ForegroundExecutor, MouseExitEvent, Size,
point, px, size, AnyWindowHandle, Bounds, CursorStyle, DevicePixels, DisplayId, FileDropEvent,
ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent,
MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScaledPixels,
ScreenCaptureSource, ScrollDelta, ScrollWheelEvent, Size, TouchPhase, WindowParams,
DOUBLE_CLICK_INTERVAL, SCROLL_LINES,
};
use crate::{
AnyWindowHandle, CursorStyle, DisplayId, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScaledPixels, ScrollDelta,
ScrollWheelEvent, TouchPhase,
};
use crate::{LinuxCommon, WindowParams};
/// Used to convert evdev scancode to xkb scancode
const MIN_KEYCODE: u32 = 8;
@@ -617,6 +617,12 @@ impl LinuxClient for WaylandClient {
None
}
fn screen_capture_sources(
&self,
) -> oneshot::Receiver<anyhow::Result<Vec<Box<dyn ScreenCaptureSource>>>> {
wayland_screen_capture_sources()
}
fn open_window(
&self,
handle: AnyWindowHandle,

View File

@@ -0,0 +1,124 @@
use crate::{
new_scap_capturer, size, DevicePixels, ScapFrame, ScapStream, ScreenCaptureFrame,
ScreenCaptureSource, ScreenCaptureStream, Size,
};
use anyhow::anyhow;
use futures::channel::oneshot;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
struct ScapCapturer {
stream_tx: std::sync::mpsc::Sender<(
oneshot::Sender<anyhow::Result<Box<dyn ScreenCaptureStream>>>,
Box<dyn Fn(ScreenCaptureFrame) + Send>,
)>,
size: Size<DevicePixels>,
}
impl ScreenCaptureSource for ScapCapturer {
fn resolution(&self) -> anyhow::Result<Size<DevicePixels>> {
Ok(self.size)
}
fn stream(
&self,
frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
) -> oneshot::Receiver<anyhow::Result<Box<dyn ScreenCaptureStream>>> {
let (tx, rx) = oneshot::channel();
self.stream_tx.send((tx, frame_callback)).ok();
rx
}
}
/// Requests that wayland prompts the user about which screen or window to capture. The receiver
/// will be filled with a capture source.
pub fn wayland_screen_capture_sources(
) -> oneshot::Receiver<anyhow::Result<Vec<Box<dyn ScreenCaptureSource>>>> {
let (mut tx, result_rx) = oneshot::channel();
// Due to use of blocking APIs a dedicated thread is used.
std::thread::spawn(|| {
let (stream_tx, stream_rx) = std::sync::mpsc::channel();
let screen_capturer = util::maybe!({
// TODO: needed?
if !scap::has_permission() {
if !scap::request_permission() {
Err(anyhow!("No permissions to share screen"))?;
}
}
let mut capturer = new_scap_capturer(None)?;
// Screen capture needs to start immediately so that the size can be determined.
// In Zed the size is needed in order to initialize the LiveKit video channel.
//
// FIXME: can this be done way simpler in capture_local_video_track?
capturer.start_capture();
let size = match capturer.get_next_frame() {
Ok(frame) => get_frame_size(&frame),
Err(std::sync::mpsc::RecvError) => Err(anyhow!(
"Failed to get first frame of screenshare to get the size."
))?,
};
Ok((
capturer,
vec![Box::new(ScapCapturer { stream_tx, size }) as Box<dyn ScreenCaptureSource>],
))
});
match screen_capturer {
Err(e) => {
tx.send(Err(e)).ok();
}
Ok((mut capturer, sources)) => {
tx.send(Ok(sources)).ok();
while let Ok((tx, callback)) = stream_rx.recv() {
let cancel_stream = Arc::new(AtomicBool::new(false));
tx.send(Ok(Box::new(ScapStream(cancel_stream.clone()))))
.ok();
while cancel_stream.load(std::sync::atomic::Ordering::SeqCst) {
match capturer.get_next_frame() {
Ok(frame) => callback(ScreenCaptureFrame(ScapFrame(frame))),
Err(std::sync::mpsc::RecvError) => {
break;
}
}
}
}
capturer.stop_capture();
}
}
});
result_rx
}
fn get_frame_size(frame: &scap::frame::Frame) -> Size<DevicePixels> {
match frame {
scap::frame::Frame::YUVFrame(frame) => {
size(DevicePixels(frame.width), DevicePixels(frame.height))
}
scap::frame::Frame::RGB(frame) => {
size(DevicePixels(frame.width), DevicePixels(frame.height))
}
scap::frame::Frame::RGBx(frame) => {
size(DevicePixels(frame.width), DevicePixels(frame.height))
}
scap::frame::Frame::XBGR(frame) => {
size(DevicePixels(frame.width), DevicePixels(frame.height))
}
scap::frame::Frame::BGRx(frame) => {
size(DevicePixels(frame.width), DevicePixels(frame.height))
}
scap::frame::Frame::BGR0(frame) => {
size(DevicePixels(frame.width), DevicePixels(frame.height))
}
scap::frame::Frame::BGRA(frame) => {
size(DevicePixels(frame.width), DevicePixels(frame.height))
}
}
}

View File

@@ -1,6 +1,7 @@
mod client;
mod display;
mod event;
mod screen_capture;
mod window;
mod xim_handler;

View File

@@ -10,6 +10,7 @@ use calloop::generic::{FdWrapper, Generic};
use calloop::{EventLoop, LoopHandle, RegistrationToken};
use collections::HashMap;
use futures::channel::oneshot;
use http_client::Url;
use smallvec::SmallVec;
use util::ResultExt;
@@ -39,7 +40,7 @@ use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Pixels,
Platform, PlatformDisplay, PlatformInput, Point, RequestFrameOptions, ScaledPixels,
ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
ScreenCaptureSource, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
};
use super::{
@@ -49,6 +50,7 @@ use super::{
use super::{X11Display, X11WindowStatePtr, XcbAtoms};
use super::{XimCallbackEvent, XimHandler};
use crate::platform::linux::platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES};
use crate::platform::linux::x11::screen_capture::x11_screen_capture_sources;
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
use crate::platform::linux::{
get_xkb_compose_state, is_within_click_distance, open_uri_internal, reveal_path_internal,
@@ -1250,6 +1252,14 @@ impl LinuxClient for X11Client {
f(&mut self.0.borrow_mut().common)
}
fn screen_capture_sources(
&self,
) -> oneshot::Receiver<anyhow::Result<Vec<Box<dyn ScreenCaptureSource>>>> {
let (mut tx, result_rx) = oneshot::channel();
tx.send(x11_screen_capture_sources()).ok();
result_rx
}
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
let state = self.0.borrow();
let setup = state.xcb_connection.setup();

View File

@@ -0,0 +1,82 @@
use crate::{
new_scap_capturer, DevicePixels, ScapFrame, ScapStream, ScreenCaptureFrame,
ScreenCaptureSource, Size,
};
use anyhow::anyhow;
use futures::channel::oneshot;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
struct X11ScreenCaptureSource {
target: scap::Target,
size: Size<DevicePixels>,
}
impl ScreenCaptureSource for X11ScreenCaptureSource {
fn resolution(&self) -> anyhow::Result<Size<DevicePixels>> {
Ok(self.size)
}
fn stream(
&self,
frame_callback: Box<dyn Fn(crate::ScreenCaptureFrame) + Send>,
) -> oneshot::Receiver<anyhow::Result<Box<dyn crate::ScreenCaptureStream>>> {
let (tx, rx) = oneshot::channel();
// TODO: can clone be avoided here and elsewhere?
let target = self.target.clone();
// Due to use of blocking APIs a dedicated thread is used.
std::thread::spawn(move || {
let cancel_stream = Arc::new(AtomicBool::new(false));
let mut capturer = match new_scap_capturer(Some(target)) {
Ok(capturer) => {
tx.send(Ok(Box::new(ScapStream(cancel_stream.clone()))
as Box<dyn crate::ScreenCaptureStream>))
.ok();
capturer
}
Err(e) => {
tx.send(Err(e)).ok();
return;
}
};
while cancel_stream.load(std::sync::atomic::Ordering::SeqCst) {
match capturer.get_next_frame() {
Ok(frame) => frame_callback(ScreenCaptureFrame(ScapFrame(frame))),
Err(std::sync::mpsc::RecvError) => {
break;
}
}
}
capturer.stop_capture();
});
rx
}
}
pub fn x11_screen_capture_sources() -> anyhow::Result<Vec<Box<dyn ScreenCaptureSource>>> {
if !scap::has_permission() {
if !scap::request_permission() {
Err(anyhow!("No permissions to share screen"))?;
}
}
// TODO(mgsloan): Handle window capture too? On Mac it's only displays.
Ok(scap::get_all_targets()
.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(Box::new(X11ScreenCaptureSource {
target: target.clone(),
size,
}) as Box<dyn ScreenCaptureSource>)
}
scap::Target::Window(_) => None,
})
.collect::<Vec<_>>())
}

View File

@@ -43,7 +43,7 @@ const FRAME_CALLBACK_IVAR: &str = "frame_callback";
const SCStreamOutputTypeScreen: NSInteger = 0;
impl ScreenCaptureSource for MacScreenCaptureSource {
fn resolution(&self) -> Result<Size<Pixels>> {
fn resolution(&self) -> Result<Size<DevicePixels>> {
unsafe {
let width: i64 = msg_send![self.sc_display, width];
let height: i64 = msg_send![self.sc_display, height];

View File

@@ -1,5 +1,5 @@
use crate::{
px, size, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor,
size, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor,
Keymap, Platform, PlatformDisplay, PlatformTextSystem, ScreenCaptureFrame, ScreenCaptureSource,
ScreenCaptureStream, Task, TestDisplay, TestWindow, WindowAppearance, WindowParams,
};
@@ -46,13 +46,13 @@ pub struct TestScreenCaptureSource {}
pub struct TestScreenCaptureStream {}
impl ScreenCaptureSource for TestScreenCaptureSource {
fn resolution(&self) -> Result<crate::Size<crate::Pixels>> {
Ok(size(px(1.), px(1.)))
fn resolution(&self) -> Result<crate::Size<crate::DevicePixels>> {
Ok(size(crate::DevicePixels(1), crate::DevicePixels(1)))
}
fn stream(
&self,
_frame_callback: Box<dyn Fn(ScreenCaptureFrame)>,
_frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
let (mut tx, rx) = oneshot::channel();
let stream = TestScreenCaptureStream {};

View File

@@ -21,5 +21,3 @@ pub(crate) use window::*;
pub(crate) use wrapper::*;
pub(crate) use windows::Win32::Foundation::HWND;
pub(crate) type PlatformScreenCaptureFrame = ();

View File

@@ -45,6 +45,7 @@ image.workspace = true
[target.'cfg(not(target_os = "windows"))'.dependencies]
livekit.workspace = true
scap.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation.workspace = true

View File

@@ -311,8 +311,8 @@ impl LivekitWindow {
let sources = cx.screen_capture_sources();
cx.spawn(|this, mut cx| async move {
let sources = sources.await.unwrap()?;
let source = sources.into_iter().next().unwrap();
let (track, stream) = capture_local_video_track(&*source).await?;
let mut source = sources.into_iter().next().unwrap();
let (track, stream) = capture_local_video_track(&mut *source).await?;
let publication = participant
.publish_track(
LocalTrack::Video(track),

View File

@@ -518,8 +518,26 @@ fn video_frame_buffer_to_webrtc(frame: ScreenCaptureFrame) -> Option<impl AsRef<
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn video_frame_buffer_to_webrtc(_frame: ScreenCaptureFrame) -> Option<impl AsRef<dyn VideoBuffer>> {
None as Option<Box<dyn VideoBuffer>>
fn video_frame_buffer_to_webrtc(frame: ScreenCaptureFrame) -> Option<impl AsRef<dyn VideoBuffer>> {
use livekit::webrtc::prelude::NV12Buffer;
match frame.0 .0 {
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 YUV frame from scap but got some other format.");
None
}
}
}
trait DeviceChangeListenerApi: Stream<Item = ()> + Sized {

View File

@@ -297,8 +297,8 @@ impl TitleBar {
let can_use_microphone = room.can_use_microphone(cx);
let can_share_projects = room.can_share_projects();
let screen_sharing_supported = match self.platform_style {
PlatformStyle::Mac => true,
PlatformStyle::Linux | PlatformStyle::Windows => false,
PlatformStyle::Mac | PlatformStyle::Linux => true,
PlatformStyle::Windows => false,
};
let mut children = Vec::new();