(feat): update grid view stuff
(feat): add desktop app screensharing
This commit is contained in:
parent
8e294d230e
commit
7b5cdca0ff
11 changed files with 1469 additions and 87 deletions
|
|
@ -4,10 +4,22 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
"./context": "./src/context.tsx",
|
"./context": {
|
||||||
"./controls": "./src/windowControls.tsx",
|
"types": "./src/context.tsx",
|
||||||
"./deeplinkHandler": "./src/deeplinkHandler.tsx",
|
"default": "./src/context.tsx"
|
||||||
"./qrCodeScanner": "./src/qrCodeScanner.tsx"
|
},
|
||||||
|
"./controls": {
|
||||||
|
"types": "./src/windowControls.tsx",
|
||||||
|
"default": "./src/windowControls.tsx"
|
||||||
|
},
|
||||||
|
"./deeplinkHandler": {
|
||||||
|
"types": "./src/deeplinkHandler.tsx",
|
||||||
|
"default": "./src/deeplinkHandler.tsx"
|
||||||
|
},
|
||||||
|
"./qrCodeScanner": {
|
||||||
|
"types": "./src/qrCodeScanner.tsx",
|
||||||
|
"default": "./src/qrCodeScanner.tsx"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:mobile:raw": "tauri android dev",
|
"dev:mobile:raw": "tauri android dev",
|
||||||
|
|
|
||||||
531
apps/tauri/src-tauri/Cargo.lock
generated
531
apps/tauri/src-tauri/Cargo.lock
generated
|
|
@ -375,6 +375,12 @@ version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
|
|
@ -588,6 +594,12 @@ dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color_quant"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
|
|
@ -853,6 +865,17 @@ dependencies = [
|
||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dbus"
|
||||||
|
version = "0.9.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libdbus-sys",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.5.8"
|
version = "0.5.8"
|
||||||
|
|
@ -934,6 +957,18 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dispatch2"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a0d569e003ff27784e0e14e4a594048698e0c0f0b66cabcb51511be55a7caa0"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"block2",
|
||||||
|
"libc",
|
||||||
|
"objc2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dispatch2"
|
name = "dispatch2"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
@ -1013,6 +1048,12 @@ dependencies = [
|
||||||
"tendril 0.5.0",
|
"tendril 0.5.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "downcast-rs"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "download-cef"
|
name = "download-cef"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
|
|
@ -1868,7 +1909,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
|
checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"png",
|
"png 0.17.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1986,6 +2027,33 @@ dependencies = [
|
||||||
"icu_properties",
|
"icu_properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.24.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder",
|
||||||
|
"color_quant",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.25.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder-lite",
|
||||||
|
"moxcms",
|
||||||
|
"num-traits",
|
||||||
|
"png 0.18.1",
|
||||||
|
"zune-core",
|
||||||
|
"zune-jpeg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "1.9.3"
|
||||||
|
|
@ -2202,6 +2270,12 @@ dependencies = [
|
||||||
"selectors 0.24.0",
|
"selectors 0.24.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leb128fmt"
|
name = "leb128fmt"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -2244,6 +2318,15 @@ version = "0.2.184"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
|
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libdbus-sys"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
|
||||||
|
dependencies = [
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
|
|
@ -2276,6 +2359,22 @@ dependencies = [
|
||||||
"redox_syscall 0.7.3",
|
"redox_syscall 0.7.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libwayshot"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2efa01ecfd021b1e7db27f21f4e79b35b048081c9cae9d2f898eddc98444d69"
|
||||||
|
dependencies = [
|
||||||
|
"image 0.24.9",
|
||||||
|
"log",
|
||||||
|
"memmap2",
|
||||||
|
"nix",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-protocols",
|
||||||
|
"wayland-protocols-wlr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
|
@ -2363,6 +2462,15 @@ version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memmap2"
|
||||||
|
version = "0.9.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memoffset"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
|
|
@ -2399,6 +2507,16 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moxcms"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"pxfm",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "muda"
|
name = "muda"
|
||||||
version = "0.17.2"
|
version = "0.17.2"
|
||||||
|
|
@ -2414,7 +2532,7 @@ dependencies = [
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"png",
|
"png 0.17.16",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
|
|
@ -2456,6 +2574,17 @@ version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.27.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nodrop"
|
name = "nodrop"
|
||||||
version = "0.1.14"
|
version = "0.1.14"
|
||||||
|
|
@ -2530,6 +2659,38 @@ dependencies = [
|
||||||
"objc2-quartz-core",
|
"objc2-quartz-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-av-foundation"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "478ae33fcac9df0a18db8302387c666b8ef08a3e2d62b510ca4fc278a384b6c0"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"block2",
|
||||||
|
"dispatch2 0.3.1",
|
||||||
|
"objc2",
|
||||||
|
"objc2-avf-audio",
|
||||||
|
"objc2-core-audio-types",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-core-graphics",
|
||||||
|
"objc2-core-image",
|
||||||
|
"objc2-core-video",
|
||||||
|
"objc2-foundation",
|
||||||
|
"objc2-image-io",
|
||||||
|
"objc2-media-toolbox",
|
||||||
|
"objc2-quartz-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-avf-audio"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be"
|
||||||
|
dependencies = [
|
||||||
|
"objc2",
|
||||||
|
"objc2-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-cloud-kit"
|
name = "objc2-cloud-kit"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
@ -2541,6 +2702,28 @@ dependencies = [
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-audio"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
|
||||||
|
dependencies = [
|
||||||
|
"dispatch2 0.3.1",
|
||||||
|
"objc2",
|
||||||
|
"objc2-core-audio-types",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-audio-types"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"objc2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-data"
|
name = "objc2-core-data"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
@ -2559,7 +2742,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"dispatch2",
|
"block2",
|
||||||
|
"dispatch2 0.3.1",
|
||||||
|
"libc",
|
||||||
"objc2",
|
"objc2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2570,10 +2755,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"dispatch2",
|
"block2",
|
||||||
|
"dispatch2 0.3.1",
|
||||||
|
"libc",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-io-surface",
|
"objc2-io-surface",
|
||||||
|
"objc2-metal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2586,6 +2774,22 @@ dependencies = [
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-media"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"block2",
|
||||||
|
"dispatch2 0.3.1",
|
||||||
|
"objc2",
|
||||||
|
"objc2-core-audio",
|
||||||
|
"objc2-core-audio-types",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-core-video",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-text"
|
name = "objc2-core-text"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
@ -2605,10 +2809,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6"
|
checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
|
"block2",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
"objc2-io-surface",
|
"objc2-io-surface",
|
||||||
|
"objc2-metal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2639,6 +2845,17 @@ dependencies = [
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-image-io"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32b0446e98cf4a784cc7a0177715ff317eeaa8463841c616cfc78aa4f953c4ea"
|
||||||
|
dependencies = [
|
||||||
|
"objc2",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-core-graphics",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-io-surface"
|
name = "objc2-io-surface"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
@ -2660,6 +2877,29 @@ dependencies = [
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-media-toolbox"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edd9fdde720df3da7046bb9097811000c1e7ab5cd579fa89d96b27d56781fb30"
|
||||||
|
dependencies = [
|
||||||
|
"objc2",
|
||||||
|
"objc2-core-audio-types",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-core-media",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-metal"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"objc2",
|
||||||
|
"objc2-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-quartz-core"
|
name = "objc2-quartz-core"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
@ -3051,7 +3291,7 @@ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"indexmap 2.13.1",
|
"indexmap 2.13.1",
|
||||||
"quick-xml",
|
"quick-xml 0.38.4",
|
||||||
"serde",
|
"serde",
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
@ -3069,6 +3309,19 @@ dependencies = [
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "polling"
|
name = "polling"
|
||||||
version = "3.11.0"
|
version = "3.11.0"
|
||||||
|
|
@ -3197,6 +3450,21 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pxfm"
|
||||||
|
version = "0.1.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-xml"
|
||||||
|
version = "0.30.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.38.4"
|
version = "0.38.4"
|
||||||
|
|
@ -3206,6 +3474,15 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-xml"
|
||||||
|
version = "0.39.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.45"
|
version = "1.0.45"
|
||||||
|
|
@ -4091,7 +4368,7 @@ dependencies = [
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dispatch2",
|
"dispatch2 0.3.1",
|
||||||
"dlopen2",
|
"dlopen2",
|
||||||
"dpi",
|
"dpi",
|
||||||
"gdkwayland-sys",
|
"gdkwayland-sys",
|
||||||
|
|
@ -4112,7 +4389,7 @@ dependencies = [
|
||||||
"tao-macros",
|
"tao-macros",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"url",
|
"url",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-version",
|
"windows-version",
|
||||||
"x11-dl",
|
"x11-dl",
|
||||||
|
|
@ -4194,7 +4471,7 @@ dependencies = [
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
"window-vibrancy",
|
"window-vibrancy",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4250,7 +4527,7 @@ dependencies = [
|
||||||
"ico",
|
"ico",
|
||||||
"json-patch",
|
"json-patch",
|
||||||
"plist",
|
"plist",
|
||||||
"png",
|
"png 0.17.16",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"semver",
|
"semver",
|
||||||
|
|
@ -4349,7 +4626,7 @@ dependencies = [
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"url",
|
"url",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -4371,7 +4648,7 @@ dependencies = [
|
||||||
"tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)",
|
"tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"url",
|
"url",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4398,7 +4675,7 @@ dependencies = [
|
||||||
"tauri-runtime",
|
"tauri-runtime",
|
||||||
"tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)",
|
"tauri-utils 2.8.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)",
|
||||||
"url",
|
"url",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"x11-dl",
|
"x11-dl",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -4424,7 +4701,7 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"wry",
|
"wry",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -4551,6 +4828,8 @@ dependencies = [
|
||||||
name = "tensamin"
|
name = "tensamin"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"image 0.25.10",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
|
|
@ -4558,6 +4837,7 @@ dependencies = [
|
||||||
"tauri-plugin-barcode-scanner",
|
"tauri-plugin-barcode-scanner",
|
||||||
"tauri-plugin-deep-link",
|
"tauri-plugin-deep-link",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
|
"xcap",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4874,7 +5154,7 @@ dependencies = [
|
||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"png",
|
"png 0.17.16",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
|
|
@ -5263,6 +5543,76 @@ dependencies = [
|
||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-backend"
|
||||||
|
version = "0.3.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"downcast-rs",
|
||||||
|
"rustix",
|
||||||
|
"smallvec",
|
||||||
|
"wayland-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-client"
|
||||||
|
version = "0.31.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"rustix",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-scanner",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-protocols"
|
||||||
|
version = "0.31.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-scanner",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-protocols-wlr"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-protocols",
|
||||||
|
"wayland-scanner",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-scanner"
|
||||||
|
version = "0.31.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quick-xml 0.39.2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-sys"
|
||||||
|
version = "0.31.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
|
||||||
|
dependencies = [
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.94"
|
version = "0.3.94"
|
||||||
|
|
@ -5356,9 +5706,9 @@ checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"webview2-com-macros",
|
"webview2-com-macros",
|
||||||
"webview2-com-sys",
|
"webview2-com-sys",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-implement",
|
"windows-implement 0.60.2",
|
||||||
"windows-interface",
|
"windows-interface",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -5380,10 +5730,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
|
checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "widestring"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
@ -5430,17 +5786,39 @@ dependencies = [
|
||||||
"windows-version",
|
"windows-version",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.60.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddf874e74c7a99773e62b1c671427abf01a425e77c3d3fb9fb1e4883ea934529"
|
||||||
|
dependencies = [
|
||||||
|
"windows-collections 0.1.1",
|
||||||
|
"windows-core 0.60.1",
|
||||||
|
"windows-future 0.1.1",
|
||||||
|
"windows-link 0.1.3",
|
||||||
|
"windows-numerics 0.1.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows"
|
name = "windows"
|
||||||
version = "0.61.3"
|
version = "0.61.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-collections",
|
"windows-collections 0.2.0",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-future",
|
"windows-future 0.2.1",
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
"windows-numerics",
|
"windows-numerics 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-collections"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5467f79cc1ba3f52ebb2ed41dbb459b8e7db636cc3429458d9a852e15bc24dec"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core 0.60.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -5452,13 +5830,26 @@ dependencies = [
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.60.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement 0.59.0",
|
||||||
|
"windows-interface",
|
||||||
|
"windows-link 0.1.3",
|
||||||
|
"windows-result 0.3.4",
|
||||||
|
"windows-strings 0.3.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement",
|
"windows-implement 0.60.2",
|
||||||
"windows-interface",
|
"windows-interface",
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
"windows-result 0.3.4",
|
"windows-result 0.3.4",
|
||||||
|
|
@ -5471,13 +5862,23 @@ version = "0.62.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement",
|
"windows-implement 0.60.2",
|
||||||
"windows-interface",
|
"windows-interface",
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
"windows-result 0.4.1",
|
"windows-result 0.4.1",
|
||||||
"windows-strings 0.5.1",
|
"windows-strings 0.5.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-future"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a787db4595e7eb80239b74ce8babfb1363d8e343ab072f2ffe901400c03349f0"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core 0.60.1",
|
||||||
|
"windows-link 0.1.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-future"
|
name = "windows-future"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
@ -5489,6 +5890,17 @@ dependencies = [
|
||||||
"windows-threading",
|
"windows-threading",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-implement"
|
name = "windows-implement"
|
||||||
version = "0.60.2"
|
version = "0.60.2"
|
||||||
|
|
@ -5523,6 +5935,16 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-numerics"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "005dea54e2f6499f2cee279b8f703b3cf3b5734a2d8d21867c8f44003182eeed"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core 0.60.1",
|
||||||
|
"windows-link 0.1.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-numerics"
|
name = "windows-numerics"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -5562,6 +5984,15 @@ dependencies = [
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-strings"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link 0.1.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-strings"
|
name = "windows-strings"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
|
@ -5998,7 +6429,7 @@ dependencies = [
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webkit2gtk-sys",
|
"webkit2gtk-sys",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
"windows",
|
"windows 0.61.3",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
"windows-version",
|
"windows-version",
|
||||||
"x11-dl",
|
"x11-dl",
|
||||||
|
|
@ -6035,6 +6466,45 @@ dependencies = [
|
||||||
"rustix",
|
"rustix",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xcap"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eabd25cdb442bb7f63f13fdee2f59d991b04668d37c69aee00dc2a1cc9d0e9a1"
|
||||||
|
dependencies = [
|
||||||
|
"dbus",
|
||||||
|
"dispatch2 0.2.0",
|
||||||
|
"image 0.25.10",
|
||||||
|
"lazy_static",
|
||||||
|
"libwayshot",
|
||||||
|
"log",
|
||||||
|
"objc2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"objc2-av-foundation",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-core-graphics",
|
||||||
|
"objc2-core-media",
|
||||||
|
"objc2-core-video",
|
||||||
|
"objc2-foundation",
|
||||||
|
"percent-encoding",
|
||||||
|
"scopeguard",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"widestring",
|
||||||
|
"windows 0.60.0",
|
||||||
|
"xcb",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xcb"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee4c580d8205abb0a5cf4eb7e927bd664e425b6c3263f9c5310583da96970cf6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"libc",
|
||||||
|
"quick-xml 0.30.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
|
@ -6205,6 +6675,21 @@ version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-core"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-jpeg"
|
||||||
|
version = "0.5.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
|
||||||
|
dependencies = [
|
||||||
|
"zune-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant"
|
name = "zvariant"
|
||||||
version = "5.10.0"
|
version = "5.10.0"
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,16 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
base64 = "0.22"
|
||||||
|
image = { version = "0.25", default-features = false, features = ["jpeg"] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tauri-plugin-deep-link = "2"
|
tauri-plugin-deep-link = "2"
|
||||||
|
|
||||||
|
[target.'cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))'.dependencies]
|
||||||
|
xcap = "0.4.1"
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies.tauri]
|
[target.'cfg(target_os = "android")'.dependencies.tauri]
|
||||||
version = "2"
|
version = "2"
|
||||||
features = []
|
features = []
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,235 @@
|
||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ScreenShareSource {
|
||||||
|
id: String,
|
||||||
|
kind: String,
|
||||||
|
name: String,
|
||||||
|
subtitle: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ScreenShareAudioOutput {
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
is_default: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ScreenShareCapabilities {
|
||||||
|
platform: String,
|
||||||
|
#[serde(rename = "usesPipeWireAudioPicker")]
|
||||||
|
uses_pipewire_audio_picker: bool,
|
||||||
|
#[serde(rename = "supportsSystemAudioSwitch")]
|
||||||
|
supports_system_audio_switch: bool,
|
||||||
|
#[serde(rename = "supportsReliableSystemAudio")]
|
||||||
|
supports_reliable_system_audio: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn greet(name: &str) -> String {
|
fn list_screen_share_sources() -> Result<Vec<ScreenShareSource>, String> {
|
||||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
|
||||||
|
{
|
||||||
|
use xcap::{Monitor, Window};
|
||||||
|
|
||||||
|
let mut sources = Vec::new();
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
match Monitor::all() {
|
||||||
|
Ok(monitors) => {
|
||||||
|
for (index, monitor) in monitors.into_iter().enumerate() {
|
||||||
|
let mut subtitle = None;
|
||||||
|
|
||||||
|
if monitor.is_primary().unwrap_or(false) {
|
||||||
|
subtitle = Some("Primary display".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
sources.push(ScreenShareSource {
|
||||||
|
id: format!("screen:{index}"),
|
||||||
|
kind: "screen".to_string(),
|
||||||
|
name: monitor
|
||||||
|
.name()
|
||||||
|
.unwrap_or_else(|_| format!("Display {}", index + 1)),
|
||||||
|
subtitle,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => errors.push(format!("display listing failed: {error}")),
|
||||||
|
}
|
||||||
|
|
||||||
|
match Window::all() {
|
||||||
|
Ok(windows) => {
|
||||||
|
for (index, window) in windows.into_iter().enumerate() {
|
||||||
|
if window.is_minimized().unwrap_or(false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = window.title().unwrap_or_default();
|
||||||
|
|
||||||
|
if title.trim().is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sources.push(ScreenShareSource {
|
||||||
|
id: format!("window:{index}"),
|
||||||
|
kind: "window".to_string(),
|
||||||
|
name: title,
|
||||||
|
subtitle: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => errors.push(format!("window listing failed: {error}")),
|
||||||
|
}
|
||||||
|
|
||||||
|
if sources.is_empty() {
|
||||||
|
if errors.is_empty() {
|
||||||
|
return Ok(sources);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(errors.join("; "));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(sources);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn list_screen_share_audio_outputs() -> Result<Vec<ScreenShareAudioOutput>, String> {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
let output = Command::new("pactl")
|
||||||
|
.args(["list", "short", "sinks"])
|
||||||
|
.output()
|
||||||
|
.map_err(|error| error.to_string())?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(String::from_utf8_lossy(&output.stderr).trim().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut outputs = Vec::new();
|
||||||
|
|
||||||
|
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||||
|
let mut parts = line.split('\t');
|
||||||
|
|
||||||
|
let Some(id) = parts.next() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(name) = parts.next() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let description = parts.next_back().unwrap_or(name).to_string();
|
||||||
|
|
||||||
|
outputs.push(ScreenShareAudioOutput {
|
||||||
|
id: id.to_string(),
|
||||||
|
name: description,
|
||||||
|
is_default: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(outputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn capture_screen_share_frame(source_id: &str) -> Result<String, String> {
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
|
||||||
|
{
|
||||||
|
use base64::Engine;
|
||||||
|
use image::{codecs::jpeg::JpegEncoder, DynamicImage};
|
||||||
|
use std::io::Cursor;
|
||||||
|
use xcap::{Monitor, Window};
|
||||||
|
|
||||||
|
let Some((kind, index)) = source_id.split_once(':') else {
|
||||||
|
return Err("Invalid source id".to_string());
|
||||||
|
};
|
||||||
|
|
||||||
|
let index = index
|
||||||
|
.parse::<usize>()
|
||||||
|
.map_err(|_| "Invalid source index".to_string())?;
|
||||||
|
|
||||||
|
let frame = match kind {
|
||||||
|
"screen" => Monitor::all()
|
||||||
|
.map_err(|error| error.to_string())?
|
||||||
|
.into_iter()
|
||||||
|
.nth(index)
|
||||||
|
.ok_or_else(|| "Screen source not found".to_string())?
|
||||||
|
.capture_image()
|
||||||
|
.map_err(|error| error.to_string())?,
|
||||||
|
"window" => Window::all()
|
||||||
|
.map_err(|error| error.to_string())?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|window| !window.is_minimized().unwrap_or(false))
|
||||||
|
.nth(index)
|
||||||
|
.ok_or_else(|| "Window source not found".to_string())?
|
||||||
|
.capture_image()
|
||||||
|
.map_err(|error| error.to_string())?,
|
||||||
|
_ => return Err("Unsupported source kind".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buffer = Cursor::new(Vec::new());
|
||||||
|
let image = DynamicImage::ImageRgba8(frame);
|
||||||
|
|
||||||
|
JpegEncoder::new_with_quality(&mut buffer, 70)
|
||||||
|
.encode_image(&image)
|
||||||
|
.map_err(|error| error.to_string())?;
|
||||||
|
|
||||||
|
let encoded = base64::engine::general_purpose::STANDARD.encode(buffer.into_inner());
|
||||||
|
return Ok(format!("data:image/jpeg;base64,{encoded}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
Err("Screen capture is not supported on this platform".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn get_screen_share_capabilities() -> ScreenShareCapabilities {
|
||||||
|
ScreenShareCapabilities {
|
||||||
|
platform: if cfg!(target_os = "linux") {
|
||||||
|
"linux"
|
||||||
|
} else if cfg!(target_os = "windows") {
|
||||||
|
"windows"
|
||||||
|
} else if cfg!(target_os = "macos") {
|
||||||
|
"macos"
|
||||||
|
} else {
|
||||||
|
"other"
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
uses_pipewire_audio_picker: cfg!(target_os = "linux"),
|
||||||
|
supports_system_audio_switch: cfg!(any(target_os = "windows", target_os = "macos")),
|
||||||
|
supports_reliable_system_audio: cfg!(target_os = "windows"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
let builder = tauri::Builder::default()
|
let builder = tauri::Builder::default();
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
|
let builder = builder.command_line_args([
|
||||||
|
("enable-media-stream", None::<String>),
|
||||||
|
("enable-usermedia-screen-capturing", None::<String>),
|
||||||
|
("allow-http-screen-capture", None::<String>),
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
(
|
||||||
|
"enable-features",
|
||||||
|
Some("WebRTCPipeWireCapturer".to_string()),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let builder = builder
|
||||||
.plugin(tauri_plugin_deep_link::init())
|
.plugin(tauri_plugin_deep_link::init())
|
||||||
.plugin(tauri_plugin_opener::init());
|
.plugin(tauri_plugin_opener::init());
|
||||||
|
|
||||||
|
|
@ -14,15 +237,27 @@ pub fn run() {
|
||||||
let builder = builder.plugin(tauri_plugin_barcode_scanner::init());
|
let builder = builder.plugin(tauri_plugin_barcode_scanner::init());
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.setup(|_app| {
|
.setup(|app| {
|
||||||
#[cfg(any(target_os = "linux", windows))]
|
#[cfg(any(target_os = "linux", windows))]
|
||||||
{
|
{
|
||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
_app.deep_link().register_all()?;
|
|
||||||
|
if let Err(error) = app.deep_link().register_all() {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
eprintln!("Skipping deep link registration during dev: {error}");
|
||||||
|
} else {
|
||||||
|
return Err(error.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![greet])
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
list_screen_share_sources,
|
||||||
|
list_screen_share_audio_outputs,
|
||||||
|
get_screen_share_capabilities,
|
||||||
|
capture_screen_share_frame
|
||||||
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,101 @@
|
||||||
import { createContext } from "react";
|
import {
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
type ReactNode,
|
||||||
|
} from "react";
|
||||||
|
import { invoke, isTauri } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
type contextType = {
|
export type DesktopScreenShareSource = {
|
||||||
nothing: undefined;
|
id: string;
|
||||||
|
kind: "screen" | "window";
|
||||||
|
name: string;
|
||||||
|
subtitle?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const context = createContext<contextType | undefined>(undefined);
|
export type DesktopScreenShareAudioOutput = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
isDefault: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export default function Provider({ children }: { children: React.ReactNode }) {
|
export type DesktopScreenShareCapabilities = {
|
||||||
|
platform: "linux" | "macos" | "windows" | "other";
|
||||||
|
usesPipeWireAudioPicker: boolean;
|
||||||
|
supportsSystemAudioSwitch: boolean;
|
||||||
|
supportsReliableSystemAudio: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DesktopMediaContextValue = {
|
||||||
|
isDesktopTauri: boolean;
|
||||||
|
getScreenShareCapabilities: () => Promise<DesktopScreenShareCapabilities>;
|
||||||
|
listScreenShareSources: () => Promise<DesktopScreenShareSource[]>;
|
||||||
|
listScreenShareAudioOutputs: () => Promise<DesktopScreenShareAudioOutput[]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultCapabilities: DesktopScreenShareCapabilities = {
|
||||||
|
platform: "other",
|
||||||
|
usesPipeWireAudioPicker: false,
|
||||||
|
supportsSystemAudioSwitch: false,
|
||||||
|
supportsReliableSystemAudio: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const desktopMediaContext = createContext<DesktopMediaContextValue | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
async function listScreenShareSources(): Promise<DesktopScreenShareSource[]> {
|
||||||
|
if (!isTauri()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoke<DesktopScreenShareSource[]>("list_screen_share_sources");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listScreenShareAudioOutputs(): Promise<
|
||||||
|
DesktopScreenShareAudioOutput[]
|
||||||
|
> {
|
||||||
|
if (!isTauri()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoke<DesktopScreenShareAudioOutput[]>(
|
||||||
|
"list_screen_share_audio_outputs",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getScreenShareCapabilities(): Promise<DesktopScreenShareCapabilities> {
|
||||||
|
if (!isTauri()) {
|
||||||
|
return defaultCapabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoke<DesktopScreenShareCapabilities>("get_screen_share_capabilities");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDesktopMedia() {
|
||||||
|
const value = useContext(desktopMediaContext);
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
throw new Error("useDesktopMedia must be used within the desktop provider");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Provider({ children }: { children: ReactNode }) {
|
||||||
|
const value = useMemo<DesktopMediaContextValue>(
|
||||||
|
() => ({
|
||||||
|
isDesktopTauri: isTauri(),
|
||||||
|
getScreenShareCapabilities,
|
||||||
|
listScreenShareSources,
|
||||||
|
listScreenShareAudioOutputs,
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<context.Provider
|
<desktopMediaContext.Provider value={value}>
|
||||||
value={{
|
|
||||||
nothing: undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</context.Provider>
|
</desktopMediaContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
bun.lock
2
bun.lock
|
|
@ -101,10 +101,12 @@
|
||||||
"@tanstack/react-query": "^5.0.0",
|
"@tanstack/react-query": "^5.0.0",
|
||||||
"@tanstack/react-router": "^1.0.0",
|
"@tanstack/react-router": "^1.0.0",
|
||||||
"@tanstack/react-virtual": "^3.0.0",
|
"@tanstack/react-virtual": "^3.0.0",
|
||||||
|
"@tauri-apps/api": "^2",
|
||||||
"@tensamin/crypto": "workspace:*",
|
"@tensamin/crypto": "workspace:*",
|
||||||
"@tensamin/markdown": "workspace:*",
|
"@tensamin/markdown": "workspace:*",
|
||||||
"@tensamin/shared": "workspace:*",
|
"@tensamin/shared": "workspace:*",
|
||||||
"@tensamin/storage": "workspace:*",
|
"@tensamin/storage": "workspace:*",
|
||||||
|
"@tensamin/tauri": "workspace:*",
|
||||||
"@tensamin/ttp": "workspace:*",
|
"@tensamin/ttp": "workspace:*",
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"@tensamin/user": "workspace:*",
|
"@tensamin/user": "workspace:*",
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,12 @@
|
||||||
"@tanstack/react-query": "^5.0.0",
|
"@tanstack/react-query": "^5.0.0",
|
||||||
"@tanstack/react-router": "^1.0.0",
|
"@tanstack/react-router": "^1.0.0",
|
||||||
"@tanstack/react-virtual": "^3.0.0",
|
"@tanstack/react-virtual": "^3.0.0",
|
||||||
|
"@tauri-apps/api": "^2",
|
||||||
"@tensamin/crypto": "workspace:*",
|
"@tensamin/crypto": "workspace:*",
|
||||||
"@tensamin/markdown": "workspace:*",
|
"@tensamin/markdown": "workspace:*",
|
||||||
"@tensamin/shared": "workspace:*",
|
"@tensamin/shared": "workspace:*",
|
||||||
"@tensamin/storage": "workspace:*",
|
"@tensamin/storage": "workspace:*",
|
||||||
|
"@tensamin/tauri": "workspace:*",
|
||||||
"@tensamin/ttp": "workspace:*",
|
"@tensamin/ttp": "workspace:*",
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"@tensamin/user": "workspace:*",
|
"@tensamin/user": "workspace:*",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
import { useState } from "react";
|
||||||
import { Button, Popover, PopoverContent, PopoverTrigger } from "@tensamin/ui";
|
import { Button, Popover, PopoverContent, PopoverTrigger } from "@tensamin/ui";
|
||||||
import { MonitorDot, ScreenShare } from "lucide-react";
|
import { MonitorDot, ScreenShare } from "lucide-react";
|
||||||
|
import { toast } from "@tensamin/shared/log";
|
||||||
|
import { useDesktopMedia } from "@tensamin/tauri/context";
|
||||||
import { setScreenShareEnabled, useCall } from "../../store";
|
import { setScreenShareEnabled, useCall } from "../../store";
|
||||||
|
import ScreenShareDialog from "./screenshareDialog";
|
||||||
|
|
||||||
export default function ScreenshareButton({
|
export default function ScreenshareButton({
|
||||||
className,
|
className,
|
||||||
|
|
@ -10,43 +14,98 @@ export default function ScreenshareButton({
|
||||||
iconSize?: number;
|
iconSize?: number;
|
||||||
}) {
|
}) {
|
||||||
const isScreensharing = useCall((state) => state.screenShareEnabled);
|
const isScreensharing = useCall((state) => state.screenShareEnabled);
|
||||||
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
const { isDesktopTauri } = useDesktopMedia();
|
||||||
|
|
||||||
|
async function startWebShare() {
|
||||||
|
try {
|
||||||
|
await setScreenShareEnabled(true, {
|
||||||
|
audio: true,
|
||||||
|
systemAudio: "include",
|
||||||
|
surfaceSwitching: "include",
|
||||||
|
video: true,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to start web screen share", error);
|
||||||
|
toast(
|
||||||
|
"error",
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Failed to start screen sharing.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stopShare() {
|
||||||
|
try {
|
||||||
|
await setScreenShareEnabled(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to stop screen share", error);
|
||||||
|
toast("error", "Failed to stop screen sharing.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<>
|
||||||
<PopoverTrigger
|
<Popover onOpenChange={setMenuOpen} open={menuOpen}>
|
||||||
render={
|
<PopoverTrigger
|
||||||
|
render={
|
||||||
|
<Button
|
||||||
|
variant={isScreensharing ? "subtleDefault" : "default"}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{isScreensharing ? (
|
||||||
|
<MonitorDot
|
||||||
|
style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ScreenShare
|
||||||
|
style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<PopoverContent className="flex w-52 flex-col gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant={isScreensharing ? "subtleDefault" : "default"}
|
disabled={isScreensharing}
|
||||||
className={className}
|
onClick={() => {
|
||||||
|
setMenuOpen(false);
|
||||||
|
|
||||||
|
if (isDesktopTauri) {
|
||||||
|
setDialogOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startWebShare();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{isScreensharing ? (
|
Start sharing
|
||||||
<MonitorDot
|
|
||||||
style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ScreenShare
|
|
||||||
style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
}
|
<Button
|
||||||
/>
|
variant="destructive"
|
||||||
<PopoverContent className="w-auto flex flex-col gap-2">
|
disabled={!isScreensharing}
|
||||||
<Button
|
onClick={() => {
|
||||||
disabled={isScreensharing}
|
setMenuOpen(false);
|
||||||
onClick={() => void setScreenShareEnabled(true)}
|
void stopShare();
|
||||||
>
|
}}
|
||||||
Start
|
>
|
||||||
</Button>
|
Stop sharing
|
||||||
<Button
|
</Button>
|
||||||
variant="destructive"
|
<Button disabled variant="outline">
|
||||||
disabled={!isScreensharing}
|
Stream quality soon
|
||||||
onClick={() => void setScreenShareEnabled(false)}
|
</Button>
|
||||||
>
|
</PopoverContent>
|
||||||
Stop
|
</Popover>
|
||||||
</Button>
|
|
||||||
<Button>idk</Button>
|
{isDesktopTauri && (
|
||||||
</PopoverContent>
|
<ScreenShareDialog
|
||||||
</Popover>
|
open={dialogOpen}
|
||||||
|
onOpenChange={setDialogOpen}
|
||||||
|
isScreensharing={isScreensharing}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
325
packages/call/src/components/buttons/screenshareDialog.tsx
Normal file
325
packages/call/src/components/buttons/screenshareDialog.tsx
Normal file
|
|
@ -0,0 +1,325 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
Label,
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
Switch,
|
||||||
|
} from "@tensamin/ui";
|
||||||
|
import {
|
||||||
|
type DesktopScreenShareAudioOutput,
|
||||||
|
type DesktopScreenShareCapabilities,
|
||||||
|
type DesktopScreenShareSource,
|
||||||
|
useDesktopMedia,
|
||||||
|
} from "@tensamin/tauri/context";
|
||||||
|
import { toast } from "@tensamin/shared/log";
|
||||||
|
import { AppWindow, Loader2, MonitorUp } from "lucide-react";
|
||||||
|
import type { ScreenShareCaptureOptions } from "livekit-client";
|
||||||
|
import {
|
||||||
|
setScreenShareEnabled,
|
||||||
|
startLinuxDesktopScreenShare,
|
||||||
|
} from "../../store";
|
||||||
|
|
||||||
|
const NONE_AUDIO_OUTPUT = "__none__";
|
||||||
|
|
||||||
|
function buildScreenShareOptions(
|
||||||
|
source: DesktopScreenShareSource,
|
||||||
|
capabilities: DesktopScreenShareCapabilities,
|
||||||
|
selectedAudioOutputId: string,
|
||||||
|
shareAudio: boolean,
|
||||||
|
): ScreenShareCaptureOptions {
|
||||||
|
const wantsAudio = capabilities.usesPipeWireAudioPicker
|
||||||
|
? selectedAudioOutputId !== NONE_AUDIO_OUTPUT
|
||||||
|
: capabilities.supportsReliableSystemAudio && shareAudio;
|
||||||
|
|
||||||
|
return {
|
||||||
|
audio: wantsAudio
|
||||||
|
? {
|
||||||
|
autoGainControl: false,
|
||||||
|
echoCancellation: false,
|
||||||
|
noiseSuppression: false,
|
||||||
|
}
|
||||||
|
: false,
|
||||||
|
video: {
|
||||||
|
displaySurface: source.kind === "window" ? "window" : "monitor",
|
||||||
|
},
|
||||||
|
systemAudio: wantsAudio ? "include" : "exclude",
|
||||||
|
surfaceSwitching: "exclude",
|
||||||
|
selfBrowserSurface: "exclude",
|
||||||
|
contentHint: "detail",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ScreenShareDialog({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
isScreensharing,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
isScreensharing: boolean;
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
getScreenShareCapabilities,
|
||||||
|
listScreenShareAudioOutputs,
|
||||||
|
listScreenShareSources,
|
||||||
|
} = useDesktopMedia();
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [sources, setSources] = useState<DesktopScreenShareSource[]>([]);
|
||||||
|
const [audioOutputs, setAudioOutputs] = useState<DesktopScreenShareAudioOutput[]>(
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const [capabilities, setCapabilities] =
|
||||||
|
useState<DesktopScreenShareCapabilities | null>(null);
|
||||||
|
const [selectedSourceId, setSelectedSourceId] = useState<string | null>(null);
|
||||||
|
const [selectedAudioOutputId, setSelectedAudioOutputId] =
|
||||||
|
useState<string>(NONE_AUDIO_OUTPUT);
|
||||||
|
const [shareAudio, setShareAudio] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let active = true;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setSelectedSourceId(null);
|
||||||
|
setSelectedAudioOutputId(NONE_AUDIO_OUTPUT);
|
||||||
|
setShareAudio(false);
|
||||||
|
|
||||||
|
Promise.all([listScreenShareSources(), getScreenShareCapabilities()])
|
||||||
|
.then(async ([nextSources, nextCapabilities]) => {
|
||||||
|
if (!active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSources(nextSources);
|
||||||
|
setCapabilities(nextCapabilities);
|
||||||
|
|
||||||
|
if (nextCapabilities.usesPipeWireAudioPicker) {
|
||||||
|
const nextOutputs = await listScreenShareAudioOutputs();
|
||||||
|
|
||||||
|
if (!active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAudioOutputs(nextOutputs);
|
||||||
|
} else {
|
||||||
|
setAudioOutputs([]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Failed to load desktop share sources", error);
|
||||||
|
toast("error", "Failed to load screen share sources.");
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (active) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
getScreenShareCapabilities,
|
||||||
|
listScreenShareAudioOutputs,
|
||||||
|
listScreenShareSources,
|
||||||
|
open,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const selectedSource =
|
||||||
|
sources.find((source) => source.id === selectedSourceId) ?? null;
|
||||||
|
|
||||||
|
async function startSharing() {
|
||||||
|
if (!selectedSource || !capabilities) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (capabilities.platform === "linux") {
|
||||||
|
await startLinuxDesktopScreenShare(selectedSource.id);
|
||||||
|
} else {
|
||||||
|
await setScreenShareEnabled(
|
||||||
|
true,
|
||||||
|
buildScreenShareOptions(
|
||||||
|
selectedSource,
|
||||||
|
capabilities,
|
||||||
|
selectedAudioOutputId,
|
||||||
|
shareAudio,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
onOpenChange(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to start screen share", error);
|
||||||
|
toast(
|
||||||
|
"error",
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Failed to start screen sharing.",
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stopSharing() {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await setScreenShareEnabled(false);
|
||||||
|
onOpenChange(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to stop screen share", error);
|
||||||
|
toast("error", "Failed to stop screen sharing.");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-w-3xl gap-0 overflow-hidden p-0">
|
||||||
|
<DialogHeader className="p-4 pb-3">
|
||||||
|
<DialogTitle>Share your screen</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Pick the window or display you want to share with the call.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="flex max-h-[75vh] flex-col gap-4 overflow-y-auto px-4 pb-4">
|
||||||
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
|
{sources.map((source) => {
|
||||||
|
const selected = source.id === selectedSourceId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={source.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setSelectedSourceId(source.id)}
|
||||||
|
className={[
|
||||||
|
"flex min-h-28 flex-col justify-between rounded-xl border p-4 text-left transition-colors",
|
||||||
|
selected
|
||||||
|
? "border-primary bg-primary/5"
|
||||||
|
: "border-border bg-card hover:bg-muted/50",
|
||||||
|
].join(" ")}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-sm font-medium">
|
||||||
|
{source.kind === "window" ? (
|
||||||
|
<AppWindow className="size-4" />
|
||||||
|
) : (
|
||||||
|
<MonitorUp className="size-4" />
|
||||||
|
)}
|
||||||
|
{source.name}
|
||||||
|
</div>
|
||||||
|
{source.subtitle && (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{source.subtitle}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!loading && sources.length === 0 && (
|
||||||
|
<p className="rounded-lg border border-dashed p-4 text-sm text-muted-foreground">
|
||||||
|
No windows or displays are currently available for capture.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{capabilities?.usesPipeWireAudioPicker ? (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Label>Share audio output</Label>
|
||||||
|
<Select
|
||||||
|
value={selectedAudioOutputId}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setSelectedAudioOutputId(value ?? NONE_AUDIO_OUTPUT)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
<SelectValue placeholder="None" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value={NONE_AUDIO_OUTPUT}>None</SelectItem>
|
||||||
|
{audioOutputs.map((output) => (
|
||||||
|
<SelectItem key={output.id} value={output.id}>
|
||||||
|
{output.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Select a PipeWire output to try sharing system audio with your
|
||||||
|
screen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : capabilities?.supportsSystemAudioSwitch ? (
|
||||||
|
<div className="flex flex-col gap-2 rounded-xl border p-4">
|
||||||
|
<div className="flex items-center justify-between gap-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="share-system-audio">Share audio</Label>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Share system audio alongside your screen when the runtime can
|
||||||
|
provide it.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
id="share-system-audio"
|
||||||
|
checked={shareAudio}
|
||||||
|
disabled={!capabilities.supportsReliableSystemAudio}
|
||||||
|
onCheckedChange={setShareAudio}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{!capabilities.supportsReliableSystemAudio && (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
System audio sharing is not available on this platform/runtime
|
||||||
|
yet.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
|
<Loader2 className="size-4 animate-spin" />
|
||||||
|
Loading available share targets...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
{isScreensharing && (
|
||||||
|
<Button variant="destructive" disabled={loading} onClick={stopSharing}>
|
||||||
|
Stop sharing
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
disabled={loading || selectedSource == null}
|
||||||
|
onClick={startSharing}
|
||||||
|
>
|
||||||
|
{loading ? <Loader2 className="size-4 animate-spin" /> : null}
|
||||||
|
Start sharing
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { useLocation, useNavigate } from "@tanstack/react-router";
|
import { useLocation, useNavigate } from "@tanstack/react-router";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { useTTP } from "@tensamin/ttp";
|
import { useTTP } from "@tensamin/ttp";
|
||||||
import { log, toast } from "@tensamin/shared/log";
|
import { log, toast } from "@tensamin/shared/log";
|
||||||
import { ttp } from "@tensamin/shared/data";
|
import { ttp } from "@tensamin/shared/data";
|
||||||
|
|
@ -11,9 +12,11 @@ import { DeepFilterNoiseFilterProcessor } from "deepfilternet3-noise-filter";
|
||||||
import {
|
import {
|
||||||
ExternalE2EEKeyProvider,
|
ExternalE2EEKeyProvider,
|
||||||
LocalAudioTrack,
|
LocalAudioTrack,
|
||||||
|
type LocalTrack,
|
||||||
Room,
|
Room,
|
||||||
RoomEvent,
|
RoomEvent,
|
||||||
type RemoteTrack,
|
type RemoteTrack,
|
||||||
|
type ScreenShareCaptureOptions,
|
||||||
Track,
|
Track,
|
||||||
setLogExtension,
|
setLogExtension,
|
||||||
getLogger,
|
getLogger,
|
||||||
|
|
@ -62,6 +65,11 @@ type Runtime = {
|
||||||
getUser: GetUserFn;
|
getUser: GetUserFn;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ScreenShareSession = {
|
||||||
|
tracks: Array<LocalTrack | MediaStreamTrack>;
|
||||||
|
cleanup?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
type CallStore = {
|
type CallStore = {
|
||||||
state: CallState;
|
state: CallState;
|
||||||
view: CallView;
|
view: CallView;
|
||||||
|
|
@ -73,6 +81,7 @@ type CallStore = {
|
||||||
deaf: boolean;
|
deaf: boolean;
|
||||||
micEnabled: boolean;
|
micEnabled: boolean;
|
||||||
screenShareEnabled: boolean;
|
screenShareEnabled: boolean;
|
||||||
|
screenShareSession: ScreenShareSession | null;
|
||||||
isEncrypted: boolean;
|
isEncrypted: boolean;
|
||||||
room: Room;
|
room: Room;
|
||||||
keyProvider: ExternalE2EEKeyProvider;
|
keyProvider: ExternalE2EEKeyProvider;
|
||||||
|
|
@ -136,11 +145,12 @@ function requireRuntime(runtime: Runtime | null): Runtime {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function syncParticipantState() {
|
export function syncParticipantState() {
|
||||||
const { room } = useCall.getState();
|
const { room, screenShareSession } = useCall.getState();
|
||||||
|
|
||||||
useCall.setState({
|
useCall.setState({
|
||||||
micEnabled: room.localParticipant.isMicrophoneEnabled,
|
micEnabled: room.localParticipant.isMicrophoneEnabled,
|
||||||
screenShareEnabled: room.localParticipant.isScreenShareEnabled,
|
screenShareEnabled:
|
||||||
|
screenShareSession != null || room.localParticipant.isScreenShareEnabled,
|
||||||
isEncrypted:
|
isEncrypted:
|
||||||
room.localParticipant.isE2EEEnabled && room.localParticipant.isEncrypted,
|
room.localParticipant.isE2EEEnabled && room.localParticipant.isEncrypted,
|
||||||
});
|
});
|
||||||
|
|
@ -225,6 +235,8 @@ export async function connect(callId: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function disconnect() {
|
export function disconnect() {
|
||||||
|
void clearPublishedScreenShare();
|
||||||
|
|
||||||
useCall.setState({
|
useCall.setState({
|
||||||
state: "closing",
|
state: "closing",
|
||||||
invitedUserId: null,
|
invitedUserId: null,
|
||||||
|
|
@ -234,6 +246,7 @@ export function disconnect() {
|
||||||
currentCallData: null,
|
currentCallData: null,
|
||||||
deaf: false,
|
deaf: false,
|
||||||
view: "preview",
|
view: "preview",
|
||||||
|
screenShareSession: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
room.remoteParticipants.forEach((participant) => {
|
room.remoteParticipants.forEach((participant) => {
|
||||||
|
|
@ -325,11 +338,163 @@ export async function toggleMute() {
|
||||||
syncParticipantState();
|
syncParticipantState();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setScreenShareEnabled(enabled: boolean) {
|
async function clearPublishedScreenShare() {
|
||||||
await room.localParticipant.setScreenShareEnabled(enabled);
|
const screenShareSession = useCall.getState().screenShareSession;
|
||||||
|
|
||||||
|
if (!screenShareSession) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
screenShareSession.tracks.map((track) =>
|
||||||
|
room.localParticipant.unpublishTrack(track, true).catch((error) => {
|
||||||
|
log(1, "call", "red", "Failed to unpublish screen share track", error);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
screenShareSession.cleanup?.();
|
||||||
|
|
||||||
|
useCall.setState({ screenShareSession: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function publishScreenShareTracks(
|
||||||
|
tracks: Array<LocalTrack | MediaStreamTrack>,
|
||||||
|
cleanup?: () => void,
|
||||||
|
) {
|
||||||
|
if (tracks.length === 0) {
|
||||||
|
throw new Error("No screen share tracks were created.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
tracks.map((track) =>
|
||||||
|
room.localParticipant.publishTrack(track, {
|
||||||
|
source:
|
||||||
|
track.kind === Track.Kind.Video
|
||||||
|
? Track.Source.ScreenShare
|
||||||
|
: Track.Source.ScreenShareAudio,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const track of tracks) {
|
||||||
|
const mediaStreamTrack =
|
||||||
|
track instanceof MediaStreamTrack ? track : track.mediaStreamTrack;
|
||||||
|
|
||||||
|
mediaStreamTrack.addEventListener(
|
||||||
|
"ended",
|
||||||
|
() => {
|
||||||
|
void stopScreenShare();
|
||||||
|
},
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
useCall.setState({ screenShareSession: { tracks, cleanup } });
|
||||||
syncParticipantState();
|
syncParticipantState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function startScreenShare(options?: ScreenShareCaptureOptions) {
|
||||||
|
await clearPublishedScreenShare();
|
||||||
|
|
||||||
|
const tracks = await room.localParticipant.createScreenTracks(options);
|
||||||
|
|
||||||
|
await publishScreenShareTracks(
|
||||||
|
tracks,
|
||||||
|
() => tracks.forEach((track) => track.stop()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startLinuxDesktopScreenShare(sourceId: string) {
|
||||||
|
await clearPublishedScreenShare();
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = 1280;
|
||||||
|
canvas.height = 720;
|
||||||
|
canvas.style.display = "none";
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
|
||||||
|
const context = canvas.getContext("2d");
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
canvas.remove();
|
||||||
|
throw new Error("Failed to initialize the screen share canvas.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = canvas.captureStream(8);
|
||||||
|
const videoTrack = stream.getVideoTracks()[0];
|
||||||
|
|
||||||
|
if (!videoTrack) {
|
||||||
|
canvas.remove();
|
||||||
|
throw new Error("Failed to create a video track for screen sharing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const image = new Image();
|
||||||
|
let stopped = false;
|
||||||
|
let frameRequestInFlight = false;
|
||||||
|
|
||||||
|
const renderFrame = async () => {
|
||||||
|
if (stopped || frameRequestInFlight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
frameRequestInFlight = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dataUrl = await invoke<string>("capture_screen_share_frame", {
|
||||||
|
sourceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
image.onload = () => resolve();
|
||||||
|
image.onerror = () => reject(new Error("Failed to decode screen share frame."));
|
||||||
|
image.src = dataUrl;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canvas.width !== image.naturalWidth || canvas.height !== image.naturalHeight) {
|
||||||
|
canvas.width = image.naturalWidth;
|
||||||
|
canvas.height = image.naturalHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||||
|
} finally {
|
||||||
|
frameRequestInFlight = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await renderFrame();
|
||||||
|
|
||||||
|
const interval = window.setInterval(() => {
|
||||||
|
void renderFrame().catch((error) => {
|
||||||
|
log(1, "call", "red", "Failed to capture Linux screen share frame", error);
|
||||||
|
});
|
||||||
|
}, 125);
|
||||||
|
|
||||||
|
await publishScreenShareTracks([videoTrack], () => {
|
||||||
|
stopped = true;
|
||||||
|
window.clearInterval(interval);
|
||||||
|
stream.getTracks().forEach((track) => track.stop());
|
||||||
|
canvas.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function stopScreenShare() {
|
||||||
|
await clearPublishedScreenShare();
|
||||||
|
syncParticipantState();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setScreenShareEnabled(
|
||||||
|
enabled: boolean,
|
||||||
|
options?: ScreenShareCaptureOptions,
|
||||||
|
) {
|
||||||
|
if (enabled) {
|
||||||
|
await startScreenShare(options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await stopScreenShare();
|
||||||
|
}
|
||||||
|
|
||||||
export function resetCallState() {
|
export function resetCallState() {
|
||||||
useCall.setState({
|
useCall.setState({
|
||||||
state: "closed",
|
state: "closed",
|
||||||
|
|
@ -340,6 +505,7 @@ export function resetCallState() {
|
||||||
livekitToken: null,
|
livekitToken: null,
|
||||||
currentCallData: null,
|
currentCallData: null,
|
||||||
deaf: false,
|
deaf: false,
|
||||||
|
screenShareSession: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
syncParticipantState();
|
syncParticipantState();
|
||||||
|
|
@ -375,6 +541,7 @@ export const useCall = create<CallStore>(() => ({
|
||||||
deaf: false,
|
deaf: false,
|
||||||
micEnabled: room.localParticipant.isMicrophoneEnabled,
|
micEnabled: room.localParticipant.isMicrophoneEnabled,
|
||||||
screenShareEnabled: room.localParticipant.isScreenShareEnabled,
|
screenShareEnabled: room.localParticipant.isScreenShareEnabled,
|
||||||
|
screenShareSession: null,
|
||||||
isEncrypted:
|
isEncrypted:
|
||||||
room.localParticipant.isE2EEEnabled && room.localParticipant.isEncrypted,
|
room.localParticipant.isE2EEEnabled && room.localParticipant.isEncrypted,
|
||||||
room,
|
room,
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ export default function View() {
|
||||||
);
|
);
|
||||||
|
|
||||||
const rows = useMemo(() => {
|
const rows = useMemo(() => {
|
||||||
let nextParticipant = 1;
|
let nextParticipant = 0;
|
||||||
|
|
||||||
return layout.rowCounts.map((count, rowIndex) => {
|
return layout.rowCounts.map((count, rowIndex) => {
|
||||||
const participants = Array.from(
|
const participants = Array.from(
|
||||||
|
|
@ -148,21 +148,29 @@ export default function View() {
|
||||||
});
|
});
|
||||||
}, [layout.rowCounts]);
|
}, [layout.rowCounts]);
|
||||||
|
|
||||||
|
const users = useMemo(() => {
|
||||||
|
const participants = [
|
||||||
|
...room.remoteParticipants.values(),
|
||||||
|
room.localParticipant,
|
||||||
|
];
|
||||||
|
return participants;
|
||||||
|
}, [room.localParticipant, room.remoteParticipants]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="h-full w-full overflow-hidden p-3">
|
<div ref={containerRef} className="h-[75vh] w-full overflow-hidden p-3">
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center gap-3">
|
<div className="flex h-full w-full flex-col items-center justify-center gap-3">
|
||||||
{rows.map((row) => (
|
{rows.map((row) => (
|
||||||
<div key={row.rowIndex} className="flex justify-center gap-3">
|
<div key={row.rowIndex} className="flex justify-center gap-3">
|
||||||
{row.participants.map((participant) => (
|
{row.participants.map((userIndex) => (
|
||||||
<div
|
<div
|
||||||
key={participant}
|
key={userIndex}
|
||||||
className="flex items-center justify-center rounded-xl bg-red-500"
|
className="flex items-center justify-center rounded-xl bg-red-500"
|
||||||
style={{
|
style={{
|
||||||
width: layout.tileWidth,
|
width: layout.tileWidth,
|
||||||
height: layout.tileHeight,
|
height: layout.tileHeight,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Test {participant}
|
Test {users[userIndex]?.identity}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue