diff --git a/.github/workflows/desktop-check.yml b/.github/workflows/desktop-check.yml new file mode 100644 index 0000000..5b835a6 --- /dev/null +++ b/.github/workflows/desktop-check.yml @@ -0,0 +1,74 @@ +name: Desktop compile check + +# Compile-checks the Tauri Rust app on all three desktop targets. This is the +# verification loop for platform-specific capture code (Windows DXGI/WGC, Linux +# PipeWire via xcap) that can't be built on a macOS dev machine. + +on: + push: + branches: + - 'feat/**' + - 'fix/**' + paths: + - 'src-tauri/**' + - '.github/workflows/desktop-check.yml' + pull_request: + branches: [develop, main, 'feat/**'] + paths: + - 'src-tauri/**' + workflow_dispatch: + +jobs: + check: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + - os: windows-latest + - os: macos-latest + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo + uses: Swatinem/rust-cache@v2 + with: + workspaces: src-tauri + + # Tauri + xcap (PipeWire/Wayland/X11/dbus) system deps on Linux. + - name: Install Linux deps + if: matrix.os == 'ubuntu-24.04' + run: | + sudo apt-get update + sudo apt-get install -y \ + libwebkit2gtk-4.1-dev \ + libgtk-3-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + libsoup-3.0-dev \ + libdbus-1-dev \ + libpipewire-0.3-dev \ + libspa-0.2-dev \ + libwayland-dev \ + libxcb1-dev \ + libxcb-randr0-dev \ + libxcb-shm0-dev \ + libclang-dev \ + clang \ + pkg-config + + # generate_context! embeds the frontend dist at compile time; a stub is + # enough for a compile check (we don't need the real built assets here). + - name: Stub frontend dist + shell: bash + run: | + mkdir -p dist + echo 'stub' > dist/index.html + + - name: cargo check (all targets) + working-directory: src-tauri + run: cargo check --all-targets diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d6ee48..4e90cb4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - platform: windows-latest args: '' label: Windows - - platform: ubuntu-22.04 + - platform: ubuntu-24.04 args: '' label: Linux @@ -49,14 +49,24 @@ jobs: workspaces: './src-tauri -> target' - name: Install Linux system dependencies - if: matrix.platform == 'ubuntu-22.04' + if: matrix.platform == 'ubuntu-24.04' run: | sudo apt-get update sudo apt-get install -y \ libwebkit2gtk-4.1-dev \ libappindicator3-dev \ librsvg2-dev \ - patchelf + patchelf \ + libdbus-1-dev \ + libpipewire-0.3-dev \ + libspa-0.2-dev \ + libwayland-dev \ + libxcb1-dev \ + libxcb-randr0-dev \ + libxcb-shm0-dev \ + libclang-dev \ + clang \ + pkg-config - name: Install npm dependencies run: npm ci --legacy-peer-deps diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a4b9b27..9f85a7e 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -59,6 +59,22 @@ dependencies = [ "libc", ] +[[package]] +name = "annotate-snippets" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" +dependencies = [ + "anstyle", + "unicode-width", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + [[package]] name = "anyhow" version = "1.0.103" @@ -121,32 +137,16 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "ashpd" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01992ad7774250d5b7fe214e2676cb99bf92564436d8135ab44fe815e71769a9" -dependencies = [ - "async-fs 2.2.0", - "async-net", - "enumflags2", - "futures-channel", - "futures-util", - "rand 0.8.7", - "serde", - "serde_repr", - "url", - "zbus", -] - [[package]] name = "async-broadcast" -version = "0.5.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ - "event-listener 2.5.3", + "event-listener", + "event-listener-strategy", "futures-core", + "pin-project-lite", ] [[package]] @@ -169,55 +169,12 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.4.1", - "futures-lite 2.6.1", + "fastrand", + "futures-lite", "pin-project-lite", "slab", ] -[[package]] -name = "async-fs" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "blocking", - "futures-lite 1.13.0", -] - -[[package]] -name = "async-fs" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" -dependencies = [ - "async-lock 3.4.2", - "blocking", - "futures-lite 2.6.1", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.28", - "slab", - "socket2 0.4.10", - "waker-fn", -] - [[package]] name = "async-io" version = "2.6.0" @@ -228,60 +185,41 @@ dependencies = [ "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "parking", - "polling 3.11.0", + "polling", "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - [[package]] name = "async-lock" version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "event-listener 5.4.1", + "event-listener", "event-listener-strategy", "pin-project-lite", ] -[[package]] -name = "async-net" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" -dependencies = [ - "async-io 2.6.0", - "blocking", - "futures-lite 2.6.1", -] - [[package]] name = "async-process" -version = "1.8.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ - "async-io 1.13.0", - "async-lock 2.8.0", + "async-channel", + "async-io", + "async-lock", "async-signal", + "async-task", "blocking", "cfg-if", - "event-listener 3.1.0", - "futures-lite 1.13.0", - "rustix 0.38.44", - "windows-sys 0.48.0", + "event-listener", + "futures-lite", + "rustix 1.1.4", ] [[package]] @@ -301,8 +239,8 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ - "async-io 2.6.0", - "async-lock 3.4.2", + "async-io", + "async-lock", "atomic-waker", "cfg-if", "futures-core", @@ -350,7 +288,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -394,7 +332,7 @@ dependencies = [ "anyhow", "arrayvec", "log", - "nom", + "nom 8.0.0", "num-rational", "v_frame", ] @@ -420,6 +358,25 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "annotate-snippets", + "bitflags 2.13.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 1.3.0", + "syn 2.0.118", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -498,7 +455,7 @@ dependencies = [ "async-channel", "async-task", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "piper", ] @@ -549,6 +506,20 @@ name = "bytemuck" version = "1.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] [[package]] name = "byteorder" @@ -593,7 +564,7 @@ checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" dependencies = [ "glib-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -647,7 +618,7 @@ dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -656,6 +627,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "cfb" version = "0.7.3" @@ -674,7 +654,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", - "target-lexicon", + "target-lexicon 0.12.16", +] + +[[package]] +name = "cfg-expr" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c" +dependencies = [ + "smallvec", + "target-lexicon 0.13.5", ] [[package]] @@ -683,6 +673,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.45" @@ -697,6 +698,17 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading 0.8.9", +] + [[package]] name = "clipboard-win" version = "5.4.1" @@ -771,6 +783,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" + [[package]] name = "core-foundation" version = "0.9.4" @@ -854,6 +872,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -1006,17 +1033,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "derive_arbitrary" version = "1.4.2" @@ -1103,6 +1119,15 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading 0.7.4", +] + [[package]] name = "dlopen2" version = "0.8.2" @@ -1156,6 +1181,61 @@ dependencies = [ "serde", ] +[[package]] +name = "drm" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" +dependencies = [ + "bitflags 2.13.0", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "libc", + "rustix 0.38.44", +] + +[[package]] +name = "drm" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a41816e58f47f49acfd956651055ddcf137c4882c2098c30c448817af21183a" +dependencies = [ + "bitflags 2.13.0", + "bytemuck", + "bytemuck_derive", + "drm-ffi", + "drm-fourcc", + "libc", + "rustix 1.1.4", +] + +[[package]] +name = "drm-ffi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51a91c9b32ac4e8105dec255e849e0d66e27d7c34d184364fb93e469db08f690" +dependencies = [ + "drm-sys", + "rustix 1.1.4", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8e1361066d91f5ffccff060a3c3be9c3ecde15be2959c1937595f7a82a9f8" +dependencies = [ + "libc", + "linux-raw-sys 0.9.4", +] + [[package]] name = "dtoa" version = "1.0.11" @@ -1233,6 +1313,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + [[package]] name = "enumflags2" version = "0.7.12" @@ -1307,23 +1393,6 @@ version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - [[package]] name = "event-listener" version = "5.4.1" @@ -1341,7 +1410,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.1", + "event-listener", "pin-project-lite", ] @@ -1362,15 +1431,6 @@ dependencies = [ "zune-inflate", ] -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.4.1" @@ -1398,7 +1458,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ - "memoffset 0.9.1", + "memoffset", "rustc_version", ] @@ -1520,28 +1580,13 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-lite" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.4.1", + "fastrand", "futures-core", "futures-io", "parking", @@ -1587,6 +1632,30 @@ dependencies = [ "slab", ] +[[package]] +name = "gbm" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce852e998d3ca5e4a97014fb31c940dc5ef344ec7d364984525fd11e8a547e6a" +dependencies = [ + "bitflags 2.13.0", + "drm 0.14.1", + "drm-fourcc", + "gbm-sys", + "libc", + "wayland-backend", + "wayland-server", +] + +[[package]] +name = "gbm-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13a5f2acc785d8fb6bf6b7ab6bfb0ef5dad4f4d97e8e70bb8e470722312f76f" +dependencies = [ + "libc", +] + [[package]] name = "gdk" version = "0.18.2" @@ -1625,7 +1694,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -1642,7 +1711,7 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -1656,7 +1725,7 @@ dependencies = [ "gobject-sys", "libc", "pkg-config", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -1682,7 +1751,7 @@ dependencies = [ "gdk-sys", "glib-sys", "libc", - "system-deps", + "system-deps 6.2.2", "x11", ] @@ -1738,6 +1807,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", ] [[package]] @@ -1778,10 +1848,30 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", "winapi", ] +[[package]] +name = "gl" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94edab108827d67608095e269cf862e60d920f144a5026d3dbcfd8b877fb404" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + [[package]] name = "glib" version = "0.18.5" @@ -1826,7 +1916,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" dependencies = [ "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -1861,14 +1951,13 @@ checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" dependencies = [ "glib-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] name = "goodwebtools" version = "1.0.0" dependencies = [ - "ashpd", "chrono", "cocoa", "core-graphics 0.23.2", @@ -1886,7 +1975,7 @@ dependencies = [ "tauri-plugin-shell", "tauri-plugin-updater", "tokio", - "windows 0.54.0", + "xcap", ] [[package]] @@ -1925,7 +2014,7 @@ dependencies = [ "gobject-sys", "libc", "pango-sys", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -1985,12 +2074,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hermit-abi" version = "0.5.2" @@ -2104,7 +2187,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.5", + "socket2", "tokio", "tower-service", "tracing", @@ -2325,15 +2408,6 @@ dependencies = [ "cfb", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "interpolate_name" version = "0.2.4" @@ -2345,17 +2419,6 @@ dependencies = [ "syn 2.0.118", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" version = "2.12.0" @@ -2381,6 +2444,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -2416,7 +2488,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -2547,6 +2619,22 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + [[package]] name = "lazy_static" version = "1.5.0" @@ -2579,7 +2667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ "gtk-sys", - "libloading", + "libloading 0.7.4", "once_cell", ] @@ -2618,6 +2706,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + [[package]] name = "libm" version = "0.2.16" @@ -2634,10 +2732,52 @@ dependencies = [ ] [[package]] -name = "linux-raw-sys" -version = "0.3.8" +name = "libspa" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2909f3be29d674e7f10604aff18d1bbe1bb03c4cd61c8a8ba19c0b1d162f7d4e" +dependencies = [ + "bitflags 2.13.0", + "cc", + "cookie-factory", + "libc", + "libspa-sys", + "nom 8.0.0", + "rustix 1.1.4", + "system-deps 7.0.8", +] + +[[package]] +name = "libspa-sys" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "69ad52764fca54818486f3cf75afec844d1f1a1568c24dcee25d41b1ab007dda" +dependencies = [ + "bindgen", + "cc", + "system-deps 7.0.8", +] + +[[package]] +name = "libwayshot-xcap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8a46e4d016ef464e386e4970f5415fe0939c2d63abe0c2f539c05ef88c5fe5" +dependencies = [ + "drm 0.15.0", + "gbm", + "gl", + "image", + "khronos-egl", + "memmap2", + "rustix 1.1.4", + "thiserror 2.0.18", + "tracing", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", +] [[package]] name = "linux-raw-sys" @@ -2645,6 +2785,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2718,12 +2864,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] -name = "memoffset" -version = "0.7.1" +name = "memmap2" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ - "autocfg", + "libc", ] [[package]] @@ -2741,6 +2887,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "minisign-verify" version = "0.2.5" @@ -2830,24 +2982,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] -name = "nix" -version = "0.26.4" +name = "no_std_io2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.7.1", + "memchr", ] [[package]] -name = "no_std_io2" -version = "0.9.4" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", + "minimal-lexical", ] [[package]] @@ -2980,9 +3130,48 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.13.0", "block2", + "libc", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-av-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478ae33fcac9df0a18db8302387c666b8ef08a3e2d62b510ca4fc278a384b6c0" +dependencies = [ + "bitflags 2.13.0", + "block2", + "dispatch2", "objc2", + "objc2-avf-audio", + "objc2-core-audio-types", "objc2-core-foundation", "objc2-core-graphics", + "objc2-core-image", + "objc2-core-video", + "objc2-foundation", + "objc2-image-io", + "objc2-media-toolbox", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-avf-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" +dependencies = [ + "objc2", "objc2-foundation", ] @@ -2997,12 +3186,35 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc2-core-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" +dependencies = [ + "dispatch2", + "objc2", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-core-audio-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" +dependencies = [ + "bitflags 2.13.0", + "objc2", +] + [[package]] name = "objc2-core-data" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ + "bitflags 2.13.0", "objc2", "objc2-foundation", ] @@ -3014,7 +3226,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.13.0", + "block2", "dispatch2", + "libc", "objc2", ] @@ -3025,10 +3239,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.13.0", + "block2", "dispatch2", + "libc", "objc2", "objc2-core-foundation", "objc2-io-surface", + "objc2-metal", ] [[package]] @@ -3051,6 +3268,22 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc2-core-media" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d" +dependencies = [ + "bitflags 2.13.0", + "block2", + "dispatch2", + "objc2", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-video", +] + [[package]] name = "objc2-core-text" version = "0.3.2" @@ -3063,6 +3296,21 @@ dependencies = [ "objc2-core-graphics", ] +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", + "objc2-metal", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -3091,6 +3339,17 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-image-io" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b0446e98cf4a784cc7a0177715ff317eeaa8463841c616cfc78aa4f953c4ea" +dependencies = [ + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + [[package]] name = "objc2-io-surface" version = "0.3.2" @@ -3102,6 +3361,29 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-media-toolbox" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd9fdde720df3da7046bb9097811000c1e7ab5cd579fa89d96b27d56781fb30" +dependencies = [ + "objc2", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-media", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.13.0", + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-osa-kit" version = "0.3.2" @@ -3256,7 +3538,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -3344,7 +3626,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ - "fastrand 2.4.1", + "fastrand", "phf_shared", ] @@ -3383,10 +3665,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.4.1", + "fastrand", "futures-io", ] +[[package]] +name = "pipewire" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8585aba8a52ad74ccc633b8e293c1dc4277976bd5d510b925533f34fd6685f38" +dependencies = [ + "bitflags 2.13.0", + "libc", + "libspa", + "libspa-sys", + "pipewire-sys", + "rustix 1.1.4", +] + +[[package]] +name = "pipewire-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2089f245b548723e60325773c27f586b7a2372c79ea941b246cd0d654706adc" +dependencies = [ + "bindgen", + "libspa-sys", + "system-deps 7.0.8", +] + [[package]] name = "pkg-config" version = "0.3.33" @@ -3422,30 +3729,14 @@ dependencies = [ [[package]] name = "png" version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.13.0", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", ] [[package]] @@ -3456,7 +3747,7 @@ checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.5.2", + "hermit-abi", "pin-project-lite", "rustix 1.1.4", "windows-sys 0.61.2", @@ -3617,6 +3908,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", +] + [[package]] name = "quick-xml" version = "0.39.4" @@ -3656,35 +3956,25 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "rand" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] @@ -3699,21 +3989,18 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom 0.2.17", + "getrandom 0.3.4", ] [[package]] name = "rand_core" -version = "0.9.5" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rav1e" @@ -3731,7 +4018,7 @@ dependencies = [ "built", "cfg-if", "interpolate_name", - "itertools", + "itertools 0.14.0", "libc", "libfuzzer-sys", "log", @@ -3743,7 +4030,7 @@ dependencies = [ "paste", "profiling", "rand 0.9.5", - "rand_chacha 0.9.0", + "rand_chacha", "simd_helpers", "thiserror 2.0.18", "v_frame", @@ -3973,20 +4260,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.37.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - [[package]] name = "rustix" version = "0.38.44" @@ -4161,6 +4434,12 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -4377,17 +4656,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "sha1" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sha2" version = "0.10.9" @@ -4395,7 +4663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -4410,6 +4678,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "shlex" version = "2.0.1" @@ -4496,16 +4770,6 @@ version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.6.5" @@ -4561,7 +4825,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -4570,12 +4834,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "string_cache" version = "0.9.0" @@ -4630,7 +4888,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote", "unicode-ident", ] @@ -4671,13 +4928,26 @@ version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ - "cfg-expr", + "cfg-expr 0.15.8", "heck 0.5.0", "pkg-config", "toml 0.8.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", + "pkg-config", + "toml 1.1.3+spec-1.1.0", + "version-compare", +] + [[package]] name = "tao" version = "0.35.3" @@ -4746,6 +5016,12 @@ 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" @@ -5107,7 +5383,7 @@ version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "fastrand 2.4.1", + "fastrand", "getrandom 0.4.3", "once_cell", "rustix 1.1.4", @@ -5244,7 +5520,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.5", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] @@ -5508,7 +5784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" dependencies = [ "memchr", - "nom", + "nom 8.0.0", "petgraph", ] @@ -5536,7 +5812,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ - "memoffset 0.9.1", + "memoffset", "tempfile", "windows-sys 0.61.2", ] @@ -5594,6 +5870,12 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "untrusted" version = "0.9.0" @@ -5686,12 +5968,6 @@ dependencies = [ "libc", ] -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - [[package]] name = "walkdir" version = "2.5.0" @@ -5803,6 +6079,7 @@ dependencies = [ "cc", "downcast-rs", "rustix 1.1.4", + "scoped-tls", "smallvec", "wayland-sys", ] @@ -5855,12 +6132,29 @@ dependencies = [ "quote", ] +[[package]] +name = "wayland-server" +version = "0.31.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1846eb04c49182e04f4a099e2a830a2b745610bbc1d61246e206f29c7000a0" +dependencies = [ + "bitflags 2.13.0", + "downcast-rs", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + [[package]] name = "wayland-sys" version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" dependencies = [ + "dlib", + "libc", + "log", + "memoffset", "pkg-config", ] @@ -5927,7 +6221,7 @@ dependencies = [ "libc", "pkg-config", "soup3-sys", - "system-deps", + "system-deps 6.2.2", ] [[package]] @@ -5981,6 +6275,12 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -6029,25 +6329,27 @@ dependencies = [ [[package]] name = "windows" -version = "0.54.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-core 0.54.0", - "windows-targets 0.52.6", + "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.61.3" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections", - "windows-core 0.61.2", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -6060,13 +6362,12 @@ dependencies = [ ] [[package]] -name = "windows-core" -version = "0.54.0" +name = "windows-collections" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-core 0.62.2", ] [[package]] @@ -6103,7 +6404,18 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +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", ] [[package]] @@ -6151,12 +6463,13 @@ dependencies = [ ] [[package]] -name = "windows-result" -version = "0.1.2" +name = "windows-numerics" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-targets 0.52.6", + "windows-core 0.62.2", + "windows-link 0.2.1", ] [[package]] @@ -6204,15 +6517,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -6264,21 +6568,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -6321,6 +6610,15 @@ 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" @@ -6336,12 +6634,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -6360,12 +6652,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -6384,12 +6670,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -6420,12 +6700,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -6444,12 +6718,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -6468,12 +6736,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -6492,12 +6754,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -6667,13 +6923,45 @@ dependencies = [ ] [[package]] -name = "xdg-home" -version = "1.3.0" +name = "xcap" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b31c7f57ddfb14983525d9149eaed160de7d3973aaa546f3e19eb3394122b6b" +dependencies = [ + "dispatch2", + "image", + "libwayshot-xcap", + "log", + "objc2", + "objc2-app-kit", + "objc2-av-foundation", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-media", + "objc2-core-video", + "objc2-foundation", + "percent-encoding", + "pipewire", + "rand 0.10.2", + "scopeguard", + "serde", + "thiserror 2.0.18", + "url", + "widestring", + "windows 0.62.2", + "xcb", + "zbus", +] + +[[package]] +name = "xcb" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +checksum = "ee4c580d8205abb0a5cf4eb7e927bd664e425b6c3263f9c5310583da96970cf6" dependencies = [ + "bitflags 1.3.2", "libc", - "windows-sys 0.59.0", + "quick-xml 0.30.0", ] [[package]] @@ -6682,6 +6970,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + [[package]] name = "y4m" version = "0.8.0" @@ -6713,40 +7007,34 @@ dependencies = [ [[package]] name = "zbus" -version = "3.15.2" +version = "5.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +checksum = "fe18fb60dc696039e738717b76eaea21e7a4489bbb1885020b43c94236d7e98a" dependencies = [ "async-broadcast", "async-executor", - "async-fs 1.6.0", - "async-io 1.13.0", - "async-lock 2.8.0", + "async-io", + "async-lock", "async-process", "async-recursion", "async-task", "async-trait", "blocking", - "byteorder", - "derivative", "enumflags2", - "event-listener 2.5.3", + "event-listener", "futures-core", - "futures-sink", - "futures-util", + "futures-lite", "hex", - "nix", - "once_cell", + "libc", "ordered-stream", - "rand 0.8.7", + "rustix 1.1.4", "serde", "serde_repr", - "sha1", - "static_assertions", "tracing", "uds_windows", - "winapi", - "xdg-home", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", "zbus_macros", "zbus_names", "zvariant", @@ -6754,26 +7042,27 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "3.15.2" +version = "5.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +checksum = "fe96480bed92df2b442a1a30df364e12d08eed03aeb061f2b8dc6afb2be91119" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "regex", - "syn 1.0.109", + "syn 2.0.118", + "zbus_names", + "zvariant", "zvariant_utils", ] [[package]] name = "zbus_names" -version = "2.6.1" +version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" dependencies = [ "serde", - "static_assertions", + "winnow 1.0.4", "zvariant", ] @@ -6901,39 +7190,40 @@ dependencies = [ [[package]] name = "zvariant" -version = "3.15.2" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +checksum = "bee2a0bcd2a907786a456fff45aaaaf54c9ba5f50b71ae9ec1a4edd200c94911" dependencies = [ - "byteorder", + "endi", "enumflags2", - "libc", "serde", - "static_assertions", - "url", + "winnow 1.0.4", "zvariant_derive", + "zvariant_utils", ] [[package]] name = "zvariant_derive" -version = "3.15.2" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +checksum = "38a708216a18780796770bfe3f4739c7c83a3e8f789b755534bbbc06e4e23e12" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.118", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "1.0.1" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +checksum = "90cb9383f9b45290407a1258b202d3f8f01db719eb60b4e4055c6375af4fc7c7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "serde", + "syn 2.0.118", + "winnow 1.0.4", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a2f5ccd..662af26 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -21,18 +21,20 @@ serde_json = "1" tokio = { version = "1", features = ["full"] } chrono = "0.4" lazy_static = "1.5" +image = "0.25" [target.'cfg(target_os = "macos")'.dependencies] cocoa = "0.25" objc = "0.2" core-graphics = "0.23" -image = "0.25" +# Cross-platform screen capture (Windows DXGI/WGC, Linux PipeWire portal). +# macOS keeps its hand-rolled core-graphics path; xcap backs Windows + Linux. [target.'cfg(target_os = "windows")'.dependencies] -windows = { version = "0.54", features = ["Graphics_Capture", "Foundation"] } +xcap = { version = "0.9.4", features = ["wgc"] } [target.'cfg(target_os = "linux")'.dependencies] -ashpd = "0.7" +xcap = "0.9.4" [features] default = ["custom-protocol"] diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index ecf6a0e..30b7427 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -156,7 +156,59 @@ pub fn capture_screen_fast(display_id: Option, fps: u32, bounds: Option= 60 { + 75 + } else if fps >= 30 { + 85 + } else { + 90 + }; + + let mut output = Vec::new(); + let mut encoder = + image::codecs::jpeg::JpegEncoder::new_with_quality(&mut output, jpeg_quality); + encoder + .encode(&rgb, rgb.width(), rgb.height(), image::ColorType::Rgb8.into()) + .map_err(|e: image::ImageError| e.to_string())?; + return Ok(output); + } + + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] return Err("Platform not supported".to_string()); } @@ -322,11 +374,41 @@ pub async fn capture_screen(options: CaptureOptions) -> Result 0.0 && scale < 1.0 { + let nw = ((img.width() as f32) * scale).round().max(1.0) as u32; + let nh = ((img.height() as f32) * scale).round().max(1.0) as u32; + img = image::imageops::resize(&img, nw, nh, image::imageops::FilterType::Triangle); + } + } + + let format = options.format.as_deref().unwrap_or("png"); + let mut output = Vec::new(); + match format { + "jpg" | "jpeg" => { + let rgb = image::DynamicImage::ImageRgba8(img).to_rgb8(); + let q = (options.quality.unwrap_or(0.92) * 100.0) as u8; + let mut encoder = + image::codecs::jpeg::JpegEncoder::new_with_quality(&mut output, q); + encoder + .encode(&rgb, rgb.width(), rgb.height(), image::ColorType::Rgb8.into()) + .map_err(|e: image::ImageError| e.to_string())?; + } + _ => { + image::codecs::png::PngEncoder::new(&mut output) + .write_image(&img, img.width(), img.height(), image::ColorType::Rgba8.into()) + .map_err(|e: image::ImageError| e.to_string())?; + } + } + return Ok(tauri::ipc::Response::new(output)); + } #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] return Err("Unsupported platform".to_string()); @@ -366,11 +448,29 @@ pub async fn list_displays() -> Result, String> { return Ok(result); } - #[cfg(target_os = "windows")] - return Err("Windows display enumeration not yet implemented".to_string()); - - #[cfg(target_os = "linux")] - return Err("Linux display enumeration not yet implemented".to_string()); + #[cfg(any(target_os = "windows", target_os = "linux"))] + { + let monitors = xcap::Monitor::all().map_err(|e| e.to_string())?; + let mut result = Vec::new(); + for (i, m) in monitors.iter().enumerate() { + let scale = m.scale_factor().unwrap_or(1.0).max(1.0); + let phys_w = m.width().unwrap_or(0); + let phys_h = m.height().unwrap_or(0); + result.push(DisplayInfo { + id: m.id().map(|v| v as i32).unwrap_or(i as i32), + name: m + .friendly_name() + .or_else(|_| m.name()) + .unwrap_or_else(|_| format!("Display {}", i + 1)), + // Report logical dimensions (physical / scale) to match macOS, + // so the frontend's logical region coords scale correctly. + width: ((phys_w as f32) / scale).round() as u32, + height: ((phys_h as f32) / scale).round() as u32, + is_main: m.is_primary().unwrap_or(false), + }); + } + return Ok(result); + } #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] return Err("Unsupported platform".to_string()); @@ -393,6 +493,183 @@ pub async fn capture_region(bounds: Rectangle, display_id: Option) -> Resul capture_screen_fast(target_display, 10, Some(bounds)).map(tauri::ipc::Response::new) } +// ── Held full-res captures for native region cropping (Phase D) ────────────── +// The in-tool region flow captures the frozen full screen once (while the main +// window is hidden), holds it in Rust, and crops server-side after the user +// selects — so only the small crop crosses IPC, never the whole screen. + +// The held frame is stored as an RGBA image so cropping is one platform-agnostic +// path (macOS core-graphics and Windows/Linux xcap both feed into it). +struct HeldCapture { + image: image::RgbaImage, // physical pixels, RGBA + logical_width: u32, + logical_height: u32, +} + +lazy_static::lazy_static! { + static ref HELD_CAPTURES: std::sync::Mutex> = + std::sync::Mutex::new(std::collections::HashMap::new()); +} + +static CAPTURE_SEQ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); + +/// Map a logical-pixel selection rect to a clamped physical-pixel crop rect, +/// applying the display's HiDPI scale. Pure — unit-tested. +fn physical_crop_rect( + region: &Rectangle, + phys_w: u32, + phys_h: u32, + logical_w: u32, + logical_h: u32, +) -> (u32, u32, u32, u32) { + if logical_w == 0 || logical_h == 0 || phys_w == 0 || phys_h == 0 { + return (0, 0, phys_w.max(1), phys_h.max(1)); + } + let scale_x = phys_w as f64 / logical_w as f64; + let scale_y = phys_h as f64 / logical_h as f64; + let x = ((region.x as f64 * scale_x).max(0.0) as u32).min(phys_w.saturating_sub(1)); + let y = ((region.y as f64 * scale_y).max(0.0) as u32).min(phys_h.saturating_sub(1)); + let w = ((region.width as f64 * scale_x).round() as u32).clamp(1, phys_w - x); + let h = ((region.height as f64 * scale_y).round() as u32).clamp(1, phys_h - y); + (x, y, w, h) +} + +/// Crop a physical RGBA frame to a logical selection rect. Pure — unit-tested. +pub(crate) fn crop_rgba( + img: &image::RgbaImage, + region: &Rectangle, + logical_w: u32, + logical_h: u32, +) -> image::RgbaImage { + let (x, y, w, h) = physical_crop_rect(region, img.width(), img.height(), logical_w, logical_h); + image::imageops::crop_imm(img, x, y, w, h).to_image() +} + +fn store_held(held: HeldCapture) -> Result { + let id = format!( + "cap_{}", + CAPTURE_SEQ.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + ); + let mut map = HELD_CAPTURES.lock().map_err(|e| e.to_string())?; + map.clear(); // only one region capture is active at a time — bound memory + map.insert(id.clone(), held); + Ok(id) +} + +/// xcap-backed full capture for Windows/Linux. Returns (physical RGBA, logical w, logical h). +#[cfg(any(target_os = "windows", target_os = "linux"))] +fn xcap_capture(display_id: Option) -> Result<(image::RgbaImage, u32, u32), String> { + let monitors = xcap::Monitor::all().map_err(|e| e.to_string())?; + let monitor = match display_id { + Some(id) => monitors + .into_iter() + .find(|m| m.id().map(|v| v as i32 == id).unwrap_or(false)) + .ok_or_else(|| format!("Display {} not found", id))?, + None => monitors + .into_iter() + .find(|m| m.is_primary().unwrap_or(false)) + .ok_or_else(|| "No primary monitor found".to_string())?, + }; + let scale = monitor.scale_factor().unwrap_or(1.0).max(1.0); + let img = monitor.capture_image().map_err(|e| e.to_string())?; + let logical_w = ((img.width() as f32) / scale).round() as u32; + let logical_h = ((img.height() as f32) / scale).round() as u32; + Ok((img, logical_w.max(1), logical_h.max(1))) +} + +/// Capture the full display and hold it in memory; returns an id for cropping. +#[tauri::command] +pub async fn capture_hold(display_id: Option) -> Result { + #[cfg(target_os = "macos")] + { + use core_graphics::display::{CGDisplay, CGMainDisplayID}; + use image::{ImageBuffer, Rgba, RgbaImage}; + + let display = if let Some(id) = display_id { + CGDisplay::new(id as u32) + } else { + unsafe { CGDisplay::new(CGMainDisplayID()) } + }; + let cg = display + .image() + .ok_or_else(|| "Failed to capture screen".to_string())?; + + let phys_w = cg.width() as u32; + let phys_h = cg.height() as u32; + let bytes_per_row = cg.bytes_per_row(); + let data = cg.data(); + + let mut buf: RgbaImage = ImageBuffer::new(phys_w, phys_h); + for y in 0..phys_h { + for x in 0..phys_w { + let offset = (y as usize * bytes_per_row) + (x as usize * 4); // BGRA + if offset + 3 < data.len() as usize { + let b = data[offset]; + let g = data[offset + 1]; + let r = data[offset + 2]; + let a = data[offset + 3]; + buf.put_pixel(x, y, Rgba([r, g, b, a])); + } + } + } + + let id = store_held(HeldCapture { + image: buf, + logical_width: display.pixels_wide() as u32, + logical_height: display.pixels_high() as u32, + })?; + println!("[Capture] Held full-res frame {} ({}x{})", id, phys_w, phys_h); + return Ok(id); + } + + #[cfg(any(target_os = "windows", target_os = "linux"))] + { + let (img, logical_w, logical_h) = xcap_capture(display_id)?; + let (pw, ph) = (img.width(), img.height()); + let id = store_held(HeldCapture { image: img, logical_width: logical_w, logical_height: logical_h })?; + println!("[Capture] Held full-res frame {} ({}x{})", id, pw, ph); + return Ok(id); + } + + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] + { + let _ = display_id; + return Err("Platform not supported".to_string()); + } +} + +/// Crop a previously-held capture to the selected region and return PNG bytes. +/// One-shot: the held frame is dropped after cropping. +#[tauri::command] +pub async fn crop_held(capture_id: String, region: Rectangle) -> Result { + use image::ImageEncoder; + + let held = { + let mut map = HELD_CAPTURES.lock().map_err(|e| e.to_string())?; + map.remove(&capture_id) + .ok_or_else(|| "Held capture not found (already used or expired)".to_string())? + }; + + let cropped = crop_rgba(&held.image, ®ion, held.logical_width, held.logical_height); + + let mut png = Vec::new(); + image::codecs::png::PngEncoder::new(&mut png) + .write_image(&cropped, cropped.width(), cropped.height(), image::ColorType::Rgba8.into()) + .map_err(|e: image::ImageError| e.to_string())?; + + println!("[Capture] Cropped held frame → {}x{} ({} bytes)", cropped.width(), cropped.height(), png.len()); + Ok(tauri::ipc::Response::new(png)) +} + +/// Free a held capture without cropping (e.g. the user cancelled selection). +#[tauri::command] +pub async fn release_held(capture_id: String) -> Result<(), String> { + if let Ok(mut map) = HELD_CAPTURES.lock() { + map.remove(&capture_id); + } + Ok(()) +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RegionSelectorOptions { @@ -649,3 +926,85 @@ pub fn get_cursor_position() -> Result<(f64, f64), String> { Err("get_cursor_position not implemented for this platform".to_string()) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn rect(x: i32, y: i32, w: u32, h: u32) -> Rectangle { + Rectangle { x, y, width: w, height: h, display_id: None } + } + + #[test] + fn crop_rect_non_hidpi_is_identity_scale() { + // logical == physical (scale 1.0) + let (x, y, w, h) = physical_crop_rect(&rect(10, 20, 100, 50), 1920, 1080, 1920, 1080); + assert_eq!((x, y, w, h), (10, 20, 100, 50)); + } + + #[test] + fn crop_rect_scales_for_2x_retina() { + // physical is 2x logical → coordinates double + let (x, y, w, h) = physical_crop_rect(&rect(10, 20, 100, 50), 2880, 1800, 1440, 900); + assert_eq!((x, y, w, h), (20, 40, 200, 100)); + } + + #[test] + fn crop_rect_clamps_to_physical_bounds() { + // a region running past the edge is clamped to the frame + let (x, y, w, h) = physical_crop_rect(&rect(1900, 1000, 500, 500), 1920, 1080, 1920, 1080); + assert_eq!(x, 1900); + assert_eq!(y, 1000); + assert_eq!(w, 20); // 1920 - 1900 + assert_eq!(h, 80); // 1080 - 1000 + } + + #[test] + fn crop_rect_negative_origin_floored_to_zero() { + let (x, y, _, _) = physical_crop_rect(&rect(-50, -30, 100, 100), 1920, 1080, 1920, 1080); + assert_eq!((x, y), (0, 0)); + } + + #[test] + fn crop_rect_zero_dims_never_produce_empty_crop() { + // width/height always at least 1 so the PNG encoder never sees 0 + let (_, _, w, h) = physical_crop_rect(&rect(0, 0, 0, 0), 1920, 1080, 1920, 1080); + assert!(w >= 1 && h >= 1); + } + + #[test] + fn crop_rect_zero_logical_falls_back_to_full_frame() { + let (x, y, w, h) = physical_crop_rect(&rect(10, 10, 100, 100), 1920, 1080, 0, 0); + assert_eq!((x, y, w, h), (0, 0, 1920, 1080)); + } + + fn gradient(w: u32, h: u32) -> image::RgbaImage { + // Each pixel encodes its own (x, y) in the R and G channels. + let mut img = image::RgbaImage::new(w, h); + for y in 0..h { + for x in 0..w { + img.put_pixel(x, y, image::Rgba([x as u8, y as u8, 0, 255])); + } + } + img + } + + #[test] + fn crop_rgba_extracts_region_at_scale_1() { + let img = gradient(4, 4); + let out = crop_rgba(&img, &rect(1, 1, 2, 2), 4, 4); + assert_eq!(out.dimensions(), (2, 2)); + assert_eq!(out.get_pixel(0, 0), &image::Rgba([1, 1, 0, 255])); + assert_eq!(out.get_pixel(1, 1), &image::Rgba([2, 2, 0, 255])); + } + + #[test] + fn crop_rgba_scales_logical_region_on_hidpi() { + // 8x8 physical, 4x4 logical (2x). Logical (1,1,2,2) → physical (2,2,4,4). + let img = gradient(8, 8); + let out = crop_rgba(&img, &rect(1, 1, 2, 2), 4, 4); + assert_eq!(out.dimensions(), (4, 4)); + assert_eq!(out.get_pixel(0, 0), &image::Rgba([2, 2, 0, 255])); + assert_eq!(out.get_pixel(3, 3), &image::Rgba([5, 5, 0, 255])); + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 0f5aade..8d06c75 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -29,6 +29,9 @@ fn main() { commands::list_displays, commands::capture_window, commands::capture_region, + commands::capture_hold, + commands::crop_held, + commands::release_held, commands::show_region_selector, commands::close_region_selector, commands::submit_region_selection, diff --git a/src-tauri/src/recording.rs b/src-tauri/src/recording.rs index b04739d..f1103a9 100644 --- a/src-tauri/src/recording.rs +++ b/src-tauri/src/recording.rs @@ -110,15 +110,21 @@ pub fn start_recording(options: RecordOptions) -> Result, + bounds: Option, + stop_flag: Arc>, + frames: Arc>>>, + duration_out: Arc>>, +) { + use std::sync::mpsc::RecvTimeoutError; + + // Resolve the target monitor. + let monitors = match xcap::Monitor::all() { + Ok(m) => m, + Err(e) => { + eprintln!("[Recording] xcap monitor enumeration failed: {}", e); + return; + } + }; + let monitor = match display_id { + Some(id) => monitors + .into_iter() + .find(|m| m.id().map(|v| v as i32 == id).unwrap_or(false)), + None => monitors.into_iter().find(|m| m.is_primary().unwrap_or(false)), + }; + let monitor = match monitor { + Some(m) => m, + None => { + eprintln!("[Recording] target monitor not found for recording"); + return; + } + }; + let scale = monitor.scale_factor().unwrap_or(1.0).max(1.0); + + // One persistent capture stream — no per-frame portal round-trips. + let (recorder, rx) = match monitor.video_recorder() { + Ok(v) => v, + Err(e) => { + eprintln!("[Recording] xcap video_recorder init failed: {}", e); + return; + } + }; + if let Err(e) = recorder.start() { + eprintln!("[Recording] xcap recorder start failed: {}", e); + return; + } + println!("[Recording] xcap capture stream started (target {}fps)", target_fps); + + let normalized_fps = if target_fps == 35 { 30 } else { target_fps.max(1) }; + let frame_interval = Duration::from_secs_f64(1.0 / normalized_fps as f64); + let jpeg_quality: u8 = if target_fps == 35 { + 95 + } else if target_fps >= 60 { + 75 + } else if target_fps >= 30 { + 85 + } else { + 90 + }; + + let start_time = Instant::now(); + let mut last_kept: Option = None; + let mut frame_count = 0usize; + + loop { + if stop_flag.lock().map(|f| *f).unwrap_or(true) { + break; + } + + // Bounded wait so we can re-check the stop flag promptly. + let frame = match rx.recv_timeout(Duration::from_millis(200)) { + Ok(f) => f, + Err(RecvTimeoutError::Timeout) => continue, + Err(RecvTimeoutError::Disconnected) => break, + }; + + // Throttle the stream down to the target frame rate. + if let Some(prev) = last_kept { + if prev.elapsed() < frame_interval { + continue; + } + } + last_kept = Some(Instant::now()); + + // frame.raw is physical, tightly-packed RGBA. + let img = match image::RgbaImage::from_raw(frame.width, frame.height, frame.raw) { + Some(i) => i, + None => continue, + }; + + // Crop to the requested region (logical → physical) if any. + let logical_w = ((frame.width as f32) / scale).round() as u32; + let logical_h = ((frame.height as f32) / scale).round() as u32; + let rgba = match &bounds { + Some(rect) => { + crate::commands::crop_rgba(&img, rect, logical_w.max(1), logical_h.max(1)) + } + None => img, + }; + + // Force even dimensions (H.264 requires them) — deterministic, so every + // frame in a recording ends up the same size for the encoder. + let ew = (rgba.width() - rgba.width() % 2).max(2); + let eh = (rgba.height() - rgba.height() % 2).max(2); + let rgba = if ew != rgba.width() || eh != rgba.height() { + image::imageops::crop_imm(&rgba, 0, 0, ew, eh).to_image() + } else { + rgba + }; + + // Encode to JPEG (the frame format stop_recording's encoder expects). + let rgb = image::DynamicImage::ImageRgba8(rgba).to_rgb8(); + let mut buf = Vec::new(); + let mut enc = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, jpeg_quality); + if enc + .encode(&rgb, rgb.width(), rgb.height(), image::ColorType::Rgb8.into()) + .is_ok() + { + if let Ok(mut v) = frames.lock() { + v.push(buf); + frame_count += 1; + } + } + } + + let _ = recorder.stop(); + let total = start_time.elapsed(); + if let Ok(mut d) = duration_out.lock() { + *d = Some(total); + } + println!( + "[Recording] xcap stream ended: {} frames in {:.2}s (~{:.1}fps)", + frame_count, + total.as_secs_f64(), + frame_count as f64 / total.as_secs_f64().max(0.001), + ); +} + pub fn stop_recording(handle_id: String) -> Result, String> { println!("[Recording] Stopping recording: {}", handle_id); diff --git a/src/islands/media/Screenshot.tsx b/src/islands/media/Screenshot.tsx index b8affa0..4685a90 100644 --- a/src/islands/media/Screenshot.tsx +++ b/src/islands/media/Screenshot.tsx @@ -179,70 +179,30 @@ export default function Screenshot() { // STEP 2: Show overlay window (user selects region) const regionPromise = captureService.showRegionSelector(selectedDisplay); - // STEP 3: Capture full-res screenshot in parallel for cropping - const fullBlob = await captureService.captureScreen({ - format: 'png', - displayId: selectedDisplay, - }); - - const fullDataUrl = await new Promise((resolve, reject) => { - const reader2 = new FileReader(); - reader2.onload = () => resolve(reader2.result as string); - reader2.onerror = reject; - reader2.readAsDataURL(fullBlob); - }); + // STEP 3: Capture + HOLD the frozen full-res frame natively (in parallel + // with selection, while the main window is hidden). Only the final crop + // crosses IPC — never the whole screen — and the crop happens in Rust. + const { invoke } = await import('@tauri-apps/api/core'); + const captureId = await invoke('capture_hold', { displayId: selectedDisplay }); // STEP 4: Wait for region selection const region = await regionPromise; - // Clean up overlay screenshot from localStorage - localStorage.removeItem('overlay-screenshot'); - if (!region) { - // User cancelled + // User cancelled — free the held frame + await invoke('release_held', { captureId }).catch(() => {}); setCapturing(false); return; } - // STEP 5: Load the full-res screenshot and crop to selected region - const fullImg = new Image(); - await new Promise((resolve, reject) => { - fullImg.onload = () => resolve(); - fullImg.onerror = () => reject(new Error('Failed to load screenshot')); - fullImg.src = fullDataUrl; // Use full-res screenshot for cropping - }); - - // Calculate HiDPI scale factor (physical pixels / logical pixels) - const targetDisplay = displays.find(d => d.id === selectedDisplay) || displays.find(d => d.isMain)!; - const scaleX = fullImg.width / targetDisplay.width; - const scaleY = fullImg.height / targetDisplay.height; - - console.log('[Screenshot] Scale factor:', scaleX, 'x', scaleY, - '(logical:', targetDisplay.width, 'x', targetDisplay.height, - 'physical:', fullImg.width, 'x', fullImg.height, ')'); - console.log('[Screenshot] Logical region:', region); - console.log('[Screenshot] Physical region:', - Math.round(region.x * scaleX), Math.round(region.y * scaleY), - Math.round(region.width * scaleX), Math.round(region.height * scaleY)); - - // Scale region coordinates to physical pixels - const physicalX = Math.round(region.x * scaleX); - const physicalY = Math.round(region.y * scaleY); - const physicalWidth = Math.round(region.width * scaleX); - const physicalHeight = Math.round(region.height * scaleY); - - // Create canvas with cropped region (in physical pixels) + // STEP 5: Crop the held frame natively (server-side HiDPI scaling) → PNG + const croppedBytes = await invoke('crop_held', { captureId, region }); + const bmp = await createImageBitmap(new Blob([croppedBytes], { type: 'image/png' })); const canvas = document.createElement('canvas'); - canvas.width = physicalWidth; - canvas.height = physicalHeight; - const ctx = canvas.getContext('2d')!; - - // Draw the selected region from the full screenshot (using physical pixel coordinates) - ctx.drawImage( - fullImg, - physicalX, physicalY, physicalWidth, physicalHeight, // source (physical pixels) - 0, 0, physicalWidth, physicalHeight // destination - ); + canvas.width = bmp.width; + canvas.height = bmp.height; + canvas.getContext('2d')!.drawImage(bmp, 0, 0); + bmp.close(); setShot(canvas); setPreviewUrl(prev => {