diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41146bf..a994115 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,70 +16,35 @@ jobs: with: components: clippy,rustfmt - run: cargo fmt --all -- --check - - run: cargo clippy --workspace --all-targets -- -D warnings + - run: cargo clippy --locked -p remotedesk-webrtc-core -p remotedesk-native-gui --all-targets -- -D warnings - run: cargo test --workspace - web: - runs-on: windows-latest - defaults: - run: - working-directory: client/web - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - cache-dependency-path: client/web/package-lock.json - - run: npm ci - - run: npm run lint - - run: npm run build - - package-preview: + native-client: runs-on: windows-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - cache-dependency-path: client/web/package-lock.json - - run: npm ci - working-directory: client/web - - name: Build preview package + - run: cargo test --locked -p remotedesk-webrtc-core + - run: cargo build --locked --release -p remotedesk-native-gui + + package-windows-client: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Build native portable preview shell: pwsh run: ./packaging/windows/package-preview.ps1 - - uses: actions/upload-artifact@v4 - with: - name: remotedesk-m0-windows-preview - path: | - artifacts/*.zip - artifacts/SHA256SUMS.txt - if-no-files-found: error - - package-installer: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - cache-dependency-path: client/web/package-lock.json - - run: npm ci - working-directory: client/web - - name: Test update manifest generator contract - run: node ./packaging/windows/test-update-manifest.mjs - - name: Build Windows installer + - name: Build native Windows installer shell: pwsh run: ./packaging/windows/package-installer.ps1 - uses: actions/upload-artifact@v4 with: - name: remotedesk-m0-windows-installer + name: remotedesk-native-windows-packages path: | + artifacts/*.zip artifacts/*.msi + artifacts/SHA256SUMS.txt artifacts/INSTALLER-SHA256SUMS.txt if-no-files-found: error diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index 90c61b5..e8a68a5 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -23,14 +23,6 @@ jobs: with: fetch-depth: 0 - uses: dtolnay/rust-toolchain@stable - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - cache-dependency-path: client/web/package-lock.json - - name: Install web dependencies - working-directory: client/web - run: npm ci - name: Validate release tag and repository version id: version shell: pwsh diff --git a/Cargo.lock b/Cargo.lock index 48f1dd7..8bf34ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,7 +311,7 @@ dependencies = [ "asn1-rs-derive 0.5.1", "asn1-rs-impl", "displaydoc", - "nom", + "nom 7.1.3", "num-traits", "rusticata-macros", "thiserror 1.0.69", @@ -327,7 +327,7 @@ dependencies = [ "asn1-rs-derive 0.6.0", "asn1-rs-impl", "displaydoc", - "nom", + "nom 7.1.3", "num-traits", "rusticata-macros", "thiserror 2.0.20", @@ -434,29 +434,6 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib 0.18.5", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "system-deps 6.2.2", -] - [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -491,6 +468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" dependencies = [ "aws-lc-sys", + "untrusted 0.7.1", "zeroize", ] @@ -519,12 +497,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -654,15 +626,6 @@ dependencies = [ "objc2 0.6.4", ] -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - [[package]] name = "bumpalo" version = "3.20.3" @@ -729,34 +692,6 @@ name = "bytes" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" -dependencies = [ - "serde", -] - -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.13.1", - "cairo-sys-rs", - "glib 0.18.5", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys 0.18.1", - "libc", - "system-deps 6.2.2", -] [[package]] name = "calloop" @@ -809,48 +744,6 @@ dependencies = [ "wayland-client", ] -[[package]] -name = "camino" -version = "1.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" -dependencies = [ - "serde_core", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.20", -] - -[[package]] -name = "cargo_toml" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" -dependencies = [ - "serde", - "toml 0.9.12+spec-1.1.0", -] - [[package]] name = "cbc" version = "0.1.2" @@ -899,27 +792,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon 0.12.16", -] - [[package]] name = "cfg-expr" version = "0.20.8" @@ -927,7 +799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c" dependencies = [ "smallvec", - "target-lexicon 0.13.5", + "target-lexicon", ] [[package]] @@ -983,18 +855,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link 0.2.1", -] - [[package]] name = "chunked_transfer" version = "1.5.0" @@ -1051,7 +911,7 @@ version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 3.0.3", @@ -1143,16 +1003,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "cookie" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" -dependencies = [ - "time", - "version_check", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -1187,20 +1037,7 @@ checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" -dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.10.1", - "core-graphics-types 0.2.0", + "core-graphics-types", "foreign-types", "libc", ] @@ -1216,17 +1053,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.10.1", - "libc", -] - [[package]] name = "coreaudio-rs" version = "0.14.2" @@ -1334,15 +1160,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" -[[package]] -name = "crossbeam-channel" -version = "0.5.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.22" @@ -1475,54 +1292,15 @@ dependencies = [ "libloading", ] -[[package]] -name = "cssparser" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "phf", - "smallvec", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "ctor" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" -dependencies = [ - "ctor-proc-macro", - "dtor 0.3.0", -] - [[package]] name = "ctor" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83cf0d42651b16c6dfe68685716d18480d18a9c39c62d76e8cf3eb6ed5d8bcbf" dependencies = [ - "dtor 0.8.1", + "dtor", ] -[[package]] -name = "ctor-proc-macro" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" - [[package]] name = "ctr" version = "0.9.2" @@ -1600,40 +1378,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.119", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.119", -] - [[package]] name = "dasp_sample" version = "0.11.0" @@ -1646,37 +1390,6 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" -[[package]] -name = "defmt" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" -dependencies = [ - "defmt-parser", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "defmt-parser" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" -dependencies = [ - "thiserror 2.0.20", -] - [[package]] name = "der" version = "0.7.10" @@ -1709,7 +1422,7 @@ checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ "asn1-rs 0.6.2", "displaydoc", - "nom", + "nom 7.1.3", "num-bigint 0.4.8", "num-traits", "rusticata-macros", @@ -1723,7 +1436,7 @@ checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" dependencies = [ "asn1-rs 0.7.2", "displaydoc", - "nom", + "nom 7.1.3", "num-bigint 0.4.8", "num-traits", "rusticata-macros", @@ -1745,9 +1458,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "serde_core", -] [[package]] name = "derive_more" @@ -1805,24 +1515,28 @@ dependencies = [ ] [[package]] -name = "dirs" -version = "6.0.0" +name = "dimpl" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +checksum = "ba6aa42b0c64c3e5311a2afad224b32db1ee129d21c63daaaf8ea747b846cdbc" dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", + "aes 0.8.4", + "arrayvec", + "aws-lc-rs", + "ccm", + "der 0.7.10", + "log", + "nom 8.0.0", + "once_cell", + "pkcs8 0.10.2", + "rand 0.9.5", + "rcgen", + "sec1 0.7.3", + "signature 2.2.0", + "spki 0.7.3", + "subtle", + "time", + "x509-cert", ] [[package]] @@ -1838,8 +1552,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ "bitflags 2.13.1", - "block2 0.6.2", - "libc", "objc2 0.6.4", ] @@ -1863,29 +1575,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "dlopen2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "document-features" version = "0.2.12" @@ -1895,21 +1584,6 @@ dependencies = [ "litrs", ] -[[package]] -name = "dom_query" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" -dependencies = [ - "bit-set", - "cssparser", - "foldhash 0.2.0", - "html5ever", - "precomputed-hash", - "selectors", - "tendril", -] - [[package]] name = "downcast-rs" version = "1.2.1" @@ -1921,9 +1595,6 @@ name = "dpi" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -dependencies = [ - "serde", -] [[package]] name = "drm" @@ -1965,42 +1636,12 @@ dependencies = [ "linux-raw-sys 0.9.4", ] -[[package]] -name = "dtoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dtor" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" -dependencies = [ - "dtor-proc-macro", -] - [[package]] name = "dtor" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edf234dd1594d6dd434a8fb8cada51ddbbc593e40e4a01556a0b31c62da2775b" -[[package]] -name = "dtor-proc-macro" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" - [[package]] name = "dunce" version = "1.0.5" @@ -2210,26 +1851,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "embed-resource" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 1.1.4+spec-1.1.0", - "vswhom", - "winreg 0.55.0", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - [[package]] name = "endi" version = "1.1.1" @@ -2287,17 +1908,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "erased-serde" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - [[package]] name = "errno" version = "0.3.14" @@ -2399,16 +2009,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset 0.9.1", - "rustc_version", -] - [[package]] name = "filedescriptor" version = "0.8.3" @@ -2455,12 +2055,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - [[package]] name = "foreign-types" version = "0.5.0" @@ -2619,78 +2213,6 @@ dependencies = [ "thread_local", ] -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib 0.18.5", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib 0.18.5", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys 0.18.1", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys 0.18.1", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "pango-sys", - "pkg-config", - "system-deps 6.2.2", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "pkg-config", - "system-deps 6.2.2", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2709,7 +2231,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ "rustix 1.1.4", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -2770,48 +2292,16 @@ dependencies = [ "polyval 0.7.3", ] -[[package]] -name = "gio" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys 0.18.1", - "glib 0.18.5", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "system-deps 6.2.2", - "winapi", -] - [[package]] name = "gio-sys" version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22" dependencies = [ - "glib-sys 0.21.5", - "gobject-sys 0.21.5", + "glib-sys", + "gobject-sys", "libc", - "system-deps 7.0.8", + "system-deps", "windows-sys 0.61.2", ] @@ -2826,29 +2316,6 @@ dependencies = [ "xml-rs", ] -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.13.1", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys 0.18.1", - "glib-macros 0.18.5", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - [[package]] name = "glib" version = "0.21.5" @@ -2861,52 +2328,28 @@ dependencies = [ "futures-executor", "futures-task", "futures-util", - "gio-sys 0.21.5", - "glib-macros 0.21.5", - "glib-sys 0.21.5", - "gobject-sys 0.21.5", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", "libc", "memchr", "smallvec", ] -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "glib-macros" version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17" dependencies = [ - "heck 0.5.0", - "proc-macro-crate 3.5.0", + "heck", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", ] -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps 6.2.2", -] - [[package]] name = "glib-sys" version = "0.21.5" @@ -2914,15 +2357,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c" dependencies = [ "libc", - "system-deps 7.0.8", + "system-deps", ] -[[package]] -name = "glob" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" - [[package]] name = "glow" version = "0.16.0" @@ -2944,26 +2381,15 @@ dependencies = [ "gl_generator", ] -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys 0.18.1", - "libc", - "system-deps 6.2.2", -] - [[package]] name = "gobject-sys" version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294" dependencies = [ - "glib-sys 0.21.5", + "glib-sys", "libc", - "system-deps 7.0.8", + "system-deps", ] [[package]] @@ -3049,7 +2475,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "glib 0.21.5", + "glib", "gstreamer-sys", "itertools", "kstring", @@ -3072,7 +2498,7 @@ checksum = "3da7017b2a2fa5cdf9123b1603947ea24174f6d8cea0ea673411df824c811921" dependencies = [ "futures-core", "futures-sink", - "glib 0.21.5", + "glib", "gstreamer", "gstreamer-app-sys", "gstreamer-base", @@ -3085,11 +2511,11 @@ version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fa9f1b12b546aea543c15a0fdbc5a53617902a74f6d357a32b6a9fb4bc4725c" dependencies = [ - "glib-sys 0.21.5", + "glib-sys", "gstreamer-base-sys", "gstreamer-sys", "libc", - "system-deps 7.0.8", + "system-deps", ] [[package]] @@ -3100,7 +2526,7 @@ checksum = "9375f9a12120a8ee17b765c816c9b23861ce258def77b0ee40a05acb00c74972" dependencies = [ "atomic_refcell", "cfg-if", - "glib 0.21.5", + "glib", "gstreamer", "gstreamer-base-sys", "libc", @@ -3112,11 +2538,11 @@ version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b844f3559b6ab0379b4b771261643783ae4e0ffa71d5f5f46e33b7acf66b752" dependencies = [ - "glib-sys 0.21.5", - "gobject-sys 0.21.5", + "glib-sys", + "gobject-sys", "gstreamer-sys", "libc", - "system-deps 7.0.8", + "system-deps", ] [[package]] @@ -3126,62 +2552,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5d37c1a599ae57b8186948bd5699f2dbfc044baea9d400228b489a85bcf2759" dependencies = [ "cfg-if", - "glib-sys 0.21.5", - "gobject-sys 0.21.5", + "glib-sys", + "gobject-sys", "libc", - "system-deps 7.0.8", -] - -[[package]] -name = "gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib 0.18.5", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys 0.18.1", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "pango-sys", - "system-deps 6.2.2", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.119", + "system-deps", ] [[package]] @@ -3196,7 +2570,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.14.0", + "indexmap", "slab", "tokio", "tokio-util", @@ -3223,12 +2597,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -3244,7 +2612,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash 0.1.5", + "foldhash", ] [[package]] @@ -3275,12 +2643,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -3341,16 +2703,6 @@ dependencies = [ "digest 0.11.3", ] -[[package]] -name = "html5ever" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" -dependencies = [ - "log", - "markup5ever", -] - [[package]] name = "http" version = "1.5.0" @@ -3450,7 +2802,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-util", @@ -3469,40 +2821,6 @@ dependencies = [ "windows-registry", ] -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.2", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ico" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" -dependencies = [ - "byteorder", - "png 0.17.16", -] - [[package]] name = "icu_collections" version = "2.2.0" @@ -3585,12 +2903,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "1.1.0" @@ -3622,21 +2934,10 @@ dependencies = [ "byteorder-lite", "moxcms", "num-traits", - "png 0.18.1", + "png", "tiff", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -3645,17 +2946,6 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.1", - "serde", - "serde_core", -] - -[[package]] -name = "infer" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" -dependencies = [ - "cfb", ] [[package]] @@ -3937,7 +3227,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19371274ea75ac1edb3431b08b823aad0dba48124c8fe7efbc4c3d8a30a22327" dependencies = [ - "base64 0.22.1", + "base64", "bitflags 2.13.1", "futures-util", "http-body-util", @@ -4087,7 +3377,7 @@ checksum = "7692ac83a98e4b3ac01405e311bbbd5a33683447032986ed92a4a44d46ad6344" dependencies = [ "ironrdp-async", "ironrdp-connector", - "reqwest 0.12.28", + "reqwest", "tokio", "url", ] @@ -4117,6 +3407,19 @@ dependencies = [ "winit", ] +[[package]] +name = "is" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08f9118d003d441f79c1070e84d0a2a89f35721ca8ae0cd5e1f9026dc4a2517" +dependencies = [ + "crc", + "serde", + "str0m-proto", + "subtle", + "tracing", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -4138,7 +3441,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7660d28d24a831d690228a275d544654a30f3b167a8e491cf31af5fe5058b546" dependencies = [ - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -4156,82 +3459,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" -dependencies = [ - "bitflags 1.3.2", - "glib 0.18.5", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "jiff" -version = "0.2.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" -dependencies = [ - "defmt", - "jiff-core", - "jiff-static", - "jiff-tzdb-platform", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", - "windows-link 0.2.1", -] - -[[package]] -name = "jiff-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" -dependencies = [ - "defmt", -] - -[[package]] -name = "jiff-static" -version = "0.2.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" -dependencies = [ - "jiff-core", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "jiff-tzdb" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" - -[[package]] -name = "jiff-tzdb-platform" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" -dependencies = [ - "jiff-tzdb", -] - [[package]] name = "jni" version = "0.21.1" @@ -4262,7 +3489,7 @@ dependencies = [ "simd_cesu8", "thiserror 2.0.20", "walkdir", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4327,28 +3554,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json-patch" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonptr" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "keccak" version = "0.2.1" @@ -4359,17 +3564,6 @@ dependencies = [ "cpufeatures 0.3.0", ] -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.13.1", - "serde", - "unicode-segmentation", -] - [[package]] name = "keyring" version = "3.6.3" @@ -4427,7 +3621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4544,17 +3738,6 @@ dependencies = [ "libc", ] -[[package]] -name = "markup5ever" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" -dependencies = [ - "log", - "tendril", - "web_atoms", -] - [[package]] name = "matchers" version = "0.2.0" @@ -4634,19 +3817,13 @@ checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" dependencies = [ "bitflags 2.13.1", "block", - "core-graphics-types 0.1.3", + "core-graphics-types", "foreign-types", "log", "objc", "paste", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -4685,27 +3862,6 @@ dependencies = [ "pxfm", ] -[[package]] -name = "muda" -version = "0.19.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "once_cell", - "png 0.18.1", - "serde", - "thiserror 2.0.20", - "windows-sys 0.61.2", -] - [[package]] name = "muldiv" version = "1.0.1" @@ -4744,7 +3900,7 @@ dependencies = [ "cfg_aliases 0.2.2", "codespan-reporting", "hexf-parse", - "indexmap 2.14.0", + "indexmap", "log", "rustc-hash 1.1.0", "spirv", @@ -4793,12 +3949,6 @@ dependencies = [ "jni-sys 0.3.1", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - [[package]] name = "nix" version = "0.26.4" @@ -4840,6 +3990,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -4931,7 +4090,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", @@ -4969,7 +4128,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", - "objc2-exception-helper", ] [[package]] @@ -4982,8 +4140,8 @@ dependencies = [ "block2 0.5.1", "libc", "objc2 0.5.2", - "objc2-core-data 0.2.2", - "objc2-core-image 0.2.2", + "objc2-core-data", + "objc2-core-image", "objc2-foundation 0.2.2", "objc2-quartz-core 0.2.2", ] @@ -4995,9 +4153,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.13.1", - "block2 0.6.2", "objc2 0.6.4", - "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", ] @@ -5036,21 +4192,10 @@ dependencies = [ "bitflags 2.13.1", "block2 0.5.1", "objc2 0.5.2", - "objc2-core-location 0.2.2", + "objc2-core-location", "objc2-foundation 0.2.2", ] -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.13.1", - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - [[package]] name = "objc2-contacts" version = "0.2.2" @@ -5097,16 +4242,6 @@ dependencies = [ "objc2-foundation 0.2.2", ] -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - [[package]] name = "objc2-core-foundation" version = "0.3.2" @@ -5145,16 +4280,6 @@ dependencies = [ "objc2-metal", ] -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - [[package]] name = "objc2-core-location" version = "0.2.2" @@ -5167,43 +4292,12 @@ dependencies = [ "objc2-foundation 0.2.2", ] -[[package]] -name = "objc2-core-location" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" -dependencies = [ - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.13.1", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-core-graphics", -] - [[package]] name = "objc2-encode" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" -[[package]] -name = "objc2-exception-helper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" -dependencies = [ - "cc", -] - [[package]] name = "objc2-foundation" version = "0.2.2" @@ -5318,37 +4412,16 @@ dependencies = [ "bitflags 2.13.1", "block2 0.5.1", "objc2 0.5.2", - "objc2-cloud-kit 0.2.2", - "objc2-core-data 0.2.2", - "objc2-core-image 0.2.2", - "objc2-core-location 0.2.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", "objc2-foundation 0.2.2", "objc2-link-presentation", "objc2-quartz-core 0.2.2", "objc2-symbols", "objc2-uniform-type-identifiers", - "objc2-user-notifications 0.2.2", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.13.1", - "block2 0.6.2", - "objc2 0.6.4", - "objc2-cloud-kit 0.3.2", - "objc2-core-data 0.3.2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image 0.3.2", - "objc2-core-location 0.3.2", - "objc2-core-text", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", - "objc2-user-notifications 0.3.2", + "objc2-user-notifications", ] [[package]] @@ -5371,34 +4444,10 @@ dependencies = [ "bitflags 2.13.1", "block2 0.5.1", "objc2 0.5.2", - "objc2-core-location 0.2.2", + "objc2-core-location", "objc2-foundation 0.2.2", ] -[[package]] -name = "objc2-user-notifications" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" -dependencies = [ - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-web-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" -dependencies = [ - "bitflags 2.13.1", - "block2 0.6.2", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-foundation 0.3.2", -] - [[package]] name = "oid" version = "0.2.1" @@ -5450,12 +4499,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "option-operations" version = "0.6.1" @@ -5577,31 +4620,6 @@ dependencies = [ "sha2 0.11.0", ] -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib 0.18.5", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "system-deps 6.2.2", -] - [[package]] name = "parking" version = "2.2.1" @@ -5628,7 +4646,7 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -5659,7 +4677,7 @@ version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64 0.22.1", + "base64", "serde_core", ] @@ -5687,59 +4705,6 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "phf" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" -dependencies = [ - "phf_macros", - "phf_shared", - "serde", -] - -[[package]] -name = "phf_codegen" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" -dependencies = [ - "fastrand", - "phf_shared", -] - -[[package]] -name = "phf_macros" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "phf_shared" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" -dependencies = [ - "siphasher", -] - [[package]] name = "picky" version = "7.0.0-rc.25" @@ -5749,7 +4714,7 @@ dependencies = [ "aes 0.9.2", "aes-gcm 0.11.0-rc.4", "aes-kw", - "base64 0.22.1", + "base64", "cbc 0.2.1", "crypto-bigint 0.7.5", "crypto-common 0.2.2", @@ -5820,7 +4785,7 @@ version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "859d4117bd1b1dc5646359ee7243c50c5000c0920ea2d1fb120335a2f4c684b8" dependencies = [ - "base64 0.22.1", + "base64", "crypto-bigint 0.7.5", "oid", "picky-asn1", @@ -5942,32 +4907,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" -[[package]] -name = "plist" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" -dependencies = [ - "base64 0.22.1", - "indexmap 2.14.0", - "quick-xml", - "serde", - "time", -] - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - [[package]] name = "png" version = "0.18.1" @@ -6035,21 +4974,6 @@ dependencies = [ "universal-hash 0.6.1", ] -[[package]] -name = "portable-atomic" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" - -[[package]] -name = "portable-atomic-util" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" -dependencies = [ - "portable-atomic", -] - [[package]] name = "portable-pty" version = "0.9.0" @@ -6068,7 +4992,7 @@ dependencies = [ "shared_library", "shell-words", "winapi", - "winreg 0.10.1", + "winreg", ] [[package]] @@ -6095,12 +5019,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - [[package]] name = "presser" version = "0.3.1" @@ -6148,57 +5066,13 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a5390699eb9ac50677729fda96fb8339d4629f257cc6cfa6eaa673730f8f63" -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - [[package]] name = "proc-macro-crate" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "toml_edit", ] [[package]] @@ -6443,6 +5317,7 @@ version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" dependencies = [ + "aws-lc-rs", "pem", "ring", "rustls-pki-types", @@ -6478,49 +5353,6 @@ dependencies = [ "bitflags 2.13.1", ] -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.20", -] - -[[package]] -name = "ref-cast" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - [[package]] name = "regex-automata" version = "0.4.18" @@ -6563,7 +5395,7 @@ name = "remotedesk-agent-runtime" version = "0.2.19" dependencies = [ "async-trait", - "base64 0.22.1", + "base64", "bytes", "clap", "ed25519-dalek 2.2.0", @@ -6577,7 +5409,8 @@ dependencies = [ "reis", "remotedesk-edge-service", "remotedesk-protocol", - "reqwest 0.12.28", + "remotedesk-webrtc-core", + "reqwest", "rtc", "rustix 1.1.4", "rustls", @@ -6598,21 +5431,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "remotedesk-app" -version = "0.2.19" -dependencies = [ - "getrandom 0.3.4", - "sha2 0.10.9", - "tauri", - "tauri-build", -] - [[package]] name = "remotedesk-client-core" version = "0.2.19" dependencies = [ - "base64 0.22.1", + "base64", "getrandom 0.3.4", "hmac 0.13.0", "remotedesk-protocol", @@ -6627,11 +5450,11 @@ dependencies = [ name = "remotedesk-control-service" version = "0.2.19" dependencies = [ - "base64 0.22.1", + "base64", "ed25519-dalek 2.2.0", "remotedesk-client-core", "remotedesk-credential-store", - "reqwest 0.12.28", + "reqwest", "rusqlite", "semver", "serde", @@ -6646,7 +5469,7 @@ dependencies = [ name = "remotedesk-credential-store" version = "0.2.19" dependencies = [ - "base64 0.22.1", + "base64", "inquire", "keyring", "remotedesk-client-core", @@ -6659,7 +5482,7 @@ dependencies = [ name = "remotedesk-edge-service" version = "0.2.19" dependencies = [ - "base64 0.22.1", + "base64", "clap", "ed25519-dalek 2.2.0", "hmac 0.13.0", @@ -6681,6 +5504,7 @@ dependencies = [ "egui-wgpu", "egui-winit", "pollster", + "remotedesk-webrtc-core", "wgpu", "winit", ] @@ -6737,12 +5561,23 @@ dependencies = [ "winit", ] +[[package]] +name = "remotedesk-webrtc-core" +version = "0.2.19" +dependencies = [ + "bytes", + "remotedesk-protocol", + "serde", + "serde_json", + "str0m", +] + [[package]] name = "remotedesk-windows-agent" version = "0.2.19" dependencies = [ "anyhow", - "base64 0.22.1", + "base64", "clap", "cpal", "flate2", @@ -6796,7 +5631,7 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-core", @@ -6831,40 +5666,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "reqwest" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "sync_wrapper", - "tokio", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] - [[package]] name = "rfc6979" version = "0.4.0" @@ -6895,7 +5696,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -6908,7 +5709,7 @@ dependencies = [ "bytecheck", "bytes", "hashbrown 0.17.1", - "indexmap 2.14.0", + "indexmap", "munge", "ptr_meta", "rancor", @@ -7197,7 +5998,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49cb2594b91ac9a3412b4b5daac685f92ed38342e2f589b303885346e57d035f" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "crc", "lazy_static", @@ -7302,7 +6103,7 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -7387,7 +6188,7 @@ dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -7426,57 +6227,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.119", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -7502,6 +6252,21 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "sctp-proto" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f895c3c33ae20283f9129bd09db2ca69138798b372ef2b98bd2946d23ea4819b" +dependencies = [ + "bytes", + "crc", + "log", + "rand 0.9.5", + "rustc-hash 2.1.2", + "slab", + "thiserror 2.0.20", +] + [[package]] name = "sec1" version = "0.7.3" @@ -7562,34 +6327,11 @@ dependencies = [ "libc", ] -[[package]] -name = "selectors" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" -dependencies = [ - "bitflags 2.13.1", - "cssparser", - "derive_more", - "log", - "new_debug_unreachable", - "phf", - "phf_codegen", - "precomputed-hash", - "rustc-hash 2.1.2", - "servo_arc", - "smallvec", -] - [[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -dependencies = [ - "serde", - "serde_core", -] [[package]] name = "serde" @@ -7601,18 +6343,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] - [[package]] name = "serde_bytes" version = "0.11.19" @@ -7643,17 +6373,6 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "serde_json" version = "1.0.151" @@ -7678,15 +6397,6 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - [[package]] name = "serde_spanned" version = "1.1.1" @@ -7708,39 +6418,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "3.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" -dependencies = [ - "base64 0.22.1", - "bs58", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.14.0", - "jiff", - "schemars 0.9.0", - "schemars 1.2.2", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "serdect" version = "0.4.3" @@ -7762,37 +6439,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "servo_arc" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" -dependencies = [ - "stable_deref_trait", -] - [[package]] name = "sha1" version = "0.10.7" @@ -7952,12 +6598,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" -[[package]] -name = "siphasher" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" - [[package]] name = "slab" version = "0.4.12" @@ -8093,32 +6733,6 @@ dependencies = [ "x11rb", ] -[[package]] -name = "soup3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib 0.18.5", - "libc", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" -dependencies = [ - "gio-sys 0.18.1", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "system-deps 6.2.2", -] - [[package]] name = "spin" version = "0.9.9" @@ -8232,36 +6846,59 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "str0m" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840d11fdd44459d71bc7b3dd6796f55e9ac86c8e3fa8678098a8642f5701c5ff" +dependencies = [ + "arrayvec", + "base64ct", + "combine", + "dimpl", + "fastrand", + "is", + "sctp-proto", + "serde", + "str0m-aws-lc-rs", + "str0m-proto", + "subtle", + "time", + "tracing", +] + +[[package]] +name = "str0m-aws-lc-rs" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73269314334fc6bfa8024f09dd652b9f5068360bf623c871e3d8d18e26d8035a" +dependencies = [ + "aws-lc-rs", + "dimpl", + "str0m-proto", + "time", +] + +[[package]] +name = "str0m-proto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8f3d99f2cf6c76a502e45feb896ea71e54787ede8744bcdb215acfbfcb905dd" +dependencies = [ + "base64ct", + "dimpl", + "fastrand", + "serde", + "subtle", + "time", +] + [[package]] name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -[[package]] -name = "string_cache" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared", - "precomputed-hash", -] - -[[package]] -name = "string_cache_codegen" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", -] - [[package]] name = "strsim" version = "0.11.1" @@ -8283,7 +6920,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", @@ -8305,17 +6942,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "swift-rs" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - [[package]] name = "syn" version = "1.0.109" @@ -8390,308 +7016,31 @@ dependencies = [ "libc", ] -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr 0.15.8", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - [[package]] name = "system-deps" version = "7.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" dependencies = [ - "cfg-expr 0.20.8", - "heck 0.5.0", + "cfg-expr", + "heck", "pkg-config", - "toml 1.1.4+spec-1.1.0", + "toml", "version-compare", ] -[[package]] -name = "tao" -version = "0.35.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" -dependencies = [ - "bitflags 2.13.1", - "block2 0.6.2", - "core-foundation 0.10.1", - "core-graphics 0.25.0", - "crossbeam-channel", - "dispatch2", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gtk", - "jni 0.21.1", - "libc", - "log", - "ndk", - "ndk-sys 0.6.0+11769913", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-foundation 0.3.2", - "objc2-ui-kit 0.3.2", - "once_cell", - "parking_lot", - "percent-encoding", - "raw-window-handle", - "tao-macros", - "unicode-segmentation", - "url", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", -] - -[[package]] -name = "tao-macros" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "target-lexicon" version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" -[[package]] -name = "tauri" -version = "2.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" -dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs", - "dunce", - "embed_plist", - "getrandom 0.3.4", - "glob", - "gtk", - "heck 0.5.0", - "http", - "jni 0.21.1", - "libc", - "log", - "mime", - "muda", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-foundation 0.3.2", - "objc2-ui-kit 0.3.2", - "objc2-web-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest 0.13.4", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.20", - "tokio", - "url", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows 0.61.3", -] - -[[package]] -name = "tauri-build" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs", - "glob", - "heck 0.5.0", - "json-patch", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" -dependencies = [ - "base64 0.22.1", - "ico", - "json-patch", - "plist", - "png 0.17.16", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2 0.10.9", - "syn 2.0.119", - "tauri-utils", - "thiserror 2.0.20", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.119", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-runtime" -version = "2.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" -dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni 0.21.1", - "objc2 0.6.4", - "objc2-ui-kit 0.3.2", - "objc2-web-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.20", - "url", - "webkit2gtk", - "webview2-com", - "windows 0.61.3", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" -dependencies = [ - "gtk", - "http", - "jni 0.21.1", - "log", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows 0.61.3", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" -dependencies = [ - "anyhow", - "cargo_metadata", - "ctor 0.8.0", - "dom_query", - "dunce", - "glob", - "http", - "infer", - "json-patch", - "log", - "memchr", - "phf", - "plist", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.20", - "toml 1.1.4+spec-1.1.0", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" -dependencies = [ - "dunce", - "embed-resource", - "toml 1.1.4+spec-1.1.0", -] - [[package]] name = "tempfile" version = "3.27.0" @@ -8705,15 +7054,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "tendril" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" -dependencies = [ - "new_debug_unreachable", -] - [[package]] name = "termcolor" version = "1.4.1" @@ -8849,7 +7189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a90a0ca3ee6a69f2ad28fd11621a4c3f03b371f366be500b64df260c4ffbafb4" dependencies = [ "as-raw-xcb-connection", - "ctor 0.10.1", + "ctor", "libloading", "pkg-config", "tracing", @@ -8982,64 +7322,19 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap 2.14.0", - "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.15", -] - [[package]] name = "toml" version = "1.1.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" dependencies = [ - "indexmap 2.14.0", + "indexmap", "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 1.1.1+spec-1.1.0", + "serde_spanned", + "toml_datetime", "toml_parser", "toml_writer", - "winnow 1.0.4", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", + "winnow", ] [[package]] @@ -9051,40 +7346,16 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.14.0", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.14.0", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ - "indexmap 2.14.0", - "toml_datetime 1.1.1+spec-1.1.0", + "indexmap", + "toml_datetime", "toml_parser", - "winnow 1.0.4", + "winnow", ] [[package]] @@ -9093,7 +7364,7 @@ version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ - "winnow 1.0.4", + "winnow", ] [[package]] @@ -9248,12 +7519,6 @@ dependencies = [ "rustc-hash 2.1.2", ] -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - [[package]] name = "typenum" version = "1.20.1" @@ -9271,47 +7536,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicase" version = "2.9.0" @@ -9368,6 +7592,12 @@ dependencies = [ "ctutils", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -9384,19 +7614,6 @@ dependencies = [ "idna", "percent-encoding", "serde", - "serde_derive", -] - -[[package]] -name = "urlpattern" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" -dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", ] [[package]] @@ -9447,26 +7664,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "walkdir" version = "2.5.0" @@ -9574,19 +7771,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-streams" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wayland-backend" version = "0.3.16" @@ -9742,18 +7926,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web_atoms" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" -dependencies = [ - "phf", - "phf_codegen", - "string_cache", - "string_cache_codegen", -] - [[package]] name = "webbrowser" version = "1.2.4" @@ -9770,50 +7942,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "webkit2gtk" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys 0.18.1", - "glib 0.18.5", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys 0.18.1", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps 6.2.2", -] - [[package]] name = "webpki-roots" version = "1.0.7" @@ -9841,42 +7969,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "webview2-com" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-implement 0.60.2", - "windows-interface 0.59.3", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "webview2-com-sys" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" -dependencies = [ - "thiserror 2.0.20", - "windows 0.61.3", - "windows-core 0.61.2", -] - [[package]] name = "weezl" version = "0.1.12" @@ -9920,7 +8012,7 @@ dependencies = [ "bitflags 2.13.1", "cfg_aliases 0.2.2", "document-features", - "indexmap 2.14.0", + "indexmap", "log", "naga", "once_cell", @@ -9948,7 +8040,7 @@ dependencies = [ "block", "bytemuck", "cfg_aliases 0.2.2", - "core-graphics-types 0.1.3", + "core-graphics-types", "glow", "glutin_wgl_sys", "gpu-alloc", @@ -10042,21 +8134,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "window-vibrancy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" -dependencies = [ - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", -] - [[package]] name = "windows" version = "0.58.0" @@ -10067,38 +8144,16 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections 0.2.0", - "windows-core 0.61.2", - "windows-future 0.2.1", - "windows-link 0.1.3", - "windows-numerics 0.2.0", -] - [[package]] name = "windows" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections 0.3.2", + "windows-collections", "windows-core 0.62.2", - "windows-future 0.3.2", - "windows-numerics 0.3.1", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", + "windows-future", + "windows-numerics", ] [[package]] @@ -10123,19 +8178,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - [[package]] name = "windows-core" version = "0.62.2" @@ -10144,22 +8186,11 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement 0.60.2", "windows-interface 0.59.3", - "windows-link 0.2.1", + "windows-link", "windows-result 0.4.1", "windows-strings 0.5.1", ] -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading 0.1.0", -] - [[package]] name = "windows-future" version = "0.3.2" @@ -10167,8 +8198,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ "windows-core 0.62.2", - "windows-link 0.2.1", - "windows-threading 0.2.1", + "windows-link", + "windows-threading", ] [[package]] @@ -10215,28 +8246,12 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - [[package]] name = "windows-numerics" version = "0.3.1" @@ -10244,7 +8259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ "windows-core 0.62.2", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -10253,7 +8268,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows-result 0.4.1", "windows-strings 0.5.1", ] @@ -10267,22 +8282,13 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -10295,22 +8301,13 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-strings" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -10355,7 +8352,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -10395,7 +8392,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -10406,31 +8403,13 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-threading" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-version" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" -dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -10587,7 +8566,7 @@ dependencies = [ "cfg_aliases 0.2.2", "concurrent-queue", "core-foundation 0.9.4", - "core-graphics 0.23.2", + "core-graphics", "cursor-icon", "dpi", "js-sys", @@ -10597,7 +8576,7 @@ dependencies = [ "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", - "objc2-ui-kit 0.2.2", + "objc2-ui-kit", "orbclient", "percent-encoding", "pin-project", @@ -10623,21 +8602,6 @@ dependencies = [ "xkbcommon-dl", ] -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" - [[package]] name = "winnow" version = "1.0.4" @@ -10656,16 +8620,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "winreg" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" -dependencies = [ - "cfg-if", - "windows-sys 0.59.0", -] - [[package]] name = "winscard" version = "0.3.3" @@ -10701,48 +8655,6 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" -[[package]] -name = "wry" -version = "0.55.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" -dependencies = [ - "base64 0.22.1", - "block2 0.6.2", - "cookie", - "crossbeam-channel", - "dirs", - "dom_query", - "dpi", - "dunce", - "gtk", - "http", - "javascriptcore-rs", - "jni 0.21.1", - "libc", - "ndk", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-ui-kit 0.3.2", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2 0.10.9", - "soup3", - "tao-macros", - "thiserror 2.0.20", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", -] - [[package]] name = "wyz" version = "0.5.1" @@ -10829,7 +8741,7 @@ dependencies = [ "data-encoding", "der-parser 9.0.0", "lazy_static", - "nom", + "nom 7.1.3", "oid-registry 0.7.1", "rusticata-macros", "thiserror 1.0.69", @@ -10843,10 +8755,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" dependencies = [ "asn1-rs 0.7.2", + "aws-lc-rs", "data-encoding", "der-parser 10.0.0", "lazy_static", - "nom", + "nom 7.1.3", "oid-registry 0.8.1", "ring", "rusticata-macros", @@ -10962,7 +8875,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 1.0.4", + "winnow", "zbus_macros", "zbus_names", "zvariant", @@ -10974,7 +8887,7 @@ version = "5.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 3.0.3", @@ -10990,7 +8903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" dependencies = [ "serde", - "winnow 1.0.4", + "winnow", "zvariant", ] @@ -11127,7 +9040,7 @@ dependencies = [ "endi", "enumflags2", "serde", - "winnow 1.0.4", + "winnow", "zcheapstr", "zvariant_derive", "zvariant_utils", @@ -11139,7 +9052,7 @@ version = "5.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d496a145685283b67e232bd9e47377f6b60ad9d51e3601b23867f77c42477f96" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 3.0.3", @@ -11156,5 +9069,5 @@ dependencies = [ "quote", "serde", "syn 3.0.3", - "winnow 1.0.4", + "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 196f290..d7e0f4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,9 +4,9 @@ members = [ "agent/agent-core", "agent/agent-runtime", "agent/windows-agent", - "client/app-shell", "client/native-gui", "client/crates/client-core", + "client/crates/webrtc-core", "client/helpers/control-service", "client/helpers/credential-store", "client/helpers/native-video", diff --git a/agent/agent-core/src/lib.rs b/agent/agent-core/src/lib.rs index f3906a0..f9db656 100644 --- a/agent/agent-core/src/lib.rs +++ b/agent/agent-core/src/lib.rs @@ -679,7 +679,7 @@ mod tests { } #[test] - fn xorg_shm_is_rejected_by_strict_policy_and_allowed_by_compatibility() { + fn xorg_shm_is_rejected_by_the_only_policy() { let capture = CaptureMemoryPath::CpuMemory; let candidates = [candidate( "renderD128", @@ -700,15 +700,6 @@ mod tests { path: CaptureMemoryPath::CpuMemory } ); - - let selected = select_encoder( - DesktopBackend::XorgShmCompatibility, - ZeroCopyPolicy::RequiredEndToEnd, - &capture, - &candidates, - ) - .unwrap(); - assert_eq!(selected.transfer_mode, TransferMode::CpuUpload); } #[test] @@ -774,7 +765,7 @@ mod tests { } #[test] - fn compatibility_policy_explicitly_allows_cross_adapter_copy() { + fn cross_adapter_copy_is_always_rejected() { let capture = CaptureMemoryPath::DmaBuf { adapter: adapter("gpu-0"), }; @@ -786,23 +777,23 @@ mod tests { }, )]; - let selected = select_encoder( + let error = select_encoder( DesktopBackend::WaylandPipeWire, ZeroCopyPolicy::RequiredEndToEnd, &capture, &candidates, ) - .unwrap(); - - assert_eq!(selected.transfer_mode, TransferMode::CrossAdapterCopy); + .unwrap_err(); assert_eq!( - selected.protocol_status(), - MemoryPathStatus::CrossAdapterCopy + error, + SelectionError::NoVerifiedSameAdapterEncoder { + capture_adapter: Some(adapter("gpu-0")) + } ); } #[test] - fn opaque_capture_is_only_available_in_compatibility_mode() { + fn opaque_capture_is_always_rejected() { let capture = CaptureMemoryPath::OpaqueGpu { adapter: Some(adapter("gpu-0")), }; @@ -825,16 +816,6 @@ mod tests { path: capture.clone() } ); - - let selected = select_encoder( - DesktopBackend::WaylandPipeWire, - ZeroCopyPolicy::RequiredEndToEnd, - &capture, - &candidates, - ) - .unwrap(); - assert_eq!(selected.transfer_mode, TransferMode::OpaqueGpuPath); - assert!(!selected.transfer_mode.is_verified_zero_copy()); } #[test] @@ -929,7 +910,7 @@ mod tests { } #[test] - fn compatibility_revalidation_accepts_software_selection() { + fn revalidation_rejects_software_selection() { let zero_copy = EncoderSelection { adapter: adapter("gpu-0"), encoder_name: "vaapi-h264".into(), @@ -943,18 +924,20 @@ mod tests { .unwrap(); assert!(pipeline.resize(Resolution::new(1280, 720).unwrap())); - pipeline + let error = pipeline .finish_revalidation(EncoderSelection { adapter: adapter("cpu"), encoder_name: "software-h264".into(), transfer_mode: TransferMode::Software, }) - .unwrap(); - - assert_eq!(pipeline.state(), PipelineState::Active); + .unwrap_err(); assert_eq!( - pipeline.selection().unwrap().protocol_status(), - MemoryPathStatus::Software + error, + PipelineTransitionError::SelectionRejectedByPolicy { + policy: ZeroCopyPolicy::RequiredEndToEnd, + status: MemoryPathStatus::Software, + } ); + assert_eq!(pipeline.state(), PipelineState::Revalidating); } } diff --git a/agent/agent-core/src/multi_display.rs b/agent/agent-core/src/multi_display.rs index e190543..a8e2894 100644 --- a/agent/agent-core/src/multi_display.rs +++ b/agent/agent-core/src/multi_display.rs @@ -971,7 +971,7 @@ mod tests { } #[test] - fn compatibility_reports_each_cross_adapter_downgrade() { + fn cross_adapter_multi_display_plan_is_rejected() { let topology = two_display_topology(7); let sources = [source(1, "gpu-0"), source(2, "gpu-1")]; let candidate = candidate( @@ -987,28 +987,16 @@ mod tests { ], ); - let plan = MultiDisplayCapturePlan::create( - DesktopBackend::WaylandPipeWire, - ZeroCopyPolicy::RequiredEndToEnd, - &topology, - &sources, - &DisplaySelection::all(7), - &[candidate], - ) - .unwrap(); - assert_eq!( - plan.compliance, - MultiDisplayCompliance::CompatibilityDegraded - ); - assert_eq!( - plan.paths[0].transfer_mode, - TransferMode::SameAdapterZeroCopy - ); - assert_eq!(plan.paths[1].transfer_mode, TransferMode::CrossAdapterCopy); - assert_eq!( - plan.paths[1].transfer_mode.protocol_status(), - MemoryPathStatus::CrossAdapterCopy + MultiDisplayCapturePlan::create( + DesktopBackend::WaylandPipeWire, + ZeroCopyPolicy::RequiredEndToEnd, + &topology, + &sources, + &DisplaySelection::all(7), + &[candidate], + ), + Err(MultiDisplayPlanError::NoVerifiedSingleAdapterEncoder) ); } @@ -1044,17 +1032,14 @@ mod tests { } #[test] - fn strict_rejects_degraded_imports_while_compatibility_reports_them() { + fn degraded_imports_are_always_rejected() { let topology = topology(7, vec![descriptor(1, 0, 0, 1920, 1080, true)]); let sources = [source(1, "gpu-0")]; - for (probe_result, expected_mode) in [ - (ImportProbeResult::CpuUpload, TransferMode::CpuUpload), - (ImportProbeResult::Software, TransferMode::Software), - ( - ImportProbeResult::OpaqueGpuPath, - TransferMode::OpaqueGpuPath, - ), + for probe_result in [ + ImportProbeResult::CpuUpload, + ImportProbeResult::Software, + ImportProbeResult::OpaqueGpuPath, ] { let candidate = candidate("gpu-0", vec![probe(1, probe_result)]); assert_eq!( @@ -1068,21 +1053,6 @@ mod tests { ), Err(MultiDisplayPlanError::NoVerifiedSingleAdapterEncoder) ); - - let plan = MultiDisplayCapturePlan::create( - DesktopBackend::WaylandPipeWire, - ZeroCopyPolicy::RequiredEndToEnd, - &topology, - &sources, - &DisplaySelection::all(7), - &[candidate], - ) - .unwrap(); - assert_eq!( - plan.compliance, - MultiDisplayCompliance::CompatibilityDegraded - ); - assert_eq!(plan.paths[0].transfer_mode, expected_mode); } } @@ -1104,7 +1074,7 @@ mod tests { &DisplaySelection::all(7), &[incomplete], ), - Err(MultiDisplayPlanError::NoCommonEncoder) + Err(MultiDisplayPlanError::NoVerifiedSingleAdapterEncoder) ); } diff --git a/agent/agent-runtime/Cargo.toml b/agent/agent-runtime/Cargo.toml index 1254551..4a44e5e 100644 --- a/agent/agent-runtime/Cargo.toml +++ b/agent/agent-runtime/Cargo.toml @@ -41,6 +41,7 @@ futures-util = "0.3" rand = "0.9" rcgen = "0.14" remotedesk-protocol = { path = "../../protocol" } +remotedesk-webrtc-core = { path = "../../client/crates/webrtc-core" } reqwest = { version = "0.12.28", default-features = false, features = ["json", "rustls-tls"] } rtc = "0.20.1" rustls = { version = "0.23", default-features = false, features = ["logging", "ring", "std", "tls12"] } diff --git a/agent/agent-runtime/src/lib.rs b/agent/agent-runtime/src/lib.rs index 2c86b89..db9d391 100644 --- a/agent/agent-runtime/src/lib.rs +++ b/agent/agent-runtime/src/lib.rs @@ -13,6 +13,7 @@ mod linux_desktop; mod opus_capture; mod pairing; mod protocol; +mod quality; mod state; #[cfg(all(target_os = "linux", feature = "wayland-eis"))] mod wayland_eis; @@ -100,6 +101,7 @@ pub use protocol::{ OPUS_AUDIO_PROTOCOL_MINOR, PROTOCOL_MAJOR, PROTOCOL_MINOR, ServerEvent, ShellCommand, ShellEvent, }; +pub use quality::{QualityController, QualityLadderEntry, QualitySample, QualityState}; pub use state::{ AgentState, ClientGrant, DeviceIdentity, EdgePresenceStatus, PairingGrant, StateError, unix_timestamp, diff --git a/agent/agent-runtime/src/quality.rs b/agent/agent-runtime/src/quality.rs new file mode 100644 index 0000000..e856c64 --- /dev/null +++ b/agent/agent-runtime/src/quality.rs @@ -0,0 +1,236 @@ +use std::time::Duration; + +/// Network/encoder health buckets used by the session quality policy. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum QualityState { + Good, + Fair, + Poor, + Critical, +} + +/// One bounded sample collected from WebRTC/RDP diagnostics. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct QualitySample { + pub rtt: Duration, + pub loss_fraction: f32, + pub jitter: Duration, + pub available_bitrate_bps: u64, + pub encode_queue: Duration, +} + +/// Output ladder entry. The controller only moves between entries and never +/// exceeds the user supplied ceiling. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct QualityLadderEntry { + pub width: u16, + pub height: u16, + pub frames_per_second: u8, + pub bitrate_bps: u64, +} + +const DEFAULT_LADDER: [QualityLadderEntry; 6] = [ + QualityLadderEntry { + width: 3840, + height: 2160, + frames_per_second: 60, + bitrate_bps: 20_000_000, + }, + QualityLadderEntry { + width: 3840, + height: 2160, + frames_per_second: 30, + bitrate_bps: 12_000_000, + }, + QualityLadderEntry { + width: 2560, + height: 1440, + frames_per_second: 30, + bitrate_bps: 8_000_000, + }, + QualityLadderEntry { + width: 1920, + height: 1080, + frames_per_second: 30, + bitrate_bps: 5_000_000, + }, + QualityLadderEntry { + width: 1280, + height: 720, + frames_per_second: 30, + bitrate_bps: 2_500_000, + }, + QualityLadderEntry { + width: 1280, + height: 720, + frames_per_second: 15, + bitrate_bps: 1_000_000, + }, +]; + +/// Adaptive policy with fast degradation and hysteretic recovery. +#[derive(Clone, Debug)] +pub struct QualityController { + ladder: Vec, + ceiling: usize, + current: usize, + state: QualityState, + short_loss: f32, + short_rtt: Duration, + short_jitter: Duration, + bad_samples: u8, + good_samples: u8, +} + +impl QualityController { + /// Creates a controller using the documented six-step ladder. + pub fn new(max_width: u16, max_height: u16, max_fps: u8, max_bitrate_bps: u64) -> Self { + let mut ladder = DEFAULT_LADDER + .into_iter() + .filter(|entry| { + entry.width <= max_width + && entry.height <= max_height + && entry.frames_per_second <= max_fps + && entry.bitrate_bps <= max_bitrate_bps + }) + .collect::>(); + if ladder.is_empty() { + ladder.push(QualityLadderEntry { + width: max_width.max(200), + height: max_height.max(200), + frames_per_second: max_fps.clamp(1, 60), + bitrate_bps: max_bitrate_bps.max(128_000), + }); + } + let ceiling = ladder.len() - 1; + Self { + ladder, + ceiling, + current: 0, + state: QualityState::Good, + short_loss: 0.0, + short_rtt: Duration::ZERO, + short_jitter: Duration::ZERO, + bad_samples: 0, + good_samples: 0, + } + } + + #[must_use] + pub fn state(&self) -> QualityState { + self.state + } + #[must_use] + pub fn current(&self) -> QualityLadderEntry { + self.ladder[self.current] + } + + /// Records a sample and returns true when the active ladder entry changed. + pub fn observe(&mut self, sample: QualitySample) -> bool { + const ALPHA: f32 = 0.35; + self.short_loss = + self.short_loss * (1.0 - ALPHA) + sample.loss_fraction.clamp(0.0, 1.0) * ALPHA; + self.short_rtt = ewma_duration(self.short_rtt, sample.rtt, ALPHA); + self.short_jitter = ewma_duration(self.short_jitter, sample.jitter, ALPHA); + let next_state = if self.short_loss > 0.12 + || self.short_rtt > Duration::from_millis(500) + || sample.encode_queue > Duration::from_millis(250) + { + QualityState::Critical + } else if self.short_loss >= 0.05 + || self.short_rtt > Duration::from_millis(250) + || self.short_jitter > Duration::from_millis(80) + { + QualityState::Poor + } else if self.short_loss >= 0.01 + || self.short_rtt > Duration::from_millis(120) + || self.short_jitter > Duration::from_millis(30) + { + QualityState::Fair + } else { + QualityState::Good + }; + self.state = next_state; + if next_state == QualityState::Good { + self.bad_samples = 0; + self.good_samples = self.good_samples.saturating_add(1); + if self.good_samples >= 10 && self.current > 0 { + self.current -= 1; + self.good_samples = 0; + return true; + } + } else { + self.good_samples = 0; + self.bad_samples = self.bad_samples.saturating_add(1); + let threshold = if next_state == QualityState::Critical { + 1 + } else { + 2 + }; + if self.bad_samples >= threshold && self.current < self.ceiling { + self.current += 1; + self.bad_samples = 0; + return true; + } + } + false + } +} + +fn ewma_duration(previous: Duration, current: Duration, alpha: f32) -> Duration { + let value = + previous.as_secs_f64() * f64::from(1.0 - alpha) + current.as_secs_f64() * f64::from(alpha); + Duration::from_secs_f64(value) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn good() -> QualitySample { + QualitySample { + rtt: Duration::from_millis(20), + loss_fraction: 0.0, + jitter: Duration::from_millis(2), + available_bitrate_bps: 20_000_000, + encode_queue: Duration::ZERO, + } + } + #[test] + fn degrades_quickly_and_recovers_with_hysteresis() { + let mut controller = QualityController::new(3840, 2160, 60, 20_000_000); + let poor = QualitySample { + rtt: Duration::from_millis(300), + loss_fraction: 0.10, + jitter: Duration::from_millis(100), + available_bitrate_bps: 1_000_000, + encode_queue: Duration::ZERO, + }; + assert!(!controller.observe(poor)); + assert!(controller.observe(poor)); + assert_eq!(controller.state(), QualityState::Poor); + let degraded = controller.current(); + for _ in 0..100 { + controller.observe(good()); + } + assert_eq!(controller.state(), QualityState::Good); + assert!(controller.current().bitrate_bps >= degraded.bitrate_bps); + } + + #[test] + fn ceiling_is_respected() { + let mut controller = QualityController::new(1280, 720, 15, 1_000_000); + let critical = QualitySample { + rtt: Duration::from_secs(1), + loss_fraction: 0.5, + jitter: Duration::from_millis(200), + available_bitrate_bps: 1, + encode_queue: Duration::from_secs(1), + }; + for _ in 0..20 { + controller.observe(critical); + } + assert_eq!(controller.current().width, 1280); + assert_eq!(controller.current().frames_per_second, 15); + } +} diff --git a/agent/agent-runtime/src/webrtc_session.rs b/agent/agent-runtime/src/webrtc_session.rs index fb1e510..1f892dc 100644 --- a/agent/agent-runtime/src/webrtc_session.rs +++ b/agent/agent-runtime/src/webrtc_session.rs @@ -38,7 +38,11 @@ use webrtc::rtp_transceiver::RtpSender; use crate::protocol::DESKTOP_MAX_OPUS_PACKET_BYTES; use crate::{EdgeNegotiationClient, EdgeNegotiationError}; -use remotedesk_protocol::{EdgeNegotiationKind, MAX_ICE_CANDIDATE_BYTES, MAX_SDP_BYTES}; +use remotedesk_protocol::{EdgeNegotiationKind, MAX_SDP_BYTES}; +use remotedesk_webrtc_core::{ + IceServerConfig as SharedIceServerConfig, SignalKind as SharedSignalKind, + SignalMessage as SharedSignalMessage, validate_ice_servers, +}; const H264_PAYLOAD_TYPE: u8 = 102; const H264_RTX_PAYLOAD_TYPE: u8 = 103; @@ -48,10 +52,6 @@ const TRACK_QUEUE_CAPACITY: usize = 4; const POLL_INTERVAL: Duration = Duration::from_millis(100); const MIN_CONNECT_TIMEOUT: Duration = Duration::from_secs(5); const MAX_CONNECT_TIMEOUT: Duration = Duration::from_secs(60); -const MAX_ICE_SERVERS: usize = 8; -const MAX_ICE_URLS_PER_SERVER: usize = 8; -const MAX_ICE_URL_BYTES: usize = 2_048; -const MAX_CREDENTIAL_BYTES: usize = 1_024; const MAX_H264_ACCESS_UNIT_BYTES: usize = 16 * 1024 * 1024; const DEFAULT_VIDEO_FRAME_DURATION: Duration = Duration::from_micros(33_333); @@ -142,32 +142,19 @@ impl WebRtcSessionConfig { if self.bind_address.port() != 0 || self.connect_timeout < MIN_CONNECT_TIMEOUT || self.connect_timeout > MAX_CONNECT_TIMEOUT - || self.ice_servers.len() > MAX_ICE_SERVERS { return Err(WebRtcSessionError::InvalidConfiguration); } - for server in &self.ice_servers { - if server.urls.is_empty() - || server.urls.len() > MAX_ICE_URLS_PER_SERVER - || server.username.len() > MAX_CREDENTIAL_BYTES - || server.credential.len() > MAX_CREDENTIAL_BYTES - || server.username.is_empty() != server.credential.is_empty() - { - return Err(WebRtcSessionError::InvalidConfiguration); - } - for url in &server.urls { - if url.is_empty() - || url.len() > MAX_ICE_URL_BYTES - || url.chars().any(char::is_control) - || !matches!( - url.split_once(':').map(|(scheme, _)| scheme), - Some("stun" | "stuns" | "turn" | "turns") - ) - { - return Err(WebRtcSessionError::InvalidConfiguration); - } - } - } + let servers = self + .ice_servers + .iter() + .map(|server| SharedIceServerConfig { + urls: server.urls.clone(), + username: server.username.clone(), + credential: server.credential.clone(), + }) + .collect::>(); + validate_ice_servers(&servers).map_err(|_| WebRtcSessionError::InvalidConfiguration)?; Ok(()) } } @@ -1278,22 +1265,21 @@ fn validate_signal_payload( kind: EdgeNegotiationKind, payload: &str, ) -> Result<(), WebRtcSessionError> { - let valid = match kind { - EdgeNegotiationKind::Offer | EdgeNegotiationKind::Answer => { - !payload.is_empty() && payload.len() <= MAX_SDP_BYTES && !payload.contains('\0') + let shared_kind = match kind { + EdgeNegotiationKind::Offer => SharedSignalKind::Offer, + EdgeNegotiationKind::Answer => SharedSignalKind::Answer, + EdgeNegotiationKind::IceCandidate => SharedSignalKind::IceCandidate, + EdgeNegotiationKind::IceEnd => SharedSignalKind::IceEnd, + EdgeNegotiationKind::Restart => { + return payload + .is_empty() + .then_some(()) + .ok_or(WebRtcSessionError::InvalidSignal); } - EdgeNegotiationKind::IceCandidate => { - !payload.is_empty() - && payload.len() <= MAX_ICE_CANDIDATE_BYTES - && !payload.contains('\0') - } - EdgeNegotiationKind::IceEnd | EdgeNegotiationKind::Restart => payload.is_empty(), }; - if valid { - Ok(()) - } else { - Err(WebRtcSessionError::InvalidSignal) - } + SharedSignalMessage::new(shared_kind, payload) + .map(|_| ()) + .map_err(|_| WebRtcSessionError::InvalidSignal) } #[cfg(test)] @@ -1353,7 +1339,7 @@ mod tests { assert!( WebRtcSignalMessage::new( EdgeNegotiationKind::IceCandidate, - "x".repeat(MAX_ICE_CANDIDATE_BYTES + 1), + "x".repeat(remotedesk_protocol::MAX_ICE_CANDIDATE_BYTES + 1), ) .is_err() ); diff --git a/agent/windows-agent/src/capture.rs b/agent/windows-agent/src/capture.rs index 003d0da..0a2852c 100644 --- a/agent/windows-agent/src/capture.rs +++ b/agent/windows-agent/src/capture.rs @@ -1,15 +1,8 @@ -//! Windows Desktop Duplication compatibility capture. -//! -//! The staging/readback path is deliberately reported as software/cpu_upload. -//! It is a compatibility path, not an end-to-end GPU zero-copy pipeline. An -//! IDD swap-chain consumer can replace this module without changing the agent -//! control protocol or frame envelope. +//! Windows Desktop Duplication capture feeding the hardware encoder probe. #[cfg(windows)] mod windows_capture { use anyhow::Context as _; - use flate2::{Compression, write::ZlibEncoder}; - use std::io::Write as _; use std::time::{Duration, Instant}; use windows::Win32::Graphics::Direct3D::D3D_DRIVER_TYPE_UNKNOWN; use windows::Win32::Graphics::Direct3D11::{ @@ -24,19 +17,9 @@ mod windows_capture { }; use windows::core::Interface as _; - const FRAME_MAGIC: &[u8; 4] = b"RDWF"; - const FRAME_VERSION: u8 = 2; - const FRAME_HEADER_LEN: usize = 40; - const CODEC_BGRA_ZLIB: u8 = 1; const MAX_DIMENSION: u32 = 16_384; const MAX_RAW_FRAME_BYTES: usize = 256 * 1024 * 1024; - pub struct EncodedFrame { - pub bytes: Vec, - pub capture_latency: Duration, - pub encode_latency: Duration, - } - pub struct CapturedFrame { pub width: u32, pub height: u32, @@ -150,23 +133,6 @@ mod windows_capture { } } - pub fn capture_frame(&mut self) -> anyhow::Result { - let captured = self.capture_bgra_frame()?; - let encode_started = Instant::now(); - let bytes = encode_frame( - captured.width, - captured.height, - &captured.pixels, - captured.capture_latency, - Duration::ZERO, - )?; - Ok(EncodedFrame { - bytes, - capture_latency: captured.capture_latency, - encode_latency: encode_started.elapsed(), - }) - } - pub fn capture_bgra_frame(&mut self) -> anyhow::Result { let capture_started = Instant::now(); let mut info = DXGI_OUTDUPL_FRAME_INFO::default(); @@ -246,42 +212,6 @@ mod windows_capture { anyhow::ensure!(bytes <= MAX_RAW_FRAME_BYTES, "DDA frame exceeds 256 MiB"); Ok(()) } - - fn encode_frame( - width: u32, - height: u32, - pixels: &[u8], - capture: Duration, - encode: Duration, - ) -> anyhow::Result> { - let mut compressor = ZlibEncoder::new(Vec::new(), Compression::fast()); - compressor.write_all(pixels)?; - let compressed = compressor.finish()?; - let raw_len = u32::try_from(pixels.len()).context("DDA raw frame is too large")?; - let compressed_len = - u32::try_from(compressed.len()).context("DDA compressed frame is too large")?; - let mut frame = Vec::with_capacity(FRAME_HEADER_LEN + compressed.len()); - frame.extend_from_slice(FRAME_MAGIC); - frame.push(FRAME_VERSION); - frame.push(CODEC_BGRA_ZLIB); - frame.extend_from_slice(&0_u16.to_le_bytes()); - frame.extend_from_slice(&width.to_le_bytes()); - frame.extend_from_slice(&height.to_le_bytes()); - frame.extend_from_slice(&raw_len.to_le_bytes()); - frame.extend_from_slice(&compressed_len.to_le_bytes()); - frame.extend_from_slice( - &u64::try_from(capture.as_micros()) - .unwrap_or(u64::MAX) - .to_le_bytes(), - ); - frame.extend_from_slice( - &u64::try_from(encode.as_micros()) - .unwrap_or(u64::MAX) - .to_le_bytes(), - ); - frame.extend_from_slice(&compressed); - Ok(frame) - } } #[cfg(windows)] diff --git a/agent/windows-agent/src/main.rs b/agent/windows-agent/src/main.rs index 25d6731..995fc3e 100644 --- a/agent/windows-agent/src/main.rs +++ b/agent/windows-agent/src/main.rs @@ -22,11 +22,8 @@ mod media_ring; mod windows_security; const PREFERRED_CAPTURE_BACKEND: &str = "idd-headless-sdr"; -const SOFTWARE_FALLBACK_BACKEND: &str = "gdi-bgra-zlib"; -const CURRENT_MEDIA_TRANSPORT: &str = "rdwf_tcp_compatibility"; -const TARGET_MEDIA_TRANSPORT: &str = "hysteria2_video_datagram_plus_encoded_ring"; -const DEFAULT_FALLBACK_FPS: u8 = 10; -const MAX_FALLBACK_FPS: u8 = 15; +const CURRENT_MEDIA_TRANSPORT: &str = "unavailable_until_str0m_driver"; +const TARGET_MEDIA_TRANSPORT: &str = "str0m_webrtc_srtp"; const DEFAULT_VIDEO_CODEC: &str = "h264"; const DEFAULT_CAPTURE_MODE: &str = "strict_hardware"; const NO_LATENCY_SAMPLE: u64 = u64::MAX; @@ -71,17 +68,15 @@ struct Status { platform: &'static str, transport: &'static str, capture_backend: &'static str, - compatibility_capture_backend: &'static str, video_codecs: [&'static str; 3], audio_codec: &'static str, media_transport: &'static str, input_backend: &'static str, session_runtime: &'static str, strict_gpu_pipeline: &'static str, - software_fallback_backend: &'static str, software_fallback_available: bool, media_pipeline_state: &'static str, - supported_capture_modes: [&'static str; 2], + supported_capture_modes: [&'static str; 1], desktop_duplication: bool, hardware_h264_mft: bool, hardware_h264_encoder_verified: bool, @@ -101,17 +96,15 @@ fn main() -> anyhow::Result<()> { platform: "windows", transport: "native-agent", capture_backend: capabilities.capture_backend(), - compatibility_capture_backend: capabilities.capture_backend(), video_codecs: ["h264", "hevc", "av1"], audio_codec: "opus", media_transport: CURRENT_MEDIA_TRANSPORT, input_backend: "windows-send-input", session_runtime: "native runtime not started", strict_gpu_pipeline: "not_implemented", - software_fallback_backend: SOFTWARE_FALLBACK_BACKEND, - software_fallback_available: cfg!(windows), + software_fallback_available: false, media_pipeline_state: capabilities.pipeline_state(), - supported_capture_modes: ["strict_hardware", "compatibility"], + supported_capture_modes: ["strict_hardware"], desktop_duplication: capabilities.desktop_duplication, hardware_h264_mft: capabilities.hardware_h264_mft, hardware_h264_encoder_verified: capabilities.hardware_h264_encoder_verified, @@ -126,7 +119,6 @@ fn main() -> anyhow::Result<()> { println!("RemoteDesk Windows Agent"); println!("Transport: native Agent protocol"); println!("Capture: IDD Headless SDR (driver integration pending)"); - println!("Compatibility capture: {}", capabilities.capture_backend()); println!( "Video codecs: H.264={} HEVC={} AV1={}", capabilities.hardware_h264_mft, @@ -139,7 +131,7 @@ fn main() -> anyhow::Result<()> { ); println!("Input: SendInput"); println!("Session runtime: not started"); - println!("Software fallback: GDI / BGRA / zlib available"); + println!("Software fallback: disabled"); } } Command::Run => run_server("0.0.0.0:39501".parse()?)?, @@ -233,8 +225,6 @@ struct Hello<'a> { protocol_minor: u16, capture_backend: &'a str, preferred_capture_backend: &'a str, - compatibility_capture_backend: &'a str, - software_fallback_backend: &'a str, video_codecs: [&'a str; 3], audio_codec: &'a str, media_transport: &'a str, @@ -243,7 +233,7 @@ struct Hello<'a> { input_backend: &'a str, authenticated: bool, media_pipeline_state: &'a str, - supported_capture_modes: [&'a str; 2], + supported_capture_modes: [&'a str; 1], desktop_duplication: bool, hardware_h264_mft: bool, hardware_h264_encoder_verified: bool, @@ -310,16 +300,12 @@ fn requested_capture_mode(command: &AgentCommand) -> &str { command .capture_mode .as_deref() - .unwrap_or(if command.allow_software_fallback { - "compatibility" - } else { - DEFAULT_CAPTURE_MODE - }) + .unwrap_or(DEFAULT_CAPTURE_MODE) } fn validate_capture_mode(mode: &str) -> anyhow::Result<()> { anyhow::ensure!( - matches!(mode, "strict_hardware" | "compatibility"), + mode == "strict_hardware", "unsupported capture mode: {mode}" ); Ok(()) @@ -340,43 +326,14 @@ fn requested_video_codec(command: &AgentCommand) -> &str { .unwrap_or(DEFAULT_VIDEO_CODEC) } -fn software_desktop_response_for_backend( - mode: &str, - codec: &str, - fps: u8, - capture_backend: &str, -) -> serde_json::Value { - serde_json::json!({ - "kind": "desktop_opened", - "capture_backend": capture_backend, - "pixel_format": "bgra8", - "compression": "zlib", - "video_codec": "bgra-zlib", - "requested_video_codec": codec, - "capture_mode": mode, - "frames_per_second": fps, - "surface_type": "cpu_readback", - "cpu_map_count": 1, - "hardware_path_verified": false, - "degraded": true, - "media_transport": CURRENT_MEDIA_TRANSPORT, - "target_media_transport": TARGET_MEDIA_TRANSPORT - }) -} - fn unavailable_media_response(mode: &str, codec: &str) -> serde_json::Value { - let error = if mode == "strict_hardware" { - "HARDWARE_CAPTURE_UNAVAILABLE" - } else { - "HARDWARE_ENCODER_UNAVAILABLE" - }; serde_json::json!({ "kind": "desktop_unavailable", - "error": error, + "error": "HARDWARE_CAPTURE_UNAVAILABLE", "capture_mode": mode, "video_codec": codec, "media_pipeline_state": "hardware_probe_failed", - "software_fallback_available": cfg!(windows), + "software_fallback_available": false, "degraded": false }) } @@ -398,17 +355,15 @@ where protocol_minor: 0, capture_backend: capabilities.capture_backend(), preferred_capture_backend: PREFERRED_CAPTURE_BACKEND, - compatibility_capture_backend: capabilities.capture_backend(), - software_fallback_backend: SOFTWARE_FALLBACK_BACKEND, video_codecs: ["h264", "hevc", "av1"], audio_codec: "opus", media_transport: CURRENT_MEDIA_TRANSPORT, target_media_transport: TARGET_MEDIA_TRANSPORT, - software_fallback_available: cfg!(windows), + software_fallback_available: false, input_backend: "windows-send-input", authenticated, media_pipeline_state: capabilities.pipeline_state(), - supported_capture_modes: ["strict_hardware", "compatibility"], + supported_capture_modes: ["strict_hardware"], desktop_duplication: capabilities.desktop_duplication, hardware_h264_mft: capabilities.hardware_h264_mft, hardware_h264_encoder_verified: capabilities.hardware_h264_encoder_verified, @@ -514,25 +469,12 @@ where writer.write_all(b"\n").await?; continue; } - if !command.allow_software_fallback && capture_mode != "compatibility" { - let response = serde_json::json!({ - "kind": "desktop_unavailable", - "error": "COMPATIBILITY_MODE_REQUIRED", - "degraded": false - }); - writer - .write_all(serde_json::to_string(&response)?.as_bytes()) - .await?; - writer.write_all(b"\n").await?; - continue; - } if let Some(previous) = encoded_session.take() { drop(previous); } match start_encoded_session( session_id.to_owned(), command.frames_per_second.unwrap_or(30).clamp(1, 120), - command.allow_software_fallback, ) { Ok((info, session)) => { let response = serde_json::json!({ @@ -632,10 +574,6 @@ where .write_all(b"{\"kind\":\"input_failed\",\"error\":\"windows_only\"}\n") .await?; } else if command.kind == "open_application" { - if !command.allow_software_fallback { - writer.write_all(b"{\"kind\":\"application_error\",\"error\":\"software_fallback_required\"}\n").await?; - continue; - } match command .application .as_deref() @@ -643,22 +581,7 @@ where .transpose() { Ok(Some(_application)) => { - let fps = command - .frames_per_second - .unwrap_or(DEFAULT_FALLBACK_FPS) - .clamp(1, MAX_FALLBACK_FPS); - let response = software_desktop_response_for_backend( - capture_mode, - codec, - fps, - capabilities.capture_backend(), - ); - writer - .write_all(serde_json::to_string(&response)?.as_bytes()) - .await?; - writer.write_all(b"\n").await?; - stream_software_desktop(&mut writer, fps).await?; - return Ok(()); + writer.write_all(b"{\"kind\":\"application_error\",\"error\":\"HARDWARE_APPLICATION_CAPTURE_UNAVAILABLE\"}\n").await?; } Ok(None) => { writer.write_all(b"{\"kind\":\"application_error\",\"error\":\"application_id_required\"}\n").await?; @@ -675,29 +598,10 @@ where continue; } } else if command.kind == "open_desktop" { - if capture_mode == "strict_hardware" || !command.allow_software_fallback { - let response = - serde_json::to_string(&unavailable_media_response(capture_mode, codec))?; - writer.write_all(response.as_bytes()).await?; - writer.write_all(b"\n").await?; - continue; - } - let fps = command - .frames_per_second - .unwrap_or(DEFAULT_FALLBACK_FPS) - .clamp(1, MAX_FALLBACK_FPS); - let response = software_desktop_response_for_backend( - capture_mode, - codec, - fps, - capabilities.capture_backend(), - ); - writer - .write_all(serde_json::to_string(&response)?.as_bytes()) - .await?; + let response = serde_json::to_string(&unavailable_media_response(capture_mode, codec))?; + writer.write_all(response.as_bytes()).await?; writer.write_all(b"\n").await?; - stream_software_desktop(&mut writer, fps).await?; - return Ok(()); + continue; } else if command.kind == "status" { let response = serde_json::to_string(&serde_json::json!({ "kind": "status", @@ -707,9 +611,7 @@ where "media_transport": CURRENT_MEDIA_TRANSPORT, "target_media_transport": TARGET_MEDIA_TRANSPORT, "preferred_capture_backend": PREFERRED_CAPTURE_BACKEND, - "compatibility_capture_backend": capabilities.capture_backend(), - "software_fallback_backend": SOFTWARE_FALLBACK_BACKEND, - "software_fallback_available": cfg!(windows), + "software_fallback_available": false, "desktop_duplication": capabilities.desktop_duplication, "hardware_h264_mft": capabilities.hardware_h264_mft, "hardware_h264_encoder_verified": capabilities.hardware_h264_encoder_verified, @@ -717,7 +619,7 @@ where "hardware_av1_mft": capabilities.hardware_av1_mft, "hardware_pipeline_verified": capabilities.hardware_pipeline_verified, "degraded": !capabilities.hardware_pipeline_verified, - "supported_capture_modes": ["strict_hardware", "compatibility"], + "supported_capture_modes": ["strict_hardware"], "capture_latency_ms": latency_ms(&CAPTURE_LATENCY_US), "encode_latency_ms": latency_ms(&ENCODE_LATENCY_US), "frame_processing_latency_ms": latency_ms(&FRAME_PROCESSING_LATENCY_US) @@ -751,7 +653,6 @@ fn validate_session_id(session_id: &str) -> anyhow::Result<()> { fn start_encoded_session( session_id: String, fps: u8, - allow_software_fallback: bool, ) -> anyhow::Result<(EncodedSessionInfo, EncodedSession)> { let generation = ENCODED_GENERATION.fetch_add(1, Ordering::Relaxed).max(1); let session_started = Instant::now(); @@ -779,16 +680,6 @@ fn start_encoded_session( let (mut encoder, encoder_backend, hardware_path_verified) = match encoder::H264Encoder::new(width, height, u32::from(fps), true) { Ok(encoder) => (encoder, "hardware-mft", true), - Err(error) if allow_software_fallback => { - match encoder::H264Encoder::new(width, height, u32::from(fps), false) { - Ok(encoder) => (encoder, "software-mft", false), - Err(fallback) => { - let _ = ready_sender - .send(Err(format!("hardware: {error}; software: {fallback}"))); - return; - } - } - } Err(error) => { let _ = ready_sender .send(Err(format!("hardware H.264 encoder unavailable: {error}"))); @@ -1104,8 +995,6 @@ fn record_latency(value: &AtomicU64, elapsed: Duration) { struct AgentCommand { kind: String, #[serde(default)] - allow_software_fallback: bool, - #[serde(default)] capture_mode: Option, #[serde(default)] video_codec: Option, @@ -1195,7 +1084,7 @@ fn apply_input(command: &AgentCommand) -> anyhow::Result<()> { Ok(()) } -#[cfg(windows)] +#[cfg(any())] async fn stream_software_desktop(writer: &mut W, fps: u8) -> anyhow::Result<()> where W: AsyncWrite + Unpin, @@ -1226,7 +1115,7 @@ where Ok(()) } -#[cfg(windows)] +#[cfg(any())] fn capture_compatibility( fps: u8, sender: &tokio::sync::mpsc::Sender>>, @@ -1270,7 +1159,7 @@ fn capture_compatibility( } } -#[cfg(not(windows))] +#[cfg(any())] fn capture_compatibility( _fps: u8, _sender: &tokio::sync::mpsc::Sender>>, @@ -1278,7 +1167,7 @@ fn capture_compatibility( anyhow::bail!("gdi_software_fallback_unavailable") } -#[cfg(not(windows))] +#[cfg(any())] async fn stream_software_desktop(_writer: &mut W, _fps: u8) -> anyhow::Result<()> where W: AsyncWrite + Unpin, @@ -1286,7 +1175,7 @@ where anyhow::bail!("gdi_software_fallback_unavailable") } -#[cfg(windows)] +#[cfg(any())] mod fallback { use anyhow::Context as _; use flate2::{Compression, write::ZlibEncoder}; @@ -1496,27 +1385,23 @@ mod tests { use super::*; #[test] - fn software_fallback_is_opt_in() { + fn legacy_software_fallback_field_does_not_enable_compatibility() { let default: AgentCommand = serde_json::from_str(r#"{"kind":"open_desktop"}"#).unwrap(); - assert!(!default.allow_software_fallback); let enabled: AgentCommand = serde_json::from_str( r#"{"kind":"open_desktop","allow_software_fallback":true,"frames_per_second":12}"#, ) .unwrap(); - assert!(enabled.allow_software_fallback); assert_eq!(enabled.frames_per_second, Some(12)); assert_eq!(requested_capture_mode(&default), "strict_hardware"); - assert_eq!(requested_capture_mode(&enabled), "compatibility"); + assert_eq!(requested_capture_mode(&enabled), "strict_hardware"); } #[test] - fn explicit_capture_mode_overrides_legacy_fallback_flag() { - let command: AgentCommand = serde_json::from_str( - r#"{"kind":"open_desktop","allow_software_fallback":true,"capture_mode":"strict_hardware"}"#, - ) - .unwrap(); - assert_eq!(requested_capture_mode(&command), "strict_hardware"); - assert!(validate_capture_mode(requested_capture_mode(&command)).is_ok()); + fn compatibility_capture_mode_is_rejected() { + let command: AgentCommand = + serde_json::from_str(r#"{"kind":"open_desktop","capture_mode":"compatibility"}"#) + .unwrap(); + assert!(validate_capture_mode(requested_capture_mode(&command)).is_err()); } #[test] @@ -1527,17 +1412,9 @@ mod tests { } #[test] - fn codec_and_degraded_response_are_explicit() { + fn supported_codec_is_explicit() { assert!(validate_video_codec("h264").is_ok()); assert!(validate_video_codec("vp9").is_err()); - let response = software_desktop_response_for_backend( - "compatibility", - "h264", - 10, - SOFTWARE_FALLBACK_BACKEND, - ); - assert_eq!(response["degraded"], true); - assert_eq!(response["hardware_path_verified"], false); } #[test] diff --git a/client/app-shell/Cargo.toml b/client/app-shell/Cargo.toml deleted file mode 100644 index 534ae67..0000000 --- a/client/app-shell/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "remotedesk-app" -version.workspace = true -edition.workspace = true -license.workspace = true -rust-version.workspace = true - -[[bin]] -name = "remotedesk" -path = "src/main.rs" - -[target.'cfg(windows)'.dependencies] -getrandom = "0.3.3" -sha2 = "0.10" -tauri = { version = "2.8.5", default-features = false, features = ["wry"] } - -[target.'cfg(windows)'.build-dependencies] -tauri-build = { version = "2.4.1", features = [] } - -[lints] -workspace = true diff --git a/client/app-shell/build.rs b/client/app-shell/build.rs deleted file mode 100644 index eb180dc..0000000 --- a/client/app-shell/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - #[cfg(windows)] - tauri_build::build(); -} diff --git a/client/app-shell/gen/schemas/acl-manifests.json b/client/app-shell/gen/schemas/acl-manifests.json deleted file mode 100644 index 0eebfc4..0000000 --- a/client/app-shell/gen/schemas/acl-manifests.json +++ /dev/null @@ -1 +0,0 @@ -{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener","allow-supports-multiple-windows"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-supports-multiple-windows":{"identifier":"allow-supports-multiple-windows","description":"Enables the supports_multiple_windows command without any pre-configured scope.","commands":{"allow":["supports_multiple_windows"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-supports-multiple-windows":{"identifier":"deny-supports-multiple-windows","description":"Denies the supports_multiple_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["supports_multiple_windows"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-icon-with-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-icon-with-as-template":{"identifier":"allow-set-icon-with-as-template","description":"Enables the set_icon_with_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_with_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-icon-with-as-template":{"identifier":"deny-set-icon-with-as-template","description":"Denies the set_icon_with_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_with_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-activity-name","allow-scene-identifier","allow-internal-toggle-maximize"]},"permissions":{"allow-activity-name":{"identifier":"allow-activity-name","description":"Enables the activity_name command without any pre-configured scope.","commands":{"allow":["activity_name"],"deny":[]}},"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-scene-identifier":{"identifier":"allow-scene-identifier","description":"Enables the scene_identifier command without any pre-configured scope.","commands":{"allow":["scene_identifier"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-activity-name":{"identifier":"deny-activity-name","description":"Denies the activity_name command without any pre-configured scope.","commands":{"allow":[],"deny":["activity_name"]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-scene-identifier":{"identifier":"deny-scene-identifier","description":"Denies the scene_identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["scene_identifier"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/client/app-shell/gen/schemas/capabilities.json b/client/app-shell/gen/schemas/capabilities.json deleted file mode 100644 index 9e26dfe..0000000 --- a/client/app-shell/gen/schemas/capabilities.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/client/app-shell/gen/schemas/desktop-schema.json b/client/app-shell/gen/schemas/desktop-schema.json deleted file mode 100644 index 3286645..0000000 --- a/client/app-shell/gen/schemas/desktop-schema.json +++ /dev/null @@ -1,2292 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CapabilityFile", - "description": "Capability formats accepted in a capability file.", - "anyOf": [ - { - "description": "A single capability.", - "allOf": [ - { - "$ref": "#/definitions/Capability" - } - ] - }, - { - "description": "A list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - }, - { - "description": "A list of capabilities.", - "type": "object", - "required": [ - "capabilities" - ], - "properties": { - "capabilities": { - "description": "The list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - } - } - } - ], - "definitions": { - "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", - "type": "object", - "required": [ - "identifier", - "permissions" - ], - "properties": { - "identifier": { - "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", - "type": "string" - }, - "description": { - "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", - "default": "", - "type": "string" - }, - "remote": { - "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", - "anyOf": [ - { - "$ref": "#/definitions/CapabilityRemote" - }, - { - "type": "null" - } - ] - }, - "local": { - "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", - "default": true, - "type": "boolean" - }, - "windows": { - "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "webviews": { - "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "permissions": { - "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionEntry" - }, - "uniqueItems": true - }, - "platforms": { - "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Target" - } - } - } - }, - "CapabilityRemote": { - "description": "Configuration for remote URLs that are associated with the capability.", - "type": "object", - "required": [ - "urls" - ], - "properties": { - "urls": { - "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PermissionEntry": { - "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", - "anyOf": [ - { - "description": "Reference a permission or permission set by identifier.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - { - "description": "Reference a permission or permission set by identifier and extends its scope.", - "type": "object", - "allOf": [ - { - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - "allow": { - "description": "Data that defines what is allowed by the scope.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - }, - "deny": { - "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - } - } - } - ], - "required": [ - "identifier" - ] - } - ] - }, - "Identifier": { - "description": "Permission identifier", - "oneOf": [ - { - "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", - "type": "string", - "const": "core:default", - "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" - }, - { - "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`", - "type": "string", - "const": "core:app:default", - "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`" - }, - { - "description": "Enables the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-hide", - "markdownDescription": "Enables the app_hide command without any pre-configured scope." - }, - { - "description": "Enables the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-show", - "markdownDescription": "Enables the app_show command without any pre-configured scope." - }, - { - "description": "Enables the bundle_type command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-bundle-type", - "markdownDescription": "Enables the bundle_type command without any pre-configured scope." - }, - { - "description": "Enables the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-default-window-icon", - "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." - }, - { - "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-fetch-data-store-identifiers", - "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." - }, - { - "description": "Enables the identifier command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-identifier", - "markdownDescription": "Enables the identifier command without any pre-configured scope." - }, - { - "description": "Enables the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-name", - "markdownDescription": "Enables the name command without any pre-configured scope." - }, - { - "description": "Enables the register_listener command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-register-listener", - "markdownDescription": "Enables the register_listener command without any pre-configured scope." - }, - { - "description": "Enables the remove_data_store command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-remove-data-store", - "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." - }, - { - "description": "Enables the remove_listener command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-remove-listener", - "markdownDescription": "Enables the remove_listener command without any pre-configured scope." - }, - { - "description": "Enables the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-set-app-theme", - "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." - }, - { - "description": "Enables the set_dock_visibility command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-set-dock-visibility", - "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." - }, - { - "description": "Enables the supports_multiple_windows command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-supports-multiple-windows", - "markdownDescription": "Enables the supports_multiple_windows command without any pre-configured scope." - }, - { - "description": "Enables the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-tauri-version", - "markdownDescription": "Enables the tauri_version command without any pre-configured scope." - }, - { - "description": "Enables the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-version", - "markdownDescription": "Enables the version command without any pre-configured scope." - }, - { - "description": "Denies the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-hide", - "markdownDescription": "Denies the app_hide command without any pre-configured scope." - }, - { - "description": "Denies the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-show", - "markdownDescription": "Denies the app_show command without any pre-configured scope." - }, - { - "description": "Denies the bundle_type command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-bundle-type", - "markdownDescription": "Denies the bundle_type command without any pre-configured scope." - }, - { - "description": "Denies the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-default-window-icon", - "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." - }, - { - "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-fetch-data-store-identifiers", - "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." - }, - { - "description": "Denies the identifier command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-identifier", - "markdownDescription": "Denies the identifier command without any pre-configured scope." - }, - { - "description": "Denies the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-name", - "markdownDescription": "Denies the name command without any pre-configured scope." - }, - { - "description": "Denies the register_listener command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-register-listener", - "markdownDescription": "Denies the register_listener command without any pre-configured scope." - }, - { - "description": "Denies the remove_data_store command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-remove-data-store", - "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." - }, - { - "description": "Denies the remove_listener command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-remove-listener", - "markdownDescription": "Denies the remove_listener command without any pre-configured scope." - }, - { - "description": "Denies the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-set-app-theme", - "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." - }, - { - "description": "Denies the set_dock_visibility command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-set-dock-visibility", - "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." - }, - { - "description": "Denies the supports_multiple_windows command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-supports-multiple-windows", - "markdownDescription": "Denies the supports_multiple_windows command without any pre-configured scope." - }, - { - "description": "Denies the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-tauri-version", - "markdownDescription": "Denies the tauri_version command without any pre-configured scope." - }, - { - "description": "Denies the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-version", - "markdownDescription": "Denies the version command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", - "type": "string", - "const": "core:event:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" - }, - { - "description": "Enables the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit", - "markdownDescription": "Enables the emit command without any pre-configured scope." - }, - { - "description": "Enables the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit-to", - "markdownDescription": "Enables the emit_to command without any pre-configured scope." - }, - { - "description": "Enables the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-listen", - "markdownDescription": "Enables the listen command without any pre-configured scope." - }, - { - "description": "Enables the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-unlisten", - "markdownDescription": "Enables the unlisten command without any pre-configured scope." - }, - { - "description": "Denies the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit", - "markdownDescription": "Denies the emit command without any pre-configured scope." - }, - { - "description": "Denies the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit-to", - "markdownDescription": "Denies the emit_to command without any pre-configured scope." - }, - { - "description": "Denies the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-listen", - "markdownDescription": "Denies the listen command without any pre-configured scope." - }, - { - "description": "Denies the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-unlisten", - "markdownDescription": "Denies the unlisten command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", - "type": "string", - "const": "core:image:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" - }, - { - "description": "Enables the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-bytes", - "markdownDescription": "Enables the from_bytes command without any pre-configured scope." - }, - { - "description": "Enables the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-path", - "markdownDescription": "Enables the from_path command without any pre-configured scope." - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-new", - "markdownDescription": "Enables the new command without any pre-configured scope." - }, - { - "description": "Enables the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-rgba", - "markdownDescription": "Enables the rgba command without any pre-configured scope." - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-size", - "markdownDescription": "Enables the size command without any pre-configured scope." - }, - { - "description": "Denies the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-bytes", - "markdownDescription": "Denies the from_bytes command without any pre-configured scope." - }, - { - "description": "Denies the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-path", - "markdownDescription": "Denies the from_path command without any pre-configured scope." - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-new", - "markdownDescription": "Denies the new command without any pre-configured scope." - }, - { - "description": "Denies the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-rgba", - "markdownDescription": "Denies the rgba command without any pre-configured scope." - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-size", - "markdownDescription": "Denies the size command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", - "type": "string", - "const": "core:menu:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" - }, - { - "description": "Enables the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-append", - "markdownDescription": "Enables the append command without any pre-configured scope." - }, - { - "description": "Enables the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-create-default", - "markdownDescription": "Enables the create_default command without any pre-configured scope." - }, - { - "description": "Enables the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-get", - "markdownDescription": "Enables the get command without any pre-configured scope." - }, - { - "description": "Enables the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-insert", - "markdownDescription": "Enables the insert command without any pre-configured scope." - }, - { - "description": "Enables the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-checked", - "markdownDescription": "Enables the is_checked command without any pre-configured scope." - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-enabled", - "markdownDescription": "Enables the is_enabled command without any pre-configured scope." - }, - { - "description": "Enables the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-items", - "markdownDescription": "Enables the items command without any pre-configured scope." - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-new", - "markdownDescription": "Enables the new command without any pre-configured scope." - }, - { - "description": "Enables the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-popup", - "markdownDescription": "Enables the popup command without any pre-configured scope." - }, - { - "description": "Enables the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-prepend", - "markdownDescription": "Enables the prepend command without any pre-configured scope." - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove", - "markdownDescription": "Enables the remove command without any pre-configured scope." - }, - { - "description": "Enables the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove-at", - "markdownDescription": "Enables the remove_at command without any pre-configured scope." - }, - { - "description": "Enables the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-accelerator", - "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." - }, - { - "description": "Enables the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-app-menu", - "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." - }, - { - "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-help-menu-for-nsapp", - "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." - }, - { - "description": "Enables the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-window-menu", - "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." - }, - { - "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-windows-menu-for-nsapp", - "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." - }, - { - "description": "Enables the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-checked", - "markdownDescription": "Enables the set_checked command without any pre-configured scope." - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-enabled", - "markdownDescription": "Enables the set_enabled command without any pre-configured scope." - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-icon", - "markdownDescription": "Enables the set_icon command without any pre-configured scope." - }, - { - "description": "Enables the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-text", - "markdownDescription": "Enables the set_text command without any pre-configured scope." - }, - { - "description": "Enables the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-text", - "markdownDescription": "Enables the text command without any pre-configured scope." - }, - { - "description": "Denies the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-append", - "markdownDescription": "Denies the append command without any pre-configured scope." - }, - { - "description": "Denies the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-create-default", - "markdownDescription": "Denies the create_default command without any pre-configured scope." - }, - { - "description": "Denies the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-get", - "markdownDescription": "Denies the get command without any pre-configured scope." - }, - { - "description": "Denies the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-insert", - "markdownDescription": "Denies the insert command without any pre-configured scope." - }, - { - "description": "Denies the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-checked", - "markdownDescription": "Denies the is_checked command without any pre-configured scope." - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-enabled", - "markdownDescription": "Denies the is_enabled command without any pre-configured scope." - }, - { - "description": "Denies the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-items", - "markdownDescription": "Denies the items command without any pre-configured scope." - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-new", - "markdownDescription": "Denies the new command without any pre-configured scope." - }, - { - "description": "Denies the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-popup", - "markdownDescription": "Denies the popup command without any pre-configured scope." - }, - { - "description": "Denies the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-prepend", - "markdownDescription": "Denies the prepend command without any pre-configured scope." - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove", - "markdownDescription": "Denies the remove command without any pre-configured scope." - }, - { - "description": "Denies the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove-at", - "markdownDescription": "Denies the remove_at command without any pre-configured scope." - }, - { - "description": "Denies the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-accelerator", - "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." - }, - { - "description": "Denies the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-app-menu", - "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." - }, - { - "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-help-menu-for-nsapp", - "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." - }, - { - "description": "Denies the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-window-menu", - "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." - }, - { - "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-windows-menu-for-nsapp", - "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." - }, - { - "description": "Denies the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-checked", - "markdownDescription": "Denies the set_checked command without any pre-configured scope." - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-enabled", - "markdownDescription": "Denies the set_enabled command without any pre-configured scope." - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-icon", - "markdownDescription": "Denies the set_icon command without any pre-configured scope." - }, - { - "description": "Denies the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-text", - "markdownDescription": "Denies the set_text command without any pre-configured scope." - }, - { - "description": "Denies the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-text", - "markdownDescription": "Denies the text command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", - "type": "string", - "const": "core:path:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" - }, - { - "description": "Enables the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-basename", - "markdownDescription": "Enables the basename command without any pre-configured scope." - }, - { - "description": "Enables the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-dirname", - "markdownDescription": "Enables the dirname command without any pre-configured scope." - }, - { - "description": "Enables the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-extname", - "markdownDescription": "Enables the extname command without any pre-configured scope." - }, - { - "description": "Enables the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-is-absolute", - "markdownDescription": "Enables the is_absolute command without any pre-configured scope." - }, - { - "description": "Enables the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-join", - "markdownDescription": "Enables the join command without any pre-configured scope." - }, - { - "description": "Enables the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-normalize", - "markdownDescription": "Enables the normalize command without any pre-configured scope." - }, - { - "description": "Enables the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve", - "markdownDescription": "Enables the resolve command without any pre-configured scope." - }, - { - "description": "Enables the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve-directory", - "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." - }, - { - "description": "Denies the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-basename", - "markdownDescription": "Denies the basename command without any pre-configured scope." - }, - { - "description": "Denies the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-dirname", - "markdownDescription": "Denies the dirname command without any pre-configured scope." - }, - { - "description": "Denies the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-extname", - "markdownDescription": "Denies the extname command without any pre-configured scope." - }, - { - "description": "Denies the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-is-absolute", - "markdownDescription": "Denies the is_absolute command without any pre-configured scope." - }, - { - "description": "Denies the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-join", - "markdownDescription": "Denies the join command without any pre-configured scope." - }, - { - "description": "Denies the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-normalize", - "markdownDescription": "Denies the normalize command without any pre-configured scope." - }, - { - "description": "Denies the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve", - "markdownDescription": "Denies the resolve command without any pre-configured scope." - }, - { - "description": "Denies the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve-directory", - "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", - "type": "string", - "const": "core:resources:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:allow-close", - "markdownDescription": "Enables the close command without any pre-configured scope." - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:deny-close", - "markdownDescription": "Denies the close command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`", - "type": "string", - "const": "core:tray:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`" - }, - { - "description": "Enables the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-get-by-id", - "markdownDescription": "Enables the get_by_id command without any pre-configured scope." - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-new", - "markdownDescription": "Enables the new command without any pre-configured scope." - }, - { - "description": "Enables the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-remove-by-id", - "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon", - "markdownDescription": "Enables the set_icon command without any pre-configured scope." - }, - { - "description": "Enables the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon-as-template", - "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." - }, - { - "description": "Enables the set_icon_with_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon-with-as-template", - "markdownDescription": "Enables the set_icon_with_as_template command without any pre-configured scope." - }, - { - "description": "Enables the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-menu", - "markdownDescription": "Enables the set_menu command without any pre-configured scope." - }, - { - "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-show-menu-on-left-click", - "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." - }, - { - "description": "Enables the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-temp-dir-path", - "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-title", - "markdownDescription": "Enables the set_title command without any pre-configured scope." - }, - { - "description": "Enables the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-tooltip", - "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." - }, - { - "description": "Enables the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-visible", - "markdownDescription": "Enables the set_visible command without any pre-configured scope." - }, - { - "description": "Denies the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-get-by-id", - "markdownDescription": "Denies the get_by_id command without any pre-configured scope." - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-new", - "markdownDescription": "Denies the new command without any pre-configured scope." - }, - { - "description": "Denies the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-remove-by-id", - "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon", - "markdownDescription": "Denies the set_icon command without any pre-configured scope." - }, - { - "description": "Denies the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon-as-template", - "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." - }, - { - "description": "Denies the set_icon_with_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon-with-as-template", - "markdownDescription": "Denies the set_icon_with_as_template command without any pre-configured scope." - }, - { - "description": "Denies the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-menu", - "markdownDescription": "Denies the set_menu command without any pre-configured scope." - }, - { - "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-show-menu-on-left-click", - "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." - }, - { - "description": "Denies the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-temp-dir-path", - "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-title", - "markdownDescription": "Denies the set_title command without any pre-configured scope." - }, - { - "description": "Denies the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-tooltip", - "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." - }, - { - "description": "Denies the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-visible", - "markdownDescription": "Denies the set_visible command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", - "type": "string", - "const": "core:webview:default", - "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" - }, - { - "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-clear-all-browsing-data", - "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." - }, - { - "description": "Enables the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview", - "markdownDescription": "Enables the create_webview command without any pre-configured scope." - }, - { - "description": "Enables the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview-window", - "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." - }, - { - "description": "Enables the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-get-all-webviews", - "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." - }, - { - "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-internal-toggle-devtools", - "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." - }, - { - "description": "Enables the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-print", - "markdownDescription": "Enables the print command without any pre-configured scope." - }, - { - "description": "Enables the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-reparent", - "markdownDescription": "Enables the reparent command without any pre-configured scope." - }, - { - "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-auto-resize", - "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." - }, - { - "description": "Enables the set_webview_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-background-color", - "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." - }, - { - "description": "Enables the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-focus", - "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." - }, - { - "description": "Enables the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-position", - "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." - }, - { - "description": "Enables the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-size", - "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." - }, - { - "description": "Enables the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-zoom", - "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." - }, - { - "description": "Enables the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-close", - "markdownDescription": "Enables the webview_close command without any pre-configured scope." - }, - { - "description": "Enables the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-hide", - "markdownDescription": "Enables the webview_hide command without any pre-configured scope." - }, - { - "description": "Enables the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-position", - "markdownDescription": "Enables the webview_position command without any pre-configured scope." - }, - { - "description": "Enables the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-show", - "markdownDescription": "Enables the webview_show command without any pre-configured scope." - }, - { - "description": "Enables the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-size", - "markdownDescription": "Enables the webview_size command without any pre-configured scope." - }, - { - "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-clear-all-browsing-data", - "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." - }, - { - "description": "Denies the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview", - "markdownDescription": "Denies the create_webview command without any pre-configured scope." - }, - { - "description": "Denies the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview-window", - "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." - }, - { - "description": "Denies the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-get-all-webviews", - "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." - }, - { - "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-internal-toggle-devtools", - "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." - }, - { - "description": "Denies the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-print", - "markdownDescription": "Denies the print command without any pre-configured scope." - }, - { - "description": "Denies the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-reparent", - "markdownDescription": "Denies the reparent command without any pre-configured scope." - }, - { - "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-auto-resize", - "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." - }, - { - "description": "Denies the set_webview_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-background-color", - "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." - }, - { - "description": "Denies the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-focus", - "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." - }, - { - "description": "Denies the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-position", - "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." - }, - { - "description": "Denies the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-size", - "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." - }, - { - "description": "Denies the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-zoom", - "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." - }, - { - "description": "Denies the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-close", - "markdownDescription": "Denies the webview_close command without any pre-configured scope." - }, - { - "description": "Denies the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-hide", - "markdownDescription": "Denies the webview_hide command without any pre-configured scope." - }, - { - "description": "Denies the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-position", - "markdownDescription": "Denies the webview_position command without any pre-configured scope." - }, - { - "description": "Denies the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-show", - "markdownDescription": "Denies the webview_show command without any pre-configured scope." - }, - { - "description": "Denies the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-size", - "markdownDescription": "Denies the webview_size command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`", - "type": "string", - "const": "core:window:default", - "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`" - }, - { - "description": "Enables the activity_name command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-activity-name", - "markdownDescription": "Enables the activity_name command without any pre-configured scope." - }, - { - "description": "Enables the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-available-monitors", - "markdownDescription": "Enables the available_monitors command without any pre-configured scope." - }, - { - "description": "Enables the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-center", - "markdownDescription": "Enables the center command without any pre-configured scope." - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-close", - "markdownDescription": "Enables the close command without any pre-configured scope." - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-create", - "markdownDescription": "Enables the create command without any pre-configured scope." - }, - { - "description": "Enables the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-current-monitor", - "markdownDescription": "Enables the current_monitor command without any pre-configured scope." - }, - { - "description": "Enables the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-cursor-position", - "markdownDescription": "Enables the cursor_position command without any pre-configured scope." - }, - { - "description": "Enables the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-destroy", - "markdownDescription": "Enables the destroy command without any pre-configured scope." - }, - { - "description": "Enables the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-get-all-windows", - "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." - }, - { - "description": "Enables the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-hide", - "markdownDescription": "Enables the hide command without any pre-configured scope." - }, - { - "description": "Enables the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-position", - "markdownDescription": "Enables the inner_position command without any pre-configured scope." - }, - { - "description": "Enables the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-size", - "markdownDescription": "Enables the inner_size command without any pre-configured scope." - }, - { - "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-internal-toggle-maximize", - "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." - }, - { - "description": "Enables the is_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-always-on-top", - "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." - }, - { - "description": "Enables the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-closable", - "markdownDescription": "Enables the is_closable command without any pre-configured scope." - }, - { - "description": "Enables the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-decorated", - "markdownDescription": "Enables the is_decorated command without any pre-configured scope." - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-enabled", - "markdownDescription": "Enables the is_enabled command without any pre-configured scope." - }, - { - "description": "Enables the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-focused", - "markdownDescription": "Enables the is_focused command without any pre-configured scope." - }, - { - "description": "Enables the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-fullscreen", - "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." - }, - { - "description": "Enables the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximizable", - "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." - }, - { - "description": "Enables the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximized", - "markdownDescription": "Enables the is_maximized command without any pre-configured scope." - }, - { - "description": "Enables the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimizable", - "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." - }, - { - "description": "Enables the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimized", - "markdownDescription": "Enables the is_minimized command without any pre-configured scope." - }, - { - "description": "Enables the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-resizable", - "markdownDescription": "Enables the is_resizable command without any pre-configured scope." - }, - { - "description": "Enables the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-visible", - "markdownDescription": "Enables the is_visible command without any pre-configured scope." - }, - { - "description": "Enables the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-maximize", - "markdownDescription": "Enables the maximize command without any pre-configured scope." - }, - { - "description": "Enables the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-minimize", - "markdownDescription": "Enables the minimize command without any pre-configured scope." - }, - { - "description": "Enables the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-monitor-from-point", - "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." - }, - { - "description": "Enables the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-position", - "markdownDescription": "Enables the outer_position command without any pre-configured scope." - }, - { - "description": "Enables the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-size", - "markdownDescription": "Enables the outer_size command without any pre-configured scope." - }, - { - "description": "Enables the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-primary-monitor", - "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." - }, - { - "description": "Enables the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-request-user-attention", - "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." - }, - { - "description": "Enables the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-scale-factor", - "markdownDescription": "Enables the scale_factor command without any pre-configured scope." - }, - { - "description": "Enables the scene_identifier command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-scene-identifier", - "markdownDescription": "Enables the scene_identifier command without any pre-configured scope." - }, - { - "description": "Enables the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-bottom", - "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." - }, - { - "description": "Enables the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-top", - "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." - }, - { - "description": "Enables the set_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-background-color", - "markdownDescription": "Enables the set_background_color command without any pre-configured scope." - }, - { - "description": "Enables the set_badge_count command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-badge-count", - "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." - }, - { - "description": "Enables the set_badge_label command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-badge-label", - "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." - }, - { - "description": "Enables the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-closable", - "markdownDescription": "Enables the set_closable command without any pre-configured scope." - }, - { - "description": "Enables the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-content-protected", - "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." - }, - { - "description": "Enables the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-grab", - "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." - }, - { - "description": "Enables the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-icon", - "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." - }, - { - "description": "Enables the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-position", - "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." - }, - { - "description": "Enables the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-visible", - "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." - }, - { - "description": "Enables the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-decorations", - "markdownDescription": "Enables the set_decorations command without any pre-configured scope." - }, - { - "description": "Enables the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-effects", - "markdownDescription": "Enables the set_effects command without any pre-configured scope." - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-enabled", - "markdownDescription": "Enables the set_enabled command without any pre-configured scope." - }, - { - "description": "Enables the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-focus", - "markdownDescription": "Enables the set_focus command without any pre-configured scope." - }, - { - "description": "Enables the set_focusable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-focusable", - "markdownDescription": "Enables the set_focusable command without any pre-configured scope." - }, - { - "description": "Enables the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-fullscreen", - "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-icon", - "markdownDescription": "Enables the set_icon command without any pre-configured scope." - }, - { - "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-ignore-cursor-events", - "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." - }, - { - "description": "Enables the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-max-size", - "markdownDescription": "Enables the set_max_size command without any pre-configured scope." - }, - { - "description": "Enables the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-maximizable", - "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." - }, - { - "description": "Enables the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-min-size", - "markdownDescription": "Enables the set_min_size command without any pre-configured scope." - }, - { - "description": "Enables the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-minimizable", - "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." - }, - { - "description": "Enables the set_overlay_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-overlay-icon", - "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." - }, - { - "description": "Enables the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-position", - "markdownDescription": "Enables the set_position command without any pre-configured scope." - }, - { - "description": "Enables the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-progress-bar", - "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." - }, - { - "description": "Enables the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-resizable", - "markdownDescription": "Enables the set_resizable command without any pre-configured scope." - }, - { - "description": "Enables the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-shadow", - "markdownDescription": "Enables the set_shadow command without any pre-configured scope." - }, - { - "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-simple-fullscreen", - "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." - }, - { - "description": "Enables the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size", - "markdownDescription": "Enables the set_size command without any pre-configured scope." - }, - { - "description": "Enables the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size-constraints", - "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." - }, - { - "description": "Enables the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-skip-taskbar", - "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." - }, - { - "description": "Enables the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-theme", - "markdownDescription": "Enables the set_theme command without any pre-configured scope." - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title", - "markdownDescription": "Enables the set_title command without any pre-configured scope." - }, - { - "description": "Enables the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title-bar-style", - "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." - }, - { - "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-visible-on-all-workspaces", - "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." - }, - { - "description": "Enables the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-show", - "markdownDescription": "Enables the show command without any pre-configured scope." - }, - { - "description": "Enables the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-dragging", - "markdownDescription": "Enables the start_dragging command without any pre-configured scope." - }, - { - "description": "Enables the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-resize-dragging", - "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." - }, - { - "description": "Enables the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-theme", - "markdownDescription": "Enables the theme command without any pre-configured scope." - }, - { - "description": "Enables the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-title", - "markdownDescription": "Enables the title command without any pre-configured scope." - }, - { - "description": "Enables the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-toggle-maximize", - "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." - }, - { - "description": "Enables the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unmaximize", - "markdownDescription": "Enables the unmaximize command without any pre-configured scope." - }, - { - "description": "Enables the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unminimize", - "markdownDescription": "Enables the unminimize command without any pre-configured scope." - }, - { - "description": "Denies the activity_name command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-activity-name", - "markdownDescription": "Denies the activity_name command without any pre-configured scope." - }, - { - "description": "Denies the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-available-monitors", - "markdownDescription": "Denies the available_monitors command without any pre-configured scope." - }, - { - "description": "Denies the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-center", - "markdownDescription": "Denies the center command without any pre-configured scope." - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-close", - "markdownDescription": "Denies the close command without any pre-configured scope." - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-create", - "markdownDescription": "Denies the create command without any pre-configured scope." - }, - { - "description": "Denies the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-current-monitor", - "markdownDescription": "Denies the current_monitor command without any pre-configured scope." - }, - { - "description": "Denies the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-cursor-position", - "markdownDescription": "Denies the cursor_position command without any pre-configured scope." - }, - { - "description": "Denies the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-destroy", - "markdownDescription": "Denies the destroy command without any pre-configured scope." - }, - { - "description": "Denies the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-get-all-windows", - "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." - }, - { - "description": "Denies the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-hide", - "markdownDescription": "Denies the hide command without any pre-configured scope." - }, - { - "description": "Denies the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-position", - "markdownDescription": "Denies the inner_position command without any pre-configured scope." - }, - { - "description": "Denies the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-size", - "markdownDescription": "Denies the inner_size command without any pre-configured scope." - }, - { - "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-internal-toggle-maximize", - "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." - }, - { - "description": "Denies the is_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-always-on-top", - "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." - }, - { - "description": "Denies the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-closable", - "markdownDescription": "Denies the is_closable command without any pre-configured scope." - }, - { - "description": "Denies the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-decorated", - "markdownDescription": "Denies the is_decorated command without any pre-configured scope." - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-enabled", - "markdownDescription": "Denies the is_enabled command without any pre-configured scope." - }, - { - "description": "Denies the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-focused", - "markdownDescription": "Denies the is_focused command without any pre-configured scope." - }, - { - "description": "Denies the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-fullscreen", - "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." - }, - { - "description": "Denies the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximizable", - "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." - }, - { - "description": "Denies the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximized", - "markdownDescription": "Denies the is_maximized command without any pre-configured scope." - }, - { - "description": "Denies the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimizable", - "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." - }, - { - "description": "Denies the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimized", - "markdownDescription": "Denies the is_minimized command without any pre-configured scope." - }, - { - "description": "Denies the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-resizable", - "markdownDescription": "Denies the is_resizable command without any pre-configured scope." - }, - { - "description": "Denies the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-visible", - "markdownDescription": "Denies the is_visible command without any pre-configured scope." - }, - { - "description": "Denies the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-maximize", - "markdownDescription": "Denies the maximize command without any pre-configured scope." - }, - { - "description": "Denies the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-minimize", - "markdownDescription": "Denies the minimize command without any pre-configured scope." - }, - { - "description": "Denies the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-monitor-from-point", - "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." - }, - { - "description": "Denies the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-position", - "markdownDescription": "Denies the outer_position command without any pre-configured scope." - }, - { - "description": "Denies the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-size", - "markdownDescription": "Denies the outer_size command without any pre-configured scope." - }, - { - "description": "Denies the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-primary-monitor", - "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." - }, - { - "description": "Denies the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-request-user-attention", - "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." - }, - { - "description": "Denies the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-scale-factor", - "markdownDescription": "Denies the scale_factor command without any pre-configured scope." - }, - { - "description": "Denies the scene_identifier command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-scene-identifier", - "markdownDescription": "Denies the scene_identifier command without any pre-configured scope." - }, - { - "description": "Denies the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-bottom", - "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." - }, - { - "description": "Denies the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-top", - "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." - }, - { - "description": "Denies the set_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-background-color", - "markdownDescription": "Denies the set_background_color command without any pre-configured scope." - }, - { - "description": "Denies the set_badge_count command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-badge-count", - "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." - }, - { - "description": "Denies the set_badge_label command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-badge-label", - "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." - }, - { - "description": "Denies the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-closable", - "markdownDescription": "Denies the set_closable command without any pre-configured scope." - }, - { - "description": "Denies the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-content-protected", - "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." - }, - { - "description": "Denies the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-grab", - "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." - }, - { - "description": "Denies the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-icon", - "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." - }, - { - "description": "Denies the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-position", - "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." - }, - { - "description": "Denies the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-visible", - "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." - }, - { - "description": "Denies the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-decorations", - "markdownDescription": "Denies the set_decorations command without any pre-configured scope." - }, - { - "description": "Denies the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-effects", - "markdownDescription": "Denies the set_effects command without any pre-configured scope." - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-enabled", - "markdownDescription": "Denies the set_enabled command without any pre-configured scope." - }, - { - "description": "Denies the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-focus", - "markdownDescription": "Denies the set_focus command without any pre-configured scope." - }, - { - "description": "Denies the set_focusable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-focusable", - "markdownDescription": "Denies the set_focusable command without any pre-configured scope." - }, - { - "description": "Denies the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-fullscreen", - "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-icon", - "markdownDescription": "Denies the set_icon command without any pre-configured scope." - }, - { - "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-ignore-cursor-events", - "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." - }, - { - "description": "Denies the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-max-size", - "markdownDescription": "Denies the set_max_size command without any pre-configured scope." - }, - { - "description": "Denies the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-maximizable", - "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." - }, - { - "description": "Denies the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-min-size", - "markdownDescription": "Denies the set_min_size command without any pre-configured scope." - }, - { - "description": "Denies the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-minimizable", - "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." - }, - { - "description": "Denies the set_overlay_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-overlay-icon", - "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." - }, - { - "description": "Denies the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-position", - "markdownDescription": "Denies the set_position command without any pre-configured scope." - }, - { - "description": "Denies the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-progress-bar", - "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." - }, - { - "description": "Denies the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-resizable", - "markdownDescription": "Denies the set_resizable command without any pre-configured scope." - }, - { - "description": "Denies the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-shadow", - "markdownDescription": "Denies the set_shadow command without any pre-configured scope." - }, - { - "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-simple-fullscreen", - "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." - }, - { - "description": "Denies the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size", - "markdownDescription": "Denies the set_size command without any pre-configured scope." - }, - { - "description": "Denies the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size-constraints", - "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." - }, - { - "description": "Denies the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-skip-taskbar", - "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." - }, - { - "description": "Denies the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-theme", - "markdownDescription": "Denies the set_theme command without any pre-configured scope." - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title", - "markdownDescription": "Denies the set_title command without any pre-configured scope." - }, - { - "description": "Denies the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title-bar-style", - "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." - }, - { - "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-visible-on-all-workspaces", - "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." - }, - { - "description": "Denies the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-show", - "markdownDescription": "Denies the show command without any pre-configured scope." - }, - { - "description": "Denies the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-dragging", - "markdownDescription": "Denies the start_dragging command without any pre-configured scope." - }, - { - "description": "Denies the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-resize-dragging", - "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." - }, - { - "description": "Denies the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-theme", - "markdownDescription": "Denies the theme command without any pre-configured scope." - }, - { - "description": "Denies the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-title", - "markdownDescription": "Denies the title command without any pre-configured scope." - }, - { - "description": "Denies the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-toggle-maximize", - "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." - }, - { - "description": "Denies the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unmaximize", - "markdownDescription": "Denies the unmaximize command without any pre-configured scope." - }, - { - "description": "Denies the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unminimize", - "markdownDescription": "Denies the unminimize command without any pre-configured scope." - } - ] - }, - "Value": { - "description": "All supported ACL values.", - "anyOf": [ - { - "description": "Represents a null JSON value.", - "type": "null" - }, - { - "description": "Represents a [`bool`].", - "type": "boolean" - }, - { - "description": "Represents a valid ACL [`Number`].", - "allOf": [ - { - "$ref": "#/definitions/Number" - } - ] - }, - { - "description": "Represents a [`String`].", - "type": "string" - }, - { - "description": "Represents a list of other [`Value`]s.", - "type": "array", - "items": { - "$ref": "#/definitions/Value" - } - }, - { - "description": "Represents a map of [`String`] keys to [`Value`]s.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Value" - } - } - ] - }, - "Number": { - "description": "A valid ACL number.", - "anyOf": [ - { - "description": "Represents an [`i64`].", - "type": "integer", - "format": "int64" - }, - { - "description": "Represents a [`f64`].", - "type": "number", - "format": "double" - } - ] - }, - "Target": { - "description": "Platform target.", - "oneOf": [ - { - "description": "MacOS.", - "type": "string", - "enum": [ - "macOS" - ] - }, - { - "description": "Windows.", - "type": "string", - "enum": [ - "windows" - ] - }, - { - "description": "Linux.", - "type": "string", - "enum": [ - "linux" - ] - }, - { - "description": "Android.", - "type": "string", - "enum": [ - "android" - ] - }, - { - "description": "iOS.", - "type": "string", - "enum": [ - "iOS" - ] - } - ] - } - } -} \ No newline at end of file diff --git a/client/app-shell/gen/schemas/windows-schema.json b/client/app-shell/gen/schemas/windows-schema.json deleted file mode 100644 index 3286645..0000000 --- a/client/app-shell/gen/schemas/windows-schema.json +++ /dev/null @@ -1,2292 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CapabilityFile", - "description": "Capability formats accepted in a capability file.", - "anyOf": [ - { - "description": "A single capability.", - "allOf": [ - { - "$ref": "#/definitions/Capability" - } - ] - }, - { - "description": "A list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - }, - { - "description": "A list of capabilities.", - "type": "object", - "required": [ - "capabilities" - ], - "properties": { - "capabilities": { - "description": "The list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - } - } - } - ], - "definitions": { - "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", - "type": "object", - "required": [ - "identifier", - "permissions" - ], - "properties": { - "identifier": { - "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", - "type": "string" - }, - "description": { - "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", - "default": "", - "type": "string" - }, - "remote": { - "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", - "anyOf": [ - { - "$ref": "#/definitions/CapabilityRemote" - }, - { - "type": "null" - } - ] - }, - "local": { - "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", - "default": true, - "type": "boolean" - }, - "windows": { - "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "webviews": { - "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "permissions": { - "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionEntry" - }, - "uniqueItems": true - }, - "platforms": { - "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Target" - } - } - } - }, - "CapabilityRemote": { - "description": "Configuration for remote URLs that are associated with the capability.", - "type": "object", - "required": [ - "urls" - ], - "properties": { - "urls": { - "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PermissionEntry": { - "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", - "anyOf": [ - { - "description": "Reference a permission or permission set by identifier.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - { - "description": "Reference a permission or permission set by identifier and extends its scope.", - "type": "object", - "allOf": [ - { - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - "allow": { - "description": "Data that defines what is allowed by the scope.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - }, - "deny": { - "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - } - } - } - ], - "required": [ - "identifier" - ] - } - ] - }, - "Identifier": { - "description": "Permission identifier", - "oneOf": [ - { - "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", - "type": "string", - "const": "core:default", - "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" - }, - { - "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`", - "type": "string", - "const": "core:app:default", - "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`" - }, - { - "description": "Enables the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-hide", - "markdownDescription": "Enables the app_hide command without any pre-configured scope." - }, - { - "description": "Enables the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-show", - "markdownDescription": "Enables the app_show command without any pre-configured scope." - }, - { - "description": "Enables the bundle_type command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-bundle-type", - "markdownDescription": "Enables the bundle_type command without any pre-configured scope." - }, - { - "description": "Enables the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-default-window-icon", - "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." - }, - { - "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-fetch-data-store-identifiers", - "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." - }, - { - "description": "Enables the identifier command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-identifier", - "markdownDescription": "Enables the identifier command without any pre-configured scope." - }, - { - "description": "Enables the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-name", - "markdownDescription": "Enables the name command without any pre-configured scope." - }, - { - "description": "Enables the register_listener command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-register-listener", - "markdownDescription": "Enables the register_listener command without any pre-configured scope." - }, - { - "description": "Enables the remove_data_store command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-remove-data-store", - "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." - }, - { - "description": "Enables the remove_listener command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-remove-listener", - "markdownDescription": "Enables the remove_listener command without any pre-configured scope." - }, - { - "description": "Enables the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-set-app-theme", - "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." - }, - { - "description": "Enables the set_dock_visibility command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-set-dock-visibility", - "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." - }, - { - "description": "Enables the supports_multiple_windows command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-supports-multiple-windows", - "markdownDescription": "Enables the supports_multiple_windows command without any pre-configured scope." - }, - { - "description": "Enables the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-tauri-version", - "markdownDescription": "Enables the tauri_version command without any pre-configured scope." - }, - { - "description": "Enables the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-version", - "markdownDescription": "Enables the version command without any pre-configured scope." - }, - { - "description": "Denies the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-hide", - "markdownDescription": "Denies the app_hide command without any pre-configured scope." - }, - { - "description": "Denies the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-show", - "markdownDescription": "Denies the app_show command without any pre-configured scope." - }, - { - "description": "Denies the bundle_type command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-bundle-type", - "markdownDescription": "Denies the bundle_type command without any pre-configured scope." - }, - { - "description": "Denies the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-default-window-icon", - "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." - }, - { - "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-fetch-data-store-identifiers", - "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." - }, - { - "description": "Denies the identifier command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-identifier", - "markdownDescription": "Denies the identifier command without any pre-configured scope." - }, - { - "description": "Denies the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-name", - "markdownDescription": "Denies the name command without any pre-configured scope." - }, - { - "description": "Denies the register_listener command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-register-listener", - "markdownDescription": "Denies the register_listener command without any pre-configured scope." - }, - { - "description": "Denies the remove_data_store command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-remove-data-store", - "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." - }, - { - "description": "Denies the remove_listener command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-remove-listener", - "markdownDescription": "Denies the remove_listener command without any pre-configured scope." - }, - { - "description": "Denies the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-set-app-theme", - "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." - }, - { - "description": "Denies the set_dock_visibility command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-set-dock-visibility", - "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." - }, - { - "description": "Denies the supports_multiple_windows command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-supports-multiple-windows", - "markdownDescription": "Denies the supports_multiple_windows command without any pre-configured scope." - }, - { - "description": "Denies the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-tauri-version", - "markdownDescription": "Denies the tauri_version command without any pre-configured scope." - }, - { - "description": "Denies the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-version", - "markdownDescription": "Denies the version command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", - "type": "string", - "const": "core:event:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" - }, - { - "description": "Enables the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit", - "markdownDescription": "Enables the emit command without any pre-configured scope." - }, - { - "description": "Enables the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit-to", - "markdownDescription": "Enables the emit_to command without any pre-configured scope." - }, - { - "description": "Enables the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-listen", - "markdownDescription": "Enables the listen command without any pre-configured scope." - }, - { - "description": "Enables the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-unlisten", - "markdownDescription": "Enables the unlisten command without any pre-configured scope." - }, - { - "description": "Denies the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit", - "markdownDescription": "Denies the emit command without any pre-configured scope." - }, - { - "description": "Denies the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit-to", - "markdownDescription": "Denies the emit_to command without any pre-configured scope." - }, - { - "description": "Denies the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-listen", - "markdownDescription": "Denies the listen command without any pre-configured scope." - }, - { - "description": "Denies the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-unlisten", - "markdownDescription": "Denies the unlisten command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", - "type": "string", - "const": "core:image:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" - }, - { - "description": "Enables the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-bytes", - "markdownDescription": "Enables the from_bytes command without any pre-configured scope." - }, - { - "description": "Enables the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-path", - "markdownDescription": "Enables the from_path command without any pre-configured scope." - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-new", - "markdownDescription": "Enables the new command without any pre-configured scope." - }, - { - "description": "Enables the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-rgba", - "markdownDescription": "Enables the rgba command without any pre-configured scope." - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-size", - "markdownDescription": "Enables the size command without any pre-configured scope." - }, - { - "description": "Denies the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-bytes", - "markdownDescription": "Denies the from_bytes command without any pre-configured scope." - }, - { - "description": "Denies the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-path", - "markdownDescription": "Denies the from_path command without any pre-configured scope." - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-new", - "markdownDescription": "Denies the new command without any pre-configured scope." - }, - { - "description": "Denies the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-rgba", - "markdownDescription": "Denies the rgba command without any pre-configured scope." - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-size", - "markdownDescription": "Denies the size command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", - "type": "string", - "const": "core:menu:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" - }, - { - "description": "Enables the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-append", - "markdownDescription": "Enables the append command without any pre-configured scope." - }, - { - "description": "Enables the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-create-default", - "markdownDescription": "Enables the create_default command without any pre-configured scope." - }, - { - "description": "Enables the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-get", - "markdownDescription": "Enables the get command without any pre-configured scope." - }, - { - "description": "Enables the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-insert", - "markdownDescription": "Enables the insert command without any pre-configured scope." - }, - { - "description": "Enables the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-checked", - "markdownDescription": "Enables the is_checked command without any pre-configured scope." - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-enabled", - "markdownDescription": "Enables the is_enabled command without any pre-configured scope." - }, - { - "description": "Enables the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-items", - "markdownDescription": "Enables the items command without any pre-configured scope." - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-new", - "markdownDescription": "Enables the new command without any pre-configured scope." - }, - { - "description": "Enables the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-popup", - "markdownDescription": "Enables the popup command without any pre-configured scope." - }, - { - "description": "Enables the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-prepend", - "markdownDescription": "Enables the prepend command without any pre-configured scope." - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove", - "markdownDescription": "Enables the remove command without any pre-configured scope." - }, - { - "description": "Enables the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove-at", - "markdownDescription": "Enables the remove_at command without any pre-configured scope." - }, - { - "description": "Enables the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-accelerator", - "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." - }, - { - "description": "Enables the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-app-menu", - "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." - }, - { - "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-help-menu-for-nsapp", - "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." - }, - { - "description": "Enables the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-window-menu", - "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." - }, - { - "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-windows-menu-for-nsapp", - "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." - }, - { - "description": "Enables the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-checked", - "markdownDescription": "Enables the set_checked command without any pre-configured scope." - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-enabled", - "markdownDescription": "Enables the set_enabled command without any pre-configured scope." - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-icon", - "markdownDescription": "Enables the set_icon command without any pre-configured scope." - }, - { - "description": "Enables the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-text", - "markdownDescription": "Enables the set_text command without any pre-configured scope." - }, - { - "description": "Enables the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-text", - "markdownDescription": "Enables the text command without any pre-configured scope." - }, - { - "description": "Denies the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-append", - "markdownDescription": "Denies the append command without any pre-configured scope." - }, - { - "description": "Denies the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-create-default", - "markdownDescription": "Denies the create_default command without any pre-configured scope." - }, - { - "description": "Denies the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-get", - "markdownDescription": "Denies the get command without any pre-configured scope." - }, - { - "description": "Denies the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-insert", - "markdownDescription": "Denies the insert command without any pre-configured scope." - }, - { - "description": "Denies the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-checked", - "markdownDescription": "Denies the is_checked command without any pre-configured scope." - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-enabled", - "markdownDescription": "Denies the is_enabled command without any pre-configured scope." - }, - { - "description": "Denies the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-items", - "markdownDescription": "Denies the items command without any pre-configured scope." - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-new", - "markdownDescription": "Denies the new command without any pre-configured scope." - }, - { - "description": "Denies the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-popup", - "markdownDescription": "Denies the popup command without any pre-configured scope." - }, - { - "description": "Denies the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-prepend", - "markdownDescription": "Denies the prepend command without any pre-configured scope." - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove", - "markdownDescription": "Denies the remove command without any pre-configured scope." - }, - { - "description": "Denies the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove-at", - "markdownDescription": "Denies the remove_at command without any pre-configured scope." - }, - { - "description": "Denies the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-accelerator", - "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." - }, - { - "description": "Denies the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-app-menu", - "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." - }, - { - "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-help-menu-for-nsapp", - "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." - }, - { - "description": "Denies the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-window-menu", - "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." - }, - { - "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-windows-menu-for-nsapp", - "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." - }, - { - "description": "Denies the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-checked", - "markdownDescription": "Denies the set_checked command without any pre-configured scope." - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-enabled", - "markdownDescription": "Denies the set_enabled command without any pre-configured scope." - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-icon", - "markdownDescription": "Denies the set_icon command without any pre-configured scope." - }, - { - "description": "Denies the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-text", - "markdownDescription": "Denies the set_text command without any pre-configured scope." - }, - { - "description": "Denies the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-text", - "markdownDescription": "Denies the text command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", - "type": "string", - "const": "core:path:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" - }, - { - "description": "Enables the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-basename", - "markdownDescription": "Enables the basename command without any pre-configured scope." - }, - { - "description": "Enables the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-dirname", - "markdownDescription": "Enables the dirname command without any pre-configured scope." - }, - { - "description": "Enables the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-extname", - "markdownDescription": "Enables the extname command without any pre-configured scope." - }, - { - "description": "Enables the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-is-absolute", - "markdownDescription": "Enables the is_absolute command without any pre-configured scope." - }, - { - "description": "Enables the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-join", - "markdownDescription": "Enables the join command without any pre-configured scope." - }, - { - "description": "Enables the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-normalize", - "markdownDescription": "Enables the normalize command without any pre-configured scope." - }, - { - "description": "Enables the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve", - "markdownDescription": "Enables the resolve command without any pre-configured scope." - }, - { - "description": "Enables the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve-directory", - "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." - }, - { - "description": "Denies the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-basename", - "markdownDescription": "Denies the basename command without any pre-configured scope." - }, - { - "description": "Denies the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-dirname", - "markdownDescription": "Denies the dirname command without any pre-configured scope." - }, - { - "description": "Denies the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-extname", - "markdownDescription": "Denies the extname command without any pre-configured scope." - }, - { - "description": "Denies the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-is-absolute", - "markdownDescription": "Denies the is_absolute command without any pre-configured scope." - }, - { - "description": "Denies the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-join", - "markdownDescription": "Denies the join command without any pre-configured scope." - }, - { - "description": "Denies the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-normalize", - "markdownDescription": "Denies the normalize command without any pre-configured scope." - }, - { - "description": "Denies the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve", - "markdownDescription": "Denies the resolve command without any pre-configured scope." - }, - { - "description": "Denies the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve-directory", - "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", - "type": "string", - "const": "core:resources:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:allow-close", - "markdownDescription": "Enables the close command without any pre-configured scope." - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:deny-close", - "markdownDescription": "Denies the close command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`", - "type": "string", - "const": "core:tray:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`" - }, - { - "description": "Enables the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-get-by-id", - "markdownDescription": "Enables the get_by_id command without any pre-configured scope." - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-new", - "markdownDescription": "Enables the new command without any pre-configured scope." - }, - { - "description": "Enables the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-remove-by-id", - "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon", - "markdownDescription": "Enables the set_icon command without any pre-configured scope." - }, - { - "description": "Enables the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon-as-template", - "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." - }, - { - "description": "Enables the set_icon_with_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon-with-as-template", - "markdownDescription": "Enables the set_icon_with_as_template command without any pre-configured scope." - }, - { - "description": "Enables the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-menu", - "markdownDescription": "Enables the set_menu command without any pre-configured scope." - }, - { - "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-show-menu-on-left-click", - "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." - }, - { - "description": "Enables the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-temp-dir-path", - "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-title", - "markdownDescription": "Enables the set_title command without any pre-configured scope." - }, - { - "description": "Enables the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-tooltip", - "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." - }, - { - "description": "Enables the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-visible", - "markdownDescription": "Enables the set_visible command without any pre-configured scope." - }, - { - "description": "Denies the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-get-by-id", - "markdownDescription": "Denies the get_by_id command without any pre-configured scope." - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-new", - "markdownDescription": "Denies the new command without any pre-configured scope." - }, - { - "description": "Denies the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-remove-by-id", - "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon", - "markdownDescription": "Denies the set_icon command without any pre-configured scope." - }, - { - "description": "Denies the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon-as-template", - "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." - }, - { - "description": "Denies the set_icon_with_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon-with-as-template", - "markdownDescription": "Denies the set_icon_with_as_template command without any pre-configured scope." - }, - { - "description": "Denies the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-menu", - "markdownDescription": "Denies the set_menu command without any pre-configured scope." - }, - { - "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-show-menu-on-left-click", - "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." - }, - { - "description": "Denies the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-temp-dir-path", - "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-title", - "markdownDescription": "Denies the set_title command without any pre-configured scope." - }, - { - "description": "Denies the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-tooltip", - "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." - }, - { - "description": "Denies the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-visible", - "markdownDescription": "Denies the set_visible command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", - "type": "string", - "const": "core:webview:default", - "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" - }, - { - "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-clear-all-browsing-data", - "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." - }, - { - "description": "Enables the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview", - "markdownDescription": "Enables the create_webview command without any pre-configured scope." - }, - { - "description": "Enables the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview-window", - "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." - }, - { - "description": "Enables the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-get-all-webviews", - "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." - }, - { - "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-internal-toggle-devtools", - "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." - }, - { - "description": "Enables the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-print", - "markdownDescription": "Enables the print command without any pre-configured scope." - }, - { - "description": "Enables the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-reparent", - "markdownDescription": "Enables the reparent command without any pre-configured scope." - }, - { - "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-auto-resize", - "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." - }, - { - "description": "Enables the set_webview_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-background-color", - "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." - }, - { - "description": "Enables the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-focus", - "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." - }, - { - "description": "Enables the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-position", - "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." - }, - { - "description": "Enables the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-size", - "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." - }, - { - "description": "Enables the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-zoom", - "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." - }, - { - "description": "Enables the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-close", - "markdownDescription": "Enables the webview_close command without any pre-configured scope." - }, - { - "description": "Enables the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-hide", - "markdownDescription": "Enables the webview_hide command without any pre-configured scope." - }, - { - "description": "Enables the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-position", - "markdownDescription": "Enables the webview_position command without any pre-configured scope." - }, - { - "description": "Enables the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-show", - "markdownDescription": "Enables the webview_show command without any pre-configured scope." - }, - { - "description": "Enables the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-size", - "markdownDescription": "Enables the webview_size command without any pre-configured scope." - }, - { - "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-clear-all-browsing-data", - "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." - }, - { - "description": "Denies the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview", - "markdownDescription": "Denies the create_webview command without any pre-configured scope." - }, - { - "description": "Denies the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview-window", - "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." - }, - { - "description": "Denies the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-get-all-webviews", - "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." - }, - { - "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-internal-toggle-devtools", - "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." - }, - { - "description": "Denies the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-print", - "markdownDescription": "Denies the print command without any pre-configured scope." - }, - { - "description": "Denies the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-reparent", - "markdownDescription": "Denies the reparent command without any pre-configured scope." - }, - { - "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-auto-resize", - "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." - }, - { - "description": "Denies the set_webview_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-background-color", - "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." - }, - { - "description": "Denies the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-focus", - "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." - }, - { - "description": "Denies the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-position", - "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." - }, - { - "description": "Denies the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-size", - "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." - }, - { - "description": "Denies the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-zoom", - "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." - }, - { - "description": "Denies the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-close", - "markdownDescription": "Denies the webview_close command without any pre-configured scope." - }, - { - "description": "Denies the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-hide", - "markdownDescription": "Denies the webview_hide command without any pre-configured scope." - }, - { - "description": "Denies the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-position", - "markdownDescription": "Denies the webview_position command without any pre-configured scope." - }, - { - "description": "Denies the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-show", - "markdownDescription": "Denies the webview_show command without any pre-configured scope." - }, - { - "description": "Denies the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-size", - "markdownDescription": "Denies the webview_size command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`", - "type": "string", - "const": "core:window:default", - "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`" - }, - { - "description": "Enables the activity_name command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-activity-name", - "markdownDescription": "Enables the activity_name command without any pre-configured scope." - }, - { - "description": "Enables the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-available-monitors", - "markdownDescription": "Enables the available_monitors command without any pre-configured scope." - }, - { - "description": "Enables the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-center", - "markdownDescription": "Enables the center command without any pre-configured scope." - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-close", - "markdownDescription": "Enables the close command without any pre-configured scope." - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-create", - "markdownDescription": "Enables the create command without any pre-configured scope." - }, - { - "description": "Enables the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-current-monitor", - "markdownDescription": "Enables the current_monitor command without any pre-configured scope." - }, - { - "description": "Enables the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-cursor-position", - "markdownDescription": "Enables the cursor_position command without any pre-configured scope." - }, - { - "description": "Enables the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-destroy", - "markdownDescription": "Enables the destroy command without any pre-configured scope." - }, - { - "description": "Enables the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-get-all-windows", - "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." - }, - { - "description": "Enables the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-hide", - "markdownDescription": "Enables the hide command without any pre-configured scope." - }, - { - "description": "Enables the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-position", - "markdownDescription": "Enables the inner_position command without any pre-configured scope." - }, - { - "description": "Enables the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-size", - "markdownDescription": "Enables the inner_size command without any pre-configured scope." - }, - { - "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-internal-toggle-maximize", - "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." - }, - { - "description": "Enables the is_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-always-on-top", - "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." - }, - { - "description": "Enables the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-closable", - "markdownDescription": "Enables the is_closable command without any pre-configured scope." - }, - { - "description": "Enables the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-decorated", - "markdownDescription": "Enables the is_decorated command without any pre-configured scope." - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-enabled", - "markdownDescription": "Enables the is_enabled command without any pre-configured scope." - }, - { - "description": "Enables the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-focused", - "markdownDescription": "Enables the is_focused command without any pre-configured scope." - }, - { - "description": "Enables the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-fullscreen", - "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." - }, - { - "description": "Enables the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximizable", - "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." - }, - { - "description": "Enables the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximized", - "markdownDescription": "Enables the is_maximized command without any pre-configured scope." - }, - { - "description": "Enables the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimizable", - "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." - }, - { - "description": "Enables the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimized", - "markdownDescription": "Enables the is_minimized command without any pre-configured scope." - }, - { - "description": "Enables the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-resizable", - "markdownDescription": "Enables the is_resizable command without any pre-configured scope." - }, - { - "description": "Enables the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-visible", - "markdownDescription": "Enables the is_visible command without any pre-configured scope." - }, - { - "description": "Enables the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-maximize", - "markdownDescription": "Enables the maximize command without any pre-configured scope." - }, - { - "description": "Enables the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-minimize", - "markdownDescription": "Enables the minimize command without any pre-configured scope." - }, - { - "description": "Enables the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-monitor-from-point", - "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." - }, - { - "description": "Enables the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-position", - "markdownDescription": "Enables the outer_position command without any pre-configured scope." - }, - { - "description": "Enables the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-size", - "markdownDescription": "Enables the outer_size command without any pre-configured scope." - }, - { - "description": "Enables the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-primary-monitor", - "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." - }, - { - "description": "Enables the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-request-user-attention", - "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." - }, - { - "description": "Enables the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-scale-factor", - "markdownDescription": "Enables the scale_factor command without any pre-configured scope." - }, - { - "description": "Enables the scene_identifier command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-scene-identifier", - "markdownDescription": "Enables the scene_identifier command without any pre-configured scope." - }, - { - "description": "Enables the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-bottom", - "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." - }, - { - "description": "Enables the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-top", - "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." - }, - { - "description": "Enables the set_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-background-color", - "markdownDescription": "Enables the set_background_color command without any pre-configured scope." - }, - { - "description": "Enables the set_badge_count command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-badge-count", - "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." - }, - { - "description": "Enables the set_badge_label command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-badge-label", - "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." - }, - { - "description": "Enables the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-closable", - "markdownDescription": "Enables the set_closable command without any pre-configured scope." - }, - { - "description": "Enables the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-content-protected", - "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." - }, - { - "description": "Enables the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-grab", - "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." - }, - { - "description": "Enables the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-icon", - "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." - }, - { - "description": "Enables the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-position", - "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." - }, - { - "description": "Enables the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-visible", - "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." - }, - { - "description": "Enables the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-decorations", - "markdownDescription": "Enables the set_decorations command without any pre-configured scope." - }, - { - "description": "Enables the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-effects", - "markdownDescription": "Enables the set_effects command without any pre-configured scope." - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-enabled", - "markdownDescription": "Enables the set_enabled command without any pre-configured scope." - }, - { - "description": "Enables the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-focus", - "markdownDescription": "Enables the set_focus command without any pre-configured scope." - }, - { - "description": "Enables the set_focusable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-focusable", - "markdownDescription": "Enables the set_focusable command without any pre-configured scope." - }, - { - "description": "Enables the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-fullscreen", - "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-icon", - "markdownDescription": "Enables the set_icon command without any pre-configured scope." - }, - { - "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-ignore-cursor-events", - "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." - }, - { - "description": "Enables the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-max-size", - "markdownDescription": "Enables the set_max_size command without any pre-configured scope." - }, - { - "description": "Enables the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-maximizable", - "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." - }, - { - "description": "Enables the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-min-size", - "markdownDescription": "Enables the set_min_size command without any pre-configured scope." - }, - { - "description": "Enables the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-minimizable", - "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." - }, - { - "description": "Enables the set_overlay_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-overlay-icon", - "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." - }, - { - "description": "Enables the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-position", - "markdownDescription": "Enables the set_position command without any pre-configured scope." - }, - { - "description": "Enables the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-progress-bar", - "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." - }, - { - "description": "Enables the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-resizable", - "markdownDescription": "Enables the set_resizable command without any pre-configured scope." - }, - { - "description": "Enables the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-shadow", - "markdownDescription": "Enables the set_shadow command without any pre-configured scope." - }, - { - "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-simple-fullscreen", - "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." - }, - { - "description": "Enables the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size", - "markdownDescription": "Enables the set_size command without any pre-configured scope." - }, - { - "description": "Enables the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size-constraints", - "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." - }, - { - "description": "Enables the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-skip-taskbar", - "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." - }, - { - "description": "Enables the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-theme", - "markdownDescription": "Enables the set_theme command without any pre-configured scope." - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title", - "markdownDescription": "Enables the set_title command without any pre-configured scope." - }, - { - "description": "Enables the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title-bar-style", - "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." - }, - { - "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-visible-on-all-workspaces", - "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." - }, - { - "description": "Enables the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-show", - "markdownDescription": "Enables the show command without any pre-configured scope." - }, - { - "description": "Enables the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-dragging", - "markdownDescription": "Enables the start_dragging command without any pre-configured scope." - }, - { - "description": "Enables the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-resize-dragging", - "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." - }, - { - "description": "Enables the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-theme", - "markdownDescription": "Enables the theme command without any pre-configured scope." - }, - { - "description": "Enables the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-title", - "markdownDescription": "Enables the title command without any pre-configured scope." - }, - { - "description": "Enables the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-toggle-maximize", - "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." - }, - { - "description": "Enables the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unmaximize", - "markdownDescription": "Enables the unmaximize command without any pre-configured scope." - }, - { - "description": "Enables the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unminimize", - "markdownDescription": "Enables the unminimize command without any pre-configured scope." - }, - { - "description": "Denies the activity_name command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-activity-name", - "markdownDescription": "Denies the activity_name command without any pre-configured scope." - }, - { - "description": "Denies the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-available-monitors", - "markdownDescription": "Denies the available_monitors command without any pre-configured scope." - }, - { - "description": "Denies the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-center", - "markdownDescription": "Denies the center command without any pre-configured scope." - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-close", - "markdownDescription": "Denies the close command without any pre-configured scope." - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-create", - "markdownDescription": "Denies the create command without any pre-configured scope." - }, - { - "description": "Denies the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-current-monitor", - "markdownDescription": "Denies the current_monitor command without any pre-configured scope." - }, - { - "description": "Denies the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-cursor-position", - "markdownDescription": "Denies the cursor_position command without any pre-configured scope." - }, - { - "description": "Denies the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-destroy", - "markdownDescription": "Denies the destroy command without any pre-configured scope." - }, - { - "description": "Denies the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-get-all-windows", - "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." - }, - { - "description": "Denies the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-hide", - "markdownDescription": "Denies the hide command without any pre-configured scope." - }, - { - "description": "Denies the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-position", - "markdownDescription": "Denies the inner_position command without any pre-configured scope." - }, - { - "description": "Denies the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-size", - "markdownDescription": "Denies the inner_size command without any pre-configured scope." - }, - { - "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-internal-toggle-maximize", - "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." - }, - { - "description": "Denies the is_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-always-on-top", - "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." - }, - { - "description": "Denies the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-closable", - "markdownDescription": "Denies the is_closable command without any pre-configured scope." - }, - { - "description": "Denies the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-decorated", - "markdownDescription": "Denies the is_decorated command without any pre-configured scope." - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-enabled", - "markdownDescription": "Denies the is_enabled command without any pre-configured scope." - }, - { - "description": "Denies the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-focused", - "markdownDescription": "Denies the is_focused command without any pre-configured scope." - }, - { - "description": "Denies the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-fullscreen", - "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." - }, - { - "description": "Denies the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximizable", - "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." - }, - { - "description": "Denies the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximized", - "markdownDescription": "Denies the is_maximized command without any pre-configured scope." - }, - { - "description": "Denies the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimizable", - "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." - }, - { - "description": "Denies the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimized", - "markdownDescription": "Denies the is_minimized command without any pre-configured scope." - }, - { - "description": "Denies the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-resizable", - "markdownDescription": "Denies the is_resizable command without any pre-configured scope." - }, - { - "description": "Denies the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-visible", - "markdownDescription": "Denies the is_visible command without any pre-configured scope." - }, - { - "description": "Denies the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-maximize", - "markdownDescription": "Denies the maximize command without any pre-configured scope." - }, - { - "description": "Denies the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-minimize", - "markdownDescription": "Denies the minimize command without any pre-configured scope." - }, - { - "description": "Denies the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-monitor-from-point", - "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." - }, - { - "description": "Denies the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-position", - "markdownDescription": "Denies the outer_position command without any pre-configured scope." - }, - { - "description": "Denies the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-size", - "markdownDescription": "Denies the outer_size command without any pre-configured scope." - }, - { - "description": "Denies the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-primary-monitor", - "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." - }, - { - "description": "Denies the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-request-user-attention", - "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." - }, - { - "description": "Denies the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-scale-factor", - "markdownDescription": "Denies the scale_factor command without any pre-configured scope." - }, - { - "description": "Denies the scene_identifier command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-scene-identifier", - "markdownDescription": "Denies the scene_identifier command without any pre-configured scope." - }, - { - "description": "Denies the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-bottom", - "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." - }, - { - "description": "Denies the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-top", - "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." - }, - { - "description": "Denies the set_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-background-color", - "markdownDescription": "Denies the set_background_color command without any pre-configured scope." - }, - { - "description": "Denies the set_badge_count command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-badge-count", - "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." - }, - { - "description": "Denies the set_badge_label command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-badge-label", - "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." - }, - { - "description": "Denies the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-closable", - "markdownDescription": "Denies the set_closable command without any pre-configured scope." - }, - { - "description": "Denies the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-content-protected", - "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." - }, - { - "description": "Denies the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-grab", - "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." - }, - { - "description": "Denies the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-icon", - "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." - }, - { - "description": "Denies the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-position", - "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." - }, - { - "description": "Denies the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-visible", - "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." - }, - { - "description": "Denies the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-decorations", - "markdownDescription": "Denies the set_decorations command without any pre-configured scope." - }, - { - "description": "Denies the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-effects", - "markdownDescription": "Denies the set_effects command without any pre-configured scope." - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-enabled", - "markdownDescription": "Denies the set_enabled command without any pre-configured scope." - }, - { - "description": "Denies the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-focus", - "markdownDescription": "Denies the set_focus command without any pre-configured scope." - }, - { - "description": "Denies the set_focusable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-focusable", - "markdownDescription": "Denies the set_focusable command without any pre-configured scope." - }, - { - "description": "Denies the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-fullscreen", - "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-icon", - "markdownDescription": "Denies the set_icon command without any pre-configured scope." - }, - { - "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-ignore-cursor-events", - "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." - }, - { - "description": "Denies the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-max-size", - "markdownDescription": "Denies the set_max_size command without any pre-configured scope." - }, - { - "description": "Denies the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-maximizable", - "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." - }, - { - "description": "Denies the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-min-size", - "markdownDescription": "Denies the set_min_size command without any pre-configured scope." - }, - { - "description": "Denies the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-minimizable", - "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." - }, - { - "description": "Denies the set_overlay_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-overlay-icon", - "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." - }, - { - "description": "Denies the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-position", - "markdownDescription": "Denies the set_position command without any pre-configured scope." - }, - { - "description": "Denies the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-progress-bar", - "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." - }, - { - "description": "Denies the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-resizable", - "markdownDescription": "Denies the set_resizable command without any pre-configured scope." - }, - { - "description": "Denies the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-shadow", - "markdownDescription": "Denies the set_shadow command without any pre-configured scope." - }, - { - "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-simple-fullscreen", - "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." - }, - { - "description": "Denies the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size", - "markdownDescription": "Denies the set_size command without any pre-configured scope." - }, - { - "description": "Denies the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size-constraints", - "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." - }, - { - "description": "Denies the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-skip-taskbar", - "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." - }, - { - "description": "Denies the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-theme", - "markdownDescription": "Denies the set_theme command without any pre-configured scope." - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title", - "markdownDescription": "Denies the set_title command without any pre-configured scope." - }, - { - "description": "Denies the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title-bar-style", - "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." - }, - { - "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-visible-on-all-workspaces", - "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." - }, - { - "description": "Denies the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-show", - "markdownDescription": "Denies the show command without any pre-configured scope." - }, - { - "description": "Denies the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-dragging", - "markdownDescription": "Denies the start_dragging command without any pre-configured scope." - }, - { - "description": "Denies the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-resize-dragging", - "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." - }, - { - "description": "Denies the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-theme", - "markdownDescription": "Denies the theme command without any pre-configured scope." - }, - { - "description": "Denies the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-title", - "markdownDescription": "Denies the title command without any pre-configured scope." - }, - { - "description": "Denies the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-toggle-maximize", - "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." - }, - { - "description": "Denies the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unmaximize", - "markdownDescription": "Denies the unmaximize command without any pre-configured scope." - }, - { - "description": "Denies the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unminimize", - "markdownDescription": "Denies the unminimize command without any pre-configured scope." - } - ] - }, - "Value": { - "description": "All supported ACL values.", - "anyOf": [ - { - "description": "Represents a null JSON value.", - "type": "null" - }, - { - "description": "Represents a [`bool`].", - "type": "boolean" - }, - { - "description": "Represents a valid ACL [`Number`].", - "allOf": [ - { - "$ref": "#/definitions/Number" - } - ] - }, - { - "description": "Represents a [`String`].", - "type": "string" - }, - { - "description": "Represents a list of other [`Value`]s.", - "type": "array", - "items": { - "$ref": "#/definitions/Value" - } - }, - { - "description": "Represents a map of [`String`] keys to [`Value`]s.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Value" - } - } - ] - }, - "Number": { - "description": "A valid ACL number.", - "anyOf": [ - { - "description": "Represents an [`i64`].", - "type": "integer", - "format": "int64" - }, - { - "description": "Represents a [`f64`].", - "type": "number", - "format": "double" - } - ] - }, - "Target": { - "description": "Platform target.", - "oneOf": [ - { - "description": "MacOS.", - "type": "string", - "enum": [ - "macOS" - ] - }, - { - "description": "Windows.", - "type": "string", - "enum": [ - "windows" - ] - }, - { - "description": "Linux.", - "type": "string", - "enum": [ - "linux" - ] - }, - { - "description": "Android.", - "type": "string", - "enum": [ - "android" - ] - }, - { - "description": "iOS.", - "type": "string", - "enum": [ - "iOS" - ] - } - ] - } - } -} \ No newline at end of file diff --git a/client/app-shell/icons/icon.ico b/client/app-shell/icons/icon.ico deleted file mode 100644 index bca8c50..0000000 Binary files a/client/app-shell/icons/icon.ico and /dev/null differ diff --git a/client/app-shell/icons/remotedesk-icon.png b/client/app-shell/icons/remotedesk-icon.png deleted file mode 100644 index 5b150b0..0000000 Binary files a/client/app-shell/icons/remotedesk-icon.png and /dev/null differ diff --git a/client/app-shell/icons/remotedesk-icon.svg b/client/app-shell/icons/remotedesk-icon.svg deleted file mode 100644 index cdd6019..0000000 --- a/client/app-shell/icons/remotedesk-icon.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/client/app-shell/src/main.rs b/client/app-shell/src/main.rs deleted file mode 100644 index 39fff9e..0000000 --- a/client/app-shell/src/main.rs +++ /dev/null @@ -1,318 +0,0 @@ -#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")] - -#[cfg(not(windows))] -fn main() { - eprintln!("RemoteDesk desktop shell is available only on Windows"); -} - -#[cfg(windows)] -mod windows_app { - use std::env; - use std::fs::{self, File, OpenOptions}; - use std::io::{self, Read as _, Write as _}; - use std::net::{Ipv4Addr, SocketAddrV4, TcpListener, TcpStream}; - use std::path::{Path, PathBuf}; - use std::process::{Child, Command, Stdio}; - use std::sync::mpsc::{self, RecvTimeoutError, Sender}; - use std::thread; - use std::time::{Duration, Instant}; - - use sha2::{Digest as _, Sha256}; - use std::os::windows::process::CommandExt as _; - use tauri::{Manager as _, WebviewUrl, WebviewWindowBuilder}; - - const CREATE_NO_WINDOW: u32 = 0x0800_0000; - const PERSISTENT_WEB_PORT: u16 = 4173; - const STARTUP_TIMEOUT: Duration = Duration::from_secs(10); - const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(2); - - struct ControlServiceHandle { - shutdown: Sender<()>, - } - - impl Drop for ControlServiceHandle { - fn drop(&mut self) { - let _ = self.shutdown.send(()); - } - } - - pub fn run() { - tauri::Builder::default() - .setup(|app| { - let (control_service, port) = start_control_service(app.handle().clone())?; - app.manage(control_service); - - // WebView2 treats localhost as a trusted loopback origin more - // consistently than a numeric loopback URL across enterprise - // policies and older runtime builds. - let url = tauri::Url::parse(&format!("http://localhost:{port}/"))?; - WebviewWindowBuilder::new(app, "main", WebviewUrl::External(url)) - .title("RemoteDesk") - .inner_size(1280.0, 800.0) - .min_inner_size(960.0, 600.0) - .devtools(cfg!(debug_assertions)) - .build()?; - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("RemoteDesk desktop runtime failed"); - } - - fn start_control_service( - app_handle: tauri::AppHandle, - ) -> Result<(ControlServiceHandle, u16), Box> { - let web_root = resolve_web_root()?; - let service_path = resolve_control_service()?; - // Keep the WebView origin stable so its localStorage (host profiles and - // preferences) survives application restarts and MSI upgrades. Fall back - // to an ephemeral port only when another local service owns the port. - let port = reserve_persistent_or_ephemeral_port()?; - let shell_token = new_shell_token()?; - let (ready_tx, ready_rx) = mpsc::sync_channel(1); - let (shutdown_tx, shutdown_rx) = mpsc::channel(); - - thread::Builder::new() - .name("remotedesk-control-service-manager".to_owned()) - .spawn(move || { - let mut child = - match spawn_control_service(&service_path, &web_root, port, &shell_token) { - Ok(child) => child, - Err(error) => { - let _ = ready_tx.send(Err(error.to_string())); - return; - } - }; - if let Err(error) = wait_for_control_service(&mut child, port, &shell_token) { - let _ = ready_tx.send(Err(error.to_string())); - stop_child(&mut child); - return; - } - if ready_tx.send(Ok(port)).is_err() { - stop_child(&mut child); - return; - } - - loop { - match shutdown_rx.recv_timeout(Duration::from_millis(250)) { - Ok(()) | Err(RecvTimeoutError::Disconnected) => { - stop_child(&mut child); - return; - } - Err(RecvTimeoutError::Timeout) => {} - } - match child.try_wait() { - Ok(Some(status)) => { - app_handle.exit(if status.success() { 0 } else { 1 }); - return; - } - Ok(None) => {} - Err(_) => { - stop_child(&mut child); - app_handle.exit(1); - return; - } - } - } - })?; - - let handle = ControlServiceHandle { - shutdown: shutdown_tx, - }; - match ready_rx.recv_timeout(STARTUP_TIMEOUT) { - Ok(Ok(ready_port)) => Ok((handle, ready_port)), - Ok(Err(error)) => Err(io::Error::other(error).into()), - Err(_) => Err(io::Error::new( - io::ErrorKind::TimedOut, - "control service did not become ready within 10 seconds", - ) - .into()), - } - } - - fn reserve_persistent_or_ephemeral_port() -> io::Result { - if TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, PERSISTENT_WEB_PORT)).is_ok() { - return Ok(PERSISTENT_WEB_PORT); - } - reserve_loopback_port() - } - - fn resolve_web_root() -> io::Result { - if let Some(path) = environment_path("REMOTEDESK_WEB_ROOT") { - return validate_web_root(path); - } - let executable = env::current_exe()?; - let executable_dir = executable - .parent() - .ok_or_else(|| io::Error::other("desktop executable has no parent directory"))?; - if let Some(install_root) = executable_dir.parent() { - let installed = install_root.join("web"); - if installed.join("index.html").is_file() { - return validate_web_root(installed); - } - } - validate_web_root( - Path::new(env!("CARGO_MANIFEST_DIR")) - .join("..") - .join("web") - .join("dist"), - ) - } - - fn validate_web_root(path: PathBuf) -> io::Result { - let canonical = path.canonicalize()?; - if !canonical.join("index.html").is_file() { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "RemoteDesk web assets do not contain index.html", - )); - } - Ok(canonical) - } - - fn resolve_control_service() -> io::Result { - let path = if let Some(path) = environment_path("REMOTEDESK_CONTROL_SERVICE") { - path - } else { - env::current_exe()? - .parent() - .ok_or_else(|| io::Error::other("desktop executable has no parent directory"))? - .join("remotedesk-control-service.exe") - }; - let canonical = path.canonicalize()?; - if !canonical.is_file() { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "RemoteDesk control service executable is missing", - )); - } - Ok(canonical) - } - - fn environment_path(name: &str) -> Option { - env::var_os(name) - .filter(|value| !value.is_empty()) - .map(PathBuf::from) - } - - fn reserve_loopback_port() -> io::Result { - let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0))?; - let port = listener.local_addr()?.port(); - drop(listener); - if port < 1_024 { - return Err(io::Error::other("Windows selected a reserved local port")); - } - Ok(port) - } - - fn new_shell_token() -> io::Result { - let mut bytes = [0_u8; 32]; - getrandom::fill(&mut bytes).map_err(|error| io::Error::other(error.to_string()))?; - let mut token = String::with_capacity(bytes.len() * 2); - const HEX: &[u8; 16] = b"0123456789abcdef"; - for byte in bytes { - token.push(char::from(HEX[usize::from(byte >> 4)])); - token.push(char::from(HEX[usize::from(byte & 0x0f)])); - } - Ok(token) - } - - fn spawn_control_service( - service: &Path, - web_root: &Path, - port: u16, - shell_token: &str, - ) -> io::Result { - let mut command = Command::new(service); - command - .arg("--web-root") - .arg(web_root) - .arg("--port") - .arg(port.to_string()) - .arg("--exit-on-stdin-close") - .env("REMOTEDESK_DESKTOP_SHELL", "tauri-2") - .env("REMOTEDESK_SHELL_TOKEN", shell_token) - .stdin(Stdio::piped()) - .creation_flags(CREATE_NO_WINDOW); - - if let Some(log) = open_control_service_log() { - command.stdout(Stdio::from(log.try_clone()?)); - command.stderr(Stdio::from(log)); - } else { - command.stdout(Stdio::null()); - command.stderr(Stdio::null()); - } - command.spawn() - } - - fn open_control_service_log() -> Option { - let root = env::var_os("LOCALAPPDATA") - .filter(|value| !value.is_empty()) - .map(PathBuf::from)? - .join("RemoteDesk") - .join("logs"); - fs::create_dir_all(&root).ok()?; - OpenOptions::new() - .create(true) - .append(true) - .open(root.join("control-service.log")) - .ok() - } - - fn wait_for_control_service(child: &mut Child, port: u16, shell_token: &str) -> io::Result<()> { - let deadline = Instant::now() + STARTUP_TIMEOUT; - let address = SocketAddrV4::new(Ipv4Addr::LOCALHOST, port); - let expected_proof = format!("{:x}", Sha256::digest(shell_token.as_bytes())); - let expected_body = format!(r#"{{"ready":true,"proof":"{expected_proof}"}}"#).into_bytes(); - while Instant::now() < deadline { - if let Some(status) = child.try_wait()? { - return Err(io::Error::other(format!( - "control service exited during startup with {status}" - ))); - } - if let Ok(mut stream) = - TcpStream::connect_timeout(&address.into(), Duration::from_millis(100)) - { - stream.set_read_timeout(Some(Duration::from_millis(250)))?; - stream.set_write_timeout(Some(Duration::from_millis(250)))?; - let request = format!( - "GET /api/v1/shell/ready HTTP/1.1\r\nHost: 127.0.0.1:{port}\r\nConnection: close\r\n\r\n" - ); - if stream.write_all(request.as_bytes()).is_ok() { - let mut response = Vec::new(); - if stream.take(4_096).read_to_end(&mut response).is_ok() - && response.starts_with(b"HTTP/1.1 200 ") - && response - .windows(expected_body.len()) - .any(|window| window == expected_body) - { - return Ok(()); - } - } - } - thread::sleep(Duration::from_millis(50)); - } - Err(io::Error::new( - io::ErrorKind::TimedOut, - "control service did not open its loopback listener", - )) - } - - fn stop_child(child: &mut Child) { - drop(child.stdin.take()); - let deadline = Instant::now() + SHUTDOWN_TIMEOUT; - while Instant::now() < deadline { - match child.try_wait() { - Ok(Some(_)) => return, - Ok(None) => thread::sleep(Duration::from_millis(50)), - Err(_) => break, - } - } - let _ = child.kill(); - let _ = child.wait(); - } -} - -#[cfg(windows)] -fn main() { - windows_app::run(); -} diff --git a/client/app-shell/tauri.conf.json b/client/app-shell/tauri.conf.json deleted file mode 100644 index 8892a39..0000000 --- a/client/app-shell/tauri.conf.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "https://schema.tauri.app/config/2", - "productName": "RemoteDesk", - "version": "0.2.19", - "identifier": "com.remotedesk.client", - "build": { - "frontendDist": "../web/dist" - }, - "app": { - "windows": [], - "security": { - "capabilities": [] - } - }, - "bundle": { - "active": false, - "icon": [ - "icons/icon.ico" - ] - } -} diff --git a/client/crates/client-core/src/display.rs b/client/crates/client-core/src/display.rs index 286e8b2..906b889 100644 --- a/client/crates/client-core/src/display.rs +++ b/client/crates/client-core/src/display.rs @@ -1056,7 +1056,7 @@ mod tests { } #[test] - fn compatibility_marks_cross_adapter_plan_without_claiming_zero_copy() { + fn cross_adapter_display_plan_is_rejected() { let mut planner = ClientDisplayPlanner::new(); let mut request = request( ClientDisplayBackend::WindowsRdpDisplayControl, @@ -1068,9 +1068,12 @@ mod tests { zero_copy_policy: ZeroCopyPolicy::RequiredEndToEnd, }; request.decoder_adapter = adapter("gpu-1"); - let plan = planner.plan(request).unwrap(); - - assert!(!plan.gpu_pipeline.is_zero_copy_candidate()); - assert_eq!(plan.gpu_pipeline.display_adapter, adapter("gpu-0")); + assert!(matches!( + planner.plan(request), + Err(DisplayPlanError::GpuPipeline( + PipelinePlanError::CrossAdapterRejected { .. } + )) + )); + assert_eq!(planner.generation(), 0); } } diff --git a/client/crates/client-core/src/gpu.rs b/client/crates/client-core/src/gpu.rs index 051796d..fd38872 100644 --- a/client/crates/client-core/src/gpu.rs +++ b/client/crates/client-core/src/gpu.rs @@ -216,8 +216,8 @@ mod tests { } #[test] - fn compatibility_policy_marks_cross_adapter_copy() { - let plan = plan_d3d11_pipeline(PipelineRequest { + fn the_only_policy_rejects_cross_adapter_copy() { + let error = plan_d3d11_pipeline(PipelineRequest { policy: GpuPolicy { selection: GpuSelection::WindowDisplayAdapter, zero_copy_policy: ZeroCopyPolicy::RequiredEndToEnd, @@ -226,16 +226,17 @@ mod tests { decoder_adapter: adapter("decode"), available_adapters: vec![adapter("render"), adapter("decode")], }) - .unwrap(); + .unwrap_err(); - assert_eq!(plan.planned_memory_path, MemoryPathStatus::CrossAdapterCopy); - assert_eq!(plan.zero_copy_policy, ZeroCopyPolicy::RequiredEndToEnd); - assert!(!plan.is_zero_copy_candidate()); + assert!(matches!( + error, + PipelinePlanError::CrossAdapterRejected { .. } + )); } #[test] - fn compatibility_allows_manual_render_adapter_to_differ_from_display() { - let plan = plan_d3d11_pipeline(PipelineRequest { + fn manual_render_adapter_must_match_display() { + let error = plan_d3d11_pipeline(PipelineRequest { policy: GpuPolicy { selection: GpuSelection::Manual(adapter("manual-gpu")), zero_copy_policy: ZeroCopyPolicy::RequiredEndToEnd, @@ -244,12 +245,11 @@ mod tests { decoder_adapter: adapter("manual-gpu"), available_adapters: vec![adapter("display-gpu"), adapter("manual-gpu")], }) - .unwrap(); + .unwrap_err(); - assert_eq!(plan.decode_adapter, adapter("manual-gpu")); - assert_eq!(plan.render_adapter, adapter("manual-gpu")); - assert_eq!(plan.display_adapter, adapter("display-gpu")); - assert_eq!(plan.planned_memory_path, MemoryPathStatus::CrossAdapterCopy); - assert!(!plan.is_zero_copy_candidate()); + assert!(matches!( + error, + PipelinePlanError::CrossAdapterRejected { .. } + )); } } diff --git a/client/crates/client-core/src/secure_pipe.rs b/client/crates/client-core/src/secure_pipe.rs index 0b2d4d1..330ce47 100644 --- a/client/crates/client-core/src/secure_pipe.rs +++ b/client/crates/client-core/src/secure_pipe.rs @@ -228,7 +228,7 @@ impl SecurePipeServer { .checked_add(timeout) .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid pipe timeout"))?; windows_platform::wait_for_client(&self.pipe, timeout)?; - windows_platform::set_blocking(&self.pipe)?; + windows_platform::set_nonblocking(&self.pipe)?; let pipe_pid = windows_platform::pipe_client_pid(&self.pipe)?; if pipe_pid != expected.pid { return Err(io::Error::new( @@ -654,12 +654,9 @@ fn read_exact_until( while offset < buffer.len() { ensure_handshake_deadline(deadline)?; match reader.read(&mut buffer[offset..]) { - Ok(0) => { - return Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "secure pipe peer closed during handshake", - )); - } + // In PIPE_NOWAIT mode Windows can report a successful zero-byte + // read while the connected peer has not produced data yet. + Ok(0) => std::thread::sleep(CONNECT_POLL_INTERVAL), Ok(count) => offset += count, Err(error) if error.kind() == io::ErrorKind::Interrupted => {} Err(error) if windows_platform::read_is_pending(&error) => { @@ -712,7 +709,7 @@ mod windows_platform { System::{ Pipes::{ ConnectNamedPipe, CreateNamedPipeW, GetNamedPipeClientProcessId, PIPE_NOWAIT, - PIPE_READMODE_BYTE, PIPE_REJECT_REMOTE_CLIENTS, PIPE_TYPE_BYTE, PIPE_WAIT, + PIPE_READMODE_BYTE, PIPE_REJECT_REMOTE_CLIENTS, PIPE_TYPE_BYTE, SetNamedPipeHandleState, }, Threading::{ @@ -807,7 +804,7 @@ mod windows_platform { loop { match OpenOptions::new().read(true).write(true).open(pipe_name) { Ok(pipe) => { - set_blocking(&pipe)?; + set_nonblocking(&pipe)?; return Ok(pipe); } Err(error) @@ -823,8 +820,8 @@ mod windows_platform { } } - pub fn set_blocking(pipe: &File) -> io::Result<()> { - let mode = PIPE_READMODE_BYTE | PIPE_WAIT; + pub fn set_nonblocking(pipe: &File) -> io::Result<()> { + let mode = PIPE_READMODE_BYTE | PIPE_NOWAIT; // SAFETY: handle is a live Named Pipe client and mode points to initialized storage. if unsafe { SetNamedPipeHandleState( @@ -1169,7 +1166,7 @@ mod tests { let _pipe = windows_platform::connect_client(&pipe_name, Duration::from_secs(2)) .expect("test client connects"); connected_sender.send(()).unwrap(); - std::thread::sleep(Duration::from_millis(500)); + std::thread::sleep(Duration::from_secs(2)); }); connected_receiver .recv_timeout(Duration::from_secs(2)) diff --git a/client/crates/webrtc-core/Cargo.toml b/client/crates/webrtc-core/Cargo.toml new file mode 100644 index 0000000..0b2285a --- /dev/null +++ b/client/crates/webrtc-core/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "remotedesk-webrtc-core" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +description = "Shared bounded WebRTC signaling and media policy for RemoteDesk" + +[dependencies] +bytes = "1.12" +remotedesk-protocol = { path = "../../../protocol" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +str0m = "0.23" + +[lints] +workspace = true diff --git a/client/crates/webrtc-core/src/lib.rs b/client/crates/webrtc-core/src/lib.rs new file mode 100644 index 0000000..ebd060c --- /dev/null +++ b/client/crates/webrtc-core/src/lib.rs @@ -0,0 +1,673 @@ +//! Shared WebRTC policy boundary. +//! +//! `str0m` is intentionally kept behind this crate so Agent and Client do not +//! exchange library-specific types. Signaling and media payloads remain +//! bounded before they reach the Sans-IO WebRTC engine. + +use std::net::SocketAddr; +use std::time::{Duration, Instant}; + +use remotedesk_protocol::{MAX_ICE_CANDIDATE_BYTES, MAX_SDP_BYTES}; + +pub const MAX_SIGNAL_MESSAGES: usize = 256; +pub const MAX_DATA_CHANNEL_MESSAGE_BYTES: usize = 64 * 1024; +pub const MAX_ICE_SERVERS: usize = 8; +pub const MAX_ICE_URLS_PER_SERVER: usize = 8; +pub const MAX_ICE_URL_BYTES: usize = 2_048; +pub const MAX_ICE_CREDENTIAL_BYTES: usize = 1_024; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct IceServerConfig { + pub urls: Vec, + pub username: String, + pub credential: String, +} + +impl IceServerConfig { + pub fn validate(&self) -> Result<(), SignalError> { + if self.urls.is_empty() + || self.urls.len() > MAX_ICE_URLS_PER_SERVER + || self.username.len() > MAX_ICE_CREDENTIAL_BYTES + || self.credential.len() > MAX_ICE_CREDENTIAL_BYTES + || self.username.is_empty() != self.credential.is_empty() + { + return Err(SignalError::InvalidIceServer); + } + for url in &self.urls { + if url.is_empty() + || url.len() > MAX_ICE_URL_BYTES + || url.chars().any(char::is_control) + || !matches!( + url.split_once(':').map(|(scheme, _)| scheme), + Some("stun" | "stuns" | "turn" | "turns") + ) + { + return Err(SignalError::InvalidIceServer); + } + } + Ok(()) + } +} + +pub fn validate_ice_servers(servers: &[IceServerConfig]) -> Result<(), SignalError> { + if servers.len() > MAX_ICE_SERVERS { + return Err(SignalError::InvalidIceServer); + } + servers.iter().try_for_each(IceServerConfig::validate) +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum WebRtcRole { + Offerer, + Answerer, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum SignalKind { + Offer, + Answer, + IceCandidate, + IceEnd, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SignalMessage { + pub kind: SignalKind, + pub payload: String, +} + +impl SignalMessage { + /// Creates a bounded signaling message. + pub fn new(kind: SignalKind, payload: impl Into) -> Result { + let payload = payload.into(); + let valid = match kind { + SignalKind::Offer | SignalKind::Answer => { + !payload.is_empty() && payload.len() <= MAX_SDP_BYTES + } + SignalKind::IceCandidate => { + !payload.is_empty() && payload.len() <= MAX_ICE_CANDIDATE_BYTES + } + SignalKind::IceEnd => payload.is_empty(), + }; + if !valid || payload.contains('\0') { + return Err(SignalError::InvalidPayload); + } + Ok(Self { kind, payload }) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SessionLimits { + pub connect_timeout: Duration, + pub max_data_channel_message_bytes: usize, +} + +impl Default for SessionLimits { + fn default() -> Self { + Self { + connect_timeout: Duration::from_secs(30), + max_data_channel_message_bytes: MAX_DATA_CHANNEL_MESSAGE_BYTES, + } + } +} + +impl SessionLimits { + pub fn validate(self) -> Result<(), SignalError> { + if !(Duration::from_secs(5)..=Duration::from_secs(60)).contains(&self.connect_timeout) + || self.max_data_channel_message_bytes == 0 + || self.max_data_channel_message_bytes > MAX_DATA_CHANNEL_MESSAGE_BYTES + { + return Err(SignalError::InvalidLimits); + } + Ok(()) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum SignalError { + InvalidPayload, + InvalidLimits, + InvalidState, + InvalidIceServer, + Engine, +} + +impl std::fmt::Display for SignalError { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str(match self { + Self::InvalidPayload => "invalid or oversized WebRTC signaling payload", + Self::InvalidLimits => "WebRTC session limits are outside the supported bounds", + Self::InvalidState => "WebRTC endpoint is not in the required state", + Self::InvalidIceServer => "STUN/TURN configuration is outside the supported bounds", + Self::Engine => "str0m rejected the WebRTC operation", + }) + } +} + +impl std::error::Error for SignalError {} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct NetworkDatagram { + pub source: SocketAddr, + pub destination: SocketAddr, + pub contents: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum EndpointEvent { + Connected, + ChannelOpen { + kind: DataChannelKind, + label: String, + }, + ChannelData { + kind: DataChannelKind, + data: Vec, + }, + ChannelClosed(DataChannelKind), + Media { + kind: MediaKind, + data: Vec, + contiguous: bool, + }, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum DataChannelKind { + Control, + Pointer, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MediaKind { + H264, + Opus, +} + +pub enum EndpointOutput { + Timeout(Instant), + Transmit(NetworkDatagram), + Event(EndpointEvent), +} + +/// A Sans-IO str0m endpoint with no str0m types in its public interface. +pub struct WebRtcEndpoint { + rtc: str0m::Rtc, + role: WebRtcRole, + limits: SessionLimits, + pending_offer: Option, + control_channel_id: Option, + pointer_channel_id: Option, + video_mid: Option, + audio_mid: Option, +} + +impl WebRtcEndpoint { + pub fn new(role: WebRtcRole, limits: SessionLimits, now: Instant) -> Result { + limits.validate()?; + Ok(Self { + rtc: str0m::Rtc::new(now), + role, + limits, + pending_offer: None, + control_channel_id: None, + pointer_channel_id: None, + video_mid: None, + audio_mid: None, + }) + } + + pub fn add_host_candidate(&mut self, address: SocketAddr) -> Result<(), SignalError> { + let candidate = str0m::Candidate::host(address, "udp").map_err(|_| SignalError::Engine)?; + self.rtc + .add_local_candidate(candidate) + .ok_or(SignalError::InvalidState)?; + Ok(()) + } + + /// Adds an address learned by an external STUN binding client. + pub fn add_server_reflexive_candidate( + &mut self, + address: SocketAddr, + base: SocketAddr, + ) -> Result<(), SignalError> { + let candidate = str0m::Candidate::server_reflexive(address, base, "udp") + .map_err(|_| SignalError::InvalidPayload)?; + self.rtc + .add_local_candidate(candidate) + .ok_or(SignalError::InvalidState)?; + Ok(()) + } + + /// Adds an allocation learned by an external TURN client. + pub fn add_relay_candidate( + &mut self, + address: SocketAddr, + local: SocketAddr, + protocol: RelayProtocol, + ) -> Result<(), SignalError> { + let protocol = match protocol { + RelayProtocol::Udp => "udp", + RelayProtocol::Tcp | RelayProtocol::Tls => "tcp", + }; + let candidate = str0m::Candidate::relayed(address, local, protocol) + .map_err(|_| SignalError::InvalidPayload)?; + self.rtc + .add_local_candidate(candidate) + .ok_or(SignalError::InvalidState)?; + Ok(()) + } + + pub fn create_offer(&mut self, channel_label: &str) -> Result { + if self.role != WebRtcRole::Offerer + || self.pending_offer.is_some() + || channel_label.is_empty() + || channel_label.len() > 64 + { + return Err(SignalError::InvalidState); + } + let mut change = self.rtc.sdp_api(); + self.control_channel_id = Some(change.add_channel(channel_label.to_owned())); + self.pointer_channel_id = Some(change.add_channel_with_config( + str0m::channel::ChannelConfig { + label: "pointer".to_owned(), + ordered: false, + reliability: str0m::channel::Reliability::MaxRetransmits { retransmits: 0 }, + ..Default::default() + }, + )); + self.video_mid = Some(change.add_media( + str0m::media::MediaKind::Video, + str0m::media::Direction::SendOnly, + None, + None, + None, + )); + self.audio_mid = Some(change.add_media( + str0m::media::MediaKind::Audio, + str0m::media::Direction::SendOnly, + None, + None, + None, + )); + let (offer, pending) = change.apply().ok_or(SignalError::InvalidState)?; + self.pending_offer = Some(pending); + let payload = serde_json::to_string(&offer).map_err(|_| SignalError::Engine)?; + SignalMessage::new(SignalKind::Offer, payload) + } + + pub fn accept_offer(&mut self, offer: &SignalMessage) -> Result { + if self.role != WebRtcRole::Answerer || offer.kind != SignalKind::Offer { + return Err(SignalError::InvalidState); + } + let offer = + serde_json::from_str(&offer.payload).map_err(|_| SignalError::InvalidPayload)?; + let answer = self + .rtc + .sdp_api() + .accept_offer(offer) + .map_err(|_| SignalError::Engine)?; + let payload = serde_json::to_string(&answer).map_err(|_| SignalError::Engine)?; + SignalMessage::new(SignalKind::Answer, payload) + } + + pub fn accept_answer(&mut self, answer: &SignalMessage) -> Result<(), SignalError> { + if self.role != WebRtcRole::Offerer || answer.kind != SignalKind::Answer { + return Err(SignalError::InvalidState); + } + let pending = self.pending_offer.take().ok_or(SignalError::InvalidState)?; + let answer = + serde_json::from_str(&answer.payload).map_err(|_| SignalError::InvalidPayload)?; + self.rtc + .sdp_api() + .accept_answer(pending, answer) + .map_err(|_| SignalError::Engine) + } + + pub fn handle_timeout(&mut self, now: Instant) -> Result<(), SignalError> { + self.rtc + .handle_input(str0m::Input::Timeout(now)) + .map_err(|_| SignalError::Engine) + } + + pub fn handle_datagram( + &mut self, + now: Instant, + datagram: &NetworkDatagram, + ) -> Result<(), SignalError> { + let receive = str0m::net::Receive::new( + str0m::net::Protocol::Udp, + datagram.source, + datagram.destination, + &datagram.contents, + ) + .map_err(|_| SignalError::InvalidPayload)?; + self.rtc + .handle_input(str0m::Input::Receive(now, receive)) + .map_err(|_| SignalError::Engine) + } + + pub fn poll_output(&mut self) -> Result { + loop { + match self.rtc.poll_output().map_err(|_| SignalError::Engine)? { + str0m::Output::Timeout(at) => return Ok(EndpointOutput::Timeout(at)), + str0m::Output::Transmit(packet) => { + return Ok(EndpointOutput::Transmit(NetworkDatagram { + source: packet.source, + destination: packet.destination, + contents: packet.contents.to_vec(), + })); + } + str0m::Output::Event(event) => { + let mapped = match event { + str0m::Event::Connected => Some(EndpointEvent::Connected), + str0m::Event::ChannelOpen(id, label) => { + let kind = if label == "pointer" { + self.pointer_channel_id.get_or_insert(id); + DataChannelKind::Pointer + } else { + self.control_channel_id.get_or_insert(id); + DataChannelKind::Control + }; + Some(EndpointEvent::ChannelOpen { kind, label }) + } + str0m::Event::ChannelData(data) => { + let kind = self.channel_kind(data.id)?; + Some(EndpointEvent::ChannelData { + kind, + data: data.data, + }) + } + str0m::Event::ChannelClose(id) => { + Some(EndpointEvent::ChannelClosed(self.channel_kind(id)?)) + } + str0m::Event::MediaData(media) => { + let kind = match media.params.spec().codec { + str0m::format::Codec::H264 => MediaKind::H264, + str0m::format::Codec::Opus => MediaKind::Opus, + _ => continue, + }; + Some(EndpointEvent::Media { + kind, + data: media.data.to_vec(), + contiguous: media.contiguous, + }) + } + _ => None, + }; + if let Some(event) = mapped { + return Ok(EndpointOutput::Event(event)); + } + } + } + } + } + + pub fn write_channel(&mut self, data: &[u8]) -> Result<(), SignalError> { + self.write_data_channel(DataChannelKind::Control, true, data) + } + + pub fn write_pointer(&mut self, data: &[u8]) -> Result<(), SignalError> { + self.write_data_channel(DataChannelKind::Pointer, true, data) + } + + fn write_data_channel( + &mut self, + kind: DataChannelKind, + binary: bool, + data: &[u8], + ) -> Result<(), SignalError> { + if data.is_empty() || data.len() > self.limits.max_data_channel_message_bytes { + return Err(SignalError::InvalidPayload); + } + let id = match kind { + DataChannelKind::Control => self.control_channel_id, + DataChannelKind::Pointer => self.pointer_channel_id, + } + .ok_or(SignalError::InvalidState)?; + self.rtc + .channel(id) + .ok_or(SignalError::InvalidState)? + .write(binary, data) + .map(|_| ()) + .map_err(|_| SignalError::Engine) + } + + fn channel_kind(&self, id: str0m::channel::ChannelId) -> Result { + if self.control_channel_id == Some(id) { + Ok(DataChannelKind::Control) + } else if self.pointer_channel_id == Some(id) { + Ok(DataChannelKind::Pointer) + } else { + Err(SignalError::InvalidState) + } + } + + pub fn write_h264( + &mut self, + wallclock: Instant, + media_time: Duration, + access_unit: &[u8], + ) -> Result<(), SignalError> { + if access_unit.is_empty() + || access_unit.len() > 16 * 1024 * 1024 + || !(access_unit.starts_with(&[0, 0, 1]) || access_unit.starts_with(&[0, 0, 0, 1])) + { + return Err(SignalError::InvalidPayload); + } + self.write_media(MediaKind::H264, wallclock, media_time, access_unit) + } + + pub fn write_opus( + &mut self, + wallclock: Instant, + media_time: Duration, + packet: &[u8], + ) -> Result<(), SignalError> { + if packet.is_empty() || packet.len() > 4 * 1024 { + return Err(SignalError::InvalidPayload); + } + self.write_media(MediaKind::Opus, wallclock, media_time, packet) + } + + fn write_media( + &mut self, + kind: MediaKind, + wallclock: Instant, + media_time: Duration, + data: &[u8], + ) -> Result<(), SignalError> { + let (mid, codec) = match kind { + MediaKind::H264 => (self.video_mid, str0m::format::Codec::H264), + MediaKind::Opus => (self.audio_mid, str0m::format::Codec::Opus), + }; + let mid = mid.ok_or(SignalError::InvalidState)?; + let pt = self + .rtc + .codec_config() + .find(|params| params.spec().codec == codec) + .map(str0m::format::PayloadParams::pt) + .ok_or(SignalError::InvalidState)?; + self.rtc + .writer(mid) + .ok_or(SignalError::InvalidState)? + .write(pt, wallclock, media_time.into(), data.to_vec()) + .map_err(|_| SignalError::Engine) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum RelayProtocol { + Udp, + Tcp, + Tls, +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::VecDeque; + use std::sync::Once; + + #[test] + fn signal_messages_are_bounded() { + assert!(SignalMessage::new(SignalKind::Offer, "offer").is_ok()); + assert!(SignalMessage::new(SignalKind::IceEnd, "").is_ok()); + assert!(SignalMessage::new(SignalKind::IceEnd, "unexpected").is_err()); + assert!( + SignalMessage::new( + SignalKind::Offer, + vec!['x'; MAX_SDP_BYTES + 1].into_iter().collect::() + ) + .is_err() + ); + } + + #[test] + fn limits_reject_unbounded_sessions() { + assert!(SessionLimits::default().validate().is_ok()); + assert!( + SessionLimits { + connect_timeout: Duration::from_secs(61), + ..Default::default() + } + .validate() + .is_err() + ); + } + + #[test] + fn ice_servers_are_bounded_and_require_credentials_in_pairs() { + let valid = IceServerConfig { + urls: vec!["stun:stun.example.invalid:3478".to_owned()], + username: String::new(), + credential: String::new(), + }; + assert!(validate_ice_servers(&[valid]).is_ok()); + let invalid = IceServerConfig { + urls: vec!["turn:turn.example.invalid:3478".to_owned()], + username: "session".to_owned(), + credential: String::new(), + }; + assert!(validate_ice_servers(&[invalid]).is_err()); + } + + #[test] + fn agent_client_sdp_ice_and_data_channel_loopback() { + static CRYPTO: Once = Once::new(); + CRYPTO.call_once(|| str0m::crypto::from_feature_flags().install_process_default()); + let start = Instant::now(); + let mut client = + WebRtcEndpoint::new(WebRtcRole::Offerer, SessionLimits::default(), start).unwrap(); + let mut agent = + WebRtcEndpoint::new(WebRtcRole::Answerer, SessionLimits::default(), start).unwrap(); + client + .add_host_candidate("127.0.0.1:41000".parse().unwrap()) + .unwrap(); + agent + .add_host_candidate("127.0.0.1:41001".parse().unwrap()) + .unwrap(); + let offer = client.create_offer("control").unwrap(); + let answer = agent.accept_offer(&offer).unwrap(); + client.accept_answer(&answer).unwrap(); + + let mut to_client = VecDeque::new(); + let mut to_agent = VecDeque::new(); + let mut client_open = false; + let mut agent_open = false; + let mut received = false; + let mut pointer_received = false; + let mut media_received = Vec::new(); + for step in 0..2_000_u64 { + let now = start + Duration::from_millis(step * 5); + while let Some(packet) = to_client.pop_front() { + client.handle_datagram(now, &packet).unwrap(); + } + while let Some(packet) = to_agent.pop_front() { + agent.handle_datagram(now, &packet).unwrap(); + } + client.handle_timeout(now).unwrap(); + agent.handle_timeout(now).unwrap(); + drain( + &mut client, + &mut to_agent, + &mut client_open, + &mut received, + &mut pointer_received, + &mut media_received, + ); + drain( + &mut agent, + &mut to_client, + &mut agent_open, + &mut received, + &mut pointer_received, + &mut media_received, + ); + if client_open && agent_open && !received { + client.write_channel(b"loopback-ping").unwrap(); + client.write_pointer(b"pointer-move").unwrap(); + } + if received && media_received.is_empty() { + client + .write_h264(now, Duration::from_millis(33), &[0, 0, 0, 1, 0x65, 1, 2, 3]) + .unwrap(); + client + .write_opus(now, Duration::from_millis(20), &[0x78, 1, 2, 3]) + .unwrap(); + } + if media_received.len() == 2 { + break; + } + } + assert!(client_open && agent_open); + assert!(received); + assert!(pointer_received); + assert!(media_received.contains(&MediaKind::H264)); + assert!(media_received.contains(&MediaKind::Opus)); + } + + fn drain( + endpoint: &mut WebRtcEndpoint, + packets: &mut VecDeque, + opened: &mut bool, + received: &mut bool, + pointer_received: &mut bool, + media_received: &mut Vec, + ) { + loop { + match endpoint.poll_output().unwrap() { + EndpointOutput::Transmit(packet) => packets.push_back(packet), + EndpointOutput::Event(EndpointEvent::ChannelOpen { kind, label }) => match kind { + DataChannelKind::Control => { + assert_eq!(label, "control"); + *opened = true; + } + DataChannelKind::Pointer => assert_eq!(label, "pointer"), + }, + EndpointOutput::Event(EndpointEvent::ChannelData { kind, data }) => match kind { + DataChannelKind::Control => { + assert_eq!(data, b"loopback-ping"); + *received = true; + } + DataChannelKind::Pointer => { + assert_eq!(data, b"pointer-move"); + *pointer_received = true; + } + }, + EndpointOutput::Event(EndpointEvent::Media { + kind, + data, + contiguous, + }) => { + assert!(!data.is_empty()); + assert!(contiguous); + if !media_received.contains(&kind) { + media_received.push(kind); + } + } + EndpointOutput::Event(_) | EndpointOutput::Timeout(_) => break, + } + } + } +} diff --git a/client/helpers/control-service/src/main.rs b/client/helpers/control-service/src/main.rs index 29a68ab..4a75f18 100644 --- a/client/helpers/control-service/src/main.rs +++ b/client/helpers/control-service/src/main.rs @@ -738,17 +738,12 @@ fn capabilities(settings: &PersistentSettings) -> CapabilitiesResponse { let update_configured = settings.update_manifest_url.is_some() && settings.update_public_key.is_some(); let d3d11_available = d3d11_device_available(); - let desktop_shell = env::var("REMOTEDESK_DESKTOP_SHELL").as_deref() == Ok("tauri-2"); CapabilitiesResponse { version: env!("CARGO_PKG_VERSION"), platform: env::consts::OS, application: ApplicationCapabilities { - desktop_shell, - shell_kind: if desktop_shell { - "tauri-2" - } else { - "standalone" - }, + desktop_shell: true, + shell_kind: "winit-wgpu-egui", }, rdp: RdpCapabilities { probe: true, @@ -3330,18 +3325,8 @@ mod tests { assert_eq!(response.version, env!("CARGO_PKG_VERSION")); assert_eq!(response.platform, env::consts::OS); - assert_eq!( - response.application.desktop_shell, - env::var("REMOTEDESK_DESKTOP_SHELL").as_deref() == Ok("tauri-2") - ); - assert_eq!( - response.application.shell_kind, - if response.application.desktop_shell { - "tauri-2" - } else { - "standalone" - } - ); + assert!(response.application.desktop_shell); + assert_eq!(response.application.shell_kind, "winit-wgpu-egui"); assert!(response.rdp.probe); assert_eq!( response.rdp.protocol_probe, diff --git a/client/helpers/native-video/src/main.rs b/client/helpers/native-video/src/main.rs index 94829f6..01c975e 100644 --- a/client/helpers/native-video/src/main.rs +++ b/client/helpers/native-video/src/main.rs @@ -809,44 +809,17 @@ mod tests { } #[test] - fn compatibility_reports_manual_to_display_cross_adapter_copy() { - let args = parse_args(strings(&[ - "--dry-run", - "--window-adapter", - "gpu-0", - "--manual-adapter", - "gpu-1", - "--required-end-to-end", - ])) - .unwrap(); - - let plan = build_plan(args).unwrap(); - assert_eq!(plan.decode_adapter, AdapterId::new("gpu-1")); - assert_eq!(plan.render_adapter, AdapterId::new("gpu-1")); - assert_eq!(plan.display_adapter, AdapterId::new("gpu-0")); - assert_eq!( - plan.planned_memory_path, - remotedesk_client_core::MemoryPathStatus::CrossAdapterCopy - ); - assert!(!plan.is_zero_copy_candidate()); - } - - #[test] - fn required_end_to_end_is_default_and_compatibility_is_explicit() { + fn required_end_to_end_is_the_only_policy() { let required = parse_args(strings(&["--dry-run", "--window-adapter", "gpu-0"])).unwrap(); assert_eq!(required.zero_copy_policy, ZeroCopyPolicy::RequiredEndToEnd); - - let compatibility = parse_args(strings(&[ + let explicit = parse_args(strings(&[ "--dry-run", "--window-adapter", "gpu-0", "--required-end-to-end", ])) .unwrap(); - assert_eq!( - compatibility.zero_copy_policy, - ZeroCopyPolicy::RequiredEndToEnd - ); + assert_eq!(explicit.zero_copy_policy, ZeroCopyPolicy::RequiredEndToEnd); } #[test] diff --git a/client/helpers/windows-agent-viewer/src/audio.rs b/client/helpers/windows-agent-viewer/src/audio.rs index 177435f..d625323 100644 --- a/client/helpers/windows-agent-viewer/src/audio.rs +++ b/client/helpers/windows-agent-viewer/src/audio.rs @@ -42,15 +42,15 @@ impl MediaClock { if is_forward { if let Some(last_pts) = self.last_audio_pts { let delta = pts.saturating_sub(last_pts); - let expected_duration = u64::from(duration_ms.max(1)).saturating_mul(10_000); - let error = i64::try_from(delta).unwrap_or(i64::MAX) - - i64::try_from(expected_duration).unwrap_or(i64::MAX); + let expected_duration = u64::from(duration_ms.max(1)).saturating_mul(10_000); + let error = i64::try_from(delta).unwrap_or(i64::MAX) + - i64::try_from(expected_duration).unwrap_or(i64::MAX); if error.abs() <= AUDIO_SYNC_DEADBAND_100NS { self.source_rate_adjustment_ppm = self.source_rate_adjustment_ppm.saturating_mul(7) / 8; } else { - let target = (error.saturating_mul(1_000_000) - / i64::try_from(expected_duration.max(1)).unwrap_or(1)) + let target = (error.saturating_mul(1_000_000) + / i64::try_from(expected_duration.max(1)).unwrap_or(1)) .clamp( -i64::from(AUDIO_RATE_LIMIT_PPM), i64::from(AUDIO_RATE_LIMIT_PPM), @@ -595,15 +595,12 @@ mod tests { let mut clock = MediaClock::new(); clock.observe_audio(1_000_000, 20); for sequence in 1..128 { - clock.observe_audio(1_000_000 + sequence * 300_000, 30); + clock.observe_audio(1_000_000 + sequence * 300_000, 20); } assert!(clock.rate_adjustment_ppm().abs() <= AUDIO_RATE_LIMIT_PPM); let corrected = clock.rate_adjustment_ppm(); for sequence in 128..160 { - clock.observe_audio( - 1_000_000 + 127 * 300_000 + (sequence - 127) * 200_000, - 20, - ); + clock.observe_audio(1_000_000 + 127 * 300_000 + (sequence - 127) * 200_000, 20); } assert!(clock.rate_adjustment_ppm().abs() < corrected.abs()); } diff --git a/client/native-gui/Cargo.toml b/client/native-gui/Cargo.toml index b63c4f3..b24dc0f 100644 --- a/client/native-gui/Cargo.toml +++ b/client/native-gui/Cargo.toml @@ -14,6 +14,7 @@ egui = "0.31" egui-wgpu = "0.31" egui-winit = "0.31" pollster = "0.4" +remotedesk-webrtc-core = { path = "../crates/webrtc-core" } wgpu = "24" winit = "0.30" diff --git a/client/native-gui/src/main.rs b/client/native-gui/src/main.rs index 8a3ac87..02ab7bd 100644 --- a/client/native-gui/src/main.rs +++ b/client/native-gui/src/main.rs @@ -5,6 +5,7 @@ use std::time::Instant; use egui_wgpu::Renderer; use egui_winit::State; +use remotedesk_webrtc_core::{SessionLimits, WebRtcEndpoint, WebRtcRole}; use wgpu::SurfaceError; use winit::application::ApplicationHandler; use winit::dpi::PhysicalSize; @@ -20,9 +21,11 @@ struct App { config: Option, renderer: Option, egui_state: Option, + egui_context: egui::Context, started: Instant, endpoint: String, status: String, + webrtc_endpoint: Option, } impl Default for App { @@ -35,9 +38,11 @@ impl Default for App { config: None, renderer: None, egui_state: None, + egui_context: egui::Context::default(), started: Instant::now(), endpoint: String::new(), status: "Disconnected".to_owned(), + webrtc_endpoint: None, } } } @@ -67,7 +72,7 @@ impl ApplicationHandler for App { })) .expect("find a compatible GPU adapter"); let (device, queue) = - pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())) + pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default(), None)) .expect("create GPU device"); let size = window.inner_size(); let capabilities = surface.get_capabilities(&adapter); @@ -84,6 +89,7 @@ impl ApplicationHandler for App { }; surface.configure(&device, &config); self.egui_state = Some(State::new( + self.egui_context.clone(), egui::ViewportId::ROOT, event_loop, None, @@ -149,7 +155,7 @@ impl App { return; }; let raw_input = state.take_egui_input(window); - let full_output = egui::Context::default().run(raw_input, |ctx| { + let full_output = self.egui_context.run(raw_input, |ctx| { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("RemoteDesk"); ui.label("Native cross-platform client"); @@ -158,7 +164,17 @@ impl App { ui.label("Server"); ui.text_edit_singleline(&mut self.endpoint); if ui.button("Connect").clicked() { - self.status = "Connecting".to_owned(); + match WebRtcEndpoint::new( + WebRtcRole::Offerer, + SessionLimits::default(), + Instant::now(), + ) { + Ok(endpoint) => { + self.webrtc_endpoint = Some(endpoint); + self.status = "Waiting for signaling".to_owned(); + } + Err(error) => self.status = error.to_string(), + } } }); ui.label(format!("Status: {}", self.status)); @@ -183,8 +199,12 @@ impl App { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("egui"), }); - let clipped = - egui::Context::default().tessellate(full_output.shapes, full_output.pixels_per_point); + let clipped = self + .egui_context + .tessellate(full_output.shapes, full_output.pixels_per_point); + for (texture_id, delta) in &full_output.textures_delta.set { + renderer.update_texture(device, queue, *texture_id, delta); + } renderer.update_buffers( device, queue, @@ -195,17 +215,30 @@ impl App { pixels_per_point: full_output.pixels_per_point, }, ); - renderer.render( - &mut encoder, - &view, - &clipped, - &egui_wgpu::ScreenDescriptor { - size_in_pixels: [config.width, config.height], - pixels_per_point: full_output.pixels_per_point, - }, - ); + let screen = egui_wgpu::ScreenDescriptor { + size_in_pixels: [config.width, config.height], + pixels_per_point: full_output.pixels_per_point, + }; + let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("egui"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + renderer.render(&mut render_pass.forget_lifetime(), &clipped, &screen); queue.submit([encoder.finish()]); output.present(); + for texture_id in &full_output.textures_delta.free { + renderer.free_texture(texture_id); + } window.request_redraw(); } } diff --git a/client/web/index.html b/client/web/index.html deleted file mode 100644 index 9da35ba..0000000 --- a/client/web/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - RemoteDesk - - -
- - - diff --git a/client/web/package-lock.json b/client/web/package-lock.json deleted file mode 100644 index 3ba763a..0000000 --- a/client/web/package-lock.json +++ /dev/null @@ -1,1189 +0,0 @@ -{ - "name": "remotedesk-web", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "remotedesk-web", - "version": "0.1.0", - "dependencies": { - "lucide-react": "0.577.0", - "react": "19.2.6", - "react-dom": "19.2.6" - }, - "devDependencies": { - "@types/react": "19.2.14", - "@types/react-dom": "19.2.3", - "typescript": "5.9.3", - "vite": "6.4.3" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", - "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", - "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", - "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", - "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", - "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", - "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", - "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", - "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", - "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", - "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", - "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", - "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", - "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", - "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", - "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", - "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", - "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", - "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", - "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", - "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", - "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", - "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", - "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", - "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", - "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/lucide-react": { - "version": "0.577.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz", - "integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.17", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/react": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", - "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", - "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.6" - } - }, - "node_modules/rollup": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", - "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.9" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.62.2", - "@rollup/rollup-android-arm64": "4.62.2", - "@rollup/rollup-darwin-arm64": "4.62.2", - "@rollup/rollup-darwin-x64": "4.62.2", - "@rollup/rollup-freebsd-arm64": "4.62.2", - "@rollup/rollup-freebsd-x64": "4.62.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", - "@rollup/rollup-linux-arm-musleabihf": "4.62.2", - "@rollup/rollup-linux-arm64-gnu": "4.62.2", - "@rollup/rollup-linux-arm64-musl": "4.62.2", - "@rollup/rollup-linux-loong64-gnu": "4.62.2", - "@rollup/rollup-linux-loong64-musl": "4.62.2", - "@rollup/rollup-linux-ppc64-gnu": "4.62.2", - "@rollup/rollup-linux-ppc64-musl": "4.62.2", - "@rollup/rollup-linux-riscv64-gnu": "4.62.2", - "@rollup/rollup-linux-riscv64-musl": "4.62.2", - "@rollup/rollup-linux-s390x-gnu": "4.62.2", - "@rollup/rollup-linux-x64-gnu": "4.62.2", - "@rollup/rollup-linux-x64-musl": "4.62.2", - "@rollup/rollup-openbsd-x64": "4.62.2", - "@rollup/rollup-openharmony-arm64": "4.62.2", - "@rollup/rollup-win32-arm64-msvc": "4.62.2", - "@rollup/rollup-win32-ia32-msvc": "4.62.2", - "@rollup/rollup-win32-x64-gnu": "4.62.2", - "@rollup/rollup-win32-x64-msvc": "4.62.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/vite": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", - "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - } - } -} diff --git a/client/web/package.json b/client/web/package.json deleted file mode 100644 index 44c38ff..0000000 --- a/client/web/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "remotedesk-web", - "private": true, - "version": "0.1.0", - "type": "module", - "scripts": { - "dev": "vite --configLoader runner", - "control": "cargo run --manifest-path ../helpers/control-service/Cargo.toml -- --web-root dist --port 4174", - "build": "npm run lint && vite build --configLoader runner", - "build:check": "npm run lint && node scripts/build-check.mjs", - "lint": "tsc --noEmit -p tsconfig.app.json --pretty false && tsc --noEmit -p tsconfig.node.json --pretty false" - }, - "dependencies": { - "react": "19.2.6", - "react-dom": "19.2.6", - "lucide-react": "0.577.0" - }, - "devDependencies": { - "@types/react": "19.2.14", - "@types/react-dom": "19.2.3", - "typescript": "5.9.3", - "vite": "6.4.3" - } -} diff --git a/client/web/public/icon.ico b/client/web/public/icon.ico deleted file mode 100644 index bb50b8c..0000000 Binary files a/client/web/public/icon.ico and /dev/null differ diff --git a/client/web/public/remotedesk-icon.svg b/client/web/public/remotedesk-icon.svg deleted file mode 100644 index cdd6019..0000000 --- a/client/web/public/remotedesk-icon.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/client/web/scripts/build-check.mjs b/client/web/scripts/build-check.mjs deleted file mode 100644 index 04120e4..0000000 --- a/client/web/scripts/build-check.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { build } from 'vite' -import { fileURLToPath } from 'node:url' - -await build({ - root: fileURLToPath(new URL('..', import.meta.url)), - configFile: false, - build: { - write: false, - }, -}) diff --git a/client/web/src/App.tsx b/client/web/src/App.tsx deleted file mode 100644 index bfeaadf..0000000 --- a/client/web/src/App.tsx +++ /dev/null @@ -1,1970 +0,0 @@ -import { FormEvent, useEffect, useMemo, useRef, useState } from 'react' -import { - CirclePlus, - Clock3, - Cpu, - Download, - ExternalLink, - KeyRound, - Keyboard, - Monitor, - Moon, - Network, - Play, - RefreshCw, - Search, - Settings, - ShieldCheck, - SquareTerminal, - Star, - Sun, - Trash2, - Upload, - X, -} from 'lucide-react' -import { - ControlApiError, - checkControlUpdate, - configureRdpCredential, - saveRdpCredential, - deleteLinuxAgentIdentity, - deleteRdpCredential, - getControlCapabilities, - getControlHosts, - getControlSettings, - getLinuxAgentIdentityStatus, - getLinuxDesktopSession, - getNativeRdpSession, - getWindowsAgentSession, - getRdpCredentialStatus, - launchRdp, - launchLinuxTerminal, - launchLinuxDesktop, - launchWindowsAgentDesktop, - launchLinuxFileTransfer, - installControlUpdate, - negotiateRdp, - openCredentialManager, - probeLinuxAgent, - probeWindowsAgent, - probeRdp, - updateControlSettings, - updateControlHosts, - type ControlCapabilities, - type ControlUpdateCheck, - type RdpClientMode, -} from './backend' -import { - createCredentialRef, - isStoredHost, - linuxProbeDetail, - normalizeStoredHost, - probeResultDetail, - protocolProbeDetail, - rdpResolution, - statusLabel, - maxRdpDimension, - minRdpDimension, - parseTags, - type DisplayMode, - type Host, - type HostKind, - type HostStatus, - type NetworkPolicy, -} from './host-model' - -type Filter = 'all' | 'windows' | 'linux' | 'favorites' | 'recent' -type Theme = 'dark' | 'light' -type ControlServiceStatus = 'checking' | 'ready' | 'offline' - -const themeStorageKey = 'remotedesk-theme' - -function initialTheme(): Theme { - try { - const stored = window.localStorage.getItem(themeStorageKey) - if (stored === 'dark' || stored === 'light') return stored - } catch { - // Storage can be unavailable in hardened WebView profiles. - } - return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark' -} - - -function linuxClipboardSessionStatus(host: Host): string { - if (host.nativeSessionState === 'connected') { - if (host.sessionClipboardRead && host.sessionClipboardWrite) return '双向已启用' - if (host.sessionClipboardRead) return '仅读取被控端' - if (host.sessionClipboardWrite) return '仅写入被控端' - return 'Agent 未授权' - } - if (host.nativeSessionState === 'connecting' || host.nativeSessionState === 'reconnecting') { - return '等待会话协商' - } - if (host.nativeSessionState === 'failed' || host.nativeSessionState === 'terminated') { - return '无活动会话' - } - return host.clipboardRead || host.clipboardWrite ? '未建立会话' : '已关闭' -} - -type GlobalSettings = { - clientGpu: 'display' | 'manual' - agentGpu: 'capture' | 'manual' - strictZeroCopy: boolean - edgeApiUrl: string - updateManifestUrl: string - updatePublicKey: string - autoCheckUpdates: boolean -} - -const initialSettings: GlobalSettings = { - clientGpu: 'display', - agentGpu: 'capture', - strictZeroCopy: true, - edgeApiUrl: '', - updateManifestUrl: '', - updatePublicKey: '', - autoCheckUpdates: true, -} - -const demoMode = new URLSearchParams(window.location.search).get('demo') === '1' -const hostStorageKey = 'remotedesk-hosts-v1' -const clockFormatter = new Intl.DateTimeFormat('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false, -}) -const metricTimeFormatter = new Intl.DateTimeFormat('zh-CN', { - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false, -}) - -function formatLatency(value?: number) { - return value === undefined ? '暂无数据' : `${value} ms` -} - -const demoCapabilities: ControlCapabilities = { - version: 'demo', - platform: 'windows', - application: { - desktop_shell: true, - shell_kind: 'tauri-2', - }, - rdp: { - probe: true, - protocol_probe: true, - external_launch: true, - native_launch: true, - native_diagnostics: true, - native_multimon: true, - session_network_metrics: true, - session_decode_metrics: true, - region_pixel_conversion: true, - secure_named_pipe: true, - secure_pipe_config_ack: true, - secure_pipe_handshake_timeout: true, - job_object_lifecycle: true, - certificate_pinning: false, - automatic_reconnect: true, - resize_confirmation: true, - clipboard: true, - audio_playback: true, - native_renderer: 'software_framebuffer', - d3d11_device_available: true, - d3d11_dirty_rect_upload: true, - credential_manager: true, - credential_store: true, - }, - linux: { - agent_session: true, - agent_probe: true, - terminal_session: true, - desktop_session: true, - x11_desktop: true, - relative_pointer: true, - adaptive_frame_pacing: true, - adaptive_compression: true, - desktop_session_resume: true, - desktop_session_crash_resume: true, - direct_webrtc_signaling: true, - opus_audio: true, - clipboard_read: true, - clipboard_write: true, - wayland_desktop: false, - native_video: true, - native_video_decoder_probe: true, - native_video_surface_presenter: true, - file_transfer: true, - agent_identity_store: true, - }, - edge: { - relay: true, - signed_intent: true, - linux_session_relay: true, - linux_known_device_rendezvous: true, - remote_pairing: true, - }, - update: { - configured: true, - check: true, - install: true, - }, -} - -const demoUpdate: ControlUpdateCheck = { - current_version: '0.2.3', - status: 'available', - release: { - version: '0.3.0', - channel: 'stable', - published_at: '2026-08-10T12:00:00Z', - notes: '稳定性改进和安全更新', - size_bytes: 24_780_800, - }, -} - -const demoHosts: Host[] = [ - { - id: 1, - name: 'dev-win', - address: '10.20.0.15', - kind: 'windows_rdp', - mode: 'RDP', - status: 'ready', - favorite: true, - lastUsed: '10:42', - latency: 18, - encodeLatency: 5, - decodeLatency: 3, - frameUploadMode: 'dirty_rect', - frameUploadPixels: 86_400, - frameConversionPixels: 43_200, - qualityUpdatedAt: Date.now(), - path: 'Direct', - networkPolicy: 'Smart', - resolution: '1920 x 1080', - customWidth: '1600', - customHeight: '900', - gpu: 'Display adapter', - memoryPath: 'cpu_upload', - recent: true, - strictZeroCopy: false, - displayMode: 'primary', - displays: [ - { id: 'display-1', name: 'DISPLAY1', resolution: '1920 x 1080', primary: true }, - { id: 'display-2', name: 'DISPLAY2', resolution: '1920 x 1080', primary: false }, - ], - selectedDisplayIds: ['display-1'], - username: '', - credentialState: 'missing', - agentIdentityState: 'missing', - credentialRef: 'RemoteDesk/RDP/demo-dev-win', - rdpClient: 'system', - nativeFullscreen: false, - captureInput: false, - redirectClipboard: true, - clipboardRead: true, - clipboardWrite: true, - }, - { - id: 2, - name: 'linux-workstation', - address: 'agent:8ef2c1', - kind: 'linux_agent', - mode: 'Wayland', - status: 'ready', - favorite: true, - lastUsed: '09:18', - latency: 26, - encodeLatency: 9, - decodeLatency: 6, - qualityUpdatedAt: Date.now(), - path: 'Dual Edge', - networkPolicy: 'Smart', - resolution: '2560 x 1440', - customWidth: '1600', - customHeight: '900', - gpu: 'NVIDIA RTX 4070', - memoryPath: 'zero_copy', - recent: true, - strictZeroCopy: true, - displayMode: 'all', - displays: [ - { id: 'dp-1', name: 'DP-1', resolution: '2560 x 1440', primary: true }, - { id: 'hdmi-a-1', name: 'HDMI-A-1', resolution: '1920 x 1080', primary: false }, - ], - selectedDisplayIds: ['dp-1', 'hdmi-a-1'], - username: '', - credentialState: 'missing', - agentIdentityState: 'ready', - credentialRef: 'RemoteDesk/RDP/demo-linux-workstation', - rdpClient: 'system', - nativeFullscreen: false, - captureInput: false, - redirectClipboard: true, - clipboardRead: true, - clipboardWrite: true, - }, - { - id: 3, - name: 'build-node', - address: 'agent:5a17bd', - kind: 'linux_agent', - mode: 'Terminal', - status: 'ready', - favorite: false, - lastUsed: 'Yesterday', - latency: 31, - qualityUpdatedAt: Date.now(), - path: 'Single Edge', - networkPolicy: 'Smart', - resolution: 'Terminal', - customWidth: '1600', - customHeight: '900', - gpu: 'Not used', - memoryPath: 'Not applicable', - recent: true, - strictZeroCopy: false, - displayMode: 'primary', - displays: [], - selectedDisplayIds: [], - username: '', - credentialState: 'missing', - agentIdentityState: 'ready', - credentialRef: 'RemoteDesk/RDP/demo-build-node', - rdpClient: 'system', - nativeFullscreen: false, - captureInput: false, - redirectClipboard: true, - clipboardRead: true, - clipboardWrite: true, - }, - { - id: 4, - name: 'game-win', - address: '10.20.0.31', - kind: 'windows_rdp', - mode: 'RDP', - status: 'offline', - favorite: false, - lastUsed: 'Aug 7', - path: 'Direct', - networkPolicy: 'Smart', - resolution: '3840 x 2160', - customWidth: '1600', - customHeight: '900', - gpu: 'AMD Radeon 780M', - memoryPath: 'Unknown', - recent: false, - strictZeroCopy: false, - displayMode: 'primary', - displays: [ - { id: 'display-1', name: 'DISPLAY1', resolution: '3840 x 2160', primary: true }, - ], - selectedDisplayIds: ['display-1'], - username: '', - credentialState: 'missing', - agentIdentityState: 'missing', - credentialRef: 'RemoteDesk/RDP/demo-game-win', - rdpClient: 'system', - nativeFullscreen: false, - captureInput: false, - redirectClipboard: true, - clipboardRead: true, - clipboardWrite: true, - }, -] - -function loadStoredHosts(): Host[] { - try { - const serialized = window.localStorage.getItem(hostStorageKey) - if (!serialized) return [] - const parsed = JSON.parse(serialized) as unknown - if (!Array.isArray(parsed)) return [] - return parsed.filter(isStoredHost).map(normalizeStoredHost) - } catch { - return [] - } -} - -const initialHosts = demoMode ? demoHosts : [] - -const filters: Array<{ id: Filter; label: string }> = [ - { id: 'all', label: '全部' }, - { id: 'windows', label: 'Windows' }, - { id: 'linux', label: 'Linux' }, - { id: 'favorites', label: '收藏' }, - { id: 'recent', label: '最近' }, -] - -function nativeSessionErrorLabel(code?: string | null) { - switch (code) { - case 'timeout': return '本地安全管道超时,请确认客户端与 helper 为同一版本' - case 'peer_pid_mismatch': return '本地 helper 进程身份不匹配' - case 'peer_creation_mismatch': return '本地 helper 进程已被替换或重启' - case 'peer_binary_mismatch': return '本地 helper 文件与启动版本不一致,请重新安装客户端' - case 'ack_failed': return 'helper 未确认连接配置' - case 'payload_invalid': return '连接配置格式不兼容,请重新安装客户端' - case 'pipe_io_failed': return '本地安全管道读写失败' - case 'handshake_failed': return '本地安全管道握手失败' - case 'resize_reconnect_timeout': return '远端未在 20 秒内返回新尺寸画面' - default: return code ?? '未知错误' - } -} - -function normalizedAgentPublicKey(value?: string) { - const trimmed = value?.trim() ?? '' - return /^[A-Za-z0-9+/]{43}$/.test(trimmed) ? trimmed : null -} - -function errorMessage(error: unknown) { - return error instanceof Error ? error.message : '未知错误' -} - -function linuxUserError(value: string): string | null { - const user = value.trim() - if (!user) return '请先配置 Linux 用户' - if (user.length > 32 || !/^[A-Za-z0-9._-]+$/.test(user)) { - return 'Linux 用户名只能包含字母、数字、点、下划线或连字符' - } - return null -} - -function App() { - const [hosts, setHosts] = useState(initialHosts) - const [filter, setFilter] = useState('all') - const [selectedTag, setSelectedTag] = useState(null) - const [query, setQuery] = useState('') - const [selectedId, setSelectedId] = useState(initialHosts[0]?.id ?? null) - const [addOpen, setAddOpen] = useState(false) - const [addHostKind, setAddHostKind] = useState('windows_rdp') - const [settingsOpen, setSettingsOpen] = useState(false) - const [refreshing, setRefreshing] = useState(false) - const [controlServiceStatus, setControlServiceStatus] = useState(demoMode ? 'ready' : 'checking') - const [capabilities, setCapabilities] = useState(demoMode ? demoCapabilities : null) - const [settings, setSettings] = useState(initialSettings) - const [settingsDraft, setSettingsDraft] = useState(initialSettings) - const [settingsSaving, setSettingsSaving] = useState(false) - const [settingsError, setSettingsError] = useState(null) - const [fileDirection, setFileDirection] = useState<'upload' | 'download'>('upload') - const [fileLocalPath, setFileLocalPath] = useState('') - const [fileRemotePath, setFileRemotePath] = useState('') - const [fileTransferBusy, setFileTransferBusy] = useState(false) - const [settingsLoaded, setSettingsLoaded] = useState(demoMode) - const [updateResult, setUpdateResult] = useState(demoMode ? demoUpdate : null) - const [updateChecking, setUpdateChecking] = useState(false) - const [updateInstalling, setUpdateInstalling] = useState(false) - const [updateError, setUpdateError] = useState(null) - const [theme, setTheme] = useState(initialTheme) - const [now, setNow] = useState(() => new Date()) - const searchInput = useRef(null) - const probeGenerations = useRef(new Map()) - const nativeSessions = useRef(new Map()) - const linuxDesktopSessions = useRef(new Map()) - const windowsAgentSessions = useRef(new Map()) - const updateAutoCheckStarted = useRef(false) - const hostsLoaded = useRef(demoMode) - - useEffect(() => { - document.documentElement.dataset.theme = theme - try { - window.localStorage.setItem(themeStorageKey, theme) - } catch { - // The in-memory choice still applies for this session. - } - }, [theme]) - - useEffect(() => { - if (demoMode || !hostsLoaded.current) return - const persistHosts = async () => { - const profiles = hosts.map((host) => ({ - ...host, - username: host.kind === 'windows_rdp' ? '' : host.username, - latency: undefined, - sessionNetworkLatency: undefined, - networkBaseLatency: undefined, - bandwidthKbps: undefined, - nativeMonitorCount: undefined, - captureLatency: undefined, - encodeLatency: undefined, - frameProcessingLatency: undefined, - decodeLatency: undefined, - sessionClipboardRead: undefined, - sessionClipboardWrite: undefined, - qualityUpdatedAt: undefined, - })) - try { - await updateControlHosts(profiles) - window.localStorage.removeItem(hostStorageKey) - } catch { - // Keep a local fallback while the control service is restarting. - try { window.localStorage.setItem(hostStorageKey, JSON.stringify(profiles)) } catch { /* best effort */ } - } - } - void persistHosts() - }, [hosts]) - - useEffect(() => { - const timer = window.setInterval(() => setNow(new Date()), 1_000) - return () => window.clearInterval(timer) - }, []) - - useEffect(() => { - if (demoMode) return - const loadHosts = async () => { - try { - const stored = await getControlHosts() - const loaded = Array.isArray(stored) ? stored.filter(isStoredHost).map(normalizeStoredHost) : [] - if (loaded.length > 0 || window.localStorage.getItem(hostStorageKey) === null) { - setHosts(loaded) - } else { - const legacy = loadStoredHosts() - setHosts(legacy) - if (legacy.length > 0) { - await updateControlHosts(legacy) - window.localStorage.removeItem(hostStorageKey) - } - } - } catch { - setHosts(loadStoredHosts()) - } finally { - hostsLoaded.current = true - void refresh() - } - } - void loadHosts() - }, []) - - useEffect(() => { - if ( - demoMode || - !settingsLoaded || - updateAutoCheckStarted.current || - !settings.autoCheckUpdates || - !capabilities?.update?.configured - ) return - updateAutoCheckStarted.current = true - void checkForUpdates() - }, [capabilities?.update?.configured, settings.autoCheckUpdates, settingsLoaded]) - - useEffect(() => { - function focusSearch(event: KeyboardEvent) { - if (event.key === 'Escape') { - setAddOpen(false) - setSettingsOpen(false) - return - } - if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'k') { - event.preventDefault() - searchInput.current?.focus() - } - } - - window.addEventListener('keydown', focusSearch) - return () => window.removeEventListener('keydown', focusSearch) - }, []) - - const visibleHosts = useMemo(() => { - const normalized = query.trim().toLowerCase() - return hosts.filter((host) => { - const matchesQuery = - !normalized || - host.name.toLowerCase().includes(normalized) || - host.address.toLowerCase().includes(normalized) - const matchesFilter = - filter === 'all' || - (filter === 'windows' && (host.kind === 'windows_rdp' || host.kind === 'windows_agent')) || - (filter === 'linux' && host.kind === 'linux_agent') || - (filter === 'favorites' && host.favorite) || - (filter === 'recent' && host.recent) - const matchesTag = selectedTag === null || (host.tags ?? []).includes(selectedTag) - return matchesQuery && matchesFilter && matchesTag - }) - }, [filter, hosts, query, selectedTag]) - - const availableTags = useMemo(() => { - const counts = new Map() - for (const host of hosts) { - for (const tag of host.tags ?? []) counts.set(tag, (counts.get(tag) ?? 0) + 1) - } - return [...counts.entries()].sort((a, b) => a[0].localeCompare(b[0], 'zh-CN')) - }, [hosts]) - - const selected = hosts.find((host) => host.id === selectedId) ?? hosts[0] - - function rdpClientName(mode: RdpClientMode) { - if (mode === 'native') return '客户端内' - return 'Windows mstsc' - } - - function connectTitle(host: Host) { - if (host.status === 'connecting') return '连接正在启动,请等待当前连接完成' - return host.kind === 'windows_rdp' - ? `启动${rdpClientName(host.rdpClient)}` - : '连接' - } - - function hostKindLabel(host: Host) { - if (host.kind === 'windows_agent') return 'Windows Agent' - if (host.kind === 'linux_agent') return 'Linux Agent' - return 'Windows RDP' - } - - function connectButtonLabel(host: Host) { - return host.status === 'connecting' ? '正在启动' : '连接' - } - - function chooseRdpClient(host: Host, rdpClient: RdpClientMode) { - updateHost(host.id, { - rdpClient, - ...(rdpClient === 'native' && host.displayMode === 'custom' && host.selectedDisplayIds.length < 2 - ? { displayMode: 'primary' as DisplayMode, selectedDisplayIds: host.displays.filter((display) => display.primary).map((display) => display.id) } - : {}), - probeDetail: rdpClient === 'native' - ? 'IronRDP 原生窗口;凭据从 Windows 凭据管理器读取' - : '使用 Windows 系统远程桌面与系统认证窗口', - }) - if (rdpClient === 'native') void refreshCredentialStatus(host, false) - } - - function updateHost(id: number, patch: Partial) { - setHosts((current) => current.map((host) => (host.id === id ? { ...host, ...patch } : host))) - } - - function updateHostIdentity(host: Host, field: 'name' | 'address', value: string) { - const limit = field === 'name' ? 128 : 253 - const next = value.slice(0, limit) - if (field === 'address' && /\s/.test(next)) return - updateHost(host.id, { - [field]: next, - ...(field === 'address' ? { status: 'unknown' as HostStatus, probeDetail: undefined } : {}), - }) - } - - function updateHostTags(host: Host, value: string) { - updateHost(host.id, { tags: parseTags(value) }) - } - - function updateMode(host: Host, mode: string) { - const resolution = mode === 'Terminal' - ? 'Terminal' - : host.resolution === 'Terminal' - ? 'Follow window' - : host.resolution - updateHost(host.id, { mode, resolution }) - } - - function updateDisplayMode(host: Host, displayMode: DisplayMode) { - const primary = host.displays.find((display) => display.primary) ?? host.displays[0] - const selectedDisplayIds = displayMode === 'all' - ? host.displays.map((display) => display.id) - : displayMode === 'primary' - ? primary ? [primary.id] : [] - : host.selectedDisplayIds.length > 0 - ? host.selectedDisplayIds - : primary ? [primary.id] : [] - updateHost(host.id, { displayMode, selectedDisplayIds }) - } - - function toggleDisplay(host: Host, displayId: string) { - const selected = new Set(host.selectedDisplayIds) - if (selected.has(displayId)) { - if (selected.size === 1) return - selected.delete(displayId) - } else { - selected.add(displayId) - } - updateHost(host.id, { selectedDisplayIds: [...selected] }) - } - - function activeDisplayCount(host: Host) { - if (host.mode === 'Terminal') return 0 - if (host.displayMode === 'all') return host.displays.length - if (host.displayMode === 'primary') return Math.min(host.displays.length, 1) - return host.displays.filter((display) => host.selectedDisplayIds.includes(display.id)).length - } - - function openSettings() { - setSettingsDraft(settings) - setSettingsError(null) - setSettingsOpen(true) - } - - async function checkForUpdates() { - if (demoMode) return - setUpdateChecking(true) - setUpdateError(null) - try { - setUpdateResult(await checkControlUpdate()) - } catch (error) { - setUpdateResult(null) - setUpdateError(errorMessage(error)) - } finally { - setUpdateChecking(false) - } - } - - async function installAvailableUpdate() { - const version = updateResult?.release?.version - if (!version || updateResult?.status !== 'available') return - if (!window.confirm(`下载并安装 RemoteDesk ${version}?当前控制端将自动关闭并在升级后重新启动。`)) return - setUpdateInstalling(true) - setUpdateError(null) - try { - await installControlUpdate(version) - } catch (error) { - setUpdateInstalling(false) - setUpdateError(errorMessage(error)) - } - } - - async function saveSettings() { - setSettingsSaving(true) - setSettingsError(null) - try { - const persisted = await updateControlSettings({ - edge_api_url: settingsDraft.edgeApiUrl.trim() || null, - update_manifest_url: settingsDraft.updateManifestUrl.trim() || null, - update_public_key: settingsDraft.updatePublicKey.trim() || null, - auto_check_updates: settingsDraft.autoCheckUpdates, - }) - const updated = { - ...settingsDraft, - edgeApiUrl: persisted.edge_api_url ?? '', - updateManifestUrl: persisted.update_manifest_url ?? '', - updatePublicKey: persisted.update_public_key ?? '', - autoCheckUpdates: persisted.auto_check_updates ?? true, - } - setSettings(updated) - setSettingsDraft(updated) - const detectedCapabilities = await getControlCapabilities() - setCapabilities(detectedCapabilities) - setSettingsOpen(false) - setUpdateResult(null) - updateAutoCheckStarted.current = false - } catch (error) { - setSettingsError(errorMessage(error)) - } finally { - setSettingsSaving(false) - } - } - - async function deleteHost(host: Host) { - if (!window.confirm(`删除主机“${host.name}”?`)) return - const fingerprint = '0'.repeat(64) - if (host.kind === 'linux_agent' - && fingerprint !== null - && host.agentIdentityState === 'ready' - && window.confirm('同时从 Windows 凭据管理器忘记该 Linux Agent 身份?选择“取消”将只删除主机配置。')) { - try { - await deleteLinuxAgentIdentity(fingerprint) - } catch (error) { - updateHost(host.id, { probeDetail: `无法忘记 Linux Agent 身份:${errorMessage(error)}` }) - return - } - } - const remaining = hosts.filter((item) => item.id !== host.id) - probeGenerations.current.delete(host.id) - nativeSessions.current.delete(host.id) - linuxDesktopSessions.current.delete(host.id) - windowsAgentSessions.current.delete(host.id) - setHosts(remaining) - if (selectedId === host.id) setSelectedId(remaining[0]?.id ?? null) - } - - async function probeHost( - host: Host, - protocolProbe = capabilities?.rdp.protocol_probe ?? false, - ) { - if (host.kind !== 'windows_rdp') return - const generation = (probeGenerations.current.get(host.id) ?? 0) + 1 - probeGenerations.current.set(host.id, generation) - updateHost(host.id, { status: 'probing', probeDetail: '正在检测 RDP 端口' }) - - try { - const result = await probeRdp(host.address) - if (probeGenerations.current.get(host.id) !== generation) return - if (result.reachable && protocolProbe) { - updateHost(host.id, { - status: 'probing', - latency: result.latency_ms ?? undefined, - qualityUpdatedAt: result.latency_ms === undefined ? undefined : Date.now(), - probeDetail: `TCP ${result.port} 可达,正在验证 RDP 协议`, - }) - try { - const protocolResult = await negotiateRdp(host.address) - if (probeGenerations.current.get(host.id) !== generation) return - const latency = protocolResult.total_latency_ms ?? result.latency_ms ?? undefined - updateHost(host.id, { - status: protocolResult.ok ? 'ready' : 'offline', - latency, - qualityUpdatedAt: latency === undefined ? undefined : Date.now(), - probeDetail: protocolProbeDetail(protocolResult), - }) - } catch (error) { - if (probeGenerations.current.get(host.id) !== generation) return - updateHost(host.id, { - status: 'ready', - latency: result.latency_ms ?? undefined, - qualityUpdatedAt: result.latency_ms === undefined ? undefined : Date.now(), - probeDetail: `TCP ${result.port} 可达;协议探针暂不可用:${errorMessage(error)}`, - }) - } - return - } - updateHost(host.id, { - status: result.reachable ? 'ready' : 'offline', - latency: result.latency_ms ?? undefined, - qualityUpdatedAt: result.latency_ms === undefined ? undefined : Date.now(), - probeDetail: probeResultDetail(result), - }) - } catch (error) { - if (probeGenerations.current.get(host.id) !== generation) return - const invalidTarget = error instanceof ControlApiError && error.status >= 400 && error.status < 500 - if (!invalidTarget) setControlServiceStatus('offline') - updateHost(host.id, { - status: 'unknown', - latency: undefined, - qualityUpdatedAt: undefined, - probeDetail: invalidTarget - ? `地址无效:${errorMessage(error)}` - : `控制服务不可用:${errorMessage(error)}`, - }) - } - } - - async function probeLinuxHost(host: Host) { - if (host.kind !== 'linux_agent') return - const fingerprint = '0'.repeat(64) - const generation = (probeGenerations.current.get(host.id) ?? 0) + 1 - probeGenerations.current.set(host.id, generation) - updateHost(host.id, { status: 'probing', probeDetail: '正在验证 Linux Agent TLS/WSS' }) - try { - const result = await probeLinuxAgent(host.address, fingerprint) - if (probeGenerations.current.get(host.id) !== generation) return - updateHost(host.id, { - status: result.ok ? 'ready' : 'offline', - latency: result.latency_ms, - qualityUpdatedAt: Date.now(), - probeDetail: linuxProbeDetail(result), - }) - } catch (error) { - if (probeGenerations.current.get(host.id) !== generation) return - const invalidRequest = error instanceof ControlApiError - && error.status >= 400 - && error.status < 500 - updateHost(host.id, { - status: invalidRequest ? 'unknown' : 'offline', - latency: undefined, - qualityUpdatedAt: undefined, - probeDetail: invalidRequest - ? `Linux Agent 配置无效:${errorMessage(error)}` - : `Linux Agent 不可用或 TLS/协议握手失败:${errorMessage(error)}`, - }) - } - } - - async function probeWindowsAgentHost(host: Host) { - if (host.kind !== 'windows_agent') return - if (windowsAgentSessions.current.has(host.id)) return - updateHost(host.id, { status: 'probing', probeDetail: '正在连接 Windows Agent' }) - try { - const result = await probeWindowsAgent(host.address) - updateHost(host.id, { - status: result.ok ? 'ready' : 'offline', - latency: result.latency_ms, - qualityUpdatedAt: Date.now(), - captureLatency: result.capture_latency_ms ?? undefined, - encodeLatency: result.encode_latency_ms ?? undefined, - frameProcessingLatency: result.frame_processing_latency_ms ?? undefined, - agentApplications: result.applications, - probeDetail: result.desktop_session - ? `Windows Agent 在线(当前:${result.capture_backend} / ${result.input_backend})` - : result.software_fallback_available && result.software_fallback_backend - ? `Windows Agent 在线(首选:${result.preferred_capture_backend};降级:${result.software_fallback_backend} 可用),可启动桌面` - : `Windows Agent 在线(首选:${result.preferred_capture_backend}),可启动桌面`, - }) - } catch (error) { - updateHost(host.id, { status: 'offline', latency: undefined, probeDetail: `Windows Agent 不可用:${errorMessage(error)}` }) - } - } - - async function connect(host: Host) { - if (host.status === 'connecting') return - setSelectedId(host.id) - if (host.kind === 'windows_agent') { - if (host.mode === 'Application' && !host.agentApplication) { - updateHost(host.id, { probeDetail: '请先选择要启动的应用' }) - return - } - updateHost(host.id, { status: 'connecting', probeDetail: host.mode === 'Application' ? '正在启动 Windows Agent 应用窗口' : '正在启动 Windows Agent 原生桌面窗口' }) - try { - const launch = await launchWindowsAgentDesktop({ address: host.address, frames_per_second: 10, fullscreen: host.nativeFullscreen, mode: host.mode === 'Application' ? 'application' : 'desktop', application: host.agentApplication }) - windowsAgentSessions.current.set(host.id, launch.session_id) - updateHost(host.id, { status: 'ready', lastUsed: 'Now', recent: true, nativeSessionState: 'connecting', probeDetail: 'Windows Agent RDWF 桌面窗口已启动,正在接收画面' }) - void monitorWindowsAgentSession(host.id, launch.session_id) - } catch (error) { - updateHost(host.id, { status: 'offline', probeDetail: `Windows Agent ${host.mode === 'Application' ? '应用' : '桌面'}启动失败:${errorMessage(error)}` }) - } - return - } - if (host.kind !== 'windows_rdp') { - const userError = linuxUserError(host.username) - if (userError) { - updateHost(host.id, { probeDetail: userError }) - return - } - const linuxUser = host.username.trim() - const fingerprint = '0'.repeat(64) - if (host.mode !== 'Terminal') { - const resolution = rdpResolution(host) ?? { mode: 'follow_window' as const } - const width = resolution.mode === 'fixed' ? resolution.width : 1920 - const height = resolution.mode === 'fixed' ? resolution.height : 1080 - updateHost(host.id, { - nativeSessionState: undefined, - sessionClipboardRead: undefined, - sessionClipboardWrite: undefined, - }) - try { - const launch = await launchLinuxDesktop({ - address: host.address, - user: linuxUser, - certificate_sha256: fingerprint, - width, - height, - frames_per_second: 15, - fullscreen: host.nativeFullscreen, - follow_window: resolution.mode === 'follow_window', - capture_input: host.captureInput, - clipboard_read: host.clipboardRead && capabilities?.linux.clipboard_read === true, - clipboard_write: host.clipboardWrite && capabilities?.linux.clipboard_write === true, - edge_api_url: settings.edgeApiUrl.trim() || null, - }) - updateHost(host.id, { - status: 'ready', - lastUsed: 'Now', - recent: true, - probeDetail: settings.edgeApiUrl.trim() - ? 'Edge 授权通过,Linux X11 桌面窗口已启动' - : 'Linux X11 桌面已在独立本地窗口中启动', - }) - linuxDesktopSessions.current.set(host.id, launch.session_id) - void monitorLinuxDesktopSession(host.id, launch.session_id) - } catch (error) { - updateHost(host.id, { probeDetail: `Linux 桌面启动失败:${errorMessage(error)}` }) - } - return - } - try { - await launchLinuxTerminal({ - address: host.address, - user: linuxUser, - certificate_sha256: fingerprint, - pair: false, - edge_api_url: settings.edgeApiUrl.trim() || null, - }) - updateHost(host.id, { - status: 'ready', - lastUsed: 'Now', - recent: true, - probeDetail: settings.edgeApiUrl.trim() - ? 'Edge 签名授权通过,Linux 终端中继已启动' - : 'Linux 终端已在独立本地窗口中启动', - }) - } catch (error) { - updateHost(host.id, { probeDetail: `Linux 终端启动失败:${errorMessage(error)}` }) - } - return - } - probeGenerations.current.set(host.id, (probeGenerations.current.get(host.id) ?? 0) + 1) - const statusBeforeLaunch = host.status === 'probing' ? 'unknown' : host.status - updateHost(host.id, { status: 'connecting', probeDetail: `正在启动${rdpClientName(host.rdpClient)}` }) - try { - const resolution = rdpResolution(host) ?? { mode: 'follow_window' as const } - const launch = await launchRdp({ - address: host.address, - username: host.rdpClient === 'system' ? host.username : '', - resolution, - use_multimon: host.displayMode === 'all', - monitor_indices: host.displayMode === 'custom' - ? host.selectedDisplayIds - .map((id) => Number.parseInt(id.replace(/\D/g, ''), 10) - 1) - .filter((index) => Number.isInteger(index) && index >= 0 && index <= 255) - : [], - client: host.rdpClient, - fullscreen: host.rdpClient === 'native' && host.nativeFullscreen, - certificate_sha256: null, - credential_ref: host.rdpClient === 'native' ? host.credentialRef : null, - redirect_clipboard: host.redirectClipboard, - }) - updateHost(host.id, { - status: statusBeforeLaunch, - lastUsed: 'Now', - recent: true, - sessionNetworkLatency: undefined, - networkBaseLatency: undefined, - bandwidthKbps: undefined, - nativeMonitorCount: undefined, - probeDetail: host.rdpClient === 'native' - ? 'IronRDP 已使用 Windows 凭据管理器中的凭据启动' - : 'Windows 远程桌面已打开,请在系统窗口完成认证', - }) - if (host.rdpClient === 'native' && launch.session_id) { - nativeSessions.current.set(host.id, launch.session_id) - void monitorNativeSession(host.id, launch.session_id) - } - } catch (error) { - updateHost(host.id, { - status: statusBeforeLaunch, - probeDetail: `启动失败:${errorMessage(error)}`, - }) - } - } - - async function monitorWindowsAgentSession(hostId: number, sessionId: string) { - let transientFailures = 0 - while (windowsAgentSessions.current.get(hostId) === sessionId) { - try { - const diagnostics = await getWindowsAgentSession(sessionId) - transientFailures = 0 - const metric = (value: number | null) => value === null ? undefined : Math.round(value * 10) / 10 - const detail = diagnostics.state === 'connecting' - ? 'Windows Agent 正在连接并等待首帧' - : diagnostics.state === 'connected' - ? `Windows Agent 已连接,${diagnostics.desktop_width} × ${diagnostics.desktop_height},已接收 ${diagnostics.frame_count} 帧` - : diagnostics.state === 'failed' - ? `Windows Agent 会话失败:${diagnostics.error_code ?? 'unknown'}` - : 'Windows Agent 会话已结束' - updateHost(hostId, { - nativeSessionState: diagnostics.state, - captureLatency: metric(diagnostics.capture_latency_ms), - encodeLatency: metric(diagnostics.encode_latency_ms), - frameProcessingLatency: metric(diagnostics.frame_processing_latency_ms), - decodeLatency: metric(diagnostics.decode_latency_ms), - presentationLatency: metric(diagnostics.presentation_latency_ms), - frameRate: metric(diagnostics.frames_per_second), - nativeRenderer: diagnostics.renderer, - qualityUpdatedAt: diagnostics.updated_at_unix_ms, - probeDetail: detail, - }) - if (diagnostics.state === 'failed' || diagnostics.state === 'terminated') { windowsAgentSessions.current.delete(hostId); return } - } catch (error) { - transientFailures += 1 - if (transientFailures >= 8) { windowsAgentSessions.current.delete(hostId); updateHost(hostId, { probeDetail: `无法读取 Windows Agent 会话状态:${errorMessage(error)}` }); return } - } - await new Promise((resolve) => window.setTimeout(resolve, 750)) - } - } - - async function pairLinuxTerminal(host: Host) { - const fingerprint = '0'.repeat(64) - const userError = linuxUserError(host.username) - if (userError) { - updateHost(host.id, { probeDetail: userError }) - return - } - try { - await launchLinuxTerminal({ - address: host.address, - user: host.username.trim(), - certificate_sha256: fingerprint, - pair: true, - agent_public_key: settings.edgeApiUrl.trim() - ? normalizedAgentPublicKey(host.agentPublicKey) - : null, - edge_api_url: settings.edgeApiUrl.trim() || null, - }) - updateHost(host.id, { agentIdentityState: 'unknown' }) - updateHost(host.id, { - probeDetail: settings.edgeApiUrl.trim() - ? normalizedAgentPublicKey(host.agentPublicKey) !== null - ? 'Edge 首次配对 relay 正在建立,请在新窗口输入一次性配对码' - : '未配置 Agent 设备公钥,请在新窗口完成一次性直连配对' - : '请在新打开的本地窗口输入一次性配对码', - }) - if (capabilities?.linux.agent_identity_store) { - for (let attempt = 0; attempt < 60; attempt += 1) { - await new Promise((resolve) => window.setTimeout(resolve, 2_000)) - const status = await getLinuxAgentIdentityStatus(fingerprint) - if (status === 'ready') { - updateHost(host.id, { - agentIdentityState: 'ready', - probeDetail: 'Linux Agent 已配对,证书绑定身份已保存', - }) - return - } - } - } - } catch (error) { - updateHost(host.id, { probeDetail: `无法启动 Linux Agent 配对:${errorMessage(error)}` }) - } - } - - async function startLinuxFileTransfer(host: Host) { - const fingerprint = '0'.repeat(64) - const userError = linuxUserError(host.username) - if (userError || !fileLocalPath.trim() || !fileRemotePath.trim()) { - updateHost(host.id, { probeDetail: userError ?? '文件传输需要 Linux 用户、本地路径和远端相对路径' }) - return - } - setFileTransferBusy(true) - try { - await launchLinuxFileTransfer({ address: host.address, user: host.username.trim(), certificate_sha256: fingerprint, direction: fileDirection, local_path: fileLocalPath.trim(), remote_path: fileRemotePath.trim(), edge_api_url: settings.edgeApiUrl.trim() || null }) - updateHost(host.id, { - probeDetail: `${settings.edgeApiUrl.trim() ? 'Edge 签名授权和中继已建立,' : ''}${fileDirection === 'upload' ? '上传' : '下载'}已在独立传输窗口中启动`, - }) - } catch (error) { - updateHost(host.id, { probeDetail: `文件传输启动失败:${errorMessage(error)}` }) - } finally { - setFileTransferBusy(false) - } - } - - async function refreshCredentialStatus(host: Host, reportError = true) { - if (host.kind !== 'windows_rdp' || !capabilities?.rdp.credential_store) return - try { - const status = await getRdpCredentialStatus(host.credentialRef) - updateHost(host.id, { credentialState: status }) - } catch (error) { - updateHost(host.id, { - credentialState: 'unknown', - probeDetail: reportError - ? `无法读取凭据状态:${errorMessage(error)}` - : host.probeDetail, - }) - } - } - - async function configureCredential(host: Host) { - try { - await configureRdpCredential(host.credentialRef) - updateHost(host.id, { - credentialState: 'unknown', - probeDetail: '请在新打开的本地窗口中输入 Windows 登录凭据', - }) - for (let attempt = 0; attempt < 60; attempt += 1) { - await new Promise((resolve) => window.setTimeout(resolve, 2_000)) - const status = await getRdpCredentialStatus(host.credentialRef) - if (status === 'ready') { - updateHost(host.id, { - credentialState: 'ready', - probeDetail: 'Windows 登录凭据已安全保存', - }) - return - } - } - } catch (error) { - updateHost(host.id, { - credentialState: 'unknown', - probeDetail: `无法配置凭据:${errorMessage(error)}`, - }) - } - } - - async function removeCredential(host: Host) { - if (!window.confirm(`删除 ${host.name} 的已保存登录凭据?`)) return - try { - await deleteRdpCredential(host.credentialRef) - updateHost(host.id, { - credentialState: 'missing', - probeDetail: '已从 Windows 凭据管理器删除登录凭据', - }) - } catch (error) { - updateHost(host.id, { probeDetail: `无法删除凭据:${errorMessage(error)}` }) - } - } - - async function monitorNativeSession(hostId: number, sessionId: string) { - let transientFailures = 0 - while (nativeSessions.current.get(hostId) === sessionId) { - try { - const diagnostics = await getNativeRdpSession(sessionId) - transientFailures = 0 - const presentationLatency = diagnostics.presentation_latency_ms === null - ? undefined - : Math.round(diagnostics.presentation_latency_ms * 10) / 10 - const decodeLatency = diagnostics.decode_latency_ms === null - ? undefined - : Math.round(diagnostics.decode_latency_ms * 10) / 10 - const networkLatency = diagnostics.network_latency_ms === null - ? undefined - : diagnostics.network_latency_ms - const networkBaseLatency = diagnostics.base_network_latency_ms === null - ? undefined - : diagnostics.base_network_latency_ms - const networkUpdate = networkLatency === undefined - ? diagnostics.state === 'reconnecting' - ? { - latency: undefined, - sessionNetworkLatency: undefined, - networkBaseLatency: undefined, - bandwidthKbps: undefined, - } - : {} - : { - latency: networkLatency, - sessionNetworkLatency: true, - networkBaseLatency, - bandwidthKbps: diagnostics.bandwidth_kbps ?? undefined, - } - const frameRate = diagnostics.frames_per_second === null - ? undefined - : Math.round(diagnostics.frames_per_second * 10) / 10 - const resizeSize = diagnostics.resize_requested_width !== null - && diagnostics.resize_requested_height !== null - ? `${diagnostics.resize_requested_width} × ${diagnostics.resize_requested_height}` - : '新尺寸' - const resizeDetail = diagnostics.state === 'failed' || diagnostics.state === 'terminated' - ? null - : diagnostics.resize_state === 'pending' - ? `IronRDP 正在请求 ${resizeSize},等待远端桌面尺寸确认` - : diagnostics.resize_state === 'reconnect_required' - ? `远端未应用 ${resizeSize}(${diagnostics.resize_failure === 'timeout' ? '确认超时' : '不支持动态调整'}),请在原生窗口确认是否重连` - : diagnostics.resize_state === 'reconnecting' - ? `已确认按 ${resizeSize} 重连,等待远端新尺寸画面` - : diagnostics.resize_state === 'cancelled' - ? `已取消按 ${resizeSize} 重连,当前会话保持连接` - : null - const detail = resizeDetail ?? (diagnostics.state === 'waiting_for_credentials' - ? 'IronRDP 等待在本地窗口输入凭据' - : diagnostics.state === 'connecting' - ? 'IronRDP 正在协商并认证远程会话' - : diagnostics.state === 'connected' - ? `IronRDP 已连接,${diagnostics.desktop_width} × ${diagnostics.desktop_height},已接收 ${diagnostics.frame_count} 帧${diagnostics.resize_state === 'confirmed' ? `,已确认第 ${diagnostics.resize_generation} 次尺寸调整` : diagnostics.resize_state === 'cancelled' ? ',最近一次尺寸重连已取消' : ''}` - : diagnostics.state === 'reconnecting' - ? `IronRDP 连接中断,正在进行第 ${diagnostics.reconnect_attempt} 次自动重连` - : diagnostics.state === 'failed' - ? `IronRDP 会话失败:${nativeSessionErrorLabel(diagnostics.error_code)}` - : 'IronRDP 会话已结束') - updateHost(hostId, { - ...networkUpdate, - nativeSessionState: diagnostics.state, - nativeMonitorCount: diagnostics.monitor_count, - decodeLatency, - presentationLatency, - frameRate, - nativeRenderer: diagnostics.renderer, - frameUploadMode: diagnostics.frame_upload_mode, - frameUploadPixels: diagnostics.frame_upload_pixels, - frameConversionPixels: diagnostics.frame_conversion_pixels, - qualityUpdatedAt: diagnostics.updated_at_unix_ms, - probeDetail: detail, - }) - if (diagnostics.state === 'failed' || diagnostics.state === 'terminated') { - nativeSessions.current.delete(hostId) - return - } - } catch (error) { - const transient = error instanceof ControlApiError - && (error.status === 404 || error.status === 409) - transientFailures += 1 - if (!transient || transientFailures >= 8) { - nativeSessions.current.delete(hostId) - updateHost(hostId, { - probeDetail: `无法读取 IronRDP 会话状态:${errorMessage(error)}`, - }) - return - } - } - await new Promise((resolve) => window.setTimeout(resolve, 750)) - } - } - - async function monitorLinuxDesktopSession(hostId: number, sessionId: string) { - let transientFailures = 0 - while (linuxDesktopSessions.current.get(hostId) === sessionId) { - try { - const diagnostics = await getLinuxDesktopSession(sessionId) - transientFailures = 0 - const metric = (value: number | null) => value === null - ? undefined - : Math.round(value * 10) / 10 - const detail = diagnostics.state === 'connecting' - ? 'Linux X11 桌面正在连接' - : diagnostics.state === 'connected' - ? `Linux X11 桌面已连接,${diagnostics.desktop_width} × ${diagnostics.desktop_height},已接收 ${diagnostics.frame_count} 帧` - : diagnostics.state === 'reconnecting' - ? `Linux X11 连接中断,正在进行第 ${diagnostics.reconnect_attempt} 次自动重连` - : diagnostics.state === 'failed' - ? `Linux X11 桌面会话失败(${diagnostics.error_code ?? 'unknown'})` - : 'Linux X11 桌面会话已结束' - updateHost(hostId, { - nativeSessionState: diagnostics.state, - latency: metric(diagnostics.network_latency_ms), - captureLatency: metric(diagnostics.capture_latency_ms), - encodeLatency: metric(diagnostics.encode_latency_ms), - compressionLevel: diagnostics.compression_level ?? undefined, - compressionRatio: metric(diagnostics.compression_ratio_percent), - decodeLatency: metric(diagnostics.decode_latency_ms), - presentationLatency: metric(diagnostics.presentation_latency_ms), - sessionClipboardRead: diagnostics.clipboard_read, - sessionClipboardWrite: diagnostics.clipboard_write, - frameRate: metric(diagnostics.frames_per_second), - qualityUpdatedAt: diagnostics.updated_at_unix_ms, - probeDetail: detail, - }) - if (diagnostics.state === 'failed' || diagnostics.state === 'terminated') { - linuxDesktopSessions.current.delete(hostId) - return - } - } catch (error) { - const transient = error instanceof ControlApiError - && (error.status === 404 || error.status === 409) - transientFailures += 1 - if (!transient || transientFailures >= 8) { - linuxDesktopSessions.current.delete(hostId) - updateHost(hostId, { - probeDetail: `无法读取 Linux 桌面会话状态:${errorMessage(error)}`, - }) - return - } - } - await new Promise((resolve) => window.setTimeout(resolve, 750)) - } - } - - async function refresh() { - setRefreshing(true) - try { - const detectedCapabilities = await getControlCapabilities() - setCapabilities(detectedCapabilities) - setControlServiceStatus('ready') - try { - const detectedSettings = await getControlSettings() - setSettings((current) => ({ - ...current, - edgeApiUrl: detectedSettings.edge_api_url ?? '', - updateManifestUrl: detectedSettings.update_manifest_url ?? '', - updatePublicKey: detectedSettings.update_public_key ?? '', - autoCheckUpdates: detectedSettings.auto_check_updates ?? true, - })) - setSettingsDraft((current) => ({ - ...current, - edgeApiUrl: detectedSettings.edge_api_url ?? '', - updateManifestUrl: detectedSettings.update_manifest_url ?? '', - updatePublicKey: detectedSettings.update_public_key ?? '', - autoCheckUpdates: detectedSettings.auto_check_updates ?? true, - })) - } catch { - // Older control services do not expose runtime settings. - } finally { - setSettingsLoaded(true) - } - setHosts((current) => current.map((host) => { - if (host.kind === 'linux_agent' && !detectedCapabilities.linux.agent_session) { - return { - ...host, - status: 'unknown', - path: 'Unavailable', - networkPolicy: detectedCapabilities.edge.relay ? host.networkPolicy : 'Direct preferred', - probeDetail: '当前构建尚未接入 Linux Agent 会话', - } - } - return { - ...host, - path: detectedCapabilities.edge.relay ? host.path : 'Direct', - networkPolicy: detectedCapabilities.edge.relay ? host.networkPolicy : 'Direct preferred', - } - })) - if (detectedCapabilities.rdp.credential_store) { - await Promise.all(hosts - .filter((host) => host.kind === 'windows_rdp') - .map(async (host) => { - try { - const status = await getRdpCredentialStatus(host.credentialRef) - updateHost(host.id, { credentialState: status }) - } catch { - updateHost(host.id, { credentialState: 'unknown' }) - } - })) - } - if (detectedCapabilities.linux.agent_identity_store) { - await Promise.all(hosts - .filter((host) => host.kind === 'linux_agent') - .map(async (host) => { - const fingerprint = '0'.repeat(64) - try { - const agentIdentityState = await getLinuxAgentIdentityStatus(fingerprint) - updateHost(host.id, { agentIdentityState }) - } catch { - updateHost(host.id, { agentIdentityState: 'unknown' }) - } - })) - } - await Promise.all(hosts - .filter((host) => host.kind === 'windows_rdp') - .map((host) => probeHost(host, detectedCapabilities.rdp.protocol_probe))) - if (detectedCapabilities.linux.agent_probe) { - await Promise.all(hosts - .filter((host) => host.kind === 'linux_agent') - .map((host) => probeLinuxHost(host))) - } - } catch (error) { - setCapabilities(null) - setControlServiceStatus('offline') - setHosts((current) => current.map((host) => host.kind === 'windows_rdp' - ? { ...host, status: 'unknown', latency: undefined, qualityUpdatedAt: undefined, probeDetail: `控制服务不可用:${errorMessage(error)}` } - : host)) - } finally { - setRefreshing(false) - } - } - - function addHost(event: FormEvent) { - event.preventDefault() - const data = new FormData(event.currentTarget) - const kind = data.get('kind') as HostKind - const username = String(data.get('username') ?? '') - const password = String(data.get('password') ?? '') - const host: Host = { - id: Math.max(0, ...hosts.map((item) => item.id)) + 1, - name: String(data.get('name')), - address: String(data.get('address')), - tags: parseTags(String(data.get('tags') ?? '')), - kind, - mode: kind === 'windows_rdp' ? 'RDP' : kind === 'windows_agent' ? 'Desktop' : 'Terminal', - status: 'unknown', - favorite: false, - lastUsed: 'Never', - path: 'Smart', - networkPolicy: 'Smart', - resolution: 'Follow window', - customWidth: '1600', - customHeight: '900', - gpu: settings.clientGpu === 'display' ? 'Display adapter' : 'Manual adapter', - memoryPath: kind === 'linux_agent' || kind === 'windows_agent' ? 'Pending validation' : 'Unknown', - recent: false, - strictZeroCopy: (kind === 'linux_agent' || kind === 'windows_agent') && settings.strictZeroCopy, - displayMode: 'primary', - displays: [], - selectedDisplayIds: [], - username, - credentialState: kind === 'windows_rdp' ? 'unknown' : 'missing', - agentIdentityState: kind === 'linux_agent' || kind === 'windows_agent' ? 'unknown' : 'missing', - credentialRef: createCredentialRef(), - rdpClient: 'system', - nativeFullscreen: false, - captureInput: false, - redirectClipboard: true, - clipboardRead: true, - clipboardWrite: true, - agentPublicKey: kind === 'linux_agent' ? String(data.get('agentPublicKey') ?? '') : undefined, - probeDetail: kind === 'windows_rdp' - ? '等待 RDP 端口检测' - : kind === 'windows_agent' ? 'Windows Agent 已配置;控制端原生会话尚未接入' : capabilities?.linux.agent_session ? '等待 Agent 上线' : '当前构建尚未接入 Linux Agent 会话', - } - setHosts((current) => [...current, host]) - setSelectedId(host.id) - setAddOpen(false) - if (kind === 'windows_rdp' && password) { - void saveRdpCredential(host.credentialRef, username, password).then(() => { - updateHost(host.id, { credentialState: 'ready', probeDetail: '登录凭据已安全保存' }) - }).catch((error) => { - updateHost(host.id, { credentialState: 'unknown', probeDetail: `凭据保存失败:${errorMessage(error)}` }) - }) - } - if (kind === 'windows_rdp') void probeHost(host) - if (kind === 'windows_agent') void probeWindowsAgentHost(host) - if (kind === 'linux_agent' && capabilities?.linux.agent_probe) void probeLinuxHost(host) - } - - return ( -
-
-
- - RemoteDesk -
-
- - setQuery(event.target.value)} - placeholder="搜索主机或地址" - ref={searchInput} - value={query} - /> - Ctrl K -
- -
- {updateResult?.status === 'available' && ( - - )} - - - - -
-
- -
- - -
-
-
-

远程主机

- {visibleHosts.length} 个结果 -
-
{capabilities?.edge.relay ? '智能路径' : '当前仅直连'}
-
- -
-
- 主机类型状态路径最近使用操作 -
- {visibleHosts.map((host) => ( -
setSelectedId(host.id)} - onKeyDown={(event) => { - if (event.target !== event.currentTarget) return - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault() - setSelectedId(host.id) - } - }} - role="row" - tabIndex={0} - > - - - {host.mode === 'Terminal' ? : } - - {host.name}{host.address}{(host.tags ?? []).length > 0 && {(host.tags ?? []).map((tag) => {tag})}} - - {hostKindLabel(host)}{host.mode} - {statusLabel(host)} - {host.path} - {host.lastUsed} - - - - - -
- ))} - {visibleHosts.length === 0 &&
没有匹配的主机
} -
-
- - {selected ? : } -
- - {addOpen && ( -
setAddOpen(false)}> -
event.stopPropagation()}> -

新增主机

创建可用的远程连接配置

- - - - - {addHostKind === 'windows_rdp' && <> - - - } - {addHostKind === 'windows_agent' &&

Windows Agent 使用原生桌面通道,不依赖 RDP。Agent 默认监听 `0.0.0.0:39501`;控制端请填写被控主机 IP 和端口。

} - {addHostKind === 'linux_agent' && <> - - - } -
-
-
- )} - - {settingsOpen && ( -
setSettingsOpen(false)}> - -
- )} - -
- ) -} - -export default App diff --git a/client/web/src/backend.ts b/client/web/src/backend.ts deleted file mode 100644 index ff3a668..0000000 --- a/client/web/src/backend.ts +++ /dev/null @@ -1,549 +0,0 @@ -export type RdpProbeStatus = - | 'reachable' - | 'dns_failed' - | 'timeout' - | 'refused' - | 'unreachable' - -export type RdpProbeResult = { - status: RdpProbeStatus - reachable: boolean - latency_ms: number | null - port: number -} - -export type LinuxAgentProbeResult = { - ok: boolean - latency_ms: number - protocol_major: number - protocol_minor: number - terminal: boolean - desktop: boolean - files: boolean - edge_presence_configured: boolean - edge_presence_online: boolean - edge_signaling_configured: boolean - edge_signaling_online: boolean -} - -export type RdpProtocolProbeResult = { - ok: boolean - stage: 'input' | 'dns' | 'tcp' | 'rdp_negotiation' | 'tls' - security_protocol?: 'hybrid' | 'hybrid_extended' - tcp_latency_ms?: number - total_latency_ms?: number - certificate_sha256?: string - error_code?: string -} - -export type LaunchRdpRequest = { - address: string - username: string - resolution: RdpResolution - use_multimon: boolean - monitor_indices?: number[] - client: RdpClientMode - fullscreen: boolean - certificate_sha256: string | null - credential_ref: string | null - redirect_clipboard: boolean -} - -export type RdpResolution = - | { mode: 'follow_window' } - | { mode: 'fixed'; width: number; height: number } - -export type RdpClientMode = 'system' | 'native' - -export type LaunchRdpResult = { - launched: boolean - client?: RdpClientMode - session_id?: string -} - -export type NativeRdpSessionState = - | 'waiting_for_credentials' - | 'connecting' - | 'connected' - | 'reconnecting' - | 'failed' - | 'terminated' - -export type NativeRdpResizeState = - | 'idle' - | 'pending' - | 'reconnect_required' - | 'reconnecting' - | 'confirmed' - | 'cancelled' - -export type NativeRdpSessionDiagnostics = { - schema_version: 1 - session_id: string - state: NativeRdpSessionState - started_at_unix_ms: number - updated_at_unix_ms: number - frame_count: number - frames_per_second: number | null - desktop_width: number - desktop_height: number - monitor_count: number - multi_monitor: boolean - network_latency_ms: number | null - base_network_latency_ms: number | null - bandwidth_kbps: number | null - decode_latency_ms: number | null - presentation_latency_ms: number | null - renderer?: string - frame_conversion_pixels: number - frame_upload_mode?: 'pending' | 'full_frame' | 'dirty_rect' | 'cached_frame' - frame_upload_pixels: number - reconnect_attempt: number - resize_generation: number - resize_state: NativeRdpResizeState - resize_requested_width: number | null - resize_requested_height: number | null - resize_failure: 'unsupported' | 'timeout' | null - error_code: string | null -} - -export type LinuxDesktopSessionDiagnostics = { - schema_version: 1 - session_id: string - state: 'connecting' | 'connected' | 'reconnecting' | 'failed' | 'terminated' - updated_at_unix_ms: number - frame_count: number - frames_per_second: number | null - desktop_width: number - desktop_height: number - network_latency_ms: number | null - capture_latency_ms: number | null - encode_latency_ms: number | null - compression_level: number | null - compression_ratio_percent: number | null - decode_latency_ms: number | null - presentation_latency_ms: number | null - clipboard_read: boolean - clipboard_write: boolean - reconnect_attempt: number - error_code: string | null -} - -function compatibleResolution(resolution: RdpResolution): string { - return resolution.mode === 'follow_window' - ? 'Follow window' - : `${resolution.width} x ${resolution.height}` -} - -export type ControlCapabilities = { - version: string - platform: string - application: { - desktop_shell: boolean - shell_kind: 'tauri-2' | 'standalone' - } - rdp: { - probe: boolean - protocol_probe: boolean - external_launch: boolean - native_launch: boolean - native_diagnostics: boolean - native_multimon: boolean - session_network_metrics: boolean - session_decode_metrics: boolean - region_pixel_conversion: boolean - secure_named_pipe: boolean - secure_pipe_config_ack: boolean - secure_pipe_handshake_timeout: boolean - job_object_lifecycle: boolean - certificate_pinning: boolean - automatic_reconnect: boolean - resize_confirmation: boolean - clipboard: boolean - audio_playback: boolean - native_renderer: 'software_framebuffer' | 'd3d11_cpu_upload' - d3d11_device_available: boolean - d3d11_dirty_rect_upload: boolean - credential_manager: boolean - credential_store: boolean - } - linux: { - agent_session: boolean - agent_probe: boolean - terminal_session: boolean - desktop_session: boolean - x11_desktop: boolean - relative_pointer: boolean - adaptive_frame_pacing: boolean - adaptive_compression: boolean - desktop_session_resume: boolean - desktop_session_crash_resume: boolean - direct_webrtc_signaling: boolean - opus_audio: boolean - clipboard_read: boolean - clipboard_write: boolean - wayland_desktop: boolean - native_video: boolean - native_video_decoder_probe: boolean - native_video_surface_presenter: boolean - file_transfer: boolean - agent_identity_store: boolean - } - edge: { - relay: boolean - signed_intent: boolean - linux_session_relay: boolean - linux_known_device_rendezvous: boolean - remote_pairing: boolean - } - update: { - configured: boolean - check: boolean - install: boolean - } -} - -export type ControlSettings = { - edge_api_url: string | null - update_manifest_url: string | null - update_public_key: string | null - auto_check_updates: boolean -} - -export type ControlUpdateRelease = { - version: string - channel: string - published_at: string - notes?: string - size_bytes: number -} - -export type ControlUpdateCheck = { - current_version: string - status: 'up_to_date' | 'available' - release?: ControlUpdateRelease -} - -type ApiError = { - error?: string -} - -export class ControlApiError extends Error { - readonly status: number - - constructor(message: string, status: number) { - super(message) - this.name = 'ControlApiError' - this.status = status - } -} - -async function readJson(response: Response): Promise { - const body = await response.json() as T & ApiError - if (!response.ok) { - throw new ControlApiError( - body.error || `control service returned HTTP ${response.status}`, - response.status, - ) - } - return body -} - -export async function probeRdp(address: string): Promise { - const response = await fetch('/api/v1/rdp/probe', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ address }), - }) - return readJson(response) -} - -export async function negotiateRdp(address: string): Promise { - const response = await fetch('/api/v1/rdp/negotiate', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ address }), - }) - return readJson(response) -} - -export async function getControlCapabilities(): Promise { - const response = await fetch('/api/v1/capabilities', { - headers: { Accept: 'application/json' }, - }) - return readJson(response) -} - -export async function getControlSettings(): Promise { - const response = await fetch('/api/v1/settings', { - headers: { Accept: 'application/json' }, - }) - return readJson(response) -} - -export async function updateControlSettings(settings: ControlSettings): Promise { - const response = await fetch('/api/v1/settings', { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(settings), - }) - return readJson(response) -} - -export async function getControlHosts(): Promise { - const response = await fetch('/api/v1/hosts', { - headers: { Accept: 'application/json' }, - }) - return readJson(response) -} - -export async function updateControlHosts(hosts: unknown): Promise { - const response = await fetch('/api/v1/hosts', { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(hosts), - }) - await readJson<{ saved: boolean }>(response) -} - -export async function checkControlUpdate(): Promise { - const response = await fetch('/api/v1/update/check', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: '{}', - }) - return readJson(response) -} - -export async function installControlUpdate(version: string): Promise { - const response = await fetch('/api/v1/update/install', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ version }), - }) - await readJson<{ accepted: boolean; version: string }>(response) -} - -export async function launchRdp(request: LaunchRdpRequest): Promise { - // Keep a reloaded UI compatible with a control service that has not restarted yet. - const compatibleRequest = { - ...request, - resolution: compatibleResolution(request.resolution), - } - const response = await fetch('/api/v1/rdp/launch', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(compatibleRequest), - }) - return readJson(response) -} - -export async function getNativeRdpSession( - sessionId: string, -): Promise { - const response = await fetch(`/api/v1/rdp/session/${encodeURIComponent(sessionId)}`, { - headers: { Accept: 'application/json' }, - }) - return readJson(response) -} - -export async function openCredentialManager(): Promise { - const response = await fetch('/api/v1/windows/credential-manager', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: '{}', - }) - await readJson<{ launched: boolean }>(response) -} - -export type RdpCredentialStatus = 'missing' | 'ready' - -async function credentialRequest( - operation: 'status' | 'set' | 'delete', - credentialRef: string, -): Promise { - return fetch(`/api/v1/rdp/credential/${operation}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ credential_ref: credentialRef }), - }) -} - -export async function getRdpCredentialStatus(credentialRef: string): Promise { - const response = await credentialRequest('status', credentialRef) - return (await readJson<{ status: RdpCredentialStatus }>(response)).status -} - -export async function configureRdpCredential(credentialRef: string): Promise { - const response = await credentialRequest('set', credentialRef) - await readJson<{ launched: boolean }>(response) -} - -export async function saveRdpCredential(credentialRef: string, account: string, password: string): Promise { - const response = await fetch('/api/v1/rdp/credential/set', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ credential_ref: credentialRef, account, password }), - }) - await readJson<{ launched: boolean }>(response) -} - -export async function deleteRdpCredential(credentialRef: string): Promise { - const response = await credentialRequest('delete', credentialRef) - await readJson<{ status: 'missing' }>(response) -} - -export async function launchLinuxTerminal(request: { - address: string - user: string - certificate_sha256: string - pair: boolean - agent_public_key?: string | null - edge_api_url?: string | null -}): Promise { - const response = await fetch('/api/v1/linux/terminal/launch', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(request), - }) - await readJson<{ launched: boolean }>(response) -} - -export async function probeLinuxAgent( - address: string, - certificateSha256: string, -): Promise { - const response = await fetch('/api/v1/linux/probe', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ address, certificate_sha256: certificateSha256 }), - }) - return readJson(response) -} - -export type WindowsAgentProbeResult = { - ok: boolean - latency_ms: number - protocol_major: number - protocol_minor: number - capture_backend: string - preferred_capture_backend: string - software_fallback_backend?: string | null - software_fallback_available: boolean - input_backend: string - authenticated: boolean - desktop_session: boolean - capture_latency_ms?: number | null - encode_latency_ms?: number | null - frame_processing_latency_ms?: number | null - applications: Array<{ id: string; name: string }> -} - -export async function probeWindowsAgent(address: string): Promise { - const response = await fetch('/api/v1/windows-agent/probe', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ address }), - }) - return readJson(response) -} - -export type WindowsAgentSessionDiagnostics = { - schema_version: 1 - session_id: string - state: 'connecting' | 'connected' | 'failed' | 'terminated' - updated_at_unix_ms: number - frame_count: number - frames_per_second: number | null - desktop_width: number - desktop_height: number - capture_latency_ms: number | null - encode_latency_ms: number | null - frame_processing_latency_ms: number | null - decode_latency_ms: number | null - presentation_latency_ms: number | null - renderer: string - error_code: string | null -} - -export async function launchWindowsAgentDesktop(request: { address: string; frames_per_second: number; fullscreen: boolean; mode?: 'desktop' | 'application'; application?: string }): Promise<{ launched: boolean; session_id: string }> { - const response = await fetch('/api/v1/windows-agent/desktop/launch', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request) }) - return readJson<{ launched: boolean; session_id: string }>(response) -} - -export async function getWindowsAgentSession(sessionId: string): Promise { - const response = await fetch(`/api/v1/windows-agent/session/${encodeURIComponent(sessionId)}`, { headers: { Accept: 'application/json' } }) - return readJson(response) -} - -export async function launchLinuxDesktop(request: { - address: string - user: string - certificate_sha256: string - width: number - height: number - frames_per_second: number - fullscreen: boolean - follow_window: boolean - capture_input: boolean - clipboard_read: boolean - clipboard_write: boolean - edge_api_url?: string | null -}): Promise<{ launched: boolean; session_id: string }> { - const response = await fetch('/api/v1/linux/desktop/launch', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(request), - }) - return readJson<{ launched: boolean; session_id: string }>(response) -} - -export async function getLinuxDesktopSession( - sessionId: string, -): Promise { - const response = await fetch( - `/api/v1/linux/desktop/session/${encodeURIComponent(sessionId)}`, - { headers: { Accept: 'application/json' } }, - ) - return readJson(response) -} - -export async function launchLinuxFileTransfer(request: { - address: string - user: string - certificate_sha256: string - direction: 'upload' | 'download' - local_path: string - remote_path: string - edge_api_url?: string | null -}): Promise { - const response = await fetch('/api/v1/linux/files/launch', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(request), - }) - await readJson<{ launched: boolean }>(response) -} - -async function linuxAgentIdentityRequest( - operation: 'status' | 'delete', - certificateSha256: string, -): Promise { - const response = await fetch(`/api/v1/linux/identity/${operation}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ certificate_sha256: certificateSha256 }), - }) - return (await readJson<{ status: RdpCredentialStatus }>(response)).status -} - -export async function getLinuxAgentIdentityStatus( - certificateSha256: string, -): Promise { - return linuxAgentIdentityRequest('status', certificateSha256) -} - -export async function deleteLinuxAgentIdentity(certificateSha256: string): Promise { - const status = await linuxAgentIdentityRequest('delete', certificateSha256) - if (status !== 'missing') throw new Error('控制服务未删除 Linux Agent 身份') -} diff --git a/client/web/src/host-model.ts b/client/web/src/host-model.ts deleted file mode 100644 index c8e8a4a..0000000 --- a/client/web/src/host-model.ts +++ /dev/null @@ -1,202 +0,0 @@ -import type { - LinuxAgentProbeResult, - NativeRdpSessionState, - RdpClientMode, - RdpProbeResult, - RdpProtocolProbeResult, - RdpResolution, -} from './backend' - -export type HostKind = 'windows_rdp' | 'windows_agent' | 'linux_agent' -export type HostStatus = 'ready' | 'offline' | 'connecting' | 'probing' | 'unknown' -export type NetworkPolicy = 'Smart' | 'Direct preferred' | 'CDN preferred' -export type DisplayMode = 'primary' | 'all' | 'custom' -export type CredentialState = 'missing' | 'ready' | 'unknown' -export type WindowsAgentApplication = { id: string; name: string } - -export type HostDisplay = { - id: string - name: string - resolution: string - primary: boolean -} - -export type Host = { - id: number - name: string - address: string - kind: HostKind - mode: string - agentApplication?: string - agentApplications?: WindowsAgentApplication[] - tags?: string[] - status: HostStatus - favorite: boolean - lastUsed: string - latency?: number - sessionNetworkLatency?: boolean - networkBaseLatency?: number - bandwidthKbps?: number - captureLatency?: number - encodeLatency?: number - frameProcessingLatency?: number - compressionLevel?: number - compressionRatio?: number - decodeLatency?: number - presentationLatency?: number - nativeMonitorCount?: number - frameRate?: number - nativeSessionState?: NativeRdpSessionState - nativeRenderer?: string - frameUploadMode?: string - frameUploadPixels?: number - frameConversionPixels?: number - qualityUpdatedAt?: number - path: string - networkPolicy: NetworkPolicy - resolution: string - customWidth: string - customHeight: string - gpu: string - memoryPath: string - recent: boolean - strictZeroCopy: boolean - displayMode: DisplayMode - displays: HostDisplay[] - selectedDisplayIds: string[] - username: string - credentialState: CredentialState - agentIdentityState: CredentialState - credentialRef: string - rdpClient: RdpClientMode - nativeFullscreen: boolean - captureInput: boolean - redirectClipboard: boolean - clipboardRead: boolean - clipboardWrite: boolean - sessionClipboardRead?: boolean - sessionClipboardWrite?: boolean - agentPublicKey?: string - probeDetail?: string -} - -export const minRdpDimension = 200 -export const maxRdpDimension = 8192 -const credentialRefPrefix = 'RemoteDesk/RDP/' - -function createRef(prefix: string) { - const profileKey = typeof crypto.randomUUID === 'function' - ? crypto.randomUUID() - : Array.from(crypto.getRandomValues(new Uint8Array(16)), (value) => value.toString(16).padStart(2, '0')).join('') - return `${prefix}${profileKey}` -} - -export function createCredentialRef() { return createRef(credentialRefPrefix) } -export function validCredentialRef(value: unknown): value is string { - return typeof value === 'string' && new RegExp(`^${credentialRefPrefix}[A-Za-z0-9_.:@-]{1,128}$`).test(value) -} - -export function isStoredHost(value: unknown): value is Host { - if (!value || typeof value !== 'object') return false - const host = value as Partial - return typeof host.id === 'number' - && typeof host.name === 'string' - && typeof host.address === 'string' - && (host.kind === 'windows_rdp' || host.kind === 'windows_agent' || host.kind === 'linux_agent') - && typeof host.mode === 'string' - && Array.isArray(host.displays) - && Array.isArray(host.selectedDisplayIds) -} - -export function normalizeTags(value: unknown): string[] { - if (!Array.isArray(value)) return [] - const tags: string[] = [] - for (const item of value) { - if (typeof item !== 'string') continue - const tag = item.trim().slice(0, 32) - if (tag && !tags.includes(tag)) tags.push(tag) - if (tags.length >= 20) break - } - return tags -} - -export function parseTags(value: string): string[] { - return normalizeTags(value.split(',')) -} - -export function normalizeStoredHost(host: Host): Host { - return { - ...host, - tags: normalizeTags(host.tags), - mode: host.kind === 'windows_agent' && host.mode !== 'Application' ? 'Desktop' : host.mode, - status: 'unknown', latency: undefined, sessionNetworkLatency: undefined, networkBaseLatency: undefined, - bandwidthKbps: undefined, captureLatency: undefined, encodeLatency: undefined, frameProcessingLatency: undefined, decodeLatency: undefined, - presentationLatency: undefined, frameRate: undefined, nativeSessionState: undefined, - sessionClipboardRead: undefined, sessionClipboardWrite: undefined, qualityUpdatedAt: undefined, - credentialState: host.kind === 'windows_rdp' ? 'unknown' : 'missing', - agentIdentityState: host.kind === 'linux_agent' || host.kind === 'windows_agent' ? 'unknown' : 'missing', - credentialRef: validCredentialRef(host.credentialRef) ? host.credentialRef : createCredentialRef(), - rdpClient: host.rdpClient === 'native' ? 'native' : 'system', nativeFullscreen: host.nativeFullscreen === true, - captureInput: host.captureInput === true, redirectClipboard: host.redirectClipboard !== false, - clipboardRead: host.clipboardRead ?? host.redirectClipboard !== false, - clipboardWrite: host.clipboardWrite ?? host.redirectClipboard !== false, - customWidth: typeof host.customWidth === 'string' ? host.customWidth : '1600', - customHeight: typeof host.customHeight === 'string' ? host.customHeight : '900', - probeDetail: '尚未检测', - } -} - -export function statusLabel(host: Host) { - if (host.status === 'ready') return host.kind === 'linux_agent' || host.kind === 'windows_agent' ? 'Agent 在线' : '端口可达' - if (host.status === 'connecting') return '连接中' - if (host.status === 'probing') return '检测中' - if (host.status === 'unknown') return '待检测' - return '离线' -} - -export function probeResultDetail(result: RdpProbeResult) { - if (result.status === 'reachable') return `TCP ${result.port} 可达,账号尚未验证` - if (result.status === 'dns_failed') return '域名解析失败' - if (result.status === 'timeout') return `TCP ${result.port} 连接超时` - if (result.status === 'refused') return `TCP ${result.port} 拒绝连接` - return `TCP ${result.port} 不可达` -} - -export function linuxProbeDetail(result: LinuxAgentProbeResult) { - const capabilities = [result.terminal ? '终端' : null, result.desktop ? 'X11 桌面' : null, result.files ? '文件' : null] - .filter((value): value is string => value !== null).join('、') || '无会话能力' - const edge = result.edge_presence_configured - ? result.edge_presence_online && result.edge_signaling_online ? ',Edge 在线' : ',Edge 离线' : '' - return `Agent v${result.protocol_major}.${result.protocol_minor} 可用,${capabilities}${edge}` -} - -export function protocolProbeDetail(result: RdpProtocolProbeResult) { - if (result.ok) { - const protocol = result.security_protocol === 'hybrid_extended' ? 'NLA Hybrid Extended' : 'NLA Hybrid' - const latency = result.total_latency_ms === undefined ? '' : `,${result.total_latency_ms} ms` - return `RDP 协商成功(${protocol}${latency}),账号尚未验证` - } - if (result.error_code === 'timeout') return 'TCP 可达,但 RDP 协商超时' - if (result.error_code === 'rdp_negotiation_failed') return 'TCP 可达,但目标未通过 RDP 协商' - return `RDP 协商未完成(${result.error_code ?? result.stage})` -} - -export function parseRdpDimension(value: string) { - if (!/^\d+$/.test(value)) return null - const dimension = Number(value) - return Number.isSafeInteger(dimension) && dimension >= minRdpDimension && dimension <= maxRdpDimension ? dimension : null -} - -export function rdpResolution(host: Host): RdpResolution | null { - if (host.resolution === 'Follow window') return { mode: 'follow_window' } - if (host.resolution === 'Custom') { - const width = parseRdpDimension(host.customWidth) - const height = parseRdpDimension(host.customHeight) - return width && height ? { mode: 'fixed', width, height } : null - } - const match = /^(\d+) x (\d+)$/.exec(host.resolution) - if (!match) return null - const width = parseRdpDimension(match[1]) - const height = parseRdpDimension(match[2]) - return width && height ? { mode: 'fixed', width, height } : null -} diff --git a/client/web/src/main.tsx b/client/web/src/main.tsx deleted file mode 100644 index c31d4c6..0000000 --- a/client/web/src/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import App from './App' -import './styles.css' - -createRoot(document.getElementById('root')!).render( - - - , -) diff --git a/client/web/src/styles.css b/client/web/src/styles.css deleted file mode 100644 index 20027de..0000000 --- a/client/web/src/styles.css +++ /dev/null @@ -1,283 +0,0 @@ -:root { - color: #dce2e7; - background: #101315; - color-scheme: dark; - font-family: Inter, "Segoe UI", "Microsoft YaHei", sans-serif; - font-synthesis: none; - letter-spacing: 0; -} - -* { box-sizing: border-box; } -body { margin: 0; min-width: 320px; min-height: 100vh; overflow: hidden; } -button, input, select { font: inherit; letter-spacing: 0; } -button { color: inherit; } -button:focus-visible, .host-row:focus-visible { outline: 2px solid #69d6a0; outline-offset: -2px; } - -.app-shell { min-height: 100vh; background: #101315; } -.topbar { height: 56px; display: grid; grid-template-columns: 220px minmax(260px, 560px) minmax(142px, 1fr) auto; align-items: center; gap: 16px; padding: 0 16px; border-bottom: 1px solid #2a3034; background: #15191c; } -.brand { display: flex; align-items: center; gap: 10px; font-size: 15px; font-weight: 700; color: #f1f4f5; } -.brand-mark { width: 30px; height: 30px; display: grid; place-items: center; color: #0f1512; background: #5ed39a; border-radius: 6px; overflow: hidden; } -.brand-mark img { display: block; width: 30px; height: 30px; } -.search-field { height: 34px; display: flex; align-items: center; gap: 9px; padding: 0 10px; border: 1px solid #343b40; border-radius: 6px; background: #0e1113; color: #7f8a91; } -.search-field:focus-within { border-color: #5d8f77; box-shadow: 0 0 0 2px #244435; } -.search-field input { flex: 1; min-width: 0; border: 0; outline: 0; color: #e8ecee; background: transparent; font-size: 13px; } -.search-field kbd { border: 1px solid #343b40; border-radius: 4px; padding: 2px 5px; color: #78838a; font-size: 10px; } -.top-actions { justify-self: end; display: flex; align-items: center; gap: 8px; } -.topbar-clock { min-width: 142px; display: inline-flex; align-items: center; justify-content: flex-end; gap: 7px; color: #98a39d; font-size: 11px; font-variant-numeric: tabular-nums; white-space: nowrap; } -.icon-button, .row-icon { width: 34px; height: 34px; display: grid; place-items: center; border: 1px solid #343b40; border-radius: 6px; background: #1a1f22; cursor: pointer; } -.icon-button:hover, .row-icon:hover { background: #252b2f; border-color: #475158; } -.icon-button.compact { width: 30px; height: 30px; } -.icon-button:disabled { opacity: .45; cursor: not-allowed; } -.icon-button.danger { color: #dd7d7d; } -.icon-button.update-available { color: #65d69d; border-color: #3d775b; } -.command-button, .connect-button, .secondary-button { min-height: 34px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; border-radius: 6px; padding: 0 13px; border: 1px solid #4fbf87; background: #4fbf87; color: #0d1712; font-weight: 700; font-size: 13px; cursor: pointer; } -.secondary-button { border-color: #3b4449; background: #20262a; color: #dce2e7; } -.command-button:disabled, .secondary-button:disabled { opacity: .45; cursor: not-allowed; } -.workspace { height: calc(100vh - 56px); display: grid; grid-template-columns: 188px minmax(520px, 1fr) 310px; } -.sidebar { display: flex; flex-direction: column; padding: 18px 12px 12px; border-right: 1px solid #272d31; background: #121619; } -.sidebar nav { display: grid; gap: 3px; } -.sidebar-label { margin: 18px 10px 5px; color: #69757a; font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; } -.tag-filter { min-height: 31px; } -.tag-filter-name { min-width: 0; display: inline-flex; align-items: center; gap: 7px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.tag-dot { width: 6px; height: 6px; flex: 0 0 auto; border-radius: 50%; background: #63c894; } -.nav-item { height: 35px; display: flex; align-items: center; justify-content: space-between; padding: 0 10px; border: 0; border-radius: 5px; background: transparent; color: #aab3b8; cursor: pointer; font-size: 13px; text-align: left; } -.nav-item:hover { background: #1b2023; color: #e3e7e9; } -.nav-item.active { color: #edf2ef; background: #22362d; } -.nav-count { color: #728078; font-size: 11px; font-variant-numeric: tabular-nums; } -.sidebar-status { margin-top: auto; display: flex; align-items: center; gap: 8px; padding: 10px; border-top: 1px solid #272d31; color: #8d999f; font-size: 11px; } -.host-area { min-width: 0; padding: 22px 22px 30px; overflow: auto; } -.section-heading { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: 16px; } -.section-heading h1 { margin: 0 0 4px; font-size: 19px; line-height: 1.2; font-weight: 700; } -.section-heading span { color: #7f8a91; font-size: 12px; } -.path-summary { display: flex; align-items: center; gap: 7px; color: #93a19a; font-size: 12px; } -.host-table { min-width: 690px; border: 1px solid #2a3135; border-radius: 6px; overflow: hidden; background: #15191c; } -.table-row { width: 100%; display: grid; grid-template-columns: minmax(190px, 1.5fr) 90px 100px 110px 100px 78px; align-items: center; min-height: 54px; padding: 0 12px; border: 0; border-bottom: 1px solid #272d31; background: transparent; color: #adb7bc; text-align: left; font-size: 12px; } -.table-header { min-height: 34px; color: #758087; background: #111517; font-size: 10px; text-transform: uppercase; } -.host-row { cursor: pointer; } -.host-row:hover { background: #1a2023; } -.host-row.selected { background: #1d2924; box-shadow: inset 3px 0 #57c890; } -.host-row:last-child { border-bottom: 0; } -.host-identity { display: flex; align-items: center; gap: 10px; min-width: 0; } -.host-icon { width: 30px; height: 30px; display: grid; place-items: center; border: 1px solid #30383d; border-radius: 5px; color: #92a19a; background: #171c1f; } -.host-identity strong, .host-identity small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.host-identity strong { color: #e2e7e9; font-size: 13px; } -.host-identity small { margin-top: 2px; color: #707b82; font-size: 10px; } -.host-tags, .tag-editor-preview { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; min-width: 0; } -.tag-chip { display: inline-flex; align-items: center; max-width: 110px; min-height: 18px; padding: 1px 6px; overflow: hidden; border: 1px solid #345344; border-radius: 4px; background: #1b3026; color: #8edbb1; text-overflow: ellipsis; white-space: nowrap; font-size: 10px; line-height: 1.3; } -.tag-editor-preview { margin: -4px 0 10px; } -.host-type-label { display: block; font-size: 11px; font-weight: 600; } -.host-mode-label { display: block; margin-top: 3px; color: #7f8b90; font-size: 10px; } -.add-host-note { padding: 8px 10px; border: 1px solid #344942; border-radius: 5px; background: #17251f; } -.status-cell { display: flex; align-items: center; gap: 7px; } -.status-dot { width: 7px; height: 7px; display: inline-block; flex: 0 0 auto; border-radius: 50%; background: #657078; } -.status-dot.ready { background: #59d394; box-shadow: 0 0 0 3px #1d3c2e; } -.status-dot.connecting { background: #e4b660; box-shadow: 0 0 0 3px #453820; } -.status-dot.probing { background: #e4b660; box-shadow: 0 0 0 3px #453820; animation: pulse 1s ease-in-out infinite; } -.status-dot.offline { background: #667078; } -.status-dot.unknown { background: #87939a; box-shadow: 0 0 0 3px #293136; } -.row-actions { display: flex; justify-content: flex-end; gap: 5px; } -.row-icon { width: 28px; height: 28px; } -.row-icon.primary { color: #65d69d; } -.row-icon:disabled { opacity: .32; cursor: not-allowed; } -.empty-state { padding: 42px; color: #778289; text-align: center; font-size: 13px; } -.inspector-empty { display: grid; align-content: center; justify-items: center; gap: 8px; color: #778289; } -.inspector-empty h2 { margin: 0; font-size: 13px; } -.inspector { padding: 20px 18px; border-left: 1px solid #272d31; background: #14181b; overflow-y: auto; } -.inspector-title { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 10px; padding-bottom: 16px; } -.inspector-title > div:first-child { min-width: 0; } -.inspector-actions { display: grid; grid-template-columns: repeat(2, 30px); grid-auto-rows: 30px; justify-content: end; gap: 6px; min-width: 66px; } -.inspector-actions .icon-button { width: 30px; height: 30px; min-width: 30px; } -.inspector-title > div { display: grid; grid-template-columns: 12px 1fr; align-items: center; } -.inspector-title h2 { margin: 0; font-size: 16px; line-height: 1.4; } -.inspector-title p { grid-column: 2; margin: 2px 0 0; color: #78838a; font-size: 11px; } -.inspector-section { padding: 16px 0; border-top: 1px solid #2a3034; } -.inspector-section h3 { margin: 0 0 12px; color: #8d989e; font-size: 11px; font-weight: 700; text-transform: uppercase; } -.inspector-section label, .modal label { display: grid; gap: 6px; margin-bottom: 11px; color: #8d989e; font-size: 11px; } -.rdp-client-field { display: grid; gap: 6px; margin-bottom: 11px; color: #8d989e; font-size: 11px; } -.segmented-control { width: 100%; min-height: 34px; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border: 1px solid #333b40; border-radius: 5px; overflow: hidden; background: #0f1315; } -.segmented-control button { min-width: 0; display: flex; align-items: center; justify-content: center; gap: 4px; padding: 0 5px; border: 0; border-right: 1px solid #333b40; background: transparent; color: #8e999f; cursor: pointer; font-size: 10px; white-space: nowrap; } -.segmented-control button:last-child { border-right: 0; } -.segmented-control button:hover:not(:disabled) { color: #e2e7e9; background: #22292c; } -.segmented-control button.active { color: #bcebd2; background: #244133; } -.segmented-control button:disabled { color: #596268; background: #151a1d; cursor: not-allowed; } -.segmented-control.file-direction { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-bottom: 11px; } -.connection-note { min-height: 28px; display: block; color: #717c82; line-height: 1.4; } -select, .modal input, .settings-drawer input { width: 100%; height: 32px; padding: 0 9px; border: 1px solid #333b40; border-radius: 5px; outline: 0; color: #dce2e5; background: #0f1315; font-size: 12px; } -select:focus, .modal input:focus, .settings-drawer input:focus { border-color: #548b70; } -select:disabled { color: #727d83; background: #151a1d; cursor: not-allowed; } -.resolution-custom { display: grid; grid-template-columns: minmax(0, 1fr) 12px minmax(0, 1fr); align-items: end; gap: 6px; margin-bottom: 11px; } -.resolution-custom label { min-width: 0; margin: 0; } -.resolution-custom span { padding-bottom: 8px; color: #727d83; text-align: center; font-size: 11px; } -.resolution-custom input { width: 100%; min-width: 0; height: 32px; padding: 0 8px; border: 1px solid #333b40; border-radius: 5px; outline: 0; color: #dce2e5; background: #0f1315; font-size: 12px; } -.resolution-custom input:focus { border-color: #548b70; } -.display-picker { display: grid; gap: 5px; margin: 0 0 12px; padding: 0; border: 0; } -.display-picker legend { margin-bottom: 6px; color: #8d989e; font-size: 11px; } -.display-option { min-height: 38px; grid-template-columns: 18px minmax(0, 1fr) !important; align-items: center; gap: 8px !important; margin: 0 !important; padding: 5px 8px; border: 1px solid #30383d; border-radius: 5px; background: #111517; cursor: pointer; } -.display-option input { width: 15px; height: 15px; margin: 0; accent-color: #3a9b6b; } -.display-option span, .display-option strong, .display-option small { display: block; min-width: 0; } -.display-option strong { overflow: hidden; color: #cbd2d5; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; } -.display-option small { margin-top: 2px; color: #707b82; font-size: 10px; } -.metric-row { display: flex; justify-content: space-between; align-items: center; gap: 14px; min-height: 30px; color: #879299; font-size: 11px; } -.metric-row span { display: inline-flex; align-items: center; gap: 6px; } -.metric-row strong { max-width: 150px; overflow: hidden; color: #cfd6d9; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; } -.good-text { color: #63d99d !important; } -.toggle-row { grid-template-columns: 1fr auto !important; align-items: center; margin-top: 8px; } -.toggle-row span, .toggle-row strong, .toggle-row small { display: block; } -.toggle-row strong { color: #cbd2d5; font-size: 11px; } -.toggle-row small { margin-top: 3px; color: #707b82; font-size: 10px; } -.toggle-row input { appearance: none; width: 30px; height: 17px; border-radius: 10px; background: #3a4247; cursor: pointer; position: relative; } -.toggle-row input::after { content: ''; position: absolute; width: 13px; height: 13px; left: 2px; top: 2px; border-radius: 50%; background: #b5bec2; transition: transform .15s; } -.toggle-row input:checked { background: #3a9b6b; } -.toggle-row input:checked::after { transform: translateX(13px); background: white; } -.toggle-row input:disabled { opacity: .55; cursor: not-allowed; } -.quality-heading { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; } -.quality-heading h3 { margin: 0; } -.quality-heading span, .quality-heading time { color: #717c82; font-size: 10px; font-variant-numeric: tabular-nums; white-space: nowrap; } -.quality-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); } -.quality-metrics > div { min-width: 0; padding: 1px 7px; border-left: 1px solid #2a3034; } -.quality-metrics > div:first-child { padding-left: 0; border-left: 0; } -.quality-metrics > div:last-child { padding-right: 0; } -.quality-metrics span, .quality-metrics strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.quality-metrics span { color: #7e898f; font-size: 10px; } -.quality-metrics strong { min-height: 19px; margin-top: 4px; color: #cfd5d8; font-size: 13px; font-variant-numeric: tabular-nums; } -.quality-details { margin-top: 13px; padding-top: 7px; border-top: 1px solid #252b2f; } -.quality-details > div { display: flex; justify-content: space-between; gap: 12px; margin: 7px 0; color: #7e898f; font-size: 11px; } -.quality-details strong { max-width: 180px; overflow: hidden; color: #cfd5d8; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; } -.connect-button { width: 100%; margin-top: 8px; } -.connection-blockers { margin-top: 14px; padding: 11px 12px; border: 1px solid #6b4c3f; border-radius: 6px; background: #2d2523; color: #f2c7b4; } -.connection-blockers h3 { margin: 0 0 7px; color: #f0a98c; font-size: 11px; font-weight: 700; } -.connection-blockers ul { margin: 0; padding-left: 18px; font-size: 12px; line-height: 1.55; } -.credential-button { width: 100%; margin-top: 10px; } -.certificate-input { width: 100%; min-width: 0; height: 32px; padding: 0 9px; border: 1px solid #333b40; border-radius: 5px; outline: 0; color: #dce2e5; background: #0f1315; font: 11px Consolas, "Cascadia Mono", monospace; } -.certificate-input:focus { border-color: #548b70; } -.certificate-observed { display: grid; gap: 4px; margin: -2px 0 9px; color: #7e898f; font-size: 10px; } -.certificate-observed strong { overflow: hidden; color: #cfd5d8; text-overflow: ellipsis; white-space: nowrap; font: 10px Consolas, "Cascadia Mono", monospace; } -.certificate-actions { display: grid; grid-template-columns: minmax(0, 1fr); grid-auto-flow: column; grid-auto-columns: 30px; gap: 7px; } -.connect-button:disabled { border-color: #394147; background: #252b2f; color: #78838a; cursor: not-allowed; } -.modal-backdrop, .drawer-backdrop { position: fixed; inset: 0; display: grid; place-items: center; background: #050708b8; z-index: 20; } -.modal { width: min(430px, calc(100vw - 32px)); padding: 20px; border: 1px solid #394248; border-radius: 7px; background: #171c1f; box-shadow: 0 24px 80px #000a; } -.modal-heading { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px; } -.modal-heading h2 { margin: 0; font-size: 17px; } -.modal-heading p { margin: 5px 0 0; color: #7c878d; font-size: 11px; } -.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; } -.drawer-backdrop { place-items: stretch end; } -.settings-drawer { width: min(390px, 100vw); height: 100%; overflow-y: auto; padding: 20px; border-left: 1px solid #394248; background: #171c1f; box-shadow: -20px 0 70px #0008; } -.drawer-save { width: 100%; } -.settings-error { margin: 10px 0 0; color: #ef8f8f; font-size: 11px; line-height: 1.4; overflow-wrap: anywhere; } -.shortcut-settings h3 { display: flex; align-items: center; gap: 6px; } -.settings-hint { margin: 8px 0 0; color: #9ca9ad; font-size: 11px; line-height: 1.45; } -.update-key-input { font-family: Consolas, "Cascadia Mono", monospace; } -.update-status { min-height: 38px; margin-top: 10px; padding: 9px 0; border-top: 1px solid #2a3034; border-bottom: 1px solid #2a3034; color: #879299; font-size: 11px; } -.update-status p { margin: 0; line-height: 1.45; overflow-wrap: anywhere; white-space: pre-line; } -.update-release > div { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; } -.update-release strong { color: #d7dddf; font-size: 12px; } -.update-release span { color: #77838a; font-size: 10px; font-variant-numeric: tabular-nums; white-space: nowrap; } -.update-release > p { margin-top: 7px; color: #9ca6ab; } -.update-actions { display: flex; gap: 8px; margin-top: 10px; } -.update-actions > button { flex: 1; padding: 0 8px; } -.spin { animation: spin .7s linear infinite; } -@keyframes spin { to { transform: rotate(360deg); } } -@keyframes pulse { 50% { opacity: .45; } } - -:root[data-theme='light'] { color: #25302b; background: #f5f7f8; color-scheme: light; } -[data-theme='light'].app-shell { color: #25302b; background: #f5f7f8; } -[data-theme='light'] .topbar { border-color: #dbe1de; background: #ffffff; } -[data-theme='light'] .brand { color: #111713; } -[data-theme='light'] .topbar-clock { color: #5f6e66; } -[data-theme='light'] .search-field { border-color: #cbd3cf; background: #f5f7f8; color: #65736c; } -[data-theme='light'] .search-field:focus-within { border-color: #36845e; box-shadow: 0 0 0 2px #d7eee2; } -[data-theme='light'] .search-field input { color: #1f2924; } -[data-theme='light'] .search-field kbd { border-color: #cbd3cf; color: #66746d; background: #ffffff; } -[data-theme='light'] .icon-button, [data-theme='light'] .row-icon { border-color: #cbd3cf; background: #ffffff; } -[data-theme='light'] .icon-button:hover, [data-theme='light'] .row-icon:hover { border-color: #94a49c; background: #edf2ef; } -[data-theme='light'] .secondary-button { border-color: #c4cec9; background: #f1f4f2; color: #26322c; } -[data-theme='light'] .sidebar { border-color: #dfe5e2; background: #f8faf9; } -[data-theme='light'] .nav-item { color: #526159; } -[data-theme='light'] .nav-item:hover { color: #1f2924; background: #edf2ef; } -[data-theme='light'] .nav-item.active { color: #17452f; background: #dff1e7; } -[data-theme='light'] .nav-count { color: #68776f; } -[data-theme='light'] .sidebar-status { border-color: #dfe5e2; color: #65736c; } -[data-theme='light'] .section-heading span, [data-theme='light'] .path-summary { color: #65736c; } -[data-theme='light'] .host-table { border-color: #d7dfdb; background: #ffffff; } -[data-theme='light'] .table-row { border-color: #e1e6e3; color: #526159; } -[data-theme='light'] .table-header { color: #68776f; background: #f2f5f4; } -[data-theme='light'] .host-row:hover { background: #f3f7f5; } -[data-theme='light'] .host-row.selected { background: #e8f5ee; box-shadow: inset 3px 0 #2b9a65; } -[data-theme='light'] .host-icon { border-color: #d5ddd9; color: #65736c; background: #f4f7f5; } -[data-theme='light'] .host-identity strong { color: #1d2822; } -[data-theme='light'] .host-identity small, [data-theme='light'] .inspector-title p { color: #6c7a73; } -[data-theme='light'] .sidebar-label { color: #7a8981; } -[data-theme='light'] .tag-chip { border-color: #b9d8c6; background: #edf8f1; color: #28734b; } -[data-theme='light'] .status-dot.ready { background: #209a63; box-shadow: 0 0 0 3px #d7f0e3; } -[data-theme='light'] .status-dot.connecting { background: #b97817; box-shadow: 0 0 0 3px #f4e6ca; } -[data-theme='light'] .status-dot.probing { background: #b97817; box-shadow: 0 0 0 3px #f4e6ca; } -[data-theme='light'] .status-dot.offline { background: #8a9690; } -[data-theme='light'] .status-dot.unknown { background: #718078; box-shadow: 0 0 0 3px #e1e7e4; } -[data-theme='light'] .row-icon.primary, [data-theme='light'] .good-text { color: #167a4c !important; } -[data-theme='light'] .empty-state { color: #69776f; } -[data-theme='light'] .inspector { border-color: #dfe5e2; background: #ffffff; } -[data-theme='light'] .inspector-section { border-color: #dfe5e2; } -[data-theme='light'] .inspector-section h3, [data-theme='light'] .inspector-section label, [data-theme='light'] .modal label { color: #5f6e66; } -[data-theme='light'] .rdp-client-field { color: #5f6e66; } -[data-theme='light'] .segmented-control { border-color: #cbd3cf; background: #ffffff; } -[data-theme='light'] .segmented-control button { border-color: #cbd3cf; color: #5f6e66; } -[data-theme='light'] .segmented-control button:hover:not(:disabled) { color: #26322c; background: #edf2ef; } -[data-theme='light'] .segmented-control button.active { color: #175c3b; background: #dff1e7; } -[data-theme='light'] .segmented-control button:disabled { color: #96a19b; background: #eef1f0; } -[data-theme='light'] .connection-note { color: #69776f; } -[data-theme='light'] select, [data-theme='light'] .modal input, [data-theme='light'] .settings-drawer input { border-color: #cbd3cf; color: #25302b; background: #ffffff; } -[data-theme='light'] select:focus, [data-theme='light'] .modal input:focus, [data-theme='light'] .settings-drawer input:focus { border-color: #36845e; } -[data-theme='light'] select:disabled { color: #77857e; background: #eef1f0; } -[data-theme='light'] .resolution-custom span { color: #77857e; } -[data-theme='light'] .resolution-custom input { border-color: #cbd3cf; color: #25302b; background: #ffffff; } -[data-theme='light'] .resolution-custom input:focus { border-color: #36845e; } -[data-theme='light'] .certificate-input { border-color: #cbd3cf; color: #25302b; background: #ffffff; } -[data-theme='light'] .certificate-input:focus { border-color: #36845e; } -[data-theme='light'] .certificate-observed strong { color: #28342e; } -[data-theme='light'] .display-picker legend { color: #5f6e66; } -[data-theme='light'] .display-option { border-color: #d5ddd9; background: #f7f9f8; } -[data-theme='light'] .display-option strong { color: #28342e; } -[data-theme='light'] .display-option small { color: #69776f; } -[data-theme='light'] .metric-row, [data-theme='light'] .quality-details > div { color: #5f6e66; } -[data-theme='light'] .metric-row strong, [data-theme='light'] .quality-metrics strong, [data-theme='light'] .quality-details strong, [data-theme='light'] .toggle-row strong { color: #28342e; } -[data-theme='light'] .quality-heading span, [data-theme='light'] .quality-heading time, [data-theme='light'] .quality-metrics span { color: #69776f; } -[data-theme='light'] .quality-metrics > div, [data-theme='light'] .quality-details { border-color: #dfe5e2; } -[data-theme='light'] .toggle-row small, [data-theme='light'] .modal-heading p { color: #69776f; } -[data-theme='light'] .toggle-row input { background: #b7c1bc; } -[data-theme='light'] .toggle-row input::after { background: #ffffff; } -[data-theme='light'] .toggle-row input:checked { background: #26865a; } -[data-theme='light'] .connect-button:disabled { border-color: #d1d8d4; color: #77857e; background: #e8ecea; } -[data-theme='light'] .connection-blockers { border-color: #e5b9a7; background: #fff5f0; color: #7a3928; } -[data-theme='light'] .connection-blockers h3 { color: #a3472e; } -[data-theme='light'] .modal-backdrop, [data-theme='light'] .drawer-backdrop { background: #10181447; } -[data-theme='light'] .modal, [data-theme='light'] .settings-drawer { border-color: #c9d1cd; background: #ffffff; box-shadow: 0 24px 70px #19251f2e; } -[data-theme='light'] .settings-error { color: #aa3030; } -[data-theme='light'] .settings-hint { color: #5e6b65; } -[data-theme='light'] .icon-button.update-available { color: #167a4c; border-color: #78aa90; } -[data-theme='light'] .update-status { border-color: #dfe5e2; color: #5f6e66; } -[data-theme='light'] .update-release strong { color: #28342e; } -[data-theme='light'] .update-release span, [data-theme='light'] .update-release > p { color: #69776f; } - -@media (max-width: 1040px) { - .workspace { grid-template-columns: 160px minmax(520px, 1fr); } - .inspector { display: none; } - .topbar { grid-template-columns: 140px minmax(150px, 1fr) 136px auto; gap: 8px; padding: 0 12px; } -} - -@media (max-width: 900px) { - .top-actions .command-button { width: 34px; padding: 0; font-size: 0; } -} - -@media (max-width: 720px) { - body { overflow: auto; } - .topbar { height: auto; min-height: 56px; grid-template-columns: 1fr auto; gap: 8px; padding: 10px 12px; } - .top-actions { gap: 4px; } - .topbar-clock { grid-column: 1 / -1; grid-row: 2; justify-self: end; min-width: 0; } - .search-field { grid-column: 1 / -1; grid-row: 3; } - .workspace { height: auto; min-height: calc(100vh - 132px); display: block; } - .sidebar { padding: 8px; border-right: 0; border-bottom: 1px solid #272d31; } - .sidebar nav { display: flex; overflow-x: auto; } - .nav-item { min-width: 82px; } - .sidebar-status { display: none; } - .host-area { padding: 16px 12px; overflow-x: auto; } -} diff --git a/client/web/tsconfig.app.json b/client/web/tsconfig.app.json deleted file mode 100644 index a35d693..0000000 --- a/client/web/tsconfig.app.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "useDefineForClassFields": true, - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "noUnusedLocals": true, - "noUnusedParameters": true - }, - "include": ["src"] -} diff --git a/client/web/tsconfig.json b/client/web/tsconfig.json deleted file mode 100644 index 1ffef60..0000000 --- a/client/web/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] -} diff --git a/client/web/tsconfig.node.json b/client/web/tsconfig.node.json deleted file mode 100644 index 05674ad..0000000 --- a/client/web/tsconfig.node.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2023", - "lib": ["ES2023"], - "module": "ESNext", - "skipLibCheck": true, - "moduleResolution": "Bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - "strict": true - }, - "include": ["vite.config.ts"] -} diff --git a/client/web/vite.config.ts b/client/web/vite.config.ts deleted file mode 100644 index bd735fd..0000000 --- a/client/web/vite.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { defineConfig } from 'vite' - -export default defineConfig({ - base: './', - build: { - emptyOutDir: true, - }, - server: { - host: '127.0.0.1', - port: 4173, - proxy: { - '/api': 'http://127.0.0.1:4174', - }, - }, -}) diff --git a/docs/all-rust-migration.md b/docs/all-rust-migration.md index 6e6326a..fdc1393 100644 --- a/docs/all-rust-migration.md +++ b/docs/all-rust-migration.md @@ -13,7 +13,7 @@ ## 阶段 1:删除 Go/Hysteria2 构建链 -状态:未开始。 +状态:已完成(源码与构建链)。 工作: @@ -27,7 +27,7 @@ ## 阶段 2:新增共享 WebRTC Rust crate -状态:未开始。 +状态:部分完成。共享 crate 已锁定 `str0m 0.23`,Windows/Linux workspace 已编译并有边界测试;macOS 目标尚未验证。 工作: @@ -41,7 +41,7 @@ ## 阶段 3:Agent/Client Loopback -状态:未开始。 +状态:部分完成。自动测试已覆盖 offer/answer、host ICE、DTLS/SCTP、可靠 control 与不可靠 pointer DataChannel;trickle ICE、ICE restart 和恶意网络测试尚缺。 工作: @@ -56,7 +56,7 @@ ## 阶段 4:接入 H.264 RTP -状态:未开始。 +状态:部分完成。`str0m` loopback 已发送和接收 H.264 access unit,现有 Agent 测试覆盖单 NAL、STAP-A、FU-A、丢包与关键帧恢复;尚缺真实网络压力测试。 工作: @@ -70,7 +70,7 @@ ## 阶段 5:平台硬件编码与解码 -状态:未开始。 +状态:部分完成。Windows Media Foundation/D3D11 与 Linux GStreamer 硬件编码路径已有源码和自动构建;尚无三平台硬件端到端及 GPU 工具验证。 工作: @@ -85,7 +85,7 @@ surface 类型和 Adapter;硬件能力不足时明确拒绝会话,不静默 ## 阶段 6:接入 wgpu 呈现 -状态:未开始。 +状态:部分完成。原生 Client 已使用 `winit`、`wgpu`、`egui` 并进入 Windows CI;解码 surface 的跨平台 GPU interop 与呈现验收尚缺。 工作: @@ -99,7 +99,7 @@ surface 类型和 Adapter;硬件能力不足时明确拒绝会话,不静默 ## 阶段 7:接入 Opus -状态:未开始。 +状态:部分完成。共享 `str0m` loopback 已承载 Opus,Linux 捕获编码和 Windows 播放路径已有源码;PLC、漂移控制和真实设备验收尚缺。 工作: @@ -113,7 +113,7 @@ surface 类型和 Adapter;硬件能力不足时明确拒绝会话,不静默 ## 阶段 8:接入 STUN/TURN -状态:未开始。 +状态:部分完成。Edge 已签发短期 TURN 凭据,共享层校验 STUN/TURN 配置并接受外部 srflx/relay 候选;真实 NAT、TURN/UDP 和 TURN/TLS 会话尚未验收。 工作: @@ -127,7 +127,7 @@ surface 类型和 Adapter;硬件能力不足时明确拒绝会话,不静默 ## 阶段 9:更新安装包和 CI -状态:未开始。 +状态:部分完成。Windows/Linux 打包与 CI 已移除 Go、Node Web 前端和 Tauri 构建入口,原生 Client 与安装包已加入 CI;macOS 包、升级矩阵和完整产物白名单尚缺。 工作: @@ -141,7 +141,7 @@ Node、Tauri CLI 或 WebView 构建工具;发布产物具备签名、校验和 ## 阶段 10:删除旧 Tauri/WebView 与兼容路径 -状态:未开始。 +状态:部分完成。旧 `client/web`、`client/app-shell` 及正式构建引用已删除;历史设计文档仍待归档改写,旧异步 WebRTC 驱动仍待由共享 `str0m` socket 驱动替换。 工作: diff --git a/docs/implementation-status.md b/docs/implementation-status.md index e52d2ee..e25f9e8 100644 --- a/docs/implementation-status.md +++ b/docs/implementation-status.md @@ -8,7 +8,26 @@ - **部分**:只有策略模型、界面或实验入口,尚不能完成端到端会话。 - **未实现**:目前只有设计或路线规划。 -## 当前可用 +## 全 Rust 迁移快照 + +| 阶段 | 状态 | 已验证边界 | +|---|---|---| +| 删除 Go/Hysteria2 | 完成 | 源码、CI、Windows/Linux 安装入口均已删除 | +| 共享 `str0m` crate | 部分 | `str0m 0.23` 已锁定;共享边界测试和 Agent 复用校验通过 | +| SDP/ICE/DataChannel | 部分 | host ICE loopback 覆盖 DTLS/SCTP、control 与 pointer 通道 | +| H.264 RTP | 部分 | loopback 媒体事件及 RFC 6184 重组/丢包测试通过 | +| 硬件编解码 | 部分 | Windows/Linux 源码与编译路径存在,尚无三平台实机证明 | +| `wgpu` 呈现 | 部分 | 原生窗口和 surface 编译通过,解码 surface interop 尚未完成 | +| Opus | 部分 | loopback、Linux 编码和 Windows 播放源码存在,尚无设备端到端 | +| STUN/TURN | 部分 | 有界配置、外部 srflx/relay 注入和 Edge 凭据已接入,尚无 NAT 矩阵 | +| 安装包/CI | 部分 | Windows/Linux 改用 Rust 原生产物,macOS 发布产物尚缺 | +| 删除 Tauri/WebView | 部分 | 源码和正式构建入口已删除;历史设计文档保留作迁移依据 | + +以下长表保留了迁移前各子系统的实现边界,其中出现的 Tauri、WebView、Hysteria2、 +软件回退或 `webrtc-rs` 只描述历史实现,不再是正式构建入口。当前状态以上述迁移快照 +和 [全 Rust 迁移计划](all-rust-migration.md) 为准。 + +## 迁移前能力明细(历史基线) | 能力 | 状态 | 当前边界 | |---|---|---| @@ -19,7 +38,7 @@ | IronRDP 协议与证书探测 | 可用 | 独立 helper 解析 X.224/RDP Negotiation Confirm,确认 HYBRID/HYBRID_EX 后完成无凭据 TLS 握手并返回服务器证书 SHA-256;不进入 CredSSP/NLA 或账号验证 | | Windows `mstsc` 启动 | 可用 | 生成不含密码的临时 `.rdp` 文件并启动系统客户端 | | RDP 检测旁路 | 可用 | TCP/协议检测仅用于诊断;待检测、检测中或检测失败均不阻止直接启动客户端 | -| 控制端时钟与质量面板 | 部分 | 顶栏显示每秒更新的本地时间;Windows 启动前网络延迟使用 TCP/RDP 探针实测值,原生会话接入 vendored IronRDP 已解析的 `NetworkCharacteristicsResult`,服务器提供时回传会话平均 RTT、基础 RTT 和估算带宽;Linux X11 源码通过当前直连 WSS 或 Edge relay 内的 Ping/Pong 测量会话 RTT;IronRDP 原生会话另回传状态、累计帧数、最近一秒实际帧率、尺寸、本地解码处理耗时和本地呈现耗时。解码计时从网络 PDU 进入 `active_stage.process` 开始,覆盖图形处理与 framebuffer 像素转换,本地输入或 resize 产生的更新不计入;Linux X11 桌面源码另回传真实采集、zlib 编码、解码/完整性校验、呈现耗时和帧率,并显示当前 zlib level 1..6 与压缩后/未压缩字节比例。RDP 服务端编码耗时仍无数据;新增指标链路按要求尚未编译 | +| 控制端时钟与质量面板 | 部分 | 顶栏显示每秒更新的本地时间;Windows 启动前网络延迟使用 TCP/RDP 探针实测值,原生会话接入 vendored IronRDP 已解析的 `NetworkCharacteristicsResult`,服务器提供时回传会话平均 RTT、基础 RTT 和估算带宽;Linux X11 源码通过当前直连 WSS 或 Edge relay 内的 Ping/Pong 测量会话 RTT;IronRDP 原生会话另回传状态、累计帧数、最近一秒实际帧率、尺寸、本地解码处理耗时和本地呈现耗时。解码计时从网络 PDU 进入 `active_stage.process` 开始,覆盖图形处理与 framebuffer 像素转换,本地输入或 resize 产生的更新不计入;Linux X11 桌面源码另回传真实采集、zlib 编码、解码/完整性校验、呈现耗时和帧率,并显示当前 zlib level 1..6 与压缩后/未压缩字节比例。RDP 服务端编码耗时仍无数据;Agent runtime 新增有界 `QualityController`,将 RTT、丢包、抖动、编码队列 EWMA 映射到 Good/Fair/Poor/Critical,并按 2160p60 到 720p15 阶梯快速降档、迟滞升档,单元测试覆盖用户上限和恢复行为;尚未接入完整 UI/实机指标采集 | | 客户端内 RDP | 部分 | 独立 IronRDP 原生窗口可发起单屏会话、跟随窗口动态调整远程桌面、启动/切换全屏和主动退出;原生多屏源码通过 Win32 枚举 2..16 个真实本地显示器,规范化主屏相对坐标,在服务端 RDPEDISP capabilities 到达后发送多 monitor layout PDU,并以无边框组合窗口覆盖本地虚拟桌面;退出组合全屏后可缩放查看,重连复用同一布局,单屏 resize 不会覆盖多屏配置,诊断回传实际 monitor count;TLS 探针返回证书 SHA-256,用户确认后原生启动强制携带指纹,实际 TLS 握手固定完整证书 DER 并验证握手签名;窗口聚焦时转发物理键盘扫描码、五键鼠标、缩放后的绝对坐标及水平/垂直滚轮,失焦或退出时释放远端输入状态;`Ctrl+Alt+Home` 发送远端 `Ctrl+Alt+End` 安全界面序列;viewer 通过 opaque 引用直接读取 Windows Credential Manager 凭据;控制服务源码已改用当前用户 SID ACL、拒绝远程客户端且单实例的 Windows Named Pipe,按 OS 返回的 pipe 客户端 PID、进程创建时间、镜像路径与 SHA-256 build hash 校验 helper,并通过一次性 32 字节密钥和 HMAC-SHA256 challenge 后才交付严格有界的连接配置,命令行只保留随机 pipe name;viewer 严格校验 schema 和全部配置字段后返回绑定载荷 SHA-256 与 challenge 的 HMAC ACK,服务端只有验证 ACK 版本、摘要和 MAC 后才确认配置已接受;连接、认证、配置与 ACK 共用 10 秒绝对 deadline,双端非阻塞有界读取避免已连接 helper 永久停滞握手线程;每个 viewer 启动后立即加入带 `KILL_ON_JOB_CLOSE` 的独立 Job Object,控制服务退出时由内核回收 helper,握手失败则终止整个 Job 并向现有诊断 API 写入 `secure_pipe_failed`;当前构建显式启用 Windows CLIPRDR 双向剪贴板并默认启用 RDPSND 本地音频;异常断线后释放输入并按 1/2/4 秒执行三次有界重连;Windows 优先创建硬件 D3D11 device、持久 CPU framebuffer、完整 GPU frame texture 与 DXGI swap chain;vendored IronRDP 只转换裁剪后的 `GraphicsUpdate` region 紧凑像素块,viewer 严格验证并合并到持久 framebuffer,再合并 redraw 前的多个待呈现区域,只对变化像素执行 CPU texture upload;discard swap chain 下每帧仍执行完整 GPU resource copy,失败时回退 software framebuffer;诊断回传本帧转换像素数、`full_frame`、`dirty_rect` 或 `cached_frame` 及实际 CPU 上传像素数;新增多屏/Named Pipe/Job/配置 ACK/全握手超时/dirty-region 转换与上传链路按要求尚未编译/实机验收,且尚非硬件解码或零拷贝,剪贴板和音频仍待真实远端专项验收 | | 分辨率和全部显示器 | 可用 | UI 提供常用档位及 200..8192 自定义宽高;`mstsc` 支持动态/固定分辨率和 `use multimon`;IronRDP 单屏窗口发送带 generation 的动态 resize、以实际远端图像确认,并在不支持/超时时经用户确认重连;原生“全部本地显示器”源码发送真实 RDPEDISP 多屏布局;自定义本地显示器子集尚未开放 | | Windows 凭据适配器 | 可用 | 每个主机保存 `RemoteDesk/RDP/` opaque 引用;独立本地遮罩窗口写入/覆盖 Windows Credential Manager,UI 可查询状态和删除;密码不进入 React、HTTP、控制服务、argv、日志或主机配置;已在 Windows 实测缺失条目查询,真实远端 NLA 登录仍待环境验收 | @@ -33,15 +52,12 @@ | Linux 文件传输 | 可用 | 与终端共用证书固定、Ed25519 配对身份、Edge opaque relay 和独立 `files` 权限;上传/下载限制为授权普通用户主目录内相对路径和 2 GiB,使用 45 KiB 分块、声明大小及 SHA-256 端到端校验;协议 minor 4 支持双向断点续传:上传 transfer ID 绑定 user/remote path/size/hash,降权 helper 从同 ID 的普通文件实测 offset、保留未完成 partial 并在整文件校验后原子替换;下载由本地隐藏 partial 长度请求 offset,Agent 按远端真实大小校验,只发送剩余字节,Client 重新哈希已有前缀和新增数据后才改名,最终哈希不符会删除 partial;Ubuntu CI 脚本实际中断 4/10 字节后续传并验证上传/下载剩余段;UI 可启动独立传输窗口,仍待真实 Linux 主机专项验收 | | Linux X11 桌面 | 部分 | 协议 minor 10 源码包含 `desktop` 权限、严格 zlib BGRA 分块/哈希/呈现 ACK、RTT、X11 `GetImage`/XTEST、绝对及有界相对鼠标、五键/滚轮/键盘、失焦释放、全屏/退出、跟随窗口 resize、三次有界重连和完整质量诊断;Windows helper 可通过 Raw Input 合并相对位移并显式抓取/释放光标。用户会话使用真实呈现 ACK 的 1..30 FPS 有界自适应 pacer,并依据真实编码预算与 ACK 在 zlib level 1..6 内调整压缩策略。Edge 桌面会话复用同一次 BGRA 捕获,以 GStreamer 按 VA/VAAPI/NVENC/QSV/OpenH264/x264 顺序建立 H.264 Annex-B AU 编码器;16 MiB AU 上限、45 KiB 分块、严格顺序、canonical base64、精确长度和 SHA-256 经 root/user IPC 验证后交给 WebRTC sender。Windows helper 已接入远端 H.264 RTP 到 Media Foundation/D3D11 原生窗口;第一帧呈现后双方在完整帧边界切换至 H.264-only,以呈现 ACK 驱动 pacer,不再并行传输完整 zlib 帧。RTP 序号中断立即触发回退;积压、重连、解码失败、媒体终止或三秒 H.264 ACK 超时也会恢复 zlib。协议 minor 14 源码增加默认关闭的 `opus_audio` 协商:只有同版本 Client 明确请求且已绑定 WebRTC sender 时,用户会话才从 Linux 输出 monitor 捕获 48 kHz 双声道 Opus,经 4 KiB 有界 IPC/SRTP 发送并由 Windows WASAPI 播放;音频与视频失败相互独立,旧客户端不接收音频事件。协议 minor 15 源码增加独立 `clipboard_read`/`clipboard_write` 权限、X11 selection 与 Windows `CF_UNICODETEXT` 双向纯文本剪贴板、Offer/Request/Data 按需传输、32 KiB/UTF-8/NUL/canonical Base64/SHA-256 校验和摘要防回环;root agentd 只授权和转发结构化消息。WSS/Edge 异常断开时立即释放输入并保留 15 秒 X11 租约、帧序列、pacer 和压缩控制器;32 字节随机恢复令牌绑定 agentd 注入的已认证 Client 指纹与 Linux 用户。新增输入/媒体/音频/剪贴板链路尚未解析依赖、编译或在真实 Windows/Xorg 主机验收,因此仍不标记为原生视频可用;Wayland 及 Wayland 剪贴板仍关闭。 | | CDN Edge 独立服务 | 部分 | 独立 Rust 进程已实现管理员与 Agent Presence 独立作用域的 Bearer 认证 API、短 TTL Presence、Direct/Single/Dual 路径评分、coturn REST 凭据和一次性 HMAC relay 票据;签名 Session Intent 队列最多全局 1024、每设备 32、每设备/Client 8 个,最长 120 秒,request ID 幂等、15 秒投递租约、Agent proof nonce 防重放并绑定当前 Presence connection;普通请求验证本地已配对 Client/permission/user,首次 `pairing` 请求只在 Agent 本机配对窗口有效且允许目标用户/terminal 时接受;Edge 分别交付 Client/Agent 角色票据,并在双方各自的签名授权响应中下发绑定相同 session/expiry 的 coturn REST 短期凭据;已接受桌面会话的 Agent/Client 使用 `webrtc-rs` 0.20.1 创建真实 PeerConnection,注册 H.264/RTX/Opus、NACK/PLI/transport-cc、控制 DataChannel,并经有界签名 mailbox 交换 SDP/ICE。Agent 侧媒体 sender 以 session、过期时间、目标用户和 Client 指纹注册,单次消费且在 PeerConnection 关闭时撤销;Linux 捕获帧已接入 H.264 track,Windows 接收端也已接入有界 RTP 重组和原生解码/呈现源码。真实 coturn POP、UDP/TLS、双 POP、跨网络媒体和 RDP Edge 尚未验收,因此不开放通用 CDN 策略;新增接线按要求尚未解析依赖、编译或运行。 | -| Hysteria2 Agent transport | 已有入口,未完成原生会话迁移 | `transport/hysteria2-agent` 使用官方 Hysteria2 Go `core/v2` 提供密码认证、TLS、UDP/QUIC-only server/client 和本机 UDP 适配端口;服务端明确拒绝 TCP proxy,未引入 SOCKS5 或 TCP bridge。Windows/Linux Rust Agent 现有 TCP/WSS 会话协议尚未改为 UDP 数据报协议,因此该入口目前是可部署的传输适配层,不宣称桌面会话已直接接通。 | IronRDP 单屏 resize 源码现已补齐单调 generation、调整后尺寸回传、匹配远端图像确认、5 秒确认超时、不支持/超时诊断和用户确认重连。vendored client 不再因缺少 Display Control 静默重连;取消确认会保持当前会话,确认后的尺寸也会保留给后续有界故障重连。该新增链路仍按要求等待编译和 Windows 真实服务端验收。 Linux 用户会话现已将 X11 与 Wayland 生命周期收敛到统一 Agent 后端。Wayland 默认编译 `wayland-eis`,通过用户 D-Bus 实际查询 RemoteDesktop/ScreenCast Portal,按 `CreateSession -> SelectDevices -> SelectSources -> Start` 顺序授权,持有 PipeWire remote 与 EIS fd,并以 DMABUF 严格硬件 H.264 管线发送;Portal/EIS/VA 能力不足时返回明确错误,不伪装成 X11 或 CPU 回退。控制服务的 `linux.wayland_desktop` 现在表示 Linux Agent helper 已包含该能力,具体桌面是否可用仍由 Agent status 的 Portal probe 和会话启动结果决定。 -Windows Headless 方案状态:架构已确定,实现在尚未实现表中跟踪。目标是 IDD/IddCx SDR 8-bit 虚拟显示器、Desktop Duplication 首阶段兼容捕获或后续 IDD GPU frame、NVENC/AMF/VCE/VCN/Quick Sync 硬件优先、x264/x265/SVT-AV1 软件兜底、独立 Opus 音频、输入优先控制流和 Hysteria2 视频 datagram。IDD 不提供 HDR10/10-bit 能力;本方案不承诺 HDR10。 - -Windows Headless compatibility 模式允许无硬件 GPU 主机使用可用的 Desktop Duplication/IDD 输出、WARP 或 CPU 编码,但只报告 software/degraded,并根据实测 CPU 能力降低分辨率、帧率和并发上限。该模式不保证多路 4K/120;strict 性能模式在硬件编码器不可用时拒绝请求。当前仍未完成真实无 GPU 主机验收。 +Windows Headless 方案只接受可验证的硬件路径:IDD/IddCx SDR 8-bit 虚拟显示器、GPU surface、硬件编码器和 `str0m` SRTP。软件编码、CPU bitmap、跨 Adapter 和 opaque 路径不再作为运行时回退;硬件能力或证明不足时明确拒绝会话。IDD 不提供 HDR10/10-bit 能力,本方案不承诺 HDR10。 ## 尚未实现 diff --git a/packaging/build-all.ps1 b/packaging/build-all.ps1 index 07821d7..000e7c8 100644 --- a/packaging/build-all.ps1 +++ b/packaging/build-all.ps1 @@ -2,7 +2,6 @@ param( [switch]$SkipClient, [switch]$SkipHost, - [switch]$SkipHysteria2, [switch]$CleanArtifacts, [ValidateSet('zh-cn', 'en-us')] [string]$Culture = 'zh-cn' @@ -14,7 +13,6 @@ $artifacts = Join-Path $repoRoot 'artifacts' $windowsScript = Join-Path $repoRoot 'packaging/windows/package-installer.ps1' $hostScript = Join-Path $repoRoot 'packaging/windows-host/package-host.ps1' $portableHostScript = Join-Path $repoRoot 'packaging/windows-host/package-portable-host.ps1' -$hysteriaScript = Join-Path $repoRoot 'packaging/windows-host/build-hysteria2-agent.ps1' $previousRustFlags = $env:RUSTFLAGS $staticRustFlags = '-C target-feature=+crt-static' @@ -53,11 +51,6 @@ try { Invoke-ChildBuild -Name 'Windows Host portable package' -Script $portableHostScript -Arguments @('-Culture', $Culture) } - # Hysteria2 is an independent UDP/QUIC transport package and must not be - # skipped merely because the Windows Host MSI is disabled. - if (-not $SkipHysteria2) { - Invoke-ChildBuild -Name 'Hysteria2 transport' -Script $hysteriaScript - } } finally { if ($null -eq $previousRustFlags) { Remove-Item Env:RUSTFLAGS -ErrorAction SilentlyContinue @@ -74,10 +67,6 @@ $portableHost = Join-Path $artifacts 'RemoteDesk-Host-portable' if (Test-Path -LiteralPath $portableHost) { $outputFiles += @(Get-ChildItem -LiteralPath $portableHost -File -Include '*.exe', 'manifest.json' -ErrorAction SilentlyContinue) } -$hysteriaOutput = Join-Path $artifacts 'hysteria2' -if (Test-Path -LiteralPath $hysteriaOutput) { - $outputFiles += @(Get-ChildItem -LiteralPath $hysteriaOutput -File -ErrorAction SilentlyContinue) -} $outputFiles | Sort-Object FullName -Unique | diff --git a/packaging/linux/README.md b/packaging/linux/README.md index 622fc84..f134371 100644 --- a/packaging/linux/README.md +++ b/packaging/linux/README.md @@ -1,9 +1,7 @@ # RemoteDesk Linux controlled endpoint -The Linux package installs four Rust processes. Packages that include the -optional `remotedesk-hysteria2-agent` binary also install a pure Hysteria2 -QUIC/UDP transport service on UDP 39502. It requires a TLS certificate and a -long random password; it does not expose TCP, a TCP proxy, or SOCKS5. +The Linux package installs four Rust processes. Real-time desktop sessions use +the Rust WebRTC stack; the package contains no Go transport sidecar. - `remotedesk-agentd`: root system service for device identity, WSS pairing/authentication, authorization, user-session registration, and terminal creation. - `remotedesk-agent-session`: per-user service that detects Wayland/X11/PipeWire state and registers through authenticated Unix IPC. @@ -29,7 +27,7 @@ sudo remotedesk-agentd revoke CLIENT_FINGERPRINT The daemon listens on TCP 39500 by default. Edit `/etc/remotedesk/agent.env` to change it and restart `remotedesk-agentd`. The package deliberately does not modify nftables, firewalld, UFW, SELinux, or AppArmor policy. -Edge Presence and signed session-intent polling are optional and disabled by default. To publish the Agent in a separately deployed Edge directory, set all four `REMOTEDESK_EDGE_API_URL`, `REMOTEDESK_EDGE_PRESENCE_TOKEN`, `REMOTEDESK_EDGE_REGION`, and `REMOTEDESK_EDGE_GATEWAY_ID` values in `agent.env`. The public API URL must use HTTPS; loopback HTTP is accepted only for local testing. The token must be a dedicated Presence token of at least 32 bytes, not the Edge administrator token or relay ticket secret. Restart the service and inspect `sudo remotedesk-agentd status --json`; graceful service shutdown removes the connection-bound registration. While online, the Agent signs each poll/ack with its device key and accepts an intent only after verifying its Client signature, local pairing grant, permission, and allowed user. Accepted terminal/file requests use an Agent-only one-use ticket to open an outbound relay tunnel to the local TLS listener. Accepted desktop requests start an Agent-side `webrtc-rs` PeerConnection, receive session-bound TURN credentials, and bind its sender to the authorized Linux user and Client fingerprint. Native DEB/RPM builds use GStreamer hardware encoders when available and bounded software fallbacks otherwise; the static musl compatibility package excludes this dynamically linked media feature. Windows remote H.264 RTP consumption and native D3D11 presentation are now wired in source with zlib/WSS fallback, but remain disabled as a runtime capability until rebuilt and validated; this is not an RDP relay. +Edge Presence and signed session-intent polling are optional and disabled by default. To publish the Agent in a separately deployed Edge directory, set all four `REMOTEDESK_EDGE_API_URL`, `REMOTEDESK_EDGE_PRESENCE_TOKEN`, `REMOTEDESK_EDGE_REGION`, and `REMOTEDESK_EDGE_GATEWAY_ID` values in `agent.env`. The public API URL must use HTTPS; loopback HTTP is accepted only for local testing. The token must be a dedicated Presence token of at least 32 bytes, not the Edge administrator token or relay ticket secret. Restart the service and inspect `sudo remotedesk-agentd status --json`; graceful service shutdown removes the connection-bound registration. While online, the Agent signs each poll/ack with its device key and accepts an intent only after verifying its Client signature, local pairing grant, permission, and allowed user. Accepted terminal/file requests use an Agent-only one-use ticket to open an outbound relay tunnel to the local TLS listener. Accepted desktop requests receive session-bound TURN credentials and bind the sender to the authorized Linux user and Client fingerprint. The shared Rust `str0m` boundary owns bounded SDP/ICE, DataChannel, H.264 RTP, and Opus events; the existing async Agent socket driver is being migrated to that boundary. Native DEB/RPM builds use GStreamer hardware encoders when available; the static musl package excludes this dynamically linked media feature. Windows remote H.264 RTP consumption and native D3D11 presentation are wired in source, but remain disabled as a runtime capability until rebuilt and validated; this is not an RDP relay. On the Windows controller, configure the same public origin under Global Settings > Edge session authorization. For first pairing, enter both `device_public_key` and `tls_certificate_sha256` printed by the Agent's local `pairing-code` command. While that local pairing window is valid, the Client submits a signed `pairing` intent and completes Agent-key verification, pinned TLS/WSS, Client challenge authentication, and the eight-digit code inside the opaque relay; the code is never sent to the Edge API. The certificate-bound Agent public key is then stored in Windows Credential Manager, and subsequent terminal/file requests submit directly to Edge without a Client-to-Agent connection. Direct pairing remains a fallback when Edge or the public key is not configured. No Edge administrator or Presence token is stored on Windows. diff --git a/packaging/linux/hysteria2-agent.env.example b/packaging/linux/hysteria2-agent.env.example deleted file mode 100644 index 1c5428b..0000000 --- a/packaging/linux/hysteria2-agent.env.example +++ /dev/null @@ -1,5 +0,0 @@ -REMOTEDESK_HYSTERIA2_LISTEN=0.0.0.0:39502 -REMOTEDESK_HYSTERIA2_PASSWORD=replace-with-a-long-random-password -REMOTEDESK_HYSTERIA2_CERT=/etc/remotedesk/server.crt -REMOTEDESK_HYSTERIA2_KEY=/etc/remotedesk/server.key -REMOTEDESK_AGENT_UDP=127.0.0.1:39502 diff --git a/packaging/linux/install.sh b/packaging/linux/install.sh index e5def2c..429243c 100644 --- a/packaging/linux/install.sh +++ b/packaging/linux/install.sh @@ -13,13 +13,6 @@ install -m 0755 "$package_root/bin/remotedesk-agentd" /usr/bin/remotedesk-agentd install -m 0755 "$package_root/bin/remotedesk-agent-session" /usr/bin/remotedesk-agent-session install -m 0755 "$package_root/bin/remotedesk-shell-session" /usr/libexec/remotedesk-shell-session install -m 0755 "$package_root/bin/remotedesk-file-session" /usr/libexec/remotedesk-file-session -if [ -f "$package_root/bin/remotedesk-hysteria2-agent" ]; then - install -m 0755 "$package_root/bin/remotedesk-hysteria2-agent" /usr/bin/remotedesk-hysteria2-agent - install -m 0644 "$package_root/systemd/remotedesk-hysteria2-agent.service" /usr/lib/systemd/system/ - if [ ! -e /etc/remotedesk/hysteria2-agent.env ]; then - install -m 0600 "$package_root/config/hysteria2-agent.env" /etc/remotedesk/hysteria2-agent.env - fi -fi install -m 0644 "$package_root/systemd/remotedesk-agentd.service" /usr/lib/systemd/system/ install -m 0644 "$package_root/systemd/remotedesk-agent-session.service" /usr/lib/systemd/user/ install -m 0644 "$package_root/README.md" /usr/share/doc/remotedesk-agent/README.md diff --git a/packaging/linux/package-deb.sh b/packaging/linux/package-deb.sh index ee65a2f..badb30c 100644 --- a/packaging/linux/package-deb.sh +++ b/packaging/linux/package-deb.sh @@ -20,18 +20,13 @@ mkdir -p "$package_root/DEBIAN" "$package_root/usr/bin" "$package_root/usr/libex "$package_root/etc/remotedesk" "$package_root/usr/share/doc/remotedesk-agent" "$artifacts_root" cargo build --locked --release --manifest-path "$repo_root/Cargo.toml" -p remotedesk-agent-runtime -CGO_ENABLED=0 go build -buildvcs=false -trimpath -ldflags '-s -w' \ - -o "$package_root/usr/bin/remotedesk-hysteria2-agent" \ - "$repo_root/transport/hysteria2-agent" install -m 0755 "$repo_root/target/release/remotedesk-agentd" "$package_root/usr/bin/" install -m 0755 "$repo_root/target/release/remotedesk-agent-session" "$package_root/usr/bin/" install -m 0755 "$repo_root/target/release/remotedesk-shell-session" "$package_root/usr/libexec/" install -m 0755 "$repo_root/target/release/remotedesk-file-session" "$package_root/usr/libexec/" install -m 0644 "$repo_root/packaging/linux/remotedesk-agentd.service" "$package_root/usr/lib/systemd/system/" install -m 0644 "$repo_root/packaging/linux/remotedesk-agent-session.service" "$package_root/usr/lib/systemd/user/" -install -m 0644 "$repo_root/packaging/linux/remotedesk-hysteria2-agent.service" "$package_root/usr/lib/systemd/system/" install -m 0644 "$repo_root/packaging/linux/agent.env" "$package_root/etc/remotedesk/" -install -m 0600 "$repo_root/packaging/linux/hysteria2-agent.env.example" "$package_root/etc/remotedesk/hysteria2-agent.env" install -m 0644 "$repo_root/packaging/linux/README.md" "$package_root/usr/share/doc/remotedesk-agent/README.md" sed -e "s/@VERSION@/$version/g" -e "s/@ARCH@/$arch/g" \ diff --git a/packaging/linux/package-hysteria2.sh b/packaging/linux/package-hysteria2.sh deleted file mode 100644 index 719caff..0000000 --- a/packaging/linux/package-hysteria2.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -set -eu - -repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) -output_dir=${1:-"$repo_root/artifacts/hysteria2-linux"} -mkdir -p "$output_dir" -CGO_ENABLED=0 go build -buildvcs=false -trimpath -ldflags '-s -w' \ - -o "$output_dir/remotedesk-hysteria2-agent" \ - "$repo_root/transport/hysteria2-agent" -install -m 0644 "$repo_root/packaging/linux/remotedesk-hysteria2-agent.service" "$output_dir/" -install -m 0644 "$repo_root/packaging/linux/hysteria2-agent.env.example" "$output_dir/" -install -m 0644 "$repo_root/transport/hysteria2-agent/README.md" "$output_dir/README-hysteria2.md" -sha256sum "$output_dir/remotedesk-hysteria2-agent" > "$output_dir/SHA256SUMS.txt" -printf 'Hysteria2 package: %s\n' "$output_dir" diff --git a/packaging/linux/package-musl.ps1 b/packaging/linux/package-musl.ps1 index 3b5fe44..64caff9 100644 --- a/packaging/linux/package-musl.ps1 +++ b/packaging/linux/package-musl.ps1 @@ -62,14 +62,6 @@ try { Invoke-Checked -Description 'Static Linux Agent build' -Command { cargo build --locked --release --jobs 2 --target x86_64-unknown-linux-musl -p remotedesk-agent-runtime --no-default-features --features gstreamer-h264,wayland-eis } - Invoke-Checked -Description 'Static Hysteria2 transport build' -Command { - $env:CGO_ENABLED = '0' - $env:GOOS = 'linux' - $env:GOARCH = 'amd64' - go build -buildvcs=false -trimpath -ldflags '-s -w' -o (Join-Path $targetRoot 'remotedesk-hysteria2-agent-linux') (Join-Path $repoRoot 'transport/hysteria2-agent') - Remove-Item Env:CGO_ENABLED, Env:GOOS, Env:GOARCH -ErrorAction SilentlyContinue - } - Assert-PathWithin -Path $buildRoot -Root $targetRoot if (Test-Path -LiteralPath $buildRoot) { Remove-Item -LiteralPath $buildRoot -Recurse -Force } New-Item -ItemType Directory -Force -Path $buildRoot, $artifactsRoot | Out-Null @@ -82,11 +74,7 @@ try { Copy-Item -LiteralPath (Join-Path $targetRoot "x86_64-unknown-linux-musl\release\$binary") -Destination $destination Invoke-Checked -Description "Strip $binary" -Command { & $llvmStrip $destination } } - Copy-Item -LiteralPath (Join-Path $targetRoot 'remotedesk-hysteria2-agent-linux') -Destination (Join-Path $portableRoot 'bin/remotedesk-hysteria2-agent') - Invoke-Checked -Description 'Strip remotedesk-hysteria2-agent' -Command { & $llvmStrip (Join-Path $portableRoot 'bin/remotedesk-hysteria2-agent') } Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'remotedesk-agentd.service') -Destination (Join-Path $portableRoot 'systemd') - Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'remotedesk-hysteria2-agent.service') -Destination (Join-Path $portableRoot 'systemd') - Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'hysteria2-agent.env.example') -Destination (Join-Path $portableRoot 'config/hysteria2-agent.env') Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'remotedesk-agent-session.service') -Destination (Join-Path $portableRoot 'systemd') Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'agent.env') -Destination (Join-Path $portableRoot 'config') Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'install.sh') -Destination $portableRoot @@ -103,8 +91,8 @@ try { $portableTar = Join-Path $buildRoot "$portableName.tar" $portableGzip = "$portableTar.gz" $portableDirectories = @($portableName, "$portableName/bin", "$portableName/config", "$portableName/systemd") - $portableExecutables = @("$portableName/install.sh", "$portableName/uninstall.sh", "$portableName/bin/remotedesk-agentd", "$portableName/bin/remotedesk-agent-session", "$portableName/bin/remotedesk-shell-session", "$portableName/bin/remotedesk-file-session", "$portableName/bin/remotedesk-hysteria2-agent") - $portableData = @("$portableName/README.md", "$portableName/SHA256SUMS.txt", "$portableName/config/agent.env", "$portableName/config/hysteria2-agent.env", "$portableName/systemd/remotedesk-agentd.service", "$portableName/systemd/remotedesk-hysteria2-agent.service", "$portableName/systemd/remotedesk-agent-session.service") + $portableExecutables = @("$portableName/install.sh", "$portableName/uninstall.sh", "$portableName/bin/remotedesk-agentd", "$portableName/bin/remotedesk-agent-session", "$portableName/bin/remotedesk-shell-session", "$portableName/bin/remotedesk-file-session") + $portableData = @("$portableName/README.md", "$portableName/SHA256SUMS.txt", "$portableName/config/agent.env", "$portableName/systemd/remotedesk-agentd.service", "$portableName/systemd/remotedesk-agent-session.service") Push-Location $buildRoot try { Invoke-Checked -Description 'Portable directory archive' -Command { & $gnuTar --force-local --no-recursion --format=gnu --owner=0 --group=0 --mode=0755 -cf $portableTar @portableDirectories } @@ -124,12 +112,9 @@ try { Copy-Item -LiteralPath (Join-Path $portableRoot 'bin\remotedesk-agent-session') -Destination (Join-Path $debianRoot 'usr\bin') Copy-Item -LiteralPath (Join-Path $portableRoot 'bin\remotedesk-shell-session') -Destination (Join-Path $debianRoot 'usr\libexec') Copy-Item -LiteralPath (Join-Path $portableRoot 'bin\remotedesk-file-session') -Destination (Join-Path $debianRoot 'usr\libexec') - Copy-Item -LiteralPath (Join-Path $portableRoot 'bin\remotedesk-hysteria2-agent') -Destination (Join-Path $debianRoot 'usr\bin') Copy-Item -LiteralPath (Join-Path $portableRoot 'systemd\remotedesk-agentd.service') -Destination (Join-Path $debianRoot 'usr\lib\systemd\system') Copy-Item -LiteralPath (Join-Path $portableRoot 'systemd\remotedesk-agent-session.service') -Destination (Join-Path $debianRoot 'usr\lib\systemd\user') - Copy-Item -LiteralPath (Join-Path $portableRoot 'systemd\remotedesk-hysteria2-agent.service') -Destination (Join-Path $debianRoot 'usr\lib\systemd\system') Copy-Item -LiteralPath (Join-Path $portableRoot 'config\agent.env') -Destination (Join-Path $debianRoot 'etc\remotedesk') - Copy-Item -LiteralPath (Join-Path $portableRoot 'config\hysteria2-agent.env') -Destination (Join-Path $debianRoot 'etc\remotedesk') Copy-Item -LiteralPath (Join-Path $portableRoot 'README.md') -Destination (Join-Path $debianRoot 'usr\share\doc\remotedesk-agent\README.md') $control = (Get-Content -LiteralPath (Join-Path $PSScriptRoot 'debian\control') -Raw).Replace('@VERSION@', $version).Replace('@ARCH@', 'amd64') $control = $control.Replace(', gstreamer1.0-plugins-base, gstreamer1.0-plugins-good, gstreamer1.0-plugins-bad, gstreamer1.0-plugins-ugly', '') @@ -149,8 +134,8 @@ try { } finally { Pop-Location } Invoke-Checked -Description 'DEB control compression' -Command { & $gzip -n -f $controlTar } $dataTar = Join-Path $debParts 'data.tar' - $execFiles = @('./usr/bin/remotedesk-agentd', './usr/bin/remotedesk-agent-session', './usr/bin/remotedesk-hysteria2-agent', './usr/libexec/remotedesk-shell-session', './usr/libexec/remotedesk-file-session') - $dataFiles = @('./usr/lib/systemd/system/remotedesk-agentd.service', './usr/lib/systemd/system/remotedesk-hysteria2-agent.service', './usr/lib/systemd/user/remotedesk-agent-session.service', './etc/remotedesk/agent.env', './etc/remotedesk/hysteria2-agent.env', './usr/share/doc/remotedesk-agent/README.md') + $execFiles = @('./usr/bin/remotedesk-agentd', './usr/bin/remotedesk-agent-session', './usr/libexec/remotedesk-shell-session', './usr/libexec/remotedesk-file-session') + $dataFiles = @('./usr/lib/systemd/system/remotedesk-agentd.service', './usr/lib/systemd/user/remotedesk-agent-session.service', './etc/remotedesk/agent.env', './usr/share/doc/remotedesk-agent/README.md') Push-Location $debianRoot try { Invoke-Checked -Description 'DEB executable payload' -Command { & $gnuTar --force-local --format=gnu --owner=0 --group=0 --mode=0755 -cf $dataTar @execFiles } diff --git a/packaging/linux/remotedesk-hysteria2-agent.service b/packaging/linux/remotedesk-hysteria2-agent.service deleted file mode 100644 index b0dc763..0000000 --- a/packaging/linux/remotedesk-hysteria2-agent.service +++ /dev/null @@ -1,20 +0,0 @@ -[Unit] -Description=RemoteDesk Hysteria2 UDP transport -After=network-online.target -Wants=network-online.target - -[Service] -Type=simple -EnvironmentFile=/etc/remotedesk/hysteria2-agent.env -ExecStart=/usr/bin/remotedesk-hysteria2-agent -Restart=on-failure -RestartSec=3s -NoNewPrivileges=yes -PrivateTmp=yes -ProtectSystem=strict -ProtectHome=read-only -ReadWritePaths=/var/lib/remotedesk -RestrictAddressFamilies=AF_INET AF_INET6 - -[Install] -WantedBy=multi-user.target diff --git a/packaging/windows-host/RemoteDeskHost.wxs b/packaging/windows-host/RemoteDeskHost.wxs index 10e196b..4aa01a6 100644 --- a/packaging/windows-host/RemoteDeskHost.wxs +++ b/packaging/windows-host/RemoteDeskHost.wxs @@ -26,8 +26,6 @@ - - - - - - - - diff --git a/packaging/windows-host/build-hysteria2-agent.ps1 b/packaging/windows-host/build-hysteria2-agent.ps1 deleted file mode 100644 index a162830..0000000 --- a/packaging/windows-host/build-hysteria2-agent.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -[CmdletBinding()] -param([string]$OutputDirectory) -$ErrorActionPreference = 'Stop' -$root = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..')) -if ([string]::IsNullOrWhiteSpace($OutputDirectory)) { - $OutputDirectory = Join-Path $root 'artifacts\hysteria2' -} -$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory) -New-Item -ItemType Directory -Force -Path $OutputDirectory | Out-Null -Push-Location (Join-Path $root 'transport\hysteria2-agent') -try { - & go.exe build -buildvcs=false -trimpath -ldflags '-s -w' -o (Join-Path $OutputDirectory 'remotedesk-hysteria2-agent.exe') . - if ($LASTEXITCODE -ne 0) { throw 'Go Hysteria2 build failed.' } -} finally { Pop-Location } -Copy-Item (Join-Path $root 'packaging\windows-host\hysteria2-agent.env.example') (Join-Path $OutputDirectory 'hysteria2-agent.env.example') -Force -Write-Output "Hysteria2 package: $OutputDirectory" diff --git a/packaging/windows-host/hysteria2-agent.env.example b/packaging/windows-host/hysteria2-agent.env.example deleted file mode 100644 index 796921b..0000000 --- a/packaging/windows-host/hysteria2-agent.env.example +++ /dev/null @@ -1,5 +0,0 @@ -REMOTEDESK_HYSTERIA2_LISTEN=0.0.0.0:39502 -REMOTEDESK_HYSTERIA2_PASSWORD=replace-with-a-long-random-password -REMOTEDESK_HYSTERIA2_CERT=server.crt -REMOTEDESK_HYSTERIA2_KEY=server.key -REMOTEDESK_AGENT_UDP=127.0.0.1:39502 diff --git a/packaging/windows-host/package-host.ps1 b/packaging/windows-host/package-host.ps1 index e7263f1..ea1d5a9 100644 --- a/packaging/windows-host/package-host.ps1 +++ b/packaging/windows-host/package-host.ps1 @@ -87,16 +87,9 @@ try { if ($LASTEXITCODE -ne 0) { throw "Windows Agent build failed with exit code $LASTEXITCODE" } $agentBinary = Join-Path $repoRoot 'target\release\remotedesk-windows-agent.exe' if (-not (Test-Path -LiteralPath $agentBinary -PathType Leaf)) { throw "Windows Agent binary is missing: $agentBinary" } - $hysteriaBinary = Join-Path $buildRoot 'remotedesk-hysteria2-agent.exe' - Push-Location (Join-Path $repoRoot 'transport\hysteria2-agent') - try { - & go.exe build -buildvcs=false -trimpath -ldflags '-s -w' -o $hysteriaBinary . - if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $hysteriaBinary -PathType Leaf)) { throw 'Hysteria2 transport build failed.' } - } finally { Pop-Location } Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'Configure-RemoteDeskHost.ps1') -Destination $payloadRoot Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'RemoteDeskHost.psm1') -Destination $payloadRoot Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'README.md') -Destination $payloadRoot - Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'hysteria2-agent.env.example') -Destination $payloadRoot $manifest = [ordered]@{ product = 'RemoteDesk Host' @@ -130,7 +123,7 @@ try { $temporaryInstaller = Join-Path $buildRoot $installerName $installerPath = Join-Path $artifactsRoot $installerName $env:DOTNET_ROLL_FORWARD = 'Major' - & $wix build -arch x64 -ext $uiExtension -culture $Culture -d "ProductVersion=$version" -d "SourceRoot=$payloadRoot" -d "WindowsAgentBinary=$agentBinary" -d "Hysteria2Binary=$hysteriaBinary" -d "IconPath=$(Join-Path $PSScriptRoot 'icon.ico')" ` + & $wix build -arch x64 -ext $uiExtension -culture $Culture -d "ProductVersion=$version" -d "SourceRoot=$payloadRoot" -d "WindowsAgentBinary=$agentBinary" -d "IconPath=$(Join-Path $PSScriptRoot 'icon.ico')" ` -intermediateFolder $intermediateRoot -pdbtype none -out $temporaryInstaller ` (Join-Path $PSScriptRoot 'RemoteDeskHost.wxs') if ($LASTEXITCODE -ne 0) { throw "WiX build failed with exit code $LASTEXITCODE" } diff --git a/packaging/windows-host/package-portable-host.ps1 b/packaging/windows-host/package-portable-host.ps1 index 8f8e5f4..ee1f7e8 100644 --- a/packaging/windows-host/package-portable-host.ps1 +++ b/packaging/windows-host/package-portable-host.ps1 @@ -24,14 +24,6 @@ $agentBinary = Join-Path $repoRoot 'target\release\remotedesk-windows-agent.exe' if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $agentBinary)) { throw 'Unable to build Windows Agent.' } Copy-Item -LiteralPath $agentBinary -Destination $OutputDirectory -Force -$hysteriaBinary = Join-Path $OutputDirectory 'remotedesk-hysteria2-agent.exe' -Push-Location (Join-Path $repoRoot 'transport\hysteria2-agent') -try { - & go.exe build -buildvcs=false -trimpath -ldflags '-s -w' -o $hysteriaBinary . - if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $hysteriaBinary)) { throw 'Unable to build Hysteria2 Agent transport.' } -} finally { Pop-Location } -Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'hysteria2-agent.env.example') -Destination $OutputDirectory -Force - $csc = Join-Path $env:WINDIR 'Microsoft.NET\Framework64\v4.0.30319\csc.exe' if (-not (Test-Path -LiteralPath $csc)) { $csc = Join-Path $env:WINDIR 'Microsoft.NET\Framework\v4.0.30319\csc.exe' } if (-not (Test-Path -LiteralPath $csc)) { throw 'Microsoft C# compiler (csc.exe) is required to create the portable EXE.' } diff --git a/packaging/windows/README-INSTALLER.md b/packaging/windows/README-INSTALLER.md index 19badd5..9b6708a 100644 --- a/packaging/windows/README-INSTALLER.md +++ b/packaging/windows/README-INSTALLER.md @@ -2,11 +2,11 @@ 此 MSI 将 RemoteDesk 安装到当前用户的 `%LOCALAPPDATA%\Programs\RemoteDesk`,不需要管理员权限。 -安装后从开始菜单打开 `RemoteDesk`。Tauri 2 原生窗口会启动仅监听随机 `127.0.0.1` 端口的本地控制服务;窗口退出或控制服务异常结束时,两者会一起关闭。可以在 Windows“已安装的应用”中卸载。 +安装后从开始菜单打开 `RemoteDesk`。程序使用 Rust `winit`、`wgpu` 和 `egui` 原生窗口。可以在 Windows“已安装的应用”中卸载。 ## 当前包含 -- Tauri 2 + React 管理界面;壳以一次性 256 位令牌的 SHA-256 proof 验证随机 loopback 控制服务后才创建 WebView,令牌本身不进入 HTTP 请求;WebView 只允许导航到本次 origin,且不向页面开放 Tauri command。 +- Rust 原生管理界面;会话凭据只交给受限本地 helper,不进入窗口标题、命令行或日志。 - RDP TCP 与 pre-NLA IronRDP 协议探测。 - Windows 系统 `mstsc` 启动回退。 - 单显示器 IronRDP 原生兼容窗口,支持固定/自定义初始尺寸、窗口动态分辨率、启动/切换全屏、退出会话、三次有界自动重连、Windows 双向剪贴板、RDPSND 本地音频,以及焦点绑定的键盘、五键鼠标和滚轮远程输入;源码中的启动配置通过当前用户 ACL、OS 进程身份和 challenge MAC 认证的 Named Pipe 交付,并用 `KILL_ON_JOB_CLOSE` Job Object 约束 helper 生命周期。 diff --git a/packaging/windows/README-PREVIEW.md b/packaging/windows/README-PREVIEW.md index b3e9b05..b4e31fe 100644 --- a/packaging/windows/README-PREVIEW.md +++ b/packaging/windows/README-PREVIEW.md @@ -4,17 +4,14 @@ This archive is a portable technical preview, not an installer or a finished rem ## Start -Run `bin/remotedesk.exe`. The Tauri desktop shell starts the Rust control service on a random loopback port, verifies its one-time startup proof, and opens the management UI only inside the RemoteDesk window. Closing the window also stops the control service. The package does not launch or support a system-browser UI. +Run `bin/remotedesk.exe`. The Rust-native `winit`/`wgpu`/`egui` application is the only supported management UI. The package does not launch or support a browser UI. The server does not listen on LAN interfaces. Its JSON API probes TCP and the pre-NLA RDP negotiation, then can start `mstsc.exe` or a native IronRDP window. The probe does not submit credentials or prove that NLA authentication succeeds. ## Included -- Production-built React management UI. -- `bin/remotedesk.exe`, providing the Tauri 2 desktop shell and the only supported management UI entry point. -- `bin/remotedesk-control-service.exe`, providing static UI hosting and RDP launch orchestration. +- `bin/remotedesk.exe`, providing the native Rust management UI and the only supported UI entry point. - `bin/remotedesk-credential-store.exe`, providing masked local credential entry and direct Windows Credential Manager access. -- `bin/remotedesk-linux-terminal.exe`, providing pinned-WSS Linux Agent pairing, certificate-bound known-device Edge rendezvous, file transfer, and an interactive terminal window. - `bin/remotedesk-rdp-session.exe`, providing the secret-free IronRDP negotiation probe. - `bin/remotedesk-rdp-viewer.exe`, providing a single-monitor native IronRDP compatibility window with fixed/custom initial sizing, dynamic resize, fullscreen, bounded automatic reconnect, Windows clipboard redirection, RDPSND local audio, session exit controls, and focus-bound keyboard/five-button mouse/wheel forwarding. - `bin/remotedesk-native-video.exe`, providing GPU pipeline planning and a real hardware D3D11 device/context probe; rendering remains software. @@ -29,6 +26,6 @@ The server does not listen on LAN interfaces. Its JSON API probes TCP and the pr The IronRDP window forwards keyboard, mouse buttons, movement, and wheel input while focused, and releases remote input state when focus is lost or the session closes. Use `F11` or `Alt+Enter` to toggle fullscreen, `Esc` to leave fullscreen, and `Ctrl+Shift+Q` to close the session; triggering these commands releases remote input and does not forward the command key itself. -Passwords remain in Windows Credential Manager after entry through the local masked helper. The native viewer reads its referenced credential directly. Passwords are never written to WebView storage, HTTP, argv, logs, or temporary `.rdp` files. Do not use this build as an unattended-access or security product. +Passwords remain in Windows Credential Manager after entry through the local masked helper. The native viewer reads its referenced credential directly. Passwords are never written to UI state, argv, logs, or temporary `.rdp` files. Do not use this build as an unattended-access or security product. Paired Linux Agent public keys are stored as integrity-sensitive, non-secret mappings at `RemoteDesk/Linux/agent/`. The Linux Terminal panel can query or explicitly forget a mapping. With a configured Edge service and the Agent-local `device_public_key` plus certificate fingerprint, initial pairing and subsequent terminal/file sessions can all be established through Agent-outbound relay connections; direct pairing remains the fallback. diff --git a/packaging/windows/package-installer.ps1 b/packaging/windows/package-installer.ps1 index 8ee5292..acb1956 100644 --- a/packaging/windows/package-installer.ps1 +++ b/packaging/windows/package-installer.ps1 @@ -210,16 +210,8 @@ function New-WixPayloadSource { Push-Location $repoRoot try { - Invoke-Checked -Description 'Web production build' -Command { - Push-Location (Join-Path $repoRoot 'client\web') - try { - & npm.cmd run build - } finally { - Pop-Location - } - } - Invoke-Checked -Description 'Native helpers release build' -Command { - & cargo.exe build --release --jobs 2 -p remotedesk-app -p remotedesk-control-service -p remotedesk-credential-store -p remotedesk-linux-terminal -p remotedesk-native-video -p remotedesk-rdp-session -p remotedesk-rdp-viewer -p remotedesk-windows-agent-viewer + Invoke-Checked -Description 'Native Rust client release build' -Command { + & cargo.exe build --release --jobs 2 -p remotedesk-native-gui -p remotedesk-credential-store -p remotedesk-native-video -p remotedesk-rdp-session -p remotedesk-rdp-viewer -p remotedesk-windows-agent-viewer } $metadata = (& cargo.exe metadata --no-deps --format-version 1 | ConvertFrom-Json) @@ -259,7 +251,6 @@ try { } $buildRoot = Join-Path $artifactsRoot ".package-installer-$PID-$([guid]::NewGuid().ToString('N'))" $payloadRoot = Join-Path $buildRoot 'payload' - $webRoot = Join-Path $payloadRoot 'web' $binRoot = Join-Path $payloadRoot 'bin' $docsRoot = Join-Path $payloadRoot 'docs' $intermediateRoot = Join-Path $buildRoot 'wixobj' @@ -271,12 +262,9 @@ try { Assert-PathWithin -Path $installerPath -Root $artifactsRoot New-Item -ItemType Directory -Force -Path $artifactsRoot | Out-Null - New-Item -ItemType Directory -Force -Path $webRoot, $binRoot, $docsRoot, $intermediateRoot | Out-Null - Copy-Item -Path (Join-Path $repoRoot 'client\web\dist\*') -Destination $webRoot -Recurse -Force - Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk.exe') -Destination $binRoot - Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-control-service.exe') -Destination $binRoot + New-Item -ItemType Directory -Force -Path $binRoot, $docsRoot, $intermediateRoot | Out-Null + Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-native.exe') -Destination (Join-Path $binRoot 'remotedesk.exe') Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-credential-store.exe') -Destination $binRoot - Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-linux-terminal.exe') -Destination $binRoot Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-native-video.exe') -Destination $binRoot Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-rdp-session.exe') -Destination $binRoot Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-rdp-viewer.exe') -Destination $binRoot @@ -297,10 +285,8 @@ try { install_scope = 'per-user' target = $hostTarget runnable_ui = 'bin/remotedesk.exe' - desktop_shell = 'tauri-2' - control_service = 'bin/remotedesk-control-service.exe' + desktop_shell = 'winit-wgpu-egui' credential_store = 'bin/remotedesk-credential-store.exe' - linux_terminal = 'bin/remotedesk-linux-terminal.exe' native_helper = 'bin/remotedesk-native-video.exe' rdp_probe_helper = 'bin/remotedesk-rdp-session.exe' rdp_viewer = 'bin/remotedesk-rdp-viewer.exe' diff --git a/packaging/windows/package-preview.ps1 b/packaging/windows/package-preview.ps1 index 1b3a029..e3f15d8 100644 --- a/packaging/windows/package-preview.ps1 +++ b/packaging/windows/package-preview.ps1 @@ -34,16 +34,8 @@ function Assert-ArtifactPath { Push-Location $repoRoot try { - Invoke-Checked -Description 'Web production build' -Command { - Push-Location (Join-Path $repoRoot 'client\web') - try { - & npm.cmd run build - } finally { - Pop-Location - } - } - Invoke-Checked -Description 'Native helpers release build' -Command { - & cargo.exe build --release --jobs 2 -p remotedesk-app -p remotedesk-control-service -p remotedesk-credential-store -p remotedesk-linux-terminal -p remotedesk-native-video -p remotedesk-rdp-session -p remotedesk-rdp-viewer -p remotedesk-windows-agent-viewer + Invoke-Checked -Description 'Native Rust client release build' -Command { + & cargo.exe build --release --jobs 2 -p remotedesk-native-gui -p remotedesk-credential-store -p remotedesk-native-video -p remotedesk-rdp-session -p remotedesk-rdp-viewer -p remotedesk-windows-agent-viewer } $metadata = (& cargo.exe metadata --no-deps --format-version 1 | ConvertFrom-Json) @@ -85,16 +77,12 @@ try { Remove-Item -LiteralPath $zipPath -Force } - $webRoot = Join-Path $stagingRoot 'web' $binRoot = Join-Path $stagingRoot 'bin' $docsRoot = Join-Path $stagingRoot 'docs' - New-Item -ItemType Directory -Force -Path $webRoot, $binRoot, $docsRoot | Out-Null + New-Item -ItemType Directory -Force -Path $binRoot, $docsRoot | Out-Null - Copy-Item -Path (Join-Path $repoRoot 'client\web\dist\*') -Destination $webRoot -Recurse -Force - Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk.exe') -Destination $binRoot - Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-control-service.exe') -Destination $binRoot + Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-native.exe') -Destination (Join-Path $binRoot 'remotedesk.exe') Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-credential-store.exe') -Destination $binRoot - Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-linux-terminal.exe') -Destination $binRoot Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-native-video.exe') -Destination $binRoot Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-rdp-session.exe') -Destination $binRoot Copy-Item -LiteralPath (Join-Path $repoRoot 'target\release\remotedesk-rdp-viewer.exe') -Destination $binRoot @@ -114,10 +102,8 @@ try { distribution = 'portable' target = $hostTarget runnable_ui = 'bin/remotedesk.exe' - desktop_shell = 'tauri-2' - control_service = 'bin/remotedesk-control-service.exe' + desktop_shell = 'winit-wgpu-egui' credential_store = 'bin/remotedesk-credential-store.exe' - linux_terminal = 'bin/remotedesk-linux-terminal.exe' native_helper = 'bin/remotedesk-native-video.exe' rdp_probe_helper = 'bin/remotedesk-rdp-session.exe' rdp_viewer = 'bin/remotedesk-rdp-viewer.exe' diff --git a/transport/hysteria2-agent/README.md b/transport/hysteria2-agent/README.md deleted file mode 100644 index a98e9f5..0000000 --- a/transport/hysteria2-agent/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# RemoteDesk Hysteria2 transport - -This helper uses the official `github.com/apernet/hysteria/core/v2` Go server. -It exposes only authenticated Hysteria2 QUIC/UDP sessions. The core's TCP -proxy hook is deliberately rejected, so the public transport never falls back -to TCP or SOCKS5. Authenticated datagrams are delivered to the local Agent UDP -endpoint (`127.0.0.1:39502` by default). - -Build with `go build -buildvcs=false -trimpath -ldflags "-s -w" -o remotedesk-hysteria2-agent ./`. -The certificate and key must be a real TLS pair; do not run without them. - -Example: - -```sh -REMOTEDESK_HYSTERIA2_PASSWORD='change-me' \ -REMOTEDESK_HYSTERIA2_CERT=/etc/remotedesk/server.crt \ -REMOTEDESK_HYSTERIA2_KEY=/etc/remotedesk/server.key \ -./remotedesk-hysteria2-agent --listen 0.0.0.0:39502 -``` - -The same binary provides the official Hysteria2 client path. It listens on a -local UDP socket and forwards datagrams through one authenticated Hysteria2 -UDP session: - -```sh -./remotedesk-hysteria2-agent --mode client --listen 127.0.0.1:39503 \ - --server relay.example:39502 --password 'change-me' \ - --server-name relay.example --agent-udp 127.0.0.1:39502 -``` - -`--insecure` is intentionally opt-in and should only be used for a local test -certificate. No TCP listener, TCP proxy, or SOCKS5 endpoint is created. - -## Rust encoded ring bridge - -The Windows Agent compatibility encoder can publish H.264 Annex-B access units -to its session-bound memory-mapped ring. The Hysteria2 client can consume that -ring directly and send RDV1 video datagrams on the same authenticated UDP -session: - -```text -remotedesk-hysteria2-agent --mode client \ - --listen 127.0.0.1:39503 \ - --server relay.example:39502 --password 'change-me' \ - --server-name relay.example \ - --agent-udp 127.0.0.1:39502 \ - --encoded-ring C:\Users\user\AppData\Local\Temp\remotedesk-session-...ring \ - --ring-stream-id 1 --ring-generation 7 -``` - -`--encoded-ring` is optional and is intended for the authenticated session -orchestrator, which obtains the path, stream ID, and generation from the Rust -control response. The Go consumer validates the ring header and geometry, -copies each encoded access unit before releasing the slot, fragments it into -RDV1, and drops video on cancellation or transport failure. It never maps or -transmits raw BGRA/NV12 surfaces. - -For production local orchestration, start the Agent and bridge with the same -random 32-byte base64url bootstrap. The Agent removes its inherited copy from -the environment after startup, restricts the pipe and ring DACL to the current -Windows user, rejects remote pipe clients, and authenticates each connection -with a nonce-bound HMAC-SHA256 challenge: - -```powershell -$token = New-Object byte[] 32 -[Security.Cryptography.RandomNumberGenerator]::Fill($token) -$env:REMOTEDESK_AGENT_PIPE_TOKEN = [Convert]::ToBase64String($token).TrimEnd('=').Replace('+','-').Replace('/','_') -Start-Process -WindowStyle Hidden remotedesk-windows-agent -ArgumentList 'pipe' - -remotedesk-hysteria2-agent --mode client ` - --server relay.example:39502 --password 'change-me' ` - --server-name relay.example --agent-udp 127.0.0.1:39502 ` - --windows-agent-pipe '\\.\pipe\RemoteDesk\windows-agent' ` - --session-id session-1 --ring-fps 120 --ring-stream-id 1 -Remove-Item Env:REMOTEDESK_AGENT_PIPE_TOKEN -``` - -`--windows-agent-control` remains available only for a same-machine development -bridge. Its address must be a literal loopback IP: - -```text -remotedesk-hysteria2-agent --mode client \ - --server relay.example:39502 --password 'change-me' \ - --server-name relay.example --agent-udp 127.0.0.1:39502 \ - --windows-agent-control 127.0.0.1:39501 \ - --session-id session-1 --ring-fps 120 --ring-stream-id 1 -``` - -The receiver-side Windows viewer binds only loopback UDP, pins the first valid -RDV1 source, uses bounded reassembly and a one-frame UI mailbox, and presents -H.264 through Media Foundation/D3D11: - -```text -remotedesk-windows-agent-viewer --target 10.0.0.20:39501 \ - --session-id session-1 --fps 120 --media-udp 127.0.0.1:39502 -``` - -RDWF remains the default when `--media-udp` is absent. The authenticated Named -Pipe is the production descriptor path; loopback TCP and manual `--encoded-ring` -are diagnostic/development paths and do not prove descriptor authenticity. - -## RemoteDesk media datagrams - -Packets beginning with RDV1 are RemoteDesk H.264/HEVC/AV1 video fragments. -The relay enforces the complete datagram limit (58-byte header plus at most -1200 bytes of payload) and otherwise forwards the bytes without decoding them. -Audio and control packets use their own framing and remain opaque to this -adapter. A missing video fragment is not retransmitted by this helper; the -receiver drops the incomplete access unit after its bounded reassembly timeout. - -The transport is an unreliable media path. Input and control traffic must use a -separate reliable path in the Agent session protocol; this UDP adapter does not -provide ordering or delivery guarantees for those messages. - -RemoteDesk Opus packets use RDA1: a 40-byte header followed by one bounded -Opus packet (maximum 4 KiB). Valid durations are 10/20/40/60 ms. The adapter -validates the framing and forwards the packet without reordering or waiting for -video. With an authenticated Windows Agent descriptor it independently drains -the Opus mmap ring and sends RDA1 datagrams. The Windows viewer uses a bounded -jitter queue plus Opus FEC/PLC and WASAPI playback; audio failure does not stop -video or input. Real-device validation and precise shared-QPC drift correction -remain release requirements. diff --git a/transport/hysteria2-agent/go.mod b/transport/hysteria2-agent/go.mod deleted file mode 100644 index 62af2ef..0000000 --- a/transport/hysteria2-agent/go.mod +++ /dev/null @@ -1,26 +0,0 @@ -module github.com/remotedesk/remotedesk/transport/hysteria2-agent - -go 1.25.0 - -require ( - github.com/Microsoft/go-winio v0.6.2 - github.com/apernet/hysteria/core/v2 v2.12.1 - golang.org/x/sys v0.47.0 -) - -require ( - github.com/andybalholm/brotli v1.1.0 // indirect - github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quic-go/qpack v0.6.0 // indirect - github.com/refraction-networking/utls v1.8.2 // indirect - github.com/stretchr/objx v0.5.2 // indirect - github.com/stretchr/testify v1.11.1 // indirect - golang.org/x/crypto v0.54.0 // indirect - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect - golang.org/x/net v0.57.0 // indirect - golang.org/x/text v0.40.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/transport/hysteria2-agent/go.sum b/transport/hysteria2-agent/go.sum deleted file mode 100644 index 0107c83..0000000 --- a/transport/hysteria2-agent/go.sum +++ /dev/null @@ -1,49 +0,0 @@ -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/apernet/hysteria/core/v2 v2.12.1 h1:7jA/jSC+1tFP8Z9COzI0AdT1p9c0SY7D8vngRd1YX7s= -github.com/apernet/hysteria/core/v2 v2.12.1/go.mod h1:YVOel66fPGf9dO2rz/9Bsu6yChyMe7gSmYagAYfPOGE= -github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e h1:5mgtR5gwIgBKMiGI1QdXldZZ+SNor06Nbu1wCBulQBg= -github.com/apernet/quic-go v0.61.1-0.20260806010916-184d081eef3e/go.mod h1:x7qxEvX6MCVtDuBKHj3E+88+BtrbEMuAL5qGUKItjW8= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0= -github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= -github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= -github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo= -github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= -go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= -golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= -golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= -golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/transport/hysteria2-agent/main.go b/transport/hysteria2-agent/main.go deleted file mode 100644 index 206bfb9..0000000 --- a/transport/hysteria2-agent/main.go +++ /dev/null @@ -1,339 +0,0 @@ -// Command remotedesk-hysteria2-agent exposes a RemoteDesk datagram endpoint -// through the official Hysteria2 QUIC server. No TCP proxy or SOCKS5 mode is -// enabled: every authenticated Hysteria2 session is delivered to the local -// Agent UDP endpoint. -package main - -import ( - "context" - "crypto/tls" - "errors" - "flag" - "fmt" - "log" - "net" - "os" - "os/signal" - "strings" - "sync" - "syscall" - "time" - - "github.com/apernet/hysteria/core/v2/client" - "github.com/apernet/hysteria/core/v2/server" -) - -type passwordAuth struct{ password string } - -func (a passwordAuth) Authenticate(_ net.Addr, auth string, _ uint64) (bool, string) { - return auth != "" && auth == a.password, "remotedesk" -} - -// localDatagramOutbound maps a Hysteria2 UDP request to a local UDP Agent. -// The public side remains QUIC/UDP; this socket is local-only and never a -// fallback transport for the remote connection. -type localDatagramOutbound struct{ target *net.UDPAddr } - -func (o localDatagramOutbound) TCP(string) (net.Conn, error) { - return nil, fmt.Errorf("TCP proxy is disabled for RemoteDesk Hysteria2") -} -func (o localDatagramOutbound) CheckUDP(string) error { return nil } -func (o localDatagramOutbound) UDP(_ string) (server.UDPConn, error) { - conn, err := net.DialUDP("udp", nil, o.target) - if err != nil { - return nil, err - } - return &udpConn{conn: conn}, nil -} - -type udpConn struct{ conn *net.UDPConn } - -type datagramKind uint8 - -const ( - datagramOpaque datagramKind = iota - datagramVideo -) - -// classifyDatagram keeps the relay payload opaque. Only the RemoteDesk video -// framing has a local size contract; control and audio packets are forwarded -// without parsing so they can evolve independently. -func classifyDatagram(data []byte) (datagramKind, error) { - if len(data) >= len(videoDatagramMagic) && string(data[:len(videoDatagramMagic)]) == videoDatagramMagic { - if err := validateVideoDatagramSize(data); err != nil { - return datagramVideo, err - } - return datagramVideo, nil - } - if len(data) >= len(audioDatagramMagic) && string(data[:len(audioDatagramMagic)]) == audioDatagramMagic { - if err := validateAudioDatagramSize(data); err != nil { - return datagramOpaque, err - } - if _, err := decodeAudioDatagram(data); err != nil { - return datagramOpaque, err - } - } - return datagramOpaque, nil -} - -func prepareDatagram(data []byte) ([]byte, datagramKind, error) { - if len(data) == 0 { - return nil, datagramOpaque, fmt.Errorf("empty RemoteDesk datagram") - } - kind, err := classifyDatagram(data) - if err != nil { - return nil, kind, err - } - // Always copy a UDP read buffer before handing it to Hysteria2. - return append([]byte(nil), data...), kind, nil -} - -func (c *udpConn) ReadFrom(b []byte) (int, string, error) { - n, addr, err := c.conn.ReadFromUDP(b) - if addr == nil { - return n, "", err - } - return n, addr.String(), err -} -func (c *udpConn) WriteTo(b []byte, _ string) (int, error) { return c.conn.Write(b) } -func (c *udpConn) Close() error { return c.conn.Close() } - -func main() { - mode := flag.String("mode", "server", "transport mode: server or client") - listen := flag.String("listen", env("REMOTEDESK_HYSTERIA2_LISTEN", "0.0.0.0:39502"), "Hysteria2 UDP listen address") - serverAddr := flag.String("server", env("REMOTEDESK_HYSTERIA2_SERVER", "127.0.0.1:39502"), "Hysteria2 server address (client mode)") - password := flag.String("password", os.Getenv("REMOTEDESK_HYSTERIA2_PASSWORD"), "Hysteria2 password") - certFile := flag.String("cert", os.Getenv("REMOTEDESK_HYSTERIA2_CERT"), "TLS certificate PEM") - keyFile := flag.String("key", os.Getenv("REMOTEDESK_HYSTERIA2_KEY"), "TLS private key PEM") - agent := flag.String("agent-udp", env("REMOTEDESK_AGENT_UDP", "127.0.0.1:39502"), "local Agent UDP endpoint") - serverName := flag.String("server-name", env("REMOTEDESK_HYSTERIA2_SERVER_NAME", "localhost"), "TLS server name (client mode)") - insecure := flag.Bool("insecure", false, "disable TLS certificate verification (client mode only)") - ringPath := flag.String("encoded-ring", os.Getenv("REMOTEDESK_ENCODED_RING"), "local Rust encoded-access-unit ring path (optional)") - ringStreamID := flag.Uint("ring-stream-id", 0, "RemoteDesk video stream ID for the encoded ring") - ringGeneration := flag.Uint64("ring-generation", 0, "RemoteDesk encoded ring generation") - agentControl := flag.String("windows-agent-control", os.Getenv("REMOTEDESK_WINDOWS_AGENT_CONTROL"), "loopback Windows Agent TCP control endpoint (optional)") - agentPipe := flag.String("windows-agent-pipe", os.Getenv("REMOTEDESK_WINDOWS_AGENT_PIPE"), "authenticated local Windows Agent Named Pipe (recommended)") - sessionID := flag.String("session-id", os.Getenv("REMOTEDESK_SESSION_ID"), "encoded desktop session ID") - ringFPS := flag.Uint("ring-fps", 30, "encoded desktop frame rate (1-120)") - allowSoftwareFallback := flag.Bool("allow-software-fallback", true, "allow Media Foundation software H.264 fallback") - flag.Parse() - if *mode == "client" { - if *agentControl != "" && *agentPipe != "" { - log.Fatal("--windows-agent-control and --windows-agent-pipe are mutually exclusive") - } - controlEndpoint := *agentPipe - if controlEndpoint == "" { - controlEndpoint = *agentControl - } - if uint64(*ringStreamID) > uint64(^uint32(0)) { - log.Fatal("--ring-stream-id exceeds uint32") - } - if *ringFPS == 0 || *ringFPS > 120 { - log.Fatal("--ring-fps must be between 1 and 120") - } - runClient(*listen, *serverAddr, *password, *agent, *serverName, *insecure, *ringPath, uint32(*ringStreamID), *ringGeneration, controlEndpoint, *sessionID, uint8(*ringFPS), *allowSoftwareFallback) - return - } - if *mode != "server" { - log.Fatalf("invalid --mode %q (must be server or client)", *mode) - } - if *password == "" || *certFile == "" || *keyFile == "" { - log.Fatal("password, cert and key are required; refusing unauthenticated Hysteria2") - } - target, err := net.ResolveUDPAddr("udp", *agent) - if err != nil { - log.Fatalf("invalid --agent-udp: %v", err) - } - cert, err := tls.LoadX509KeyPair(*certFile, *keyFile) - if err != nil { - log.Fatalf("load TLS certificate: %v", err) - } - packetConn, err := net.ListenPacket("udp", *listen) - if err != nil { - log.Fatalf("listen Hysteria2 UDP: %v", err) - } - srv, err := server.NewServer(&server.Config{ - TLSConfig: server.TLSConfig{Certificates: []tls.Certificate{cert}}, - Conn: packetConn, - Authenticator: passwordAuth{password: *password}, - Outbound: localDatagramOutbound{target: target}, - DisableUDP: false, - }) - if err != nil { - log.Fatalf("create Hysteria2 server: %v", err) - } - log.Printf("RemoteDesk Hysteria2 server listening on %s (UDP/QUIC only)", *listen) - log.Printf("authenticated datagrams delivered to local Agent UDP %s", target) - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGTERM) - done := make(chan error, 1) - go func() { done <- srv.Serve() }() - select { - case sig := <-stop: - log.Printf("shutting down on %s", sig) - _ = srv.Close() - case err := <-done: - if err != nil && !strings.Contains(err.Error(), "closed") { - log.Fatal(err) - } - } - // Allow the QUIC listener to finish releasing its packet socket. - time.Sleep(50 * time.Millisecond) -} - -func runClient(localListen, serverAddr, password, remoteAgent, serverName string, insecure bool, ringPath string, ringStreamID uint32, ringGeneration uint64, agentControl, sessionID string, ringFPS uint8, allowSoftwareFallback bool) { - if password == "" { - log.Fatal("--password is required in client mode") - } - serverUDP, err := net.ResolveUDPAddr("udp", serverAddr) - if err != nil { - log.Fatalf("invalid --server: %v", err) - } - localUDP, err := net.ResolveUDPAddr("udp", localListen) - if err != nil { - log.Fatalf("invalid --listen: %v", err) - } - localConn, err := net.ListenUDP("udp", localUDP) - if err != nil { - log.Fatalf("listen local UDP: %v", err) - } - defer localConn.Close() - hy, _, err := client.NewClient(&client.Config{ - ServerAddr: serverUDP, - Auth: password, - TLSConfig: client.TLSConfig{ - ServerName: serverName, - InsecureSkipVerify: insecure, - }, - }) - if err != nil { - log.Fatalf("connect Hysteria2 server: %v", err) - } - defer hy.Close() - stream, err := hy.UDP() - if err != nil { - log.Fatalf("open Hysteria2 UDP session: %v", err) - } - defer stream.Close() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - var sendMu sync.Mutex - log.Printf("RemoteDesk Hysteria2 client listening on %s (UDP only)", localConn.LocalAddr()) - log.Printf("authenticated Hysteria2 server %s; remote Agent %s", serverAddr, remoteAgent) - var agentControlConnection net.Conn - var audioDescriptor *audioRingDescriptor - if ringPath == "" && agentControl != "" { - connection, descriptor, err := openWindowsEncodedSession(agentControl, sessionID, ringFPS, allowSoftwareFallback) - if err != nil { - log.Fatalf("start Windows encoded desktop: %v", err) - } - agentControlConnection = connection - ringPath = descriptor.RingPath - ringGeneration = descriptor.Generation - if ringStreamID == 0 { - ringStreamID = 1 - } - audioDescriptor = descriptor.AudioRing - log.Printf("Windows encoded desktop session %s generation %d opened", sessionID, ringGeneration) - } - if agentControlConnection != nil { - defer agentControlConnection.Close() - } - if ringPath != "" { - if ringStreamID == 0 || ringGeneration == 0 { - log.Fatal("--ring-stream-id and --ring-generation are required with --encoded-ring") - } - ring, err := openEncodedRing(ringPath) - if err != nil { - log.Fatalf("open encoded media ring: %v", err) - } - go func() { - defer ring.Close() - err := pumpEncodedRing(ctx, ring, ringStreamID, ringGeneration, func(data []byte) error { - sendMu.Lock() - defer sendMu.Unlock() - return stream.Send(data, remoteAgent) - }) - if err != nil && !errors.Is(err, context.Canceled) { - log.Printf("encoded media ring stopped: %v", err) - } - cancel() - if agentControlConnection != nil { - _ = agentControlConnection.Close() - } - _ = localConn.Close() - }() - } - if audioDescriptor != nil { - audioRing, err := openAudioRing(audioDescriptor.RingPath) - if err != nil { - log.Printf("audio ring unavailable, continuing without audio: %v", err) - } else { - go func() { - defer audioRing.Close() - err := pumpAudioRing(ctx, audioRing, ringStreamID, audioDescriptor.Generation, func(data []byte) error { - sendMu.Lock() - defer sendMu.Unlock() - return stream.Send(data, remoteAgent) - }) - if err != nil && !errors.Is(err, context.Canceled) { - log.Printf("audio media ring stopped: %v", err) - } - }() - } - } - var peerMu sync.RWMutex - var peer *net.UDPAddr - go func() { - for { - data, _, err := stream.Receive() - if err != nil { - cancel() - _ = localConn.Close() - return - } - prepared, _, err := prepareDatagram(data) - if err != nil { - log.Printf("drop invalid datagram from Hysteria2: %v", err) - continue - } - peerMu.RLock() - currentPeer := peer - if currentPeer != nil { - _, _ = localConn.WriteToUDP(prepared, currentPeer) - } - peerMu.RUnlock() - } - }() - buf := make([]byte, 64*1024) - for { - n, sourcePeer, err := localConn.ReadFromUDP(buf) - if err != nil { - return - } - peerMu.Lock() - peer = sourcePeer - peerMu.Unlock() - prepared, _, err := prepareDatagram(buf[:n]) - if err != nil { - log.Printf("drop invalid local datagram from %s: %v", sourcePeer, err) - continue - } - // The Hysteria2 destination is metadata only. The server's outbound - // implementation pins delivery to its configured local Agent UDP port. - sendMu.Lock() - err = stream.Send(prepared, remoteAgent) - sendMu.Unlock() - if err != nil { - log.Printf("send Hysteria2 datagram from %s: %v", sourcePeer, err) - return - } - } -} - -func env(name, fallback string) string { - if value := os.Getenv(name); value != "" { - return value - } - return fallback -} diff --git a/transport/hysteria2-agent/media.go b/transport/hysteria2-agent/media.go deleted file mode 100644 index 7d1df8b..0000000 --- a/transport/hysteria2-agent/media.go +++ /dev/null @@ -1,352 +0,0 @@ -package main - -import ( - "encoding/binary" - "errors" - "fmt" - "time" -) - -const ( - videoDatagramMagic = "RDV1" - videoDatagramVersion = 1 - videoDatagramHeaderBytes = 58 - videoDatagramPayloadMax = 1200 - videoDatagramMaxBytes = videoDatagramHeaderBytes + videoDatagramPayloadMax - videoFrameMaxBytes = 16 * 1024 * 1024 - videoFrameMaxFragments = 4096 - videoReassemblyMaxFrames = 8 - videoReassemblyTimeout = 100 * time.Millisecond -) - -const ( - audioDatagramMagic = "RDA1" - audioDatagramVersion = 1 - audioDatagramHeaderSize = 40 - audioPacketMaxBytes = 4 * 1024 - audioDatagramMaxBytes = audioDatagramHeaderSize + audioPacketMaxBytes -) - -const ( - videoFlagConfig uint16 = 1 << 0 - videoFlagKeyFrame uint16 = 1 << 1 - videoFlagFirstFragment uint16 = 1 << 2 - videoFlagLastFragment uint16 = 1 << 3 - videoFlagDiscontinuity uint16 = 1 << 4 -) - -const audioFlagDiscontinuity uint16 = 1 << 0 - -// VideoAccessUnit is a complete encoded frame before transport fragmentation. -// The Hysteria2 relay must treat the resulting datagrams as opaque bytes. -type VideoAccessUnit struct { - StreamID uint32 - Generation uint64 - Sequence uint64 - FrameID uint64 - PTS uint64 - DTS uint64 - Flags uint16 - Payload []byte -} - -// AudioPacket is one independently decodable Opus packet. Audio is never -// reassembled with video and is forwarded on its own priority path. -type AudioPacket struct { - StreamID uint32 - Generation uint64 - Sequence uint64 - PTS uint64 - DurationMS uint16 - Flags uint16 - Payload []byte -} - -func encodeAudioDatagram(packet AudioPacket) ([]byte, error) { - if err := validateAudioPacket(packet); err != nil { - return nil, err - } - output := make([]byte, audioDatagramHeaderSize+len(packet.Payload)) - copy(output, audioDatagramMagic) - output[4] = audioDatagramVersion - binary.LittleEndian.PutUint16(output[6:8], audioDatagramHeaderSize) - binary.LittleEndian.PutUint32(output[8:12], packet.StreamID) - binary.LittleEndian.PutUint64(output[12:20], packet.Generation) - binary.LittleEndian.PutUint64(output[20:28], packet.Sequence) - binary.LittleEndian.PutUint64(output[28:36], packet.PTS) - binary.LittleEndian.PutUint16(output[36:38], packet.DurationMS) - binary.LittleEndian.PutUint16(output[38:40], packet.Flags) - copy(output[audioDatagramHeaderSize:], packet.Payload) - return output, nil -} - -func decodeAudioDatagram(input []byte) (AudioPacket, error) { - if len(input) < audioDatagramHeaderSize || len(input) > audioDatagramMaxBytes { - return AudioPacket{}, errors.New("audio datagram size is invalid") - } - if string(input[:4]) != audioDatagramMagic || input[4] != audioDatagramVersion || input[5] != 0 { - return AudioPacket{}, errors.New("audio datagram version is invalid") - } - headerBytes := int(binary.LittleEndian.Uint16(input[6:8])) - if headerBytes != audioDatagramHeaderSize || headerBytes > len(input) { - return AudioPacket{}, errors.New("audio datagram header is invalid") - } - packet := AudioPacket{ - StreamID: binary.LittleEndian.Uint32(input[8:12]), - Generation: binary.LittleEndian.Uint64(input[12:20]), - Sequence: binary.LittleEndian.Uint64(input[20:28]), - PTS: binary.LittleEndian.Uint64(input[28:36]), - DurationMS: binary.LittleEndian.Uint16(input[36:38]), - Flags: binary.LittleEndian.Uint16(input[38:40]), - Payload: input[headerBytes:], - } - if err := validateAudioPacket(packet); err != nil { - return AudioPacket{}, err - } - return packet, nil -} - -func validateAudioPacket(packet AudioPacket) error { - if packet.StreamID == 0 || packet.Generation == 0 || packet.Sequence == 0 { - return errors.New("audio packet identity is invalid") - } - if packet.DurationMS != 10 && packet.DurationMS != 20 && packet.DurationMS != 40 && packet.DurationMS != 60 { - return errors.New("audio packet duration is invalid") - } - if len(packet.Payload) == 0 || len(packet.Payload) > audioPacketMaxBytes { - return errors.New("audio packet payload size is invalid") - } - if packet.Flags & ^audioFlagDiscontinuity != 0 { - return errors.New("audio packet flags are invalid") - } - return nil -} - -type videoDatagram struct { - StreamID uint32 - Generation uint64 - Sequence uint64 - FrameID uint64 - PTS uint64 - DTS uint64 - FragmentID uint16 - FragmentCount uint16 - Flags uint16 - Payload []byte -} - -func fragmentVideoAccessUnit(unit VideoAccessUnit) ([][]byte, error) { - if unit.StreamID == 0 || unit.Generation == 0 { - return nil, errors.New("video stream identity is invalid") - } - if len(unit.Payload) == 0 || len(unit.Payload) > videoFrameMaxBytes { - return nil, errors.New("video access unit size is invalid") - } - fragmentCount := (len(unit.Payload) + videoDatagramPayloadMax - 1) / videoDatagramPayloadMax - if fragmentCount > videoFrameMaxFragments { - return nil, errors.New("video access unit has too many fragments") - } - - datagrams := make([][]byte, 0, fragmentCount) - for fragmentID, offset := 0, 0; offset < len(unit.Payload); fragmentID++ { - end := min(offset+videoDatagramPayloadMax, len(unit.Payload)) - flags := unit.Flags - if fragmentID == 0 { - flags |= videoFlagFirstFragment - } - if end == len(unit.Payload) { - flags |= videoFlagLastFragment - } - packet := videoDatagram{ - StreamID: unit.StreamID, - Generation: unit.Generation, - Sequence: unit.Sequence, - FrameID: unit.FrameID, - PTS: unit.PTS, - DTS: unit.DTS, - FragmentID: uint16(fragmentID), - FragmentCount: uint16(fragmentCount), - Flags: flags, - Payload: unit.Payload[offset:end], - } - datagrams = append(datagrams, encodeVideoDatagram(packet)) - offset = end - } - return datagrams, nil -} - -func encodeVideoDatagram(packet videoDatagram) []byte { - output := make([]byte, videoDatagramHeaderBytes+len(packet.Payload)) - copy(output, videoDatagramMagic) - output[4] = videoDatagramVersion - binary.LittleEndian.PutUint16(output[6:8], videoDatagramHeaderBytes) - binary.LittleEndian.PutUint32(output[8:12], packet.StreamID) - binary.LittleEndian.PutUint64(output[12:20], packet.Generation) - binary.LittleEndian.PutUint64(output[20:28], packet.Sequence) - binary.LittleEndian.PutUint64(output[28:36], packet.FrameID) - binary.LittleEndian.PutUint64(output[36:44], packet.PTS) - binary.LittleEndian.PutUint64(output[44:52], packet.DTS) - binary.LittleEndian.PutUint16(output[52:54], packet.FragmentID) - binary.LittleEndian.PutUint16(output[54:56], packet.FragmentCount) - binary.LittleEndian.PutUint16(output[56:58], packet.Flags) - copy(output[videoDatagramHeaderBytes:], packet.Payload) - return output -} - -func decodeVideoDatagram(input []byte) (videoDatagram, error) { - if len(input) < videoDatagramHeaderBytes || len(input) > videoDatagramHeaderBytes+videoDatagramPayloadMax { - return videoDatagram{}, errors.New("video datagram size is invalid") - } - if string(input[:4]) != videoDatagramMagic || input[4] != videoDatagramVersion { - return videoDatagram{}, errors.New("video datagram version is invalid") - } - headerBytes := int(binary.LittleEndian.Uint16(input[6:8])) - if headerBytes != videoDatagramHeaderBytes || headerBytes > len(input) { - return videoDatagram{}, errors.New("video datagram header is invalid") - } - packet := videoDatagram{ - StreamID: binary.LittleEndian.Uint32(input[8:12]), - Generation: binary.LittleEndian.Uint64(input[12:20]), - Sequence: binary.LittleEndian.Uint64(input[20:28]), - FrameID: binary.LittleEndian.Uint64(input[28:36]), - PTS: binary.LittleEndian.Uint64(input[36:44]), - DTS: binary.LittleEndian.Uint64(input[44:52]), - FragmentID: binary.LittleEndian.Uint16(input[52:54]), - FragmentCount: binary.LittleEndian.Uint16(input[54:56]), - Flags: binary.LittleEndian.Uint16(input[56:58]), - Payload: input[headerBytes:], - } - if packet.StreamID == 0 || packet.Generation == 0 || packet.FragmentCount == 0 || - packet.FragmentCount > videoFrameMaxFragments || packet.FragmentID >= packet.FragmentCount || - len(packet.Payload) == 0 || len(packet.Payload) > videoDatagramPayloadMax { - return videoDatagram{}, errors.New("video datagram fields are invalid") - } - if packet.Flags&videoFlagFirstFragment != 0 != (packet.FragmentID == 0) || - packet.Flags&videoFlagLastFragment != 0 != (packet.FragmentID+1 == packet.FragmentCount) { - return videoDatagram{}, errors.New("video datagram fragment flags are invalid") - } - return packet, nil -} - -type videoFrameKey struct { - streamID uint32 - generation uint64 - frameID uint64 -} - -type videoFrameAssembly struct { - created time.Time - sequence uint64 - pts uint64 - dts uint64 - flags uint16 - fragmentCount uint16 - fragments map[uint16][]byte - bytes int -} - -// videoReassembler is intentionally bounded. It never waits for a missing -// datagram past the deadline and never retransmits an expired video frame. -type videoReassembler struct { - frames map[videoFrameKey]*videoFrameAssembly -} - -func newVideoReassembler() *videoReassembler { - return &videoReassembler{frames: make(map[videoFrameKey]*videoFrameAssembly)} -} - -func (r *videoReassembler) push(input []byte, now time.Time) (*VideoAccessUnit, error) { - packet, err := decodeVideoDatagram(input) - if err != nil { - return nil, err - } - r.expire(now) - key := videoFrameKey{streamID: packet.StreamID, generation: packet.Generation, frameID: packet.FrameID} - assembly := r.frames[key] - if assembly == nil { - if len(r.frames) >= videoReassemblyMaxFrames { - r.dropOldest() - } - assembly = &videoFrameAssembly{ - created: now, - sequence: packet.Sequence, - pts: packet.PTS, - dts: packet.DTS, - flags: packet.Flags &^ (videoFlagFirstFragment | videoFlagLastFragment), - fragmentCount: packet.FragmentCount, - fragments: make(map[uint16][]byte, packet.FragmentCount), - } - r.frames[key] = assembly - } else if assembly.fragmentCount != packet.FragmentCount || assembly.sequence != packet.Sequence { - delete(r.frames, key) - return nil, errors.New("video frame metadata changed during reassembly") - } - if _, exists := assembly.fragments[packet.FragmentID]; !exists { - assembly.fragments[packet.FragmentID] = append([]byte(nil), packet.Payload...) - assembly.bytes += len(packet.Payload) - } - if assembly.bytes > videoFrameMaxBytes || len(assembly.fragments) != int(assembly.fragmentCount) { - return nil, nil - } - payload := make([]byte, 0, assembly.bytes) - for index := uint16(0); index < assembly.fragmentCount; index++ { - fragment, ok := assembly.fragments[index] - if !ok { - return nil, nil - } - payload = append(payload, fragment...) - } - delete(r.frames, key) - return &VideoAccessUnit{ - StreamID: packet.StreamID, - Generation: packet.Generation, - Sequence: assembly.sequence, - FrameID: packet.FrameID, - PTS: assembly.pts, - DTS: assembly.dts, - Flags: assembly.flags | (packet.Flags & (videoFlagConfig | videoFlagKeyFrame | videoFlagDiscontinuity)), - Payload: payload, - }, nil -} - -func (r *videoReassembler) expire(now time.Time) { - for key, assembly := range r.frames { - if now.Sub(assembly.created) >= videoReassemblyTimeout { - delete(r.frames, key) - } - } -} - -func (r *videoReassembler) dropOldest() { - var oldest videoFrameKey - var oldestTime time.Time - for key, assembly := range r.frames { - if oldestTime.IsZero() || assembly.created.Before(oldestTime) { - oldest, oldestTime = key, assembly.created - } - } - if !oldestTime.IsZero() { - delete(r.frames, oldest) - } -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func validateVideoDatagramSize(input []byte) error { - if len(input) > videoDatagramMaxBytes { - return fmt.Errorf("video datagram exceeds %d bytes", videoDatagramMaxBytes) - } - return nil -} - -func validateAudioDatagramSize(input []byte) error { - if len(input) > audioDatagramMaxBytes { - return fmt.Errorf("audio datagram exceeds %d bytes", audioDatagramMaxBytes) - } - return nil -} diff --git a/transport/hysteria2-agent/media_ring.go b/transport/hysteria2-agent/media_ring.go deleted file mode 100644 index a770323..0000000 --- a/transport/hysteria2-agent/media_ring.go +++ /dev/null @@ -1,156 +0,0 @@ -package main - -import ( - "context" - "errors" - "fmt" -) - -// EncodedRingRecord is the cross-process contract produced by the Rust -// Windows agent. PTS is the Media Foundation 100-ns media timeline. -type EncodedRingRecord struct { - Sequence uint64 - PTS uint64 - Kind uint32 - KeyFrame bool - Payload []byte -} - -// EncodedRing is intentionally a single-consumer interface. The producer may -// drop a video record when this queue is full; the consumer must never block -// input or audio while polling it. -type EncodedRing interface { - Pop() (*EncodedRingRecord, error) - Close() error -} - -// AudioRing is a separate consumer contract even though its mmap slot layout -// matches the video ring. Keeping the interfaces distinct prevents audio from -// accidentally sharing video backpressure or drop policy. -type AudioRing interface { - Pop() (*EncodedRingRecord, error) - Close() error -} - -func validateRingRecord(record *EncodedRingRecord) error { - if record == nil || record.Sequence == 0 || len(record.Payload) == 0 || len(record.Payload) > videoFrameMaxBytes { - return errors.New("encoded ring record is invalid") - } - return nil -} - -// pumpEncodedRing drains the ring into RDV1 datagrams. The callback is the -// already-authenticated Hysteria2 UDP stream; this function never retries a -// failed video packet. -func pumpEncodedRing(ctx context.Context, ring EncodedRing, streamID uint32, generation uint64, send func([]byte) error) error { - if streamID == 0 || generation == 0 { - return errors.New("encoded ring stream identity is invalid") - } - if ring == nil || send == nil { - return errors.New("encoded ring pump arguments are invalid") - } - ticker := newMediaPollTicker() - defer ticker.Stop() - for { - drained := false - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - record, err := ring.Pop() - if err != nil { - return fmt.Errorf("read encoded media ring: %w", err) - } - if record == nil { - break - } - drained = true - if err := validateRingRecord(record); err != nil { - return err - } - flags := uint16(0) - if record.KeyFrame { - flags |= videoFlagKeyFrame - } - datagrams, err := fragmentVideoAccessUnit(VideoAccessUnit{ - StreamID: streamID, Generation: generation, Sequence: record.Sequence, - FrameID: record.Sequence, PTS: record.PTS, DTS: record.PTS, - Flags: flags, Payload: record.Payload, - }) - if err != nil { - return fmt.Errorf("fragment encoded media: %w", err) - } - for _, datagram := range datagrams { - if err := send(datagram); err != nil { - return fmt.Errorf("send encoded video datagram: %w", err) - } - } - } - if drained { - continue - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - } -} - -// pumpAudioRing sends independent Opus packets as RDA1 datagrams. Audio does -// not wait for video and does not use video frame reassembly. -func pumpAudioRing(ctx context.Context, ring AudioRing, streamID uint32, generation uint64, send func([]byte) error) error { - if streamID == 0 || generation == 0 { - return errors.New("audio ring stream identity is invalid") - } - if ring == nil || send == nil { - return errors.New("audio ring pump arguments are invalid") - } - ticker := newMediaPollTicker() - defer ticker.Stop() - for { - drained := false - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - record, err := ring.Pop() - if err != nil { - return fmt.Errorf("read audio media ring: %w", err) - } - if record == nil { - break - } - drained = true - if record.Sequence == 0 || len(record.Payload) == 0 || len(record.Payload) > audioPacketMaxBytes { - return errors.New("audio ring record is invalid") - } - flags := uint16(0) - if record.Kind == 2 { - flags |= audioFlagDiscontinuity - } - datagram, err := encodeAudioDatagram(AudioPacket{ - StreamID: streamID, Generation: generation, Sequence: record.Sequence, - PTS: record.PTS, DurationMS: 20, Flags: flags, Payload: record.Payload, - }) - if err != nil { - return fmt.Errorf("encode audio datagram: %w", err) - } - if err := send(datagram); err != nil { - return fmt.Errorf("send audio datagram: %w", err) - } - } - if drained { - continue - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - } -} diff --git a/transport/hysteria2-agent/media_ring_poll.go b/transport/hysteria2-agent/media_ring_poll.go deleted file mode 100644 index f43f167..0000000 --- a/transport/hysteria2-agent/media_ring_poll.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import "time" - -type mediaPollTicker struct { - *time.Ticker -} - -func newMediaPollTicker() *mediaPollTicker { - return &mediaPollTicker{Ticker: time.NewTicker(2 * time.Millisecond)} -} diff --git a/transport/hysteria2-agent/media_ring_unix.go b/transport/hysteria2-agent/media_ring_unix.go deleted file mode 100644 index 553333d..0000000 --- a/transport/hysteria2-agent/media_ring_unix.go +++ /dev/null @@ -1,105 +0,0 @@ -//go:build !windows - -package main - -import ( - "encoding/binary" - "errors" - "fmt" - "os" - "sync/atomic" - "syscall" - "unsafe" -) - -const ( - ringMagic = "RDMRING1" - ringVersion = uint32(1) - ringHeaderBytes = 40 - slotHeaderBytes = 24 - maxRingSlots = uint32(256) - maxRingSlotBytes = uint32(16 * 1024 * 1024) -) - -type mappedEncodedRing struct { - file *os.File - path string - data []byte - slotCount uint32 - slotBytes uint32 -} - -func openEncodedRing(path string) (EncodedRing, error) { - file, err := os.OpenFile(path, os.O_RDWR, 0) - if err != nil { - return nil, fmt.Errorf("open encoded ring: %w", err) - } - stat, err := file.Stat() - if err != nil { - _ = file.Close() - return nil, err - } - if stat.Size() < ringHeaderBytes { - _ = file.Close() - return nil, errors.New("encoded ring is shorter than its header") - } - data, err := syscall.Mmap(int(file.Fd()), 0, int(stat.Size()), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) - if err != nil { - _ = file.Close() - return nil, fmt.Errorf("map encoded ring: %w", err) - } - if string(data[:8]) != ringMagic || binary.LittleEndian.Uint32(data[8:12]) != ringVersion { - _ = syscall.Munmap(data) - _ = file.Close() - return nil, errors.New("encoded ring header is invalid") - } - slots, slotBytes := binary.LittleEndian.Uint32(data[12:16]), binary.LittleEndian.Uint32(data[16:20]) - expected := uint64(ringHeaderBytes) + uint64(slots)*uint64(slotHeaderBytes+slotBytes) - if slots < 2 || slots > maxRingSlots || slots&(slots-1) != 0 || slotBytes == 0 || slotBytes > maxRingSlotBytes || expected != uint64(len(data)) { - _ = syscall.Munmap(data) - _ = file.Close() - return nil, errors.New("encoded ring geometry is invalid") - } - return &mappedEncodedRing{file: file, path: path, data: data, slotCount: slots, slotBytes: slotBytes}, nil -} - -func openAudioRing(path string) (AudioRing, error) { - ring, err := openEncodedRing(path) - if err != nil { - return nil, err - } - return ring, nil -} - -func (r *mappedEncodedRing) Pop() (*EncodedRingRecord, error) { - read := atomic.LoadUint64((*uint64)(unsafe.Pointer(&r.data[32]))) - write := atomic.LoadUint64((*uint64)(unsafe.Pointer(&r.data[24]))) - if read == write { - return nil, nil - } - start := ringHeaderBytes + int(read%uint64(r.slotCount))*int(slotHeaderBytes+r.slotBytes) - length := binary.LittleEndian.Uint32(r.data[start+20 : start+24]) - if length == 0 || length > r.slotBytes { - return nil, errors.New("encoded ring record length is invalid") - } - record := &EncodedRingRecord{Sequence: binary.LittleEndian.Uint64(r.data[start : start+8]), PTS: binary.LittleEndian.Uint64(r.data[start+8 : start+16]), Kind: binary.LittleEndian.Uint32(r.data[start+16 : start+20]), Payload: append([]byte(nil), r.data[start+slotHeaderBytes:start+slotHeaderBytes+int(length)]...)} - record.KeyFrame = record.Kind == 2 - if record.Sequence == 0 { - return nil, errors.New("encoded ring record sequence is invalid") - } - atomic.StoreUint64((*uint64)(unsafe.Pointer(&r.data[32])), read+1) - return record, nil -} - -func (r *mappedEncodedRing) Close() error { - err := syscall.Munmap(r.data) - if r.file != nil { - if closeErr := r.file.Close(); err == nil { - err = closeErr - } - } - if removeErr := os.Remove(r.path); err == nil && removeErr != nil && !errors.Is(removeErr, os.ErrNotExist) { - err = removeErr - } - return err -} diff --git a/transport/hysteria2-agent/media_ring_windows.go b/transport/hysteria2-agent/media_ring_windows.go deleted file mode 100644 index bf47e29..0000000 --- a/transport/hysteria2-agent/media_ring_windows.go +++ /dev/null @@ -1,146 +0,0 @@ -//go:build windows - -package main - -import ( - "encoding/binary" - "errors" - "fmt" - "os" - "sync/atomic" - "unsafe" - - "golang.org/x/sys/windows" -) - -const ( - ringMagic = "RDMRING1" - ringVersion = uint32(1) - ringHeaderBytes = 40 - slotHeaderBytes = 24 - maxRingSlots = uint32(256) - maxRingSlotBytes = uint32(16 * 1024 * 1024) -) - -type mappedEncodedRing struct { - file *os.File - path string - mapping windows.Handle - address uintptr - data []byte - slotCount uint32 - slotBytes uint32 -} - -func openEncodedRing(path string) (EncodedRing, error) { - file, err := os.OpenFile(path, os.O_RDWR, 0) - if err != nil { - return nil, fmt.Errorf("open encoded ring: %w", err) - } - closeOnError := true - defer func() { - if closeOnError { - _ = file.Close() - } - }() - stat, err := file.Stat() - if err != nil { - return nil, fmt.Errorf("stat encoded ring: %w", err) - } - length := stat.Size() - if length < ringHeaderBytes { - return nil, errors.New("encoded ring is shorter than its header") - } - mapping, err := windows.CreateFileMapping(windows.Handle(file.Fd()), nil, windows.PAGE_READWRITE, 0, 0, nil) - if err != nil { - return nil, fmt.Errorf("map encoded ring file: %w", err) - } - address, err := windows.MapViewOfFile(mapping, windows.FILE_MAP_READ|windows.FILE_MAP_WRITE, 0, 0, uintptr(length)) - if err != nil { - _ = windows.CloseHandle(mapping) - return nil, fmt.Errorf("map encoded ring view: %w", err) - } - if uintptr(length) > uintptr(^uint(0)>>1) { - _ = windows.UnmapViewOfFile(address) - _ = windows.CloseHandle(mapping) - return nil, errors.New("encoded ring is too large for this process") - } - data := unsafe.Slice((*byte)(unsafe.Pointer(address)), int(length)) - if string(data[:8]) != ringMagic || binary.LittleEndian.Uint32(data[8:12]) != ringVersion { - _ = windows.UnmapViewOfFile(address) - _ = windows.CloseHandle(mapping) - return nil, errors.New("encoded ring header magic or version is invalid") - } - slots := binary.LittleEndian.Uint32(data[12:16]) - slotBytes := binary.LittleEndian.Uint32(data[16:20]) - if slots < 2 || slots > maxRingSlots || slots&(slots-1) != 0 || slotBytes == 0 || slotBytes > maxRingSlotBytes { - _ = windows.UnmapViewOfFile(address) - _ = windows.CloseHandle(mapping) - return nil, errors.New("encoded ring geometry is invalid") - } - expected := uint64(ringHeaderBytes) + uint64(slots)*uint64(slotHeaderBytes+slotBytes) - if expected != uint64(length) { - _ = windows.UnmapViewOfFile(address) - _ = windows.CloseHandle(mapping) - return nil, errors.New("encoded ring file size does not match its header") - } - closeOnError = false - return &mappedEncodedRing{file: file, path: path, mapping: mapping, address: address, data: data, slotCount: slots, slotBytes: slotBytes}, nil -} - -func openAudioRing(path string) (AudioRing, error) { - ring, err := openEncodedRing(path) - if err != nil { - return nil, err - } - return ring, nil -} - -func (r *mappedEncodedRing) Pop() (*EncodedRingRecord, error) { - read := atomic.LoadUint64((*uint64)(unsafe.Pointer(&r.data[32]))) - write := atomic.LoadUint64((*uint64)(unsafe.Pointer(&r.data[24]))) - if read == write { - return nil, nil - } - offset := ringHeaderBytes + (read%uint64(r.slotCount))*uint64(slotHeaderBytes+r.slotBytes) - start := int(offset) - if start+slotHeaderBytes > len(r.data) { - return nil, errors.New("encoded ring slot header is out of bounds") - } - length := binary.LittleEndian.Uint32(r.data[start+20 : start+24]) - if length == 0 || length > r.slotBytes || uint64(start+slotHeaderBytes)+uint64(length) > uint64(len(r.data)) { - return nil, errors.New("encoded ring record length is invalid") - } - payload := append([]byte(nil), r.data[start+slotHeaderBytes:start+slotHeaderBytes+int(length)]...) - record := &EncodedRingRecord{ - Sequence: binary.LittleEndian.Uint64(r.data[start : start+8]), - PTS: binary.LittleEndian.Uint64(r.data[start+8 : start+16]), - Kind: binary.LittleEndian.Uint32(r.data[start+16 : start+20]), - Payload: payload, - } - record.KeyFrame = record.Kind == 2 - if record.Sequence == 0 { - return nil, errors.New("encoded ring record sequence is invalid") - } - atomic.StoreUint64((*uint64)(unsafe.Pointer(&r.data[32])), read+1) - return record, nil -} - -func (r *mappedEncodedRing) Close() error { - if r.address != 0 { - _ = windows.UnmapViewOfFile(r.address) - r.address = 0 - } - if r.mapping != 0 { - _ = windows.CloseHandle(r.mapping) - r.mapping = 0 - } - if r.file != nil { - err := r.file.Close() - if removeErr := os.Remove(r.path); err == nil && removeErr != nil && !errors.Is(removeErr, os.ErrNotExist) { - err = removeErr - } - return err - } - return nil -} diff --git a/transport/hysteria2-agent/media_test.go b/transport/hysteria2-agent/media_test.go deleted file mode 100644 index a2c1ffb..0000000 --- a/transport/hysteria2-agent/media_test.go +++ /dev/null @@ -1,302 +0,0 @@ -package main - -import ( - "bytes" - "context" - "encoding/binary" - "testing" - "time" -) - -type testEncodedRing struct { - records []*EncodedRingRecord -} - -func (r *testEncodedRing) Pop() (*EncodedRingRecord, error) { - if len(r.records) == 0 { - return nil, nil - } - record := r.records[0] - r.records = r.records[1:] - return record, nil -} - -func (*testEncodedRing) Close() error { return nil } - -func TestVideoAccessUnitRoundTripThroughFragments(t *testing.T) { - payload := bytes.Repeat([]byte{0x5a}, videoDatagramPayloadMax*2+17) - unit := VideoAccessUnit{ - StreamID: 1, - Generation: 2, - Sequence: 3, - FrameID: 4, - PTS: 5, - DTS: 6, - Flags: videoFlagKeyFrame, - Payload: payload, - } - datagrams, err := fragmentVideoAccessUnit(unit) - if err != nil { - t.Fatal(err) - } - if len(datagrams) != 3 { - t.Fatalf("got %d datagrams, want 3", len(datagrams)) - } - - reassembler := newVideoReassembler() - var recovered *VideoAccessUnit - now := time.Unix(100, 0) - for _, datagram := range datagrams { - recovered, err = reassembler.push(datagram, now) - if err != nil { - t.Fatal(err) - } - } - if recovered == nil { - t.Fatal("frame was not reassembled") - } - if !bytes.Equal(recovered.Payload, payload) || recovered.Flags&videoFlagKeyFrame == 0 { - t.Fatal("reassembled frame does not match source") - } -} - -func TestVideoReassemblerExpiresIncompleteFrame(t *testing.T) { - datagrams, err := fragmentVideoAccessUnit(VideoAccessUnit{ - StreamID: 1, - Generation: 1, - Sequence: 1, - FrameID: 1, - PTS: 1, - DTS: 1, - Flags: videoFlagKeyFrame, - Payload: bytes.Repeat([]byte{1}, videoDatagramPayloadMax+1), - }) - if err != nil { - t.Fatal(err) - } - reassembler := newVideoReassembler() - now := time.Unix(200, 0) - if _, err := reassembler.push(datagrams[0], now); err != nil { - t.Fatal(err) - } - if _, err := reassembler.push(datagrams[0], now.Add(videoReassemblyTimeout+time.Nanosecond)); err != nil { - t.Fatal(err) - } - if len(reassembler.frames) != 1 { - t.Fatalf("expected only the new incomplete assembly, got %d", len(reassembler.frames)) - } -} - -func TestVideoDatagramRejectsInvalidFragmentFlags(t *testing.T) { - datagrams, err := fragmentVideoAccessUnit(VideoAccessUnit{ - StreamID: 1, - Generation: 1, - Sequence: 1, - FrameID: 1, - Payload: []byte{1}, - }) - if err != nil { - t.Fatal(err) - } - datagrams[0][56] = 0 - datagrams[0][57] = 0 - if _, err := decodeVideoDatagram(datagrams[0]); err == nil { - t.Fatal("invalid first/last flags were accepted") - } -} - -func TestVideoDatagramSizeUsesCompleteDatagramLimit(t *testing.T) { - if err := validateVideoDatagramSize(make([]byte, videoDatagramMaxBytes)); err != nil { - t.Fatal(err) - } - if err := validateVideoDatagramSize(make([]byte, videoDatagramMaxBytes+1)); err == nil { - t.Fatal("oversized datagram was accepted") - } -} - -func TestPrepareDatagramKeepsOpaquePacketsUnparsed(t *testing.T) { - input := []byte("audio-or-control") - prepared, kind, err := prepareDatagram(input) - if err != nil { - t.Fatal(err) - } - if kind != datagramOpaque || !bytes.Equal(prepared, input) { - t.Fatalf("unexpected opaque packet result: kind=%d payload=%q", kind, prepared) - } - prepared[0] = 'X' - if input[0] == 'X' { - t.Fatal("prepared datagram aliases the UDP input buffer") - } -} - -func TestPrepareDatagramRecognizesVideoPackets(t *testing.T) { - datagrams, err := fragmentVideoAccessUnit(VideoAccessUnit{ - StreamID: 1, Generation: 1, Sequence: 1, FrameID: 1, Payload: []byte{1}, - }) - if err != nil { - t.Fatal(err) - } - _, kind, err := prepareDatagram(datagrams[0]) - if err != nil { - t.Fatal(err) - } - if kind != datagramVideo { - t.Fatalf("got datagram kind %d, want video", kind) - } -} - -func TestAudioPacketRoundTripIsIndependent(t *testing.T) { - input, err := encodeAudioDatagram(AudioPacket{ - StreamID: 1, Generation: 2, Sequence: 3, PTS: 4, DurationMS: 20, - Payload: []byte{0x01, 0x02, 0x03}, - }) - if err != nil { - t.Fatal(err) - } - decoded, err := decodeAudioDatagram(input) - if err != nil { - t.Fatal(err) - } - if decoded.StreamID != 1 || decoded.Generation != 2 || decoded.Sequence != 3 || - decoded.PTS != 4 || decoded.DurationMS != 20 || !bytes.Equal(decoded.Payload, []byte{1, 2, 3}) { - t.Fatalf("decoded audio packet does not match source: %+v", decoded) - } - prepared, kind, err := prepareDatagram(input) - if err != nil { - t.Fatal(err) - } - if kind != datagramOpaque || !bytes.Equal(prepared, input) { - t.Fatalf("audio packet was not preserved as opaque transport data") - } -} - -func TestAudioDatagramRejectsInvalidDurationAndSize(t *testing.T) { - if _, err := encodeAudioDatagram(AudioPacket{ - StreamID: 1, Generation: 1, Sequence: 1, PTS: 1, DurationMS: 15, Payload: []byte{1}, - }); err == nil { - t.Fatal("invalid audio duration was accepted") - } - if err := validateAudioDatagramSize(make([]byte, audioDatagramMaxBytes+1)); err == nil { - t.Fatal("oversized audio datagram was accepted") - } -} - -func TestAudioDatagramRejectsReservedByteAndUnknownFlags(t *testing.T) { - encoded, err := encodeAudioDatagram(AudioPacket{ - StreamID: 1, Generation: 2, Sequence: 3, PTS: 4, - DurationMS: 20, Payload: []byte("opus"), - }) - if err != nil { - t.Fatal(err) - } - encoded[5] = 1 - if _, err := decodeAudioDatagram(encoded); err == nil { - t.Fatal("audio reserved byte was accepted") - } - encoded[5] = 0 - binary.LittleEndian.PutUint16(encoded[38:40], 2) - if _, err := decodeAudioDatagram(encoded); err == nil { - t.Fatal("unknown audio flags were accepted") - } -} - -func TestPumpEncodedRingFragmentsAndMarksKeyFrame(t *testing.T) { - payload := bytes.Repeat([]byte{0x42}, videoDatagramPayloadMax+3) - ring := &testEncodedRing{records: []*EncodedRingRecord{{ - Sequence: 9, PTS: 123, KeyFrame: true, Payload: payload, - }}} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - var datagrams [][]byte - send := func(data []byte) error { - datagrams = append(datagrams, append([]byte(nil), data...)) - if len(datagrams) == 2 { - cancel() - } - return nil - } - err := pumpEncodedRing(ctx, ring, 7, 8, send) - if err != context.Canceled { - t.Fatalf("pump returned %v, want context cancellation", err) - } - if len(datagrams) != 2 { - t.Fatalf("got %d datagrams, want 2", len(datagrams)) - } - first, err := decodeVideoDatagram(datagrams[0]) - if err != nil { - t.Fatal(err) - } - if first.Flags&videoFlagKeyFrame == 0 || first.PTS != 123 || first.FrameID != 9 { - t.Fatalf("ring metadata was not preserved: %+v", first) - } -} - -func TestPumpEncodedRingRejectsInvalidRecord(t *testing.T) { - ring := &testEncodedRing{records: []*EncodedRingRecord{{Sequence: 0, Payload: []byte{1}}}} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - err := pumpEncodedRing(ctx, ring, 1, 1, func([]byte) error { return nil }) - if err == nil { - t.Fatal("invalid encoded ring record was accepted") - } -} - -func TestPumpAudioRingEncodesRDA1AndDiscontinuity(t *testing.T) { - ring := &testEncodedRing{records: []*EncodedRingRecord{{ - Sequence: 7, - PTS: 400_000, - Kind: 2, - Payload: []byte("opus"), - }}} - ctx, cancel := context.WithCancel(context.Background()) - var datagram []byte - err := pumpAudioRing(ctx, ring, 11, 12, func(data []byte) error { - datagram = append([]byte(nil), data...) - cancel() - return nil - }) - if err != context.Canceled { - t.Fatalf("pump returned %v, want context cancellation", err) - } - packet, err := decodeAudioDatagram(datagram) - if err != nil { - t.Fatal(err) - } - if packet.StreamID != 11 || packet.Generation != 12 || packet.Sequence != 7 || - packet.PTS != 400_000 || packet.DurationMS != 20 || - packet.Flags&audioFlagDiscontinuity == 0 || !bytes.Equal(packet.Payload, []byte("opus")) { - t.Fatalf("audio ring metadata was not preserved: %+v", packet) - } -} - -func TestPumpAudioRingRejectsInvalidRecords(t *testing.T) { - tests := []struct { - name string - record *EncodedRingRecord - }{ - {name: "zero sequence", record: &EncodedRingRecord{Payload: []byte{1}}}, - {name: "empty payload", record: &EncodedRingRecord{Sequence: 1}}, - { - name: "oversized payload", - record: &EncodedRingRecord{ - Sequence: 1, - Payload: make([]byte, audioPacketMaxBytes+1), - }, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ring := &testEncodedRing{records: []*EncodedRingRecord{test.record}} - err := pumpAudioRing( - context.Background(), - ring, - 1, - 1, - func([]byte) error { return nil }, - ) - if err == nil { - t.Fatal("invalid audio ring record was accepted") - } - }) - } -} diff --git a/transport/hysteria2-agent/windows_agent_control.go b/transport/hysteria2-agent/windows_agent_control.go deleted file mode 100644 index 498ae29..0000000 --- a/transport/hysteria2-agent/windows_agent_control.go +++ /dev/null @@ -1,254 +0,0 @@ -package main - -import ( - "bufio" - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "net" - "os" - "path/filepath" - "strings" - "time" -) - -const ( - agentControlLineMax = 16 * 1024 - agentPipeTokenEnv = "REMOTEDESK_AGENT_PIPE_TOKEN" - agentPipeAuthDomain = "RemoteDesk Windows Agent pipe auth v1\x00" -) - -type mediaRingDescriptor struct { - SchemaVersion uint16 `json:"schema_version"` - SessionID string `json:"session_id"` - Generation uint64 `json:"generation"` - OwnerPID uint32 `json:"owner_pid"` - RingPath string `json:"ring_path"` - SlotCount uint32 `json:"slot_count"` - SlotBytes uint32 `json:"slot_bytes"` - VideoTransport string `json:"video_transport"` - AudioTransport string `json:"audio_transport"` - AudioRing *audioRingDescriptor `json:"audio_ring,omitempty"` -} - -type audioRingDescriptor struct { - SchemaVersion uint16 `json:"schema_version"` - SessionID string `json:"session_id"` - Generation uint64 `json:"generation"` - OwnerPID uint32 `json:"owner_pid"` - RingPath string `json:"ring_path"` - SlotCount uint32 `json:"slot_count"` - SlotBytes uint32 `json:"slot_bytes"` - AudioTransport string `json:"audio_transport"` -} - -type encodedDesktopOpened struct { - Kind string `json:"kind"` - Error string `json:"error"` - VideoCodec string `json:"video_codec"` - MediaTransport string `json:"media_transport"` - Ring mediaRingDescriptor `json:"ring"` -} - -type agentHello struct { - Kind string `json:"kind"` - Authenticated bool `json:"authenticated"` -} - -type agentAuthChallenge struct { - Kind string `json:"kind"` - ProtocolVersion uint8 `json:"protocol_version"` - Nonce string `json:"nonce"` -} - -func openWindowsEncodedSession(address, sessionID string, fps uint8, allowSoftwareFallback bool) (net.Conn, mediaRingDescriptor, error) { - if err := validateAgentControlAddress(address); err != nil { - return nil, mediaRingDescriptor{}, err - } - if !validSessionID(sessionID) || fps == 0 || fps > 120 { - return nil, mediaRingDescriptor{}, errors.New("Windows Agent encoded session parameters are invalid") - } - isPipe := isWindowsAgentPipe(address) - connection, err := dialWindowsAgentControl(address, 2*time.Second) - if err != nil { - return nil, mediaRingDescriptor{}, fmt.Errorf("connect Windows Agent control: %w", err) - } - closeOnError := true - defer func() { - if closeOnError { - _ = connection.Close() - } - }() - _ = connection.SetDeadline(time.Now().Add(10 * time.Second)) - reader := bufio.NewReaderSize(connection, agentControlLineMax+1) - if isPipe { - if err := authenticateWindowsAgentPipe(reader, connection); err != nil { - return nil, mediaRingDescriptor{}, err - } - } - var hello agentHello - if err := readBoundedAgentJSON(reader, &hello); err != nil || hello.Kind != "windows_agent_hello" || (isPipe && !hello.Authenticated) { - return nil, mediaRingDescriptor{}, errors.New("Windows Agent control hello is invalid") - } - request := struct { - Kind string `json:"kind"` - SessionID string `json:"session_id"` - CaptureMode string `json:"capture_mode"` - VideoCodec string `json:"video_codec"` - FramesPerSecond uint8 `json:"frames_per_second"` - AllowSoftwareFallback bool `json:"allow_software_fallback"` - }{ - Kind: "open_encoded_desktop", SessionID: sessionID, - CaptureMode: "compatibility", VideoCodec: "h264", FramesPerSecond: fps, - AllowSoftwareFallback: allowSoftwareFallback, - } - encoded, err := json.Marshal(request) - if err != nil { - return nil, mediaRingDescriptor{}, err - } - encoded = append(encoded, '\n') - if _, err := connection.Write(encoded); err != nil { - return nil, mediaRingDescriptor{}, fmt.Errorf("open Windows encoded desktop: %w", err) - } - var opened encodedDesktopOpened - if err := readBoundedAgentJSON(reader, &opened); err != nil { - return nil, mediaRingDescriptor{}, err - } - if opened.Kind != "encoded_desktop_opened" || opened.VideoCodec != "h264" || - opened.MediaTransport != "shared_memory_encoded_access_units" { - return nil, mediaRingDescriptor{}, fmt.Errorf("Windows encoded desktop unavailable: %s", opened.Error) - } - if err := validateMediaRingDescriptor(opened.Ring, sessionID); err != nil { - return nil, mediaRingDescriptor{}, err - } - _ = connection.SetDeadline(time.Time{}) - closeOnError = false - return connection, opened.Ring, nil -} - -func dialWindowsAgentControl(address string, timeout time.Duration) (net.Conn, error) { - if isWindowsAgentPipe(address) { - return dialWindowsAgentPipe(address, timeout) - } - return net.DialTimeout("tcp", address, timeout) -} - -func authenticateWindowsAgentPipe(reader *bufio.Reader, connection net.Conn) error { - var challenge agentAuthChallenge - if err := readBoundedAgentJSON(reader, &challenge); err != nil { - return err - } - if challenge.Kind != "agent_auth_challenge" || challenge.ProtocolVersion != 1 { - return errors.New("Windows Agent Pipe challenge is invalid") - } - nonce, err := base64.RawURLEncoding.DecodeString(challenge.Nonce) - if err != nil || len(nonce) != 32 { - return errors.New("Windows Agent Pipe nonce is invalid") - } - encodedToken, ok := os.LookupEnv(agentPipeTokenEnv) - if !ok { - return fmt.Errorf("%s is required for authenticated Windows Agent Pipe", agentPipeTokenEnv) - } - _ = os.Unsetenv(agentPipeTokenEnv) - key, err := base64.RawURLEncoding.DecodeString(encodedToken) - if err != nil || len(key) != 32 { - return errors.New("Windows Agent Pipe token must be 32-byte base64url") - } - mac := windowsAgentPipeMAC(key, nonce) - request := struct { - Kind string `json:"kind"` - ProtocolVersion uint8 `json:"protocol_version"` - MAC string `json:"mac"` - }{ - Kind: "agent_auth", - ProtocolVersion: 1, - MAC: base64.RawURLEncoding.EncodeToString(mac), - } - encoded, err := json.Marshal(request) - if err != nil { - return err - } - encoded = append(encoded, '\n') - if _, err := connection.Write(encoded); err != nil { - return fmt.Errorf("authenticate Windows Agent Pipe: %w", err) - } - return nil -} - -func windowsAgentPipeMAC(key, nonce []byte) []byte { - mac := hmac.New(sha256.New, key) - _, _ = mac.Write([]byte(agentPipeAuthDomain)) - _, _ = mac.Write(nonce) - return mac.Sum(nil) -} - -func readBoundedAgentJSON(reader *bufio.Reader, output any) error { - line, err := reader.ReadSlice('\n') - if err != nil { - return fmt.Errorf("read Windows Agent control: %w", err) - } - if len(line) == 0 || len(line) > agentControlLineMax || line[len(line)-1] != '\n' { - return errors.New("Windows Agent control response exceeds its bound") - } - if err := json.Unmarshal(line, output); err != nil { - return errors.New("Windows Agent control response is invalid JSON") - } - return nil -} - -func validateAgentControlAddress(address string) error { - if isWindowsAgentPipe(address) { - if len(address) > 256 || !strings.HasPrefix(strings.ToLower(address), `\\.\pipe\remotedesk\`) || strings.Contains(address, "..") { - return errors.New("Windows Agent Pipe name is outside the RemoteDesk namespace") - } - return nil - } - host, port, err := net.SplitHostPort(address) - if err != nil || port == "" { - return errors.New("Windows Agent control must be an IP socket address") - } - ip := net.ParseIP(host) - if ip == nil || !ip.IsLoopback() { - return errors.New("Windows Agent control must use a loopback IP") - } - return nil -} - -func isWindowsAgentPipe(address string) bool { - return strings.HasPrefix(strings.ToLower(address), `\\.\pipe\`) -} - -func validateMediaRingDescriptor(descriptor mediaRingDescriptor, sessionID string) error { - if descriptor.SchemaVersion != 1 || descriptor.SessionID != sessionID || descriptor.Generation == 0 || descriptor.OwnerPID == 0 || - descriptor.VideoTransport != "shared_memory_encoded_access_units" || descriptor.AudioTransport != "separate_transport" || - !filepath.IsAbs(descriptor.RingPath) || descriptor.SlotCount < 2 || descriptor.SlotCount > maxRingSlots || - descriptor.SlotCount&(descriptor.SlotCount-1) != 0 || descriptor.SlotBytes == 0 || descriptor.SlotBytes > maxRingSlotBytes { - return errors.New("Windows Agent media ring descriptor is invalid") - } - if descriptor.AudioRing != nil { - audio := descriptor.AudioRing - if audio.SchemaVersion != 1 || audio.SessionID != sessionID || audio.Generation != descriptor.Generation || - audio.OwnerPID != descriptor.OwnerPID || audio.AudioTransport != "shared_memory_opus_packets" || - !filepath.IsAbs(audio.RingPath) || audio.SlotCount < 2 || audio.SlotCount > maxRingSlots || - audio.SlotCount&(audio.SlotCount-1) != 0 || audio.SlotBytes == 0 || audio.SlotBytes > maxRingSlotBytes { - return errors.New("Windows Agent audio ring descriptor is invalid") - } - } - return nil -} - -func validSessionID(value string) bool { - if len(value) == 0 || len(value) > 128 { - return false - } - for _, character := range []byte(value) { - if !((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || - (character >= '0' && character <= '9') || character == '-' || character == '_') { - return false - } - } - return true -} diff --git a/transport/hysteria2-agent/windows_agent_control_test.go b/transport/hysteria2-agent/windows_agent_control_test.go deleted file mode 100644 index e2366e0..0000000 --- a/transport/hysteria2-agent/windows_agent_control_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package main - -import ( - "bufio" - "encoding/base64" - "encoding/json" - "net" - "path/filepath" - "testing" -) - -func TestOpenWindowsEncodedSessionBindsDescriptorToRequest(t *testing.T) { - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - defer listener.Close() - serverError := make(chan error, 1) - go func() { - connection, err := listener.Accept() - if err != nil { - serverError <- err - return - } - defer connection.Close() - if _, err := connection.Write([]byte("{\"kind\":\"windows_agent_hello\"}\n")); err != nil { - serverError <- err - return - } - var request struct { - Kind string `json:"kind"` - SessionID string `json:"session_id"` - FramesPerSecond uint8 `json:"frames_per_second"` - AllowSoftwareFallback bool `json:"allow_software_fallback"` - } - if err := readBoundedAgentJSON(bufio.NewReader(connection), &request); err != nil { - serverError <- err - return - } - if request.Kind != "open_encoded_desktop" || request.SessionID != "session-1" || request.FramesPerSecond != 60 || !request.AllowSoftwareFallback { - serverError <- &testControlError{"encoded desktop request mismatch"} - return - } - response := encodedDesktopOpened{ - Kind: "encoded_desktop_opened", VideoCodec: "h264", - MediaTransport: "shared_memory_encoded_access_units", - Ring: mediaRingDescriptor{ - SchemaVersion: 1, SessionID: "session-1", Generation: 7, OwnerPID: 42, - RingPath: filepath.Join(t.TempDir(), "media.ring"), SlotCount: 16, - SlotBytes: 8 * 1024 * 1024, VideoTransport: "shared_memory_encoded_access_units", - AudioTransport: "separate_transport", - }, - } - encoded, err := json.Marshal(response) - if err == nil { - encoded = append(encoded, '\n') - _, err = connection.Write(encoded) - } - serverError <- err - }() - connection, descriptor, err := openWindowsEncodedSession(listener.Addr().String(), "session-1", 60, true) - if err != nil { - t.Fatal(err) - } - defer connection.Close() - if descriptor.Generation != 7 || descriptor.SessionID != "session-1" { - t.Fatalf("unexpected descriptor: %+v", descriptor) - } - if err := <-serverError; err != nil { - t.Fatal(err) - } -} - -func TestWindowsAgentControlRejectsRemoteAndMismatchedDescriptor(t *testing.T) { - if validateAgentControlAddress("192.0.2.10:39501") == nil { - t.Fatal("remote Windows Agent control address was accepted") - } - descriptor := mediaRingDescriptor{ - SchemaVersion: 1, SessionID: "other", Generation: 1, OwnerPID: 1, - RingPath: filepath.Join(t.TempDir(), "media.ring"), SlotCount: 16, - SlotBytes: 1024, VideoTransport: "shared_memory_encoded_access_units", - AudioTransport: "separate_transport", - } - if validateMediaRingDescriptor(descriptor, "session-1") == nil { - t.Fatal("descriptor for another session was accepted") - } -} - -func TestWindowsAgentPipeAuthenticationMatchesRustWireVector(t *testing.T) { - key := make([]byte, 32) - nonce := make([]byte, 32) - for index := range key { - key[index] = byte(index) - nonce[index] = byte(index + 32) - } - actual := base64.RawURLEncoding.EncodeToString(windowsAgentPipeMAC(key, nonce)) - const expected = "KinBDOBvVhz0eRv37iahq8BO0z8QdiI0Qnx7HZmcxIk" - if actual != expected { - t.Fatalf("unexpected Agent Pipe MAC: %s", actual) - } - if validateAgentControlAddress(`\\.\pipe\RemoteDesk\windows-agent`) != nil { - t.Fatal("RemoteDesk Agent Pipe was rejected") - } - if validateAgentControlAddress(`\\.\pipe\Other\windows-agent`) == nil { - t.Fatal("Pipe outside the RemoteDesk namespace was accepted") - } -} - -type testControlError struct{ message string } - -func (e *testControlError) Error() string { return e.message } diff --git a/transport/hysteria2-agent/windows_agent_pipe_other.go b/transport/hysteria2-agent/windows_agent_pipe_other.go deleted file mode 100644 index d0794aa..0000000 --- a/transport/hysteria2-agent/windows_agent_pipe_other.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build !windows - -package main - -import ( - "errors" - "net" - "time" -) - -func dialWindowsAgentPipe(_ string, _ time.Duration) (net.Conn, error) { - return nil, errors.New("Windows Agent Named Pipe requires Windows") -} diff --git a/transport/hysteria2-agent/windows_agent_pipe_windows.go b/transport/hysteria2-agent/windows_agent_pipe_windows.go deleted file mode 100644 index f16564f..0000000 --- a/transport/hysteria2-agent/windows_agent_pipe_windows.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build windows - -package main - -import ( - "context" - "net" - "time" - - "github.com/Microsoft/go-winio" -) - -func dialWindowsAgentPipe(name string, timeout time.Duration) (net.Conn, error) { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - return winio.DialPipeContext(ctx, name) -}