From fc1d9f242a639e0dd5159e697b091362290b9a17 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:05:39 +0100 Subject: [PATCH 001/146] feat(desktop-gpui): scaffold standalone gpui app Phase 0 of the gpui rewrite: a standalone crate that opens the main recording window shell. No tauri, no webview. Kept out of the root cargo workspace on purpose. The gpui tree pulls its own git revisions of the wgpu/font stacks, and the root workspace carries `[patch.crates-io]` entries (vendored wgpu-hal, tao) that exist only for the Tauri app. A nested workspace leaves the root Cargo.lock, the `cargo hakari` workspace-hack and CI untouched. gpui is pinned to the wingleeio/zed fork at 5d1f83d, which carries the Porter-Duff OVER fix for destination alpha on transparent windows that the rounded main-window shell depends on. Device enumeration reuses cap-camera, scap-targets and the same cpal fork the recorder uses, so device identities stay byte-identical to what cap-recording expects. rust-toolchain.toml pins stable here rather than the root's 1.88.0: gpui's tree needs 1.89+ (smol_str 0.3.6, cosmic-text 0.19). The nearest toolchain file wins, so the Tauri app is unaffected. Dev profile builds dependencies at opt-level 2 and the app crate at 0, matching zed/comet -- gpui is unusable with unoptimized deps. --- apps/desktop-gpui/Cargo.lock | 10223 ++++++++++++++++ apps/desktop-gpui/Cargo.toml | 54 + apps/desktop-gpui/assets/fonts/Geist-Bold.ttf | Bin 0 -> 128824 bytes .../assets/fonts/Geist-Medium.ttf | Bin 0 -> 127660 bytes .../assets/fonts/Geist-SemiBold.ttf | Bin 0 -> 127872 bytes apps/desktop-gpui/assets/fonts/Geist.ttf | Bin 0 -> 169056 bytes apps/desktop-gpui/rust-toolchain.toml | 6 + apps/desktop-gpui/src/main.rs | 61 + 8 files changed, 10344 insertions(+) create mode 100644 apps/desktop-gpui/Cargo.lock create mode 100644 apps/desktop-gpui/Cargo.toml create mode 100644 apps/desktop-gpui/assets/fonts/Geist-Bold.ttf create mode 100644 apps/desktop-gpui/assets/fonts/Geist-Medium.ttf create mode 100644 apps/desktop-gpui/assets/fonts/Geist-SemiBold.ttf create mode 100644 apps/desktop-gpui/assets/fonts/Geist.ttf create mode 100644 apps/desktop-gpui/rust-toolchain.toml create mode 100644 apps/desktop-gpui/src/main.rs diff --git a/apps/desktop-gpui/Cargo.lock b/apps/desktop-gpui/Cargo.lock new file mode 100644 index 0000000000..7536f3a15d --- /dev/null +++ b/apps/desktop-gpui/Cargo.lock @@ -0,0 +1,10223 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "accesskit" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" +dependencies = [ + "enumn", + "uuid", +] + +[[package]] +name = "accesskit_atspi_common" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8c61bee90b42a772d39d06a740207dc71a4e780004ace1db8d99fb1baaa954" +dependencies = [ + "accesskit", + "accesskit_consumer 0.36.0", + "atspi-common", + "phf 0.13.1", + "serde", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e0d7e25d06f4dc21d1774d67146e9e80d6789216cbd4d1e88185b0095dba60" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_consumer" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950720ce064757a1b629caad3a408e8d2c63bb01f29b8a3ff8daa331053ffeb" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_consumer" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_macos" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce02dc63b43f0c9296af9ac946312a2dc8814427d7a64d2d600971dac55b6076" +dependencies = [ + "accesskit", + "accesskit_consumer 0.38.0", + "hashbrown 0.16.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b016ca8db0ea0ea2ceff29a9d6240391492d960716aa471967c00e8cc8cb197c" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36e93ac7bf50b964f1cbb75f741629a4e950571baa1ef1274457ab5a80d9bcc2" +dependencies = [ + "accesskit", + "accesskit_consumer 0.37.0", + "hashbrown 0.16.1", + "static_assertions", + "windows 0.62.2", + "windows-core 0.62.2", +] + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.13.1", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "ar_archive_writer" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cd58deff2140a0a8eae87e417bd01db68a33e148aa93d1e8cd837e55e312b6" +dependencies = [ + "object 0.39.1", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "ashpd" +version = "0.13.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8421aaa9644a5faf26735f258b669b15f063313ef8f8e2bdb28912a1a6f111" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "getrandom 0.4.3", + "serde", + "serde_repr", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-io", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "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", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.1.4", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.4", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atspi" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" +dependencies = [ + "atspi-common", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-proxies" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" +dependencies = [ + "atspi-common", + "serde", + "zbus", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.20", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "axum-macros", + "base64 0.22.1", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object 0.37.3", + "rustc-demangle", + "windows-link 0.2.1", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex 1.3.0", + "syn 2.0.119", + "which 4.4.2", +] + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags 2.13.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.3", + "shlex 1.3.0", + "syn 2.0.119", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.13.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.3", + "shlex 1.3.0", + "syn 2.0.119", +] + +[[package]] +name = "bit-set" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +dependencies = [ + "serde_core", +] + +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.4", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "borsh" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" +dependencies = [ + "bytes", + "cfg_aliases", +] + +[[package]] +name = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "built" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.13.1", + "polling", + "rustix 1.1.4", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop", + "rustix 1.1.4", + "wayland-backend", + "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 = "cap-camera" +version = "0.1.0" +dependencies = [ + "cap-camera-avfoundation", + "cap-camera-windows", + "cidre", + "objc2-av-foundation", + "thiserror 1.0.69", + "tracing", + "v4l", + "windows 0.60.0", + "windows-core 0.60.1", + "workspace-hack", +] + +[[package]] +name = "cap-camera-avfoundation" +version = "0.1.0" +dependencies = [ + "cidre", + "tracing", + "workspace-hack", +] + +[[package]] +name = "cap-camera-directshow" +version = "0.1.0" +dependencies = [ + "thiserror 1.0.69", + "tracing", + "windows 0.60.0", + "windows-core 0.60.1", + "workspace-hack", +] + +[[package]] +name = "cap-camera-mediafoundation" +version = "0.1.0" +dependencies = [ + "cap-mediafoundation-utils", + "parking_lot", + "thiserror 1.0.69", + "tracing", + "windows 0.60.0", + "windows-core 0.60.1", + "workspace-hack", +] + +[[package]] +name = "cap-camera-windows" +version = "0.1.0" +dependencies = [ + "cap-camera-directshow", + "cap-camera-mediafoundation", + "cap-mediafoundation-utils", + "inquire", + "thiserror 1.0.69", + "windows 0.60.0", + "windows-core 0.60.1", + "workspace-hack", +] + +[[package]] +name = "cap-desktop-gpui" +version = "0.1.0" +dependencies = [ + "cap-camera", + "cpal", + "gpui", + "gpui_platform", + "gpui_tokio", + "scap-targets", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "cap-mediafoundation-utils" +version = "0.1.0" +dependencies = [ + "windows 0.60.0", + "windows-core 0.60.1", + "workspace-hack", +] + +[[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 = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cbindgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" +dependencies = [ + "heck 0.4.1", + "indexmap 2.14.0", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.119", + "tempfile", + "toml 0.8.23", +] + +[[package]] +name = "cc" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", +] + +[[package]] +name = "cesu8" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "cidre" +version = "0.11.0" +source = "git+https://github.com/CapSoftware/cidre?rev=bf84b67079a8#bf84b67079a89d6eaf01c1dab073baaba1ea8f77" +dependencies = [ + "cidre-macros", + "parking_lot", +] + +[[package]] +name = "cidre-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82bc2f84c0baaa09299da3a03864491549685912c1e338a54211e00589dc1e4c" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation 0.1.2", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types 0.5.0", + "libc", + "objc", +] + +[[package]] +name = "cocoa" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" +dependencies = [ + "bitflags 2.13.1", + "block", + "cocoa-foundation 0.2.0", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "foreign-types 0.5.0", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" +dependencies = [ + "bitflags 2.13.1", + "block", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width 0.2.2", +] + +[[package]] +name = "collections" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "gpui_util", + "indexmap 2.14.0", + "rustc-hash 2.1.3", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compression-codecs" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" +dependencies = [ + "bzip2", + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-helmer-fork" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "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 = "core-graphics2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4416167a69126e617f8d0a214af0e3c1dbdeffcb100ddf72dcd1a1ac9893c146" +dependencies = [ + "bitflags 2.13.1", + "block", + "cfg-if", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "core-text" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130" +dependencies = [ + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-video" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139679cc63eb9504bdbe37e37874b0247136177655f0008588781e90863afa62" +dependencies = [ + "block", + "core-foundation 0.10.1", + "core-graphics2", + "io-surface", + "libc", + "metal", +] + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "coreaudio-rs" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" +dependencies = [ + "bitflags 1.3.2", + "core-foundation-sys", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b4739a805a62757a83e5654fa3faabec0442666b263bb2287d5a8185bfd953" +dependencies = [ + "bindgen 0.72.1", +] + +[[package]] +name = "cosmic-text" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73" +dependencies = [ + "bitflags 2.13.1", + "fontdb", + "harfrust", + "linebender_resource_handle", + "log", + "rangemap", + "rustc-hash 2.1.3", + "self_cell", + "skrifa 0.40.0", + "smol_str", + "swash", + "sys-locale", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "cpal" +version = "0.15.3" +source = "git+https://github.com/CapSoftware/cpal?rev=6013cb5f8bd3#6013cb5f8bd34ddfbf7b215b9540fd211ce829fc" +dependencies = [ + "alsa", + "core-foundation-sys", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2 0.4.3", + "ndk", + "ndk-context", + "oboe", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.54.0", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[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", +] + +[[package]] +name = "ctor" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" +dependencies = [ + "link-section", + "linktime-proc-macro", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + +[[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 = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case 0.10.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "derive_refineable" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +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", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.1" +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", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[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-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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dwrote" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[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", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.74.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "num-complex", + "pulp", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +dependencies = [ + "getrandom 0.4.3", +] + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "flume" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" +dependencies = [ + "fastrand", + "futures-core", + "futures-sink", + "spin 0.9.9", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +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 = "font-types" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "font-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75382bc7392ef10aad10935f92fc3db36d2d4dad0e5d96d8d65e04f89a07ec39" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree 0.20.0", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-concurrency" +version = "7.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" +dependencies = [ + "fixedbitset", + "futures-core", + "futures-lite", + "pin-project", + "smallvec", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link 0.2.1", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[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 = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.20", + "windows 0.62.2", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.13.1", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "gpui" +version = "0.2.2" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "accesskit", + "anyhow", + "async-channel", + "async-task", + "bindgen 0.71.1", + "bitflags 2.13.1", + "block", + "cbindgen", + "chrono", + "cocoa 0.26.0", + "cocoa-foundation 0.2.0", + "collections", + "core-foundation 0.10.1", + "core-foundation-sys", + "core-graphics 0.24.0", + "core-text", + "core-video", + "ctor 1.0.13", + "derive_more 2.1.1", + "embed-resource", + "etagere", + "foreign-types 0.5.0", + "futures", + "futures-concurrency", + "getrandom 0.3.4", + "gpui_macros", + "gpui_shared_string", + "gpui_util", + "heapless", + "http_client", + "image 0.25.10", + "inventory", + "itertools 0.14.0", + "log", + "lyon", + "mach2 0.5.0", + "media", + "metal", + "num_cpus", + "objc", + "parking", + "parking_lot", + "pathfinder_geometry", + "pin-project", + "pollster 0.4.0", + "postage", + "profiling", + "rand 0.9.5", + "raw-window-handle", + "refineable", + "regex", + "resvg", + "scheduler", + "schemars 1.2.2", + "seahash", + "serde", + "serde_json", + "slotmap", + "smallvec", + "spin 0.10.1", + "stacksafe", + "strum", + "sum_tree", + "taffy", + "thiserror 2.0.20", + "ttf-parser", + "url", + "usvg", + "util_macros", + "uuid", + "waker-fn", + "web-time", + "windows 0.61.3", + "zed-font-kit", + "zed-scap", +] + +[[package]] +name = "gpui_linux" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "accesskit", + "accesskit_unix", + "anyhow", + "as-raw-xcb-connection", + "ashpd", + "bitflags 2.13.1", + "bytemuck", + "calloop", + "calloop-wayland-source", + "collections", + "filedescriptor", + "futures", + "gpui", + "gpui_util", + "gpui_wgpu", + "http_client", + "image 0.25.10", + "itertools 0.14.0", + "libc", + "log", + "notify-rust", + "oo7", + "open", + "parking_lot", + "pathfinder_geometry", + "pollster 0.4.0", + "profiling", + "raw-window-handle", + "smallvec", + "smol", + "strum", + "swash", + "url", + "uuid", + "wayland-backend", + "wayland-client", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-plasma", + "wayland-protocols-wlr", + "x11-clipboard", + "x11rb", + "xkbcommon", + "zed-scap", + "zed-xim", +] + +[[package]] +name = "gpui_macos" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "accesskit", + "accesskit_macos", + "anyhow", + "async-task", + "block", + "block2 0.6.2", + "cbindgen", + "cocoa 0.26.0", + "collections", + "core-foundation 0.10.1", + "core-foundation-sys", + "core-graphics 0.24.0", + "core-text", + "core-video", + "ctor 1.0.13", + "derive_more 2.1.1", + "dispatch2", + "etagere", + "foreign-types 0.5.0", + "futures", + "gpui", + "gpui_util", + "image 0.25.10", + "itertools 0.14.0", + "libc", + "log", + "mach2 0.5.0", + "media", + "metal", + "objc", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "objc2-user-notifications", + "parking_lot", + "pathfinder_geometry", + "raw-window-handle", + "semver", + "smallvec", + "strum", + "uuid", + "zed-font-kit", +] + +[[package]] +name = "gpui_macros" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui_platform" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "console_error_panic_hook", + "gpui", + "gpui_linux", + "gpui_macos", + "gpui_web", + "gpui_windows", +] + +[[package]] +name = "gpui_shared_string" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "schemars 1.2.2", + "serde", + "smol_str", +] + +[[package]] +name = "gpui_tokio" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "anyhow", + "gpui", + "gpui_util", + "tokio", +] + +[[package]] +name = "gpui_util" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "anyhow", + "log", + "which 6.0.3", +] + +[[package]] +name = "gpui_web" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "anyhow", + "console_error_panic_hook", + "futures", + "gpui", + "gpui_wgpu", + "http_client", + "js-sys", + "log", + "parking_lot", + "raw-window-handle", + "smallvec", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm_thread", + "web-sys", + "web-time", +] + +[[package]] +name = "gpui_wgpu" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "anyhow", + "bytemuck", + "collections", + "cosmic-text", + "etagere", + "gpui", + "gpui_util", + "itertools 0.14.0", + "js-sys", + "log", + "parking_lot", + "pollster 0.4.0", + "profiling", + "raw-window-handle", + "smallvec", + "swash", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu", + "zed-font-kit", +] + +[[package]] +name = "gpui_windows" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "accesskit", + "accesskit_windows", + "anyhow", + "collections", + "dunce", + "etagere", + "futures", + "gpui", + "gpui_util", + "image 0.25.10", + "itertools 0.14.0", + "log", + "parking_lot", + "rand 0.9.5", + "raw-window-handle", + "smallvec", + "uuid", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-numerics 0.2.0", + "windows-registry 0.5.3", +] + +[[package]] +name = "grid" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5" + +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "harfrust" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da2e5ae821f6e96664977bf974d6d6a2d6682f9ccee23e62ec1d134246845f9" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "core_maths", + "read-fonts 0.37.0", + "smallvec", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http_client" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "anyhow", + "async-compression", + "bytes", + "derive_more 2.1.1", + "futures", + "http", + "http-body", + "log", + "parking_lot", + "serde", + "serde_json", + "serde_urlencoded", + "url", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry 0.6.1", +] + +[[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 = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif 0.13.3", + "jpeg-decoder", + "num-traits", + "png 0.17.16", + "qoi", + "tiff 0.9.1", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif 0.14.2", + "image-webp", + "moxcms", + "num-traits", + "png 0.18.1", + "qoi", + "ravif", + "rayon", + "tiff 0.11.3", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imagesize" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" + +[[package]] +name = "imgref" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +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]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "inquire" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" +dependencies = [ + "bitflags 2.13.1", + "crossterm", + "dyn-clone", + "fuzzy-matcher", + "fxhash", + "newline-converter", + "once_cell", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "io-surface" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e" +dependencies = [ + "cgl", + "core-foundation 0.10.1", + "core-foundation-sys", + "leaky-cow", +] + +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" +dependencies = [ + "rayon", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "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 = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 2.14.0", + "selectors", +] + +[[package]] +name = "kurbo" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" +dependencies = [ + "arrayvec", + "euclid", + "polycool", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "leak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73" + +[[package]] +name = "leaky-cow" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc" +dependencies = [ + "leak", +] + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" +dependencies = [ + "libc", +] + +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + +[[package]] +name = "link-section" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c29a617ce3df32c08497bdc1ab6e2376e0b17948ac166a2fbe5977c5954cd9" + +[[package]] +name = "linktime-proc-macro" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e57c38c1e860fd37c604281cdfb1dd2216977fd76a50f85ba2f388ef3219616" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +dependencies = [ + "serde_core", + "value-bag", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lyon" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0578bdecb7d6d88987b8b2b1e3a4e2f81df9d0ece1078623324a567904e7b7" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8575c0d003ae459399623c4def180c63b77f343b1a7fee64f249b349e7699a31" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c463f9c428b7fc5ec885dcd39ce4aa61e29111d0e33483f6f98c74e89d8621e" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e43b7e44161571868f5c931d12583592c223c5583eef86b08aa02b7048a3552" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "mac-notification-sys" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" +dependencies = [ + "cc", + "log", + "objc2 0.6.4", + "objc2-foundation 0.3.2", + "time", + "uuid", +] + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "mach2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "media" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "anyhow", + "bindgen 0.71.1", + "core-foundation 0.10.1", + "core-video", + "ctor 1.0.13", + "foreign-types 0.5.0", + "metal", + "objc", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" +dependencies = [ + "bitflags 2.13.1", + "block", + "core-graphics-types 0.2.0", + "foreign-types 0.5.0", + "log", + "objc", + "paste", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "naga" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap 2.14.0", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.20", + "unicode-ident", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.13.1", + "jni-sys 0.3.1", + "log", + "ndk-sys 0.5.0+25.2.9519653", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +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 = "newline-converter" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "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 = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "notify-rust" +version = "4.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b4c1b4f2aa9f25f63a7a49d3dd0ed567b3670da15330a66b29434be899b891" +dependencies = [ + "futures-lite", + "log", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f9a86e097b0d187ad0e65667c2f58b9254671e86e7dbb78036b16692eae099" +dependencies = [ + "libm", + "num-integer", + "num-iter", + "num-traits", + "once_cell", + "rand 0.9.5", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "bytemuck", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image 0.2.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.1", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[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.1", + "block2 0.6.2", + "dispatch2", + "objc2 0.6.4", + "objc2-avf-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image 0.3.2", + "objc2-core-media", + "objc2-core-video", + "objc2-foundation 0.3.2", + "objc2-image-io", + "objc2-media-toolbox", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "objc2-avf-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" +dependencies = [ + "dispatch2", + "objc2 0.6.4", + "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.1", + "objc2 0.6.4", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2 0.6.4", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[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.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-media" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2 0.6.4", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-video", +] + +[[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.1", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.1", + "block2 0.6.2", + "libc", + "objc2 0.6.4", + "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 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.1", + "objc2 0.6.4", + "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 0.6.4", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-media", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.13.1", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.1", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "bitflags 2.13.1", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-core-location", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "object" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" +dependencies = [ + "memchr", +] + +[[package]] +name = "oboe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" +dependencies = [ + "jni", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" +dependencies = [ + "cc", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oo7" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f2bfed90f1618b4b48dcad9307f25e14ae894e2949642c87c351601d62cebd" +dependencies = [ + "aes", + "ashpd", + "async-fs", + "async-io", + "async-lock", + "blocking", + "cbc", + "cipher", + "digest", + "endi", + "futures-lite", + "futures-util", + "getrandom 0.4.3", + "hkdf", + "hmac", + "md-5", + "num", + "num-bigint-dig", + "pbkdf2", + "serde", + "serde_bytes", + "sha2", + "subtle", + "zbus", + "zbus_macros", + "zeroize", + "zvariant", +] + +[[package]] +name = "open" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cfef937e9c486488c7e3d949ae31c0f1d06bdacd75b99c086cb35356e30408" +dependencies = [ + "is-wsl", + "libc", +] + +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4500030c302e4af1d423f36f3b958d1aecb6c04184356ed5a833bf6b60435777" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "perf" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "collections", + "serde", + "serde_json", +] + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.7", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.7", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.3", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher 1.0.3", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + +[[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 = "postage" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" +dependencies = [ + "atomic", + "crossbeam-queue", + "futures", + "log", + "parking_lot", + "pin-project", + "pollster 0.2.5", + "static_assertions", + "thiserror 1.0.69", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[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-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "psm" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd034599e63b970727f70d79e02d62390a4a84f7c6b827c27c46d5ac3fa622" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + +[[package]] +name = "pulp" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", +] + +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.3", + "rustls", + "socket2", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg 0.10.2", + "ring", + "rustc-hash 2.1.3", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg 0.2.1", +] + +[[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_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "rangemap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d" + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.5", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.20", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "core_maths", + "font-types 0.11.3", +] + +[[package]] +name = "read-fonts" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" +dependencies = [ + "bytemuck", + "font-types 0.12.3", + "once_cell", +] + +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +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 = "refineable" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "derive_refineable", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "reqwest" +version = "0.12.24" +source = "git+https://github.com/CapSoftware/reqwest?rev=9b5ecbd5210a9510fde766015cabb724c1e70d2e#9b5ecbd5210a9510fde766015cabb724c1e70d2e" +dependencies = [ + "base64 0.22.1", + "bytes", + "cookie", + "cookie_store", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "resvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b563218631706d614e23059436526d005b50ab5f2d506b55a17eb65c5eb83419" +dependencies = [ + "gif 0.14.2", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", + "zune-jpeg", +] + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scap-targets" +version = "0.1.0" +dependencies = [ + "cidre", + "cocoa 0.26.0", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "image 0.24.9", + "objc", + "serde", + "specta", + "tokio", + "tracing", + "windows 0.60.0", + "workspace-hack", + "x11rb", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scheduler" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "async-task", + "backtrace", + "chrono", + "flume", + "futures", + "parking_lot", + "rand 0.9.5", + "web-time", +] + +[[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 0.8.22", + "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", + "indexmap 2.14.0", + "ref-cast", + "schemars_derive 1.2.2", + "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 0.29.1", + "syn 2.0.119", +] + +[[package]] +name = "schemars_derive" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals 0.30.0", + "syn 3.0.3", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "screencapturekit" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e" +dependencies = [ + "screencapturekit-sys", +] + +[[package]] +name = "screencapturekit-sys" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60" +dependencies = [ + "block", + "dispatch", + "objc", + "objc-foundation", + "objc_id", + "once_cell", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more 0.99.20", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + +[[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" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "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_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_fmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e497af288b3b95d067a23a4f749f2861121ffcb2f6d8379310dcda040c345ed" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "indexmap 2.14.0", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "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 = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "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 0.2.17", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio 0.8.11", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "skrifa" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" +dependencies = [ + "bytemuck", + "read-fonts 0.37.0", +] + +[[package]] +name = "skrifa" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" +dependencies = [ + "bytemuck", + "read-fonts 0.41.0", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "smol" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", +] + +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "borsh", + "serde_core", +] + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "specta" +version = "2.0.0-rc.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ccbb212565d2dc177bc15ecb7b039d66c4490da892436a4eee5b394d620c9bc" +dependencies = [ + "chrono", + "paste", + "serde_json", + "specta-macros", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "specta-macros" +version = "2.0.0-rc.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68999d29816965eb9e5201f60aec02a76512139811661a7e8e653abc810b8f72" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stacker" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707f49d46706bacf8a2b00d51dace3f9de527c13eec3778f570c411f89e69967" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.61.2", +] + +[[package]] +name = "stacksafe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9c1172965d317e87ddb6d364a040d958b40a1db82b6ef97da26253a8b3d090" +dependencies = [ + "stacker", + "stacksafe-macro", +] + +[[package]] +name = "stacksafe-macro" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172175341049678163e979d9107ca3508046d4d2a7c6682bee46ac541b17db69" +dependencies = [ + "proc-macro-error2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "sum_tree" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "heapless", + "log", + "rayon", + "tracing", + "ztracing", +] + +[[package]] +name = "sval" +version = "2.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4a2a7d92fa86fcc6222e4c3845f8486cff899d9db32480b26c91a5dbf2e22d" + +[[package]] +name = "sval_buffer" +version = "2.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4324db9ac500c609d659b752edf9c8abbf2233f8afd61a503fd6f88ed625032" +dependencies = [ + "sval", + "sval_ref", + "zerocopy", +] + +[[package]] +name = "sval_dynamic" +version = "2.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4046add0eecf55e680b9e207edf5fc7737b18a1d950db363d97e7f1b2d7c629c" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911a3486b5984a0a4f25edefcf2c2dba23654c29f63e75493b671d338bf24243" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da53aae7c737b5b5f1be4bcb0ff20e057bf6b2ee4e9d025560075c5830d09f95" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_nested" +version = "2.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df24df43cbdc4bb8c9f5ed19d0d57dc8f60a1a4259cdce52d597fe774ad3a71f" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + +[[package]] +name = "sval_ref" +version = "2.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bebc17f0f1fad060e57b778728d41ef87627e9111a6365d7463472cb58fc1b3" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f26fe3f6a68b40e6c8d654ea48c00e4316272fddf68c80493714c1b034ae70b" +dependencies = [ + "serde_core", + "sval", + "sval_nested", +] + +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "svgtypes" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" +dependencies = [ + "kurbo", + "siphasher 1.0.3", +] + +[[package]] +name = "swash" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e" +dependencies = [ + "skrifa 0.44.0", + "yazi", + "zeno", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "sysinfo" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "taffy" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340a09581f29809fc0df82a3955501dc7f2a21f887e5d1c13dbe288fe1c0bef4" +dependencies = [ + "arrayvec", + "grid", + "serde", + "slotmap", +] + +[[package]] +name = "tao-core-video-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "objc", +] + +[[package]] +name = "tauri-utils" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor 0.8.0", + "dunce", + "glob", + "html5ever", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.13.1", + "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-winrt-notification" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed071c670382e85fc2f48ae706492d8c338f4f89bf72520d32f8abfe880aade" +dependencies = [ + "thiserror 2.0.20", + "windows 0.61.3", + "windows-version", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png 0.17.16", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio 1.2.2", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[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", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[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", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +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", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.1", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.8.7", + "sha1", + "thiserror 1.0.69", + "utf-8", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "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]] +name = "usvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e419dff010bb12512b0ae9e3d2f318dfbdf0167fde7eb05465134d4e8756076f" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree 0.21.1", + "rustybuzz", + "simplecss", + "siphasher 1.0.3", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "util_macros" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "perf", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "uuid" +version = "1.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "v4l" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fbfea44a46799d62c55323f3c55d06df722fbe577851d848d328a1041c3403" +dependencies = [ + "bitflags 1.3.2", + "libc", + "v4l2-sys-mit", +] + +[[package]] +name = "v4l2-sys-mit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6779878362b9bacadc7893eac76abe69612e8837ef746573c4a5239daf11990b" +dependencies = [ + "bindgen 0.65.1", +] + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "value-bag" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "068e763e8279de7ab94b6afebded2cb701678af094feb1c12ccb061b4783c1be" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417d6197dd0ee696783d6be4276ac6ea74b985e00024c85ccfb37aff4f2bed82" +dependencies = [ + "erased-serde", + "serde_core", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f7251ecde2c9ed431bbe0659853e7991753447447bbf1ae59d8b31c578d4e" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm_thread" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7516db7f32decdadb1c3b8deb1b7d78b9df7606c5cc2f6241737c2ab3a0258e" +dependencies = [ + "futures", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" +dependencies = [ + "bitflags 2.13.1", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.4", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e8840e1ba2881d4cbb18d2147627a56af426ff064c0401eb0c8410c6325d07" +dependencies = [ + "arrayvec", + "bitflags 2.13.1", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f519832254e56965a9940c4af57dcb75f702b6f6fa4a0b172f685395843a4d7" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.13.1", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap 2.14.0", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.20", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-naga-bridge", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e39e26c4c0e07589e67d18546cf79ff45383659fc72fca4dd293358a0347f3" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e09be551dc939498bdd5f6b2c66e55ab275dad25825267a08605a80fc9f0af" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e592c1bbef6ad047647ae6e666ebd8cee7a32bb4544d9700ec96cbf73230257" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ace1c17727311c22a46e4e3faf56ea6de81af99dcc839bdfb54857b94d448d" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.13.1", + "block2 0.6.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "glow", + "glutin_wgl_sys", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "naga", + "ndk-sys 0.6.0+11769913", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "raw-window-metal", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.20", + "wasm-bindgen", + "wayland-sys", + "web-sys", + "wgpu-naga-bridge", + "wgpu-types", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-result 0.4.1", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95226013f547544b223281cd16a4fb549aa9dcb562adbda0faae4c73ffbbc161" +dependencies = [ + "naga", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84bf84cd9ca8ca45e2b223a3868f1adf9bfc0c66aeac212e76ee7e40fdadf8f5" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "js-sys", + "log", + "raw-window-handle", + "web-sys", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "which" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +dependencies = [ + "either", + "home", + "rustix 0.38.44", + "winsafe", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf874e74c7a99773e62b1c671427abf01a425e77c3d3fb9fb1e4883ea934529" +dependencies = [ + "windows-collections 0.1.1", + "windows-core 0.60.1", + "windows-future 0.1.1", + "windows-link 0.1.3", + "windows-numerics 0.1.1", +] + +[[package]] +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-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-capture" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4df73e95feddb9ec1a7e9c2ca6323b8c97d5eeeff78d28f1eccdf19c882b24" +dependencies = [ + "parking_lot", + "rayon", + "thiserror 2.0.20", + "windows 0.61.3", + "windows-future 0.2.1", +] + +[[package]] +name = "windows-collections" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5467f79cc1ba3f52ebb2ed41dbb459b8e7db636cc3429458d9a852e15bc24dec" +dependencies = [ + "windows-core 0.60.1", +] + +[[package]] +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", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" +dependencies = [ + "windows-implement 0.59.0", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.3.1", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a787db4595e7eb80239b74ce8babfb1363d8e343ab072f2ffe901400c03349f0" +dependencies = [ + "windows-core 0.60.1", + "windows-link 0.1.3", +] + +[[package]] +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" +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]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-implement" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "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.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005dea54e2f6499f2cee279b8f703b3cf3b5734a2d8d21867c8f44003182eeed" +dependencies = [ + "windows-core 0.60.1", + "windows-link 0.1.3", +] + +[[package]] +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +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", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +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", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[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", +] + +[[package]] +name = "windows_aarch64_gnullvm" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[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 = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "workspace-hack" +version = "0.1.0" +dependencies = [ + "arrayvec", + "axum", + "bitflags 2.13.1", + "bytemuck", + "chrono", + "clang-sys", + "clap", + "clap_builder", + "deranged", + "either", + "flate2", + "form_urlencoded", + "futures-channel", + "futures-core", + "futures-executor", + "futures-sink", + "futures-task", + "futures-util", + "getrandom 0.2.17", + "gif 0.13.3", + "hashbrown 0.15.5", + "idna", + "itertools 0.12.1", + "itertools 0.13.0", + "libc", + "log", + "memchr", + "miniz_oxide", + "nom 7.1.3", + "num-rational", + "num-traits", + "percent-encoding", + "phf_shared 0.10.0", + "phf_shared 0.11.3", + "proc-macro2", + "profiling", + "quote", + "rand 0.8.7", + "regex", + "regex-automata", + "regex-syntax", + "reqwest", + "rgb", + "rustc-hash 1.1.0", + "schemars 0.8.22", + "semver", + "serde", + "serde_core", + "serde_json", + "simd-adler32", + "smallvec", + "specta", + "specta-macros", + "stable_deref_trait", + "syn 2.0.119", + "tauri-utils", + "thiserror 2.0.20", + "time", + "tokio", + "tracing", + "tracing-core", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-clipboard" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" +dependencies = [ + "libc", + "x11rb", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "rustix 1.1.4", + "x11rb-protocol", + "xcursor", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcb" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6c2ad15e0e922856ee89afe862b8992334bbe7953adad56cd1199358cb30566" +dependencies = [ + "bitflags 2.13.1", + "libc", + "quick-xml", + "x11", +] + +[[package]] +name = "xcursor" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" + +[[package]] +name = "xim-ctext" +version = "0.3.0" +source = "git+https://github.com/zed-industries/xim-rs.git?rev=16f35a2c881b815a2b6cdfd6687988e84f8447d8#16f35a2c881b815a2b6cdfd6687988e84f8447d8" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "xim-parser" +version = "0.2.1" +source = "git+https://github.com/zed-industries/xim-rs.git?rev=16f35a2c881b815a2b6cdfd6687988e84f8447d8#16f35a2c881b815a2b6cdfd6687988e84f8447d8" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "xkbcommon" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" +dependencies = [ + "as-raw-xcb-connection", + "libc", + "memmap2", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yazi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8b8abf912b9a29ff112e1671c97c33636903d13a69712037190e6805af4f76" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.4", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus-lockstep" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" +dependencies = [ + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.3", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant", +] + +[[package]] +name = "zbus_xml" +version = "5.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1586c021a01ca0a9216dcd874e546382e156a5cbab5fab6cb5f10087e22682a" +dependencies = [ + "serde", + "winnow 1.0.4", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", +] + +[[package]] +name = "zed-font-kit" +version = "0.14.1-zed" +source = "git+https://github.com/zed-industries/font-kit?rev=94b0f28166665e8fd2f53ff6d268a14955c82269#94b0f28166665e8fd2f53ff6d268a14955c82269" +dependencies = [ + "bitflags 2.13.1", + "byteorder", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "core-text", + "dirs", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "zed-scap" +version = "0.0.8-zed" +source = "git+https://github.com/zed-industries/scap?rev=4afea48c3b002197176fb19cd0f9b180dd36eaac#4afea48c3b002197176fb19cd0f9b180dd36eaac" +dependencies = [ + "anyhow", + "cocoa 0.25.0", + "core-graphics-helmer-fork", + "log", + "objc", + "rand 0.8.7", + "screencapturekit", + "screencapturekit-sys", + "sysinfo", + "tao-core-video-sys", + "windows 0.61.3", + "windows-capture", + "x11", + "xcb", +] + +[[package]] +name = "zed-xim" +version = "0.4.0-zed" +source = "git+https://github.com/zed-industries/xim-rs.git?rev=16f35a2c881b815a2b6cdfd6687988e84f8447d8#16f35a2c881b815a2b6cdfd6687988e84f8447d8" +dependencies = [ + "ahash", + "hashbrown 0.14.5", + "log", + "x11rb", + "xim-ctext", + "xim-parser", +] + +[[package]] +name = "zeno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47402523226a02bfe5230160dc3ccc089aa6f6f19e7fcbb4e6f824bbb1b4aa62" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + +[[package]] +name = "zlog" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "anyhow", + "chrono", + "collections", + "log", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "ztracing" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +dependencies = [ + "tracing", + "tracing-subscriber", + "zlog", + "ztracing_macro", +] + +[[package]] +name = "ztracing_macro" +version = "0.1.0" +source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" + +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] + +[[package]] +name = "zvariant" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e28c25bd8bb8da5a1f3e7065d0c156b9ee9a7973adf78b0e35eaefdf3b1b5c" +dependencies = [ + "endi", + "enumflags2", + "serde", + "serde_bytes", + "winnow 1.0.4", + "zcheapstr", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d496a145685283b67e232bd9e47377f6b60ad9d51e3601b23867f77c42477f96" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.3", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b84ebb462416c27cdb97f2e7f5f0ccc844da1fe2ecc7121e1b690b41318bf42" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.3", + "winnow 1.0.4", +] diff --git a/apps/desktop-gpui/Cargo.toml b/apps/desktop-gpui/Cargo.toml new file mode 100644 index 0000000000..d6dbdab583 --- /dev/null +++ b/apps/desktop-gpui/Cargo.toml @@ -0,0 +1,54 @@ +# Standalone workspace on purpose: the gpui dependency tree is enormous and +# pulls its own git revisions of wgpu/font stacks. Keeping it out of the root +# workspace leaves the root Cargo.lock, `cargo hakari` workspace-hack and CI +# untouched, and sidesteps the root `[patch.crates-io]` entries (vendored +# wgpu-hal, tao) that only exist for the Tauri app. +[workspace] +resolver = "2" +members = ["."] + +[package] +name = "cap-desktop-gpui" +version = "0.1.0" +edition = "2024" +license = "AGPL-3.0" +publish = false + +[[bin]] +name = "cap-gpui" +path = "src/main.rs" + +[dependencies] +# gpui — the wingleeio/zed fork Comet ships. The pinned rev carries f596cde +# ("destination alpha on transparent windows: Porter-Duff OVER, not additive"), +# which our transparent rounded main-window shell depends on. +gpui = { git = "https://github.com/wingleeio/zed", rev = "5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" } +gpui_platform = { git = "https://github.com/wingleeio/zed", rev = "5d1f83d9f27a19bec1fb241dc33b42238af9cf8d", features = [ + "wayland", + "x11", + "font-kit", + "runtime_shaders", +] } +gpui_tokio = { git = "https://github.com/wingleeio/zed", rev = "5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" } + +# Device enumeration reuses the same crates the Tauri app records with, so the +# device identities here are byte-identical to the ones cap-recording expects. +cap-camera = { path = "../../crates/camera" } +scap-targets = { path = "../../crates/scap-targets" } +# Same fork/rev as the root workspace: MicrophoneFeed enumerates through this. +cpal = { git = "https://github.com/CapSoftware/cpal", rev = "6013cb5f8bd3" } + +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[profile.dev] +# gpui is unusably slow when its dependencies are unoptimized; zed/comet both +# build deps at opt-level 2 and keep the app crate itself at 0. +opt-level = 0 + +[profile.dev.package."*"] +opt-level = 2 + +[profile.release] +lto = "thin" +strip = "symbols" diff --git a/apps/desktop-gpui/assets/fonts/Geist-Bold.ttf b/apps/desktop-gpui/assets/fonts/Geist-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..863b2868c15bfce525a09174b4d322c12b5d498c GIT binary patch literal 128824 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 uU+YqKA^+Hg0}M|ZQ+?e zq`w6W>97*!7;_GV=~ortEMWvs;OvLicMl7Gl6jA_@B8MZ_V%|>URZ6`*#p8=MaM!o z_#M`dOej_{8m&nY#Cb?nX~Uo%1GbkuMhCVORq6DCB6V?SG---bRg7Q}7OSUMh(cJx zVjhG6q{L0DnGHKy;^X23#bWp$uNP06wJavyBRpuDXBL ztov8Vzs;SU@NDi3oqu}$`bTfL;nDT$PtWh(zG1^{w{6(4UGxpl`T0Ic#~r zU93T6oqZ$1qR!FQNhIin5%8!`PSw?n2Kb+_ApY=5mQA&5<(iVfLRR(1zwn z33C!UDvht9G+yX|`40c#2r&0a7|>6^kSxTRlgC-8ySzSMIugyba=r2h{D;LsYl|1G zY|}{P6YaFNdF7F9{H8n_KY8UjF6_nMlxLvrJ?s}ae?x8G_6=<(+(_?`bU(qQ4mE`C zo_;l`Pn((7xESDhiJMoS$?>l%#Q4XFULGwMjQ#=GWrD}GL75tF#}7XxmSd6VUQb%F zC!{y%^jI{IoFI*|bXtH(p*6bEU-4c`7P�pPzfl3BlJ2!6(-L##Tlv!~kwGv37q= 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 z%0D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

_e`PJ@7?Hro{A8GiEbw#-HCsja8vnjlV+| zTy_S8`3T8)SD4aR{L`Wbf2k#~jHi(x2IaRs(WKK6o3gNPY=?LG?Z3XGD2SBeS(Ipl ziN{~6F@_pv6mnAR*b5#1?|OnCcVPyCA8K+}=$+6Sw(kqg4b2EG2|dR>X%p)H5Z+>z z1Np$UiSVXYmkV+HYw-R`lLleDTaNggs&TX562`=dUx)sRlxRkcSuEUpOen}!mOAh` z$}_;jR6>O5v~Hc6a-ImuU)7w8rO~9zKHyq}_!4VeF+B6I{Ap#w2lywF0ROP;LyM?L z%ua38OZjKwll4R@Q3~pFhlxU#g_Z-IUIJgL0qQyUs^FRkOE;4`r;9pi|Ld%$MnZrq zk8uAyXy(K8#;h!yD8EdsNEsrjAP&jMD0)oo!jIZ$2LL@E^wXM!n@bp`ljgaRpW#ya z8t*(GgiMcwjnC$8ZFlRU+sUudm)!nWPvx=#8heNUg( zHxj3fZzS_Oan*{Yv-A42o(`v;R;2%0(|*67K+N_eq{H7%4}7G>+^dS%-IT(^n_L?#0(qhq)}# z5SD#Uo{S==4Lb{m-1c`EwK@y-t$xv83MSCWQ=^fv^Yxf-&E~&&(Mf-XO!z;E#2h;5v8R*Ze-*Z~UUBk-PNe;0aiZHN zEC;bdbh3D-`@Z8B@#)5IAHL()V*9s_%aqO82XcJzOpUVtpIA@)I%z+@{@=^=Wb1wC zpB>iwPL{XB1t$wf@0}foNZ8Zoz)0Ma+3ZeN_}k10|6OB%6XwKc$32J>La5w%xi~d} zQ$9G0Aw(|SH=#(UjL^BYxpQ;roQ{G%h+n}y%ZAl_?g!^Ja7tt^hsD=-?o_}G;mVzc zc!()HCVcLq+^cf0g==Z4!=~}d&RBu4P6EGx@ zRL@}LL#i*q6H?ubnIhp-!su0m58{lC$jt%t0}KWf0)_&H1FE6G|K`vJ#NUFloa~H} z*6|`!Cjx3IClL!hGB-oh%Wt6Dk_o6wS~Zq{I^F2Z|L};3QhM zSPBh4gT|8nz#bGzk=ds=+l}i?v{Q(VLv`KA*)~(;mEn)}sM!hR`$sMTa zz0d}=Uwp(T0zZ>qK#$WQXkqFq{z=-G#OKhibf!20%}T}M3+PZ9F206Fq^TlAnvl?) z+f*B>;0lE+JZ%|@PzE}OScJOW18rt>0&zE;K!jkx2}J1I!r4Q)ht3|#zd?)Cak&rO zR^i5Pm-68(VhpagIHuxMg8Vm5BPQdPrBoGwE-BIhr(J!RZXDU1)zA z1I+$J>ISN-tnt}c3B6L;oZE{` zR@jW{bvh={%Q0EKD#OO4Fc3NAg64dNQOKnfX`hQwqk3Hkc^NMTpoBOlh@+*eVM|3j z_Qp*E^^jHCvEgbF2ub;~(8s8ghjkWa`W~4cIx0T~d;<71bX3g^9o79Xhw!0y#o-iW z9JC_EK`&7p`dS=%NSw+5WWtRQkcZa;ZU8I?+!)%VJb(;9CfpR_AVM5Oh=T}m5FrjC z#6g5Oh!6)sCGMRBml3y(!Sf>U+-T;x$C&5d=TooWFwfo0Jof?f+-~rgl79oYdE`Ht z+m5JsaN8H8eN27{?PJ}*dEk9;-Wlo)d7OGQ_^%hVjR|NQ!_9%z8z7m^fiAIR=n)%% z(8Prq%!NIe3$s~&SPu0SXe*lz(+53fHnf&OT0vhKX$l(zj{F|9mbIW41tATt2JbF| zIh%QO81v}4%%kTqj}B)ZEfYV?+8j2AvQFK+opmZ^vrW;2-JS)Log6lv?9(t=yTWE~ z)+rd|0!38Ta6UHHp{!4cs%%fzk*u#kfdIszSN;gxdOhF6RH;I0ket_|R>4dAW~;ujco?+5%6uoCbqzypB40Coc2 z4{ZSlZU6^v00(YRIq*wItR0B81F?1>)(*tlfmk~bYX@TOK&&0yQ|d#X%XxtLfCj(< zKveIM2ST4C#T{xKo|gig-Xx)+lJuqZ7NixjFGdB@@pvXQunxkgdKOMQ=Rreb0eWyT z^ad`1_SNf9FV~|^mZLsyheoeEpxNs#@iUAaMg!yyXrfGquE(KaU5oSE=|}9S9r1Pf zzT5@9fgj65=s|~B_um)tsQeQB#6vQgHTIEyzJjp6J)=u+KI_6;5;Ys?r^}=-bI~cA z$q0GvNzL-i&dv19Q!%$>o@fk(PR=HEla>j)F7tHEtjxzxYVOT^=3C9Y%!Q|9R%V`> zc{uabj8jI3`e+IbMNWQBdQNLyWn0A5*0pfmf*LdF+`;9%7qmVMAEXK0s$XL-=^Mat zKnOhpyH-*H6hH%905`w`@B(~*E&%Ml%2+@gARdqaNCYGSx&o2`DS%W!H$Znl8o&<- z05SmB(UoTaG66jSS%6AF6`&e$K41i3Bw!Ta0>Eg%7{FKndaS$aAW-Gj*XrXSClOi{=GAX2R22Fe}FL={T$;DI<|~{(e{k)89j{g z7xQy-hKtSMjIi+!i`X-gGLrn|{t;}h_Kyg>k+GSK|31Ljj2vV9>%Yrp!z2gx_$Ora z@K0qkBXHa?{@MP9j6=rwoBgka&6ZAV_ASc4(=iz_8GDWKFR{-Zeq*loU+2Gx&21Tl zj`82+*dk>?X&ykmJ`mXCf0SwtxChmO|4ILI89Dy-pyW;emcY)8z5aLnJ1D+?H)0Pp z#=p<8(KF(T*x3I-#`uh9BF2B1OvD{%?cAPmZ^n_pwDucDFM*J-U}0t(X^BfZhWLz7_vL-jMG;FT# z#OAym7+B$$jO7_CjS1x2=iLEgN7&%^evq zfeQhX0+TX^0-gypQW*n{foZmMwHfQE4#_{zic%~KTpw7Gu`+Nw=(;~}KPb3Auqv<$ zIjo|7O0^tV6Ig5dT;Lcu61^<&GIRLBz#HgcIe|UsuUiA#m}~b0_6H7TxB|zC#{!?@ z?J?M0%)K$_bA!2`=2$-M2X`SjXt?E%t1_xHu7w#5H*wTtuC?VE(_v?wn?f@S>gOCrS7M`(THh_oU&e)!@3pszHrumD>q_q;HElpd-uIu5tK5a$X z?e^{6{Cp0+_ro?jV$xO_;~#5G+G_vUwAE>A($HVTj+8gL$WDcZl@*jwp zw5`^Dmmel=8=1&k|J1-*$E58vJw9!Ze@EJ$wEbxZt^0E;33jA~Z3uD9za;H=x+^`# z7%L-KQluw2awV`ceQ-O|J?x6u5CXMuU)a2cHV&Kg9)Ydtg~s>~1lFeK7-M>Vdz?Nr zVoXh=W^GNT_p_yDsb;9~-$(hR7n)Skhx(6_Ngqy-UY$OgUE|>zpFTN#x_vv#JmEVp z5CAuaP5MGI%*Wv6^hE^eSEXMY_E~OiS>R)Eemk=a{bL#Uznw|H#kl>+DOVelzN>?| zBP<_CDljeS_XciHzc+nl`oq@!xIf*P^k@9(4!!B?Kz*w}GyPR#0+kVyz9BW&G5*YG zlfK!JIO%T(Rz%(eYW=+(gH-KUO3(D|z(HfucY#Lq{lNX{dkNA%Vf!K252YV5Z^D@C zC;h9ys`Rh?g6hieA@IkwtG88d?NK(GAuqC0fU-yphSUtqaNgpTu_wsr+5I{PA4=Brbmjmp{&~Z$5?f&8JbdPvPc}IoO@3$2xi))-(^} z&Wg`*)51aAwD1kqVnSFo{}QX_@k-+!1wZau=z(>|B&;{~gX>JJTK3?35wfsyS&cgs zMqrJ+fLA9gaJ#`Ytd38|{RLIHy7lNr8c{z&UG!1cLo9N)9)wB8|Mwrms^jBWX->mR^IC+Yy_s0?x}clqX@|xm zHp6JFwQa@Qw_r5y)=1uE(!6KmgC_d-k>)O#G~U07#ZJi{g!~(f2YMU!B466kaq+H{ z#JwA7Sf9sLq#^}3zhuFljXfTTyITrz@p6$Yft_}%y5o+P3gPBGD~0=1E`@!XoQAiT z$;%LHx~zduEpAg$axUpp;C-zm-X-#44e)wg)^~%%YAS9>xfS-?pf@6&cfGpfUX;5K z_h)joaN*9ASCInk)=Az~a^ZfGKf-6L+y)=o8B@HsoW zKDc27@&`!)(_SH14mV#)aK0OM+L24deat@GG0{bJfe-eTAn|BFQKFZ01x`^Z!q2;Q zaky_J4Pnw%I{f|E_rhKUR*e(UYkI&Z6RXFGSl{dk|16b-bZAFXqaWo!equ`pKDd(v zA#p1SWNbgx4?eh=1U>`RK!l{dOHDlxKE>Eeb760)1d_8`5uclO)DuZ+_SLN`p zP}T4sp+<-^=#CQDY2OnUc&jn6k5yw~AE(BNB)YRibW!8gc=%xd3n|dvs2jUr6CvSn ziwW#guw&;&U%nLK(c6*cW$H5cOjk1y3p;tR&r~xpQp{4bKq2j`x^b(?T#OX;svc?5 z9;~R0Di)(+NW2t`qoR9|Jzksu?-b-ln8|L4;qV79pJ_Y6-$zp{_uwaSICk zaSsZ@T#dE(ZjdoQz-R?215ENhgU7EzmiMJB?@L(T=drvGvb@h=d0))({w$XF1uX9~ zc>LE?1 z#uB|Wej*2Vlel1~F)p8Fe-X?6uDFAw2kx09+25CCe=f`ZzAXEDVi!JJWU=h;iTg=< z!A`Qj56MB1!Xv<$JOT{n5nu@J8Mzdr2aSXMAPsSr0CEC%1;Th)CMK{<>j`8bH>V+`aYcnh~;R6xr|CGHtg*fAJ|G%tYMR4g~UKyF?P*@v5d zklQ4f!H}SnVJGRSAU)fV%OV&rWawh#xg;z@Q=?=k?akZ>iFp%lh3bM`h@au!m%AZP zyF|&;M3$#XEKmKAr%xl7XJ8cWNLh>B9FnbZEL&ruWNTNJtzMR`F)UkEShl{0^mo8? zkCL!{mauUwVPha)_ae;?#lIoTuz3&hp`{jQux#zgvQ^4#*$Z;2H)N|~+1d@VbqK-_ zl|vEoY{*$R%UKsoRmoB{1(I|gZit*O=OavmTmU=my-Sv=K9;K8SgQJ1s%EfMbwjE; z@>J68Pss6g`8rZ~1F}`IY<1(7r#~VclC4InN|vfVmZ}+1Qk8W3d@Oq69;Z(b@>57y zH%r(6OW3|)3HuL`fce8=$WxND$x(9F%W^i6LH;IokiWg*Ly|X%C2u01Sx{E;4pM`VuOoj8AfXB&h3WR} zV#uZ;Y8YhPIgr9qk^Joj$y)_`HRNwM$lp=0k5;20xk%cku(b8Dob3iVi@61#hL9{n zCCgC7GSr2=UYrn8bJQHfB^la{rKb<`r$*#Xa?{5WQ$k|4APk*fF%na<#Po$F=2E0{ z6=bD4K~`q5tjuLuc{a<+p)4y4SymRXtSn_&S;Dds^DWHvvshMUv8+sES((YQGL7fM z16WE1SxOFJDcOsqWIvXYX)Gl(SxTnyOn3mx$NnrI`>}lN&+@T|zX6I_KBlpJ9LSP! zAj`zwEED^%Ozh1vu@B3{Gg&6~WSMvt%fvjEiMcEjV_7Elf`kyhw}tY(?b{C7xDj(? zV^_ZIzFqtl*te^LU1A(aMfd@Xr-d;85iKD)IW-eEAuHdjz74oM`xs&02H$4bKWBW9 zuy1oXN9C(VzBVu4aLUCul<_Vf&Vb>(5@syoC4DjQD|!yw7XiPlOBq8a7VH>jaT6M} zOW;01!ZR5^Mi{sL;jU}hNV5g;2&mw=^`gb$zljKa;-{i8?Fn*Nr6vitV zzry%N##N2$_sZ5QtnU7tAOv3G4Od2d@aR0 zG7I=Rc^iC^m~x!CfqekD_$9+h)ACHZqiF4#6_K9boO2)(4zm+*^1>2{S9b*UO z(iXybNQ@w4uQ8b4j%8fSK0VmppW_Z;do$y4{B|7K#RvShjr}KsZ>1bgSgc_CVvC8w zOF7o1>~kr{>d*FU@<%!pH--J*<&bBw|1$O&%y~V=xvb~6AKSN7qGW!H|E43Ie{q;} z4)aSYqqv8C`Y}%A)G`T+o7pGD(7+*=vHfbUnOwH#a`+rhEr)$_*av4ck?+HyI$)ed z1Wx3)nT&B#5kB)6-^zF)<5w6rGscNVgc-(oALEgPm9aOm&qa*s-`i4-WjvelIgF<> z#`#aYZDqWe@o>Vpb&c=`?32Qn&Ou5^w?#{Qp%Knw>}UKd!fFQFli9A>Uds5ljPn@> z8RL{C)gjyc#NpyauD3+?$z+_%VU{tzh2OGlK~JOHbH$~=H0zV{M)v8?_#wtP>4`Mo zV*6wlbmqyat{mXWxcM1aA7D(qW3sC|+kXxE`|qdR zPgk(dSPtLDcrnLX%&``8ti==t-&550hca$uJb`g7<4ZZ6{*1@5&-aW!<7~#wjC+tA zmYEtSlVuMLISdRr3=BC83^^=&ki?enk_?e&G0xS+u;*&b1%PV_t1nPL%Q)nE8-~)! z<#cj6om_q!VEi$K5pQ$I0)~Uw#PE=$vA`Y9gN2_PG|gE#%D4PGVVuMT%+h@_{~gJ z7W?NirqjM?JI1}OKSm|ADfuts@VTl9_G0xS@Qv)#pYcPCdl1IRMfIFOBcphMMn=_# z$HxrldXV};!V>F>uwP6V>j>zlu1h%A>)e)i{~gc%)2Sgf3rP?xKzBLeggkKz(@%YBd)^tnj$@zvLETblmkT` zzJ+G1*VHYTyWEYj{Q=y@do^y{y#?Pn_v6;wCvorXtGLnjO?=Jl$1SzH<$m?JJfzlQ z1@kz*A`dD#AF|9A83p~_wr2AT}XZ#xDHyLkX{0?bIS38=TXEo~K3*k_^Tk0EU zt9{Vvnx_r`=c~iO1?nhpq52xQNGsrC?E@~+3BaW~6?ljaw6x>}brx`*9?{ZP+oE$@ z+7`Cxfvrdx>zvPEiu$C{sp;NCIyGH;NT;TI6Y11+Zz7$V?f~i3bl*ZcHQf_Pr>6TB z(y8g*L^?Iyo4D3xmudL^Mho~Vg^Qk=c#fXKAlKJ+%76=@i;$zI|@Mdmr+<$v)pO{%%X3 zd$J9OFdW}wp02~zy~aGTCdlu-#FL;OD~MbVuGQ9U>BowrBrT^{TNGSEg_E_nTidb+N375u z*n_A!={9?*EqFqo@_>eG;`Iz4r-xGXRr)82~)seE@JZU7H88Y@s}NAomw=BlC-5y?6jcKEGz}HOKqMvD?$> z?ey($rl))9vD&UYS0Oy#Pmht8@201_=y4PM^O5`K>GpZLdtNOyt?mL3c<8$WJmL|) zW0-3c!sYNpCVVqJBvTBJBna(EfFT(Sj&Oq)U5=aynlNriX!ij&dpHzQa6gKXY8PC@ zIZn7YgxmdwHS0K}cJFo{aie$R-+ArB-X%QlyTWD#hjzKwz`W_c5aAtOcha6Myzbdi zW*WzEyRT+*hZ_<_Q(uUtCtvv7`RxpSgK6YE!kXzW^iOTRoWkXBeI>fMzUp8Ob4;&0 zA#8fPp2TRlH!4q`>u|)lJ+9mEenZszF0QxRnf)$E5X{XSs+o9+5@1~$WC74B(hY#T zkY0cf0Lf1COZ3}8uIF6qU9Y*`bl1AJxZZK?aL;q?cI|T=02N2w&91MpxVzMSt=k8h zQ{4e~7Dl^)sE< z#P6{m>iqNIEv(o73IAPtS8T(7?zUr}7ypyMt=#WpC-eic8~_DkC%#WaMVBU07OePVz5lnE9Uru%!pL*dk_A8u0a4`(2JT-&xPpYTX z8uhe#Mm?*ZQ_ril>V=MVpx!_YBA#TtAI^_R=^^R5a3+(1b7!V-n(X-puKpu z={b5KbGE`xOdj?#(vb_{QjWV$y>7zM$pjM)y8eGod1L?MQ+kTAh(}K;ZbF_ujMy>W zByV?b57^SZncm*s0YZDry(7G1@m%I@_Krqajeij1K6->%?p*@kx$tv|Ys7M#yJ%p$ z6f^J-vr4k7V>UGGpsgM&-?Zv0)fQYzgMG|6 z9glwtCt_tjTaVWl6F=Y`_O5{A(XN%EN>t-Ns3S0sy8!>P8$Pz``KLI75%MVZ-I93cB^4obbPVi9e1f?!=AS3}@_quHi1{7X zrxeCUto>tr{1foYkS0prEguKIM?L|3uY3~tKDh??7xHP~`z7`i z-5oduE6z%%YJ3yxZrYE3Oa2{W^<#Mc9AobWJb#68_jh=%lItYqgz_QzBL0!9!9!Ri z$6nN1>Mv?N`t8H8zpZwvm(YVBf&EYFef2W>@}sc7gD;X-(5rt9`@8A`^(y-JYS_1_ z-T2@4^Xf6!|E&IsZ}7GF#&N6dYL9vyz5fa9uK!WJr`CbLR;dTz+KR8~7a=DeQpACS zz-6X|KNS1KM`FMD7_E0e{9POrpNT_g!+(m;#lO&sN5z-onD{sKN_jT8pz~91|3s-Q zS0VL@;0F3;=qdW!f2}#W%@T7(qqs%fihrs7N<1PS#n~D&_xT+tI7E3$DPv_Kb{4wI zOyt^6_Ll?XU>QXIBjji~UN*}X>^1yI-Yi$(bkxsL&#O?kPocKgp@zQ%jd^M~?a*O= z?i1SY!fw}R>JZKZ99Bdp^^`lHLtw3bNu&M2>*$f-T)GK^?kY(F^~o^!C!y$zuNwN_ zT?S??L457xqgQ02SB%7$%mtzrd+u{##)x?^WASg8TQE}G2GfAu??=P}@oSjx;}p?L z*eibpUk0~ggxnosq#lQn`fYg;M(J&GBF57}XKVV1ZQw zDe#49F*K~I{TTJ!7;Oe1v|D|IuWOvDfqRhkKa-zsJm*ESkvpw;=@dXJ0BZvp w)abr|IsnEk*cC>wT;SQD*HBGQn!T%84UKcG-PL03v|6G^V+FI~ie~Zu02#Qq)Bpeg literal 0 HcmV?d00001 diff --git a/apps/desktop-gpui/assets/fonts/Geist-SemiBold.ttf b/apps/desktop-gpui/assets/fonts/Geist-SemiBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b2b0618fa4ef73c5585043410a6ed9ed8a3c8e2a GIT binary patch literal 127872 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

HN?lC-Oc-rs>!)u23 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

se!*q_486Mc}s4Sad7c0 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, +} + +impl Render for Placeholder { + fn render(&mut self, _window: &mut gpui::Window, _cx: &mut Context) -> impl IntoElement { + div() + .size_full() + .rounded(px(16.)) + .bg(gpui::rgb(0x111111)) + .text_color(gpui::rgb(0xa1a1a1)) + .child("Cap") + } +} From faa73262cc79ca647c3ab2c3f2193375dc88810a Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:08:27 +0100 Subject: [PATCH 002/146] feat(desktop-gpui): embed Geist and register it with the text system gpui resolves `.font_family("Geist")` against the platform text system, so the faces have to be added before the first window renders or every label silently falls back to the system UI font. Only four files are embedded, so `include_bytes!` covers it -- zed reaches for `rust_embed` because it walks icons/themes/sounds/prompts as well. The `AssetSource` impl is still worth having: gpui's `svg()` element and image loader resolve their paths through it. --- apps/desktop-gpui/src/assets.rs | 66 +++++++++++++++++++++++++++++++++ apps/desktop-gpui/src/main.rs | 11 +++++- 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 apps/desktop-gpui/src/assets.rs diff --git a/apps/desktop-gpui/src/assets.rs b/apps/desktop-gpui/src/assets.rs new file mode 100644 index 0000000000..506b9a62b9 --- /dev/null +++ b/apps/desktop-gpui/src/assets.rs @@ -0,0 +1,66 @@ +//! Embedded assets. +//! +//! There are only a handful of files, so they are baked in with `include_bytes!` +//! rather than pulling in `rust_embed` for a directory walk we can spell out by +//! hand. The [`AssetSource`] impl is still worth having: gpui's `svg()` element +//! and image loader resolve paths through it. + +use std::borrow::Cow; + +use gpui::{App, AssetSource, Result, SharedString}; + +/// Every embedded asset, keyed by the path `AssetSource` consumers ask for. +const ASSETS: &[(&str, &[u8])] = &[ + ( + "fonts/Geist.ttf", + include_bytes!("../assets/fonts/Geist.ttf"), + ), + ( + "fonts/Geist-Medium.ttf", + include_bytes!("../assets/fonts/Geist-Medium.ttf"), + ), + ( + "fonts/Geist-SemiBold.ttf", + include_bytes!("../assets/fonts/Geist-SemiBold.ttf"), + ), + ( + "fonts/Geist-Bold.ttf", + include_bytes!("../assets/fonts/Geist-Bold.ttf"), + ), +]; + +pub struct Assets; + +impl AssetSource for Assets { + fn load(&self, path: &str) -> Result>> { + Ok(ASSETS + .iter() + .find(|(name, _)| *name == path) + .map(|(_, bytes)| Cow::Borrowed(*bytes))) + } + + fn list(&self, path: &str) -> Result> { + Ok(ASSETS + .iter() + .filter(|(name, _)| name.starts_with(path)) + .map(|(name, _)| SharedString::from(*name)) + .collect()) + } +} + +impl Assets { + /// Register the embedded Geist faces with the text system. + /// + /// gpui resolves `.font_family("Geist")` against whatever the platform text + /// system knows about, so this has to run before the first window renders or + /// text silently falls back to the system UI font. + pub fn load_fonts(&self, cx: &App) -> Result<()> { + let fonts = ASSETS + .iter() + .filter(|(name, _)| name.ends_with(".ttf")) + .map(|(_, bytes)| Cow::Borrowed(*bytes)) + .collect(); + + cx.text_system().add_fonts(fonts) + } +} diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index 4c90f939d5..8f09aba0f8 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -3,11 +3,16 @@ //! Milestone 1 is the main recording window (compact + expanded) with real //! device enumeration. No tauri, no webview: the whole UI is gpui. +mod assets; +mod devices; + use gpui::{ App, AppContext as _, Bounds, TitlebarOptions, WindowBounds, WindowOptions, div, prelude::*, px, size, }; +use crate::assets::Assets; + fn main() { tracing_subscriber::fmt() .with_env_filter( @@ -16,10 +21,14 @@ fn main() { ) .init(); - let app = gpui_platform::application(); + let app = gpui_platform::application().with_assets(Assets); app.run(|cx: &mut App| { gpui_tokio::init(cx); + if let Err(error) = Assets.load_fonts(cx) { + tracing::error!("failed to load embedded fonts: {error:#}"); + } + let bounds = Bounds::centered(None, size(px(330.), px(395.)), cx); cx.open_window( WindowOptions { From 8ac78df62e1dace0765166f534cbe96d5ffffef8 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:08:27 +0100 Subject: [PATCH 003/146] feat(desktop-gpui): real device enumeration Cameras, microphones, displays and windows, through the same crates the recorder uses. The identities handed back are the ones cap-recording already expects -- camera device_id, microphone name, DisplayId, WindowId -- rather than a new identity space that would need translating later. Microphone listing mirrors MicrophoneFeed::list_with_settings: default input device first so it heads the list, then the rest, deduped by name. Window listing mirrors the picker path of screen_capture::list_windows (include_accessory_panels: false), which collapses to a non-empty title, not owned by the Window Server, and window level 0. The level check is the one that matters -- a raw Window::list() on this machine returns 33 entries, 19 of which are menu-bar extras (Control Centre alone contributes seven `Item-0` windows), leaving 14 real application windows. It is duplicated rather than imported because cap-recording drags in ffmpeg and the entire encode stack, which this app has no other reason to build. Enumeration blocks on AVFoundation and the window server, so it is documented as background-executor work, never to be called from render. --- apps/desktop-gpui/src/devices.rs | 192 +++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 apps/desktop-gpui/src/devices.rs diff --git a/apps/desktop-gpui/src/devices.rs b/apps/desktop-gpui/src/devices.rs new file mode 100644 index 0000000000..dbba3fd2f5 --- /dev/null +++ b/apps/desktop-gpui/src/devices.rs @@ -0,0 +1,192 @@ +//! Real device enumeration for the capture-target and device pickers. +//! +//! Everything here goes through the same crates the recorder uses +//! (`cap-camera`, `scap-targets`, and the Cap `cpal` fork) so the identities the +//! UI hands back — camera `device_id`, microphone name, `DisplayId`, `WindowId` +//! — are byte-identical to what `cap-recording` expects to be given. Nothing is +//! re-derived or prettified into a new identity space. + +use cpal::traits::{DeviceTrait, HostTrait}; +use scap_targets::{Display, DisplayId, Window, WindowId}; + +/// A camera, identified the way `cap-recording` identifies one. +#[derive(Debug, Clone, PartialEq)] +pub struct CameraOption { + pub device_id: String, + pub label: String, +} + +/// A microphone. cpal has no stable id, so the name *is* the identity — which is +/// also exactly what `MicrophoneFeed::list()` keys its map on. +#[derive(Debug, Clone, PartialEq)] +pub struct MicrophoneOption { + pub name: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct DisplayOption { + pub id: DisplayId, + pub label: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct WindowOption { + pub id: WindowId, + /// The window's own title. + pub label: String, + /// Owning application, shown as the secondary line. + pub app: String, +} + +#[derive(Debug, Clone, Default, PartialEq)] +pub struct DeviceSnapshot { + pub cameras: Vec, + pub microphones: Vec, + pub displays: Vec, + pub windows: Vec, +} + +impl DeviceSnapshot { + /// Enumerate everything. This blocks — AVFoundation camera discovery and the + /// window-server queries are both slow enough to drop frames — so callers + /// should run it on the background executor, never inside `render`. + pub fn enumerate() -> Self { + Self { + cameras: list_cameras(), + microphones: list_microphones(), + displays: list_displays(), + windows: list_windows(), + } + } +} + +fn list_cameras() -> Vec { + cap_camera::list_cameras() + .map(|camera| CameraOption { + device_id: camera.device_id().to_string(), + label: camera.display_name().to_string(), + }) + .collect() +} + +/// Mirrors `MicrophoneFeed::list_with_settings`: the default input device is +/// inserted first so it heads the list, then every other input device is +/// appended, deduped by name. +fn list_microphones() -> Vec { + let host = cpal::default_host(); + let mut names: Vec = Vec::new(); + + if let Some(name) = host + .default_input_device() + .and_then(|device| device.name().ok()) + { + names.push(name); + } + + match host.input_devices() { + Ok(devices) => { + for name in devices.filter_map(|device| device.name().ok()) { + if !names.contains(&name) { + names.push(name); + } + } + } + Err(error) => { + tracing::error!("could not access audio input devices: {error}"); + } + } + + names + .into_iter() + .map(|name| MicrophoneOption { name }) + .collect() +} + +fn list_displays() -> Vec { + Display::list() + .into_iter() + .map(|display| { + let id = display.id(); + let label = display + .name() + .unwrap_or_else(|| format!("Display {}", &id)); + DisplayOption { id, label } + }) + .collect() +} + +/// Mirrors the picker path of `cap_recording::sources::screen_capture::list_windows` +/// (i.e. `include_accessory_panels: false`), which collapses to: a non-empty +/// title, not owned by the Window Server, and at window level 0. +/// +/// The level check is what actually matters. A raw `Window::list()` on macOS is +/// dominated by menu-bar extras and status items — Control Centre alone +/// contributes a dozen `Item-0` windows at level 25 — so without it the picker +/// is unusable. This is deliberately duplicated rather than imported: +/// `cap-recording` drags in ffmpeg and the whole encode stack, which this app +/// has no other reason to build. +fn list_windows() -> Vec { + Window::list() + .into_iter() + .filter_map(|window| { + let label = window.name().filter(|name| !name.trim().is_empty())?; + let app = window.owner_name()?; + + if app == "Window Server" { + return None; + } + + #[cfg(target_os = "macos")] + if window.raw_handle().level() != Some(0) { + return None; + } + + Some(WindowOption { + id: window.id(), + label, + app, + }) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Smoke test for the real enumeration path. Displays are the only thing we + /// can assert on unconditionally — a machine running this test has at least + /// one screen, but it may genuinely have no camera and no microphone, and on + /// a fresh machine the TCC prompt for camera access has not been answered + /// yet. The rest is printed for eyeballing with `--nocapture`. + #[test] + fn enumerates_real_devices() { + let snapshot = DeviceSnapshot::enumerate(); + + println!("cameras ({}):", snapshot.cameras.len()); + for camera in &snapshot.cameras { + println!(" {} [{}]", camera.label, camera.device_id); + } + println!("microphones ({}):", snapshot.microphones.len()); + for mic in &snapshot.microphones { + println!(" {}", mic.name); + } + println!("displays ({}):", snapshot.displays.len()); + for display in &snapshot.displays { + println!(" {} [{}]", display.label, display.id); + } + println!("windows ({}):", snapshot.windows.len()); + for window in snapshot.windows.iter().take(10) { + println!(" {} — {} [{}]", window.app, window.label, window.id); + } + + assert!( + !snapshot.displays.is_empty(), + "expected at least one display" + ); + assert!( + snapshot.windows.iter().all(|w| !w.label.trim().is_empty()), + "untitled windows should have been filtered out" + ); + } +} From d467a66b22f415fca1848b3735141c157454d095 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:24:19 +0100 Subject: [PATCH 004/146] feat(desktop-gpui): main recording window shell, compact and expanded The whole window drawn in gpui: custom header with traffic lights, logo, mode pill, the four capture-target tiles and the three device rows. Metrics are transcribed from the Tauri implementation rather than eyeballed, and the Tailwind class each one comes from is quoted next to it, so `pl-3` and `gap-2.5` stay checkable against the original. Colours are the resolved Radix values with the dark-mode overrides from theme.css applied -- six of the dark grays and gray-11 deviate from stock Radix, so regenerating the palette from a Radix crate would silently change it. Two things could not be drawn the obvious way. gpui rasterises an SVG and keeps only its alpha, tinting the result with one colour, so the logo mark's three concentric circles would have collapsed into a solid block: the mark is rebuilt from divs and only the genuinely single-colour wordmark goes through `svg()`, which also makes it swap between the app's light and dark logo variants for free. And the window drag region is scoped to the header spacer alone, not the header root -- a mouse-down handler on the root starts a window drag for every press in the header, which eats the button clicks before they are delivered. The expanded state is the real 600x660 layout, not a scaled compact one: section headings appear, the target tiles turn horizontal and gain descriptions, and the control rhythm goes 8px -> 10px. Verified at both sizes, in light and dark. Device enumeration runs on the background executor and reports 3 cameras, 3 microphones, 1 display and 14 windows on this machine; the rows render the result but selecting a device is not wired up yet. --- apps/desktop-gpui/assets/icons/area.svg | 3 + apps/desktop-gpui/assets/icons/bell.svg | 3 + apps/desktop-gpui/assets/icons/camera.svg | 4 + apps/desktop-gpui/assets/icons/check.svg | 1 + .../assets/icons/chevron-down.svg | 3 + apps/desktop-gpui/assets/icons/enlarge.svg | 3 + apps/desktop-gpui/assets/icons/film-cut.svg | 3 + apps/desktop-gpui/assets/icons/image.svg | 3 + apps/desktop-gpui/assets/icons/info.svg | 3 + apps/desktop-gpui/assets/icons/instant.svg | 3 + .../assets/icons/logo-full-dark.svg | 8 + apps/desktop-gpui/assets/icons/logo-full.svg | 8 + .../assets/icons/logo-wordmark.svg | 3 + apps/desktop-gpui/assets/icons/microphone.svg | 3 + apps/desktop-gpui/assets/icons/minimize.svg | 3 + .../desktop-gpui/assets/icons/play-circle.svg | 4 + apps/desktop-gpui/assets/icons/scan-text.svg | 3 + apps/desktop-gpui/assets/icons/screen.svg | 3 + apps/desktop-gpui/assets/icons/screenshot.svg | 1 + apps/desktop-gpui/assets/icons/search.svg | 3 + apps/desktop-gpui/assets/icons/settings.svg | 4 + apps/desktop-gpui/assets/icons/support.svg | 3 + apps/desktop-gpui/assets/icons/window.svg | 4 + apps/desktop-gpui/src/assets.rs | 136 ++- apps/desktop-gpui/src/main.rs | 60 +- apps/desktop-gpui/src/main_window.rs | 848 ++++++++++++++++++ apps/desktop-gpui/src/theme.rs | 171 ++++ 27 files changed, 1231 insertions(+), 63 deletions(-) create mode 100644 apps/desktop-gpui/assets/icons/area.svg create mode 100644 apps/desktop-gpui/assets/icons/bell.svg create mode 100644 apps/desktop-gpui/assets/icons/camera.svg create mode 100644 apps/desktop-gpui/assets/icons/check.svg create mode 100644 apps/desktop-gpui/assets/icons/chevron-down.svg create mode 100644 apps/desktop-gpui/assets/icons/enlarge.svg create mode 100644 apps/desktop-gpui/assets/icons/film-cut.svg create mode 100644 apps/desktop-gpui/assets/icons/image.svg create mode 100644 apps/desktop-gpui/assets/icons/info.svg create mode 100644 apps/desktop-gpui/assets/icons/instant.svg create mode 100644 apps/desktop-gpui/assets/icons/logo-full-dark.svg create mode 100644 apps/desktop-gpui/assets/icons/logo-full.svg create mode 100644 apps/desktop-gpui/assets/icons/logo-wordmark.svg create mode 100644 apps/desktop-gpui/assets/icons/microphone.svg create mode 100644 apps/desktop-gpui/assets/icons/minimize.svg create mode 100644 apps/desktop-gpui/assets/icons/play-circle.svg create mode 100644 apps/desktop-gpui/assets/icons/scan-text.svg create mode 100644 apps/desktop-gpui/assets/icons/screen.svg create mode 100644 apps/desktop-gpui/assets/icons/screenshot.svg create mode 100644 apps/desktop-gpui/assets/icons/search.svg create mode 100644 apps/desktop-gpui/assets/icons/settings.svg create mode 100644 apps/desktop-gpui/assets/icons/support.svg create mode 100644 apps/desktop-gpui/assets/icons/window.svg create mode 100644 apps/desktop-gpui/src/main_window.rs create mode 100644 apps/desktop-gpui/src/theme.rs diff --git a/apps/desktop-gpui/assets/icons/area.svg b/apps/desktop-gpui/assets/icons/area.svg new file mode 100644 index 0000000000..b366042250 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/area.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/bell.svg b/apps/desktop-gpui/assets/icons/bell.svg new file mode 100644 index 0000000000..87683d1349 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/bell.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/camera.svg b/apps/desktop-gpui/assets/icons/camera.svg new file mode 100644 index 0000000000..33da47edc5 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/camera.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/desktop-gpui/assets/icons/check.svg b/apps/desktop-gpui/assets/icons/check.svg new file mode 100644 index 0000000000..6e390aaca8 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-gpui/assets/icons/chevron-down.svg b/apps/desktop-gpui/assets/icons/chevron-down.svg new file mode 100644 index 0000000000..55f0688cea --- /dev/null +++ b/apps/desktop-gpui/assets/icons/chevron-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/enlarge.svg b/apps/desktop-gpui/assets/icons/enlarge.svg new file mode 100644 index 0000000000..04d21e3202 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/enlarge.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/film-cut.svg b/apps/desktop-gpui/assets/icons/film-cut.svg new file mode 100644 index 0000000000..6759fce298 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/film-cut.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/image.svg b/apps/desktop-gpui/assets/icons/image.svg new file mode 100644 index 0000000000..0b527cf6c4 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/image.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/info.svg b/apps/desktop-gpui/assets/icons/info.svg new file mode 100644 index 0000000000..90b18769f9 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/info.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/instant.svg b/apps/desktop-gpui/assets/icons/instant.svg new file mode 100644 index 0000000000..d16dff512c --- /dev/null +++ b/apps/desktop-gpui/assets/icons/instant.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/logo-full-dark.svg b/apps/desktop-gpui/assets/icons/logo-full-dark.svg new file mode 100644 index 0000000000..ada743fb8e --- /dev/null +++ b/apps/desktop-gpui/assets/icons/logo-full-dark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/apps/desktop-gpui/assets/icons/logo-full.svg b/apps/desktop-gpui/assets/icons/logo-full.svg new file mode 100644 index 0000000000..61f9aa7c5a --- /dev/null +++ b/apps/desktop-gpui/assets/icons/logo-full.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/desktop-gpui/assets/icons/logo-wordmark.svg b/apps/desktop-gpui/assets/icons/logo-wordmark.svg new file mode 100644 index 0000000000..2bc02103f3 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/logo-wordmark.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/desktop-gpui/assets/icons/microphone.svg b/apps/desktop-gpui/assets/icons/microphone.svg new file mode 100644 index 0000000000..4c3f63582e --- /dev/null +++ b/apps/desktop-gpui/assets/icons/microphone.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/minimize.svg b/apps/desktop-gpui/assets/icons/minimize.svg new file mode 100644 index 0000000000..665b9e9657 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/minimize.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/play-circle.svg b/apps/desktop-gpui/assets/icons/play-circle.svg new file mode 100644 index 0000000000..be76bcd810 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/play-circle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/desktop-gpui/assets/icons/scan-text.svg b/apps/desktop-gpui/assets/icons/scan-text.svg new file mode 100644 index 0000000000..8fce79adc4 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/scan-text.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/screen.svg b/apps/desktop-gpui/assets/icons/screen.svg new file mode 100644 index 0000000000..9b95db12ed --- /dev/null +++ b/apps/desktop-gpui/assets/icons/screen.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/screenshot.svg b/apps/desktop-gpui/assets/icons/screenshot.svg new file mode 100644 index 0000000000..f5a9956bbd --- /dev/null +++ b/apps/desktop-gpui/assets/icons/screenshot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-gpui/assets/icons/search.svg b/apps/desktop-gpui/assets/icons/search.svg new file mode 100644 index 0000000000..1412eddae8 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/settings.svg b/apps/desktop-gpui/assets/icons/settings.svg new file mode 100644 index 0000000000..5a7dfe5ad0 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/settings.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/desktop-gpui/assets/icons/support.svg b/apps/desktop-gpui/assets/icons/support.svg new file mode 100644 index 0000000000..a4c7b262aa --- /dev/null +++ b/apps/desktop-gpui/assets/icons/support.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/window.svg b/apps/desktop-gpui/assets/icons/window.svg new file mode 100644 index 0000000000..8bb0c4fc44 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/window.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/desktop-gpui/src/assets.rs b/apps/desktop-gpui/src/assets.rs index 506b9a62b9..c9f6599a96 100644 --- a/apps/desktop-gpui/src/assets.rs +++ b/apps/desktop-gpui/src/assets.rs @@ -1,47 +1,79 @@ //! Embedded assets. //! -//! There are only a handful of files, so they are baked in with `include_bytes!` +//! There are only a few dozen files, so they are baked in with `include_bytes!` //! rather than pulling in `rust_embed` for a directory walk we can spell out by -//! hand. The [`AssetSource`] impl is still worth having: gpui's `svg()` element -//! and image loader resolve paths through it. +//! hand. The [`AssetSource`] impl is load-bearing, not just tidy: gpui's `svg()` +//! element resolves every icon path through it, and an unregistered path fails +//! silently -- the element lays out at its declared size and draws nothing. use std::borrow::Cow; use gpui::{App, AssetSource, Result, SharedString}; -/// Every embedded asset, keyed by the path `AssetSource` consumers ask for. -const ASSETS: &[(&str, &[u8])] = &[ - ( - "fonts/Geist.ttf", - include_bytes!("../assets/fonts/Geist.ttf"), - ), - ( - "fonts/Geist-Medium.ttf", - include_bytes!("../assets/fonts/Geist-Medium.ttf"), - ), - ( - "fonts/Geist-SemiBold.ttf", - include_bytes!("../assets/fonts/Geist-SemiBold.ttf"), - ), - ( - "fonts/Geist-Bold.ttf", - include_bytes!("../assets/fonts/Geist-Bold.ttf"), - ), -]; +/// Expands to `(path, bytes)` pairs rooted at `assets/`. +macro_rules! assets { + ($dir:literal: $($name:literal),* $(,)?) => { + &[$(( + concat!($dir, "/", $name), + include_bytes!(concat!("../assets/", $dir, "/", $name)) as &[u8], + )),*] + }; +} + +const FONTS: &[(&str, &[u8])] = assets!("fonts": + "Geist.ttf", + "Geist-Medium.ttf", + "Geist-SemiBold.ttf", + "Geist-Bold.ttf", +); + +/// Mostly Cap's own icon set, lifted from `packages/ui-solid/icons`. gpui +/// rasterises an SVG and keeps only its alpha, tinting the result with the +/// element's text colour, so these must be single-colour glyphs -- a full-colour +/// illustration would come out as a filled silhouette. +const ICONS: &[(&str, &[u8])] = assets!("icons": + "area.svg", + "bell.svg", + "camera.svg", + "check.svg", + "chevron-down.svg", + "enlarge.svg", + "film-cut.svg", + "image.svg", + "info.svg", + "instant.svg", + "logo-full-dark.svg", + "logo-full.svg", + "logo-wordmark.svg", + "microphone.svg", + "minimize.svg", + "play-circle.svg", + "scan-text.svg", + "screen.svg", + "screenshot.svg", + "search.svg", + "settings.svg", + "support.svg", + "window.svg", +); pub struct Assets; +impl Assets { + fn all() -> impl Iterator { + FONTS.iter().chain(ICONS.iter()) + } +} + impl AssetSource for Assets { fn load(&self, path: &str) -> Result>> { - Ok(ASSETS - .iter() + Ok(Self::all() .find(|(name, _)| *name == path) .map(|(_, bytes)| Cow::Borrowed(*bytes))) } fn list(&self, path: &str) -> Result> { - Ok(ASSETS - .iter() + Ok(Self::all() .filter(|(name, _)| name.starts_with(path)) .map(|(name, _)| SharedString::from(*name)) .collect()) @@ -55,12 +87,54 @@ impl Assets { /// system knows about, so this has to run before the first window renders or /// text silently falls back to the system UI font. pub fn load_fonts(&self, cx: &App) -> Result<()> { - let fonts = ASSETS - .iter() - .filter(|(name, _)| name.ends_with(".ttf")) - .map(|(_, bytes)| Cow::Borrowed(*bytes)) + cx.text_system() + .add_fonts(FONTS.iter().map(|(_, bytes)| Cow::Borrowed(*bytes)).collect()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Every icon `main_window` asks for must be in the table. gpui draws + /// nothing at all for a path it cannot resolve, and does not log, so a typo + /// here is otherwise only visible by looking at the window. + #[test] + fn every_referenced_icon_is_embedded() { + let source = include_str!("main_window.rs"); + + let referenced: Vec<&str> = source + .match_indices("\"icons/") + .filter_map(|(start, _)| { + let rest = &source[start + 1..]; + rest.split('"').next() + }) + .filter(|path| path.ends_with(".svg")) + .collect(); + + assert!( + !referenced.is_empty(), + "found no icon references to check -- has main_window.rs stopped \ + spelling paths as string literals?" + ); + + let missing: Vec<&str> = referenced + .into_iter() + .filter(|path| Assets.load(path).unwrap().is_none()) .collect(); - cx.text_system().add_fonts(fonts) + assert!( + missing.is_empty(), + "icons referenced but not embedded: {missing:?}" + ); + } + + #[test] + fn fonts_and_icons_resolve() { + assert!(Assets.load("fonts/Geist.ttf").unwrap().is_some()); + assert!(Assets.load("icons/camera.svg").unwrap().is_some()); + assert!(Assets.load("icons/nope.svg").unwrap().is_none()); + assert_eq!(Assets.list("fonts").unwrap().len(), FONTS.len()); + assert_eq!(Assets.list("icons").unwrap().len(), ICONS.len()); } } diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index 8f09aba0f8..3b3ff8cdb9 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -5,19 +5,25 @@ mod assets; mod devices; +mod main_window; +mod theme; -use gpui::{ - App, AppContext as _, Bounds, TitlebarOptions, WindowBounds, WindowOptions, div, prelude::*, px, - size, -}; +use gpui::{App, AppContext as _, Bounds, WindowBounds, WindowOptions, px, size}; -use crate::assets::Assets; +use crate::{assets::Assets, main_window::MainWindow}; + +/// Matches the Tauri main window exactly (`CapWindowId::Main`). +const MAIN_WINDOW_WIDTH: f32 = 330.; +const MAIN_WINDOW_HEIGHT: f32 = 395.; fn main() { tracing_subscriber::fmt() .with_env_filter( + // The binary is `cap-gpui`, so the crate these spans are recorded + // under is `cap_gpui` -- not `cap_desktop_gpui`, which is the + // package name and matches nothing. tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| "cap_desktop_gpui=info".into()), + .unwrap_or_else(|_| "cap_gpui=info".into()), ) .init(); @@ -29,42 +35,32 @@ fn main() { tracing::error!("failed to load embedded fonts: {error:#}"); } - let bounds = Bounds::centered(None, size(px(330.), px(395.)), cx); + // 330x395 is what `CapWindowId::Main::min_size` uses in the Tauri app, + // and the window is fixed at that size there too. + let bounds = Bounds::centered(None, size(px(MAIN_WINDOW_WIDTH), px(MAIN_WINDOW_HEIGHT)), cx); cx.open_window( WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), - titlebar: Some(TitlebarOptions { - title: None, - appears_transparent: true, - traffic_light_position: Some(gpui::point(px(12.), px(12.))), - }), + // `None`, not a transparent titlebar with repositioned traffic + // lights: the Tauri main window returns `None` from + // `traffic_lights_position`, which routes it to + // `.decorations(false)`. There is no titlebar and there are no + // traffic lights -- the whole shell is custom-drawn. In gpui a + // `None` titlebar drops NSClosable/NSMiniaturizable/NSResizable + // from the style mask, which is the equivalent. + titlebar: None, + // The header is dragged by the app via `start_window_move` + // rather than by AppKit, so mark the content view as app-owned + // titlebar content. app_owns_titlebar_drag: true, window_background: gpui::WindowBackgroundAppearance::Transparent, is_resizable: false, + is_minimizable: false, ..Default::default() }, - |_window, cx| { - cx.new(|_cx| Placeholder { - _private: std::marker::PhantomData, - }) - }, + |window, cx| cx.new(|cx| MainWindow::new(window, cx)), ) .expect("failed to open the main window"); cx.activate(true); }); } - -struct Placeholder { - _private: std::marker::PhantomData<()>, -} - -impl Render for Placeholder { - fn render(&mut self, _window: &mut gpui::Window, _cx: &mut Context) -> impl IntoElement { - div() - .size_full() - .rounded(px(16.)) - .bg(gpui::rgb(0x111111)) - .text_color(gpui::rgb(0xa1a1a1)) - .child("Cap") - } -} diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs new file mode 100644 index 0000000000..1cd09dd14c --- /dev/null +++ b/apps/desktop-gpui/src/main_window.rs @@ -0,0 +1,848 @@ +//! The main recording window. +//! +//! Every metric here is transcribed from the Tauri implementation +//! (`apps/desktop/src/routes/(window-chrome)/new-main/index.tsx` and its +//! siblings) so the two windows are pixel-comparable. Tailwind classes are +//! quoted next to the values they turn into, because `pl-3` and `gap-2.5` are +//! considerably easier to check against the original than `12.` and `10.`. + +use gpui::{ + Context, FontWeight, Hsla, InteractiveElement, IntoElement, ParentElement, Render, SharedString, + StatefulInteractiveElement, Styled, Window, div, prelude::FluentBuilder, px, rgb, svg, +}; + +use crate::{ + MAIN_WINDOW_HEIGHT, MAIN_WINDOW_WIDTH, + devices::{CameraOption, DeviceSnapshot, MicrophoneOption}, + theme::{Appearance, Theme}, +}; + +/// `MAIN_WINDOW_SIZE.expanded` in index.tsx. +const EXPANDED_WIDTH: f32 = 600.; +const EXPANDED_HEIGHT: f32 = 660.; + +/// `h-9` on `.cap-window-header`. +const HEADER_HEIGHT: f32 = 36.; +/// `h-[42px]` in deviceRowStyles.ts. +const DEVICE_ROW_HEIGHT: f32 = 42.; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Mode { + Instant, + Studio, + Screenshot, +} + +impl Mode { + fn icon(self) -> &'static str { + match self { + Self::Instant => "icons/instant.svg", + Self::Studio => "icons/film-cut.svg", + Self::Screenshot => "icons/screenshot.svg", + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TargetType { + Display, + Window, + Area, + CameraOnly, +} + +impl TargetType { + fn label(self) -> &'static str { + match self { + Self::Display => "Display", + Self::Window => "Window", + Self::Area => "Area", + Self::CameraOnly => "Camera Only", + } + } + + /// Shown only when expanded. + fn description(self) -> &'static str { + match self { + Self::Display => "Entire screen", + Self::Window => "One app", + Self::Area => "Custom region", + Self::CameraOnly => "No screen", + } + } + + fn icon(self) -> &'static str { + match self { + Self::Display => "icons/screen.svg", + Self::Window => "icons/window.svg", + Self::Area => "icons/area.svg", + Self::CameraOnly => "icons/camera.svg", + } + } +} + +pub struct MainWindow { + theme: Theme, + expanded: bool, + mode: Mode, + target: Option, + devices: DeviceSnapshot, + camera: Option, + microphone: Option, + system_audio: bool, +} + +impl MainWindow { + pub fn new(window: &mut Window, cx: &mut Context) -> Self { + let theme = Theme::new(Appearance::from_window(window.appearance())); + + // Enumeration hits AVFoundation and the window server, so it must not + // run on the main thread -- doing it inline here costs ~180ms of a + // blank window on this machine, and more on a machine with more + // capture devices. + cx.spawn(async move |this, cx| { + let snapshot = cx + .background_executor() + .spawn(async { DeviceSnapshot::enumerate() }) + .await; + + this.update(cx, |this, cx| { + tracing::info!( + cameras = snapshot.cameras.len(), + microphones = snapshot.microphones.len(), + displays = snapshot.displays.len(), + windows = snapshot.windows.len(), + "enumerated capture devices" + ); + this.devices = snapshot; + cx.notify(); + }) + .ok(); + }) + .detach(); + + let this = Self { + theme, + expanded: false, + mode: Mode::Instant, + target: None, + devices: DeviceSnapshot::default(), + camera: None, + microphone: None, + system_audio: false, + }; + + // The Tauri app restores the persisted expanded state on mount and + // resizes without animating. There is nothing persisted yet, but the + // window size still has to agree with the state the view starts in. + this.apply_window_size(window); + + this + } + + fn toggle_expanded(&mut self, window: &mut Window, cx: &mut Context) { + self.expanded = !self.expanded; + tracing::info!(expanded = self.expanded, "toggling main window size"); + self.apply_window_size(window); + cx.notify(); + } + + /// Resize the window to match `expanded`. + /// + /// The Tauri version animates this over 180ms with an ease-out cubic and + /// re-clamps the window into the monitor work area with 12px of padding. + /// This is the un-animated version of the same size change; the animation + /// is deliberately left for a later pass rather than faked with a timer + /// that would fight gpui's own frame pacing. + fn apply_window_size(&self, window: &mut Window) { + window.resize(if self.expanded { + gpui::size(px(EXPANDED_WIDTH), px(EXPANDED_HEIGHT)) + } else { + gpui::size(px(MAIN_WINDOW_WIDTH), px(MAIN_WINDOW_HEIGHT)) + }); + } + + fn sync_appearance(&mut self, window: &Window) { + let appearance = Appearance::from_window(window.appearance()); + if appearance != self.theme.appearance { + self.theme = Theme::new(appearance); + } + } +} + +impl Render for MainWindow { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + self.sync_appearance(window); + let theme = self.theme; + + div() + .size_full() + .flex() + .flex_col() + .overflow_hidden() + // `rounded-[16px]` on `.cap-window-shell`, matched natively by + // `apply_squircle_corners(&window, 16.0)` in the Tauri app. + .rounded(px(16.)) + .bg(theme.gray_1) + .font_family("Geist") + .text_color(theme.text_primary) + .child(self.render_header(window, cx)) + .child(self.render_body(cx)) + } +} + +impl MainWindow { + fn render_header(&self, window: &Window, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let focused = window.is_window_active(); + + div() + .flex() + .flex_row() + .items_center() + .w_full() + .h(px(HEADER_HEIGHT)) + .flex_shrink_0() + .bg(theme.gray_2) + // `divide-y divide-gray-5` between header and body. + .border_b_1() + .border_color(theme.gray_5) + .child(self.render_traffic_lights(focused, cx)) + .child(self.render_header_actions(cx)) + } + + /// `CaptionControlsMacOS`: 14px circles (`size-3.5`), 10px apart + /// (`gap-2.5`), 12px from the left edge (`ml-3`). Minimize is not drawn -- + /// the main window passes `showMinimize={false}` -- and zoom is bound to + /// expand/collapse rather than a real window zoom. + fn render_traffic_lights(&self, focused: bool, cx: &mut Context) -> impl IntoElement { + let light = |color: u32, id: &'static str| { + div() + .id(id) + .size(px(14.)) + .rounded_full() + .bg(if focused { + rgb(color) + } else { + rgb(Theme::TRAFFIC_INACTIVE) + }) + .cursor_default() + }; + + div() + .flex() + .flex_row() + .items_center() + .gap(px(10.)) + .h_full() + .ml(px(12.)) + .flex_shrink_0() + .child( + light(Theme::TRAFFIC_CLOSE, "traffic-close").on_click(cx.listener( + |_, _, _window, cx| { + cx.quit(); + }, + )), + ) + .child( + light(Theme::TRAFFIC_ZOOM, "traffic-zoom").on_click(cx.listener( + |this, _, window, cx| { + this.toggle_expanded(window, cx); + }, + )), + ) + } + + /// The teleported header content: a help button, a drag spacer, then the + /// right-hand cluster. 20px hit targets (`size-5`) 4px apart (`gap-1`), + /// 8px from the window edges (`mx-2`). + fn render_header_actions(&self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let expanded = self.expanded; + + let icon_button = |id: &'static str, path: &'static str, size: f32| { + div() + .id(SharedString::from(id)) + .flex() + .items_center() + .justify_center() + .size(px(20.)) + .flex_shrink_0() + .child( + svg() + .path(path) + .size(px(size)) + .text_color(theme.gray_11), + ) + .hover(|style| style.text_color(theme.gray_12)) + }; + + div() + .flex() + .flex_1() + .items_center() + .gap(px(4.)) + .mx(px(8.)) + .min_w_0() + .child(icon_button("help", "icons/support.svg", 16.)) + // The drag handle, and *only* this. The Tauri header puts + // `data-tauri-drag-region` on the header and this spacer but not on + // the buttons; putting the handler on the header root instead makes + // every mouse-down in the header start a window drag, which eats + // the button clicks before they are delivered. + .child( + div() + .id("drag-region") + .flex_1() + .min_w_0() + .h_full() + .on_mouse_down(gpui::MouseButton::Left, |_, window, _| { + window.start_window_move(); + }), + ) + .child( + div() + .flex() + .flex_row() + .items_center() + .gap(px(4.)) + .flex_shrink_0() + .child( + icon_button( + "expand", + if expanded { + "icons/minimize.svg" + } else { + "icons/enlarge.svg" + }, + 14., + ) + .on_click(cx.listener(|this, _, window, cx| { + this.toggle_expanded(window, cx); + })), + ) + .child(icon_button("settings", "icons/settings.svg", 16.)) + .child(icon_button("screenshots", "icons/image.svg", 16.)) + .child(icon_button("recordings", "icons/play-circle.svg", 16.)) + .child(icon_button("teleprompter", "icons/scan-text.svg", 16.)) + .child(icon_button("changelog", "icons/bell.svg", 16.)), + ) + } + + /// Page root: `px-[13px] gap-2 pb-[8px]`. + fn render_body(&self, cx: &mut Context) -> impl IntoElement { + div() + .flex() + .flex_col() + .flex_1() + .min_h_0() + .px(px(13.)) + .pb(px(8.)) + .gap(px(8.)) + .child(self.render_logo_row(cx)) + .child( + div() + .flex() + .flex_col() + .flex_1() + .min_h_0() + .gap(px(8.)) + .child(self.render_targets(cx)) + .child(self.render_base_controls(cx)), + ) + } + + /// `mt-[16px] mb-[6px]`, logo `w-[92px]`, Mode pill on the right. + fn render_logo_row(&self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + + div() + .flex() + .flex_row() + .items_center() + .justify_between() + .mt(px(16.)) + .mb(px(6.)) + .flex_shrink_0() + .child(self.render_logo()) + .child(self.render_mode_pill(cx)) + } + + /// `*:w-[92px]` on the logo link, against a 103x40 viewBox, so the whole + /// lockup is 92x35.7 and everything below is that scale factor applied to + /// the SVG's own coordinates. + /// + /// The mark is rebuilt out of divs rather than drawn from `logo-full.svg`, + /// because gpui keeps only an SVG's alpha and tints it with one colour -- + /// the mark's three concentric circles would collapse into a solid block. + /// Only the wordmark, which really is single-colour, goes through `svg()`, + /// which also means it picks up `gray_12` and so swaps between the app's + /// light and dark logo variants for free. + fn render_logo(&self) -> impl IntoElement { + const SCALE: f32 = 92. / 103.; + + let theme = self.theme; + let ring = |diameter: f32, color: u32| { + div() + .absolute() + .size(px(diameter * SCALE)) + .rounded_full() + .bg(rgb(color)) + }; + + div() + .flex() + .flex_row() + .items_center() + // The mark ends at x=40 and the wordmark starts at x=49; re-framing + // the wordmark's viewBox to start at 49 dropped that gap, so it is + // put back here. + .gap(px(9. * SCALE)) + .child( + div() + .relative() + .flex() + .items_center() + .justify_center() + .size(px(40. * SCALE)) + .flex_shrink_0() + .rounded(px(7.75 * SCALE)) + .bg(gpui::white()) + // Without this the white tile is invisible against gray-1 + // in light mode. + .border_1() + .border_color(rgb(0xe7eaf0)) + .child(ring(32., 0x4785ff)) + .child(ring(26., 0xadc9ff)) + .child(ring(20., 0xffffff)), + ) + .child( + svg() + .path("icons/logo-wordmark.svg") + // 54x40 of the original viewBox. + .w(px(54. * SCALE)) + .h(px(40. * SCALE)) + .text_color(theme.gray_12), + ) + } + + /// `Mode.tsx`: `p-1.5 gap-2 rounded-full border border-gray-5 bg-gray-3`, + /// 28px round buttons (`size-7`). Selected gets `bg-gray-7` plus a 2px + /// `blue-500` ring offset 1px against `gray-1`. + fn render_mode_pill(&self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let selected_mode = self.mode; + + let button = |mode: Mode, id: &'static str| { + let selected = mode == selected_mode; + div() + .id(SharedString::from(id)) + .flex() + .items_center() + .justify_center() + .size(px(28.)) + .rounded_full() + .bg(if selected { theme.gray_7 } else { theme.gray_3 }) + .when(selected, |this| { + // `ring-2 ring-blue-500 ring-offset-1 ring-offset-gray-1`. + this.border_2() + .border_color(theme.blue_500) + .shadow(vec![gpui::BoxShadow { + color: Hsla::from(theme.gray_1), + offset: gpui::point(px(0.), px(0.)), + blur_radius: px(0.), + spread_radius: px(1.), + inset: false, + }]) + }) + .child( + svg() + .path(mode.icon()) + .size(px(if matches!(mode, Mode::Instant) { 16. } else { 14.4 })) + .text_color(theme.gray_12), + ) + .hover(|style| style.bg(theme.gray_7)) + .on_click(cx.listener(move |this, _, _window, cx| { + this.mode = mode; + cx.notify(); + })) + }; + + div() + .flex() + .flex_row() + .items_center() + .gap(px(8.)) + .p(px(6.)) + .rounded_full() + .border_1() + .border_color(theme.gray_5) + .bg(theme.gray_3) + .child(button(Mode::Instant, "mode-instant")) + .child(button(Mode::Studio, "mode-studio")) + .child(button(Mode::Screenshot, "mode-screenshot")) + } + + fn render_targets(&self, cx: &mut Context) -> impl IntoElement { + div() + .flex() + .flex_col() + .gap(px(8.)) + .w_full() + .flex_shrink_0() + .when(self.expanded, |this| { + this.child( + // `px-1 pb-0.5` + `text-xs font-semibold text-gray-12`. + div().px(px(4.)).pb(px(2.)).child( + div() + .text_size(px(12.)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(self.theme.gray_12) + .child("Capture"), + ), + ) + }) + .child( + div() + .flex() + .flex_row() + .gap(px(8.)) + .items_stretch() + .w_full() + .child(self.render_split_target(TargetType::Display, cx)) + .child(self.render_split_target(TargetType::Window, cx)), + ) + .child( + div() + .flex() + .flex_row() + .gap(px(8.)) + .items_stretch() + .w_full() + .child(self.render_target_tile(TargetType::Area, cx)) + .child(self.render_target_tile(TargetType::CameraOnly, cx)), + ) + } + + /// Display and Window are split controls: the tile plus a 28px + /// (`w-7`) chevron button, sharing one rounded border. + fn render_split_target(&self, target: TargetType, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let selected = self.target == Some(target); + + div() + .flex() + .flex_1() + .overflow_hidden() + .rounded(px(8.)) + .border_1() + .border_color(if selected { theme.blue_8 } else { theme.gray_6 }) + .bg(if selected { + theme.tile_selected_bg() + } else { + Hsla::from(theme.gray_2) + }) + .child(self.target_button_inner(target, true, cx)) + .child( + div() + .id(SharedString::from(format!("{}-dropdown", target.label()))) + .flex() + .w(px(28.)) + .flex_shrink_0() + .items_center() + .justify_center() + .border_l_1() + .border_color(theme.gray_6) + .bg(theme.gray_4) + .child( + svg() + .path("icons/chevron-down.svg") + .size(px(16.)) + .text_color(theme.gray_11), + ) + .hover(|style| style.bg(theme.gray_6)), + ) + } + + /// Area and Camera Only are plain tiles with their own border. + fn render_target_tile(&self, target: TargetType, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let selected = self.target == Some(target); + + div() + .flex() + .flex_1() + .overflow_hidden() + .rounded(px(8.)) + .border_1() + .border_color(if selected { theme.blue_8 } else { theme.gray_6 }) + .bg(if selected { + theme.tile_selected_bg() + } else { + Hsla::from(theme.gray_2) + }) + .child(self.target_button_inner(target, false, cx)) + } + + /// `TargetTypeButton`. Compact stacks the icon over the label + /// (`flex-col items-center gap-1 py-2 justify-end`); expanded lays them out + /// horizontally with a description (`min-h-14 flex-row gap-2.5 px-3`). + fn target_button_inner( + &self, + target: TargetType, + split: bool, + cx: &mut Context, + ) -> impl IntoElement { + let theme = self.theme; + let selected = self.target == Some(target); + let expanded = self.expanded; + + let icon_color = if selected { + theme.blue_10 + } else { + theme.gray_10 + }; + let label_color = if selected { + theme.blue_11 + } else { + theme.gray_12 + }; + let description_color = icon_color; + + let icon = svg() + .path(target.icon()) + .size(px(20.)) + .flex_shrink_0() + .text_color(icon_color); + + let base = div() + .id(SharedString::from(target.label())) + .flex() + .flex_1() + .py(px(8.)) + .on_click(cx.listener(move |this, _, _window, cx| { + this.target = if this.target == Some(target) { + None + } else { + Some(target) + }; + cx.notify(); + })); + + if expanded { + base.flex_row() + .items_center() + .justify_start() + .gap(px(10.)) + .min_h(px(56.)) + // `pl-3` when expanded for the split controls, `px-3` otherwise. + .px(px(12.)) + .child(icon) + .child( + div() + .flex() + .flex_col() + .min_w_0() + .child( + div() + .text_size(px(12.)) + .font_weight(FontWeight::MEDIUM) + .text_color(label_color) + .child(target.label()), + ) + .child( + div() + .text_size(px(10.)) + .text_color(description_color) + .child(target.description()), + ), + ) + } else { + base.flex_col() + .items_center() + .justify_end() + .gap(px(4.)) + // `pl-5` on the split controls when compact, to keep the icon + // optically centred against the chevron on the right. + .when(split, |this| this.pl(px(20.))) + .child(icon) + .child( + div() + .text_size(px(12.)) + .text_color(label_color) + .child(target.label()), + ) + } + } + + /// `BaseControls`: camera, microphone, system audio. + fn render_base_controls(&self, _cx: &mut Context) -> impl IntoElement { + let gap = if self.expanded { 10. } else { 8. }; + + div() + .flex() + .flex_col() + .gap(px(gap)) + .w_full() + .child(self.labelled( + "Camera", + self.render_device_row( + "camera-row", + "icons/camera.svg", + self.camera + .as_ref() + .map(|camera| camera.label.clone()) + .unwrap_or_else(|| "No Camera".into()), + if self.camera.is_some() { + PillState::On + } else { + PillState::Off + }, + ), + )) + .child(self.labelled( + "Microphone", + self.render_device_row( + "microphone-row", + "icons/microphone.svg", + self.microphone + .as_ref() + .map(|mic| mic.name.clone()) + .unwrap_or_else(|| "No Microphone".into()), + if self.microphone.is_some() { + PillState::On + } else { + PillState::Off + }, + ), + )) + .child(self.labelled( + "System audio", + self.render_device_row( + "system-audio-row", + "icons/screen.svg", + if self.system_audio { + "Record System Audio".into() + } else { + "No System Audio".into() + }, + if self.system_audio { + PillState::On + } else { + PillState::Off + }, + ), + )) + } + + /// `ExpandedControlLabel`: `mb-1 px-1`, `text-xs font-semibold text-gray-12`. + /// Only rendered when expanded. + fn labelled(&self, title: &'static str, row: impl IntoElement) -> impl IntoElement { + let theme = self.theme; + let expanded = self.expanded; + + div() + .flex() + .flex_col() + .when(expanded, |this| { + this.child( + div().mb(px(4.)).px(px(4.)).child( + div() + .text_size(px(12.)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(theme.gray_12) + .child(title), + ), + ) + }) + .child(row) + } + + /// `DEVICE_ROW_CLASS`: 42px tall, `rounded-lg`, `border-gray-6`, `bg-gray-2`, + /// `pl-3 pr-1.5 gap-2.5`. + fn render_device_row( + &self, + id: &'static str, + icon: &'static str, + label: String, + pill: PillState, + ) -> impl IntoElement { + let theme = self.theme; + + div() + .id(SharedString::from(id)) + .flex() + .flex_row() + .items_center() + .gap(px(10.)) + .pl(px(12.)) + .pr(px(6.)) + .w_full() + .h(px(DEVICE_ROW_HEIGHT)) + .rounded(px(8.)) + .border_1() + .border_color(theme.gray_6) + .bg(theme.gray_2) + .cursor_default() + .overflow_hidden() + .child( + svg() + .path(icon) + .size(px(16.)) + .flex_shrink_0() + .text_color(theme.gray_11), + ) + .child( + div() + .flex_1() + .min_w_0() + .text_size(px(14.)) + .font_weight(FontWeight::MEDIUM) + .text_color(theme.gray_12) + .truncate() + .child(label), + ) + .child(pill.render(theme)) + .hover(|style| style.bg(theme.gray_4).border_color(theme.gray_8)) + } +} + +/// `InfoPill` + `TargetSelectInfoPill`: 24px tall, min 40px wide, `px-2.5`, +/// `rounded-full`, 11px medium text. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum PillState { + On, + Off, +} + +impl PillState { + fn render(self, theme: Theme) -> impl IntoElement { + let (bg, fg, text) = match self { + Self::On => ( + Hsla::from(theme.blue_9), + Hsla::from(gpui::white()), + "On", + ), + Self::Off => ( + Hsla::from(theme.gray_5), + Hsla::from(theme.gray_11), + "Off", + ), + }; + + div() + .flex() + .items_center() + .justify_center() + .h(px(24.)) + .min_w(px(40.)) + .px(px(10.)) + .flex_shrink_0() + .rounded_full() + .bg(bg) + .text_size(px(11.)) + .font_weight(FontWeight::MEDIUM) + .text_color(fg) + .child(text) + } +} diff --git a/apps/desktop-gpui/src/theme.rs b/apps/desktop-gpui/src/theme.rs new file mode 100644 index 0000000000..115528cd1d --- /dev/null +++ b/apps/desktop-gpui/src/theme.rs @@ -0,0 +1,171 @@ +//! Colour tokens, transcribed from the web app's resolved values. +//! +//! The Tauri UI gets these from Radix Colors via Tailwind v4 `@theme` aliases, +//! with a handful of dark-mode entries overridden in +//! `apps/desktop/src/styles/theme.css` (`:root.dark`, which outranks Radix's own +//! `.dark`). The overridden ones are marked below -- they are *not* stock Radix, +//! so regenerating this from a Radix crate would silently change the palette. + +use gpui::{Hsla, Rgba, WindowAppearance, rgb, rgba}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Appearance { + Light, + Dark, +} + +impl Appearance { + pub fn from_window(appearance: WindowAppearance) -> Self { + match appearance { + WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light, + WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark, + } + } +} + +/// Every colour the main window draws with, already resolved for one appearance. +#[derive(Debug, Clone, Copy)] +pub struct Theme { + pub appearance: Appearance, + + pub gray_1: Rgba, + pub gray_2: Rgba, + pub gray_3: Rgba, + pub gray_4: Rgba, + pub gray_5: Rgba, + pub gray_6: Rgba, + pub gray_7: Rgba, + pub gray_8: Rgba, + pub gray_9: Rgba, + pub gray_10: Rgba, + pub gray_11: Rgba, + pub gray_12: Rgba, + + pub blue_3: Rgba, + pub blue_4: Rgba, + pub blue_5: Rgba, + pub blue_8: Rgba, + pub blue_9: Rgba, + pub blue_10: Rgba, + pub blue_11: Rgba, + + pub red_9: Rgba, + pub red_10: Rgba, + + /// `--blue-500`: the device-list selection fill and the Mode pill's ring. + /// Distinct from `blue-9`; the two are not interchangeable. + pub blue_500: Rgba, + + /// `--text-primary`, the root text colour. Slightly translucent by design. + pub text_primary: Rgba, +} + +impl Theme { + pub fn new(appearance: Appearance) -> Self { + match appearance { + Appearance::Light => Self::light(), + Appearance::Dark => Self::dark(), + } + } + + pub fn light() -> Self { + Self { + appearance: Appearance::Light, + + gray_1: rgb(0xfcfcfc), + gray_2: rgb(0xf9f9f9), + gray_3: rgb(0xf0f0f0), + gray_4: rgb(0xe8e8e8), + gray_5: rgb(0xe0e0e0), + gray_6: rgb(0xd9d9d9), + gray_7: rgb(0xcecece), + gray_8: rgb(0xbbbbbb), + gray_9: rgb(0x8d8d8d), + gray_10: rgb(0x838383), + gray_11: rgb(0x646464), + gray_12: rgb(0x202020), + + blue_3: rgb(0xe6f4fe), + blue_4: rgb(0xd5efff), + blue_5: rgb(0xc2e5ff), + blue_8: rgb(0x5eb1ef), + blue_9: rgb(0x0090ff), + blue_10: rgb(0x0588f0), + blue_11: rgb(0x0d74ce), + + red_9: rgb(0xe5484d), + red_10: rgb(0xdc3e42), + + blue_500: rgb(0x3666c5), + + text_primary: rgba(0x12161ff2), + } + } + + pub fn dark() -> Self { + Self { + appearance: Appearance::Dark, + + // gray 1-6 and 11 are the theme.css overrides, not stock Radix. + gray_1: rgb(0x111111), + gray_2: rgb(0x1c1c1c), + gray_3: rgb(0x282828), + gray_4: rgb(0x323232), + gray_5: rgb(0x3a3a3a), + gray_6: rgb(0x444444), + gray_7: rgb(0x484848), + gray_8: rgb(0x606060), + gray_9: rgb(0x6e6e6e), + gray_10: rgb(0x7b7b7b), + gray_11: rgb(0xa1a1a1), + gray_12: rgb(0xeeeeee), + + blue_3: rgb(0x0d2847), + blue_4: rgb(0x003362), + blue_5: rgb(0x004074), + blue_8: rgb(0x2870bd), + blue_9: rgb(0x0090ff), + blue_10: rgb(0x3b9eff), + blue_11: rgb(0x70b8ff), + + red_9: rgb(0xe5484d), + red_10: rgb(0xec5d5e), + + blue_500: rgb(0x0a84ff), + + text_primary: rgba(0xfffffff2), + } + } + + pub fn is_dark(&self) -> bool { + self.appearance == Appearance::Dark + } + + /// The selected-tile fill. Dark mode uses `blue-3/30` rather than solid + /// `blue-3`, so it reads as a tint over `gray-1` instead of a slab. + pub fn tile_selected_bg(&self) -> Hsla { + if self.is_dark() { + let mut color: Hsla = self.blue_3.into(); + color.a = 0.3; + color + } else { + self.blue_3.into() + } + } + + pub fn tile_selected_hover_bg(&self) -> Hsla { + if self.is_dark() { + let mut color: Hsla = self.blue_4.into(); + color.a = 0.4; + color + } else { + self.blue_4.into() + } + } + + /// Traffic light fills. Minimize is never drawn in the main window + /// (`showMinimize={false}`), so only close and zoom are needed. + pub const TRAFFIC_CLOSE: u32 = 0xff5f57; + pub const TRAFFIC_ZOOM: u32 = 0x28c840; + pub const TRAFFIC_INACTIVE: u32 = 0xdcdcdc; +} From 6395badc653c2f63c0b0b4db9e074d8f950cb0a2 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:30:06 +0100 Subject: [PATCH 005/146] feat(desktop-gpui): device picker panels Clicking the camera or microphone row now opens a picker, and picking a device updates the row and its pill. Like the Tauri app, this is not a popup: the panel takes over the whole window body and offers a Back button. Rows carry real subtitles rather than placeholder text. Cameras report their highest-resolution advertised format via `CameraInfo::formats()`, ordered by pixel count then frame rate to match the web UI's `bestFormat`; microphones report the config they would actually be opened with, from cpal's `default_input_config`. On this machine that reads 1920x1080 @ 30fps for the Camo camera and 44.1kHz Mono for the built-in microphone. Enumerating all of it still takes 0.27s, so it stays on the background executor. Selection uses `--blue-500`, which is a Cap-specific token and a different colour from `blue-9` used by the pills -- worth keeping straight, since both are "the blue one". The search field that sits beside Back in the Tauri panel is not here: it needs real text input, which is its own piece of gpui plumbing. System audio has no device to choose, so its row is a plain toggle. --- apps/desktop-gpui/assets/icons/circle-x.svg | 3 + apps/desktop-gpui/assets/icons/move-left.svg | 1 + apps/desktop-gpui/src/assets.rs | 10 +- apps/desktop-gpui/src/devices.rs | 121 ++++- apps/desktop-gpui/src/main.rs | 6 +- apps/desktop-gpui/src/main_window.rs | 479 ++++++++++++++++--- apps/desktop-gpui/src/theme.rs | 6 + 7 files changed, 528 insertions(+), 98 deletions(-) create mode 100644 apps/desktop-gpui/assets/icons/circle-x.svg create mode 100644 apps/desktop-gpui/assets/icons/move-left.svg diff --git a/apps/desktop-gpui/assets/icons/circle-x.svg b/apps/desktop-gpui/assets/icons/circle-x.svg new file mode 100644 index 0000000000..1af8ef0d58 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/circle-x.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/move-left.svg b/apps/desktop-gpui/assets/icons/move-left.svg new file mode 100644 index 0000000000..6fc9e50d28 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/move-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-gpui/src/assets.rs b/apps/desktop-gpui/src/assets.rs index c9f6599a96..1b27984900 100644 --- a/apps/desktop-gpui/src/assets.rs +++ b/apps/desktop-gpui/src/assets.rs @@ -37,6 +37,7 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": "camera.svg", "check.svg", "chevron-down.svg", + "circle-x.svg", "enlarge.svg", "film-cut.svg", "image.svg", @@ -47,6 +48,7 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": "logo-wordmark.svg", "microphone.svg", "minimize.svg", + "move-left.svg", "play-circle.svg", "scan-text.svg", "screen.svg", @@ -87,8 +89,12 @@ impl Assets { /// system knows about, so this has to run before the first window renders or /// text silently falls back to the system UI font. pub fn load_fonts(&self, cx: &App) -> Result<()> { - cx.text_system() - .add_fonts(FONTS.iter().map(|(_, bytes)| Cow::Borrowed(*bytes)).collect()) + cx.text_system().add_fonts( + FONTS + .iter() + .map(|(_, bytes)| Cow::Borrowed(*bytes)) + .collect(), + ) } } diff --git a/apps/desktop-gpui/src/devices.rs b/apps/desktop-gpui/src/devices.rs index dbba3fd2f5..c6cbf0c19c 100644 --- a/apps/desktop-gpui/src/devices.rs +++ b/apps/desktop-gpui/src/devices.rs @@ -14,6 +14,28 @@ use scap_targets::{Display, DisplayId, Window, WindowId}; pub struct CameraOption { pub device_id: String, pub label: String, + /// Highest-resolution format the device advertises, shown as the row's + /// subtitle. `None` when the device reports no formats. + pub best_format: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct CameraFormat { + pub width: u32, + pub height: u32, + pub frame_rate: f32, +} + +impl CameraFormat { + /// Matches the web UI: `1920×1080 @ 30fps`. + pub fn describe(&self) -> String { + format!( + "{}×{} @ {}fps", + self.width, + self.height, + self.frame_rate.round() as u32 + ) + } } /// A microphone. cpal has no stable id, so the name *is* the identity — which is @@ -21,6 +43,23 @@ pub struct CameraOption { #[derive(Debug, Clone, PartialEq)] pub struct MicrophoneOption { pub name: String, + pub sample_rate: Option, + pub channels: Option, +} + +impl MicrophoneOption { + /// Matches the web UI: `48kHz Stereo`. + pub fn describe(&self) -> Option { + let sample_rate = self.sample_rate?; + let khz = sample_rate as f32 / 1000.; + let layout = match self.channels { + Some(1) => "Mono".to_string(), + Some(2) => "Stereo".to_string(), + Some(n) => format!("{n}ch"), + None => return Some(format!("{khz}kHz")), + }; + Some(format!("{khz}kHz {layout}")) + } } #[derive(Debug, Clone, PartialEq)] @@ -62,9 +101,29 @@ impl DeviceSnapshot { fn list_cameras() -> Vec { cap_camera::list_cameras() - .map(|camera| CameraOption { - device_id: camera.device_id().to_string(), - label: camera.display_name().to_string(), + .map(|camera| { + // Highest resolution first, then highest frame rate at that + // resolution -- the same ordering the web UI's `bestFormat` uses. + let best_format = camera.formats().and_then(|formats| { + formats + .into_iter() + .max_by(|a, b| { + (a.width() * a.height()) + .cmp(&(b.width() * b.height())) + .then(a.frame_rate().total_cmp(&b.frame_rate())) + }) + .map(|format| CameraFormat { + width: format.width(), + height: format.height(), + frame_rate: format.frame_rate(), + }) + }); + + CameraOption { + device_id: camera.device_id().to_string(), + label: camera.display_name().to_string(), + best_format, + } }) .collect() } @@ -74,21 +133,32 @@ fn list_cameras() -> Vec { /// appended, deduped by name. fn list_microphones() -> Vec { let host = cpal::default_host(); - let mut names: Vec = Vec::new(); + let mut mics: Vec = Vec::new(); + + let mut push = |device: cpal::Device| { + let Ok(name) = device.name() else { return }; + if mics.iter().any(|mic| mic.name == name) { + return; + } - if let Some(name) = host - .default_input_device() - .and_then(|device| device.name().ok()) - { - names.push(name); + // `default_input_config` is the config the device would actually be + // opened with, which is what the row should describe. + let config = device.default_input_config().ok(); + mics.push(MicrophoneOption { + name, + sample_rate: config.as_ref().map(|config| config.sample_rate().0), + channels: config.as_ref().map(|config| config.channels()), + }); + }; + + if let Some(device) = host.default_input_device() { + push(device); } match host.input_devices() { Ok(devices) => { - for name in devices.filter_map(|device| device.name().ok()) { - if !names.contains(&name) { - names.push(name); - } + for device in devices { + push(device); } } Err(error) => { @@ -96,10 +166,7 @@ fn list_microphones() -> Vec { } } - names - .into_iter() - .map(|name| MicrophoneOption { name }) - .collect() + mics } fn list_displays() -> Vec { @@ -107,9 +174,7 @@ fn list_displays() -> Vec { .into_iter() .map(|display| { let id = display.id(); - let label = display - .name() - .unwrap_or_else(|| format!("Display {}", &id)); + let label = display.name().unwrap_or_else(|| format!("Display {}", id)); DisplayOption { id, label } }) .collect() @@ -165,11 +230,23 @@ mod tests { println!("cameras ({}):", snapshot.cameras.len()); for camera in &snapshot.cameras { - println!(" {} [{}]", camera.label, camera.device_id); + println!( + " {} [{}] {}", + camera.label, + camera.device_id, + camera + .best_format + .map(|format| format.describe()) + .unwrap_or_else(|| "".into()) + ); } println!("microphones ({}):", snapshot.microphones.len()); for mic in &snapshot.microphones { - println!(" {}", mic.name); + println!( + " {} {}", + mic.name, + mic.describe().unwrap_or_else(|| "".into()) + ); } println!("displays ({}):", snapshot.displays.len()); for display in &snapshot.displays { diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index 3b3ff8cdb9..b925d2bb0a 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -37,7 +37,11 @@ fn main() { // 330x395 is what `CapWindowId::Main::min_size` uses in the Tauri app, // and the window is fixed at that size there too. - let bounds = Bounds::centered(None, size(px(MAIN_WINDOW_WIDTH), px(MAIN_WINDOW_HEIGHT)), cx); + let bounds = Bounds::centered( + None, + size(px(MAIN_WINDOW_WIDTH), px(MAIN_WINDOW_HEIGHT)), + cx, + ); cx.open_window( WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index 1cd09dd14c..1706b29e43 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -7,8 +7,9 @@ //! considerably easier to check against the original than `12.` and `10.`. use gpui::{ - Context, FontWeight, Hsla, InteractiveElement, IntoElement, ParentElement, Render, SharedString, - StatefulInteractiveElement, Styled, Window, div, prelude::FluentBuilder, px, rgb, svg, + Context, FontWeight, Hsla, InteractiveElement, IntoElement, ParentElement, Render, + SharedString, StatefulInteractiveElement, Styled, Window, div, prelude::FluentBuilder, px, rgb, + svg, }; use crate::{ @@ -81,6 +82,39 @@ impl TargetType { } } +/// Which device picker has taken over the window body, if any. +/// +/// Clicking a device row does not open a popup in the Tauri app either: it +/// swaps the whole body for a full-height panel and offers a Back button. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DeviceMenu { + Camera, + Microphone, +} + +impl DeviceMenu { + fn title(self) -> &'static str { + match self { + Self::Camera => "Camera", + Self::Microphone => "Microphone", + } + } + + fn none_label(self) -> &'static str { + match self { + Self::Camera => "No Camera", + Self::Microphone => "No Microphone", + } + } + + fn icon(self) -> &'static str { + match self { + Self::Camera => "icons/camera.svg", + Self::Microphone => "icons/microphone.svg", + } + } +} + pub struct MainWindow { theme: Theme, expanded: bool, @@ -90,6 +124,10 @@ pub struct MainWindow { camera: Option, microphone: Option, system_audio: bool, + active_menu: Option, + /// True until the background enumeration has reported back, so the panel can + /// say "Loading..." rather than "No cameras found". + enumerating: bool, } impl MainWindow { @@ -115,6 +153,7 @@ impl MainWindow { "enumerated capture devices" ); this.devices = snapshot; + this.enumerating = false; cx.notify(); }) .ok(); @@ -130,6 +169,8 @@ impl MainWindow { camera: None, microphone: None, system_audio: false, + active_menu: None, + enumerating: true, }; // The Tauri app restores the persisted expanded state on mount and @@ -268,12 +309,7 @@ impl MainWindow { .justify_center() .size(px(20.)) .flex_shrink_0() - .child( - svg() - .path(path) - .size(px(size)) - .text_color(theme.gray_11), - ) + .child(svg().path(path).size(px(size)).text_color(theme.gray_11)) .hover(|style| style.text_color(theme.gray_12)) }; @@ -331,16 +367,20 @@ impl MainWindow { /// Page root: `px-[13px] gap-2 pb-[8px]`. fn render_body(&self, cx: &mut Context) -> impl IntoElement { - div() + let root = div() .flex() .flex_col() .flex_1() .min_h_0() .px(px(13.)) .pb(px(8.)) - .gap(px(8.)) - .child(self.render_logo_row(cx)) - .child( + .gap(px(8.)); + + // The logo/mode row is hidden while a picker is open -- the panel takes + // the full body, exactly as `!activeMenu() && ...` does in index.tsx. + match self.active_menu { + Some(menu) => root.child(self.render_device_panel(menu, cx)), + None => root.child(self.render_logo_row(cx)).child( div() .flex() .flex_col() @@ -349,13 +389,281 @@ impl MainWindow { .gap(px(8.)) .child(self.render_targets(cx)) .child(self.render_base_controls(cx)), + ), + } + } + + /// `TargetMenuPanel`: a Back button above a scrolling device list. + /// + /// The search field the Tauri panel puts next to Back is not here yet -- + /// it needs real text input, which is its own piece of gpui plumbing. + fn render_device_panel(&self, menu: DeviceMenu, cx: &mut Context) -> gpui::Div { + let theme = self.theme; + + div() + .flex() + .flex_col() + .flex_1() + .min_h_0() + .child( + div() + .flex() + .flex_row() + .items_center() + .gap(px(12.)) + .mt(px(12.)) + .h(px(36.)) + .flex_shrink_0() + .child( + div() + .id("device-panel-back") + .flex() + .flex_row() + .items_center() + .gap(px(4.)) + .h(px(36.)) + .px(px(8.)) + .flex_shrink_0() + .rounded(px(6.)) + .text_size(px(12.)) + .text_color(theme.gray_11) + .child( + svg() + .path("icons/move-left.svg") + .size(px(12.)) + .text_color(theme.gray_11), + ) + .child( + div() + .font_weight(FontWeight::MEDIUM) + .text_color(theme.gray_12) + .child("Back"), + ) + .hover(|style| style.bg(theme.gray_4)) + .on_click(cx.listener(|this, _, _window, cx| { + this.active_menu = None; + cx.notify(); + })), + ) + .child( + div() + .flex_1() + .min_w_0() + .text_size(px(12.)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(theme.gray_12) + .child(menu.title()), + ), + ) + .child( + div() + .id("device-panel-list") + .flex() + .flex_col() + .flex_1() + .min_h_0() + .pt(px(16.)) + .px(px(8.)) + .gap(px(4.)) + .overflow_y_scroll() + .children(self.render_device_list(menu, cx)), ) } - /// `mt-[16px] mb-[6px]`, logo `w-[92px]`, Mode pill on the right. - fn render_logo_row(&self, cx: &mut Context) -> impl IntoElement { + fn render_device_list( + &self, + menu: DeviceMenu, + cx: &mut Context, + ) -> Vec { let theme = self.theme; + if self.enumerating { + return vec![ + div() + .py(px(24.)) + .w_full() + .text_size(px(14.)) + .text_color(theme.gray_11) + .child("Loading...") + .into_any_element(), + ]; + } + + // Index 0 is always the "none" row, matching `DeviceListPanel`. + let mut rows = vec![ + self.render_device_list_row( + SharedString::from(format!("{}-none", menu.title())), + "icons/circle-x.svg", + menu.none_label().to_string(), + None, + match menu { + DeviceMenu::Camera => self.camera.is_none(), + DeviceMenu::Microphone => self.microphone.is_none(), + }, + cx.listener(move |this, _, _window, cx| { + match menu { + DeviceMenu::Camera => this.camera = None, + DeviceMenu::Microphone => this.microphone = None, + } + this.active_menu = None; + cx.notify(); + }), + ) + .into_any_element(), + ]; + + match menu { + DeviceMenu::Camera => { + for camera in &self.devices.cameras { + let selected = self + .camera + .as_ref() + .is_some_and(|selected| selected.device_id == camera.device_id); + let chosen = camera.clone(); + + rows.push( + self.render_device_list_row( + SharedString::from(format!("camera-{}", camera.device_id)), + menu.icon(), + camera.label.clone(), + camera.best_format.map(|format| format.describe()), + selected, + cx.listener(move |this, _, _window, cx| { + this.camera = Some(chosen.clone()); + this.active_menu = None; + cx.notify(); + }), + ) + .into_any_element(), + ); + } + } + DeviceMenu::Microphone => { + for mic in &self.devices.microphones { + let selected = self + .microphone + .as_ref() + .is_some_and(|selected| selected.name == mic.name); + let chosen = mic.clone(); + + rows.push( + self.render_device_list_row( + SharedString::from(format!("mic-{}", mic.name)), + menu.icon(), + mic.name.clone(), + mic.describe(), + selected, + cx.listener(move |this, _, _window, cx| { + this.microphone = Some(chosen.clone()); + this.active_menu = None; + cx.notify(); + }), + ) + .into_any_element(), + ); + } + } + } + + if rows.len() == 1 { + rows.push( + div() + .py(px(16.)) + .w_full() + .text_size(px(14.)) + .text_color(theme.gray_11) + .child(match menu { + DeviceMenu::Camera => "No cameras found", + DeviceMenu::Microphone => "No microphones found", + }) + .into_any_element(), + ); + } + + rows + } + + /// `CameraListItem` / `MicrophoneListItem`: `px-3 py-2.5`, `rounded-lg`, + /// 14px label over an optional 11px detail line indented `pl-7`. + /// + /// Selection is `bg-blue-500` with white text -- note that is the custom + /// `--blue-500`, not `blue-9`; the two are different colours. + fn render_device_list_row( + &self, + id: SharedString, + icon: &'static str, + label: String, + detail: Option, + selected: bool, + on_click: impl Fn(&gpui::ClickEvent, &mut Window, &mut gpui::App) + 'static, + ) -> gpui::Stateful { + let theme = self.theme; + + let foreground = if selected { + gpui::white() + } else { + Hsla::from(theme.gray_12) + }; + let detail_color = if selected { + let mut color = gpui::white(); + color.a = 0.7; + color + } else { + Hsla::from(theme.gray_10) + }; + + div() + .id(id) + .flex() + .flex_col() + .gap(px(2.)) + .px(px(12.)) + .py(px(10.)) + .w_full() + .rounded(px(8.)) + .text_size(px(14.)) + .text_color(foreground) + .when(selected, |this| this.bg(theme.blue_500)) + .when(!selected, |this| this.hover(|style| style.bg(theme.gray_4))) + .child( + div() + .flex() + .flex_row() + .items_center() + .gap(px(12.)) + .w_full() + .child( + svg() + .path(icon) + .size(px(16.)) + .flex_shrink_0() + .text_color(foreground), + ) + .child(div().flex_1().min_w_0().truncate().child(label)) + .when(selected, |this| { + this.child( + svg() + .path("icons/check.svg") + .size(px(16.)) + .flex_shrink_0() + .text_color(foreground), + ) + }), + ) + .children(detail.map(|detail| { + div() + // `pl-7` = 16px icon + 12px gap. + .pl(px(28.)) + .text_size(px(11.)) + .text_color(detail_color) + .truncate() + .child(detail) + })) + .on_click(on_click) + } + + /// `mt-[16px] mb-[6px]`, logo `w-[92px]`, Mode pill on the right. + fn render_logo_row(&self, cx: &mut Context) -> impl IntoElement { div() .flex() .flex_row() @@ -458,7 +766,11 @@ impl MainWindow { .child( svg() .path(mode.icon()) - .size(px(if matches!(mode, Mode::Instant) { 16. } else { 14.4 })) + .size(px(if matches!(mode, Mode::Instant) { + 16. + } else { + 14.4 + })) .text_color(theme.gray_12), ) .hover(|style| style.bg(theme.gray_7)) @@ -620,6 +932,15 @@ impl MainWindow { .flex() .flex_1() .py(px(8.)) + // `hover:bg-blue-4` / `dark:hover:bg-blue-4/40` when selected, + // `hover:bg-gray-4` otherwise. + .hover(move |style| { + style.bg(if selected { + theme.tile_selected_hover_bg() + } else { + Hsla::from(theme.gray_4) + }) + }) .on_click(cx.listener(move |this, _, _window, cx| { this.target = if this.target == Some(target) { None @@ -676,7 +997,7 @@ impl MainWindow { } /// `BaseControls`: camera, microphone, system audio. - fn render_base_controls(&self, _cx: &mut Context) -> impl IntoElement { + fn render_base_controls(&self, cx: &mut Context) -> impl IntoElement { let gap = if self.expanded { 10. } else { 8. }; div() @@ -684,55 +1005,75 @@ impl MainWindow { .flex_col() .gap(px(gap)) .w_full() - .child(self.labelled( - "Camera", - self.render_device_row( - "camera-row", - "icons/camera.svg", - self.camera - .as_ref() - .map(|camera| camera.label.clone()) - .unwrap_or_else(|| "No Camera".into()), - if self.camera.is_some() { - PillState::On - } else { - PillState::Off - }, + .child( + self.labelled( + "Camera", + self.render_device_row( + "camera-row", + "icons/camera.svg", + self.camera + .as_ref() + .map(|camera| camera.label.clone()) + .unwrap_or_else(|| "No Camera".into()), + if self.camera.is_some() { + PillState::On + } else { + PillState::Off + }, + ) + .on_click(cx.listener(|this, _, _window, cx| { + this.active_menu = Some(DeviceMenu::Camera); + cx.notify(); + })), ), - )) - .child(self.labelled( - "Microphone", - self.render_device_row( - "microphone-row", - "icons/microphone.svg", - self.microphone - .as_ref() - .map(|mic| mic.name.clone()) - .unwrap_or_else(|| "No Microphone".into()), - if self.microphone.is_some() { - PillState::On - } else { - PillState::Off - }, + ) + .child( + self.labelled( + "Microphone", + self.render_device_row( + "microphone-row", + "icons/microphone.svg", + self.microphone + .as_ref() + .map(|mic| mic.name.clone()) + .unwrap_or_else(|| "No Microphone".into()), + if self.microphone.is_some() { + PillState::On + } else { + PillState::Off + }, + ) + .on_click(cx.listener(|this, _, _window, cx| { + this.active_menu = Some(DeviceMenu::Microphone); + cx.notify(); + })), ), - )) - .child(self.labelled( - "System audio", - self.render_device_row( - "system-audio-row", - "icons/screen.svg", - if self.system_audio { - "Record System Audio".into() - } else { - "No System Audio".into() - }, - if self.system_audio { - PillState::On - } else { - PillState::Off - }, + ) + .child( + self.labelled( + "System audio", + self.render_device_row( + "system-audio-row", + "icons/screen.svg", + if self.system_audio { + "Record System Audio".into() + } else { + "No System Audio".into() + }, + if self.system_audio { + PillState::On + } else { + PillState::Off + }, + ) + // System audio has no device to choose, so the row is a + // plain toggle rather than a picker. + .on_click(cx.listener(|this, _, _window, cx| { + this.system_audio = !this.system_audio; + cx.notify(); + })), ), - )) + ) } /// `ExpandedControlLabel`: `mb-1 px-1`, `text-xs font-semibold text-gray-12`. @@ -766,7 +1107,7 @@ impl MainWindow { icon: &'static str, label: String, pill: PillState, - ) -> impl IntoElement { + ) -> gpui::Stateful { let theme = self.theme; div() @@ -818,16 +1159,8 @@ enum PillState { impl PillState { fn render(self, theme: Theme) -> impl IntoElement { let (bg, fg, text) = match self { - Self::On => ( - Hsla::from(theme.blue_9), - Hsla::from(gpui::white()), - "On", - ), - Self::Off => ( - Hsla::from(theme.gray_5), - Hsla::from(theme.gray_11), - "Off", - ), + Self::On => (Hsla::from(theme.blue_9), gpui::white(), "On"), + Self::Off => (Hsla::from(theme.gray_5), Hsla::from(theme.gray_11), "Off"), }; div() diff --git a/apps/desktop-gpui/src/theme.rs b/apps/desktop-gpui/src/theme.rs index 115528cd1d..75c906035b 100644 --- a/apps/desktop-gpui/src/theme.rs +++ b/apps/desktop-gpui/src/theme.rs @@ -24,6 +24,12 @@ impl Appearance { } /// Every colour the main window draws with, already resolved for one appearance. +/// +/// The scales are transcribed whole rather than trimmed to today's callers. +/// Half a Radix scale is worse than none: the next person needing `red-9` for +/// the Stop Recording button or `blue-5` for a pressed tile would otherwise +/// have to go back to the CSS and re-derive which entries were overridden. +#[allow(dead_code)] #[derive(Debug, Clone, Copy)] pub struct Theme { pub appearance: Appearance, From 701993471a3612176e826ca368c6cb24af0d3370 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:55:42 +0100 Subject: [PATCH 006/146] feat(desktop-gpui): fidelity pass and the remaining picker panels Groups 1-5 and 7 of the punch list, plus two window-lifecycle bugs the screenshots turned up. They land together because generalising the panel state underpins the mode-info panel, the target pickers and the search field, and splitting that refactor from its three callers would have meant three commits that do not build. Fidelity: - The logo is the real `logo-full.svg` again, through `img()` rather than `svg()`. The two take different paths in gpui -- `svg()` keeps only the alpha and tints it one colour, which flattened the badge, the blue rings and the wordmark into a silhouette, so it had been rebuilt out of divs. `img()` rasterises through resvg and keeps the colour, at 2x, so a 103px source covers the 184 device pixels a 92px lockup needs. Dark mode picks `logo-full-dark.svg`, as the app does. - The plan badge ("Personal") sits next to it. Only the free variant is drawn: which of Pro/Commercial applies comes from the license query, and claiming a plan with no license plumbing would be worse than showing none. - The help button is lucide `circle-help`, not the chat bubble. - The mode pill gains its info dot, hanging off the top-left corner. Panels. The Display and Window chevrons now open two-column card grids of real targets: displays with their refresh rate, windows with their owner and `1920x1080 @ 60 Hz` from real bounds. The mode info panel carries `ModeInfoPanel`'s copy, which is deliberately not `MODE_BUTTONS`' copy -- the hover cards and the panel describe the modes differently and the app ships both. All three share one panel chrome with the device pickers. Search is hand-rolled; gpui has no stock text input. Focus is tracked so the panel keeps receiving keys, `key_char` supplies the typed character so dead keys and option-layouts work, and the caret is a 1px div. There is no selection, no cursor movement and no blink -- enough for a filter and nothing more. Escape clears, then closes. Recents appears when expanded, header and empty state only; thumbnails need the recordings library. Its copy is the real "Your latest captures will appear here.", not the shorter string in the brief. --- .../desktop-gpui/assets/icons/circle-help.svg | 5 + apps/desktop-gpui/assets/icons/history.svg | 4 + .../desktop-gpui/docs/main-window-compact.png | Bin 0 -> 77689 bytes .../docs/main-window-expanded.png | Bin 0 -> 137254 bytes apps/desktop-gpui/src/assets.rs | 2 + apps/desktop-gpui/src/devices.rs | 43 +- apps/desktop-gpui/src/main.rs | 63 +- apps/desktop-gpui/src/main_window.rs | 862 +++++++++++++++--- 8 files changed, 815 insertions(+), 164 deletions(-) create mode 100644 apps/desktop-gpui/assets/icons/circle-help.svg create mode 100644 apps/desktop-gpui/assets/icons/history.svg create mode 100644 apps/desktop-gpui/docs/main-window-compact.png create mode 100644 apps/desktop-gpui/docs/main-window-expanded.png diff --git a/apps/desktop-gpui/assets/icons/circle-help.svg b/apps/desktop-gpui/assets/icons/circle-help.svg new file mode 100644 index 0000000000..957e4f73b4 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/circle-help.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/desktop-gpui/assets/icons/history.svg b/apps/desktop-gpui/assets/icons/history.svg new file mode 100644 index 0000000000..ec7e003b7d --- /dev/null +++ b/apps/desktop-gpui/assets/icons/history.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/desktop-gpui/docs/main-window-compact.png b/apps/desktop-gpui/docs/main-window-compact.png new file mode 100644 index 0000000000000000000000000000000000000000..60068ce15c64a533d80f39007d206124104e7aa3 GIT binary patch literal 77689 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

>Y68t6L9=U>k&-ig$xUjpn@g7G| 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

    )LC%X9jZoh(pt)WFj>PXuLt^dL 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 NC#5V|B>vR<{{t7-1&9Cu literal 0 HcmV?d00001 diff --git a/apps/desktop-gpui/docs/main-window-expanded.png b/apps/desktop-gpui/docs/main-window-expanded.png new file mode 100644 index 0000000000000000000000000000000000000000..f1ed14c0cfd78e84de9e5ab89e3614307e5e8948 GIT binary patch literal 137254 zcmeFYbzD|m_6JILcQ+#4(%p^ZLzi^-Lw75k(gGsg-Hmj2w}g~{AmBaDZ|0pDo%gSM z|Ga#j!`UbI-fOS?u63eRm1R(oh>##4AW-FGCDkAxV5%V?U|%A@0cYCHm^2|EP!z2t zBvj=jBq&r}94)Nv%po9Tqf)ftLF%9Ja`efH6Lj+2SWxYs7cA*qTf!LslAGv~{kf(GM=2R)|x20T2=z*ZDRNFzp zbx_;k!tLbW!p83l`_2yW#$DJ5=vkU|Pv<@u< jG8A2uDpSxBx@qp(+86Oid~rKy zzpX5{B4L3u;_SCw)*n%AukH!^Kq;ev3DEs6)@T8@KXqb%+PCmU=kt}Ds81$`^}lot zclXH>5>kwsr}U?ji-`;c5kuhMBf~$Yq-T^ML}}j_yn^DxUqE4_%%PZ!+DE-&7xxz> z=nuyzR&b%}h4(|69T1Zz2Z_`xW(~!JfY5gs5(L>irVf+syvL^6n`X@QCn$ctWhIUj zEBPsvKsx#Qi`hyB)kdE10Vtj@4S^kZC(zef9HyTLLuRMaL8 za-=zxTy>W`Iipe<>;THc`9uo(er)y#&NUU>WkdT|GlphG^$^sv=p!3`xi-yqKl+!* zy`R~8&AiAAIEt5zvVM9mS+LhcnsYu7?~ zDHl#H3lT?2{VXHty9C1)L9&$m^fQOtw2OmeI24^DI-gMI8>NaV1$!WYI11Iu4qP+( z*RY7b0&=?vUgA4~k=-(pZN&U{7m-d$Q_9T(O8PH}G4~~>&~8HDPLgiz{hSy-eTn|D zyP-t%N|@q1?;idhL6Lp$B9;gJJL z2qZ(*S=m|Z5UTzG8dXh;)6ko^q03h(dXK~q8+1Q@{D|}07gvFD>L%^PiuK>OTe_J2 z0M|WG02Rh$G_XAa4cWX)8OGw5ZepR%_z_UE0Q&@O0AuFij9xo?CeOR6zp=R&aZsaNCjlejB;CKw8UeRxu z*!@^FGFh0fc0ypNwq;QgLkp=0BVk@sxF!t9BGwYo%QRBX4@2&~bRlX9t*7)%Kr1A^ z$G#6V{ls4yLX@v{1WJDAGR4M)mK|)7$2kS%hS*BNEWtP}WJT&t)bVyha&;QtiX4uO z`)yQ)DH;Lsu4w{@BQ^GO^*xAPD_YaE0Yu$Cxme>-4(}6(?F={3OS*-G*0bvjDGZ$( z$J#e~$X7e2R$rI(m0VVFi2 zdwshuU2=?Sf|7;Wf$A%D4pk$jK$IU1mR#-|m8%z0iQfwa)iAB0tVKNTd;BbZsB?s`wQ;L*=$?Momg%W`KR29 zF8MCQpFxlg#+DfNY_rC7J$xZK)BFV;(#X>Ial*V0+7fe96KsP{vD+SfczwduW(CI7 zCDU*PoYF#rie@6aOuG$(bZ^^~2wn)kFimQBL8p{c=cYrVg;w^~J$2sJ#)`)qGfqC`Ssz=h9roi09Jj-ll@7HJ{n{x}+ z#?3dIufo{|&ECFI+)rOcJ4Pdk@QomfK$H1s)@+t<*0DQUgJ+gBR7vDxa*9u4ffw1g$G*`X##P&<#X7_V zY)Nbn#iMVpt`B@1uO048?h7C6ZmjRPZ}xv^JqTZM-eEtSee)Tq*?;4F>ty&Ka6kGO z_q}W1+vU(%1=bj{2XX+iB4{P(GH5>tJ!CAHBiJU`JH#O55GEIiGBh--9I0iTa|aPT z-@c)GH|3RY**Di`+Beci7PcBT+DlCL0b7EuG6O8q%OQ^SnjDeJE5VkETvA77Jdz(z z>1}LuWu!kACyRx{SQtq9w0>#5#&PFySF3+LrYTChpNDP=TMered*cNhO{KD3E|gU3}(7o)>eo4@DRherkJMYz!U2LOol6BNsl(^I1bKZRCdfz`XM$^D-2DDQ7X#A+{jh-sH9lTw-OnAI*88NHYQB~534$<%qj zOnneNI#5+BUfcMlV?1_jBV|1|DZP$0Ei3cAkH!b)ḬuXCQ-(Hn;)XT02_qh(X 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

    oj)<7;$l_)GIf9vEl@3byaZgh8S7hg;Awf_F@ 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 BgqZ*U literal 0 HcmV?d00001 diff --git a/apps/desktop-gpui/src/assets.rs b/apps/desktop-gpui/src/assets.rs index 1b27984900..4fe9b2578b 100644 --- a/apps/desktop-gpui/src/assets.rs +++ b/apps/desktop-gpui/src/assets.rs @@ -37,9 +37,11 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": "camera.svg", "check.svg", "chevron-down.svg", + "circle-help.svg", "circle-x.svg", "enlarge.svg", "film-cut.svg", + "history.svg", "image.svg", "info.svg", "instant.svg", diff --git a/apps/desktop-gpui/src/devices.rs b/apps/desktop-gpui/src/devices.rs index c6cbf0c19c..8743c8e793 100644 --- a/apps/desktop-gpui/src/devices.rs +++ b/apps/desktop-gpui/src/devices.rs @@ -66,6 +66,15 @@ impl MicrophoneOption { pub struct DisplayOption { pub id: DisplayId, pub label: String, + pub refresh_rate: f64, +} + +impl DisplayOption { + /// `formatRefreshRate`: `60 Hz`, or nothing when the display does not + /// report one. + pub fn describe_refresh_rate(&self) -> Option { + (self.refresh_rate > 0.).then(|| format!("{} Hz", self.refresh_rate.round() as u32)) + } } #[derive(Debug, Clone, PartialEq)] @@ -75,6 +84,30 @@ pub struct WindowOption { pub label: String, /// Owning application, shown as the secondary line. pub app: String, + pub size: Option<(u32, u32)>, + pub refresh_rate: Option, +} + +impl WindowOption { + /// The card's third line. The Tauri card joins resolution and refresh rate + /// with `@` when it has both and falls back to whichever it has: + /// `1920×1080 @ 60 Hz`. Note the multiplication sign, not a letter x. + pub fn describe_metadata(&self) -> Option { + let resolution = self + .size + .filter(|(width, height)| *width > 0 && *height > 0) + .map(|(width, height)| format!("{width}×{height}")); + let refresh = self + .refresh_rate + .filter(|rate| *rate > 0.) + .map(|rate| format!("{} Hz", rate.round() as u32)); + + match (resolution, refresh) { + (Some(resolution), Some(refresh)) => Some(format!("{resolution} @ {refresh}")), + (Some(only), None) | (None, Some(only)) => Some(only), + (None, None) => None, + } + } } #[derive(Debug, Clone, Default, PartialEq)] @@ -175,7 +208,11 @@ fn list_displays() -> Vec { .map(|display| { let id = display.id(); let label = display.name().unwrap_or_else(|| format!("Display {}", id)); - DisplayOption { id, label } + DisplayOption { + id, + label, + refresh_rate: display.refresh_rate(), + } }) .collect() } @@ -210,6 +247,10 @@ fn list_windows() -> Vec { id: window.id(), label, app, + size: window + .logical_size() + .map(|size| (size.width() as u32, size.height() as u32)), + refresh_rate: window.display().map(|display| display.refresh_rate()), }) }) .collect() diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index b925d2bb0a..0ad632f926 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -42,29 +42,46 @@ fn main() { size(px(MAIN_WINDOW_WIDTH), px(MAIN_WINDOW_HEIGHT)), cx, ); - cx.open_window( - WindowOptions { - window_bounds: Some(WindowBounds::Windowed(bounds)), - // `None`, not a transparent titlebar with repositioned traffic - // lights: the Tauri main window returns `None` from - // `traffic_lights_position`, which routes it to - // `.decorations(false)`. There is no titlebar and there are no - // traffic lights -- the whole shell is custom-drawn. In gpui a - // `None` titlebar drops NSClosable/NSMiniaturizable/NSResizable - // from the style mask, which is the equivalent. - titlebar: None, - // The header is dragged by the app via `start_window_move` - // rather than by AppKit, so mark the content view as app-owned - // titlebar content. - app_owns_titlebar_drag: true, - window_background: gpui::WindowBackgroundAppearance::Transparent, - is_resizable: false, - is_minimizable: false, - ..Default::default() - }, - |window, cx| cx.new(|cx| MainWindow::new(window, cx)), - ) - .expect("failed to open the main window"); + let window_handle = cx + .open_window( + WindowOptions { + window_bounds: Some(WindowBounds::Windowed(bounds)), + // `None`, not a transparent titlebar with repositioned traffic + // lights: the Tauri main window returns `None` from + // `traffic_lights_position`, which routes it to + // `.decorations(false)`. There is no titlebar and there are no + // traffic lights -- the whole shell is custom-drawn. In gpui a + // `None` titlebar drops NSClosable/NSMiniaturizable/NSResizable + // from the style mask, which is the equivalent. + titlebar: None, + // TODO: always-on-top, `visible_on_all_workspaces(true)` + // and the NSPanel level-100 treatment the Tauri window + // gets. `WindowKind::Floating` is not the answer: it does + // float at NSFloatingWindowLevel, but it allocates an + // NSPanel, and a panel hides itself when the application + // deactivates -- the window vanishes the moment you click + // another app, which is exactly wrong for a recorder. This + // needs the AppKit calls `windows.rs` makes, on a normal + // window. + kind: gpui::WindowKind::Normal, + // The header is dragged by the app via `start_window_move` + // rather than by AppKit, so mark the content view as app-owned + // titlebar content. + app_owns_titlebar_drag: true, + window_background: gpui::WindowBackgroundAppearance::Transparent, + is_resizable: false, + is_minimizable: false, + ..Default::default() + }, + |window, cx| cx.new(|cx| MainWindow::new(window, cx)), + ) + .expect("failed to open the main window"); + + // Enumeration is started here rather than in `MainWindow::new`, which + // runs before the window is fully built -- see `start_enumeration`. + window_handle + .update(cx, |view, window, cx| view.start_enumeration(window, cx)) + .expect("failed to start device enumeration"); cx.activate(true); }); } diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index 1706b29e43..a71b243552 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -8,13 +8,13 @@ use gpui::{ Context, FontWeight, Hsla, InteractiveElement, IntoElement, ParentElement, Render, - SharedString, StatefulInteractiveElement, Styled, Window, div, prelude::FluentBuilder, px, rgb, - svg, + SharedString, StatefulInteractiveElement, Styled, Window, div, img, prelude::FluentBuilder, px, + rgb, svg, }; use crate::{ MAIN_WINDOW_HEIGHT, MAIN_WINDOW_WIDTH, - devices::{CameraOption, DeviceSnapshot, MicrophoneOption}, + devices::{CameraOption, DeviceSnapshot, DisplayOption, MicrophoneOption, WindowOption}, theme::{Appearance, Theme}, }; @@ -42,6 +42,42 @@ impl Mode { Self::Screenshot => "icons/screenshot.svg", } } + + /// `size-4` for instant, `size-[0.9rem]` for the other two. + fn icon_size(self) -> f32 { + match self { + Self::Instant => 16., + Self::Studio | Self::Screenshot => 14.4, + } + } + + /// `ModeInfoPanel`'s `modeOptions`, which is *not* `MODE_BUTTONS` -- the + /// hover cards and the info panel describe the modes differently and the + /// app carries both sets of strings. + fn panel_title(self) -> &'static str { + match self { + Self::Instant => "Instant", + Self::Studio => "Studio", + Self::Screenshot => "Screenshot", + } + } + + fn panel_description(self) -> &'static str { + match self { + Self::Instant => { + "Share instantly with a link. Your recording uploads as you record, so you \ + can share it immediately when you're done." + } + Self::Studio => { + "Record locally in the highest quality for editing later. Perfect for \ + creating polished content with effects and transitions." + } + Self::Screenshot => { + "Capture and annotate screenshots instantly. Great for quick captures, bug \ + reports, and visual communication." + } + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -113,6 +149,31 @@ impl DeviceMenu { Self::Microphone => "icons/microphone.svg", } } + + fn empty_message(self, searching: bool) -> &'static str { + match (self, searching) { + (Self::Camera, false) => "No cameras found", + (Self::Camera, true) => "No matching cameras", + (Self::Microphone, false) => "No microphones found", + (Self::Microphone, true) => "No matching microphones", + } + } +} + +/// Whatever has taken over the window body in place of the home screen. +/// +/// The Tauri app opens some of these as separate windows (mode info is the +/// 580x340 ModeSelect window) and some as in-place panels. There is only one +/// window here, so they are all panels; the ones that differ are called out +/// in the README's deviations table. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Panel { + /// Pick a camera or a microphone. + Device(DeviceMenu), + /// Pick which display or which window to capture. + Target(TargetType), + /// What the three recording modes do. + ModeInfo, } pub struct MainWindow { @@ -124,7 +185,13 @@ pub struct MainWindow { camera: Option, microphone: Option, system_audio: bool, - active_menu: Option, + /// Which display/window is selected for each split target. + selected_display: Option, + selected_window: Option, + panel: Option, + /// Live filter text for the device and target panels. + search: String, + search_focus: gpui::FocusHandle, /// True until the background enumeration has reported back, so the panel can /// say "Loading..." rather than "No cameras found". enumerating: bool, @@ -138,13 +205,42 @@ impl MainWindow { // run on the main thread -- doing it inline here costs ~180ms of a // blank window on this machine, and more on a machine with more // capture devices. - cx.spawn(async move |this, cx| { + + Self { + theme, + expanded: false, + mode: Mode::Instant, + target: None, + devices: DeviceSnapshot::default(), + camera: None, + microphone: None, + system_audio: false, + selected_display: None, + selected_window: None, + panel: None, + search: String::new(), + search_focus: cx.focus_handle(), + enumerating: true, + } + } + + /// Kick off device enumeration. + /// + /// Deliberately *not* called from `new`. `new` runs inside `open_window`'s + /// builder closure, before the window is fully constructed, and a task + /// spawned there resolves against a window whose invalidator is not yet + /// wired to the platform window -- the update runs, the model updates, and + /// no frame is ever scheduled, so the panels stay on "Loading..." until + /// some unrelated event forces a redraw. Calling this once the window + /// handle exists is what makes the refresh land. + pub fn start_enumeration(&mut self, window: &mut Window, cx: &mut Context) { + cx.spawn_in(window, async move |this, cx| { let snapshot = cx .background_executor() .spawn(async { DeviceSnapshot::enumerate() }) .await; - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { tracing::info!( cameras = snapshot.cameras.len(), microphones = snapshot.microphones.len(), @@ -156,29 +252,9 @@ impl MainWindow { this.enumerating = false; cx.notify(); }) - .ok(); + .unwrap_or_else(|error| tracing::error!("device enumeration update failed: {error:#}")); }) .detach(); - - let this = Self { - theme, - expanded: false, - mode: Mode::Instant, - target: None, - devices: DeviceSnapshot::default(), - camera: None, - microphone: None, - system_audio: false, - active_menu: None, - enumerating: true, - }; - - // The Tauri app restores the persisted expanded state on mount and - // resizes without animating. There is nothing persisted yet, but the - // window size still has to agree with the state the view starts in. - this.apply_window_size(window); - - this } fn toggle_expanded(&mut self, window: &mut Window, cx: &mut Context) { @@ -320,7 +396,7 @@ impl MainWindow { .gap(px(4.)) .mx(px(8.)) .min_w_0() - .child(icon_button("help", "icons/support.svg", 16.)) + .child(icon_button("help", "icons/circle-help.svg", 16.)) // The drag handle, and *only* this. The Tauri header puts // `data-tauri-drag-region` on the header and this spacer but not on // the buttons; putting the handler on the header root instead makes @@ -378,28 +454,51 @@ impl MainWindow { // The logo/mode row is hidden while a picker is open -- the panel takes // the full body, exactly as `!activeMenu() && ...` does in index.tsx. - match self.active_menu { - Some(menu) => root.child(self.render_device_panel(menu, cx)), + match self.panel { + Some(panel) => root.child(self.render_panel(panel, cx)), None => root.child(self.render_logo_row(cx)).child( + // `flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto pb-1 + // w-full` -- expanded overflows 660px once Recents is in, so + // this column has to scroll. div() + .id("home-scroll") .flex() .flex_col() .flex_1() .min_h_0() + .w_full() + .pb(px(4.)) .gap(px(8.)) + .overflow_y_scroll() .child(self.render_targets(cx)) - .child(self.render_base_controls(cx)), + .child(self.render_base_controls(cx)) + .when(self.expanded, |this| this.child(self.render_recents())), ), } } - /// `TargetMenuPanel`: a Back button above a scrolling device list. - /// - /// The search field the Tauri panel puts next to Back is not here yet -- - /// it needs real text input, which is its own piece of gpui plumbing. - fn render_device_panel(&self, menu: DeviceMenu, cx: &mut Context) -> gpui::Div { + /// Shared panel chrome: a Back button, then either a title or a search + /// field, then a scrolling body. `TargetMenuPanel` and `ModeInfoPanel` in + /// the Tauri app are the same shape, so they share one implementation here. + fn render_panel(&self, panel: Panel, cx: &mut Context) -> gpui::Div { let theme = self.theme; + // Mode info has a static title where the target and device panels put a + // search field. + let header_trailing = match panel { + Panel::ModeInfo => div() + .flex_1() + .min_w_0() + .text_size(px(12.)) + .font_weight(FontWeight::MEDIUM) + .text_color(theme.gray_11) + .child("Recording Modes") + .into_any_element(), + Panel::Device(_) | Panel::Target(_) => { + self.render_search_field(panel, cx).into_any_element() + } + }; + div() .flex() .flex_col() @@ -412,11 +511,12 @@ impl MainWindow { .items_center() .gap(px(12.)) .mt(px(12.)) + // `min-h-[36px]`. .h(px(36.)) .flex_shrink_0() .child( div() - .id("device-panel-back") + .id("panel-back") .flex() .flex_row() .items_center() @@ -440,56 +540,169 @@ impl MainWindow { .child("Back"), ) .hover(|style| style.bg(theme.gray_4)) - .on_click(cx.listener(|this, _, _window, cx| { - this.active_menu = None; - cx.notify(); - })), + .on_click(cx.listener(|this, _, _window, cx| this.close_panel(cx))), ) - .child( - div() - .flex_1() - .min_w_0() - .text_size(px(12.)) - .font_weight(FontWeight::SEMIBOLD) - .text_color(theme.gray_12) - .child(menu.title()), - ), + .child(header_trailing), ) .child( div() - .id("device-panel-list") + .id("panel-body") .flex() .flex_col() .flex_1() .min_h_0() .pt(px(16.)) .px(px(8.)) - .gap(px(4.)) + .gap(px(8.)) .overflow_y_scroll() - .children(self.render_device_list(menu, cx)), + .child(match panel { + Panel::Device(menu) => self.render_device_list(menu, cx).into_any_element(), + Panel::Target(target) => { + self.render_target_grid(target, cx).into_any_element() + } + Panel::ModeInfo => self.render_mode_info(cx).into_any_element(), + }), ) } - fn render_device_list( - &self, - menu: DeviceMenu, - cx: &mut Context, - ) -> Vec { + fn close_panel(&mut self, cx: &mut Context) { + self.panel = None; + self.search.clear(); + cx.notify(); + } + + fn open_panel(&mut self, panel: Panel, window: &mut Window, cx: &mut Context) { + self.panel = Some(panel); + self.search.clear(); + if matches!(panel, Panel::Device(_) | Panel::Target(_)) { + window.focus(&self.search_focus, cx); + } + cx.notify(); + } + + /// A single-line text input, hand-rolled: gpui ships no stock one. + /// + /// Focus is tracked so the panel keeps receiving keys, `key_char` supplies + /// the typed character (which is what handles dead keys and option-layouts, + /// rather than reading `key` directly), and the caret is a plain 1px div -- + /// it does not blink, and there is no selection or cursor movement. That is + /// enough for a filter field and nothing more. + fn render_search_field(&self, panel: Panel, cx: &mut Context) -> gpui::Div { + let theme = self.theme; + let placeholder = match panel { + Panel::Target(TargetType::Display) => "Search displays", + Panel::Target(TargetType::Window) => "Search windows", + Panel::Device(DeviceMenu::Camera) => "Search cameras", + Panel::Device(DeviceMenu::Microphone) => "Search microphones", + _ => "Search", + }; + let empty = self.search.is_empty(); + + div() + .track_focus(&self.search_focus) + .on_key_down( + cx.listener(|this, event: &gpui::KeyDownEvent, _window, cx| { + let keystroke = &event.keystroke; + + match keystroke.key.as_str() { + // First Escape clears the filter, a second one leaves. + "escape" => { + if this.search.is_empty() { + this.close_panel(cx); + } else { + this.search.clear(); + cx.notify(); + } + return; + } + "backspace" => { + this.search.pop(); + cx.notify(); + return; + } + _ => {} + } + + // Command/control chords are shortcuts, not text. + if keystroke.modifiers.platform || keystroke.modifiers.control { + return; + } + + if let Some(text) = keystroke.key_char.as_ref() + && !text.is_empty() + && !text.chars().any(char::is_control) + { + this.search.push_str(text); + cx.notify(); + } + }), + ) + .relative() + .flex() + .flex_row() + .items_center() + .gap(px(4.)) + .flex_1() + .min_w_0() + .h(px(36.)) + .px(px(8.)) + .rounded(px(6.)) + .border_1() + .border_color(theme.gray_5) + .bg(theme.gray_2) + .text_size(px(12.)) + .child( + svg() + .path("icons/search.svg") + .size(px(12.)) + .flex_shrink_0() + .text_color(theme.gray_10), + ) + .child( + div() + .flex_1() + .min_w_0() + .truncate() + .text_color(if empty { theme.gray_10 } else { theme.gray_12 }) + .child(if empty { + placeholder.to_string() + } else { + self.search.clone() + }), + ) + .when(!empty, |this| { + this.child(div().w(px(1.)).h(px(14.)).flex_shrink_0().bg(theme.gray_12)) + }) + } + + /// Case-insensitive substring match, the same test the Tauri panels filter + /// on before they highlight the matched run. + fn matches_search(&self, haystack: &str) -> bool { + if self.search.is_empty() { + return true; + } + haystack + .to_lowercase() + .contains(&self.search.to_lowercase()) + } + + fn render_device_list(&self, menu: DeviceMenu, cx: &mut Context) -> gpui::Div { let theme = self.theme; + let list = div().flex().flex_col().gap(px(4.)).w_full(); if self.enumerating { - return vec![ + return list.child( div() .py(px(24.)) .w_full() .text_size(px(14.)) .text_color(theme.gray_11) - .child("Loading...") - .into_any_element(), - ]; + .child("Loading..."), + ); } - // Index 0 is always the "none" row, matching `DeviceListPanel`. + // The "none" row is always offered and is never filtered out -- it is + // how you turn the device off, not a search result. let mut rows = vec![ self.render_device_list_row( SharedString::from(format!("{}-none", menu.title())), @@ -505,16 +718,22 @@ impl MainWindow { DeviceMenu::Camera => this.camera = None, DeviceMenu::Microphone => this.microphone = None, } - this.active_menu = None; - cx.notify(); + this.close_panel(cx); }), ) .into_any_element(), ]; + let mut matched = 0usize; match menu { DeviceMenu::Camera => { - for camera in &self.devices.cameras { + for camera in self + .devices + .cameras + .iter() + .filter(|camera| self.matches_search(&camera.label)) + { + matched += 1; let selected = self .camera .as_ref() @@ -530,8 +749,7 @@ impl MainWindow { selected, cx.listener(move |this, _, _window, cx| { this.camera = Some(chosen.clone()); - this.active_menu = None; - cx.notify(); + this.close_panel(cx); }), ) .into_any_element(), @@ -539,7 +757,13 @@ impl MainWindow { } } DeviceMenu::Microphone => { - for mic in &self.devices.microphones { + for mic in self + .devices + .microphones + .iter() + .filter(|mic| self.matches_search(&mic.name)) + { + matched += 1; let selected = self .microphone .as_ref() @@ -555,8 +779,7 @@ impl MainWindow { selected, cx.listener(move |this, _, _window, cx| { this.microphone = Some(chosen.clone()); - this.active_menu = None; - cx.notify(); + this.close_panel(cx); }), ) .into_any_element(), @@ -565,29 +788,306 @@ impl MainWindow { } } - if rows.len() == 1 { - rows.push( + if matched == 0 { + rows.push(self.render_empty_state(menu.empty_message(!self.search.is_empty()))); + } + + list.children(rows) + } + + /// `TargetMenuGrid`: two columns of cards. + /// + /// The real cards lead with a live thumbnail of the display or window; that + /// needs the capture pipeline, so these render the same fallback the Tauri + /// card falls back to when no thumbnail has arrived -- the target's icon on + /// `gray-4`. + fn render_target_grid(&self, target: TargetType, cx: &mut Context) -> gpui::Div { + let theme = self.theme; + let grid = div().flex().flex_col().gap(px(8.)).w_full(); + + if self.enumerating { + return grid.child( div() - .py(px(16.)) + .py(px(24.)) .w_full() .text_size(px(14.)) .text_color(theme.gray_11) - .child(match menu { - DeviceMenu::Camera => "No cameras found", - DeviceMenu::Microphone => "No microphones found", - }) - .into_any_element(), + .child("Loading..."), + ); + } + + let mut cards: Vec = Vec::new(); + + match target { + TargetType::Display => { + for display in self + .devices + .displays + .iter() + .filter(|display| self.matches_search(&display.label)) + { + let selected = self + .selected_display + .as_ref() + .is_some_and(|current| current.id == display.id); + let chosen = display.clone(); + + cards.push( + self.render_target_card( + SharedString::from(format!("display-{}", display.id)), + "icons/screen.svg", + display.label.clone(), + None, + display.describe_refresh_rate(), + selected, + cx.listener(move |this, _, _window, cx| { + this.selected_display = Some(chosen.clone()); + this.target = Some(TargetType::Display); + this.close_panel(cx); + }), + ) + .into_any_element(), + ); + } + } + TargetType::Window => { + for window in self + .devices + .windows + .iter() + .filter(|w| self.matches_search(&w.label) || self.matches_search(&w.app)) + { + let selected = self + .selected_window + .as_ref() + .is_some_and(|current| current.id == window.id); + let chosen = window.clone(); + + cards.push( + self.render_target_card( + SharedString::from(format!("window-{}", window.id)), + "icons/window.svg", + window.label.clone(), + Some(window.app.clone()), + window.describe_metadata(), + selected, + cx.listener(move |this, _, _window, cx| { + this.selected_window = Some(chosen.clone()); + this.target = Some(TargetType::Window); + this.close_panel(cx); + }), + ) + .into_any_element(), + ); + } + } + // Area and Camera Only have nothing to pick between. + TargetType::Area | TargetType::CameraOnly => {} + } + + if cards.is_empty() { + return grid.child( + self.render_empty_state(match (target, self.search.is_empty()) { + (TargetType::Display, true) => "No displays found", + (TargetType::Display, false) => "No matching displays", + (_, true) => "No windows found", + (_, false) => "No matching windows", + }), ); } - rows + // Two columns, laid out as rows of two so the cards stretch evenly. + grid.children( + cards + .into_iter() + .collect::>() + .chunks_mut(2) + .map(|pair| { + let mut row = div().flex().flex_row().gap(px(8.)).w_full().items_stretch(); + for card in pair.iter_mut() { + row = row.child(std::mem::replace(card, div().into_any_element())); + } + // Keep a lone trailing card at half width rather than + // letting it span the grid. + if pair.len() == 1 { + row = row.child(div().flex_1()); + } + row + }) + .collect::>(), + ) + } + + /// `TargetCard`: a 76px (`h-19`) thumbnail area over three 11px lines. + #[allow(clippy::too_many_arguments)] + fn render_target_card( + &self, + id: SharedString, + icon: &'static str, + label: String, + subtitle: Option, + metadata: Option, + selected: bool, + on_click: impl Fn(&gpui::ClickEvent, &mut Window, &mut gpui::App) + 'static, + ) -> gpui::Stateful { + let theme = self.theme; + + div() + .id(id) + .flex() + .flex_col() + .flex_1() + .min_w_0() + .overflow_hidden() + .rounded(px(8.)) + .border_1() + .border_color(if selected { + Hsla::from(theme.blue_8) + } else { + gpui::transparent_black() + }) + .bg(theme.gray_3) + .child( + div() + .flex() + .items_center() + .justify_center() + .w_full() + .h(px(76.)) + .bg(theme.gray_4) + .child(svg().path(icon).size(px(24.)).text_color(theme.gray_9)), + ) + .child( + div() + .flex() + .flex_col() + .w_full() + .min_w_0() + .px(px(8.)) + .py(px(6.)) + .text_size(px(11.)) + .child( + div() + .w_full() + .truncate() + .font_weight(FontWeight::MEDIUM) + .text_color(theme.gray_12) + .child(label), + ) + .children(subtitle.map(|subtitle| { + div() + .w_full() + .truncate() + .text_color(theme.gray_11) + .child(subtitle) + })) + .children(metadata.map(|metadata| { + div() + .w_full() + .truncate() + .text_color(theme.gray_10) + .child(metadata) + })), + ) + .hover(|style| style.bg(theme.gray_4)) + .on_click(on_click) + } + + /// `ModeInfoPanel`. Its copy is deliberately not the copy in `MODE_BUTTONS` + /// -- the hover cards and this panel describe the modes differently, so + /// both sets of strings are carried. + fn render_mode_info(&self, cx: &mut Context) -> gpui::Div { + let theme = self.theme; + + div().flex().flex_col().gap(px(8.)).w_full().children( + [Mode::Instant, Mode::Studio, Mode::Screenshot].map(|mode| { + let selected = mode == self.mode; + + div() + .id(SharedString::from(mode.panel_title())) + .flex() + .flex_row() + .items_center() + .gap(px(12.)) + .p(px(12.)) + .w_full() + .rounded(px(12.)) + .border_2() + .border_color(if selected { + Hsla::from(theme.blue_9) + } else if theme.is_dark() { + Hsla::from(theme.gray_5) + } else { + Hsla::from(theme.gray_4) + }) + .bg(if selected { + theme.tile_selected_bg() + } else if theme.is_dark() { + Hsla::from(theme.gray_3) + } else { + Hsla::from(theme.gray_2) + }) + .child( + svg() + .path(mode.icon()) + .size(px(20.)) + .flex_shrink_0() + .text_color(if selected { + theme.blue_11 + } else { + theme.gray_12 + }), + ) + .child( + div() + .flex() + .flex_col() + .gap(px(2.)) + .flex_1() + .min_w_0() + .child( + div() + .text_size(px(14.)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(if selected { + theme.blue_11 + } else { + theme.gray_12 + }) + .child(mode.panel_title()), + ) + .child( + div() + .text_size(px(12.)) + .text_color(theme.gray_11) + .child(mode.panel_description()), + ), + ) + .hover(|style| style.bg(theme.gray_4)) + .on_click(cx.listener(move |this, _, _window, cx| { + this.mode = mode; + this.close_panel(cx); + })) + }), + ) + } + + fn render_empty_state(&self, message: &'static str) -> gpui::AnyElement { + div() + .py(px(24.)) + .w_full() + .text_size(px(14.)) + .text_color(self.theme.gray_11) + .child(message) + .into_any_element() } /// `CameraListItem` / `MicrophoneListItem`: `px-3 py-2.5`, `rounded-lg`, /// 14px label over an optional 11px detail line indented `pl-7`. /// /// Selection is `bg-blue-500` with white text -- note that is the custom - /// `--blue-500`, not `blue-9`; the two are different colours. + /// `--blue-500`, not `blue-9` used by the pills; the two are different + /// colours. fn render_device_list_row( &self, id: SharedString, @@ -672,66 +1172,70 @@ impl MainWindow { .mt(px(16.)) .mb(px(6.)) .flex_shrink_0() - .child(self.render_logo()) + .child( + // `flex items-center space-x-1` around the logo and its badge. + div() + .flex() + .flex_row() + .items_center() + .gap(px(4.)) + .child(self.render_logo()) + .child(self.render_plan_badge()), + ) .child(self.render_mode_pill(cx)) } - /// `*:w-[92px]` on the logo link, against a 103x40 viewBox, so the whole - /// lockup is 92x35.7 and everything below is that scale factor applied to - /// the SVG's own coordinates. + /// The plan badge: `text-[0.6rem] ml-2 rounded-lg border border-gray-5 + /// px-1 py-0.5 bg-gray-3 hover:bg-gray-5`. /// - /// The mark is rebuilt out of divs rather than drawn from `logo-full.svg`, - /// because gpui keeps only an SVG's alpha and tints it with one colour -- - /// the mark's three concentric circles would collapse into a solid block. - /// Only the wordmark, which really is single-colour, goes through `svg()`, - /// which also means it picks up `gray_12` and so swaps between the app's - /// light and dark logo variants for free. - fn render_logo(&self) -> impl IntoElement { - const SCALE: f32 = 92. / 103.; - + /// Only the free variant is drawn. The Pro and Commercial badges are a + /// non-interactive span on `--blue-400`, and which one applies comes from + /// the license query -- there is no auth or license plumbing here yet, so + /// claiming a plan would be worse than showing the free one. + fn render_plan_badge(&self) -> impl IntoElement { let theme = self.theme; - let ring = |diameter: f32, color: u32| { - div() - .absolute() - .size(px(diameter * SCALE)) - .rounded_full() - .bg(rgb(color)) - }; div() + .id("plan-badge") .flex() - .flex_row() .items_center() - // The mark ends at x=40 and the wordmark starts at x=49; re-framing - // the wordmark's viewBox to start at 49 dropped that gap, so it is - // put back here. - .gap(px(9. * SCALE)) - .child( - div() - .relative() - .flex() - .items_center() - .justify_center() - .size(px(40. * SCALE)) - .flex_shrink_0() - .rounded(px(7.75 * SCALE)) - .bg(gpui::white()) - // Without this the white tile is invisible against gray-1 - // in light mode. - .border_1() - .border_color(rgb(0xe7eaf0)) - .child(ring(32., 0x4785ff)) - .child(ring(26., 0xadc9ff)) - .child(ring(20., 0xffffff)), - ) - .child( - svg() - .path("icons/logo-wordmark.svg") - // 54x40 of the original viewBox. - .w(px(54. * SCALE)) - .h(px(40. * SCALE)) - .text_color(theme.gray_12), - ) + .flex_shrink_0() + .ml(px(8.)) + .px(px(4.)) + .py(px(2.)) + .rounded(px(8.)) + .border_1() + .border_color(theme.gray_5) + .bg(theme.gray_3) + .text_size(px(9.6)) + .text_color(theme.gray_12) + .child("Personal") + .hover(|style| style.bg(theme.gray_5)) + // TODO: opens the Upgrade window (950x850) once that window exists. + } + + /// `*:w-[92px]` on the logo link, against a 103x40 viewBox, so the lockup + /// is 92x35.7. + /// + /// This goes through `img()`, not `svg()`. The two take different paths in + /// gpui: `svg()` keeps only the alpha and tints it with one colour, which + /// would flatten the badge, the three blue rings and the wordmark into a + /// single silhouette, whereas `img()` rasterises through resvg and keeps + /// the colour. `img()` also renders at `SMOOTH_SVG_SCALE_FACTOR` (2x), so + /// the 103px-wide source becomes a 206px raster -- more than the 184 device + /// pixels a 92px lockup needs on a 2x display. + /// + /// The app ships two files rather than recolouring one, so this picks the + /// same way it does. + fn render_logo(&self) -> impl IntoElement { + img(if self.theme.is_dark() { + "icons/logo-full-dark.svg" + } else { + "icons/logo-full.svg" + }) + .w(px(92.)) + .h(px(92. * 40. / 103.)) + .flex_shrink_0() } /// `Mode.tsx`: `p-1.5 gap-2 rounded-full border border-gray-5 bg-gray-3`, @@ -766,11 +1270,7 @@ impl MainWindow { .child( svg() .path(mode.icon()) - .size(px(if matches!(mode, Mode::Instant) { - 16. - } else { - 14.4 - })) + .size(px(mode.icon_size())) .text_color(theme.gray_12), ) .hover(|style| style.bg(theme.gray_7)) @@ -781,6 +1281,7 @@ impl MainWindow { }; div() + .relative() .flex() .flex_row() .items_center() @@ -793,6 +1294,31 @@ impl MainWindow { .child(button(Mode::Instant, "mode-instant")) .child(button(Mode::Studio, "mode-studio")) .child(button(Mode::Screenshot, "mode-screenshot")) + // `absolute -left-1.5 -top-2 p-1 rounded-full bg-gray-5`, hanging + // off the pill's top-left corner. + .child( + div() + .id("mode-info") + .absolute() + .left(px(-6.)) + .top(px(-8.)) + .flex() + .items_center() + .justify_center() + .p(px(4.)) + .rounded_full() + .bg(theme.gray_5) + .child( + svg() + .path("icons/info.svg") + .size(px(10.)) + .text_color(theme.gray_12), + ) + .hover(|style| style.opacity(0.5)) + .on_click(cx.listener(|this, _, window, cx| { + this.open_panel(Panel::ModeInfo, window, cx); + })), + ) } fn render_targets(&self, cx: &mut Context) -> impl IntoElement { @@ -872,7 +1398,10 @@ impl MainWindow { .size(px(16.)) .text_color(theme.gray_11), ) - .hover(|style| style.bg(theme.gray_6)), + .hover(|style| style.bg(theme.gray_6)) + .on_click(cx.listener(move |this, _, window, cx| { + this.open_panel(Panel::Target(target), window, cx); + })), ) } @@ -1022,7 +1551,7 @@ impl MainWindow { }, ) .on_click(cx.listener(|this, _, _window, cx| { - this.active_menu = Some(DeviceMenu::Camera); + this.panel = Some(Panel::Device(DeviceMenu::Camera)); cx.notify(); })), ), @@ -1044,7 +1573,7 @@ impl MainWindow { }, ) .on_click(cx.listener(|this, _, _window, cx| { - this.active_menu = Some(DeviceMenu::Microphone); + this.panel = Some(Panel::Device(DeviceMenu::Microphone)); cx.notify(); })), ), @@ -1076,6 +1605,59 @@ impl MainWindow { ) } + /// `Recents.tsx`, expanded only. + /// + /// Thumbnails need the recordings library, so only the header and the empty + /// state are here -- which is what the real section shows on a machine with + /// no captures yet anyway. + fn render_recents(&self) -> impl IntoElement { + let theme = self.theme; + + div() + // `

    ` around the section in index.tsx. + .pt(px(8.)) + .w_full() + .flex_shrink_0() + .child( + // `mb-2 flex items-center px-0.5`. + div().flex().items_center().mb(px(8.)).px(px(2.)).child( + div() + .text_size(px(12.)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(theme.gray_12) + .child("Recents"), + ), + ) + .child( + // `h-28 ... rounded-xl border border-dashed border-gray-5 bg-gray-2`. + div() + .flex() + .flex_col() + .items_center() + .justify_center() + .gap(px(8.)) + .w_full() + .h(px(112.)) + .rounded(px(12.)) + .border_dashed() + .border_1() + .border_color(theme.gray_5) + .bg(theme.gray_2) + .child( + svg() + .path("icons/history.svg") + .size(px(20.)) + .text_color(theme.gray_9), + ) + .child( + div() + .text_size(px(12.)) + .text_color(theme.gray_10) + .child("Your latest captures will appear here."), + ), + ) + } + /// `ExpandedControlLabel`: `mb-1 px-1`, `text-xs font-semibold text-gray-12`. /// Only rendered when expanded. fn labelled(&self, title: &'static str, row: impl IntoElement) -> impl IntoElement { From 36bfe21ccb729e9a74e76040cfb5e3323bbc626b Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:57:43 +0100 Subject: [PATCH 007/146] docs(desktop-gpui): README with deviations and a parity roadmap How to run it, what is real, and a table of every deviation from the Tauri app with the reason. Several of those reasons are non-obvious enough to be worth writing down -- notably that `WindowKind::Floating` is not the way to get always-on-top, because it allocates an NSPanel and a panel hides itself when the application deactivates. The roadmap lists all fourteen app windows with their real sizes from windows.rs, so the size of what is left is legible: the main window is one of fourteen, and the editor alone is larger than everything done so far. Also records the gpui trap that cost the most time today -- touching the window from inside `open_window`'s builder fails silently, twice over -- and the per-window `screencapture -l` recipe, since a plain full-screen capture returns only the desktop without Screen Recording permission. --- apps/desktop-gpui/README.md | 135 ++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 apps/desktop-gpui/README.md diff --git a/apps/desktop-gpui/README.md b/apps/desktop-gpui/README.md new file mode 100644 index 0000000000..d230798b7e --- /dev/null +++ b/apps/desktop-gpui/README.md @@ -0,0 +1,135 @@ +# cap-desktop-gpui + +Cap's desktop app, rewritten in [gpui](https://www.gpui.rs/). No Tauri, no +webview — the whole UI is drawn by gpui and every pixel is Rust. + +This is milestone 1: **the main recording window**, compact and expanded, with +real device enumeration. It is a parallel implementation, not a replacement. +`apps/desktop` is untouched and remains the shipping app. + +| | | +|---|---| +| ![compact](docs/main-window-compact.png) | ![expanded](docs/main-window-expanded.png) | +| 330×395 | 600×660 | + +## Running it + +```sh +cd apps/desktop-gpui +cargo run +``` + +`RUST_LOG=cap_gpui=debug cargo run` for logs — note the filter is `cap_gpui` +(the binary), not `cap_desktop_gpui` (the package). + +The first build takes a few minutes and about 8 GB of `target/`: gpui pulls its +own revisions of the wgpu and font stacks. Incremental builds after that are +under a second. + +### Why it is a separate workspace + +`Cargo.toml` declares its own `[workspace]`. The root workspace carries +`[patch.crates-io]` entries — a vendored `wgpu-hal`, a forked `tao` — that exist +only for the Tauri app and that gpui's tree does not want. Nesting a second +workspace here leaves the root `Cargo.lock`, the `cargo hakari` workspace-hack +and CI completely untouched. + +`rust-toolchain.toml` pins `stable` rather than the root's 1.88.0, because +gpui's dependencies need 1.89+ (`smol_str` 0.3.6, `cosmic-text` 0.19). The +nearest toolchain file wins, so the Tauri app is unaffected. + +## What is implemented + +Everything below is real, not mocked. + +- **Window shell.** Undecorated, transparent, 16px rounded, fixed size. Custom + header with hand-drawn traffic lights and a scoped drag region. +- **Compact and expanded layouts.** 330×395 and 600×660. Expanding adds section + headings, turns the capture tiles horizontal with descriptions, widens the + control rhythm from 8px to 10px, and reveals Recents. +- **Device enumeration.** Cameras via `cap-camera`, microphones via the Cap + `cpal` fork, displays and windows via `scap-targets` — the same crates the + recorder uses, so the identities are the ones `cap-recording` expects. Camera + rows show the device's best advertised format, microphone rows the config the + device would actually open with. +- **Pickers.** Camera, microphone, display and window, each a full-body panel + with a live filter field. Displays and windows render as a two-column card + grid with real refresh rates and bounds. +- **Mode selector**, with the info panel behind its dot. +- **Light and dark**, following the system appearance, from the app's real + resolved Radix values. + +### Layout fidelity + +Metrics are transcribed from `apps/desktop`, not eyeballed, and the Tailwind +class each one came from is quoted in a comment next to it — `pl-3` and +`gap-2.5` are much easier to check against the original than `12.` and `10.`. + +The colour tokens are the resolved Radix values **with the dark-mode overrides +from `apps/desktop/src/styles/theme.css` applied**. Six of the dark grays and +`gray-11` are not stock Radix, so regenerating the palette from a Radix crate +would quietly change the app's colours. + +## Deviations from the Tauri app + +Things that are deliberately different, and why. + +| | | +|---|---| +| **Traffic lights are hand-drawn** | The Tauri main window returns `None` from `traffic_lights_position`, which routes it to `decorations(false)`; the lights are HTML there too. `titlebar: None` is the gpui equivalent. Minimize is not drawn, and zoom toggles expand/collapse. | +| **No vibrancy** | The real macOS shell is a translucent material, not `bg-gray-1`. This is the single largest visual gap. | +| **Not always-on-top** | `WindowKind::Floating` is *not* the fix: it floats, but it allocates an `NSPanel`, and a panel hides itself when the app deactivates — the window would vanish the moment you click another app. This needs the AppKit calls `windows.rs` makes, on a normal window. Same for `visible_on_all_workspaces` and the NSPanel level-100 treatment. | +| **Resize snaps** | Expand/collapse changes size in one step. The Tauri app animates over 180ms with an ease-out cubic and re-clamps into the work area. | +| **Panels instead of windows** | Mode info is the 580×340 ModeSelect *window* in the Tauri app; here it is a body panel, because there is only one window so far. The device and target pickers are body panels in both. | +| **No target thumbnails** | Display and window cards render the icon fallback the real card falls back to before its thumbnail arrives. Live previews need the capture pipeline. | +| **Search is minimal** | gpui ships no text input. Ours tracks focus, takes `key_char` so dead keys and option-layouts work, and draws a static 1px caret. No selection, no cursor movement, no blink. Escape clears, then closes. | +| **Plan badge is always "Personal"** | Which of Pro/Commercial applies comes from the license query. There is no auth or license plumbing yet, and claiming a plan would be worse than showing none. | +| **Recents is header + empty state** | Thumbnails need the recordings library. | +| **Window filter is duplicated** | The level-0 listability rule is copied from `cap_recording::sources::screen_capture` rather than imported — that crate drags in ffmpeg and the whole encode stack, which this app has no other reason to build. | + +### One gpui trap worth knowing + +**Do not touch the window from inside `open_window`'s builder closure.** +`MainWindow::new` runs before the platform window is finished. A `resize` there +produces a window whose viewport disagrees with its scale factor — every `px()` +comes out at exactly twice its size — and a task spawned there updates the model +without ever scheduling a frame. Both failures are silent. Set the initial size +through the bounds passed to `open_window`, and start async work from `main` +once the window handle exists. + +## Parity roadmap + +Sizes are the Tauri app's, from `apps/desktop/src-tauri/src/windows.rs`. + +| Window | Size | Status | +|---|---|---| +| Main | 330×395 / 600×660 | **Done** — layout, devices, pickers, modes | +| Camera preview | 460×460 | Not started — needs the camera feed | +| Recording controls | 320×150 | Not started — needs a recording session | +| Target select overlay | per display | Not started — one transparent window per display | +| Window capture occluder | per display | Not started | +| Capture area | per display | Not started | +| Recordings overlay | per display | Not started | +| Mode select | 580×340 | Partial — exists as a panel, not a window | +| Settings | 782×775 (min 780×560) | Not started | +| Upgrade | 950×850 | Not started | +| Onboarding | dynamic, 860–1080 wide | Not started | +| Teleprompter | 560×320 | Not started | +| Editor | 1275×800 | Not started — by far the largest | +| Screenshot editor | 1240×800 (min 800×600) | Not started | + +Nothing here records yet. Milestone 1 is the window; wiring the buttons to +`cap-recording` is milestone 2. + +## Verifying changes + +`screencapture` cannot see windows without Screen Recording permission, but +per-window capture by id works. Get the window number from +`CGWindowListCopyWindowInfo` and then: + +```sh +screencapture -x -o -l shot.png +``` + +Measure against it rather than trusting the eye — the 2×-scale bug above was +invisible until the compact and expanded headers were compared pixel by pixel. From 582f6a21f73c7b565fae0a79091895c45651d8d5 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:00:02 +0100 Subject: [PATCH 008/146] feat(desktop-gpui): animate the expand/collapse resize 180ms with an ease-out cubic, matching `resizeMainWindow`. The task is held on the view so that assigning a new one drops the old: a second toggle mid-animation cancels the first, rather than both driving `resize` and leaving the window at some interpolated size. Verified by sampling the window's bounds while it runs -- it interpolates and lands exactly on 600x660. Smoothness could not be verified here: while the app is inactive its foreground run loop is barely pumped, so the 8ms timer between steps takes closer to a second and the resize arrives as two or three jumps. That is the same starvation that stops an inactive window repainting at all, not something in the animation. Does not re-clamp into the monitor work area afterwards, which the Tauri version does; noted in the README. --- apps/desktop-gpui/README.md | 2 +- apps/desktop-gpui/src/main_window.rs | 65 ++++++++++++++++++++++------ 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/apps/desktop-gpui/README.md b/apps/desktop-gpui/README.md index d230798b7e..55762967fb 100644 --- a/apps/desktop-gpui/README.md +++ b/apps/desktop-gpui/README.md @@ -79,7 +79,7 @@ Things that are deliberately different, and why. | **Traffic lights are hand-drawn** | The Tauri main window returns `None` from `traffic_lights_position`, which routes it to `decorations(false)`; the lights are HTML there too. `titlebar: None` is the gpui equivalent. Minimize is not drawn, and zoom toggles expand/collapse. | | **No vibrancy** | The real macOS shell is a translucent material, not `bg-gray-1`. This is the single largest visual gap. | | **Not always-on-top** | `WindowKind::Floating` is *not* the fix: it floats, but it allocates an `NSPanel`, and a panel hides itself when the app deactivates — the window would vanish the moment you click another app. This needs the AppKit calls `windows.rs` makes, on a normal window. Same for `visible_on_all_workspaces` and the NSPanel level-100 treatment. | -| **Resize snaps** | Expand/collapse changes size in one step. The Tauri app animates over 180ms with an ease-out cubic and re-clamps into the work area. | +| **Resize does not re-clamp** | Expand/collapse animates over 180ms with an ease-out cubic, as the Tauri app does, but does not re-clamp the window into the monitor work area afterwards — expanding near a screen edge can push the window off it. | | **Panels instead of windows** | Mode info is the 580×340 ModeSelect *window* in the Tauri app; here it is a body panel, because there is only one window so far. The device and target pickers are body panels in both. | | **No target thumbnails** | Display and window cards render the icon fallback the real card falls back to before its thumbnail arrives. Live previews need the capture pipeline. | | **Search is minimal** | gpui ships no text input. Ours tracks focus, takes `key_char` so dead keys and option-layouts work, and draws a static 1px caret. No selection, no cursor movement, no blink. Escape clears, then closes. | diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index a71b243552..9041e03931 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -22,6 +22,9 @@ use crate::{ const EXPANDED_WIDTH: f32 = 600.; const EXPANDED_HEIGHT: f32 = 660.; +/// `duration: 180` in `resizeMainWindow`. +const RESIZE_DURATION_SECS: f32 = 0.18; + /// `h-9` on `.cap-window-header`. const HEADER_HEIGHT: f32 = 36.; /// `h-[42px]` in deviceRowStyles.ts. @@ -189,6 +192,9 @@ pub struct MainWindow { selected_display: Option, selected_window: Option, panel: Option, + /// Holds the in-flight expand/collapse animation. Dropping it cancels, + /// which is how a second toggle mid-animation takes over cleanly. + resize_task: Option>, /// Live filter text for the device and target panels. search: String, search_focus: gpui::FocusHandle, @@ -218,6 +224,7 @@ impl MainWindow { selected_display: None, selected_window: None, panel: None, + resize_task: None, search: String::new(), search_focus: cx.focus_handle(), enumerating: true, @@ -258,25 +265,57 @@ impl MainWindow { } fn toggle_expanded(&mut self, window: &mut Window, cx: &mut Context) { + let from = self.window_size(); self.expanded = !self.expanded; + let to = self.window_size(); tracing::info!(expanded = self.expanded, "toggling main window size"); - self.apply_window_size(window); + + // Matches `resizeMainWindow`: 180ms, ease-out cubic. + // + // Assigning over the previous task drops it, which cancels a toggle + // that is still in flight -- otherwise two animations would fight over + // `resize` and the window could settle at an interpolated size. + self.resize_task = Some(cx.spawn_in(window, async move |this, cx| { + let start = std::time::Instant::now(); + + loop { + let elapsed = start.elapsed().as_secs_f32(); + let t = (elapsed / RESIZE_DURATION_SECS).clamp(0., 1.); + // ease-out cubic + let eased = 1. - (1. - t).powi(3); + + let size = gpui::size( + px(from.0 + (to.0 - from.0) * eased), + px(from.1 + (to.1 - from.1) * eased), + ); + + if this + .update_in(cx, |_this, window, _cx| window.resize(size)) + .is_err() + { + return; + } + + if t >= 1. { + return; + } + + cx.background_executor() + .timer(std::time::Duration::from_millis(8)) + .await; + } + })); + cx.notify(); } - /// Resize the window to match `expanded`. - /// - /// The Tauri version animates this over 180ms with an ease-out cubic and - /// re-clamps the window into the monitor work area with 12px of padding. - /// This is the un-animated version of the same size change; the animation - /// is deliberately left for a later pass rather than faked with a timer - /// that would fight gpui's own frame pacing. - fn apply_window_size(&self, window: &mut Window) { - window.resize(if self.expanded { - gpui::size(px(EXPANDED_WIDTH), px(EXPANDED_HEIGHT)) + /// The window size the current state should be drawn at. + fn window_size(&self) -> (f32, f32) { + if self.expanded { + (EXPANDED_WIDTH, EXPANDED_HEIGHT) } else { - gpui::size(px(MAIN_WINDOW_WIDTH), px(MAIN_WINDOW_HEIGHT)) - }); + (MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT) + } } fn sync_appearance(&mut self, window: &Window) { From 346fd4c215031b2860fed661ae68e8f77d96f4ad Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:00:37 +0100 Subject: [PATCH 009/146] chore(desktop-gpui): drop two icons that went dead in the refactor `logo-wordmark.svg` was cut out of the logo for the div-rebuilt mark, which `img()` replaced; `support.svg` was the chat bubble standing in for the help button before lucide `circle-help`. Both were still embedded. The existing test only checked that every icon referenced is embedded. It now checks the reverse as well, since a dead asset is invisible -- it costs binary size and nothing else notices. --- .../assets/icons/logo-wordmark.svg | 3 --- apps/desktop-gpui/assets/icons/support.svg | 3 --- apps/desktop-gpui/src/assets.rs | 20 +++++++++++++++++-- 3 files changed, 18 insertions(+), 8 deletions(-) delete mode 100644 apps/desktop-gpui/assets/icons/logo-wordmark.svg delete mode 100644 apps/desktop-gpui/assets/icons/support.svg diff --git a/apps/desktop-gpui/assets/icons/logo-wordmark.svg b/apps/desktop-gpui/assets/icons/logo-wordmark.svg deleted file mode 100644 index 2bc02103f3..0000000000 --- a/apps/desktop-gpui/assets/icons/logo-wordmark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/apps/desktop-gpui/assets/icons/support.svg b/apps/desktop-gpui/assets/icons/support.svg deleted file mode 100644 index a4c7b262aa..0000000000 --- a/apps/desktop-gpui/assets/icons/support.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/desktop-gpui/src/assets.rs b/apps/desktop-gpui/src/assets.rs index 4fe9b2578b..9dd2a957f3 100644 --- a/apps/desktop-gpui/src/assets.rs +++ b/apps/desktop-gpui/src/assets.rs @@ -47,7 +47,6 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": "instant.svg", "logo-full-dark.svg", "logo-full.svg", - "logo-wordmark.svg", "microphone.svg", "minimize.svg", "move-left.svg", @@ -57,7 +56,6 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": "screenshot.svg", "search.svg", "settings.svg", - "support.svg", "window.svg", ); @@ -137,6 +135,24 @@ mod tests { ); } + /// And the other direction: an icon that stopped being drawn during a + /// refactor is dead weight in the binary, and nothing else would notice. + #[test] + fn every_embedded_icon_is_referenced() { + let source = include_str!("main_window.rs"); + + let unused: Vec<&str> = ICONS + .iter() + .map(|(path, _)| *path) + .filter(|path| !source.contains(path)) + .collect(); + + assert!( + unused.is_empty(), + "icons embedded but never drawn: {unused:?}" + ); + } + #[test] fn fonts_and_icons_resolve() { assert!(Assets.load("fonts/Geist.ttf").unwrap().is_some()); From 6f8d39b077fbc1307462341ad0a537ae830e9c62 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:29:43 +0100 Subject: [PATCH 010/146] feat(desktop-gpui): recording engine integration cap-recording as a path dep (studio + instant actors, kameo feeds), the root's vendored wgpu-hal patch mirrored, ort pinned to the root lockfile's rc.12 (rc.13 renames CoreMLExecutionProvider out from under camera-effects). Projects land in the same recordings library as the Tauri app and are finalized the way the CLI finalizes: remux for studio, output.mp4 + meta pair for instant. --- apps/desktop-gpui/Cargo.lock | 2022 ++++++++++++++++++++++++++-- apps/desktop-gpui/Cargo.toml | 40 + apps/desktop-gpui/src/recording.rs | 342 +++++ 3 files changed, 2299 insertions(+), 105 deletions(-) create mode 100644 apps/desktop-gpui/src/recording.rs diff --git a/apps/desktop-gpui/Cargo.lock b/apps/desktop-gpui/Cargo.lock index 7536f3a15d..89c1c9a69d 100644 --- a/apps/desktop-gpui/Cargo.lock +++ b/apps/desktop-gpui/Cargo.lock @@ -227,6 +227,16 @@ 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 0.2.2", +] + [[package]] name = "anstream" version = "1.0.0" @@ -277,6 +287,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "any_spawner" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41058deaa38c9d9dd933d6d238d825227cffa668e2839b52879f6619c63eee3b" +dependencies = [ + "futures", + "thiserror 2.0.20", +] + [[package]] name = "anyhow" version = "1.0.104" @@ -342,7 +362,24 @@ version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading", + "libloading 0.8.9", +] + +[[package]] +name = "ashpd" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f3f79755c74fd155000314eb349864caa787c6592eace6c6882dad873d9c39" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.5", + "serde", + "serde_repr", + "tokio", + "url", + "zbus", ] [[package]] @@ -723,6 +760,44 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bezier_easing" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa4734ba28c4eb6dcd44213f969e007bd2a17f966151656d1c7676f8526bedd" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + [[package]] name = "bindgen" version = "0.65.1" @@ -746,6 +821,24 @@ dependencies = [ "which 4.4.2", ] +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags 2.13.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex 1.3.0", + "syn 2.0.119", +] + [[package]] name = "bindgen" version = "0.71.1" @@ -772,6 +865,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ + "annotate-snippets", "bitflags 2.13.1", "cexpr", "clang-sys", @@ -784,15 +878,30 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", +] + [[package]] name = "bit-set" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" dependencies = [ - "bit-vec", + "bit-vec 0.9.1", ] +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bit-vec" version = "0.9.1" @@ -924,6 +1033,19 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "build-time" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1219c19fc29b7bfd74b7968b420aff5bc951cf517800176e795d6b2300dd382" +dependencies = [ + "chrono", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "built" version = "0.8.1" @@ -1017,6 +1139,21 @@ dependencies = [ "serde_core", ] +[[package]] +name = "cap-audio" +version = "0.1.0" +dependencies = [ + "cidre", + "cpal", + "ffmpeg-next", + "serde", + "serde_json", + "tokio", + "tracing", + "windows 0.60.0", + "workspace-hack", +] + [[package]] name = "cap-camera" version = "0.1.0" @@ -1025,6 +1162,8 @@ dependencies = [ "cap-camera-windows", "cidre", "objc2-av-foundation", + "serde", + "specta", "thiserror 1.0.69", "tracing", "v4l", @@ -1053,6 +1192,34 @@ dependencies = [ "workspace-hack", ] +[[package]] +name = "cap-camera-effects" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytemuck", + "ndarray 0.16.1", + "ort", + "tracing", + "wgpu 25.0.2", +] + +[[package]] +name = "cap-camera-ffmpeg" +version = "0.1.0" +dependencies = [ + "cap-camera", + "cap-camera-avfoundation", + "cap-camera-windows", + "cidre", + "ffmpeg-next", + "thiserror 1.0.69", + "tracing", + "windows 0.60.0", + "windows-core 0.60.1", + "workspace-hack", +] + [[package]] name = "cap-camera-mediafoundation" version = "0.1.0" @@ -1081,25 +1248,372 @@ dependencies = [ ] [[package]] -name = "cap-desktop-gpui" +name = "cap-cursor-capture" +version = "0.1.0" +dependencies = [ + "core-graphics 0.24.0", + "device_query", + "scap-targets", + "workspace-hack", +] + +[[package]] +name = "cap-cursor-info" +version = "0.0.0" +dependencies = [ + "serde", + "specta", + "strum 0.27.2", + "windows 0.60.0", + "workspace-hack", +] + +[[package]] +name = "cap-d3d-adapter" +version = "0.1.0" +dependencies = [ + "tracing", + "windows 0.60.0", + "workspace-hack", +] + +[[package]] +name = "cap-desktop-gpui" +version = "0.1.0" +dependencies = [ + "anyhow", + "cap-camera", + "cap-project", + "cap-recording", + "cap-utils", + "chrono", + "cidre", + "cpal", + "flume 0.11.1", + "gpui", + "gpui_platform", + "gpui_tokio", + "kameo", + "scap-targets", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "cap-enc-avfoundation" +version = "0.1.0" +dependencies = [ + "cap-media-info", + "cidre", + "ffmpeg-next", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", + "workspace-hack", +] + +[[package]] +name = "cap-enc-ffmpeg" +version = "0.1.0" +dependencies = [ + "cap-frame-converter", + "cap-media-info", + "ffmpeg-next", + "serde", + "serde_json", + "sysinfo 0.32.1", + "thiserror 1.0.69", + "tracing", + "workspace-hack", +] + +[[package]] +name = "cap-enc-mediafoundation" +version = "0.1.0" +dependencies = [ + "cap-d3d-adapter", + "cap-media-info", + "futures", + "scap-direct3d", + "thiserror 1.0.69", + "tracing", + "windows 0.60.0", + "windows-core 0.60.1", + "windows-numerics 0.2.0", + "workspace-hack", +] + +[[package]] +name = "cap-fail" +version = "0.1.0" +dependencies = [ + "inventory", + "workspace-hack", +] + +[[package]] +name = "cap-flags" +version = "0.1.0" +dependencies = [ + "serde", + "specta", + "workspace-hack", +] + +[[package]] +name = "cap-frame-converter" +version = "0.1.0" +dependencies = [ + "cap-media-info", + "crossbeam-channel", + "ffmpeg-next", + "flume 0.11.1", + "parking_lot", + "thiserror 1.0.69", + "thread_local", + "tracing", + "windows 0.60.0", + "workspace-hack", +] + +[[package]] +name = "cap-media" +version = "0.1.0" +dependencies = [ + "cap-media-info", + "ffmpeg-next", + "thiserror 1.0.69", + "workspace-hack", +] + +[[package]] +name = "cap-media-info" +version = "0.1.0" +dependencies = [ + "cpal", + "ffmpeg-next", + "thiserror 1.0.69", + "workspace-hack", +] + +[[package]] +name = "cap-mediafoundation-ffmpeg" +version = "0.1.0" +dependencies = [ + "cap-media-info", + "cap-mediafoundation-utils", + "ffmpeg-next", + "flume 0.11.1", + "thiserror 1.0.69", + "tracing", + "windows 0.60.0", + "windows-numerics 0.2.0", + "workspace-hack", +] + +[[package]] +name = "cap-mediafoundation-utils" +version = "0.1.0" +dependencies = [ + "windows 0.60.0", + "windows-core 0.60.1", + "workspace-hack", +] + +[[package]] +name = "cap-muxer-protocol" +version = "0.1.0" +dependencies = [ + "byteorder", + "crc32fast", + "thiserror 1.0.69", +] + +[[package]] +name = "cap-project" +version = "0.1.0" +dependencies = [ + "bincode", + "cap-cursor-info", + "either", + "log", + "relative-path", + "serde", + "serde_json", + "specta", + "tempfile", + "tracing", + "uuid", + "workspace-hack", +] + +[[package]] +name = "cap-recording" +version = "0.1.0" +dependencies = [ + "anyhow", + "ashpd 0.11.1", + "cap-audio", + "cap-camera", + "cap-camera-ffmpeg", + "cap-camera-windows", + "cap-cursor-capture", + "cap-cursor-info", + "cap-d3d-adapter", + "cap-enc-avfoundation", + "cap-enc-ffmpeg", + "cap-enc-mediafoundation", + "cap-fail", + "cap-flags", + "cap-frame-converter", + "cap-media", + "cap-media-info", + "cap-mediafoundation-ffmpeg", + "cap-mediafoundation-utils", + "cap-muxer-protocol", + "cap-project", + "cap-rendering", + "cap-timestamp", + "cap-utils", + "chrono", + "cidre", + "cocoa 0.26.0", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "coreaudio-rs", + "cpal", + "device_query", + "either", + "ffmpeg-next", + "flume 0.11.1", + "foreign-types-shared 0.3.1", + "futures", + "hex", + "image 0.25.10", + "indexmap 2.14.0", + "inquire", + "kameo", + "libc", + "objc", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "parking_lot", + "pipewire", + "relative-path", + "replace_with", + "retry", + "ringbuf", + "scap-cpal", + "scap-direct3d", + "scap-ffmpeg", + "scap-screencapturekit", + "scap-targets", + "screencapturekit 0.3.6", + "serde", + "serde_json", + "sha2", + "specta", + "sysinfo 0.35.2", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "tracing", + "tracing-subscriber", + "windows 0.60.0", + "workspace-hack", + "x11rb", +] + +[[package]] +name = "cap-rendering" +version = "0.1.0" +dependencies = [ + "anyhow", + "bezier_easing", + "build-time", + "bytemuck", + "cap-camera-effects", + "cap-cursor-info", + "cap-flags", + "cap-project", + "cap-video-decode", + "cidre", + "clap", + "dirs 5.0.1", + "ffmpeg-hw-device", + "ffmpeg-next", + "foreign-types 0.5.0", + "futures", + "futures-intrusive", + "glyphon", + "image 0.25.10", + "log", + "metal 0.31.0", + "objc2 0.6.4", + "rayon", + "reactive_graph", + "resvg 0.45.1", + "serde", + "serde_json", + "specta", + "thiserror 1.0.69", + "tiny-skia", + "tokio", + "tracing", + "wgpu 25.0.2", + "wgpu-core 25.0.2", + "wgpu-hal 25.0.2", + "windows 0.58.0", + "windows 0.60.0", + "workspace-hack", +] + +[[package]] +name = "cap-timestamp" +version = "0.1.0" +dependencies = [ + "cidre", + "cpal", + "windows 0.60.0", + "workspace-hack", +] + +[[package]] +name = "cap-utils" version = "0.1.0" dependencies = [ - "cap-camera", - "cpal", - "gpui", - "gpui_platform", - "gpui_tokio", - "scap-targets", + "aho-corasick", + "directories", + "flume 0.11.1", + "futures", + "libc", + "nix", + "serde", + "serde_json", + "tokio", "tracing", - "tracing-subscriber", + "uuid", + "windows 0.58.0", + "windows-sys 0.52.0", + "workspace-hack", ] [[package]] -name = "cap-mediafoundation-utils" +name = "cap-video-decode" version = "0.1.0" dependencies = [ + "cap-d3d-adapter", + "cidre", + "ffmpeg-hw-device", + "ffmpeg-next", + "num_cpus", + "tempfile", + "tokio", + "tracing", "windows 0.60.0", - "windows-core 0.60.1", "workspace-hack", ] @@ -1191,6 +1705,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "cfg-expr" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -1271,7 +1795,7 @@ checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.8.9", ] [[package]] @@ -1374,6 +1898,17 @@ dependencies = [ "objc", ] +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width 0.2.2", +] + [[package]] name = "codespan-reporting" version = "0.13.1" @@ -1500,6 +2035,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 = "cookie_store" version = "0.21.1" @@ -1518,6 +2059,15 @@ dependencies = [ "url", ] +[[package]] +name = "core-audio-types-rs" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebfc66b5ef3f159b3c4a6097e7c2a2e2cc71ec77fff7913e9895fae45f4c349" +dependencies = [ + "core-foundation 0.10.1", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1570,6 +2120,19 @@ dependencies = [ "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", + "foreign-types 0.5.0", + "libc", +] + [[package]] name = "core-graphics-helmer-fork" version = "0.24.0" @@ -1618,6 +2181,19 @@ dependencies = [ "libc", ] +[[package]] +name = "core-media-rs" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6592bdc3e2b3479140e8c64d419b62d613526c8b61136ca990cd0bae4377db4f" +dependencies = [ + "core-audio-types-rs", + "core-foundation 0.10.1", + "core-utils-rs", + "core-video-rs", + "thiserror 2.0.20", +] + [[package]] name = "core-text" version = "21.0.0" @@ -1630,6 +2206,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-utils-rs" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791d9e8eb99cff8db5e2b6a5bb420b893476b7a064bb6889b2a691c1bb6d745c" +dependencies = [ + "core-foundation 0.10.1", + "four-char-code", +] + [[package]] name = "core-video" version = "0.5.2" @@ -1641,7 +2227,20 @@ dependencies = [ "core-graphics2", "io-surface", "libc", - "metal", + "metal 0.33.0", +] + +[[package]] +name = "core-video-rs" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f1614e163a5d6fb972305fa14fc5c2989143da0426c213b74785b31e1fadb8" +dependencies = [ + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "core-utils-rs", + "io-surface", + "thiserror 2.0.20", ] [[package]] @@ -1673,6 +2272,29 @@ dependencies = [ "bindgen 0.72.1", ] +[[package]] +name = "cosmic-text" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da46a9d5a8905cc538a4a5bceb6a4510de7a51049c5588c0114efce102bcbbe8" +dependencies = [ + "bitflags 2.13.1", + "fontdb 0.16.2", + "log", + "rangemap", + "rustc-hash 1.1.0", + "rustybuzz 0.14.1", + "self_cell", + "smol_str 0.2.2", + "swash", + "sys-locale", + "ttf-parser 0.21.1", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + [[package]] name = "cosmic-text" version = "0.19.0" @@ -1680,7 +2302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73" dependencies = [ "bitflags 2.13.1", - "fontdb", + "fontdb 0.23.0", "harfrust", "linebender_resource_handle", "log", @@ -1688,7 +2310,7 @@ dependencies = [ "rustc-hash 2.1.3", "self_cell", "skrifa 0.40.0", - "smol_str", + "smol_str 0.3.6", "swash", "sys-locale", "unicode-bidi", @@ -1746,6 +2368,15 @@ dependencies = [ "cfg-if", ] +[[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-deque" version = "0.8.7" @@ -1957,6 +2588,16 @@ dependencies = [ "thiserror 2.0.20", ] +[[package]] +name = "der" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" +dependencies = [ + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.8" @@ -2013,6 +2654,20 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "device_query" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7331225604b9b097b41872d550134933d4be83465d70a2c4a132b929f99aaca" +dependencies = [ + "macos-accessibility-client", + "pkg-config", + "readkey", + "readmouse", + "windows 0.48.0", + "x11", +] + [[package]] name = "digest" version = "0.10.7" @@ -2024,13 +2679,43 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", ] [[package]] @@ -2041,8 +2726,8 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", - "windows-sys 0.61.2", + "redox_users 0.5.2", + "windows-sys 0.59.0", ] [[package]] @@ -2080,7 +2765,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ - "libloading", + "libloading 0.8.9", ] [[package]] @@ -2098,6 +2783,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + [[package]] name = "dtoa" version = "1.0.11" @@ -2263,7 +2954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2346,6 +3037,39 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ffmpeg-hw-device" +version = "0.1.0" +dependencies = [ + "ffmpeg-next", + "tracing", + "workspace-hack", +] + +[[package]] +name = "ffmpeg-next" +version = "7.1.0" +source = "git+https://github.com/CapSoftware/rust-ffmpeg?rev=49db1fede112#49db1fede1123029670728bd6e3fcbb07b0f5716" +dependencies = [ + "bitflags 2.13.1", + "ffmpeg-sys-next", + "libc", +] + +[[package]] +name = "ffmpeg-sys-next" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e9c75ebd4463de9d8998fb134ba26347fe5faee62fabf0a4b4d41bd500b4ad" +dependencies = [ + "bindgen 0.70.1", + "cc", + "libc", + "num_cpus", + "pkg-config", + "vcpkg", +] + [[package]] name = "filedescriptor" version = "0.8.3" @@ -2398,6 +3122,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin 0.9.9", +] + [[package]] name = "flume" version = "0.12.0" @@ -2455,6 +3191,20 @@ dependencies = [ "roxmltree 0.20.0", ] +[[package]] +name = "fontdb" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.20.0", +] + [[package]] name = "fontdb" version = "0.23.0" @@ -2466,7 +3216,7 @@ dependencies = [ "memmap2", "slotmap", "tinyvec", - "ttf-parser", + "ttf-parser 0.25.1", ] [[package]] @@ -2520,6 +3270,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "four-char-code" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42da99970737c0150e3c5cd1cdc510735a2511739f5c3aa3c6bfc9f31441488d" + [[package]] name = "freetype-sys" version = "0.20.1" @@ -2596,6 +3352,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.34" @@ -2786,27 +3553,83 @@ dependencies = [ name = "glob" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "glyphon" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6a289ad2a23656ccf4306fc818cef6776471a136d909123fd26c0f2ecb44ba" +dependencies = [ + "cosmic-text 0.14.2", + "etagere", + "lru", + "rustc-hash 2.1.3", + "wgpu 25.0.2", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45cf04b2726f02df5508c6de726acdc90cdf97ac771a9a0ffd8ba10a6e696bf9" +dependencies = [ + "bitflags 2.13.1", + "gpu-alloc-types", +] [[package]] -name = "glow" -version = "0.17.0" +name = "gpu-alloc-types" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +checksum = "b2bbed164dd10ed526c2e4fe3e721ca4a71c61730e5aafac6844b417b3227058" dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", + "bitflags 2.13.1", ] [[package]] -name = "glutin_wgl_sys" -version = "0.6.1" +name = "gpu-allocator" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" dependencies = [ - "gl_generator", + "log", + "presser", + "thiserror 1.0.69", + "windows 0.57.0", ] [[package]] @@ -2885,7 +3708,7 @@ dependencies = [ "lyon", "mach2 0.5.0", "media", - "metal", + "metal 0.33.0", "num_cpus", "objc", "parking", @@ -2899,7 +3722,7 @@ dependencies = [ "raw-window-handle", "refineable", "regex", - "resvg", + "resvg 0.46.0", "scheduler", "schemars 1.2.2", "seahash", @@ -2909,13 +3732,13 @@ dependencies = [ "smallvec", "spin 0.10.1", "stacksafe", - "strum", + "strum 0.27.2", "sum_tree", "taffy", "thiserror 2.0.20", - "ttf-parser", + "ttf-parser 0.25.1", "url", - "usvg", + "usvg 0.46.0", "util_macros", "uuid", "waker-fn", @@ -2934,7 +3757,7 @@ dependencies = [ "accesskit_unix", "anyhow", "as-raw-xcb-connection", - "ashpd", + "ashpd 0.13.13", "bitflags 2.13.1", "bytemuck", "calloop", @@ -2960,7 +3783,7 @@ dependencies = [ "raw-window-handle", "smallvec", "smol", - "strum", + "strum 0.27.2", "swash", "url", "uuid", @@ -3010,7 +3833,7 @@ dependencies = [ "log", "mach2 0.5.0", "media", - "metal", + "metal 0.33.0", "objc", "objc2 0.6.4", "objc2-app-kit 0.3.2", @@ -3021,7 +3844,7 @@ dependencies = [ "raw-window-handle", "semver", "smallvec", - "strum", + "strum 0.27.2", "uuid", "zed-font-kit", ] @@ -3057,7 +3880,7 @@ source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b4 dependencies = [ "schemars 1.2.2", "serde", - "smol_str", + "smol_str 0.3.6", ] [[package]] @@ -3113,7 +3936,7 @@ dependencies = [ "anyhow", "bytemuck", "collections", - "cosmic-text", + "cosmic-text 0.19.0", "etagere", "gpui", "gpui_util", @@ -3130,7 +3953,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu", + "wgpu 29.0.4", "zed-font-kit", ] @@ -3168,6 +3991,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5" +[[package]] +name = "guardian" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f" + [[package]] name = "h2" version = "0.4.15" @@ -3317,6 +4146,12 @@ dependencies = [ "digest", ] +[[package]] +name = "hmac-sha256" +version = "1.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f" + [[package]] name = "home" version = "0.5.12" @@ -3652,9 +4487,10 @@ dependencies = [ "qoi", "ravif", "rayon", + "rgb", "tiff 0.11.3", - "zune-core", - "zune-jpeg", + "zune-core 0.5.3", + "zune-jpeg 0.5.15", ] [[package]] @@ -3667,6 +4503,12 @@ dependencies = [ "quick-error", ] +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + [[package]] name = "imagesize" version = "0.14.0" @@ -3983,6 +4825,35 @@ dependencies = [ "serde_json", ] +[[package]] +name = "kameo" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a73be96f616ca2784f597b5b6635582f5a7b3ba73b1dbe7afa5d9667955d39" +dependencies = [ + "downcast-rs 2.0.2", + "dyn-clone", + "futures", + "kameo_macros", + "once_cell", + "serde", + "tokio", + "tracing", +] + +[[package]] +name = "kameo_macros" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3f384b32bf6426ae93a8b37da62c85073b676a31a82a86d608ad86453878de0" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "uuid", +] + [[package]] name = "khronos-egl" version = "6.0.0" @@ -3990,7 +4861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading", + "libloading 0.8.9", "pkg-config", ] @@ -4012,6 +4883,17 @@ dependencies = [ "selectors", ] +[[package]] +name = "kurbo" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + [[package]] name = "kurbo" version = "0.13.1" @@ -4089,6 +4971,16 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + [[package]] name = "libm" version = "0.2.16" @@ -4104,6 +4996,33 @@ dependencies = [ "libc", ] +[[package]] +name = "libspa" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2909f3be29d674e7f10604aff18d1bbe1bb03c4cd61c8a8ba19c0b1d162f7d4e" +dependencies = [ + "bitflags 2.13.1", + "cc", + "cookie-factory", + "libc", + "libspa-sys", + "nom 8.0.0", + "rustix 1.1.4", + "system-deps", +] + +[[package]] +name = "libspa-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ad52764fca54818486f3cf75afec844d1f1a1568c24dcee25d41b1ab007dda" +dependencies = [ + "bindgen 0.72.1", + "cc", + "system-deps", +] + [[package]] name = "linebender_resource_handle" version = "0.1.1" @@ -4174,6 +5093,12 @@ dependencies = [ "imgref", ] +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" + [[package]] name = "lru-slab" version = "0.1.2" @@ -4232,6 +5157,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "lzma-rust2" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e20f57f9918e5bd7bc58c22cdd70a6afc7375d4dd9683af5f2b34bd3d2bba619" + [[package]] name = "mac" version = "0.1.1" @@ -4270,6 +5201,16 @@ dependencies = [ "libc", ] +[[package]] +name = "macos-accessibility-client" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edf7710fbff50c24124331760978fb9086d6de6288dcdb38b25a97f8b1bdebbb" +dependencies = [ + "core-foundation 0.9.4", + "core-foundation-sys", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -4325,6 +5266,16 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "matrixmultiply" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f607c237553f086e7043417a51df26b2eb899d3caff94e6a67592ff992fedc7" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -4356,7 +5307,7 @@ dependencies = [ "core-video", "ctor 1.0.13", "foreign-types 0.5.0", - "metal", + "metal 0.33.0", "objc", ] @@ -4384,6 +5335,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metal" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" +dependencies = [ + "bitflags 2.13.1", + "block", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", + "log", + "objc", + "paste", +] + [[package]] name = "metal" version = "0.33.0" @@ -4464,6 +5430,31 @@ dependencies = [ "pxfm", ] +[[package]] +name = "naga" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" +dependencies = [ + "arrayvec", + "bit-set 0.8.0", + "bitflags 2.13.1", + "cfg_aliases", + "codespan-reporting 0.12.0", + "half", + "hashbrown 0.15.5", + "hexf-parse", + "indexmap 2.14.0", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv 0.3.0+sdk-1.3.268.0", + "strum 0.26.3", + "thiserror 2.0.20", + "unicode-ident", +] + [[package]] name = "naga" version = "29.0.4" @@ -4471,11 +5462,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df" dependencies = [ "arrayvec", - "bit-set", + "bit-set 0.9.1", "bitflags 2.13.1", "cfg-if", "cfg_aliases", - "codespan-reporting", + "codespan-reporting 0.13.1", "half", "hashbrown 0.16.1", "hexf-parse", @@ -4485,11 +5476,20 @@ dependencies = [ "num-traits", "once_cell", "rustc-hash 1.1.0", - "spirv", + "spirv 0.4.0+sdk-1.4.341.0", "thiserror 2.0.20", "unicode-ident", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.17", +] + [[package]] name = "native-tls" version = "0.2.18" @@ -4507,6 +5507,36 @@ dependencies = [ "tempfile", ] +[[package]] +name = "ndarray" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "ndarray" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + [[package]] name = "ndk" version = "0.8.0" @@ -4560,6 +5590,18 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "no_std_io2" version = "0.9.4" @@ -4629,7 +5671,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4829,7 +5871,7 @@ dependencies = [ "block2 0.5.1", "libc", "objc2 0.5.2", - "objc2-core-data", + "objc2-core-data 0.2.2", "objc2-core-image 0.2.2", "objc2-foundation 0.2.2", "objc2-quartz-core 0.2.2", @@ -4842,9 +5884,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.13.1", + "block2 0.6.2", + "libc", "objc2 0.6.4", + "objc2-cloud-kit", + "objc2-core-data 0.3.2", "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image 0.3.2", + "objc2-core-text", + "objc2-core-video", "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", ] [[package]] @@ -4880,6 +5931,17 @@ dependencies = [ "objc2-foundation 0.3.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-core-audio" version = "0.3.2" @@ -4914,6 +5976,17 @@ 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 = [ + "bitflags 2.13.1", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-core-foundation" version = "0.3.2" @@ -4985,6 +6058,18 @@ dependencies = [ "objc2-core-video", ] +[[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-core-video" version = "0.3.2" @@ -5040,6 +6125,16 @@ dependencies = [ "objc2-core-graphics", ] +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "objc2-io-surface" version = "0.3.2" @@ -5204,7 +6299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78f2bfed90f1618b4b48dcad9307f25e14ae894e2949642c87c351601d62cebd" dependencies = [ "aes", - "ashpd", + "ashpd 0.13.13", "async-fs", "async-io", "async-lock", @@ -5291,6 +6386,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "or_poisoned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c04f5d74368e4d0dfe06c45c8627c81bd7c317d52762d118fb9b3076f6420fd" + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-float" version = "5.3.0" @@ -5310,6 +6420,31 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "ort" +version = "2.0.0-rc.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7de3af33d24a745ffb8fab904b13478438d1cd52868e6f17735ef6e1f8bf133" +dependencies = [ + "libloading 0.9.0", + "ndarray 0.17.2", + "ort-sys", + "smallvec", + "tracing", + "ureq", +] + +[[package]] +name = "ort-sys" +version = "2.0.0-rc.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7b497d21a8b6fbb4b5a544f8fadb77e801a09ae0add9e411d31c6f89e3c1e90" +dependencies = [ + "hmac-sha256", + "lzma-rust2", + "ureq", +] + [[package]] name = "parking" version = "2.2.1" @@ -5386,6 +6521,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -5608,6 +6752,31 @@ dependencies = [ "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.1", + "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 0.72.1", + "libspa-sys", + "system-deps", +] + [[package]] name = "pkg-config" version = "0.3.34" @@ -5963,7 +7132,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6212,6 +7381,12 @@ dependencies = [ "objc2-quartz-core 0.3.2", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.12.0" @@ -6232,6 +7407,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "reactive_graph" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a0ccddbc11a648bd09761801dac9e3f246ef7641130987d6120fced22515e6" +dependencies = [ + "any_spawner", + "async-lock", + "futures", + "guardian", + "or_poisoned", + "pin-project-lite", + "rustc-hash 2.1.3", + "send_wrapper", + "slotmap", + "thiserror 2.0.20", + "web-sys", +] + [[package]] name = "read-fonts" version = "0.37.0" @@ -6254,6 +7448,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "readkey" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a36870cefdfcff57edbc0fa62165f42dfd4e5a0d8965117c1ea84c5700e4450" + +[[package]] +name = "readmouse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be105c72a1e6a5a1198acee3d5b506a15676b74a02ecd78060042a447f408d94" + [[package]] name = "reborrow" version = "0.5.5" @@ -6269,6 +7475,17 @@ dependencies = [ "bitflags 2.13.1", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -6337,12 +7554,27 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +dependencies = [ + "serde", +] + [[package]] name = "renderdoc-sys" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "replace_with" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51743d3e274e2b18df81c4dc6caf8a5b8e15dbe799e0dca05c7617380094e884" + [[package]] name = "reqwest" version = "0.12.24" @@ -6393,6 +7625,23 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "resvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" +dependencies = [ + "gif 0.13.3", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes 0.15.3", + "tiny-skia", + "usvg 0.45.1", + "zune-jpeg 0.4.21", +] + [[package]] name = "resvg" version = "0.46.0" @@ -6404,10 +7653,19 @@ dependencies = [ "log", "pico-args", "rgb", - "svgtypes", + "svgtypes 0.16.1", "tiny-skia", - "usvg", - "zune-jpeg", + "usvg 0.46.0", + "zune-jpeg 0.5.15", +] + +[[package]] +name = "retry" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cab9bd343c737660e523ee69f788018f3db686d537d2fd0f99c9f747c1bda4f" +dependencies = [ + "rand 0.9.5", ] [[package]] @@ -6433,6 +7691,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ringbuf" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe47b720588c8702e34b5979cb3271a8b1842c7cb6f57408efa70c779363488c" +dependencies = [ + "crossbeam-utils", + "portable-atomic", + "portable-atomic-util", +] + [[package]] name = "roxmltree" version = "0.20.0" @@ -6498,7 +7767,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6542,6 +7811,23 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "libm", + "smallvec", + "ttf-parser 0.21.1", + "unicode-bidi-mirroring 0.2.0", + "unicode-ccc 0.2.0", + "unicode-properties", + "unicode-script", +] + [[package]] name = "rustybuzz" version = "0.20.1" @@ -6553,9 +7839,9 @@ dependencies = [ "core_maths", "log", "smallvec", - "ttf-parser", - "unicode-bidi-mirroring", - "unicode-ccc", + "ttf-parser 0.25.1", + "unicode-bidi-mirroring 0.4.0", + "unicode-ccc 0.4.0", "unicode-properties", "unicode-script", ] @@ -6575,6 +7861,53 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scap-cpal" +version = "0.1.0" +dependencies = [ + "cpal", + "thiserror 1.0.69", + "tracing", + "workspace-hack", +] + +[[package]] +name = "scap-direct3d" +version = "0.1.0" +dependencies = [ + "cap-d3d-adapter", + "thiserror 1.0.69", + "tracing", + "windows 0.60.0", + "workspace-hack", +] + +[[package]] +name = "scap-ffmpeg" +version = "0.1.0" +dependencies = [ + "cidre", + "cpal", + "ffmpeg-next", + "scap-direct3d", + "scap-screencapturekit", + "windows 0.60.0", + "workspace-hack", +] + +[[package]] +name = "scap-screencapturekit" +version = "0.1.0" +dependencies = [ + "cidre", + "futures", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "tracing", + "workspace-hack", +] + [[package]] name = "scap-targets" version = "0.1.0" @@ -6611,7 +7944,7 @@ dependencies = [ "async-task", "backtrace", "chrono", - "flume", + "flume 0.12.0", "futures", "parking_lot", "rand 0.9.5", @@ -6704,6 +8037,22 @@ dependencies = [ "screencapturekit-sys", ] +[[package]] +name = "screencapturekit" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c62ae379564f834110c5b020cc731a7c14b8ec4879b4367579a467a0a704c21" +dependencies = [ + "block2 0.6.2", + "core-foundation 0.10.1", + "core-graphics 0.25.0", + "core-media-rs", + "core-utils-rs", + "core-video-rs", + "dispatch", + "objc", +] + [[package]] name = "screencapturekit-sys" version = "0.2.8" @@ -6781,6 +8130,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +dependencies = [ + "futures-core", +] + [[package]] name = "serde" version = "1.0.229" @@ -7147,6 +8505,12 @@ dependencies = [ "futures-lite", ] +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" + [[package]] name = "smol_str" version = "0.3.6" @@ -7167,6 +8531,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "socks" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + [[package]] name = "specta" version = "2.0.0-rc.20" @@ -7211,6 +8586,15 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.13.1", +] + [[package]] name = "spirv" version = "0.4.0+sdk-1.4.341.0" @@ -7306,13 +8690,35 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.119", ] [[package]] @@ -7430,13 +8836,23 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" +[[package]] +name = "svgtypes" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +dependencies = [ + "kurbo 0.11.3", + "siphasher 1.0.3", +] + [[package]] name = "svgtypes" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" dependencies = [ - "kurbo", + "kurbo 0.13.1", "siphasher 1.0.3", ] @@ -7538,6 +8954,34 @@ dependencies = [ "windows 0.57.0", ] +[[package]] +name = "sysinfo" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + +[[package]] +name = "sysinfo" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.61.3", +] + [[package]] name = "system-configuration" version = "0.7.0" @@ -7559,6 +9003,19 @@ dependencies = [ "libc", ] +[[package]] +name = "system-deps" +version = "7.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 1.1.4+spec-1.1.0", + "version-compare", +] + [[package]] name = "taffy" version = "0.12.2" @@ -7583,6 +9040,12 @@ dependencies = [ "objc", ] +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + [[package]] name = "tauri-utils" version = "2.9.3" @@ -7640,10 +9103,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7737,7 +9200,7 @@ dependencies = [ "half", "quick-error", "weezl", - "zune-jpeg", + "zune-jpeg 0.5.15", ] [[package]] @@ -8110,6 +9573,18 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + [[package]] name = "ttf-parser" version = "0.25.1" @@ -8213,12 +9688,24 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + [[package]] name = "unicode-bidi-mirroring" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" +[[package]] +name = "unicode-ccc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" + [[package]] name = "unicode-ccc" version = "0.4.0" @@ -8285,6 +9772,42 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "ureq" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d" +dependencies = [ + "base64 0.23.1", + "der", + "log", + "native-tls", + "percent-encoding", + "rustls-pki-types", + "socks", + "ureq-proto", + "utf8-zero", + "webpki-root-certs", +] + +[[package]] +name = "ureq-proto" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613" +dependencies = [ + "base64 0.23.1", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -8310,6 +9833,33 @@ dependencies = [ "url", ] +[[package]] +name = "usvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb 0.23.0", + "imagesize 0.13.0", + "kurbo 0.11.3", + "log", + "pico-args", + "roxmltree 0.20.0", + "rustybuzz 0.20.1", + "simplecss", + "siphasher 1.0.3", + "strict-num", + "svgtypes 0.15.3", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + [[package]] name = "usvg" version = "0.46.0" @@ -8319,17 +9869,17 @@ dependencies = [ "base64 0.22.1", "data-url", "flate2", - "fontdb", - "imagesize", - "kurbo", + "fontdb 0.23.0", + "imagesize 0.14.0", + "kurbo 0.13.1", "log", "pico-args", "roxmltree 0.21.1", - "rustybuzz", + "rustybuzz 0.20.1", "simplecss", "siphasher 1.0.3", "strict-num", - "svgtypes", + "svgtypes 0.16.1", "tiny-skia-path", "unicode-bidi", "unicode-script", @@ -8343,6 +9893,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -8457,12 +10013,24 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "vswhom" version = "0.1.0" @@ -8616,7 +10184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" dependencies = [ "cc", - "downcast-rs", + "downcast-rs 1.2.1", "rustix 1.1.4", "scoped-tls", "smallvec", @@ -8727,6 +10295,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "1.0.9" @@ -8742,6 +10319,34 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +[[package]] +name = "wgpu" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8fb398f119472be4d80bc3647339f56eb63b2a331f6a3d16e25d8144197dd9" +dependencies = [ + "arrayvec", + "bitflags 2.13.1", + "cfg_aliases", + "document-features", + "hashbrown 0.15.5", + "js-sys", + "log", + "naga 25.0.1", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core 25.0.2", + "wgpu-hal 25.0.2", + "wgpu-types 25.0.0", +] + [[package]] name = "wgpu" version = "29.0.4" @@ -8757,7 +10362,7 @@ dependencies = [ "hashbrown 0.16.1", "js-sys", "log", - "naga", + "naga 29.0.4", "parking_lot", "portable-atomic", "profiling", @@ -8767,9 +10372,40 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu-core", - "wgpu-hal", - "wgpu-types", + "wgpu-core 29.0.4", + "wgpu-hal 29.0.4", + "wgpu-types 29.0.4", +] + +[[package]] +name = "wgpu-core" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b882196f8368511d613c6aeec80655160db6646aebddf8328879a88d54e500" +dependencies = [ + "arrayvec", + "bit-set 0.8.0", + "bit-vec 0.8.0", + "bitflags 2.13.1", + "cfg_aliases", + "document-features", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "log", + "naga 25.0.1", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.20", + "wgpu-core-deps-apple 25.0.0", + "wgpu-core-deps-emscripten 25.0.0", + "wgpu-core-deps-windows-linux-android 25.0.0", + "wgpu-hal 25.0.2", + "wgpu-types 25.0.0", ] [[package]] @@ -8779,8 +10415,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f519832254e56965a9940c4af57dcb75f702b6f6fa4a0b172f685395843a4d7" dependencies = [ "arrayvec", - "bit-set", - "bit-vec", + "bit-set 0.9.1", + "bit-vec 0.9.1", "bitflags 2.13.1", "bytemuck", "cfg_aliases", @@ -8788,7 +10424,7 @@ dependencies = [ "hashbrown 0.16.1", "indexmap 2.14.0", "log", - "naga", + "naga 29.0.4", "once_cell", "parking_lot", "portable-atomic", @@ -8797,12 +10433,21 @@ dependencies = [ "rustc-hash 1.1.0", "smallvec", "thiserror 2.0.20", - "wgpu-core-deps-apple", - "wgpu-core-deps-emscripten", - "wgpu-core-deps-windows-linux-android", - "wgpu-hal", + "wgpu-core-deps-apple 29.0.4", + "wgpu-core-deps-emscripten 29.0.4", + "wgpu-core-deps-windows-linux-android 29.0.4", + "wgpu-hal 29.0.4", "wgpu-naga-bridge", - "wgpu-types", + "wgpu-types 29.0.4", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd488b3239b6b7b185c3b045c39ca6bf8af34467a4c5de4e0b1a564135d093d" +dependencies = [ + "wgpu-hal 25.0.2", ] [[package]] @@ -8811,7 +10456,16 @@ version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5e39e26c4c0e07589e67d18546cf79ff45383659fc72fca4dd293358a0347f3" dependencies = [ - "wgpu-hal", + "wgpu-hal 29.0.4", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09ad7aceb3818e52539acc679f049d3475775586f3f4e311c30165cf2c00445" +dependencies = [ + "wgpu-hal 25.0.2", ] [[package]] @@ -8820,7 +10474,16 @@ version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01e09be551dc939498bdd5f6b2c66e55ab275dad25825267a08605a80fc9f0af" dependencies = [ - "wgpu-hal", + "wgpu-hal 29.0.4", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cba5fb5f7f9c98baa7c889d444f63ace25574833df56f5b817985f641af58e46" +dependencies = [ + "wgpu-hal 25.0.2", ] [[package]] @@ -8829,7 +10492,52 @@ version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e592c1bbef6ad047647ae6e666ebd8cee7a32bb4544d9700ec96cbf73230257" dependencies = [ - "wgpu-hal", + "wgpu-hal 29.0.4", +] + +[[package]] +name = "wgpu-hal" +version = "25.0.2" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set 0.8.0", + "bitflags 2.13.1", + "block", + "bytemuck", + "cfg-if", + "cfg_aliases", + "core-graphics-types 0.1.3", + "glow 0.16.0", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator 0.27.0", + "gpu-descriptor", + "hashbrown 0.15.5", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.9", + "log", + "metal 0.31.0", + "naga 25.0.1", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "ordered-float 4.6.0", + "parking_lot", + "portable-atomic", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.20", + "wasm-bindgen", + "web-sys", + "wgpu-types 25.0.0", + "windows 0.58.0", + "windows-core 0.58.0", ] [[package]] @@ -8841,23 +10549,23 @@ dependencies = [ "android_system_properties", "arrayvec", "ash", - "bit-set", + "bit-set 0.9.1", "bitflags 2.13.1", "block2 0.6.2", "bytemuck", "cfg-if", "cfg_aliases", - "glow", + "glow 0.17.0", "glutin_wgl_sys", - "gpu-allocator", + "gpu-allocator 0.28.0", "gpu-descriptor", "hashbrown 0.16.1", "js-sys", "khronos-egl", "libc", - "libloading", + "libloading 0.8.9", "log", - "naga", + "naga 29.0.4", "ndk-sys 0.6.0+11769913", "objc2 0.6.4", "objc2-core-foundation", @@ -8865,7 +10573,7 @@ dependencies = [ "objc2-metal 0.3.2", "objc2-quartz-core 0.3.2", "once_cell", - "ordered-float", + "ordered-float 5.3.0", "parking_lot", "portable-atomic", "portable-atomic-util", @@ -8880,7 +10588,7 @@ dependencies = [ "wayland-sys", "web-sys", "wgpu-naga-bridge", - "wgpu-types", + "wgpu-types 29.0.4", "windows 0.62.2", "windows-core 0.62.2", "windows-result 0.4.1", @@ -8892,8 +10600,22 @@ version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95226013f547544b223281cd16a4fb549aa9dcb562adbda0faae4c73ffbbc161" dependencies = [ - "naga", - "wgpu-types", + "naga 29.0.4", + "wgpu-types 29.0.4", +] + +[[package]] +name = "wgpu-types" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "js-sys", + "log", + "thiserror 2.0.20", + "web-sys", ] [[package]] @@ -8956,7 +10678,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -8965,6 +10687,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows" version = "0.54.0" @@ -8985,6 +10716,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.60.0" @@ -9085,6 +10826,19 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.60.1" @@ -9167,6 +10921,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "windows-implement" version = "0.59.0" @@ -9200,6 +10965,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "windows-interface" version = "0.59.3" @@ -9284,6 +11060,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -9302,6 +11087,16 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-strings" version = "0.3.1" @@ -9878,6 +11673,7 @@ dependencies = [ "rustix 1.1.4", "serde", "serde_repr", + "tokio", "tracing", "uds_windows", "uuid", @@ -9969,7 +11765,7 @@ dependencies = [ "core-foundation 0.10.1", "core-graphics 0.24.0", "core-text", - "dirs", + "dirs 6.0.0", "dwrote", "float-ord", "freetype-sys", @@ -9994,9 +11790,9 @@ dependencies = [ "log", "objc", "rand 0.8.7", - "screencapturekit", + "screencapturekit 0.2.8", "screencapturekit-sys", - "sysinfo", + "sysinfo 0.31.4", "tao-core-video-sys", "windows 0.61.3", "windows-capture", @@ -10156,6 +11952,12 @@ name = "ztracing_macro" version = "0.1.0" source = "git+https://github.com/wingleeio/zed?rev=5d1f83d9f27a19bec1fb241dc33b42238af9cf8d#5d1f83d9f27a19bec1fb241dc33b42238af9cf8d" +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-core" version = "0.5.3" @@ -10171,13 +11973,22 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + [[package]] name = "zune-jpeg" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ - "zune-core", + "zune-core 0.5.3", ] [[package]] @@ -10190,6 +12001,7 @@ dependencies = [ "enumflags2", "serde", "serde_bytes", + "url", "winnow 1.0.4", "zcheapstr", "zvariant_derive", diff --git a/apps/desktop-gpui/Cargo.toml b/apps/desktop-gpui/Cargo.toml index d6dbdab583..08ea8b3ff3 100644 --- a/apps/desktop-gpui/Cargo.toml +++ b/apps/desktop-gpui/Cargo.toml @@ -38,9 +38,49 @@ scap-targets = { path = "../../crates/scap-targets" } # Same fork/rev as the root workspace: MicrophoneFeed enumerates through this. cpal = { git = "https://github.com/CapSoftware/cpal", rev = "6013cb5f8bd3" } +# The actual recording engine -- the same studio/instant actors the Tauri app +# drives. Drags in the ffmpeg encode stack; that is the point. +cap-recording = { path = "../../crates/recording" } +cap-utils = { path = "../../crates/utils" } +cap-project = { path = "../../crates/project" } +# spawn_blocking for finalize; the runtime itself comes from gpui_tokio. +tokio = { version = "1", features = ["rt"] } +# cap-recording's actor system; version must match its `kameo = "0.17.2"`. +kameo = "0.17.2" +flume = "0.11.0" +anyhow = "1" +chrono = "0.4" +# Read the Tauri app's settings store so both apps share one recordings library. +serde_json = "1" + tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } +[target.'cfg(target_os = "macos")'.dependencies] +# ScreenCaptureKit shareable content, acquired the same way recording.rs in +# src-tauri does. Same fork/rev/features as the root workspace. +cidre = { git = "https://github.com/CapSoftware/cidre", rev = "bf84b67079a8", features = [ + "macos_12_7", + "cv", + "cf", + "cg", + "core_audio", + "sc", + "av", + "blocks", + "async", + "dispatch", + "io_surface", + "mtl", + "vt", + "vn", +], default-features = false } + +# Mirror of the root workspace patch: cap-recording's converter stack is built +# and tested against the vendored wgpu-hal, not the crates.io release. +[patch.crates-io] +wgpu-hal = { path = "../../vendor/wgpu-hal" } + [profile.dev] # gpui is unusably slow when its dependencies are unoptimized; zed/comet both # build deps at opt-level 2 and keep the app crate itself at 0. diff --git a/apps/desktop-gpui/src/recording.rs b/apps/desktop-gpui/src/recording.rs new file mode 100644 index 0000000000..27d4c52eec --- /dev/null +++ b/apps/desktop-gpui/src/recording.rs @@ -0,0 +1,342 @@ +//! Recording, through the same engine the Tauri app drives. +//! +//! The construction here mirrors `apps/desktop/src-tauri/src/recording.rs` +//! (`start_recording`): spawn the feed actors, lock the selected devices, +//! acquire ScreenCaptureKit shareable content, then hand everything to the +//! studio/instant actor builder from `cap-recording`. Everything in this module +//! runs on the tokio runtime (`gpui_tokio`), never on gpui's main thread -- +//! kameo actors and the capture pipeline both assume tokio. + +use std::path::PathBuf; +use std::sync::Arc; + +use anyhow::{Context as _, anyhow}; +use cap_recording::{ + feeds::{camera, camera::CameraFeed, microphone, microphone::MicrophoneFeed}, + instant_recording, + sources::screen_capture::ScreenCaptureTarget, + studio_recording, +}; +use kameo::{Actor, actor::ActorRef}; + +pub use cap_recording::feeds::camera::DeviceOrModelID; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RecordingMode { + Studio, + Instant, +} + +/// Everything `start` needs, captured from UI state up front so the future is +/// `'static` and owns its inputs. +pub struct StartConfig { + pub mode: RecordingMode, + pub target: ScreenCaptureTarget, + /// Microphone by name -- the identity `MicrophoneFeed` keys on. + pub microphone: Option, + pub camera: Option, + pub system_audio: bool, +} + +enum Handle { + Studio(studio_recording::ActorHandle), + Instant(instant_recording::ActorHandle), +} + +/// A live recording. Stopping consumes it; dropping it without stopping leaves +/// the actors to wind down on their own when the refs go away. +pub struct ActiveRecording { + handle: Handle, + pub project_dir: PathBuf, + // Held for the duration of the recording: dropping an ActorRef early would + // stop the feed under the pipeline. + _mic_feed: Option>, + _camera_feed: Option>, + // The mic error channel must outlive the stream or error sends panic the + // sender side into logs; we keep it and drain nothing. + _mic_errors: Option>, +} + +impl ActiveRecording { + /// Stop and finalize. Returns the project directory. + /// + /// Finalization mirrors the CLI's `finalize_completed`: studio projects get + /// `RecoveryManager::remux_if_needed` (fragmented segments -> playable + /// mp4s), instant projects get their DASH segments muxed into + /// `content/output.mp4` plus a `recording-meta.json`/`project-config.json` + /// pair so the library and share flows recognize the project. + pub async fn stop(self) -> anyhow::Result { + match self.handle { + Handle::Studio(handle) => { + let completed = handle.stop().await?; + let project_path = completed.project_path.clone(); + tokio::task::spawn_blocking(move || { + cap_recording::recovery::RecoveryManager::remux_if_needed(&project_path) + }) + .await + .context("studio finalize task")? + .context("studio finalize")?; + Ok(completed.project_path) + } + Handle::Instant(handle) => { + let completed = handle.stop().await?; + let project_path = completed.project_path.clone(); + + let display_dir = project_path.join("content/display"); + let audio_dir = project_path.join("content/audio"); + let output_path = project_path.join("content/output.mp4"); + tokio::task::spawn_blocking(move || { + cap_recording::recovery::RecoveryManager::finalize_instant_output( + &display_dir, + &audio_dir, + &output_path, + ) + }) + .await + .context("instant finalize task")? + .context("instant finalize")?; + + persist_instant_meta(&completed)?; + Ok(completed.project_path) + } + } + } +} + +/// `persist_instant_recording_meta` from the CLI, verbatim in behavior: without +/// this pair of files the recording plays but no Cap surface lists it. +fn persist_instant_meta( + completed: &instant_recording::CompletedRecording, +) -> anyhow::Result<()> { + use cap_project::{ + InstantRecordingMeta, Platform, ProjectConfiguration, RecordingMeta, RecordingMetaInner, + }; + + let pretty_name = completed + .project_path + .file_stem() + .and_then(|name| name.to_str()) + .filter(|name| !name.is_empty()) + .unwrap_or("Cap Recording") + .to_string(); + let meta = match &completed.meta { + InstantRecordingMeta::InProgress { .. } => InstantRecordingMeta::Failed { + error: "instant recording stopped before completion".to_string(), + }, + other => other.clone(), + }; + + RecordingMeta { + platform: Some(Platform::default()), + project_path: completed.project_path.clone(), + pretty_name, + sharing: None, + inner: RecordingMetaInner::Instant(meta), + upload: None, + } + .save_for_project() + .map_err(|e| anyhow!("saving instant recording meta: {e}"))?; + + ProjectConfiguration::default() + .write(&completed.project_path) + .map_err(|e| anyhow!("saving instant project config: {e}"))?; + Ok(()) +} + +pub async fn start(config: StartConfig) -> anyhow::Result { + let project_dir = create_project_dir(&config.target)?; + tracing::info!(dir = %project_dir.display(), "starting recording"); + + // Feed actors are per-recording here, where the Tauri app keeps app-wide + // ones (it needs them for previews and level meters between recordings). + // Once the camera preview window exists this moves to app scope too. + let (mic_feed, mic_lock, mic_errors) = match &config.microphone { + Some(label) => { + let (error_tx, error_rx) = flume::unbounded(); + let feed = MicrophoneFeed::spawn(MicrophoneFeed::new(error_tx)); + let ready = feed + .ask(microphone::SetInput { + label: label.clone(), + settings: None, + }) + .await + .map_err(|e| anyhow!("microphone '{label}' setup: {e}"))?; + ready + .await + .map_err(|e| anyhow!("microphone '{label}' init: {e}"))?; + let lock = feed + .ask(microphone::Lock) + .await + .map_err(|e| anyhow!("microphone '{label}' lock: {e}"))?; + (Some(feed), Some(Arc::new(lock)), Some(error_rx)) + } + None => (None, None, None), + }; + + let (camera_feed, camera_lock) = match &config.camera { + Some(id) => { + let feed = CameraFeed::spawn(CameraFeed::default()); + let ready = feed + .ask(camera::SetInput { + id: id.clone(), + settings: None, + }) + .await + .map_err(|e| anyhow!("camera setup: {e}"))?; + ready.await.map_err(|e| anyhow!("camera init: {e}"))?; + let lock = feed + .ask(camera::Lock) + .await + .map_err(|e| anyhow!("camera lock: {e}"))?; + (Some(feed), Some(Arc::new(lock))) + } + None => { + if matches!(config.target, ScreenCaptureTarget::CameraOnly) { + return Err(anyhow!( + "Camera-only recording requires a selected camera." + )); + } + (None, None) + } + }; + + // ScreenCaptureKit content, exactly as `read_recording_shareable_content` + // does it: the current-process fallback covers the sandboxed case where the + // full query returns no displays. + #[cfg(target_os = "macos")] + let shareable_content = match config.target { + ScreenCaptureTarget::CameraOnly => None, + _ => Some(read_shareable_content().await?), + }; + + let handle = match config.mode { + RecordingMode::Studio => { + let mut builder = + studio_recording::Actor::builder(project_dir.clone(), config.target.clone()) + .with_system_audio(config.system_audio); + if let Some(lock) = camera_lock.clone() { + builder = builder.with_camera_feed(lock); + } + if let Some(lock) = mic_lock.clone() { + builder = builder.with_mic_feed(lock); + } + Handle::Studio( + builder + .build( + #[cfg(target_os = "macos")] + shareable_content, + ) + .await + .context("studio recording actor")?, + ) + } + RecordingMode::Instant => { + let mut builder = + instant_recording::Actor::builder(project_dir.clone(), config.target.clone()) + .with_system_audio(config.system_audio); + if let Some(lock) = camera_lock.clone() { + builder = builder.with_camera_feed(lock); + } + if let Some(lock) = mic_lock.clone() { + builder = builder.with_mic_feed(lock); + } + Handle::Instant( + builder + .build( + #[cfg(target_os = "macos")] + shareable_content, + ) + .await + .context("instant recording actor")?, + ) + } + }; + + Ok(ActiveRecording { + handle, + project_dir, + _mic_feed: mic_feed, + _camera_feed: camera_feed, + _mic_errors: mic_errors, + }) +} + +#[cfg(target_os = "macos")] +async fn read_shareable_content() -> anyhow::Result { + let content = cidre::sc::ShareableContent::current() + .await + .map_err(|e| anyhow!("ReadShareableContent: {e}"))?; + if !content.displays().is_empty() { + return Ok(content.into()); + } + let process_content = cidre::sc::ShareableContent::current_process() + .await + .map_err(|e| anyhow!("ReadCurrentProcessShareableContent: {e}"))?; + if !process_content.displays().is_empty() { + return Ok(process_content.into()); + } + Ok(content.into()) +} + +/// The recordings library root, resolved the way `GeneralSettingsStore:: +/// recordings_dir` resolves it so both apps share one library: the custom +/// `recordingsPath` from the Tauri store if it is absolute and creatable, +/// otherwise `/recordings`. `CAP_GPUI_RECORDINGS_DIR` overrides both +/// (used by the automated end-to-end check so test recordings stay out of the +/// user's library). +pub fn recordings_dir() -> PathBuf { + if let Ok(dir) = std::env::var("CAP_GPUI_RECORDINGS_DIR") + && !dir.trim().is_empty() + { + return PathBuf::from(dir); + } + + let app_data = PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".into())) + .join("Library/Application Support/so.cap.desktop"); + + let custom = std::fs::read(app_data.join("store")) + .ok() + .and_then(|bytes| serde_json::from_slice::(&bytes).ok()) + .and_then(|store| { + store + .get("general_settings")? + .get("recordingsPath")? + .as_str() + .map(PathBuf::from) + }) + .filter(|path| path.is_absolute()); + + if let Some(path) = custom + && std::fs::create_dir_all(&path).is_ok() + { + return path; + } + + app_data.join("recordings") +} + +/// `format_project_name` with the default template +/// (`{target_name} ({target_kind}) {date} {time}`), then the same `:` -> `.` +/// replacement and uniquing the Tauri app applies. +fn create_project_dir(target: &ScreenCaptureTarget) -> anyhow::Result { + let base = recordings_dir(); + std::fs::create_dir_all(&base) + .with_context(|| format!("creating recordings dir {}", base.display()))?; + + let target_name = target.title().unwrap_or_else(|| "Unknown".into()); + let now = chrono::Local::now(); + let name = format!( + "{} ({}) {} {}", + target_name, + target.kind_str(), + now.format("%Y-%m-%d"), + now.format("%I.%M %p"), + ); + // Same normalization chain as the Tauri app: colons break Finder, slashes + // break paths. + let filename = format!("{}.cap", name.replace([':', '/'], ".")); + let filename = cap_utils::ensure_unique_filename(&filename, &base) + .map_err(|e| anyhow!("unique filename: {e}"))?; + + Ok(base.join(filename)) +} From 1c6e1894205a4f19c9f12c0d849a9b9985980628 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:29:51 +0100 Subject: [PATCH 011/146] feat(desktop-gpui): arm, start, stop recording from the main window Idle/Starting/Recording/Stopping on the entity, driven over gpui_tokio. The recording takeover matches index.tsx 3510-3529 (gray-1/80 wash, red stop button); a TEMP blue start footer stands in for the target-select overlay that owns starting in the real app. CAP_GPUI_AUTO_RECORD drives the same code paths for the end-to-end check. --- .../desktop-gpui/assets/icons/stop-circle.svg | 4 + apps/desktop-gpui/src/main.rs | 24 ++ apps/desktop-gpui/src/main_window.rs | 362 +++++++++++++++++- 3 files changed, 375 insertions(+), 15 deletions(-) create mode 100644 apps/desktop-gpui/assets/icons/stop-circle.svg diff --git a/apps/desktop-gpui/assets/icons/stop-circle.svg b/apps/desktop-gpui/assets/icons/stop-circle.svg new file mode 100644 index 0000000000..9597148f3e --- /dev/null +++ b/apps/desktop-gpui/assets/icons/stop-circle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index 0ad632f926..45de4cf367 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -6,6 +6,7 @@ mod assets; mod devices; mod main_window; +mod recording; mod theme; use gpui::{App, AppContext as _, Bounds, WindowBounds, WindowOptions, px, size}; @@ -16,6 +17,16 @@ use crate::{assets::Assets, main_window::MainWindow}; const MAIN_WINDOW_WIDTH: f32 = 330.; const MAIN_WINDOW_HEIGHT: f32 = 395.; +fn parse_auto_record(spec: &str) -> Option<(main_window::Mode, u64)> { + let (mode, secs) = spec.split_once(':')?; + let mode = match mode { + "studio" => main_window::Mode::Studio, + "instant" => main_window::Mode::Instant, + _ => return None, + }; + Some((mode, secs.parse().ok()?)) +} + fn main() { tracing_subscriber::fmt() .with_env_filter( @@ -82,6 +93,19 @@ fn main() { window_handle .update(cx, |view, window, cx| view.start_enumeration(window, cx)) .expect("failed to start device enumeration"); + + // `CAP_GPUI_AUTO_RECORD=studio:5` / `instant:4`: arm the primary + // display and record for N seconds. The end-to-end check drives the + // recorder this way because unprivileged synthetic clicks are dropped. + if let Ok(auto) = std::env::var("CAP_GPUI_AUTO_RECORD") + && let Some((mode, secs)) = parse_auto_record(&auto) + { + window_handle + .update(cx, |view, window, cx| { + view.auto_record(mode, secs, window, cx) + }) + .expect("failed to arm auto-record"); + } cx.activate(true); }); } diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index 9041e03931..c0bfa85453 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -6,6 +6,7 @@ //! quoted next to the values they turn into, because `pl-3` and `gap-2.5` are //! considerably easier to check against the original than `12.` and `10.`. +use cap_recording::sources::screen_capture::ScreenCaptureTarget; use gpui::{ Context, FontWeight, Hsla, InteractiveElement, IntoElement, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window, div, img, prelude::FluentBuilder, px, @@ -15,6 +16,7 @@ use gpui::{ use crate::{ MAIN_WINDOW_HEIGHT, MAIN_WINDOW_WIDTH, devices::{CameraOption, DeviceSnapshot, DisplayOption, MicrophoneOption, WindowOption}, + recording::{self, ActiveRecording}, theme::{Appearance, Theme}, }; @@ -201,6 +203,22 @@ pub struct MainWindow { /// True until the background enumeration has reported back, so the panel can /// say "Loading..." rather than "No cameras found". enumerating: bool, + recording_phase: RecordingPhase, + /// The live recording while `recording_phase` is `Recording`. Taken (moved + /// into the stop future) the moment stopping starts. + active_recording: Option, + /// Why the last start attempt failed, shown above the start button. + recording_error: Option, +} + +/// `Idle -> Starting -> Recording -> Stopping -> Idle`, with failed starts +/// falling straight back to `Idle` + `recording_error`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum RecordingPhase { + Idle, + Starting, + Recording, + Stopping, } impl MainWindow { @@ -228,6 +246,9 @@ impl MainWindow { search: String::new(), search_focus: cx.focus_handle(), enumerating: true, + recording_phase: RecordingPhase::Idle, + active_recording: None, + recording_error: None, } } @@ -324,6 +345,192 @@ impl MainWindow { self.theme = Theme::new(appearance); } } + + /// The concrete capture target the current UI state describes, or `None` + /// when starting makes no sense yet (no target mode, Window mode with no + /// window picked, Area mode -- which needs the selection overlay that does + /// not exist yet). + fn armed_target(&self) -> Option { + match self.target? { + TargetType::Display => { + // The Tauri flow preselects the primary display the moment the + // Display tile is clicked; mirror that when nothing specific + // was picked from the dropdown. + let id = self + .selected_display + .as_ref() + .map(|display| display.id.clone()) + .unwrap_or_else(|| scap_targets::Display::primary().id()); + Some(ScreenCaptureTarget::Display { id }) + } + TargetType::Window => self + .selected_window + .as_ref() + .map(|window| ScreenCaptureTarget::Window { + id: window.id.clone(), + }), + TargetType::Area => None, + TargetType::CameraOnly => Some(ScreenCaptureTarget::CameraOnly), + } + } + + /// The recording mode the Mode pill maps to, `None` for Screenshot (that + /// path does not go through the recording actors at all). + fn recording_mode(&self) -> Option { + match self.mode { + Mode::Instant => Some(recording::RecordingMode::Instant), + Mode::Studio => Some(recording::RecordingMode::Studio), + Mode::Screenshot => None, + } + } + + /// Dev-only end-to-end driver (`CAP_GPUI_AUTO_RECORD=studio:5`): synthetic + /// clicks are dropped without Accessibility permission, so the automated + /// check arms the primary display and drives start/stop through the same + /// methods the buttons call. + pub fn auto_record( + &mut self, + mode: Mode, + record_secs: u64, + window: &mut Window, + cx: &mut Context, + ) { + self.mode = mode; + self.target = Some(TargetType::Display); + cx.notify(); + + cx.spawn_in(window, async move |this, cx| { + // Give enumeration and the first paint a moment; the recorder + // itself does not depend on it, but the screenshots should show + // real device rows. + cx.background_executor() + .timer(std::time::Duration::from_secs(2)) + .await; + if this + .update_in(cx, |this, window, cx| { + // Exercise the microphone path too: record with the default + // input when the machine has one. + this.microphone = this.devices.microphones.first().cloned(); + this.start_recording(window, cx) + }) + .is_err() + { + return; + } + cx.background_executor() + .timer(std::time::Duration::from_secs(record_secs)) + .await; + this.update_in(cx, |this, window, cx| this.stop_recording(window, cx)) + .ok(); + }) + .detach(); + } + + pub fn start_recording(&mut self, window: &mut Window, cx: &mut Context) { + if self.recording_phase != RecordingPhase::Idle { + return; + } + let Some(target) = self.armed_target() else { + return; + }; + let Some(mode) = self.recording_mode() else { + return; + }; + if matches!(target, ScreenCaptureTarget::CameraOnly) && self.camera.is_none() { + self.recording_error = + Some("Camera-only recording requires a selected camera.".into()); + cx.notify(); + return; + } + + let config = recording::StartConfig { + mode, + target, + microphone: self.microphone.as_ref().map(|mic| mic.name.clone()), + camera: self + .camera + .as_ref() + .map(|camera| recording::DeviceOrModelID::DeviceID(camera.device_id.clone())), + system_audio: self.system_audio, + }; + + self.recording_phase = RecordingPhase::Starting; + self.recording_error = None; + cx.notify(); + + // The engine lives on tokio (kameo actors + the capture pipeline); + // gpui_tokio bridges its completion back into this entity. + let task = gpui_tokio::Tokio::spawn(cx, recording::start(config)); + cx.spawn_in(window, async move |this, cx| { + let result = task.await; + this.update_in(cx, |this, _window, cx| { + match result { + Ok(Ok(active)) => { + tracing::info!(dir = %active.project_dir.display(), "recording started"); + this.active_recording = Some(active); + this.recording_phase = RecordingPhase::Recording; + } + Ok(Err(error)) => { + tracing::error!("recording failed to start: {error:#}"); + this.recording_error = Some(format!("{error:#}")); + this.recording_phase = RecordingPhase::Idle; + } + Err(join_error) => { + tracing::error!("recording start task died: {join_error}"); + this.recording_error = Some("Recording task failed.".into()); + this.recording_phase = RecordingPhase::Idle; + } + } + cx.notify(); + }) + .ok(); + }) + .detach(); + } + + pub fn stop_recording(&mut self, window: &mut Window, cx: &mut Context) { + if self.recording_phase != RecordingPhase::Recording { + return; + } + let Some(active) = self.active_recording.take() else { + return; + }; + self.recording_phase = RecordingPhase::Stopping; + cx.notify(); + + let task = gpui_tokio::Tokio::spawn(cx, active.stop()); + cx.spawn_in(window, async move |this, cx| { + let result = task.await; + this.update_in(cx, |this, _window, cx| { + match result { + Ok(Ok(project_dir)) => { + tracing::info!(dir = %project_dir.display(), "recording finished"); + // v1 completion affordance until the editor window and + // Recents thumbnails exist: reveal the project. + #[cfg(target_os = "macos")] + { + let _ = std::process::Command::new("open") + .arg("-R") + .arg(&project_dir) + .spawn(); + } + } + Ok(Err(error)) => { + tracing::error!("recording failed to stop cleanly: {error:#}"); + this.recording_error = Some(format!("{error:#}")); + } + Err(join_error) => { + tracing::error!("recording stop task died: {join_error}"); + this.recording_error = Some("Stop task failed.".into()); + } + } + this.recording_phase = RecordingPhase::Idle; + cx.notify(); + }) + .ok(); + }) + .detach(); + } } impl Render for MainWindow { @@ -344,6 +551,9 @@ impl Render for MainWindow { .text_color(theme.text_primary) .child(self.render_header(window, cx)) .child(self.render_body(cx)) + .when(self.recording_phase != RecordingPhase::Idle, |this| { + this.child(self.render_recording_overlay(cx)) + }) } } @@ -495,25 +705,147 @@ impl MainWindow { // the full body, exactly as `!activeMenu() && ...` does in index.tsx. match self.panel { Some(panel) => root.child(self.render_panel(panel, cx)), - None => root.child(self.render_logo_row(cx)).child( - // `flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto pb-1 - // w-full` -- expanded overflows 660px once Recents is in, so - // this column has to scroll. + None => root + .child(self.render_logo_row(cx)) + .child( + // `flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto pb-1 + // w-full` -- expanded overflows 660px once Recents is in, so + // this column has to scroll. + div() + .id("home-scroll") + .flex() + .flex_col() + .flex_1() + .min_h_0() + .w_full() + .pb(px(4.)) + .gap(px(8.)) + .overflow_y_scroll() + .child(self.render_targets(cx)) + .child(self.render_base_controls(cx)) + .when(self.expanded, |this| this.child(self.render_recents())), + ) + .when(self.can_start_recording(), |this| { + this.child(self.render_start_footer(cx)) + }), + } + } + + /// TEMP: the real app has no start button in the main window -- clicking a + /// target tile opens the fullscreen target-select overlay, which owns + /// "Start Recording". Until that overlay window exists, an armed target + /// gets a pinned footer button here so the recorder is drivable at all. + fn can_start_recording(&self) -> bool { + self.recording_phase == RecordingPhase::Idle + && self.recording_mode().is_some() + && self.armed_target().is_some() + } + + fn render_start_footer(&self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + + div() + .flex() + .flex_col() + .gap(px(4.)) + .flex_shrink_0() + .when_some(self.recording_error.clone(), |this, error| { + this.child( + div() + .text_size(px(11.)) + .text_color(theme.red_9) + .text_center() + .child(error), + ) + }) + .child( div() - .id("home-scroll") + .id("start-recording") + .h(px(44.)) + .w_full() + .flex_shrink_0() .flex() - .flex_col() - .flex_1() - .min_h_0() + .flex_row() + .items_center() + .justify_center() + .gap(px(8.)) + .rounded(px(12.)) + .bg(theme.blue_500) + .hover(|style| style.opacity(0.9)) + .text_size(px(14.)) + .font_weight(FontWeight::MEDIUM) + .text_color(gpui::white()) + .child( + svg() + .path("icons/instant.svg") + .size(px(16.)) + .text_color(gpui::white()), + ) + .child("Start Recording") + .on_click(cx.listener(|this, _, window, cx| { + this.start_recording(window, cx); + })), + ) + } + + /// The recording takeover, from index.tsx 3510-3529: an absolute overlay + /// (`bg-gray-1/80 backdrop-blur-xs` -- the blur is skipped here, this gpui + /// rev has no per-element backdrop blur hook) with the Stop button pinned + /// to the bottom (`px-6 pb-8`, `h-11 rounded-xl bg-red-9`). + fn render_recording_overlay(&self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let stopping = self.recording_phase == RecordingPhase::Stopping; + let starting = self.recording_phase == RecordingPhase::Starting; + + let mut wash: Hsla = theme.gray_1.into(); + wash.a = 0.8; + + div() + .absolute() + .inset_0() + .rounded(px(16.)) + .flex() + .flex_col() + .justify_end() + .px(px(24.)) + .pb(px(32.)) + .bg(wash) + .child( + div() + .id("stop-recording") + .h(px(44.)) .w_full() - .pb(px(4.)) + .flex() + .flex_row() + .items_center() + .justify_center() .gap(px(8.)) - .overflow_y_scroll() - .child(self.render_targets(cx)) - .child(self.render_base_controls(cx)) - .when(self.expanded, |this| this.child(self.render_recents())), - ), - } + .rounded(px(12.)) + .bg(theme.red_9) + .text_size(px(14.)) + .font_weight(FontWeight::MEDIUM) + .text_color(gpui::white()) + // `disabled:opacity-60` while pending. + .when(stopping || starting, |this| this.opacity(0.6)) + .when(!stopping && !starting, |this| { + this.hover(|style| style.bg(theme.red_10)).on_click(cx.listener( + |this, _, window, cx| { + this.stop_recording(window, cx); + }, + )) + }) + .child( + svg() + .path("icons/stop-circle.svg") + .size(px(16.)) + .text_color(gpui::white()), + ) + .child(match self.recording_phase { + RecordingPhase::Starting => "Starting...", + RecordingPhase::Stopping => "Stopping...", + _ => "Stop Recording", + }), + ) } /// Shared panel chrome: a Back button, then either a title or a search From 2b74bd318080c5340a92e188a3476fac31c96993 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:30:51 +0100 Subject: [PATCH 012/146] fix(desktop-gpui): embed stop-circle.svg (caught by the asset round-trip test) --- apps/desktop-gpui/src/assets.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/desktop-gpui/src/assets.rs b/apps/desktop-gpui/src/assets.rs index 9dd2a957f3..90e960a028 100644 --- a/apps/desktop-gpui/src/assets.rs +++ b/apps/desktop-gpui/src/assets.rs @@ -54,6 +54,7 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": "scan-text.svg", "screen.svg", "screenshot.svg", + "stop-circle.svg", "search.svg", "settings.svg", "window.svg", From c9c11dd3dfac31df77dba551d73bf4cfb6ef17fc Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:30:51 +0100 Subject: [PATCH 013/146] docs(desktop-gpui): recording section, deviations, screenshots --- apps/desktop-gpui/README.md | 38 +++++++++++++++++-- apps/desktop-gpui/docs/recording-armed.png | Bin 0 -> 80532 bytes apps/desktop-gpui/docs/recording-overlay.png | Bin 0 -> 80994 bytes 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 apps/desktop-gpui/docs/recording-armed.png create mode 100644 apps/desktop-gpui/docs/recording-overlay.png diff --git a/apps/desktop-gpui/README.md b/apps/desktop-gpui/README.md index 55762967fb..4729d1e720 100644 --- a/apps/desktop-gpui/README.md +++ b/apps/desktop-gpui/README.md @@ -103,7 +103,7 @@ Sizes are the Tauri app's, from `apps/desktop/src-tauri/src/windows.rs`. | Window | Size | Status | |---|---|---| -| Main | 330×395 / 600×660 | **Done** — layout, devices, pickers, modes | +| Main | 330×395 / 600×660 | **Done** — layout, devices, pickers, modes, recording | | Camera preview | 460×460 | Not started — needs the camera feed | | Recording controls | 320×150 | Not started — needs a recording session | | Target select overlay | per display | Not started — one transparent window per display | @@ -118,8 +118,40 @@ Sizes are the Tauri app's, from `apps/desktop/src-tauri/src/windows.rs`. | Editor | 1275×800 | Not started — by far the largest | | Screenshot editor | 1240×800 (min 800×600) | Not started | -Nothing here records yet. Milestone 1 is the window; wiring the buttons to -`cap-recording` is milestone 2. +## Recording + +The app records for real, through the same `cap-recording` actors the Tauri +app drives — studio and instant, screen + microphone, written into the same +recordings library (`recordingsPath` from the Tauri settings store, falling +back to `/recordings`; `CAP_GPUI_RECORDINGS_DIR` overrides both for +tests). Studio projects are finalized with `RecoveryManager::remux_if_needed`, +instant projects get `content/output.mp4` plus the +`recording-meta.json`/`project-config.json` pair, mirroring the CLI's +`finalize_completed` — a project recorded here exports cleanly with +`cap export`. + +Recording-specific deviations: + +| | | +|---|---| +| **TEMP start button** | The real app starts recording from the fullscreen target-select overlay, which does not exist yet. An armed target (Display selected, or a concrete window picked) shows a pinned blue Start Recording footer instead. | +| **Area does not arm** | Area needs the selection overlay. | +| **Per-recording feed actors** | The Tauri app keeps app-wide `MicrophoneFeed`/`CameraFeed` actors for previews and level meters; here they are spawned per recording and dropped after. Moves to app scope with the camera preview window. | +| **No overlay blur** | The recording overlay is `bg-gray-1/80` without `backdrop-blur-xs`; this gpui rev has no per-element backdrop blur hook. | +| **Camera id is DeviceID-only** | The Tauri app persists `ModelID` when a camera advertises one, so the same camera survives re-plugging into a different port. | +| **Defaults are the builder's** | `desktop_recording_defaults` (studio quality, fps caps, custom cursor) is not applied yet — it reads the Tauri settings store. | + +`CAP_GPUI_AUTO_RECORD=studio:5` (or `instant:4`) arms the primary display and +drives a start/stop through the button code paths — the end-to-end check uses +it because unprivileged synthetic clicks are dropped. + +Running from a dev build needs the ffmpeg dylibs the binary's install names +point at (`@executable_path/../Frameworks/Spacedrive.framework/...`): + +```sh +mkdir -p target/Frameworks +ln -sfn "$(pwd)/../../target/native-deps/Spacedrive.framework" target/Frameworks/Spacedrive.framework +``` ## Verifying changes diff --git a/apps/desktop-gpui/docs/recording-armed.png b/apps/desktop-gpui/docs/recording-armed.png new file mode 100644 index 0000000000000000000000000000000000000000..de7e6d9b5c6f3888a3cb6317443a676effaa2f39 GIT binary patch literal 80532 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

    gJzMUjlC&XY#>QZG-Ma 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

    Iz4F+x^ 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

    SYpE#u%1)_b|s9c{c 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

    b*;O>vWLcw#a-6f|;JYozoM^9MuE7$R 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 z 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

    5Z>am_>j&ETk?ib00pWq;D2U5wf~7>rT;nf|DIu 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%0D7>>;5=cKkNhc= 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 M07*qoM6N<$g47WNmH+?% literal 0 HcmV?d00001 diff --git a/apps/desktop-gpui/docs/camera-square.png b/apps/desktop-gpui/docs/camera-square.png new file mode 100644 index 0000000000000000000000000000000000000000..c03421baf51965520a5e95dbee29a4e6a36f6a82 GIT binary patch literal 278822 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 pAJlhehQj<{p#okY+KavKcxo`2McuJ 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%0A6eoh6&@ZkIF-~P5ubAK%Pf45gZD#+~CH49rc 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�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 R?Fs+@002ovPDHLkV1h!#^^gDn literal 0 HcmV?d00001 diff --git a/apps/desktop-gpui/docs/controls-bar-live-timer.png b/apps/desktop-gpui/docs/controls-bar-live-timer.png new file mode 100644 index 0000000000000000000000000000000000000000..ea0da56704dd3a301ed3fa6f577df1e228022b7a GIT binary patch literal 20274 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 mU;l<#_- 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 Jmvv4FO#mrW_M-p* literal 0 HcmV?d00001 From dcf05ae2f416c02aacdc5e3762293cdc33705d3c Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:30:38 +0100 Subject: [PATCH 019/146] feat(desktop-gpui): target-select overlays with the native start flow One transparent non-activating panel per display at the Tauri-verbatim level 7 (CGWindowLevelForKey(10) - 1), translated from target-select-overlay.tsx: the display variant's hover wash and monitor art, the window variant's cursor-following highlight with click-to-pin and app icons, the area variant's draw/move/resize crop with eight handles and min-size validation, and the camera-only variant. The overlays own Start Recording: starting closes them, opens the bar, hides the main window, and excludes the overlay windows from capture. An app-scoped TargetSelect entity polls the cursor's display and topmost window on the background executor (the Tauri app's 50ms tokio loop, at 80ms) and repaints every overlay explicitly, since none of them is the active window. Two platform findings shipped alongside: gpui windows all carry NSTitledWindowMask, so AppKit's constrainFrameRect: pushes a display-covering window below the menu bar - a shim on GPUIWindow and GPUIPanel returns the rect unchanged, the same override tao gives the Tauri app; and setFrame:/orderFrontRegardless synchronously re-enter gpui's window callbacks, so placement runs from a spawned task instead of inside a window update (inside one it logs 'RefCell already borrowed' and drops the callback). Verified: all four variants screenshotted; overlay probed at CG layer 7 covering 0,0 1512x982; a seeded 800x500 area crop recorded a 1600x1000 display track (ffprobe); CAP_GPUI_AUTO_OVERLAY / CAP_GPUI_AUTO_AREA document the harness. --- apps/desktop-gpui/Cargo.lock | 1 + apps/desktop-gpui/Cargo.toml | 3 + apps/desktop-gpui/assets/icons/caret-down.svg | 3 + apps/desktop-gpui/assets/icons/gear.svg | 12 + apps/desktop-gpui/assets/icons/monitor.svg | 45 + apps/desktop-gpui/assets/icons/x.svg | 3 + apps/desktop-gpui/src/app_windows.rs | 324 +++- apps/desktop-gpui/src/assets.rs | 5 + apps/desktop-gpui/src/main.rs | 10 + apps/desktop-gpui/src/main_window.rs | 274 +++- apps/desktop-gpui/src/platform.rs | 139 ++ apps/desktop-gpui/src/target_overlay.rs | 1457 +++++++++++++++++ apps/desktop-gpui/src/theme.rs | 12 + 13 files changed, 2211 insertions(+), 77 deletions(-) create mode 100644 apps/desktop-gpui/assets/icons/caret-down.svg create mode 100644 apps/desktop-gpui/assets/icons/gear.svg create mode 100644 apps/desktop-gpui/assets/icons/monitor.svg create mode 100644 apps/desktop-gpui/assets/icons/x.svg create mode 100644 apps/desktop-gpui/src/target_overlay.rs diff --git a/apps/desktop-gpui/Cargo.lock b/apps/desktop-gpui/Cargo.lock index 530f6f5403..56613f5a7b 100644 --- a/apps/desktop-gpui/Cargo.lock +++ b/apps/desktop-gpui/Cargo.lock @@ -1297,6 +1297,7 @@ dependencies = [ "kameo", "objc2 0.5.2", "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", "raw-window-handle", "scap-targets", "serde", diff --git a/apps/desktop-gpui/Cargo.toml b/apps/desktop-gpui/Cargo.toml index fa87f0e670..8576d98958 100644 --- a/apps/desktop-gpui/Cargo.toml +++ b/apps/desktop-gpui/Cargo.toml @@ -62,6 +62,9 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } # versions gpui's own tree compiles, so no second objc2 universe gets built. objc2 = "0.5.2" objc2-app-kit = { version = "0.2.2", features = ["NSWindow", "NSView", "NSResponder", "NSGraphics", "NSPasteboard"] } +# NSRect/NSPoint/NSSize for `setFrame:display:`. Already in the tree as +# objc2-app-kit's own dependency, so this adds nothing to the build. +objc2-foundation = "0.2.2" raw-window-handle = "0.6" # Camera preview frames become gpui `RenderImage`s; the frame container type # must match gpui's image crate version exactly (`image = "0.25.1"` in the zed diff --git a/apps/desktop-gpui/assets/icons/caret-down.svg b/apps/desktop-gpui/assets/icons/caret-down.svg new file mode 100644 index 0000000000..0a4bb6787a --- /dev/null +++ b/apps/desktop-gpui/assets/icons/caret-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/gear.svg b/apps/desktop-gpui/assets/icons/gear.svg new file mode 100644 index 0000000000..152b22dd61 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/gear.svg @@ -0,0 +1,12 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/monitor.svg b/apps/desktop-gpui/assets/icons/monitor.svg new file mode 100644 index 0000000000..3687f88b1a --- /dev/null +++ b/apps/desktop-gpui/assets/icons/monitor.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/desktop-gpui/assets/icons/x.svg b/apps/desktop-gpui/assets/icons/x.svg new file mode 100644 index 0000000000..ebb57ec9be --- /dev/null +++ b/apps/desktop-gpui/assets/icons/x.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/src/app_windows.rs b/apps/desktop-gpui/src/app_windows.rs index 51f78a6cc6..057a70b2d0 100644 --- a/apps/desktop-gpui/src/app_windows.rs +++ b/apps/desktop-gpui/src/app_windows.rs @@ -7,18 +7,21 @@ //! "Starting" state from t=0), and the observer closes the bar and reshows the //! main window whenever the session comes back to rest. +use cap_recording::sources::screen_capture::ScreenCaptureTarget; use gpui::{ App, AppContext as _, Bounds, Entity, Global, WindowBounds, WindowHandle, WindowKind, WindowOptions, point, px, size, }; +use scap_targets::DisplayId; use crate::{ camera_window::{self, CameraWindow}, controls_window::ControlsWindow, - main_window::MainWindow, + main_window::{MainWindow, Mode, TargetType}, platform, recording::{RecordingMode, StartConfig}, session::{Phase, RecordingSession}, + target_overlay::{AreaRect, HoveredWindow, OverlayWindow, TargetSelect}, }; /// Matches the Tauri `InProgressRecording` window and @@ -32,6 +35,9 @@ pub struct AppWindows { pub main: WindowHandle, pub controls: Option>, pub camera: Option>, + /// One target-select overlay per display, keyed by display so a mode + /// switch can keep the ones it still wants. + pub overlays: Vec<(DisplayId, WindowHandle)>, } impl Global for AppWindows {} @@ -43,6 +49,7 @@ pub fn init(main: WindowHandle, session: Entity, c main, controls: None, camera: None, + overlays: Vec::new(), }); let mut last_phase = Phase::Idle; @@ -59,6 +66,317 @@ pub fn init(main: WindowHandle, session: Entity, c .detach(); } +/// What the main window is asking the overlays to show. +#[derive(Clone)] +pub struct OverlayRequest { + pub mode: TargetType, + pub recording_mode: Mode, + /// Restrict the overlays to one display. The Tauri command takes the same + /// two narrowing inputs (`focused_target`, `specific_display_id`): a + /// display picked from the main window's dropdown, or the display the + /// picked window lives on. + pub display: Option, + /// A window picked in the main window: the overlay pins its highlight to + /// that window instead of following the cursor, the way the Tauri poll + /// substitutes `focused_target`'s window for `get_topmost_at_cursor`. + pub pinned_window: Option, +} + +/// Everything the overlay needs about a pinned window, resolved once when the +/// overlays open. Cheap enough for the main thread (one window-list walk); the +/// cursor probe does the same work on the background executor because it does +/// it every 80ms. +fn resolve_window(id: &scap_targets::WindowId) -> Option { + HoveredWindow::from_window(&scap_targets::Window::from_id(id)?) +} + +/// Open (or re-target) the fullscreen overlays. +/// +/// A mode change tears the old windows down first: the overlay carries +/// per-mode state -- an area selection above all -- and re-opening is both +/// cheaper to reason about and what the Tauri flow does (the webviews are +/// recreated with a new `targetMode` query parameter). +pub fn open_target_overlays(request: OverlayRequest, cx: &mut App) { + let select = TargetSelect::global(cx); + let mode_changed = select.read(cx).mode != Some(request.mode); + if mode_changed { + close_overlay_windows(cx); + } + + let pinned = request.pinned_window.as_ref().and_then(resolve_window); + let display = request + .display + .clone() + .or_else(|| pinned.as_ref().map(|window| window.display_id.clone())); + + select.update(cx, |select, cx| { + select.arm(Some(request.mode), request.recording_mode, pinned, cx) + }); + + let displays = match &display { + Some(id) => scap_targets::Display::from_id(id) + .map(|display| vec![display]) + .unwrap_or_default(), + None => scap_targets::Display::list(), + }; + let displays = if displays.is_empty() { + vec![scap_targets::Display::primary()] + } else { + displays + }; + let wanted: Vec = displays.iter().map(|display| display.id()).collect(); + + // Drop overlays for displays this request no longer covers. + let stale: Vec<_> = cx + .global::() + .overlays + .iter() + .filter(|(id, _)| !wanted.contains(id)) + .map(|(id, _)| id.clone()) + .collect(); + for id in stale { + close_overlay(&id, cx); + } + + let focus_display = scap_targets::Display::get_containing_cursor() + .map(|display| display.id()) + .filter(|id| wanted.contains(id)) + .or_else(|| wanted.first().cloned()); + + for display in displays { + let id = display.id(); + if cx + .global::() + .overlays + .iter() + .any(|(existing, _)| existing == &id) + { + continue; + } + open_overlay(&display, select.clone(), Some(&id) == focus_display.as_ref(), cx); + } +} + +/// Close the overlays and clear the main window's armed target -- Escape, the +/// overlay's own close button, or the main window toggling the mode off. +pub fn dismiss_target_overlays(cx: &mut App) { + close_target_overlays(cx); + let main = cx.global::().main; + main.update(cx, |view, _window, cx| view.clear_target(cx)).ok(); +} + +/// Close the overlays and disarm the cursor probe, leaving the main window's +/// selection alone. +pub fn close_target_overlays(cx: &mut App) { + close_overlay_windows(cx); + let select = TargetSelect::global(cx); + select.update(cx, |select, cx| { + let recording_mode = select.recording_mode; + select.arm(None, recording_mode, None, cx); + }); +} + +/// Repaint every overlay. The cursor probe runs while none of them is the +/// active window, and an inactive window only repaints when told to. +pub fn refresh_target_overlays(cx: &mut App) { + let handles: Vec<_> = cx + .global::() + .overlays + .iter() + .map(|(_, handle)| *handle) + .collect(); + for handle in handles { + handle + .update(cx, |_, window, cx| { + cx.notify(); + window.refresh(); + }) + .ok(); + } +} + +/// The overlays' native window numbers, for capture exclusion. +fn overlay_window_ids(cx: &mut App) -> Vec { + let handles: Vec<_> = cx + .global::() + .overlays + .iter() + .map(|(_, handle)| *handle) + .collect(); + handles + .into_iter() + .filter_map(|handle| { + handle + .update(cx, |_, window, _| platform::window_number(window)) + .ok() + .flatten() + }) + .filter_map(|number| number.to_string().parse().ok()) + .collect() +} + +/// The overlay's Start button: overlays down, then the main window's start +/// path with the target the overlay resolved. +pub fn start_recording_from_overlay(target: ScreenCaptureTarget, cx: &mut App) { + // Collected before the windows go away: the capture starts a beat after + // this, and an overlay that has not finished closing must not end up in + // the recording. + let excluded = overlay_window_ids(cx); + close_target_overlays(cx); + + let main = cx.global::().main; + main.update(cx, |view, window, cx| { + view.start_recording_with_target(target, excluded, window, cx) + }) + .ok(); +} + +/// Seed an area selection on the overlay for a display (harness path -- the +/// real one is a mouse drag, and unprivileged synthetic drags are dropped). +pub fn seed_area_selection(display: Option, crop: AreaRect, cx: &mut App) -> bool { + let Some(handle) = overlay_handle(display, cx) else { + return false; + }; + handle + .update(cx, |view, _window, cx| view.set_crop(crop, cx)) + .is_ok() +} + +/// Drive an overlay's Start button (harness path). +pub fn start_from_overlay(display: Option, cx: &mut App) -> bool { + let Some(handle) = overlay_handle(display, cx) else { + return false; + }; + handle + .update(cx, |view, window, cx| view.start_recording(window, cx)) + .is_ok() +} + +fn overlay_handle( + display: Option, + cx: &App, +) -> Option> { + let overlays = &cx.global::().overlays; + match display { + Some(id) => overlays + .iter() + .find(|(existing, _)| existing == &id) + .map(|(_, handle)| *handle), + None => overlays.first().map(|(_, handle)| *handle), + } +} + +fn open_overlay( + display: &scap_targets::Display, + select: Entity, + focus: bool, + cx: &mut App, +) { + let Some(bounds) = display.raw_handle().logical_bounds() else { + tracing::error!("display has no logical bounds; overlay not opened"); + return; + }; + let width = bounds.size().width(); + let height = bounds.size().height(); + + let handle = cx.open_window( + WindowOptions { + // The origin is corrected right after opening -- gpui's own + // window-origin math cannot express "cover this display" (see + // `platform::set_window_frame_cg`). The size is honoured, and it is + // the size the renderer is built for. + window_bounds: Some(WindowBounds::Windowed(Bounds { + origin: point(px(0.), px(0.)), + size: size(px(width as f32), px(height as f32)), + })), + titlebar: None, + // `NSWindowStyleMaskNonActivatingPanel` in windows.rs: the overlay + // takes clicks without activating the app over the one being + // recorded. + kind: WindowKind::PopUp, + focus: false, + // Shown once it has been moved onto its display, so it never + // paints a frame in the wrong place. + show: false, + is_resizable: false, + is_minimizable: false, + window_background: gpui::WindowBackgroundAppearance::Transparent, + ..Default::default() + }, + { + let display = *display; + move |window, cx| cx.new(|cx| OverlayWindow::new(&display, select, window, cx)) + }, + ); + + match handle { + Ok(handle) => { + cx.global_mut::() + .overlays + .push((display.id(), handle)); + // Only *read* the native handle while the App is borrowed; the + // AppKit calls that move, raise and show the panel synchronously + // re-enter gpui's own window callbacks, so they run from a fresh + // runloop turn where nothing is borrowed (see + // `platform::place_overlay_panel`). + let native = handle + .update(cx, |_, window, _| platform::native_window(window)) + .ok() + .flatten(); + let (x, y) = (bounds.position().x(), bounds.position().y()); + cx.spawn(async move |cx| { + if let Some(native) = &native { + platform::place_overlay_panel( + native, + x, + y, + width, + height, + platform::target_overlay_level(), + ); + } + handle + .update(cx, |view, window, cx| { + // Restart the macOS 26 display link now that the + // window is visible (the unit-3 finding). + platform::kick_display_link(window); + if focus { + // Escape is a key handler on the overlay here + // rather than the Tauri app's global shortcut, so + // one overlay has to hold focus for it to arrive. + let focus_handle = view.focus_handle(); + window.focus(&focus_handle, cx); + } + window.refresh(); + }) + .ok(); + }) + .detach(); + } + Err(error) => tracing::error!("target select overlay failed to open: {error:#}"), + } +} + +fn close_overlay(id: &DisplayId, cx: &mut App) { + let overlays = &mut cx.global_mut::().overlays; + let Some(index) = overlays.iter().position(|(existing, _)| existing == id) else { + return; + }; + let (_, handle) = overlays.remove(index); + handle + .update(cx, |_, window, _| window.remove_window()) + .ok(); +} + +fn close_overlay_windows(cx: &mut App) { + let overlays = std::mem::take(&mut cx.global_mut::().overlays); + for (_, handle) in overlays { + handle + .update(cx, |_, window, _| window.remove_window()) + .ok(); + } +} + /// The whole start flow: bar up (in its Starting state), main window hidden, /// engine started with the bar excluded from capture. pub fn begin_recording(config: StartConfig, cx: &mut App) { @@ -67,8 +385,8 @@ pub fn begin_recording(config: StartConfig, cx: &mut App) { return; } - let mut excluded: Vec = - open_controls(&config, session.clone(), cx).into_iter().collect(); + let mut excluded: Vec = config.excluded_windows.clone(); + excluded.extend(open_controls(&config, session.clone(), cx)); // The camera bubble is excluded from studio captures (the camera is its own // track, composited in the editor) but *included* in instant captures -- // `filter_for_instant_mode` in the Tauri app strips the camera exclusion diff --git a/apps/desktop-gpui/src/assets.rs b/apps/desktop-gpui/src/assets.rs index 56969fc07b..428ac96fed 100644 --- a/apps/desktop-gpui/src/assets.rs +++ b/apps/desktop-gpui/src/assets.rs @@ -36,6 +36,7 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": "arrows.svg", "bell.svg", "camera.svg", + "caret-down.svg", "circle.svg", "check.svg", "chevron-down.svg", @@ -43,6 +44,7 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": "circle-x.svg", "enlarge.svg", "film-cut.svg", + "gear.svg", "history.svg", "image.svg", "info.svg", @@ -52,6 +54,7 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": "mic-off.svg", "microphone.svg", "minimize.svg", + "monitor.svg", "more-vertical.svg", "move-left.svg", "pause-circle.svg", @@ -68,6 +71,7 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": "search.svg", "settings.svg", "window.svg", + "x.svg", ); pub struct Assets; @@ -119,6 +123,7 @@ mod tests { include_str!("main_window.rs"), include_str!("controls_window.rs"), include_str!("camera_window.rs"), + include_str!("target_overlay.rs"), ]; /// Every icon a window asks for must be in the table. gpui draws diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index bcb3dd9cf0..59bb2844be 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -14,6 +14,7 @@ mod platform; mod recording; mod session; mod store; +mod target_overlay; mod theme; use gpui::{App, AppContext as _, Bounds, WindowBounds, WindowOptions, px, size}; @@ -57,6 +58,7 @@ fn main() { // and the window is fixed at that size there too. let session = RecordingSession::init(cx); crate::feeds::Feeds::init(cx); + crate::target_overlay::TargetSelect::init(cx); let bounds = Bounds::centered( None, @@ -129,6 +131,14 @@ fn main() { view.auto_record(mode, secs, window, cx) }) .expect("failed to arm auto-record"); + } else if let Some(kind) = main_window::auto_overlay_kind() { + // `CAP_GPUI_AUTO_OVERLAY` without a recording: put the overlays up + // and leave them there, which is how they get screenshotted. + window_handle + .update(cx, |view, window, cx| { + view.auto_open_overlay(kind, window, cx) + }) + .expect("failed to arm the target overlay"); } cx.activate(true); }); diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index 6cef1903b6..3a5c55e5e0 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -376,10 +376,49 @@ impl MainWindow { } } + /// Bring the target-select overlays in line with the armed target. + /// + /// Deferred, because opening a window inside an entity update paints it + /// synchronously and double-leases this very view. This mirrors + /// `toggleTargetMode` / `selectDisplayTarget` / `selectWindowTarget` in + /// the Tauri main window, which each call `openTargetSelectOverlays` (or + /// `closeTargetSelectOverlays`) right after setting the mode. + fn sync_overlays(&self, cx: &mut Context) { + let Some(mode) = self.target else { + cx.defer(app_windows::close_target_overlays); + return; + }; + let request = app_windows::OverlayRequest { + mode, + recording_mode: self.mode, + // A display picked from the dropdown narrows the overlays to that + // display; otherwise every display gets one. + display: match mode { + TargetType::Display => self + .selected_display + .as_ref() + .map(|display| display.id.clone()), + _ => None, + }, + pinned_window: match mode { + TargetType::Window => self.selected_window.as_ref().map(|window| window.id.clone()), + _ => None, + }, + }; + cx.defer(move |cx: &mut gpui::App| app_windows::open_target_overlays(request, cx)); + } + + /// Called when the overlays are dismissed (Escape, their close button) so + /// the armed tile stops looking armed. + pub fn clear_target(&mut self, cx: &mut Context) { + if self.target.take().is_some() { + cx.notify(); + } + } + /// The concrete capture target the current UI state describes, or `None` /// when starting makes no sense yet (no target mode, Window mode with no - /// window picked, Area mode -- which needs the selection overlay that does - /// not exist yet). + /// window picked, Area mode -- which is drawn on the overlay). fn armed_target(&self) -> Option { match self.target? { TargetType::Display => { @@ -416,8 +455,14 @@ impl MainWindow { /// Dev-only end-to-end driver (`CAP_GPUI_AUTO_RECORD=studio:5`): synthetic /// clicks are dropped without Accessibility permission, so the automated - /// check arms the primary display and drives start/stop through the same - /// methods the buttons call. + /// check arms a target and drives start/stop through the same methods the + /// buttons call. + /// + /// `CAP_GPUI_AUTO_OVERLAY=display|window|area` routes the start through the + /// target-select overlay instead of straight off this window -- + /// `CAP_GPUI_AUTO_AREA=x,y,w,h` seeds the crop the drag would have drawn, + /// and the window variant pins the first enumerated window the way picking + /// one from the dropdown does. pub fn auto_record( &mut self, mode: Mode, @@ -425,8 +470,11 @@ impl MainWindow { window: &mut Window, cx: &mut Context, ) { + let overlay = auto_overlay_kind(); + let area = auto_area_rect(); + self.mode = mode; - self.target = Some(TargetType::Display); + self.target = Some(overlay.unwrap_or(TargetType::Display)); cx.notify(); // `CAP_GPUI_AUTO_PAUSE=1`: wiggle pause/resume in the middle third, so @@ -481,10 +529,38 @@ impl MainWindow { cx.background_executor() .timer(std::time::Duration::from_millis(1500)) .await; - if this - .update_in(cx, |this, window, cx| this.start_recording(window, cx)) - .is_err() - { + + let started = match overlay { + None => this + .update_in(cx, |this, window, cx| this.start_recording(window, cx)) + .is_ok(), + Some(kind) => { + // The overlay route: arm the mode (which opens the + // overlays), let them come up, seed what a drag or a hover + // would have produced, then press their Start button. + if this + .update_in(cx, |this, _window, cx| this.arm_overlay(kind, cx)) + .is_err() + { + return; + } + cx.background_executor() + .timer(std::time::Duration::from_millis(1500)) + .await; + if let Some(area) = area { + tracing::info!(?area, "seeding area selection"); + cx.update(|_, cx| app_windows::seed_area_selection(None, area, cx)) + .ok(); + cx.background_executor() + .timer(std::time::Duration::from_millis(300)) + .await; + } + cx.update(|_, cx| app_windows::start_from_overlay(None, cx)) + .unwrap_or(false) + } + }; + if !started { + tracing::error!("auto-record could not start"); return; } @@ -516,17 +592,79 @@ impl MainWindow { .detach(); } + /// Arm a target mode the way clicking its tile does, picking a concrete + /// window for the window variant since the harness cannot hover one. + fn arm_overlay(&mut self, kind: TargetType, cx: &mut Context) { + if kind == TargetType::Window { + self.selected_window = self.devices.windows.first().cloned(); + if let Some(window) = &self.selected_window { + tracing::info!(app = %window.app, title = %window.label, "auto window target"); + } + } + self.target = Some(kind); + self.sync_overlays(cx); + cx.notify(); + } + + /// `CAP_GPUI_AUTO_OVERLAY` on its own (no `CAP_GPUI_AUTO_RECORD`): open the + /// overlays and leave them up, which is how the screenshots are taken. + pub fn auto_open_overlay( + &mut self, + kind: TargetType, + window: &mut Window, + cx: &mut Context, + ) { + let crop = auto_area_rect(); + cx.spawn_in(window, async move |this, cx| { + // Enumeration first: the window variant pins a real window. + cx.background_executor() + .timer(std::time::Duration::from_secs(2)) + .await; + if this + .update_in(cx, |this, _window, cx| this.arm_overlay(kind, cx)) + .is_err() + { + return; + } + if let Some(crop) = crop { + cx.background_executor() + .timer(std::time::Duration::from_millis(1500)) + .await; + cx.update(|_, cx| app_windows::seed_area_selection(None, crop, cx)) + .ok(); + } + }) + .detach(); + } + + /// Start with the target the UI state describes. The overlays own the real + /// start affordance now; this is the harness path + /// (`CAP_GPUI_AUTO_RECORD` without `CAP_GPUI_AUTO_OVERLAY`). + pub fn start_recording(&mut self, window: &mut Window, cx: &mut Context) { + let Some(target) = self.armed_target() else { + return; + }; + self.start_recording_with_target(target, Vec::new(), window, cx); + } + /// Collect the UI state into a start config and hand it to the /// orchestrator, which opens the controls bar, hides this window, and /// starts the engine. Deferred because the orchestrator updates this very /// window (hide), which would re-enter the update we are inside of. - pub fn start_recording(&mut self, _window: &mut Window, cx: &mut Context) { + /// + /// The target comes in rather than being read off this view: it is the + /// overlay that knows which display was clicked, which window is under the + /// cursor, or what area was drawn. + pub fn start_recording_with_target( + &mut self, + target: ScreenCaptureTarget, + excluded_windows: Vec, + _window: &mut Window, + cx: &mut Context, + ) { if self.session.read(cx).phase != Phase::Idle { return; } - let Some(target) = self.armed_target() else { - return; - }; let Some(mode) = self.recording_mode() else { return; }; @@ -552,7 +690,7 @@ impl MainWindow { .as_ref() .map(|camera| recording::DeviceOrModelID::DeviceID(camera.device_id.clone())), system_audio: self.system_audio, - excluded_windows: Vec::new(), + excluded_windows, camera_feed, mic_feed, }; @@ -759,69 +897,28 @@ impl MainWindow { .child(self.render_base_controls(cx)) .when(self.expanded, |this| this.child(self.render_recents())), ) - .when(self.can_start_recording(cx), |this| { - this.child(self.render_start_footer(cx)) - }), + // A failed start has nowhere else to surface: the overlays are + // gone by then and the bar closed itself. + .when_some( + self.session + .read(cx) + .error + .clone() + .filter(|_| self.session.read(cx).phase == Phase::Idle), + |this, error| { + this.child( + div() + .flex_shrink_0() + .text_size(px(11.)) + .text_color(self.theme.red_9) + .text_center() + .child(error), + ) + }, + ), } } - /// TEMP: the real app has no start button in the main window -- clicking a - /// target tile opens the fullscreen target-select overlay, which owns - /// "Start Recording". Until that overlay window exists, an armed target - /// gets a pinned footer button here so the recorder is drivable at all. - fn can_start_recording(&self, cx: &Context) -> bool { - self.session.read(cx).phase == Phase::Idle - && self.recording_mode().is_some() - && self.armed_target().is_some() - } - - fn render_start_footer(&self, cx: &mut Context) -> impl IntoElement { - let theme = self.theme; - - div() - .flex() - .flex_col() - .gap(px(4.)) - .flex_shrink_0() - .when_some(self.session.read(cx).error.clone(), |this, error| { - this.child( - div() - .text_size(px(11.)) - .text_color(theme.red_9) - .text_center() - .child(error), - ) - }) - .child( - div() - .id("start-recording") - .h(px(44.)) - .w_full() - .flex_shrink_0() - .flex() - .flex_row() - .items_center() - .justify_center() - .gap(px(8.)) - .rounded(px(12.)) - .bg(theme.blue_500) - .hover(|style| style.opacity(0.9)) - .text_size(px(14.)) - .font_weight(FontWeight::MEDIUM) - .text_color(gpui::white()) - .child( - svg() - .path("icons/instant.svg") - .size(px(16.)) - .text_color(gpui::white()), - ) - .child("Start Recording") - .on_click(cx.listener(|this, _, window, cx| { - this.start_recording(window, cx); - })), - ) - } - /// The recording takeover, from index.tsx 3510-3529: an absolute overlay /// (`bg-gray-1/80 backdrop-blur-xs` -- the blur is skipped here, this gpui /// rev has no per-element backdrop blur hook) with the Stop button pinned @@ -1276,6 +1373,7 @@ impl MainWindow { this.selected_display = Some(chosen.clone()); this.target = Some(TargetType::Display); this.close_panel(cx); + this.sync_overlays(cx); }), ) .into_any_element(), @@ -1307,6 +1405,7 @@ impl MainWindow { this.selected_window = Some(chosen.clone()); this.target = Some(TargetType::Window); this.close_panel(cx); + this.sync_overlays(cx); }), ) .into_any_element(), @@ -1498,6 +1597,8 @@ impl MainWindow { .hover(|style| style.bg(theme.gray_4)) .on_click(cx.listener(move |this, _, _window, cx| { this.mode = mode; + crate::target_overlay::TargetSelect::global(cx) + .update(cx, |select, cx| select.set_recording_mode(mode, cx)); this.close_panel(cx); })) }), @@ -1728,6 +1829,10 @@ impl MainWindow { .hover(|style| style.bg(theme.gray_7)) .on_click(cx.listener(move |this, _, _window, cx| { this.mode = mode; + // Open overlays label their start button with the mode. + crate::target_overlay::TargetSelect::global(cx) + .update(cx, |select, cx| select.set_recording_mode(mode, cx)); + cx.defer(app_windows::refresh_target_overlays); cx.notify(); })) }; @@ -1923,11 +2028,14 @@ impl MainWindow { }) }) .on_click(cx.listener(move |this, _, _window, cx| { + // `toggleTargetMode`: clicking the armed tile again is a + // cancel, which takes the overlays down with it. this.target = if this.target == Some(target) { None } else { Some(target) }; + this.sync_overlays(cx); cx.notify(); })); @@ -2182,6 +2290,24 @@ impl MainWindow { } } +/// `CAP_GPUI_AUTO_OVERLAY=display|window|area|camera`, the harness's stand-in +/// for clicking a target tile. +pub fn auto_overlay_kind() -> Option { + match std::env::var("CAP_GPUI_AUTO_OVERLAY").ok()?.as_str() { + "display" => Some(TargetType::Display), + "window" => Some(TargetType::Window), + "area" => Some(TargetType::Area), + "camera" => Some(TargetType::CameraOnly), + _ => None, + } +} + +/// `CAP_GPUI_AUTO_AREA=x,y,width,height`, the harness's stand-in for drawing a +/// crop with the mouse. +fn auto_area_rect() -> Option { + crate::target_overlay::AreaRect::parse(&std::env::var("CAP_GPUI_AUTO_AREA").ok()?) +} + /// `InfoPill` + `TargetSelectInfoPill`: 24px tall, min 40px wide, `px-2.5`, /// `rounded-full`, 11px medium text. #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/apps/desktop-gpui/src/platform.rs b/apps/desktop-gpui/src/platform.rs index a832df8a76..8cc345fa75 100644 --- a/apps/desktop-gpui/src/platform.rs +++ b/apps/desktop-gpui/src/platform.rs @@ -105,6 +105,54 @@ mod mac { tracing::info!("installed macOS 26 occlusion shim on {name}"); } } + + // Same per-class, retried-from-the-same-spots lifecycle, so it rides + // along here. + install_frame_constraint_shim(); + } + + /// Let windows cover the menu bar. + /// + /// AppKit's `constrainFrameRect:toScreen:` pushes any titled window below + /// the menu bar -- and gpui's windows all carry `NSTitledWindowMask`, even + /// `WindowKind::PopUp` panels. A fullscreen target-select overlay set to + /// cover the display therefore lands 33pt down (and hangs 33pt off the + /// bottom). The Tauri app never sees this because tao's `NSWindow` + /// subclass overrides the method to return the rect unchanged; this shim + /// gives gpui's window classes the same override. + fn install_frame_constraint_shim() { + use objc2::ffi::class_addMethod; + use objc2_foundation::NSRect; + + unsafe extern "C" fn constrain_shim( + _this: *mut AnyObject, + _sel: Sel, + frame: NSRect, + _screen: *mut AnyObject, + ) -> NSRect { + frame + } + + for name in ["GPUIWindow", "GPUIPanel"] { + let Some(class) = objc2::runtime::AnyClass::get(name) else { + continue; + }; + let added = unsafe { + class_addMethod( + (class as *const objc2::runtime::AnyClass as *mut objc2::ffi::objc_class) + .cast(), + objc2::sel!(constrainFrameRect:toScreen:).as_ptr(), + Some(std::mem::transmute::< + unsafe extern "C" fn(*mut AnyObject, Sel, NSRect, *mut AnyObject) -> NSRect, + unsafe extern "C" fn(), + >(constrain_shim)), + c"{CGRect={CGPoint=dd}{CGSize=dd}}@:{CGRect={CGPoint=dd}{CGSize=dd}}@".as_ptr(), + ) + }; + if added { + tracing::info!("installed frame-constraint shim on {name}"); + } + } } /// Re-run gpui's occlusion handler so a window whose display link never @@ -135,6 +183,81 @@ mod mac { unsafe { CGWindowLevelForKey(10) as isize } } + /// The level `windows.rs` gives every `TargetSelectOverlay`: + /// `CGWindowLevelForKey(10) - 1`, i.e. one below the recording controls + /// bar. Same mislabeled constant as [`recording_controls_level`] (key 10 is + /// `kCGModalPanelWindowLevelKey`, level 8), so the shipping overlay really + /// runs at level 7 -- above ordinary app windows, below the bar. Reproduced + /// verbatim rather than "fixed" from over here. + pub fn target_overlay_level() -> isize { + recording_controls_level() - 1 + } + + /// A retained `NSWindow` that can outlive a `&Window` borrow -- what + /// [`place_overlay_panel`] operates on. + pub struct NativeWindow(Id); + + /// The retained `NSWindow` behind a gpui window, for AppKit calls that + /// must run *outside* any gpui update (see [`place_overlay_panel`]). + pub fn native_window(window: &Window) -> Option { + ns_window(window).map(NativeWindow) + } + + /// Everything that puts a target-select overlay onto its display: frame, + /// level, Spaces behavior, no shadow, ordered front without focus. + /// + /// The rect comes in as CoreGraphics coordinates -- global, top-left + /// origin, y down, points, exactly what `scap_targets` reports for a + /// display (`CGDisplayBounds`). Not gpui `WindowBounds`: gpui's macOS + /// origin math is display-relative and disagrees with its own `bounds()` + /// getter by the window height, so a window asked to cover a display lands + /// a screenful away. AppKit's coordinates are unambiguous, so the frame is + /// set here instead. The window frame equals the content rect for our + /// windows -- gpui gives titlebar-less windows `NSFullSizeContentView`. + /// + /// Takes the retained [`NativeWindow`] rather than `&Window` because + /// `setFrame:` and `orderFrontRegardless` synchronously re-enter gpui's + /// own move/resize/frame callbacks, which need the App RefCell -- so the + /// caller must run this with no gpui borrow held (a spawned task, not a + /// window update; running it inside one logs "RefCell already borrowed" + /// and the callbacks are dropped). + pub fn place_overlay_panel( + native: &NativeWindow, + x: f64, + y: f64, + width: f64, + height: f64, + level: isize, + ) { + use objc2_foundation::{NSPoint, NSRect, NSSize}; + + let ns_window = &native.0; + // AppKit's global space has its origin at the bottom-left of the + // primary display (`CGMainDisplayID`, which is what + // `scap_targets::Display::primary` wraps); CG's is that display's + // top-left. + let primary_height = scap_targets::Display::primary() + .logical_size() + .map(|size| size.height()) + .unwrap_or(height); + let appkit_y = primary_height - (y + height); + + ns_window.setFrame_display( + NSRect::new(NSPoint::new(x, appkit_y), NSSize::new(width, height)), + true, + ); + ns_window.setLevel(level); + unsafe { + ns_window.setCollectionBehavior( + NSWindowCollectionBehavior::CanJoinAllSpaces + | NSWindowCollectionBehavior::FullScreenPrimary, + ); + } + // `.shadow(false)` in the Tauri builder. + ns_window.setHasShadow(false); + unsafe { ns_window.orderFrontRegardless() }; + } + /// The `NSWindow` behind a gpui window. pub fn ns_window(window: &Window) -> Option> { // Fully qualified: `Window` also has an inherent `window_handle()` @@ -231,6 +354,22 @@ mod stub { pub fn recording_controls_level() -> isize { 0 } + pub fn target_overlay_level() -> isize { + 0 + } + pub struct NativeWindow; + pub fn native_window(_window: &Window) -> Option { + None + } + pub fn place_overlay_panel( + _native: &NativeWindow, + _x: f64, + _y: f64, + _width: f64, + _height: f64, + _level: isize, + ) { + } pub fn install_occlusion_shim() {} pub fn kick_display_link(_window: &Window) {} pub fn apply_panel_behavior(_window: &Window, _behavior: PanelBehavior) {} diff --git a/apps/desktop-gpui/src/target_overlay.rs b/apps/desktop-gpui/src/target_overlay.rs new file mode 100644 index 0000000000..3e4905200f --- /dev/null +++ b/apps/desktop-gpui/src/target_overlay.rs @@ -0,0 +1,1457 @@ +//! The fullscreen target-select overlays -- `target-select-overlay.tsx`, +//! natively. +//! +//! One transparent window per display, opened when a capture target mode is +//! armed in the main window. The overlays own the real "Start Recording" flow: +//! the display variant records the display it covers, the window variant the +//! window under the cursor, the area variant a crop drawn on top of the +//! desktop. +//! +//! Two pieces live here: +//! +//! * [`TargetSelect`], an app-scoped entity holding the armed mode and what is +//! under the cursor. The Tauri app computes the same thing in a 50ms tokio +//! loop and pushes it to every overlay webview as a `TargetUnderCursor` +//! event (`src-tauri/src/target_select_overlay.rs`); here the loop runs on +//! gpui's background executor and the overlays observe the entity. +//! * [`OverlayWindow`], the per-display view. +//! +//! Metrics are transcribed from the TSX with the Tailwind class quoted next to +//! them, the same convention `main_window.rs` uses. + +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Duration; + +use cap_recording::sources::screen_capture::ScreenCaptureTarget; +use gpui::{ + App, AppContext as _, Context, Entity, FontWeight, Global, Hsla, ImageFormat, + InteractiveElement as _, IntoElement, MouseButton, MouseMoveEvent, MouseUpEvent, + ParentElement as _, Pixels, Point, Render, SharedString, StatefulInteractiveElement as _, + Styled, Window, div, img, linear_color_stop, linear_gradient, prelude::FluentBuilder as _, px, + rgb, svg, +}; +use scap_targets::{ + DisplayId, WindowId, + bounds::{LogicalBounds, LogicalPosition, LogicalSize}, +}; + +use crate::{ + app_windows, + main_window::{Mode, TargetType}, + theme::{Appearance, Theme}, +}; + +/// How often the cursor probe runs. The Tauri loop sleeps 50ms between +/// `Display::get_containing_cursor` / `Window::get_topmost_at_cursor` reads; +/// this one is a touch lazier because each probe walks the whole window list +/// through `CGWindowListCopyWindowInfo` and the result only drives a +/// highlight rectangle. +const POLL_INTERVAL: Duration = Duration::from_millis(80); + +/// `MIN_SIZE` in the TSX -- the smallest area selection that can be recorded. +const AREA_MIN_SIZE: f32 = 150.; + +/// How close to an edge or corner counts as grabbing that handle. The TSX's +/// corner buttons are 30px boxes hung 12px outside the crop and its edge +/// buttons are 10px strips straddling the border; this is the same reach +/// expressed as a distance test, because the hit zones are computed in one +/// place here rather than being eight elements with their own listeners. +const AREA_HANDLE_GRAB: f32 = 12.; + +/// `w-104` on the controls cluster, and the height its one card comes out at +/// (`p-3` + `h-11` + `p-3`, plus `my-2.5` above and below). The TSX measures +/// its own controls element with a ResizeObserver to place it against the +/// crop; gpui has no layout read-back, so the two numbers are constants. +const CLUSTER_WIDTH: f32 = 416.; +const CLUSTER_HEIGHT: f32 = 88.; + +/// The window under the cursor, as the overlays need it. +#[derive(Clone, Debug, PartialEq)] +pub struct HoveredWindow { + pub id: WindowId, + pub app_name: String, + pub display_id: DisplayId, + /// Bounds relative to the display's top-left, in logical points -- exactly + /// what the overlay window's own coordinate space is. Carried as our own + /// rect rather than `LogicalBounds` so the poll can compare frames for + /// equality (`scap_targets`' bounds are not `PartialEq`). + pub bounds: AreaRect, +} + +impl HoveredWindow { + pub fn from_window(window: &scap_targets::Window) -> Option { + let bounds = window.display_relative_logical_bounds()?; + Some(Self { + id: window.id(), + app_name: window.owner_name()?, + display_id: window.display()?.id(), + bounds: AreaRect { + x: bounds.position().x() as f32, + y: bounds.position().y() as f32, + width: bounds.size().width() as f32, + height: bounds.size().height() as f32, + }, + }) + } +} + +/// App-scoped target-select state: which mode is armed, what the cursor is +/// over, and which window a click has locked onto. +pub struct TargetSelect { + /// `None` when no overlays are up. + pub mode: Option, + /// The recording mode the start cluster labels itself with. + pub recording_mode: Mode, + pub cursor_display: Option, + pub hovered_window: Option, + /// A window locked in by a click (or seeded from the main window's picker), + /// which the highlight sticks to until another window is clicked. + pub pinned_window: Option, + /// App icons by window id, fetched once per window. + icons: HashMap>, + _poll: Option>, +} + +struct TargetSelectGlobal(Entity); +impl Global for TargetSelectGlobal {} + +impl TargetSelect { + pub fn init(cx: &mut App) -> Entity { + let select = cx.new(|_| Self { + mode: None, + recording_mode: Mode::Instant, + cursor_display: None, + hovered_window: None, + pinned_window: None, + icons: HashMap::new(), + _poll: None, + }); + cx.set_global(TargetSelectGlobal(select.clone())); + select + } + + pub fn global(cx: &App) -> Entity { + cx.global::().0.clone() + } + + /// Arm (or disarm) a target mode. Starts and stops the cursor probe. + pub fn arm( + &mut self, + mode: Option, + recording_mode: Mode, + pinned_window: Option, + cx: &mut Context, + ) { + self.recording_mode = recording_mode; + self.pinned_window = pinned_window; + if self.mode == mode { + cx.notify(); + return; + } + self.mode = mode; + self.hovered_window = None; + if mode.is_some() { + self.start_polling(cx); + } else { + self._poll = None; + self.cursor_display = None; + } + cx.notify(); + } + + pub fn set_recording_mode(&mut self, mode: Mode, cx: &mut Context) { + if self.recording_mode != mode { + self.recording_mode = mode; + cx.notify(); + } + } + + /// The window the window-variant overlay should highlight: a locked + /// selection wins over what the cursor is currently over, matching + /// `activeWindow = selectedWindow() ?? targetUnderCursor.window`. + pub fn active_window(&self) -> Option<&HoveredWindow> { + self.pinned_window.as_ref().or(self.hovered_window.as_ref()) + } + + pub fn icon_for(&self, id: &WindowId) -> Option> { + self.icons.get(&id.to_string()).cloned() + } + + /// Lock the highlight onto whatever the cursor is over -- the TSX's + /// click-anywhere-to-select behavior. + fn pin_hovered(&mut self, cx: &mut Context) { + if let Some(hovered) = self.hovered_window.clone() + && self.pinned_window.as_ref().map(|window| &window.id) != Some(&hovered.id) + { + self.pinned_window = Some(hovered); + cx.notify(); + } + } + + fn start_polling(&mut self, cx: &mut Context) { + self._poll = Some(cx.spawn(async move |this, cx| { + loop { + // Only the window variant needs the (much more expensive) + // window probe; the others just want to know which display the + // cursor is on. + let Ok(want_window) = + this.read_with(cx, |this, _| this.mode == Some(TargetType::Window)) + else { + return; + }; + + let probe = cx + .background_executor() + .spawn(async move { + let display = scap_targets::Display::get_containing_cursor() + .map(|display| display.id()); + let window = want_window + .then(scap_targets::Window::get_topmost_at_cursor) + .flatten() + .as_ref() + .and_then(HoveredWindow::from_window); + (display, window) + }) + .await; + + let Ok(icon_wanted) = this.update(cx, |this: &mut Self, cx| { + let (display, window) = probe; + let changed = this.cursor_display != display || this.hovered_window != window; + this.cursor_display = display; + this.hovered_window = window; + if changed { + cx.notify(); + } + + this.active_window() + .map(|window| window.id.clone()) + .filter(|id| !this.icons.contains_key(&id.to_string())) + }) else { + return; + }; + if let Some(id) = icon_wanted { + Self::fetch_icon(id, &this, cx).await; + } + + // Repaint the overlays explicitly: none of them is the active + // window while the user hovers another app, and an inactive + // window only repaints when asked (the unit-2 finding). + cx.update(app_windows::refresh_target_overlays); + cx.background_executor().timer(POLL_INTERVAL).await; + } + })); + } + + /// The hovered window's app icon, the way `get_window_icon` fetches it: + /// `Window::app_icon()` PNG bytes, decoded by gpui's image path (`img`, + /// not `svg` -- an app icon is full colour). + async fn fetch_icon(id: WindowId, this: &gpui::WeakEntity, cx: &mut gpui::AsyncApp) { + let key = id.to_string(); + let lookup = id.clone(); + let bytes = cx + .background_executor() + .spawn(async move { + scap_targets::Window::from_id(&lookup).and_then(|window| window.app_icon()) + }) + .await; + let Some(bytes) = bytes else { return }; + let image = Arc::new(gpui::Image::from_bytes(ImageFormat::Png, bytes)); + this.update(cx, |this, cx| { + this.icons.insert(key, image); + cx.notify(); + }) + .ok(); + } +} + +/// A rectangle in the overlay's own coordinate space: display-relative logical +/// points, which is also what `ScreenCaptureTarget::Area` wants. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct AreaRect { + pub x: f32, + pub y: f32, + pub width: f32, + pub height: f32, +} + +impl AreaRect { + fn right(&self) -> f32 { + self.x + self.width + } + + fn bottom(&self) -> f32 { + self.y + self.height + } + + fn is_valid(&self) -> bool { + self.width >= AREA_MIN_SIZE && self.height >= AREA_MIN_SIZE + } + + fn clamped(self, display: (f32, f32)) -> Self { + let width = self.width.min(display.0); + let height = self.height.min(display.1); + Self { + x: self.x.clamp(0., (display.0 - width).max(0.)), + y: self.y.clamp(0., (display.1 - height).max(0.)), + width, + height, + } + } + + /// `x,y,width,height` -- the `CAP_GPUI_AUTO_AREA` harness format. + pub fn parse(spec: &str) -> Option { + let values: Vec = spec + .split(',') + .map(|part| part.trim().parse().ok()) + .collect::>>()?; + let [x, y, width, height] = values[..] else { + return None; + }; + Some(Self { + x, + y, + width, + height, + }) + } + + fn to_bounds(self) -> LogicalBounds { + LogicalBounds::new( + LogicalPosition::new(self.x as f64, self.y as f64), + LogicalSize::new(self.width as f64, self.height as f64), + ) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum AreaHandle { + North, + South, + East, + West, + NorthWest, + NorthEast, + SouthWest, + SouthEast, +} + +impl AreaHandle { + const ALL: [Self; 8] = [ + Self::NorthWest, + Self::North, + Self::NorthEast, + Self::East, + Self::SouthEast, + Self::South, + Self::SouthWest, + Self::West, + ]; + + fn north(self) -> bool { + matches!(self, Self::North | Self::NorthWest | Self::NorthEast) + } + + fn south(self) -> bool { + matches!(self, Self::South | Self::SouthWest | Self::SouthEast) + } + + fn west(self) -> bool { + matches!(self, Self::West | Self::NorthWest | Self::SouthWest) + } + + fn east(self) -> bool { + matches!(self, Self::East | Self::NorthEast | Self::SouthEast) + } + + fn is_corner(self) -> bool { + matches!( + self, + Self::NorthWest | Self::NorthEast | Self::SouthWest | Self::SouthEast + ) + } + + fn cursor(self) -> gpui::CursorStyle { + use gpui::CursorStyle::*; + match self { + Self::North | Self::South => ResizeUpDown, + Self::East | Self::West => ResizeLeftRight, + Self::NorthWest | Self::SouthEast => ResizeUpLeftDownRight, + Self::NorthEast | Self::SouthWest => ResizeUpRightDownLeft, + } + } + + fn id(self) -> &'static str { + match self { + Self::North => "handle-n", + Self::South => "handle-s", + Self::East => "handle-e", + Self::West => "handle-w", + Self::NorthWest => "handle-nw", + Self::NorthEast => "handle-ne", + Self::SouthWest => "handle-sw", + Self::SouthEast => "handle-se", + } + } +} + +enum AreaDrag { + /// Drawing a fresh selection from an anchor point. + Draw { anchor: (f32, f32) }, + /// Dragging the whole selection. + Move { grab: (f32, f32), start: AreaRect }, + Resize { handle: AreaHandle, start: AreaRect }, +} + +/// One display's overlay. +pub struct OverlayWindow { + theme: Theme, + select: Entity, + display_id: DisplayId, + display_name: String, + /// Logical size, which is this window's size and the space every + /// coordinate here lives in. + display_size: (f32, f32), + /// Physical pixels, for the `1920x1080 · 60FPS` line. + physical_size: Option<(u32, u32)>, + refresh_rate: f64, + focus: gpui::FocusHandle, + crop: Option, + drag: Option, +} + +impl OverlayWindow { + pub fn new( + display: &scap_targets::Display, + select: Entity, + window: &mut Window, + cx: &mut Context, + ) -> Self { + let theme = Theme::new(Appearance::from_window(window.appearance())); + cx.observe(&select, |_, _, cx| cx.notify()).detach(); + + let logical = display + .logical_size() + .map(|size| (size.width() as f32, size.height() as f32)) + .unwrap_or((1920., 1080.)); + + Self { + theme, + select, + display_id: display.id(), + display_name: display + .name() + .unwrap_or_else(|| format!("Display {}", display.id())), + display_size: logical, + physical_size: display + .physical_size() + .map(|size| (size.width() as u32, size.height() as u32)), + refresh_rate: display.refresh_rate(), + focus: cx.focus_handle(), + crop: None, + drag: None, + } + } + + pub fn focus_handle(&self) -> gpui::FocusHandle { + self.focus.clone() + } + + /// Seed an area selection without drawing one -- the harness path + /// (`CAP_GPUI_AUTO_AREA`), since unprivileged synthetic drags are dropped. + pub fn set_crop(&mut self, crop: AreaRect, cx: &mut Context) { + self.crop = Some(crop.clamped(self.display_size)); + cx.notify(); + } + + /// The target this overlay would record, or `None` when it has nothing to + /// record yet (no window under the cursor, no valid area drawn). + pub fn target(&self, cx: &App) -> Option { + let select = self.select.read(cx); + match select.mode? { + TargetType::Display => Some(ScreenCaptureTarget::Display { + id: self.display_id.clone(), + }), + TargetType::Window => select.active_window().map(|window| { + ScreenCaptureTarget::Window { + id: window.id.clone(), + } + }), + TargetType::Area => self.crop.filter(|crop| crop.is_valid()).map(|crop| { + ScreenCaptureTarget::Area { + screen: self.display_id.clone(), + bounds: crop.to_bounds(), + } + }), + TargetType::CameraOnly => Some(ScreenCaptureTarget::CameraOnly), + } + } + + /// The overlay's Start button: close every overlay, then hand the target to + /// the main window's start path (which opens the bar, hides the main + /// window and starts the engine). + pub fn start_recording(&mut self, _window: &mut Window, cx: &mut Context) { + let Some(target) = self.target(cx) else { return }; + tracing::info!(target = ?target.kind_str(), "overlay start pressed"); + if self.select.read(cx).recording_mode == Mode::Screenshot { + // Screenshots do not go through the recording actors at all, and + // that path does not exist in this app yet. + return; + } + // Deferred: this runs inside the overlay's own window update and the + // orchestrator closes that very window. + cx.defer(move |cx: &mut App| app_windows::start_recording_from_overlay(target, cx)); + } + + fn dismiss(&mut self, cx: &mut Context) { + cx.defer(app_windows::dismiss_target_overlays); + } + + fn sync_appearance(&mut self, window: &Window) { + let appearance = Appearance::from_window(window.appearance()); + if appearance != self.theme.appearance { + self.theme = Theme::new(appearance); + } + } + + /// True when the cursor is on this overlay's display -- `data-over` in the + /// display variant, `isActiveDisplay` in the area variant. + fn is_active_display(&self, cx: &App) -> bool { + self.select.read(cx).cursor_display.as_ref() == Some(&self.display_id) + } +} + +impl Render for OverlayWindow { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + self.sync_appearance(window); + let mode = self.select.read(cx).mode; + + let root = div() + .id("overlay-root") + .track_focus(&self.focus) + .key_context("TargetSelectOverlay") + .on_key_down(cx.listener(|this, event: &gpui::KeyDownEvent, _window, cx| { + // The Tauri app registers Escape as a *global* shortcut while + // the overlays are up; here it is a plain key handler on the + // overlay that has focus (see the README deviation). + tracing::debug!(key = %event.keystroke.key, "overlay key"); + if event.keystroke.key.as_str() == "escape" { + this.dismiss(cx); + } + })) + .size_full() + .relative() + .font_family("Geist") + .text_color(gpui::white()); + + match mode { + Some(TargetType::Display) => root + .child(self.render_display_variant(cx)) + .into_any_element(), + Some(TargetType::Window) => { + root.child(self.render_window_variant(cx)).into_any_element() + } + Some(TargetType::Area) => root + .child(self.render_area_variant(cx)) + .into_any_element(), + Some(TargetType::CameraOnly) => root + .child(self.render_camera_variant(cx)) + .into_any_element(), + None => root.into_any_element(), + } + } +} + +impl OverlayWindow { + /// `data-[over='true']:bg-blue-600/40` over `bg-black/60`, centered + /// monitor art, name, resolution. + fn render_display_variant(&self, cx: &mut Context) -> impl IntoElement { + let over = self.is_active_display(cx); + let resolution = self.physical_size.map(|(width, height)| { + // `${size().width}x${size().height} · ${display.refresh_rate}FPS` + format!("{width}x{height} · {}FPS", self.refresh_rate) + }); + + div() + .size_full() + .relative() + .flex() + .flex_col() + .items_center() + .justify_center() + // `

    `, then the + // root's own `bg-blue-600/40` on top of it when hovered. + .child(div().absolute().inset_0().bg(gpui::hsla(0., 0., 0., 0.6))) + .when(over, |this| { + this.child( + div() + .absolute() + .inset_0() + .bg(Theme::with_alpha(rgb(Theme::TARGET_HIGHLIGHT), 0.4)), + ) + }) + .child( + div() + .relative() + .flex() + .flex_col() + .items_center() + // `IconCapMonitor class="size-20 mb-3"`: the art is a + // full-colour gradient, so it goes through `img()` (an + // `svg()` would flatten it to a silhouette), and its + // 125x84 viewBox keeps its aspect inside the 80px box. + .child( + img("icons/monitor.svg") + .w(px(80.)) + .h(px(80. * 84. / 125.)) + .mb(px(12.)), + ) + .child( + div() + .mb(px(8.)) + .text_size(px(30.)) + .font_weight(FontWeight::SEMIBOLD) + .child(SharedString::from(self.display_name.clone())), + ) + .children(resolution.map(|resolution| { + div().mb(px(8.)).text_size(px(12.)).child(resolution) + })) + .child(self.render_controls_cluster(self.target(cx), false, cx)), + ) + } + + /// `bg-black/70` everywhere, `bg-blue-600/40` over the hovered window's + /// rectangle with the app's icon, name and size stacked in it. + /// + /// Like the TSX, the whole variant only draws on the display the cursor is + /// on -- the other overlays stay empty and transparent. + fn render_window_variant(&self, cx: &mut Context) -> gpui::AnyElement { + let select = self.select.read(cx); + let active = select + .active_window() + .filter(|window| window.display_id == self.display_id) + .cloned(); + let icon = active + .as_ref() + .and_then(|window| select.icon_for(&window.id)); + + let Some(active) = active else { + return div().size_full().into_any_element(); + }; + let bounds = active.bounds; + let width = bounds.width; + let height = bounds.height; + + div() + .size_full() + .relative() + .bg(gpui::hsla(0., 0., 0., 0.7)) + .id("window-variant") + // Clicking anywhere locks the highlight onto whatever is under the + // cursor, exactly as the TSX's two click handlers add up to. + .on_click(cx.listener(|this, _, _window, cx| { + this.select + .update(cx, |select, cx| select.pin_hovered(cx)); + })) + .child( + div() + .absolute() + .left(px(bounds.x)) + .top(px(bounds.y)) + .w(px(width)) + .h(px(height)) + .flex() + .flex_col() + .justify_center() + .items_center() + .bg(Theme::with_alpha(rgb(Theme::TARGET_HIGHLIGHT), 0.4)) + .child( + div() + .flex() + .flex_col() + .justify_center() + .items_center() + // `
    ` around the icon. + .children(icon.map(|icon| { + img(icon).size(px(96.)).mb(px(12.)).rounded(px(8.)) + })) + .child( + div() + .mb(px(8.)) + .text_size(px(30.)) + .font_weight(FontWeight::SEMIBOLD) + .child(SharedString::from(active.app_name.clone())), + ) + .child( + div() + .mb(px(8.)) + .text_size(px(12.)) + .child(format!("{}x{}", width as u32, height as u32)), + ), + ) + .child(self.render_controls_cluster(self.target(cx), false, cx)), + ) + .into_any_element() + } + + /// The camera-only variant: no capture target to pick, just the start + /// cluster over a dim screen. The TSX also inlines a camera preview here; + /// ours keeps the preview bubble window it already has (README deviation). + fn render_camera_variant(&self, cx: &mut Context) -> impl IntoElement { + div() + .size_full() + .relative() + .flex() + .flex_col() + .items_center() + .justify_center() + // `bg-black/70` with a second `bg-black/60` layer behind it. + .bg(gpui::hsla(0., 0., 0., 0.7)) + .child( + div() + .flex() + .flex_col() + .items_center() + .mb(px(16.)) + .child( + div() + .mb(px(8.)) + .text_size(px(30.)) + .font_weight(FontWeight::SEMIBOLD) + .child("Camera Only"), + ) + .child( + div() + .text_size(px(12.)) + .text_color(self.theme.gray_11) + .child("Record using only your camera and microphone"), + ), + ) + .child(self.render_controls_cluster(self.target(cx), false, cx)) + } + + /// The crop overlay: `bg-black/45` outside the selection, a `border-white/50` + /// region with eight handles, a size readout on top and the start cluster + /// placed against the crop. + fn render_area_variant(&self, cx: &mut Context) -> gpui::Stateful { + let interacting = self.drag.is_some(); + // `shouldShowOverlay = isInteracting() || isActiveDisplay()`. + let visible = interacting || self.is_active_display(cx); + let crop = self.crop; + let valid = crop.is_some_and(|crop| crop.is_valid()); + + let mut root = div() + .id("area-root") + .size_full() + .relative() + .cursor(gpui::CursorStyle::Crosshair) + .on_mouse_down( + MouseButton::Left, + cx.listener(|this, event: &gpui::MouseDownEvent, _window, cx| { + this.area_mouse_down(event.position, cx); + }), + ) + .on_mouse_move(cx.listener(|this, event: &MouseMoveEvent, _window, cx| { + this.area_mouse_move(event.position, cx); + })) + .on_mouse_up( + MouseButton::Left, + cx.listener(|this, _: &MouseUpEvent, _window, cx| { + this.area_mouse_up(cx); + }), + ); + + if !visible { + // `classList={{ "opacity-0 pointer-events-none": !shouldShowOverlay() }}` + return root.invisible(); + } + + // The occluder: four `bg-black/45` rectangles around the crop (one + // full-screen rectangle while nothing is drawn). + let dim: Hsla = gpui::hsla(0., 0., 0., 0.45); + root = match crop { + None => root.child(div().absolute().inset_0().bg(dim)), + Some(crop) => root + .child( + div() + .absolute() + .top_0() + .left_0() + .h_full() + .w(px(crop.x.max(0.))) + .bg(dim), + ) + .child( + div() + .absolute() + .top_0() + .right_0() + .h_full() + .w(px((self.display_size.0 - crop.right()).max(0.))) + .bg(dim), + ) + .child( + div() + .absolute() + .top_0() + .left(px(crop.x)) + .w(px(crop.width)) + .h(px(crop.y.max(0.))) + .bg(dim), + ) + .child( + div() + .absolute() + .bottom_0() + .left(px(crop.x)) + .w(px(crop.width)) + .h(px((self.display_size.1 - crop.bottom()).max(0.))) + .bg(dim), + ), + }; + + if let Some(crop) = crop { + root = root.child(self.render_crop_region(crop)); + } + + root.child(self.render_area_toolbar(crop, valid)) + .child(self.render_area_controls(crop, valid, cx)) + } + + /// `border border-white/50` around the selection, plus the eight resize + /// zones. The zones carry only a cursor: the drag itself is dispatched from + /// the root's mouse-down through [`Self::area_zone_at`], so a grab that + /// starts a pixel outside the handle still resizes. + fn render_crop_region(&self, crop: AreaRect) -> impl IntoElement { + let mut region = div() + .absolute() + .left(px(crop.x)) + .top(px(crop.y)) + .w(px(crop.width)) + .h(px(crop.height)) + .border_1() + .border_color(gpui::hsla(0., 0., 1., 0.5)) + .cursor(gpui::CursorStyle::OpenHand); + + for handle in AreaHandle::ALL { + let mut zone = div() + .id(handle.id()) + .absolute() + .cursor(handle.cursor()); + let reach = px(AREA_HANDLE_GRAB * 2.); + + zone = if handle.is_corner() { + let zone = zone.size(reach); + let zone = if handle.west() { + zone.left(px(-AREA_HANDLE_GRAB)) + } else { + zone.right(px(-AREA_HANDLE_GRAB)) + }; + if handle.north() { + zone.top(px(-AREA_HANDLE_GRAB)) + } else { + zone.bottom(px(-AREA_HANDLE_GRAB)) + } + } else if handle.north() || handle.south() { + let zone = zone + .left(px(AREA_HANDLE_GRAB)) + .right(px(AREA_HANDLE_GRAB)) + .h(reach); + if handle.north() { + zone.top(px(-AREA_HANDLE_GRAB)) + } else { + zone.bottom(px(-AREA_HANDLE_GRAB)) + } + } else { + let zone = zone + .top(px(AREA_HANDLE_GRAB)) + .bottom(px(AREA_HANDLE_GRAB)) + .w(reach); + if handle.west() { + zone.left(px(-AREA_HANDLE_GRAB)) + } else { + zone.right(px(-AREA_HANDLE_GRAB)) + } + }; + + // The corner glyph: the TSX draws an `M0 0 H12 M0 0 V12` stroke in + // white at `stroke-width 4`. gpui's `div` has no paths, so the same + // L is two 4px bars. + if handle.is_corner() { + let bar = |width: f32, height: f32| { + div() + .absolute() + .w(px(width)) + .h(px(height)) + .bg(gpui::white()) + }; + let arm = 18.; + let thickness = 4.; + let mut horizontal = bar(arm, thickness); + let mut vertical = bar(thickness, arm); + horizontal = if handle.west() { + horizontal.left(px(AREA_HANDLE_GRAB)) + } else { + horizontal.right(px(AREA_HANDLE_GRAB)) + }; + vertical = if handle.west() { + vertical.left(px(AREA_HANDLE_GRAB)) + } else { + vertical.right(px(AREA_HANDLE_GRAB)) + }; + horizontal = if handle.north() { + horizontal.top(px(AREA_HANDLE_GRAB)) + } else { + horizontal.bottom(px(AREA_HANDLE_GRAB)) + }; + vertical = if handle.north() { + vertical.top(px(AREA_HANDLE_GRAB)) + } else { + vertical.bottom(px(AREA_HANDLE_GRAB)) + }; + zone = zone.child(horizontal).child(vertical); + } + + region = region.child(zone); + } + + region + } + + /// The floating readout: `top-12 left-1/2 -translate-x-1/2` over the + /// liquid-glass surface, `min-w-28 px-2 text-base tabular-nums`. The + /// aspect-ratio, reset, fill and lock controls that share this bar in the + /// TSX are deferred (README). + fn render_area_toolbar(&self, crop: Option, valid: bool) -> impl IntoElement { + let label = match crop.filter(|_| valid) { + Some(crop) => format!("{} × {}", crop.width.round(), crop.height.round()), + None => "Draw an area".to_string(), + }; + + div() + .absolute() + // `top-12` on macOS, centred by hand: gpui has no translate. + .top(px(48.)) + .left(px((self.display_size.0 - 200.) / 2.)) + .w(px(200.)) + .flex() + .justify_center() + .child( + self.glass_surface() + .h(px(48.)) + .flex() + .items_center() + .gap(px(6.)) + .p(px(6.)) + .text_color(self.theme.gray_12) + .child( + div() + .min_w(px(112.)) + .px(px(8.)) + .text_size(px(16.)) + .child(label), + ), + ) + } + + /// The controls cluster, placed against the crop the way `controlsStyle` + /// places it: below when it fits, above when it does not, inside the crop + /// otherwise. + fn render_area_controls( + &self, + crop: Option, + valid: bool, + cx: &mut Context, + ) -> impl IntoElement { + const SIDE_MARGIN: f32 = 16.; + const MARGIN_BELOW: f32 = 16.; + const MARGIN_TOP_OUTSIDE: f32 = 16.; + // `macos ? 40 : 28` / `macos ? 40 : 10`. + const MARGIN_TOP_INSIDE: f32 = 40.; + const TOP_SAFE_MARGIN: f32 = 40.; + + let crop = crop.unwrap_or(AreaRect { + x: 0., + y: 0., + width: 0., + height: 0., + }); + let (screen_width, screen_height) = self.display_size; + + let below = crop.bottom() + MARGIN_BELOW; + let y = if below + CLUSTER_HEIGHT <= screen_height { + below + } else { + let above = crop.y - CLUSTER_HEIGHT - MARGIN_TOP_OUTSIDE; + if above >= TOP_SAFE_MARGIN { + above + } else { + crop.y + MARGIN_TOP_INSIDE + } + }; + let x = (crop.x + crop.width / 2. - CLUSTER_WIDTH / 2.) + .clamp(SIDE_MARGIN, (screen_width - CLUSTER_WIDTH - SIDE_MARGIN).max(SIDE_MARGIN)); + + div() + .absolute() + .left(px(x)) + .top(px(y)) + .w(px(CLUSTER_WIDTH)) + .flex() + .flex_col() + .items_center() + .child(self.render_controls_cluster(self.target(cx), !valid, cx)) + .when(!valid, |this| { + // `Minimum size is 150 x 150` / `W x H is too small`. + this.child( + div() + .flex() + .flex_col() + .gap(px(4.)) + .items_center() + .p(px(10.)) + .my(px(8.)) + .rounded(px(12.)) + .border_1() + .border_color(self.theme.red_4) + .bg(self.theme.red_2) + .text_size(px(14.)) + .text_color(self.theme.gray_12) + .child(format!( + "Minimum size is {} x {}", + AREA_MIN_SIZE as u32, AREA_MIN_SIZE as u32 + )) + .child( + div() + .text_size(px(11.)) + .child(format!( + "{} x {} is too small", + crop.width.round(), + crop.height.round() + )), + ), + ) + }) + } + + /// `LIQUID_GLASS_SURFACE_CLASS`: `rounded-2xl border border-gray-12/10 + /// bg-gray-1/82 shadow-xl shadow-black/20 dark:border-white/10 + /// dark:bg-gray-2/82`. The `backdrop-blur-xl` is dropped -- this gpui rev + /// has no per-element backdrop blur (same gap the recording overlay has). + fn glass_surface(&self) -> gpui::Div { + let theme = self.theme; + div() + .rounded(px(16.)) + .border_1() + .border_color(if theme.is_dark() { + gpui::hsla(0., 0., 1., 0.1) + } else { + Theme::with_alpha(theme.gray_12, 0.1) + }) + .bg(if theme.is_dark() { + Theme::with_alpha(theme.gray_2, 0.82) + } else { + Theme::with_alpha(theme.gray_1, 0.82) + }) + .shadow(vec![gpui::BoxShadow { + color: gpui::hsla(0., 0., 0., 0.2), + offset: gpui::point(px(0.), px(8.)), + blur_radius: px(10.), + spread_radius: px(-6.), + inset: false, + }]) + } + + /// `RecordingControls`: the close button, the gradient start pill with its + /// mode line and caret, and the pre-recording settings button. + /// + /// The device row (a second glass card with camera and microphone selects) + /// and the "What is X Mode?" link below it are deferred -- the device + /// pickers live in the main window here (README). + fn render_controls_cluster( + &self, + target: Option, + disabled: bool, + cx: &mut Context, + ) -> impl IntoElement { + let theme = self.theme; + let mode = self.select.read(cx).recording_mode; + let disabled = disabled || target.is_none() || mode == Mode::Screenshot; + + // `flex flex-col gap-2.5 items-stretch my-2.5 w-104`. + div() + .flex() + .flex_col() + .gap(px(10.)) + .items_stretch() + .my(px(10.)) + .w(px(CLUSTER_WIDTH)) + // The cluster sits on top of the area overlay's drawing surface; + // without this a click on Start would also start a new crop. + .occlude() + .child( + self.glass_surface().p(px(12.)).child( + div() + .flex() + .flex_row() + .gap(px(10.)) + .items_center() + .child(self.render_close_button(cx)) + .child(self.render_start_button(target, disabled, mode, cx)) + .child( + // `size-9 rounded-full border bg-gray-6 text-gray-12`. + // Inert: the countdown menu it opens needs the + // settings store (same gap as the bar's settings + // button). + div() + .flex() + .justify_center() + .items_center() + .size(px(36.)) + .flex_shrink_0() + .rounded_full() + .border_1() + .border_color(theme.gray_5) + .bg(theme.gray_6) + .child( + svg() + .path("icons/gear.svg") + .size(px(20.)) + .text_color(theme.gray_12), + ), + ), + ), + ) + } + + /// `size-9 rounded-full bg-gray-12` with the inverted X glyph. + fn render_close_button(&self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + + div() + .id("overlay-close") + .flex() + .justify_center() + .items_center() + .size(px(36.)) + .flex_shrink_0() + .rounded_full() + .bg(theme.gray_12) + .hover(|style| style.opacity(0.8)) + .child( + svg() + .path("icons/x.svg") + // `invert dark:invert-0` on a black glyph: white on the + // light theme's dark circle, black on the dark theme's + // light one. + .size(px(12.)) + .text_color(theme.gray_1), + ) + .on_click(cx.listener(|this, _, _window, cx| { + cx.stop_propagation(); + this.dismiss(cx); + })) + } + + /// The start affordance: `h-11 rounded-full bg-linear-to-r from-blue-10 to + /// blue-11` (`blue-9`/`blue-10` in the dark theme), mode icon, two-line + /// label, and the mode caret on a divided trailing block. + fn render_start_button( + &self, + target: Option, + disabled: bool, + mode: Mode, + cx: &mut Context, + ) -> impl IntoElement { + let theme = self.theme; + let (from, to) = if theme.is_dark() { + (theme.blue_9, theme.blue_10) + } else { + (theme.blue_10, theme.blue_11) + }; + let label = match mode { + Mode::Screenshot => "Take Screenshot", + _ => "Start Recording", + }; + let icon = match mode { + Mode::Instant => "icons/instant.svg", + Mode::Studio => "icons/film-cut.svg", + Mode::Screenshot => "icons/camera.svg", + }; + let mode_label = match mode { + Mode::Instant => "Instant Mode", + Mode::Studio => "Studio Mode", + Mode::Screenshot => "Screenshot Mode", + }; + let _ = target; + + div() + .id("overlay-start") + .flex() + .flex_1() + .min_w_0() + .max_w(px(288.)) + .overflow_hidden() + .flex_row() + .h(px(44.)) + .rounded_full() + .text_color(gpui::white()) + // `bg-linear-to-r from-blue-10 via-blue-10 to-blue-11`: the via + // stop sits at 50% with the from colour, so the ramp only starts + // halfway. + .bg(linear_gradient( + 90., + linear_color_stop(Hsla::from(from), 0.5), + linear_color_stop(Hsla::from(to), 1.0), + )) + .child( + div() + .flex() + .flex_1() + .items_center() + .py(px(4.)) + .pl(px(16.)) + .min_w_0() + // `opacity-60 cursor-not-allowed hover:bg-transparent`. + .when(disabled, |this| this.opacity(0.6)) + .when(!disabled, |this| { + this.hover(|style| style.bg(gpui::hsla(0., 0., 1., 0.1))) + }) + .child( + svg() + .path(icon) + .size(px(16.)) + .flex_shrink_0() + .text_color(gpui::white()), + ) + .child( + div() + .flex() + .flex_col() + .ml(px(12.)) + .mr(px(8.)) + .min_w_0() + .child( + div() + // `text-[0.95rem] font-medium`. + .text_size(px(15.2)) + .font_weight(FontWeight::MEDIUM) + .child(label), + ) + .child( + div() + .mt(px(-2.)) + .text_size(px(11.)) + .text_color(gpui::hsla(0., 0., 1., 0.9)) + .child(mode_label), + ), + ), + ) + .child( + // `pl-2.5 pr-3 py-1.5 border-l border-white/20 bg-white/5`. + // The mode menu it drops down is deferred (README). + div() + .flex() + .items_center() + .pl(px(10.)) + .pr(px(12.)) + .py(px(6.)) + .border_l_1() + .border_color(gpui::hsla(0., 0., 1., 0.2)) + .bg(gpui::hsla(0., 0., 1., 0.05)) + .child( + svg() + .path("icons/caret-down.svg") + .w(px(10.)) + .h(px(6.)) + .text_color(gpui::white()), + ), + ) + .when(!disabled, |this| { + this.on_click(cx.listener(|this, _, window, cx| { + cx.stop_propagation(); + this.start_recording(window, cx); + })) + }) + } +} + +/// Area interaction: which part of the selection a point grabs. +enum AreaZone { + Handle(AreaHandle), + Inside, +} + +impl OverlayWindow { + fn area_zone_at(&self, point: (f32, f32)) -> Option { + let crop = self.crop?; + let (x, y) = point; + let near_left = (x - crop.x).abs() <= AREA_HANDLE_GRAB; + let near_right = (x - crop.right()).abs() <= AREA_HANDLE_GRAB; + let near_top = (y - crop.y).abs() <= AREA_HANDLE_GRAB; + let near_bottom = (y - crop.bottom()).abs() <= AREA_HANDLE_GRAB; + let within_x = x >= crop.x - AREA_HANDLE_GRAB && x <= crop.right() + AREA_HANDLE_GRAB; + let within_y = y >= crop.y - AREA_HANDLE_GRAB && y <= crop.bottom() + AREA_HANDLE_GRAB; + + let handle = match (near_left, near_right, near_top, near_bottom) { + (true, _, true, _) => Some(AreaHandle::NorthWest), + (_, true, true, _) => Some(AreaHandle::NorthEast), + (true, _, _, true) => Some(AreaHandle::SouthWest), + (_, true, _, true) => Some(AreaHandle::SouthEast), + (true, _, _, _) if within_y => Some(AreaHandle::West), + (_, true, _, _) if within_y => Some(AreaHandle::East), + (_, _, true, _) if within_x => Some(AreaHandle::North), + (_, _, _, true) if within_x => Some(AreaHandle::South), + _ => None, + }; + + if let Some(handle) = handle { + return Some(AreaZone::Handle(handle)); + } + (x > crop.x && x < crop.right() && y > crop.y && y < crop.bottom()).then_some(AreaZone::Inside) + } + + fn area_mouse_down(&mut self, position: Point, cx: &mut Context) { + let point = (f32::from(position.x), f32::from(position.y)); + + self.drag = match self.area_zone_at(point) { + Some(AreaZone::Handle(handle)) => Some(AreaDrag::Resize { + handle, + start: self.crop.unwrap_or(AreaRect { + x: point.0, + y: point.1, + width: 0., + height: 0., + }), + }), + Some(AreaZone::Inside) => Some(AreaDrag::Move { + grab: point, + start: self.crop.unwrap(), + }), + None => { + // A press outside the selection starts a fresh one, the way the + // Cropper's full-screen "Start selection" button does. + self.crop = Some(AreaRect { + x: point.0, + y: point.1, + width: 0., + height: 0., + }); + Some(AreaDrag::Draw { anchor: point }) + } + }; + cx.notify(); + } + + fn area_mouse_move(&mut self, position: Point, cx: &mut Context) { + let Some(drag) = &self.drag else { return }; + let x = f32::from(position.x).clamp(0., self.display_size.0); + let y = f32::from(position.y).clamp(0., self.display_size.1); + + let next = match drag { + AreaDrag::Draw { anchor } => AreaRect { + x: anchor.0.min(x), + y: anchor.1.min(y), + width: (x - anchor.0).abs(), + height: (y - anchor.1).abs(), + }, + AreaDrag::Move { grab, start } => AreaRect { + x: start.x + (x - grab.0), + y: start.y + (y - grab.1), + ..*start + } + .clamped(self.display_size), + AreaDrag::Resize { handle, start } => { + let mut left = start.x; + let mut top = start.y; + let mut right = start.right(); + let mut bottom = start.bottom(); + if handle.west() { + left = x; + } + if handle.east() { + right = x; + } + if handle.north() { + top = y; + } + if handle.south() { + bottom = y; + } + AreaRect { + x: left.min(right), + y: top.min(bottom), + width: (right - left).abs(), + height: (bottom - top).abs(), + } + } + }; + + self.crop = Some(next); + cx.notify(); + } + + fn area_mouse_up(&mut self, cx: &mut Context) { + if self.drag.take().is_none() { + return; + } + if let Some(crop) = self.crop { + // A click with no drag is not a selection. + if crop.width < 2. || crop.height < 2. { + self.crop = None; + } else { + self.crop = Some(crop.clamped(self.display_size)); + } + } + cx.notify(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn rect(x: f32, y: f32, width: f32, height: f32) -> AreaRect { + AreaRect { + x, + y, + width, + height, + } + } + + #[test] + fn area_rects_clamp_into_the_display() { + let display = (1000., 800.); + // Dragged off the right edge: pushed back, size preserved. + assert_eq!( + rect(950., 10., 200., 100.).clamped(display), + rect(800., 10., 200., 100.) + ); + // Bigger than the display: capped, not offset. + assert_eq!( + rect(-50., -50., 2000., 2000.).clamped(display), + rect(0., 0., 1000., 800.) + ); + } + + /// `MIN_SIZE` is 150x150; anything smaller is drawn but not recordable. + #[test] + fn area_validity_matches_the_min_size() { + assert!(rect(0., 0., 150., 150.).is_valid()); + assert!(!rect(0., 0., 149., 400.).is_valid()); + assert!(!rect(0., 0., 400., 149.).is_valid()); + } + + /// The `ScreenCaptureTarget::Area` bounds are display-relative logical + /// points -- the same space the overlay draws in, which is what makes the + /// crop and the recording agree. + #[test] + fn area_bounds_are_display_relative_logical_points() { + let bounds = rect(120., 64., 800., 600.).to_bounds(); + assert_eq!(bounds.position().x(), 120.); + assert_eq!(bounds.position().y(), 64.); + assert_eq!(bounds.size().width(), 800.); + assert_eq!(bounds.size().height(), 600.); + } +} diff --git a/apps/desktop-gpui/src/theme.rs b/apps/desktop-gpui/src/theme.rs index 38dd47a183..1d76b5301e 100644 --- a/apps/desktop-gpui/src/theme.rs +++ b/apps/desktop-gpui/src/theme.rs @@ -55,6 +55,8 @@ pub struct Theme { pub blue_10: Rgba, pub blue_11: Rgba, + pub red_2: Rgba, + pub red_4: Rgba, pub red_9: Rgba, pub red_10: Rgba, @@ -102,6 +104,8 @@ impl Theme { blue_10: rgb(0x0588f0), blue_11: rgb(0x0d74ce), + red_2: rgb(0xfff7f7), + red_4: rgb(0xffdbdc), red_9: rgb(0xe5484d), red_10: rgb(0xdc3e42), @@ -138,6 +142,8 @@ impl Theme { blue_10: rgb(0x3b9eff), blue_11: rgb(0x70b8ff), + red_2: rgb(0x201314), + red_4: rgb(0x500f1c), red_9: rgb(0xe5484d), red_10: rgb(0xec5d5e), @@ -182,6 +188,12 @@ impl Theme { } } + /// Tailwind v4's stock `blue-600` -- the target-select overlay's highlight + /// wash (`bg-blue-600/40`). Not a Radix step and not one of the app's own + /// `--blue-*` overrides: the overlay reaches straight for the Tailwind + /// palette there. + pub const TARGET_HIGHLIGHT: u32 = 0x155dfc; + /// Traffic light fills. Minimize is never drawn in the main window /// (`showMinimize={false}`), so only close and zoom are needed. pub const TRAFFIC_CLOSE: u32 = 0xff5f57; From af4b33ab7fd0f803fc950f54766f4aebbb2545a7 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:30:43 +0100 Subject: [PATCH 020/146] docs(desktop-gpui): roadmap, deviations and traps for the target-select overlays Target-select row done (capture-area marked superseded - nothing in the Tauri frontend opens the standalone window), the TEMP start footer and 'Area does not arm' deviations removed, overlay deviations added (focused-key Escape, start-only cluster, bubble over inline preview), and two new gpui traps: AppKit window mutations inside a gpui update, and the titled-window menu-bar constraint. --- apps/desktop-gpui/README.md | 38 ++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/apps/desktop-gpui/README.md b/apps/desktop-gpui/README.md index 7c8bf1d766..2cd4d3b8ba 100644 --- a/apps/desktop-gpui/README.md +++ b/apps/desktop-gpui/README.md @@ -98,7 +98,7 @@ Things that are deliberately different, and why. | **Recents is header + empty state** | Thumbnails need the recordings library. | | **Window filter is duplicated** | The level-0 listability rule is copied from `cap_recording::sources::screen_capture` rather than imported — that crate drags in ffmpeg and the whole encode stack, which this app has no other reason to build. | -### One gpui trap worth knowing +### gpui traps worth knowing **Do not touch the window from inside `open_window`'s builder closure.** `MainWindow::new` runs before the platform window is finished. A `resize` there @@ -108,6 +108,20 @@ without ever scheduling a frame. Both failures are silent. Set the initial size through the bounds passed to `open_window`, and start async work from `main` once the window handle exists. +**Do not mutate AppKit window state from inside a gpui update.** `setFrame:` +and `orderFrontRegardless` synchronously fire gpui's own move/resize/frame +callbacks, which re-borrow the App — inside a window or entity update that +logs `RefCell already borrowed` and silently drops the callback. Grab the +`NSWindow` inside the update, then do the AppKit calls from a spawned task +(`platform::place_overlay_panel`). + +**Titled windows cannot cover the menu bar.** Every gpui window — `PopUp` +panels included — carries `NSTitledWindowMask`, so AppKit's +`constrainFrameRect:toScreen:` pushes a display-covering window 33pt down. +The Tauri app never sees this because tao's `NSWindow` subclass overrides the +method to return the rect unchanged; `platform::install_occlusion_shim` +installs the same override on gpui's window classes. + ## Parity roadmap Sizes are the Tauri app's, from `apps/desktop/src-tauri/src/windows.rs`. @@ -117,9 +131,9 @@ Sizes are the Tauri app's, from `apps/desktop/src-tauri/src/windows.rs`. | Main | 330×395 / 600×660 | **Done** — layout, devices, pickers, modes, recording, level-100 panel behavior | | Camera preview | size×(size+56), 150–600 | **Done** — live frames, round/square/full shapes, S/L sizes, hover toolbar, corner resize, drag, persisted chrome state, capture-excluded in studio / included in instant | | Recording controls | 320×150 | **Done** — live timer, pause/resume, restart, delete, live mic level, instant-mode mute, drag; capture-excluded, non-activating | -| Target select overlay | per display | Not started — one transparent window per display | +| Target select overlay | per display | **Done** — all four variants (display / window / area / camera-only), one transparent non-activating panel per display at the Tauri-verbatim level 7, cursor-following highlight, click-to-pin windows with app icons, draw/move/resize area selection with min-size validation, the real Start Recording flow (overlays close, bar opens, overlays excluded from capture), Escape/close dismiss | | Window capture occluder | per display | Not started | -| Capture area | per display | Not started | +| Capture area | per display | Superseded — area selection is the target-select overlay's area variant; the Tauri app still registers a standalone `capture-area` window but nothing in its frontend opens it | | Recordings overlay | per display | Not started | | Mode select | 580×340 | Partial — exists as a panel, not a window | | Settings | 782×775 (min 780×560) | Not started | @@ -155,11 +169,12 @@ Recording-specific deviations: | | | |---|---| -| **TEMP start button** | The real app starts recording from the fullscreen target-select overlay, which does not exist yet. An armed target (Display selected, or a concrete window picked) shows a pinned blue Start Recording footer instead. | | **Bar timer, no countdown** | The countdown variant (`window.COUNTDOWN`) needs the settings store; the bar goes straight from Starting to the timer. | -| **Settings button is inert** | The bar's recording-settings popover menu is not built. | -| **Area does not arm** | Area needs the selection overlay. | -| **No overlay blur** | The recording overlay is `bg-gray-1/80` without `backdrop-blur-xs`; this gpui rev has no per-element backdrop blur hook. | +| **Settings button is inert** | The bar's recording-settings popover menu is not built; the same applies to the gear button in the overlay's start cluster. | +| **No overlay blur** | The recording overlay is `bg-gray-1/80` without `backdrop-blur-xs`; this gpui rev has no per-element backdrop blur hook. The target-select overlay's liquid-glass surfaces drop their `backdrop-blur-xl` for the same reason. | +| **Escape is a focused key handler** | The Tauri app registers Escape as a *global* shortcut while the overlays are up; here it is a key handler on the overlay that holds focus (the one on the cursor's display). Escape pressed while another app is active does not dismiss. | +| **Overlay cluster is start-only** | The device row (camera/microphone selects under the start pill), the mode dropdown behind the caret, and the "What is X Mode?" link are deferred — device pickers live in the main window. The area toolbar shows the size readout but not the aspect-ratio/reset/fill/lock controls. | +| **Camera-only keeps the bubble** | The TSX inlines a camera preview into the camera-only overlay; ours keeps the separate camera preview window it already has. | | **Camera id is DeviceID-only** | The Tauri app persists `ModelID` when a camera advertises one, so the same camera survives re-plugging into a different port. | | **Defaults are the builder's** | `desktop_recording_defaults` (studio quality, fps caps, custom cursor) is not applied yet — it reads the Tauri settings store. | @@ -172,6 +187,15 @@ summed duration is ~⅔ of wall time is the proof the pause reached the engine. would, opening the preview bubble; combined with `CAP_GPUI_AUTO_RECORD` it verifies the camera track end to end. +`CAP_GPUI_AUTO_OVERLAY=display|window|area|camera` arms a target mode the way +clicking its tile does and opens the target-select overlays; on its own it +leaves them up (how the screenshots are taken), combined with +`CAP_GPUI_AUTO_RECORD` it routes the start through the overlay's own Start +button. `CAP_GPUI_AUTO_AREA=x,y,width,height` seeds the crop a drag would have +drawn (synthetic drags are dropped without Accessibility). The area variant +was verified end to end: a seeded 800×500 crop recorded a 1600×1000 (2×) +display track. + ## Camera preview and app-scoped feeds Selecting a camera spins up an app-scoped `CameraFeed` actor and opens the From 81b6d4df8954c9bc48aad2a27ec19daece4e2d89 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:32:47 +0100 Subject: [PATCH 021/146] fix(desktop-gpui): hide/reshow the main window outside the App borrow The recording start/stop flows called orderOut:/makeKeyAndOrderFront: inside window updates, which re-enters gpui's window callbacks and logged 'RefCell already borrowed' three times per stop, dropping the callbacks. Same fix shape as the overlay placement: grab the retained NSWindow inside the update, do the AppKit call from a spawned task. A full record-stop-reshow cycle now logs zero borrow errors. --- apps/desktop-gpui/src/app_windows.rs | 29 ++++++++++++++++++++++++---- apps/desktop-gpui/src/platform.rs | 25 ++++++++++++------------ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/apps/desktop-gpui/src/app_windows.rs b/apps/desktop-gpui/src/app_windows.rs index 057a70b2d0..6f3e43c6e2 100644 --- a/apps/desktop-gpui/src/app_windows.rs +++ b/apps/desktop-gpui/src/app_windows.rs @@ -58,8 +58,19 @@ pub fn init(main: WindowHandle, session: Entity, c if phase == Phase::Idle && last_phase != Phase::Idle { close_controls(&session, cx); let main = cx.global::().main; - main.update(cx, |_, window, _| platform::show_window(window)) - .ok(); + // `makeKeyAndOrderFront:` re-enters gpui's window callbacks, so it + // runs from a task, not inside this observer's borrow (the + // `place_overlay_panel` rule). + let native = main + .update(cx, |_, window, _| platform::native_window(window)) + .ok() + .flatten(); + cx.spawn(async move |_| { + if let Some(native) = &native { + platform::show_native(native); + } + }) + .detach(); } last_phase = phase; }) @@ -404,8 +415,18 @@ pub fn begin_recording(config: StartConfig, cx: &mut App) { }); if bar_open { let main = cx.global::().main; - main.update(cx, |_, window, _| platform::hide_window(window)) - .ok(); + // Same rule as the reshow in `init`: `orderOut:` runs outside the + // borrow. + let native = main + .update(cx, |_, window, _| platform::native_window(window)) + .ok() + .flatten(); + cx.spawn(async move |_| { + if let Some(native) = &native { + platform::hide_native(native); + } + }) + .detach(); } let config = StartConfig { diff --git a/apps/desktop-gpui/src/platform.rs b/apps/desktop-gpui/src/platform.rs index 8cc345fa75..96722247ab 100644 --- a/apps/desktop-gpui/src/platform.rs +++ b/apps/desktop-gpui/src/platform.rs @@ -298,19 +298,18 @@ mod mac { ns_window.setHasShadow(behavior.shadow); } - /// `orderOut:` -- hide without closing, the way the Tauri main window hides - /// while the recording controls bar is up. - pub fn hide_window(window: &Window) { - if let Some(ns_window) = ns_window(window) { - ns_window.orderOut(None); - } + /// `orderOut:` -- hide without closing, the way the Tauri main window + /// hides while the recording controls bar is up. Takes the retained + /// handle for the same reason [`place_overlay_panel`] does: ordering a + /// window in or out synchronously re-enters gpui's window callbacks, so + /// it must run with no gpui borrow held. + pub fn hide_native(native: &NativeWindow) { + native.0.orderOut(None); } - /// Reverse of [`hide_window`]. - pub fn show_window(window: &Window) { - if let Some(ns_window) = ns_window(window) { - ns_window.makeKeyAndOrderFront(None); - } + /// Reverse of [`hide_native`] -- `makeKeyAndOrderFront:`. + pub fn show_native(native: &NativeWindow) { + native.0.makeKeyAndOrderFront(None); } /// Show without stealing key status -- `orderFrontRegardless`, what the @@ -373,8 +372,8 @@ mod stub { pub fn install_occlusion_shim() {} pub fn kick_display_link(_window: &Window) {} pub fn apply_panel_behavior(_window: &Window, _behavior: PanelBehavior) {} - pub fn hide_window(_window: &Window) {} - pub fn show_window(_window: &Window) {} + pub fn hide_native(_native: &NativeWindow) {} + pub fn show_native(_native: &NativeWindow) {} pub fn show_window_without_focus(_window: &Window) {} pub fn window_number(_window: &Window) -> Option { None From 08fe9abe952176f08ec4db032b6974360d0492c0 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:01:07 +0100 Subject: [PATCH 022/146] feat(desktop-gpui): native liquid-glass window material for the main window The main window now sits on the same native material the Tauri app gives its chrome windows via applyMacOSWindowMaterial("panel"): an NSGlassEffectView on macOS 26+ (runtime class lookup, style regular, radius 16, the SystemManaged main-window path -- no always-active SPI pinning), an NSVisualEffectView windowBackground/BehindWindow fallback below that, and on both paths the content view's layer clipped to a 16px continuous-corner squircle. gpui's contentView is a plain AppKit container with the Metal view as a subview, so the material drops in NSWindowBelow it exactly as the Tauri recipe assumes. Installed from a spawned task on the retained NSWindow -- subview insertion re-enters gpui's window callbacks, so it cannot run inside an update. What the shell paints over the material is transcribed from theme.css's --macos-settings-* tokens into theme::MaterialTokens (glass tint rgba(255,255,255,0.55) light / rgba(17,17,17,0.88) dark, control fills, selection, text), with the CSS rule quoted next to each value. The body remaps are liquid-glass-only, which is the CSS's own asymmetry: under vibrancy only the shell and header change. No occlusion SPI, no CGS, no setState:/setActive: probing -- the shipping app's WindowServer wedge is the recorded reason. CAP_GPUI_AUTO_EXPAND=1 drives the expand animation for verification. Verified: NSGlassEffectView path confirmed by log on macOS 26; translucency screenshotted light+dark, compact+expanded (glass tracks the resize via the autoresizing mask); full record cycle finalizes with zero borrow errors; 12 tests green. --- apps/desktop-gpui/Cargo.toml | 4 +- apps/desktop-gpui/README.md | 83 +++++- apps/desktop-gpui/src/main.rs | 45 ++- apps/desktop-gpui/src/main_window.rs | 138 ++++++--- apps/desktop-gpui/src/platform.rs | 161 ++++++++++- apps/desktop-gpui/src/theme.rs | 400 +++++++++++++++++++++++++++ 6 files changed, 774 insertions(+), 57 deletions(-) diff --git a/apps/desktop-gpui/Cargo.toml b/apps/desktop-gpui/Cargo.toml index 8576d98958..abc0076833 100644 --- a/apps/desktop-gpui/Cargo.toml +++ b/apps/desktop-gpui/Cargo.toml @@ -61,7 +61,9 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } # Native window treatment (panel levels, Spaces) gpui does not expose. Same # versions gpui's own tree compiles, so no second objc2 universe gets built. objc2 = "0.5.2" -objc2-app-kit = { version = "0.2.2", features = ["NSWindow", "NSView", "NSResponder", "NSGraphics", "NSPasteboard"] } +# `NSVisualEffectView` is the pre-macOS-26 window-material fallback. Features +# only -- the versions stay pinned, or a second objc2 universe gets built. +objc2-app-kit = { version = "0.2.2", features = ["NSWindow", "NSView", "NSResponder", "NSGraphics", "NSPasteboard", "NSVisualEffectView"] } # NSRect/NSPoint/NSSize for `setFrame:display:`. Already in the tree as # objc2-app-kit's own dependency, so this adds nothing to the build. objc2-foundation = "0.2.2" diff --git a/apps/desktop-gpui/README.md b/apps/desktop-gpui/README.md index 2cd4d3b8ba..4797a4acf3 100644 --- a/apps/desktop-gpui/README.md +++ b/apps/desktop-gpui/README.md @@ -62,6 +62,8 @@ Everything below is real, not mocked. Spaces, exactly as `windows.rs` configures it — applied through a small `platform` module that reaches the `NSWindow` behind a gpui window via `raw-window-handle` (gpui exposes no level/Spaces API). +- **The native window material.** The main window sits on real Liquid Glass, + with the vibrancy fallback behind it. See below. - **Recording controls bar.** The 320×150 always-on-screen panel from `in-progress-recording.tsx`: stop with a live timer, pause/resume, restart, delete, mic indicator, drag handle. A non-activating panel @@ -80,6 +82,58 @@ from `apps/desktop/src/styles/theme.css` applied**. Six of the dark grays and `gray-11` are not stock Radix, so regenerating the palette from a Radix crate would quietly change the app's colours. +### The native window material + +The main window is not a `bg-gray-1` slab. `platform::install_window_material` +does what `applyMacOSWindowMaterial("panel")` plus +`apply_main_window_liquid_glass_background` do in the shipping app: + +- **macOS 26+** — an `NSGlassEffectView`, found by runtime class lookup, + `setStyle:` regular (`SystemManaged`, radius 16), inserted `NSWindowBelow` + gpui's Metal view. gpui's `contentView` is a plain AppKit container with the + renderer view added as a subview, which is exactly the shape the Tauri code + assumes, so the material drops in underneath unchanged. The *always-active* + pin (`setState:` / `setActive:` probing) is deliberately **not** reproduced: + that is the other windows' path, and the main window is system-managed in + the shipping app too. +- **Below that** — an `NSVisualEffectView` on `windowBackground` (12), + `BehindWindow`, `FollowsWindowActiveState`: the + `setEffects({ effects: [Effect.WindowBackground] })` fallback. +- **Both paths** clip the content view's layer to a 16px `continuous` corner + (`setCornerRadius:` + `setMasksToBounds:`), or the material renders a square + corner outside the shell's own rounded quad. + +Nothing here is private SPI. No occlusion mutation, no CGS, no window-alpha +tricks — an occlusion-suppressed surface is what wedged WindowServer +machine-wide once already, and the comment in +`apply_liquid_glass_background_inner` is the record of it. + +What the shell paints *over* the material is transcribed from `theme.css`: + +| | Liquid Glass | Vibrancy | +|---|---|---| +| `.cap-window-shell` | `rgba(255,255,255,0.55)` / `rgba(17,17,17,0.88)`, no border | `rgba(244,244,243,0.84)` / `rgba(17,17,17,0.94)` + 1px `--macos-settings-border` | +| `.cap-window-header` | transparent, hairline transparent | `rgba(250,250,249,0.72)` / `rgba(28,28,28,0.88)` | +| body `bg-gray-2`, `bg-gray-3` | `--macos-settings-control-fill` | unchanged | +| body `bg-gray-4` | `--macos-settings-control-hover` | unchanged | +| body `bg-gray-5` | `--macos-settings-control-active` | unchanged | +| body `hover:bg-gray-4/5` | `control-hover` | unchanged | +| body `hover:bg-gray-6/7` | `--macos-settings-selection` | unchanged | +| body `border-gray-4/5/6` | `--macos-settings-border` | unchanged | +| body text, `ring-offset-gray-1` | `--macos-settings-text`, transparent | unchanged | + +The asymmetry is the CSS's, not ours: every `.cap-window-body` remap in +`theme.css` is gated on `[data-macos-visual-system="liquid-glass"]`, so +vibrancy only ever changes the shell and the header. The resolved values live +in `theme::MaterialTokens` with the rule each came from quoted next to it, and +`Theme` exposes them as `shell_bg()` / `body_fill(n)` / `body_hover_fill(n)` / +`body_border(n)` so a call site still reads as the Tailwind class it came from. + +The material is installed from a task after the window handle exists — subview +insertion re-enters gpui's window callbacks, so it cannot run inside an update +— and the result lands in a `platform::WindowMaterial` global that `render` +polls through `sync_appearance`. + ## Deviations from the Tauri app Things that are deliberately different, and why. @@ -87,7 +141,9 @@ Things that are deliberately different, and why. | | | |---|---| | **Traffic lights are hand-drawn** | The Tauri main window returns `None` from `traffic_lights_position`, which routes it to `decorations(false)`; the lights are HTML there too. `titlebar: None` is the gpui equivalent. Minimize is not drawn, and zoom toggles expand/collapse. | -| **No vibrancy** | The real macOS shell is a translucent material, not `bg-gray-1`. This is the single largest visual gap. | +| **Only the main window is on a material** | The main window is native (see below), which matches the shipping app exactly: `applyMacOSWindowMaterial` runs only in the `(window-chrome)` layout, so the camera bubble, the recording bar and the target overlays never had a native material in the Tauri app either — the bar's liquid-glass look is painted CSS. The windows that will need the material later are the other chrome windows: settings (radius 26), mode select, upgrade. | +| **No header backdrop filter** | On the vibrancy path `.cap-window-header` is `rgba(250,250,249,0.72)` *plus* `backdrop-filter: blur(28px) saturate(1.45)`. The wash is here, the filter is not — same missing hook as the recording overlay's `backdrop-blur-xs`. | +| **Appearance changes need a relaunch** | `sync_appearance` runs from `render`, and gpui only renders on invalidation, so flipping the system to light while the app is up leaves the dark palette on screen until something else forces a frame. Pre-existing, not specific to the material. | | **NSWindow, not NSPanel** | The Tauri app class-swizzles its windows into `NSPanel`s via `tauri_nspanel`. Here the main window stays a normal `NSWindow` and gets the observable parts — level 100, `CanJoinAllSpaces \| FullScreenPrimary` — from the `platform` module. (`WindowKind::Floating` is *not* a shortcut to this: its panel hides on app deactivation.) The controls bar *is* a real panel via `WindowKind::PopUp`, whose non-activating behavior it genuinely needs. | | **Controls bar level 8, faithfully** | `windows.rs` raises the bar with `CGWindowLevelForKey(10)` under a constant named `kCGMaximumWindowLevelKey` — but key 10 is `kCGModalPanelWindowLevelKey` (maximum is 14), so the shipping bar actually runs at level 8. Reproduced verbatim rather than "fixed" from over here. | | **Resize does not re-clamp** | Expand/collapse animates over 180ms with an ease-out cubic, as the Tauri app does, but does not re-clamp the window into the monitor work area afterwards — expanding near a screen edge can push the window off it. | @@ -108,12 +164,13 @@ without ever scheduling a frame. Both failures are silent. Set the initial size through the bounds passed to `open_window`, and start async work from `main` once the window handle exists. -**Do not mutate AppKit window state from inside a gpui update.** `setFrame:` -and `orderFrontRegardless` synchronously fire gpui's own move/resize/frame -callbacks, which re-borrow the App — inside a window or entity update that -logs `RefCell already borrowed` and silently drops the callback. Grab the -`NSWindow` inside the update, then do the AppKit calls from a spawned task -(`platform::place_overlay_panel`). +**Do not mutate AppKit window state from inside a gpui update.** `setFrame:`, +`orderFrontRegardless`, subview insertion and content-view layer mutation all +synchronously fire gpui's own move/resize/frame callbacks, which re-borrow the +App — inside a window or entity update that logs `RefCell already borrowed` +and silently drops the callback. Grab the `NSWindow` inside the update, then +do the AppKit calls from a spawned task (`platform::place_overlay_panel`, +`platform::install_window_material`). **Titled windows cannot cover the menu bar.** Every gpui window — `PopUp` panels included — carries `NSTitledWindowMask`, so AppKit's @@ -128,7 +185,7 @@ Sizes are the Tauri app's, from `apps/desktop/src-tauri/src/windows.rs`. | Window | Size | Status | |---|---|---| -| Main | 330×395 / 600×660 | **Done** — layout, devices, pickers, modes, recording, level-100 panel behavior | +| Main | 330×395 / 600×660 | **Done** — layout, devices, pickers, modes, recording, level-100 panel behavior, native Liquid Glass / vibrancy material | | Camera preview | size×(size+56), 150–600 | **Done** — live frames, round/square/full shapes, S/L sizes, hover toolbar, corner resize, drag, persisted chrome state, capture-excluded in studio / included in instant | | Recording controls | 320×150 | **Done** — live timer, pause/resume, restart, delete, live mic level, instant-mode mute, drag; capture-excluded, non-activating | | Target select overlay | per display | **Done** — all four variants (display / window / area / camera-only), one transparent non-activating panel per display at the Tauri-verbatim level 7, cursor-following highlight, click-to-pin windows with app icons, draw/move/resize area selection with min-size validation, the real Start Recording flow (overlays close, bar opens, overlays excluded from capture), Escape/close dismiss | @@ -257,3 +314,13 @@ screencapture -x -o -l shot.png Measure against it rather than trusting the eye — the 2×-scale bug above was invisible until the compact and expanded headers were compared pixel by pixel. + +A per-window capture flattens only the window's *own* layers, so it never +contains the behind-window backdrop: the shell comes out as the flat tint over +black (a useful way to read the tint's alpha back, in fact — a 0.55 white tint +lands on `#8c8c8c`). Anything about the material actually being live has to be +judged from a full-screen `screencapture -x`. + +`CAP_GPUI_AUTO_EXPAND=1` opens the window expanded, the way clicking the zoom +light does, for the same reason as the other `CAP_GPUI_AUTO_*` hooks: +unprivileged synthetic clicks are dropped. diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index 59bb2844be..644a7d10b0 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -25,6 +25,12 @@ use crate::{assets::Assets, main_window::MainWindow, session::RecordingSession}; const MAIN_WINDOW_WIDTH: f32 = 330.; const MAIN_WINDOW_HEIGHT: f32 = 395.; +/// The corner radius the native material is clipped to. `radius = 16` for +/// material `"panel"` on both visual systems in +/// `apps/desktop/src/utils/macos-window-material.ts`, and the same 16 the +/// shell paints with (`rounded-[16px]`). +const MAIN_WINDOW_MATERIAL_RADIUS: f64 = 16.; + fn parse_auto_record(spec: &str) -> Option<(main_window::Mode, u64)> { let (mode, secs) = spec.split_once(':')?; let mode = match mode { @@ -106,7 +112,7 @@ fn main() { // The panel behavior (`MAIN_PANEL_LEVEL`, all Spaces -- what the Tauri // app does via tauri_nspanel) is applied here for the same reason: the // NSWindow does not exist yet inside the builder closure. - window_handle + let native_main = window_handle .update(cx, |view, window, cx| { platform::apply_panel_behavior( window, @@ -116,10 +122,45 @@ fn main() { shadow: true, }, ); - view.start_enumeration(window, cx) + tracing::info!( + number = platform::window_number(window), + "main window opened" + ); + view.start_enumeration(window, cx); + view.auto_expand(window, cx); + // The AppKit work below must not run inside this update: + // inserting a subview and mutating the content view's layer + // synchronously re-enters gpui's own window callbacks, which + // re-borrow the App. Grab the retained NSWindow here, act on + // it from a task (the `place_overlay_panel` rule). + platform::native_window(window) }) .expect("failed to start device enumeration"); + // The native window material: `NSGlassEffectView` on macOS 26+, + // `NSVisualEffectView` vibrancy below that -- what + // `applyMacOSWindowMaterial("panel")` does in the Tauri app. Nothing + // paints it; the shell paints a translucent tint *over* it, so the + // window has to be told which one landed. + cx.spawn(async move |cx| { + let Some(native) = native_main else { + tracing::error!("no NSWindow behind the main window; material not installed"); + return; + }; + let kind = platform::install_window_material(&native, MAIN_WINDOW_MATERIAL_RADIUS); + match kind { + Some(kind) => tracing::info!(?kind, "installed main window material"), + None => tracing::info!("no native window material available"), + } + cx.update(|cx| { + cx.set_global(platform::WindowMaterial(kind)); + // The palette is resolved in `render`, which has already run + // by now -- nudge it so the tint replaces the opaque shell. + window_handle.update(cx, |_, _, cx| cx.notify()).ok(); + }); + }) + .detach(); + // `CAP_GPUI_AUTO_RECORD=studio:5` / `instant:4`: arm the primary // display and record for N seconds. The end-to-end check drives the // recorder this way because unprivileged synthetic clicks are dropped. diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index 3a5c55e5e0..a14febf6d2 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -369,10 +369,28 @@ impl MainWindow { } } - fn sync_appearance(&mut self, window: &Window) { + /// Re-resolve the palette when the system appearance flips, or when the + /// native material lands. + /// + /// The material is installed from a spawned task after the window exists + /// (see `main`), so the first frames paint before it is known -- the + /// global is polled here rather than pushed, and the install notifies the + /// window once so this runs again. + fn sync_appearance(&mut self, window: &Window, cx: &gpui::App) { let appearance = Appearance::from_window(window.appearance()); - if appearance != self.theme.appearance { - self.theme = Theme::new(appearance); + let material = crate::platform::active_material(cx); + if appearance != self.theme.appearance || material != self.theme.material_kind() { + self.theme = Theme::new(appearance).with_material(material); + } + } + + /// `CAP_GPUI_AUTO_EXPAND=1`: open expanded, the way clicking the zoom + /// light does. Same reason as the other `CAP_GPUI_AUTO_*` hooks -- + /// unprivileged synthetic clicks are dropped, so the screenshot harness + /// needs a way in. + pub fn auto_expand(&mut self, window: &mut Window, cx: &mut Context) { + if std::env::var("CAP_GPUI_AUTO_EXPAND").is_ok_and(|value| value == "1") && !self.expanded { + self.toggle_expanded(window, cx); } } @@ -701,7 +719,7 @@ impl MainWindow { impl Render for MainWindow { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - self.sync_appearance(window); + self.sync_appearance(window, cx); let theme = self.theme; div() @@ -710,9 +728,18 @@ impl Render for MainWindow { .flex_col() .overflow_hidden() // `rounded-[16px]` on `.cap-window-shell`, matched natively by - // `apply_squircle_corners(&window, 16.0)` in the Tauri app. + // `apply_squircle_corners(&window, 16.0)` in the Tauri app -- and, + // when a material is installed, by the content-view squircle clip + // in `platform::install_window_material`. .rounded(px(16.)) - .bg(theme.gray_1) + // Opaque `bg-gray-1` with no material; a translucent tint over the + // live `NSGlassEffectView`/`NSVisualEffectView` backdrop with one. + .bg(theme.shell_bg()) + // Only the vibrancy path draws a shell border -- Liquid Glass sets + // `border: 0`. + .when_some(theme.shell_border(), |this, color| { + this.border_1().border_color(color) + }) .font_family("Geist") .text_color(theme.text_primary) .child(self.render_header(window, cx)) @@ -741,10 +768,10 @@ impl MainWindow { .w_full() .h(px(HEADER_HEIGHT)) .flex_shrink_0() - .bg(theme.gray_2) + .bg(theme.header_bg()) // `divide-y divide-gray-5` between header and body. .border_b_1() - .border_color(theme.gray_5) + .border_color(theme.header_border()) .child(self.render_traffic_lights(focused, cx)) .child(self.render_header_actions(cx)) } @@ -871,7 +898,10 @@ impl MainWindow { .min_h_0() .px(px(13.)) .pb(px(8.)) - .gap(px(8.)); + .gap(px(8.)) + // `.cap-window-body { color: var(--macos-settings-text) }` under + // panel glass; `--text-primary` otherwise. + .text_color(self.theme.body_text()); // The logo/mode row is hidden while a picker is open -- the panel takes // the full body, exactly as `!activeMenu() && ...` does in index.tsx. @@ -1042,7 +1072,7 @@ impl MainWindow { .text_color(theme.gray_12) .child("Back"), ) - .hover(|style| style.bg(theme.gray_4)) + .hover(|style| style.bg(theme.body_hover_fill(4))) .on_click(cx.listener(|this, _, _window, cx| this.close_panel(cx))), ) .child(header_trailing), @@ -1151,8 +1181,8 @@ impl MainWindow { .px(px(8.)) .rounded(px(6.)) .border_1() - .border_color(theme.gray_5) - .bg(theme.gray_2) + .border_color(theme.body_border(5)) + .bg(theme.body_fill(2)) .text_size(px(12.)) .child( svg() @@ -1477,7 +1507,7 @@ impl MainWindow { } else { gpui::transparent_black() }) - .bg(theme.gray_3) + .bg(theme.body_fill(3)) .child( div() .flex() @@ -1485,7 +1515,7 @@ impl MainWindow { .justify_center() .w_full() .h(px(76.)) - .bg(theme.gray_4) + .bg(theme.body_fill(4)) .child(svg().path(icon).size(px(24.)).text_color(theme.gray_9)), ) .child( @@ -1520,7 +1550,7 @@ impl MainWindow { .child(metadata) })), ) - .hover(|style| style.bg(theme.gray_4)) + .hover(|style| style.bg(theme.body_hover_fill(4))) .on_click(on_click) } @@ -1547,16 +1577,16 @@ impl MainWindow { .border_color(if selected { Hsla::from(theme.blue_9) } else if theme.is_dark() { - Hsla::from(theme.gray_5) + theme.body_border(5) } else { - Hsla::from(theme.gray_4) + theme.body_border(4) }) .bg(if selected { theme.tile_selected_bg() } else if theme.is_dark() { - Hsla::from(theme.gray_3) + theme.body_fill(3) } else { - Hsla::from(theme.gray_2) + theme.body_fill(2) }) .child( svg() @@ -1594,7 +1624,7 @@ impl MainWindow { .child(mode.panel_description()), ), ) - .hover(|style| style.bg(theme.gray_4)) + .hover(|style| style.bg(theme.body_hover_fill(4))) .on_click(cx.listener(move |this, _, _window, cx| { this.mode = mode; crate::target_overlay::TargetSelect::global(cx) @@ -1665,7 +1695,9 @@ impl MainWindow { .text_size(px(14.)) .text_color(foreground) .when(selected, |this| this.bg(theme.blue_500)) - .when(!selected, |this| this.hover(|style| style.bg(theme.gray_4))) + .when(!selected, |this| { + this.hover(|style| style.bg(theme.body_hover_fill(4))) + }) .when_some(audio_level.filter(|_| selected), |this, level| { this.child( div() @@ -1758,12 +1790,12 @@ impl MainWindow { .py(px(2.)) .rounded(px(8.)) .border_1() - .border_color(theme.gray_5) - .bg(theme.gray_3) + .border_color(theme.body_border(5)) + .bg(theme.body_fill(3)) .text_size(px(9.6)) .text_color(theme.gray_12) .child("Personal") - .hover(|style| style.bg(theme.gray_5)) + .hover(|style| style.bg(theme.body_hover_fill(5))) // TODO: opens the Upgrade window (950x850) once that window exists. } @@ -1807,13 +1839,17 @@ impl MainWindow { .justify_center() .size(px(28.)) .rounded_full() - .bg(if selected { theme.gray_7 } else { theme.gray_3 }) + .bg(if selected { + Hsla::from(theme.gray_7) + } else { + theme.body_fill(3) + }) .when(selected, |this| { // `ring-2 ring-blue-500 ring-offset-1 ring-offset-gray-1`. this.border_2() .border_color(theme.blue_500) .shadow(vec![gpui::BoxShadow { - color: Hsla::from(theme.gray_1), + color: theme.ring_offset(), offset: gpui::point(px(0.), px(0.)), blur_radius: px(0.), spread_radius: px(1.), @@ -1826,7 +1862,7 @@ impl MainWindow { .size(px(mode.icon_size())) .text_color(theme.gray_12), ) - .hover(|style| style.bg(theme.gray_7)) + .hover(|style| style.bg(theme.body_hover_fill(7))) .on_click(cx.listener(move |this, _, _window, cx| { this.mode = mode; // Open overlays label their start button with the mode. @@ -1846,8 +1882,8 @@ impl MainWindow { .p(px(6.)) .rounded_full() .border_1() - .border_color(theme.gray_5) - .bg(theme.gray_3) + .border_color(theme.body_border(5)) + .bg(theme.body_fill(3)) .child(button(Mode::Instant, "mode-instant")) .child(button(Mode::Studio, "mode-studio")) .child(button(Mode::Screenshot, "mode-screenshot")) @@ -1864,7 +1900,7 @@ impl MainWindow { .justify_center() .p(px(4.)) .rounded_full() - .bg(theme.gray_5) + .bg(theme.body_fill(5)) .child( svg() .path("icons/info.svg") @@ -1931,11 +1967,15 @@ impl MainWindow { .overflow_hidden() .rounded(px(8.)) .border_1() - .border_color(if selected { theme.blue_8 } else { theme.gray_6 }) + .border_color(if selected { + Hsla::from(theme.blue_8) + } else { + theme.body_border(6) + }) .bg(if selected { theme.tile_selected_bg() } else { - Hsla::from(theme.gray_2) + theme.body_fill(2) }) .child(self.target_button_inner(target, true, cx)) .child( @@ -1947,15 +1987,15 @@ impl MainWindow { .items_center() .justify_center() .border_l_1() - .border_color(theme.gray_6) - .bg(theme.gray_4) + .border_color(theme.body_border(6)) + .bg(theme.body_fill(4)) .child( svg() .path("icons/chevron-down.svg") .size(px(16.)) .text_color(theme.gray_11), ) - .hover(|style| style.bg(theme.gray_6)) + .hover(|style| style.bg(theme.body_hover_fill(6))) .on_click(cx.listener(move |this, _, window, cx| { this.open_panel(Panel::Target(target), window, cx); })), @@ -1973,11 +2013,15 @@ impl MainWindow { .overflow_hidden() .rounded(px(8.)) .border_1() - .border_color(if selected { theme.blue_8 } else { theme.gray_6 }) + .border_color(if selected { + Hsla::from(theme.blue_8) + } else { + theme.body_border(6) + }) .bg(if selected { theme.tile_selected_bg() } else { - Hsla::from(theme.gray_2) + theme.body_fill(2) }) .child(self.target_button_inner(target, false, cx)) } @@ -2024,7 +2068,7 @@ impl MainWindow { style.bg(if selected { theme.tile_selected_hover_bg() } else { - Hsla::from(theme.gray_4) + theme.body_hover_fill(4) }) }) .on_click(cx.listener(move |this, _, _window, cx| { @@ -2201,8 +2245,8 @@ impl MainWindow { .rounded(px(12.)) .border_dashed() .border_1() - .border_color(theme.gray_5) - .bg(theme.gray_2) + .border_color(theme.body_border(5)) + .bg(theme.body_fill(2)) .child( svg() .path("icons/history.svg") @@ -2264,8 +2308,8 @@ impl MainWindow { .h(px(DEVICE_ROW_HEIGHT)) .rounded(px(8.)) .border_1() - .border_color(theme.gray_6) - .bg(theme.gray_2) + .border_color(theme.body_border(6)) + .bg(theme.body_fill(2)) .cursor_default() .overflow_hidden() .child( @@ -2286,7 +2330,13 @@ impl MainWindow { .child(label), ) .child(pill.render(theme)) - .hover(|style| style.bg(theme.gray_4).border_color(theme.gray_8)) + // `hover:border-gray-8` is not one of the classes theme.css remaps, so + // the border keeps its Radix step under the material. + .hover(|style| { + style + .bg(theme.body_hover_fill(4)) + .border_color(theme.gray_8) + }) } } @@ -2320,7 +2370,7 @@ impl PillState { fn render(self, theme: Theme) -> impl IntoElement { let (bg, fg, text) = match self { Self::On => (Hsla::from(theme.blue_9), gpui::white(), "On"), - Self::Off => (Hsla::from(theme.gray_5), Hsla::from(theme.gray_11), "Off"), + Self::Off => (theme.body_fill(5), Hsla::from(theme.gray_11), "Off"), }; div() diff --git a/apps/desktop-gpui/src/platform.rs b/apps/desktop-gpui/src/platform.rs index 96722247ab..50f9e5356c 100644 --- a/apps/desktop-gpui/src/platform.rs +++ b/apps/desktop-gpui/src/platform.rs @@ -18,6 +18,34 @@ /// auto-hide reveal, below context menus. pub const MAIN_WINDOW_LEVEL: isize = 100; +/// Which native material sits behind a window's content. +/// +/// `applyMacOSWindowMaterial` in +/// `apps/desktop/src/utils/macos-window-material.ts` picks between exactly +/// these two: `visualSystem = majorVersion >= 26 ? "liquid-glass" : +/// "vibrancy"`. The main window is material `"panel"`, radius 16 on both. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MaterialKind { + /// `NSGlassEffectView`, macOS 26+ only. + LiquidGlass, + /// `NSVisualEffectView` with the `windowBackground` material -- the + /// `setEffects({ effects: [Effect.WindowBackground], ... })` fallback. + Vibrancy, +} + +/// The material [`install_window_material`] actually installed on the main +/// window, so `render` can pick the tint that belongs over it. `None` means +/// the shell paints its opaque `gray-1` self (non-mac, or the install failed). +#[derive(Debug, Clone, Copy, Default)] +pub struct WindowMaterial(pub Option); + +impl gpui::Global for WindowMaterial {} + +pub fn active_material(cx: &gpui::App) -> Option { + cx.try_global::() + .and_then(|material| material.0) +} + #[derive(Debug, Clone, Copy)] pub struct PanelBehavior { pub level: isize, @@ -40,7 +68,133 @@ mod mac { use objc2_app_kit::{NSView, NSWindow, NSWindowCollectionBehavior}; use raw_window_handle::{HasWindowHandle, RawWindowHandle}; - use super::PanelBehavior; + use super::{MaterialKind, PanelBehavior}; + + /// `NS_GLASS_EFFECT_VIEW_STYLE_REGULAR` in + /// `apps/desktop/src-tauri/src/platform/macos/mod.rs`. The main window + /// takes the `SystemManaged` path, which is `setStyle:` and nothing else: + /// the always-active pin (`setState:` / `setActive:` probing) is for the + /// *other* Cap windows and is deliberately not reproduced here. + const NS_GLASS_EFFECT_VIEW_STYLE_REGULAR: isize = 0; + + /// `LIQUID_GLASS_IDENTIFIER`, kept byte-identical to the Tauri app so the + /// two view hierarchies read the same in a debugger. + const LIQUID_GLASS_IDENTIFIER: &str = "so.cap.liquid-glass-background"; + + /// `NSViewWidthSizable | NSViewHeightSizable` -- the mask the Tauri glass + /// view gets (`setAutoresizingMask: 18usize`), which is what makes the + /// material track the 330x395 <-> 600x660 resize animation. + const NS_VIEW_WIDTH_HEIGHT_SIZABLE: usize = 2 | 16; + + /// Put the window's native material behind gpui's content. + /// + /// This is `apply_liquid_glass_background_inner(.., SystemManaged)` from + /// `platform/macos/mod.rs` plus the vibrancy fallback + /// `macos-window-material.ts` reaches for when the glass class is missing, + /// translated to the one gpui window we own. gpui's `contentView` is a + /// plain AppKit container with the Metal-backed view added as a subview + /// (`gpui_macos::window`), exactly the shape the Tauri code assumes, so + /// the material goes in underneath it with `NSWindowBelow`. + /// + /// Takes the retained [`NativeWindow`] rather than a `&Window` for the + /// same reason [`place_overlay_panel`] does, and more so: subview + /// insertion and content-layer mutation synchronously re-enter gpui's own + /// window callbacks, so this must run with no gpui borrow held. + /// + /// Everything here is plain AppKit/Core Animation. No occlusion SPI, no + /// CGS, no `setState:`/`setActive:` probing -- see the comment in + /// `apply_liquid_glass_background_inner` for what that cost the shipping + /// app. + pub fn install_window_material(native: &NativeWindow, radius: f64) -> Option { + use objc2::{msg_send, runtime::AnyClass, sel}; + use objc2_app_kit::{ + NSAutoresizingMaskOptions, NSVisualEffectBlendingMode, NSVisualEffectMaterial, + NSVisualEffectState, NSVisualEffectView, NSWindowOrderingMode, + }; + use objc2_foundation::{MainThreadMarker, NSString}; + + // Every caller is on gpui's foreground executor, which is the main + // thread -- `NSVisualEffectView` is main-thread-only and objc2 wants + // that proven. + let mtm = MainThreadMarker::new()?; + let content_view = native.0.contentView()?; + + // Clip the content view itself to the same continuous (squircle) curve + // the material below gets. Without it the material renders a square + // corner outside the shell's own `rounded(16.)` quad. The Tauri app + // applies this on *both* paths for the same reason, and it is plain + // Core Animation -- no private SPI. + content_view.setWantsLayer(true); + unsafe { + let layer: *mut AnyObject = msg_send![&*content_view, layer]; + if !layer.is_null() { + let _: () = msg_send![layer, setCornerRadius: radius]; + let _: () = msg_send![layer, setMasksToBounds: true]; + let continuous = NSString::from_str("continuous"); + let _: () = msg_send![layer, setCornerCurve: &*continuous]; + } + } + + let bounds = content_view.bounds(); + + // `NSGlassEffectView` exists only on macOS 26+; its absence is what + // sends `macos-window-material.ts` down the vibrancy branch. + if let Some(glass_class) = AnyClass::get("NSGlassEffectView") { + unsafe { + let glass: *mut AnyObject = msg_send![glass_class, alloc]; + let glass: *mut AnyObject = msg_send![glass, initWithFrame: bounds]; + if !glass.is_null() { + let identifier = NSString::from_str(LIQUID_GLASS_IDENTIFIER); + let _: () = msg_send![glass, setIdentifier: &*identifier]; + + let responds: bool = + msg_send![glass, respondsToSelector: sel!(setCornerRadius:)]; + if responds { + let _: () = msg_send![glass, setCornerRadius: radius]; + } + + let _: () = msg_send![glass, setStyle: NS_GLASS_EFFECT_VIEW_STYLE_REGULAR]; + let _: () = + msg_send![glass, setAutoresizingMask: NS_VIEW_WIDTH_HEIGHT_SIZABLE]; + // The `alloc` claim is deliberately not balanced: the view + // lives for the life of the process (there is no teardown + // path here, unlike the Tauri command that can be called + // with `enabled: false`), and the superview's retain is + // what keeps it alive. Same steady state the shipping app + // sits in after a single apply. + let _: () = msg_send![ + &*content_view, + addSubview: glass, + positioned: NSWindowOrderingMode::NSWindowBelow, + relativeTo: std::ptr::null_mut::(), + ]; + return Some(MaterialKind::LiquidGlass); + } + } + } + + // The pre-macOS-26 fallback: `setEffects({ effects: + // [Effect.WindowBackground], state: FollowsWindowActiveState, radius: + // 16 })`. Tauri's `Effect.WindowBackground` is `NSVisualEffectMaterial` + // `windowBackground` (12) blended behind the window; the radius is + // already covered by the content-view squircle clip above. + let vibrancy = unsafe { NSVisualEffectView::initWithFrame(mtm.alloc(), bounds) }; + unsafe { + vibrancy.setMaterial(NSVisualEffectMaterial::WindowBackground); + vibrancy.setBlendingMode(NSVisualEffectBlendingMode::BehindWindow); + vibrancy.setState(NSVisualEffectState::FollowsWindowActiveState); + vibrancy.setAutoresizingMask( + NSAutoresizingMaskOptions::NSViewWidthSizable + | NSAutoresizingMaskOptions::NSViewHeightSizable, + ); + content_view.addSubview_positioned_relativeTo( + &vibrancy, + NSWindowOrderingMode::NSWindowBelow, + None, + ); + } + Some(MaterialKind::Vibrancy) + } /// Repair gpui's per-window display link on macOS 26. /// @@ -348,8 +502,11 @@ mod mac { mod stub { use gpui::Window; - use super::PanelBehavior; + use super::{MaterialKind, PanelBehavior}; + pub fn install_window_material(_native: &NativeWindow, _radius: f64) -> Option { + None + } pub fn recording_controls_level() -> isize { 0 } diff --git a/apps/desktop-gpui/src/theme.rs b/apps/desktop-gpui/src/theme.rs index 1d76b5301e..0f453b9346 100644 --- a/apps/desktop-gpui/src/theme.rs +++ b/apps/desktop-gpui/src/theme.rs @@ -8,6 +8,8 @@ use gpui::{Hsla, Rgba, WindowAppearance, rgb, rgba}; +use crate::platform::MaterialKind; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Appearance { Light, @@ -23,6 +25,151 @@ impl Appearance { } } +/// What the shell paints *over* a native window material, resolved for one +/// appearance and one visual system. +/// +/// The web app expresses this as `--macos-settings-*` custom properties +/// (`apps/desktop/src/styles/theme.css`) selected by two data attributes on +/// ``: `data-macos-native-material` ("panel" for the main window) and +/// `data-macos-visual-system` ("liquid-glass" or "vibrancy"). Only the panel +/// material's resolved values are transcribed here -- the settings and +/// teleprompter materials belong to windows this app does not have yet. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct MaterialTokens { + pub kind: MaterialKind, + + /// `.cap-window-shell`'s background. + pub shell: Rgba, + /// `.cap-window-header`'s background. + pub header: Rgba, + /// `--macos-settings-border`. + pub border: Rgba, + /// `--macos-settings-control-fill`. + pub control_fill: Rgba, + /// `--macos-settings-control-hover`. + pub control_hover: Rgba, + /// `--macos-settings-control-active`. + pub control_active: Rgba, + /// `--macos-settings-selection`. + pub selection: Rgba, + /// `--macos-settings-text`. + pub text: Rgba, +} + +impl MaterialTokens { + /// Only Liquid Glass remaps the body's gray fills: every + /// `.cap-window-body .bg-gray-N` rule in `theme.css` is gated on + /// `[data-macos-visual-system="liquid-glass"]`. Under vibrancy the body + /// keeps its Radix grays and only the shell and header change. + fn remaps_body(&self) -> bool { + self.kind == MaterialKind::LiquidGlass + } + + /// The panel material under `NSGlassEffectView`. + fn liquid_glass(appearance: Appearance) -> Self { + match appearance { + Appearance::Light => Self { + kind: MaterialKind::LiquidGlass, + // `[data-macos-visual-system="liquid-glass"][data-macos-native-material="panel"] + // .cap-window-shell { background: rgba(255, 255, 255, 0.55) }` + // -- a wash *over* the live glass backdrop, not a replacement + // for it. The base glass rule strips the border and shadow: + // `.cap-window-shell { background: transparent; border: 0; + // box-shadow: none }`. + shell: rgba(0xffffff8c), + // `[data-macos-visual-system="liquid-glass"] .cap-window-header + // { background: transparent; border-bottom-color: transparent }` + header: rgba(0x00000000), + // `[data-macos-visual-system="liquid-glass"] { + // --macos-settings-border: rgba(0, 0, 0, 0.14) }` -- the glass + // block overrides the base `rgba(0, 0, 0, 0.08)`. + border: rgba(0x00000024), + // Not overridden by the glass block, so the `:root` values: + // `--macos-settings-control-fill: rgba(255, 255, 255, 0.78)` + control_fill: rgba(0xffffffc7), + // `--macos-settings-control-hover: rgba(255, 255, 255, 0.96)` + control_hover: rgba(0xfffffff5), + // `--macos-settings-control-active: rgba(0, 0, 0, 0.08)` + control_active: rgba(0x00000014), + // `--macos-settings-selection: rgba(0, 0, 0, 0.1)` + selection: rgba(0x0000001a), + // `--macos-settings-text: rgba(0, 0, 0, 0.88)` + text: rgba(0x000000e0), + }, + Appearance::Dark => Self { + kind: MaterialKind::LiquidGlass, + // `.dark[data-macos-visual-system="liquid-glass"][data-macos-native-material="panel"] + // .cap-window-shell { background: rgba(17, 17, 17, 0.88) }` + shell: rgba(0x111111e0), + header: rgba(0x00000000), + // `:root.dark[data-macos-visual-system="liquid-glass"]`: + // `--macos-settings-border: rgba(255, 255, 255, 0.07)` + border: rgba(0xffffff12), + // `--macos-settings-control-fill: rgba(255, 255, 255, 0.08)` + control_fill: rgba(0xffffff14), + // `--macos-settings-control-hover: rgba(255, 255, 255, 0.12)` + control_hover: rgba(0xffffff1f), + // `--macos-settings-control-active: rgba(255, 255, 255, 0.06)` + control_active: rgba(0xffffff0f), + // `--macos-settings-selection: #2c2c2c` + selection: rgb(0x2c2c2c), + // `--macos-settings-text: rgba(255, 255, 255, 0.95)` + text: rgba(0xfffffff2), + }, + } + } + + /// The panel material under `NSVisualEffectView`, i.e. pre-macOS-26. The + /// shell keeps the border and the header keeps its own wash, because none + /// of the `liquid-glass` overrides apply. + fn vibrancy(appearance: Appearance) -> Self { + match appearance { + Appearance::Light => Self { + kind: MaterialKind::Vibrancy, + // `[data-macos-native-material] .cap-window-shell { background: + // var(--macos-settings-window); border: 1px solid + // var(--macos-settings-border) }`, and `:root`'s + // `--macos-settings-window: rgba(244, 244, 243, 0.84)`. + shell: rgba(0xf4f4f3d6), + // `[data-macos-native-material] .cap-window-header { background: + // rgba(250, 250, 249, 0.72) }`. Its `backdrop-filter: + // blur(28px) saturate(1.45)` has no equivalent in this gpui + // rev (same gap as the recording overlay's `backdrop-blur-xs`). + header: rgba(0xfafaf9b8), + // `--macos-settings-border: rgba(0, 0, 0, 0.08)` + border: rgba(0x00000014), + control_fill: rgba(0xffffffc7), + control_hover: rgba(0xfffffff5), + control_active: rgba(0x00000014), + selection: rgba(0x0000001a), + text: rgba(0x000000e0), + }, + Appearance::Dark => Self { + kind: MaterialKind::Vibrancy, + // `:root.dark { --macos-settings-window: rgba(17, 17, 17, 0.94) }` + shell: rgba(0x111111f0), + // `:root.dark[data-macos-native-material] .cap-window-header + // { background: rgba(28, 28, 28, 0.88) }` + header: rgba(0x1c1c1ce0), + // `:root.dark { --macos-settings-border: rgba(255, 255, 255, 0.07) }` + border: rgba(0xffffff12), + control_fill: rgba(0xffffff14), + control_hover: rgba(0xffffff1f), + control_active: rgba(0xffffff0f), + selection: rgb(0x2c2c2c), + text: rgba(0xfffffff2), + }, + } + } + + pub fn new(kind: MaterialKind, appearance: Appearance) -> Self { + match kind { + MaterialKind::LiquidGlass => Self::liquid_glass(appearance), + MaterialKind::Vibrancy => Self::vibrancy(appearance), + } + } +} + /// Every colour the main window draws with, already resolved for one appearance. /// /// The scales are transcribed whole rather than trimmed to today's callers. @@ -34,6 +181,12 @@ impl Appearance { pub struct Theme { pub appearance: Appearance, + /// The native window material behind the shell, already resolved for this + /// appearance. `None` -- non-mac, or the install failed -- keeps the + /// opaque `gray-1` shell, which is what the web app falls back to when + /// `applyMacOSWindowMaterial` never runs. + pub material: Option, + pub gray_1: Rgba, pub gray_2: Rgba, pub gray_3: Rgba, @@ -82,6 +235,7 @@ impl Theme { pub fn light() -> Self { Self { appearance: Appearance::Light, + material: None, gray_1: rgb(0xfcfcfc), gray_2: rgb(0xf9f9f9), @@ -119,6 +273,7 @@ impl Theme { pub fn dark() -> Self { Self { appearance: Appearance::Dark, + material: None, // gray 1-6 and 11 are the theme.css overrides, not stock Radix. gray_1: rgb(0x111111), @@ -154,6 +309,153 @@ impl Theme { } } + /// Resolve the panel material for this theme's appearance. + /// `None` leaves the opaque shell alone. + pub fn with_material(mut self, kind: Option) -> Self { + self.material = kind.map(|kind| MaterialTokens::new(kind, self.appearance)); + self + } + + /// The material's own kind, for the `render`-side "is a material live" + /// checks that have nothing to paint. + pub fn material_kind(&self) -> Option { + self.material.map(|material| material.kind) + } + + /// One Radix gray step by number, so the `.bg-gray-N` remaps below can be + /// written the way `theme.css` writes them. + pub fn gray(&self, step: u8) -> Hsla { + match step { + 1 => self.gray_1, + 2 => self.gray_2, + 3 => self.gray_3, + 4 => self.gray_4, + 5 => self.gray_5, + 6 => self.gray_6, + 7 => self.gray_7, + 8 => self.gray_8, + 9 => self.gray_9, + 10 => self.gray_10, + 11 => self.gray_11, + _ => self.gray_12, + } + .into() + } + + /// `.cap-window-shell`'s background: the material's tint when one is + /// installed, `bg-gray-1` otherwise. + pub fn shell_bg(&self) -> Hsla { + match self.material { + Some(material) => material.shell.into(), + None => self.gray_1.into(), + } + } + + /// The shell's `1px solid var(--macos-settings-border)`. Liquid Glass + /// draws none (`.cap-window-shell { border: 0 }`), and neither does the + /// bare window. + pub fn shell_border(&self) -> Option { + self.material + .filter(|material| !material.remaps_body()) + .map(|material| material.border.into()) + } + + /// `.cap-window-header`'s background. + pub fn header_bg(&self) -> Hsla { + match self.material { + Some(material) => material.header.into(), + None => self.gray_2.into(), + } + } + + /// The `divide-y divide-gray-5` hairline under the header. Liquid Glass + /// erases it: `.cap-window-shell > * + * { border-color: transparent }`. + pub fn header_border(&self) -> Hsla { + match self.material { + Some(material) if material.remaps_body() => gpui::transparent_black(), + Some(material) => material.border.into(), + None => self.gray_5.into(), + } + } + + /// `.cap-window-body`'s inherited text colour. + pub fn body_text(&self) -> Hsla { + match self.material { + Some(material) if material.remaps_body() => material.text.into(), + _ => self.text_primary.into(), + } + } + + /// A `.bg-gray-N` fill inside `.cap-window-body`. + /// + /// ```text + /// .bg-gray-2, .bg-gray-3 { background-color: var(--macos-settings-control-fill) } + /// .bg-gray-4 { background-color: var(--macos-settings-control-hover) } + /// .bg-gray-5 { background-color: var(--macos-settings-control-active) } + /// ``` + /// + /// (The `.dark\:bg-gray-3` / `.dark\:bg-gray-4` rule lands on the same + /// `control-fill`, so a dark-mode `bg-gray-3 dark:bg-gray-4` pair -- + /// already collapsed to one token by the time it reaches here -- resolves + /// identically either way.) + pub fn body_fill(&self, step: u8) -> Hsla { + let Some(material) = self.material.filter(MaterialTokens::remaps_body) else { + return self.gray(step); + }; + match step { + 2 | 3 => material.control_fill.into(), + 4 => material.control_hover.into(), + 5 => material.control_active.into(), + _ => self.gray(step), + } + } + + /// A `hover:bg-gray-N` fill inside `.cap-window-body`. + /// + /// ```text + /// [class*="hover:bg-gray-4"]:hover, [class*="hover:bg-gray-5"]:hover + /// { background-color: var(--macos-settings-control-hover) } + /// [class*="hover:bg-gray-6"]:hover, [class*="hover:bg-gray-7"]:hover + /// { background-color: var(--macos-settings-selection) } + /// ``` + pub fn body_hover_fill(&self, step: u8) -> Hsla { + let Some(material) = self.material.filter(MaterialTokens::remaps_body) else { + return self.gray(step); + }; + match step { + 4 | 5 => material.control_hover.into(), + 6 | 7 => material.selection.into(), + _ => self.gray(step), + } + } + + /// A `border-gray-N` inside `.cap-window-body`. + /// + /// ```text + /// .border-gray-4, .dark\:border-gray-5 { border-color: var(--macos-settings-border) } + /// .border-gray-5, .border-gray-6 { border-color: var(--macos-settings-border) } + /// ``` + pub fn body_border(&self, step: u8) -> Hsla { + let Some(material) = self.material.filter(MaterialTokens::remaps_body) else { + return self.gray(step); + }; + match step { + 4..=6 => material.border.into(), + _ => self.gray(step), + } + } + + /// `ring-offset-gray-1`, the 1px gap between the selected Mode button and + /// its blue ring. Liquid Glass makes it transparent + /// (`--tw-ring-offset-color: transparent`) so the ring is not sitting on a + /// solid disc floating over the material. + pub fn ring_offset(&self) -> Hsla { + match self.material { + Some(material) if material.remaps_body() => gpui::transparent_black(), + _ => self.gray_1.into(), + } + } + pub fn is_dark(&self) -> bool { self.appearance == Appearance::Dark } @@ -200,3 +502,101 @@ impl Theme { pub const TRAFFIC_ZOOM: u32 = 0x28c840; pub const TRAFFIC_INACTIVE: u32 = 0xdcdcdc; } + +#[cfg(test)] +mod tests { + use super::*; + + /// With no material the palette must be byte-for-byte what it was before + /// the material existed: the shell is opaque `gray-1`, and every body fill + /// is its plain Radix step. + #[test] + fn no_material_keeps_the_radix_palette() { + for theme in [Theme::light(), Theme::dark()] { + assert_eq!(theme.shell_bg(), Hsla::from(theme.gray_1)); + assert_eq!(theme.shell_border(), None); + assert_eq!(theme.header_bg(), Hsla::from(theme.gray_2)); + assert_eq!(theme.header_border(), Hsla::from(theme.gray_5)); + assert_eq!(theme.body_text(), Hsla::from(theme.text_primary)); + assert_eq!(theme.ring_offset(), Hsla::from(theme.gray_1)); + for step in 1..=12 { + assert_eq!(theme.body_fill(step), theme.gray(step)); + assert_eq!(theme.body_hover_fill(step), theme.gray(step)); + assert_eq!(theme.body_border(step), theme.gray(step)); + } + } + } + + /// The `.cap-window-body` remaps are gated on + /// `[data-macos-visual-system="liquid-glass"]`, so vibrancy must leave + /// them alone and change only the shell and header. + #[test] + fn vibrancy_changes_the_shell_but_not_the_body() { + for appearance in [Appearance::Light, Appearance::Dark] { + let theme = Theme::new(appearance).with_material(Some(MaterialKind::Vibrancy)); + assert_ne!(theme.shell_bg(), Hsla::from(theme.gray_1)); + // `border: 1px solid var(--macos-settings-border)` -- only the + // vibrancy path draws it. + assert!(theme.shell_border().is_some()); + for step in 1..=12 { + assert_eq!(theme.body_fill(step), theme.gray(step)); + assert_eq!(theme.body_hover_fill(step), theme.gray(step)); + assert_eq!(theme.body_border(step), theme.gray(step)); + } + } + } + + /// The exact `theme.css` mapping for material "panel" under Liquid Glass. + #[test] + fn liquid_glass_maps_body_fills_to_the_panel_material() { + for appearance in [Appearance::Light, Appearance::Dark] { + let theme = Theme::new(appearance).with_material(Some(MaterialKind::LiquidGlass)); + let material = theme.material.expect("material was just installed"); + + // `.cap-window-shell { border: 0; box-shadow: none }`, then the + // panel tint. + assert_eq!(theme.shell_border(), None); + assert_eq!(theme.shell_bg(), Hsla::from(material.shell)); + // `.cap-window-header { background: transparent; + // border-bottom-color: transparent }` + assert_eq!(theme.header_bg().a, 0.); + assert_eq!(theme.header_border(), gpui::transparent_black()); + assert_eq!(theme.body_text(), Hsla::from(material.text)); + // `.ring-offset-gray-1 { --tw-ring-offset-color: transparent }` + assert_eq!(theme.ring_offset(), gpui::transparent_black()); + + // `.bg-gray-2, .bg-gray-3 -> control-fill`, `.bg-gray-4 -> + // control-hover`, `.bg-gray-5 -> control-active`. + assert_eq!(theme.body_fill(2), Hsla::from(material.control_fill)); + assert_eq!(theme.body_fill(3), Hsla::from(material.control_fill)); + assert_eq!(theme.body_fill(4), Hsla::from(material.control_hover)); + assert_eq!(theme.body_fill(5), Hsla::from(material.control_active)); + // Untouched steps stay Radix. + assert_eq!(theme.body_fill(6), theme.gray(6)); + + // `hover:bg-gray-4|5 -> control-hover`, `hover:bg-gray-6|7 -> + // selection`. + assert_eq!(theme.body_hover_fill(4), Hsla::from(material.control_hover)); + assert_eq!(theme.body_hover_fill(5), Hsla::from(material.control_hover)); + assert_eq!(theme.body_hover_fill(6), Hsla::from(material.selection)); + assert_eq!(theme.body_hover_fill(7), Hsla::from(material.selection)); + + // `border-gray-4|5|6 -> --macos-settings-border`. + for step in 4..=6 { + assert_eq!(theme.body_border(step), Hsla::from(material.border)); + } + assert_eq!(theme.body_border(8), theme.gray(8)); + } + } + + /// The light panel tint is `rgba(255, 255, 255, 0.55)` and the dark one + /// `rgba(17, 17, 17, 0.88)` -- both translucent, or there would be no + /// glass to see. + #[test] + fn panel_glass_tint_is_translucent() { + let light = Theme::light().with_material(Some(MaterialKind::LiquidGlass)); + let dark = Theme::dark().with_material(Some(MaterialKind::LiquidGlass)); + assert!((light.shell_bg().a - 0.55).abs() < 0.01); + assert!((dark.shell_bg().a - 0.88).abs() < 0.01); + } +} From 28b041a5671eb90e6235548275ba562ef19e64c7 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:48:31 +0100 Subject: [PATCH 023/146] feat(desktop-gpui): settings window with the shared Tauri store A real second chrome window: 782x775, resizable to the 780x560 minimum (gpui's window_min_size covers setContentMinSize:, and TitlebarOptions appears_transparent + traffic_light_position covers the (22,22) lights -- no new AppKit needed), on the settings material at radius 26 with the settings-surface tokens (sidebar/content/card/fill/hover/muted) added to theme::MaterialTokens from their --macos-settings-* rules. The sidebar carries all twelve pages from settings.tsx's settingsItems (ungated there, ungated here); General is built in full -- theme tiles, app toggles, the Pro rows, quality, recording behaviours, countdown, storage path, project-name template, excluded windows, update channel, self-host, telemetry -- each row writing its real store key. Writes go through store::set_store_setting: a read-modify-write on the raw JSON of the tauri-plugin-store file ('store', no extension) that replaces exactly one key and preserves everything else -- auth, presets, migration flags, the settings no page renders. A corrupt store is refused, never replaced; writes are temp-file + rename. Typed reads are field-by-field with per-field defaults so one unknown value cannot blank the page. recordings_dir now reads through the same module, so CAP_GPUI_TAURI_STORE redirects everything at once. The main window's header gear opens settings and hides the main window (the showWindow({Settings}) + hide() pair from new-main); closing settings brings the main window back. Verified: material log LiquidGlass radius=26; window screenshot at 782x775 with sidebar + full General page; single-key write against a byte-copy of the real store diffs to exactly one line with all 12 sections and 40 general_settings keys preserved (md5-proven the real store was never touched); record cycle finalizes with zero borrow errors; 23 tests green. --- apps/desktop-gpui/README.md | 109 +- apps/desktop-gpui/assets/icons/captions.svg | 1 + apps/desktop-gpui/assets/icons/gift.svg | 1 + apps/desktop-gpui/assets/icons/hotkeys.svg | 3 + .../assets/icons/message-square-plus.svg | 1 + apps/desktop-gpui/assets/icons/plus.svg | 1 + .../desktop-gpui/assets/icons/square-play.svg | 1 + apps/desktop-gpui/assets/icons/terminal.svg | 1 + .../assets/icons/triangle-alert.svg | 1 + apps/desktop-gpui/assets/icons/unplug.svg | 1 + apps/desktop-gpui/assets/icons/user-round.svg | 1 + apps/desktop-gpui/assets/icons/zap.svg | 1 + apps/desktop-gpui/assets/images/auto.jpg | Bin 0 -> 44560 bytes apps/desktop-gpui/assets/images/dark.jpg | Bin 0 -> 35259 bytes apps/desktop-gpui/assets/images/light.jpg | Bin 0 -> 35868 bytes apps/desktop-gpui/src/app_windows.rs | 230 +- apps/desktop-gpui/src/assets.rs | 58 +- apps/desktop-gpui/src/main.rs | 13 + apps/desktop-gpui/src/main_window.rs | 17 +- apps/desktop-gpui/src/recording.rs | 18 +- apps/desktop-gpui/src/settings_window.rs | 2927 +++++++++++++++++ apps/desktop-gpui/src/store.rs | 710 +++- apps/desktop-gpui/src/theme.rs | 297 +- 23 files changed, 4331 insertions(+), 61 deletions(-) create mode 100644 apps/desktop-gpui/assets/icons/captions.svg create mode 100644 apps/desktop-gpui/assets/icons/gift.svg create mode 100644 apps/desktop-gpui/assets/icons/hotkeys.svg create mode 100644 apps/desktop-gpui/assets/icons/message-square-plus.svg create mode 100644 apps/desktop-gpui/assets/icons/plus.svg create mode 100644 apps/desktop-gpui/assets/icons/square-play.svg create mode 100644 apps/desktop-gpui/assets/icons/terminal.svg create mode 100644 apps/desktop-gpui/assets/icons/triangle-alert.svg create mode 100644 apps/desktop-gpui/assets/icons/unplug.svg create mode 100644 apps/desktop-gpui/assets/icons/user-round.svg create mode 100644 apps/desktop-gpui/assets/icons/zap.svg create mode 100644 apps/desktop-gpui/assets/images/auto.jpg create mode 100644 apps/desktop-gpui/assets/images/dark.jpg create mode 100644 apps/desktop-gpui/assets/images/light.jpg create mode 100644 apps/desktop-gpui/src/settings_window.rs diff --git a/apps/desktop-gpui/README.md b/apps/desktop-gpui/README.md index 4797a4acf3..8950d0d52e 100644 --- a/apps/desktop-gpui/README.md +++ b/apps/desktop-gpui/README.md @@ -70,6 +70,11 @@ Everything below is real, not mocked. (`WindowKind::PopUp`), so its buttons work without stealing focus from the app being recorded. While it is up the main window hides, and the bar's own window is excluded from the capture. +- **The settings window.** 782×775, resizable down to 780×560, on the + `"settings"` material (radius 26) with the real traffic lights repositioned + to (22, 22). The sidebar lists all twelve pages; **General** is built in + full and writes to the same tauri-plugin-store file the shipping app uses. + See below. ### Layout fidelity @@ -84,8 +89,10 @@ would quietly change the app's colours. ### The native window material -The main window is not a `bg-gray-1` slab. `platform::install_window_material` -does what `applyMacOSWindowMaterial("panel")` plus +The main window is not a `bg-gray-1` slab, and neither is the settings window. +`platform::install_window_material` does what +`applyMacOSWindowMaterial("panel")` (radius 16) or +`applyMacOSWindowMaterial("settings")` (radius 26) plus `apply_main_window_liquid_glass_background` do in the shipping app: - **macOS 26+** — an `NSGlassEffectView`, found by runtime class lookup, @@ -129,6 +136,26 @@ in `theme::MaterialTokens` with the rule each came from quoted next to it, and `Theme` exposes them as `shell_bg()` / `body_fill(n)` / `body_hover_fill(n)` / `body_border(n)` so a call site still reads as the Tailwind class it came from. +The settings window uses the *same* token set — the `--macos-settings-*` +custom properties are set by the visual-system blocks, not per material, so +one struct serves both windows and only the elements differ. It adds the +surfaces the panel material never lands on: + +| | Liquid Glass | Vibrancy | +|---|---|---| +| `--macos-settings-window-radius` | 26 | 16 | +| `--macos-settings-sidebar-radius` | 18, then zeroed again by the settings rule | 0 | +| `.cap-settings-sidebar` | `rgba(255,255,255,0.58)` / `rgba(28,28,28,0.88)` | `rgba(250,250,249,0.74)` / `rgba(22,22,22,0.9)` | +| `.cap-settings-content` | `#f6f6f5` (opaque) / `rgba(17,17,17,0.92)` | `rgba(244,244,243,0.84)` / `rgba(17,17,17,0.94)` | +| `.cap-settings-card`, page `bg-gray-2` | `rgba(255,255,255,0.92)` / `rgba(28,28,28,0.94)` | `rgba(249,249,248,0.94)` / `rgba(28,28,28,0.96)` | +| page `bg-gray-3/4/5` → `--macos-settings-fill` | `rgba(0,0,0,0.045)` / `rgba(255,255,255,0.05)` | `rgba(0,0,0,0.055)` / `rgba(255,255,255,0.05)` | +| nav/profile `:hover` → `--macos-settings-hover` | `rgba(0,0,0,0.065)` / `rgba(255,255,255,0.05)` | `rgba(0,0,0,0.055)` / `rgba(255,255,255,0.05)` | +| description text → `--macos-settings-muted` | `rgba(0,0,0,0.48)` / `#a1a1a1` | same | + +Only the sidebar is translucent under Liquid Glass; `--macos-settings-content` +is `#f6f6f5`, fully opaque, so the glass reads as a single lit edge down the +left rather than a see-through window. + The material is installed from a task after the window handle exists — subview insertion re-enters gpui's window callbacks, so it cannot run inside an update — and the result lands in a `platform::WindowMaterial` global that `render` @@ -141,7 +168,8 @@ Things that are deliberately different, and why. | | | |---|---| | **Traffic lights are hand-drawn** | The Tauri main window returns `None` from `traffic_lights_position`, which routes it to `decorations(false)`; the lights are HTML there too. `titlebar: None` is the gpui equivalent. Minimize is not drawn, and zoom toggles expand/collapse. | -| **Only the main window is on a material** | The main window is native (see below), which matches the shipping app exactly: `applyMacOSWindowMaterial` runs only in the `(window-chrome)` layout, so the camera bubble, the recording bar and the target overlays never had a native material in the Tauri app either — the bar's liquid-glass look is painted CSS. The windows that will need the material later are the other chrome windows: settings (radius 26), mode select, upgrade. | +| **Only the chrome windows are on a material** | The main and settings windows are native (see below), which matches the shipping app exactly: `applyMacOSWindowMaterial` runs only in the `(window-chrome)` layout, so the camera bubble, the recording bar and the target overlays never had a native material in the Tauri app either — the bar's liquid-glass look is painted CSS. The chrome windows still to come are mode select and upgrade. | +| **No always-active pin on the settings glass** | `apply_liquid_glass_background_inner` gives the *non-main* Tauri windows an "always active" pin (`setState:` / `setActive:` probing on the glass view) so the material does not dim when the app deactivates. It is not reproduced: it broke on 26.3 and falls back to the plain `SystemManaged` install anyway, and the hard rule here is to make only the AppKit calls the shipping app can be shown to rely on. The settings window therefore takes the same `setStyle:`-only path the main window does. | | **No header backdrop filter** | On the vibrancy path `.cap-window-header` is `rgba(250,250,249,0.72)` *plus* `backdrop-filter: blur(28px) saturate(1.45)`. The wash is here, the filter is not — same missing hook as the recording overlay's `backdrop-blur-xs`. | | **Appearance changes need a relaunch** | `sync_appearance` runs from `render`, and gpui only renders on invalidation, so flipping the system to light while the app is up leaves the dark palette on screen until something else forces a frame. Pre-existing, not specific to the material. | | **NSWindow, not NSPanel** | The Tauri app class-swizzles its windows into `NSPanel`s via `tauri_nspanel`. Here the main window stays a normal `NSWindow` and gets the observable parts — level 100, `CanJoinAllSpaces \| FullScreenPrimary` — from the `platform` module. (`WindowKind::Floating` is *not* a shortcut to this: its panel hides on app deactivation.) The controls bar *is* a real panel via `WindowKind::PopUp`, whose non-activating behavior it genuinely needs. | @@ -193,7 +221,7 @@ Sizes are the Tauri app's, from `apps/desktop/src-tauri/src/windows.rs`. | Capture area | per display | Superseded — area selection is the target-select overlay's area variant; the Tauri app still registers a standalone `capture-area` window but nothing in its frontend opens it | | Recordings overlay | per display | Not started | | Mode select | 580×340 | Partial — exists as a panel, not a window | -| Settings | 782×775 (min 780×560) | Not started | +| Settings | 782×775 (min 780×560) | **Done — General** — window shell on the `"settings"` material (radius 26), native traffic lights at (22, 22), resizable with the real min size, sidebar with all twelve pages, the General page in full against the shared Tauri store. The other eleven pages are placeholder bodies | | Upgrade | 950×850 | Not started | | Onboarding | dynamic, 860–1080 wide | Not started | | Teleprompter | 560×320 | Not started | @@ -302,6 +330,68 @@ mkdir -p target/Frameworks ln -sfn "$(pwd)/../../target/native-deps/Spacedrive.framework" target/Frameworks/Spacedrive.framework ``` +## Settings, and the store both apps share + +The header gear opens the real second window — 782×775, resizable to a +780×560 minimum, the `"settings"` material at radius 26 — and hides the main +window, which is exactly what `new-main/index.tsx` does +(`showWindow({ Settings: { page: "general" } })` then +`getCurrentWindow().hide()`). Closing it brings the main window back, matching +the `CapWindowId::Settings` arm of the Tauri app's `Destroyed` handler +(`restore_main_and_target_select_windows`) — without that the gear would leave +the app with no visible window at all. Cmd-W closes it, as +`(window-chrome).tsx` binds for every chrome window; Escape is not bound +there and is not bound here. + +Unlike the main window these are the **real** traffic lights: the Tauri +settings window returns `Some(Some(LogicalPosition::new(22.0, 22.0)))` from +`traffic_lights_position`, i.e. it keeps AppKit's buttons and moves them, and +the chrome layout returns `null` for its own header on the settings route. +gpui expresses that as `TitlebarOptions { appears_transparent: true, +traffic_light_position: Some(point(px(22.), px(22.))) }`, and the min size as +`window_min_size` — no `setContentMinSize:` helper was needed. + +The sidebar carries all twelve entries of `settingsItems`, in order, none of +them gated: General, Shortcuts (route `hotkeys`), CLI, Recordings, +Screenshots, Automations, Transcription, Integrations, License, Experimental, +Feedback, Changelog. Only **General** is built. + +### The store contract + +`general.tsx` writes through `generalSettingsStore`, i.e. the +tauri-plugin-store file `Store.load("store")` — `store` (no extension) inside +`so.cap.desktop`'s app-data dir. This app writes the same file, and every +write is a read-modify-write on the raw JSON that replaces exactly +`store[section][key]`: `store::set_store_setting`. Serializing a typed struct +back over the file would silently drop `auth`, `presets`, the migration flags +and the two thirds of `general_settings` no page here renders — the store test +`writing_one_setting_preserves_unknown_keys` is what holds that line. A store +file that exists but does not parse is never written to at all, because +replacing it with a fresh object would delete someone's auth token. + +Reads are field-by-field rather than `derive(Deserialize)` on a struct: one +enum value written by a newer Tauri build would otherwise fail the whole +deserialize and blank every row on the page. `CAP_GPUI_TAURI_STORE` points the +whole module at a copy, which is how the tests — and any verification run that +must not touch real settings — work. + +Settings-specific deviations: + +| | | +|---|---| +| **Eleven placeholder pages** | Shortcuts, CLI, Recordings, Screenshots, Automations, Transcription, Integrations, License, Experimental, Feedback and Changelog render their name, a one-line description and a card saying they are not part of the rewrite yet. The sidebar is real; the bodies are not. | +| **No auth, so the free-plan variant** | There is no auth store here (same gap as the main window's plan badge), so the profile row shows the signed-out "Click to sign in" state and does nothing when clicked, and the Cap Pro section renders as it does for a free user: Instant Mode quality pinned to 720p, the other tiers inert. In the Tauri app clicking a locked tier raises an upgrade toast. `instantModeMaxResolution` is therefore displayed but never written. | +| **Selects are in-window menus** | `SelectSettingItem` and the excluded-windows Add button pop a real `NSMenu` via `Menu.popup()`. Ours draws a menu-shaped panel at the pointer (which is where `popup()` with no argument puts it), with the same check marks and the same click-away dismiss. | +| **Text fields are the search field's cousin** | gpui ships no text input, so the project-name template and the server URL use the same hand-rolled field as the main window's search: focus tracking, `key_char` for the typed character, a static caret, Escape to revert. No selection, no cursor movement, no blink. | +| **The project-name preview is literal-only** | `commands.formatProjectName` understands `{moment:}` and custom `{date:...}`/`{time:...}` formats through a moment-to-chrono translation. The preview here substitutes the six literal placeholders the card documents and leaves anything else alone — which is also what an unknown placeholder does there. | +| **Theme tiles keep a fixed height** | `aspect-[5/3]` has no gpui equivalent, so the three previews are 93px tall, the height they have at the window's default 782 width. Widen the window and they stay 93. | +| **`AccentColor` is macOS blue** | `--macos-settings-accent: AccentColor` resolves to the user's system accent; gpui exposes no query for it, so the checked toggles and the selected sidebar icon use `#007aff`. A user on a non-blue accent sees blue here and their own colour in the shipping app. | +| **No toggle bevel** | `.cap-toggle` carries `box-shadow: inset 0 1px 2px rgba(0,0,0,0.16)`; there is no inset-shadow hook in this gpui rev, so the track is flat. | +| **Settings does not park the other windows** | `ShowCapWindow::Settings::show` also calls `hide_recording_windows` and `release_camera_preview_if_idle`, and its close calls `restore_camera_window`. Neither half is reproduced — the gear hides the main window and nothing else. | +| **The theme setting is stored, not applied** | Selecting Light or Dark writes `theme`, which the Tauri app uses to force an appearance. This app follows the system appearance only (`sync_appearance`), so the tile is persisted parity, not behaviour. The same is true of `hideDockIcon`, `enableNotifications`, the countdown, the post-recording behaviours and the update channel: they persist, and the machinery that would obey them is not built yet. | +| **No confirm on the recordings folder move** | `pickRecordingsFolder` offers to migrate existing recordings afterwards; here the path is written and nothing is moved. | +| **Version is this crate's** | The sidebar footer shows `v0.1.0` from `CARGO_PKG_VERSION`, not `getVersion()`; "Check for updates" is drawn in its disabled state because there is no updater. | + ## Verifying changes `screencapture` cannot see windows without Screen Recording permission, but @@ -324,3 +414,14 @@ judged from a full-screen `screencapture -x`. `CAP_GPUI_AUTO_EXPAND=1` opens the window expanded, the way clicking the zoom light does, for the same reason as the other `CAP_GPUI_AUTO_*` hooks: unprivileged synthetic clicks are dropped. + +`CAP_GPUI_AUTO_SETTINGS=1` opens the settings window at startup, exactly as +the header gear does (main window hidden included). Pass a page slug instead +of `1` — `CAP_GPUI_AUTO_SETTINGS=hotkeys` — to land on another sidebar entry; +an unknown slug falls back to General. + +`CAP_GPUI_TAURI_STORE=` points every settings read and write at another +file. Use it for anything that toggles a setting: the default is the store the +shipping app is also writing, and a probe run should not be editing the real +one. `cp "$HOME/Library/Application Support/so.cap.desktop/store" /tmp/probe` +first, so the copy carries the real unknown keys. diff --git a/apps/desktop-gpui/assets/icons/captions.svg b/apps/desktop-gpui/assets/icons/captions.svg new file mode 100644 index 0000000000..16b2c91d23 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/captions.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-gpui/assets/icons/gift.svg b/apps/desktop-gpui/assets/icons/gift.svg new file mode 100644 index 0000000000..04d155c78e --- /dev/null +++ b/apps/desktop-gpui/assets/icons/gift.svg @@ -0,0 +1 @@ + diff --git a/apps/desktop-gpui/assets/icons/hotkeys.svg b/apps/desktop-gpui/assets/icons/hotkeys.svg new file mode 100644 index 0000000000..020521815d --- /dev/null +++ b/apps/desktop-gpui/assets/icons/hotkeys.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/message-square-plus.svg b/apps/desktop-gpui/assets/icons/message-square-plus.svg new file mode 100644 index 0000000000..664a7c964c --- /dev/null +++ b/apps/desktop-gpui/assets/icons/message-square-plus.svg @@ -0,0 +1 @@ + diff --git a/apps/desktop-gpui/assets/icons/plus.svg b/apps/desktop-gpui/assets/icons/plus.svg new file mode 100644 index 0000000000..e4c60061be --- /dev/null +++ b/apps/desktop-gpui/assets/icons/plus.svg @@ -0,0 +1 @@ + diff --git a/apps/desktop-gpui/assets/icons/square-play.svg b/apps/desktop-gpui/assets/icons/square-play.svg new file mode 100644 index 0000000000..20d65f1a16 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/square-play.svg @@ -0,0 +1 @@ + diff --git a/apps/desktop-gpui/assets/icons/terminal.svg b/apps/desktop-gpui/assets/icons/terminal.svg new file mode 100644 index 0000000000..d8b48e4ba8 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/terminal.svg @@ -0,0 +1 @@ + diff --git a/apps/desktop-gpui/assets/icons/triangle-alert.svg b/apps/desktop-gpui/assets/icons/triangle-alert.svg new file mode 100644 index 0000000000..588b1b364a --- /dev/null +++ b/apps/desktop-gpui/assets/icons/triangle-alert.svg @@ -0,0 +1 @@ + diff --git a/apps/desktop-gpui/assets/icons/unplug.svg b/apps/desktop-gpui/assets/icons/unplug.svg new file mode 100644 index 0000000000..feb42b16bc --- /dev/null +++ b/apps/desktop-gpui/assets/icons/unplug.svg @@ -0,0 +1 @@ + diff --git a/apps/desktop-gpui/assets/icons/user-round.svg b/apps/desktop-gpui/assets/icons/user-round.svg new file mode 100644 index 0000000000..c0ae694ea7 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/user-round.svg @@ -0,0 +1 @@ + diff --git a/apps/desktop-gpui/assets/icons/zap.svg b/apps/desktop-gpui/assets/icons/zap.svg new file mode 100644 index 0000000000..4555a4c96f --- /dev/null +++ b/apps/desktop-gpui/assets/icons/zap.svg @@ -0,0 +1 @@ + diff --git a/apps/desktop-gpui/assets/images/auto.jpg b/apps/desktop-gpui/assets/images/auto.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2a9304cb45527f50fc25865a3f6a510a1421b6af GIT binary patch literal 44560 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

    ); diff --git a/apps/desktop/src/utils/general-settings.ts b/apps/desktop/src/utils/general-settings.ts index f878b44884..c0eeb93b56 100644 --- a/apps/desktop/src/utils/general-settings.ts +++ b/apps/desktop/src/utils/general-settings.ts @@ -8,6 +8,7 @@ export type GeneralSettingsStore = TauriGeneralSettingsStore & { transcriptionHints?: string[]; enableTelemetry?: boolean; outOfProcessMuxer?: boolean; + enableGpuiApp?: boolean; }; export const DEFAULT_TRANSCRIPTION_HINTS = [ From 3e0cd1b3b2ad9ae71667f44bc4f270bac69df46e Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:53:58 +0100 Subject: [PATCH 134/146] feat(desktop-gpui): capture screenshots from the overlay, tray and harness --- apps/desktop-gpui/src/app_windows.rs | 41 ++++++ apps/desktop-gpui/src/main.rs | 13 +- apps/desktop-gpui/src/main_window.rs | 9 +- apps/desktop-gpui/src/screenshot.rs | 162 ++++++++++++++++++++++++ apps/desktop-gpui/src/target_overlay.rs | 67 +++++++--- apps/desktop-gpui/src/tray.rs | 26 ++-- 6 files changed, 285 insertions(+), 33 deletions(-) create mode 100644 apps/desktop-gpui/src/screenshot.rs diff --git a/apps/desktop-gpui/src/app_windows.rs b/apps/desktop-gpui/src/app_windows.rs index 429510993b..7e2167f646 100644 --- a/apps/desktop-gpui/src/app_windows.rs +++ b/apps/desktop-gpui/src/app_windows.rs @@ -2356,6 +2356,47 @@ fn open_controls( } } +/// Hide the pickers before a screenshot grab, the way `take_screenshot` hides +/// TargetSelectOverlay / CaptureArea / ModeSelect (`recording.rs:2876-2895`): +/// an overlay still on screen would be in the shot. Returns whether anything +/// was up -- the caller's cue to wait the same 150ms the Tauri command waits. +pub fn prepare_for_screenshot_capture(cx: &mut App) -> bool { + if cx.global::().overlays.is_empty() { + return false; + } + close_target_overlays(cx); + true +} + +/// A screenshot finished (`Some` PNG path) or failed (`None`): reveal the +/// main window if the picker hid it, and refresh every surface that lists +/// screenshots -- the `NewScreenshotAdded` listeners in the Tauri app. The +/// Tauri flow opens the screenshot editor from this seam; until that window +/// exists, Recents is the completion affordance. +pub fn screenshot_finished(captured: Option, cx: &mut App) { + let hidden = std::mem::take(&mut cx.global_mut::().main_hidden_for_picker); + let reveal = hidden && RecordingSession::global(cx).read(cx).phase == Phase::Idle; + + if captured.is_some() { + crate::tray::refresh_previous(cx); + if let Some(settings) = cx.global::().settings { + settings + .update(cx, |view, window, cx| view.refresh_screenshots(window, cx)) + .ok(); + } + } + + if reveal { + // `show_main_window` rescans Recents on the way up, so the new + // screenshot is already in the carousel. + show_main_window(cx); + } else if captured.is_some() { + let main = cx.global::().main; + main.update(cx, |view, window, cx| view.refresh_recents(window, cx)) + .ok(); + } +} + pub fn refresh_library_after_delete(cx: &mut App) { let main = cx.global::().main; let settings = cx.global::().settings; diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index fe72580d89..c6554cd8c7 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -35,6 +35,7 @@ mod permissions; mod platform; mod presets; mod recording; +mod screenshot; mod session; mod settings_pages; mod settings_window; @@ -63,6 +64,11 @@ const MAIN_WINDOW_HEIGHT: f32 = 395.; const MAIN_WINDOW_MATERIAL_RADIUS: f64 = 16.; fn parse_auto_record(spec: &str) -> Option<(main_window::Mode, u64)> { + if spec == "screenshot" { + // A capture has no duration; the harness arms the target, captures, + // and the stop half of the driver is a no-op on an idle session. + return Some((main_window::Mode::Screenshot, 0)); + } let (mode, secs) = spec.split_once(':')?; let mode = match mode { "studio" => main_window::Mode::Studio, @@ -399,9 +405,10 @@ fn main() { } } - // `CAP_GPUI_AUTO_RECORD=studio:5` / `instant:4`: arm the primary - // display and record for N seconds. The end-to-end check drives the - // recorder this way because unprivileged synthetic clicks are dropped. + // `CAP_GPUI_AUTO_RECORD=studio:5` / `instant:4` / `screenshot`: arm + // the primary display and record for N seconds (or capture once). The + // end-to-end check drives the recorder this way because unprivileged + // synthetic clicks are dropped. if let Ok(auto) = std::env::var("CAP_GPUI_AUTO_RECORD") && let Some((mode, secs)) = parse_auto_record(&auto) { diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index 70875541e2..8ab9c15995 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -893,7 +893,8 @@ impl MainWindow { // ffprobe duration < wall time proves the pause reached the engine. let pause_wiggle = std::env::var("CAP_GPUI_AUTO_PAUSE").is_ok_and(|v| v == "1"); - let skip_mic = std::env::var("CAP_GPUI_AUTO_NO_MIC").is_ok_and(|v| v == "1"); + let skip_mic = std::env::var("CAP_GPUI_AUTO_NO_MIC").is_ok_and(|v| v == "1") + || mode == Mode::Screenshot; let required_window = auto_window_title(); cx.spawn_in(window, async move |this, cx| { @@ -1105,6 +1106,12 @@ impl MainWindow { let Some(target) = self.armed_target() else { return; }; + if self.mode == Mode::Screenshot { + // Screenshot mode never reaches the recording actors -- the + // target goes straight to the capture path (`take_screenshot`). + cx.defer(move |cx: &mut gpui::App| crate::screenshot::take_screenshot(target, cx)); + return; + } self.start_recording_with_target(target, Vec::new(), window, cx); } diff --git a/apps/desktop-gpui/src/screenshot.rs b/apps/desktop-gpui/src/screenshot.rs new file mode 100644 index 0000000000..08805c9143 --- /dev/null +++ b/apps/desktop-gpui/src/screenshot.rs @@ -0,0 +1,162 @@ +//! Screenshot capture -- `take_screenshot` (`recording.rs:2852-3057` in the +//! Tauri app), the path Screenshot mode takes instead of the recording +//! actors. +//! +//! The Tauri command hides the picker overlays, waits 150ms for the fade, +//! captures through `cap_recording::screenshot::capture_screenshot`, and +//! writes the same bundle shape image import writes: `/screenshots/ +//! {name}.cap/` holding `original.png`, a Studio `SingleSegment` meta whose +//! display track is the PNG at `fps: 0`, and a project config whose +//! background is transparent white with no shadow (`recording.rs:2963-2985`). +//! Known deviations from that command: no capture sound (this app has no +//! sound assets yet), no native notification, and no `ScreenshotTaken` +//! automations (no automation runner here). + +use std::path::PathBuf; +use std::time::Duration; + +use anyhow::{Context as _, anyhow}; +use cap_recording::sources::screen_capture::ScreenCaptureTarget; +use gpui::App; + +use crate::app_windows; + +pub fn take_screenshot(target: ScreenCaptureTarget, cx: &mut App) { + if matches!(target, ScreenCaptureTarget::CameraOnly) { + // `capture_screenshot` rejects it too; failing here keeps the pickers + // up instead of tearing them down for nothing. + tracing::error!("camera-only is not a screenshot target"); + return; + } + tracing::info!(kind = target.kind_str(), "taking a screenshot"); + + let settle = app_windows::prepare_for_screenshot_capture(cx); + + let task = gpui_tokio::Tokio::spawn_result(cx, async move { + if settle { + // The 150ms the Tauri command sleeps after hiding overlays + // (`recording.rs:2893-2895`): the windows close synchronously but + // the compositor needs a beat to actually drop them from screen. + tokio::time::sleep(Duration::from_millis(150)).await; + } + capture_and_write(target).await + }); + + cx.spawn(async move |cx| { + let captured = match task.await { + Ok(png) => { + tracing::info!(path = %png.display(), "screenshot captured"); + Some(png) + } + Err(error) => { + tracing::error!("screenshot capture failed: {error:#}"); + None + } + }; + cx.update(|cx| app_windows::screenshot_finished(captured, cx)); + }) + .detach(); +} + +/// Capture, then write the `.cap` bundle. Returns the PNG path, which is what +/// the Tauri command returns (`NewScreenshotAdded.path` is the PNG, not the +/// bundle). +async fn capture_and_write(target: ScreenCaptureTarget) -> anyhow::Result { + let image = cap_recording::screenshot::capture_screenshot(target.clone()) + .await + .context("capturing the screenshot")?; + + let base = crate::library::screenshots_dir(); + std::fs::create_dir_all(&base) + .with_context(|| format!("creating screenshots dir {}", base.display()))?; + + // The same naming chain `create_project_dir` uses for recordings: default + // template only (the custom-template deviation recordings already have), + // colons and slashes dotted, then uniqued against the directory. + let target_name = target.title().unwrap_or_else(|| "Unknown".into()); + let now = chrono::Local::now(); + let pretty_name = format!( + "{} ({}) {} {}", + target_name, + target.kind_str(), + now.format("%Y-%m-%d"), + now.format("%I.%M %p"), + ); + let filename = format!("{}.cap", pretty_name.replace([':', '/'], ".")); + let filename = cap_utils::ensure_unique_filename(&filename, &base) + .map_err(|e| anyhow!("unique filename: {e}"))?; + let bundle = base.join(filename); + std::fs::create_dir_all(&bundle) + .with_context(|| format!("creating screenshot bundle {}", bundle.display()))?; + + let meta = cap_project::RecordingMeta { + platform: Some(cap_project::Platform::default()), + project_path: bundle.clone(), + pretty_name, + sharing: None, + inner: cap_project::RecordingMetaInner::Studio(Box::new( + cap_project::StudioRecordingMeta::SingleSegment { + segment: cap_project::SingleSegment { + display: cap_project::VideoMeta { + path: "original.png".into(), + fps: 0, + start_time: Some(0.0), + device_id: None, + }, + camera: None, + audio: None, + cursor: None, + }, + }, + )), + upload: None, + }; + meta.save_for_project() + .map_err(|e| anyhow!("saving screenshot meta: {e}"))?; + + let mut config = cap_project::ProjectConfiguration::default(); + config.background.source = cap_project::BackgroundSource::Color { + value: [255, 255, 255], + alpha: 0, + }; + config.background.shadow = 0.0; + config + .write(&bundle) + .map_err(|e| anyhow!("saving screenshot project config: {e}"))?; + + let png = bundle.join("original.png"); + write_png(png.clone(), image).await?; + Ok(png) +} + +/// The Tauri encode settings (`recording.rs:2987-3016`): RGB stays RGB, a +/// capture past 8MP trades compression for speed, adaptive filtering either +/// way. Blocking work, so off the runtime workers. +async fn write_png(path: PathBuf, image: image::DynamicImage) -> anyhow::Result<()> { + tokio::task::spawn_blocking(move || { + let width = image.width(); + let height = image.height(); + let color_type = match &image { + image::DynamicImage::ImageRgba8(_) => image::ColorType::Rgba8, + _ => image::ColorType::Rgb8, + }; + let compression = if (width as u64).saturating_mul(height as u64) > 8_000_000 { + image::codecs::png::CompressionType::Fast + } else { + image::codecs::png::CompressionType::Default + }; + let data = image.into_bytes(); + + let file = + std::fs::File::create(&path).with_context(|| format!("creating {}", path.display()))?; + let encoder = image::codecs::png::PngEncoder::new_with_quality( + std::io::BufWriter::new(file), + compression, + image::codecs::png::FilterType::Adaptive, + ); + image::ImageEncoder::write_image(encoder, &data, width, height, color_type.into()) + .context("encoding the screenshot PNG") + }) + .await + .context("the PNG encode task died")? +} diff --git a/apps/desktop-gpui/src/target_overlay.rs b/apps/desktop-gpui/src/target_overlay.rs index d61e7f61a7..a226e24b28 100644 --- a/apps/desktop-gpui/src/target_overlay.rs +++ b/apps/desktop-gpui/src/target_overlay.rs @@ -52,6 +52,17 @@ const POLL_INTERVAL: Duration = Duration::from_millis(80); /// `MIN_SIZE` in the TSX -- the smallest area selection that can be recorded. const AREA_MIN_SIZE: f32 = 150.; +/// `MIN_SCREENSHOT_SIZE` in the TSX: a screenshot area only has to be a +/// pixel, not the 150px a recording needs. +const SCREENSHOT_MIN_SIZE: f32 = 1.; + +fn area_min_size(mode: Mode) -> f32 { + match mode { + Mode::Screenshot => SCREENSHOT_MIN_SIZE, + _ => AREA_MIN_SIZE, + } +} + /// How close to an edge or corner counts as grabbing that handle. The TSX's /// corner buttons are 30px boxes hung 12px outside the crop and its edge /// buttons are 10px strips straddling the border; this is the same reach @@ -284,8 +295,8 @@ impl AreaRect { self.y + self.height } - fn is_valid(&self) -> bool { - self.width >= AREA_MIN_SIZE && self.height >= AREA_MIN_SIZE + fn is_valid_for(&self, min: f32) -> bool { + self.width >= min && self.height >= min } fn clamped(self, display: (f32, f32)) -> Self { @@ -477,6 +488,7 @@ impl OverlayWindow { /// record yet (no window under the cursor, no valid area drawn). pub fn target(&self, cx: &App) -> Option { let select = self.select.read(cx); + let min = area_min_size(select.recording_mode); match select.mode? { TargetType::Display => Some(ScreenCaptureTarget::Display { id: self.display_id.clone(), @@ -488,14 +500,12 @@ impl OverlayWindow { id: window.id.clone(), }) } - TargetType::Area => { - self.crop - .filter(|crop| crop.is_valid()) - .map(|crop| ScreenCaptureTarget::Area { - screen: self.display_id.clone(), - bounds: crop.to_bounds(), - }) - } + TargetType::Area => self.crop.filter(|crop| crop.is_valid_for(min)).map(|crop| { + ScreenCaptureTarget::Area { + screen: self.display_id.clone(), + bounds: crop.to_bounds(), + } + }), TargetType::CameraOnly => Some(ScreenCaptureTarget::CameraOnly), } } @@ -509,8 +519,11 @@ impl OverlayWindow { }; tracing::info!(target = ?target.kind_str(), "overlay start pressed"); if self.select.read(cx).recording_mode == Mode::Screenshot { - // Screenshots do not go through the recording actors at all, and - // that path does not exist in this app yet. + // Screenshots never reach the recording actors: the target goes + // straight to the capture path, which closes these overlays + // itself (`startRecording`'s screenshot branch, + // `target-select-overlay.tsx:1995`). + cx.defer(move |cx: &mut App| crate::screenshot::take_screenshot(target, cx)); return; } // Deferred: this runs inside the overlay's own window update and the @@ -765,7 +778,8 @@ impl OverlayWindow { // `shouldShowOverlay = isInteracting() || isActiveDisplay()`. let visible = interacting || self.is_active_display(cx); let crop = self.crop; - let valid = crop.is_some_and(|crop| crop.is_valid()); + let min = area_min_size(self.select.read(cx).recording_mode); + let valid = crop.is_some_and(|crop| crop.is_valid_for(min)); let mut root = div() .id("area-root") @@ -1104,7 +1118,7 @@ impl OverlayWindow { ) -> impl IntoElement { let theme = self.theme; let mode = self.select.read(cx).recording_mode; - let disabled = disabled || target.is_none() || mode == Mode::Screenshot; + let disabled = disabled || target.is_none(); // `flex flex-col gap-2.5 items-stretch my-2.5 w-104`. div() @@ -1430,9 +1444,9 @@ impl OverlayWindow { } fn area_mouse_up(&mut self, cx: &mut Context) { - if self.drag.take().is_none() { + let Some(drag) = self.drag.take() else { return; - } + }; if let Some(crop) = self.crop { // A click with no drag is not a selection. if crop.width < 2. || crop.height < 2. { @@ -1441,6 +1455,16 @@ impl OverlayWindow { self.crop = Some(crop.clamped(self.display_size)); } } + // Releasing a fresh draw in screenshot mode captures immediately -- + // the screenshot area picker has no confirm step + // (`target-select-overlay.tsx`'s area mouse-up). Move/resize grabs + // keep the crop for the Start button, same as recording mode. + if matches!(drag, AreaDrag::Draw { .. }) + && self.select.read(cx).recording_mode == Mode::Screenshot + && let Some(target) = self.target(cx) + { + cx.defer(move |cx: &mut App| crate::screenshot::take_screenshot(target, cx)); + } cx.notify(); } } @@ -1473,12 +1497,15 @@ mod tests { ); } - /// `MIN_SIZE` is 150x150; anything smaller is drawn but not recordable. + /// `MIN_SIZE` is 150x150 for a recording; anything smaller is drawn but + /// not recordable. A screenshot only needs `MIN_SCREENSHOT_SIZE` (1px). #[test] fn area_validity_matches_the_min_size() { - assert!(rect(0., 0., 150., 150.).is_valid()); - assert!(!rect(0., 0., 149., 400.).is_valid()); - assert!(!rect(0., 0., 400., 149.).is_valid()); + assert!(rect(0., 0., 150., 150.).is_valid_for(area_min_size(Mode::Studio))); + assert!(!rect(0., 0., 149., 400.).is_valid_for(area_min_size(Mode::Studio))); + assert!(!rect(0., 0., 400., 149.).is_valid_for(area_min_size(Mode::Studio))); + assert!(rect(0., 0., 20., 20.).is_valid_for(area_min_size(Mode::Screenshot))); + assert!(!rect(0., 0., 0.5, 20.).is_valid_for(area_min_size(Mode::Screenshot))); } /// The `ScreenCaptureTarget::Area` bounds are display-relative logical diff --git a/apps/desktop-gpui/src/tray.rs b/apps/desktop-gpui/src/tray.rs index c3d93791cc..9e64833f3d 100644 --- a/apps/desktop-gpui/src/tray.rs +++ b/apps/desktop-gpui/src/tray.rs @@ -14,8 +14,8 @@ //! Same seam the editor's overlay panels use for AppKit callbacks. //! //! Menu strings, order and separators are byte-identical to `build_tray_menu`. -//! "Take a Screenshot" and "Upload Logs" render disabled because the capture -//! and log-upload paths do not exist here. +//! "Upload Logs" renders disabled because the log-upload path does not exist +//! here. use std::path::{Path, PathBuf}; @@ -61,6 +61,7 @@ pub enum TrayItem { RecordDisplay, RecordWindow, RecordArea, + TakeScreenshot, ImportMedia, ViewAllRecordings, ViewAllScreenshots, @@ -296,9 +297,7 @@ pub fn build_menu(mode: Mode, previous: &[PreviousItem], version: &str) -> Vec app_windows::arm_target_mode(TargetType::Display, cx), TrayItem::RecordWindow => app_windows::arm_target_mode(TargetType::Window, cx), TrayItem::RecordArea => app_windows::arm_target_mode(TargetType::Area, cx), + TrayItem::TakeScreenshot => { + // `TrayItem::TakeScreenshot` captures the display under the + // cursor, primary as the fallback (`tray.rs:816` over there). + let display = scap_targets::Display::get_containing_cursor() + .unwrap_or_else(scap_targets::Display::primary); + crate::screenshot::take_screenshot( + cap_recording::sources::screen_capture::ScreenCaptureTarget::Display { + id: display.id(), + }, + cx, + ); + } // `TrayItem::ImportVideo` (`src-tauri/src/tray.rs:839-911`): one // picker over both media filters, routed by extension. TrayItem::ImportMedia => crate::import::pick_and_import_media(cx), @@ -1286,9 +1297,6 @@ mod tests { _ => None, }) .collect(); - assert_eq!( - disabled, - vec!["Take a Screenshot", "Upload Logs", "Cap v0.1.0",] - ); + assert_eq!(disabled, vec!["Upload Logs", "Cap v0.1.0"]); } } From 273e63907ea724ec7fab16cbab9c278d6bec6f64 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:13:43 +0100 Subject: [PATCH 135/146] feat(rendering): let RenderVideoConstants build its device from explicit options --- crates/rendering/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/rendering/src/lib.rs b/crates/rendering/src/lib.rs index db38d14556..1ca2a9a5c7 100644 --- a/crates/rendering/src/lib.rs +++ b/crates/rendering/src/lib.rs @@ -2042,6 +2042,18 @@ impl RenderVideoConstants { preserve_screen_alpha: false, }; + Self::new_with_options(options, recording_meta, meta).await + } + + /// [`Self::new`] with the `RenderOptions` supplied by the caller instead + /// of probed from segment videos -- the still-image path (screenshot + /// editors), where there is no `SegmentRecordings` to measure and the + /// alpha channel must survive (`preserve_screen_alpha`). + pub async fn new_with_options( + options: RenderOptions, + recording_meta: RecordingMeta, + meta: StudioRecordingMeta, + ) -> Result { let instance = create_wgpu_instance().await; let force_software_adapter = force_software_wgpu_adapter(); From 7a34cf614bc6254f7327caaf31b76c117abe0e0f Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:13:43 +0100 Subject: [PATCH 136/146] feat(desktop-gpui): add a screenshot editor window with a GPU-styled preview --- apps/desktop-gpui/src/app_windows.rs | 190 +++++- apps/desktop-gpui/src/import.rs | 10 +- apps/desktop-gpui/src/main.rs | 30 + apps/desktop-gpui/src/main_window.rs | 18 +- apps/desktop-gpui/src/screenshot_editor.rs | 727 +++++++++++++++++++++ apps/desktop-gpui/src/settings_window.rs | 5 +- apps/desktop-gpui/src/tray.rs | 6 +- 7 files changed, 948 insertions(+), 38 deletions(-) create mode 100644 apps/desktop-gpui/src/screenshot_editor.rs diff --git a/apps/desktop-gpui/src/app_windows.rs b/apps/desktop-gpui/src/app_windows.rs index 7e2167f646..76e3d27607 100644 --- a/apps/desktop-gpui/src/app_windows.rs +++ b/apps/desktop-gpui/src/app_windows.rs @@ -29,6 +29,7 @@ use crate::{ onboarding_window::{self, OnboardingWindow}, platform, recording::{RecordingMode, StartConfig}, + screenshot_editor::{self, ScreenshotEditorWindow}, session::{Phase, RecordingSession}, settings_window::{self, Page, SettingsWindow}, target_overlay::{AreaRect, HoveredWindow, OverlayWindow, TargetSelect}, @@ -58,6 +59,9 @@ pub struct AppWindows { /// (`windows.rs:3656-3659`). The Tauri app keys its label off an /// incrementing id; the path is the identity in both. pub editors: Vec<(PathBuf, WindowHandle)>, + /// One screenshot editor per `.cap` bundle -- the gpui spelling of + /// `ScreenshotEditorWindowIds`, keyed by the bundle directory. + pub screenshot_editors: Vec<(PathBuf, WindowHandle)>, /// `hasHiddenMainWindowForPicker` (`new-main/index.tsx:2016-2059`): the /// main window hides while the target picker is up, and comes back only on /// a dismissal that reveals ("cancelled" -- Escape, the overlay's close @@ -83,6 +87,7 @@ pub fn init(main: WindowHandle, session: Entity, c teleprompter: None, overlays: Vec::new(), editors: Vec::new(), + screenshot_editors: Vec::new(), main_hidden_for_picker: false, }); @@ -155,6 +160,11 @@ pub fn broadcast_theme(cx: &mut App) { let camera = windows.camera; let overlays: Vec<_> = windows.overlays.iter().map(|(_, handle)| *handle).collect(); let editors: Vec<_> = windows.editors.iter().map(|(_, handle)| *handle).collect(); + let screenshot_editors: Vec<_> = windows + .screenshot_editors + .iter() + .map(|(_, handle)| *handle) + .collect(); let refresh = |window: &mut gpui::Window, cx: &mut gpui::App| { crate::theme::apply_native(window, cx); @@ -212,6 +222,12 @@ pub fn broadcast_theme(cx: &mut App) { cx.notify(); }); } + for handle in screenshot_editors { + let _ = handle.update(cx, |_, window, cx| { + refresh(window, cx); + cx.notify(); + }); + } } /// `makeKeyAndOrderFront:` re-enters gpui's window callbacks, so it runs from @@ -2175,7 +2191,8 @@ pub fn editor_closed(project_path: &Path, cx: &mut App) { } } - let editors_left = cx.global::().editors.len(); + let windows = cx.global::(); + let editors_left = windows.editors.len() + windows.screenshot_editors.len(); tracing::info!( path = %key.display(), editors_left, @@ -2368,32 +2385,163 @@ pub fn prepare_for_screenshot_capture(cx: &mut App) -> bool { true } -/// A screenshot finished (`Some` PNG path) or failed (`None`): reveal the -/// main window if the picker hid it, and refresh every surface that lists -/// screenshots -- the `NewScreenshotAdded` listeners in the Tauri app. The -/// Tauri flow opens the screenshot editor from this seam; until that window -/// exists, Recents is the completion affordance. +/// A screenshot finished (`Some` PNG path) or failed (`None`): refresh every +/// surface that lists screenshots -- the `NewScreenshotAdded` listeners in +/// the Tauri app -- and open the screenshot editor on the new bundle, the +/// `automationShouldOpenScreenshotEditor` default. A failure reveals the main +/// window if the picker had hidden it, since nothing else will. pub fn screenshot_finished(captured: Option, cx: &mut App) { - let hidden = std::mem::take(&mut cx.global_mut::().main_hidden_for_picker); - let reveal = hidden && RecordingSession::global(cx).read(cx).phase == Phase::Idle; + let Some(png) = captured else { + let hidden = std::mem::take(&mut cx.global_mut::().main_hidden_for_picker); + if hidden && RecordingSession::global(cx).read(cx).phase == Phase::Idle { + show_main_window(cx); + } + return; + }; - if captured.is_some() { - crate::tray::refresh_previous(cx); - if let Some(settings) = cx.global::().settings { - settings - .update(cx, |view, window, cx| view.refresh_screenshots(window, cx)) - .ok(); + crate::tray::refresh_previous(cx); + if let Some(settings) = cx.global::().settings { + settings + .update(cx, |view, window, cx| view.refresh_screenshots(window, cx)) + .ok(); + } + let main = cx.global::().main; + main.update(cx, |view, window, cx| view.refresh_recents(window, cx)) + .ok(); + + // The editor owns the foreground now, the way a stopped studio recording + // hands off to the video editor; `screenshot_editor_closed` brings the + // main window back. + cx.global_mut::().main_hidden_for_picker = false; + open_screenshot_editor(png, cx); +} + +/// Open (or focus) the screenshot editor for a bundle -- the +/// `ShowCapWindow::ScreenshotEditor` arm: 1240x800, min 800x600, centered, +/// reused per path. Accepts the PNG or the `.cap` directory. +/// +/// Must be reached through `cx.defer` from anything inside an entity update: +/// opening a window paints it synchronously and would double-lease the caller. +pub fn open_screenshot_editor(path: PathBuf, cx: &mut App) { + let Some(bundle) = screenshot_editor::resolve_bundle(&path) else { + tracing::error!(path = %path.display(), "not a screenshot bundle; not opening the editor"); + return; + }; + let key = editor_key(&bundle); + + if let Some(handle) = cx + .global::() + .screenshot_editors + .iter() + .find(|(existing, _)| existing == &key) + .map(|(_, handle)| *handle) + { + tracing::info!( + path = %key.display(), + "screenshot editor already open for this bundle; focusing it" + ); + let native = handle + .update(cx, |_, window, _| platform::native_window(window)) + .ok() + .flatten(); + cx.spawn(async move |_| { + if let Some(native) = &native { + platform::show_native(native); + } + }) + .detach(); + hide_main_window(cx); + return; + } + + let bounds = Bounds::centered( + None, + size( + px(screenshot_editor::SCREENSHOT_EDITOR_WIDTH), + px(screenshot_editor::SCREENSHOT_EDITOR_HEIGHT), + ), + cx, + ); + + let handle = cx.open_window( + WindowOptions { + window_bounds: Some(WindowBounds::Windowed(bounds)), + titlebar: Some(gpui::TitlebarOptions { + title: Some("Cap Screenshot Editor".into()), + appears_transparent: true, + traffic_light_position: None, + }), + kind: WindowKind::Normal, + focus: true, + show: true, + is_resizable: true, + is_minimizable: true, + window_min_size: Some(size( + px(screenshot_editor::SCREENSHOT_EDITOR_MIN_WIDTH), + px(screenshot_editor::SCREENSHOT_EDITOR_MIN_HEIGHT), + )), + ..Default::default() + }, + { + let key = key.clone(); + move |window, cx| cx.new(|cx| ScreenshotEditorWindow::new(key, window, cx)) + }, + ); + + let handle = match handle { + Ok(handle) => handle, + Err(error) => { + tracing::error!("screenshot editor window failed to open: {error:#}"); + return; } + }; + + cx.global_mut::() + .screenshot_editors + .push((key.clone(), handle)); + handle + .update(cx, |_, window, _| { + platform::kick_display_link(window); + tracing::info!( + number = platform::window_number(window), + path = %key.display(), + "screenshot editor window opened" + ); + }) + .ok(); + + hide_main_window(cx); + screenshot_editor::load_screenshot_project(key, handle, cx); +} + +/// A screenshot editor window is going away: flush its pending config write +/// and bring the main window back once the last editor of either kind closes +/// -- the same `Destroyed` arm `editor_closed` mirrors. +pub fn screenshot_editor_closed(bundle: &Path, cx: &mut App) { + let key = editor_key(bundle); + let handle = { + let editors = &mut cx.global_mut::().screenshot_editors; + let index = editors.iter().position(|(path, _)| path == &key); + index.map(|index| editors.remove(index).1) + }; + + if let Some(handle) = handle + && let Ok(pending) = handle.update(cx, |view, _window, _cx| view.pending_save()) + { + pending.borrow_mut().flush(); } - if reveal { - // `show_main_window` rescans Recents on the way up, so the new - // screenshot is already in the carousel. + let windows = cx.global::(); + let editors_left = windows.editors.len() + windows.screenshot_editors.len(); + tracing::info!( + path = %key.display(), + editors_left, + "screenshot editor window closed" + ); + if editors_left == 0 && RecordingSession::global(cx).read(cx).phase == Phase::Idle { show_main_window(cx); - } else if captured.is_some() { - let main = cx.global::().main; - main.update(cx, |view, window, cx| view.refresh_recents(window, cx)) - .ok(); + } else { + crate::menus::schedule_dock_sync(cx); } } diff --git a/apps/desktop-gpui/src/import.rs b/apps/desktop-gpui/src/import.rs index ba6fb0df70..f993b7f6a9 100644 --- a/apps/desktop-gpui/src/import.rs +++ b/apps/desktop-gpui/src/import.rs @@ -218,9 +218,13 @@ fn apply_progress(update: ImportProgress, cx: &mut App) { // `importVideoPath` opens the editor on the imported bundle; the // Tauri version opens it up front and shows `ImportProgress.tsx` // inside, this app opens it once the bundle is Complete (deviation: - // the gpui editor has no importing screen). - if kind == ImportKind::Video && cx.has_global::() { - crate::app_windows::open_editor(project_path, cx); + // the gpui editor has no importing screen). `importImagePath` ends in + // `ShowCapWindow::ScreenshotEditor` the same way. + if cx.has_global::() { + match kind { + ImportKind::Video => crate::app_windows::open_editor(project_path, cx), + ImportKind::Image => crate::app_windows::open_screenshot_editor(project_path, cx), + } } } diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index c6554cd8c7..57c1adb7f9 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -36,6 +36,7 @@ mod platform; mod presets; mod recording; mod screenshot; +mod screenshot_editor; mod session; mod settings_pages; mod settings_window; @@ -93,6 +94,20 @@ fn resolve_auto_editor(target: &str) -> Option { .map(|item| item.bundle) } +/// `CAP_GPUI_AUTO_SCREENSHOT_EDITOR`: a screenshot `.cap` path, or `1` for +/// the newest screenshot in the library -- [`resolve_auto_editor`]'s shape +/// for the screenshot editor. +fn resolve_auto_screenshot_editor(target: &str) -> Option { + if target != "1" { + let path = std::path::PathBuf::from(target); + return path.is_dir().then_some(path); + } + library::recent_media() + .into_iter() + .find(|item| item.kind == library::MediaKind::Screenshot) + .map(|item| item.bundle) +} + fn main() { tracing_subscriber::fmt() .with_env_filter( @@ -405,6 +420,21 @@ fn main() { } } + // `CAP_GPUI_AUTO_SCREENSHOT_EDITOR=` (or `1` for the + // newest screenshot): open the screenshot editor the way a Recents + // card does. + if let Ok(target) = std::env::var("CAP_GPUI_AUTO_SCREENSHOT_EDITOR") + && !target.is_empty() + { + match resolve_auto_screenshot_editor(&target) { + Some(path) => app_windows::open_screenshot_editor(path, cx), + None => tracing::error!( + target, + "CAP_GPUI_AUTO_SCREENSHOT_EDITOR: no screenshot .cap to open" + ), + } + } + // `CAP_GPUI_AUTO_RECORD=studio:5` / `instant:4` / `screenshot`: arm // the primary display and record for N seconds (or capture once). The // end-to-end check drives the recorder this way because unprivileged diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index 8ab9c15995..4b5baaca5e 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -2238,7 +2238,10 @@ impl MainWindow { item.pretty_name.clone(), None, 0, - move |_, _, _| library::open_path(&png), + move |_, _, cx| { + let png = png.clone(); + cx.defer(move |cx| app_windows::open_screenshot_editor(png, cx)); + }, actions, theme, ) @@ -3934,9 +3937,9 @@ impl MainWindow { /// What a click on a Recents card does -- `openRecentMedia`. /// -/// Studio recordings open the editor. Instant recordings open the share -/// link when one exists, otherwise the bundle is revealed. Screenshots -/// open the PNG (the screenshot editor is not in this app yet). +/// Studio recordings open the editor, screenshots the screenshot editor. +/// Instant recordings open the share link when one exists, otherwise the +/// bundle is revealed. pub fn activate_recent(item: &RecentItem, cx: &mut gpui::App) { match item.kind { MediaKind::Studio => { @@ -3952,11 +3955,8 @@ pub fn activate_recent(item: &RecentItem, cx: &mut gpui::App) { } } MediaKind::Screenshot => { - if let Some(path) = &item.thumbnail { - library::open_path(path); - } else { - library::open_path(&item.bundle); - } + let bundle = item.bundle.clone(); + cx.defer(move |cx| app_windows::open_screenshot_editor(bundle, cx)); } } } diff --git a/apps/desktop-gpui/src/screenshot_editor.rs b/apps/desktop-gpui/src/screenshot_editor.rs new file mode 100644 index 0000000000..184f6288b9 --- /dev/null +++ b/apps/desktop-gpui/src/screenshot_editor.rs @@ -0,0 +1,727 @@ +//! The screenshot editor window -- `screenshot_editor.rs` + +//! `routes/screenshot-editor/` in the Tauri app, natively. +//! +//! The Tauri editor styles a still PNG through the same GPU renderer the +//! video editor uses: one `DecodedFrame`, `preserve_screen_alpha: true`, no +//! camera, re-rendered whenever `ProjectConfiguration` changes and pushed to +//! the webview over a websocket (`screenshot_editor.rs:316-476` over there). +//! Here the loop is the same -- `FrameRenderer::render_immediate` on a +//! `tokio::sync::watch` of config revisions -- and the frame skips the +//! websocket: it is un-padded and BGRA-swapped by `editor_window::frame_image` +//! and painted with `paint_image`, exactly like the video editor's CPU path. +//! +//! Annotations, OCR and the share flow are not here yet; styling and export +//! grow on this seam. + +use std::cell::RefCell; +use std::path::{Path, PathBuf}; +use std::rc::Rc; +use std::sync::Arc; +use std::time::Duration; + +use cap_project::{ProjectConfiguration, RecordingMeta, RecordingMetaInner, StudioRecordingMeta}; +use cap_rendering::{ + DecodedFrame, DecodedSegmentFrames, FrameRenderer, ProjectUniforms, RenderOptions, + RenderVideoConstants, RenderedFrame, RendererLayers, ZoomTransformTimeline, +}; +use gpui::{ + App, Context, FontWeight, InteractiveElement as _, IntoElement, ParentElement as _, Render, + RenderImage, StatefulInteractiveElement as _, Styled, Window, WindowHandle, div, + prelude::FluentBuilder as _, px, +}; + +use crate::theme::Theme; +use crate::ui; + +/// `ShowCapWindow::ScreenshotEditor`: 1240x800, min 800x600, resizable. +pub const SCREENSHOT_EDITOR_WIDTH: f32 = 1240.; +pub const SCREENSHOT_EDITOR_HEIGHT: f32 = 800.; +pub const SCREENSHOT_EDITOR_MIN_WIDTH: f32 = 800.; +pub const SCREENSHOT_EDITOR_MIN_HEIGHT: f32 = 600.; + +/// `MAX_DIMENSION` (`screenshot_editor.rs:38` over there). +const MAX_DIMENSION: u32 = 16_384; + +/// `PROJECT_SAVE_DEBOUNCE_MS`, the same 250ms the video editor debounces. +const SAVE_DEBOUNCE: Duration = Duration::from_millis(250); + +/// The `.cap` bundle a screenshot path belongs to: the directory itself, or +/// the parent of `original.png` -- `create_standalone_instance`'s cap_dir +/// resolution (`screenshot_editor.rs:205-215` over there). +pub fn resolve_bundle(path: &Path) -> Option { + if path.extension().and_then(|ext| ext.to_str()) == Some("cap") { + return Some(path.to_path_buf()); + } + let parent = path.parent()?; + (parent.extension().and_then(|ext| ext.to_str()) == Some("cap")).then(|| parent.to_path_buf()) +} + +// --------------------------------------------------------------------------- +// Source loading +// --------------------------------------------------------------------------- + +/// Everything the window and the renderer need off disk. +pub struct LoadedSource { + pub rgba: Vec, + pub width: u32, + pub height: u32, + pub pretty_name: String, + pub config: ProjectConfiguration, + pub meta: RecordingMeta, + pub studio_meta: StudioRecordingMeta, +} + +/// Read `original.png` (or the first PNG in the bundle), the recording meta +/// and the project config -- the disk half of `create_standalone_instance`. +pub fn load_source(bundle: &Path) -> Result { + let image_path = { + let original = bundle.join("original.png"); + if original.exists() { + original + } else { + std::fs::read_dir(bundle) + .ok() + .and_then(|dir| { + dir.flatten() + .find(|entry| { + entry.path().extension().and_then(|ext| ext.to_str()) == Some("png") + }) + .map(|entry| entry.path()) + }) + .ok_or_else(|| format!("No PNG found in {}", bundle.display()))? + } + }; + + let image = image::open(&image_path).map_err(|e| format!("Failed to open image: {e}"))?; + let (width, height) = (image.width(), image.height()); + if width > MAX_DIMENSION || height > MAX_DIMENSION { + return Err(format!("Image dimensions exceed maximum: {width}x{height}")); + } + + let meta = RecordingMeta::load_for_project(bundle) + .map_err(|e| format!("Failed to load screenshot meta: {e}"))?; + let studio_meta = match &meta.inner { + RecordingMetaInner::Studio(inner) => (**inner).clone(), + RecordingMetaInner::Instant(_) => { + return Err("Not a screenshot bundle".to_string()); + } + }; + let config = ProjectConfiguration::load(bundle).unwrap_or_default(); + + Ok(LoadedSource { + rgba: image.to_rgba8().into_raw(), + width, + height, + pretty_name: meta.pretty_name.clone(), + config, + meta, + studio_meta, + }) +} + +// --------------------------------------------------------------------------- +// The still renderer +// --------------------------------------------------------------------------- + +/// One config revision for the render loop. The revision rides along so a +/// frame can be matched back to the edit that produced it. +#[derive(Clone)] +pub struct ConfigUpdate { + pub revision: u64, + pub config: ProjectConfiguration, +} + +/// The render loop, on the tokio runtime: build the GPU constants, then +/// re-render the still whenever the config changes. Ends when the window +/// drops its `config_tx` or the pump drops `frame_rx`. +pub async fn run_still_renderer( + source: LoadedSource, + mut config_rx: tokio::sync::watch::Receiver, + frame_tx: flume::Sender<(RenderedFrame, u64)>, + setup_tx: flume::Sender>, +) { + let options = RenderOptions { + screen_size: cap_project::XY::new(source.width, source.height), + camera_size: None, + preserve_screen_alpha: true, + }; + + let constants = match RenderVideoConstants::new_with_options( + options, + source.meta, + source.studio_meta, + ) + .await + { + Ok(constants) => constants, + Err(error) => { + let _ = setup_tx.send(Err(format!("The GPU renderer failed to start: {error}"))); + return; + } + }; + let _ = setup_tx.send(Ok(())); + + let decoded = DecodedFrame::new_with_arc(Arc::new(source.rgba), source.width, source.height); + + let mut frame_renderer = FrameRenderer::new(&constants); + let mut layers = RendererLayers::new_with_options( + &constants.device, + &constants.queue, + constants.is_software_adapter, + ); + + loop { + let update = config_rx.borrow().clone(); + match render_still( + &constants, + &mut frame_renderer, + &mut layers, + &decoded, + &update.config, + ) + .await + { + Ok(frame) => { + if frame_tx.send_async((frame, update.revision)).await.is_err() { + break; + } + } + Err(error) => tracing::error!("screenshot render failed: {error}"), + } + if config_rx.changed().await.is_err() { + break; + } + } +} + +/// One `render_immediate` of the still -- the body of the Tauri loop +/// (`screenshot_editor.rs:385-430` over there): frame 0 at 30fps, empty +/// cursor events, a zoom timeline precomputed to one frame. +async fn render_still( + constants: &RenderVideoConstants, + frame_renderer: &mut FrameRenderer<'_>, + layers: &mut RendererLayers, + source: &DecodedFrame, + config: &ProjectConfiguration, +) -> Result { + let segment_frames = DecodedSegmentFrames { + screen_frame: Some(source.clone()), + camera_frame: None, + segment_time: 0.0, + recording_time: 0.0, + segment_has_camera: false, + }; + + let (base_w, base_h) = ProjectUniforms::get_base_size(&constants.options, config); + let cursor_events = cap_project::CursorEvents::default(); + let mut zoom_timeline = ZoomTransformTimeline::from_project( + config, + &cursor_events, + 0.0, + constants.options.screen_size, + ); + zoom_timeline.ensure_precomputed_until(1.0 / 30.0); + + let uniforms = ProjectUniforms::new( + constants, + config, + 0, + 30, + cap_project::XY::new(base_w, base_h), + &cursor_events, + &segment_frames, + 0.0, + &zoom_timeline, + ); + + frame_renderer + .render_immediate(segment_frames, uniforms, &cursor_events, true, layers) + .await + .map_err(|e| e.to_string()) +} + +// --------------------------------------------------------------------------- +// The window +// --------------------------------------------------------------------------- + +/// The debounced `project-config.json` write -- the same shape the video +/// editor's `PendingProjectSave` has, kept separate because that one lives on +/// `EditorWindow` and this window has no reason to share its cell. +#[derive(Default)] +pub struct PendingConfigSave { + path: Option, + config: Option, +} + +impl PendingConfigSave { + pub fn flush(&mut self) { + let (Some(path), Some(config)) = (self.path.clone(), self.config.take()) else { + return; + }; + match config.write(&path) { + Ok(()) => tracing::debug!(path = %path.display(), "screenshot config written"), + Err(error) => { + tracing::error!(path = %path.display(), "failed to persist screenshot config: {error}") + } + } + } +} + +pub struct ScreenshotEditorWindow { + theme: Theme, + bundle: PathBuf, + pretty_name: String, + error: Option, + /// The live config, published to the render loop on every edit. + project: ProjectConfiguration, + revision: u64, + config_tx: Option>, + /// The latest GPU frame, already BGRA in gpui's atlas format. + frame: Option>, + frame_size: (f32, f32), + pending_save: Rc>, + save_task: Option>, + aspect_menu: Option, + focus: gpui::FocusHandle, +} + +impl ScreenshotEditorWindow { + pub fn new(bundle: PathBuf, window: &mut Window, cx: &mut Context) -> Self { + let close_bundle = bundle.clone(); + window.on_window_should_close(cx, move |_window, cx| { + let bundle = close_bundle.clone(); + cx.defer(move |cx| crate::app_windows::screenshot_editor_closed(&bundle, cx)); + true + }); + + let pretty_name = bundle + .file_stem() + .and_then(|stem| stem.to_str()) + .unwrap_or("Screenshot") + .to_string(); + + Self { + theme: Theme::for_window(window, cx, false), + bundle, + pretty_name, + error: None, + project: ProjectConfiguration::default(), + revision: 0, + config_tx: None, + frame: None, + frame_size: (1., 1.), + pending_save: Rc::new(RefCell::new(PendingConfigSave::default())), + save_task: None, + aspect_menu: None, + focus: cx.focus_handle(), + } + } + + pub fn set_error(&mut self, message: String, cx: &mut Context) { + self.error = Some(message); + cx.notify(); + } + + pub fn set_loaded( + &mut self, + pretty_name: String, + config: ProjectConfiguration, + config_tx: tokio::sync::watch::Sender, + cx: &mut Context, + ) { + self.pretty_name = pretty_name; + self.project = config; + self.config_tx = Some(config_tx); + self.pending_save.borrow_mut().path = Some(self.bundle.clone()); + cx.notify(); + } + + pub fn frame_arrived( + &mut self, + image: Arc, + size: (u32, u32), + window: &mut Window, + cx: &mut Context, + ) { + self.frame_size = (size.0.max(1) as f32, size.1.max(1) as f32); + if let Some(previous) = self.frame.replace(image) { + let _ = window.drop_image(previous); + } + cx.notify(); + } + + pub fn pending_save(&self) -> Rc> { + self.pending_save.clone() + } + + /// Every styling edit funnels through here: mutate, publish to the + /// renderer, schedule the debounced write -- `updateScreenshotConfig`'s + /// throttled-render + debounced-save pair, minus history (deviation: no + /// undo stack yet). + fn edit_project( + &mut self, + change: impl FnOnce(&mut ProjectConfiguration) -> bool, + window: &mut Window, + cx: &mut Context, + ) { + if !change(&mut self.project) { + return; + } + self.publish(); + self.schedule_save(window, cx); + cx.notify(); + } + + fn publish(&mut self) { + let Some(config_tx) = &self.config_tx else { + return; + }; + self.revision += 1; + let _ = config_tx.send(ConfigUpdate { + revision: self.revision, + config: self.project.clone(), + }); + } + + fn schedule_save(&mut self, window: &mut Window, cx: &mut Context) { + self.pending_save.borrow_mut().config = Some(self.project.clone()); + let pending = self.pending_save.clone(); + self.save_task = Some(cx.spawn_in(window, async move |_, cx| { + cx.background_executor().timer(SAVE_DEBOUNCE).await; + pending.borrow_mut().flush(); + })); + } + + fn sync_appearance(&mut self, window: &Window, cx: &gpui::App) { + self.theme.refresh(window, cx, false); + } + + // -- Aspect ratio ------------------------------------------------------- + + const ASPECT_OPTIONS: [(Option, &'static str); 6] = [ + (None, "Auto"), + (Some(cap_project::AspectRatio::Wide), "Wide ⋅16:9"), + (Some(cap_project::AspectRatio::Vertical), "Vertical ⋅9:16"), + (Some(cap_project::AspectRatio::Square), "Square ⋅1:1"), + (Some(cap_project::AspectRatio::Classic), "Classic ⋅4:3"), + (Some(cap_project::AspectRatio::Tall), "Tall ⋅3:4"), + ]; + + fn aspect_label(&self) -> &'static str { + match &self.project.aspect_ratio { + None => "Auto", + Some(cap_project::AspectRatio::Wide) => "Wide", + Some(cap_project::AspectRatio::Vertical) => "Vertical", + Some(cap_project::AspectRatio::Square) => "Square", + Some(cap_project::AspectRatio::Classic) => "Classic", + Some(cap_project::AspectRatio::Tall) => "Tall", + } + } + + fn aspect_menu_items(&self) -> Vec { + Self::ASPECT_OPTIONS + .iter() + .map(|(value, label)| { + ui::MenuItem::new(*label, aspect_eq(value, &self.project.aspect_ratio)) + }) + .collect() + } + + fn choose_aspect(&mut self, index: usize, window: &mut Window, cx: &mut Context) { + self.aspect_menu = None; + let Some((next, _)) = Self::ASPECT_OPTIONS.get(index) else { + return; + }; + let next = next.clone(); + self.edit_project( + move |project| { + if aspect_eq(&project.aspect_ratio, &next) { + return false; + } + project.aspect_ratio = next; + true + }, + window, + cx, + ); + } + + fn on_key(&mut self, key: &str, window: &mut Window, cx: &mut Context) { + let Some(menu) = self.aspect_menu.as_mut() else { + return; + }; + match menu.on_key(key) { + ui::MenuKey::Moved => cx.notify(), + ui::MenuKey::Commit(index) => self.choose_aspect(index, window, cx), + ui::MenuKey::Dismiss => { + self.aspect_menu = None; + cx.notify(); + } + ui::MenuKey::Ignored => {} + } + } + + // -- Rendering ---------------------------------------------------------- + + /// Traffic-light spacer + name on the left, the aspect menu button on the + /// right -- the skeleton of the Tauri editor's `Header.tsx`. + fn render_header(&self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + div() + .flex() + .flex_row() + .items_center() + .h(px(52.)) + .px(px(14.)) + .flex_shrink_0() + .child(div().w(px(72.)).flex_shrink_0()) + .child( + div() + .flex_1() + .min_w_0() + .overflow_hidden() + .text_size(px(13.)) + .font_weight(FontWeight::MEDIUM) + .text_color(theme.gray_12) + .child(self.pretty_name.clone()), + ) + .child( + div() + .id("aspect-button") + .flex() + .flex_row() + .items_center() + .gap(px(6.)) + .px(px(10.)) + .py(px(6.)) + .rounded(px(8.)) + .border_1() + .border_color(theme.gray_4) + .bg(theme.gray_2) + .hover(|style| style.bg(theme.gray_3)) + .text_size(px(12.)) + .text_color(theme.gray_12) + .child(format!("Aspect: {}", self.aspect_label())) + .on_click(cx.listener(|this, event: &gpui::ClickEvent, window, cx| { + cx.stop_propagation(); + if this.aspect_menu.take().is_none() { + let items = this.aspect_menu_items(); + this.aspect_menu = Some(ui::MenuState::new(event.position(), &items)); + window.focus(&this.focus, cx); + } + cx.notify(); + })), + ) + } + + fn render_preview(&self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let frame = self.frame.clone(); + let frame_size = self.frame_size; + + div() + .flex_1() + .min_h_0() + .m(px(14.)) + .mt(px(0.)) + .rounded(px(12.)) + .border_1() + .border_color(theme.gray_4) + .overflow_hidden() + .bg(if theme.is_dark() { + theme.gray_2 + } else { + theme.gray_3 + }) + .child(match (&self.error, frame) { + (Some(message), _) => div() + .size_full() + .flex() + .items_center() + .justify_center() + .text_size(px(13.)) + .text_color(theme.gray_11) + .child(message.clone()) + .into_any_element(), + (None, Some(frame)) => gpui::canvas( + |bounds, _window, _cx| bounds, + move |_, bounds, window, _cx| { + let container_width: f32 = bounds.size.width.into(); + let container_height: f32 = bounds.size.height.into(); + let (width, height) = crate::editor_window::letterbox( + (container_width, container_height), + frame_size, + ); + let fitted = gpui::Bounds { + origin: gpui::point( + bounds.origin.x + px((container_width - width) / 2.), + bounds.origin.y + px((container_height - height) / 2.), + ), + size: gpui::size(px(width), px(height)), + }; + let _ = + window.paint_image(fitted, gpui::Corners::default(), frame, 0, false); + }, + ) + .size_full() + .into_any_element(), + (None, None) => div() + .size_full() + .flex() + .items_center() + .justify_center() + .text_size(px(13.)) + .text_color(theme.gray_10) + .child("Loading screenshot...") + .into_any_element(), + }) + .when(self.aspect_menu.is_some(), |this| { + let state = self.aspect_menu.as_ref().unwrap(); + let items = self.aspect_menu_items(); + this.child( + ui::Menu::plain(&self.theme, "aspect-menu", items, state) + .min_width(px(160.)) + .on_select(cx.listener(|this, index: &usize, window, cx| { + this.choose_aspect(*index, window, cx); + })) + .on_dismiss(cx.listener(|this, _, _window, cx| { + this.aspect_menu = None; + cx.notify(); + })), + ) + }) + } +} + +impl Render for ScreenshotEditorWindow { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + self.sync_appearance(window, cx); + let theme = self.theme; + + div() + .id("screenshot-editor-root") + .track_focus(&self.focus) + .key_context("ScreenshotEditor") + .on_key_down(cx.listener(|this, event: &gpui::KeyDownEvent, window, cx| { + this.on_key(event.keystroke.key.as_str(), window, cx); + })) + .size_full() + .flex() + .flex_col() + .font_family("Geist") + .font_weight(FontWeight::MEDIUM) + // The video editor's root: `bg-gray-2 dark:bg-gray-1`. + .bg(if theme.is_dark() { + theme.gray_1 + } else { + theme.gray_2 + }) + .text_color(theme.gray_12) + .child(self.render_header(cx)) + .child(self.render_preview(cx)) + } +} + +// --------------------------------------------------------------------------- +// Loading +// --------------------------------------------------------------------------- + +/// Read the bundle, start the still renderer on tokio, pump frames back -- +/// `load_editor_project`'s shape, without the playback half. +pub fn load_screenshot_project( + bundle: PathBuf, + handle: WindowHandle, + cx: &mut App, +) { + cx.spawn(async move |cx| { + let load_bundle = bundle.clone(); + let source = cx + .background_executor() + .spawn(async move { load_source(&load_bundle) }) + .await; + let source = match source { + Ok(source) => source, + Err(message) => { + handle + .update(cx, |view, _window, cx| view.set_error(message, cx)) + .ok(); + return; + } + }; + + let (config_tx, config_rx) = tokio::sync::watch::channel(ConfigUpdate { + revision: 0, + config: source.config.clone(), + }); + // Bounded and latest-wins like the video pump; stills only re-render + // on edits, so it never actually fills. + let (frame_tx, frame_rx) = flume::bounded(2); + let (setup_tx, setup_rx) = flume::bounded(1); + + if handle + .update(cx, |view, _window, cx| { + view.set_loaded( + source.pretty_name.clone(), + source.config.clone(), + config_tx, + cx, + ) + }) + .is_err() + { + return; + } + + cx.update(|cx| { + gpui_tokio::Tokio::spawn( + cx, + run_still_renderer(source, config_rx, frame_tx, setup_tx), + ) + .detach(); + }); + + if let Ok(Err(message)) = setup_rx.recv_async().await { + handle + .update(cx, |view, _window, cx| view.set_error(message, cx)) + .ok(); + return; + } + + while let Ok((frame, revision)) = frame_rx.recv_async().await { + let size = (frame.width, frame.height); + let image = cx + .background_executor() + .spawn(async move { crate::editor_window::frame_image(&frame) }) + .await; + let Some(image) = image else { + tracing::warn!("a rendered screenshot frame could not be converted for display"); + continue; + }; + tracing::debug!( + revision, + width = size.0, + height = size.1, + "screenshot frame" + ); + if handle + .update(cx, |view, window, cx| { + view.frame_arrived(image, size, window, cx) + }) + .is_err() + { + return; + } + } + }) + .detach(); +} + +/// `AspectRatio` derives no `PartialEq`; same discriminant comparison the +/// video editor's `aspect_ratio_eq` uses. +fn aspect_eq(a: &Option, b: &Option) -> bool { + match (a, b) { + (None, None) => true, + (Some(a), Some(b)) => std::mem::discriminant(a) == std::mem::discriminant(b), + _ => false, + } +} diff --git a/apps/desktop-gpui/src/settings_window.rs b/apps/desktop-gpui/src/settings_window.rs index 9ee0b2aa6c..aaf1394edf 100644 --- a/apps/desktop-gpui/src/settings_window.rs +++ b/apps/desktop-gpui/src/settings_window.rs @@ -2018,7 +2018,10 @@ impl SettingsWindow { .child(actions) .on_click({ let png = item.path.clone(); - move |_, _, _| library::open_path(&png) + move |_, _, cx| { + let png = png.clone(); + cx.defer(move |cx| crate::app_windows::open_screenshot_editor(png, cx)); + } }) } diff --git a/apps/desktop-gpui/src/tray.rs b/apps/desktop-gpui/src/tray.rs index 9e64833f3d..fec35c0bd1 100644 --- a/apps/desktop-gpui/src/tray.rs +++ b/apps/desktop-gpui/src/tray.rs @@ -424,10 +424,8 @@ fn open_previous_item(path: PathBuf, cx: &mut App) { .unwrap_or(false); if is_screenshot { - // `ShowCapWindow::ScreenshotEditor` has no gpui counterpart yet - // (deviation): the bundle is revealed in Finder instead, which is what - // a Recents screenshot card does in this app. - library::open_recording_folder(&path, library::RecordingMode::Studio); + app_windows::open_screenshot_editor(path, cx); + cx.activate(true); return; } From 93fdd088399c82a4dfedf16fbd72180d8cf50af0 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:24:35 +0100 Subject: [PATCH 137/146] feat(desktop-gpui): export the edited screenshot to a PNG or the clipboard --- apps/desktop-gpui/src/app_windows.rs | 37 ++ apps/desktop-gpui/src/screenshot_editor.rs | 411 +++++++++++++++++++-- 2 files changed, 423 insertions(+), 25 deletions(-) diff --git a/apps/desktop-gpui/src/app_windows.rs b/apps/desktop-gpui/src/app_windows.rs index 76e3d27607..d004c3a598 100644 --- a/apps/desktop-gpui/src/app_windows.rs +++ b/apps/desktop-gpui/src/app_windows.rs @@ -2514,6 +2514,43 @@ pub fn open_screenshot_editor(path: PathBuf, cx: &mut App) { screenshot_editor::load_screenshot_project(key, handle, cx); } +/// The screenshot editor's Delete finished: drop the window (its pending +/// write is for a bundle that no longer exists), refresh every surface that +/// lists screenshots, and run the ordinary closed bookkeeping. +pub fn close_screenshot_editor_after_delete(bundle: &Path, cx: &mut App) { + let key = editor_key(bundle); + let handle = cx + .global::() + .screenshot_editors + .iter() + .find(|(path, _)| path == &key) + .map(|(_, handle)| *handle); + if let Some(handle) = handle { + if let Ok(pending) = handle.update(cx, |view, _window, _cx| view.pending_save()) { + pending.borrow_mut().discard(); + } + handle + .update(cx, |_, window, _| window.remove_window()) + .ok(); + } + screenshot_editor_closed(&key, cx); + refresh_screenshot_surfaces(cx); +} + +/// Every surface that lists screenshots: the tray's Previous, the settings +/// Screenshots page, and the main window's Recents. +pub fn refresh_screenshot_surfaces(cx: &mut App) { + crate::tray::refresh_previous(cx); + if let Some(settings) = cx.global::().settings { + settings + .update(cx, |view, window, cx| view.refresh_screenshots(window, cx)) + .ok(); + } + let main = cx.global::().main; + main.update(cx, |view, window, cx| view.refresh_recents(window, cx)) + .ok(); +} + /// A screenshot editor window is going away: flush its pending config write /// and bring the main window back once the last editor of either kind closes /// -- the same `Destroyed` arm `editor_closed` mirrors. diff --git a/apps/desktop-gpui/src/screenshot_editor.rs b/apps/desktop-gpui/src/screenshot_editor.rs index 184f6288b9..942912a0b7 100644 --- a/apps/desktop-gpui/src/screenshot_editor.rs +++ b/apps/desktop-gpui/src/screenshot_editor.rs @@ -131,12 +131,23 @@ pub struct ConfigUpdate { pub config: ProjectConfiguration, } +/// A one-off render on the loop's GPU device -- `render_screenshot_png` in +/// the Tauri app, which rebuilds the whole device per export because it is a +/// stateless command; here the loop already owns one. +pub enum ExportRequest { + Png { + config: ProjectConfiguration, + reply: tokio::sync::oneshot::Sender, String>>, + }, +} + /// The render loop, on the tokio runtime: build the GPU constants, then -/// re-render the still whenever the config changes. Ends when the window -/// drops its `config_tx` or the pump drops `frame_rx`. +/// re-render the still whenever the config changes and serve export requests +/// on the same device. Ends when the window goes away (both senders drop). pub async fn run_still_renderer( source: LoadedSource, mut config_rx: tokio::sync::watch::Receiver, + mut export_rx: tokio::sync::mpsc::Receiver, frame_tx: flume::Sender<(RenderedFrame, u64)>, setup_tx: flume::Sender>, ) { @@ -170,39 +181,67 @@ pub async fn run_still_renderer( constants.is_software_adapter, ); + let mut dirty = true; loop { - let update = config_rx.borrow().clone(); - match render_still( - &constants, - &mut frame_renderer, - &mut layers, - &decoded, - &update.config, - ) - .await - { - Ok(frame) => { - if frame_tx.send_async((frame, update.revision)).await.is_err() { - break; + if dirty { + dirty = false; + let update = config_rx.borrow_and_update().clone(); + match render_still( + &constants, + &mut frame_renderer, + &mut layers, + &decoded, + &update.config, + None, + ) + .await + { + Ok(frame) => { + if frame_tx.send_async((frame, update.revision)).await.is_err() { + break; + } } + Err(error) => tracing::error!("screenshot render failed: {error}"), } - Err(error) => tracing::error!("screenshot render failed: {error}"), } - if config_rx.changed().await.is_err() { - break; + + tokio::select! { + changed = config_rx.changed() => { + if changed.is_err() { + break; + } + dirty = true; + } + request = export_rx.recv() => { + let Some(ExportRequest::Png { config, reply }) = request else { + break; + }; + let result = render_export_png( + &constants, + &mut frame_renderer, + &mut layers, + &decoded, + &config, + ) + .await; + let _ = reply.send(result); + } } } } /// One `render_immediate` of the still -- the body of the Tauri loop /// (`screenshot_editor.rs:385-430` over there): frame 0 at 30fps, empty -/// cursor events, a zoom timeline precomputed to one frame. +/// cursor events, a zoom timeline precomputed to one frame. The preview +/// renders at the config's base size; an export passes the upscaled +/// `resolution_base`. async fn render_still( constants: &RenderVideoConstants, frame_renderer: &mut FrameRenderer<'_>, layers: &mut RendererLayers, source: &DecodedFrame, config: &ProjectConfiguration, + resolution_base: Option>, ) -> Result { let segment_frames = DecodedSegmentFrames { screen_frame: Some(source.clone()), @@ -212,7 +251,10 @@ async fn render_still( segment_has_camera: false, }; - let (base_w, base_h) = ProjectUniforms::get_base_size(&constants.options, config); + let resolution_base = resolution_base.unwrap_or_else(|| { + let (base_w, base_h) = ProjectUniforms::get_base_size(&constants.options, config); + cap_project::XY::new(base_w, base_h) + }); let cursor_events = cap_project::CursorEvents::default(); let mut zoom_timeline = ZoomTransformTimeline::from_project( config, @@ -227,7 +269,7 @@ async fn render_still( config, 0, 30, - cap_project::XY::new(base_w, base_h), + resolution_base, &cursor_events, &segment_frames, 0.0, @@ -240,6 +282,86 @@ async fn render_still( .map_err(|e| e.to_string()) } +/// The export render -- `render_screenshot_png`'s upscale + unpad + encode +/// (`screenshot_editor.rs:1618-1742` over there): scale the output so a crop +/// is not downsampled, align the dimensions the way the exporter does, strip +/// the wgpu row padding and encode an RGBA PNG. +async fn render_export_png( + constants: &RenderVideoConstants, + frame_renderer: &mut FrameRenderer<'_>, + layers: &mut RendererLayers, + source: &DecodedFrame, + config: &ProjectConfiguration, +) -> Result, String> { + use image::ImageEncoder as _; + + let (base_width, base_height) = ProjectUniforms::get_base_size(&constants.options, config); + let display_size = ProjectUniforms::display_size( + &constants.options, + config, + cap_project::XY::new(base_width, base_height), + ) + .coord; + let crop = ProjectUniforms::get_crop(&constants.options, config); + let export_scale = f64::max( + f64::max( + crop.size.x as f64 / f64::max(display_size.x, 1.0), + crop.size.y as f64 / f64::max(display_size.y, 1.0), + ), + 1.0, + ); + + let resolution_base = cap_project::XY::new( + (((base_width as f64 * export_scale).ceil() as u32) + 3) & !3, + (((base_height as f64 * export_scale).ceil() as u32) + 1) & !1, + ); + if resolution_base.x > MAX_DIMENSION || resolution_base.y > MAX_DIMENSION { + return Err(format!( + "Export dimensions exceed maximum: {}x{}", + resolution_base.x, resolution_base.y + )); + } + + let frame = render_still( + constants, + frame_renderer, + layers, + source, + config, + Some(resolution_base), + ) + .await?; + + let row_bytes = frame.width as usize * 4; + let padded = frame.padded_bytes_per_row as usize; + if padded < row_bytes || frame.data.len() < padded * frame.height as usize { + return Err(format!( + "Invalid export buffer: {} bytes, stride {} for {}x{}", + frame.data.len(), + padded, + frame.width, + frame.height + )); + } + let rgba: Vec = frame + .data + .chunks(padded) + .take(frame.height as usize) + .flat_map(|row| row[..row_bytes].iter().copied()) + .collect(); + + let mut png = std::io::Cursor::new(Vec::new()); + image::codecs::png::PngEncoder::new(&mut png) + .write_image( + &rgba, + frame.width, + frame.height, + image::ExtendedColorType::Rgba8, + ) + .map_err(|e| format!("Failed to encode screenshot export: {e}"))?; + Ok(png.into_inner()) +} + // --------------------------------------------------------------------------- // The window // --------------------------------------------------------------------------- @@ -254,6 +376,12 @@ pub struct PendingConfigSave { } impl PendingConfigSave { + /// Drop a scheduled write without performing it -- the delete path, where + /// the bundle the write would land in is already gone. + pub fn discard(&mut self) { + self.config = None; + } + pub fn flush(&mut self) { let (Some(path), Some(config)) = (self.path.clone(), self.config.take()) else { return; @@ -267,6 +395,16 @@ impl PendingConfigSave { } } +#[derive(Clone, PartialEq, Eq)] +enum ExportDestination { + Clipboard, + File, + /// `CAP_GPUI_AUTO_SCREENSHOT_EXPORT=`: the harness destination -- + /// the same GPU export, written straight to the path (a save panel + /// cannot be driven without Accessibility). + Harness(PathBuf), +} + pub struct ScreenshotEditorWindow { theme: Theme, bundle: PathBuf, @@ -276,6 +414,10 @@ pub struct ScreenshotEditorWindow { project: ProjectConfiguration, revision: u64, config_tx: Option>, + export_tx: Option>, + /// One export at a time: the copy/save buttons grey out while the GPU + /// renders and the panel is up. + exporting: bool, /// The latest GPU frame, already BGRA in gpui's atlas format. frame: Option>, frame_size: (f32, f32), @@ -308,6 +450,8 @@ impl ScreenshotEditorWindow { project: ProjectConfiguration::default(), revision: 0, config_tx: None, + export_tx: None, + exporting: false, frame: None, frame_size: (1., 1.), pending_save: Rc::new(RefCell::new(PendingConfigSave::default())), @@ -327,12 +471,20 @@ impl ScreenshotEditorWindow { pretty_name: String, config: ProjectConfiguration, config_tx: tokio::sync::watch::Sender, + export_tx: tokio::sync::mpsc::Sender, + window: &mut Window, cx: &mut Context, ) { self.pretty_name = pretty_name; self.project = config; self.config_tx = Some(config_tx); + self.export_tx = Some(export_tx); self.pending_save.borrow_mut().path = Some(self.bundle.clone()); + if let Ok(dest) = std::env::var("CAP_GPUI_AUTO_SCREENSHOT_EXPORT") + && !dest.trim().is_empty() + { + self.export_png(ExportDestination::Harness(PathBuf::from(dest)), window, cx); + } cx.notify(); } @@ -396,6 +548,142 @@ impl ScreenshotEditorWindow { self.theme.refresh(window, cx, false); } + // -- Export actions ------------------------------------------------------- + + /// Render at export resolution and hand the PNG to its destination -- + /// `useScreenshotExport`'s Copy and Save destinations (the canvas + /// composite is annotation work, which does not exist here yet). + fn export_png( + &mut self, + destination: ExportDestination, + window: &mut Window, + cx: &mut Context, + ) { + if self.exporting { + return; + } + let Some(export_tx) = self.export_tx.clone() else { + return; + }; + self.exporting = true; + cx.notify(); + + let config = self.project.clone(); + let name = self.pretty_name.clone(); + cx.spawn_in(window, async move |this, cx| { + let (reply_tx, reply_rx) = tokio::sync::oneshot::channel(); + let request = ExportRequest::Png { + config, + reply: reply_tx, + }; + let result = if export_tx.send(request).await.is_ok() { + match reply_rx.await { + Ok(result) => result, + Err(_) => Err("The renderer stopped before the export finished".into()), + } + } else { + Err("The screenshot renderer is not running".into()) + }; + + match result { + Ok(bytes) => match destination { + ExportDestination::Clipboard => { + // The clipboard seam this app has is "NSImage from a + // path" (`platform::copy_image_to_clipboard`); a temp + // file bridges the rendered bytes to it. + let path = std::env::temp_dir() + .join(format!("cap-screenshot-copy-{}.png", std::process::id())); + let written = + cx.background_executor() + .spawn({ + let path = path.clone(); + async move { + std::fs::write(&path, &bytes).map_err(|e| e.to_string()) + } + }) + .await; + match written { + Ok(()) => { + let copied = this.update_in(cx, |_, _, _| { + crate::platform::copy_image_to_clipboard(&path) + }); + if let Ok(Err(error)) = copied { + tracing::error!("copying the screenshot failed: {error}"); + } + cx.background_executor() + .spawn(async move { + let _ = std::fs::remove_file(&path); + }) + .detach(); + } + Err(error) => { + tracing::error!("writing the clipboard temp file failed: {error}") + } + } + } + ExportDestination::File => { + let dest = + crate::platform::save_file_panel(&format!("{name}.png"), &["png"]); + if let Some(dest) = dest { + let written = cx + .background_executor() + .spawn(async move { std::fs::write(&dest, &bytes) }) + .await; + if let Err(error) = written { + tracing::error!("saving the screenshot failed: {error}"); + } + } + } + ExportDestination::Harness(dest) => match std::fs::write(&dest, &bytes) { + Ok(()) => { + tracing::info!(path = %dest.display(), "harness export written") + } + Err(error) => tracing::error!("harness export failed: {error}"), + }, + }, + Err(error) => tracing::error!("screenshot export failed: {error}"), + } + + this.update_in(cx, |this, _window, cx| { + this.exporting = false; + cx.notify(); + }) + .ok(); + }) + .detach(); + } + + /// The header's Delete: confirm, remove the bundle, close this window -- + /// `Header.tsx`'s delete (`remove(path)` then close). + fn delete_screenshot(&mut self, window: &mut Window, cx: &mut Context) { + let bundle = self.bundle.clone(); + cx.spawn_in(window, async move |_this, cx| { + if !crate::platform::confirm_dialog( + "Cap", + "Are you sure you want to delete this screenshot?", + "Yes", + "No", + false, + ) { + return; + } + let deleted = cx + .background_executor() + .spawn({ + let bundle = bundle.clone(); + async move { crate::library::delete_screenshot(&bundle) } + }) + .await; + if let Err(error) = deleted { + tracing::error!(path = %bundle.display(), "deleting the screenshot failed: {error}"); + return; + } + cx.update(|_, cx| crate::app_windows::close_screenshot_editor_after_delete(&bundle, cx)) + .ok(); + }) + .detach(); + } + // -- Aspect ratio ------------------------------------------------------- const ASPECT_OPTIONS: [(Option, &'static str); 6] = [ @@ -463,14 +751,54 @@ impl ScreenshotEditorWindow { // -- Rendering ---------------------------------------------------------- - /// Traffic-light spacer + name on the left, the aspect menu button on the - /// right -- the skeleton of the Tauri editor's `Header.tsx`. + /// A small bordered icon button, the aspect button's palette. + fn header_action( + &self, + id: &'static str, + icon: &'static str, + enabled: bool, + handler: impl Fn(&mut Self, &mut Window, &mut Context) + 'static, + cx: &mut Context, + ) -> impl IntoElement { + let theme = self.theme; + div() + .id(id) + .flex() + .items_center() + .justify_center() + .size(px(30.)) + .rounded(px(8.)) + .border_1() + .border_color(theme.gray_4) + .bg(theme.gray_2) + .when(enabled, |this| { + this.hover(|style| style.bg(theme.gray_3)) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + handler(this, window, cx); + })) + }) + .when(!enabled, |this| this.opacity(0.5)) + .child( + gpui::svg() + .path(icon) + .size(px(14.)) + .text_color(theme.gray_11), + ) + } + + /// Traffic-light spacer + name on the left; copy, save, reveal, delete + /// and the aspect menu on the right -- the skeleton of the Tauri editor's + /// `Header.tsx`. fn render_header(&self, cx: &mut Context) -> impl IntoElement { let theme = self.theme; + let ready = self.export_tx.is_some() && self.error.is_none(); + let exportable = ready && !self.exporting; div() .flex() .flex_row() .items_center() + .gap(px(8.)) .h(px(52.)) .px(px(14.)) .flex_shrink_0() @@ -485,6 +813,36 @@ impl ScreenshotEditorWindow { .text_color(theme.gray_12) .child(self.pretty_name.clone()), ) + .child(self.header_action( + "screenshot-copy", + "icons/copy.svg", + exportable, + |this, window, cx| this.export_png(ExportDestination::Clipboard, window, cx), + cx, + )) + .child(self.header_action( + "screenshot-save", + "icons/download.svg", + exportable, + |this, window, cx| this.export_png(ExportDestination::File, window, cx), + cx, + )) + .child(self.header_action( + "screenshot-reveal", + "icons/folder-open.svg", + true, + |this, _window, _cx| { + crate::library::reveal_in_folder(&this.bundle.join("original.png")); + }, + cx, + )) + .child(self.header_action( + "screenshot-delete", + "icons/trash.svg", + true, + |this, window, cx| this.delete_screenshot(window, cx), + cx, + )) .child( div() .id("aspect-button") @@ -653,17 +1011,20 @@ pub fn load_screenshot_project( revision: 0, config: source.config.clone(), }); + let (export_tx, export_rx) = tokio::sync::mpsc::channel(1); // Bounded and latest-wins like the video pump; stills only re-render // on edits, so it never actually fills. let (frame_tx, frame_rx) = flume::bounded(2); let (setup_tx, setup_rx) = flume::bounded(1); if handle - .update(cx, |view, _window, cx| { + .update(cx, |view, window, cx| { view.set_loaded( source.pretty_name.clone(), source.config.clone(), config_tx, + export_tx, + window, cx, ) }) @@ -675,7 +1036,7 @@ pub fn load_screenshot_project( cx.update(|cx| { gpui_tokio::Tokio::spawn( cx, - run_still_renderer(source, config_rx, frame_tx, setup_tx), + run_still_renderer(source, config_rx, export_rx, frame_tx, setup_tx), ) .detach(); }); From 9e13af53936184718abd2e4d7e980609dd5b9021 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:40:42 +0100 Subject: [PATCH 138/146] feat(desktop-gpui): screenshot editor styling panel --- apps/desktop-gpui/README.md | 6 +- apps/desktop-gpui/src/editor_sidebar.rs | 2 +- apps/desktop-gpui/src/screenshot_editor.rs | 709 ++++++++++++++++++++- 3 files changed, 708 insertions(+), 9 deletions(-) diff --git a/apps/desktop-gpui/README.md b/apps/desktop-gpui/README.md index 21567fc7cf..2d5a413e38 100644 --- a/apps/desktop-gpui/README.md +++ b/apps/desktop-gpui/README.md @@ -497,7 +497,7 @@ Things that are deliberately different, and why. | **No target thumbnails** | Display and window cards render the icon fallback the real card falls back to before its thumbnail arrives. Live previews need the capture pipeline. | | **Search has no highlighted match** | The field itself is real (`ui::TextInput::search`, see [Text input](#text-input)); what is not reproduced is the Tauri panels' highlighting of the matched run inside each row. The filter test is the same case-insensitive `includes`. Escape still clears before it closes — that pair is the window's, not the field's. Opening the camera or microphone panel now focuses the field, which the display and window panels already did (`open_panel`); before the field was real there was nothing to focus into. | | **Plan badge is always "Personal"** | Which of Pro/Commercial applies comes from the license query. There is no auth or license plumbing yet, and claiming a plan would be worse than showing none. | -| **Only studio Recents cards open** | `openRecentMedia` routes a studio card to the Editor window (recovering first if needed), an instant card to its share link, and a screenshot to the Screenshot Editor. The studio arm is real now; the other two still reveal the `.cap` bundle in Finder — the action the Recordings settings page calls "Open recording bundle". The recovery step is not reproduced either, so an `InProgress`/`NeedsRemux` bundle reaches the editor's error state instead of being remuxed first. No hover affordance was invented: the real card has none either, it is click-only with no context menu. | +| **Instant Recents cards open the share link or Finder** | `openRecentMedia` routes a studio card to the Editor window (recovering first if needed), an instant card to its share link, and a screenshot to the Screenshot Editor. The studio and screenshot arms are real now; an instant card opens its share URL when one exists and otherwise reveals the bundle in Finder. The recovery step is not reproduced either, so an `InProgress`/`NeedsRemux` bundle reaches the editor's error state instead of being remuxed first. No hover affordance was invented: the real card has none either, it is click-only with no context menu. | | **No carousel mask, snap or hover lift** | `RecentCarousel`'s edge fade is a scroll-position-driven `mask-image` and the cards are `snap-x snap-proximity`; the card's `hover:-translate-y-0.5` and the thumbnail's `group-hover:scale-[1.025]` are transforms. This gpui rev has neither a mask hook (same gap as the teleprompter vignette) nor a transform. The scroller, the gap, the `pr-8` gutter, the border/shadow hover and the trailing skeletons are all real — the skeletons just do not pulse (`animate-pulse` has no keyframe hook either). | | **Thumbnails are downsampled to the card** | `create_screenshot(.., None)` writes `display.jpg` at the display's *native* resolution — 3024×1964 here. The browser scales that per ``; a gpui sprite atlas would hold nine of them whole, so each is resized to 392×224 (the card at 2×) during the same background decode. `ObjectFit::Cover` then crops as `object-cover` does. | | **Blur bridges on studio only** | `project_config_from_recording` is the studio arm of `handle_recording_finish`; the instant arm never writes a project config, so neither does this. | @@ -553,8 +553,8 @@ Sizes are the Tauri app's, from `apps/desktop/src-tauri/src/windows.rs`. | Onboarding | dynamic, 860–1080 wide | Not started | | Teleprompter | 560×320 | **Done, with deviations** — resizable to the 420×220 floor, level 101 on all Spaces, the `"teleprompter"` material at radius 22, traffic lights at (14, 11) — the Tauri `(14, 14)` is a `position_window_controls` inset, not a top-left, auto-scroll from the ported `teleprompter-utils` maths, the full footer and settings popover, native window opacity, the `teleprompter` store section, capture exclusion + content protection. The script editor is a real multi-line field and Mirror is inert — see below | | Editor | 1275×800 | **E1–E5 done — window, shell, playback, timeline, editing, config sidebar.** The real 1275×800 window with the traffic lights at (20, 20) — the Tauri `(20, 32)` is a `position_window_controls` inset, not a top-left, the header, the letterboxed player, the 260px timeline strip and the 416px config sidebar with its six-tab rail; a real project through `EditorInstance`; play/pause (button + Space), a 60fps live playhead and clock, real audio, click/drag seeking, end-of-media stop; the timeline at 1:1 — all nine track types from the project's own config, waveforms, the ruler's resolution ladder, minimap, edge fade, hover ghost, zoom (keys, buttons, slider, wheel, pinch) and pan; and **timeline editing**: selection (single, ⌘-multi, ⇧-range, ⌘A), trim, move, split (S + C, with snapping), zoom-segment create/resize/move, delete, undo/redo and the debounced write back to `project-config.json`. and **the config sidebar in full**: the six-tab rail, the scroll body, selection routing, all six tabs at 1:1, the shared colour-correction section on both its targets, and the eight per-segment panels with multi-select where the source has it. Crop and export are the remaining units | -| Screenshot editor | 1240×800 (min 800×600) | Not started | -| Tray (status item) | menu bar | **Done, with deviations** — a native `NSStatusItem` (no gpui API exists for one; built on AppKit with the color-panel channel discipline), menu byte-identical to `build_tray_menu`: Open Main Window, Record Display/Window/Area (screenshot-mode relabels), Select Mode ▸ with the ✓, Previous ▸ with 32px cover-cropped thumbnails and the 🎬⚡📷 prefixes, View all recordings / screenshots, Settings, version row, Quit Cap. The icon follows the mode and becomes the stop icon while recording, when clicking the item stops the capture (the menu is detached so the button's action can fire). Select Mode writes the same `recording_settings.mode` store key the Tauri app reads. Deviations: Take a Screenshot / Import Media… / Upload Logs render disabled (no capture, import or upload infrastructure yet), a Previous screenshot reveals in Finder (no screenshot editor), and the onboarding-minimal menu variant is not reproduced (no onboarding) | +| Screenshot editor | 1240×800 (min 800×600) | **Done, with deviations** — the real window (native traffic lights, resizable to the 800×600 floor), one editor per bundle keyed like the video editor's registry, opened from capture, Recents, the settings Screenshots page, the tray's Previous and image import. The still renders through the same GPU path the Tauri instance uses (`FrameRenderer::render_immediate`, `preserve_screen_alpha`, no camera) on a config watch, painted with `paint_image` instead of shipped over the frame websocket. Styling panel: background source tabs (color swatches, gradient presets, wallpaper grid with theme tabs and cached thumbnails, image picker), blur/padding/rounding/shadow sliders at the popovers' ranges, squircle/rounded corner style, border toggle + width/opacity, aspect-ratio menu; edits publish to the renderer live and ride a 250ms debounced `project-config.json` write. Header: copy (GPU export → clipboard), save PNG (the `render_screenshot_png` upscale + unpad + encode, on the live renderer's device), reveal, delete behind a native confirm. Deviations: no annotations/OCR/layers, no share/upload, no crop dialog, no undo history, no border color picker, no advanced-shadow or gradient angle/noise controls, no `PendingScreenshots` in-memory hand-off (the PNG is on disk before the editor opens) | +| Tray (status item) | menu bar | **Done, with deviations** — a native `NSStatusItem` (no gpui API exists for one; built on AppKit with the color-panel channel discipline), menu byte-identical to `build_tray_menu`: Open Main Window, Record Display/Window/Area (screenshot-mode relabels), Select Mode ▸ with the ✓, Previous ▸ with 32px cover-cropped thumbnails and the 🎬⚡📷 prefixes, View all recordings / screenshots, Settings, version row, Quit Cap. The icon follows the mode and becomes the stop icon while recording, when clicking the item stops the capture (the menu is detached so the button's action can fire). Select Mode writes the same `recording_settings.mode` store key the Tauri app reads. Take a Screenshot captures the display under the cursor and Import Media… routes video/image imports; a Previous screenshot opens the screenshot editor. Deviations: Upload Logs renders disabled (no log-upload infrastructure yet) | | App menu + shortcuts | menu bar | **Done** — the `build_macos_app_menu` structure (Cap / File / Edit / View / Window / Help) via `cx.set_menus`, with ⌘W closing the key window through each window's own close path (main hides, Tauri's `CloseRequested` arm transcribed: camera bubble closed, overlays closed, feeds released when idle), ⌘M / Zoom / ⌃⌘F on the key window, ⌘H / ⌥⌘H, ⌘Q, and Edit items dispatching the text-input actions. Dock-icon policy (Regular ↔ Accessory per `hideDockIcon` + visible windows) synced on every window show/hide seam | ## Recording diff --git a/apps/desktop-gpui/src/editor_sidebar.rs b/apps/desktop-gpui/src/editor_sidebar.rs index 3b1ee09a53..b7c431c1fc 100644 --- a/apps/desktop-gpui/src/editor_sidebar.rs +++ b/apps/desktop-gpui/src/editor_sidebar.rs @@ -1736,7 +1736,7 @@ const WALLPAPER_TILE_MAX: u32 = 128; /// immutable in practice, so after the first-ever run every theme switch is /// eighteen ~3KB JPEG reads instead of eighteen 4000x2500 decodes; the mtime /// in the key still invalidates if an app update ever replaces them. -fn decode_wallpaper_thumbnail(id: &str) -> Option> { +pub(crate) fn decode_wallpaper_thumbnail(id: &str) -> Option> { let path = wallpaper_path(id)?; // Ids are catalogue paths ("macOS/tahoe-dusk-min"); flattened so the key // stays a file name rather than a subdirectory. diff --git a/apps/desktop-gpui/src/screenshot_editor.rs b/apps/desktop-gpui/src/screenshot_editor.rs index 942912a0b7..9c07d77a61 100644 --- a/apps/desktop-gpui/src/screenshot_editor.rs +++ b/apps/desktop-gpui/src/screenshot_editor.rs @@ -14,22 +14,31 @@ //! grow on this seam. use std::cell::RefCell; +use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::sync::Arc; use std::time::Duration; -use cap_project::{ProjectConfiguration, RecordingMeta, RecordingMetaInner, StudioRecordingMeta}; +use cap_project::{ + BackgroundSource, BorderConfiguration, CornerStyle, ProjectConfiguration, RecordingMeta, + RecordingMetaInner, StudioRecordingMeta, +}; use cap_rendering::{ DecodedFrame, DecodedSegmentFrames, FrameRenderer, ProjectUniforms, RenderOptions, RenderVideoConstants, RenderedFrame, RendererLayers, ZoomTransformTimeline, }; use gpui::{ - App, Context, FontWeight, InteractiveElement as _, IntoElement, ParentElement as _, Render, - RenderImage, StatefulInteractiveElement as _, Styled, Window, WindowHandle, div, - prelude::FluentBuilder as _, px, + App, Context, FontWeight, Hsla, InteractiveElement as _, IntoElement, MouseDownEvent, + MouseMoveEvent, ParentElement as _, Point, Render, RenderImage, + StatefulInteractiveElement as _, Styled, StyledImage as _, Window, WindowHandle, div, img, + linear_color_stop, linear_gradient, prelude::FluentBuilder as _, px, }; +use crate::editor_sidebar::{ + self, BACKGROUND_COLORS, BACKGROUND_IMAGE_EXTENSIONS, BACKGROUND_THEMES, GRADIENT_PRESETS, + color_to_hsla, hex_to_rgb, +}; use crate::theme::Theme; use crate::ui; @@ -395,6 +404,134 @@ impl PendingConfigSave { } } +/// The background popover's source tabs (`BackgroundSettingsPopover.tsx`). +#[derive(Clone, Copy, PartialEq, Eq)] +enum BgTab { + Color, + Gradient, + Wallpaper, + Image, +} + +impl BgTab { + const ALL: [(Self, &'static str); 4] = [ + (Self::Color, "Color"), + (Self::Gradient, "Gradient"), + (Self::Wallpaper, "Wallpaper"), + (Self::Image, "Image"), + ]; + + fn for_source(source: &BackgroundSource) -> Self { + match source { + BackgroundSource::Color { .. } => Self::Color, + BackgroundSource::Gradient { .. } => Self::Gradient, + BackgroundSource::Wallpaper { .. } => Self::Wallpaper, + BackgroundSource::Image { .. } => Self::Image, + } + } +} + +/// The screenshot editor's border fallback -- `BorderPopover.tsx:42-45`, +/// black at 50%, not `BorderConfiguration::default()`'s white at 80%. +const BORDER_FALLBACK: BorderConfiguration = BorderConfiguration { + enabled: false, + width: 5.0, + color: [0, 0, 0], + opacity: 50.0, +}; + +/// Every slider in the styling panel, with the popovers' exact ranges. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +enum StyleSlider { + Blur, + Padding, + Rounding, + Shadow, + BorderWidth, + BorderOpacity, +} + +impl StyleSlider { + /// `(min, max, step)` -- `BackgroundSettingsPopover` blur, + /// `PaddingPopover`, `RoundingPopover`, `ShadowPopover`, `BorderPopover`. + fn range(self) -> (f32, f32, f32) { + match self { + Self::Blur | Self::Padding | Self::Rounding | Self::Shadow => (0., 100., 1.), + Self::BorderWidth => (1., 20., 0.1), + Self::BorderOpacity => (0., 100., 0.1), + } + } + + fn value(self, project: &ProjectConfiguration) -> f32 { + let background = &project.background; + match self { + Self::Blur => background.blur as f32, + Self::Padding => background.padding as f32, + Self::Rounding => background.rounding as f32, + Self::Shadow => background.shadow, + Self::BorderWidth => background + .border + .as_ref() + .map_or(BORDER_FALLBACK.width, |border| border.width), + Self::BorderOpacity => background + .border + .as_ref() + .map_or(BORDER_FALLBACK.opacity, |border| border.opacity), + } + } + + fn apply(self, project: &mut ProjectConfiguration, value: f32) -> bool { + let background = &mut project.background; + match self { + Self::Blur => { + if background.blur == value as f64 { + return false; + } + background.blur = value as f64; + } + Self::Padding => { + if background.padding == value as f64 { + return false; + } + background.padding = value as f64; + } + Self::Rounding => { + if background.rounding == value as f64 { + return false; + } + background.rounding = value as f64; + } + Self::Shadow => { + if background.shadow == value { + return false; + } + background.shadow = value; + } + Self::BorderWidth => { + let border = background.border.get_or_insert(BorderConfiguration { + enabled: true, + ..BORDER_FALLBACK + }); + if border.width == value { + return false; + } + border.width = value; + } + Self::BorderOpacity => { + let border = background.border.get_or_insert(BorderConfiguration { + enabled: true, + ..BORDER_FALLBACK + }); + if border.opacity == value { + return false; + } + border.opacity = value; + } + } + true + } +} + #[derive(Clone, PartialEq, Eq)] enum ExportDestination { Clipboard, @@ -424,6 +561,12 @@ pub struct ScreenshotEditorWindow { pending_save: Rc>, save_task: Option>, aspect_menu: Option, + bg_tab: BgTab, + wallpaper_theme: usize, + wallpapers: HashMap<&'static str, Arc>, + wallpaper_task: Option>, + slider_tracks: HashMap, + active_slider: Option, focus: gpui::FocusHandle, } @@ -457,6 +600,12 @@ impl ScreenshotEditorWindow { pending_save: Rc::new(RefCell::new(PendingConfigSave::default())), save_task: None, aspect_menu: None, + bg_tab: BgTab::Color, + wallpaper_theme: 0, + wallpapers: HashMap::new(), + wallpaper_task: None, + slider_tracks: HashMap::new(), + active_slider: None, focus: cx.focus_handle(), } } @@ -476,10 +625,14 @@ impl ScreenshotEditorWindow { cx: &mut Context, ) { self.pretty_name = pretty_name; + self.bg_tab = BgTab::for_source(&config.background.source); self.project = config; self.config_tx = Some(config_tx); self.export_tx = Some(export_tx); self.pending_save.borrow_mut().path = Some(self.bundle.clone()); + if self.bg_tab == BgTab::Wallpaper { + self.ensure_wallpapers(cx); + } if let Ok(dest) = std::env::var("CAP_GPUI_AUTO_SCREENSHOT_EXPORT") && !dest.trim().is_empty() { @@ -548,6 +701,81 @@ impl ScreenshotEditorWindow { self.theme.refresh(window, cx, false); } + // -- Styling ---------------------------------------------------------------- + + /// A slider press or drag move: map the pointer to the slider's value and + /// apply it. + fn apply_slider( + &mut self, + slider: StyleSlider, + position: Point, + window: &mut Window, + cx: &mut Context, + ) { + let Some(track) = self.slider_tracks.get(&slider) else { + return; + }; + let (min, max, step) = slider.range(); + let Some(value) = ui::slider_value_at(track, position, min, max, step) else { + return; + }; + self.edit_project(move |project| slider.apply(project, value), window, cx); + } + + /// Decode the current theme's wallpaper tiles that are not cached yet -- + /// `ensure_wallpapers` on the video editor, against this window's own map. + fn ensure_wallpapers(&mut self, cx: &mut Context) { + let theme_name = BACKGROUND_THEMES[self.wallpaper_theme].0; + let wanted: Vec<&'static str> = editor_sidebar::wallpapers_for_theme(theme_name) + .into_iter() + .filter(|id| !self.wallpapers.contains_key(id)) + .collect(); + if wanted.is_empty() { + return; + } + self.wallpaper_task = Some(cx.spawn(async move |this, cx| { + let decoded = cx + .background_executor() + .spawn(async move { + wanted + .into_iter() + .filter_map(|id| { + editor_sidebar::decode_wallpaper_thumbnail(id).map(|image| (id, image)) + }) + .collect::>() + }) + .await; + this.update(cx, |this, cx| { + for (id, image) in decoded { + this.wallpapers.insert(id, image); + } + cx.notify(); + }) + .ok(); + })); + } + + fn pick_background_image(&mut self, window: &mut Window, cx: &mut Context) { + cx.spawn_in(window, async move |this, cx| { + let Some(path) = crate::platform::open_image_panel(&BACKGROUND_IMAGE_EXTENSIONS) else { + return; + }; + this.update_in(cx, |this, window, cx| { + let path = path.to_string_lossy().to_string(); + this.edit_project( + move |project| { + project.background.source = BackgroundSource::Image { path: Some(path) }; + true + }, + window, + cx, + ); + }) + .ok(); + }) + .detach(); + } + // -- Export actions ------------------------------------------------------- /// Render at export resolution and hand the PNG to its destination -- @@ -872,6 +1100,457 @@ impl ScreenshotEditorWindow { ) } + // -- Styling panel ---------------------------------------------------------- + + fn section_label(&self, label: &'static str) -> impl IntoElement { + div() + .text_size(px(11.)) + .font_weight(FontWeight::MEDIUM) + .text_color(self.theme.gray_10) + .child(label) + } + + fn slider_row( + &mut self, + label: &'static str, + slider: StyleSlider, + cx: &mut Context, + ) -> impl IntoElement { + let theme = self.theme; + let (min, max, _) = slider.range(); + let value = slider.value(&self.project); + let fraction = ((value - min) / (max - min)).clamp(0., 1.); + let track = self.slider_tracks.entry(slider).or_default().clone(); + + div() + .flex() + .flex_col() + .gap(px(4.)) + .child( + div() + .flex() + .flex_row() + .justify_between() + .items_center() + .text_size(px(12.)) + .text_color(theme.gray_11) + .child(label) + .child(format!("{}", value.round() as i64)), + ) + .child( + ui::Slider::new( + gpui::SharedString::from(format!("style-slider-{label}")), + fraction, + track, + ) + .flex() + .track(px(4.), theme.gray_4.into()) + .fill(theme.blue_9.into()) + .thumb(px(14.), gpui::white(), Some(theme.gray_6.into())) + .on_drag_start(cx.listener( + move |this, event: &MouseDownEvent, window, cx| { + cx.stop_propagation(); + this.active_slider = Some(slider); + this.apply_slider(slider, event.position, window, cx); + cx.notify(); + }, + )), + ) + } + + fn render_bg_tabs(&self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let current = self.bg_tab; + let mut row = div() + .flex() + .flex_row() + .gap(px(4.)) + .p(px(2.)) + .rounded(px(8.)) + .bg(theme.gray_3); + for (tab, label) in BgTab::ALL { + let selected = tab == current; + row = row.child( + div() + .id(gpui::SharedString::from(format!("bg-tab-{label}"))) + .flex_1() + .flex() + .justify_center() + .py(px(4.)) + .rounded(px(6.)) + .text_size(px(11.)) + .text_color(if selected { + theme.gray_12 + } else { + theme.gray_10 + }) + .when(selected, |this| this.bg(theme.gray_1)) + .when(!selected, |this| this.hover(|style| style.bg(theme.gray_4))) + .child(label) + .on_click(cx.listener(move |this, _, _window, cx| { + cx.stop_propagation(); + this.bg_tab = tab; + if tab == BgTab::Wallpaper { + this.ensure_wallpapers(cx); + } + cx.notify(); + })), + ); + } + row + } + + fn render_color_pane(&mut self, cx: &mut Context) -> gpui::Div { + let theme = self.theme; + let current = match &self.project.background.source { + BackgroundSource::Color { value, alpha } => Some((*value, *alpha)), + _ => None, + }; + let mut grid = div().flex().flex_row().flex_wrap().gap(px(6.)); + for (index, hex) in BACKGROUND_COLORS.iter().enumerate() { + let Some(rgba) = hex_to_rgb(hex) else { + continue; + }; + let value = [rgba[0] as u16, rgba[1] as u16, rgba[2] as u16]; + let alpha = rgba[3]; + let selected = current == Some((value, alpha)); + let mut fill: Hsla = color_to_hsla(value); + fill.a = alpha as f32 / 255.; + grid = grid.child( + div() + .id(("bg-color", index)) + .size(px(24.)) + .rounded(px(6.)) + .border_2() + .border_color(if selected { theme.blue_9 } else { theme.gray_4 }) + .bg(fill) + .hover(|style| style.border_color(theme.gray_8)) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + this.edit_project( + move |project| { + project.background.source = + BackgroundSource::Color { value, alpha }; + true + }, + window, + cx, + ); + })), + ); + } + grid + } + + fn render_gradient_pane(&mut self, cx: &mut Context) -> gpui::Div { + let theme = self.theme; + let current = match &self.project.background.source { + BackgroundSource::Gradient { from, to, .. } => Some((*from, *to)), + _ => None, + }; + let angle = match &self.project.background.source { + BackgroundSource::Gradient { angle, .. } => *angle, + _ => 90, + }; + let mut grid = div().flex().flex_row().flex_wrap().gap(px(6.)); + for (index, (from, to)) in GRADIENT_PRESETS.iter().enumerate() { + let (from, to) = (*from, *to); + let selected = current == Some((from, to)); + grid = grid.child( + div() + .id(("bg-gradient", index)) + .size(px(24.)) + .rounded(px(6.)) + .border_2() + .border_color(if selected { theme.blue_9 } else { theme.gray_4 }) + .bg(linear_gradient( + 135., + linear_color_stop(color_to_hsla(from), 0.), + linear_color_stop(color_to_hsla(to), 1.), + )) + .hover(|style| style.border_color(theme.gray_8)) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + this.edit_project( + move |project| { + project.background.source = BackgroundSource::Gradient { + from, + to, + angle, + noise_intensity: None, + noise_scale: None, + animated: None, + animation_speed: None, + }; + true + }, + window, + cx, + ); + })), + ); + } + grid + } + + fn render_wallpaper_pane(&mut self, cx: &mut Context) -> gpui::Div { + let theme = self.theme; + // The config stores the wallpaper's absolute path (the render layer + // opens it as a file); selection matches by catalogue id the way the + // Tauri sidebar's `path.includes(w.id)` does. + let current = match &self.project.background.source { + BackgroundSource::Wallpaper { path: Some(path) } => { + editor_sidebar::wallpaper_id_for_path(path) + } + _ => None, + }; + + let mut theme_row = div().flex().flex_row().flex_wrap().gap(px(4.)); + for (index, (_, label)) in BACKGROUND_THEMES.iter().enumerate() { + let selected = index == self.wallpaper_theme; + theme_row = theme_row.child( + div() + .id(("wallpaper-theme", index)) + .px(px(8.)) + .py(px(3.)) + .rounded(px(6.)) + .text_size(px(11.)) + .text_color(if selected { + theme.gray_12 + } else { + theme.gray_10 + }) + .bg(if selected { theme.gray_4 } else { theme.gray_2 }) + .hover(|style| style.bg(theme.gray_4)) + .child(*label) + .on_click(cx.listener(move |this, _, _window, cx| { + cx.stop_propagation(); + this.wallpaper_theme = index; + this.ensure_wallpapers(cx); + cx.notify(); + })), + ); + } + + let theme_name = BACKGROUND_THEMES[self.wallpaper_theme].0; + let mut grid = div().flex().flex_row().flex_wrap().gap(px(6.)); + for (index, id) in editor_sidebar::wallpapers_for_theme(theme_name) + .into_iter() + .enumerate() + { + let selected = current == Some(id); + let tile = div() + .id(("wallpaper-tile", index)) + .size(px(56.)) + .rounded(px(8.)) + .border_2() + .border_color(if selected { theme.blue_9 } else { theme.gray_4 }) + .overflow_hidden() + .bg(theme.gray_3) + .hover(|style| style.border_color(theme.gray_8)) + .when_some(self.wallpapers.get(id).cloned(), |this, image| { + this.child(img(image).size_full().object_fit(gpui::ObjectFit::Cover)) + }) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + let Some(path) = editor_sidebar::wallpaper_path(id) else { + tracing::error!(id, "wallpaper asset not found"); + return; + }; + let path = path.to_string_lossy().into_owned(); + this.edit_project( + move |project| { + project.background.source = + BackgroundSource::Wallpaper { path: Some(path) }; + true + }, + window, + cx, + ); + })); + grid = grid.child(tile); + } + + div() + .flex() + .flex_col() + .gap(px(8.)) + .child(theme_row) + .child(grid) + } + + fn render_image_pane(&mut self, cx: &mut Context) -> gpui::Div { + let theme = self.theme; + let current = match &self.project.background.source { + BackgroundSource::Image { path } => path.clone(), + _ => None, + }; + div() + .flex() + .flex_col() + .gap(px(6.)) + .child( + div() + .id("bg-image-pick") + .flex() + .justify_center() + .py(px(8.)) + .rounded(px(8.)) + .border_1() + .border_color(theme.gray_4) + .bg(theme.gray_2) + .hover(|style| style.bg(theme.gray_3)) + .text_size(px(12.)) + .text_color(theme.gray_12) + .child("Choose Image...") + .on_click(cx.listener(|this, _, window, cx| { + cx.stop_propagation(); + this.pick_background_image(window, cx); + })), + ) + .when_some(current, |this, path| { + this.child( + div() + .text_size(px(11.)) + .text_color(theme.gray_10) + .overflow_hidden() + .child(path), + ) + }) + } + + fn render_corner_style(&mut self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let squircle = matches!(self.project.background.rounding_type, CornerStyle::Squircle); + let mut row = div() + .flex() + .flex_row() + .gap(px(4.)) + .p(px(2.)) + .rounded(px(8.)) + .bg(theme.gray_3); + for (label, style, selected) in [ + ("Squircle", CornerStyle::Squircle, squircle), + ("Rounded", CornerStyle::Rounded, !squircle), + ] { + row = row.child( + div() + .id(gpui::SharedString::from(format!("corner-style-{label}"))) + .flex_1() + .flex() + .justify_center() + .py(px(4.)) + .rounded(px(6.)) + .text_size(px(11.)) + .text_color(if selected { + theme.gray_12 + } else { + theme.gray_10 + }) + .when(selected, |this| this.bg(theme.gray_1)) + .when(!selected, |this| this.hover(|style| style.bg(theme.gray_4))) + .child(label) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + this.edit_project( + move |project| { + if project.background.rounding_type == style { + return false; + } + project.background.rounding_type = style; + true + }, + window, + cx, + ); + })), + ); + } + row + } + + fn render_border_section(&mut self, cx: &mut Context) -> gpui::Div { + let theme = self.theme; + let enabled = self + .project + .background + .border + .as_ref() + .is_some_and(|border| border.enabled); + + let mut section = div().flex().flex_col().gap(px(10.)).child( + div() + .flex() + .flex_row() + .justify_between() + .items_center() + .child(self.section_label("Border")) + .child( + ui::Toggle::plain(&theme, "border-toggle", enabled).on_click(cx.listener( + move |this, _, window, cx| { + cx.stop_propagation(); + this.edit_project( + move |project| { + let border = + project.background.border.get_or_insert(BORDER_FALLBACK); + border.enabled = !enabled; + true + }, + window, + cx, + ); + }, + )), + ), + ); + if enabled { + section = section + .child(self.slider_row("Width", StyleSlider::BorderWidth, cx)) + .child(self.slider_row("Opacity", StyleSlider::BorderOpacity, cx)); + } + section + } + + fn render_sidebar(&mut self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let bg_pane = match self.bg_tab { + BgTab::Color => self.render_color_pane(cx), + BgTab::Gradient => self.render_gradient_pane(cx), + BgTab::Wallpaper => self.render_wallpaper_pane(cx), + BgTab::Image => self.render_image_pane(cx), + }; + + div() + .id("screenshot-style-panel") + .w(px(280.)) + .flex_shrink_0() + .m(px(14.)) + .mt(px(0.)) + .ml(px(0.)) + .p(px(14.)) + .rounded(px(12.)) + .border_1() + .border_color(theme.gray_4) + .bg(if theme.is_dark() { + theme.gray_2 + } else { + theme.gray_1 + }) + .overflow_y_scroll() + .flex() + .flex_col() + .gap(px(14.)) + .child(self.section_label("Background")) + .child(self.render_bg_tabs(cx)) + .child(bg_pane) + .child(self.slider_row("Blur", StyleSlider::Blur, cx)) + .child(self.slider_row("Padding", StyleSlider::Padding, cx)) + .child(self.slider_row("Rounding", StyleSlider::Rounding, cx)) + .child(self.render_corner_style(cx)) + .child(self.slider_row("Shadow", StyleSlider::Shadow, cx)) + .child(self.render_border_section(cx)) + } + fn render_preview(&self, cx: &mut Context) -> impl IntoElement { let theme = self.theme; let frame = self.frame.clone(); @@ -976,7 +1655,27 @@ impl Render for ScreenshotEditorWindow { }) .text_color(theme.gray_12) .child(self.render_header(cx)) - .child(self.render_preview(cx)) + .child( + div() + .flex() + .flex_row() + .flex_1() + .min_h_0() + .child(self.render_preview(cx)) + .child(self.render_sidebar(cx)), + ) + .when_some(self.active_slider, |root, slider| { + root.child(ui::Slider::drag_layer( + "style-slider-drag", + cx.listener(move |this, event: &MouseMoveEvent, window, cx| { + this.apply_slider(slider, event.position, window, cx); + }), + cx.listener(|this, _: &gpui::MouseUpEvent, _window, cx| { + this.active_slider = None; + cx.notify(); + }), + )) + }) } } From b212640355efba361d91a886701c60b296bdf122 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:59:59 +0100 Subject: [PATCH 139/146] feat(desktop-gpui): register the store's global hotkeys --- apps/desktop-gpui/Cargo.lock | 43 +++- apps/desktop-gpui/Cargo.toml | 4 + apps/desktop-gpui/README.md | 2 +- apps/desktop-gpui/src/hotkeys.rs | 269 ++++++++++++++++++++++++ apps/desktop-gpui/src/main.rs | 20 ++ apps/desktop-gpui/src/settings_pages.rs | 16 +- 6 files changed, 338 insertions(+), 16 deletions(-) create mode 100644 apps/desktop-gpui/src/hotkeys.rs diff --git a/apps/desktop-gpui/Cargo.lock b/apps/desktop-gpui/Cargo.lock index fbd9ca64a3..85f1b28dc2 100644 --- a/apps/desktop-gpui/Cargo.lock +++ b/apps/desktop-gpui/Cargo.lock @@ -1494,6 +1494,7 @@ dependencies = [ "ffmpeg-next", "flume 0.11.1", "futures-util", + "global-hotkey", "gpui", "gpui_platform", "gpui_tokio", @@ -3133,7 +3134,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3360,7 +3361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4035,6 +4036,23 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" +[[package]] +name = "global-hotkey" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7" +dependencies = [ + "crossbeam-channel", + "keyboard-types", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "once_cell", + "thiserror 2.0.20", + "windows-sys 0.59.0", + "x11rb", + "xkeysym", +] + [[package]] name = "glow" version = "0.16.0" @@ -5377,6 +5395,17 @@ dependencies = [ "uuid", ] +[[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 = "khronos-egl" version = "6.0.0" @@ -6291,7 +6320,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7861,7 +7890,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -8614,7 +8643,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -10102,7 +10131,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -11777,7 +11806,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/apps/desktop-gpui/Cargo.toml b/apps/desktop-gpui/Cargo.toml index bf1dc70847..d4caa797b9 100644 --- a/apps/desktop-gpui/Cargo.toml +++ b/apps/desktop-gpui/Cargo.toml @@ -93,6 +93,10 @@ unicode-segmentation = "1" # spawn_blocking for finalize; the runtime itself comes from gpui_tokio. tokio = { version = "1", features = ["rt", "time"] } md-5 = "0.10" +# The same crate `tauri_plugin_global_shortcut` wraps, at the Tauri app's pin: +# store bindings are W3C `KeyboardEvent.code` strings and this owns the +# platform mapping (Carbon RegisterEventHotKey on macOS). +global-hotkey = "0.7.0" # cap-recording's actor system; version must match its `kameo = "0.17.2"`. kameo = "0.17.2" flume = "0.11.0" diff --git a/apps/desktop-gpui/README.md b/apps/desktop-gpui/README.md index 2d5a413e38..122850c764 100644 --- a/apps/desktop-gpui/README.md +++ b/apps/desktop-gpui/README.md @@ -854,7 +854,7 @@ Settings-specific deviations: | | | |---|---| -| **Ten placeholder pages** | Shortcuts, CLI, Screenshots, Automations, Transcription, Integrations, License, Experimental, Feedback and Changelog render their name, a one-line description and a card saying they are not part of the rewrite yet. The sidebar is real; those bodies are not. | +| **Placeholder pages** | CLI, Automations, Transcription, Integrations, License, Experimental, Feedback and Changelog render their name, a one-line description and a card saying they are not part of the rewrite yet. The sidebar is real; those bodies are not. Shortcuts and Screenshots have since grown real bodies: the Shortcuts page edits the shared `hotkeys` store **and** re-registers the OS shortcuts through `crate::hotkeys` (the `global-hotkey` crate `tauri_plugin_global_shortcut` wraps, same pin), and the Screenshots page is the full library. | | **Import and Reupload are disabled** | `importVideoFromPicker` remuxes the picked file through `commands.importVideoToProject`, and Reupload is `commands.uploadExportedVideo(path, "Reupload", ..)`. Neither has a counterpart in this app — no import remux, no upload infrastructure — so both buttons are drawn in their disabled state, the same precedent as the editor sidebar's transcription buttons. | | **No upload progress on a recording row** | The Tauri row draws a `ProgressCircle` while `uploadProgressEvent` is arriving for that video, and `hasActiveRecording` also keeps polling while `upload.state` is `MultipartUpload` / `SinglePartUpload`. There are no uploads here, so the ring is not drawn and the poll's upload half is not reproduced — only `InProgress` / `NeedsRemux` keep it running. | | **An instant bundle with no `content` directory is revealed, not opened** | `openRecordingFolder` treats *spawning* `open` as success, so an instant bundle missing its `content` directory opens nothing at all. Here the directory is stat'd first and the bundle is revealed instead. | diff --git a/apps/desktop-gpui/src/hotkeys.rs b/apps/desktop-gpui/src/hotkeys.rs new file mode 100644 index 0000000000..7c3f5b2bb3 --- /dev/null +++ b/apps/desktop-gpui/src/hotkeys.rs @@ -0,0 +1,269 @@ +//! Global hotkeys -- `apps/desktop/src-tauri/src/hotkeys.rs`, natively. +//! +//! The Tauri app registers the store's bindings through +//! `tauri_plugin_global_shortcut`, which wraps the `global-hotkey` crate; +//! this module uses that crate directly at the same pin, so the platform +//! mapping from the store's W3C `KeyboardEvent.code` strings to Carbon +//! virtual keycodes is the exact code path the shipping app runs. Bindings +//! live in the shared store (`hotkeys.hotkeys`, see `store::hotkeys_raw`), +//! and the settings Shortcuts page's commit calls [`reload`] where the Tauri +//! page calls `commands.setHotkey`. +//! +//! The overlay's global Escape stays on its own Carbon registration +//! (`platform::register_escape_hotkey`) exactly as it is separate in the +//! Tauri app (`target_select_overlay.rs`, not a `HotkeyAction`). +//! +//! Deviation: the Tauri handler's mic-confirm dialog before a hotkey start +//! (`confirm_before_recording_without_microphone`) is not ported yet -- it +//! belongs with the recording countdown / mic-confirm parity unit. + +use std::str::FromStr as _; + +use cap_recording::sources::screen_capture::ScreenCaptureTarget; +use global_hotkey::{ + GlobalHotKeyEvent, GlobalHotKeyManager, HotKeyState, + hotkey::{Code, HotKey, Modifiers}, +}; +use gpui::{App, Global}; + +use crate::{ + app_windows, + main_window::{Mode, TargetType}, + session::RecordingSession, + store, +}; + +/// `HotkeyAction` (`hotkeys.rs:52-71` over there); the variants are the +/// camelCase store keys. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum HotkeyAction { + StartStudioRecording, + StartInstantRecording, + StopRecording, + RestartRecording, + TogglePauseRecording, + CycleRecordingMode, + OpenRecordingPicker, + OpenRecordingPickerDisplay, + OpenRecordingPickerWindow, + OpenRecordingPickerArea, + ScreenshotDisplay, + ScreenshotWindow, + ScreenshotArea, +} + +impl HotkeyAction { + fn from_store_key(key: &str) -> Option { + Some(match key { + "startStudioRecording" => Self::StartStudioRecording, + "startInstantRecording" => Self::StartInstantRecording, + "stopRecording" => Self::StopRecording, + "restartRecording" => Self::RestartRecording, + "togglePauseRecording" => Self::TogglePauseRecording, + "cycleRecordingMode" => Self::CycleRecordingMode, + "openRecordingPicker" => Self::OpenRecordingPicker, + "openRecordingPickerDisplay" => Self::OpenRecordingPickerDisplay, + "openRecordingPickerWindow" => Self::OpenRecordingPickerWindow, + "openRecordingPickerArea" => Self::OpenRecordingPickerArea, + "screenshotDisplay" => Self::ScreenshotDisplay, + "screenshotWindow" => Self::ScreenshotWindow, + "screenshotArea" => Self::ScreenshotArea, + // `#[serde(other)] Other` over there: a newer app's action + // survives in the store and registers nothing here. + _ => return None, + }) + } +} + +struct Hotkeys { + manager: GlobalHotKeyManager, + bindings: Vec<(HotKey, HotkeyAction)>, +} + +impl Global for Hotkeys {} + +/// Create the manager (main thread -- it installs the Carbon event handler), +/// register the store's bindings, and start the drain. The handler callback +/// fires on the OS event seam, so it only forwards into a channel; the gpui +/// task dispatches with a clean borrow -- the tray-channel discipline. +pub fn init(cx: &mut App) { + let manager = match GlobalHotKeyManager::new() { + Ok(manager) => manager, + Err(error) => { + tracing::error!("the global hotkey manager failed to start: {error}"); + return; + } + }; + cx.set_global(Hotkeys { + manager, + bindings: Vec::new(), + }); + register_from_store(cx); + + let (tx, rx) = flume::unbounded::(); + GlobalHotKeyEvent::set_event_handler(Some(move |event: GlobalHotKeyEvent| { + let _ = tx.send(event); + })); + cx.spawn(async move |cx| { + while let Ok(event) = rx.recv_async().await { + // Pressed only, like the Tauri handler (`hotkeys.rs:183-185`). + if event.state == HotKeyState::Pressed { + cx.update(|cx| dispatch_id(event.id(), cx)); + } + } + }) + .detach(); +} + +/// Re-read the store and swap every OS registration -- the settings page's +/// commit seam (`commands.setHotkey` re-registers over there). +pub fn reload(cx: &mut App) { + if !cx.has_global::() { + return; + } + let hotkeys = cx.global_mut::(); + let old: Vec = hotkeys.bindings.iter().map(|(hotkey, _)| *hotkey).collect(); + if let Err(error) = hotkeys.manager.unregister_all(&old) { + tracing::warn!("unregistering global hotkeys failed: {error}"); + } + hotkeys.bindings.clear(); + register_from_store(cx); +} + +fn register_from_store(cx: &mut App) { + let stored = store::hotkeys_raw(); + let hotkeys = cx.global_mut::(); + for (key, value) in &stored { + let Some(action) = HotkeyAction::from_store_key(key) else { + tracing::debug!(key, "hotkey action unknown to this build; skipped"); + continue; + }; + let Some(binding) = store::hotkey_from_value(value) else { + tracing::warn!(key, "unparseable hotkey binding in the store"); + continue; + }; + let Ok(code) = Code::from_str(&binding.code) else { + tracing::warn!(key, code = %binding.code, "unknown hotkey code"); + continue; + }; + let mut mods = Modifiers::empty(); + if binding.meta { + mods |= Modifiers::META; + } + if binding.ctrl { + mods |= Modifiers::CONTROL; + } + if binding.alt { + mods |= Modifiers::ALT; + } + if binding.shift { + mods |= Modifiers::SHIFT; + } + let hotkey = HotKey::new(Some(mods), code); + match hotkeys.manager.register(hotkey) { + Ok(()) => hotkeys.bindings.push((hotkey, action)), + Err(error) => { + tracing::warn!(?action, code = %binding.code, "registering global hotkey failed: {error}") + } + } + } + tracing::info!(count = hotkeys.bindings.len(), "global hotkeys registered"); +} + +/// `CAP_GPUI_AUTO_HOTKEY=`: run an action's dispatch arm without +/// the OS keypress (unprivileged synthetic key events are dropped, the same +/// reason every other `CAP_GPUI_AUTO_*` hook exists). +pub fn dispatch_for_harness(key: &str, cx: &mut App) { + match HotkeyAction::from_store_key(key) { + Some(action) => dispatch(action, cx), + None => tracing::error!(key, "CAP_GPUI_AUTO_HOTKEY: unknown action"), + } +} + +fn dispatch_id(id: u32, cx: &mut App) { + let action = cx + .global::() + .bindings + .iter() + .find(|(hotkey, _)| hotkey.id() == id) + .map(|(_, action)| *action); + if let Some(action) = action { + dispatch(action, cx); + } +} + +/// `handle_hotkey` (`hotkeys.rs:228-337` over there), against this app's +/// counterparts. Session methods are no-ops in the wrong phase, matching the +/// Tauri commands' own early returns. +fn dispatch(action: HotkeyAction, cx: &mut App) { + tracing::info!(?action, "global hotkey"); + match action { + HotkeyAction::StartStudioRecording => start_primary_display(Mode::Studio, cx), + HotkeyAction::StartInstantRecording => start_primary_display(Mode::Instant, cx), + HotkeyAction::StopRecording => { + RecordingSession::global(cx).update(cx, |session, cx| session.stop(cx)); + } + HotkeyAction::RestartRecording => { + RecordingSession::global(cx).update(cx, |session, cx| session.restart(cx)); + } + HotkeyAction::TogglePauseRecording => { + RecordingSession::global(cx).update(cx, |session, cx| session.toggle_pause(cx)); + } + HotkeyAction::CycleRecordingMode => { + // `Studio -> Instant -> Screenshot -> Studio` (`hotkeys.rs:262`). + let next = match Mode::from_store() { + Mode::Studio => Mode::Instant, + Mode::Instant => Mode::Screenshot, + Mode::Screenshot => Mode::Studio, + }; + app_windows::set_recording_mode(next, cx); + } + HotkeyAction::OpenRecordingPicker => { + app_windows::show_main_window(cx); + cx.activate(true); + } + HotkeyAction::OpenRecordingPickerDisplay => { + app_windows::arm_target_mode(TargetType::Display, cx) + } + HotkeyAction::OpenRecordingPickerWindow => { + app_windows::arm_target_mode(TargetType::Window, cx) + } + HotkeyAction::OpenRecordingPickerArea => app_windows::arm_target_mode(TargetType::Area, cx), + HotkeyAction::ScreenshotDisplay => { + let display = scap_targets::Display::get_containing_cursor() + .unwrap_or_else(scap_targets::Display::primary); + crate::screenshot::take_screenshot( + ScreenCaptureTarget::Display { id: display.id() }, + cx, + ); + } + HotkeyAction::ScreenshotWindow => match scap_targets::Window::get_topmost_at_cursor() { + Some(window) => crate::screenshot::take_screenshot( + ScreenCaptureTarget::Window { id: window.id() }, + cx, + ), + None => tracing::warn!("no window under the cursor to screenshot"), + }, + HotkeyAction::ScreenshotArea => { + // `set_mode(Screenshot)` + the area picker (`hotkeys.rs:311-323`): + // the grab happens when the drawn area is released. + app_windows::set_recording_mode(Mode::Screenshot, cx); + app_windows::arm_target_mode(TargetType::Area, cx); + } + } +} + +/// `RequestStartRecording`'s listener with no armed target records the +/// primary display (`lib.rs:5517-5538` over there); mic/camera come off the +/// main window's current selections through its ordinary start path. +fn start_primary_display(mode: Mode, cx: &mut App) { + app_windows::set_recording_mode(mode, cx); + let target = ScreenCaptureTarget::Display { + id: scap_targets::Display::primary().id(), + }; + let main = cx.global::().main; + main.update(cx, |view, window, cx| { + view.start_recording_with_target(target, Vec::new(), window, cx) + }) + .ok(); +} diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index 57c1adb7f9..f88d75c1a7 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -25,6 +25,7 @@ mod editor_tabs; mod editor_timeline; mod editor_window; mod feeds; +mod hotkeys; mod import; mod library; mod main_window; @@ -210,6 +211,8 @@ fn main() { if !std::env::var("CAP_GPUI_NO_TRAY").is_ok_and(|v| v == "1") { tray::init(cx); } + // The store's global shortcuts, after the registry they dispatch into. + hotkeys::init(cx); // The gate's TCC queries (screen recording preflight, AXIsProcessTrusted) // are ~30ms of synchronous XPC on this Mac -- the whole startup delta // between the 08-17 and 08-18 builds. Both are thread-safe reads, so @@ -420,6 +423,23 @@ fn main() { } } + // `CAP_GPUI_AUTO_HOTKEY=`: run a hotkey action's dispatch + // arm (e.g. `screenshotDisplay`) as if the OS shortcut fired. + if let Ok(action) = std::env::var("CAP_GPUI_AUTO_HOTKEY") + && !action.is_empty() + { + let action = action.clone(); + cx.spawn(async move |cx| { + // Give enumeration and the first paint the same beat the + // other harness hooks give them. + cx.background_executor() + .timer(std::time::Duration::from_secs(2)) + .await; + cx.update(|cx| hotkeys::dispatch_for_harness(&action, cx)); + }) + .detach(); + } + // `CAP_GPUI_AUTO_SCREENSHOT_EDITOR=` (or `1` for the // newest screenshot): open the screenshot editor the way a Recents // card does. diff --git a/apps/desktop-gpui/src/settings_pages.rs b/apps/desktop-gpui/src/settings_pages.rs index 8391f4a25d..dd134269dc 100644 --- a/apps/desktop-gpui/src/settings_pages.rs +++ b/apps/desktop-gpui/src/settings_pages.rs @@ -947,15 +947,18 @@ impl SettingsWindow { let value = serde_json::to_value(&hotkey).unwrap_or(Value::Null); self.pages.hotkeys.insert(action.to_string(), value); // `createEffect` persists on every store change, captures included. - self.shortcuts_persist(); + self.shortcuts_persist(cx); cx.notify(); true } - fn shortcuts_persist(&self) { + fn shortcuts_persist(&self, cx: &mut Context) { if !store::set_hotkeys_raw(&self.pages.hotkeys) { tracing::warn!("saving the hotkeys store failed"); } + // `commands.setHotkey` re-registers the OS shortcut as it persists; + // deferred because the registry swap reads the store this just wrote. + cx.defer(crate::hotkeys::reload); } /// The window click listener: an abandoned capture puts the previous @@ -973,7 +976,7 @@ impl SettingsWindow { self.pages.hotkeys.remove(action); } } - self.shortcuts_persist(); + self.shortcuts_persist(cx); cx.notify(); } @@ -1136,10 +1139,7 @@ impl SettingsWindow { .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) .on_click(cx.listener(move |this, _, _window, cx| { this.pages.listening = None; - // `commands.setHotkey(action, binding)` also registers - // the OS shortcut; there is no global-shortcut layer - // here, so the persisted binding is the whole commit. - this.shortcuts_persist(); + this.shortcuts_persist(cx); cx.notify(); })), ); @@ -1160,7 +1160,7 @@ impl SettingsWindow { .on_click(cx.listener(move |this, _, _window, cx| { this.pages.listening = None; this.pages.hotkeys.remove(HOTKEY_ACTIONS[index].0); - this.shortcuts_persist(); + this.shortcuts_persist(cx); cx.notify(); })), ); From 9db8bd6ecc71f743d4ffd6d23b267dd1d0b7fecb Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:03:48 +0100 Subject: [PATCH 140/146] feat(desktop-gpui): rebuild screenshot editor to 1:1 Tauri parity The full screenshot-editor port in three layers: the window shell (centred toolbar, aspect menu, five anchored popovers with the Tauri side effects, checkerboard preview with Preview.tsx zoom/pan/pinch math, undo history, toasts, keyboard map), the complete annotation engine (all six tools, layout.ts/arrow.ts geometry verbatim, selection handles, inline text editing, layers panel with drag reorder, config bar, blur/pixelate mask overlays resampled off-thread), and the crop dialog riding the editor_crop engine over original.png. screenshot_export.rs is the renderScreenshotExportCanvas pass: masks re-filtered from an unmodified copy at export scale, shapes via tiny-skia, text via cosmic-text at the preview's Helvetica baseline, the output expanded to the union of image and annotation boxes with the source's white-fill rules, and per-destination encodings (PNG, PNG-over-white for the clipboard, JPEG 0.9 for share). --- .../assets/icons/arrow-up-right.svg | 1 + .../assets/icons/grip-vertical.svg | 1 + .../assets/icons/more-horizontal.svg | 1 + apps/desktop-gpui/assets/icons/pencil.svg | 1 + apps/desktop-gpui/assets/icons/save.svg | 1 + apps/desktop-gpui/src/assets.rs | 16 +- apps/desktop-gpui/src/editor_crop.rs | 24 +- .../src/screenshot_annotations.rs | 4397 ++++++++++++++++ apps/desktop-gpui/src/screenshot_crop.rs | 1155 +++++ apps/desktop-gpui/src/screenshot_editor.rs | 4474 ++++++++++++++--- apps/desktop-gpui/src/screenshot_export.rs | 1026 ++++ apps/desktop-gpui/src/theme.rs | 14 + scripts/local-cargo.mjs | 56 - 13 files changed, 10392 insertions(+), 775 deletions(-) create mode 100644 apps/desktop-gpui/assets/icons/arrow-up-right.svg create mode 100644 apps/desktop-gpui/assets/icons/grip-vertical.svg create mode 100644 apps/desktop-gpui/assets/icons/more-horizontal.svg create mode 100644 apps/desktop-gpui/assets/icons/pencil.svg create mode 100644 apps/desktop-gpui/assets/icons/save.svg create mode 100644 apps/desktop-gpui/src/screenshot_annotations.rs create mode 100644 apps/desktop-gpui/src/screenshot_crop.rs create mode 100644 apps/desktop-gpui/src/screenshot_export.rs delete mode 100644 scripts/local-cargo.mjs diff --git a/apps/desktop-gpui/assets/icons/arrow-up-right.svg b/apps/desktop-gpui/assets/icons/arrow-up-right.svg new file mode 100644 index 0000000000..9324aa5545 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/arrow-up-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-gpui/assets/icons/grip-vertical.svg b/apps/desktop-gpui/assets/icons/grip-vertical.svg new file mode 100644 index 0000000000..ee6447f738 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/grip-vertical.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-gpui/assets/icons/more-horizontal.svg b/apps/desktop-gpui/assets/icons/more-horizontal.svg new file mode 100644 index 0000000000..a2d9eaab1b --- /dev/null +++ b/apps/desktop-gpui/assets/icons/more-horizontal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-gpui/assets/icons/pencil.svg b/apps/desktop-gpui/assets/icons/pencil.svg new file mode 100644 index 0000000000..946f7f1989 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/pencil.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-gpui/assets/icons/save.svg b/apps/desktop-gpui/assets/icons/save.svg new file mode 100644 index 0000000000..99e83001ba --- /dev/null +++ b/apps/desktop-gpui/assets/icons/save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/desktop-gpui/src/assets.rs b/apps/desktop-gpui/src/assets.rs index c9bd37eb8d..e176c98079 100644 --- a/apps/desktop-gpui/src/assets.rs +++ b/apps/desktop-gpui/src/assets.rs @@ -174,9 +174,7 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": "sparkles.svg", "timer.svg", "volume-2.svg", - "volume-x.svg", "diamond.svg", - "fast-forward.svg", "x-mark.svg", "zap.svg", "zoom-in.svg", @@ -203,6 +201,14 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": // integrations/index.tsx's inline multi-colour logo flattened to one fill // (gpui's `svg()` keeps only the alpha). "circle-check.svg", + // The screenshot editor's annotation toolbar, layers panel and header + // (`AnnotationTools.tsx`, `LayersPanel.tsx`, `Header.tsx`). Lucide 24x24 + // originals, matching the `~icons/lucide/*` imports over there. + "pencil.svg", + "arrow-up-right.svg", + "more-horizontal.svg", + "save.svg", + "grip-vertical.svg", "chevron-up.svg", "arrow-left.svg", "film.svg", @@ -318,6 +324,12 @@ mod tests { include_str!("editor_crop.rs"), // The clips sidebar's cards, import menu and record modal. include_str!("editor_clips.rs"), + // The export page's destination cards and share-status glyphs. + include_str!("editor_export.rs"), + // The screenshot editor's toolbar, popovers and zoom HUD; the + // annotation module names the tool and layer glyphs. + include_str!("screenshot_editor.rs"), + include_str!("screenshot_annotations.rs"), // `ui::SelectionHeader` names the check and the trash itself. include_str!("ui/selection_header.rs"), ]; diff --git a/apps/desktop-gpui/src/editor_crop.rs b/apps/desktop-gpui/src/editor_crop.rs index e55fbf6042..553de486f3 100644 --- a/apps/desktop-gpui/src/editor_crop.rs +++ b/apps/desktop-gpui/src/editor_crop.rs @@ -920,7 +920,7 @@ pub enum CropDrag { } impl CropDrag { - fn cursor(&self) -> CursorStyle { + pub(crate) fn cursor(&self) -> CursorStyle { match self { // `cursorStyle()` (`:290-294`): both the region and the draw drag // show `grabbing`. @@ -1177,7 +1177,7 @@ impl CropState { /// `setRawBounds` -- the drag path, which skips the constraint pass /// because the solver has already applied it. - fn set_raw(&mut self, bounds: CropBounds) { + pub(crate) fn set_raw(&mut self, bounds: CropBounds) { self.raw = bounds; if self.anim.is_none() { self.display_raw = bounds; @@ -2103,7 +2103,7 @@ fn crop_log(bounds: CropBounds) -> String { } /// Window coordinates -> container-local container pixels. -fn crop_local(area: Option>, position: Point) -> Vec2 { +pub(crate) fn crop_local(area: Option>, position: Point) -> Vec2 { let origin = area.map(|bounds| bounds.origin).unwrap_or_default(); Vec2::new( f64::from(f32::from(position.x - origin.x)), @@ -2717,7 +2717,7 @@ impl EditorWindow { } /// The four `bg-black/45` quads around the region (`:1124-1133`). -fn occluders(bounds: CropBounds, w: f32, h: f32) -> Vec { +pub(crate) fn occluders(bounds: CropBounds, w: f32, h: f32) -> Vec { let fill = gpui::hsla(0., 0., 0., OCCLUDER_ALPHA); let (x, y, bw, bh) = ( bounds.x as f32, @@ -2762,7 +2762,7 @@ fn occluders(bounds: CropBounds, w: f32, h: f32) -> Vec { } /// The rule-of-thirds cross drawn inside the region during a drag. -fn thirds_grid(bounds: CropBounds) -> gpui::Div { +pub(crate) fn thirds_grid(bounds: CropBounds) -> gpui::Div { let line = gpui::hsla(0., 0., 1., 0.4); let (w, h) = (bounds.width as f32, bounds.height as f32); div() @@ -2811,7 +2811,7 @@ fn thirds_grid(bounds: CropBounds) -> gpui::Div { /// button that starts 12px outside the corner -- which resolves to two 24x6 /// bars whose inner edges sit exactly on the region's corner. `size-1` when /// the region is under 30px (`:1208-1211`). -fn corner_glyph(handle: Handle, bounds: CropBounds, too_small: bool) -> Vec { +pub(crate) fn corner_glyph(handle: Handle, bounds: CropBounds, too_small: bool) -> Vec { let scale = if too_small { 4. / 24. } else { 1. }; let arm = 24. * scale; let thickness = 6. * scale; @@ -2850,7 +2850,11 @@ fn corner_glyph(handle: Handle, bounds: CropBounds, too_small: bool) -> Vec) -> gpui::Div { +pub(crate) fn handle_zone( + handle: Handle, + bounds: CropBounds, + drag_cursor: Option, +) -> gpui::Div { handle_zone_with_cursor( handle, bounds, @@ -2858,7 +2862,11 @@ fn handle_zone(handle: Handle, bounds: CropBounds, drag_cursor: Option gpui::Div { +pub(crate) fn handle_zone_with_cursor( + handle: Handle, + bounds: CropBounds, + cursor: CursorStyle, +) -> gpui::Div { let rect = handle_rect(handle, bounds); div() .absolute() diff --git a/apps/desktop-gpui/src/screenshot_annotations.rs b/apps/desktop-gpui/src/screenshot_annotations.rs new file mode 100644 index 0000000000..684467e20b --- /dev/null +++ b/apps/desktop-gpui/src/screenshot_annotations.rs @@ -0,0 +1,4397 @@ +//! Screenshot annotations -- the tool set, the frame-space geometry, and the +//! seam the annotation engine grows on. +//! +//! The Tauri editor keeps annotations in **frame space**: the SVG layer's +//! viewBox is `0 0 frameW frameH` (`AnnotationLayer.tsx`), so an annotation's +//! `x`/`y`/`width`/`height` are pixels of the *rendered preview frame*, not of +//! the source PNG and not of the screen. Everything in this module works in +//! that space, which is why the maths here is a literal transcription of +//! `screenshot-editor/layout.ts` and `screenshot-editor/arrow.ts` rather than +//! a re-derivation: the renderer, the SVG layer and the export path all agree +//! on those numbers today, and any drift shows up as annotations sliding off +//! the thing they point at. +//! +//! `calculate_image_transform` is the one function both halves need: it says +//! where the screenshot's content sits inside the padded frame, and the +//! context's resize effect (`context.tsx:474-547`) rescales every annotation +//! through it whenever the frame changes size. + +// The geometry is transcribed whole rather than trimmed to today's callers -- +// `ArrowHead::length`, `Tool::annotation_type` and friends are part of the +// transcription even where the engine reads them indirectly. Same rationale as +// `ui/mod.rs`'s allow: half a transcription is worse than none, because the +// next person would go back to `layout.ts` and re-derive it. +#![allow(dead_code)] + +use std::cell::Cell; +use std::collections::HashMap; +use std::hash::{Hash as _, Hasher as _}; +use std::rc::Rc; +use std::sync::Arc; + +use cap_project::{Annotation, AnnotationType, AspectRatio, Crop, MaskType}; +use gpui::{ + AnyElement, App, AppContext as _, Bounds, Context, Corners, CursorStyle, Entity, FontWeight, + Hsla, InteractiveElement as _, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent, + MouseUpEvent, ParentElement as _, PathBuilder, Pixels, Point, RenderImage, + StatefulInteractiveElement as _, Styled as _, TextRun, Window, canvas, div, + prelude::FluentBuilder as _, px, svg, +}; + +use crate::editor_sidebar::{BACKGROUND_COLORS, hex_digit_count, hex_to_rgb}; +use crate::screenshot_editor::ScreenshotEditorWindow; +use crate::theme::Theme; +use crate::ui; + +// --------------------------------------------------------------------------- +// Tools +// --------------------------------------------------------------------------- + +/// `ScreenshotEditorTool` (`context.tsx:99`) -- the six annotation types plus +/// the selection arrow, in the order `AnnotationTools.tsx:37-73` lays them out. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Tool { + Select, + Draw, + Arrow, + Rectangle, + Mask, + Circle, + Text, +} + +impl Tool { + /// Toolbar order (`AnnotationTools.tsx:37-73`). + pub const ALL: [Tool; 7] = [ + Tool::Select, + Tool::Draw, + Tool::Arrow, + Tool::Rectangle, + Tool::Mask, + Tool::Circle, + Tool::Text, + ]; + + /// The Lucide glyph each button draws at `size-4`. + pub fn icon(self) -> &'static str { + match self { + Tool::Select => "icons/mouse-pointer-2.svg", + Tool::Draw => "icons/pencil.svg", + Tool::Arrow => "icons/arrow-up-right.svg", + Tool::Rectangle => "icons/square.svg", + Tool::Mask => "icons/eye-off.svg", + Tool::Circle => "icons/circle.svg", + Tool::Text => "icons/type.svg", + } + } + + pub fn label(self) -> &'static str { + match self { + Tool::Select => "Select", + Tool::Draw => "Draw", + Tool::Arrow => "Arrow", + Tool::Rectangle => "Rectangle", + Tool::Mask => "Mask", + Tool::Circle => "Circle", + Tool::Text => "Text", + } + } + + /// The `kbd` chip on the button's tooltip, and the bare key `Editor.tsx` + /// binds it to (`:120-152`). + pub fn shortcut(self) -> &'static str { + match self { + Tool::Select => "V", + Tool::Draw => "D", + Tool::Arrow => "A", + Tool::Rectangle => "R", + Tool::Mask => "M", + Tool::Circle => "C", + Tool::Text => "T", + } + } + + /// The annotation this tool draws, if it draws one. + pub fn annotation_type(self) -> Option { + match self { + Tool::Select => None, + Tool::Draw => Some(AnnotationType::Draw), + Tool::Arrow => Some(AnnotationType::Arrow), + Tool::Rectangle => Some(AnnotationType::Rectangle), + Tool::Mask => Some(AnnotationType::Mask), + Tool::Circle => Some(AnnotationType::Circle), + Tool::Text => Some(AnnotationType::Text), + } + } +} + +/// `ANNOTATION_TYPE_ICONS` (`LayersPanel.tsx:14-21`). +pub fn annotation_icon(annotation_type: AnnotationType) -> &'static str { + match annotation_type { + AnnotationType::Arrow => "icons/arrow-up-right.svg", + AnnotationType::Rectangle => "icons/square.svg", + AnnotationType::Circle => "icons/circle.svg", + AnnotationType::Mask => "icons/eye-off.svg", + AnnotationType::Text => "icons/type.svg", + AnnotationType::Draw => "icons/pencil.svg", + } +} + +/// `ANNOTATION_TYPE_LABELS` (`LayersPanel.tsx:23-30`). +pub fn annotation_label(annotation_type: AnnotationType) -> &'static str { + match annotation_type { + AnnotationType::Arrow => "Arrow", + AnnotationType::Rectangle => "Rectangle", + AnnotationType::Circle => "Circle", + AnnotationType::Mask => "Mask", + AnnotationType::Text => "Text", + AnnotationType::Draw => "Draw", + } +} + +/// `getTypeLabel` (`LayersPanel.tsx:53-60`): a text annotation shows its own +/// content, truncated at twelve characters. +pub fn layer_label(annotation: &Annotation) -> String { + if annotation.annotation_type == AnnotationType::Text + && let Some(text) = annotation.text.as_ref().filter(|text| !text.is_empty()) + { + let mut chars = text.chars(); + let head: String = chars.by_ref().take(12).collect(); + return if chars.next().is_some() { + format!("{head}...") + } else { + head + }; + } + annotation_label(annotation.annotation_type).to_string() +} + +// --------------------------------------------------------------------------- +// Frame-space geometry (`layout.ts`) +// --------------------------------------------------------------------------- + +/// `SCREEN_MAX_PADDING` (`layout.ts:3`), and the same constant the renderer +/// scales padding by. +pub const SCREEN_MAX_PADDING: f64 = 0.4; + +/// A rectangle in frame space. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Rect { + pub x: f64, + pub y: f64, + pub width: f64, + pub height: f64, +} + +/// `calculateImageTransform`'s return: where the screenshot's content lands +/// inside the padded frame. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ImageTransform { + pub offset: (f64, f64), + pub size: (f64, f64), +} + +/// `roundBaseDimension` (`layout.ts:5-6`). The `& ~1` is a 32-bit bitwise op +/// in the source, so the intermediate is an integer here too. +fn round_base_dimension(value: f64) -> f64 { + (((value.ceil() as i64) + 1) & !1).max(2) as f64 +} + +/// `roundAutoBaseDimension` (`layout.ts:8`). +fn round_auto_base_dimension(value: f64) -> f64 { + (((value.floor() as i64) + 1) & !1) as f64 +} + +/// `getAspectRatioValue` (`layout.ts:10-23`). +fn aspect_ratio_value(aspect: &AspectRatio) -> f64 { + match aspect { + AspectRatio::Wide => 16. / 9., + AspectRatio::Vertical => 9. / 16., + AspectRatio::Square => 1., + AspectRatio::Classic => 4. / 3., + AspectRatio::Tall => 3. / 4., + } +} + +/// `getBaseSize` (`layout.ts:25-58`). +fn base_size( + crop_width: f64, + crop_height: f64, + padding_factor: f64, + aspect: Option<&AspectRatio>, +) -> (f64, f64) { + let Some(aspect) = aspect else { + let scale = 1. + padding_factor * 2.; + return ( + round_auto_base_dimension(crop_width * scale), + round_auto_base_dimension(crop_height * scale), + ); + }; + + let crop_aspect = crop_width / crop_height; + let target_aspect = aspect_ratio_value(aspect); + let padding = crop_width.max(crop_height) * padding_factor * 2.; + + if crop_aspect > target_aspect { + let width = crop_width + padding; + let height = width / target_aspect; + return (round_base_dimension(width), round_base_dimension(height)); + } + + let height = crop_height + padding; + let width = height * target_aspect; + (round_base_dimension(width), round_base_dimension(height)) +} + +/// `calculateImageTransform` (`layout.ts:60-148`), verbatim. +/// +/// `frame` is the rendered frame's size, `image` the original PNG's. `padding` +/// is the config's 0..100 slider, not a pixel count. +pub fn calculate_image_transform( + frame: (f64, f64), + image: (f64, f64), + padding: f64, + crop: Option<&Crop>, + aspect: Option<&AspectRatio>, +) -> ImageTransform { + let crop_width = crop.map_or(image.0, |crop| crop.size.x as f64); + let crop_height = crop.map_or(image.1, |crop| crop.size.y as f64); + + if frame.0 <= 0. || frame.1 <= 0. || crop_width <= 0. || crop_height <= 0. { + return ImageTransform { + offset: (0., 0.), + size: (frame.0.max(0.), frame.1.max(0.)), + }; + } + + let cropped_aspect = crop_width / crop_height; + let output_aspect = frame.0 / frame.1; + + let padding_factor = (padding / 100.) * SCREEN_MAX_PADDING; + let base = base_size(crop_width, crop_height, padding_factor, aspect); + let output_scale = (frame.0 / base.0.max(1.)).min(frame.1 / base.1.max(1.)); + + if aspect.is_none() { + let offset_x = crop_width * padding_factor * output_scale; + let offset_y = crop_height * padding_factor * output_scale; + return ImageTransform { + offset: (offset_x, offset_y), + size: ( + (frame.0 - offset_x * 2.).max(1.), + (frame.1 - offset_y * 2.).max(1.), + ), + }; + } + + let crop_basis = crop_width.max(crop_height); + let max_padding = ((frame.0 - 1.) / 2.).min((frame.1 - 1.) / 2.).max(0.); + let padding_pixels = (crop_basis * padding_factor * output_scale).min(max_padding); + + let available_width = (frame.0 - 2. * padding_pixels).max(1.); + let available_height = (frame.1 - 2. * padding_pixels).max(1.); + + let height_constrained = cropped_aspect <= output_aspect; + let (target_width, target_height) = if height_constrained { + (available_height * cropped_aspect, available_height) + } else { + (available_width, available_width / cropped_aspect) + }; + + let target_offset_x = (frame.0 - target_width) / 2.; + let target_offset_y = (frame.1 - target_height) / 2.; + + ImageTransform { + offset: ( + if height_constrained { + target_offset_x + } else { + padding_pixels + }, + if height_constrained { + padding_pixels + } else { + target_offset_y + }, + ), + size: (target_width, target_height), + } +} + +/// `getImageRect` (`layout.ts:150-180`): the transform as a rectangle, with the +/// whole frame as the fallback when the image's size is not known yet. +pub fn image_rect( + frame: (f64, f64), + image: Option<(f64, f64)>, + padding: f64, + crop: Option<&Crop>, + aspect: Option<&AspectRatio>, +) -> Rect { + let Some(image) = image else { + return Rect { + x: 0., + y: 0., + width: frame.0, + height: frame.1, + }; + }; + let transform = calculate_image_transform(frame, image, padding, crop, aspect); + Rect { + x: transform.offset.0, + y: transform.offset.1, + width: transform.size.0, + height: transform.size.1, + } +} + +/// The context's resize effect (`context.tsx:474-547`): when the frame changes +/// size by more than a pixel, every annotation is carried across from the old +/// content rect to the new one so it keeps pointing at the same pixel of the +/// screenshot. +pub fn rescale_annotations( + annotations: &mut [Annotation], + previous: &ImageTransform, + current: &ImageTransform, +) { + if annotations.is_empty() || previous.size.0 <= 0. || previous.size.1 <= 0. { + return; + } + let scale_x = current.size.0 / previous.size.0; + let scale_y = current.size.1 / previous.size.1; + + for annotation in annotations { + let relative_x = annotation.x - previous.offset.0; + let relative_y = annotation.y - previous.offset.1; + annotation.x = current.offset.0 + relative_x * scale_x; + annotation.y = current.offset.1 + relative_y * scale_y; + annotation.width *= scale_x; + annotation.height *= scale_y; + } +} + +// --------------------------------------------------------------------------- +// Arrow heads (`arrow.ts`) +// --------------------------------------------------------------------------- + +/// `getArrowHeadSize` (`arrow.ts:1-6`) -- `(length, width)`. +pub fn arrow_head_size(stroke_width: f64) -> (f64, f64) { + let width = stroke_width.max(1.); + ((width * 6.).max(20.), (width * 5.).max(14.)) +} + +/// `getArrowHeadPoints` (`arrow.ts:8-30`). +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ArrowHead { + pub base: (f64, f64), + pub length: f64, + pub width: f64, + /// Tip, then the two flanks. + pub points: [(f64, f64); 3], +} + +pub fn arrow_head_points(end_x: f64, end_y: f64, angle: f64, stroke_width: f64) -> ArrowHead { + let (length, width) = arrow_head_size(stroke_width); + let base_x = end_x - length * angle.cos(); + let base_y = end_y - length * angle.sin(); + let offset_x = (width / 2.) * angle.sin(); + let offset_y = (width / 2.) * -angle.cos(); + + ArrowHead { + base: (base_x, base_y), + length, + width, + points: [ + (end_x, end_y), + (base_x + offset_x, base_y + offset_y), + (base_x - offset_x, base_y - offset_y), + ], + } +} + +// --------------------------------------------------------------------------- +// Freehand paths (`AnnotationLayer.tsx:795-811`) +// --------------------------------------------------------------------------- + +/// One `d` command of a smoothed freehand stroke. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum PathSegment { + Move { + x: f64, + y: f64, + }, + /// `Q control end` -- the control point is the sample, the end point the + /// midpoint to the next sample. + Quad { + cx: f64, + cy: f64, + x: f64, + y: f64, + }, + Line { + x: f64, + y: f64, + }, +} + +/// `smoothPathFromPoints`: a move to the first sample, a quadratic through +/// every midpoint, and a straight run out to the last sample. +pub fn smooth_path(points: &[[f64; 2]]) -> Vec { + if points.len() < 2 { + return Vec::new(); + } + if points.len() == 2 { + return vec![ + PathSegment::Move { + x: points[0][0], + y: points[0][1], + }, + PathSegment::Line { + x: points[1][0], + y: points[1][1], + }, + ]; + } + + let mut segments = Vec::with_capacity(points.len() + 1); + segments.push(PathSegment::Move { + x: points[0][0], + y: points[0][1], + }); + for pair in points.windows(2) { + let (previous, next) = (pair[0], pair[1]); + segments.push(PathSegment::Quad { + cx: previous[0], + cy: previous[1], + x: (previous[0] + next[0]) / 2., + y: (previous[1] + next[1]) / 2., + }); + } + let last = points[points.len() - 1]; + segments.push(PathSegment::Line { + x: last[0], + y: last[1], + }); + segments +} + +// --------------------------------------------------------------------------- +// The engine's own geometry +// --------------------------------------------------------------------------- + +/// `#F05656` -- what every new annotation strokes with +/// (`AnnotationLayer.tsx:215-218`). +const DEFAULT_STROKE: &str = "#F05656"; +/// `strokeWidth: 4` (`:219`). +const DEFAULT_STROKE_WIDTH: f64 = 4.; +/// `fillColor: "transparent"` -- the sentinel a mask strokes with too, and the +/// one `ColorPickerButton` draws a checkerboard for. +const TRANSPARENT: &str = "transparent"; +/// `handleSize` (`:611-614`): ten **screen** pixels, expressed in frame units +/// at the zoom in force. +const HANDLE_SCREEN_SIZE: f64 = 10.; +/// The selection blue -- `#3b82f6`, hardcoded in the source rather than themed. +const SELECTION_BLUE: u32 = 0x3b82f6; +/// A creation smaller than this in both axes is discarded on mouse-up +/// (`:498`), and a mask thinner than this on either axis is deleted by the +/// clamp effect (`:118`). +const MIN_SIZE: f64 = 5.; +/// A text annotation's defaults (`:229-232`). +const TEXT_DEFAULT_SIZE: f64 = 40.; +const TEXT_DEFAULT_WIDTH: f64 = 150.; +/// `maskLevel: 7` (`:225`), and the `?? 16` every reader falls back to +/// (`Preview.tsx:634`). +const MASK_DEFAULT_LEVEL: f64 = 7.; +const MASK_FALLBACK_LEVEL: f64 = 16.; +/// The layers list's row pitch: a `size-6` chip inside `py-1.5` +/// (`LayersPanel.tsx:262`). +const LAYER_ROW_HEIGHT: f32 = 36.; +/// The font the renderer resolves `sans-serif` to (`layers/mod.rs:42`), so the +/// preview and the export agree on what a text annotation looks like. +pub(crate) const TEXT_FONT: &str = "Helvetica"; + +/// A `getBoundingClientRect` stand-in: an element's window-space rect, written +/// at prepaint. The same shape `screenshot_editor`'s anchors use. +type BoundsCell = Rc>>>; + +/// `clampValue` (`AnnotationLayer.tsx:68-69`) -- `Math.min(Math.max(v, min), +/// max)`, so a window narrower than the value collapses to `max`. +pub fn clamp_value(value: f64, min: f64, max: f64) -> f64 { + value.max(min).min(max) +} + +/// The rectangle an annotation covers once its negative extents are folded in +/// -- `RenderAnnotation`'s `Math.min` / `Math.abs` pairs (`:818-827`). +pub fn normalized_rect(annotation: &Annotation) -> Rect { + Rect { + x: annotation.x.min(annotation.x + annotation.width), + y: annotation.y.min(annotation.y + annotation.height), + width: annotation.width.abs(), + height: annotation.height.abs(), + } +} + +/// The eight (or four, or two) grips `SelectionHandles` draws (`:935-1039`). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Handle { + Nw, + N, + Ne, + W, + E, + Sw, + S, + Se, + /// An arrow's tail. + Start, + /// An arrow's tip. + End, +} + +impl Handle { + /// `state.handle.includes("w")` and friends (`:375-384`). + fn west(self) -> bool { + matches!(self, Handle::Nw | Handle::W | Handle::Sw) + } + + fn east(self) -> bool { + matches!(self, Handle::Ne | Handle::E | Handle::Se) + } + + fn north(self) -> bool { + matches!(self, Handle::Nw | Handle::N | Handle::Ne) + } + + fn south(self) -> bool { + matches!(self, Handle::Sw | Handle::S | Handle::Se) + } + + /// `cursor={`${handle.id}-resize`}` (`:1028`), and `crosshair` for the two + /// an arrow carries (`:988`). + fn cursor(self) -> CursorStyle { + match self { + Handle::Nw | Handle::Se => CursorStyle::ResizeUpLeftDownRight, + Handle::Ne | Handle::Sw => CursorStyle::ResizeUpRightDownLeft, + Handle::N | Handle::S => CursorStyle::ResizeUpDown, + Handle::W | Handle::E => CursorStyle::ResizeLeftRight, + Handle::Start | Handle::End => CursorStyle::Crosshair, + } + } +} + +/// `selectionRect` (`:947-956`): the shape's own box, padded by 30 % of a +/// handle for text so the wash clears the glyphs. +pub fn selection_rect(annotation: &Annotation, handle_size: f64) -> Rect { + let padding = if annotation.annotation_type == AnnotationType::Text { + handle_size * 0.3 + } else { + 0. + }; + let rect = normalized_rect(annotation); + Rect { + x: rect.x - padding, + y: rect.y - padding, + width: rect.width + padding * 2., + height: rect.height + padding * 2., + } +} + +/// `cornerHandles` (`:958-977`) placed on the selection rect: eight for a +/// shape, the four corners for text, and the two endpoints for an arrow. +pub fn handles(annotation: &Annotation, handle_size: f64) -> Vec<(Handle, (f64, f64))> { + if annotation.annotation_type == AnnotationType::Arrow { + return vec![ + (Handle::Start, (annotation.x, annotation.y)), + ( + Handle::End, + ( + annotation.x + annotation.width, + annotation.y + annotation.height, + ), + ), + ]; + } + + let rect = selection_rect(annotation, handle_size); + let corners: &[(Handle, f64, f64)] = if annotation.annotation_type == AnnotationType::Text { + &[ + (Handle::Nw, 0., 0.), + (Handle::Ne, 1., 0.), + (Handle::Sw, 0., 1.), + (Handle::Se, 1., 1.), + ] + } else { + &[ + (Handle::Nw, 0., 0.), + (Handle::N, 0.5, 0.), + (Handle::Ne, 1., 0.), + (Handle::W, 0., 0.5), + (Handle::E, 1., 0.5), + (Handle::Sw, 0., 1.), + (Handle::S, 0.5, 1.), + (Handle::Se, 1., 1.), + ] + }; + corners + .iter() + .map(|(handle, fraction_x, fraction_y)| { + ( + *handle, + ( + rect.x + fraction_x * rect.width, + rect.y + fraction_y * rect.height, + ), + ) + }) + .collect() +} + +/// The grip under a pointer, if any. The handles are ``, so +/// the hit area is the circle itself. +pub fn hit_handle(annotation: &Annotation, point: (f64, f64), handle_size: f64) -> Option { + let radius = handle_size / 2.; + handles(annotation, handle_size) + .into_iter() + .find(|(_, (x, y))| { + let (dx, dy) = (point.0 - x, point.1 - y); + dx * dx + dy * dy <= radius * radius + }) + .map(|(handle, _)| handle) +} + +/// The annotation under a pointer, topmost first -- later siblings paint over +/// earlier ones (`:649-768`), so the hit test walks the list backwards. +pub fn hit_test(annotations: &[Annotation], point: (f64, f64), handle_size: f64) -> Option { + annotations + .iter() + .enumerate() + .rev() + .find(|(_, annotation)| hit_annotation(annotation, point, handle_size)) + .map(|(index, _)| index) +} + +/// The hit test the SVG performs. Every shape sits inside a `` that sets +/// `pointer-events: all` (`:655-658`), and that value is inherited: a shape is +/// a target over its **interior or its perimeter, whatever `fill` and `stroke` +/// say**. So a `fill="transparent"` rectangle is hit across its face, a +/// `fill="none"` stroke is hit inside the region it encloses as well as along +/// its line, and the mask's invisible rect takes a press anywhere inside it. +pub fn hit_annotation(annotation: &Annotation, point: (f64, f64), handle_size: f64) -> bool { + let stroke = (annotation.stroke_width / 2.).max(0.); + match annotation.annotation_type { + AnnotationType::Rectangle | AnnotationType::Mask => { + let rect = normalized_rect(annotation); + point.0 >= rect.x - stroke + && point.0 <= rect.x + rect.width + stroke + && point.1 >= rect.y - stroke + && point.1 <= rect.y + rect.height + stroke + } + AnnotationType::Circle => { + // SVG disables rendering for `rx="0"`/`ry="0"`, and an unrendered + // element is not a pointer target however permissive + // `pointer-events` is. + if annotation.width == 0. || annotation.height == 0. { + return false; + } + let center = ( + annotation.x + annotation.width / 2., + annotation.y + annotation.height / 2., + ); + let radius_x = (annotation.width / 2.).abs() + stroke; + let radius_y = (annotation.height / 2.).abs() + stroke; + if radius_x <= 0. || radius_y <= 0. { + return false; + } + let dx = (point.0 - center.0) / radius_x; + let dy = (point.1 - center.1) / radius_y; + dx * dx + dy * dy <= 1. + } + AnnotationType::Text => { + // The invisible hover rect, which is what a press actually lands on + // (`:738-750`). + let rect = selection_rect(annotation, handle_size); + point.0 >= rect.x + && point.0 <= rect.x + rect.width + && point.1 >= rect.y + && point.1 <= rect.y + rect.height + } + AnnotationType::Arrow => { + let end = ( + annotation.x + annotation.width, + annotation.y + annotation.height, + ); + let angle = (end.1 - annotation.y).atan2(end.0 - annotation.x); + let head = arrow_head_points(end.0, end.1, angle, annotation.stroke_width); + let tolerance = stroke.max(2.); + distance_to_segment(point, (annotation.x, annotation.y), head.base) <= tolerance + || point_in_triangle(point, head.points) + } + AnnotationType::Draw => { + let points = draw_points(annotation); + if points.len() < 2 { + return false; + } + let tolerance = stroke.max(2.); + // The line itself, and -- because `pointer-events: all` ignores + // `fill="none"` -- whatever the path would have enclosed. Without + // the second half a scribble is only selectable within half a + // stroke of its own line, which at 50 % zoom is about a pixel. + points.windows(2).any(|pair| { + distance_to_segment(point, (pair[0][0], pair[0][1]), (pair[1][0], pair[1][1])) + <= tolerance + }) || point_in_polygon(point, &points) + } + } +} + +/// A stroke's samples in frame space -- `RenderAnnotation`'s draw branch +/// (`:915-921`), including its `|| 1` guard against a zero-width box. +pub fn draw_points(annotation: &Annotation) -> Vec<[f64; 2]> { + let width = if annotation.width == 0. { + 1. + } else { + annotation.width + }; + let height = if annotation.height == 0. { + 1. + } else { + annotation.height + }; + annotation + .points + .as_deref() + .unwrap_or_default() + .iter() + .map(|point| { + [ + annotation.x + point[0] * width, + annotation.y + point[1] * height, + ] + }) + .collect() +} + +/// `handleMouseUp`'s draw branch (`:449-453`): the samples become fractions of +/// the stroke's own bounding box, so a later resize carries them along. +pub fn normalize_draw_points( + points: &[[f64; 2]], + x: f64, + y: f64, + width: f64, + height: f64, +) -> Vec<[f64; 2]> { + let width = if width == 0. { 1. } else { width }; + let height = if height == 0. { 1. } else { height }; + points + .iter() + .map(|point| [(point[0] - x) / width, (point[1] - y) / height]) + .collect() +} + +/// A resize that dragged a stroke inside out (`:544-556`): the box is +/// un-negated and the normalized samples mirror with it. +pub fn flip_draw_points(points: &[[f64; 2]], flip_x: bool, flip_y: bool) -> Vec<[f64; 2]> { + points + .iter() + .map(|point| { + [ + if flip_x { 1. - point[0] } else { point[0] }, + if flip_y { 1. - point[1] } else { point[1] }, + ] + }) + .collect() +} + +/// What the auto-clamp effect (`:88-141`) decided about one mask. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum MaskClamp { + /// Already inside the image rect. + Unchanged, + /// Smaller than 5px on an axis once clipped: delete it. + Remove, + /// Move it here. + Clamped(Rect), +} + +/// Clip one mask into the screenshot's content rect. +pub fn clamp_mask(annotation: &Annotation, rect: Rect) -> MaskClamp { + let (right_edge, bottom_edge) = (rect.x + rect.width, rect.y + rect.height); + let raw_left = annotation.x.min(annotation.x + annotation.width); + let raw_top = annotation.y.min(annotation.y + annotation.height); + let left = clamp_value(raw_left, rect.x, right_edge); + let right = clamp_value( + annotation.x.max(annotation.x + annotation.width), + rect.x, + right_edge, + ); + let top = clamp_value(raw_top, rect.y, bottom_edge); + let bottom = clamp_value( + annotation.y.max(annotation.y + annotation.height), + rect.y, + bottom_edge, + ); + let width = (right - left).max(0.); + let height = (bottom - top).max(0.); + + if width < MIN_SIZE || height < MIN_SIZE { + return MaskClamp::Remove; + } + if left != raw_left + || top != raw_top + || width != annotation.width.abs() + || height != annotation.height.abs() + { + return MaskClamp::Clamped(Rect { + x: left, + y: top, + width, + height, + }); + } + MaskClamp::Unchanged +} + +/// `finalizeDrag` (`LayersPanel.tsx:93-124`): the panel lists the annotations +/// reversed, so a drop index in that list has to be walked back through +/// `getActualIndex`, and a drop *below* the dragged row loses one slot to the +/// row's own removal. +pub fn reorder_move(len: usize, dragged_reversed: usize, target: usize) -> Option<(usize, usize)> { + if len == 0 || dragged_reversed >= len || dragged_reversed == target { + return None; + } + let actual = |reversed: usize| len - 1 - reversed.min(len - 1); + let from = actual(dragged_reversed); + let to = if target > dragged_reversed { + actual(target.saturating_sub(1)) + } else { + actual(target) + }; + (from != to).then_some((from, to)) +} + +/// Whether a drag actually moved anything -- the "only record if something +/// changed" half of the snapshot semantics. +fn geometry_changed(before: &Annotation, after: &Annotation) -> bool { + before.x != after.x + || before.y != after.y + || before.width != after.width + || before.height != after.height + || before.points != after.points +} + +fn distance_to_segment(point: (f64, f64), start: (f64, f64), end: (f64, f64)) -> f64 { + let (dx, dy) = (end.0 - start.0, end.1 - start.1); + let length_squared = dx * dx + dy * dy; + let t = if length_squared <= f64::EPSILON { + 0. + } else { + (((point.0 - start.0) * dx + (point.1 - start.1) * dy) / length_squared).clamp(0., 1.) + }; + let closest = (start.0 + t * dx, start.1 + t * dy); + ((point.0 - closest.0).powi(2) + (point.1 - closest.1).powi(2)).sqrt() +} + +/// SVG's default `fill-rule: nonzero`, over a stroke's samples closed back on +/// themselves -- the region a `fill="none"` path still counts as its interior +/// for hit testing. The winding number, rather than an even-odd crossing +/// count, so a scribble that loops over itself stays solid. +fn point_in_polygon(point: (f64, f64), points: &[[f64; 2]]) -> bool { + if points.len() < 3 { + return false; + } + // Which side of `a -> b` the point falls on. + let side = |a: [f64; 2], b: [f64; 2]| { + (b[0] - a[0]) * (point.1 - a[1]) - (point.0 - a[0]) * (b[1] - a[1]) + }; + let mut winding = 0i32; + for index in 0..points.len() { + let a = points[index]; + let b = points[(index + 1) % points.len()]; + if a[1] <= point.1 { + if b[1] > point.1 && side(a, b) > 0. { + winding += 1; + } + } else if b[1] <= point.1 && side(a, b) < 0. { + winding -= 1; + } + } + winding != 0 +} + +fn point_in_triangle(point: (f64, f64), triangle: [(f64, f64); 3]) -> bool { + let sign = |a: (f64, f64), b: (f64, f64), c: (f64, f64)| { + (a.0 - c.0) * (b.1 - c.1) - (b.0 - c.0) * (a.1 - c.1) + }; + let d1 = sign(point, triangle[0], triangle[1]); + let d2 = sign(point, triangle[1], triangle[2]); + let d3 = sign(point, triangle[2], triangle[0]); + let negative = d1 < 0. || d2 < 0. || d3 < 0.; + let positive = d1 > 0. || d2 > 0. || d3 > 0.; + !(negative && positive) +} + +/// A CSS colour string as RGB bytes plus a resolved 0..1 alpha, with the +/// annotation's `globalAlpha` folded in. `transparent` -- the sentinel a mask +/// strokes with and an unfilled shape fills with -- paints nothing at all, so +/// it comes back as `None`. Shared between the preview's gpui paint and the +/// export's tiny-skia paint so both resolve a colour identically. +pub(crate) fn parse_css_rgba(value: &str, opacity: f64) -> Option<([u8; 3], f32)> { + let value = value.trim(); + if value.is_empty() + || value.eq_ignore_ascii_case(TRANSPARENT) + || value.eq_ignore_ascii_case("none") + { + return None; + } + let rgba = hex_to_rgb(value)?; + let alpha = (f32::from(rgba[3]) / 255.) * opacity.clamp(0., 1.) as f32; + Some(([rgba[0], rgba[1], rgba[2]], alpha)) +} + +/// A CSS colour string as gpui takes it. +fn annotation_color(value: &str, opacity: f64) -> Option { + let (rgb, alpha) = parse_css_rgba(value, opacity)?; + Some(Hsla::from(gpui::Rgba { + r: f32::from(rgb[0]) / 255., + g: f32::from(rgb[1]) / 255., + b: f32::from(rgb[2]) / 255., + a: alpha, + })) +} + +// --------------------------------------------------------------------------- +// Engine state +// --------------------------------------------------------------------------- + +/// One live gesture over the preview. +enum Gesture { + /// A tool drawing a new annotation (`isDrawing` + `tempAnnotation`). + Create(Creating), + /// `dragState.action === "move"`. + Move(Dragging), + /// `dragState.action === "resize"`. + Resize(Dragging), +} + +struct Creating { + temp: Annotation, + /// A mask joins the live list the moment its drag starts (`:236-238`), so + /// its blur preview follows the pointer. + in_list: bool, +} + +struct Dragging { + id: String, + handle: Option, + start: (f64, f64), + /// `dragState.original` -- every frame of the drag is computed from the + /// state the press found, never from the previous frame. + original: Annotation, +} + +/// The inline text editor's bracket: which annotation, and what it said when +/// the editor opened (`textSnapshot`, `:703-708`). +struct TextEdit { + id: String, + original: String, +} + +/// A live layer-reorder drag (`LayersPanel.tsx:43-51`). +struct Reorder { + id: String, + /// `dropTargetIndex`, in the reversed list's coordinates. + target: Option, + /// The list length the drag started against. The source cancels outright + /// when the count changes underneath it (`LayersPanel.tsx:192-200`), which + /// an undo mid-drag can do. + count: usize, +} + +/// `ColorPickerButton`'s two call sites (`AnnotationConfig.tsx:47-79`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum ColorTarget { + Stroke, + Fill, +} + +impl ColorTarget { + const ALL: [ColorTarget; 2] = [ColorTarget::Stroke, ColorTarget::Fill]; + + fn id(self) -> &'static str { + match self { + Self::Stroke => "screenshot-annotation-stroke", + Self::Fill => "screenshot-annotation-fill", + } + } + + /// Only Fill offers the checkerboard swatch (`allowTransparent`, `:76`). + fn allows_transparent(self) -> bool { + self == Self::Fill + } +} + +/// The config bar's sliders. A parallel of `screenshot_editor`'s `StyleSlider` +/// -- same `ui::Slider` plumbing, same pause/resume bracket -- kept separate +/// because these read and write the *selected annotation*, not the project's +/// background. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum AnnotationSlider { + Width, + Opacity, + MaskLevel, + TextSize, +} + +impl AnnotationSlider { + const ALL: [AnnotationSlider; 4] = [ + AnnotationSlider::Width, + AnnotationSlider::Opacity, + AnnotationSlider::MaskLevel, + AnnotationSlider::TextSize, + ]; + + /// `(min, max, step)` -- `AnnotationConfig.tsx:60-157`. + fn range(self) -> (f32, f32, f32) { + match self { + Self::Width => (1., 20., 1.), + Self::Opacity => (0.1, 1., 0.1), + Self::MaskLevel => (4., 50., 1.), + Self::TextSize => (12., 100., 1.), + } + } + + fn id(self) -> &'static str { + match self { + Self::Width => "width", + Self::Opacity => "opacity", + Self::MaskLevel => "mask-level", + Self::TextSize => "text-size", + } + } + + fn value(self, annotation: &Annotation) -> f32 { + match self { + Self::Width => annotation.stroke_width as f32, + Self::Opacity => annotation.opacity as f32, + Self::MaskLevel => annotation.mask_level.unwrap_or(MASK_FALLBACK_LEVEL) as f32, + Self::TextSize => annotation.height as f32, + } + } + + fn apply(self, annotation: &mut Annotation, value: f32) -> bool { + let value = value as f64; + let field = match self { + Self::Width => &mut annotation.stroke_width, + Self::Opacity => &mut annotation.opacity, + Self::TextSize => &mut annotation.height, + Self::MaskLevel => { + let level = annotation.mask_level.get_or_insert(MASK_FALLBACK_LEVEL); + if *level == value { + return false; + } + *level = value; + return true; + } + }; + if *field == value { + return false; + } + *field = value; + true + } +} + +/// One blurred or pixelated region, ready to paint between the frame and the +/// annotations -- `renderMaskOverlays`' second canvas (`Preview.tsx:742-758`). +#[derive(Clone)] +struct MaskOverlay { + /// The region in frame space, snapped to whole frame pixels because that + /// is what was resampled. + rect: Rect, + image: Arc, +} + +/// Everything the annotation engine keeps that is not the annotations +/// themselves. One field on the window rather than fifteen. +#[derive(Default)] +pub struct AnnotationState { + /// The layer's own window-space rect -- the preview's content wrapper, + /// which is what maps a pointer into frame space. + layer: BoundsCell, + gesture: Option, + /// The annotation the pointer is over, for the text hover wash. + hover: Option, + hover_cursor: Option, + /// `copiedAnnotation` (`Editor.tsx:53-54`). + clipboard: Option, + editing: Option, + text_input: Option>, + /// The open swatch, and the annotation it was opened for: a selection + /// change closes it rather than repointing it. + color_popover: Option<(ColorTarget, String)>, + color_anchors: HashMap, + color_inputs: HashMap>, + slider_tracks: HashMap, + active_slider: Option, + /// The layers list's rows container, for the reorder drag's hit testing. + list: BoundsCell, + reorder: Option, + masks: Vec, + /// The inputs the painted overlays were computed from, and the ones a + /// computation in flight is for. + mask_key: u64, + mask_pending: Option, + mask_task: Option>, +} + +/// One paint of the layer, copied out of the window so the closure owns it. +/// Cloning the list is the cost of a `'static` paint closure; a screenshot +/// carries a handful of annotations, not a timeline's worth. +struct LayerPaint { + annotations: Vec, + temp: Option, + masks: Vec, + selected: Option, + editing: Option, + hover: Option, + select_tool: bool, + scale: f32, + handle_size: f64, +} + +// --------------------------------------------------------------------------- +// The window's seam +// --------------------------------------------------------------------------- + +impl ScreenshotEditorWindow { + /// The text field the inline editor drives, and the two hex fields the + /// colour popover carries. Entities need a `Window`, so they are minted in + /// `ScreenshotEditorWindow::new` the way the styling popovers' are. + pub(crate) fn init_annotation_inputs(&mut self, window: &mut Window, cx: &mut Context) { + let text = cx.new(|cx| ui::TextInputState::single_line(window, cx)); + let subscription = cx.subscribe_in( + &text, + window, + move |this: &mut Self, _input, event: &ui::TextInputEvent, window, cx| { + this.on_annotation_text_event(event, window, cx); + }, + ); + self.text_subscriptions.push(subscription); + self.annotation_state.text_input = Some(text); + + for target in ColorTarget::ALL { + let input = cx.new(|cx| ui::TextInputState::single_line(window, cx)); + let subscription = cx.subscribe_in( + &input, + window, + move |this: &mut Self, _input, event: &ui::TextInputEvent, window, cx| { + this.on_annotation_color_event(target, event, window, cx); + }, + ); + self.text_subscriptions.push(subscription); + self.annotation_state.color_inputs.insert(target, input); + self.annotation_state + .color_anchors + .insert(target, BoundsCell::default()); + } + + for slider in AnnotationSlider::ALL { + self.annotation_state + .slider_tracks + .insert(slider, ui::SliderTrack::default()); + } + } + + // -- Frame-space plumbing ------------------------------------------------- + + /// Screen pixels per frame pixel -- `cssWidth / bounds.width` + /// (`AnnotationLayer.tsx:613`), read off the layer's painted rect so it + /// needs no zoom bookkeeping of its own. + fn annotation_scale(&self) -> Option { + let bounds = self.annotation_state.layer.get()?; + let width = f32::from(bounds.size.width); + (width > 0. && self.frame_size.0 > 0.).then(|| width / self.frame_size.0) + } + + /// `handleSize` in frame units. + fn handle_size(&self) -> f64 { + HANDLE_SCREEN_SIZE / self.annotation_scale().unwrap_or(1.).max(0.001) as f64 + } + + /// `getSvgPoint` (`:144-156`). The viewBox's origin is always `0 0` here, + /// so the mapping is the layer's rect and the scale. + fn frame_point(&self, position: Point) -> Option<(f64, f64)> { + let bounds = self.annotation_state.layer.get()?; + let scale = self.annotation_scale()?; + Some(( + f64::from(f32::from(position.x - bounds.origin.x) / scale), + f64::from(f32::from(position.y - bounds.origin.y) / scale), + )) + } + + /// `getImageRect(...)` for the config in force -- what clips every mask. + pub(crate) fn annotation_image_rect(&self) -> Rect { + image_rect( + (self.frame_size.0 as f64, self.frame_size.1 as f64), + self.image_size + .map(|(width, height)| (width as f64, height as f64)), + self.project.background.padding, + self.project.background.crop.as_ref(), + self.project.aspect_ratio.as_ref(), + ) + } + + fn annotation(&self, id: &str) -> Option<&Annotation> { + self.project + .annotations + .iter() + .find(|annotation| annotation.id == id) + } + + fn annotation_mut(&mut self, id: &str) -> Option<&mut Annotation> { + self.project + .annotations + .iter_mut() + .find(|annotation| annotation.id == id) + } + + fn selected(&self) -> Option<&Annotation> { + self.annotation(self.selected_annotation.as_deref()?) + } + + /// One committed annotation change: the sequence `edit_project` runs -- + /// record, publish, schedule the debounced write -- for a mutation already + /// applied to `self.project`. + fn commit_annotations(&mut self, window: &mut Window, cx: &mut Context) { + self.clamp_masks(); + self.history.record(&self.project); + self.publish(); + self.schedule_save(window, cx); + self.refresh_mask_overlays(window, cx); + cx.notify(); + } + + /// The auto-clamp effect (`AnnotationLayer.tsx:88-141`): every mask is + /// clipped into the screenshot's content rect, and one that clips away to + /// nothing is deleted. Runs where the geometry moves -- a new frame, or an + /// edit to padding / crop / aspect -- not in the paint path. + /// + /// Returns whether anything moved, so the caller can decide whether the + /// change is worth publishing. + pub(crate) fn clamp_masks(&mut self) -> bool { + let rect = self.annotation_image_rect(); + if rect.width <= 0. || rect.height <= 0. { + return false; + } + // The mask being dragged out right now is exempt: it is mid-gesture and + // clamps itself against the same rect on every move (`:91`, `:95`). + let drawing = match &self.annotation_state.gesture { + Some(Gesture::Create(creating)) => Some(creating.temp.id.clone()), + _ => None, + }; + + let mut changed = false; + let mut removed = Vec::new(); + for annotation in &mut self.project.annotations { + if annotation.annotation_type != AnnotationType::Mask { + continue; + } + if drawing.as_deref() == Some(annotation.id.as_str()) { + continue; + } + match clamp_mask(annotation, rect) { + MaskClamp::Unchanged => {} + MaskClamp::Remove => removed.push(annotation.id.clone()), + MaskClamp::Clamped(clamped) => { + annotation.x = clamped.x; + annotation.y = clamped.y; + annotation.width = clamped.width; + annotation.height = clamped.height; + changed = true; + } + } + } + if !removed.is_empty() { + self.project + .annotations + .retain(|annotation| !removed.contains(&annotation.id)); + if let Some(selected) = &self.selected_annotation + && removed.contains(selected) + { + self.selected_annotation = None; + } + changed = true; + } + changed + } +} + +impl ScreenshotEditorWindow { + // -- Pointer -------------------------------------------------------------- + + /// A left press inside the preview. `true` means the annotation layer took + /// it -- a hit on a shape, a handle, or the start of a new drawing. + pub(crate) fn annotation_mouse_down( + &mut self, + event: &MouseDownEvent, + window: &mut Window, + cx: &mut Context, + ) -> bool { + // `handleMouseDown`'s first act (`AnnotationLayer.tsx:159-164`): a press + // outside the open editor commits it; one inside belongs to the field. + if self.annotation_state.editing.is_some() { + if self + .text_editor_bounds() + .is_some_and(|bounds| bounds.contains(&event.position)) + { + return true; + } + self.commit_text_edit(window, cx); + } + + let Some(bounds) = self.annotation_state.layer.get() else { + return false; + }; + if !bounds.contains(&event.position) { + return false; + } + let Some(point) = self.frame_point(event.position) else { + return false; + }; + + if self.tool == Tool::Select { + return self.begin_select_gesture(point, event.click_count, window, cx); + } + self.begin_create(self.tool, point, cx); + true + } + + /// `startDrag` (`:568-596`) and `handleDoubleClick` (`:598-609`). + fn begin_select_gesture( + &mut self, + point: (f64, f64), + click_count: usize, + window: &mut Window, + cx: &mut Context, + ) -> bool { + let handle_size = self.handle_size(); + let selected = self.selected_annotation.clone(); + + // Document order settles every tie. The grips live *inside* the + // selected annotation's own `` (`:760-765`), so they paint -- and + // therefore hit -- over its shape but under any annotation later in the + // list. One top-down walk expresses both. + for index in (0..self.project.annotations.len()).rev() { + let annotation = &self.project.annotations[index]; + if selected.as_deref() == Some(annotation.id.as_str()) + && let Some(handle) = hit_handle(annotation, point, handle_size) + { + let id = annotation.id.clone(); + let original = annotation.clone(); + self.annotation_state.gesture = Some(Gesture::Resize(Dragging { + id, + handle: Some(handle), + start: point, + original, + })); + cx.notify(); + return true; + } + if !hit_annotation(annotation, point, handle_size) { + continue; + } + + let annotation = annotation.clone(); + self.selected_annotation = Some(annotation.id.clone()); + if click_count >= 2 && annotation.annotation_type == AnnotationType::Text { + self.begin_text_edit(annotation.id, window, cx); + return true; + } + self.annotation_state.gesture = Some(Gesture::Move(Dragging { + id: annotation.id.clone(), + handle: None, + start: point, + original: annotation, + })); + cx.notify(); + return true; + } + false + } + + /// `handleMouseDown`'s tool branch (`:176-238`). + fn begin_create(&mut self, tool: Tool, point: (f64, f64), cx: &mut Context) { + let Some(annotation_type) = tool.annotation_type() else { + return; + }; + let is_mask = annotation_type == AnnotationType::Mask; + let rect = self.annotation_image_rect(); + let (x, y) = if is_mask { + ( + clamp_value(point.0, rect.x, rect.x + rect.width), + clamp_value(point.1, rect.y, rect.y + rect.height), + ) + } else { + point + }; + + // `styleSource` (`:203-207`): a new stroke inherits from the selection, + // or else from the last stroke drawn. + let style = + (annotation_type == AnnotationType::Draw) + .then(|| { + self.selected() + .or_else(|| { + self.project.annotations.iter().rev().find(|annotation| { + annotation.annotation_type == AnnotationType::Draw + }) + }) + .map(|annotation| { + ( + annotation.stroke_color.clone(), + annotation.stroke_width, + annotation.opacity, + ) + }) + }) + .flatten(); + + let is_text = annotation_type == AnnotationType::Text; + let annotation = Annotation { + id: crate::store::new_uuid_v4(), + annotation_type, + x, + y, + width: if is_text { TEXT_DEFAULT_WIDTH } else { 0. }, + height: if is_text { TEXT_DEFAULT_SIZE } else { 0. }, + stroke_color: if is_mask { + TRANSPARENT.to_string() + } else { + style + .as_ref() + .map(|(color, _, _)| color.clone()) + .unwrap_or_else(|| DEFAULT_STROKE.to_string()) + }, + stroke_width: if is_mask { + 0. + } else { + style + .as_ref() + .map_or(DEFAULT_STROKE_WIDTH, |(_, width, _)| *width) + }, + fill_color: TRANSPARENT.to_string(), + opacity: style.as_ref().map_or(1., |(_, _, opacity)| *opacity), + rotation: 0., + text: is_text.then(|| "Text".to_string()), + mask_type: is_mask.then_some(MaskType::Pixelate), + mask_level: is_mask.then_some(MASK_DEFAULT_LEVEL), + points: (annotation_type == AnnotationType::Draw).then(|| vec![[x, y]]), + }; + + if is_mask { + self.project.annotations.push(annotation.clone()); + } + self.annotation_state.gesture = Some(Gesture::Create(Creating { + temp: annotation, + in_list: is_mask, + })); + cx.notify(); + } + + /// A drag move while the annotation layer owns the pointer. + pub(crate) fn annotation_mouse_move( + &mut self, + position: Point, + window: &mut Window, + cx: &mut Context, + ) -> bool { + if self.annotation_state.gesture.is_none() { + return false; + } + let Some(point) = self.frame_point(position) else { + return false; + }; + // `handleMouseMove` reads `e.shiftKey` off the event; gpui's drag layer + // hands the position only, so the modifier comes off the window. + let shift = window.modifiers().shift; + let Some(gesture) = self.annotation_state.gesture.take() else { + return false; + }; + let gesture = match gesture { + Gesture::Create(creating) => Gesture::Create(self.drag_create(creating, point, shift)), + Gesture::Move(dragging) => { + self.drag_move(&dragging, point); + Gesture::Move(dragging) + } + Gesture::Resize(dragging) => { + self.drag_resize(&dragging, point, shift); + Gesture::Resize(dragging) + } + }; + self.annotation_state.gesture = Some(gesture); + self.refresh_mask_overlays(window, cx); + cx.notify(); + true + } + + /// `handleMouseMove`'s drawing branch (`:245-320`). + fn drag_create(&mut self, mut creating: Creating, point: (f64, f64), shift: bool) -> Creating { + let temp = &mut creating.temp; + if temp.annotation_type == AnnotationType::Text { + return creating; + } + + if temp.annotation_type == AnnotationType::Draw { + let points = temp.points.get_or_insert_with(Vec::new); + let last = points.last().copied().unwrap_or([temp.x, temp.y]); + let (dx, dy) = (point.0 - last[0], point.1 - last[1]); + // A 2px floor on the sample spacing (`:254`). + if dx * dx + dy * dy < 4. { + return creating; + } + points.push([point.0, point.1]); + let (mut min_x, mut min_y) = (f64::INFINITY, f64::INFINITY); + let (mut max_x, mut max_y) = (f64::NEG_INFINITY, f64::NEG_INFINITY); + for sample in points.iter() { + min_x = min_x.min(sample[0]); + min_y = min_y.min(sample[1]); + max_x = max_x.max(sample[0]); + max_y = max_y.max(sample[1]); + } + temp.x = min_x; + temp.y = min_y; + temp.width = max_x - min_x; + temp.height = max_y - min_y; + return creating; + } + + let is_mask = temp.annotation_type == AnnotationType::Mask; + let rect = self.annotation_image_rect(); + let (current_x, current_y) = if is_mask { + ( + clamp_value(point.0, rect.x, rect.x + rect.width), + clamp_value(point.1, rect.y, rect.y + rect.height), + ) + } else { + point + }; + + let mut width = current_x - temp.x; + let mut height = current_y - temp.y; + + if temp.annotation_type == AnnotationType::Circle && !shift { + let size = width.abs().max(height.abs()); + width = if width < 0. { -size } else { size }; + height = if height < 0. { -size } else { size }; + } else if shift { + match temp.annotation_type { + AnnotationType::Rectangle | AnnotationType::Mask => { + let size = width.abs().max(height.abs()); + width = if width < 0. { -size } else { size }; + height = if height < 0. { -size } else { size }; + } + AnnotationType::Arrow => { + let quarter = std::f64::consts::FRAC_PI_4; + let snapped = (height.atan2(width) / quarter).round() * quarter; + let distance = (width * width + height * height).sqrt(); + width = snapped.cos() * distance; + height = snapped.sin() * distance; + } + _ => {} + } + } + + temp.width = width; + temp.height = height; + + if creating.in_list { + let temp = creating.temp.clone(); + if let Some(live) = self.annotation_mut(&temp.id) { + *live = temp; + } + } + creating + } + + /// `dragState.action === "move"` (`:328-354`). + fn drag_move(&mut self, dragging: &Dragging, point: (f64, f64)) { + let (dx, dy) = (point.0 - dragging.start.0, point.1 - dragging.start.1); + let rect = self.annotation_image_rect(); + let Some(annotation) = self.annotation_mut(&dragging.id) else { + return; + }; + if annotation.annotation_type == AnnotationType::Mask { + annotation.x = clamp_value( + dragging.original.x + dx, + rect.x, + rect.x + rect.width - annotation.width, + ); + annotation.y = clamp_value( + dragging.original.y + dy, + rect.y, + rect.y + rect.height - annotation.height, + ); + return; + } + annotation.x = dragging.original.x + dx; + annotation.y = dragging.original.y + dy; + } + + /// `dragState.action === "resize"` (`:355-432`), handle by handle. + fn drag_resize(&mut self, dragging: &Dragging, point: (f64, f64), shift: bool) { + let Some(handle) = dragging.handle else { + return; + }; + let (dx, dy) = (point.0 - dragging.start.0, point.1 - dragging.start.1); + let original = &dragging.original; + let mut x = original.x; + let mut y = original.y; + let mut width = original.width; + let mut height = original.height; + + if original.annotation_type == AnnotationType::Arrow { + match handle { + Handle::Start => { + x = original.x + dx; + y = original.y + dy; + width = original.width - dx; + height = original.height - dy; + } + Handle::End => { + width = original.width + dx; + height = original.height + dy; + } + _ => {} + } + } else { + if handle.east() { + width = original.width + dx; + } + if handle.south() { + height = original.height + dy; + } + if handle.west() { + x = original.x + dx; + width = original.width - dx; + } + if handle.north() { + y = original.y + dy; + height = original.height - dy; + } + + // A circle is square unless shift says otherwise; a rectangle is + // square only while shift is held (`:386-405`). + let constrain = (original.annotation_type == AnnotationType::Circle && !shift) + || (original.annotation_type == AnnotationType::Rectangle && shift); + if constrain { + let size = width.abs().max(height.abs()); + let sign_width = if width < 0. { -1. } else { 1. }; + let sign_height = if height < 0. { -1. } else { 1. }; + if handle.west() { + x = original.x + original.width - sign_width * size; + } + if handle.north() { + y = original.y + original.height - sign_height * size; + } + width = sign_width * size; + height = sign_height * size; + } + } + + if original.annotation_type == AnnotationType::Mask { + let rect = self.annotation_image_rect(); + let (right_edge, bottom_edge) = (rect.x + rect.width, rect.y + rect.height); + let left = clamp_value(x.min(x + width), rect.x, right_edge); + let right = clamp_value(x.max(x + width), rect.x, right_edge); + let top = clamp_value(y.min(y + height), rect.y, bottom_edge); + let bottom = clamp_value(y.max(y + height), rect.y, bottom_edge); + x = left; + y = top; + width = (right - left).max(0.); + height = (bottom - top).max(0.); + } + + if let Some(annotation) = self.annotation_mut(&dragging.id) { + annotation.x = x; + annotation.y = y; + annotation.width = width; + annotation.height = height; + } + } + + /// The release that ends an annotation drag -- `handleMouseUp` + /// (`:437-566`). + pub(crate) fn annotation_mouse_up( + &mut self, + window: &mut Window, + cx: &mut Context, + ) -> bool { + let Some(gesture) = self.annotation_state.gesture.take() else { + return false; + }; + match gesture { + Gesture::Create(creating) => self.finish_create(creating, window, cx), + Gesture::Move(dragging) | Gesture::Resize(dragging) => { + self.finish_drag(dragging, window, cx) + } + } + true + } + + fn finish_create(&mut self, creating: Creating, window: &mut Window, cx: &mut Context) { + let mut annotation = creating.temp; + + if annotation.annotation_type == AnnotationType::Draw { + let raw = annotation.points.clone().unwrap_or_default(); + // A tap is not a stroke (`:443-448`). + if raw.len() < 2 { + cx.notify(); + return; + } + annotation.points = Some(normalize_draw_points( + &raw, + annotation.x, + annotation.y, + annotation.width, + annotation.height, + )); + self.selected_annotation = Some(annotation.id.clone()); + self.project.annotations.push(annotation); + // The draw branch returns before `setActiveTool("select")`, so the + // pencil stays armed for the next stroke (`:460`). + self.commit_annotations(window, cx); + return; + } + + if matches!( + annotation.annotation_type, + AnnotationType::Rectangle | AnnotationType::Circle | AnnotationType::Mask + ) { + if annotation.width < 0. { + annotation.x += annotation.width; + annotation.width = annotation.width.abs(); + } + if annotation.height < 0. { + annotation.y += annotation.height; + annotation.height = annotation.height.abs(); + } + if annotation.annotation_type == AnnotationType::Mask { + let rect = self.annotation_image_rect(); + let (right_edge, bottom_edge) = (rect.x + rect.width, rect.y + rect.height); + let left = clamp_value(annotation.x, rect.x, right_edge); + let top = clamp_value(annotation.y, rect.y, bottom_edge); + let right = clamp_value(annotation.x + annotation.width, rect.x, right_edge); + let bottom = clamp_value(annotation.y + annotation.height, rect.y, bottom_edge); + annotation.x = left; + annotation.y = top; + annotation.width = (right - left).max(0.); + annotation.height = (bottom - top).max(0.); + } + // A click that never became a drag draws nothing, and records + // nothing (`:498-506`). + if annotation.width < MIN_SIZE && annotation.height < MIN_SIZE { + if creating.in_list { + self.project + .annotations + .retain(|live| live.id != annotation.id); + self.refresh_mask_overlays(window, cx); + } + cx.notify(); + return; + } + } + + let id = annotation.id.clone(); + let is_text = annotation.annotation_type == AnnotationType::Text; + if creating.in_list { + if let Some(live) = self.annotation_mut(&id) { + *live = annotation; + } + } else { + self.project.annotations.push(annotation); + } + self.tool = Tool::Select; + self.selected_annotation = Some(id.clone()); + self.commit_annotations(window, cx); + + if is_text { + self.begin_text_edit(id, window, cx); + } + } + + fn finish_drag(&mut self, dragging: Dragging, window: &mut Window, cx: &mut Context) { + // A stroke dragged inside out keeps its samples by mirroring them + // (`:531-558`). + if dragging.handle.is_some() + && let Some(annotation) = self.annotation_mut(&dragging.id) + && annotation.annotation_type == AnnotationType::Draw + && (annotation.width < 0. || annotation.height < 0.) + { + let flip_x = annotation.width < 0.; + let flip_y = annotation.height < 0.; + if flip_x { + annotation.x += annotation.width; + } + if flip_y { + annotation.y += annotation.height; + } + annotation.width = annotation.width.abs(); + annotation.height = annotation.height.abs(); + if let Some(points) = annotation.points.as_deref() { + annotation.points = Some(flip_draw_points(points, flip_x, flip_y)); + } + } + + let moved = self + .annotation(&dragging.id) + .is_some_and(|annotation| geometry_changed(&dragging.original, annotation)); + if moved { + self.commit_annotations(window, cx); + } else { + cx.notify(); + } + } + + /// The text hover wash and the resize cursors, which the SVG gets from + /// `:641-647` and `cursor={...}` and gpui has to track itself. + fn annotation_hover( + &mut self, + event: &MouseMoveEvent, + _window: &mut Window, + cx: &mut Context, + ) { + if self.annotation_state.gesture.is_some() { + return; + } + if self.tool != Tool::Select { + self.clear_annotation_hover(cx); + return; + } + let Some(point) = self.frame_point(event.position) else { + return; + }; + let handle_size = self.handle_size(); + let cursor = self + .selected() + .and_then(|annotation| hit_handle(annotation, point, handle_size)) + .map(Handle::cursor); + let hover = hit_test(&self.project.annotations, point, handle_size) + .map(|index| self.project.annotations[index].id.clone()); + if hover != self.annotation_state.hover || cursor != self.annotation_state.hover_cursor { + self.annotation_state.hover = hover; + self.annotation_state.hover_cursor = cursor; + cx.notify(); + } + } + + fn clear_annotation_hover(&mut self, cx: &mut Context) { + if self.annotation_state.hover.take().is_some() + || self.annotation_state.hover_cursor.take().is_some() + { + cx.notify(); + } + } + + // -- Clipboard ------------------------------------------------------------ + + /// `Editor.tsx:72-81` -- Cmd-C over a selected annotation copies it instead + /// of exporting the screenshot. + pub(crate) fn copy_selected_annotation(&mut self, _cx: &mut Context) -> bool { + let Some(annotation) = self.selected().cloned() else { + return false; + }; + self.annotation_state.clipboard = Some(annotation); + true + } + + /// `Editor.tsx:83-101` -- Cmd-V drops the copy 16px down and right, selects + /// it, and copies *it* in turn so a held Cmd-V cascades. + pub(crate) fn paste_annotation(&mut self, window: &mut Window, cx: &mut Context) -> bool { + let Some(source) = self.annotation_state.clipboard.clone() else { + return false; + }; + let mut duplicate = source; + duplicate.id = crate::store::new_uuid_v4(); + duplicate.x += 16.; + duplicate.y += 16.; + self.annotation_state.clipboard = Some(duplicate.clone()); + self.selected_annotation = Some(duplicate.id.clone()); + self.project.annotations.push(duplicate); + self.tool = Tool::Select; + self.commit_annotations(window, cx); + true + } + + /// Backspace / Delete over a selection (`AnnotationLayer.tsx:72-86`), which + /// the open text editor swallows. + pub(crate) fn delete_selected_annotation( + &mut self, + window: &mut Window, + cx: &mut Context, + ) { + if self.annotation_state.editing.is_some() { + return; + } + let Some(id) = self.selected_annotation.take() else { + return; + }; + let before = self.project.annotations.len(); + self.project + .annotations + .retain(|annotation| annotation.id != id); + if self.project.annotations.len() == before { + cx.notify(); + return; + } + self.annotation_state.reorder = None; + self.commit_annotations(window, cx); + } + + // -- Inline text ---------------------------------------------------------- + + /// The `foreignObject` editor (`:661-722`): transparent, at the + /// annotation's own font size, focused and fully selected on open. + fn begin_text_edit(&mut self, id: String, window: &mut Window, cx: &mut Context) { + let Some(original) = self + .annotation(&id) + .map(|annotation| annotation.text.clone().unwrap_or_default()) + else { + return; + }; + let Some(input) = self.annotation_state.text_input.clone() else { + return; + }; + input.update(cx, |input, cx| { + input.set_text(original.clone(), cx); + input.focus_and_select_all(window, cx); + }); + self.annotation_state.editing = Some(TextEdit { id, original }); + cx.notify(); + } + + fn on_annotation_text_event( + &mut self, + event: &ui::TextInputEvent, + window: &mut Window, + cx: &mut Context, + ) { + match event { + // `onInput` (`:690-693`): the annotation follows the field live, so + // the box the handles wrap keeps up. No history, no save -- the + // commit does both. + ui::TextInputEvent::Changed => { + let Some(id) = self + .annotation_state + .editing + .as_ref() + .map(|edit| edit.id.clone()) + else { + return; + }; + let Some(text) = self + .annotation_state + .text_input + .as_ref() + .map(|input| input.read(cx).text().to_string()) + else { + return; + }; + if let Some(annotation) = self.annotation_mut(&id) { + annotation.text = Some(text); + } + cx.notify(); + } + // Enter and blur both commit (`:694-720`). Escape has no meaning in + // a `contentEditable`; the nearest thing here is the same commit. + ui::TextInputEvent::Confirmed + | ui::TextInputEvent::Cancelled + | ui::TextInputEvent::Blurred => self.commit_text_edit(window, cx), + } + } + + /// `onBlur` (`:694-713`): empty text deletes the annotation, and history + /// only records when the text actually moved. + fn commit_text_edit(&mut self, window: &mut Window, cx: &mut Context) { + let Some(edit) = self.annotation_state.editing.take() else { + return; + }; + let text = self + .annotation_state + .text_input + .as_ref() + .map(|input| input.read(cx).text().to_string()) + .unwrap_or_default(); + + if text.trim().is_empty() { + self.project + .annotations + .retain(|annotation| annotation.id != edit.id); + if self.selected_annotation.as_deref() == Some(edit.id.as_str()) { + self.selected_annotation = None; + } + self.commit_annotations(window, cx); + } else if text != edit.original { + if let Some(annotation) = self.annotation_mut(&edit.id) { + annotation.text = Some(text); + } + self.commit_annotations(window, cx); + } else { + cx.notify(); + } + + let focus = self.focus.clone(); + window.focus(&focus, cx); + } + + /// Where the editor sits on screen, so a press can tell "inside the field" + /// from "outside it". + fn text_editor_bounds(&self) -> Option> { + let edit = self.annotation_state.editing.as_ref()?; + let annotation = self.annotation(&edit.id)?; + let bounds = self.annotation_state.layer.get()?; + let scale = self.annotation_scale()?; + Some(Bounds { + origin: gpui::point( + bounds.origin.x + px(annotation.x as f32 * scale), + bounds.origin.y + px(annotation.y as f32 * scale), + ), + size: gpui::size( + px((annotation.width.abs() as f32 * scale).max(100.)), + px((annotation.height as f32 * scale).max(16.)), + ), + }) + } + + // -- Mask overlays --------------------------------------------------------- + + /// What the painted overlays were computed from: the frame, the image rect, + /// and every mask's geometry and style. + fn mask_key(&self) -> u64 { + let mut hasher = std::hash::DefaultHasher::new(); + match &self.frame_rgba { + Some(rgba) => { + (Arc::as_ptr(rgba) as usize).hash(&mut hasher); + rgba.len().hash(&mut hasher); + } + None => 0usize.hash(&mut hasher), + } + self.frame_size.0.to_bits().hash(&mut hasher); + self.frame_size.1.to_bits().hash(&mut hasher); + let rect = self.annotation_image_rect(); + for value in [rect.x, rect.y, rect.width, rect.height] { + value.to_bits().hash(&mut hasher); + } + for annotation in &self.project.annotations { + if annotation.annotation_type != AnnotationType::Mask { + continue; + } + annotation.id.hash(&mut hasher); + for value in [ + annotation.x, + annotation.y, + annotation.width, + annotation.height, + annotation.mask_level.unwrap_or(MASK_FALLBACK_LEVEL), + ] { + value.to_bits().hash(&mut hasher); + } + matches!(annotation.mask_type, Some(MaskType::Pixelate)).hash(&mut hasher); + } + hasher.finish() + } + + /// `renderMaskOverlays` (`Preview.tsx:579-687`), off the main thread. The + /// stale overlay keeps painting until the new one lands, so a drag never + /// blocks on a resample. + pub(crate) fn refresh_mask_overlays(&mut self, window: &mut Window, cx: &mut Context) { + let key = self.mask_key(); + if self.annotation_state.mask_key == key || self.annotation_state.mask_pending == Some(key) + { + return; + } + + let masks: Vec = self + .project + .annotations + .iter() + .filter(|annotation| annotation.annotation_type == AnnotationType::Mask) + .cloned() + .collect(); + let frame = self.frame_rgba.clone(); + + let (Some(rgba), false) = (frame, masks.is_empty()) else { + self.drop_mask_overlays(window); + // Dropping the task matters as much as dropping the tiles: a + // resample still in flight would re-install the regions of a mask + // that has just been deleted, and stamp its key over this one. + self.annotation_state.mask_task = None; + self.annotation_state.mask_key = key; + self.annotation_state.mask_pending = None; + cx.notify(); + return; + }; + + let size = ( + self.frame_size.0.max(0.) as u32, + self.frame_size.1.max(0.) as u32, + ); + let rect = self.annotation_image_rect(); + self.annotation_state.mask_pending = Some(key); + self.annotation_state.mask_task = Some(cx.spawn_in(window, async move |this, cx| { + let overlays = cx + .background_executor() + .spawn(async move { build_mask_overlays(&rgba, size, &masks, rect) }) + .await; + this.update_in(cx, |this, window, cx| { + this.drop_mask_overlays(window); + this.annotation_state.masks = overlays; + this.annotation_state.mask_key = key; + this.annotation_state.mask_pending = None; + cx.notify(); + // The masks may have moved on while this was in flight. + this.refresh_mask_overlays(window, cx); + }) + .ok(); + })); + } + + /// Hand the atlas back the tiles the old overlays held. + fn drop_mask_overlays(&mut self, window: &mut Window) { + for overlay in self.annotation_state.masks.drain(..) { + let _ = window.drop_image(overlay.image); + } + } +} + +impl ScreenshotEditorWindow { + // -- The layer ------------------------------------------------------------- + + /// `AnnotationLayer.tsx` -- the SVG overlay over the preview's content + /// wrapper, as one canvas plus the inline text editor. `content` is the + /// wrapper's on-screen size; the image rect the window passes is what the + /// mask overlays were already clipped to, so the paint itself needs it no + /// further. + pub(crate) fn render_annotation_layer( + &self, + content: (f32, f32), + _rect: Rect, + cx: &mut Context, + ) -> Option { + if self.frame_size.0 <= 0. || content.0 <= 0. { + return None; + } + let scale = content.0 / self.frame_size.0; + let temp = match &self.annotation_state.gesture { + Some(Gesture::Create(creating)) if !creating.in_list => Some(creating.temp.clone()), + _ => None, + }; + let paint = LayerPaint { + annotations: self.project.annotations.clone(), + temp, + masks: self.annotation_state.masks.clone(), + selected: self.selected_annotation.clone(), + editing: self + .annotation_state + .editing + .as_ref() + .map(|edit| edit.id.clone()), + hover: self.annotation_state.hover.clone(), + select_tool: self.tool == Tool::Select, + scale, + handle_size: HANDLE_SCREEN_SIZE / scale.max(0.001) as f64, + }; + let cell = self.annotation_state.layer.clone(); + let drawing = self.tool != Tool::Select; + let cursor = self.annotation_state.hover_cursor; + + Some( + div() + .absolute() + .top_0() + .left_0() + .size_full() + .when(drawing, |this| this.cursor(CursorStyle::Crosshair)) + .when_some(cursor, |this, cursor| this.cursor(cursor)) + .on_mouse_move(cx.listener(|this, event: &MouseMoveEvent, window, cx| { + this.annotation_hover(event, window, cx); + })) + .child( + canvas( + move |bounds, _window, _cx| cell.set(Some(bounds)), + move |bounds, _, window, cx| paint_layer(&paint, bounds, window, cx), + ) + .absolute() + .size_full(), + ) + .children(self.render_text_editor(scale, cx)) + .into_any_element(), + ) + } + + /// The `foreignObject` editor (`:662-722`): no chrome, the annotation's own + /// font size and colour, sitting at the annotation's top-left the way the + /// source's does -- which is a fifth of a line above the glyphs it replaces, + /// because SVG anchors text on its baseline and HTML on its box. + fn render_text_editor(&self, scale: f32, _cx: &mut Context) -> Option { + let edit = self.annotation_state.editing.as_ref()?; + let annotation = self.annotation(&edit.id)?; + let input = self.annotation_state.text_input.clone()?; + let font_size = (annotation.height as f32 * scale).max(1.); + let color = annotation_color(&annotation.stroke_color, 1.).unwrap_or(gpui::black()); + + Some( + div() + .absolute() + .left(px(annotation.x as f32 * scale)) + .top(px(annotation.y as f32 * scale)) + .w(px((annotation.width.abs() as f32 * scale).max(100.))) + .child( + ui::TextInput::bare(&self.theme, "screenshot-annotation-text", &input) + .text_size(px(font_size)) + .line_height(px(font_size)) + .text_color(color) + .caret_color(color), + ) + .into_any_element(), + ) + } + + /// The drag layers and the colour popover, which have to hang off the + /// window root rather than off the preview: a gesture outlives the element + /// it started on, and a popover has to escape the config bar's 44px. + pub(crate) fn render_annotation_overlays( + &self, + window: &Window, + cx: &mut Context, + ) -> Vec { + let mut layers = Vec::new(); + if self.annotation_state.gesture.is_some() { + layers.push( + ui::Slider::drag_layer( + "screenshot-annotation-drag", + cx.listener(|this, event: &MouseMoveEvent, window, cx| { + this.annotation_mouse_move(event.position, window, cx); + }), + cx.listener(|this, _: &MouseUpEvent, window, cx| { + this.annotation_mouse_up(window, cx); + }), + ) + .into_any_element(), + ); + } + if let Some(slider) = self.annotation_state.active_slider { + layers.push( + ui::Slider::drag_layer( + "screenshot-annotation-slider-drag", + cx.listener(move |this, event: &MouseMoveEvent, window, cx| { + this.apply_annotation_slider(slider, event.position, window, cx); + }), + cx.listener(|this, _: &MouseUpEvent, _window, cx| { + this.end_annotation_slider(cx); + }), + ) + .into_any_element(), + ); + } + if self.annotation_state.reorder.is_some() { + layers.push( + ui::Slider::drag_layer( + "screenshot-layer-reorder-drag", + cx.listener(|this, event: &MouseMoveEvent, _window, cx| { + this.drag_reorder(event.position.y, cx); + }), + cx.listener(|this, _: &MouseUpEvent, window, cx| { + this.finish_reorder(window, cx); + }), + ) + .into_any_element(), + ); + } + layers.extend(self.render_annotation_color_popover(window, cx)); + layers + } + + // -- The config bar -------------------------------------------------------- + + /// `AnnotationConfig.tsx:44-158` -- the per-type controls that sit left of + /// the bar's Done button. + pub(crate) fn render_annotation_config_controls( + &self, + cx: &mut Context, + ) -> Option { + let annotation = self.selected()?; + let theme = self.theme; + let kind = annotation.annotation_type; + let is_mask = kind == AnnotationType::Mask; + let is_text = kind == AnnotationType::Text; + + let mut row = div() + .flex() + .flex_row() + .items_center() + .justify_center() + .gap(px(24.)); + + if !is_mask { + row = row.child(config_item( + &theme, + if is_text { "Color" } else { "Stroke" }, + None, + self.render_color_button(ColorTarget::Stroke, cx), + )); + } + if !is_mask && !is_text { + row = row.child(config_item( + &theme, + "Width", + Some(format!("{}px", annotation.stroke_width.round() as i64)), + self.annotation_slider(AnnotationSlider::Width, px(80.), cx) + .into_any_element(), + )); + } + if matches!(kind, AnnotationType::Rectangle | AnnotationType::Circle) { + row = row.child(config_item( + &theme, + "Fill", + None, + self.render_color_button(ColorTarget::Fill, cx), + )); + } + if !is_mask { + row = row.child(config_item( + &theme, + "Opacity", + Some(format!("{}%", (annotation.opacity * 100.).round() as i64)), + self.annotation_slider(AnnotationSlider::Opacity, px(80.), cx) + .into_any_element(), + )); + } + if is_mask { + let mask_type = annotation.mask_type.unwrap_or(MaskType::Blur); + row = row + .child(config_item( + &theme, + "Style", + None, + div() + .flex() + .flex_row() + .gap(px(4.)) + .child(self.render_mask_pill("Blur", MaskType::Blur, mask_type, cx)) + .child(self.render_mask_pill("Pixelate", MaskType::Pixelate, mask_type, cx)) + .into_any_element(), + )) + .child(config_item( + &theme, + "Intensity", + Some(format!( + "{}", + annotation.mask_level.unwrap_or(MASK_FALLBACK_LEVEL).round() as i64 + )), + self.annotation_slider(AnnotationSlider::MaskLevel, px(96.), cx) + .into_any_element(), + )); + } + if is_text { + row = row.child(config_item( + &theme, + "Size", + Some(format!("{}px", annotation.height.round() as i64)), + self.annotation_slider(AnnotationSlider::TextSize, px(80.), cx) + .into_any_element(), + )); + } + + Some(row.into_any_element()) + } + + /// The two `Style` pills (`AnnotationConfig.tsx:100-123`). + fn render_mask_pill( + &self, + label: &'static str, + value: MaskType, + current: MaskType, + cx: &mut Context, + ) -> AnyElement { + let theme = self.theme; + let active = value == current; + div() + .id(gpui::SharedString::from(format!( + "screenshot-mask-style-{label}" + ))) + .flex() + .items_center() + .justify_center() + .h(px(24.)) + .px(px(10.)) + .rounded(px(6.)) + .cursor_pointer() + .text_size(px(12.)) + .font_weight(FontWeight::MEDIUM) + .when(active, |this| { + this.bg(theme.blue_9).text_color(gpui::white()) + }) + .when(!active, |this| { + this.bg(theme.gray_3) + .text_color(theme.gray_11) + .hover(|style| style.bg(theme.gray_4)) + }) + .child(label) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + let Some(id) = this.selected_annotation.clone() else { + return; + }; + this.edit_project( + move |project| { + let Some(annotation) = project + .annotations + .iter_mut() + .find(|annotation| annotation.id == id) + else { + return false; + }; + if annotation.mask_type == Some(value) { + return false; + } + annotation.mask_type = Some(value); + true + }, + window, + cx, + ); + this.refresh_mask_overlays(window, cx); + })) + .into_any_element() + } + + fn annotation_slider( + &self, + slider: AnnotationSlider, + width: Pixels, + cx: &mut Context, + ) -> impl IntoElement { + let theme = self.theme; + let (min, max, _) = slider.range(); + let value = self + .selected() + .map_or(min, |annotation| slider.value(annotation)); + let fraction = ((value - min) / (max - min).max(f32::EPSILON)).clamp(0., 1.); + let track = self + .annotation_state + .slider_tracks + .get(&slider) + .cloned() + .unwrap_or_default(); + + ui::Slider::new( + gpui::SharedString::from(format!("screenshot-annotation-slider-{}", slider.id())), + fraction, + track, + ) + .row_width(width) + .track(px(4.), theme.gray_4.into()) + .fill(theme.blue_9.into()) + .thumb(px(14.), gpui::white(), Some(theme.gray_6.into())) + .on_drag_start( + cx.listener(move |this, event: &MouseDownEvent, window, cx| { + cx.stop_propagation(); + this.begin_annotation_slider(slider, event.position, window, cx); + }), + ) + } + + /// The same bracket the styling sliders take (`ui/slider.rs`'s + /// `SliderDrag`): one history entry for a whole drag. + fn begin_annotation_slider( + &mut self, + slider: AnnotationSlider, + position: Point, + window: &mut Window, + cx: &mut Context, + ) { + if self.annotation_state.active_slider.is_none() { + self.history.pause(); + } + self.annotation_state.active_slider = Some(slider); + self.apply_annotation_slider(slider, position, window, cx); + cx.notify(); + } + + fn apply_annotation_slider( + &mut self, + slider: AnnotationSlider, + position: Point, + window: &mut Window, + cx: &mut Context, + ) { + let track = self + .annotation_state + .slider_tracks + .get(&slider) + .cloned() + .unwrap_or_default(); + let (min, max, step) = slider.range(); + let Some(value) = ui::slider_value_at(&track, position, min, max, step) else { + return; + }; + let Some(id) = self.selected_annotation.clone() else { + return; + }; + self.edit_project( + move |project| { + project + .annotations + .iter_mut() + .find(|annotation| annotation.id == id) + .is_some_and(|annotation| slider.apply(annotation, value)) + }, + window, + cx, + ); + if slider == AnnotationSlider::MaskLevel { + self.refresh_mask_overlays(window, cx); + } + } + + fn end_annotation_slider(&mut self, cx: &mut Context) { + if self.annotation_state.active_slider.take().is_some() { + self.history.resume(&self.project); + } + cx.notify(); + } + + // -- The colour popover ---------------------------------------------------- + + fn annotation_color_value(&self, target: ColorTarget) -> Option { + let annotation = self.selected()?; + Some(match target { + ColorTarget::Stroke => annotation.stroke_color.clone(), + ColorTarget::Fill => annotation.fill_color.clone(), + }) + } + + fn set_annotation_color( + &mut self, + target: ColorTarget, + value: String, + window: &mut Window, + cx: &mut Context, + ) { + let Some(id) = self.selected_annotation.clone() else { + return; + }; + self.edit_project( + move |project| { + let Some(annotation) = project + .annotations + .iter_mut() + .find(|annotation| annotation.id == id) + else { + return false; + }; + let field = match target { + ColorTarget::Stroke => &mut annotation.stroke_color, + ColorTarget::Fill => &mut annotation.fill_color, + }; + if *field == value { + return false; + } + *field = value; + true + }, + window, + cx, + ); + } + + /// `Popover.Trigger`'s `size-5 rounded-full` swatch + /// (`AnnotationConfig.tsx:212-225`). + fn render_color_button(&self, target: ColorTarget, cx: &mut Context) -> AnyElement { + let theme = self.theme; + let value = self.annotation_color_value(target).unwrap_or_default(); + let transparent = annotation_color(&value, 1.).is_none(); + let cell = self + .annotation_state + .color_anchors + .get(&target) + .cloned() + .unwrap_or_default(); + + div() + .relative() + .flex_shrink_0() + .size(px(20.)) + .child( + canvas( + move |bounds, _window, _cx| cell.set(Some(bounds)), + |_, _, _, _| {}, + ) + .absolute() + .size_full(), + ) + .child( + div() + .id(target.id()) + .size(px(20.)) + .rounded_full() + .overflow_hidden() + .cursor_pointer() + .border_1() + .border_color(theme.gray_5) + .hover(|style| style.border_color(theme.gray_7)) + .when(transparent, |this| this.children(checker_quarters(20.))) + .when(!transparent, |this| { + this.bg(annotation_color(&value, 1.).unwrap_or(gpui::transparent_black())) + }) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + this.toggle_color_popover(target, window, cx); + })), + ) + .into_any_element() + } + + fn toggle_color_popover( + &mut self, + target: ColorTarget, + window: &mut Window, + cx: &mut Context, + ) { + let Some(id) = self.selected_annotation.clone() else { + return; + }; + let open = self + .annotation_state + .color_popover + .as_ref() + .is_some_and(|(open, open_id)| *open == target && open_id == &id); + self.annotation_state.color_popover = if open { + None + } else { + let focus = self.focus.clone(); + window.focus(&focus, cx); + Some((target, id)) + }; + cx.notify(); + } + + /// `Popover.Content` (`AnnotationConfig.tsx:227-274`): the hex field over a + /// six-column palette, anchored under its swatch. + fn render_annotation_color_popover( + &self, + window: &Window, + cx: &mut Context, + ) -> Option { + let (target, id) = self.annotation_state.color_popover.clone()?; + if self.selected_annotation.as_deref() != Some(id.as_str()) { + return None; + } + let value = self.annotation_color_value(target)?; + let theme = self.theme; + let width = 220.; + let viewport = window.viewport_size(); + let anchor = self + .annotation_state + .color_anchors + .get(&target) + .and_then(|cell| cell.get()); + let left = anchor + .map(|bounds| f32::from(bounds.origin.x + bounds.size.width / 2.) - width / 2.) + .unwrap_or(16.) + .clamp(8., (f32::from(viewport.width) - width - 8.).max(8.)); + let top = anchor + .map(|bounds| f32::from(bounds.origin.y + bounds.size.height) + 8.) + .unwrap_or(120.); + // `grid-cols-6 gap-1.5` inside `w-[220px] p-2.5`: six equal columns of + // the 200px content box, each centring a `size-5` swatch. + // Rounded down so six columns plus their gaps can never round past the + // content box and wrap the sixth swatch onto its own row. + let cell_width = ((width - 20. - 5. * 6.) / 6.).floor(); + + let mut palette: Vec = Vec::new(); + if target.allows_transparent() { + let selected = annotation_color(&value, 1.).is_none(); + palette.push( + div() + .w(px(cell_width)) + .flex() + .justify_center() + .child( + div() + .id("screenshot-annotation-swatch-transparent") + .size(px(20.)) + .rounded_full() + .overflow_hidden() + .cursor_pointer() + .border_1() + .border_color(if selected { theme.blue_9 } else { theme.gray_4 }) + .when(selected, |this| this.border_2()) + .children(checker_quarters(20.)) + .tooltip(move |_window, cx| { + ui::Tooltip::new(&theme, "Transparent").view(cx) + }) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + this.set_annotation_color( + target, + TRANSPARENT.to_string(), + window, + cx, + ); + })), + ) + .into_any_element(), + ); + } + for (index, hex) in BACKGROUND_COLORS + .iter() + .filter(|hex| **hex != "#00000000") + .enumerate() + { + let hex = *hex; + let selected = value.eq_ignore_ascii_case(hex); + palette.push( + div() + .w(px(cell_width)) + .flex() + .justify_center() + .child( + div() + .id(("screenshot-annotation-swatch", index)) + .size(px(20.)) + .rounded_full() + .cursor_pointer() + .border_1() + .border_color(Theme::with_alpha(gpui::rgb(0x000000), 0.1)) + .when(selected, |this| { + this.border_2() + .border_color(Theme::with_alpha(gpui::rgb(0xffffff), 0.5)) + }) + .bg(annotation_color(hex, 1.).unwrap_or(gpui::transparent_black())) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + this.set_annotation_color(target, hex.to_string(), window, cx); + })), + ) + .into_any_element(), + ); + } + + Some( + div() + .absolute() + .top_0() + .left_0() + .size_full() + .child( + div() + .id("screenshot-annotation-color-backdrop") + .absolute() + .top_0() + .left_0() + .size_full() + .on_click(cx.listener(|this, _, _window, cx| { + this.annotation_state.color_popover = None; + cx.notify(); + })), + ) + .child( + div() + .absolute() + .left(px(left)) + .top(px(top)) + .w(px(width)) + .p(px(10.)) + .flex() + .flex_col() + .gap(px(10.)) + .rounded(px(8.)) + .border_1() + .border_color(theme.gray_4) + .bg(if theme.is_dark() { + theme.gray_2 + } else { + theme.gray_1 + }) + .shadow(vec![gpui::BoxShadow { + color: Theme::with_alpha(gpui::rgb(0x000000), 0.18), + offset: gpui::point(px(0.), px(8.)), + blur_radius: px(24.), + spread_radius: px(0.), + inset: false, + }]) + .child( + // `RgbInput` (`ColorPicker.tsx:47-102`). + div() + .flex() + .flex_row() + .items_center() + .gap(px(12.)) + .child( + div() + .size(px(32.)) + .rounded(px(8.)) + .overflow_hidden() + .border_1() + .border_color(theme.gray_4) + .when(annotation_color(&value, 1.).is_none(), |this| { + this.children(checker_quarters(32.)) + }) + .when_some(annotation_color(&value, 1.), |this, color| { + this.bg(color) + }), + ) + .children(self.annotation_state.color_inputs.get(&target).map( + |input| { + ui::TextInput::plain( + &theme, + match target { + ColorTarget::Stroke => { + "screenshot-annotation-hex-stroke" + } + ColorTarget::Fill => { + "screenshot-annotation-hex-fill" + } + }, + input, + ) + .width(px(73.6)) + .padding_x(px(6.)) + .height(px(30.)) + .radius(px(8.)) + .text_size(px(13.)) + }, + )), + ) + .child( + div() + .flex() + .flex_row() + .flex_wrap() + .gap(px(6.)) + .children(palette), + ), + ) + .into_any_element(), + ) + } + + /// The hex field re-derives from the colour whenever it moves underneath, + /// but never while it has focus -- the same rule `sync_hex_inputs` follows + /// for the styling popovers. + pub(crate) fn sync_annotation_inputs(&mut self, window: &Window, cx: &mut Context) { + // A popover left open by a selection that has moved on is closed, not + // repointed: its swatch belongs to an annotation that is no longer + // under the bar. + if let Some((_, id)) = &self.annotation_state.color_popover + && self.selected_annotation.as_deref() != Some(id.as_str()) + { + self.annotation_state.color_popover = None; + } + for target in ColorTarget::ALL { + let Some(input) = self.annotation_state.color_inputs.get(&target).cloned() else { + continue; + }; + if input.read(cx).focus_handle().is_focused(window) { + continue; + } + let Some(value) = self.annotation_color_value(target) else { + continue; + }; + // `rgbValue()` reads `transparent` as black rather than as an + // empty field (`AnnotationConfig.tsx:200-206`). + let hex = match hex_to_rgb(&value) { + Some(rgba) => format!("#{:02X}{:02X}{:02X}", rgba[0], rgba[1], rgba[2]), + None => "#000000".to_string(), + }; + if input.read(cx).text() != hex { + input.update(cx, |input, cx| input.set_text(hex, cx)); + } + } + } + + /// `RgbInput`'s handlers: a complete 6- or 8-digit value commits live, and + /// Enter or blur commits whatever is in the box. + fn on_annotation_color_event( + &mut self, + target: ColorTarget, + event: &ui::TextInputEvent, + window: &mut Window, + cx: &mut Context, + ) { + let Some(input) = self.annotation_state.color_inputs.get(&target).cloned() else { + return; + }; + let text = input.read(cx).text().trim().to_string(); + match event { + ui::TextInputEvent::Changed => { + let digits = hex_digit_count(&text); + if digits != 6 && digits != 8 { + return; + } + } + ui::TextInputEvent::Confirmed | ui::TextInputEvent::Cancelled => { + let focus = self.focus.clone(); + window.focus(&focus, cx); + } + ui::TextInputEvent::Blurred => {} + } + let Some(rgba) = hex_to_rgb(&text) else { + return; + }; + let hex = format!("#{:02X}{:02X}{:02X}", rgba[0], rgba[1], rgba[2]); + if self.annotation_color_value(target).as_deref() != Some(hex.as_str()) { + self.set_annotation_color(target, hex, window, cx); + } + } + + // -- The layers panel ------------------------------------------------------ + + /// `LayersPanel.tsx:218-318`: the reversed list, one grip-dragged row each. + pub(crate) fn render_layer_rows(&self, cx: &mut Context) -> AnyElement { + let theme = self.theme; + + if self.project.annotations.is_empty() { + return div() + .flex() + .flex_col() + .items_center() + .justify_center() + .h_full() + .px(px(16.)) + .child( + svg() + .path("icons/layers.svg") + .size(px(32.)) + .mb(px(8.)) + .text_color(theme.gray_7), + ) + .child( + div() + .text_size(px(12.)) + .text_color(theme.gray_10) + .child("No layers yet"), + ) + .child( + div() + .mt(px(4.)) + .text_size(px(10.)) + .text_color(theme.gray_8) + .child("Use the tools above to add annotations"), + ) + .into_any_element(); + } + + let dragged = self + .annotation_state + .reorder + .as_ref() + .map(|reorder| reorder.id.clone()); + // The rule is drawn only where the drop would actually move the row: + // the source hides it in the dragged row's own two gaps + // (`LayersPanel.tsx:236-250`), which are exactly the drops + // `reorder_move` rejects. + let target = self.annotation_state.reorder.as_ref().and_then(|reorder| { + let target = reorder.target?; + let dragged_reversed = self + .project + .annotations + .iter() + .rev() + .position(|annotation| annotation.id == reorder.id)?; + reorder_move(self.project.annotations.len(), dragged_reversed, target).map(|_| target) + }); + let cell = self.annotation_state.list.clone(); + + // `reversedAnnotations()` -- the top of the list is the front of the + // stack (`LayersPanel.tsx:62`). + div() + .relative() + .flex() + .flex_col() + .child( + canvas( + move |bounds, _window, _cx| cell.set(Some(bounds)), + |_, _, _, _| {}, + ) + .absolute() + .size_full(), + ) + .children( + self.project + .annotations + .iter() + .rev() + .enumerate() + .map(|(reversed, annotation)| { + self.render_layer_row(reversed, annotation, dragged.as_deref(), cx) + }) + .collect::>(), + ) + .children(target.map(|target| { + // A 2px rule in the gap the row would land in. Absolute, so the + // rows do not shuffle underneath the pointer mid-drag. + div() + .absolute() + .left(px(8.)) + .right(px(8.)) + .top(px(target as f32 * LAYER_ROW_HEIGHT - 1.)) + .h(px(2.)) + .rounded_full() + .bg(theme.blue_9) + })) + .into_any_element() + } + + fn render_layer_row( + &self, + reversed: usize, + annotation: &Annotation, + dragged: Option<&str>, + cx: &mut Context, + ) -> AnyElement { + let theme = self.theme; + let id = annotation.id.clone(); + let selected = self.selected_annotation.as_deref() == Some(annotation.id.as_str()); + let dragging = dragged == Some(annotation.id.as_str()); + let delete_id = id.clone(); + let grip_id = id.clone(); + + div() + .id(("screenshot-layer-row", reversed)) + .group("screenshot-layer-row") + .flex() + .flex_row() + .items_center() + .gap(px(8.)) + .h(px(LAYER_ROW_HEIGHT)) + .px(px(8.)) + .mx(px(4.)) + .rounded(px(6.)) + .cursor_pointer() + .when(dragging, |this| this.opacity(0.5).bg(theme.gray_3)) + .when(selected && !dragging, |this| this.bg(theme.blue_3)) + .when(!selected && !dragging, |this| { + this.hover(|style| style.bg(theme.gray_3)) + }) + .child( + div() + .id(("screenshot-layer-grip", reversed)) + .flex() + .items_center() + .justify_center() + .flex_shrink_0() + .cursor(CursorStyle::OpenHand) + .text_color(theme.gray_8) + .hover(|style| style.text_color(theme.gray_11)) + .child(svg().path("icons/grip-vertical.svg").size(px(14.))) + .on_mouse_down( + MouseButton::Left, + cx.listener(move |this, _: &MouseDownEvent, _window, cx| { + cx.stop_propagation(); + this.begin_reorder(grip_id.clone(), cx); + }), + ), + ) + .child( + div() + .flex() + .items_center() + .justify_center() + .size(px(24.)) + .flex_shrink_0() + .rounded(px(4.)) + .bg(if selected { theme.blue_5 } else { theme.gray_3 }) + .child( + svg() + .path(annotation_icon(annotation.annotation_type)) + .size(px(14.)) + .text_color(if selected { + theme.blue_11 + } else { + theme.gray_11 + }), + ), + ) + .child( + div() + .flex_1() + .min_w_0() + .truncate() + .text_size(px(12.)) + .text_color(if selected { + theme.blue_12 + } else { + theme.gray_12 + }) + .child(layer_label(annotation)), + ) + .child( + div() + .id(("screenshot-layer-delete", reversed)) + .flex() + .items_center() + .justify_center() + .flex_shrink_0() + .p(px(2.)) + .rounded(px(4.)) + .cursor_pointer() + .opacity(0.) + .text_color(theme.gray_9) + .group_hover("screenshot-layer-row", |style| style.opacity(1.)) + .hover(|style| style.bg(theme.red_3).text_color(theme.red_11)) + .child(svg().path("icons/x.svg").size(px(12.))) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + this.delete_annotation(&delete_id, window, cx); + })), + ) + .on_click(cx.listener(move |this, _, _window, cx| { + cx.stop_propagation(); + this.selected_annotation = Some(id.clone()); + this.tool = Tool::Select; + cx.notify(); + })) + .into_any_element() + } + + /// `handleDelete` (`LayersPanel.tsx:183-190`). + fn delete_annotation(&mut self, id: &str, window: &mut Window, cx: &mut Context) { + let before = self.project.annotations.len(); + self.project + .annotations + .retain(|annotation| annotation.id != id); + if self.project.annotations.len() == before { + return; + } + if self.selected_annotation.as_deref() == Some(id) { + self.selected_annotation = None; + } + // Deleting the row that is being edited has to close the editor too, or + // `editing` outlives its annotation and swallows the delete key. + if self + .annotation_state + .editing + .as_ref() + .is_some_and(|edit| edit.id == id) + { + self.annotation_state.editing = None; + } + self.annotation_state.reorder = None; + self.commit_annotations(window, cx); + } + + fn begin_reorder(&mut self, id: String, cx: &mut Context) { + self.annotation_state.reorder = Some(Reorder { + id, + target: None, + count: self.project.annotations.len(), + }); + cx.notify(); + } + + /// `handleDragMove` (`LayersPanel.tsx:67-91`): the gap the pointer is + /// nearest, found against each row's midpoint. + fn drag_reorder(&mut self, y: Pixels, cx: &mut Context) { + let Some(bounds) = self.annotation_state.list.get() else { + return; + }; + let count = self.project.annotations.len(); + if count == 0 { + return; + } + let top = f32::from(bounds.origin.y); + let mut target = 0; + for index in 0..count { + let middle = top + index as f32 * LAYER_ROW_HEIGHT + LAYER_ROW_HEIGHT / 2.; + if f32::from(y) < middle { + target = index; + break; + } + target = index + 1; + } + if let Some(reorder) = self.annotation_state.reorder.as_mut() + && reorder.target != Some(target) + { + reorder.target = Some(target); + cx.notify(); + } + } + + /// `finalizeDrag` (`LayersPanel.tsx:93-124`). + fn finish_reorder(&mut self, window: &mut Window, cx: &mut Context) { + let Some(reorder) = self.annotation_state.reorder.take() else { + return; + }; + let Some(target) = reorder.target else { + cx.notify(); + return; + }; + let count = self.project.annotations.len(); + if count != reorder.count { + cx.notify(); + return; + } + let Some(dragged_reversed) = self + .project + .annotations + .iter() + .rev() + .position(|annotation| annotation.id == reorder.id) + else { + cx.notify(); + return; + }; + let Some((from, to)) = reorder_move(count, dragged_reversed, target) else { + cx.notify(); + return; + }; + let annotation = self.project.annotations.remove(from); + self.project.annotations.insert(to, annotation); + self.commit_annotations(window, cx); + } +} + +// --------------------------------------------------------------------------- +// Painting +// --------------------------------------------------------------------------- +// +// The source is an SVG with `viewBox="0 0 frameW frameH"`, so every number in +// an annotation is a frame pixel and the browser scales the lot. Here the same +// numbers are multiplied by the layer's own scale on the way into a +// `PathBuilder`. Two things stay in *screen* units on purpose: the selection +// chrome (handles, their rings, the text box) which the source sizes off +// `handleSize` and would otherwise thin out at low zoom, and the round caps, +// which lyon's `StrokeOptions` cannot be given without naming `lyon::LineCap` +// -- a filled disc at each end is the same shape. + +/// Frame space to window space. +fn map_point(origin: Point, scale: f32, x: f64, y: f64) -> Point { + gpui::point( + origin.x + px(x as f32 * scale), + origin.y + px(y as f32 * scale), + ) +} + +fn push_rect(builder: &mut PathBuilder, origin: Point, scale: f32, rect: Rect) { + let (right, bottom) = (rect.x + rect.width, rect.y + rect.height); + builder.move_to(map_point(origin, scale, rect.x, rect.y)); + builder.line_to(map_point(origin, scale, right, rect.y)); + builder.line_to(map_point(origin, scale, right, bottom)); + builder.line_to(map_point(origin, scale, rect.x, bottom)); + builder.close(); +} + +/// A screen-space rounded rectangle -- the selection box's `rx={4}`. +fn push_rounded_rect(builder: &mut PathBuilder, bounds: Bounds, radius: f32) { + let (left, top) = (f32::from(bounds.origin.x), f32::from(bounds.origin.y)); + let (width, height) = (f32::from(bounds.size.width), f32::from(bounds.size.height)); + let radius = radius.min(width / 2.).min(height / 2.).max(0.); + let (right, bottom) = (left + width, top + height); + let point = |x: f32, y: f32| gpui::point(px(x), px(y)); + builder.move_to(point(left + radius, top)); + builder.line_to(point(right - radius, top)); + builder.curve_to(point(right, top + radius), point(right, top)); + builder.line_to(point(right, bottom - radius)); + builder.curve_to(point(right - radius, bottom), point(right, bottom)); + builder.line_to(point(left + radius, bottom)); + builder.curve_to(point(left, bottom - radius), point(left, bottom)); + builder.line_to(point(left, top + radius)); + builder.curve_to(point(left + radius, top), point(left, top)); + builder.close(); +} + +/// A circle in screen units, as four cubics. +fn push_circle(builder: &mut PathBuilder, center: Point, radius_x: f32, radius_y: f32) { + // The usual cubic approximation of a quarter turn. + const KAPPA: f32 = 0.552_284_75; + let (cx, cy) = (f32::from(center.x), f32::from(center.y)); + let (ox, oy) = (radius_x * KAPPA, radius_y * KAPPA); + let point = |x: f32, y: f32| gpui::point(px(x), px(y)); + builder.move_to(point(cx + radius_x, cy)); + builder.cubic_bezier_to( + point(cx, cy + radius_y), + point(cx + radius_x, cy + oy), + point(cx + ox, cy + radius_y), + ); + builder.cubic_bezier_to( + point(cx - radius_x, cy), + point(cx - ox, cy + radius_y), + point(cx - radius_x, cy + oy), + ); + builder.cubic_bezier_to( + point(cx, cy - radius_y), + point(cx - radius_x, cy - oy), + point(cx - ox, cy - radius_y), + ); + builder.cubic_bezier_to( + point(cx + radius_x, cy), + point(cx + ox, cy - radius_y), + point(cx + radius_x, cy - oy), + ); + builder.close(); +} + +fn paint_fill(builder: PathBuilder, color: Hsla, window: &mut Window) { + if let Ok(path) = builder.build() { + window.paint_path(path, color); + } +} + +/// A `stroke-linecap="round"` end, as the disc the cap is. +fn paint_disc(window: &mut Window, center: Point, radius: f32, color: Hsla) { + if radius <= 0.25 { + return; + } + let mut builder = PathBuilder::fill(); + push_circle(&mut builder, center, radius, radius); + paint_fill(builder, color, window); +} + +fn paint_layer(paint: &LayerPaint, bounds: Bounds, window: &mut Window, cx: &mut App) { + let origin = bounds.origin; + let scale = paint.scale; + + // The blur/pixelate regions sit between the frame and the annotations, the + // way the second canvas does over there. + for overlay in &paint.masks { + let region = Bounds { + origin: map_point(origin, scale, overlay.rect.x, overlay.rect.y), + size: gpui::size( + px(overlay.rect.width as f32 * scale), + px(overlay.rect.height as f32 * scale), + ), + }; + let _ = window.paint_image(region, Corners::default(), overlay.image.clone(), 0, false); + } + + for annotation in &paint.annotations { + if paint.editing.as_deref() == Some(annotation.id.as_str()) { + continue; + } + paint_annotation(annotation, origin, scale, window, cx); + } + + if paint.select_tool && paint.editing.is_none() { + // The hover wash, on an unselected text annotation only (`:730-751`). + if let Some(hovered) = paint + .hover + .as_deref() + .filter(|id| paint.selected.as_deref() != Some(id)) + .and_then(|id| { + paint + .annotations + .iter() + .find(|annotation| annotation.id == id) + }) + && hovered.annotation_type == AnnotationType::Text + { + let rect = screen_rect(origin, scale, selection_rect(hovered, paint.handle_size)); + let mut fill = PathBuilder::fill(); + push_rounded_rect(&mut fill, rect, 4.); + paint_fill( + fill, + Theme::with_alpha(gpui::rgb(SELECTION_BLUE), 0.05), + window, + ); + let mut stroke = PathBuilder::stroke(px(2.)); + push_rounded_rect(&mut stroke, rect, 4.); + paint_fill( + stroke, + Theme::with_alpha(gpui::rgb(SELECTION_BLUE), 0.4), + window, + ); + } + + if let Some(selected) = paint.selected.as_deref().and_then(|id| { + paint + .annotations + .iter() + .find(|annotation| annotation.id == id) + }) { + paint_selection(selected, origin, scale, paint.handle_size, window); + } + } + + if let Some(temp) = &paint.temp { + // A stroke's samples are absolute until mouse-up normalizes them, so + // the live preview paints them straight rather than through + // `RenderAnnotation`'s denormalizing branch (`:769-790`). Under two + // samples that branch draws nothing either, so the fallback is a + // no-op for a stroke and the real shape for everything else. + if temp.annotation_type == AnnotationType::Draw { + let points = temp.points.clone().unwrap_or_default(); + if points.len() >= 2 + && let Some(color) = annotation_color(&temp.stroke_color, temp.opacity) + { + paint_stroke_path( + &points, + origin, + scale, + (temp.stroke_width as f32 * scale).max(0.), + color, + window, + ); + } + } else { + paint_annotation(temp, origin, scale, window, cx); + } + } +} + +fn screen_rect(origin: Point, scale: f32, rect: Rect) -> Bounds { + Bounds { + origin: map_point(origin, scale, rect.x, rect.y), + size: gpui::size( + px(rect.width as f32 * scale), + px(rect.height as f32 * scale), + ), + } +} + +/// `RenderAnnotation` (`AnnotationLayer.tsx:813-933`). +fn paint_annotation( + annotation: &Annotation, + origin: Point, + scale: f32, + window: &mut Window, + cx: &mut App, +) { + let opacity = annotation.opacity; + let stroke_color = annotation_color(&annotation.stroke_color, opacity); + let fill_color = annotation_color(&annotation.fill_color, opacity); + let stroke_width = (annotation.stroke_width as f32 * scale).max(0.); + + match annotation.annotation_type { + // The mask's own rect is `fill="none" stroke="none"`; the overlay is + // what makes it visible (`:892-909`). + AnnotationType::Mask => {} + AnnotationType::Rectangle => { + let rect = normalized_rect(annotation); + if let Some(fill) = fill_color { + let mut builder = PathBuilder::fill(); + push_rect(&mut builder, origin, scale, rect); + paint_fill(builder, fill, window); + } + if let Some(stroke) = stroke_color + && stroke_width > 0. + { + let mut builder = PathBuilder::stroke(px(stroke_width)); + push_rect(&mut builder, origin, scale, rect); + paint_fill(builder, stroke, window); + } + } + AnnotationType::Circle => { + let center = map_point( + origin, + scale, + annotation.x + annotation.width / 2., + annotation.y + annotation.height / 2., + ); + let radius_x = (annotation.width / 2.).abs() as f32 * scale; + let radius_y = (annotation.height / 2.).abs() as f32 * scale; + if radius_x <= 0. || radius_y <= 0. { + return; + } + if let Some(fill) = fill_color { + let mut builder = PathBuilder::fill(); + push_circle(&mut builder, center, radius_x, radius_y); + paint_fill(builder, fill, window); + } + if let Some(stroke) = stroke_color + && stroke_width > 0. + { + let mut builder = PathBuilder::stroke(px(stroke_width)); + push_circle(&mut builder, center, radius_x, radius_y); + paint_fill(builder, stroke, window); + } + } + AnnotationType::Arrow => { + let Some(stroke) = stroke_color else { + return; + }; + let end = ( + annotation.x + annotation.width, + annotation.y + annotation.height, + ); + let angle = (end.1 - annotation.y).atan2(end.0 - annotation.x); + let head = arrow_head_points(end.0, end.1, angle, annotation.stroke_width); + if stroke_width > 0. { + let tail = map_point(origin, scale, annotation.x, annotation.y); + let base = map_point(origin, scale, head.base.0, head.base.1); + let mut builder = PathBuilder::stroke(px(stroke_width)); + builder.move_to(tail); + builder.line_to(base); + paint_fill(builder, stroke, window); + paint_disc(window, tail, stroke_width / 2., stroke); + paint_disc(window, base, stroke_width / 2., stroke); + } + let mut builder = PathBuilder::fill(); + let points: Vec> = head + .points + .iter() + .map(|(x, y)| map_point(origin, scale, *x, *y)) + .collect(); + builder.add_polygon(&points, true); + paint_fill(builder, stroke, window); + } + AnnotationType::Draw => { + let Some(stroke) = stroke_color else { + return; + }; + if stroke_width <= 0. { + return; + } + let points = draw_points(annotation); + paint_stroke_path(&points, origin, scale, stroke_width, stroke, window); + } + AnnotationType::Text => paint_text(annotation, origin, scale, window, cx), + } +} + +/// A smoothed freehand stroke, with the round caps its `stroke-linecap` asks +/// for. The quadratics `smooth_path` emits are already tangent-continuous, so +/// the joins need nothing extra. +fn paint_stroke_path( + points: &[[f64; 2]], + origin: Point, + scale: f32, + stroke_width: f32, + color: Hsla, + window: &mut Window, +) { + let segments = smooth_path(points); + if segments.is_empty() { + return; + } + let mut builder = PathBuilder::stroke(px(stroke_width)); + for segment in &segments { + match *segment { + PathSegment::Move { x, y } => builder.move_to(map_point(origin, scale, x, y)), + PathSegment::Line { x, y } => builder.line_to(map_point(origin, scale, x, y)), + PathSegment::Quad { cx, cy, x, y } => builder.curve_to( + map_point(origin, scale, x, y), + map_point(origin, scale, cx, cy), + ), + } + } + paint_fill(builder, color, window); + if let (Some(first), Some(last)) = (points.first(), points.last()) { + paint_disc( + window, + map_point(origin, scale, first[0], first[1]), + stroke_width / 2., + color, + ); + paint_disc( + window, + map_point(origin, scale, last[0], last[1]), + stroke_width / 2., + color, + ); + } +} + +/// `` (`:879-891`). SVG anchors a line on its +/// baseline; gpui centres the glyphs in the line box it is given, so a box of +/// exactly ascent+descent puts the baseline one ascent below the origin. +fn paint_text( + annotation: &Annotation, + origin: Point, + scale: f32, + window: &mut Window, + cx: &mut App, +) { + let Some(text) = annotation.text.as_deref().filter(|text| !text.is_empty()) else { + return; + }; + let Some(color) = annotation_color(&annotation.stroke_color, annotation.opacity) else { + return; + }; + let font_size = px((annotation.height as f32 * scale).max(1.)); + // `shape_line` takes one line; `white-space: pre` in an SVG `` never + // wraps either, so a stray newline becomes a space rather than a panic. + let text = if text.contains('\n') { + text.replace('\n', " ") + } else { + text.to_string() + }; + let run = TextRun { + len: text.len(), + font: gpui::font(TEXT_FONT), + color, + background_color: None, + underline: None, + strikethrough: None, + }; + let line = window + .text_system() + .shape_line(text.into(), font_size, &[run], None); + let line_height = line.ascent + line.descent; + let baseline = origin.y + px((annotation.y + annotation.height) as f32 * scale); + let _ = line.paint( + gpui::point( + origin.x + px(annotation.x as f32 * scale), + baseline - line.ascent, + ), + line_height, + gpui::TextAlign::Left, + None, + window, + cx, + ); +} + +/// `SelectionHandles` (`:935-1064`), in screen units. +fn paint_selection( + annotation: &Annotation, + origin: Point, + scale: f32, + handle_size: f64, + window: &mut Window, +) { + let is_text = annotation.annotation_type == AnnotationType::Text; + if is_text { + let rect = screen_rect(origin, scale, selection_rect(annotation, handle_size)); + let mut fill = PathBuilder::fill(); + push_rounded_rect(&mut fill, rect, 4.); + paint_fill( + fill, + Theme::with_alpha(gpui::rgb(SELECTION_BLUE), 0.1), + window, + ); + let mut stroke = PathBuilder::stroke(px(2.)); + push_rounded_rect(&mut stroke, rect, 4.); + paint_fill(stroke, gpui::rgb(SELECTION_BLUE).into(), window); + } + + let radius = HANDLE_SCREEN_SIZE as f32 / 2.; + for (_, (x, y)) in handles(annotation, handle_size) { + let center = map_point(origin, scale, x, y); + let mut fill = PathBuilder::fill(); + push_circle(&mut fill, center, radius, radius); + paint_fill( + fill, + if is_text { + gpui::rgb(SELECTION_BLUE).into() + } else { + gpui::white() + }, + window, + ); + let mut ring = PathBuilder::stroke(px(if is_text { 1.5 } else { 1. })); + push_circle(&mut ring, center, radius, radius); + paint_fill( + ring, + if is_text { + gpui::white() + } else { + gpui::rgb(SELECTION_BLUE).into() + }, + window, + ); + } +} + +// --------------------------------------------------------------------------- +// Chrome helpers +// --------------------------------------------------------------------------- + +/// `ConfigItem` (`AnnotationConfig.tsx:177-193`). +fn config_item( + theme: &Theme, + label: &'static str, + value: Option, + control: AnyElement, +) -> AnyElement { + let theme = *theme; + div() + .flex() + .flex_row() + .items_center() + .gap(px(8.)) + .flex_shrink_0() + .child( + div() + .flex() + .flex_row() + .items_center() + .gap(px(4.)) + .flex_shrink_0() + .text_size(px(11.)) + .font_weight(FontWeight::MEDIUM) + .text_color(theme.gray_10) + .child(label) + .children(value.map(|value| div().text_color(theme.gray_11).child(value))), + ) + .child(control) + .into_any_element() +} + +/// The transparent swatch's checkerboard -- four quarters, which is what the +/// CSS pattern reduces to at this size. +fn checker_quarters(size: f32) -> Vec { + let half = size / 2.; + let light: Hsla = gpui::white(); + let dark: Hsla = gpui::rgb(0xcccccc).into(); + [ + (0., 0., light), + (half, 0., dark), + (0., half, dark), + (half, half, light), + ] + .into_iter() + .map(|(x, y, fill)| { + div() + .absolute() + .left(px(x)) + .top(px(y)) + .size(px(half)) + .bg(fill) + .into_any_element() + }) + .collect() +} + +// --------------------------------------------------------------------------- +// Mask overlays +// --------------------------------------------------------------------------- + +/// `renderMaskOverlays` + `blurRegion` (`Preview.tsx:537-687`): resample the +/// frame's own pixels inside each mask and hand back one image per region. +/// Runs on the background executor -- the regions are small, but a 50-block +/// pixelate over a 4K screenshot is still not main-thread work. +fn build_mask_overlays( + rgba: &[u8], + size: (u32, u32), + masks: &[Annotation], + rect: Rect, +) -> Vec { + let mut overlays = Vec::new(); + for mask in masks { + let Some((x0, y0, processed)) = masked_region_image(rgba, size, mask, rect) else { + continue; + }; + overlays.push(MaskOverlay { + rect: Rect { + x: x0 as f64, + y: y0 as f64, + width: processed.width() as f64, + height: processed.height() as f64, + }, + image: crate::library::rgba_to_render_image(processed), + }); + } + overlays +} + +/// One mask's blurred or pixelated pixels, sampled from `rgba` (an unmodified +/// copy of the frame) and returned with the region's top-left in frame +/// coordinates. This is the single implementation of the two filters -- the +/// preview's overlay tiles come through it, and so does the export's +/// `applyMaskAnnotations` pass, which calls it with `rect` set to the whole +/// canvas (`screenshotExport.ts:290-295` passes `{x:0,y:0,w,h}`). +pub(crate) fn masked_region_image( + rgba: &[u8], + size: (u32, u32), + mask: &Annotation, + rect: Rect, +) -> Option<(u32, u32, image::RgbaImage)> { + let (frame_width, frame_height) = size; + if frame_width == 0 || frame_height == 0 { + return None; + } + let stride = frame_width as usize * 4; + if rgba.len() < stride * frame_height as usize { + return None; + } + + let (x0, y0, width, height) = mask_region(mask, rect, (frame_width, frame_height))?; + + let mut region = Vec::with_capacity(width as usize * height as usize * 4); + for row in 0..height { + let start = (y0 + row) as usize * stride + x0 as usize * 4; + region.extend_from_slice(&rgba[start..start + width as usize * 4]); + } + let source = image::RgbaImage::from_raw(width, height, region)?; + + let level = mask.mask_level.unwrap_or(MASK_FALLBACK_LEVEL).max(1.); + let processed = if mask.mask_type == Some(MaskType::Pixelate) { + // `blockSize = Math.max(2, Math.round(level))`, nearest both ways. + let block = (level.round() as u32).max(2); + let small = image::imageops::resize( + &source, + (width / block).max(1), + (height / block).max(1), + image::imageops::FilterType::Nearest, + ); + image::imageops::resize(&small, width, height, image::imageops::FilterType::Nearest) + } else { + // `blurRegion`: `scale = Math.max(2, Math.round(level / 4))`, with + // smoothing on both passes. + let factor = ((level / 4.).round() as u32).max(2); + let small = image::imageops::resize( + &source, + (width / factor).max(1), + (height / factor).max(1), + image::imageops::FilterType::Triangle, + ); + image::imageops::resize(&small, width, height, image::imageops::FilterType::Triangle) + }; + + Some((x0, y0, processed)) +} + +/// One mask's region in whole frame pixels: its own rect, clipped to the +/// screenshot's content rect and then to the frame. +fn mask_region(mask: &Annotation, rect: Rect, frame: (u32, u32)) -> Option<(u32, u32, u32, u32)> { + let left = rect.x.max(mask.x.min(mask.x + mask.width)); + let top = rect.y.max(mask.y.min(mask.y + mask.height)); + let right = (rect.x + rect.width).min(mask.x.max(mask.x + mask.width)); + let bottom = (rect.y + rect.height).min(mask.y.max(mask.y + mask.height)); + if right <= left || bottom <= top { + return None; + } + let x0 = (left.round().max(0.) as u32).min(frame.0); + let y0 = (top.round().max(0.) as u32).min(frame.1); + let x1 = (right.round().max(0.) as u32).min(frame.0); + let y1 = (bottom.round().max(0.) as u32).min(frame.1); + (x1 > x0 && y1 > y0).then(|| (x0, y0, x1 - x0, y1 - y0)) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn transform( + frame: (f64, f64), + image: (f64, f64), + padding: f64, + aspect: Option, + ) -> ImageTransform { + calculate_image_transform(frame, image, padding, None, aspect.as_ref()) + } + + /// Auto aspect, no padding: the content fills the frame exactly. + /// + /// `paddingFactor` is 0, so `getBaseSize`'s auto branch rounds 800x600 to + /// 800x600 (`(floor(800) + 1) & ~1`), `outputScale` is 1 and both offsets + /// are zero. + #[test] + fn the_auto_branch_with_no_padding_is_the_identity() { + let result = transform((800., 600.), (800., 600.), 0., None); + assert_eq!(result.offset, (0., 0.)); + assert_eq!(result.size, (800., 600.)); + } + + /// Auto aspect at padding 50: `paddingFactor` is `0.5 * 0.4 = 0.2`, so the + /// base size is `800 * 1.4 = 1120` by `600 * 1.4 = 840` (both already even) + /// and a frame of exactly that size scales 1:1. The offsets are then + /// `800 * 0.2` and `600 * 0.2`, and the content is back to 800x600. + #[test] + fn the_auto_branch_insets_by_the_padding_factor() { + let result = transform((1120., 840.), (800., 600.), 50., None); + assert_eq!(result.offset, (160., 120.)); + assert_eq!(result.size, (800., 600.)); + } + + /// A square frame around a 4:3 image, no padding: the image is wider than + /// the frame, so `isHeightConstrained` is false, the content takes the full + /// width and is centred vertically at `(800 - 600) / 2`. + #[test] + fn a_fixed_aspect_letterboxes_the_content_and_centres_it() { + let result = transform((800., 800.), (800., 600.), 0., Some(AspectRatio::Square)); + assert_eq!(result.offset, (0., 100.)); + assert_eq!(result.size, (800., 600.)); + } + + /// A frame that has not been measured yet must not divide by zero. + #[test] + fn a_degenerate_frame_falls_back_to_the_frame_itself() { + let result = transform((0., 0.), (800., 600.), 20., None); + assert_eq!(result.offset, (0., 0.)); + assert_eq!(result.size, (0., 0.)); + } + + #[test] + fn image_rect_without_an_image_size_is_the_whole_frame() { + let rect = image_rect((1280., 720.), None, 40., None, None); + assert_eq!( + rect, + Rect { + x: 0., + y: 0., + width: 1280., + height: 720. + } + ); + } + + /// The resize effect carries an annotation across proportionally: a shape + /// that sat at the content's top-left corner stays there, and one that was + /// half as wide as the content stays half as wide. + #[test] + fn annotations_ride_the_content_rect_across_a_resize() { + let previous = ImageTransform { + offset: (10., 20.), + size: (100., 50.), + }; + let current = ImageTransform { + offset: (30., 40.), + size: (200., 100.), + }; + let mut annotations = vec![Annotation { + id: "a".into(), + annotation_type: AnnotationType::Rectangle, + x: 10., + y: 20., + width: 50., + height: 25., + stroke_color: "#000000".into(), + stroke_width: 2., + fill_color: "transparent".into(), + opacity: 1., + rotation: 0., + text: None, + mask_type: None, + mask_level: None, + points: None, + }]; + rescale_annotations(&mut annotations, &previous, ¤t); + assert_eq!((annotations[0].x, annotations[0].y), (30., 40.)); + assert_eq!((annotations[0].width, annotations[0].height), (100., 50.)); + } + + #[test] + fn an_arrow_head_grows_with_its_stroke_but_never_below_the_floor() { + assert_eq!(arrow_head_size(1.), (20., 14.)); + assert_eq!(arrow_head_size(10.), (60., 50.)); + } + + /// Pointing straight right: the base sits `length` behind the tip and the + /// flanks are half the head's width above and below it. + #[test] + fn an_arrow_head_squares_up_along_its_angle() { + let head = arrow_head_points(100., 100., 0., 4.); + assert_eq!(head.base, (76., 100.)); + assert_eq!(head.points[0], (100., 100.)); + assert_eq!(head.points[1], (76., 90.)); + assert_eq!(head.points[2], (76., 110.)); + } + + #[test] + fn a_two_point_stroke_is_a_straight_line() { + let path = smooth_path(&[[0., 0.], [10., 10.]]); + assert_eq!( + path, + vec![ + PathSegment::Move { x: 0., y: 0. }, + PathSegment::Line { x: 10., y: 10. } + ] + ); + } + + #[test] + fn a_longer_stroke_runs_quadratics_through_the_midpoints() { + let path = smooth_path(&[[0., 0.], [10., 0.], [20., 0.]]); + assert_eq!( + path, + vec![ + PathSegment::Move { x: 0., y: 0. }, + PathSegment::Quad { + cx: 0., + cy: 0., + x: 5., + y: 0. + }, + PathSegment::Quad { + cx: 10., + cy: 0., + x: 15., + y: 0. + }, + PathSegment::Line { x: 20., y: 0. }, + ] + ); + } + + #[test] + fn a_stroke_with_fewer_than_two_samples_draws_nothing() { + assert!(smooth_path(&[]).is_empty()); + assert!(smooth_path(&[[1., 1.]]).is_empty()); + } + + #[test] + fn a_text_layer_is_labelled_with_its_own_content() { + let mut annotation = Annotation { + id: "a".into(), + annotation_type: AnnotationType::Text, + x: 0., + y: 0., + width: 0., + height: 0., + stroke_color: "#000000".into(), + stroke_width: 1., + fill_color: "transparent".into(), + opacity: 1., + rotation: 0., + text: Some("Hello".into()), + mask_type: None, + mask_level: None, + points: None, + }; + assert_eq!(layer_label(&annotation), "Hello"); + annotation.text = Some("A very long caption".into()); + assert_eq!(layer_label(&annotation), "A very long ..."); + annotation.text = None; + assert_eq!(layer_label(&annotation), "Text"); + } + + // ----------------------------------------------------------------------- + // The engine + // ----------------------------------------------------------------------- + + fn shape( + annotation_type: AnnotationType, + x: f64, + y: f64, + width: f64, + height: f64, + ) -> Annotation { + Annotation { + id: "a".into(), + annotation_type, + x, + y, + width, + height, + stroke_color: "#F05656".into(), + stroke_width: 4., + fill_color: "transparent".into(), + opacity: 1., + rotation: 0., + text: None, + mask_type: None, + mask_level: None, + points: None, + } + } + + /// `Math.min(Math.max(v, min), max)`: a window narrower than the value + /// collapses to its *upper* bound, which is what keeps a mask wider than + /// the screenshot pinned to the right edge rather than the left. + #[test] + fn clamping_follows_the_sources_argument_order() { + assert_eq!(clamp_value(5., 0., 10.), 5.); + assert_eq!(clamp_value(-5., 0., 10.), 0.); + assert_eq!(clamp_value(50., 0., 10.), 10.); + assert_eq!(clamp_value(5., 10., 0.), 0.); + } + + #[test] + fn a_negative_box_normalizes_to_its_top_left() { + let rect = normalized_rect(&shape(AnnotationType::Rectangle, 100., 100., -40., -20.)); + assert_eq!( + rect, + Rect { + x: 60., + y: 80., + width: 40., + height: 20. + } + ); + } + + /// A rectangle is hit anywhere inside it -- `fill="transparent"` is still a + /// paint, so SVG's `visiblePainted` counts it -- plus half a stroke out. + #[test] + fn a_rectangle_is_hit_across_its_whole_face() { + let rectangle = shape(AnnotationType::Rectangle, 10., 10., 100., 50.); + assert!(hit_annotation(&rectangle, (60., 30.), 10.)); + assert!(hit_annotation(&rectangle, (10., 10.), 10.)); + // Half of the 4px stroke hangs outside the box. + assert!(hit_annotation(&rectangle, (8.5, 30.), 10.)); + assert!(!hit_annotation(&rectangle, (5., 30.), 10.)); + } + + /// An ellipse's corners are not inside it. + #[test] + fn a_circle_is_hit_inside_its_ellipse_only() { + let circle = shape(AnnotationType::Circle, 0., 0., 100., 50.); + assert!(hit_annotation(&circle, (50., 25.), 10.)); + assert!(hit_annotation(&circle, (98., 25.), 10.)); + assert!(!hit_annotation(&circle, (2., 2.), 10.)); + } + + /// A mask paints nothing, but its rect still takes the pointer + /// (`pointer-events: all`, `AnnotationLayer.tsx:907`). + #[test] + fn a_mask_is_hit_inside_its_invisible_rect() { + let mut mask = shape(AnnotationType::Mask, 0., 0., 40., 40.); + mask.stroke_width = 0.; + assert!(hit_annotation(&mask, (20., 20.), 10.)); + assert!(!hit_annotation(&mask, (60., 20.), 10.)); + } + + /// Text is hit on the padded box the hover overlay draws, not on the + /// glyphs (`:738-750`). + #[test] + fn text_is_hit_on_its_padded_box() { + let mut text = shape(AnnotationType::Text, 100., 100., 150., 40.); + text.text = Some("Hello".into()); + // handleSize 10 -> 3 units of padding on every side. + assert!(hit_annotation(&text, (98., 98.), 10.)); + assert!(!hit_annotation(&text, (96., 98.), 10.)); + assert!(hit_annotation(&text, (252., 142.), 10.)); + } + + /// A stroke is `fill="none"`, but the `` around it sets + /// `pointer-events: all`, which ignores that: the line *and* the region it + /// encloses are both targets. + #[test] + fn a_stroke_is_hit_on_its_line_and_inside_what_it_encloses() { + let mut open = shape(AnnotationType::Draw, 0., 0., 100., 100.); + open.points = Some(vec![[0., 0.], [1., 1.]]); + assert!(hit_annotation(&open, (50., 50.), 10.)); + // Two samples enclose nothing, so the far corner stays clear. + assert!(!hit_annotation(&open, (90., 10.), 10.)); + + let mut loop_back = shape(AnnotationType::Draw, 0., 0., 100., 100.); + loop_back.points = Some(vec![[0., 0.], [1., 0.], [1., 1.], [0., 1.]]); + // Dead centre of the loop: nowhere near the line, still selectable. + assert!(hit_annotation(&loop_back, (50., 50.), 10.)); + assert!(!hit_annotation(&loop_back, (150., 50.), 10.)); + } + + /// SVG disables rendering for a zero radius, and an unrendered element is + /// not a pointer target. + #[test] + fn a_flat_circle_is_not_a_target() { + let flat = shape(AnnotationType::Circle, 0., 0., 100., 0.); + assert!(!hit_annotation(&flat, (50., 0.), 10.)); + } + + /// An arrow is its shaft plus its head, not its bounding box. + #[test] + fn an_arrow_is_hit_on_its_shaft_or_its_head() { + let arrow = shape(AnnotationType::Arrow, 0., 0., 200., 0.); + assert!(hit_annotation(&arrow, (100., 1.), 10.)); + assert!(hit_annotation(&arrow, (195., 0.), 10.)); + assert!(!hit_annotation(&arrow, (100., 40.), 10.)); + } + + /// Later siblings paint over earlier ones, so the hit test walks backwards. + #[test] + fn the_hit_test_takes_the_topmost_annotation() { + let mut under = shape(AnnotationType::Rectangle, 0., 0., 100., 100.); + under.id = "under".into(); + let mut over = shape(AnnotationType::Rectangle, 0., 0., 100., 100.); + over.id = "over".into(); + let annotations = vec![under, over]; + assert_eq!(hit_test(&annotations, (50., 50.), 10.), Some(1)); + assert_eq!(hit_test(&annotations, (500., 50.), 10.), None); + } + + /// Eight grips for a shape, the four corners for text, and the two + /// endpoints for an arrow. + #[test] + fn the_handle_set_depends_on_the_annotation() { + let rectangle = shape(AnnotationType::Rectangle, 0., 0., 100., 50.); + let grips = handles(&rectangle, 10.); + assert_eq!(grips.len(), 8); + assert_eq!(grips[0], (Handle::Nw, (0., 0.))); + assert_eq!(grips[7], (Handle::Se, (100., 50.))); + + let text = shape(AnnotationType::Text, 0., 0., 100., 40.); + let grips = handles(&text, 10.); + assert_eq!(grips.len(), 4); + // The text box is padded by 30 % of a handle before the corners land. + assert_eq!(grips[0], (Handle::Nw, (-3., -3.))); + + let arrow = shape(AnnotationType::Arrow, 10., 10., 40., 20.); + assert_eq!( + handles(&arrow, 10.), + vec![(Handle::Start, (10., 10.)), (Handle::End, (50., 30.))] + ); + } + + #[test] + fn a_grip_is_hit_within_its_own_radius() { + let rectangle = shape(AnnotationType::Rectangle, 0., 0., 100., 50.); + assert_eq!(hit_handle(&rectangle, (100., 50.), 10.), Some(Handle::Se)); + assert_eq!(hit_handle(&rectangle, (52., 0.), 10.), Some(Handle::N)); + assert_eq!(hit_handle(&rectangle, (50., 25.), 10.), None); + } + + /// `finalizeDrag` (`LayersPanel.tsx:93-124`). The panel is reversed, so + /// reversed index 0 is the *last* annotation; dropping below the dragged + /// row also loses a slot to the row's own removal. + #[test] + fn a_reorder_maps_the_reversed_list_back_onto_the_store() { + // Three annotations, listed [2, 1, 0]. Dragging the front one (reversed + // 0, actual 2) to the bottom of the panel lands it at actual 0. + assert_eq!(reorder_move(3, 0, 3), Some((2, 0))); + // Dragging the back one (reversed 2, actual 0) to the top puts it last. + assert_eq!(reorder_move(3, 2, 0), Some((0, 2))); + // One step down the panel is one step back in the store. + assert_eq!(reorder_move(3, 0, 2), Some((2, 1))); + // A drop in the row's own gaps is a no-op, on either side. + assert_eq!(reorder_move(3, 1, 1), None); + assert_eq!(reorder_move(3, 1, 2), None); + assert_eq!(reorder_move(0, 0, 0), None); + } + + /// The auto-clamp effect (`AnnotationLayer.tsx:88-141`). + #[test] + fn a_mask_is_clipped_into_the_screenshot_and_dropped_when_it_vanishes() { + let rect = Rect { + x: 100., + y: 100., + width: 400., + height: 300., + }; + let inside = shape(AnnotationType::Mask, 150., 150., 100., 100.); + assert_eq!(clamp_mask(&inside, rect), MaskClamp::Unchanged); + + let overhanging = shape(AnnotationType::Mask, 50., 50., 200., 200.); + assert_eq!( + clamp_mask(&overhanging, rect), + MaskClamp::Clamped(Rect { + x: 100., + y: 100., + width: 150., + height: 150. + }) + ); + + // Slid off the left edge: what is left is thinner than 5px. + let outside = shape(AnnotationType::Mask, 20., 150., 82., 100.); + assert_eq!(clamp_mask(&outside, rect), MaskClamp::Remove); + } + + /// The region `renderMaskOverlays` resamples: the mask, clipped to the + /// content rect and then to the frame, in whole pixels. + #[test] + fn a_mask_region_is_clipped_to_the_content_rect_and_the_frame() { + let rect = Rect { + x: 10., + y: 10., + width: 100., + height: 100., + }; + let mask = shape(AnnotationType::Mask, 0., 0., 60., 60.); + assert_eq!(mask_region(&mask, rect, (200, 200)), Some((10, 10, 50, 50))); + + // A negative box is the same region. + let flipped = shape(AnnotationType::Mask, 60., 60., -50., -50.); + assert_eq!( + mask_region(&flipped, rect, (200, 200)), + Some((10, 10, 50, 50)) + ); + + // Entirely outside the content rect. + let away = shape(AnnotationType::Mask, 500., 500., 50., 50.); + assert_eq!(mask_region(&away, rect, (200, 200)), None); + } + + /// The resample itself: one overlay per mask, at the region's own size, + /// with the row slicing staying inside the frame buffer. + #[test] + fn a_mask_resamples_the_frames_own_pixels() { + let frame = (64u32, 48u32); + let rgba = vec![128u8; frame.0 as usize * frame.1 as usize * 4]; + let rect = Rect { + x: 0., + y: 0., + width: 64., + height: 48., + }; + let mut blur = shape(AnnotationType::Mask, 8., 8., 32., 24.); + blur.mask_type = Some(MaskType::Blur); + blur.mask_level = Some(16.); + let mut pixelate = shape(AnnotationType::Mask, 40., 30., 20., 16.); + pixelate.id = "b".into(); + pixelate.mask_type = Some(MaskType::Pixelate); + pixelate.mask_level = Some(7.); + + let overlays = build_mask_overlays(&rgba, frame, &[blur, pixelate], rect); + assert_eq!(overlays.len(), 2); + assert_eq!( + overlays[0].rect, + Rect { + x: 8., + y: 8., + width: 32., + height: 24. + } + ); + assert_eq!( + overlays[1].rect, + Rect { + x: 40., + y: 30., + width: 20., + height: 16. + } + ); + + // A frame buffer that does not match the size it was handed is not + // sliced at all. + assert!(build_mask_overlays(&rgba[..16], frame, &[], rect).is_empty()); + } + + /// A finished stroke stores fractions of its own box, so a later resize + /// carries the samples with it (`:449-453`, `:915-921`). + #[test] + fn a_stroke_normalizes_into_its_box_and_denormalizes_back() { + let raw = [[10., 20.], [30., 20.], [30., 60.]]; + let normalized = normalize_draw_points(&raw, 10., 20., 20., 40.); + assert_eq!(normalized, vec![[0., 0.], [1., 0.], [1., 1.]]); + + let mut annotation = shape(AnnotationType::Draw, 10., 20., 20., 40.); + annotation.points = Some(normalized); + assert_eq!(draw_points(&annotation), raw.to_vec()); + } + + /// A zero-width box would divide by zero; the source's `|| 1` is why it + /// does not (`:449-450`). + #[test] + fn a_flat_stroke_normalizes_against_one_pixel() { + let normalized = normalize_draw_points(&[[5., 5.], [5., 25.]], 5., 5., 0., 20.); + assert_eq!(normalized, vec![[0., 0.], [0., 1.]]); + } + + /// A resize dragged through the origin mirrors the samples rather than + /// leaving the stroke inside out (`:544-556`). + #[test] + fn a_stroke_dragged_inside_out_mirrors_its_samples() { + let points = [[0., 0.25], [1., 0.75]]; + assert_eq!( + flip_draw_points(&points, true, false), + vec![[1., 0.25], [0., 0.75]] + ); + assert_eq!( + flip_draw_points(&points, false, true), + vec![[0., 0.75], [1., 0.25]] + ); + assert_eq!(flip_draw_points(&points, false, false), points.to_vec()); + } + + /// `transparent` is a sentinel, not a colour: it paints nothing at all. + #[test] + fn transparent_paints_nothing_and_a_hex_carries_the_opacity() { + assert!(annotation_color("transparent", 1.).is_none()); + assert!(annotation_color("", 1.).is_none()); + let color = annotation_color("#F05656", 0.5).expect("a hex colour"); + assert!((color.a - 0.5).abs() < 0.001); + } + + /// The drag-end snapshot only records when the drag moved something. + #[test] + fn an_unmoved_drag_is_not_a_change() { + let before = shape(AnnotationType::Rectangle, 0., 0., 10., 10.); + let mut after = before.clone(); + assert!(!geometry_changed(&before, &after)); + after.x = 1.; + assert!(geometry_changed(&before, &after)); + } +} diff --git a/apps/desktop-gpui/src/screenshot_crop.rs b/apps/desktop-gpui/src/screenshot_crop.rs new file mode 100644 index 0000000000..21e6f1c712 --- /dev/null +++ b/apps/desktop-gpui/src/screenshot_crop.rs @@ -0,0 +1,1155 @@ +//! The screenshot editor's crop dialog -- the `type: "crop"` arm of +//! `routes/screenshot-editor/Editor.tsx`'s `Dialogs()` (`:199-492`). +//! +//! It is the *simpler* of the two crop dialogs: one cropper over the original +//! PNG, no live preview pane beside it. The header carries the Size/Position +//! number boxes, the round ratio button, Full and Reset; the footer a single +//! Save that writes `background.crop` as one history entry. Escape and the +//! backdrop cancel without writing anything -- the dialog never touches the +//! project until Save, which is why nothing here publishes to the renderer. +//! +//! The engine is [`crate::editor_crop`]'s wholesale: the same `CropState` +//! (container/target space split, ratio locking, snap, the 240ms animation), +//! the same `hit_test`/`handle_rect` zones, the same painters for the +//! occluders, thirds grid and corner glyphs, and the same window-wide drag +//! layer. What differs is the box sizing -- `previewSize()` +//! (`Editor.tsx:278-289`): `min(vw * 0.8, 768) x vh * 0.65` fitted to the +//! *image's* aspect, not the video dialog's `min(vw * 0.4, 520)` pair -- and +//! the snap toggle's home, `store::PersistedState.screenshot_crop_snap_to_ratio` +//! (`editorCropSnapToRatio` in localStorage over there). + +use std::cell::Cell; +use std::collections::HashMap; +use std::path::Path; +use std::rc::Rc; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use cap_project::{Crop, XY}; +use gpui::{ + AnyElement, AppContext as _, Bounds, Context, CursorStyle, Entity, FontWeight, Hsla, + InteractiveElement as _, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent, + ParentElement as _, Pixels, Point, RenderImage, SharedString, StatefulInteractiveElement as _, + Styled as _, Window, div, img, px, svg, +}; + +use crate::editor_crop::{ + self as engine, CropAnim, CropBounds, CropDrag, CropField, CropHit, CropMenuChoice, CropState, + HANDLES, ResizeSession, SE_HANDLE, Vec2, +}; +use crate::screenshot_editor::ScreenshotEditorWindow; +use crate::theme::Theme; +use crate::ui; + +/// `Dialog.Header` -- `h-14 px-4` (`ui.tsx:224-226`). +const DIALOG_HEADER_HEIGHT: f32 = 56.; +/// `Dialog.Footer` -- `h-16 px-4` (`ui.tsx:204-222`). +const DIALOG_FOOTER_HEIGHT: f32 = 64.; +/// `w-13` on each of the four header boxes (`Editor.tsx:351-366`). +const BOUND_INPUT_WIDTH: f32 = 52.; + +/// The header boxes, in `Editor.tsx`'s order: Size W x H, then Position X x Y. +const CROP_FIELDS: [CropField; 4] = [ + CropField::Width, + CropField::Height, + CropField::X, + CropField::Y, +]; + +/// `previewSize()` (`Editor.tsx:278-289`): the source's aspect fitted into +/// `min(vw * 0.8, 768) x vh * 0.65`. Unlike the video dialog's box this can +/// upscale a small screenshot -- the source does not clamp the ratio at 1. +pub fn crop_box_size(viewport: (f32, f32), image: (u32, u32)) -> (f32, f32) { + let source_width = image.0.max(1) as f32; + let source_height = image.1.max(1) as f32; + let max_width = (viewport.0 * 0.8).min(768.); + let max_height = viewport.1 * 0.65; + let ratio = (max_width / source_width).min(max_height / source_height); + (source_width * ratio, source_height * ratio) +} + +/// Everything the open dialog owns. `None` on the window means closed. +pub struct ScreenshotCropDialog { + pub(crate) state: CropState, + /// The cropper box's painted rect -- the `getBoundingClientRect` stand-in + /// every pointer position is made container-local through. + pub(crate) area: Rc>>>, + /// `bundle/original.png`, decoded once on the background executor -- the + /// `` (`Editor.tsx:453-457`). + image: Option>, + image_task: Option>, + /// `editorCropSnapToRatio`, loaded from the store at open and written back + /// on toggle. + snap_to_ratio: bool, + fields: HashMap>, + /// Dropped with the dialog, which is what unsubscribes the field events. + _field_subscriptions: Vec, +} + +impl ScreenshotEditorWindow { + /// The header's crop button (`Header.tsx:84-93`): open the dialog seeded + /// with the crop in force, or the whole image. Gated on `image_size` the + /// same way the button's `disabled` is. + pub(crate) fn open_crop_dialog(&mut self, window: &mut Window, cx: &mut Context) { + if self.crop.is_some() { + return; + } + let Some(target) = self.image_size else { + return; + }; + + let initial = match &self.project.background.crop { + Some(crop) => CropBounds::new( + f64::from(crop.position.x), + f64::from(crop.position.y), + f64::from(crop.size.x), + f64::from(crop.size.y), + ), + None => CropBounds::new(0., 0., f64::from(target.0), f64::from(target.1)), + }; + + let viewport = window.viewport_size(); + let box_size = crop_box_size((viewport.width.into(), viewport.height.into()), target); + let state = CropState::new(target, box_size, initial); + + let mut fields = HashMap::new(); + let mut subscriptions = Vec::new(); + for field in CROP_FIELDS { + let input = cx.new(|cx| ui::TextInputState::single_line(window, cx)); + subscriptions.push(cx.subscribe_in( + &input, + window, + move |this: &mut Self, _input, event: &ui::TextInputEvent, window, cx| { + this.on_crop_field_event(field, event, window, cx); + }, + )); + fields.insert(field, input); + } + + let mut dialog = ScreenshotCropDialog { + state, + area: Rc::default(), + image: None, + image_task: None, + snap_to_ratio: crate::store::load() + .screenshot_crop_snap_to_ratio + .unwrap_or(true), + fields, + _field_subscriptions: subscriptions, + }; + + let image_path = crate::screenshot_editor::bundle_image_path(&self.bundle); + dialog.image_task = Some(cx.spawn_in(window, async move |this, cx| { + let decoded = cx + .background_executor() + .spawn(async move { decode_crop_image(image_path.as_deref()) }) + .await; + this.update(cx, |this, cx| { + if let Some(dialog) = this.crop.as_mut() { + dialog.image = decoded; + cx.notify(); + } + }) + .ok(); + })); + + self.crop = Some(dialog); + // The five styling popovers and the chrome menus sit under the modal. + self.dismiss_chrome_popups(); + window.focus(&self.focus, cx); + cx.notify(); + window.refresh(); + } + + /// Close without writing -- Escape or the backdrop. The project was never + /// touched, so there is nothing to roll back. + pub(crate) fn cancel_crop_dialog(&mut self, reason: &'static str, cx: &mut Context) { + if self.crop.take().is_none() { + return; + } + tracing::info!(reason, "screenshot crop cancelled"); + cx.notify(); + } + + /// The footer's Save (`Editor.tsx:463-482`): one `setProject` write of + /// `background.crop`, so one history entry, then close. + fn save_crop_dialog(&mut self, window: &mut Window, cx: &mut Context) { + let Some(dialog) = self.crop.take() else { + return; + }; + let bounds = dialog.state.real(); + let crop = Crop { + position: XY::new(bounds.x.max(0.) as u32, bounds.y.max(0.) as u32), + size: XY::new(bounds.width.max(0.) as u32, bounds.height.max(0.) as u32), + }; + tracing::info!( + crop = format!( + "{},{} {}x{}", + crop.position.x, crop.position.y, crop.size.x, crop.size.y + ), + "screenshot crop saved" + ); + self.edit_project( + move |project| { + project.background.crop = Some(crop); + true + }, + window, + cx, + ); + window.refresh(); + } + + /// The source's `ResizeObserver` + window-resize pair: the box is a + /// fraction of the viewport, so every render re-derives it and the + /// *target*-space rect survives. Runs from `render`. + pub(crate) fn sync_crop_dialog_container(&mut self, window: &Window) { + let Some((target, area)) = self + .crop + .as_ref() + .map(|dialog| (dialog.state.target, dialog.area.get())) + else { + return; + }; + let viewport = window.viewport_size(); + let box_size = crop_box_size((viewport.width.into(), viewport.height.into()), target); + // Measured, not computed: the screenshot cropper has no hairline + // border, so its content box is the box itself -- but the cell is + // still the truth once it has been painted. + let container = match area { + Some(bounds) if bounds.size.width > px(1.) && bounds.size.height > px(1.) => { + (f32::from(bounds.size.width), f32::from(bounds.size.height)) + } + _ => box_size, + }; + if let Some(dialog) = self.crop.as_mut() { + dialog.state.set_container(box_size, container); + } + } + + // -- Pointer -------------------------------------------------------------- + + /// One handler for the whole container -- `editor_crop::crop_mouse_down`'s + /// shape, against this window's dialog. + pub(crate) fn crop_dialog_mouse_down( + &mut self, + event: &MouseDownEvent, + window: &mut Window, + cx: &mut Context, + ) { + let Some(dialog) = self.crop.as_mut() else { + return; + }; + let area = dialog.area.get(); + let state = &mut dialog.state; + let point = engine::crop_local(area, event.position); + + if event.click_count == 2 { + match engine::hit_test(point, state.raw) { + CropHit::Handle(handle) => { + state.stop_animation(); + let container = state.container_vec(); + let bounds = engine::double_click_bounds(handle, state.raw, container); + state.set_raw_and_animate(bounds, handle.origin, CropAnim::DEFAULT); + } + CropHit::Move => {} + CropHit::Draw => state.fill(), + } + state.drag = None; + self.start_crop_dialog_ticker(window, cx); + cx.notify(); + return; + } + + state.stop_animation(); + match engine::hit_test(point, state.raw) { + CropHit::Handle(handle) => { + state.hovering = Some(handle); + state.drag = Some(CropDrag::Handle(ResizeSession { + start_bounds: state.raw, + is_alt: event.modifiers.alt, + active_handle: handle, + original_handle: handle, + })); + } + CropHit::Move => { + let bounds = state.raw; + state.drag = Some(CropDrag::Region { + start_offset: Vec2::new(point.x - bounds.x, point.y - bounds.y), + bounds, + }); + } + CropHit::Draw => { + let restore = state.raw; + let start = CropBounds::new(point.x, point.y, 1., 1.); + state.drag = Some(CropDrag::Overlay { + restore, + session: ResizeSession { + start_bounds: start, + is_alt: event.modifiers.alt, + active_handle: SE_HANDLE, + original_handle: SE_HANDLE, + }, + }); + state.hovering = Some(SE_HANDLE); + } + } + cx.notify(); + } + + pub(crate) fn crop_dialog_mouse_move( + &mut self, + event: &MouseMoveEvent, + _window: &mut Window, + cx: &mut Context, + ) { + let Some(dialog) = self.crop.as_mut() else { + return; + }; + let snap = dialog.snap_to_ratio; + let area = dialog.area.get(); + let state = &mut dialog.state; + let point = engine::crop_local(area, event.position); + let Some(drag) = state.drag else { + let hovering = match engine::hit_test(point, state.raw) { + CropHit::Handle(handle) => Some(handle), + _ => None, + }; + if hovering.map(|h| h.direction) != state.hovering.map(|h| h.direction) { + state.hovering = hovering; + cx.notify(); + } + return; + }; + + match drag { + CropDrag::Region { + start_offset, + bounds, + } => { + let container = state.container_vec(); + // The source's own `clamp`: max first, min last, so a region + // wider than the container pins to the overflow rather than + // panicking the way `f64::clamp` would. + let new_x = (point.x - start_offset.x) + .max(0.) + .min(container.x - bounds.width); + let new_y = (point.y - start_offset.y) + .max(0.) + .min(container.y - bounds.height); + let moved = engine::move_bounds(bounds, Some(new_x), Some(new_y)); + state.set_raw(moved); + } + CropDrag::Handle(_) | CropDrag::Overlay { .. } => { + state.resize_move(point, event.modifiers.alt, event.modifiers.shift, snap); + } + } + cx.notify(); + } + + pub(crate) fn crop_dialog_mouse_up(&mut self, cx: &mut Context) { + let Some(dialog) = self.crop.as_mut() else { + return; + }; + let state = &mut dialog.state; + let Some(drag) = state.drag.take() else { + return; + }; + // A draw that never grew past 5px is a stray click; the previous + // region comes back (`Cropper.tsx:934-941`). + if let CropDrag::Overlay { restore, .. } = drag + && (state.raw.width < 5. || state.raw.height < 5.) + { + state.set_raw(restore); + } + cx.notify(); + } + + // -- The options menu ----------------------------------------------------- + + /// The ratio button (anchored under itself) and the right-click (anchored + /// at the cursor) open the same menu (`showCropOptionsMenu`, + /// `Editor.tsx:296-317`). + fn open_crop_dialog_menu( + &mut self, + origin: Point, + window: &mut Window, + cx: &mut Context, + ) { + let focus = self.focus.clone(); + window.focus(&focus, cx); + let Some(dialog) = self.crop.as_mut() else { + return; + }; + let items = engine::crop_menu_items(dialog.state.aspect, dialog.snap_to_ratio); + dialog.state.menu = Some(ui::MenuState::new(origin, &items)); + cx.notify(); + } + + fn choose_crop_dialog_menu( + &mut self, + index: usize, + window: &mut Window, + cx: &mut Context, + ) { + let Some(choice) = engine::crop_menu_choice(index) else { + return; + }; + match choice { + CropMenuChoice::Aspect(aspect) => { + if let Some(dialog) = self.crop.as_mut() { + dialog.state.menu = None; + dialog.state.set_aspect(aspect); + } + self.start_crop_dialog_ticker(window, cx); + } + CropMenuChoice::ToggleSnap => { + if let Some(dialog) = self.crop.as_mut() { + dialog.snap_to_ratio = !dialog.snap_to_ratio; + dialog.state.menu = None; + let snap = dialog.snap_to_ratio; + cx.background_executor() + .spawn(async move { + crate::store::update(|state| { + state.screenshot_crop_snap_to_ratio = Some(snap) + }); + }) + .detach(); + } + } + } + cx.notify(); + } + + fn crop_dialog_menu_key( + &mut self, + key: &str, + window: &mut Window, + cx: &mut Context, + ) -> bool { + let Some(dialog) = self.crop.as_mut() else { + return false; + }; + let Some(menu) = dialog.state.menu.as_mut() else { + return false; + }; + match menu.on_key(key) { + ui::MenuKey::Moved => { + cx.notify(); + true + } + ui::MenuKey::Commit(index) => { + self.choose_crop_dialog_menu(index, window, cx); + true + } + ui::MenuKey::Dismiss => { + dialog.state.menu = None; + cx.notify(); + true + } + ui::MenuKey::Ignored => false, + } + } + + // -- Keyboard ------------------------------------------------------------- + + /// Returns whether the key was consumed. The window's `on_key` calls this + /// first while the dialog is up; the modal owns the keyboard, so the + /// caller drops everything else on the floor either way. + pub(crate) fn crop_dialog_key_down( + &mut self, + event: &gpui::KeyDownEvent, + window: &mut Window, + cx: &mut Context, + ) -> bool { + if self.crop.is_none() { + return false; + } + let key = event.keystroke.key.as_str(); + if self.crop_dialog_menu_key(key, window, cx) { + return true; + } + if key == "escape" { + self.cancel_crop_dialog("escape", cx); + return true; + } + let Some(nudge) = engine::is_nudge_key(key) else { + return false; + }; + let modifiers = event.keystroke.modifiers; + let Some(dialog) = self.crop.as_mut() else { + return false; + }; + // A live pointer drag suppresses the keyboard entirely + // (`Cropper.tsx:1007`). + if dialog.state.drag.is_some() { + return true; + } + dialog.state.keys.keys.insert(nudge); + dialog.state.keys.shift = modifiers.shift; + dialog.state.keys.alt = modifiers.alt; + dialog.state.keys.meta = modifiers.platform || modifiers.control; + dialog.state.stop_animation(); + self.crop_dialog_nudge_step(cx); + self.start_crop_dialog_ticker(window, cx); + true + } + + pub(crate) fn crop_dialog_key_up(&mut self, event: &gpui::KeyUpEvent, cx: &mut Context) { + let Some(dialog) = self.crop.as_mut() else { + return; + }; + let key = event.keystroke.key.as_str(); + let modifiers = event.keystroke.modifiers; + if let Some(nudge) = engine::is_nudge_key(key) { + dialog.state.keys.keys.remove(nudge); + } else if !matches!( + key, + "shift" | "alt" | "cmd" | "ctrl" | "control" | "platform" + ) { + return; + } + dialog.state.keys.shift = modifiers.shift; + dialog.state.keys.alt = modifiers.alt; + dialog.state.keys.meta = modifiers.platform || modifiers.control; + cx.notify(); + } + + fn crop_dialog_nudge_step(&mut self, cx: &mut Context) { + let Some(dialog) = self.crop.as_mut() else { + return; + }; + if dialog.state.keys.keys.is_empty() { + return; + } + let (bounds, origin) = dialog.state.keys.step(dialog.state.raw); + dialog.state.set_raw_constraining(bounds, origin); + cx.notify(); + } + + /// The rAF loop driving the 240ms animation and the held-arrow nudge -- + /// `editor_crop::start_crop_ticker` against this window's dialog. + fn start_crop_dialog_ticker(&mut self, window: &mut Window, cx: &mut Context) { + let wanted = self.crop.as_ref().is_some_and(|dialog| { + dialog.state.anim.is_some() || !dialog.state.keys.keys.is_empty() + }); + if !wanted { + if let Some(dialog) = self.crop.as_mut() { + dialog.state.ticker = None; + } + return; + } + if self + .crop + .as_ref() + .is_some_and(|dialog| dialog.state.ticker.is_some()) + { + return; + } + let task = cx.spawn_in(window, async move |this, cx| { + loop { + cx.background_executor() + .timer(Duration::from_millis(1000 / 60)) + .await; + let keep = this + .update(cx, |this: &mut Self, cx| { + let Some(dialog) = this.crop.as_mut() else { + return false; + }; + let animating = dialog.state.tick_anim(Instant::now()); + let nudging = !dialog.state.keys.keys.is_empty(); + if nudging { + this.crop_dialog_nudge_step(cx); + } else { + cx.notify(); + } + animating || nudging + }) + .unwrap_or(false); + if !keep { + break; + } + } + this.update(cx, |this: &mut Self, _| { + if let Some(dialog) = this.crop.as_mut() { + dialog.state.ticker = None; + } + }) + .ok(); + }); + if let Some(dialog) = self.crop.as_mut() { + dialog.state.ticker = Some(task); + } + } + + // -- The header's number boxes -------------------------------------------- + + /// `NumberField`'s commit: Enter and blur push the typed value through + /// `setCropProperty`; anything unparseable snaps back. + fn on_crop_field_event( + &mut self, + field: CropField, + event: &ui::TextInputEvent, + window: &mut Window, + cx: &mut Context, + ) { + match event { + ui::TextInputEvent::Changed => {} + ui::TextInputEvent::Confirmed | ui::TextInputEvent::Cancelled => { + self.commit_crop_field(field, cx); + let focus = self.focus.clone(); + window.focus(&focus, cx); + } + ui::TextInputEvent::Blurred => self.commit_crop_field(field, cx), + } + } + + fn commit_crop_field(&mut self, field: CropField, cx: &mut Context) { + let Some(dialog) = self.crop.as_mut() else { + return; + }; + let Some(input) = dialog.fields.get(&field).cloned() else { + return; + }; + let text = input.read(cx).text().trim().to_string(); + if let Ok(value) = text.parse::() + && value.is_finite() + && field.read(dialog.state.real()) != value + { + dialog.state.set_property(field, value); + } + let resolved = field.read(dialog.state.real()); + input.update(cx, |input, cx| { + input.set_text(format!("{}", resolved.round() as i64), cx) + }); + cx.notify(); + } + + /// Re-derive every unfocused box from the crop rect -- the same + /// no-fighting-the-keyboard rule the hex fields follow. Runs from `render`. + pub(crate) fn sync_crop_field_inputs(&mut self, window: &Window, cx: &mut Context) { + let Some(dialog) = self.crop.as_ref() else { + return; + }; + let real = dialog.state.real(); + for field in CROP_FIELDS { + let Some(input) = dialog.fields.get(&field).cloned() else { + continue; + }; + if input.read(cx).focus_handle().is_focused(window) { + continue; + } + let expected = format!("{}", field.read(real).round() as i64); + if input.read(cx).text() != expected { + input.update(cx, |input, cx| input.set_text(expected, cx)); + } + } + } + + // -- Rendering ------------------------------------------------------------ + + /// The whole modal, painted over everything in the window root. + pub(crate) fn render_crop_dialog_overlay(&self, cx: &mut Context) -> Option { + let dialog = self.crop.as_ref()?; + let theme = self.theme; + + Some( + div() + .absolute() + .inset_0() + .flex() + .items_center() + .justify_center() + // `KDialog.Overlay class="fixed inset-0 z-50 bg-black/80"`. + .child( + div() + .id("screenshot-crop-backdrop") + .absolute() + .inset_0() + .bg(gpui::hsla(0., 0., 0., 0.8)) + .on_click(cx.listener(|this, _, _window, cx| { + this.cancel_crop_dialog("backdrop", cx) + })), + ) + .child( + // `rounded-[1.25rem] overflow-hidden border border-gray-3 + // bg-gray-1` (`ui.tsx:179`), with the same `occlude()` hit + // shield the video dialog's card needs. + div() + .occlude() + .relative() + .flex() + .flex_col() + .rounded(px(20.)) + .border_1() + .border_color(Hsla::from(theme.gray_3)) + .bg(Hsla::from(theme.gray_1)) + .overflow_hidden() + .child(self.render_crop_dialog_header(dialog, cx)) + .child(self.render_crop_dialog_body(dialog, cx)) + .child(self.render_crop_dialog_footer(cx)), + ) + .children(self.render_crop_dialog_menu(cx)) + .into_any_element(), + ) + } + + fn render_crop_dialog_header( + &self, + dialog: &ScreenshotCropDialog, + cx: &mut Context, + ) -> AnyElement { + let theme = self.theme; + let state = &dialog.state; + let real = state.real(); + // `disabled={crop().width === originalSize.x && crop().height === + // originalSize.y}` (`Editor.tsx:405-408`). + let full = + real.width == f64::from(state.target.0) && real.height == f64::from(state.target.1); + let untouched = real == state.initial; + + let group = |label: &'static str, a: CropField, b: CropField, this: &Self| { + div() + .flex() + .flex_row() + .items_center() + .gap(px(12.)) + .text_color(Hsla::from(theme.gray_11)) + .child(label) + .child(this.render_crop_dialog_field(dialog, a)) + .child("×") + .child(this.render_crop_dialog_field(dialog, b)) + }; + + div() + .flex() + .flex_row() + .items_center() + .gap(px(12.)) + .h(px(DIALOG_HEADER_HEIGHT)) + .px(px(16.)) + .flex_none() + .text_size(px(14.)) + .child( + // `flex flex-row space-x-8` (`Editor.tsx:348`). + div() + .flex() + .flex_row() + .gap(px(32.)) + .flex_none() + .child(group("Size", CropField::Width, CropField::Height, self)) + .child(group("Position", CropField::X, CropField::Y, self)), + ) + .child( + div() + .flex() + .flex_row() + .flex_1() + .items_center() + .justify_end() + .gap(px(12.)) + // The ratio button (`Editor.tsx:373-400`): a 32px circle + // showing the ratio glyph when free and `N:M` in blue-10 + // when locked. + .child( + div() + .id("screenshot-crop-ratio") + .flex() + .items_center() + .justify_center() + .size(px(32.)) + .rounded_full() + .border_1() + .border_color(Hsla::from(theme.gray_4)) + .bg(Hsla::from(theme.gray_1)) + .cursor_pointer() + .child(match state.aspect { + Some(ratio) => div() + .text_size(px(12.)) + .font_weight(FontWeight::MEDIUM) + .text_color(Hsla::from(theme.blue_10)) + .child(SharedString::from(format!("{}:{}", ratio.0, ratio.1))) + .into_any_element(), + None => svg() + .path("icons/ratio.svg") + .size(px(16.)) + .text_color(Hsla::from(theme.gray_12)) + .into_any_element(), + }) + .on_click(cx.listener(|this, event: &gpui::ClickEvent, window, cx| { + // `pos = new LogicalPosition(rect.x, rect.y + 40)`. + let position = event.position(); + let origin = + gpui::point(position.x - px(16.), position.y + px(24.)); + this.open_crop_dialog_menu(origin, window, cx); + })), + ) + .child( + ui::EditorButton::plain(&theme, "screenshot-crop-full") + .left_icon("icons/maximize.svg") + .label("Full") + .disabled(full) + .on_click(cx.listener(|this, _, window, cx| { + if let Some(dialog) = this.crop.as_mut() { + dialog.state.fill(); + } + this.start_crop_dialog_ticker(window, cx); + cx.notify(); + })), + ) + .child( + ui::EditorButton::plain(&theme, "screenshot-crop-reset") + .left_icon("icons/circle-x.svg") + .label("Reset") + .disabled(untouched) + .on_click(cx.listener(|this, _, window, cx| { + if let Some(dialog) = this.crop.as_mut() { + // `cropperRef?.reset(); setAspect(null)` -- + // `reset` drops the aspect itself. + dialog.state.reset(); + } + this.start_crop_dialog_ticker(window, cx); + cx.notify(); + })), + ), + ) + .into_any_element() + } + + /// One `NumberField.Input`: `w-13 h-8 rounded-lg bg-gray-2` + /// (`Editor.tsx:336`). + fn render_crop_dialog_field( + &self, + dialog: &ScreenshotCropDialog, + field: CropField, + ) -> AnyElement { + let theme = self.theme; + let Some(input) = dialog.fields.get(&field) else { + return div().w(px(BOUND_INPUT_WIDTH)).into_any_element(); + }; + ui::TextInput::plain( + &theme, + SharedString::from(format!("screenshot-crop-{field:?}")), + input, + ) + .width(px(BOUND_INPUT_WIDTH)) + .padding_x(px(8.)) + .height(px(32.)) + .radius(px(8.)) + .text_size(px(14.)) + .bg(Hsla::from(theme.gray_2)) + .border(Hsla::from(theme.gray_2)) + .into_any_element() + } + + /// `Dialog.Content` (`p-4 border-y border-gray-3`) holding the + /// `rounded-[1.25rem] bg-gray-2/80 p-3` card the cropper sits in + /// (`Editor.tsx:429-461`). + fn render_crop_dialog_body( + &self, + dialog: &ScreenshotCropDialog, + cx: &mut Context, + ) -> AnyElement { + let theme = self.theme; + let mut card_bg = Hsla::from(theme.gray_2); + card_bg.a = 0.8; + + div() + .flex() + .flex_col() + .p(px(16.)) + .border_t_1() + .border_b_1() + .border_color(Hsla::from(theme.gray_3)) + .child( + div() + .flex() + .flex_row() + .justify_center() + .items_center() + .child( + div() + .rounded(px(20.)) + .p(px(12.)) + .bg(card_bg) + // `ring-1 ring-black/5 shadow-xs`. + .border_1() + .border_color(gpui::hsla(0., 0., 0., 0.05)) + .shadow(vec![gpui::BoxShadow { + color: Theme::with_alpha(gpui::rgb(0x000000), 0.05), + offset: gpui::point(px(0.), px(1.)), + blur_radius: px(2.), + spread_radius: px(0.), + inset: false, + }]) + .child(self.render_crop_dialog_area(dialog, cx)), + ), + ) + .into_any_element() + } + + /// The cropper itself -- `editor_crop::render_crop_area`'s shape over the + /// original PNG: the frame, the occluders, the region with its thirds + /// grid, badge and corner glyphs, and the handle hit zones. + fn render_crop_dialog_area( + &self, + dialog: &ScreenshotCropDialog, + cx: &mut Context, + ) -> AnyElement { + let theme = self.theme; + let state = &dialog.state; + let (w, h) = state.container; + let (box_w, box_h) = state.box_size; + let bounds = state.display_raw; + let too_small = state.bounds_too_small(); + let dragging = state.drag.is_some(); + let drag_cursor = state.drag.map(|drag| drag.cursor()); + let base_cursor = drag_cursor.unwrap_or(if state.aspect.is_some() { + CursorStyle::Arrow + } else { + CursorStyle::Crosshair + }); + + let region = div() + .absolute() + .left(px(bounds.x as f32)) + .top(px(bounds.y as f32)) + .w(px(bounds.width as f32)) + .h(px(bounds.height as f32)) + .border_1() + .border_color(gpui::hsla(0., 0., 1., 0.5)) + .cursor(drag_cursor.unwrap_or(CursorStyle::OpenHand)) + .children(dragging.then(|| engine::thirds_grid(bounds))) + // The snapped-ratio badge, free mode only. + .children( + (state.aspect.is_none() && !too_small) + .then_some(state.snapped) + .flatten() + .map(|ratio| { + div() + .absolute() + .top_0() + .w(px(bounds.width as f32)) + .h(px(32.)) + .flex() + .items_center() + .justify_center() + .child( + div() + .h(px(18.)) + .w(px(44.)) + .rounded_full() + .flex() + .items_center() + .justify_center() + .border_1() + .border_color(gpui::hsla(0., 0., 1., 0.7)) + .bg(if theme.is_dark() { + gpui::hsla(0., 0., 0., 0.5) + } else { + gpui::hsla(0., 0., 1., 0.5) + }) + .text_size(px(12.)) + .text_color(Hsla::from(theme.gray_12)) + .child(SharedString::from(format!("{}:{}", ratio.0, ratio.1))), + ) + }), + ) + .children( + HANDLES + .iter() + .filter(|handle| handle.is_corner) + .flat_map(|handle| engine::corner_glyph(*handle, bounds, too_small)), + ); + + // The hit zones, in paint order: edges, the move layer, the corners. + let mut layers = div().absolute().inset_0(); + for handle in HANDLES.iter().filter(|handle| !handle.is_corner) { + layers = layers.child(engine::handle_zone(*handle, bounds, drag_cursor)); + } + layers = layers.child( + div() + .absolute() + .left(px(bounds.x as f32)) + .top(px(bounds.y as f32)) + .w(px(bounds.width as f32)) + .h(px(bounds.height as f32)) + .cursor(drag_cursor.unwrap_or(CursorStyle::OpenHand)), + ); + for handle in HANDLES.iter().filter(|handle| handle.is_corner) { + let cursor = match (drag_cursor, state.hovering) { + (Some(_), Some(hovering)) if hovering.is_corner => hovering.direction.cursor(), + (Some(cursor), _) => cursor, + (None, _) => handle.direction.cursor(), + }; + layers = layers.child(engine::handle_zone_with_cursor(*handle, bounds, cursor)); + } + + div() + .id("screenshot-crop-area") + .relative() + .w(px(box_w)) + .h(px(box_h)) + // `rounded-sm overflow-visible` -- unlike the video dialog's box + // there is no hairline border and nothing clips the corner glyphs. + .rounded(px(2.)) + .cursor(base_cursor) + .child( + gpui::canvas( + { + let cell = dialog.area.clone(); + move |bounds, _window, _cx| { + cell.set(Some(bounds)); + } + }, + |_, _, _, _| {}, + ) + .absolute() + .size_full(), + ) + .children(match &dialog.image { + Some(image) => Some( + img(image.clone()) + .absolute() + .inset_0() + .size_full() + .into_any_element(), + ), + None => Some( + div() + .absolute() + .inset_0() + .flex() + .items_center() + .justify_center() + .bg(Hsla::from(theme.gray_3)) + .text_size(px(12.)) + .font_weight(FontWeight::MEDIUM) + .text_color(Hsla::from(theme.gray_10)) + .child("Loading frame…") + .into_any_element(), + ), + }) + .children(engine::occluders(bounds, w, h)) + .child(region) + .child(layers) + .on_mouse_down( + MouseButton::Left, + cx.listener(|this, event: &MouseDownEvent, window, cx| { + this.crop_dialog_mouse_down(event, window, cx); + }), + ) + // `onContextMenu={(e) => showCropOptionsMenu(e, true)}` -- at the + // cursor. + .on_mouse_down( + MouseButton::Right, + cx.listener(|this, event: &MouseDownEvent, window, cx| { + this.open_crop_dialog_menu(event.position, window, cx); + }), + ) + .into_any_element() + } + + /// `Dialog.Footer` with the single Save (`Editor.tsx:463-482`). + fn render_crop_dialog_footer(&self, cx: &mut Context) -> AnyElement { + let theme = self.theme; + div() + .flex() + .flex_row() + .items_center() + .justify_center() + .gap(px(12.)) + .h(px(DIALOG_FOOTER_HEIGHT)) + .px(px(16.)) + .flex_none() + .child( + ui::Button::plain( + &theme, + "screenshot-crop-save", + ui::ButtonVariant::Primary, + ui::ButtonSize::Md, + ) + .label("Save") + .on_click(cx.listener(|this, _, window, cx| this.save_crop_dialog(window, cx))), + ) + .into_any_element() + } + + fn render_crop_dialog_menu(&self, cx: &mut Context) -> Option { + let dialog = self.crop.as_ref()?; + let menu = dialog.state.menu.as_ref()?; + let items = engine::crop_menu_items(dialog.state.aspect, dialog.snap_to_ratio); + Some( + ui::Menu::plain(&self.theme, "screenshot-crop-menu", items, menu) + .on_select(cx.listener(|this, index: &usize, window, cx| { + this.choose_crop_dialog_menu(*index, window, cx); + })) + .on_dismiss(cx.listener(|this, _, _window, cx| { + if let Some(dialog) = this.crop.as_mut() { + dialog.state.menu = None; + } + cx.notify(); + })) + .into_any_element(), + ) + } + + /// The window-wide pointer-capture layer while a crop drag is live. + pub(crate) fn render_crop_dialog_drag_layer( + &self, + cx: &mut Context, + ) -> Option { + self.crop + .as_ref() + .is_some_and(|dialog| dialog.state.drag.is_some()) + .then(|| { + ui::Slider::drag_layer( + "screenshot-crop-drag", + cx.listener(|this, event: &MouseMoveEvent, window, cx| { + this.crop_dialog_mouse_move(event, window, cx); + }), + cx.listener(|this, _: &gpui::MouseUpEvent, _window, cx| { + this.crop_dialog_mouse_up(cx); + }), + ) + .into_any_element() + }) + } +} + +/// Decode the original PNG for the cropper -- full resolution, since the box +/// can be up to 768 logical (1536 physical) pixels wide and the load path +/// already refused anything past `MAX_DIMENSION`. +fn decode_crop_image(path: Option<&Path>) -> Option> { + let decoded = image::open(path?).ok()?; + Some(crate::library::rgba_to_render_image(decoded.into_rgba8())) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// `previewSize()` at the editor's default 1240x800: a 3024x1964 original + /// hits the 768 width cap, so the box is 768 wide at the image's aspect. + #[test] + fn the_box_fits_the_image_into_the_width_and_height_caps() { + let (w, h) = crop_box_size((1240., 800.), (3024, 1964)); + assert!((w - 768.).abs() < 0.001, "{w}"); + let expected_height = 768. / (3024. / 1964.); + assert!((h - expected_height).abs() < 0.01, "{h}"); + + // A tall image is height-capped at vh * 0.65 instead. + let (w, h) = crop_box_size((1240., 800.), (1000, 2000)); + assert!((h - 520.).abs() < 0.001, "{h}"); + assert!((w - 260.).abs() < 0.001, "{w}"); + } + + /// The source does not clamp the ratio at 1 -- a small screenshot scales + /// up to fill the box. + #[test] + fn a_small_image_upscales_like_the_source() { + let (w, h) = crop_box_size((1240., 800.), (100, 50)); + assert!(w > 100.); + assert_eq!(w / h, 2.); + } +} diff --git a/apps/desktop-gpui/src/screenshot_editor.rs b/apps/desktop-gpui/src/screenshot_editor.rs index 9c07d77a61..d8827de922 100644 --- a/apps/desktop-gpui/src/screenshot_editor.rs +++ b/apps/desktop-gpui/src/screenshot_editor.rs @@ -7,13 +7,18 @@ //! the webview over a websocket (`screenshot_editor.rs:316-476` over there). //! Here the loop is the same -- `FrameRenderer::render_immediate` on a //! `tokio::sync::watch` of config revisions -- and the frame skips the -//! websocket: it is un-padded and BGRA-swapped by `editor_window::frame_image` -//! and painted with `paint_image`, exactly like the video editor's CPU path. +//! websocket: it is un-padded by the pump, kept once as tight RGBA (which is +//! what the Phase 2 mask overlay resamples) and once BGRA-swapped for gpui's +//! atlas. //! -//! Annotations, OCR and the share flow are not here yet; styling and export -//! grow on this seam. +//! The chrome is `Editor.tsx`'s: a 56px `Header.tsx` whose centre cluster +//! carries the aspect select, the crop button, `AnnotationTools.tsx` and the +//! five styling popovers; `LayersPanel.tsx` on the left; `Preview.tsx` filling +//! the rest with its checkerboard, its zoom HUD and its pan/zoom maths. The +//! annotation engine itself lives behind the seam in +//! [`crate::screenshot_annotations`]; sharing and the crop dialog are Phase 3. -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::rc::Rc; @@ -22,23 +27,26 @@ use std::time::Duration; use cap_project::{ BackgroundSource, BorderConfiguration, CornerStyle, ProjectConfiguration, RecordingMeta, - RecordingMetaInner, StudioRecordingMeta, + RecordingMetaInner, ShadowConfiguration, StudioRecordingMeta, }; use cap_rendering::{ DecodedFrame, DecodedSegmentFrames, FrameRenderer, ProjectUniforms, RenderOptions, RenderVideoConstants, RenderedFrame, RendererLayers, ZoomTransformTimeline, }; use gpui::{ - App, Context, FontWeight, Hsla, InteractiveElement as _, IntoElement, MouseDownEvent, - MouseMoveEvent, ParentElement as _, Point, Render, RenderImage, - StatefulInteractiveElement as _, Styled, StyledImage as _, Window, WindowHandle, div, img, - linear_color_stop, linear_gradient, prelude::FluentBuilder as _, px, + AnyElement, App, AppContext as _, Bounds, Context, Entity, FontWeight, Hsla, + InteractiveElement as _, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent, + MouseUpEvent, ParentElement as _, Pixels, Point, Render, RenderImage, + StatefulInteractiveElement as _, Styled, StyledImage as _, Window, WindowHandle, canvas, div, + img, linear_color_stop, linear_gradient, prelude::FluentBuilder as _, px, svg, }; +use crate::editor_edits::ProjectHistory; use crate::editor_sidebar::{ - self, BACKGROUND_COLORS, BACKGROUND_IMAGE_EXTENSIONS, BACKGROUND_THEMES, GRADIENT_PRESETS, - color_to_hsla, hex_to_rgb, + self, BACKGROUND_COLORS, BACKGROUND_IMAGE_EXTENSIONS, BACKGROUND_THEMES, DEFAULT_GRADIENT_FROM, + DEFAULT_GRADIENT_TO, GRADIENT_PRESETS, color_to_hsla, hex_to_rgb, }; +use crate::screenshot_annotations::{self as annotations, AnnotationState, Tool}; use crate::theme::Theme; use crate::ui; @@ -51,8 +59,28 @@ pub const SCREENSHOT_EDITOR_MIN_HEIGHT: f32 = 600.; /// `MAX_DIMENSION` (`screenshot_editor.rs:38` over there). const MAX_DIMENSION: u32 = 16_384; -/// `PROJECT_SAVE_DEBOUNCE_MS`, the same 250ms the video editor debounces. -const SAVE_DEBOUNCE: Duration = Duration::from_millis(250); +/// `saveConfig`'s debounce (`context.tsx:427-438`) -- 1000ms, not the video +/// editor's 250. +const SAVE_DEBOUNCE: Duration = Duration::from_millis(1000); + +/// `Header.tsx:112` -- `h-14`. +const HEADER_HEIGHT: f32 = 56.; +/// `AnnotationConfig.tsx:44` -- `h-11`. +const CONFIG_BAR_HEIGHT: f32 = 44.; +/// `LayersPanel.tsx:203` -- `w-56`. +const LAYERS_PANEL_WIDTH: f32 = 224.; +/// `Preview.tsx:52`. +const PREVIEW_PADDING: f32 = 20.; +/// `clampZoom` (`Preview.tsx:193`). +const MIN_ZOOM: f32 = 0.1; +const MAX_ZOOM: f32 = 3.; +/// `DEFAULT_BACKGROUND_SHADOW` (`BackgroundSettingsPopover.tsx:29`). +const DEFAULT_BACKGROUND_SHADOW: f32 = 73.6; +/// `solid-toast`'s default duration. +const TOAST_DURATION: Duration = Duration::from_millis(3500); +/// The checkerboard tile: 24 cells of 10px, so it repeats seamlessly. +const CHECKER_TILE: u32 = 240; +const CHECKER_CELL: u32 = 10; /// The `.cap` bundle a screenshot path belongs to: the directory itself, or /// the parent of `original.png` -- `create_standalone_instance`'s cap_dir @@ -80,26 +108,26 @@ pub struct LoadedSource { pub studio_meta: StudioRecordingMeta, } +/// The bundle's source PNG: `original.png`, or the first PNG the directory +/// scan finds -- `create_standalone_instance`'s resolution. Shared with the +/// crop dialog, whose cropper draws this exact file. +pub(crate) fn bundle_image_path(bundle: &Path) -> Option { + let original = bundle.join("original.png"); + if original.exists() { + return Some(original); + } + std::fs::read_dir(bundle).ok().and_then(|dir| { + dir.flatten() + .find(|entry| entry.path().extension().and_then(|ext| ext.to_str()) == Some("png")) + .map(|entry| entry.path()) + }) +} + /// Read `original.png` (or the first PNG in the bundle), the recording meta /// and the project config -- the disk half of `create_standalone_instance`. pub fn load_source(bundle: &Path) -> Result { - let image_path = { - let original = bundle.join("original.png"); - if original.exists() { - original - } else { - std::fs::read_dir(bundle) - .ok() - .and_then(|dir| { - dir.flatten() - .find(|entry| { - entry.path().extension().and_then(|ext| ext.to_str()) == Some("png") - }) - .map(|entry| entry.path()) - }) - .ok_or_else(|| format!("No PNG found in {}", bundle.display()))? - } - }; + let image_path = + bundle_image_path(bundle).ok_or_else(|| format!("No PNG found in {}", bundle.display()))?; let image = image::open(&image_path).map_err(|e| format!("Failed to open image: {e}"))?; let (width, height) = (image.width(), image.height()); @@ -142,11 +170,14 @@ pub struct ConfigUpdate { /// A one-off render on the loop's GPU device -- `render_screenshot_png` in /// the Tauri app, which rebuilds the whole device per export because it is a -/// stateless command; here the loop already owns one. +/// stateless command; here the loop already owns one. The reply is the raw +/// styled frame; the annotation composite (`crate::screenshot_export`) runs on +/// the caller's background executor, the way the webview's canvas pass runs +/// outside the Tauri command. pub enum ExportRequest { - Png { + Render { config: ProjectConfiguration, - reply: tokio::sync::oneshot::Sender, String>>, + reply: tokio::sync::oneshot::Sender>, }, } @@ -222,10 +253,10 @@ pub async fn run_still_renderer( dirty = true; } request = export_rx.recv() => { - let Some(ExportRequest::Png { config, reply }) = request else { + let Some(ExportRequest::Render { config, reply }) = request else { break; }; - let result = render_export_png( + let result = render_export_rgba( &constants, &mut frame_renderer, &mut layers, @@ -291,19 +322,19 @@ async fn render_still( .map_err(|e| e.to_string()) } -/// The export render -- `render_screenshot_png`'s upscale + unpad + encode +/// The export render -- `render_screenshot_png`'s upscale + unpad /// (`screenshot_editor.rs:1618-1742` over there): scale the output so a crop -/// is not downsampled, align the dimensions the way the exporter does, strip -/// the wgpu row padding and encode an RGBA PNG. -async fn render_export_png( +/// is not downsampled, align the dimensions the way the exporter does, and +/// strip the wgpu row padding into tight straight-alpha RGBA. The preview's +/// own dimensions for the same config ride along, because that is the space +/// the annotations live in and the compositor's scale denominator. +async fn render_export_rgba( constants: &RenderVideoConstants, frame_renderer: &mut FrameRenderer<'_>, layers: &mut RendererLayers, source: &DecodedFrame, config: &ProjectConfiguration, -) -> Result, String> { - use image::ImageEncoder as _; - +) -> Result { let (base_width, base_height) = ProjectUniforms::get_base_size(&constants.options, config); let display_size = ProjectUniforms::display_size( &constants.options, @@ -359,16 +390,39 @@ async fn render_export_png( .flat_map(|row| row[..row_bytes].iter().copied()) .collect(); - let mut png = std::io::Cursor::new(Vec::new()); - image::codecs::png::PngEncoder::new(&mut png) - .write_image( - &rgba, - frame.width, - frame.height, - image::ExtendedColorType::Rgba8, - ) - .map_err(|e| format!("Failed to encode screenshot export: {e}"))?; - Ok(png.into_inner()) + // What the preview renders this config at: `get_base_size`, then the + // uniforms' own alignment -- `get_output_size` at scale 1. That is what + // `latestFrame()`'s width/height would be after `waitForSyncedPreview`, + // and the compositor's `canvas.width / frame.width` denominator. + let preview = ProjectUniforms::get_output_size( + &constants.options, + config, + cap_project::XY::new(base_width, base_height), + ); + + Ok(crate::screenshot_export::RawFrame { + rgba, + width: frame.width, + height: frame.height, + base_width: preview.0, + base_height: preview.1, + }) +} + +/// The Phase 1/2 export shape -- the styled frame straight to PNG, no +/// annotation composite. Nothing in the shipping window calls it any more +/// (every destination composites first), but the seam stays callable for +/// probes that want the renderer's own output, hence the allow. +#[allow(dead_code)] +async fn render_export_png( + constants: &RenderVideoConstants, + frame_renderer: &mut FrameRenderer<'_>, + layers: &mut RendererLayers, + source: &DecodedFrame, + config: &ProjectConfiguration, +) -> Result, String> { + let raw = render_export_rgba(constants, frame_renderer, layers, source, config).await?; + crate::screenshot_export::encode_rgba_png(&raw.rgba, raw.width, raw.height) } // --------------------------------------------------------------------------- @@ -404,21 +458,22 @@ impl PendingConfigSave { } } -/// The background popover's source tabs (`BackgroundSettingsPopover.tsx`). +/// The background popover's source tabs, in `BACKGROUND_SOURCES_LIST` order +/// (`BackgroundSettingsPopover.tsx:48-53`). #[derive(Clone, Copy, PartialEq, Eq)] enum BgTab { - Color, - Gradient, Wallpaper, Image, + Color, + Gradient, } impl BgTab { const ALL: [(Self, &'static str); 4] = [ - (Self::Color, "Color"), - (Self::Gradient, "Gradient"), (Self::Wallpaper, "Wallpaper"), (Self::Image, "Image"), + (Self::Color, "Color"), + (Self::Gradient, "Gradient"), ]; fn for_source(source: &BackgroundSource) -> Self { @@ -431,6 +486,117 @@ impl BgTab { } } +/// `activePopover` (`context.tsx:198-200`) -- the five styling popovers on the +/// header's right-hand cluster. One at a time, by construction. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +enum Popover { + Background, + Padding, + Rounding, + Shadow, + Border, +} + +impl Popover { + fn anchor(self) -> Anchor { + match self { + Self::Background => Anchor::Background, + Self::Padding => Anchor::Padding, + Self::Rounding => Anchor::Rounding, + Self::Shadow => Anchor::Shadow, + Self::Border => Anchor::Border, + } + } + + /// `Popover.Content`'s `w-[...]` per popover. + fn width(self) -> f32 { + match self { + Self::Background => 400., + Self::Padding => 200., + Self::Rounding => 240., + Self::Shadow | Self::Border => 280., + } + } + + fn icon(self) -> &'static str { + match self { + Self::Background => "icons/image.svg", + Self::Padding => "icons/padding.svg", + Self::Rounding => "icons/corners.svg", + Self::Shadow => "icons/shadow.svg", + Self::Border => "icons/square.svg", + } + } + + fn tooltip(self) -> &'static str { + match self { + Self::Background => "Background", + Self::Padding => "Padding", + Self::Rounding => "Corner Rounding", + Self::Shadow => "Shadow", + Self::Border => "Border", + } + } + + /// The `kbd` chips on the trigger's tooltip -- and the bare key + /// `Editor.tsx:153-169` binds. `RoundingPopover` passes no `kbd`. + fn keys(self) -> &'static [&'static str] { + match self { + Self::Background => &["B"], + Self::Padding => &["P"], + Self::Rounding => &[], + Self::Shadow => &["H"], + Self::Border => &["E"], + } + } + + fn id(self) -> &'static str { + match self { + Self::Background => "screenshot-popover-background", + Self::Padding => "screenshot-popover-padding", + Self::Rounding => "screenshot-popover-rounding", + Self::Shadow => "screenshot-popover-shadow", + Self::Border => "screenshot-popover-border", + } + } +} + +/// Every element whose painted rect a popover or menu is positioned against. +/// gpui has no `getBoundingClientRect`; each one writes its prepaint bounds +/// into a shared cell, the way `ui::Slider` does with its track. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +enum Anchor { + Aspect, + Background, + Padding, + Rounding, + Shadow, + Border, + More, + CornerStyle, +} + +impl Anchor { + const ALL: [Anchor; 8] = [ + Anchor::Aspect, + Anchor::Background, + Anchor::Padding, + Anchor::Rounding, + Anchor::Shadow, + Anchor::Border, + Anchor::More, + Anchor::CornerStyle, + ]; +} + +/// The three `KSelect` / `KDropdownMenu` popups on the chrome. +#[derive(Clone, Copy, PartialEq, Eq)] +enum MenuKind { + Aspect, + More, + CornerStyle, +} + /// The screenshot editor's border fallback -- `BorderPopover.tsx:42-45`, /// black at 50%, not `BorderConfiguration::default()`'s white at 80%. const BORDER_FALLBACK: BorderConfiguration = BorderConfiguration { @@ -440,35 +606,75 @@ const BORDER_FALLBACK: BorderConfiguration = BorderConfiguration { opacity: 50.0, }; -/// Every slider in the styling panel, with the popovers' exact ranges. +/// `{ size: 50, opacity: 18, blur: 50 }` -- what `ShadowPopover.tsx:41-45` +/// seeds the advanced block with, and what its three sliders read through when +/// the block is absent. +const ADVANCED_SHADOW_FALLBACK: ShadowConfiguration = ShadowConfiguration { + size: 50., + opacity: 18., + blur: 50., +}; + +/// Every slider in the chrome, with the popovers' exact ranges. #[derive(Clone, Copy, PartialEq, Eq, Hash)] enum StyleSlider { Blur, Padding, Rounding, Shadow, + ShadowSize, + ShadowOpacity, + ShadowBlur, BorderWidth, BorderOpacity, + /// The preview's zoom HUD. Window state only -- it never touches the + /// project, so `apply` is never called for it. + Zoom, } impl StyleSlider { + const ALL: [StyleSlider; 10] = [ + StyleSlider::Blur, + StyleSlider::Padding, + StyleSlider::Rounding, + StyleSlider::Shadow, + StyleSlider::ShadowSize, + StyleSlider::ShadowOpacity, + StyleSlider::ShadowBlur, + StyleSlider::BorderWidth, + StyleSlider::BorderOpacity, + StyleSlider::Zoom, + ]; + /// `(min, max, step)` -- `BackgroundSettingsPopover` blur, - /// `PaddingPopover`, `RoundingPopover`, `ShadowPopover`, `BorderPopover`. + /// `PaddingPopover`, `RoundingPopover`, `ShadowPopover` + `ShadowSettings`, + /// `BorderPopover`, and `Preview.tsx`'s HUD. fn range(self) -> (f32, f32, f32) { match self { - Self::Blur | Self::Padding | Self::Rounding | Self::Shadow => (0., 100., 1.), + Self::Padding | Self::Rounding | Self::Shadow => (0., 100., 1.), + Self::Blur | Self::ShadowSize | Self::ShadowOpacity | Self::ShadowBlur => { + (0., 100., 0.1) + } Self::BorderWidth => (1., 20., 0.1), Self::BorderOpacity => (0., 100., 0.1), + Self::Zoom => (MIN_ZOOM, MAX_ZOOM, 0.1), } } fn value(self, project: &ProjectConfiguration) -> f32 { let background = &project.background; + let advanced = background + .advanced_shadow + .as_ref() + .unwrap_or(&ADVANCED_SHADOW_FALLBACK); match self { Self::Blur => background.blur as f32, Self::Padding => background.padding as f32, Self::Rounding => background.rounding as f32, Self::Shadow => background.shadow, + Self::ShadowSize => advanced.size, + Self::ShadowOpacity => advanced.opacity, + Self::ShadowBlur => advanced.blur, Self::BorderWidth => background .border .as_ref() @@ -477,6 +683,8 @@ impl StyleSlider { .border .as_ref() .map_or(BORDER_FALLBACK.opacity, |border| border.opacity), + // Never read: the HUD reads `self.zoom` directly. + Self::Zoom => 1., } } @@ -493,6 +701,15 @@ impl StyleSlider { if background.padding == value as f64 { return false; } + // `handlePaddingChange` (`PaddingPopover.tsx:25-46`): padding + // over an invisible background would render as nothing, so the + // first non-zero value also lights the background up. + if value > 0. && has_no_visible_background(&background.source) { + background.source = BackgroundSource::Color { + value: [255, 255, 255], + alpha: 255, + }; + } background.padding = value as f64; } Self::Rounding => { @@ -506,6 +723,26 @@ impl StyleSlider { return false; } background.shadow = value; + // `ShadowPopover.tsx:37-48` seeds the advanced block in the + // same batch, so the first drag off zero has something to + // shape the shadow with. + if value > 0. && background.advanced_shadow.is_none() { + background.advanced_shadow = Some(ADVANCED_SHADOW_FALLBACK); + } + } + Self::ShadowSize | Self::ShadowOpacity | Self::ShadowBlur => { + let shadow = background + .advanced_shadow + .get_or_insert(ADVANCED_SHADOW_FALLBACK); + let field = match self { + Self::ShadowSize => &mut shadow.size, + Self::ShadowOpacity => &mut shadow.opacity, + _ => &mut shadow.blur, + }; + if *field == value { + return false; + } + *field = value; } Self::BorderWidth => { let border = background.border.get_or_insert(BorderConfiguration { @@ -527,11 +764,50 @@ impl StyleSlider { } border.opacity = value; } + Self::Zoom => return false, } true } } +/// `hasNoVisibleBackground` (`context.tsx:30-42`) -- also what the export +/// compositor's white fill and transparency scan key off +/// (`screenshotExport.ts:15-27`). +pub(crate) fn has_no_visible_background(source: &BackgroundSource) -> bool { + match source { + BackgroundSource::Color { alpha, .. } => *alpha == 0, + BackgroundSource::Wallpaper { path } | BackgroundSource::Image { path } => path.is_none(), + BackgroundSource::Gradient { .. } => false, + } +} + +/// The four `RgbInput`s the popovers carry (`ColorPicker.tsx:27-103`). +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +enum HexTarget { + BackgroundColor, + GradientFrom, + GradientTo, + BorderColor, +} + +impl HexTarget { + const ALL: [HexTarget; 4] = [ + HexTarget::BackgroundColor, + HexTarget::GradientFrom, + HexTarget::GradientTo, + HexTarget::BorderColor, + ]; + + fn id(self) -> &'static str { + match self { + Self::BackgroundColor => "screenshot-hex-background", + Self::GradientFrom => "screenshot-hex-gradient-from", + Self::GradientTo => "screenshot-hex-gradient-to", + Self::BorderColor => "screenshot-hex-border", + } + } +} + #[derive(Clone, PartialEq, Eq)] enum ExportDestination { Clipboard, @@ -542,32 +818,125 @@ enum ExportDestination { Harness(PathBuf), } +/// `exportStatus` (`useScreenshotExport.ts`), which is what the Share button's +/// tooltip reads. Copy and Save walk Rendering -> Encoding; only the share +/// flow reaches Uploading. +#[derive(Clone, Copy, PartialEq, Eq)] +enum ExportStatus { + Idle, + Rendering, + Encoding, + Uploading, +} + +/// One `solid-toast` bubble. `Loading` is the share flow's +/// (`useScreenshotExport.ts:153-239` posts one and updates it in place); +/// copy and save go straight to a success bubble. +#[derive(Clone, Copy, PartialEq, Eq)] +enum ToastKind { + Loading, + Success, + Error, +} + +struct Toast { + id: u64, + kind: ToastKind, + message: String, +} + +/// A live pan drag on the preview (`Preview.tsx:402-442`). +#[derive(Clone, Copy)] +struct PanDrag { + start: Point, + origin: (f32, f32), +} + +/// The measured geometry `Preview.tsx` derives from the container's bounds. +/// Recomputed each frame from last frame's prepaint, which is the same +/// one-frame lag `createElementBounds` has on a resize. +#[derive(Clone, Copy)] +struct PreviewGeometry { + /// The letterboxed viewport, centred in the area. + size: (f32, f32), + fit_scale: f32, + scaled: (f32, f32), + content: (f32, f32), +} + +type BoundsCell = Rc>>>; + pub struct ScreenshotEditorWindow { - theme: Theme, - bundle: PathBuf, + pub(crate) theme: Theme, + pub(crate) bundle: PathBuf, pretty_name: String, error: Option, /// The live config, published to the render loop on every edit. - project: ProjectConfiguration, + pub(crate) project: ProjectConfiguration, + /// `projectHistory` (`context.tsx:549-659`), reusing the video editor's. + pub(crate) history: ProjectHistory, revision: u64, config_tx: Option>, export_tx: Option>, /// One export at a time: the copy/save buttons grey out while the GPU - /// renders and the panel is up. + /// renders. exporting: bool, + export_status: ExportStatus, /// The latest GPU frame, already BGRA in gpui's atlas format. frame: Option>, - frame_size: (f32, f32), + /// The same frame as tight RGBA. `Preview.tsx` reads the preview canvas + /// back to blur/pixelate mask regions (`:537-687`); the mask overlay + /// resamples this instead of the atlas image. + pub(crate) frame_rgba: Option>>, + pub(crate) frame_size: (f32, f32), + /// `originalImageSize` -- the source PNG's dimensions, which the crop + /// button gates on and the frame-space transform needs. + pub(crate) image_size: Option<(u32, u32)>, + /// `prevState` in the resize effect (`context.tsx:465-472`). + previous_transform: Option<((f32, f32), annotations::ImageTransform)>, + /// `isRenderReady`: the skeleton stands in until the first frame lands. + ready: bool, pending_save: Rc>, save_task: Option>, - aspect_menu: Option, + + // -- Chrome state -------------------------------------------------------- + pub(crate) tool: Tool, + pub(crate) selected_annotation: Option, + /// Everything the annotation engine keeps ([`crate::screenshot_annotations`]). + pub(crate) annotation_state: AnnotationState, + layers_panel_open: bool, + /// The crop dialog ([`crate::screenshot_crop`]). `None` means closed. + pub(crate) crop: Option, + active_popover: Option, + menu: Option<(MenuKind, ui::MenuState)>, + anchors: HashMap, bg_tab: BgTab, wallpaper_theme: usize, wallpapers: HashMap<&'static str, Arc>, wallpaper_task: Option>, + /// The Image tab's `h-48` preview, decoded off-thread and keyed by path. + bg_image: Option<(String, Arc)>, + bg_image_task: Option>, + shadow_advanced: ui::CollapsibleState, + border_body: ui::CollapsibleState, + hex_inputs: HashMap>, + pub(crate) text_subscriptions: Vec, slider_tracks: HashMap, active_slider: Option, - focus: gpui::FocusHandle, + + // -- Preview state ------------------------------------------------------- + zoom: f32, + pan: (f32, f32), + preview_area: BoundsCell, + viewport: BoundsCell, + panning: Option, + /// The one cached checkerboard tile the preview paints under everything. + checker: Option>, + + toasts: Vec, + next_toast: u64, + toast_tasks: HashMap>, + pub(crate) focus: gpui::FocusHandle, } impl ScreenshotEditorWindow { @@ -585,33 +954,92 @@ impl ScreenshotEditorWindow { .unwrap_or("Screenshot") .to_string(); - Self { + let mut this = Self { theme: Theme::for_window(window, cx, false), bundle, pretty_name, error: None, project: ProjectConfiguration::default(), + history: ProjectHistory::new(ProjectConfiguration::default()), revision: 0, config_tx: None, export_tx: None, exporting: false, + export_status: ExportStatus::Idle, frame: None, + frame_rgba: None, frame_size: (1., 1.), + image_size: None, + previous_transform: None, + ready: false, pending_save: Rc::new(RefCell::new(PendingConfigSave::default())), save_task: None, - aspect_menu: None, + + tool: Tool::Select, + selected_annotation: None, + annotation_state: AnnotationState::default(), + // `makePersisted(createSignal(false), { name: + // "screenshotEditorLayersPanelOpen" })` -- localStorage over + // there, `store.rs` here. + layers_panel_open: crate::store::load() + .screenshot_layers_panel_open + .unwrap_or(false), + crop: None, + active_popover: None, + menu: None, + anchors: Anchor::ALL + .into_iter() + .map(|anchor| (anchor, BoundsCell::default())) + .collect(), bg_tab: BgTab::Color, wallpaper_theme: 0, wallpapers: HashMap::new(), wallpaper_task: None, - slider_tracks: HashMap::new(), + bg_image: None, + bg_image_task: None, + shadow_advanced: ui::CollapsibleState::new(false), + border_body: ui::CollapsibleState::new(false), + hex_inputs: HashMap::new(), + text_subscriptions: Vec::new(), + slider_tracks: StyleSlider::ALL + .into_iter() + .map(|slider| (slider, ui::SliderTrack::default())) + .collect(), active_slider: None, + + zoom: 1., + pan: (0., 0.), + preview_area: BoundsCell::default(), + viewport: BoundsCell::default(), + panning: None, + checker: Some(checkerboard_tile()), + + toasts: Vec::new(), + next_toast: 0, + toast_tasks: HashMap::new(), focus: cx.focus_handle(), + }; + + for target in HexTarget::ALL { + let input = cx.new(|cx| ui::TextInputState::single_line(window, cx)); + let subscription = cx.subscribe_in( + &input, + window, + move |this: &mut Self, _input, event: &ui::TextInputEvent, window, cx| { + this.on_hex_event(target, event, window, cx); + }, + ); + this.text_subscriptions.push(subscription); + this.hex_inputs.insert(target, input); } + this.init_annotation_inputs(window, cx); + + this } pub fn set_error(&mut self, message: String, cx: &mut Context) { self.error = Some(message); + self.ready = true; cx.notify(); } @@ -619,6 +1047,7 @@ impl ScreenshotEditorWindow { &mut self, pretty_name: String, config: ProjectConfiguration, + image_size: (u32, u32), config_tx: tokio::sync::watch::Sender, export_tx: tokio::sync::mpsc::Sender, window: &mut Window, @@ -626,17 +1055,29 @@ impl ScreenshotEditorWindow { ) { self.pretty_name = pretty_name; self.bg_tab = BgTab::for_source(&config.background.source); + self.border_body + .set_open(config.background.border.as_ref().is_some_and(|b| b.enabled)); + self.history = ProjectHistory::new(config.clone()); self.project = config; + self.image_size = Some(image_size); self.config_tx = Some(config_tx); self.export_tx = Some(export_tx); self.pending_save.borrow_mut().path = Some(self.bundle.clone()); if self.bg_tab == BgTab::Wallpaper { self.ensure_wallpapers(cx); } + self.ensure_background_image(cx); if let Ok(dest) = std::env::var("CAP_GPUI_AUTO_SCREENSHOT_EXPORT") && !dest.trim().is_empty() { - self.export_png(ExportDestination::Harness(PathBuf::from(dest)), window, cx); + self.export_image(ExportDestination::Harness(PathBuf::from(dest)), window, cx); + } + // `CAP_GPUI_AUTO_SCREENSHOT_CROP=1`: open the crop dialog once the + // image size is known -- the same reason as `CAP_GPUI_AUTO_CROP` on + // the video editor: unprivileged synthetic clicks are dropped, and the + // dialog has to be up before a real drag inside it can be posted. + if std::env::var("CAP_GPUI_AUTO_SCREENSHOT_CROP").is_ok_and(|value| value == "1") { + self.open_crop_dialog(window, cx); } cx.notify(); } @@ -644,14 +1085,26 @@ impl ScreenshotEditorWindow { pub fn frame_arrived( &mut self, image: Arc, + rgba: Arc>, size: (u32, u32), window: &mut Window, cx: &mut Context, ) { - self.frame_size = (size.0.max(1) as f32, size.1.max(1) as f32); + let next = (size.0.max(1) as f32, size.1.max(1) as f32); + self.rescale_annotations_for(next); + self.frame_size = next; + self.frame_rgba = Some(rgba); + self.ready = true; if let Some(previous) = self.frame.replace(image) { let _ = window.drop_image(previous); } + // The content rect moves with the frame, so every mask is re-clipped + // into it (`AnnotationLayer.tsx:88-141`) before the overlays resample. + if self.clamp_masks() { + self.publish(); + self.schedule_save(window, cx); + } + self.refresh_mask_overlays(window, cx); cx.notify(); } @@ -659,11 +1112,42 @@ impl ScreenshotEditorWindow { self.pending_save.clone() } - /// Every styling edit funnels through here: mutate, publish to the - /// renderer, schedule the debounced write -- `updateScreenshotConfig`'s - /// throttled-render + debounced-save pair, minus history (deviation: no - /// undo stack yet). - fn edit_project( + /// The context's resize effect (`context.tsx:474-547`): a frame that has + /// changed size by more than a pixel moves every annotation across from + /// the old content rect to the new one. + fn rescale_annotations_for(&mut self, frame_size: (f32, f32)) { + let Some(image_size) = self.image_size else { + return; + }; + let changed = self + .previous_transform + .as_ref() + .is_none_or(|(previous, _)| { + (frame_size.0 - previous.0).abs() > 1. || (frame_size.1 - previous.1).abs() > 1. + }); + if !changed { + return; + } + + let current = annotations::calculate_image_transform( + (frame_size.0 as f64, frame_size.1 as f64), + (image_size.0 as f64, image_size.1 as f64), + self.project.background.padding, + self.project.background.crop.as_ref(), + self.project.aspect_ratio.as_ref(), + ); + if let Some((_, previous)) = self.previous_transform { + annotations::rescale_annotations(&mut self.project.annotations, &previous, ¤t); + } + self.previous_transform = Some((frame_size, current)); + } + + /// Every project edit funnels through here: mutate, record one history + /// entry, publish to the renderer, schedule the debounced write -- + /// `updateScreenshotConfig`'s throttled-render + debounced-save pair. + /// `ProjectHistory::record` is a no-op while a drag holds the pause, so a + /// slider's sixty intermediate values still collapse into one entry. + pub(crate) fn edit_project( &mut self, change: impl FnOnce(&mut ProjectConfiguration) -> bool, window: &mut Window, @@ -672,12 +1156,17 @@ impl ScreenshotEditorWindow { if !change(&mut self.project) { return; } + // Padding, crop and aspect all move the content rect underneath the + // masks; the clamp rides along in the same history entry. + self.clamp_masks(); + self.history.record(&self.project); self.publish(); self.schedule_save(window, cx); + self.refresh_mask_overlays(window, cx); cx.notify(); } - fn publish(&mut self) { + pub(crate) fn publish(&mut self) { let Some(config_tx) = &self.config_tx else { return; }; @@ -688,7 +1177,7 @@ impl ScreenshotEditorWindow { }); } - fn schedule_save(&mut self, window: &mut Window, cx: &mut Context) { + pub(crate) fn schedule_save(&mut self, window: &mut Window, cx: &mut Context) { self.pending_save.borrow_mut().config = Some(self.project.clone()); let pending = self.pending_save.clone(); self.save_task = Some(cx.spawn_in(window, async move |_, cx| { @@ -701,27 +1190,238 @@ impl ScreenshotEditorWindow { self.theme.refresh(window, cx, false); } + // -- History --------------------------------------------------------------- + + fn undo(&mut self, window: &mut Window, cx: &mut Context) { + let Some(config) = self.history.undo().cloned() else { + return; + }; + self.apply_history(config, window, cx); + } + + fn redo(&mut self, window: &mut Window, cx: &mut Context) { + let Some(config) = self.history.redo().cloned() else { + return; + }; + self.apply_history(config, window, cx); + } + + fn apply_history( + &mut self, + config: ProjectConfiguration, + window: &mut Window, + cx: &mut Context, + ) { + self.project = config; + self.bg_tab = BgTab::for_source(&self.project.background.source); + if let Some(id) = &self.selected_annotation + && !self + .project + .annotations + .iter() + .any(|annotation| &annotation.id == id) + { + self.selected_annotation = None; + } + self.ensure_background_image(cx); + self.publish(); + self.schedule_save(window, cx); + self.refresh_mask_overlays(window, cx); + cx.notify(); + } + + // -- Toasts ----------------------------------------------------------------- + + /// `toast.loading(..)` -- the bubble the share flow posts before it starts + /// rendering and updates in place as the upload progresses. + #[allow(dead_code)] + fn toast_loading(&mut self, message: impl Into, cx: &mut Context) -> u64 { + let id = self.next_toast; + self.next_toast += 1; + self.toasts.push(Toast { + id, + kind: ToastKind::Loading, + message: message.into(), + }); + cx.notify(); + id + } + + /// `toast.success(msg, { id })` -- replace a loading bubble in place. + /// Success and error bubbles then time out; the loading one persists until + /// it is updated. + #[allow(dead_code)] + fn toast_update( + &mut self, + id: u64, + kind: ToastKind, + message: impl Into, + window: &mut Window, + cx: &mut Context, + ) { + let message = message.into(); + match self.toasts.iter_mut().find(|toast| toast.id == id) { + Some(toast) => { + toast.kind = kind; + toast.message = message; + } + None => self.toasts.push(Toast { id, kind, message }), + } + if kind != ToastKind::Loading { + self.dismiss_after(id, window, cx); + } + cx.notify(); + } + + fn toast_success( + &mut self, + message: impl Into, + window: &mut Window, + cx: &mut Context, + ) { + let id = self.next_toast; + self.next_toast += 1; + self.toasts.push(Toast { + id, + kind: ToastKind::Success, + message: message.into(), + }); + self.dismiss_after(id, window, cx); + cx.notify(); + } + + fn toast_error( + &mut self, + message: impl Into, + window: &mut Window, + cx: &mut Context, + ) { + let id = self.next_toast; + self.next_toast += 1; + self.toasts.push(Toast { + id, + kind: ToastKind::Error, + message: message.into(), + }); + self.dismiss_after(id, window, cx); + cx.notify(); + } + + fn dismiss_after(&mut self, id: u64, window: &mut Window, cx: &mut Context) { + let task = cx.spawn_in(window, async move |this, cx| { + cx.background_executor().timer(TOAST_DURATION).await; + this.update(cx, |this, cx| { + this.toasts.retain(|toast| toast.id != id); + this.toast_tasks.remove(&id); + cx.notify(); + }) + .ok(); + }); + self.toast_tasks.insert(id, task); + } + // -- Styling ---------------------------------------------------------------- + fn anchor(&self, anchor: Anchor) -> BoundsCell { + self.anchors.get(&anchor).cloned().unwrap_or_default() + } + + fn track(&self, slider: StyleSlider) -> ui::SliderTrack { + self.slider_tracks.get(&slider).cloned().unwrap_or_default() + } + /// A slider press or drag move: map the pointer to the slider's value and /// apply it. fn apply_slider( &mut self, slider: StyleSlider, - position: Point, + position: Point, window: &mut Window, cx: &mut Context, ) { - let Some(track) = self.slider_tracks.get(&slider) else { - return; - }; + let track = self.track(slider); let (min, max, step) = slider.range(); - let Some(value) = ui::slider_value_at(track, position, min, max, step) else { + let Some(value) = ui::slider_value_at(&track, position, min, max, step) else { return; }; + if slider == StyleSlider::Zoom { + // The HUD's slider sets zoom outright and leaves the pan alone + // (`Preview.tsx:484-492`). + if self.zoom != value { + self.zoom = value; + cx.notify(); + } + return; + } self.edit_project(move |project| slider.apply(project, value), window, cx); } + fn begin_slider( + &mut self, + slider: StyleSlider, + position: Point, + window: &mut Window, + cx: &mut Context, + ) { + if self.active_slider.is_none() { + self.history.pause(); + } + self.active_slider = Some(slider); + self.apply_slider(slider, position, window, cx); + cx.notify(); + } + + fn end_slider(&mut self, cx: &mut Context) { + if self.active_slider.take().is_some() { + self.history.resume(&self.project); + } + cx.notify(); + } + + /// A collapsible's height animation has no frame loop of its own -- gpui + /// only repaints on demand -- so a toggle pumps the ~220ms transition, the + /// same way `editor_sidebar::animate_collapsibles` does. + fn animate_collapsibles(&mut self, window: &mut Window, cx: &mut Context) { + cx.notify(); + window.refresh(); + cx.spawn_in(window, async move |this, cx| { + for _ in 0..14 { + cx.background_executor() + .timer(Duration::from_millis(16)) + .await; + if this + .update_in(cx, |_, window, cx| { + cx.notify(); + window.refresh(); + }) + .is_err() + { + return; + } + } + }) + .detach(); + } + + /// `ensurePaddingForBackground` (`BackgroundSettingsPopover.tsx:205-223`): + /// picking a visible background with everything at zero would render as a + /// bare screenshot, so the first pick also gives it padding, a corner + /// radius and a shadow -- as one history entry. + fn ensure_padding_for_background(project: &mut ProjectConfiguration) { + let background = &mut project.background; + let padding_zero = background.padding == 0.; + let rounding_zero = background.rounding == 0.; + if padding_zero { + background.padding = 10.; + } + if padding_zero && rounding_zero { + background.rounding = 8.; + } + if background.shadow == 0. { + background.shadow = DEFAULT_BACKGROUND_SHADOW; + } + } + /// Decode the current theme's wallpaper tiles that are not cached yet -- /// `ensure_wallpapers` on the video editor, against this window's own map. fn ensure_wallpapers(&mut self, cx: &mut Context) { @@ -755,33 +1455,410 @@ impl ScreenshotEditorWindow { })); } - fn pick_background_image(&mut self, window: &mut Window, cx: &mut Context) { - cx.spawn_in(window, async move |this, cx| { - let Some(path) = crate::platform::open_image_panel(&BACKGROUND_IMAGE_EXTENSIONS) else { - return; - }; - this.update_in(cx, |this, window, cx| { - let path = path.to_string_lossy().to_string(); - this.edit_project( - move |project| { - project.background.source = BackgroundSource::Image { path: Some(path) }; - true - }, - window, + /// The Image tab's `h-48` preview. Decoded off-thread and cached by path, + /// so switching tabs does not re-read the file. + fn ensure_background_image(&mut self, cx: &mut Context) { + let BackgroundSource::Image { path: Some(path) } = &self.project.background.source else { + self.bg_image = None; + return; + }; + if self + .bg_image + .as_ref() + .is_some_and(|(cached, _)| cached == path) + { + return; + } + let path = path.clone(); + self.bg_image_task = Some(cx.spawn(async move |this, cx| { + let decoded = cx + .background_executor() + .spawn({ + let path = path.clone(); + async move { decode_preview_image(Path::new(&path)) } + }) + .await; + this.update(cx, |this, cx| { + this.bg_image = decoded.map(|image| (path, image)); + cx.notify(); + }) + .ok(); + })); + } + + fn pick_background_image(&mut self, window: &mut Window, cx: &mut Context) { + cx.spawn_in(window, async move |this, cx| { + let Some(path) = crate::platform::open_image_panel(&BACKGROUND_IMAGE_EXTENSIONS) else { + return; + }; + this.update_in(cx, |this, window, cx| { + let path = path.to_string_lossy().to_string(); + this.edit_project( + move |project| { + project.background.source = BackgroundSource::Image { path: Some(path) }; + Self::ensure_padding_for_background(project); + true + }, + window, cx, ); + this.ensure_background_image(cx); }) .ok(); }) .detach(); } + // -- Hex fields ------------------------------------------------------------- + + fn hex_color(&self, target: HexTarget) -> Option { + match (target, &self.project.background.source) { + (HexTarget::BackgroundColor, BackgroundSource::Color { value, .. }) => Some(*value), + (HexTarget::GradientFrom, BackgroundSource::Gradient { from, .. }) => Some(*from), + (HexTarget::GradientTo, BackgroundSource::Gradient { to, .. }) => Some(*to), + (HexTarget::BorderColor, _) => Some( + self.project + .background + .border + .as_ref() + .map_or(BORDER_FALLBACK.color, |border| border.color), + ), + _ => None, + } + } + + fn set_hex_color( + &mut self, + target: HexTarget, + color: cap_project::Color, + window: &mut Window, + cx: &mut Context, + ) { + self.edit_project( + move |project| { + match (target, &mut project.background.source) { + (HexTarget::BackgroundColor, source) => { + let alpha = match source { + BackgroundSource::Color { alpha, .. } => *alpha, + _ => 255, + }; + *source = BackgroundSource::Color { + value: color, + alpha, + }; + } + (HexTarget::GradientFrom, BackgroundSource::Gradient { from, .. }) => { + *from = color; + } + (HexTarget::GradientTo, BackgroundSource::Gradient { to, .. }) => *to = color, + (HexTarget::BorderColor, _) => { + let border = project + .background + .border + .get_or_insert(BorderConfiguration { + enabled: true, + ..BORDER_FALLBACK + }); + border.color = color; + } + _ => return false, + } + true + }, + window, + cx, + ); + } + + /// `createWritableMemo(() => rgbToHex(props.value))`: the field re-derives + /// from the colour whenever it moves underneath -- a preset, an undo -- but + /// never while it has focus, or it would fight what is being typed. Runs + /// from `render`, where the focus is knowable. + fn sync_hex_inputs(&mut self, window: &Window, cx: &mut Context) { + for target in HexTarget::ALL { + let Some(value) = self.hex_color(target) else { + continue; + }; + let Some(input) = self.hex_inputs.get(&target).cloned() else { + continue; + }; + if input.read(cx).focus_handle().is_focused(window) { + continue; + } + let hex = editor_sidebar::rgb_to_hex(value); + if input.read(cx).text() != hex { + input.update(cx, |input, cx| input.set_text(hex, cx)); + } + } + } + + /// `RgbInput`'s three handlers (`ColorPicker.tsx:35-100`): a complete 6- or + /// 8-digit value commits live, Enter and blur commit whatever is in the + /// box, and anything that does not parse snaps back. + fn on_hex_event( + &mut self, + target: HexTarget, + event: &ui::TextInputEvent, + window: &mut Window, + cx: &mut Context, + ) { + match event { + ui::TextInputEvent::Changed => { + let Some(input) = self.hex_inputs.get(&target) else { + return; + }; + let text = input.read(cx).text().to_string(); + let digits = editor_sidebar::hex_digit_count(&text); + if digits != 6 && digits != 8 { + return; + } + let Some(rgba) = hex_to_rgb(text.trim()) else { + return; + }; + let color = [rgba[0] as u16, rgba[1] as u16, rgba[2] as u16]; + if self.hex_color(target) != Some(color) { + self.set_hex_color(target, color, window, cx); + } + } + ui::TextInputEvent::Confirmed | ui::TextInputEvent::Cancelled => { + self.commit_hex(target, window, cx); + let focus = self.focus.clone(); + window.focus(&focus, cx); + } + ui::TextInputEvent::Blurred => self.commit_hex(target, window, cx), + } + } + + fn commit_hex(&mut self, target: HexTarget, window: &mut Window, cx: &mut Context) { + let Some(input) = self.hex_inputs.get(&target).cloned() else { + return; + }; + let text = input.read(cx).text().to_string(); + match hex_to_rgb(text.trim()) { + Some(rgba) => { + let color = [rgba[0] as u16, rgba[1] as u16, rgba[2] as u16]; + if self.hex_color(target) != Some(color) { + self.set_hex_color(target, color, window, cx); + } + input.update(cx, |input, cx| { + input.set_text(editor_sidebar::rgb_to_hex(color), cx) + }); + } + None => { + if let Some(value) = self.hex_color(target) { + input.update(cx, |input, cx| { + input.set_text(editor_sidebar::rgb_to_hex(value), cx) + }); + } + } + } + } + + // -- Preview geometry ------------------------------------------------------- + + /// `Preview.tsx:88-139`: letterbox the frame into the padded area, cap it + /// at the frame's native size (no upscale past 100%), then place the + /// content wrapper inside the centred viewport. + fn preview_geometry(&self) -> Option { + let area = self.preview_area.get()?; + let available_width = (f32::from(area.size.width) - PREVIEW_PADDING * 2.).max(0.); + let available_height = (f32::from(area.size.height) - PREVIEW_PADDING * 2.).max(0.); + if available_width <= 0. || available_height <= 0. { + return None; + } + let container_aspect = available_width / available_height; + let (frame_width, frame_height) = self.frame_size; + let content_aspect = if frame_width == 0. || frame_height == 0. { + container_aspect + } else { + frame_width / frame_height + }; + + let (mut width, mut height) = if content_aspect < container_aspect { + (available_height * content_aspect, available_height) + } else { + (available_width, available_width / content_aspect) + }; + width = width.min(frame_width); + height = height.min(frame_height); + + let fit_scale = if frame_width == 0. { + 1. + } else { + width / frame_width + }; + // `cssScale` (`Preview.tsx:129-131`). + let css_scale = fit_scale * self.zoom; + let scaled = (frame_width * css_scale, frame_height * css_scale); + Some(PreviewGeometry { + size: (width, height), + fit_scale, + scaled, + content: ( + (width - scaled.0) / 2. + self.pan.0, + (height - scaled.1) / 2. + self.pan.1, + ), + }) + } + + fn clamp_zoom(zoom: f32) -> f32 { + zoom.clamp(MIN_ZOOM, MAX_ZOOM) + } + + /// `zoomAtPoint` (`Preview.tsx:221-264`), with `bounds.x`/`bounds.y` zero + /// because the preview's content bounds are always the whole frame here. + /// `pointer` is relative to the viewport's top-left. + fn zoom_at_point(&mut self, pointer: (f32, f32), new_zoom: f32, cx: &mut Context) { + let Some(geometry) = self.preview_geometry() else { + self.zoom = new_zoom; + cx.notify(); + return; + }; + let current_scale = geometry.fit_scale * self.zoom; + let next_scale = geometry.fit_scale * new_zoom; + if current_scale > 0. && next_scale > 0. && geometry.size.0 > 0. && geometry.size.1 > 0. { + let content_x = + (pointer.0 - (geometry.size.0 - geometry.size.0 * self.zoom) / 2. - self.pan.0) + / current_scale; + let content_y = + (pointer.1 - (geometry.size.1 - geometry.size.1 * self.zoom) / 2. - self.pan.1) + / current_scale; + self.pan = ( + pointer.0 + - (geometry.size.0 - geometry.size.0 * new_zoom) / 2. + - content_x * next_scale, + pointer.1 + - (geometry.size.1 - geometry.size.1 * new_zoom) / 2. + - content_y * next_scale, + ); + } + self.zoom = new_zoom; + cx.notify(); + } + + /// `zoomIn` / `zoomOut` (`Preview.tsx:183-191`): a tenth either way, and + /// the pan resets. + fn nudge_zoom(&mut self, delta: f32, cx: &mut Context) { + self.zoom = Self::clamp_zoom(self.zoom + delta); + self.pan = (0., 0.); + cx.notify(); + } + + /// `handleWheel` (`Preview.tsx:300-322`): a plain scroll pans, ctrl+scroll + /// zooms at the pointer. gpui's `Lines` delta is the browser's + /// `deltaMode === 1`, so it takes the same x16. + fn preview_wheel( + &mut self, + event: &gpui::ScrollWheelEvent, + _window: &mut Window, + cx: &mut Context, + ) { + let (delta_x, delta_y) = match event.delta { + gpui::ScrollDelta::Pixels(delta) => (f32::from(delta.x), f32::from(delta.y)), + gpui::ScrollDelta::Lines(delta) => (delta.x * 16., delta.y * 16.), + }; + if event.modifiers.control { + if delta_y == 0. { + return; + } + let delta = -delta_y.signum() * delta_y.abs().max(8.); + let zoom = Self::clamp_zoom(self.zoom + delta * 0.005); + let pointer = self.viewport_pointer(event.position); + self.zoom_at_point(pointer, zoom, cx); + return; + } + self.pan = (self.pan.0 - delta_x, self.pan.1 - delta_y); + cx.notify(); + } + + /// The trackpad pinch the webview would have delivered as `gesturechange` + /// (`Preview.tsx:341-350`): `delta` is already the fractional scale change. + fn preview_pinch( + &mut self, + event: &gpui::PinchEvent, + _window: &mut Window, + cx: &mut Context, + ) { + let zoom = Self::clamp_zoom(self.zoom * (1. + event.delta)); + let pointer = self.viewport_pointer(event.position); + self.zoom_at_point(pointer, zoom, cx); + } + + /// A window-space pointer, made viewport-local -- `getBoundingClientRect`'s + /// job over there. + fn viewport_pointer(&self, position: Point) -> (f32, f32) { + match self.viewport.get() { + Some(bounds) => ( + f32::from(position.x - bounds.origin.x), + f32::from(position.y - bounds.origin.y), + ), + None => (f32::from(position.x), f32::from(position.y)), + } + } + + fn preview_mouse_down( + &mut self, + event: &MouseDownEvent, + window: &mut Window, + cx: &mut Context, + ) { + // The image surface hands the press to the annotation layer first; a + // press it does not want falls through to the pan, which is what + // `onBackgroundMouseDown` does over there. + if event.button == MouseButton::Left && self.annotation_mouse_down(event, window, cx) { + // `onMouseDown={dismissActivePopover}` sits on the viewport itself + // (`Preview.tsx:711`), so a press that draws still closes whatever + // styling popover was open. + if self.active_popover.take().is_some() { + cx.notify(); + } + return; + } + if event.button != MouseButton::Left && event.button != MouseButton::Middle { + return; + } + self.active_popover = None; + self.panning = Some(PanDrag { + start: event.position, + origin: self.pan, + }); + cx.notify(); + } + + fn preview_mouse_move( + &mut self, + event: &MouseMoveEvent, + window: &mut Window, + cx: &mut Context, + ) { + if self.annotation_mouse_move(event.position, window, cx) { + return; + } + let Some(drag) = self.panning else { + return; + }; + self.pan = ( + drag.origin.0 + f32::from(event.position.x - drag.start.x), + drag.origin.1 + f32::from(event.position.y - drag.start.y), + ); + cx.notify(); + } + + fn preview_mouse_up(&mut self, window: &mut Window, cx: &mut Context) { + self.annotation_mouse_up(window, cx); + if self.panning.take().is_some() { + cx.notify(); + } + } + // -- Export actions ------------------------------------------------------- - /// Render at export resolution and hand the PNG to its destination -- - /// `useScreenshotExport`'s Copy and Save destinations (the canvas - /// composite is annotation work, which does not exist here yet). - fn export_png( + /// Render at export resolution, bake the annotations in + /// (`renderScreenshotExportCanvas` -- `crate::screenshot_export`), and hand + /// the encoded bytes to their destination -- `exportImage`'s Copy and Save + /// arms (`useScreenshotExport.ts:139-253`). The composite and the encode + /// both run on the background executor. + fn export_image( &mut self, destination: ExportDestination, window: &mut Window, @@ -794,17 +1871,18 @@ impl ScreenshotEditorWindow { return; }; self.exporting = true; + self.export_status = ExportStatus::Rendering; cx.notify(); let config = self.project.clone(); let name = self.pretty_name.clone(); cx.spawn_in(window, async move |this, cx| { let (reply_tx, reply_rx) = tokio::sync::oneshot::channel(); - let request = ExportRequest::Png { - config, + let request = ExportRequest::Render { + config: config.clone(), reply: reply_tx, }; - let result = if export_tx.send(request).await.is_ok() { + let rendered = if export_tx.send(request).await.is_ok() { match reply_rx.await { Ok(result) => result, Err(_) => Err("The renderer stopped before the export finished".into()), @@ -813,6 +1891,44 @@ impl ScreenshotEditorWindow { Err("The screenshot renderer is not running".into()) }; + // The canvas pass counts as "rendering" over there too + // (`setExportStatus("encoding")` only lands after + // `renderExportCanvas` returns). + let composited = match rendered { + Ok(raw) => { + let composite_config = config.clone(); + Ok(cx + .background_executor() + .spawn(async move { + crate::screenshot_export::composite(&raw, &composite_config) + }) + .await) + } + Err(error) => Err(error), + }; + + let result = match composited { + Ok(out) => { + this.update_in(cx, |this, _window, cx| { + this.export_status = ExportStatus::Encoding; + cx.notify(); + }) + .ok(); + let encode_config = config.clone(); + let for_clipboard = destination == ExportDestination::Clipboard; + cx.background_executor() + .spawn(async move { + if for_clipboard { + crate::screenshot_export::encode_for_copy(&out, &encode_config) + } else { + crate::screenshot_export::encode_for_save(&out) + } + }) + .await + } + Err(error) => Err(error), + }; + match result { Ok(bytes) => match destination { ExportDestination::Clipboard => { @@ -835,8 +1951,25 @@ impl ScreenshotEditorWindow { let copied = this.update_in(cx, |_, _, _| { crate::platform::copy_image_to_clipboard(&path) }); - if let Ok(Err(error)) = copied { - tracing::error!("copying the screenshot failed: {error}"); + match copied { + Ok(Err(error)) => { + tracing::error!("copying the screenshot failed: {error}"); + this.update_in(cx, |this, window, cx| { + this.toast_error(error, window, cx) + }) + .ok(); + } + Ok(Ok(())) => { + this.update_in(cx, |this, window, cx| { + this.toast_success( + "Screenshot copied to clipboard!", + window, + cx, + ) + }) + .ok(); + } + Err(_) => {} } cx.background_executor() .spawn(async move { @@ -845,7 +1978,11 @@ impl ScreenshotEditorWindow { .detach(); } Err(error) => { - tracing::error!("writing the clipboard temp file failed: {error}") + tracing::error!("writing the clipboard temp file failed: {error}"); + this.update_in(cx, |this, window, cx| { + this.toast_error(error, window, cx) + }) + .ok(); } } } @@ -857,8 +1994,20 @@ impl ScreenshotEditorWindow { .background_executor() .spawn(async move { std::fs::write(&dest, &bytes) }) .await; - if let Err(error) = written { - tracing::error!("saving the screenshot failed: {error}"); + match written { + Ok(()) => { + this.update_in(cx, |this, window, cx| { + this.toast_success("Screenshot saved!", window, cx) + }) + .ok(); + } + Err(error) => { + tracing::error!("saving the screenshot failed: {error}"); + this.update_in(cx, |this, window, cx| { + this.toast_error(error.to_string(), window, cx) + }) + .ok(); + } } } } @@ -869,11 +2018,16 @@ impl ScreenshotEditorWindow { Err(error) => tracing::error!("harness export failed: {error}"), }, }, - Err(error) => tracing::error!("screenshot export failed: {error}"), + Err(error) => { + tracing::error!("screenshot export failed: {error}"); + this.update_in(cx, |this, window, cx| this.toast_error(error, window, cx)) + .ok(); + } } this.update_in(cx, |this, _window, cx| { this.exporting = false; + this.export_status = ExportStatus::Idle; cx.notify(); }) .ok(); @@ -881,74 +2035,356 @@ impl ScreenshotEditorWindow { .detach(); } - /// The header's Delete: confirm, remove the bundle, close this window -- - /// `Header.tsx`'s delete (`remove(path)` then close). - fn delete_screenshot(&mut self, window: &mut Window, cx: &mut Context) { - let bundle = self.bundle.clone(); - cx.spawn_in(window, async move |_this, cx| { - if !crate::platform::confirm_dialog( - "Cap", - "Are you sure you want to delete this screenshot?", - "Yes", - "No", - false, - ) { - return; - } - let deleted = cx - .background_executor() - .spawn({ - let bundle = bundle.clone(); - async move { crate::library::delete_screenshot(&bundle) } - }) - .await; - if let Err(error) = deleted { - tracing::error!(path = %bundle.display(), "deleting the screenshot failed: {error}"); - return; - } - cx.update(|_, cx| crate::app_windows::close_screenshot_editor_after_delete(&bundle, cx)) - .ok(); - }) - .detach(); + /// One step of the share flow's status ladder: the header tooltip's + /// `exportStatus` and the one loading toast, updated in place. + fn share_progress( + &mut self, + toast: u64, + status: ExportStatus, + message: &'static str, + window: &mut Window, + cx: &mut Context, + ) { + self.export_status = status; + self.toast_update(toast, ToastKind::Loading, message, window, cx); } - // -- Aspect ratio ------------------------------------------------------- - - const ASPECT_OPTIONS: [(Option, &'static str); 6] = [ - (None, "Auto"), - (Some(cap_project::AspectRatio::Wide), "Wide ⋅16:9"), - (Some(cap_project::AspectRatio::Vertical), "Vertical ⋅9:16"), - (Some(cap_project::AspectRatio::Square), "Square ⋅1:1"), - (Some(cap_project::AspectRatio::Classic), "Classic ⋅4:3"), - (Some(cap_project::AspectRatio::Tall), "Tall ⋅3:4"), - ]; - - fn aspect_label(&self) -> &'static str { - match &self.project.aspect_ratio { - None => "Auto", - Some(cap_project::AspectRatio::Wide) => "Wide", - Some(cap_project::AspectRatio::Vertical) => "Vertical", - Some(cap_project::AspectRatio::Square) => "Square", - Some(cap_project::AspectRatio::Classic) => "Classic", - Some(cap_project::AspectRatio::Tall) => "Tall", - } + fn finish_share_error( + &mut self, + toast: u64, + message: String, + window: &mut Window, + cx: &mut Context, + ) { + tracing::error!("screenshot share failed: {message}"); + self.toast_update(toast, ToastKind::Error, message, window, cx); + self.exporting = false; + self.export_status = ExportStatus::Idle; + cx.notify(); } - fn aspect_menu_items(&self) -> Vec { - Self::ASPECT_OPTIONS - .iter() - .map(|(value, label)| { - ui::MenuItem::new(*label, aspect_eq(value, &self.project.aspect_ratio)) - }) - .collect() + /// `copy_screenshot_share_link`: the link onto the clipboard, the loading + /// toast flipped to "Share link copied to clipboard". + fn finish_share_success( + &mut self, + toast: u64, + link: String, + window: &mut Window, + cx: &mut Context, + ) { + cx.write_to_clipboard(gpui::ClipboardItem::new_string(link)); + self.toast_update( + toast, + ToastKind::Success, + "Share link copied to clipboard", + window, + cx, + ); + self.exporting = false; + self.export_status = ExportStatus::Idle; + cx.notify(); } - fn choose_aspect(&mut self, index: usize, window: &mut Window, cx: &mut Context) { - self.aspect_menu = None; - let Some((next, _)) = Self::ASPECT_OPTIONS.get(index) else { + /// `exportImage("share")` (`useScreenshotExport.ts:139-253`) end to end: + /// fingerprint the config; an unchanged one just re-copies the stored link + /// (`copy_current_screenshot_share_link`, no upload). Otherwise render + + /// composite, encode JPEG-0.9-or-PNG per transparency, create-or-get the + /// screenshot video record (reusing `sharing.id` so the link stays + /// stable), presigned-PUT the bytes, and persist + /// `SharingMeta { id, link, content_hash }` into the bundle's meta -- + /// `upload_rendered_screenshot` + `save_screenshot_sharing` over there. + /// + /// Failure modes, deliberately Tauri's: a failed upload (or a failed meta + /// write after a successful upload) leaves the existing sharing meta + /// untouched and toasts the error; the next attempt re-runs create-or-get, + /// which also heals a sharing id deleted server-side by minting a fresh + /// record. + fn share_screenshot(&mut self, window: &mut Window, cx: &mut Context) { + if self.exporting { + return; + } + let Some(export_tx) = self.export_tx.clone() else { return; }; - let next = next.clone(); + self.exporting = true; + self.export_status = ExportStatus::Encoding; + let toast = self.toast_loading("Preparing upload", cx); + cx.notify(); + + let config = self.project.clone(); + let bundle = self.bundle.clone(); + cx.spawn_in(window, async move |this, cx| { + // 1. The fingerprint, and the sharing meta it is compared against. + let hash_config = config.clone(); + let content_hash = match cx + .background_executor() + .spawn(async move { crate::screenshot_export::content_hash(&hash_config) }) + .await + { + Ok(hash) => hash, + Err(error) => { + this.update_in(cx, |this, window, cx| { + this.finish_share_error(toast, error, window, cx) + }) + .ok(); + return; + } + }; + + let meta_bundle = bundle.clone(); + let meta = cx + .background_executor() + .spawn(async move { + RecordingMeta::load_for_project(&meta_bundle) + .map_err(|error| format!("Failed to load screenshot metadata: {error}")) + }) + .await; + let mut meta = match meta { + Ok(meta) => meta, + Err(error) => { + this.update_in(cx, |this, window, cx| { + this.finish_share_error(toast, error, window, cx) + }) + .ok(); + return; + } + }; + + // 2. An unchanged config short-circuits to the stored link -- + // `screenshot_share_link_for_hash`. + if let Some(sharing) = meta.sharing.as_ref() + && sharing.content_hash.as_deref() == Some(content_hash.as_str()) + { + let link = sharing.link.clone(); + this.update_in(cx, |this, window, cx| { + this.finish_share_success(toast, link, window, cx) + }) + .ok(); + return; + } + + // 3. Render + composite. + this.update_in(cx, |this, window, cx| { + this.share_progress( + toast, + ExportStatus::Rendering, + "Rendering screenshot", + window, + cx, + ); + }) + .ok(); + let (reply_tx, reply_rx) = tokio::sync::oneshot::channel(); + let request = ExportRequest::Render { + config: config.clone(), + reply: reply_tx, + }; + let rendered = if export_tx.send(request).await.is_ok() { + match reply_rx.await { + Ok(result) => result, + Err(_) => Err("The renderer stopped before the export finished".into()), + } + } else { + Err("The screenshot renderer is not running".into()) + }; + let raw = match rendered { + Ok(raw) => raw, + Err(error) => { + this.update_in(cx, |this, window, cx| { + this.finish_share_error(toast, error, window, cx) + }) + .ok(); + return; + } + }; + let composite_config = config.clone(); + let out = cx + .background_executor() + .spawn(async move { crate::screenshot_export::composite(&raw, &composite_config) }) + .await; + + // 4. Encode -- JPEG at 0.9 unless the output needs its alpha. + this.update_in(cx, |this, window, cx| { + this.share_progress( + toast, + ExportStatus::Encoding, + "Preparing upload", + window, + cx, + ); + }) + .ok(); + let encode_config = config.clone(); + let encoded = match cx + .background_executor() + .spawn( + async move { crate::screenshot_export::encode_for_share(&out, &encode_config) }, + ) + .await + { + Ok(encoded) => encoded, + Err(error) => { + this.update_in(cx, |this, window, cx| { + this.finish_share_error(toast, error, window, cx) + }) + .ok(); + return; + } + }; + + // 5. Upload, on the tokio runtime the network stack lives on. + this.update_in(cx, |this, window, cx| { + this.share_progress( + toast, + ExportStatus::Uploading, + "Uploading screenshot", + window, + cx, + ); + }) + .ok(); + let existing_id = meta.sharing.as_ref().map(|sharing| sharing.id.clone()); + let content_type = encoded.content_type(); + let bytes = encoded.into_bytes(); + let upload = gpui_tokio::Tokio::spawn(cx, async move { + crate::upload::upload_rendered_screenshot(bytes, content_type, existing_id).await + }); + let outcome = match upload.await { + Ok(outcome) => outcome, + Err(error) => Err(format!("The upload task failed: {error}")), + }; + + let uploaded = match outcome { + Ok(crate::upload::ScreenshotShareOutcome::Uploaded(item)) => item, + Ok(crate::upload::ScreenshotShareOutcome::NotAuthenticated) => { + this.update_in(cx, |this, window, cx| { + this.finish_share_error( + toast, + "You need to sign in to create shareable links".into(), + window, + cx, + ) + }) + .ok(); + return; + } + Ok(crate::upload::ScreenshotShareOutcome::UpgradeRequired) => { + this.update_in(cx, |this, window, cx| { + this.finish_share_error( + toast, + "This feature requires an upgraded plan".into(), + window, + cx, + ) + }) + .ok(); + return; + } + Err(error) => { + this.update_in(cx, |this, window, cx| { + this.finish_share_error(toast, error, window, cx) + }) + .ok(); + return; + } + }; + + // 6. `save_screenshot_sharing`: the meta write is part of the + // flow's success -- a failure here surfaces as the error, and + // the sharing meta on disk stays what it was. + let link = uploaded.link.clone(); + meta.sharing = Some(cap_project::SharingMeta { + id: uploaded.id, + link: uploaded.link, + content_hash: Some(content_hash), + }); + let saved = cx + .background_executor() + .spawn(async move { + meta.save_for_project() + .map_err(|error| format!("Error saving project: {error}")) + }) + .await; + + this.update_in(cx, |this, window, cx| match saved { + Ok(()) => this.finish_share_success(toast, link, window, cx), + Err(error) => this.finish_share_error(toast, error, window, cx), + }) + .ok(); + }) + .detach(); + } + + /// The header's Delete: confirm, remove the bundle, close this window -- + /// `Header.tsx`'s delete (`remove(path)` then close). + fn delete_screenshot(&mut self, window: &mut Window, cx: &mut Context) { + let bundle = self.bundle.clone(); + cx.spawn_in(window, async move |_this, cx| { + if !crate::platform::confirm_dialog( + "Cap", + "Are you sure you want to delete this screenshot?", + "Yes", + "No", + false, + ) { + return; + } + let deleted = cx + .background_executor() + .spawn({ + let bundle = bundle.clone(); + async move { crate::library::delete_screenshot(&bundle) } + }) + .await; + if let Err(error) = deleted { + tracing::error!(path = %bundle.display(), "deleting the screenshot failed: {error}"); + return; + } + cx.update(|_, cx| crate::app_windows::close_screenshot_editor_after_delete(&bundle, cx)) + .ok(); + }) + .detach(); + } + + // -- Aspect ratio ------------------------------------------------------- + + const ASPECT_OPTIONS: [(Option, &'static str); 6] = [ + (None, "Auto"), + (Some(cap_project::AspectRatio::Wide), "Wide ⋅16:9"), + (Some(cap_project::AspectRatio::Vertical), "Vertical ⋅9:16"), + (Some(cap_project::AspectRatio::Square), "Square ⋅1:1"), + (Some(cap_project::AspectRatio::Classic), "Classic ⋅4:3"), + (Some(cap_project::AspectRatio::Tall), "Tall ⋅3:4"), + ]; + + /// `KSelect.Value` (`AspectRatioSelect.tsx:78-88`) -- the trigger shows the + /// bare ratio, not the option's name. + fn aspect_label(&self) -> &'static str { + match &self.project.aspect_ratio { + None => "Auto", + Some(cap_project::AspectRatio::Wide) => "16:9", + Some(cap_project::AspectRatio::Vertical) => "9:16", + Some(cap_project::AspectRatio::Square) => "1:1", + Some(cap_project::AspectRatio::Classic) => "4:3", + Some(cap_project::AspectRatio::Tall) => "3:4", + } + } + + fn aspect_menu_items(&self) -> Vec { + Self::ASPECT_OPTIONS + .iter() + .map(|(value, label)| { + ui::MenuItem::new(*label, aspect_eq(value, &self.project.aspect_ratio)) + }) + .collect() + } + + fn choose_aspect(&mut self, index: usize, window: &mut Window, cx: &mut Context) { + self.menu = None; + let Some((next, _)) = Self::ASPECT_OPTIONS.get(index) else { + return; + }; + let next = next.clone(); self.edit_project( move |project| { if aspect_eq(&project.aspect_ratio, &next) { @@ -962,727 +2398,2247 @@ impl ScreenshotEditorWindow { ); } - fn on_key(&mut self, key: &str, window: &mut Window, cx: &mut Context) { - let Some(menu) = self.aspect_menu.as_mut() else { + const CORNER_STYLES: [(CornerStyle, &'static str); 2] = [ + (CornerStyle::Squircle, "Squircle"), + (CornerStyle::Rounded, "Rounded"), + ]; + + fn corner_style_label(&self) -> &'static str { + match self.project.background.rounding_type { + CornerStyle::Squircle => "Squircle", + CornerStyle::Rounded => "Rounded", + } + } + + fn menu_items(&self, kind: MenuKind) -> Vec { + match kind { + MenuKind::Aspect => self.aspect_menu_items(), + MenuKind::More => vec![ + ui::MenuItem::new("Open Folder", false), + ui::MenuItem::new("Delete", false), + ], + MenuKind::CornerStyle => Self::CORNER_STYLES + .iter() + .map(|(style, label)| { + ui::MenuItem::new(*label, *style == self.project.background.rounding_type) + }) + .collect(), + } + } + + fn commit_menu(&mut self, index: usize, window: &mut Window, cx: &mut Context) { + let Some((kind, _)) = self.menu else { return; }; - match menu.on_key(key) { - ui::MenuKey::Moved => cx.notify(), - ui::MenuKey::Commit(index) => self.choose_aspect(index, window, cx), - ui::MenuKey::Dismiss => { - self.aspect_menu = None; + match kind { + MenuKind::Aspect => self.choose_aspect(index, window, cx), + MenuKind::More => { + self.menu = None; + match index { + 0 => crate::library::reveal_in_folder(&self.bundle.join("original.png")), + 1 => self.delete_screenshot(window, cx), + _ => {} + } cx.notify(); } - ui::MenuKey::Ignored => {} + MenuKind::CornerStyle => { + self.menu = None; + let Some((style, _)) = Self::CORNER_STYLES.get(index) else { + return; + }; + let style = *style; + self.edit_project( + move |project| { + if project.background.rounding_type == style { + return false; + } + project.background.rounding_type = style; + true + }, + window, + cx, + ); + } } } - // -- Rendering ---------------------------------------------------------- - - /// A small bordered icon button, the aspect button's palette. - fn header_action( - &self, - id: &'static str, - icon: &'static str, - enabled: bool, - handler: impl Fn(&mut Self, &mut Window, &mut Context) + 'static, + /// Open a menu under `anchor`, or close it if it is already the open one. + fn toggle_menu( + &mut self, + kind: MenuKind, + anchor: Anchor, + window: &mut Window, cx: &mut Context, - ) -> impl IntoElement { + ) { + if self.menu.as_ref().is_some_and(|(open, _)| *open == kind) { + self.menu = None; + cx.notify(); + return; + } + let items = self.menu_items(kind); + let origin = self + .anchor(anchor) + .get() + .map(|bounds| { + let x = match kind { + // `placement="bottom-end"` (`Header.tsx:168`). + MenuKind::More => bounds.origin.x + bounds.size.width - px(200.), + _ => bounds.origin.x, + }; + gpui::point(x.max(px(8.)), bounds.origin.y + bounds.size.height + px(4.)) + }) + .unwrap_or_else(|| gpui::point(px(16.), px(HEADER_HEIGHT))); + self.menu = Some((kind, ui::MenuState::new(origin, &items))); + // The corner-style select lives *inside* the rounding popover, so it + // is the one menu that must not close what it is standing on. + if kind != MenuKind::CornerStyle { + self.active_popover = None; + } + window.focus(&self.focus, cx); + cx.notify(); + } + + /// Close the styling popover and any chrome menu -- what opening a modal + /// (the crop dialog) does to the layers beneath it. + pub(crate) fn dismiss_chrome_popups(&mut self) { + self.active_popover = None; + self.menu = None; + } + + fn toggle_popover(&mut self, popover: Popover, window: &mut Window, cx: &mut Context) { + self.active_popover = if self.active_popover == Some(popover) { + None + } else { + self.menu = None; + window.focus(&self.focus, cx); + Some(popover) + }; + cx.notify(); + } + + fn set_tool(&mut self, tool: Tool, cx: &mut Context) { + self.tool = tool; + if tool != Tool::Select { + self.selected_annotation = None; + } + cx.notify(); + } + + fn toggle_layers_panel(&mut self, cx: &mut Context) { + self.layers_panel_open = !self.layers_panel_open; + let open = self.layers_panel_open; + cx.background_executor() + .spawn(async move { + crate::store::update(|state| state.screenshot_layers_panel_open = Some(open)); + }) + .detach(); + cx.notify(); + } + + // -- Keyboard --------------------------------------------------------------- + + /// `Editor.tsx:56-176` plus `Header.tsx:49-82` and `Preview.tsx:195-219`, + /// which are three `window` listeners over there and one handler here. + fn on_key(&mut self, event: &gpui::KeyDownEvent, window: &mut Window, cx: &mut Context) { + if event.is_held { + return; + } + // Every one of the three listeners bails on a focused input first. + if ui::text_input_has_focus(window, cx) { + return; + } + + // The crop dialog is modal: it takes its own keys (the options menu, + // Escape, the arrow nudge) and everything else stops at it -- no tool + // switching or popover toggling under the overlay. + if self.crop.is_some() { + if self.crop_dialog_key_down(event, window, cx) { + cx.stop_propagation(); + } + return; + } + + let key = event.keystroke.key.as_str(); + let modifiers = event.keystroke.modifiers; + let modified = modifiers.platform || modifiers.control; + + // An open menu takes its own keys -- arrows, Home/End, Enter, Escape. + if self.menu.is_some() { + let action = self + .menu + .as_mut() + .map(|(_, state)| state.on_key(key)) + .unwrap_or(ui::MenuKey::Ignored); + match action { + ui::MenuKey::Moved => { + cx.stop_propagation(); + cx.notify(); + return; + } + ui::MenuKey::Commit(index) => { + cx.stop_propagation(); + self.commit_menu(index, window, cx); + return; + } + ui::MenuKey::Dismiss => { + cx.stop_propagation(); + self.menu = None; + cx.notify(); + return; + } + ui::MenuKey::Ignored => {} + } + } + + if modified { + match key { + "z" => { + cx.stop_propagation(); + if modifiers.shift { + self.redo(window, cx); + } else { + self.undo(window, cx); + } + } + "y" => { + cx.stop_propagation(); + self.redo(window, cx); + } + "c" => { + cx.stop_propagation(); + if !self.copy_selected_annotation(cx) { + self.export_image(ExportDestination::Clipboard, window, cx); + } + } + "v" => { + cx.stop_propagation(); + self.paste_annotation(window, cx); + } + "s" => { + cx.stop_propagation(); + self.export_image(ExportDestination::File, window, cx); + } + "-" => { + cx.stop_propagation(); + self.nudge_zoom(-0.1, cx); + } + "=" | "+" => { + cx.stop_propagation(); + self.nudge_zoom(0.1, cx); + } + _ => {} + } + return; + } + if modifiers.shift || modifiers.alt { + return; + } + + match key { + "a" => self.set_tool(Tool::Arrow, cx), + "r" => self.set_tool(Tool::Rectangle, cx), + "m" => self.set_tool(Tool::Mask, cx), + // 'o' for oval, the same alias `Editor.tsx:134-138` carries. + "c" | "o" => self.set_tool(Tool::Circle, cx), + "d" => self.set_tool(Tool::Draw, cx), + "t" => self.set_tool(Tool::Text, cx), + "v" | "s" => { + self.set_tool(Tool::Select, cx); + self.selected_annotation = None; + cx.notify(); + } + "escape" => { + if self.active_popover.take().is_some() || self.menu.take().is_some() { + cx.notify(); + return; + } + self.set_tool(Tool::Select, cx); + self.selected_annotation = None; + cx.notify(); + } + "p" => self.toggle_popover(Popover::Padding, window, cx), + "b" => self.toggle_popover(Popover::Background, window, cx), + "h" => self.toggle_popover(Popover::Shadow, window, cx), + "e" => self.toggle_popover(Popover::Border, window, cx), + "l" => self.toggle_layers_panel(cx), + "backspace" | "delete" => self.delete_selected_annotation(window, cx), + _ => {} + } + } + + // -- Header ----------------------------------------------------------------- + + /// The shared-cell capture every anchored trigger wraps itself in. gpui has + /// no `getBoundingClientRect`; this is the same shape `ui::Slider` uses for + /// its track and `editor_crop` for its crop box. + fn anchored(&self, anchor: Anchor, child: impl IntoElement) -> impl IntoElement { + let cell = self.anchor(anchor); + div() + .relative() + .flex_shrink_0() + .child( + canvas( + move |bounds, _window, _cx| cell.set(Some(bounds)), + |_, _, _, _| {}, + ) + .absolute() + .size_full(), + ) + .child(child) + } + + /// `AnnotationTools.tsx:20-75`: the layers toggle, a divider, then the + /// seven tool buttons. + fn render_annotation_tools(&self, cx: &mut Context) -> impl IntoElement { let theme = self.theme; + let layers_open = self.layers_panel_open; + div() - .id(id) .flex() + .flex_row() .items_center() - .justify_center() - .size(px(30.)) - .rounded(px(8.)) - .border_1() - .border_color(theme.gray_4) - .bg(theme.gray_2) - .when(enabled, |this| { - this.hover(|style| style.bg(theme.gray_3)) - .on_click(cx.listener(move |this, _, window, cx| { + .gap(px(4.)) + .child(tool_button( + &theme, + "screenshot-layers-toggle", + "icons/layers.svg", + "Layers", + "L", + layers_open, + cx.listener(|this, _, _window, cx| { + cx.stop_propagation(); + this.toggle_layers_panel(cx); + }), + )) + .child(divider(&theme, 16.)) + .children(Tool::ALL.map(|tool| { + tool_button( + &theme, + gpui::SharedString::from(format!("screenshot-tool-{}", tool.label())), + tool.icon(), + tool.label(), + tool.shortcut(), + self.tool == tool, + cx.listener(move |this, _, _window, cx| { cx.stop_propagation(); - handler(this, window, cx); - })) - }) - .when(!enabled, |this| this.opacity(0.5)) - .child( - gpui::svg() - .path(icon) - .size(px(14.)) - .text_color(theme.gray_11), - ) + this.set_tool(tool, cx); + }), + ) + })) } - /// Traffic-light spacer + name on the left; copy, save, reveal, delete - /// and the aspect menu on the right -- the skeleton of the Tauri editor's - /// `Header.tsx`. + /// `Header.tsx:109-216`. fn render_header(&self, cx: &mut Context) -> impl IntoElement { let theme = self.theme; - let ready = self.export_tx.is_some() && self.error.is_none(); - let exportable = ready && !self.exporting; + let crop_enabled = self.image_size.is_some(); + let exporting = self.exporting; + let share_tooltip = match self.export_status { + ExportStatus::Rendering => "Rendering screenshot", + ExportStatus::Encoding => "Preparing upload", + ExportStatus::Uploading => "Uploading screenshot", + ExportStatus::Idle => "Create shareable link", + }; + div() + .relative() .flex() .flex_row() .items_center() - .gap(px(8.)) - .h(px(52.)) - .px(px(14.)) + .justify_between() + .w_full() + .h(px(HEADER_HEIGHT)) + .px(px(16.)) .flex_shrink_0() - .child(div().w(px(72.)).flex_shrink_0()) + .border_b_1() + .border_color(theme.gray_3) + .bg(if theme.is_dark() { + theme.gray_2 + } else { + theme.gray_1 + }) + // The inset traffic lights' spacer (`Header.tsx:115`). + .child(div().flex().items_center().child(div().w(px(56.)))) + // `absolute left-1/2 -translate-x-1/2` -- a full-width centred row + // is the same placement without a transform, and it is not + // interactive itself, so the right cluster painted after it still + // takes its own clicks. .child( div() - .flex_1() - .min_w_0() - .overflow_hidden() - .text_size(px(13.)) - .font_weight(FontWeight::MEDIUM) - .text_color(theme.gray_12) - .child(self.pretty_name.clone()), + .absolute() + .top_0() + .left_0() + .size_full() + .flex() + .flex_row() + .items_center() + .justify_center() + .gap(px(8.)) + .child( + self.anchored( + Anchor::Aspect, + ui::EditorButton::plain(&theme, "screenshot-aspect") + .width(px(80.)) + .left_icon("icons/layout.svg") + .icon_size(px(16.)) + .label(self.aspect_label()) + .right_icon("icons/chevron-down.svg") + .right_icon_end(true) + .pressed( + self.menu + .as_ref() + .is_some_and(|(kind, _)| *kind == MenuKind::Aspect), + ) + .tooltip(&theme, "Aspect Ratio") + .on_click(cx.listener(|this, _, window, cx| { + cx.stop_propagation(); + this.toggle_menu(MenuKind::Aspect, Anchor::Aspect, window, cx); + })), + ), + ) + .child( + ui::EditorButton::plain(&theme, "screenshot-crop") + .left_icon("icons/crop.svg") + .icon_size(px(16.)) + .disabled(!crop_enabled) + .tooltip(&theme, "Crop Image") + .on_click(cx.listener(|this, _, window, cx| { + cx.stop_propagation(); + this.open_crop_dialog(window, cx); + })), + ) + .child(divider(&theme, 24.)) + .child(self.render_annotation_tools(cx)) + .child(divider(&theme, 24.)) + .children( + [ + Popover::Background, + Popover::Padding, + Popover::Rounding, + Popover::Shadow, + Popover::Border, + ] + .map(|popover| { + let button = ui::EditorButton::plain(&theme, popover.id()) + .left_icon(popover.icon()) + .icon_size(px(16.)) + .pressed(self.active_popover == Some(popover)) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + this.toggle_popover(popover, window, cx); + })); + // The `kbd` prop rides the tooltip, which + // `ui::EditorButton` does not carry, so the wrapper the + // popover anchors against owns it instead. + self.anchored( + popover.anchor(), + kbd_tooltip(&theme, popover.tooltip(), popover.keys(), button), + ) + .into_any_element() + }), + ), ) - .child(self.header_action( - "screenshot-copy", - "icons/copy.svg", - exportable, - |this, window, cx| this.export_png(ExportDestination::Clipboard, window, cx), - cx, - )) - .child(self.header_action( - "screenshot-save", - "icons/download.svg", - exportable, - |this, window, cx| this.export_png(ExportDestination::File, window, cx), - cx, - )) - .child(self.header_action( - "screenshot-reveal", - "icons/folder-open.svg", - true, - |this, _window, _cx| { - crate::library::reveal_in_folder(&this.bundle.join("original.png")); - }, - cx, - )) - .child(self.header_action( - "screenshot-delete", - "icons/trash.svg", - true, - |this, window, cx| this.delete_screenshot(window, cx), - cx, - )) .child( div() - .id("aspect-button") .flex() .flex_row() .items_center() - .gap(px(6.)) - .px(px(10.)) - .py(px(6.)) - .rounded(px(8.)) - .border_1() - .border_color(theme.gray_4) - .bg(theme.gray_2) - .hover(|style| style.bg(theme.gray_3)) - .text_size(px(12.)) - .text_color(theme.gray_12) - .child(format!("Aspect: {}", self.aspect_label())) - .on_click(cx.listener(|this, event: &gpui::ClickEvent, window, cx| { - cx.stop_propagation(); - if this.aspect_menu.take().is_none() { - let items = this.aspect_menu_items(); - this.aspect_menu = Some(ui::MenuState::new(event.position(), &items)); - window.focus(&this.focus, cx); - } - cx.notify(); - })), + .gap(px(8.)) + .h_full() + .pr(px(8.)) + .child(divider(&theme, 24.)) + .child( + ui::EditorButton::plain(&theme, "screenshot-copy") + .left_icon("icons/copy.svg") + .icon_size(px(16.)) + .disabled(exporting) + .tooltip(&theme, "Copy to Clipboard") + .on_click(cx.listener(|this, _, window, cx| { + cx.stop_propagation(); + this.export_image(ExportDestination::Clipboard, window, cx); + })), + ) + .child( + ui::EditorButton::plain(&theme, "screenshot-save") + .left_icon("icons/save.svg") + .icon_size(px(16.)) + .disabled(exporting) + .tooltip(&theme, "Save") + .on_click(cx.listener(|this, _, window, cx| { + cx.stop_propagation(); + this.export_image(ExportDestination::File, window, cx); + })), + ) + .child( + ui::EditorButton::plain(&theme, "screenshot-share") + .left_icon("icons/link.svg") + .icon_size(px(16.)) + .disabled(exporting) + .tooltip(&theme, share_tooltip) + .on_click(cx.listener(|this, _, window, cx| { + cx.stop_propagation(); + this.share_screenshot(window, cx); + })), + ) + .child( + self.anchored( + Anchor::More, + ui::EditorButton::plain(&theme, "screenshot-more") + .left_icon("icons/more-horizontal.svg") + .icon_size(px(16.)) + .disabled(exporting) + .pressed( + self.menu + .as_ref() + .is_some_and(|(kind, _)| *kind == MenuKind::More), + ) + .tooltip(&theme, "More Actions") + .on_click(cx.listener(|this, _, window, cx| { + cx.stop_propagation(); + this.toggle_menu(MenuKind::More, Anchor::More, window, cx); + })), + ), + ), ) } - // -- Styling panel ---------------------------------------------------------- + // -- Layers panel ----------------------------------------------------------- - fn section_label(&self, label: &'static str) -> impl IntoElement { + /// `LayersPanel.tsx:202-325`. + fn render_layers_panel(&self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; div() - .text_size(px(11.)) - .font_weight(FontWeight::MEDIUM) - .text_color(self.theme.gray_10) - .child(label) + .flex() + .flex_col() + .h_full() + .w(px(LAYERS_PANEL_WIDTH)) + .flex_shrink_0() + .border_r_1() + .border_color(theme.gray_3) + .bg(if theme.is_dark() { + theme.gray_2 + } else { + theme.gray_1 + }) + .child( + div() + .flex() + .flex_row() + .items_center() + .justify_between() + .px(px(12.)) + .h(px(40.)) + .flex_shrink_0() + .border_b_1() + .border_color(theme.gray_3) + .child( + div() + .flex() + .flex_row() + .items_center() + .gap(px(8.)) + .text_size(px(14.)) + .font_weight(FontWeight::MEDIUM) + .text_color(theme.gray_12) + .child( + svg() + .path("icons/layers.svg") + .size(px(16.)) + .text_color(theme.gray_12), + ) + .child("Layers"), + ) + .child( + div() + .id("screenshot-layers-close") + .flex() + .items_center() + .justify_center() + .p(px(4.)) + .rounded(px(4.)) + .cursor_pointer() + .hover(|style| style.bg(theme.gray_3)) + .child( + svg() + .path("icons/x.svg") + .size(px(16.)) + .text_color(theme.gray_11), + ) + .on_click(cx.listener(|this, _, _window, cx| { + cx.stop_propagation(); + this.toggle_layers_panel(cx); + })), + ), + ) + .child( + div() + .id("screenshot-layers-list") + .flex_1() + .min_h_0() + .py(px(4.)) + .overflow_y_scroll() + .child(self.render_layer_rows(cx)), + ) + .child( + div() + .px(px(12.)) + .py(px(8.)) + .flex_shrink_0() + .border_t_1() + .border_color(theme.gray_3) + .text_size(px(10.)) + .text_color(theme.gray_9) + .child("Drag to reorder • Top = front"), + ) } - fn slider_row( - &mut self, - label: &'static str, + /// `AnnotationConfig.tsx:38-168` -- Phase 1 draws the bar and its Done + /// button; the per-type controls come with the engine. + fn render_annotation_config_bar(&self, cx: &mut Context) -> Option { + self.selected_annotation.as_ref()?; + let theme = self.theme; + Some( + div() + // The bar floats over the preview, and gpui hitboxes do not + // occlude by default: without this a press on a swatch would + // also land on the annotation underneath it. + .occlude() + .absolute() + .top(px(HEADER_HEIGHT)) + .left(px(if self.layers_panel_open { + LAYERS_PANEL_WIDTH + } else { + 0. + })) + .right_0() + .h(px(CONFIG_BAR_HEIGHT)) + .border_b_1() + .border_color(theme.gray_3) + .bg(if theme.is_dark() { + theme.gray_2 + } else { + theme.gray_1 + }) + .flex() + .flex_row() + .items_center() + .justify_center() + .gap(px(24.)) + .px(px(16.)) + .children(self.render_annotation_config_controls(cx)) + .child(divider(&theme, 20.)) + .child( + div() + .id("screenshot-annotation-done") + .cursor_pointer() + .text_size(px(12.)) + .font_weight(FontWeight::MEDIUM) + .text_color(theme.blue_11) + .hover(|style| style.text_color(theme.blue_9)) + .child("Done") + .on_click(cx.listener(|this, _, _window, cx| { + cx.stop_propagation(); + this.selected_annotation = None; + cx.notify(); + })), + ) + .into_any_element(), + ) + } + + // -- Popovers --------------------------------------------------------------- + + fn slider( + &self, slider: StyleSlider, + width: Option, cx: &mut Context, ) -> impl IntoElement { let theme = self.theme; let (min, max, _) = slider.range(); - let value = slider.value(&self.project); + let value = if slider == StyleSlider::Zoom { + self.zoom + } else { + slider.value(&self.project) + }; let fraction = ((value - min) / (max - min)).clamp(0., 1.); - let track = self.slider_tracks.entry(slider).or_default().clone(); + let track = self.track(slider); + + ui::Slider::new( + gpui::SharedString::from(format!("screenshot-slider-{}", slider_id(slider))), + fraction, + track, + ) + .when(width.is_none(), |this| this.flex()) + .when_some(width, |this, width| this.row_width(width)) + .track(px(4.), theme.gray_4.into()) + .fill(theme.blue_9.into()) + .thumb(px(14.), gpui::white(), Some(theme.gray_6.into())) + .on_drag_start( + cx.listener(move |this, event: &MouseDownEvent, window, cx| { + cx.stop_propagation(); + this.begin_slider(slider, event.position, window, cx); + }), + ) + } + /// The `text-xs font-medium text-gray-11` caption every popover slider + /// carries, with the live value on the right -- gpui's tooltip is + /// hover-driven only, so the readout is a label rather than a bubble + /// (the same substitution `ui::Tooltip` documents). + fn slider_row( + &self, + label: &'static str, + slider: StyleSlider, + suffix: &'static str, + cx: &mut Context, + ) -> impl IntoElement { + let theme = self.theme; + let value = slider.value(&self.project); div() .flex() .flex_col() - .gap(px(4.)) + .gap(px(8.)) .child( div() .flex() .flex_row() - .justify_between() .items_center() + .justify_between() .text_size(px(12.)) + .font_weight(FontWeight::MEDIUM) .text_color(theme.gray_11) .child(label) - .child(format!("{}", value.round() as i64)), + .child(format!("{}{suffix}", value.round() as i64)), ) + .child(self.slider(slider, None, cx)) + } + + /// `RgbInput` (`ColorPicker.tsx:47-102`): a `size-8 rounded-lg` swatch + /// beside a hex field. The swatch opens `` over there; + /// there is no native colour panel wired to this window yet, so it is inert + /// here (README deviation). + fn render_rgb_input(&self, target: HexTarget, value: cap_project::Color) -> impl IntoElement { + let theme = self.theme; + div() + .flex() + .flex_row() + .items_center() + .gap(px(12.)) .child( - ui::Slider::new( - gpui::SharedString::from(format!("style-slider-{label}")), - fraction, - track, - ) - .flex() - .track(px(4.), theme.gray_4.into()) - .fill(theme.blue_9.into()) - .thumb(px(14.), gpui::white(), Some(theme.gray_6.into())) - .on_drag_start(cx.listener( - move |this, event: &MouseDownEvent, window, cx| { - cx.stop_propagation(); - this.active_slider = Some(slider); - this.apply_slider(slider, event.position, window, cx); - cx.notify(); - }, - )), + div() + .size(px(32.)) + .rounded(px(8.)) + .border_1() + .border_color(theme.gray_4) + .bg(color_to_hsla(value)), ) + .children(self.hex_inputs.get(&target).map(|input| { + ui::TextInput::plain(&theme, target.id(), input) + .width(px(73.6)) + .padding_x(px(6.)) + .padding_y(px(6.)) + .height(px(30.)) + .radius(px(8.)) + .bg(Hsla::from(theme.gray_1)) + .border(Hsla::from(theme.gray_12)) + .text_size(px(13.)) + .text_color(Hsla::from(theme.gray_12)) + })) } + /// `KTabs.List` (`BackgroundSettingsPopover.tsx:296-309`). fn render_bg_tabs(&self, cx: &mut Context) -> impl IntoElement { let theme = self.theme; let current = self.bg_tab; - let mut row = div() + div() .flex() .flex_row() - .gap(px(4.)) - .p(px(2.)) - .rounded(px(8.)) - .bg(theme.gray_3); - for (tab, label) in BgTab::ALL { - let selected = tab == current; - row = row.child( + .items_center() + .gap(px(8.)) + .children(BgTab::ALL.map(|(tab, label)| { + let selected = tab == current; div() - .id(gpui::SharedString::from(format!("bg-tab-{label}"))) + .id(gpui::SharedString::from(format!( + "screenshot-bg-tab-{label}" + ))) .flex_1() .flex() .justify_center() - .py(px(4.)) - .rounded(px(6.)) - .text_size(px(11.)) - .text_color(if selected { - theme.gray_12 - } else { - theme.gray_10 + .py(px(10.)) + .px(px(8.)) + .rounded(px(10.)) + .border_1() + .cursor_pointer() + .text_size(px(12.)) + .when(selected, |this| { + this.bg(theme.gray_3) + .border_color(theme.gray_3) + .text_color(theme.gray_12) + }) + .when(!selected, |this| { + this.border_color(gpui::transparent_black()) + .text_color(theme.gray_11) + .hover(|style| style.border_color(theme.gray_7)) }) - .when(selected, |this| this.bg(theme.gray_1)) - .when(!selected, |this| this.hover(|style| style.bg(theme.gray_4))) .child(label) - .on_click(cx.listener(move |this, _, _window, cx| { + .on_click(cx.listener(move |this, _, window, cx| { cx.stop_propagation(); - this.bg_tab = tab; - if tab == BgTab::Wallpaper { - this.ensure_wallpapers(cx); - } - cx.notify(); - })), - ); + this.choose_bg_tab(tab, window, cx); + })) + })) + } + + /// `KTabs`' `onChange` (`BackgroundSettingsPopover.tsx:256-294`): a tab + /// switch installs a default source of that type unless the current source + /// already is one, and the three visible kinds also light the frame up. + fn choose_bg_tab(&mut self, tab: BgTab, window: &mut Window, cx: &mut Context) { + // `KTabs.onChange` does not fire for the tab already in force, so + // re-clicking it must not seed padding or push a history entry. + if self.bg_tab == tab { + return; + } + self.bg_tab = tab; + if tab == BgTab::Wallpaper { + self.ensure_wallpapers(cx); } - row + self.edit_project( + move |project| { + if BgTab::for_source(&project.background.source) != tab { + project.background.source = match tab { + BgTab::Wallpaper => BackgroundSource::Wallpaper { path: None }, + BgTab::Image => BackgroundSource::Image { path: None }, + BgTab::Color => BackgroundSource::Color { + value: DEFAULT_GRADIENT_FROM, + alpha: 255, + }, + BgTab::Gradient => BackgroundSource::Gradient { + from: DEFAULT_GRADIENT_FROM, + to: DEFAULT_GRADIENT_TO, + angle: 90, + noise_intensity: None, + noise_scale: None, + animated: None, + animation_speed: None, + }, + }; + } + if matches!(tab, BgTab::Wallpaper | BgTab::Image | BgTab::Gradient) { + Self::ensure_padding_for_background(project); + } + true + }, + window, + cx, + ); + self.ensure_background_image(cx); } - fn render_color_pane(&mut self, cx: &mut Context) -> gpui::Div { + fn render_wallpaper_tab(&self, cx: &mut Context) -> AnyElement { let theme = self.theme; let current = match &self.project.background.source { - BackgroundSource::Color { value, alpha } => Some((*value, *alpha)), + BackgroundSource::Wallpaper { path: Some(path) } => { + editor_sidebar::wallpaper_id_for_path(path) + } _ => None, }; - let mut grid = div().flex().flex_row().flex_wrap().gap(px(6.)); - for (index, hex) in BACKGROUND_COLORS.iter().enumerate() { - let Some(rgba) = hex_to_rgb(hex) else { - continue; - }; - let value = [rgba[0] as u16, rgba[1] as u16, rgba[2] as u16]; - let alpha = rgba[3]; - let selected = current == Some((value, alpha)); - let mut fill: Hsla = color_to_hsla(value); - fill.a = alpha as f32 / 255.; - grid = grid.child( + let theme_name = BACKGROUND_THEMES[self.wallpaper_theme].0; + + div() + .flex() + .flex_col() + .gap(px(12.)) + .child( div() - .id(("bg-color", index)) - .size(px(24.)) - .rounded(px(6.)) - .border_2() - .border_color(if selected { theme.blue_9 } else { theme.gray_4 }) - .bg(fill) - .hover(|style| style.border_color(theme.gray_8)) - .on_click(cx.listener(move |this, _, window, cx| { - cx.stop_propagation(); - this.edit_project( - move |project| { - project.background.source = - BackgroundSource::Color { value, alpha }; - true - }, - window, - cx, - ); - })), - ); - } - grid + .flex() + .flex_row() + .flex_wrap() + .gap(px(8.)) + .text_size(px(12.)) + .children( + BACKGROUND_THEMES + .iter() + .enumerate() + .map(|(index, (_, label))| { + let selected = index == self.wallpaper_theme; + div() + .id(("screenshot-wallpaper-theme", index)) + .flex() + .items_center() + .justify_center() + .px(px(16.)) + .py(px(8.)) + .rounded(px(8.)) + .border_1() + .cursor_pointer() + .when(selected, |this| { + this.bg(theme.gray_3) + .border_color(theme.gray_3) + .text_color(theme.gray_12) + }) + .when(!selected, |this| { + this.border_color(gpui::transparent_black()) + .text_color(theme.gray_11) + .hover(|style| style.border_color(theme.gray_7)) + }) + .child(*label) + .on_click(cx.listener(move |this, _, _window, cx| { + cx.stop_propagation(); + this.wallpaper_theme = index; + this.ensure_wallpapers(cx); + cx.notify(); + })) + }), + ), + ) + .child( + // `grid grid-cols-7 gap-2` over the first 21 tiles + // (`BackgroundSettingsPopover.tsx:356-358`). + div().flex().flex_row().flex_wrap().gap(px(8.)).children( + editor_sidebar::wallpapers_for_theme(theme_name) + .into_iter() + .take(21) + .enumerate() + .map(|(index, id)| { + let selected = current == Some(id); + div() + .id(("screenshot-wallpaper", index)) + .size(px(44.)) + .rounded(px(8.)) + .overflow_hidden() + .cursor_pointer() + .bg(theme.gray_3) + .when(selected, |this| { + this.border_2().border_color(theme.gray_500_legacy) + }) + .when(!selected, |this| { + this.border_2() + .border_color(gpui::transparent_black()) + .hover(|style| style.border_color(theme.gray_8)) + }) + .when_some(self.wallpapers.get(id).cloned(), |this, image| { + this.child( + img(image).size_full().object_fit(gpui::ObjectFit::Cover), + ) + }) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + let Some(path) = editor_sidebar::wallpaper_path(id) else { + tracing::error!(id, "wallpaper asset not found"); + return; + }; + let path = path.to_string_lossy().into_owned(); + this.edit_project( + move |project| { + project.background.source = + BackgroundSource::Wallpaper { path: Some(path) }; + Self::ensure_padding_for_background(project); + true + }, + window, + cx, + ); + })) + }), + ), + ) + .into_any_element() } - fn render_gradient_pane(&mut self, cx: &mut Context) -> gpui::Div { + fn render_image_tab(&self, cx: &mut Context) -> AnyElement { let theme = self.theme; - let current = match &self.project.background.source { - BackgroundSource::Gradient { from, to, .. } => Some((*from, *to)), + let path = match &self.project.background.source { + BackgroundSource::Image { path } => path.clone(), _ => None, }; - let angle = match &self.project.background.source { - BackgroundSource::Gradient { angle, .. } => *angle, - _ => 90, + + let Some(_) = path else { + return div() + .id("screenshot-bg-image-pick") + .flex() + .flex_col() + .items_center() + .justify_center() + .gap(px(8.)) + .w_full() + .p(px(24.)) + .rounded(px(8.)) + .border_1() + .border_dashed() + .border_color(theme.gray_5) + .bg(theme.gray_2) + .cursor_pointer() + .hover(|style| style.bg(theme.gray_3)) + .child( + svg() + .path("icons/image.svg") + .size(px(24.)) + .text_color(theme.gray_11), + ) + .child( + div() + .text_size(px(13.)) + .text_color(theme.gray_12) + .child("Click to select or drag and drop image"), + ) + .on_click(cx.listener(|this, _, window, cx| { + cx.stop_propagation(); + this.pick_background_image(window, cx); + })) + .into_any_element(); }; - let mut grid = div().flex().flex_row().flex_wrap().gap(px(6.)); - for (index, (from, to)) in GRADIENT_PRESETS.iter().enumerate() { - let (from, to) = (*from, *to); - let selected = current == Some((from, to)); - grid = grid.child( + + div() + .relative() + .w_full() + .h(px(192.)) + .rounded(px(6.)) + .overflow_hidden() + .border_1() + .border_color(theme.gray_3) + .bg(theme.gray_2) + .when_some( + self.bg_image.as_ref().map(|(_, image)| image.clone()), + |this, image| this.child(img(image).size_full().object_fit(gpui::ObjectFit::Cover)), + ) + .child( div() - .id(("bg-gradient", index)) - .size(px(24.)) - .rounded(px(6.)) - .border_2() - .border_color(if selected { theme.blue_9 } else { theme.gray_4 }) - .bg(linear_gradient( - 135., - linear_color_stop(color_to_hsla(from), 0.), - linear_color_stop(color_to_hsla(to), 1.), - )) - .hover(|style| style.border_color(theme.gray_8)) - .on_click(cx.listener(move |this, _, window, cx| { + .id("screenshot-bg-image-clear") + .absolute() + .top(px(8.)) + .right(px(8.)) + .flex() + .items_center() + .justify_center() + .size(px(32.)) + .rounded_full() + .cursor_pointer() + .bg(Theme::with_alpha(gpui::rgb(0x000000), 0.5)) + .hover(|style| style.bg(Theme::with_alpha(gpui::rgb(0x000000), 0.7))) + .child( + svg() + .path("icons/circle-x.svg") + .size(px(16.)) + .text_color(gpui::white()), + ) + .on_click(cx.listener(|this, _, window, cx| { cx.stop_propagation(); this.edit_project( - move |project| { - project.background.source = BackgroundSource::Gradient { - from, - to, - angle, - noise_intensity: None, - noise_scale: None, - animated: None, - animation_speed: None, - }; + |project| { + project.background.source = BackgroundSource::Image { path: None }; true }, window, cx, ); + this.ensure_background_image(cx); })), - ); - } - grid + ) + .into_any_element() } - fn render_wallpaper_pane(&mut self, cx: &mut Context) -> gpui::Div { + fn render_color_tab(&self, cx: &mut Context) -> AnyElement { let theme = self.theme; - // The config stores the wallpaper's absolute path (the render layer - // opens it as a file); selection matches by catalogue id the way the - // Tauri sidebar's `path.includes(w.id)` does. let current = match &self.project.background.source { - BackgroundSource::Wallpaper { path: Some(path) } => { - editor_sidebar::wallpaper_id_for_path(path) - } + BackgroundSource::Color { value, alpha } => Some((*value, *alpha)), _ => None, }; - let mut theme_row = div().flex().flex_row().flex_wrap().gap(px(4.)); - for (index, (_, label)) in BACKGROUND_THEMES.iter().enumerate() { - let selected = index == self.wallpaper_theme; - theme_row = theme_row.child( + div() + .flex() + .flex_col() + .gap(px(12.)) + .child(self.render_rgb_input( + HexTarget::BackgroundColor, + current.map_or(DEFAULT_GRADIENT_FROM, |(value, _)| value), + )) + .child( + div().flex().flex_row().flex_wrap().gap(px(8.)).children( + BACKGROUND_COLORS + .iter() + .enumerate() + .filter_map(|(index, hex)| { + let rgba = hex_to_rgb(hex)?; + let value = [rgba[0] as u16, rgba[1] as u16, rgba[2] as u16]; + let alpha = rgba[3]; + let selected = current == Some((value, alpha)); + Some( + div() + .id(("screenshot-bg-color", index)) + .size(px(32.)) + .rounded(px(8.)) + .overflow_hidden() + .cursor_pointer() + .when(selected, |this| { + this.border_2().border_color(theme.gray_500_legacy) + }) + .when(!selected, |this| { + this.border_2().border_color(gpui::transparent_black()) + }) + // The transparent swatch reads as a + // checkerboard, not as nothing. + .when(alpha == 0, |this| this.children(mini_checker())) + .when(alpha != 0, |this| this.bg(color_to_hsla(value))) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + this.edit_project( + move |project| { + project.background.source = + BackgroundSource::Color { value, alpha }; + true + }, + window, + cx, + ); + })), + ) + }), + ), + ) + .into_any_element() + } + + fn render_gradient_tab(&self, cx: &mut Context) -> AnyElement { + let theme = self.theme; + let (from, to, angle) = match &self.project.background.source { + BackgroundSource::Gradient { + from, to, angle, .. + } => (*from, *to, *angle), + _ => (DEFAULT_GRADIENT_FROM, DEFAULT_GRADIENT_TO, 90), + }; + + div() + .flex() + .flex_col() + .gap(px(12.)) + .child( div() - .id(("wallpaper-theme", index)) - .px(px(8.)) - .py(px(3.)) - .rounded(px(6.)) - .text_size(px(11.)) - .text_color(if selected { - theme.gray_12 - } else { - theme.gray_10 - }) - .bg(if selected { theme.gray_4 } else { theme.gray_2 }) - .hover(|style| style.bg(theme.gray_4)) - .child(*label) - .on_click(cx.listener(move |this, _, _window, cx| { - cx.stop_propagation(); - this.wallpaper_theme = index; - this.ensure_wallpapers(cx); - cx.notify(); - })), - ); - } + .flex() + .flex_row() + .gap(px(20.)) + .child(self.render_rgb_input(HexTarget::GradientFrom, from)) + .child(self.render_rgb_input(HexTarget::GradientTo, to)), + ) + .child( + div().flex().flex_row().flex_wrap().gap(px(8.)).children( + GRADIENT_PRESETS + .iter() + .enumerate() + .map(|(index, (preset_from, preset_to))| { + let (preset_from, preset_to) = (*preset_from, *preset_to); + let selected = (from, to) == (preset_from, preset_to); + div() + .id(("screenshot-bg-gradient", index)) + .size(px(32.)) + .rounded(px(8.)) + .cursor_pointer() + .when(selected, |this| { + this.border_2().border_color(theme.gray_500_legacy) + }) + .when(!selected, |this| { + this.border_2().border_color(gpui::transparent_black()) + }) + .bg(linear_gradient( + f32::from(angle), + linear_color_stop(color_to_hsla(preset_from), 0.), + linear_color_stop(color_to_hsla(preset_to), 1.), + )) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + this.edit_project( + move |project| { + project.background.source = + BackgroundSource::Gradient { + from: preset_from, + to: preset_to, + angle, + noise_intensity: None, + noise_scale: None, + animated: None, + animation_speed: None, + }; + Self::ensure_padding_for_background(project); + true + }, + window, + cx, + ); + })) + }), + ), + ) + .into_any_element() + } - let theme_name = BACKGROUND_THEMES[self.wallpaper_theme].0; - let mut grid = div().flex().flex_row().flex_wrap().gap(px(6.)); - for (index, id) in editor_sidebar::wallpapers_for_theme(theme_name) - .into_iter() - .enumerate() - { - let selected = current == Some(id); - let tile = div() - .id(("wallpaper-tile", index)) - .size(px(56.)) - .rounded(px(8.)) - .border_2() - .border_color(if selected { theme.blue_9 } else { theme.gray_4 }) - .overflow_hidden() - .bg(theme.gray_3) - .hover(|style| style.border_color(theme.gray_8)) - .when_some(self.wallpapers.get(id).cloned(), |this, image| { - this.child(img(image).size_full().object_fit(gpui::ObjectFit::Cover)) - }) - .on_click(cx.listener(move |this, _, window, cx| { - cx.stop_propagation(); - let Some(path) = editor_sidebar::wallpaper_path(id) else { - tracing::error!(id, "wallpaper asset not found"); - return; - }; - let path = path.to_string_lossy().into_owned(); - this.edit_project( - move |project| { - project.background.source = - BackgroundSource::Wallpaper { path: Some(path) }; - true - }, - window, - cx, - ); - })); - grid = grid.child(tile); - } + /// `BackgroundSettingsPopover.tsx:245-570`. + fn render_background_popover(&self, cx: &mut Context) -> AnyElement { + let theme = self.theme; + let tab_content = match self.bg_tab { + BgTab::Wallpaper => self.render_wallpaper_tab(cx), + BgTab::Image => self.render_image_tab(cx), + BgTab::Color => self.render_color_tab(cx), + BgTab::Gradient => self.render_gradient_tab(cx), + }; + + div() + .id("screenshot-background-scroll") + .max_h(px(600.)) + .overflow_y_scroll() + .p(px(16.)) + .flex() + .flex_col() + .gap(px(24.)) + .child( + ui::Field::plain(&theme, "Background Image") + .icon("icons/image.svg") + .icon_size(px(16.)) + .child(self.render_bg_tabs(cx)) + .child( + div() + .my(px(20.)) + .w_full() + .border_t_1() + .border_dashed() + .border_color(theme.gray_5), + ) + .child(tab_content), + ) + .child( + ui::Field::plain(&theme, "Background Blur") + .icon("icons/bg-blur.svg") + .icon_size(px(16.)) + .child(self.slider_row("Blur", StyleSlider::Blur, "%", cx)), + ) + .into_any_element() + } + + fn render_padding_popover(&self, cx: &mut Context) -> AnyElement { + div() + .p(px(16.)) + .child(self.slider_row("Padding", StyleSlider::Padding, "px", cx)) + .into_any_element() + } + + fn render_rounding_popover(&self, cx: &mut Context) -> AnyElement { + let theme = self.theme; + div() + .p(px(16.)) + .flex() + .flex_col() + .gap(px(16.)) + .child(self.slider_row("Rounding", StyleSlider::Rounding, "px", cx)) + .child( + div() + .flex() + .flex_col() + .gap(px(6.)) + .child( + div() + .text_size(px(10.4)) + .text_color(theme.gray_11) + .child("CORNER STYLE"), + ) + .child( + self.anchored( + Anchor::CornerStyle, + div() + .id("screenshot-corner-style") + .flex() + .flex_row() + .items_center() + .gap(px(8.)) + .px(px(8.)) + .w_full() + .h(px(32.)) + .rounded(px(8.)) + .cursor_pointer() + .bg(theme.gray_3) + .child( + div() + .flex_1() + .min_w_0() + .truncate() + .text_size(px(14.)) + .text_color(theme.gray_500_legacy) + .child(self.corner_style_label()), + ) + .child( + svg() + .path("icons/chevron-down.svg") + .size(px(16.)) + .flex_shrink_0() + .text_color(theme.gray_500_legacy), + ) + .on_click(cx.listener(|this, _, window, cx| { + cx.stop_propagation(); + this.toggle_menu( + MenuKind::CornerStyle, + Anchor::CornerStyle, + window, + cx, + ); + })), + ), + ), + ) + .into_any_element() + } + + fn render_shadow_popover(&self, cx: &mut Context) -> AnyElement { + let theme = self.theme; + let open = self.shadow_advanced.is_open(); + let body = div() + .mt(px(16.)) + .flex() + .flex_col() + .gap(px(24.)) + .font_weight(FontWeight::MEDIUM) + .child(ui::Field::plain(&theme, "Size").child(self.slider( + StyleSlider::ShadowSize, + None, + cx, + ))) + .child(ui::Field::plain(&theme, "Opacity").child(self.slider( + StyleSlider::ShadowOpacity, + None, + cx, + ))) + .child(ui::Field::plain(&theme, "Blur").child(self.slider( + StyleSlider::ShadowBlur, + None, + cx, + ))) + .into_any_element(); + + div() + .p(px(16.)) + .flex() + .flex_col() + .gap(px(16.)) + .child(self.slider_row("Shadow", StyleSlider::Shadow, "%", cx)) + .child( + div() + .w_full() + .child( + div() + .id("screenshot-shadow-advanced") + .flex() + .flex_row() + .items_center() + .gap(px(4.)) + .w_full() + .cursor_pointer() + .text_size(px(14.)) + .font_weight(FontWeight::MEDIUM) + .text_color(theme.gray_12) + .hover(|style| style.text_color(theme.gray_10)) + .child("Advanced shadow settings") + .child( + svg() + .path("icons/chevron-down.svg") + .size(px(20.)) + .text_color(theme.gray_12) + .when(open, |this| { + this.with_transformation(gpui::Transformation::rotate( + gpui::radians(std::f32::consts::PI), + )) + }), + ) + .on_click(cx.listener(|this, _, window, cx| { + cx.stop_propagation(); + this.shadow_advanced.toggle(); + this.animate_collapsibles(window, cx); + })), + ) + .child(editor_sidebar::collapsible(&self.shadow_advanced, body)), + ) + .into_any_element() + } + + fn render_border_popover(&self, cx: &mut Context) -> AnyElement { + let theme = self.theme; + let enabled = self + .project + .background + .border + .as_ref() + .is_some_and(|border| border.enabled); + let color = self + .project + .background + .border + .as_ref() + .map_or(BORDER_FALLBACK.color, |border| border.color); + + let body = div() + .flex() + .flex_col() + .gap(px(16.)) + .child( + ui::Field::plain(&theme, "Width") + .icon("icons/enlarge.svg") + .icon_size(px(16.)) + .child(self.slider(StyleSlider::BorderWidth, None, cx)), + ) + .child( + ui::Field::plain(&theme, "Color") + .icon("icons/image.svg") + .icon_size(px(16.)) + .child(self.render_rgb_input(HexTarget::BorderColor, color)), + ) + .child( + ui::Field::plain(&theme, "Opacity") + .icon("icons/shadow.svg") + .icon_size(px(16.)) + .child(self.slider(StyleSlider::BorderOpacity, None, cx)), + ) + .into_any_element(); + + div() + .p(px(16.)) + .flex() + .flex_col() + .gap(px(16.)) + .child( + div() + .flex() + .flex_row() + .justify_between() + .items_center() + .child( + div() + .text_size(px(12.)) + .font_weight(FontWeight::MEDIUM) + .text_color(theme.gray_11) + .child("Border"), + ) + .child( + ui::Toggle::plain(&theme, "screenshot-border-toggle", enabled).on_click( + cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + this.border_body.set_open(!enabled); + this.animate_collapsibles(window, cx); + this.edit_project( + move |project| { + let border = project + .background + .border + .get_or_insert(BORDER_FALLBACK); + border.enabled = !enabled; + true + }, + window, + cx, + ); + }), + ), + ), + ) + .child(editor_sidebar::collapsible(&self.border_body, body)) + .into_any_element() + } + + /// The popover shell: a full-window click-away backdrop and the panel + /// itself, anchored under its trigger and clamped inside the window. + fn render_popover(&self, window: &Window, cx: &mut Context) -> Option { + let popover = self.active_popover?; + let theme = self.theme; + let width = popover.width(); + let viewport = window.viewport_size(); + let anchor = self.anchor(popover.anchor()).get(); + let left = anchor + .map(|bounds| f32::from(bounds.origin.x)) + .unwrap_or(16.) + .clamp(8., (f32::from(viewport.width) - width - 8.).max(8.)); + let top = anchor + .map(|bounds| f32::from(bounds.origin.y + bounds.size.height) + 8.) + .unwrap_or(HEADER_HEIGHT + 8.); + + let body = match popover { + Popover::Background => self.render_background_popover(cx), + Popover::Padding => self.render_padding_popover(cx), + Popover::Rounding => self.render_rounding_popover(cx), + Popover::Shadow => self.render_shadow_popover(cx), + Popover::Border => self.render_border_popover(cx), + }; + + Some( + div() + .absolute() + .top_0() + .left_0() + .size_full() + .child( + div() + .id("screenshot-popover-backdrop") + .absolute() + .top_0() + .left_0() + .size_full() + .on_click(cx.listener(|this, _, _window, cx| { + this.active_popover = None; + cx.notify(); + })), + ) + .child( + div() + .absolute() + .left(px(left)) + .top(px(top)) + .w(px(width)) + .overflow_hidden() + .rounded(px(12.)) + .border_1() + .border_color(theme.gray_3) + .bg(theme.gray_1) + .shadow(vec![gpui::BoxShadow { + color: Theme::with_alpha(gpui::rgb(0x000000), 0.18), + offset: gpui::point(px(0.), px(10.)), + blur_radius: px(30.), + spread_radius: px(0.), + inset: false, + }]) + .child(body), + ) + .into_any_element(), + ) + } - div() - .flex() - .flex_col() - .gap(px(8.)) - .child(theme_row) - .child(grid) + fn render_menu(&self, cx: &mut Context) -> Option { + let (kind, state) = self.menu.as_ref()?; + let items = self.menu_items(*kind); + Some( + ui::Menu::plain(&self.theme, "screenshot-menu", items, state) + .min_width(px(200.)) + .on_select(cx.listener(|this, index: &usize, window, cx| { + this.commit_menu(*index, window, cx); + })) + .on_dismiss(cx.listener(|this, _, _window, cx| { + this.menu = None; + cx.notify(); + })) + .into_any_element(), + ) } - fn render_image_pane(&mut self, cx: &mut Context) -> gpui::Div { + // -- Preview ---------------------------------------------------------------- + + /// `Preview.tsx:467-794`. + fn render_preview(&self, cx: &mut Context) -> impl IntoElement { let theme = self.theme; - let current = match &self.project.background.source { - BackgroundSource::Image { path } => path.clone(), - _ => None, - }; + let geometry = self.preview_geometry(); + let frame = self.frame.clone(); + let checker = self.checker.clone(); + let area_cell = self.preview_area.clone(); + let viewport_cell = self.viewport.clone(); + let entity = cx.entity().downgrade(); + let zoom = self.zoom; + let panning = self.panning.is_some(); + let image_rect = annotations::image_rect( + (self.frame_size.0 as f64, self.frame_size.1 as f64), + self.image_size + .map(|(width, height)| (width as f64, height as f64)), + self.project.background.padding, + self.project.background.crop.as_ref(), + self.project.aspect_ratio.as_ref(), + ); + div() .flex() .flex_col() - .gap(px(6.)) + .flex_1() + .min_w_0() + .overflow_hidden() + .bg(if theme.is_dark() { + theme.gray_2 + } else { + theme.gray_1 + }) .child( div() - .id("bg-image-pick") + .id("screenshot-preview-area") + .flex_1() + .min_h_0() + .relative() .flex() + .items_center() .justify_center() - .py(px(8.)) - .rounded(px(8.)) - .border_1() - .border_color(theme.gray_4) - .bg(theme.gray_2) - .hover(|style| style.bg(theme.gray_3)) - .text_size(px(12.)) - .text_color(theme.gray_12) - .child("Choose Image...") - .on_click(cx.listener(|this, _, window, cx| { - cx.stop_propagation(); - this.pick_background_image(window, cx); - })), + .overflow_hidden() + .when(panning, |this| this.cursor(gpui::CursorStyle::ClosedHand)) + .when(!panning, |this| this.cursor(gpui::CursorStyle::OpenHand)) + // The checkerboard, and the area's own measurement. Both + // come off one canvas: prepaint writes the rect the layout + // maths reads next frame, paint tiles the cached tile. + .child( + canvas( + move |bounds, _window, cx| { + if area_cell.get() != Some(bounds) { + area_cell.set(Some(bounds)); + if let Some(entity) = entity.upgrade() { + cx.defer(move |cx| { + entity.update(cx, |_, cx| cx.notify()); + }); + } + } + bounds + }, + move |_, bounds, window, _cx| { + let Some(tile) = checker.clone() else { + return; + }; + paint_checkerboard(window, bounds, tile); + }, + ) + .absolute() + .size_full(), + ) + .on_mouse_down( + MouseButton::Left, + cx.listener(|this, event: &MouseDownEvent, window, cx| { + this.preview_mouse_down(event, window, cx); + }), + ) + .on_mouse_down( + MouseButton::Middle, + cx.listener(|this, event: &MouseDownEvent, window, cx| { + this.preview_mouse_down(event, window, cx); + }), + ) + .on_scroll_wheel(cx.listener( + |this, event: &gpui::ScrollWheelEvent, window, cx| { + this.preview_wheel(event, window, cx); + }, + )) + .on_pinch(cx.listener(|this, event: &gpui::PinchEvent, window, cx| { + this.preview_pinch(event, window, cx); + })) + .children(match (&self.error, frame, geometry) { + (Some(message), _, _) => Some( + div() + .text_size(px(13.)) + .text_color(theme.gray_11) + .child(message.clone()) + .into_any_element(), + ), + (None, Some(frame), Some(geometry)) => Some( + // The `size`d viewport, centred, with the content + // wrapper placed inside it (`Preview.tsx:700-788`). + div() + .relative() + .w(px(geometry.size.0)) + .h(px(geometry.size.1)) + .child( + canvas( + move |bounds, _window, _cx| viewport_cell.set(Some(bounds)), + |_, _, _, _| {}, + ) + .absolute() + .size_full(), + ) + .child( + div() + .absolute() + .left(px(geometry.content.0)) + .top(px(geometry.content.1)) + .w(px(geometry.scaled.0)) + .h(px(geometry.scaled.1)) + .overflow_hidden() + .rounded(px(4.)) + // `imageShadow()` -- dropped past 100%, + // where the picture is bigger than its + // viewport and the shadow would smear. + .when(zoom <= 1., |this| { + this.shadow(vec![ + gpui::BoxShadow { + color: Theme::with_alpha( + gpui::rgb(0x000000), + 0.15, + ), + offset: gpui::point(px(0.), px(4.)), + blur_radius: px(20.), + spread_radius: px(0.), + inset: false, + }, + gpui::BoxShadow { + color: Theme::with_alpha( + gpui::rgb(0x000000), + 0.10, + ), + offset: gpui::point(px(0.), px(2.)), + blur_radius: px(8.), + spread_radius: px(0.), + inset: false, + }, + ]) + }) + .child(img(frame).size_full()) + .children(self.render_annotation_layer( + geometry.scaled, + image_rect, + cx, + )), + ) + .into_any_element(), + ), + // `fallback={
    Loading + // preview...
    }` (`Preview.tsx:503`). + (None, _, _) => Some( + div() + .text_size(px(13.)) + .text_color(theme.gray_11) + .child("Loading preview...") + .into_any_element(), + ), + }) + .child(self.render_zoom_hud(cx)), ) - .when_some(current, |this, path| { - this.child( - div() - .text_size(px(11.)) - .text_color(theme.gray_10) - .overflow_hidden() - .child(path), - ) - }) } - fn render_corner_style(&mut self, cx: &mut Context) -> impl IntoElement { + /// `Preview.tsx:476-500` -- `absolute left-4 bottom-4`. + fn render_zoom_hud(&self, cx: &mut Context) -> impl IntoElement { let theme = self.theme; - let squircle = matches!(self.project.background.rounding_type, CornerStyle::Squircle); - let mut row = div() + div() + .id("screenshot-zoom-hud") + .absolute() + .left(px(16.)) + .bottom(px(16.)) + // The pan surface is a *sibling* of the HUD over there + // (`absolute inset-0 z-0`, `Preview.tsx:693-699`), so a press on + // the HUD never starts a drag. Here the pan listener is on the + // area itself, so the HUD has to stop the press reaching it. + .on_mouse_down(MouseButton::Left, |_, _window, cx| cx.stop_propagation()) .flex() .flex_row() - .gap(px(4.)) - .p(px(2.)) + .items_center() + .gap(px(8.)) + .p(px(4.)) .rounded(px(8.)) - .bg(theme.gray_3); - for (label, style, selected) in [ - ("Squircle", CornerStyle::Squircle, squircle), - ("Rounded", CornerStyle::Rounded, !squircle), - ] { - row = row.child( - div() - .id(gpui::SharedString::from(format!("corner-style-{label}"))) - .flex_1() - .flex() - .justify_center() - .py(px(4.)) - .rounded(px(6.)) - .text_size(px(11.)) - .text_color(if selected { - theme.gray_12 - } else { - theme.gray_10 - }) - .when(selected, |this| this.bg(theme.gray_1)) - .when(!selected, |this| this.hover(|style| style.bg(theme.gray_4))) - .child(label) - .on_click(cx.listener(move |this, _, window, cx| { + .border_1() + .border_color(theme.gray_4) + .bg(if theme.is_dark() { + theme.gray_3 + } else { + theme.gray_1 + }) + .shadow(vec![gpui::BoxShadow { + color: Theme::with_alpha(gpui::rgb(0x000000), 0.05), + offset: gpui::point(px(0.), px(1.)), + blur_radius: px(2.), + spread_radius: px(0.), + inset: false, + }]) + .child(kbd_tooltip( + &theme, + "Zoom Out", + &["meta", "-"], + ui::EditorButton::plain(&theme, "screenshot-zoom-out") + .left_icon("icons/zoom-out.svg") + .icon_size(px(16.)) + .on_click(cx.listener(|this, _, _window, cx| { cx.stop_propagation(); - this.edit_project( - move |project| { - if project.background.rounding_type == style { - return false; - } - project.background.rounding_type = style; - true - }, - window, - cx, - ); + this.nudge_zoom(-0.1, cx); })), - ); - } - row + )) + .child(self.slider(StyleSlider::Zoom, Some(px(80.)), cx)) + .child(kbd_tooltip( + &theme, + "Zoom In", + &["meta", "+"], + ui::EditorButton::plain(&theme, "screenshot-zoom-in") + .left_icon("icons/zoom-in.svg") + .icon_size(px(16.)) + .on_click(cx.listener(|this, _, _window, cx| { + cx.stop_propagation(); + this.nudge_zoom(0.1, cx); + })), + )) } - fn render_border_section(&mut self, cx: &mut Context) -> gpui::Div { - let theme = self.theme; - let enabled = self - .project - .background - .border - .as_ref() - .is_some_and(|border| border.enabled); + // -- Toasts ----------------------------------------------------------------- - let mut section = div().flex().flex_col().gap(px(10.)).child( + fn render_toasts(&self) -> Option { + if self.toasts.is_empty() { + return None; + } + let theme = self.theme; + Some( div() + .absolute() + .right(px(16.)) + .bottom(px(16.)) .flex() - .flex_row() - .justify_between() - .items_center() - .child(self.section_label("Border")) - .child( - ui::Toggle::plain(&theme, "border-toggle", enabled).on_click(cx.listener( - move |this, _, window, cx| { - cx.stop_propagation(); - this.edit_project( - move |project| { - let border = - project.background.border.get_or_insert(BORDER_FALLBACK); - border.enabled = !enabled; - true - }, - window, - cx, - ); - }, - )), - ), - ); - if enabled { - section = section - .child(self.slider_row("Width", StyleSlider::BorderWidth, cx)) - .child(self.slider_row("Opacity", StyleSlider::BorderOpacity, cx)); - } - section + .flex_col() + .items_end() + .gap(px(8.)) + .children(self.toasts.iter().map(|toast| { + div() + .flex() + .flex_row() + .items_center() + .gap(px(8.)) + .px(px(12.)) + .py(px(10.)) + .rounded(px(15.)) + .border_1() + .border_color(theme.gray_200_legacy) + .bg(theme.gray_1) + .shadow(vec![gpui::BoxShadow { + color: Theme::with_alpha(gpui::rgb(0x000000), 0.12), + offset: gpui::point(px(0.), px(4.)), + blur_radius: px(14.), + spread_radius: px(0.), + inset: false, + }]) + .text_size(px(16.)) + .text_color(match toast.kind { + ToastKind::Loading => theme.gray_11, + _ => theme.gray_12, + }) + .child(match toast.kind { + ToastKind::Success => svg() + .path("icons/check.svg") + .size(px(16.)) + .flex_shrink_0() + .text_color(gpui::rgb(0x22c55e)) + .into_any_element(), + ToastKind::Error => svg() + .path("icons/x.svg") + .size(px(16.)) + .flex_shrink_0() + .text_color(theme.red_11) + .into_any_element(), + ToastKind::Loading => div() + .size(px(10.)) + .flex_shrink_0() + .rounded_full() + .bg(theme.gray_9) + .into_any_element(), + }) + .child(toast.message.clone()) + })) + .into_any_element(), + ) } - fn render_sidebar(&mut self, cx: &mut Context) -> impl IntoElement { + /// `ScreenshotEditorSkeleton` (`screenshot-editor-skeleton.tsx`): the + /// header's three clusters as `rounded-lg` placeholders, and the Cap logo + /// spinning over a flat preview. + fn render_skeleton(&self) -> impl IntoElement { let theme = self.theme; - let bg_pane = match self.bg_tab { - BgTab::Color => self.render_color_pane(cx), - BgTab::Gradient => self.render_gradient_pane(cx), - BgTab::Wallpaper => self.render_wallpaper_pane(cx), - BgTab::Image => self.render_image_pane(cx), + let block = move |width: f32| { + div() + .h(px(36.)) + .w(px(width)) + .rounded(px(8.)) + .bg(if theme.is_dark() { + theme.gray_4 + } else { + theme.gray_3 + }) }; div() - .id("screenshot-style-panel") - .w(px(280.)) - .flex_shrink_0() - .m(px(14.)) - .mt(px(0.)) - .ml(px(0.)) - .p(px(14.)) - .rounded(px(12.)) - .border_1() - .border_color(theme.gray_4) - .bg(if theme.is_dark() { - theme.gray_2 - } else { - theme.gray_1 - }) - .overflow_y_scroll() + .size_full() .flex() .flex_col() - .gap(px(14.)) - .child(self.section_label("Background")) - .child(self.render_bg_tabs(cx)) - .child(bg_pane) - .child(self.slider_row("Blur", StyleSlider::Blur, cx)) - .child(self.slider_row("Padding", StyleSlider::Padding, cx)) - .child(self.slider_row("Rounding", StyleSlider::Rounding, cx)) - .child(self.render_corner_style(cx)) - .child(self.slider_row("Shadow", StyleSlider::Shadow, cx)) - .child(self.render_border_section(cx)) - } - - fn render_preview(&self, cx: &mut Context) -> impl IntoElement { - let theme = self.theme; - let frame = self.frame.clone(); - let frame_size = self.frame_size; - - div() - .flex_1() - .min_h_0() - .m(px(14.)) - .mt(px(0.)) - .rounded(px(12.)) - .border_1() - .border_color(theme.gray_4) - .overflow_hidden() - .bg(if theme.is_dark() { - theme.gray_2 - } else { - theme.gray_3 - }) - .child(match (&self.error, frame) { - (Some(message), _) => div() - .size_full() + .child( + div() + .relative() .flex() + .flex_row() .items_center() - .justify_center() - .text_size(px(13.)) - .text_color(theme.gray_11) - .child(message.clone()) - .into_any_element(), - (None, Some(frame)) => gpui::canvas( - |bounds, _window, _cx| bounds, - move |_, bounds, window, _cx| { - let container_width: f32 = bounds.size.width.into(); - let container_height: f32 = bounds.size.height.into(); - let (width, height) = crate::editor_window::letterbox( - (container_width, container_height), - frame_size, - ); - let fitted = gpui::Bounds { - origin: gpui::point( - bounds.origin.x + px((container_width - width) / 2.), - bounds.origin.y + px((container_height - height) / 2.), - ), - size: gpui::size(px(width), px(height)), - }; - let _ = - window.paint_image(fitted, gpui::Corners::default(), frame, 0, false); - }, - ) - .size_full() - .into_any_element(), - (None, None) => div() - .size_full() + .justify_between() + .w_full() + .h(px(HEADER_HEIGHT)) + .px(px(16.)) + .flex_shrink_0() + .border_b_1() + .border_color(theme.gray_3) + .bg(if theme.is_dark() { + theme.gray_2 + } else { + theme.gray_1 + }) + .child(div().w(px(56.))) + .child( + div() + .absolute() + .top_0() + .left_0() + .size_full() + .flex() + .flex_row() + .items_center() + .justify_center() + .gap(px(8.)) + .child(block(96.)) + .child(block(36.)) + .child(divider(&theme, 24.)) + .children((0..5).map(|_| block(36.))) + .child(divider(&theme, 24.)) + .children((0..5).map(|_| block(36.))), + ) + .child( + div() + .flex() + .flex_row() + .items_center() + .gap(px(8.)) + .pr(px(8.)) + .child(divider(&theme, 24.)) + .children((0..3).map(|_| block(36.))), + ), + ) + .child( + div() + .flex_1() + .min_h_0() + .relative() .flex() .items_center() .justify_center() - .text_size(px(13.)) - .text_color(theme.gray_10) - .child("Loading screenshot...") - .into_any_element(), - }) - .when(self.aspect_menu.is_some(), |this| { - let state = self.aspect_menu.as_ref().unwrap(); - let items = self.aspect_menu_items(); - this.child( - ui::Menu::plain(&self.theme, "aspect-menu", items, state) - .min_width(px(160.)) - .on_select(cx.listener(|this, index: &usize, window, cx| { - this.choose_aspect(*index, window, cx); - })) - .on_dismiss(cx.listener(|this, _, _window, cx| { - this.aspect_menu = None; - cx.notify(); - })), - ) - }) + .bg(if theme.is_dark() { + theme.gray_3 + } else { + theme.gray_2 + }) + .child( + div() + .absolute() + .left(px(16.)) + .bottom(px(16.)) + .flex() + .flex_row() + .items_center() + .gap(px(8.)) + .p(px(4.)) + .rounded(px(8.)) + .border_1() + .border_color(theme.gray_4) + .bg(if theme.is_dark() { + theme.gray_3 + } else { + theme.gray_1 + }) + .child(block(36.)) + .child(div().w(px(80.)).h(px(8.)).rounded_full().bg(theme.gray_4)) + .child(block(36.)), + ) + .child(spinning_logo(&theme)), + ) } } impl Render for ScreenshotEditorWindow { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { self.sync_appearance(window, cx); + self.sync_hex_inputs(window, cx); + self.sync_annotation_inputs(window, cx); + self.sync_crop_dialog_container(window); + self.sync_crop_field_inputs(window, cx); let theme = self.theme; - div() + let root = div() .id("screenshot-editor-root") .track_focus(&self.focus) .key_context("ScreenshotEditor") .on_key_down(cx.listener(|this, event: &gpui::KeyDownEvent, window, cx| { - this.on_key(event.keystroke.key.as_str(), window, cx); + this.on_key(event, window, cx); })) + // Key-up only matters to the crop dialog's held-arrow nudge. + .on_key_up(cx.listener(|this, event: &gpui::KeyUpEvent, _window, cx| { + this.crop_dialog_key_up(event, cx); + })) + .relative() .size_full() .flex() .flex_col() .font_family("Geist") .font_weight(FontWeight::MEDIUM) - // The video editor's root: `bg-gray-2 dark:bg-gray-1`. .bg(if theme.is_dark() { theme.gray_1 } else { theme.gray_2 }) - .text_color(theme.gray_12) - .child(self.render_header(cx)) + .text_color(theme.gray_12); + + if !self.ready { + return root.child(self.render_skeleton()); + } + + root.child(self.render_header(cx)) .child( div() .flex() .flex_row() .flex_1() .min_h_0() - .child(self.render_preview(cx)) - .child(self.render_sidebar(cx)), + .w_full() + .overflow_hidden() + .when(self.layers_panel_open, |this| { + this.child(self.render_layers_panel(cx)) + }) + .child(self.render_preview(cx)), ) + .children(self.render_annotation_config_bar(cx)) + .children(self.render_annotation_overlays(window, cx)) + .children(self.render_popover(window, cx)) + .children(self.render_menu(cx)) + // The crop dialog is a modal over everything except the toasts + // (`solid-toast` floats over Kobalte's dialog too). + .children(self.render_crop_dialog_overlay(cx)) + .children(self.render_crop_dialog_drag_layer(cx)) + .children(self.render_toasts()) .when_some(self.active_slider, |root, slider| { root.child(ui::Slider::drag_layer( - "style-slider-drag", + "screenshot-slider-drag", cx.listener(move |this, event: &MouseMoveEvent, window, cx| { this.apply_slider(slider, event.position, window, cx); }), - cx.listener(|this, _: &gpui::MouseUpEvent, _window, cx| { - this.active_slider = None; - cx.notify(); + cx.listener(|this, _: &MouseUpEvent, _window, cx| { + this.end_slider(cx); + }), + )) + }) + .when(self.panning.is_some(), |root| { + root.child(ui::Slider::drag_layer( + "screenshot-pan-drag", + cx.listener(|this, event: &MouseMoveEvent, window, cx| { + this.preview_mouse_move(event, window, cx); + }), + cx.listener(|this, _: &MouseUpEvent, window, cx| { + this.preview_mouse_up(window, cx); }), )) }) } } +// --------------------------------------------------------------------------- +// Chrome helpers +// --------------------------------------------------------------------------- + +/// `
    `. +fn divider(theme: &Theme, height: f32) -> impl IntoElement { + div() + .w(px(1.)) + .h(px(height)) + .mx(px(4.)) + .flex_shrink_0() + .bg(theme.gray_4) +} + +/// `Tooltip`'s `kbd` prop (`components/Tooltip.tsx`). `ui::EditorButton`'s own +/// `tooltip` builder carries a label only, so a trigger that needs key chips +/// wears the tooltip on a wrapper instead. +fn kbd_tooltip( + theme: &Theme, + label: &'static str, + keys: &'static [&'static str], + child: impl IntoElement, +) -> impl IntoElement { + let theme = *theme; + div() + // gpui hangs tooltips off `StatefulInteractiveElement`, so the wrapper + // needs an id of its own; the label is unique across the chrome. + .id(gpui::SharedString::from(format!("{label}-tooltip"))) + .flex_shrink_0() + .child(child) + .tooltip(move |_window, cx| ui::Tooltip::new(&theme, label).keys(keys).view(cx)) +} + +/// `AnnotationTools.tsx`'s `size-8 rounded-lg` button: `bg-blue-3 text-blue-11` +/// when it is the active tool, a `hover:bg-gray-3` wash otherwise. +fn tool_button( + theme: &Theme, + id: impl Into, + icon: &'static str, + label: &'static str, + shortcut: &'static str, + active: bool, + on_click: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static, +) -> impl IntoElement { + let theme = *theme; + let label: gpui::SharedString = label.into(); + let shortcut: gpui::SharedString = shortcut.into(); + div() + .id(id.into()) + .flex() + .items_center() + .justify_center() + .size(px(32.)) + .flex_shrink_0() + .rounded(px(8.)) + .cursor_pointer() + .when(active, |this| this.bg(theme.blue_3)) + .when(!active, |this| this.hover(|style| style.bg(theme.gray_3))) + .child(svg().path(icon).size(px(16.)).text_color(if active { + theme.blue_11 + } else { + theme.gray_11 + })) + .tooltip(move |_window, cx| { + ui::Tooltip::new(&theme, label.clone()) + .keys([shortcut.clone()]) + .view(cx) + }) + .on_click(on_click) +} + +/// The transparent swatch's mini checkerboard -- four 16px quarters, which is +/// what the CSS pattern reduces to at `size-8`. +fn mini_checker() -> Vec { + let light: Hsla = gpui::white(); + let dark: Hsla = gpui::rgb(0xf0f0f0).into(); + [ + (0., 0., light), + (16., 0., dark), + (0., 16., dark), + (16., 16., light), + ] + .into_iter() + .map(|(x, y, fill)| { + div() + .absolute() + .left(px(x)) + .top(px(y)) + .size(px(16.)) + .bg(fill) + .into_any_element() + }) + .collect() +} + +/// The skeleton's `
    `. The app +/// has no bare logo glyph -- only the full lockup -- so the wordmark would spin +/// with it; the lockup is drawn still instead, at the skeleton's opacity +/// (README deviation). +fn spinning_logo(theme: &Theme) -> impl IntoElement { + div().flex().items_center().justify_center().child( + svg() + .path(if theme.is_dark() { + "icons/logo-full-dark.svg" + } else { + "icons/logo-full.svg" + }) + .w(px(103.)) + .h(px(40.)) + .opacity(0.5) + .text_color(theme.gray_8), + ) +} + +/// The preview's checkerboard: `background-color: white` under `#f0f0f0` +/// squares on a 20px period (`Preview.tsx:24-31`), identical in both +/// appearances because the Tauri style is hardcoded. +fn checkerboard_tile() -> Arc { + let mut tile = image::RgbaImage::new(CHECKER_TILE, CHECKER_TILE); + for (x, y, pixel) in tile.enumerate_pixels_mut() { + let dark = ((x / CHECKER_CELL) + (y / CHECKER_CELL)) % 2 == 0; + *pixel = if dark { + image::Rgba([240, 240, 240, 255]) + } else { + image::Rgba([255, 255, 255, 255]) + }; + } + crate::library::rgba_to_render_image(tile) +} + +/// Tile the cached checkerboard across `bounds`. One `paint_image` per tile, +/// clipped by a content mask so the edges are cropped rather than squashed. +fn paint_checkerboard(window: &mut Window, bounds: Bounds, tile: Arc) { + let size = CHECKER_TILE as f32; + let columns = (f32::from(bounds.size.width) / size).ceil() as i32; + let rows = (f32::from(bounds.size.height) / size).ceil() as i32; + if columns <= 0 || rows <= 0 { + return; + } + window.with_content_mask(Some(gpui::ContentMask { bounds }), |window| { + for row in 0..rows { + for column in 0..columns { + let origin = gpui::point( + bounds.origin.x + px(column as f32 * size), + bounds.origin.y + px(row as f32 * size), + ); + let cell = Bounds { + origin, + size: gpui::size(px(size), px(size)), + }; + let _ = window.paint_image(cell, gpui::Corners::default(), tile.clone(), 0, false); + } + } + }); +} + +/// A stable element-id fragment per slider. +fn slider_id(slider: StyleSlider) -> &'static str { + match slider { + StyleSlider::Blur => "blur", + StyleSlider::Padding => "padding", + StyleSlider::Rounding => "rounding", + StyleSlider::Shadow => "shadow", + StyleSlider::ShadowSize => "shadow-size", + StyleSlider::ShadowOpacity => "shadow-opacity", + StyleSlider::ShadowBlur => "shadow-blur", + StyleSlider::BorderWidth => "border-width", + StyleSlider::BorderOpacity => "border-opacity", + StyleSlider::Zoom => "zoom", + } +} + +/// The Image tab's `h-48` preview, at 2x. +fn decode_preview_image(path: &Path) -> Option> { + let bytes = std::fs::read(path).ok()?; + let format = image::guess_format(&bytes).ok()?; + let decoded = image::load_from_memory_with_format(&bytes, format).ok()?; + let (width, height) = (decoded.width().max(1), decoded.height().max(1)); + let scale = (768. / width.max(height) as f32).min(1.); + let scaled = if scale < 1. { + decoded + .thumbnail( + ((width as f32 * scale) as u32).max(1), + ((height as f32 * scale) as u32).max(1), + ) + .into_rgba8() + } else { + decoded.into_rgba8() + }; + Some(crate::library::rgba_to_render_image(scaled)) +} + // --------------------------------------------------------------------------- // Loading // --------------------------------------------------------------------------- +/// One rendered frame, in both the forms the window needs: tight RGBA (what a +/// mask overlay resamples) and the BGRA copy gpui's atlas takes. The rows are +/// copied once and the swap runs over a clone, so the padded GPU buffer is +/// walked a single time. +fn frame_buffers(frame: &RenderedFrame) -> Option<(Arc>, Arc)> { + let row_bytes = frame.width as usize * 4; + let mut rgba = Vec::with_capacity(row_bytes * frame.height as usize); + for row in frame + .data + .chunks(frame.padded_bytes_per_row as usize) + .take(frame.height as usize) + { + if row.len() < row_bytes { + return None; + } + rgba.extend_from_slice(&row[..row_bytes]); + } + let mut bgra = rgba.clone(); + for pixel in bgra.chunks_exact_mut(4) { + pixel.swap(0, 2); + } + let buffer = image::RgbaImage::from_raw(frame.width, frame.height, bgra)?; + Some(( + Arc::new(rgba), + Arc::new(RenderImage::new(smallvec::smallvec![image::Frame::new( + buffer + )])), + )) +} + /// Read the bundle, start the still renderer on tokio, pump frames back -- /// `load_editor_project`'s shape, without the playback half. pub fn load_screenshot_project( @@ -1716,11 +4672,13 @@ pub fn load_screenshot_project( let (frame_tx, frame_rx) = flume::bounded(2); let (setup_tx, setup_rx) = flume::bounded(1); + let image_size = (source.width, source.height); if handle .update(cx, |view, window, cx| { view.set_loaded( source.pretty_name.clone(), source.config.clone(), + image_size, config_tx, export_tx, window, @@ -1749,11 +4707,11 @@ pub fn load_screenshot_project( while let Ok((frame, revision)) = frame_rx.recv_async().await { let size = (frame.width, frame.height); - let image = cx + let buffers = cx .background_executor() - .spawn(async move { crate::editor_window::frame_image(&frame) }) + .spawn(async move { frame_buffers(&frame) }) .await; - let Some(image) = image else { + let Some((rgba, image)) = buffers else { tracing::warn!("a rendered screenshot frame could not be converted for display"); continue; }; @@ -1765,7 +4723,7 @@ pub fn load_screenshot_project( ); if handle .update(cx, |view, window, cx| { - view.frame_arrived(image, size, window, cx) + view.frame_arrived(image, rgba, size, window, cx) }) .is_err() { @@ -1785,3 +4743,101 @@ fn aspect_eq(a: &Option, b: &Option false, } } + +#[cfg(test)] +mod tests { + use super::*; + + /// The checkerboard is one 240x240 tile of 10px cells -- an even number of + /// cells per axis, so tiling it leaves no seam. + #[test] + fn the_checkerboard_tile_repeats_without_a_seam() { + assert_eq!(CHECKER_TILE % (CHECKER_CELL * 2), 0); + } + + /// `PaddingPopover`'s guard (`context.tsx:30-42`). + #[test] + fn an_invisible_background_is_one_with_nothing_to_show() { + assert!(has_no_visible_background(&BackgroundSource::Color { + value: [255, 255, 255], + alpha: 0 + })); + assert!(!has_no_visible_background(&BackgroundSource::Color { + value: [255, 255, 255], + alpha: 255 + })); + assert!(has_no_visible_background(&BackgroundSource::Wallpaper { + path: None + })); + assert!(!has_no_visible_background(&BackgroundSource::Image { + path: Some("/tmp/a.png".into()) + })); + } + + /// `ensurePaddingForBackground` only fills in what is still at zero, and + /// leaves a rounding the user already chose alone. + #[test] + fn choosing_a_background_seeds_padding_rounding_and_shadow() { + let mut project = ProjectConfiguration::default(); + project.background.padding = 0.; + project.background.rounding = 0.; + project.background.shadow = 0.; + ScreenshotEditorWindow::ensure_padding_for_background(&mut project); + assert_eq!(project.background.padding, 10.); + assert_eq!(project.background.rounding, 8.); + assert_eq!(project.background.shadow, DEFAULT_BACKGROUND_SHADOW); + + let mut kept = ProjectConfiguration::default(); + kept.background.padding = 0.; + kept.background.rounding = 24.; + kept.background.shadow = 5.; + ScreenshotEditorWindow::ensure_padding_for_background(&mut kept); + assert_eq!(kept.background.padding, 10.); + assert_eq!(kept.background.rounding, 24.); + assert_eq!(kept.background.shadow, 5.); + } + + /// `ShadowPopover.tsx:37-48`: the first drag off zero seeds the advanced + /// block in the same edit. + #[test] + fn the_first_shadow_seeds_the_advanced_block() { + let mut project = ProjectConfiguration::default(); + project.background.shadow = 0.; + project.background.advanced_shadow = None; + assert!(StyleSlider::Shadow.apply(&mut project, 40.)); + assert_eq!( + project.background.advanced_shadow.map(|shadow| ( + shadow.size, + shadow.opacity, + shadow.blur + )), + Some((50., 18., 50.)) + ); + } + + /// `handlePaddingChange`: padding over an invisible background lights it up + /// first, and padding back to zero leaves the colour alone. + #[test] + fn padding_over_an_invisible_background_makes_it_white() { + let mut project = ProjectConfiguration::default(); + project.background.source = BackgroundSource::Color { + value: [0, 0, 0], + alpha: 0, + }; + project.background.padding = 0.; + assert!(StyleSlider::Padding.apply(&mut project, 20.)); + assert!(matches!( + project.background.source, + BackgroundSource::Color { + value: [255, 255, 255], + alpha: 255 + } + )); + } + + #[test] + fn the_zoom_slider_never_writes_the_project() { + let mut project = ProjectConfiguration::default(); + assert!(!StyleSlider::Zoom.apply(&mut project, 2.)); + } +} diff --git a/apps/desktop-gpui/src/screenshot_export.rs b/apps/desktop-gpui/src/screenshot_export.rs new file mode 100644 index 0000000000..d296fb941d --- /dev/null +++ b/apps/desktop-gpui/src/screenshot_export.rs @@ -0,0 +1,1026 @@ +//! Screenshot export compositing -- `screenshot-editor/screenshotExport.ts` +//! and the encode half of `useScreenshotExport.ts`, natively. +//! +//! The Tauri editor exports in two stages: the GPU renders the *styled* +//! screenshot (background, padding, shadow -- everything but annotations) at +//! export resolution, and a `` pass then bakes the annotations in +//! (`renderScreenshotExportCanvas`, `screenshotExport.ts:250-338`). This +//! module is that canvas pass: scale the frame-space annotations up to the +//! export frame, re-apply the mask filters at export resolution, draw the +//! shapes with tiny-skia and the text with cosmic-text, and expand the output +//! to the union of the image and every annotation's box. +//! +//! It also carries the share fingerprint (`screenshotProjectFingerprint`, +//! `:419-431`) and the per-destination encodings (`exportImage`, +//! `useScreenshotExport.ts:139-253`): PNG for Save, PNG-over-white for Copy, +//! and JPEG at 0.9 for Share unless the output actually needs its alpha. +//! +//! Everything here is pure CPU work on plain buffers -- the callers run it on +//! the background executor. + +use std::sync::{Mutex, OnceLock}; + +use cap_project::{Annotation, AnnotationType, ProjectConfiguration}; + +use crate::screenshot_annotations::{self as annotations, Rect}; +use crate::screenshot_editor::has_no_visible_background; + +/// One export-resolution GPU render, plus the size the *preview* renders the +/// same config at. Annotations live in preview-frame space +/// (`AnnotationLayer.tsx`'s viewBox), so the compositor's scale factors are +/// `export / base` -- exactly `canvas.width / frame.width` over there, where +/// `frame` is `latestFrame()` after `waitForSyncedPreview`. +pub struct RawFrame { + /// Tight RGBA, straight alpha -- the wgpu row padding already stripped. + pub rgba: Vec, + pub width: u32, + pub height: u32, + /// What `ProjectUniforms::get_output_size` yields for the preview's own + /// `get_base_size` resolution -- i.e. the dimensions the preview frame for + /// this config actually has. + pub base_width: u32, + pub base_height: u32, +} + +/// The composited output canvas -- straight-alpha RGBA. +pub struct Composited { + pub rgba: Vec, + pub width: u32, + pub height: u32, +} + +/// The output canvas rect before rounding: where the working canvas lands +/// inside it, and its final pixel size. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ExportBounds { + pub min_x: f64, + pub min_y: f64, + pub width: u32, + pub height: u32, +} + +// --------------------------------------------------------------------------- +// Annotation scaling (`scaleAnnotations`, `screenshotExport.ts:232-248`) +// --------------------------------------------------------------------------- + +/// Scale every annotation from preview-frame space into export space: x/w by +/// the x factor, y/h by the y factor, and strokeWidth/maskLevel by the average +/// of the two. `points` are fractions of the stroke's own box, so they ride +/// along untouched. +pub fn scale_annotations(list: &[Annotation], scale_x: f64, scale_y: f64) -> Vec { + let scalar = (scale_x + scale_y) / 2.; + list.iter() + .map(|annotation| Annotation { + x: annotation.x * scale_x, + y: annotation.y * scale_y, + width: annotation.width * scale_x, + height: annotation.height * scale_y, + stroke_width: annotation.stroke_width * scalar, + mask_level: annotation.mask_level.map(|level| level * scalar), + ..annotation.clone() + }) + .collect() +} + +// --------------------------------------------------------------------------- +// The composite (`renderScreenshotExportCanvas`) +// --------------------------------------------------------------------------- + +/// The output canvas rect: the working canvas's own rect, unioned with every +/// non-mask annotation's normalized box (`:307-325`). `Math.max(1, +/// Math.round(..))` on each axis. +pub fn export_bounds(scaled: &[Annotation], canvas: (u32, u32)) -> ExportBounds { + let mut min_x = 0f64; + let mut min_y = 0f64; + let mut max_x = f64::from(canvas.0); + let mut max_y = f64::from(canvas.1); + + for annotation in scaled { + if annotation.annotation_type == AnnotationType::Mask { + continue; + } + let left = annotation.x.min(annotation.x + annotation.width); + let right = annotation.x.max(annotation.x + annotation.width); + let top = annotation.y.min(annotation.y + annotation.height); + let bottom = annotation.y.max(annotation.y + annotation.height); + min_x = min_x.min(left); + max_x = max_x.max(right); + min_y = min_y.min(top); + max_y = max_y.max(bottom); + } + + ExportBounds { + min_x, + min_y, + width: ((max_x - min_x).round() as i64).max(1) as u32, + height: ((max_y - min_y).round() as i64).max(1) as u32, + } +} + +/// The whole canvas pass. Faithfully in the source's order: masks from an +/// unmodified copy of the frame, then the shape/text draw, then the expanded +/// output canvas -- white-filled unless the background is invisible -- with +/// the working canvas blitted at `(-minX, -minY)`. +/// +/// Two deliberate matches of the source's own quirks: annotations are drawn +/// onto the *frame-sized* working canvas, so the parts dragged past the frame +/// are clipped there and the union expansion only pads the margins (that is +/// what `drawAnnotations(ctx, ..)` before the final `drawImage` does); and the +/// blit offset is rounded to a whole pixel where the browser would resample at +/// a fractional one. +pub fn composite(raw: &RawFrame, config: &ProjectConfiguration) -> Composited { + let width = raw.width.max(1); + let height = raw.height.max(1); + let scale_x = f64::from(width) / f64::from(raw.base_width.max(1)); + let scale_y = f64::from(height) / f64::from(raw.base_height.max(1)); + let scaled = scale_annotations(&config.annotations, scale_x, scale_y); + + let mut canvas = raw.rgba.clone(); + + // `applyMaskAnnotations(ctx, sourceCanvas, .., {x:0,y:0,w,h})` -- every + // mask samples the untouched `raw.rgba`, so overlapping masks never + // re-filter each other's output. + let full = Rect { + x: 0., + y: 0., + width: f64::from(width), + height: f64::from(height), + }; + for mask in &scaled { + if mask.annotation_type != AnnotationType::Mask { + continue; + } + if let Some((x0, y0, region)) = + annotations::masked_region_image(&raw.rgba, (width, height), mask, full) + { + blit_over(&mut canvas, (width, height), ®ion, (x0, y0)); + } + } + + draw_annotations_onto(&mut canvas, width, height, &scaled); + + let bounds = export_bounds(&scaled, (width, height)); + let mut out = vec![0u8; bounds.width as usize * bounds.height as usize * 4]; + if !has_no_visible_background(&config.background.source) { + out.fill(255); + } + let offset = ( + (-bounds.min_x).round() as i64, + (-bounds.min_y).round() as i64, + ); + blit_over_offset( + &mut out, + (bounds.width, bounds.height), + &canvas, + (width, height), + offset, + ); + + Composited { + rgba: out, + width: bounds.width, + height: bounds.height, + } +} + +// --------------------------------------------------------------------------- +// Transparency + encodings (`canvasNeedsTransparency`, `exportImage`) +// --------------------------------------------------------------------------- + +/// `canvasNeedsTransparency` (`screenshotExport.ts:356-371`): only a project +/// whose background is invisible can need alpha at all, and only then is the +/// output actually scanned. +pub fn needs_transparency(out: &Composited, config: &ProjectConfiguration) -> bool { + if !has_no_visible_background(&config.background.source) { + return false; + } + out.rgba + .iter() + .skip(3) + .step_by(4) + .any(|&alpha| alpha != 255) +} + +/// `withWhiteBackground` (`useScreenshotExport.ts:18-28`). +pub fn flatten_onto_white(out: &Composited) -> Composited { + let mut rgba = vec![255u8; out.rgba.len()]; + blit_over_offset( + &mut rgba, + (out.width, out.height), + &out.rgba, + (out.width, out.height), + (0, 0), + ); + Composited { + rgba, + width: out.width, + height: out.height, + } +} + +/// The one PNG encode every destination shares -- and the Phase 1-2 "styled +/// frame straight to PNG" path, still callable through +/// `screenshot_editor::render_export_png`. +pub fn encode_rgba_png(rgba: &[u8], width: u32, height: u32) -> Result, String> { + use image::ImageEncoder as _; + let mut png = std::io::Cursor::new(Vec::new()); + image::codecs::png::PngEncoder::new(&mut png) + .write_image(rgba, width, height, image::ExtendedColorType::Rgba8) + .map_err(|e| format!("Failed to encode screenshot export: {e}"))?; + Ok(png.into_inner()) +} + +fn encode_png(out: &Composited) -> Result, String> { + encode_rgba_png(&out.rgba, out.width, out.height) +} + +/// `canvasToBlob(canvas, "image/jpeg", 0.9)`. JPEG has no alpha channel; the +/// share flow only ever picks it when the output is fully opaque, and the +/// flatten here is the same belt the browser's own encode wears. +fn encode_jpeg(out: &Composited) -> Result, String> { + let flat = flatten_onto_white(out); + let rgb: Vec = flat + .rgba + .chunks_exact(4) + .flat_map(|px| [px[0], px[1], px[2]]) + .collect(); + let mut buffer = Vec::new(); + image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buffer, 90) + .encode( + &rgb, + flat.width, + flat.height, + image::ExtendedColorType::Rgb8, + ) + .map_err(|e| format!("Failed to encode screenshot export: {e}"))?; + Ok(buffer) +} + +/// An encoded export, tagged with the content type the upload sends. +pub enum EncodedImage { + Png(Vec), + Jpeg(Vec), +} + +impl EncodedImage { + pub fn content_type(&self) -> &'static str { + match self { + Self::Png(_) => "image/png", + Self::Jpeg(_) => "image/jpeg", + } + } + + pub fn into_bytes(self) -> Vec { + match self { + Self::Png(bytes) | Self::Jpeg(bytes) => bytes, + } + } +} + +/// Share: JPEG at 0.9 unless the output needs its alpha, then PNG +/// (`useScreenshotExport.ts:191-198`). +pub fn encode_for_share( + out: &Composited, + config: &ProjectConfiguration, +) -> Result { + if needs_transparency(out, config) { + encode_png(out).map(EncodedImage::Png) + } else { + encode_jpeg(out).map(EncodedImage::Jpeg) + } +} + +/// Copy: always PNG, composited over white when transparency is not needed +/// (`:183-198` -- `withWhiteBackground` only on the clipboard path). +pub fn encode_for_copy(out: &Composited, config: &ProjectConfiguration) -> Result, String> { + if needs_transparency(out, config) { + encode_png(out) + } else { + encode_png(&flatten_onto_white(out)) + } +} + +/// Save: PNG of the output canvas as it stands (`:198` -- no transparency +/// check and no white pass on the file path). +pub fn encode_for_save(out: &Composited) -> Result, String> { + encode_png(out) +} + +// --------------------------------------------------------------------------- +// The share fingerprint (`screenshotProjectFingerprint`) +// --------------------------------------------------------------------------- + +/// `stableValue` (`screenshotExport.ts:373-392`): arrays element-wise, objects +/// rebuilt with their keys sorted, everything else as-is. serde's skip +/// attributes have already dropped the `None`s JS would have dropped as +/// `undefined`. +fn stable_value(value: &serde_json::Value) -> serde_json::Value { + match value { + serde_json::Value::Array(items) => { + serde_json::Value::Array(items.iter().map(stable_value).collect()) + } + serde_json::Value::Object(map) => { + let mut keys: Vec<&String> = map.keys().collect(); + keys.sort(); + let mut sorted = serde_json::Map::new(); + for key in keys { + sorted.insert(key.clone(), stable_value(&map[key])); + } + serde_json::Value::Object(sorted) + } + other => other.clone(), + } +} + +fn stable_stringify(value: &serde_json::Value) -> Result { + serde_json::to_string(&stable_value(value)) + .map_err(|e| format!("Failed to serialize screenshot config: {e}")) +} + +/// The content hash the share flow compares against `sharing.contentHash`: +/// the key-sorted config JSON, annotations included, SHA-256'd. Stable across +/// runs of this app -- which is the contract; a hash minted by the Tauri app +/// simply misses and re-uploads against the same video id. +pub fn content_hash(config: &ProjectConfiguration) -> Result { + use sha2::Digest as _; + let value = serde_json::to_value(config) + .map_err(|e| format!("Failed to serialize screenshot config: {e}"))?; + let payload = stable_stringify(&value)?; + let digest = sha2::Sha256::digest(payload.as_bytes()); + let mut hex = String::with_capacity(64); + for byte in digest { + use std::fmt::Write as _; + let _ = write!(hex, "{byte:02x}"); + } + Ok(format!("sha256:{hex}")) +} + +// --------------------------------------------------------------------------- +// Pixel plumbing +// --------------------------------------------------------------------------- + +/// Straight-alpha source-over of one pixel -- what `drawImage` does. +fn blend_pixel_over(dst: &mut [u8], src: [u8; 4]) { + if src[3] == 0 { + return; + } + if src[3] == 255 { + dst.copy_from_slice(&src); + return; + } + let sa = f32::from(src[3]) / 255.; + let da = f32::from(dst[3]) / 255.; + let oa = sa + da * (1. - sa); + if oa <= 0. { + dst.fill(0); + return; + } + for channel in 0..3 { + let s = f32::from(src[channel]); + let d = f32::from(dst[channel]); + dst[channel] = ((s * sa + d * da * (1. - sa)) / oa).round().clamp(0., 255.) as u8; + } + dst[3] = (oa * 255.).round() as u8; +} + +/// Source-over blit of a filtered mask region into the working canvas. +fn blit_over(canvas: &mut [u8], size: (u32, u32), region: &image::RgbaImage, at: (u32, u32)) { + let stride = size.0 as usize * 4; + for (row_index, row) in region.rows().enumerate() { + let y = at.1 as usize + row_index; + if y >= size.1 as usize { + break; + } + for (column_index, pixel) in row.enumerate() { + let x = at.0 as usize + column_index; + if x >= size.0 as usize { + break; + } + let start = y * stride + x * 4; + blend_pixel_over(&mut canvas[start..start + 4], pixel.0); + } + } +} + +/// Source-over blit of one straight-alpha buffer into another at a (possibly +/// negative) offset, clipping to the destination. +fn blit_over_offset( + dst: &mut [u8], + dst_size: (u32, u32), + src: &[u8], + src_size: (u32, u32), + offset: (i64, i64), +) { + let dst_stride = dst_size.0 as usize * 4; + let src_stride = src_size.0 as usize * 4; + for src_y in 0..src_size.1 as i64 { + let dst_y = src_y + offset.1; + if dst_y < 0 || dst_y >= i64::from(dst_size.1) { + continue; + } + for src_x in 0..src_size.0 as i64 { + let dst_x = src_x + offset.0; + if dst_x < 0 || dst_x >= i64::from(dst_size.0) { + continue; + } + let src_start = src_y as usize * src_stride + src_x as usize * 4; + let dst_start = dst_y as usize * dst_stride + dst_x as usize * 4; + let pixel = [ + src[src_start], + src[src_start + 1], + src[src_start + 2], + src[src_start + 3], + ]; + blend_pixel_over(&mut dst[dst_start..dst_start + 4], pixel); + } + } +} + +/// `(c * a + 127) / 255` rounding both ways -- the standard integer +/// premultiply pair. tiny-skia's pixmaps are premultiplied, our canvases are +/// straight, so the draw pass converts in and back out. +fn premultiply(rgba: &mut [u8]) { + for pixel in rgba.chunks_exact_mut(4) { + let alpha = u16::from(pixel[3]); + if alpha == 255 { + continue; + } + for channel in 0..3 { + pixel[channel] = ((u16::from(pixel[channel]) * alpha + 127) / 255) as u8; + } + } +} + +fn demultiply(rgba: &mut [u8]) { + for pixel in rgba.chunks_exact_mut(4) { + let alpha = u32::from(pixel[3]); + if alpha == 255 || alpha == 0 { + continue; + } + for channel in 0..3 { + let value = (u32::from(pixel[channel]) * 255 + alpha / 2) / alpha; + pixel[channel] = value.min(255) as u8; + } + } +} + +// --------------------------------------------------------------------------- +// The shape draw (`drawAnnotations`, `screenshotExport.ts:42-121`) +// --------------------------------------------------------------------------- + +fn skia_color(rgb: [u8; 3], alpha: f32) -> Option { + tiny_skia::Color::from_rgba( + f32::from(rgb[0]) / 255., + f32::from(rgb[1]) / 255., + f32::from(rgb[2]) / 255., + alpha.clamp(0., 1.), + ) +} + +fn skia_paint(color: tiny_skia::Color) -> tiny_skia::Paint<'static> { + let mut paint = tiny_skia::Paint::default(); + paint.set_color(color); + paint.anti_alias = true; + paint +} + +/// `ctx.lineWidth` semantics: canvas' default join is miter with limit 10. +fn skia_stroke(width: f64, round: bool) -> tiny_skia::Stroke { + tiny_skia::Stroke { + width: width.max(0.) as f32, + miter_limit: 10., + line_cap: if round { + tiny_skia::LineCap::Round + } else { + tiny_skia::LineCap::Butt + }, + line_join: if round { + tiny_skia::LineJoin::Round + } else { + tiny_skia::LineJoin::Miter + }, + dash: None, + } +} + +/// Draw every non-mask annotation onto the straight-alpha canvas, in list +/// order -- rect, ellipse, arrow, freehand with tiny-skia, text with +/// cosmic-text, each under its own `globalAlpha`. +fn draw_annotations_onto(canvas: &mut [u8], width: u32, height: u32, scaled: &[Annotation]) { + premultiply(canvas); + if let Some(mut pixmap) = tiny_skia::PixmapMut::from_bytes(canvas, width, height) { + for annotation in scaled { + if annotation.annotation_type == AnnotationType::Mask { + continue; + } + draw_one(&mut pixmap, annotation); + } + } + demultiply(canvas); +} + +fn draw_one(pixmap: &mut tiny_skia::PixmapMut<'_>, annotation: &Annotation) { + let stroke_color = annotations::parse_css_rgba(&annotation.stroke_color, annotation.opacity); + let fill_color = annotations::parse_css_rgba(&annotation.fill_color, annotation.opacity); + let transform = tiny_skia::Transform::identity(); + + match annotation.annotation_type { + AnnotationType::Mask => {} + AnnotationType::Rectangle => { + let rect = annotations::normalized_rect(annotation); + let Some(skia_rect) = tiny_skia::Rect::from_xywh( + rect.x as f32, + rect.y as f32, + (rect.width as f32).max(f32::EPSILON), + (rect.height as f32).max(f32::EPSILON), + ) else { + return; + }; + let path = { + let mut builder = tiny_skia::PathBuilder::new(); + builder.push_rect(skia_rect); + builder.finish() + }; + let Some(path) = path else { return }; + if let Some(color) = fill_color.and_then(|(rgb, a)| skia_color(rgb, a)) { + pixmap.fill_path( + &path, + &skia_paint(color), + tiny_skia::FillRule::Winding, + transform, + None, + ); + } + if let Some(color) = stroke_color.and_then(|(rgb, a)| skia_color(rgb, a)) + && annotation.stroke_width > 0. + { + pixmap.stroke_path( + &path, + &skia_paint(color), + &skia_stroke(annotation.stroke_width, false), + transform, + None, + ); + } + } + AnnotationType::Circle => { + let rect = annotations::normalized_rect(annotation); + // The preview's own guard: SVG (and the canvas) render nothing for + // a zero radius. + if rect.width <= 0. || rect.height <= 0. { + return; + } + let Some(oval) = tiny_skia::Rect::from_xywh( + rect.x as f32, + rect.y as f32, + rect.width as f32, + rect.height as f32, + ) else { + return; + }; + let path = { + let mut builder = tiny_skia::PathBuilder::new(); + builder.push_oval(oval); + builder.finish() + }; + let Some(path) = path else { return }; + if let Some(color) = fill_color.and_then(|(rgb, a)| skia_color(rgb, a)) { + pixmap.fill_path( + &path, + &skia_paint(color), + tiny_skia::FillRule::Winding, + transform, + None, + ); + } + if let Some(color) = stroke_color.and_then(|(rgb, a)| skia_color(rgb, a)) + && annotation.stroke_width > 0. + { + pixmap.stroke_path( + &path, + &skia_paint(color), + &skia_stroke(annotation.stroke_width, false), + transform, + None, + ); + } + } + AnnotationType::Arrow => { + let Some(color) = stroke_color.and_then(|(rgb, a)| skia_color(rgb, a)) else { + return; + }; + let end = ( + annotation.x + annotation.width, + annotation.y + annotation.height, + ); + let angle = (end.1 - annotation.y).atan2(end.0 - annotation.x); + let head = annotations::arrow_head_points(end.0, end.1, angle, annotation.stroke_width); + if annotation.stroke_width > 0. { + let mut builder = tiny_skia::PathBuilder::new(); + builder.move_to(annotation.x as f32, annotation.y as f32); + builder.line_to(head.base.0 as f32, head.base.1 as f32); + if let Some(path) = builder.finish() { + pixmap.stroke_path( + &path, + &skia_paint(color), + &skia_stroke(annotation.stroke_width, true), + transform, + None, + ); + } + } + let mut builder = tiny_skia::PathBuilder::new(); + builder.move_to(head.points[0].0 as f32, head.points[0].1 as f32); + builder.line_to(head.points[1].0 as f32, head.points[1].1 as f32); + builder.line_to(head.points[2].0 as f32, head.points[2].1 as f32); + builder.close(); + if let Some(path) = builder.finish() { + pixmap.fill_path( + &path, + &skia_paint(color), + tiny_skia::FillRule::Winding, + transform, + None, + ); + } + } + AnnotationType::Draw => { + let Some(color) = stroke_color.and_then(|(rgb, a)| skia_color(rgb, a)) else { + return; + }; + if annotation.stroke_width <= 0. { + return; + } + let points = annotations::draw_points(annotation); + if points.len() < 2 { + return; + } + let segments = annotations::smooth_path(&points); + let mut builder = tiny_skia::PathBuilder::new(); + for segment in &segments { + match *segment { + annotations::PathSegment::Move { x, y } => builder.move_to(x as f32, y as f32), + annotations::PathSegment::Line { x, y } => builder.line_to(x as f32, y as f32), + annotations::PathSegment::Quad { cx, cy, x, y } => { + builder.quad_to(cx as f32, cy as f32, x as f32, y as f32) + } + } + } + if let Some(path) = builder.finish() { + pixmap.stroke_path( + &path, + &skia_paint(color), + &skia_stroke(annotation.stroke_width, true), + transform, + None, + ); + } + } + AnnotationType::Text => draw_text(pixmap, annotation), + } +} + +// --------------------------------------------------------------------------- +// Text (`ctx.font = `${ann.height}px sans-serif``, `:113-117`) +// --------------------------------------------------------------------------- + +/// One process-wide font system: `FontSystem::new` walks the installed font +/// database, which is far too slow to redo per export. +static TEXT_STACK: OnceLock> = + OnceLock::new(); + +/// `` at `font-size = height`, baseline at `y + height`, filled with the +/// stroke colour -- the same placement the preview's `paint_text` resolves, +/// down to the Helvetica the renderer maps `sans-serif` to. +fn draw_text(pixmap: &mut tiny_skia::PixmapMut<'_>, annotation: &Annotation) { + let Some(text) = annotation.text.as_deref().filter(|text| !text.is_empty()) else { + return; + }; + let Some((rgb, alpha)) = + annotations::parse_css_rgba(&annotation.stroke_color, annotation.opacity) + else { + return; + }; + let font_size = (annotation.height as f32).max(1.); + // One line, like the preview: a stray newline becomes a space. + let text = text.replace('\n', " "); + + let stack = TEXT_STACK.get_or_init(|| { + Mutex::new(( + cosmic_text::FontSystem::new(), + cosmic_text::SwashCache::new(), + )) + }); + let mut guard = match stack.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + }; + let (font_system, swash_cache) = &mut *guard; + + let mut buffer = + cosmic_text::Buffer::new(font_system, cosmic_text::Metrics::new(font_size, font_size)); + buffer.set_size(font_system, None, None); + let attrs = cosmic_text::Attrs::new().family(cosmic_text::Family::Name(annotations::TEXT_FONT)); + buffer.set_text(font_system, &text, &attrs, cosmic_text::Shaping::Advanced); + buffer.shape_until_scroll(font_system, false); + + // The buffer's own baseline, cancelled out so ours lands exactly at + // `y + height`. + let line_y = buffer + .layout_runs() + .next() + .map(|run| run.line_y) + .unwrap_or(font_size); + let origin_x = annotation.x; + let origin_y = annotation.y + annotation.height - f64::from(line_y); + + let color = cosmic_text::Color::rgba(rgb[0], rgb[1], rgb[2], (alpha * 255.).round() as u8); + let (canvas_width, canvas_height) = (pixmap.width(), pixmap.height()); + let data = pixmap.data_mut(); + buffer.draw(font_system, swash_cache, color, |x, y, w, h, pixel| { + blend_rect_premultiplied( + data, + (canvas_width, canvas_height), + (origin_x + f64::from(x), origin_y + f64::from(y)), + (w, h), + pixel, + ); + }); +} + +/// Premultiplied source-over of one glyph-coverage rect (cosmic-text hands +/// back the coverage folded into the colour's alpha). +fn blend_rect_premultiplied( + data: &mut [u8], + size: (u32, u32), + at: (f64, f64), + rect: (u32, u32), + color: cosmic_text::Color, +) { + let alpha = f32::from(color.a()) / 255.; + if alpha <= 0. { + return; + } + let src = [ + (f32::from(color.r()) * alpha).round() as u8, + (f32::from(color.g()) * alpha).round() as u8, + (f32::from(color.b()) * alpha).round() as u8, + color.a(), + ]; + let inverse = 1. - alpha; + let stride = size.0 as usize * 4; + let x0 = at.0.round() as i64; + let y0 = at.1.round() as i64; + for row in 0..i64::from(rect.1) { + let y = y0 + row; + if y < 0 || y >= i64::from(size.1) { + continue; + } + for column in 0..i64::from(rect.0) { + let x = x0 + column; + if x < 0 || x >= i64::from(size.0) { + continue; + } + let start = y as usize * stride + x as usize * 4; + for channel in 0..4 { + let d = f32::from(data[start + channel]); + data[start + channel] = + (f32::from(src[channel]) + d * inverse).round().min(255.) as u8; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cap_project::BackgroundSource; + use serde_json::json; + + fn annotation(kind: AnnotationType, x: f64, y: f64, w: f64, h: f64) -> Annotation { + Annotation { + id: "a".into(), + annotation_type: kind, + x, + y, + width: w, + height: h, + stroke_color: "#F05656".into(), + stroke_width: 4., + fill_color: "transparent".into(), + opacity: 1., + rotation: 0., + text: None, + mask_type: None, + mask_level: None, + points: None, + } + } + + fn invisible_background(config: &mut ProjectConfiguration) { + config.background.source = BackgroundSource::Color { + value: [0, 0, 0], + alpha: 0, + }; + } + + /// `stableValue`: keys sorted at every depth, arrays kept in order. + #[test] + fn stable_stringify_sorts_keys_recursively() { + let value = json!({ + "zeta": { "b": 1, "a": [ { "y": 2, "x": 1 } ] }, + "alpha": 3, + }); + assert_eq!( + stable_stringify(&value).unwrap(), + r#"{"alpha":3,"zeta":{"a":[{"x":1,"y":2}],"b":1}}"# + ); + } + + /// The fingerprint is stable for the same config and moves when an + /// annotation moves -- which is what decides re-upload vs copied link. + #[test] + fn content_hash_is_stable_and_annotation_sensitive() { + let mut config = ProjectConfiguration::default(); + config + .annotations + .push(annotation(AnnotationType::Rectangle, 10., 10., 40., 20.)); + let first = content_hash(&config).unwrap(); + let again = content_hash(&config).unwrap(); + assert_eq!(first, again); + assert!(first.starts_with("sha256:")); + assert_eq!(first.len(), "sha256:".len() + 64); + + config.annotations[0].x = 11.; + assert_ne!(content_hash(&config).unwrap(), first); + } + + /// `scaleAnnotations`: x/w by the x factor, y/h by the y factor, + /// strokeWidth and maskLevel by the average, points untouched. + #[test] + fn scale_annotations_uses_axis_factors_and_the_average_scalar() { + let mut mask = annotation(AnnotationType::Mask, 10., 20., 30., 40.); + mask.mask_level = Some(8.); + let mut stroke = annotation(AnnotationType::Draw, 1., 2., 3., 4.); + stroke.points = Some(vec![[0.25, 0.75]]); + + let scaled = scale_annotations(&[mask, stroke], 2., 4.); + assert_eq!( + (scaled[0].x, scaled[0].y, scaled[0].width, scaled[0].height), + (20., 80., 60., 160.) + ); + assert_eq!(scaled[0].stroke_width, 12.); // 4 * (2+4)/2 + assert_eq!(scaled[0].mask_level, Some(24.)); // 8 * 3 + assert_eq!(scaled[1].mask_level, None); + assert_eq!(scaled[1].points, Some(vec![[0.25, 0.75]])); + } + + /// The union expansion: annotations dragged past the frame stretch the + /// output, negative boxes normalize first, and masks never count. + #[test] + fn export_bounds_unions_the_canvas_with_non_mask_annotations() { + let inside = annotation(AnnotationType::Rectangle, 10., 10., 20., 20.); + assert_eq!( + export_bounds(&[inside], (100, 50)), + ExportBounds { + min_x: 0., + min_y: 0., + width: 100, + height: 50 + } + ); + + let overflowing = annotation(AnnotationType::Arrow, 90., -10., 30., 15.); + let bounds = export_bounds(&[overflowing], (100, 50)); + assert_eq!((bounds.min_x, bounds.min_y), (0., -10.)); + assert_eq!((bounds.width, bounds.height), (120, 60)); + + let negative = annotation(AnnotationType::Rectangle, -5., 0., -10., 10.); + let bounds = export_bounds(&[negative], (100, 50)); + assert_eq!(bounds.min_x, -15.); + assert_eq!(bounds.width, 115); + + let mask = annotation(AnnotationType::Mask, -50., -50., 300., 300.); + assert_eq!( + export_bounds(&[mask], (100, 50)), + ExportBounds { + min_x: 0., + min_y: 0., + width: 100, + height: 50 + } + ); + } + + /// `canvasNeedsTransparency`: a visible background short-circuits to + /// false; an invisible one scans the actual alpha. + #[test] + fn transparency_is_only_needed_when_the_background_is_invisible_and_alpha_shows() { + let opaque = Composited { + rgba: vec![255, 0, 0, 255, 0, 255, 0, 255], + width: 2, + height: 1, + }; + let translucent = Composited { + rgba: vec![255, 0, 0, 255, 0, 255, 0, 128], + width: 2, + height: 1, + }; + + let visible = ProjectConfiguration::default(); + assert!(!needs_transparency(&translucent, &visible)); + + let mut invisible = ProjectConfiguration::default(); + invisible_background(&mut invisible); + assert!(!needs_transparency(&opaque, &invisible)); + assert!(needs_transparency(&translucent, &invisible)); + } + + /// The share encoding: PNG only when the output needs its alpha, JPEG + /// otherwise; Copy flattens onto white first. + #[test] + fn share_picks_jpeg_unless_alpha_is_needed_and_copy_flattens() { + let translucent = Composited { + rgba: vec![255, 0, 0, 128], + width: 1, + height: 1, + }; + + let visible = ProjectConfiguration::default(); + assert!(matches!( + encode_for_share(&translucent, &visible).unwrap(), + EncodedImage::Jpeg(_) + )); + + let mut invisible = ProjectConfiguration::default(); + invisible_background(&mut invisible); + assert!(matches!( + encode_for_share(&translucent, &invisible).unwrap(), + EncodedImage::Png(_) + )); + + // Copy over a visible background composites onto white: decoding the + // PNG back shows the half-red pixel blended with white. + let png = encode_for_copy(&translucent, &visible).unwrap(); + let decoded = image::load_from_memory(&png).unwrap().to_rgba8(); + let pixel = decoded.get_pixel(0, 0).0; + assert_eq!(pixel[3], 255); + assert_eq!(pixel[0], 255); + assert!(pixel[1] >= 127 && pixel[1] <= 128, "{pixel:?}"); + } + + /// `withWhiteBackground` leaves an opaque canvas untouched. + #[test] + fn flattening_an_opaque_canvas_is_the_identity() { + let out = Composited { + rgba: vec![10, 20, 30, 255, 40, 50, 60, 255], + width: 2, + height: 1, + }; + assert_eq!(flatten_onto_white(&out).rgba, out.rgba); + } + + /// A composite with no annotations is the frame itself over the white + /// output canvas, and an annotation past the edge pads the output -- + /// matching the source, the part outside the frame is clipped by the + /// working canvas, so the pad is background-coloured. + #[test] + fn composite_passes_the_frame_through_and_pads_for_overflow() { + let raw = RawFrame { + rgba: [0u8, 0, 255, 255].repeat(16), // 4x4 solid blue + width: 4, + height: 4, + base_width: 4, + base_height: 4, + }; + let config = ProjectConfiguration::default(); + let out = composite(&raw, &config); + assert_eq!((out.width, out.height), (4, 4)); + assert_eq!(&out.rgba[..4], &[0, 0, 255, 255]); + + // A rectangle hanging entirely past the right edge: its stroke band + // (x 4..12 at width 4) misses the 4px working canvas, so -- like the + // source -- the expansion pads white with nothing drawn in it. + let mut with_overflow = ProjectConfiguration::default(); + with_overflow + .annotations + .push(annotation(AnnotationType::Rectangle, 6., 0., 4., 4.)); + let out = composite(&raw, &with_overflow); + assert_eq!((out.width, out.height), (10, 4)); + // Column 0 is still the frame's blue... + assert_eq!(&out.rgba[..4], &[0, 0, 255, 255]); + // ...and the padded margin past the frame is the white fill. + let margin = 9 * 4; + assert_eq!(&out.rgba[margin..margin + 4], &[255, 255, 255, 255]); + } + + /// Premultiply/demultiply round-trips opaque pixels exactly; a fully + /// transparent pixel's colour is unrecoverable by definition and zeroes. + #[test] + fn premultiply_roundtrip_is_exact_for_opaque_pixels() { + let mut rgba = vec![13, 200, 91, 255, 1, 2, 3, 0]; + premultiply(&mut rgba); + demultiply(&mut rgba); + assert_eq!(rgba, vec![13, 200, 91, 255, 0, 0, 0, 0]); + } +} diff --git a/apps/desktop-gpui/src/theme.rs b/apps/desktop-gpui/src/theme.rs index d93db4e661..e6ed855af9 100644 --- a/apps/desktop-gpui/src/theme.rs +++ b/apps/desktop-gpui/src/theme.rs @@ -361,11 +361,19 @@ pub struct Theme { pub blue_9: Rgba, pub blue_10: Rgba, pub blue_11: Rgba, + /// The screenshot editor's layers panel selected-row label + /// (`text-blue-12`). Stock Radix -- `theme.css` overrides no blue-12. + pub blue_12: Rgba, pub red_2: Rgba, + /// The layers panel's delete-hover wash (`hover:bg-red-3`). Stock Radix. + pub red_3: Rgba, pub red_4: Rgba, pub red_9: Rgba, pub red_10: Rgba, + /// The layers panel's delete-hover glyph (`hover:text-red-11`). Stock + /// Radix. + pub red_11: Rgba, /// The settings page's one non-gray, non-blue accent: the "recommended Cap /// windows are not excluded" warning is `border-amber-6 bg-amber-3/30 @@ -468,11 +476,14 @@ impl Theme { blue_9: rgb(0x0090ff), blue_10: rgb(0x0588f0), blue_11: rgb(0x0d74ce), + blue_12: rgb(0x113264), red_2: rgb(0xfff7f7), + red_3: rgb(0xfeebec), red_4: rgb(0xffdbdc), red_9: rgb(0xe5484d), red_10: rgb(0xdc3e42), + red_11: rgb(0xce2c31), amber_3: rgb(0xfff7c2), amber_6: rgb(0xf3d673), @@ -520,11 +531,14 @@ impl Theme { blue_9: rgb(0x0090ff), blue_10: rgb(0x3b9eff), blue_11: rgb(0x70b8ff), + blue_12: rgb(0xc2e6ff), red_2: rgb(0x201314), + red_3: rgb(0x3b1219), red_4: rgb(0x500f1c), red_9: rgb(0xe5484d), red_10: rgb(0xec5d5e), + red_11: rgb(0xff9592), amber_3: rgb(0x302008), amber_6: rgb(0x5c3d05), diff --git a/scripts/local-cargo.mjs b/scripts/local-cargo.mjs deleted file mode 100644 index c3ac5a419a..0000000000 --- a/scripts/local-cargo.mjs +++ /dev/null @@ -1,56 +0,0 @@ -import { spawn, spawnSync } from "node:child_process"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -function findOnPath(name) { - const command = process.platform === "win32" ? "where.exe" : "which"; - const result = spawnSync(command, [name], { encoding: "utf8" }); - if (result.status !== 0) return null; - return result.stdout.trim().split(/\r?\n/).find(Boolean) ?? null; -} - -export function resolveLocalCargo() { - if (process.env.CAP_USE_CINDER === "0") return "cargo"; - if (process.env.CARGO) return process.env.CARGO; - if (process.platform !== "darwin") return "cargo"; - return findOnPath("cinder") ?? "cargo"; -} - -export function usesCinder(cargo = resolveLocalCargo()) { - return path.basename(cargo, ".exe") === "cinder"; -} - -export function localCargoEnv(env = process.env) { - return { ...env, CARGO: resolveLocalCargo() }; -} - -export function withTauriDevRunner(args, cargo = resolveLocalCargo()) { - if (!usesCinder(cargo) || args.includes("--runner")) return args; - const tauriDev = args.findIndex( - (argument, index) => argument === "tauri" && args[index + 1] === "dev", - ); - if (tauriDev === -1) return args; - return [...args, "--runner", cargo]; -} - -const isMain = - process.argv[1] !== undefined && - path.resolve(process.argv[1]) === fileURLToPath(import.meta.url); - -if (isMain) { - const cargo = resolveLocalCargo(); - const args = withTauriDevRunner(process.argv.slice(2), cargo); - if (args.length === 0) { - process.stdout.write(`${cargo}\n`); - process.exit(0); - } - if (args[0] === "cargo") args[0] = cargo; - const child = spawn(args[0], args.slice(1), { - stdio: "inherit", - env: { ...process.env, CARGO: cargo }, - }); - child.on("exit", (code, signal) => { - if (signal) process.kill(process.pid, signal); - else process.exit(code ?? 1); - }); -} From 90d96a5340bf34e3ccc5907bc0e3036810595252 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:03:59 +0100 Subject: [PATCH 141/146] feat(desktop-gpui): bring the camera preview bubble to full parity Live mirroring through a hardware VTPixelRotationSession flip (preview-only, matching the Tauri bubble), and live background blur through a dedicated worker thread that owns a wgpu device and runs the same cap-camera-effects BlurProcessor the recordings get, so the preview shows exactly what will be baked. The blur-off path keeps the existing zero-copy IOSurface paint untouched (p50 212us). Also ported from camera.tsx: the always-dark chrome, the 24px shipped radius, the bracket resize handles with diagonal cursors, the camera-issue overlay, top-left-anchored resize clamped into the monitor, and debounced position persistence under the shared store's cameraWindowPosition keys so both apps remember one bubble position. --- apps/desktop-gpui/src/camera_blur.rs | 336 +++++++++ apps/desktop-gpui/src/camera_window.rs | 937 +++++++++++++++++++++---- 2 files changed, 1152 insertions(+), 121 deletions(-) create mode 100644 apps/desktop-gpui/src/camera_blur.rs diff --git a/apps/desktop-gpui/src/camera_blur.rs b/apps/desktop-gpui/src/camera_blur.rs new file mode 100644 index 0000000000..d4a4b08eb5 --- /dev/null +++ b/apps/desktop-gpui/src/camera_blur.rs @@ -0,0 +1,336 @@ +//! Live background blur for the camera preview bubble -- the gpui counterpart +//! of the Tauri preview's blur path (`src-tauri/src/camera.rs:1328-1546`). +//! +//! The Tauri native preview renders every frame through its own wgpu device: +//! the camera IOSurface is imported as a texture, `cap_camera_effects:: +//! BlurProcessor` segments + blurs it, and the output lands in the preview +//! surface. This app paints CVPixelBuffers directly (`paint_surface_fitted`), +//! so the same pipeline is bent into surface-in / surface-out form: +//! +//! 1. [`crate::camera_window`]'s `FrameConverter` hands over the converted +//! BGRA IOSurface pixel buffer (already downscaled to [`BLUR_MAX_DIMS`] and +//! mirrored when the toolbar says so). +//! 2. The buffer's IOSurface is imported as a `Bgra8Unorm` wgpu texture +//! through `cap-rendering`'s `iosurface_texture` seam -- the exact import +//! the Tauri `NativeFrameConverter` uses (`camera_native.rs:301-310`). +//! Imports are cached per ring slot (keyed by pixel-buffer pointer + +//! ring generation), so the steady state creates no textures. +//! 3. `BlurProcessor::process_into_encoder` runs the segmentation mask + +//! separable blur + composite, with the preview's 150ms inference interval +//! (`CAMERA_PREVIEW_BLUR_INFERENCE_INTERVAL`, `camera.rs:50`). +//! 4. `cap_rendering::RgbaToBgraSurfaceConverter` blits the RGBA output into +//! a BGRA IOSurface-backed CVPixelBuffer from its own ring, and +//! `PendingSurface::wait` blocks until the GPU has finished writing it, so +//! the buffer handed back to the window is always complete when painted. +//! +//! Everything runs on one dedicated `camera-blur` thread (the Tauri preview +//! renders on a dedicated thread too, `camera.rs:466-490`), so the ~10ms +//! CoreML/CPU segmentation inference every 150ms never blocks the UI thread. +//! The channel back to the window is how "what you see is what records" holds: +//! this is the same `BlurProcessor` the editor/export camera layer runs +//! (`cap-rendering/src/lib.rs:5742`), fed by the same `BackgroundBlurMode` +//! the recording bridge writes into `project-config.json`. +//! +//! Failure degrades exactly like Tauri's `ensure_blur_processor` +//! (`camera.rs:1486-1519`): if the device, the ONNX runtime or the model +//! cannot be brought up, the worker exits, the window notices the closed +//! channel and keeps painting raw frames -- the toggle still cycles and +//! persists, nothing crashes. + +#![cfg(target_os = "macos")] + +use std::sync::OnceLock; +use std::time::Duration; + +use anyhow::{Context as _, anyhow}; +use cap_rendering::{ + RgbaToBgraSurfaceConverter, + iosurface_texture::{IOSurfaceTextureCache, import_metal_texture_to_wgpu}, +}; +use cidre::{arc, cv}; + +/// `CAMERA_PREVIEW_BLUR_MAX_TEXTURE_WIDTH/HEIGHT` (`camera.rs:46-47`): with +/// blur on, the Tauri preview processes at most 640x360 -- the blur passes +/// and the 256x256 segmentation downsample get a bounded input no matter what +/// the camera delivers. The `FrameConverter` applies this cap during its +/// hardware conversion, so the scale costs nothing extra. +pub const BLUR_MAX_DIMS: (usize, usize) = (640, 360); + +/// `CAMERA_PREVIEW_BLUR_INFERENCE_INTERVAL` (`camera.rs:50`). +const BLUR_INFERENCE_INTERVAL: Duration = Duration::from_millis(150); + +/// `LOW_SPEC_PREVIEW_RAM_THRESHOLD_BYTES` (`camera.rs:66`): machines at or +/// under 8GB never spin up the ONNX/wgpu blur processor -- the heaviest cost +/// in the preview. The toggle still cycles and persists; it just takes no +/// visual effect, exactly like `ensure_blur_processor`'s low-spec early +/// return (`camera.rs:1491-1498`). +const LOW_SPEC_RAM_THRESHOLD_BYTES: u64 = 8 * 1024 * 1024 * 1024; + +/// The Tauri check reads total RAM once at startup (`init_preview_profile`, +/// `camera.rs:75-87`, via sysinfo); `hw.memsize` is the same number without +/// the dependency. +pub fn is_low_spec_preview() -> bool { + static LOW_SPEC: OnceLock = OnceLock::new(); + *LOW_SPEC.get_or_init(|| { + let mut bytes: u64 = 0; + let mut len = std::mem::size_of::(); + let result = unsafe { + libc::sysctlbyname( + c"hw.memsize".as_ptr(), + &mut bytes as *mut u64 as *mut libc::c_void, + &mut len, + std::ptr::null_mut(), + 0, + ) + }; + let low_spec = result == 0 && bytes > 0 && bytes <= LOW_SPEC_RAM_THRESHOLD_BYTES; + if low_spec { + tracing::info!( + total_ram_mb = bytes / 1_048_576, + "low-spec camera preview profile: background blur preview disabled" + ); + } + low_spec + }) +} + +/// A CVPixelBuffer crossing the worker boundary. CF objects have atomic +/// refcounts; same contract as `FrameConverter` and cap-rendering's +/// `SurfaceFrame`. +pub struct SendPixelBuf(pub arc::R); +unsafe impl Send for SendPixelBuf {} + +pub struct BlurJob { + /// Converted BGRA IOSurface pixel buffer from the window's ring. + pub buffer: SendPixelBuf, + pub width: u32, + pub height: u32, + /// The `FrameConverter` ring generation the buffer belongs to; a rebuilt + /// ring invalidates the worker's imported-texture cache so a recycled + /// allocation address cannot alias a stale import. + pub ring_generation: u64, + pub mode: cap_camera_effects::BlurMode, +} + +pub struct BlurOutput { + /// Blurred BGRA IOSurface pixel buffer, GPU work complete. + pub buffer: SendPixelBuf, + pub width: u32, + pub height: u32, +} + +/// The worker loop. Runs until the job sender is dropped (blur switched off, +/// window closed) or an unrecoverable error; either way every GPU/ONNX +/// resource is dropped on exit, the same release-on-off behaviour as +/// `release_blur_resources` (`camera.rs:1477-1484`). +pub fn run(jobs: flume::Receiver, results: flume::Sender) { + // Current-thread runtime for `request_adapter` and the tokio yields + // inside `PendingSurface::wait`. + let runtime = match tokio::runtime::Builder::new_current_thread() + .enable_time() + .build() + { + Ok(runtime) => runtime, + Err(error) => { + tracing::warn!("camera blur worker runtime failed: {error}"); + return; + } + }; + + let Some((device, queue)) = shared_device(&runtime) else { + return; + }; + + let mut worker = match Worker::new(device, queue) { + Ok(worker) => worker, + Err(error) => { + tracing::warn!("camera blur preview unavailable: {error:#}"); + return; + } + }; + + while let Ok(mut job) = jobs.recv() { + // Latest-wins, like the Tauri render loop draining `camera_rx` + // (`camera.rs:1018-1020`). + while let Ok(newer) = jobs.try_recv() { + job = newer; + } + match worker.process(&runtime, job) { + Ok(output) => { + if results.send(output).is_err() { + return; + } + } + Err(error) => { + tracing::warn!("camera blur preview stopped: {error:#}"); + return; + } + } + } +} + +/// One device + queue for the app's blur lifetime, latched like Tauri's +/// `blur_processor_init_attempted`: a failed bring-up is not retried on every +/// toggle. The Tauri preview keeps its device for the window's lifetime +/// (`camera.rs:573-583`, `LowPower`, downlevel limits); only the ONNX/blur +/// resources are per-enable. +fn shared_device(runtime: &tokio::runtime::Runtime) -> Option<(wgpu::Device, wgpu::Queue)> { + static DEVICE: OnceLock> = OnceLock::new(); + DEVICE + .get_or_init(|| { + runtime.block_on(async { + let instance = cap_rendering::create_wgpu_instance_sync(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::LowPower, + force_fallback_adapter: cap_rendering::force_software_wgpu_adapter(), + compatible_surface: None, + }) + .await + .map_err(|error| { + tracing::warn!("camera blur adapter unavailable: {error}"); + }) + .ok()?; + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor { + label: Some("Camera Preview Blur"), + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()), + memory_hints: Default::default(), + trace: wgpu::Trace::Off, + }) + .await + .map_err(|error| { + tracing::warn!("camera blur device unavailable: {error}"); + }) + .ok()?; + Some((device, queue)) + }) + }) + .clone() +} + +struct Worker { + device: wgpu::Device, + queue: wgpu::Queue, + cache: IOSurfaceTextureCache, + processor: cap_camera_effects::BlurProcessor, + converter: RgbaToBgraSurfaceConverter, + /// Imported input textures keyed by pixel-buffer pointer. The converter + /// ring is four fixed buffers, so four entries cover the steady state and + /// nothing is created per frame (stricter than the Tauri path, which + /// re-imports each frame, `camera_native.rs:301-310`). + imported: Vec<(usize, wgpu::Texture)>, + imported_generation: u64, + frame_number: u32, +} + +impl Worker { + fn new(device: wgpu::Device, queue: wgpu::Queue) -> anyhow::Result { + let mut processor = + cap_camera_effects::BlurProcessor::new(&device, wgpu::TextureFormat::Rgba8Unorm) + .context("blur processor init")?; + processor.set_inference_interval(BLUR_INFERENCE_INTERVAL); + let converter = + RgbaToBgraSurfaceConverter::new(&device).map_err(|error| anyhow!("{error}"))?; + let cache = IOSurfaceTextureCache::new().context("Metal device unavailable")?; + tracing::info!("camera blur preview initialized"); + Ok(Self { + device, + queue, + cache, + processor, + converter, + imported: Vec::new(), + imported_generation: 0, + frame_number: 0, + }) + } + + fn process( + &mut self, + runtime: &tokio::runtime::Runtime, + job: BlurJob, + ) -> anyhow::Result { + let mode = job.mode; + let (width, height) = (job.width, job.height); + let texture = self.input_texture(&job)?.clone(); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Camera Preview Blur"), + }); + self.processor.process_into_encoder( + &self.device, + &self.queue, + &texture, + &mut encoder, + mode, + ); + let output = self + .processor + .process_returning_output() + .context("blur output missing")?; + + self.frame_number = self.frame_number.wrapping_add(1); + let pending = self + .converter + .encode( + &self.device, + &mut encoder, + output, + width, + height, + self.frame_number, + 30, + ) + .map_err(|error| anyhow!("{error}"))?; + self.queue.submit(std::iter::once(encoder.finish())); + + let frame = runtime + .block_on(pending.wait(&self.device, &self.queue)) + .map_err(|error| anyhow!("{error}"))?; + Ok(BlurOutput { + buffer: SendPixelBuf(frame.pixel_buffer), + width: frame.width, + height: frame.height, + }) + } + + fn input_texture(&mut self, job: &BlurJob) -> anyhow::Result<&wgpu::Texture> { + if self.imported_generation != job.ring_generation { + self.imported.clear(); + self.imported_generation = job.ring_generation; + } + let key = &*job.buffer.0 as *const cv::PixelBuf as usize; + if let Some(index) = self.imported.iter().position(|(k, _)| *k == key) { + return Ok(&self.imported[index].1); + } + let io_surface = job + .buffer + .0 + .io_surf() + .context("pixel buffer has no IOSurface")?; + // A `Bgra8Unorm` binding samples as RGBA-ordered floats, so the blur + // pipelines (all `Float { filterable: true }` layouts) and the 256px + // segmentation downsample read correct channels with no extra + // conversion pass. + let metal = self + .cache + .create_bgra_texture(io_surface, job.width, job.height) + .map_err(|error| anyhow!("{error}"))?; + let texture = import_metal_texture_to_wgpu( + &self.device, + &metal, + wgpu::TextureFormat::Bgra8Unorm, + job.width, + job.height, + Some("Camera Blur Input"), + ) + .map_err(|error| anyhow!("{error}"))?; + self.imported.push((key, texture)); + Ok(&self.imported.last().expect("just pushed").1) + } +} diff --git a/apps/desktop-gpui/src/camera_window.rs b/apps/desktop-gpui/src/camera_window.rs index e06cbdcecc..282686a296 100644 --- a/apps/desktop-gpui/src/camera_window.rs +++ b/apps/desktop-gpui/src/camera_window.rs @@ -1,14 +1,19 @@ //! The camera preview bubble -- `camera.tsx` + `CameraPreviewChrome.tsx`, //! natively. //! -//! Layout parity: a transparent window, 56px toolbar strip on top (chrome -//! appears on hover), preview container below it clipped to the shape -//! (round / square / full), four corner resize handles below the toolbar, +//! Layout parity: a transparent always-dark window (`camera.tsx:128` forces +//! the dark class), 56px toolbar strip on top (chrome appears on hover), +//! preview container below it clipped to the shape (round / square / full), +//! four corner resize handles with the bracket visuals below the toolbar, //! drag-anywhere to move the window. Frames arrive as CoreVideo pixel buffers //! from the app-scoped [`CameraFeed`]; VideoToolbox converts them to BGRA -//! IOSurfaces and gpui paints those directly via `paint_surface_fitted` -//! (cover-fit + rounded clip on the primitive) -- no CPU pixel copies and no -//! sprite-atlas uploads on the frame path. +//! IOSurfaces (and flips them in hardware when mirrored) and gpui paints +//! those directly via `paint_surface_fitted` (cover-fit + rounded clip on the +//! primitive) -- no CPU pixel copies and no sprite-atlas uploads on the frame +//! path. With background blur on, the converted surface takes a detour +//! through the [`crate::camera_blur`] worker -- the same +//! `cap_camera_effects::BlurProcessor` the recording's project config points +//! the editor at -- and the blurred surface is painted instead. //! //! Invalidation mirrors the editor window: each frame notifies only the //! [`CameraPreviewView`] child entity, while the toolbar chrome lives in a @@ -21,13 +26,25 @@ //! Verified live on a real C920: 29.9fps delivered, 29.9fps painted with no //! per-frame refresh. //! -//! Known deviations (also in the README): no mirroring (the toolbar button is -//! present but disabled), background blur cycles/persists but does not -//! process frames yet, and the window position is not persisted per-monitor. +//! Window placement is the Tauri decision (`windows.rs:2278-2345`): the saved +//! per-monitor position when it is still on that monitor, else the saved +//! global position, else bottom-right of the main window's display; moves are +//! debounced into the shared store's `cameraWindowPosition` keys +//! (`lib.rs:4621-4634`), so the two apps remember one bubble position. +//! +//! Known deviations (also in the README): no 150ms opacity/translate +//! transitions on the toolbar and resize brackets (no animation pass in this +//! port), no `backdrop-blur-xs` behind the issue overlay (no per-element +//! backdrop blur hook in this gpui rev), no "Camera disconnected" overlay on +//! recording input loss (the Tauri `recordingEvent` InputLost seam has no +//! counterpart in this app yet), no `cursor-move` on the drag surface (gpui +//! has no 4-way move cursor; the resize handles do get their diagonal resize +//! cursors), and chrome state persists to `gpui-state.json` rather than +//! `localStorage`. use std::sync::{ Arc, - atomic::{AtomicU32, Ordering}, + atomic::{AtomicU32, AtomicU64, Ordering}, }; use std::time::{Duration, Instant}; @@ -37,13 +54,18 @@ use gpui::{ StatefulInteractiveElement as _, StyleRefinement, Styled, Subscription, WeakEntity, Window, div, prelude::FluentBuilder as _, px, size, svg, }; +use serde_json::Value; use crate::{ feeds::Feeds, + platform, store::{self, BlurMode, CameraShape, CameraWindowState}, theme::Theme, }; +#[cfg(target_os = "macos")] +use crate::camera_blur; + pub const CAMERA_MIN_SIZE: f32 = 150.; pub const CAMERA_MAX_SIZE: f32 = 600.; pub const CAMERA_DEFAULT_SIZE: f32 = 230.; @@ -76,23 +98,40 @@ pub fn window_size(state: &CameraWindowState, frame_aspect: Option) -> (f32 } /// 0..1 across the 150..600 size range -- drives the toolbar scale and the -/// square-shape corner radius, like `cameraToolbarScale` / -/// `cameraBorderRadius`. +/// issue overlay's text metrics, like `cameraToolbarScale` / +/// `cameraOverlayTextMetrics`. fn normalized_size(state: &CameraWindowState) -> f32 { (clamp_size(state.size) - CAMERA_MIN_SIZE) / (CAMERA_MAX_SIZE - CAMERA_MIN_SIZE) } +/// The visible bubble's corner radius. The shipped Tauri experience is the +/// legacy webview preview (`enable_native_camera_preview` defaults to false, +/// re-forced by the `native_camera_preview_default_rollback_v1` migration, +/// `general_settings.rs:259-261, 526-530`), whose video container is +/// `rounded-full` for round and `rounded-3xl` -- 24px -- for square and full +/// (`camera.tsx:805-807`). The `cameraBorderRadius` 3rem formula styles only +/// the native page's issue overlay, and the native WGSL mask uses its own +/// smaller radii; the 24px container is what users see. fn preview_radius(state: &CameraWindowState) -> f32 { match state.shape { CameraShape::Round => clamp_size(state.size) / 2., - // 3rem + normalized * 1.5rem. - _ => 48. + normalized_size(state) * 24., + _ => 24., } } #[cfg(target_os = "macos")] mod frame { - use cidre::{arc, cf, cv}; + use cidre::{arc, cf, cv, vt}; + + /// A converted preview frame: the BGRA IOSurface pixel buffer to paint or + /// blur, its dimensions, and the ring generation (bumped on every ring + /// rebuild so the blur worker's imported-texture cache can never alias a + /// recycled allocation). + pub struct Converted { + pub buffer: arc::R, + pub dims: (usize, usize), + pub generation: u64, + } /// Converts camera frames (typically `420v`) into BGRA IOSurface-backed /// pixel buffers that gpui paints directly via `paint_surface_fitted`. @@ -102,15 +141,32 @@ mod frame { /// row-copy into a `RenderImage` and the sprite-atlas re-upload /// (`camera-preview-convert-benchmark`: 196us -> 137us per 720p frame on /// the conversion alone, byte-identical output, before atlas savings). + /// The same transfer also downscales when a maximum size is given (blur + /// caps its input at 640x360, `camera.rs:46-47`), so the blur path's + /// scaling costs nothing extra. /// - /// The fixed ring keeps a buffer alive while gpui's scene still references + /// Mirroring is a second hardware pass: a `VTPixelRotationSession` with + /// `FlipHorizontalOrientation` flips the converted BGRA buffer into a + /// sibling ring. This is the preview-only mirror the Tauri bubble has -- + /// its shader flips the sampling UV (`camera.wgsl:97-100`), its legacy + /// page flips the canvas with `scaleX(-1)` (`camera.tsx:869`), and + /// neither touches the recorded camera track. + /// + /// The fixed rings keep a buffer alive while gpui's scene still references /// the previous one; four slots at camera rates leaves ~100ms before a /// slot is overwritten. pub struct FrameConverter { - session: arc::R, + session: arc::R, + /// `None` when unmirrored, or when the rotation session could not be + /// created (the preview then degrades to unmirrored, logged once). + flip_session: Option>, ring: Vec>, + mirror_ring: Vec>, next: usize, - dims: (usize, usize), + src_dims: (usize, usize), + dst_dims: (usize, usize), + mirrored: bool, + generation: u64, } // Only touched from the main thread; the CF objects have atomic refcounts. @@ -118,57 +174,129 @@ mod frame { const RING_SIZE: usize = 4; + fn buffer_ring(width: usize, height: usize) -> Option>> { + let io_surface_properties = cf::Dictionary::new(); + let keys: [&cf::Type; 2] = [ + cv::pixel_buffer::keys::io_surf_props().as_ref(), + cv::pixel_buffer::keys::metal_compatibility().as_ref(), + ]; + let values: [&cf::Type; 2] = [ + io_surface_properties.as_ref(), + cf::Boolean::value_true().as_ref(), + ]; + let attrs = cf::Dictionary::with_keys_values(&keys, &values)?; + (0..RING_SIZE) + .map(|_| { + cv::PixelBuf::new(width, height, cv::PixelFormat::_32_BGRA, Some(&attrs)) + .ok() + .filter(|buf| buf.io_surf().is_some()) + }) + .collect::>>() + } + + /// Scale-to-fit inside `max`, aspect preserved, never upscaling. + fn fit_within(src: (usize, usize), max: (usize, usize)) -> (usize, usize) { + let scale = (max.0 as f64 / src.0.max(1) as f64) + .min(max.1 as f64 / src.1.max(1) as f64) + .min(1.0); + if scale >= 1.0 { + return src; + } + ( + ((src.0 as f64 * scale).round() as usize).max(1), + ((src.1 as f64 * scale).round() as usize).max(1), + ) + } + impl FrameConverter { - fn new(width: usize, height: usize) -> Option { - let mut session = cidre::vt::PixelTransferSession::new().ok()?; + fn new( + src_dims: (usize, usize), + dst_dims: (usize, usize), + mirrored: bool, + generation: u64, + ) -> Option { + let mut session = vt::PixelTransferSession::new().ok()?; session.set_realtime(true).ok()?; - let io_surface_properties = cf::Dictionary::new(); - let keys: [&cf::Type; 2] = [ - cv::pixel_buffer::keys::io_surf_props().as_ref(), - cv::pixel_buffer::keys::metal_compatibility().as_ref(), - ]; - let values: [&cf::Type; 2] = [ - io_surface_properties.as_ref(), - cf::Boolean::value_true().as_ref(), - ]; - let attrs = cf::Dictionary::with_keys_values(&keys, &values)?; - let ring = (0..RING_SIZE) - .map(|_| { - cv::PixelBuf::new(width, height, cv::PixelFormat::_32_BGRA, Some(&attrs)) - .ok() - .filter(|buf| buf.io_surf().is_some()) - }) - .collect::>>()?; + + let flip_session = if mirrored { + let flip = vt::PixelRotationSession::new() + .ok() + .and_then(|mut session| { + session.set_horizontal_flip(true).ok().map(|_| session) + }); + if flip.is_none() { + tracing::warn!( + "VTPixelRotationSession unavailable; camera preview mirroring disabled" + ); + } + flip + } else { + None + }; + + let ring = buffer_ring(dst_dims.0, dst_dims.1)?; + let mirror_ring = if flip_session.is_some() { + buffer_ring(dst_dims.0, dst_dims.1)? + } else { + Vec::new() + }; + Some(Self { session, + flip_session, ring, + mirror_ring, next: 0, - dims: (width, height), + src_dims, + dst_dims, + mirrored, + generation, }) } - /// The frame as a BGRA IOSurface-backed pixel buffer. Recreates the - /// ring when the camera's frame size changes. + /// The frame as a BGRA IOSurface-backed pixel buffer, downscaled to + /// fit `max_dims` when given and horizontally flipped when + /// `mirrored`. Recreates the rings when the camera's frame size, the + /// target size or the mirror toggle changes. pub fn convert( this: &mut Option, frame: &cap_recording::NativeCameraFrame, - ) -> Option> { + max_dims: Option<(usize, usize)>, + mirrored: bool, + ) -> Option { let src = frame.sample_buf.image_buf()?; - let dims = (src.width(), src.height()); - if this.as_ref().is_none_or(|converter| converter.dims != dims) { - *this = Self::new(dims.0, dims.1); + let src_dims = (src.width(), src.height()); + let dst_dims = max_dims.map_or(src_dims, |max| fit_within(src_dims, max)); + if this.as_ref().is_none_or(|converter| { + converter.src_dims != src_dims + || converter.dst_dims != dst_dims + || converter.mirrored != mirrored + }) { + let generation = this.as_ref().map_or(0, |c| c.generation + 1); + *this = Self::new(src_dims, dst_dims, mirrored, generation); } let converter = this.as_mut()?; let dst = converter.ring[converter.next].clone(); - converter.next = (converter.next + 1) % converter.ring.len(); converter.session.transfer(&src, &dst).ok()?; - Some(dst) + let out = if let Some(flip) = &converter.flip_session { + let mut flipped = converter.mirror_ring[converter.next].clone(); + flip.rotate(&dst, &mut flipped).ok()?; + flipped + } else { + dst + }; + converter.next = (converter.next + 1) % RING_SIZE; + Some(Converted { + buffer: out, + dims: converter.dst_dims, + generation: converter.generation, + }) } } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ResizeCorner { NorthWest, NorthEast, @@ -199,13 +327,53 @@ struct ResizeDrag { start_position: gpui::Point, } -/// The per-frame half of the window: owns the latest converted frame and is -/// the only entity notified at camera rate. Chrome invalidation goes through -/// the parent [`CameraWindow`] instead, so a frame draw reuses the cached -/// toolbar subtree. +/// `cameraOverlayTextMetrics` (`camera.tsx:891-909`), rem resolved at 16px. +struct OverlayMetrics { + gap: f32, + max_width: f32, + line_height: f32, + message_size: f32, + title_size: f32, +} + +fn overlay_metrics(size: f32) -> OverlayMetrics { + let normalized = (clamp_size(size) - CAMERA_MIN_SIZE) / (CAMERA_MAX_SIZE - CAMERA_MIN_SIZE); + OverlayMetrics { + gap: (0.375 + normalized * 0.25) * 16., + max_width: (size / 16.).clamp(7.5, 18.) * 16., + line_height: (1.2 + normalized * 0.2) * 16., + message_size: (0.625 + normalized * 0.25) * 16., + title_size: (0.75 + normalized * 0.375) * 16., + } +} + +/// `camera_preview_error_message` (`lib.rs:755-774`) plus the fixed title +/// from `emit_camera_preview_error` (`lib.rs:776-784`). The error strings +/// come from the same `cap-recording` camera errors the Tauri app matches on. +fn camera_issue(error: &str) -> (&'static str, &'static str) { + let message = if error.contains("DeviceNotFound") { + "This camera is no longer available. Check that it is connected and allowed by system permissions." + } else if error.contains("CameraTimeout") { + "No frames were received from this camera. It may be closed, disconnected, covered, or in use by another app." + } else if error.contains("StartCapturing") { + "The system could not start this camera. It may be unavailable or in use by another app." + } else if error.contains("InvalidFormat") { + "This camera did not report a usable capture format." + } else { + "The selected camera could not be started. Choose another camera or reconnect this one." + }; + ("Camera unavailable", message) +} + +/// The per-frame half of the window: owns the latest converted (or blurred) +/// frame and is the only entity notified at camera rate. Chrome invalidation +/// goes through the parent [`CameraWindow`] instead, so a frame draw reuses +/// the cached toolbar subtree. struct CameraPreviewView { theme: Theme, radius: f32, + /// Clamped bubble size, for the issue overlay's scaled text metrics. + size: f32, #[cfg(target_os = "macos")] latest_frame: Option, frame_dims: Option<(usize, usize)>, @@ -221,7 +389,13 @@ struct CameraPreviewView { } impl CameraPreviewView { - fn new(theme: Theme, radius: f32, paints: Arc, cx: &mut Context) -> Self { + fn new( + theme: Theme, + radius: f32, + size: f32, + paints: Arc, + cx: &mut Context, + ) -> Self { let feeds = Feeds::global(cx); let camera_error = feeds.read(cx).camera_error.clone(); let feeds_subscription = cx.observe(&feeds, |this: &mut Self, feeds, cx| { @@ -234,6 +408,7 @@ impl CameraPreviewView { Self { theme, radius, + size, #[cfg(target_os = "macos")] latest_frame: None, frame_dims: None, @@ -255,10 +430,10 @@ impl CameraPreviewView { cx.notify(); } - fn set_chrome(&mut self, theme: Theme, radius: f32, cx: &mut Context) { - if self.theme.appearance != theme.appearance || self.radius != radius { - self.theme = theme; + fn set_chrome(&mut self, radius: f32, size: f32, cx: &mut Context) { + if self.radius != radius || self.size != size { self.radius = radius; + self.size = size; cx.notify(); } } @@ -316,19 +491,59 @@ impl Render for CameraPreviewView { let showing_frame = false; if !showing_frame { - let message = self - .camera_error - .clone() - .unwrap_or_else(|| "Loading camera...".into()); container = container.child( div() .size_full() .flex() .items_center() .justify_center() - .text_size(px(14.)) + .text_size(px(16.)) .text_color(theme.gray_11) - .child(message), + .child("Loading camera..."), + ); + } + + // `CameraIssueOverlay` (`camera.tsx:911-955`): a black/75 wash over + // the preview with a centred, size-scaled title + message. The + // `backdrop-blur-xs` behind it has no per-element hook in this gpui + // rev (the recording overlay documents the same gap). + if let Some(error) = self.camera_error.clone() { + let (title, message) = camera_issue(&error); + let metrics = overlay_metrics(self.size); + container = container.child( + div() + .absolute() + .inset_0() + .flex() + .items_center() + .justify_center() + .overflow_hidden() + .rounded(px(radius)) + .bg(gpui::hsla(0., 0., 0., 0.75)) + .px(px(16.)) + .child( + div() + .flex() + .flex_col() + .items_center() + .text_center() + .gap(px(metrics.gap)) + .max_w(px(metrics.max_width)) + .child( + div() + .text_size(px(metrics.title_size)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(gpui::white()) + .child(title), + ) + .child( + div() + .text_size(px(metrics.message_size)) + .line_height(px(metrics.line_height)) + .text_color(gpui::hsla(0., 0., 1., 0.75)) + .child(message), + ), + ), ); } @@ -364,13 +579,35 @@ impl Render for CameraToolbarView { } } +/// Channels to and from the `camera-blur` worker thread, plus the foreground +/// pump that paints its outputs. Dropping this closes the job channel, which +/// ends the thread and releases the ONNX/GPU resources -- the +/// `release_blur_resources` behaviour (`camera.rs:1477-1484`). +#[cfg(target_os = "macos")] +struct BlurBridge { + tx: flume::Sender, + /// The first blurred output may land while the window is inactive, where + /// a notify alone may not present (the unit-2 first-frame finding); the + /// pump answers it with one explicit `refresh`. + first_output_pending: bool, + _pump: gpui::Task<()>, +} + pub struct CameraWindow { theme: Theme, state: CameraWindowState, chrome_visible: bool, resizing: Option, + hovered_handle: Option, #[cfg(target_os = "macos")] converter: Option, + #[cfg(target_os = "macos")] + blur: Option, + /// Latched when the worker dies (device/ONNX bring-up failed); cleared + /// when the blur mode changes, which is the retry point -- the + /// `blur_processor_init_attempted` shape (`camera.rs:1500-1518`). + #[cfg(target_os = "macos")] + blur_failed: bool, preview: Entity, toolbar: Entity, frame_dims: Option<(usize, usize)>, @@ -380,28 +617,50 @@ pub struct CameraWindow { cadence_window_start: Instant, paints: Arc, paints_at_window_start: u32, + /// Debounce for the position save, the `queueCameraPositionSave` 200ms + /// (`camera.tsx:93-112`): only the newest generation writes. + position_save_generation: Arc, + last_saved_position: Option<(f32, f32)>, } impl CameraWindow { pub fn new(window: &mut Window, cx: &mut Context) -> Self { - crate::theme::bind_window(window, cx); - let theme = Theme::for_window(window, cx, false); + // `document.documentElement.classList.toggle("dark", true)` + // (`camera.tsx:128`): the bubble is always dark, whatever the app + // theme preference says. + platform::apply_window_theme(window, platform::ForcedAppearance::Dark); + let theme = Theme::dark(); let state = store::load().camera_window.unwrap_or_default(); let paints = Arc::new(AtomicU32::new(0)); let preview = cx.new({ let paints = paints.clone(); let radius = preview_radius(&state); - move |cx| CameraPreviewView::new(theme, radius, paints, cx) + let size = clamp_size(state.size); + move |cx| CameraPreviewView::new(theme, radius, size, paints, cx) }); let camera = cx.entity(); let toolbar = cx.new(|cx| CameraToolbarView::new(&camera, cx)); + + // `currentWindow.onMoved` + the 400ms position sync + // (`camera.tsx:167-216`): every move lands in the shared store, + // debounced, so the next open -- from either app -- restores it. + cx.observe_window_bounds(window, |this, window, cx| { + this.queue_position_save(window, cx); + }) + .detach(); + Self { theme, state, chrome_visible: false, resizing: None, + hovered_handle: None, #[cfg(target_os = "macos")] converter: None, + #[cfg(target_os = "macos")] + blur: None, + #[cfg(target_os = "macos")] + blur_failed: false, preview, toolbar, frame_dims: None, @@ -409,6 +668,8 @@ impl CameraWindow { cadence_window_start: Instant::now(), paints, paints_at_window_start: 0, + position_save_generation: Arc::new(AtomicU64::new(0)), + last_saved_position: None, } } @@ -427,17 +688,58 @@ impl CameraWindow { use core_foundation::base::TCFType as _; use core_video::pixel_buffer::{CVPixelBuffer, CVPixelBufferRef}; - if let Some(buffer) = frame::FrameConverter::convert(&mut self.converter, &frame) { + let blur_mode = self.active_blur_mode(); + let max_dims = blur_mode.is_some().then_some(camera_blur::BLUR_MAX_DIMS); + if let Some(converted) = frame::FrameConverter::convert( + &mut self.converter, + &frame, + max_dims, + self.state.mirrored, + ) { let first_frame = self.frame_dims.is_none(); - let dims = (buffer.width(), buffer.height()); + let dims = converted.dims; let dims_changed = self.frame_dims != Some(dims); self.frame_dims = Some(dims); - let raw = buffer.as_ref() as *const cidre::cv::PixelBuf as CVPixelBufferRef; - let buffer = unsafe { CVPixelBuffer::wrap_under_get_rule(raw) }; - self.preview - .update(cx, |preview, cx| preview.set_frame(buffer, dims, cx)); + + let mut paint_raw = blur_mode.is_none(); + if let Some(mode) = blur_mode { + let job = camera_blur::BlurJob { + buffer: camera_blur::SendPixelBuf(converted.buffer.clone()), + width: dims.0 as u32, + height: dims.1 as u32, + ring_generation: converted.generation, + mode, + }; + match self.ensure_blur_bridge(window, cx).tx.try_send(job) { + Ok(()) => {} + // Worker busy: drop this frame and keep the last + // painted one -- the bounded(1) latest-wins shape of + // the Tauri preview's `camera_tx` (`camera.rs:424`). + Err(flume::TrySendError::Full(_)) => {} + Err(flume::TrySendError::Disconnected(_)) => { + // The worker could not come up (no device, no + // ONNX runtime, low-level failure). Same degrade + // as `ensure_blur_processor` returning false: + // raw frames, latched until the mode changes. + tracing::warn!( + "camera blur worker unavailable; preview continues unblurred" + ); + self.blur = None; + self.blur_failed = true; + paint_raw = true; + } + } + } + + if paint_raw { + let raw = + converted.buffer.as_ref() as *const cidre::cv::PixelBuf as CVPixelBufferRef; + let buffer = unsafe { CVPixelBuffer::wrap_under_get_rule(raw) }; + self.preview + .update(cx, |preview, cx| preview.set_frame(buffer, dims, cx)); + } if dims_changed { - self.apply_window_size(window); + self.apply_window_size(window, cx); cx.notify(); } if first_frame && !window.is_window_active() { @@ -449,7 +751,7 @@ impl CameraWindow { } #[cfg(not(target_os = "macos"))] { - let _ = (frame, window); + let _ = (frame, window, cx); } self.frames_in_window += 1; @@ -473,6 +775,93 @@ impl CameraWindow { } } + /// The blur mode frames should be processed with right now: `None` when + /// off, latched off after a worker failure, and always `None` on low-spec + /// machines (`ensure_blur_processor`'s early return, `camera.rs:1491-1498` + /// -- the toggle still cycles and persists there too). + #[cfg(target_os = "macos")] + fn active_blur_mode(&self) -> Option { + if self.blur_failed || camera_blur::is_low_spec_preview() { + return None; + } + match self.state.background_blur { + BlurMode::Off => None, + BlurMode::Light => Some(cap_camera_effects::BlurMode::Light), + BlurMode::Heavy => Some(cap_camera_effects::BlurMode::Heavy), + } + } + + #[cfg(target_os = "macos")] + fn ensure_blur_bridge(&mut self, window: &Window, cx: &mut Context) -> &BlurBridge { + if self.blur.is_none() { + let (job_tx, job_rx) = flume::bounded::(1); + let (out_tx, out_rx) = flume::bounded::(2); + if let Err(error) = std::thread::Builder::new() + .name("camera-blur".into()) + .spawn(move || camera_blur::run(job_rx, out_tx)) + { + // job_rx is gone; the caller's try_send sees Disconnected and + // latches the raw-frame fallback. + tracing::warn!("camera blur thread failed to spawn: {error}"); + } + let handle = window.window_handle(); + let pump = cx.spawn(async move |this, cx| { + while let Ok(output) = out_rx.recv_async().await { + let first = match this.update(cx, |this: &mut CameraWindow, cx| { + this.blurred_frame_arrived(output, cx) + }) { + Ok(first) => first, + Err(_) => break, + }; + if first { + handle + .update(cx, |_, window, _| { + if !window.is_window_active() { + window.refresh(); + } + }) + .ok(); + } + } + }); + self.blur = Some(BlurBridge { + tx: job_tx, + first_output_pending: true, + _pump: pump, + }); + } + self.blur.as_ref().expect("just ensured") + } + + /// A blurred surface came back from the worker; paint it. Returns whether + /// it was the bridge's first output (the pump may owe an explicit + /// refresh). + #[cfg(target_os = "macos")] + fn blurred_frame_arrived( + &mut self, + output: camera_blur::BlurOutput, + cx: &mut Context, + ) -> bool { + // A stale output can land after the mode flips back to Off; the raw + // path is already painting again, so drop it. + if self.state.background_blur == BlurMode::Off { + return false; + } + let first = self + .blur + .as_mut() + .is_some_and(|bridge| std::mem::take(&mut bridge.first_output_pending)); + + use core_foundation::base::TCFType as _; + use core_video::pixel_buffer::{CVPixelBuffer, CVPixelBufferRef}; + let dims = (output.width as usize, output.height as usize); + let raw = &*output.buffer.0 as *const cidre::cv::PixelBuf as CVPixelBufferRef; + let buffer = unsafe { CVPixelBuffer::wrap_under_get_rule(raw) }; + self.preview + .update(cx, |preview, cx| preview.set_frame(buffer, dims, cx)); + first + } + fn frame_aspect(&self) -> Option { self.frame_dims .map(|(width, height)| width as f32 / height.max(1) as f32) @@ -485,10 +874,10 @@ impl CameraWindow { /// Pushes the chrome inputs the preview renders with (its own notify is /// the only thing that busts its cache). fn sync_preview_chrome(&mut self, cx: &mut Context) { - let theme = self.theme; let radius = preview_radius(&self.state); + let size = clamp_size(self.state.size); self.preview - .update(cx, |preview, cx| preview.set_chrome(theme, radius, cx)); + .update(cx, |preview, cx| preview.set_chrome(radius, size, cx)); } fn mutate_state( @@ -497,17 +886,87 @@ impl CameraWindow { cx: &mut Context, mutate: impl FnOnce(&mut CameraWindowState), ) { + #[cfg(target_os = "macos")] + let blur_before = self.state.background_blur; mutate(&mut self.state); self.state.size = clamp_size(self.state.size); - self.apply_window_size(window); + #[cfg(target_os = "macos")] + { + if self.state.background_blur != blur_before { + // Changing the mode is the retry point after a failed + // bring-up. + self.blur_failed = false; + } + if self.state.background_blur == BlurMode::Off { + // Ends the worker thread, dropping the ONNX session and every + // GPU texture -- `release_blur_resources` + // (`camera.rs:1477-1484`). + self.blur = None; + } + } + self.apply_window_size(window, cx); self.sync_preview_chrome(cx); self.persist(cx); cx.notify(); } - fn apply_window_size(&self, window: &mut Window) { + /// Resize to the state's dimensions, top-left anchored, clamped inside + /// the current monitor -- `resize_window` (`camera.rs:1683-1788`), which + /// runs on every size/shape/aspect change over there. gpui's + /// `Window::resize` is AppKit `setContentSize:`, which anchors the + /// *bottom*-left, so the anchor and the clamp both go through one native + /// `setFrame:` instead. + fn apply_window_size(&self, window: &mut Window, cx: &mut Context) { let (width, height) = window_size(&self.state, self.frame_aspect()); - window.resize(size(px(width), px(height))); + + let bounds = window.bounds(); + let (x, y) = (f32::from(bounds.origin.x), f32::from(bounds.origin.y)); + let (mut new_x, mut new_y) = (x, y); + if let Some(display_bounds) = window.display(cx).map(|display| display.bounds()) { + let (dx, dy) = ( + f32::from(display_bounds.origin.x), + f32::from(display_bounds.origin.y), + ); + let (dw, dh) = ( + f32::from(display_bounds.size.width), + f32::from(display_bounds.size.height), + ); + if new_x + width > dx + dw { + new_x = dx + dw - width; + } + if new_y + height > dy + dh { + new_y = dy + dh - height; + } + if new_x < dx { + new_x = dx; + } + if new_y < dy { + new_y = dy; + } + } + + if let Some(native) = platform::native_window(window) { + let (frame_x, frame_y, _, frame_height) = platform::window_frame(&native); + // AppKit y grows upward: keep the gpui-space top edge, then apply + // the clamp deltas (gpui +y = AppKit -y). + let appkit_x = frame_x + (new_x - x) as f64; + let appkit_y = (frame_y + frame_height) - (new_y - y) as f64 - height as f64; + // `setFrame:` synchronously re-enters gpui's own window + // callbacks, so it runs from a fresh runloop turn (the + // `set_window_frame` rule). + cx.spawn(async move |_, _| { + platform::set_window_frame( + &native, + appkit_x, + appkit_y, + width as f64, + height as f64, + ); + }) + .detach(); + } else { + window.resize(size(px(width), px(height))); + } } fn persist(&self, cx: &mut Context) { @@ -519,6 +978,32 @@ impl CameraWindow { .detach(); } + /// `queueCameraPositionSave` (`camera.tsx:93-112`): a 200ms debounce over + /// the window's logical top-left, skipped when it has not moved a whole + /// pixel. + fn queue_position_save(&mut self, window: &Window, cx: &mut Context) { + let origin = window.bounds().origin; + let (x, y) = (f32::from(origin.x), f32::from(origin.y)); + if self + .last_saved_position + .is_some_and(|(sx, sy)| (sx - x).abs() < 1. && (sy - y).abs() < 1.) + { + return; + } + self.last_saved_position = Some((x, y)); + let generation = self.position_save_generation.fetch_add(1, Ordering::AcqRel) + 1; + let generations = self.position_save_generation.clone(); + let executor = cx.background_executor().clone(); + cx.background_executor() + .spawn(async move { + executor.timer(Duration::from_millis(200)).await; + if generations.load(Ordering::Acquire) == generation { + persist_camera_position(x as f64, y as f64); + } + }) + .detach(); + } + fn close(&mut self, cx: &mut Context) { // Deselecting the camera is what closes the window (`Feeds` observes // the selection); mirrors the Tauri close button closing the webview @@ -527,14 +1012,14 @@ impl CameraWindow { } // Mirrors `ControlButton`'s prop list; a config struct would just rename - // the same nine things. + // the same eight things. No hover style on purpose: `ControlButton` + // (`CameraPreviewChrome.tsx:206-216`) styles only the pressed state. #[allow(clippy::too_many_arguments)] fn toolbar_button( &self, id: &'static str, icon: &'static str, pressed: bool, - enabled: bool, scale: f32, label: Option<&'static str>, cx: &mut Context, @@ -548,17 +1033,13 @@ impl CameraWindow { .when(pressed, |this| { this.bg(theme.gray_3).text_color(theme.gray_12) }) - .when(!enabled, |this| this.opacity(0.5)) - .when(enabled, |this| { - this.hover(|style| style.bg(theme.gray_3)) - .on_mouse_down(MouseButton::Left, |_, window, _| { - window.prevent_default(); - }) - .on_click(cx.listener(move |this, _, window, cx| { - cx.stop_propagation(); - on_click(this, window, cx); - })) + .on_mouse_down(MouseButton::Left, |_, window, _| { + window.prevent_default(); }) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + on_click(this, window, cx); + })) .occlude() .child( div() @@ -626,7 +1107,6 @@ impl CameraWindow { "close", "icons/circle-x.svg", false, - true, scale, None, cx, @@ -636,7 +1116,6 @@ impl CameraWindow { "size", "icons/enlarge.svg", self.state.size >= CAMERA_PRESET_LARGE, - true, scale, None, cx, @@ -654,7 +1133,6 @@ impl CameraWindow { "shape", shape_icon, self.state.shape != CameraShape::Round, - true, scale, None, cx, @@ -668,23 +1146,26 @@ impl CameraWindow { }) }, )) - // Mirroring needs a horizontal flip on the painted surface; - // this gpui rev has no such transform. Present but disabled. + // Preview-only, like both Tauri paths (the WGSL UV flip / + // the legacy `scaleX(-1)`); the recorded camera track is + // never mirrored by either app. .child(self.toolbar_button( "mirror", "icons/arrows.svg", - false, - false, + self.state.mirrored, scale, None, cx, - |_, _, _| {}, + |this, window, cx| { + this.mutate_state(window, cx, |state| { + state.mirrored = !state.mirrored; + }) + }, )) .child(self.toolbar_button( "blur", "icons/person-standing.svg", self.state.background_blur != BlurMode::Off, - true, scale, self.state.background_blur.label(), cx, @@ -697,7 +1178,16 @@ impl CameraWindow { ) } + /// `CameraResizeHandles` + `ResizeCornerHandle` + /// (`CameraPreviewChrome.tsx:218-357`): a 28px hit area per corner, with + /// a 14px white 2px-bordered bracket inset 6px, rounded 6px on its outer + /// corner. Opacity 0 hidden / 0.7 with chrome visible / 1.0 hovered or + /// resizing. The 150ms transition, the hover `scale-110` and the + /// `drop-shadow` filter have no hooks here (no animation pass, no + /// transform, and a box shadow would shadow the bracket's full rect, not + /// its L-shape). fn render_resize_handles(&self, cx: &mut Context) -> impl IntoElement { + let visible = self.chrome_visible || self.resizing.is_some(); let mut layer = div() .absolute() .top(px(CAMERA_TOOLBAR_HEIGHT)) @@ -712,26 +1202,99 @@ impl CameraWindow { ResizeCorner::SouthWest => "resize-sw", ResizeCorner::SouthEast => "resize-se", }; - let mut handle = div().id(id).absolute().size(px(16.)).occlude(); + let active = self + .resizing + .as_ref() + .is_some_and(|drag| drag.corner == corner) + || self.hovered_handle == Some(corner); + + let mut bracket = div() + .absolute() + .size(px(14.)) + .border_color(gpui::white()) + .opacity(if active { + 1.0 + } else if visible { + 0.7 + } else { + 0.0 + }); + bracket = match corner { + ResizeCorner::NorthWest => bracket + .top(px(6.)) + .left(px(6.)) + .border_t_2() + .border_l_2() + .rounded_tl(px(6.)), + ResizeCorner::NorthEast => bracket + .top(px(6.)) + .right(px(6.)) + .border_t_2() + .border_r_2() + .rounded_tr(px(6.)), + ResizeCorner::SouthWest => bracket + .bottom(px(6.)) + .left(px(6.)) + .border_b_2() + .border_l_2() + .rounded_bl(px(6.)), + ResizeCorner::SouthEast => bracket + .bottom(px(6.)) + .right(px(6.)) + .border_b_2() + .border_r_2() + .rounded_br(px(6.)), + }; + + let mut handle = div() + .id(id) + .absolute() + .size(px(28.)) + .occlude() + .child(bracket); + // `cursor-nw-resize` and friends (`CameraPreviewChrome.tsx:306-317`). handle = match corner { - ResizeCorner::NorthWest => handle.top_0().left_0(), - ResizeCorner::NorthEast => handle.top_0().right_0(), - ResizeCorner::SouthWest => handle.bottom_0().left_0(), - ResizeCorner::SouthEast => handle.bottom_0().right_0(), + ResizeCorner::NorthWest => handle + .top_0() + .left_0() + .cursor(gpui::CursorStyle::ResizeUpLeftDownRight), + ResizeCorner::NorthEast => handle + .top_0() + .right_0() + .cursor(gpui::CursorStyle::ResizeUpRightDownLeft), + ResizeCorner::SouthWest => handle + .bottom_0() + .left_0() + .cursor(gpui::CursorStyle::ResizeUpRightDownLeft), + ResizeCorner::SouthEast => handle + .bottom_0() + .right_0() + .cursor(gpui::CursorStyle::ResizeUpLeftDownRight), }; - layer = layer.child(handle.on_mouse_down( - MouseButton::Left, - cx.listener(move |this, event: &gpui::MouseDownEvent, window, cx| { - window.prevent_default(); - cx.stop_propagation(); - this.resizing = Some(ResizeDrag { - corner, - start_size: this.state.size, - start_position: event.position, - }); - cx.notify(); - }), - )); + layer = layer.child( + handle + .on_hover(cx.listener(move |this, hovered: &bool, _, cx| { + let next = if *hovered { Some(corner) } else { None }; + let relevant = *hovered || this.hovered_handle == Some(corner); + if relevant && this.hovered_handle != next { + this.hovered_handle = next; + cx.notify(); + } + })) + .on_mouse_down( + MouseButton::Left, + cx.listener(move |this, event: &gpui::MouseDownEvent, window, cx| { + window.prevent_default(); + cx.stop_propagation(); + this.resizing = Some(ResizeDrag { + corner, + start_size: this.state.size, + start_position: event.position, + }); + cx.notify(); + }), + ), + ); } layer } @@ -763,7 +1326,7 @@ impl CameraWindow { let next = clamp_size(drag.start_size + delta); if (next - self.state.size).abs() > 0.5 { self.state.size = next; - self.apply_window_size(window); + self.apply_window_size(window, cx); self.sync_preview_chrome(cx); cx.notify(); } @@ -778,10 +1341,7 @@ impl CameraWindow { } impl Render for CameraWindow { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - if self.theme.refresh(window, cx, false) { - self.sync_preview_chrome(cx); - } + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let resizing = self.resizing.is_some(); div() @@ -831,3 +1391,138 @@ impl Render for CameraWindow { .child(self.render_resize_handles(cx)) } } + +// --------------------------------------------------------------------------- +// Position persistence -- the shared store's `cameraWindowPosition` +// --------------------------------------------------------------------------- + +/// `WindowPosition` (`general_settings.rs:145-150`), camelCase in the store +/// like the rest of `general_settings`. `DisplayId` is the same +/// `scap_targets` type the Tauri app serializes, so the bytes interop. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct StoredWindowPosition { + x: f64, + y: f64, + #[serde(default)] + display_id: Option, +} + +fn display_contains(display: &scap_targets::Display, x: f64, y: f64) -> bool { + display.raw_handle().logical_bounds().is_some_and(|bounds| { + x >= bounds.position().x() + && x < bounds.position().x() + bounds.size().width() + && y >= bounds.position().y() + && y < bounds.position().y() + bounds.size().height() + }) +} + +/// `display_for_position` (`lib.rs:1612-1625`). +pub fn display_for_point(x: f64, y: f64) -> Option { + scap_targets::Display::list() + .into_iter() + .find(|display| display_contains(display, x, y)) +} + +/// `is_position_on_monitor_name` (`windows.rs:857-878`). +fn position_on_monitor_name(name: &str, x: f64, y: f64) -> bool { + scap_targets::Display::list() + .into_iter() + .any(|display| display.name().as_deref() == Some(name) && display_contains(&display, x, y)) +} + +/// `is_position_on_any_screen` (`windows.rs:880-896`). +fn any_display_contains(x: f64, y: f64) -> bool { + scap_targets::Display::list() + .into_iter() + .any(|display| display_contains(&display, x, y)) +} + +/// The saved position that is still valid for `preferred` -- the Tauri +/// restore decision (`windows.rs:2289-2321`): the per-monitor-name entry for +/// the preferred monitor when the point is still on a monitor of that name, +/// else the global `cameraWindowPosition` on the preferred monitor; with no +/// monitor name, the global position validated against its recorded display +/// (or any screen). +fn saved_position(preferred: &scap_targets::Display) -> Option<(f64, f64)> { + let settings = store::store_section(store::GENERAL_SETTINGS); + let parse = |value: &Value| serde_json::from_value::(value.clone()).ok(); + let global = settings.get("cameraWindowPosition").and_then(parse); + + let position = match preferred.name().filter(|name| !name.trim().is_empty()) { + Some(name) => settings + .get("cameraWindowPositionsByMonitorName") + .and_then(|value| value.as_object()) + .and_then(|map| map.get(&name)) + .and_then(parse) + .filter(|pos| position_on_monitor_name(&name, pos.x, pos.y)) + .or_else(|| global.filter(|pos| position_on_monitor_name(&name, pos.x, pos.y))), + None => global.filter(|pos| match &pos.display_id { + Some(id) => scap_targets::Display::from_id(id) + .is_some_and(|display| display_contains(&display, pos.x, pos.y)), + None => any_display_contains(pos.x, pos.y), + }), + }; + position.map(|pos| (pos.x, pos.y)) +} + +/// Where a fresh bubble opens: the saved position when still valid, else the +/// Tauri default slot -- bottom-right of the preferred monitor measured with +/// `DEFAULT_WINDOW_SIZE = 230.0 * 2.0` (`windows.rs:2160, 2340-2344`; the +/// constant, not the actual window size, is what the shipping app subtracts). +pub fn opening_position(preferred: &scap_targets::Display) -> (f32, f32) { + if let Some((x, y)) = saved_position(preferred) { + tracing::info!(x, y, "camera window restored to its saved position"); + return (x as f32, y as f32); + } + const TAURI_DEFAULT_WINDOW_SIZE: f64 = 230.0 * 2.0; + match preferred.raw_handle().logical_bounds() { + Some(bounds) => ( + (bounds.position().x() + bounds.size().width() - TAURI_DEFAULT_WINDOW_SIZE - 100.) + as f32, + (bounds.position().y() + bounds.size().height() - TAURI_DEFAULT_WINDOW_SIZE - 100.) + as f32, + ), + None => (100., 100.), + } +} + +/// `update_camera_window_position_settings` (`lib.rs:1645-1666`): the global +/// position plus the per-monitor-name map entry, both carrying the display +/// id. Runs on the background executor -- two store read-modify-writes. +fn persist_camera_position(x: f64, y: f64) { + let display = display_for_point(x, y); + let position = StoredWindowPosition { + x, + y, + display_id: display.as_ref().map(|display| display.id()), + }; + let Ok(value) = serde_json::to_value(&position) else { + return; + }; + store::set_store_setting( + store::GENERAL_SETTINGS, + "cameraWindowPosition", + value.clone(), + ); + + let monitor_name = display + .as_ref() + .and_then(|display| display.name()) + .filter(|name| !name.trim().is_empty()); + if let Some(name) = monitor_name { + let mut map = store::store_section(store::GENERAL_SETTINGS) + .remove("cameraWindowPositionsByMonitorName") + .and_then(|value| match value { + Value::Object(map) => Some(map), + _ => None, + }) + .unwrap_or_default(); + map.insert(name, value); + store::set_store_setting( + store::GENERAL_SETTINGS, + "cameraWindowPositionsByMonitorName", + Value::Object(map), + ); + } +} From 187b4e4dc1e6cb4c72922f317f2d60f85134debe Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:04:00 +0100 Subject: [PATCH 142/146] feat(desktop-gpui): handle every cap:// deep-link action deeplink.rs ports deeplink_actions.rs verbatim: StartRecording (with the name-to-target resolution and device selection), StopRecording, OpenEditor with the status gates, OpenSettings, the debug-gated pause/resume/camera actions, and the macOS file:// branch. Both delivery paths work: the GURL AppleEvent while running (buffer grown to 64KiB so JSON-bearing URLs are not truncated) and the launch argv under new-instance-wins single instancing. The host is matched whole against "action", no URL is ever built from parts, and parse failures never log URL contents, so an auth callback landing in an error arm cannot leak its token. The parser carries the Tauri file's own tests plus hostile-input coverage. --- apps/desktop-gpui/src/deeplink.rs | 920 +++++++++++++++++++++++ apps/desktop-gpui/src/platform.rs | 13 +- apps/desktop-gpui/src/single_instance.rs | 8 + 3 files changed, 939 insertions(+), 2 deletions(-) create mode 100644 apps/desktop-gpui/src/deeplink.rs diff --git a/apps/desktop-gpui/src/deeplink.rs b/apps/desktop-gpui/src/deeplink.rs new file mode 100644 index 0000000000..feeeec28b1 --- /dev/null +++ b/apps/desktop-gpui/src/deeplink.rs @@ -0,0 +1,920 @@ +//! `cap-desktop://` deep-link actions -- the gpui port of +//! `apps/desktop/src-tauri/src/deeplink_actions.rs`. +//! +//! The URL grammar is byte-identical to the Tauri app's: an action deep link +//! is `://action?value=`, where the JSON is the +//! serde form of [`DeepLinkAction`] (externally tagged, `snake_case` variant +//! names). The scheme itself is deliberately not checked -- the Tauri parser +//! (`deeplink_actions.rs:155-183`) discriminates on the *host* alone, so +//! `cap-desktop://` and `cap://` both work, and any URL whose host is not +//! `action` is either "not ours" ([`ActionParseFromUrlError::NotAction`], the +//! login callback's host is `auth`) or malformed. No URL is ever *built* from +//! these parts, so the userinfo/port authority-injection class that bit the +//! web session endpoint (2026-08, the `port=pwn@evil.example` takeover) has no +//! sink here -- but the host match is still exact, never a substring, and every +//! parse failure is a dropped action, never a panic: this input arrives +//! straight from the OS on behalf of whatever wrote the URL. +//! +//! Delivery, and how it maps onto the Tauri app's two paths: +//! +//! * **App already running.** Tauri receives the URL through +//! `tauri_plugin_deep_link`'s `on_open_url` (`lib.rs:5601-5603`); on macOS +//! that plugin is a GURL AppleEvent handler, and this app installs its own in +//! [`crate::platform::install_url_scheme_handler`], which forwards every URL +//! to [`submit_deep_link`]. A URL opened while the app runs never spawns a +//! second instance on macOS -- the AppleEvent goes to the running process. +//! * **App launched by the URL.** Tauri's single-instance plugin hands the new +//! launch's `cap-desktop://` argv to the surviving old instance +//! (`lib.rs:5193-5204`). This app's single instancing is new-instance-wins +//! (see [`crate::single_instance`]), so the launch that carries the URL *is* +//! the instance that survives: [`init`] reads its own argv. On macOS the +//! launch URL additionally arrives as the same GURL event once the run loop +//! starts; both roads feed one channel, and the channel buffers until the +//! drain task exists, so neither ordering loses the action. +//! +//! Execution mirrors `DeepLinkActionExecutor` (`deeplink_actions.rs:69-105`): +//! a single queue, actions run strictly in arrival order. Over there the +//! consumer is a dedicated thread `block_on`-ing each action; here it is one +//! foreground task draining into `cx.update`, the same channel discipline the +//! tray and the global hotkeys use. + +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::OnceLock; + +use cap_recording::feeds::camera::DeviceOrModelID; +use cap_recording::sources::screen_capture::ScreenCaptureTarget; +use gpui::App; +use reqwest::Url; +use serde::{Deserialize, Serialize}; + +use crate::{ + app_windows, + feeds::{Feeds, SelectedCamera}, + library, recording, + session::RecordingSession, + settings_window::Page, +}; + +/// `CaptureArea` (`deeplink_actions.rs:18-25`). Debug-gated exactly as it is +/// over there: area capture via deep link is a harness affordance, not a +/// shipping surface. +#[cfg(debug_assertions)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct CaptureArea { + screen: String, + x: f64, + y: f64, + width: f64, + height: f64, +} + +/// `CaptureMode` (`deeplink_actions.rs:27-36`): the screen and window are +/// addressed by *name*, resolved against the live target lists at execution +/// time, matching the Tauri lookup verbatim. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum CaptureMode { + Screen(String), + Window(String), + #[cfg(debug_assertions)] + Area(Box), + #[cfg(debug_assertions)] + CameraOnly, +} + +/// `CameraPreviewShape` (`src-tauri/src/camera.rs:161-168`), mirrored here so +/// the debug `set_camera_preview_state` payload deserializes identically. +#[cfg(debug_assertions)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum CameraPreviewShape { + #[default] + Round, + Square, + Full, +} + +/// `CameraPreviewState` (`src-tauri/src/camera.rs:170-177`). `background_blur` +/// keeps its `#[serde(default)]` and its `cap_project` type, so the accepted +/// JSON is the Tauri app's exactly. +#[cfg(debug_assertions)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct CameraPreviewState { + pub size: f32, + pub shape: CameraPreviewShape, + pub mirrored: bool, + #[serde(default)] + pub background_blur: cap_project::BackgroundBlurMode, +} + +/// `DeepLinkAction` (`deeplink_actions.rs:38-67`), variant for variant, +/// including which ones exist only under `debug_assertions`. `mode` uses +/// `cap_recording::RecordingMode` -- the exact type the Tauri enum embeds -- +/// so `"studio"`/`"instant"`/`"screenshot"` all *parse*; the screenshot case +/// is then refused at execution with the Tauri app's own error (its +/// `start_recording` returns "Use take_screenshot for screenshots", +/// `src-tauri/src/recording.rs:1699-1704`). +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum DeepLinkAction { + StartRecording { + capture_mode: CaptureMode, + camera: Option, + mic_label: Option, + capture_system_audio: bool, + mode: cap_recording::RecordingMode, + }, + StopRecording, + #[cfg(debug_assertions)] + PauseRecording, + #[cfg(debug_assertions)] + ResumeRecording, + #[cfg(debug_assertions)] + OpenCamera { + camera: DeviceOrModelID, + }, + #[cfg(debug_assertions)] + SetCameraPreviewState { + state: CameraPreviewState, + }, + OpenEditor { + project_path: PathBuf, + }, + OpenSettings { + page: Option, + }, +} + +/// `ActionParseFromUrlError` (`deeplink_actions.rs:148-153`). +#[derive(Debug, PartialEq, Eq)] +pub enum ActionParseFromUrlError { + ParseFailed(String), + Invalid, + NotAction, +} + +impl TryFrom<&Url> for DeepLinkAction { + type Error = ActionParseFromUrlError; + + /// `deeplink_actions.rs:158-182`, verbatim. The `file://` branch is the + /// macOS "open with Cap" path; this binary registers no document types so + /// only tests reach it today, but the grammar stays whole. + fn try_from(url: &Url) -> Result { + #[cfg(target_os = "macos")] + if url.scheme() == "file" { + return url + .to_file_path() + .map(|project_path| Self::OpenEditor { project_path }) + .map_err(|_| ActionParseFromUrlError::Invalid); + } + + match url.domain() { + Some("action") => {} + Some(_) => return Err(ActionParseFromUrlError::NotAction), + None => return Err(ActionParseFromUrlError::Invalid), + } + + let params = url.query_pairs().collect::>(); + let json_value = params + .get("value") + .ok_or(ActionParseFromUrlError::Invalid)?; + let action: Self = serde_json::from_str(json_value) + .map_err(|e| ActionParseFromUrlError::ParseFailed(e.to_string()))?; + Ok(action) + } +} + +/// The action queue. Unbounded and created on first touch, so URLs submitted +/// before [`init`] runs (the launch AppleEvent, argv) wait for the drain task +/// instead of being dropped -- the buffering the Tauri app gets from its +/// executor being managed before `on_open_url` is wired. +fn channel() -> &'static ( + flume::Sender, + flume::Receiver, +) { + static CHANNEL: OnceLock<( + flume::Sender, + flume::Receiver, + )> = OnceLock::new(); + CHANNEL.get_or_init(flume::unbounded) +} + +/// The one entry point for a URL handed over by the OS -- what +/// `platform::install_url_scheme_handler`'s AppleEvent handler calls. Safe +/// from any thread; does no gpui work itself. +/// +/// Every URL is first forwarded to the sign-in flow ([`crate::auth`]), whose +/// slot only exists during an active sign-in and filters for its own +/// `token`/`api_key` parameters -- the pre-existing behavior of the handler, +/// unchanged. Action parsing happens on top. +pub fn submit_deep_link(raw: &str) { + crate::auth::submit_deep_link(raw); + submit_action_url(raw); +} + +/// Parse-and-queue half of [`submit_deep_link`]; also the argv path in +/// [`init`], which skips the auth forward because `main` already routes +/// token-bearing arguments there. +/// +/// Error reporting mirrors `deeplink_actions::handle` +/// (`deeplink_actions.rs:107-133`) with one deliberate deviation: the failed +/// URL's *content* is never logged, only its shape -- an auth callback that +/// lands in the `Invalid` arm must not write its token into the log. +fn submit_action_url(raw: &str) { + if raw.is_empty() { + return; + } + let Ok(url) = Url::parse(raw) else { + tracing::debug!("deep link is not a URL; ignored"); + return; + }; + match DeepLinkAction::try_from(&url) { + Ok(action) => { + tracing::info!(?action, "queueing deep link action"); + if let Err(error) = channel().0.send(action) { + tracing::error!(%error, "failed to queue deep link action"); + } + } + Err(ActionParseFromUrlError::ParseFailed(message)) => { + tracing::error!( + scheme = url.scheme(), + host = url.host_str().unwrap_or_default(), + %message, + "failed to parse deep link action" + ); + } + Err(ActionParseFromUrlError::Invalid) => { + tracing::error!( + scheme = url.scheme(), + host = url.host_str().unwrap_or_default(), + "invalid deep link format" + ); + } + // Likely login action, not handled here -- the auth slot got it. + Err(ActionParseFromUrlError::NotAction) => {} + } +} + +/// Wire the executor: scan the launch argv for action URLs (the +/// new-instance-wins counterpart of Tauri's single-instance argv forwarding, +/// `lib.rs:5195-5204`, which filters on the `cap-desktop://` prefix), then +/// start the drain task that runs each queued action on the main thread with +/// a clean borrow. Called once from [`crate::app_windows::init`], after the +/// window registry the actions dispatch into exists. +pub fn init(cx: &mut App) { + for argument in std::env::args().skip(1) { + if argument.starts_with("cap-desktop://") || argument.starts_with("cap://") { + submit_action_url(&argument); + } + } + + let rx = channel().1.clone(); + cx.spawn(async move |cx| { + while let Ok(action) = rx.recv_async().await { + cx.update(|cx| { + tracing::info!(?action, "executing deep link action"); + if let Err(error) = action.execute(cx) { + tracing::error!(%error, "failed to handle deep link action"); + } + }); + } + }) + .detach(); +} + +impl DeepLinkAction { + /// `DeepLinkAction::execute` (`deeplink_actions.rs:186-296`), arm for arm, + /// against this app's counterparts: the session and feeds globals where + /// the Tauri arms call commands on `ArcLock`, and + /// [`crate::app_windows`] where they call `ShowCapWindow`. + pub fn execute(self, cx: &mut App) -> Result<(), String> { + match self { + DeepLinkAction::StartRecording { + capture_mode, + camera, + mic_label, + capture_system_audio, + mode, + } => { + // `set_camera_input` / `set_mic_input`: the deep link's devices + // replace the current selections (including `None` clearing + // them). `Feeds::set_camera` also opens/closes the preview + // bubble, which is what `set_camera_input` does through the + // camera preview over there. + let selection = camera.clone().map(|id| SelectedCamera { + label: camera_label(&id), + id, + }); + Feeds::global(cx).update(cx, |feeds, cx| { + feeds.set_camera(selection, cx); + feeds.set_microphone(mic_label.clone(), cx); + }); + + // The name -> target resolution, verbatim from + // `deeplink_actions.rs:200-231`. + let capture_target: ScreenCaptureTarget = match capture_mode { + CaptureMode::Screen(name) => cap_recording::screen_capture::list_displays() + .into_iter() + .find(|(s, _)| s.name == name) + .map(|(s, _)| ScreenCaptureTarget::Display { id: s.id }) + .ok_or(format!("No screen with name \"{}\"", &name))?, + CaptureMode::Window(name) => cap_recording::screen_capture::list_windows() + .into_iter() + .find(|(w, _)| w.name == name) + .map(|(w, _)| ScreenCaptureTarget::Window { id: w.id }) + .ok_or(format!("No window with name \"{}\"", &name))?, + #[cfg(debug_assertions)] + CaptureMode::Area(area) => { + if area.width <= 0.0 || area.height <= 0.0 { + return Err("Area width and height must be positive".to_string()); + } + let screen = cap_recording::screen_capture::list_displays() + .into_iter() + .find(|(display, _)| display.name == area.screen) + .map(|(display, _)| display.id) + .ok_or(format!("No screen with name \"{}\"", &area.screen))?; + ScreenCaptureTarget::Area { + screen, + bounds: scap_targets::bounds::LogicalBounds::new( + scap_targets::bounds::LogicalPosition::new(area.x, area.y), + scap_targets::bounds::LogicalSize::new(area.width, area.height), + ), + } + } + #[cfg(debug_assertions)] + CaptureMode::CameraOnly => ScreenCaptureTarget::CameraOnly, + }; + + let mode = match mode { + cap_recording::RecordingMode::Studio => recording::RecordingMode::Studio, + cap_recording::RecordingMode::Instant => recording::RecordingMode::Instant, + // The Tauri parser admits it and its `start_recording` + // refuses it (`recording.rs:1699-1704`); same split here. + cap_recording::RecordingMode::Screenshot => { + return Err("Use take_screenshot for screenshots".to_string()); + } + }; + + // Deferred behind `set_camera`'s own deferred + // `open_camera_window`, so a studio start finds the bubble + // already open and excludes it from capture + // (`begin_recording`'s `camera_window_number` read). gpui + // flushes deferred callbacks in FIFO order. + cx.defer(move |cx| { + let (camera_feed, mic_feed) = { + let feeds = Feeds::global(cx); + let feeds = feeds.read(cx); + (feeds.camera_actor(), feeds.mic_actor()) + }; + app_windows::begin_recording( + recording::StartConfig { + mode, + target: capture_target, + microphone: mic_label, + camera, + system_audio: capture_system_audio, + excluded_windows: Vec::new(), + camera_feed, + mic_feed, + }, + cx, + ); + }); + Ok(()) + } + // `recording::stop_recording`; the session's own phase guard is + // the Tauri command's "no recording in progress" early-out. + DeepLinkAction::StopRecording => { + RecordingSession::global(cx).update(cx, |session, cx| session.stop(cx)); + Ok(()) + } + // `pause_recording` / `resume_recording` + // (`src-tauri/src/recording.rs:2421-2444`) are directional and + // no-ops without a live recording; the session only exposes the + // bar's toggle, so the direction is enforced here. + #[cfg(debug_assertions)] + DeepLinkAction::PauseRecording => { + RecordingSession::global(cx).update(cx, |session, cx| { + if matches!( + session.phase, + crate::session::Phase::Recording { paused: false } + ) { + session.toggle_pause(cx); + } + }); + Ok(()) + } + #[cfg(debug_assertions)] + DeepLinkAction::ResumeRecording => { + RecordingSession::global(cx).update(cx, |session, cx| { + if session.is_paused() { + session.toggle_pause(cx); + } + }); + Ok(()) + } + // `set_camera_input(Some)` + the native preview: `Feeds::set_camera` + // is both halves here -- the gpui camera window *is* the native + // preview, and `set_camera` opens it. The + // `instant-mode-harness-camera-opened` emits are webview harness + // signals with no consumer in this app. + #[cfg(debug_assertions)] + DeepLinkAction::OpenCamera { camera } => { + let selection = SelectedCamera { + label: camera_label(&camera), + id: camera, + }; + Feeds::global(cx).update(cx, |feeds, cx| feeds.set_camera(Some(selection), cx)); + Ok(()) + } + // `set_camera_preview_state`: persist the state, then bounce the + // bubble if it is up -- the gpui camera window reads its chrome + // state at construction (`CameraWindow::new`), so a reopen is the + // live-apply seam. + #[cfg(debug_assertions)] + DeepLinkAction::SetCameraPreviewState { state } => { + let shape = match state.shape { + CameraPreviewShape::Round => crate::store::CameraShape::Round, + CameraPreviewShape::Square => crate::store::CameraShape::Square, + CameraPreviewShape::Full => crate::store::CameraShape::Full, + }; + let background_blur = match state.background_blur { + cap_project::BackgroundBlurMode::Off => crate::store::BlurMode::Off, + cap_project::BackgroundBlurMode::Light => crate::store::BlurMode::Light, + cap_project::BackgroundBlurMode::Heavy => crate::store::BlurMode::Heavy, + }; + crate::store::update(|persisted| { + persisted.camera_window = Some(crate::store::CameraWindowState { + size: state.size, + shape, + mirrored: state.mirrored, + background_blur, + }); + }); + if cx.global::().camera.is_some() { + app_windows::close_camera_window(cx); + app_windows::open_camera_window(cx); + } + Ok(()) + } + // `open_project_from_path` (`src-tauri/src/lib.rs:6877-6907`): + // studio bundles go to the editor after the status gate, instant + // bundles open their mp4 in the system player and hide the main + // window. The meta load is also the validation seam for this + // OS-supplied path -- anything that is not a readable `.cap` + // bundle fails here and opens nothing. + DeepLinkAction::OpenEditor { project_path } => { + use cap_project::{RecordingMeta, RecordingMetaInner, StudioRecordingStatus}; + + let meta = + RecordingMeta::load_for_project(&project_path).map_err(|v| v.to_string())?; + match &meta.inner { + RecordingMetaInner::Studio(studio) => { + let status = studio.status(); + if let StudioRecordingStatus::Failed { .. } = status { + return Err("Unable to open failed recording".to_string()); + } else if let StudioRecordingStatus::InProgress = status { + return Err("Recording in progress".to_string()); + } + // Deferred like `activate_recent`: `open_editor` opens + // a window, which must not happen from inside an + // entity update. + cx.defer(move |cx| app_windows::open_editor(project_path, cx)); + } + RecordingMetaInner::Instant(_) => { + let mp4_path = project_path.join("content/output.mp4"); + if mp4_path.exists() && mp4_path.is_file() { + library::open_path(&mp4_path); + app_windows::hide_main_window(cx); + } + } + } + Ok(()) + } + // `ShowCapWindow::Settings { page }`. The Tauri window navigates + // to `/settings/{page}` and an unknown slug is a dead route; here + // an unknown (or absent) slug lands on General, the settings + // window's own default. + DeepLinkAction::OpenSettings { page } => { + let page = page + .as_deref() + .and_then(Page::from_slug) + .unwrap_or(Page::General); + app_windows::open_settings(page, cx); + Ok(()) + } + } + } +} + +/// The display label for a deep-linked camera id. The Tauri action carries no +/// label (its command takes the bare `DeviceOrModelID`); this app's +/// [`SelectedCamera`] wants one for the pickers, so it is resolved against the +/// same enumeration `devices.rs` lists from, falling back to the raw device id +/// for a device that is not currently attached. +fn camera_label(id: &DeviceOrModelID) -> String { + cap_camera::list_cameras() + .find(|info| match id { + DeviceOrModelID::DeviceID(device_id) => info.device_id() == device_id.as_str(), + DeviceOrModelID::ModelID(model) => info.model_id() == Some(model), + }) + .map(|info| info.display_name().to_string()) + .unwrap_or_else(|| match id { + DeviceOrModelID::DeviceID(device_id) => device_id.clone(), + DeviceOrModelID::ModelID(_) => "Camera".to_string(), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + // -- The Tauri parser's own tests (`deeplink_actions.rs:299-514`), + // -- unchanged except for crate paths, so grammar drift fails loudly. + + #[test] + fn parses_stop_recording_action_url() { + let url = Url::parse("cap-desktop://action?value=%22stop_recording%22").unwrap(); + + assert_eq!( + DeepLinkAction::try_from(&url), + Ok(DeepLinkAction::StopRecording) + ); + } + + #[cfg(debug_assertions)] + #[test] + fn parses_open_camera_action_url() { + let value = serde_json::json!({ + "open_camera": { + "camera": { "DeviceID": "camera-1" } + } + }) + .to_string(); + let url = Url::parse_with_params("cap-desktop://action", &[("value", value)]).unwrap(); + + assert_eq!( + DeepLinkAction::try_from(&url), + Ok(DeepLinkAction::OpenCamera { + camera: DeviceOrModelID::DeviceID("camera-1".to_string()) + }) + ); + } + + #[cfg(debug_assertions)] + #[test] + fn parses_camera_preview_state_action_url() { + let value = serde_json::json!({ + "set_camera_preview_state": { + "state": { + "size": 400.0, + "shape": "full", + "mirrored": true, + "background_blur": "heavy" + } + } + }) + .to_string(); + let url = Url::parse_with_params("cap-desktop://action", &[("value", value)]).unwrap(); + + assert_eq!( + DeepLinkAction::try_from(&url), + Ok(DeepLinkAction::SetCameraPreviewState { + state: CameraPreviewState { + size: 400.0, + shape: CameraPreviewShape::Full, + mirrored: true, + background_blur: cap_project::BackgroundBlurMode::Heavy, + } + }) + ); + } + + #[cfg(debug_assertions)] + #[test] + fn parses_pause_and_resume_action_urls() { + let pause_url = Url::parse("cap-desktop://action?value=%22pause_recording%22").unwrap(); + let resume_url = Url::parse("cap-desktop://action?value=%22resume_recording%22").unwrap(); + + assert_eq!( + DeepLinkAction::try_from(&pause_url), + Ok(DeepLinkAction::PauseRecording) + ); + assert_eq!( + DeepLinkAction::try_from(&resume_url), + Ok(DeepLinkAction::ResumeRecording) + ); + } + + #[cfg(debug_assertions)] + #[test] + fn parses_area_recording_action_url() { + let value = serde_json::json!({ + "start_recording": { + "capture_mode": { + "area": { + "screen": "Built-in Retina Display", + "x": 10.0, + "y": 20.0, + "width": 800.0, + "height": 600.0 + } + }, + "camera": { "DeviceID": "camera-1" }, + "mic_label": "microphone-1", + "capture_system_audio": false, + "mode": "instant" + } + }) + .to_string(); + let url = Url::parse_with_params("cap-desktop://action", &[("value", value)]).unwrap(); + + assert_eq!( + DeepLinkAction::try_from(&url), + Ok(DeepLinkAction::StartRecording { + capture_mode: CaptureMode::Area(Box::new(CaptureArea { + screen: "Built-in Retina Display".to_string(), + x: 10.0, + y: 20.0, + width: 800.0, + height: 600.0, + })), + camera: Some(DeviceOrModelID::DeviceID("camera-1".to_string())), + mic_label: Some("microphone-1".to_string()), + capture_system_audio: false, + mode: cap_recording::RecordingMode::Instant, + }) + ); + } + + #[cfg(debug_assertions)] + #[test] + fn parses_camera_only_recording_action_url() { + let value = serde_json::json!({ + "start_recording": { + "capture_mode": "camera_only", + "camera": { "DeviceID": "camera-1" }, + "mic_label": "microphone-1", + "capture_system_audio": false, + "mode": "studio" + } + }) + .to_string(); + let url = Url::parse_with_params("cap-desktop://action", &[("value", value)]).unwrap(); + + assert_eq!( + DeepLinkAction::try_from(&url), + Ok(DeepLinkAction::StartRecording { + capture_mode: CaptureMode::CameraOnly, + camera: Some(DeviceOrModelID::DeviceID("camera-1".to_string())), + mic_label: Some("microphone-1".to_string()), + capture_system_audio: false, + mode: cap_recording::RecordingMode::Studio, + }) + ); + } + + #[test] + fn parses_start_recording_action_url() { + let url = Url::parse( + "cap-desktop://action?value=%7B%22start_recording%22%3A%7B%22capture_mode%22%3A%7B%22screen%22%3A%22Odyssey%20G93SC%22%7D%2C%22camera%22%3Anull%2C%22mic_label%22%3A%22Shure%20MV7%2B%22%2C%22capture_system_audio%22%3Atrue%2C%22mode%22%3A%22studio%22%7D%7D", + ) + .unwrap(); + + let Ok(DeepLinkAction::StartRecording { + capture_mode, + camera, + mic_label, + capture_system_audio, + mode, + }) = DeepLinkAction::try_from(&url) + else { + panic!("expected start recording action"); + }; + + assert_eq!( + capture_mode, + CaptureMode::Screen("Odyssey G93SC".to_string()) + ); + assert_eq!(camera, None); + assert_eq!(mic_label.as_deref(), Some("Shure MV7+")); + assert!(capture_system_audio); + assert_eq!(mode, cap_recording::RecordingMode::Studio); + } + + #[test] + fn parses_start_recording_action_with_camera_device_id() { + let value = serde_json::json!({ + "start_recording": { + "capture_mode": { "screen": "Odyssey G93SC" }, + "camera": { "DeviceID": "camera-1" }, + "mic_label": "Shure MV7+", + "capture_system_audio": true, + "mode": "studio" + } + }) + .to_string(); + let url = Url::parse_with_params("cap-desktop://action", &[("value", value)]).unwrap(); + + let Ok(DeepLinkAction::StartRecording { + camera, + mic_label, + capture_system_audio, + .. + }) = DeepLinkAction::try_from(&url) + else { + panic!("expected start recording action"); + }; + + assert_eq!( + camera, + Some(DeviceOrModelID::DeviceID("camera-1".to_string())) + ); + assert_eq!(mic_label.as_deref(), Some("Shure MV7+")); + assert!(capture_system_audio); + } + + #[test] + fn rejects_non_action_host() { + let url = Url::parse("cap-desktop://login?value=%22stop_recording%22").unwrap(); + + assert_eq!( + DeepLinkAction::try_from(&url), + Err(ActionParseFromUrlError::NotAction) + ); + } + + // -- This port's own coverage: the editor/settings shapes the Tauri file + // -- never wrote tests for, and hostile input. + + #[test] + fn parses_open_editor_and_open_settings_urls() { + let editor = serde_json::json!({ + "open_editor": { "project_path": "/tmp/My Recording.cap" } + }) + .to_string(); + let url = Url::parse_with_params("cap-desktop://action", &[("value", editor)]).unwrap(); + assert_eq!( + DeepLinkAction::try_from(&url), + Ok(DeepLinkAction::OpenEditor { + project_path: PathBuf::from("/tmp/My Recording.cap"), + }) + ); + + let settings = serde_json::json!({ "open_settings": { "page": "hotkeys" } }).to_string(); + let url = Url::parse_with_params("cap-desktop://action", &[("value", settings)]).unwrap(); + assert_eq!( + DeepLinkAction::try_from(&url), + Ok(DeepLinkAction::OpenSettings { + page: Some("hotkeys".to_string()), + }) + ); + + let settings = serde_json::json!({ "open_settings": { "page": null } }).to_string(); + let url = Url::parse_with_params("cap-desktop://action", &[("value", settings)]).unwrap(); + assert_eq!( + DeepLinkAction::try_from(&url), + Ok(DeepLinkAction::OpenSettings { page: None }) + ); + } + + /// Screenshot mode parses -- the type is `cap_recording::RecordingMode`, + /// same as the Tauri enum -- and is refused later, at execution. + #[test] + fn parses_screenshot_mode() { + let value = serde_json::json!({ + "start_recording": { + "capture_mode": { "screen": "Display" }, + "camera": null, + "mic_label": null, + "capture_system_audio": false, + "mode": "screenshot" + } + }) + .to_string(); + let url = Url::parse_with_params("cap-desktop://action", &[("value", value)]).unwrap(); + + let Ok(DeepLinkAction::StartRecording { mode, .. }) = DeepLinkAction::try_from(&url) else { + panic!("expected start recording action"); + }; + assert_eq!(mode, cap_recording::RecordingMode::Screenshot); + } + + /// Malformed shapes are all `Err`, never a panic: this parser eats + /// whatever the OS relays. + #[test] + fn rejects_malformed_urls() { + // No authority at all -> no domain -> Invalid. + let url = Url::parse("cap-desktop:action?value=%22stop_recording%22").unwrap(); + assert_eq!( + DeepLinkAction::try_from(&url), + Err(ActionParseFromUrlError::Invalid) + ); + + // Right host, no `value` parameter. + let url = Url::parse("cap-desktop://action").unwrap(); + assert_eq!( + DeepLinkAction::try_from(&url), + Err(ActionParseFromUrlError::Invalid) + ); + + // `value` present but empty / not JSON / unknown action. + for value in ["", "not json", "\"unknown_action\"", "{}", "[1,2,3]"] { + let url = Url::parse_with_params("cap-desktop://action", &[("value", value)]).unwrap(); + assert!( + matches!( + DeepLinkAction::try_from(&url), + Err(ActionParseFromUrlError::ParseFailed(_)) + ), + "value {value:?} should fail to parse" + ); + } + + // A known action with the wrong payload shape. + let value = serde_json::json!({ "start_recording": { "capture_mode": 42 } }).to_string(); + let url = Url::parse_with_params("cap-desktop://action", &[("value", value)]).unwrap(); + assert!(matches!( + DeepLinkAction::try_from(&url), + Err(ActionParseFromUrlError::ParseFailed(_)) + )); + } + + /// Any host other than exactly `action` is not ours -- the host is + /// compared whole, never substring-matched, so `action.evil.example` + /// cannot smuggle an action in. + #[test] + fn rejects_foreign_hosts() { + for hostile in [ + "cap-desktop://evil.example?value=%22stop_recording%22", + "cap-desktop://action.evil.example?value=%22stop_recording%22", + "cap-desktop://xaction?value=%22stop_recording%22", + ] { + let url = Url::parse(hostile).unwrap(); + assert_eq!( + DeepLinkAction::try_from(&url), + Err(ActionParseFromUrlError::NotAction), + "{hostile} must not parse as an action" + ); + } + } + + /// The Tauri parser discriminates on `Url::domain()`, which ignores the + /// authority's userinfo and port -- so `user@action:8080` still reads as + /// host `action`. Reproduced (and pinned) rather than tightened: nothing + /// here ever *builds* a URL from these parts, which is where the + /// port-injection class lives, and diverging would break URLs the + /// shipping app accepts. + #[test] + fn authority_decorations_match_tauri() { + for accepted in [ + "cap-desktop://action:8080?value=%22stop_recording%22", + "cap-desktop://user@action?value=%22stop_recording%22", + ] { + let url = Url::parse(accepted).unwrap(); + assert_eq!( + DeepLinkAction::try_from(&url), + Ok(DeepLinkAction::StopRecording), + "{accepted} parses on the Tauri grammar" + ); + } + } + + /// A nesting bomb is an `Err`, not a crash: the enum deserializer rejects + /// the wrong shape immediately, and serde_json's own recursion limit + /// backstops anything that gets deeper. Pinned so a hand-rolled parser + /// can never regress this into a stack overflow. + #[test] + fn hostile_json_does_not_panic() { + let bomb = format!("{}{}", "[".repeat(400), "]".repeat(400)); + let url = Url::parse_with_params("cap-desktop://action", &[("value", bomb)]).unwrap(); + assert!(matches!( + DeepLinkAction::try_from(&url), + Err(ActionParseFromUrlError::ParseFailed(_)) + )); + + let nul = "\"stop_recording\u{0}\""; + let url = Url::parse_with_params("cap-desktop://action", &[("value", nul)]).unwrap(); + assert!(DeepLinkAction::try_from(&url).is_err()); + } + + /// The macOS `file://` branch: a local path becomes `OpenEditor`; a + /// remote-host file URL does not convert to a path and is rejected. + #[cfg(target_os = "macos")] + #[test] + fn file_urls_open_the_editor() { + let url = Url::parse("file:///tmp/My%20Recording.cap").unwrap(); + assert_eq!( + DeepLinkAction::try_from(&url), + Ok(DeepLinkAction::OpenEditor { + project_path: PathBuf::from("/tmp/My Recording.cap"), + }) + ); + + let url = Url::parse("file://evil.example/tmp/foo.cap").unwrap(); + assert_eq!( + DeepLinkAction::try_from(&url), + Err(ActionParseFromUrlError::Invalid) + ); + } +} diff --git a/apps/desktop-gpui/src/platform.rs b/apps/desktop-gpui/src/platform.rs index b7610bab27..a4f9ff3d7f 100644 --- a/apps/desktop-gpui/src/platform.rs +++ b/apps/desktop-gpui/src/platform.rs @@ -1276,7 +1276,13 @@ mod mac { ) -> OsErr { let mut actual_type = 0u32; let mut actual_size = 0isize; - let mut buffer = [0u8; 4096]; + // Heap, and 64 KiB rather than the old 4 KiB stack array: an + // `action` deep link carries a URL-encoded JSON payload (window + // titles, screen names -- see `crate::deeplink`), and the Tauri + // deep-link plugin it mirrors imposes no length cap. Anything + // longer still is truncated to a URL that fails to parse and is + // dropped, never mis-executed. + let mut buffer = vec![0u8; 64 * 1024]; let status = unsafe { AEGetParamPtr( event, @@ -1291,7 +1297,10 @@ mod mac { if status == 0 && actual_size > 0 { let len = (actual_size as usize).min(buffer.len()); if let Ok(url) = std::str::from_utf8(&buffer[..len]) { - crate::auth::submit_deep_link(url.trim_end_matches('\0')); + // Auth callbacks and `action` URLs both come through this + // one AppleEvent; `deeplink` forwards to the sign-in flow + // and parses actions itself. + crate::deeplink::submit_deep_link(url.trim_end_matches('\0')); } } 0 diff --git a/apps/desktop-gpui/src/single_instance.rs b/apps/desktop-gpui/src/single_instance.rs index 4942b8a079..d76018c548 100644 --- a/apps/desktop-gpui/src/single_instance.rs +++ b/apps/desktop-gpui/src/single_instance.rs @@ -13,6 +13,14 @@ //! //! The pidfile lives in [`crate::store::app_data_dir`], so a harness run //! pointed at a `CAP_GPUI_APP_DATA_DIR` sandbox never kills the dev app. +//! +//! Deep links need no forwarding under this polarity. The Tauri plugin's +//! callback relays a second launch's `cap-desktop://` argv to the surviving +//! old instance (`src-tauri/src/lib.rs:5193-5204`); here the launch that +//! carries the URL *is* the survivor, and [`crate::deeplink::init`] reads its +//! own argv. A URL opened while the app is already running never launches a +//! second instance on macOS at all -- the GURL AppleEvent goes straight to +//! the running process (`crate::platform::install_url_scheme_handler`). use std::path::PathBuf; From 32ef6dcdb517bc79fbb595426895ed906435a12e Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:04:11 +0100 Subject: [PATCH 143/146] feat(desktop-gpui): record a new clip from inside the editor The Clips sidebar's record modal is live end to end: the target buttons open the real target-select overlays with the editor hidden for the picker, studio mode is forced while an editor target is armed, and the finished capture is appended to the open project through the same segment-copy the import path uses, then the standalone recording is deleted behind parent-dir and canonical-containment guards -- only after a successful append, so a failed append leaves the capture in the library. The armed target is consumed on every recording end, clean or not, so it can never leak into the next session. --- apps/desktop-gpui/src/editor_clips.rs | 1254 ++++++++++++++++++++++--- apps/desktop-gpui/src/main_window.rs | 76 +- apps/desktop-gpui/src/recording.rs | 53 ++ apps/desktop-gpui/src/session.rs | 30 + 4 files changed, 1255 insertions(+), 158 deletions(-) diff --git a/apps/desktop-gpui/src/editor_clips.rs b/apps/desktop-gpui/src/editor_clips.rs index 930879b6b8..7a76efe183 100644 --- a/apps/desktop-gpui/src/editor_clips.rs +++ b/apps/desktop-gpui/src/editor_clips.rs @@ -14,9 +14,19 @@ //! copied into this bundle, the metas and the timeline extended, and the //! editor reloaded -- `window.location.reload()`'s native spelling is //! [`crate::app_windows::reload_editor`]. -//! * **"Record a new clip"** opens the modal 1:1, but the start actions are -//! disabled: `setEditorRecordingTarget` -- recording *into* an open editor -//! project -- has no backend in this app yet. +//! * **"Record a new clip"** is the whole `setEditorRecordingTarget` flow, +//! natively: the modal's start actions arm the session's editor recording +//! target (`EditorRecordingTarget`, `src-tauri/src/windows.rs:3679-3697`) +//! and open the target-select overlays with the editor hidden for the +//! picker; the capture is forced into Studio mode; and when it stops +//! cleanly the session observer reveals this editor and hands it the +//! bundle, which lands through the same whole-segment-copy the import path +//! uses, followed by the recording directory's deletion and an editor +//! reload -- the `EditorRecordingAdded` listener (`Editor.tsx:312-335`), +//! transcribed. The Display/Window chevron menus list the same enumeration +//! the main window's pickers use, icon cards standing in for the live +//! thumbnails for the same capture-pipeline reason documented on +//! `MainWindow::render_target_grid`. use std::{ cell::RefCell, @@ -41,9 +51,13 @@ use gpui::{ }; use crate::{ + app_windows, + devices::{CameraOption, DeviceSnapshot, DisplayOption, MicrophoneOption, WindowOption}, editor_edits::{self as edits, TrackSegmentOps}, editor_timeline::clip_timeline_offsets, editor_window::EditorWindow, + main_window::{Mode, TargetType}, + session::{Phase, RecordingSession}, theme::Theme, ui, }; @@ -288,6 +302,16 @@ pub(crate) struct ClipDrag { active: bool, } +/// Which of the record modal's device selects is unfolded -- +/// `CameraSelectBase` / `MicrophoneSelectBase`'s open dropdown +/// (`ClipsSidebar.tsx:1084-1122`), drawn here as an in-modal list the way the +/// chevron target menus are (a KSelect popover has no gpui counterpart). +#[derive(Clone, Copy, PartialEq, Eq)] +enum RecordDeviceMenu { + Camera, + Microphone, +} + /// Everything the clips layout mode owns. Lives on [`EditorWindow`] so the /// mode is hidden, not destroyed, while closed -- the Tauri sidebar stays /// mounted with `class="hidden"` (`Editor.tsx:739-746`). @@ -296,6 +320,18 @@ pub(crate) struct ClipsState { pub(crate) open: bool, /// `recordOpen`. record_open: bool, + /// `displayMenuOpen` / `windowMenuOpen` (`:292-296`) -- which chevron + /// target menu the modal body is showing. Only `Display` and `Window` + /// ever land here. + record_target_menu: Option, + /// Which device select is unfolded. + record_device_menu: Option, + /// `targetSearch`, as the shared text-input entity (created on first use, + /// like the rename field -- it needs a window). + target_search_input: Option>, + /// `createDevicesQuery` (`:365`): enumerated when the modal opens, `None` + /// while the scan is in flight so the menus can say "Loading...". + record_devices: Option, /// The import menu's anchor, while it is up. A native `Menu.popup()` in /// the Tauri app (`:541-556`); here the `ui::Menu` shape -- full-window /// backdrop, panel at the click position. @@ -322,6 +358,10 @@ impl Default for ClipsState { Self { open: false, record_open: false, + record_target_menu: None, + record_device_menu: None, + target_search_input: None, + record_devices: None, import_menu: None, importing: false, editing: None, @@ -406,18 +446,28 @@ impl EditorWindow { } /// `backToEditor` and the close half of the `createEffect(on(open))` - /// cleanup (`ClipsSidebar.tsx:278, 343-363`) -- the recording-target - /// resets in the source have no backend here. A live rename commits - /// first: hiding the DOM input blurs it, and blur commits. + /// cleanup (`ClipsSidebar.tsx:278, 343-363`), `resetRecordingTarget` + /// included: a target armed by the modal but never recorded is cleared so + /// it cannot redirect a later capture. Guarded on Idle -- mid-recording + /// the target belongs to the live capture and must survive (the Tauri + /// effect runs unguarded, but its editor window is hidden then, so the + /// guard only closes a hole rather than changing behaviour). A live + /// rename commits first: hiding the DOM input blurs it, and blur commits. pub(crate) fn close_clips(&mut self, window: &mut Window, cx: &mut Context) { if self.clips.editing.is_some() { self.commit_clip_rename(window, cx); } self.clips.open = false; self.clips.record_open = false; + self.clips.record_target_menu = None; + self.clips.record_device_menu = None; self.clips.import_menu = None; self.clips.drag = None; self.clips.drop_index = None; + let session = RecordingSession::global(cx); + if session.read(cx).phase == Phase::Idle { + session.update(cx, |session, _| session.set_editor_recording_target(None)); + } cx.notify(); } @@ -430,7 +480,14 @@ impl EditorWindow { return true; } if self.clips.record_open { - self.clips.record_open = false; + // The modal's own Escape ladder (`ClipsSidebar.tsx:764-775`): an + // open target or device menu closes first, the modal second. + if self.clips.record_target_menu.is_some() || self.clips.record_device_menu.is_some() { + self.clips.record_target_menu = None; + self.clips.record_device_menu = None; + } else { + self.clips.record_open = false; + } cx.notify(); return true; } @@ -778,8 +835,7 @@ impl EditorWindow { .full_width() .on_click(cx.listener( |this, _, _window, cx| { - this.clips.record_open = true; - cx.notify(); + this.open_record_modal(cx); }, )), ), @@ -1158,6 +1214,297 @@ impl EditorWindow { .into_any_element() } + // -- Record a new clip (`ClipsSidebar.tsx:434-501`, `Editor.tsx:312-335`) -- + + /// The action row's blue button: open the modal and refresh the device + /// enumeration behind it (`createDevicesQuery` is enabled by + /// `props.open && recordOpen()`, so the lists are re-read per opening). + fn open_record_modal(&mut self, cx: &mut Context) { + self.clips.record_open = true; + self.clips.record_target_menu = None; + self.clips.record_device_menu = None; + self.refresh_record_devices(cx); + cx.notify(); + } + + /// The enumeration hits AVFoundation and the window server, so it runs on + /// the background executor -- the `start_enumeration` rule from the main + /// window, which shares `DeviceSnapshot`. + fn refresh_record_devices(&mut self, cx: &mut Context) { + self.clips.record_devices = None; + cx.spawn(async move |this, cx| { + let snapshot = cx + .background_executor() + .spawn(async move { DeviceSnapshot::enumerate() }) + .await; + this.update(cx, |this, cx| { + this.clips.record_devices = Some(snapshot); + cx.notify(); + }) + .ok(); + }) + .detach(); + } + + /// The chevron menus' search field, created on first use like the rename + /// input. Escape with text clears it (the source input's own + /// `onKeyDown`, `:1155-1161`); Escape empty falls back to closing the + /// menu, which is what bubbling to the window handler would do. + fn ensure_target_search_input(&mut self, window: &mut Window, cx: &mut Context) { + if self.clips.target_search_input.is_some() { + return; + } + let input = cx.new(|cx| ui::TextInputState::single_line(window, cx)); + let subscription = cx.subscribe_in( + &input, + window, + |this: &mut Self, input, event: &ui::TextInputEvent, _window, cx| match event { + ui::TextInputEvent::Changed => cx.notify(), + ui::TextInputEvent::Cancelled => { + if input.read(cx).text().is_empty() { + this.clips.record_target_menu = None; + this.clips.record_device_menu = None; + } else { + input.update(cx, |input, cx| input.set_text("", cx)); + } + cx.notify(); + } + ui::TextInputEvent::Confirmed | ui::TextInputEvent::Blurred => {} + }, + ); + self.push_text_subscription(subscription); + self.clips.target_search_input = Some(input); + } + + fn target_search_text(&self, cx: &Context) -> String { + self.clips + .target_search_input + .as_ref() + .map(|input| input.read(cx).text().trim().to_lowercase()) + .unwrap_or_default() + } + + /// Toggle a chevron target menu open (`:1029-1035, 1062-1068`), focusing + /// the search field the way the Tauri menu's `Input` autofocuses. + fn toggle_record_target_menu( + &mut self, + kind: TargetType, + window: &mut Window, + cx: &mut Context, + ) { + if self.clips.record_target_menu == Some(kind) { + self.clips.record_target_menu = None; + } else { + self.ensure_target_search_input(window, cx); + if let Some(input) = self.clips.target_search_input.clone() { + input.update(cx, |input, cx| { + // `placeholder={... ? "Search windows" : "Search displays"}` + // (`:1162-1166`). + input.set_placeholder(if kind == TargetType::Window { + "Search windows" + } else { + "Search displays" + }); + input.set_text("", cx); + input.focus_and_select_all(window, cx); + }); + } + self.clips.record_target_menu = Some(kind); + self.clips.record_device_menu = None; + } + cx.notify(); + } + + /// `beginEditorRecording` (`ClipsSidebar.tsx:434-445`): close the modal, + /// stop playback, persist the live project config (the `setProjectConfig` + /// call -- the finish-time append merges into what is on disk), and arm + /// the session's editor recording target. Studio mode is forced app-side + /// (`begin_recording`), so the persisted recording-mode setting is left + /// alone -- the sidebar's `previousMode` save/restore dance nets out to + /// exactly that. + /// + /// Returns false without arming when a recording is already live: the + /// Tauri backend refuses the second start in `set_pending_recording`, and + /// refusing *before* the target write means the modal cannot re-point a + /// live recording's append at a different project. + fn begin_editor_recording(&mut self, cx: &mut Context) -> bool { + let session = RecordingSession::global(cx); + if session.read(cx).phase != Phase::Idle { + tracing::warn!("a recording is already live; not starting another from the editor"); + return false; + } + self.clips.record_open = false; + self.clips.record_target_menu = None; + self.clips.record_device_menu = None; + if self.playing { + self.stop_playback(cx); + } + self.pending_save().borrow_mut().flush(); + if let Err(error) = self.project.write(&self.project_path) { + tracing::error!("failed to persist the project config before recording: {error}"); + } + session.update(cx, |session, _| { + session.set_editor_recording_target(Some(self.project_path.clone())) + }); + cx.notify(); + true + } + + /// `openTargetMode` (`:447-463`): arm the target, then the picker + /// overlays with the editor hidden behind them. + fn open_editor_target_mode(&mut self, kind: TargetType, cx: &mut Context) { + if !self.begin_editor_recording(cx) { + return; + } + if kind == TargetType::CameraOnly { + // `setOptions("captureSystemAudio", false)` (`:452-458`): a + // camera-only capture records no system audio, and the option is + // the shared one the main window owns. + let main = cx.global::().main; + cx.defer(move |cx: &mut gpui::App| { + main.update(cx, |view, _window, cx| view.set_system_audio(false, cx)) + .ok(); + }); + } + let project_path = self.project_path.clone(); + let request = app_windows::OverlayRequest { + mode: kind, + recording_mode: Mode::Studio, + display: None, + pinned_window: None, + }; + cx.defer(move |cx: &mut gpui::App| { + app_windows::open_editor_target_overlays(project_path, request, cx) + }); + } + + /// `selectDisplayTarget` (`:465-479`): the overlays narrowed to the + /// picked display, editor hidden. + fn select_record_display(&mut self, display: DisplayOption, cx: &mut Context) { + if !self.begin_editor_recording(cx) { + return; + } + let project_path = self.project_path.clone(); + let request = app_windows::OverlayRequest { + mode: TargetType::Display, + recording_mode: Mode::Studio, + display: Some(display.id), + pinned_window: None, + }; + cx.defer(move |cx: &mut gpui::App| { + app_windows::open_editor_target_overlays(project_path, request, cx) + }); + } + + /// `selectWindowTarget` (`:481-501`): the overlays pinned to the picked + /// window. The source's trailing `commands.focusWindow(target.id)` has no + /// counterpart -- the app's own window-dropdown flow pins without + /// focusing, and the overlay highlight carries the selection either way. + fn select_record_window(&mut self, target: WindowOption, cx: &mut Context) { + if !self.begin_editor_recording(cx) { + return; + } + let project_path = self.project_path.clone(); + let request = app_windows::OverlayRequest { + mode: TargetType::Window, + recording_mode: Mode::Studio, + display: None, + pinned_window: Some(target.id), + }; + cx.defer(move |cx: &mut gpui::App| { + app_windows::open_editor_target_overlays(project_path, request, cx) + }); + } + + /// The picker was cancelled while this editor was hidden for it + /// (`rawOptions.targetMode == null && hiddenForPicker`, + /// `ClipsSidebar.tsx:413-426`): the reveal and the target clear happen in + /// `app_windows::dismiss_target_overlays`; this is the modal-state half + /// (`restoreMode(); closeRecord()`). + pub(crate) fn editor_picker_dismissed(&mut self, cx: &mut Context) { + self.clips.record_open = false; + self.clips.record_target_menu = None; + self.clips.record_device_menu = None; + cx.notify(); + } + + /// `appendRecordedClip` (`Editor.tsx:319-335`), driven by the session + /// observer where the Tauri app has the `EditorRecordingAdded` event: + /// stop playback, persist the live config, append the finished bundle + /// through the same whole-segment-copy the import path uses, delete the + /// recording directory (best-effort, the `.catch(() => {})`), and reload + /// the editor. On failure the recording directory is left alone -- it + /// stays a standalone library project, exactly what an errored append + /// leaves behind over there. + pub(crate) fn append_recorded_clip( + &mut self, + recording_dir: PathBuf, + _window: &mut Window, + cx: &mut Context, + ) { + if self.clips.importing { + // A concurrent import owns the bundle merge; the capture stays in + // the library and can be pulled in through "Existing recording". + tracing::warn!( + recording = %recording_dir.display(), + "an import is already running; leaving the recording in the library" + ); + return; + } + if self.playing { + self.stop_playback(cx); + } + self.pending_save().borrow_mut().flush(); + if let Err(error) = self.project.write(&self.project_path) { + tracing::error!("failed to persist the project config before append: {error}"); + } + self.clips.importing = true; + cx.notify(); + + let target = self.project_path.clone(); + cx.spawn(async move |this, cx| { + let result = cx + .background_executor() + .spawn({ + let target = target.clone(); + let recording_dir = recording_dir.clone(); + async move { + let count = append_cap_project_to_editor(&target, &recording_dir)?; + if let Err(error) = + crate::recording::delete_recording_directory(&recording_dir) + { + tracing::warn!( + recording = %recording_dir.display(), + "could not delete the appended recording: {error}" + ); + } + Ok::(count) + } + }) + .await; + match result { + Ok(count) => { + tracing::info!( + count, + path = %target.display(), + "recorded clip appended to the editor project" + ); + cx.update(|cx| crate::app_windows::reload_editor(&target, cx)); + } + Err(error) => { + tracing::error!("failed to add the recorded clip: {error}"); + this.update(cx, |this, cx| { + this.clips.importing = false; + cx.notify(); + }) + .ok(); + show_append_error(&error); + } + } + }) + .detach(); + } + // -- Overlays: import menu, record modal, drag ghost ----------------------- pub(crate) fn render_clips_overlays(&mut self, cx: &mut Context) -> Vec { @@ -1301,101 +1648,26 @@ impl EditorWindow { ) } - /// The record modal (`:969-1222`), chrome 1:1. The pickers are drawn - /// disabled: starting a recording that lands back in this project is - /// `setEditorRecordingTarget`, which has no backend in this app yet. + /// The record modal (`:969-1222`), chrome 1:1 and live: the four target + /// actions arm the session's editor recording target and open the picker + /// overlays, the chevron menus unfold the concrete display/window lists, + /// and the device selects read and write the shared recording options on + /// the main window (the gpui home of `rawOptions`). fn render_clips_record_modal(&self, cx: &mut Context) -> Option { if !self.clips.record_open { return None; } let theme = self.theme; - // `TargetTypeButton` (`new-main/TargetTypeButton.tsx:28-48`), the - // name-only variant: `flex flex-col items-center gap-1 rounded-lg - // border py-2 justify-end`, icon `size-5 text-gray-10`, text-xs. - let target_button = |icon: &'static str, name: &'static str, bordered: bool| { - div() - .flex() - .flex_1() - .flex_col() - .items_center() - .justify_end() - .gap(px(4.)) - .py(px(8.)) - .when(bordered, |this| { - this.rounded(px(8.)) - .border_1() - .border_color(Hsla::from(theme.gray_6)) - .bg(Hsla::from(theme.gray_2)) - }) - .text_color(Hsla::from(theme.gray_12)) - .child( - svg() - .path(icon) - .size(px(20.)) - .flex_shrink_0() - .text_color(Hsla::from(theme.gray_10)), - ) - .child(div().text_size(px(12.)).child(name)) - }; - - // The Display/Window split buttons (`:1006-1072`): a shared - // `rounded-lg border border-gray-5 bg-gray-3` shell around the type - // button and a `border-l border-gray-6` chevron. - let split_target = |icon: &'static str, name: &'static str| { - div() - .flex() - .flex_1() - .overflow_hidden() - .rounded(px(8.)) - .border_1() - .border_color(Hsla::from(theme.gray_5)) - .bg(Hsla::from(theme.gray_3)) - .child(target_button(icon, name, false)) - .child( - div() - .flex() - .items_center() - .justify_center() - .px(px(8.)) - .border_l_1() - .border_color(Hsla::from(theme.gray_6)) - .child( - svg() - .path("icons/chevron-down.svg") - .size(px(14.)) - .text_color(Hsla::from(theme.gray_10)), - ), - ) - }; - - // `CameraSelectBase` / `MicrophoneSelectBase`'s shell (`:1106, - // 1119`): `h-[42px] rounded-lg border border-gray-5 bg-gray-3 px-2 - // gap-2`, at the source's `disabled:opacity-70 disabled:text-gray-11`. - let select_row = |icon: &'static str, label: &'static str| { - div() - .flex() - .flex_row() - .items_center() - .gap(px(8.)) - .px(px(8.)) - .w_full() - .h(px(42.)) - .rounded(px(8.)) - .border_1() - .border_color(Hsla::from(theme.gray_5)) - .bg(Hsla::from(theme.gray_3)) - .opacity(0.7) - .text_size(px(13.)) - .text_color(Hsla::from(theme.gray_11)) - .child( - svg() - .path(icon) - .size(px(16.)) - .flex_shrink_0() - .text_color(Hsla::from(theme.gray_10)), - ) - .child(label) + // `activeTargetMenu()` swaps the whole body (`:1001-1217`); the + // device selects reuse the same swap where the Tauri KSelect floats a + // popover, which has no gpui counterpart here. + let body: AnyElement = if let Some(kind) = self.clips.record_target_menu { + self.render_record_target_menu(kind, cx).into_any_element() + } else if let Some(menu) = self.clips.record_device_menu { + self.render_record_device_menu(menu, cx).into_any_element() + } else { + self.render_record_modal_home(cx).into_any_element() }; Some( @@ -1510,7 +1782,8 @@ impl EditorWindow { })), ), ) - // The body (`:1000-1124`), disabled wholesale. + // The body container (`:1000`): `flex flex-col flex-1 + // p-5 min-h-0`. .child( div() .flex() @@ -1518,69 +1791,711 @@ impl EditorWindow { .flex_1() .p(px(20.)) .min_h_0() - .gap(px(16.)) - .child( - div() - .flex() - .flex_col() - .gap(px(8.)) - .w_full() - .opacity(0.6) - .child( - div() - .flex() - .flex_row() - .gap(px(8.)) - .w_full() - .child(split_target("icons/monitor.svg", "Display")) - .child(split_target( - "icons/app-window-mac.svg", - "Window", - )), - ) - .child( - div() - .flex() - .flex_row() - .gap(px(8.)) - .w_full() - .child(target_button( - "icons/area.svg", - "Area", - true, - )) - .child(target_button( - "icons/video.svg", - "Camera Only", - true, - )), - ), - ) - .child( - div() - .flex() - .flex_col() - .gap(px(8.)) - .child(select_row("icons/camera.svg", "No Camera")) - .child(select_row("icons/microphone.svg", "No Microphone")) - .child(select_row("icons/volume-2.svg", "System Audio")), - ) - .child( - div() - .mt_auto() - .text_size(px(12.)) - .text_color(Hsla::from(theme.gray_10)) - .child( - "Recording into an open project isn't wired up \ - in the native editor yet.", - ), - ), + .child(body), ), ) .into_any_element(), ) } + /// The modal's home body (`:1004-1125`): the target grid over the device + /// selects. + fn render_record_modal_home(&self, cx: &mut Context) -> gpui::Div { + let (camera, microphone, system_audio) = record_input_snapshot(cx); + + div() + .flex() + .flex_col() + .flex_1() + .min_h_0() + .gap(px(16.)) + .child( + div() + .flex() + .flex_col() + .gap(px(8.)) + .w_full() + .child( + div() + .flex() + .flex_row() + .gap(px(8.)) + .w_full() + .child(self.record_split_target( + "clips-record-display", + "icons/monitor.svg", + "Display", + TargetType::Display, + cx, + )) + .child(self.record_split_target( + "clips-record-window", + "icons/app-window-mac.svg", + "Window", + TargetType::Window, + cx, + )), + ) + .child( + div() + .flex() + .flex_row() + .gap(px(8.)) + .w_full() + .child(self.record_type_button( + "clips-record-area", + "icons/area.svg", + "Area", + TargetType::Area, + true, + cx, + )) + .child(self.record_type_button( + "clips-record-camera-only", + "icons/video.svg", + "Camera Only", + TargetType::CameraOnly, + true, + cx, + )), + ), + ) + .child( + div() + .flex() + .flex_col() + .gap(px(8.)) + .child(self.record_select_row( + "clips-record-camera", + "icons/camera.svg", + camera.unwrap_or_else(|| "No Camera".to_string()), + true, + cx.listener(|this, _, _window, cx| { + this.clips.record_device_menu = Some(RecordDeviceMenu::Camera); + this.clips.record_target_menu = None; + cx.notify(); + }), + )) + .child(self.record_select_row( + "clips-record-mic", + "icons/microphone.svg", + microphone.unwrap_or_else(|| "No Microphone".to_string()), + true, + cx.listener(|this, _, _window, cx| { + this.clips.record_device_menu = Some(RecordDeviceMenu::Microphone); + this.clips.record_target_menu = None; + cx.notify(); + }), + )) + .child(self.record_select_row( + "clips-record-system-audio", + "icons/volume-2.svg", + if system_audio { + "Record System Audio".to_string() + } else { + "No System Audio".to_string() + }, + false, + cx.listener(|_this, _, _window, cx| { + // A plain toggle, like the main window's own + // system-audio row -- on the shared option, so it + // goes through that window's entity, deferred. + let main = cx.global::().main; + cx.defer(move |cx: &mut gpui::App| { + main.update(cx, |view, _window, cx| { + let next = !view.system_audio_enabled(); + view.set_system_audio(next, cx); + }) + .ok(); + }); + cx.notify(); + }), + )), + ) + } + + /// `TargetTypeButton` (`new-main/TargetTypeButton.tsx:28-48`), the + /// name-only variant: `flex flex-col items-center gap-1 rounded-lg + /// border py-2 justify-end`, icon `size-5 text-gray-10`, text-xs. + fn record_type_button( + &self, + id: &'static str, + icon: &'static str, + name: &'static str, + kind: TargetType, + bordered: bool, + cx: &mut Context, + ) -> gpui::Stateful { + let theme = self.theme; + div() + .id(id) + .flex() + .flex_1() + .flex_col() + .items_center() + .justify_end() + .gap(px(4.)) + .py(px(8.)) + .when(bordered, |this| { + this.rounded(px(8.)) + .border_1() + .border_color(Hsla::from(theme.gray_6)) + .bg(Hsla::from(theme.gray_2)) + }) + .cursor(CursorStyle::PointingHand) + .hover(move |style| style.bg(Hsla::from(theme.gray_4))) + .text_color(Hsla::from(theme.gray_12)) + .child( + svg() + .path(icon) + .size(px(20.)) + .flex_shrink_0() + .text_color(Hsla::from(theme.gray_10)), + ) + .child(div().text_size(px(12.)).child(name)) + .on_click(cx.listener(move |this, _, _window, cx| { + this.open_editor_target_mode(kind, cx); + })) + } + + /// The Display/Window split buttons (`:1006-1072`): a shared `rounded-lg + /// border border-gray-5 bg-gray-3` shell -- the type half opens the + /// picker across every display, the `border-l border-gray-6` chevron + /// unfolds the concrete list. + fn record_split_target( + &self, + id: &'static str, + icon: &'static str, + name: &'static str, + kind: TargetType, + cx: &mut Context, + ) -> gpui::Div { + let theme = self.theme; + div() + .flex() + .flex_1() + .overflow_hidden() + .rounded(px(8.)) + .border_1() + .border_color(Hsla::from(theme.gray_5)) + .bg(Hsla::from(theme.gray_3)) + .child(self.record_type_button(id, icon, name, kind, false, cx)) + .child( + div() + .id(SharedString::from(format!("{id}-menu"))) + .flex() + .items_center() + .justify_center() + .px(px(8.)) + .border_l_1() + .border_color(Hsla::from(theme.gray_6)) + .cursor(CursorStyle::PointingHand) + .hover(move |style| style.bg(Hsla::from(theme.gray_5))) + .child( + svg() + .path("icons/chevron-down.svg") + .size(px(14.)) + .text_color(Hsla::from(theme.gray_10)), + ) + .on_click(cx.listener(move |this, _, window, cx| { + this.toggle_record_target_menu(kind, window, cx); + })), + ) + } + + /// `CameraSelectBase` / `MicrophoneSelectBase`'s shell (`:1106, 1119`): + /// `h-[42px] rounded-lg border border-gray-5 bg-gray-3 px-2 gap-2`, live. + /// `chevron` marks the two pickers; the system-audio toggle goes without. + fn record_select_row( + &self, + id: &'static str, + icon: &'static str, + label: String, + chevron: bool, + on_click: impl Fn(&gpui::ClickEvent, &mut Window, &mut gpui::App) + 'static, + ) -> gpui::Stateful { + let theme = self.theme; + div() + .id(id) + .flex() + .flex_row() + .items_center() + .gap(px(8.)) + .px(px(8.)) + .w_full() + .h(px(42.)) + .rounded(px(8.)) + .border_1() + .border_color(Hsla::from(theme.gray_5)) + .bg(Hsla::from(theme.gray_3)) + .cursor(CursorStyle::PointingHand) + .hover(move |style| style.bg(Hsla::from(theme.gray_4))) + .text_size(px(13.)) + .text_color(Hsla::from(theme.gray_12)) + .child( + svg() + .path(icon) + .size(px(16.)) + .flex_shrink_0() + .text_color(Hsla::from(theme.gray_10)), + ) + .child(div().flex_1().min_w_0().truncate().child(label)) + .when(chevron, |this| { + this.child( + svg() + .path("icons/chevron-down.svg") + .size(px(12.)) + .flex_shrink_0() + .text_color(Hsla::from(theme.gray_10)), + ) + }) + .on_click(on_click) + } + + /// The back row shared by the unfolded menus (`:1129-1145`). + fn record_menu_back(&self, cx: &mut Context) -> gpui::Stateful { + let theme = self.theme; + div() + .id("clips-record-menu-back") + .flex() + .flex_none() + .flex_row() + .items_center() + .gap(px(4.)) + .h(px(36.)) + .px(px(8.)) + .rounded(px(6.)) + .text_size(px(12.)) + .text_color(Hsla::from(theme.gray_11)) + .cursor(CursorStyle::PointingHand) + .hover(move |style| style.bg(Hsla::from(theme.gray_4))) + .child( + svg() + .path("icons/move-left.svg") + .size(px(12.)) + .flex_shrink_0() + .text_color(Hsla::from(theme.gray_11)), + ) + .child( + div() + .font_weight(FontWeight::MEDIUM) + .text_color(Hsla::from(theme.gray_12)) + .child("Back"), + ) + .on_click(cx.listener(|this, _, _window, cx| { + this.clips.record_target_menu = None; + this.clips.record_device_menu = None; + cx.notify(); + })) + } + + /// A chevron menu's body (`:1128-1216`): back, search, and the concrete + /// targets -- `TargetMenuGrid` as two columns of cards, with the icon + /// block standing in for the live thumbnail for the capture-pipeline + /// reason `MainWindow::render_target_grid` documents. + fn render_record_target_menu(&self, kind: TargetType, cx: &mut Context) -> gpui::Div { + let theme = self.theme; + let query = self.target_search_text(cx); + let matches = |value: &str| query.is_empty() || value.to_lowercase().contains(&query); + + let loading = self.clips.record_devices.is_none(); + let mut cards: Vec = Vec::new(); + if let Some(devices) = &self.clips.record_devices { + match kind { + TargetType::Display => { + for display in devices + .displays + .iter() + .filter(|display| matches(&display.label)) + { + let chosen = display.clone(); + cards.push( + self.record_target_card( + SharedString::from(format!("record-display-{}", display.id)), + "icons/monitor.svg", + display.label.clone(), + None, + display.describe_refresh_rate(), + cx.listener(move |this, _, _window, cx| { + this.select_record_display(chosen.clone(), cx); + }), + ) + .into_any_element(), + ); + } + } + TargetType::Window => { + for target in devices + .windows + .iter() + .filter(|window| matches(&window.label) || matches(&window.app)) + { + let chosen = target.clone(); + cards.push( + self.record_target_card( + SharedString::from(format!("record-window-{}", target.id)), + "icons/app-window-mac.svg", + target.label.clone(), + Some(target.app.clone()), + target.describe_metadata(), + cx.listener(move |this, _, _window, cx| { + this.select_record_window(chosen.clone(), cx); + }), + ) + .into_any_element(), + ); + } + } + TargetType::Area | TargetType::CameraOnly => {} + } + } + + let empty_message = |theme: &Theme, message: &'static str| { + div() + .py(px(24.)) + .w_full() + .text_size(px(13.)) + .text_color(Hsla::from(theme.gray_11)) + .child(message) + }; + + let content: AnyElement = if loading { + empty_message(&theme, "Loading...").into_any_element() + } else if cards.is_empty() { + empty_message( + &theme, + match (kind, query.is_empty()) { + (TargetType::Display, true) => "No displays found", + (TargetType::Display, false) => "No matching displays", + (_, true) => "No windows found", + (_, false) => "No matching windows", + }, + ) + .into_any_element() + } else { + // Two columns as rows of two, the `render_target_grid` layout; a + // lone trailing card keeps half width. + let mut grid = div().flex().flex_col().gap(px(8.)).w_full(); + let mut cards = cards.into_iter(); + while let Some(first) = cards.next() { + let mut row = div() + .flex() + .flex_row() + .gap(px(8.)) + .w_full() + .items_stretch() + .child(first); + row = match cards.next() { + Some(second) => row.child(second), + None => row.child(div().flex_1()), + }; + grid = grid.child(row); + } + grid.into_any_element() + }; + + div() + .flex() + .flex_col() + .flex_1() + .min_h_0() + .child( + div() + .flex() + .flex_none() + .flex_row() + .items_center() + .gap(px(12.)) + .min_h(px(36.)) + .child(self.record_menu_back(cx)) + .children(self.clips.target_search_input.as_ref().map(|input| { + div().flex_1().min_w_0().child( + ui::TextInput::plain(&theme, "clips-record-target-search", input) + .height(px(36.)) + .text_size(px(13.)) + .padding_x(px(8.)) + .radius(px(6.)) + .bg(Hsla::from(theme.gray_3)) + .border(Hsla::from(theme.gray_5)) + .flex(true), + ) + })), + ) + .child( + div() + .id("clips-record-menu-scroll") + .flex_1() + .min_h_0() + .pt(px(16.)) + .overflow_y_scroll() + .child(content), + ) + } + + /// `TargetCard`, icon-fallback form. + #[allow(clippy::too_many_arguments)] + fn record_target_card( + &self, + id: SharedString, + icon: &'static str, + label: String, + subtitle: Option, + metadata: Option, + on_click: impl Fn(&gpui::ClickEvent, &mut Window, &mut gpui::App) + 'static, + ) -> gpui::Stateful { + let theme = self.theme; + div() + .id(id) + .flex() + .flex_col() + .flex_1() + .min_w_0() + .overflow_hidden() + .rounded(px(8.)) + .bg(theme.body_fill(3)) + .cursor(CursorStyle::PointingHand) + .child( + div() + .flex() + .items_center() + .justify_center() + .w_full() + .h(px(76.)) + .bg(theme.body_fill(4)) + .child( + svg() + .path(icon) + .size(px(24.)) + .text_color(Hsla::from(theme.gray_9)), + ), + ) + .child( + div() + .flex() + .flex_col() + .w_full() + .min_w_0() + .px(px(8.)) + .py(px(6.)) + .text_size(px(11.)) + .child( + div() + .w_full() + .truncate() + .font_weight(FontWeight::MEDIUM) + .text_color(Hsla::from(theme.gray_12)) + .child(label), + ) + .children(subtitle.map(|subtitle| { + div() + .w_full() + .truncate() + .text_color(Hsla::from(theme.gray_11)) + .child(subtitle) + })) + .children(metadata.map(|metadata| { + div() + .w_full() + .truncate() + .text_color(Hsla::from(theme.gray_10)) + .child(metadata) + })), + ) + .hover(move |style| style.bg(theme.body_hover_fill(4))) + .on_click(on_click) + } + + /// A device select unfolded (`:1084-1122`): the "none" row first -- + /// turning the device off is not a search result -- then the enumerated + /// options, exactly the main window panel's ordering. The selection + /// lands on the shared options through the same setters that window's + /// own rows use, deferred because it is another window's entity. + fn render_record_device_menu( + &self, + menu: RecordDeviceMenu, + cx: &mut Context, + ) -> gpui::Div { + let theme = self.theme; + let (camera, microphone, _) = record_input_snapshot(cx); + + let mut rows: Vec = Vec::new(); + + let none_selected = match menu { + RecordDeviceMenu::Camera => camera.is_none(), + RecordDeviceMenu::Microphone => microphone.is_none(), + }; + rows.push( + self.record_device_row( + SharedString::from("clips-record-device-none"), + "icons/circle-x.svg", + match menu { + RecordDeviceMenu::Camera => "No Camera", + RecordDeviceMenu::Microphone => "No Microphone", + } + .to_string(), + none_selected, + cx.listener(move |this, _, _window, cx| match menu { + RecordDeviceMenu::Camera => this.set_record_camera(None, cx), + RecordDeviceMenu::Microphone => this.set_record_microphone(None, cx), + }), + ) + .into_any_element(), + ); + + match &self.clips.record_devices { + None => rows.push( + div() + .py(px(16.)) + .w_full() + .text_size(px(13.)) + .text_color(Hsla::from(theme.gray_11)) + .child("Loading...") + .into_any_element(), + ), + Some(devices) => match menu { + RecordDeviceMenu::Camera => { + for option in &devices.cameras { + let selected = camera.as_deref() == Some(option.label.as_str()); + let chosen = option.clone(); + rows.push( + self.record_device_row( + SharedString::from(format!( + "clips-record-camera-{}", + option.device_id + )), + "icons/camera.svg", + option.label.clone(), + selected, + cx.listener(move |this, _, _window, cx| { + this.set_record_camera(Some(chosen.clone()), cx); + }), + ) + .into_any_element(), + ); + } + } + RecordDeviceMenu::Microphone => { + for option in &devices.microphones { + let selected = microphone.as_deref() == Some(option.name.as_str()); + let chosen = option.clone(); + rows.push( + self.record_device_row( + SharedString::from(format!("clips-record-mic-{}", option.name)), + "icons/microphone.svg", + option.name.clone(), + selected, + cx.listener(move |this, _, _window, cx| { + this.set_record_microphone(Some(chosen.clone()), cx); + }), + ) + .into_any_element(), + ); + } + } + }, + } + + div() + .flex() + .flex_col() + .flex_1() + .min_h_0() + .child( + div() + .flex() + .flex_none() + .flex_row() + .items_center() + .min_h(px(36.)) + .child(self.record_menu_back(cx)), + ) + .child( + div() + .id("clips-record-device-scroll") + .flex_1() + .min_h_0() + .pt(px(16.)) + .overflow_y_scroll() + .child(div().flex().flex_col().gap(px(8.)).children(rows)), + ) + } + + /// One row of a device select. + fn record_device_row( + &self, + id: SharedString, + icon: &'static str, + label: String, + selected: bool, + on_click: impl Fn(&gpui::ClickEvent, &mut Window, &mut gpui::App) + 'static, + ) -> gpui::Stateful { + let theme = self.theme; + div() + .id(id) + .flex() + .flex_row() + .items_center() + .gap(px(8.)) + .px(px(8.)) + .w_full() + .h(px(42.)) + .rounded(px(8.)) + .border_1() + .border_color(if selected { + Hsla::from(theme.blue_9) + } else { + Hsla::from(theme.gray_5) + }) + .bg(Hsla::from(theme.gray_3)) + .cursor(CursorStyle::PointingHand) + .hover(move |style| style.bg(Hsla::from(theme.gray_4))) + .text_size(px(13.)) + .text_color(Hsla::from(theme.gray_12)) + .child( + svg() + .path(icon) + .size(px(16.)) + .flex_shrink_0() + .text_color(Hsla::from(theme.gray_10)), + ) + .child(div().flex_1().min_w_0().truncate().child(label)) + .on_click(on_click) + } + + /// `createCameraMutation` from the modal (`:1089-1103`), routed through + /// the main window's setter (state plus the app-scoped feed, preview + /// bubble included). The `skipCameraWindow: isCameraOnly` refinement has + /// no seam here -- the bubble opens either way, as it does for every + /// other selection path in this app. + fn set_record_camera(&mut self, camera: Option, cx: &mut Context) { + self.clips.record_device_menu = None; + let main = cx.global::().main; + cx.defer(move |cx: &mut gpui::App| { + main.update(cx, |view, _window, cx| { + view.set_camera_selection(camera, cx) + }) + .ok(); + }); + cx.notify(); + } + + /// `setOptions("micName", ...) + commands.setMicInput` (`:1113-1116`). + fn set_record_microphone( + &mut self, + microphone: Option, + cx: &mut Context, + ) { + self.clips.record_device_menu = None; + let main = cx.global::().main; + cx.defer(move |cx: &mut gpui::App| { + main.update(cx, |view, _window, cx| { + view.set_microphone_selection(microphone, cx) + }) + .ok(); + }); + cx.notify(); + } + // -- Import (`ClipsSidebar.tsx:503-539`) ----------------------------------- /// `pickCapRecording` (`:533-539`): the `.cap` picker, rooted at the @@ -1686,6 +2601,33 @@ fn show_import_error(message: &str) { .show(); } +/// `toast.error(\`Failed to add clip: ...\`)` (`Editor.tsx:332-333`), same +/// stand-in. +fn show_append_error(message: &str) { + let _ = rfd::MessageDialog::new() + .set_title("Recording Error") + .set_description(format!("Failed to add clip: {message}")) + .set_level(rfd::MessageLevel::Error) + .set_buttons(rfd::MessageButtons::Ok) + .show(); +} + +/// The shared recording options, read off the main window -- the gpui home of +/// `rawOptions`: the selected camera's label, the microphone name, and the +/// system-audio flag, in that order. +fn record_input_snapshot(cx: &gpui::App) -> (Option, Option, bool) { + let main = cx.global::().main; + main.read(cx) + .map(|view| { + ( + view.camera_selection().map(|camera| camera.label.clone()), + view.microphone_selection().map(|mic| mic.name.clone()), + view.system_audio_enabled(), + ) + }) + .unwrap_or((None, None, false)) +} + // --------------------------------------------------------------------------- // Thumbnail decode -- `decode_clip_thumbnail` // (`src-tauri/src/clip_thumbnails.rs:67-215`), minus the JPEG cache: the diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index 4b5baaca5e..32bf443915 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -1133,14 +1133,30 @@ impl MainWindow { if self.session.read(cx).phase != Phase::Idle { return; } - let Some(mode) = self.recording_mode() else { - return; + // An armed editor recording target forces Studio before this window's + // own mode is consulted -- the front half of the same rule + // `start_recording` applies (`src-tauri/src/recording.rs:1492-1494`), + // needed here because a Screenshot-mode pill maps to no recording + // mode at all and would otherwise bail out of an editor-flow start. + let editor_flow = self.session.read(cx).editor_recording_target().is_some(); + let mode = if editor_flow { + recording::RecordingMode::Studio + } else { + match self.recording_mode() { + Some(mode) => mode, + None => return, + } }; if matches!(target, ScreenCaptureTarget::CameraOnly) && self.camera.is_none() { self.session.update(cx, |session, cx| { session.error = Some("Camera-only recording requires a selected camera.".into()); cx.notify(); }); + if editor_flow { + // No session phase transition is coming; restore the hidden + // editor the way a cancelled picker would. + cx.defer(app_windows::abort_editor_recording_flow); + } return; } @@ -1165,6 +1181,62 @@ impl MainWindow { cx.defer(move |cx: &mut gpui::App| app_windows::begin_recording(config, cx)); } + + // -- The editor record modal's device seams ------------------------------ + // + // The Tauri editor modal reads and writes the *same* recording-options + // store the main window uses (`useRecordingOptions` + `createCameraMutation` + // + `setMicInput`, `ClipsSidebar.tsx:1084-1123`); in this app that store is + // this window's state, so the modal reaches it through these. + + /// The camera the next recording will use, for whoever else renders it. + pub fn camera_selection(&self) -> Option<&CameraOption> { + self.camera.as_ref() + } + + /// The microphone the next recording will use. + pub fn microphone_selection(&self) -> Option<&MicrophoneOption> { + self.microphone.as_ref() + } + + /// Whether the next recording captures system audio. + pub fn system_audio_enabled(&self) -> bool { + self.system_audio + } + + /// Select (or clear) the camera -- the same wiring as the camera panel's + /// rows: this window's state plus the app-scoped feed, which opens or + /// closes the preview bubble. + pub fn set_camera_selection(&mut self, camera: Option, cx: &mut Context) { + let selection = camera.as_ref().map(|camera| feeds::SelectedCamera { + id: recording::DeviceOrModelID::DeviceID(camera.device_id.clone()), + label: camera.label.clone(), + }); + self.camera = camera; + Feeds::global(cx).update(cx, |feeds, cx| feeds.set_camera(selection, cx)); + cx.notify(); + } + + /// Select (or clear) the microphone -- state plus the app-scoped feed, the + /// mic panel rows' wiring. + pub fn set_microphone_selection( + &mut self, + microphone: Option, + cx: &mut Context, + ) { + let label = microphone.as_ref().map(|mic| mic.name.clone()); + self.microphone = microphone; + Feeds::global(cx).update(cx, |feeds, cx| feeds.set_microphone(label, cx)); + cx.notify(); + } + + /// Toggle system audio capture -- the system-audio row is a plain flag. + pub fn set_system_audio(&mut self, enabled: bool, cx: &mut Context) { + if self.system_audio != enabled { + self.system_audio = enabled; + cx.notify(); + } + } } impl Render for MainWindow { diff --git a/apps/desktop-gpui/src/recording.rs b/apps/desktop-gpui/src/recording.rs index 13ac2a571d..b60f0b58b9 100644 --- a/apps/desktop-gpui/src/recording.rs +++ b/apps/desktop-gpui/src/recording.rs @@ -724,6 +724,43 @@ pub fn recordings_dir() -> PathBuf { crate::store::app_data_dir().join("recordings") } +/// `delete_recording_directory` (`src-tauri/src/lib.rs:4006-4051`), for the +/// clip the editor-append flow just copied out of: reject `..` components, +/// require the path to live inside the recordings library (canonically, so a +/// symlink cannot escape it), then `remove_dir_all`. The Tauri command accepts +/// every known storage folder; this app resolves exactly one +/// ([`recordings_dir`]), so that one is the whole allow-list. +pub fn delete_recording_directory(path: &std::path::Path) -> Result<(), String> { + if path + .components() + .any(|component| matches!(component, std::path::Component::ParentDir)) + { + return Err("Invalid path".to_string()); + } + + let recordings = recordings_dir(); + if !path.starts_with(&recordings) { + return Err("Path is not inside the recordings directory".to_string()); + } + + if path.exists() { + let canonical = path + .canonicalize() + .map_err(|e| format!("Failed to resolve recording path: {e}"))?; + let inside = recordings + .canonicalize() + .map(|dir| canonical.starts_with(&dir)) + .unwrap_or(false); + if !inside { + return Err("Path is not inside the recordings directory".to_string()); + } + std::fs::remove_dir_all(&canonical) + .map_err(|e| format!("Failed to delete recording: {e}"))?; + } + + Ok(()) +} + /// `format_project_name` with the default template /// (`{target_name} ({target_kind}) {date} {time}`), then the same `:` -> `.` /// replacement and uniquing the Tauri app applies. @@ -873,6 +910,22 @@ mod tests { std::fs::remove_dir_all(&empty).ok(); } + /// The delete guard refuses anything that could reach outside the + /// recordings library -- `..` components before any IO at all, and a path + /// that is not under the library root. (The happy path is exercised by + /// the editor-append flow; it depends on the user's configured library + /// location, which a unit test must not touch.) + #[test] + fn recording_delete_guards_the_library_root() { + assert!(delete_recording_directory(std::path::Path::new("/tmp/../etc")).is_err()); + assert!( + delete_recording_directory(std::path::Path::new( + "/System/definitely-not-the-cap-library.cap" + )) + .is_err() + ); + } + /// The spelling has to match `BackgroundBlurMode`'s serde exactly or the /// editor's `#[serde(default)]` would quietly swallow the value. #[test] diff --git a/apps/desktop-gpui/src/session.rs b/apps/desktop-gpui/src/session.rs index 35e3458157..b3de9f64d8 100644 --- a/apps/desktop-gpui/src/session.rs +++ b/apps/desktop-gpui/src/session.rs @@ -40,6 +40,16 @@ pub struct RecordingSession { /// Taken by the phase observer to honour `postStudioRecordingBehaviour` /// (`openEditor` by default) the way the Tauri app does. pub finished_studio: Option, + /// `EditorRecordingTarget` (`src-tauri/src/windows.rs:3679-3697`): the + /// open editor project a "Record a new clip" capture must land back in. + /// Set by the editor's record modal (`setEditorRecordingTarget`, + /// `ClipsSidebar.tsx:444`), cleared when its picker is cancelled, and + /// *taken* -- never merely read -- by the phase observer when the session + /// comes back to rest, exactly the way `apply_post_studio_editor_behaviour` + /// and the stop-cleanup fallback both `take()` it + /// (`src-tauri/src/recording.rs:3231-3287`) so a stale target can never + /// leak into the next recording. + editor_recording_target: Option, started_at: Option, paused_accum: Duration, paused_since: Option, @@ -58,6 +68,7 @@ impl RecordingSession { controls_open: false, mic_muted: false, finished_studio: None, + editor_recording_target: None, started_at: None, paused_accum: Duration::ZERO, paused_since: None, @@ -116,6 +127,25 @@ impl RecordingSession { cx.notify(); } + /// `set_editor_recording_target` (`src-tauri/src/lib.rs:3166-3172`): + /// arm (or disarm) the editor project the next studio recording appends + /// into. No phase guard here -- the guard lives at the call sites, the way + /// the Tauri command is a bare state write. + pub fn set_editor_recording_target(&mut self, target: Option) { + self.editor_recording_target = target; + } + + /// `EditorRecordingTarget::current`. + pub fn editor_recording_target(&self) -> Option { + self.editor_recording_target.clone() + } + + /// `EditorRecordingTarget::take` -- the consuming read both finish paths + /// use, so the target clears no matter how the recording ended. + pub fn take_editor_recording_target(&mut self) -> Option { + self.editor_recording_target.take() + } + pub fn set_controls_open(&mut self, open: bool, cx: &mut Context) { if self.controls_open != open { self.controls_open = open; From 72042355a3d2584d217fd59a3bdf7f8e724f118b Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:04:11 +0100 Subject: [PATCH 144/146] fix(desktop-gpui): hotkey parity audit fixes; drop the overlay setting From a shortcut-by-shortcut audit against the Tauri app: one OS registration per combo with every bound action dispatched (a second action on a shared combo was silently dropped), hotkey-started recordings honour the stored target instead of always the primary display, and the mic-confirm dialog is ported with the same store toggle and availability probe. The Shortcuts page no longer re-registers mid-capture, and a captured code is stored only if it parses -- one unparseable code makes the Tauri app drop every binding. The "After a Studio recording" selector is gone from General: the recordings overlay will not exist in this app, so the option is not surfaced. A Tauri-written showOverlay value still reads fine and maps to reshowing the main window. --- apps/desktop-gpui/src/hotkeys.rs | 234 +++++++++- apps/desktop-gpui/src/menus.rs | 4 + apps/desktop-gpui/src/settings_pages.rs | 517 ++++++++++++++++++++++- apps/desktop-gpui/src/settings_window.rs | 36 +- 4 files changed, 737 insertions(+), 54 deletions(-) diff --git a/apps/desktop-gpui/src/hotkeys.rs b/apps/desktop-gpui/src/hotkeys.rs index 7c3f5b2bb3..1335dd87c1 100644 --- a/apps/desktop-gpui/src/hotkeys.rs +++ b/apps/desktop-gpui/src/hotkeys.rs @@ -11,11 +11,10 @@ //! //! The overlay's global Escape stays on its own Carbon registration //! (`platform::register_escape_hotkey`) exactly as it is separate in the -//! Tauri app (`target_select_overlay.rs`, not a `HotkeyAction`). -//! -//! Deviation: the Tauri handler's mic-confirm dialog before a hotkey start -//! (`confirm_before_recording_without_microphone`) is not ported yet -- it -//! belongs with the recording countdown / mic-confirm parity unit. +//! Tauri app (`target_select_overlay.rs`, not a `HotkeyAction`). The Tauri +//! handler's `OnEscapePress` emit only ever reaches that overlay too -- its +//! sole listener is `target-select-overlay.tsx` -- so the native registration +//! covers the whole behaviour. use std::str::FromStr as _; @@ -24,12 +23,12 @@ use global_hotkey::{ GlobalHotKeyEvent, GlobalHotKeyManager, HotKeyState, hotkey::{Code, HotKey, Modifiers}, }; -use gpui::{App, Global}; +use gpui::{App, Global, PromptButton, PromptLevel}; use crate::{ app_windows, main_window::{Mode, TargetType}, - session::RecordingSession, + session::{Phase, RecordingSession}, store, }; @@ -122,7 +121,14 @@ pub fn reload(cx: &mut App) { return; } let hotkeys = cx.global_mut::(); - let old: Vec = hotkeys.bindings.iter().map(|(hotkey, _)| *hotkey).collect(); + // Two actions on one combo share one OS registration, so unregister each + // combo once. + let mut old: Vec = Vec::new(); + for (hotkey, _) in &hotkeys.bindings { + if !old.iter().any(|existing| existing.id() == hotkey.id()) { + old.push(*hotkey); + } + } if let Err(error) = hotkeys.manager.unregister_all(&old) { tracing::warn!("unregistering global hotkeys failed: {error}"); } @@ -160,6 +166,18 @@ fn register_from_store(cx: &mut App) { mods |= Modifiers::SHIFT; } let hotkey = HotKey::new(Some(mods), code); + // The same combo bound to a second action keeps the one OS + // registration; the dispatch fires every action on it, the way the + // Tauri handler loops the whole map per press (`hotkeys.rs:201-205` + // over there). + if hotkeys + .bindings + .iter() + .any(|(existing, _)| existing.id() == hotkey.id()) + { + hotkeys.bindings.push((hotkey, action)); + continue; + } match hotkeys.manager.register(hotkey) { Ok(()) => hotkeys.bindings.push((hotkey, action)), Err(error) => { @@ -180,26 +198,39 @@ pub fn dispatch_for_harness(key: &str, cx: &mut App) { } } +// The Tauri handler carries a ⌘, special case (`shortcut.mods == +// Modifiers::META && shortcut.key == Code::Comma` opens Settings, +// `hotkeys.rs:191-196` over there) that is dead code at this pin: +// `HotKey::new` rewrites META into SUPER on every registration path, so no +// shortcut the handler ever sees carries META. Not reproduced -- the audit +// keeps the *observed* behaviour, which is that a Comma binding runs its +// action and nothing else. fn dispatch_id(id: u32, cx: &mut App) { - let action = cx - .global::() - .bindings - .iter() - .find(|(hotkey, _)| hotkey.id() == id) - .map(|(_, action)| *action); - if let Some(action) = action { + let actions = actions_for(&cx.global::().bindings, id); + for action in actions { dispatch(action, cx); } } +/// Every action bound to the pressed combo, in store order. The Tauri handler +/// loops the whole map per press (`hotkeys.rs:201-205` over there), so two +/// actions sharing one binding both fire. +fn actions_for(bindings: &[(HotKey, HotkeyAction)], id: u32) -> Vec { + bindings + .iter() + .filter(|(hotkey, _)| hotkey.id() == id) + .map(|(_, action)| *action) + .collect() +} + /// `handle_hotkey` (`hotkeys.rs:228-337` over there), against this app's /// counterparts. Session methods are no-ops in the wrong phase, matching the /// Tauri commands' own early returns. fn dispatch(action: HotkeyAction, cx: &mut App) { tracing::info!(?action, "global hotkey"); match action { - HotkeyAction::StartStudioRecording => start_primary_display(Mode::Studio, cx), - HotkeyAction::StartInstantRecording => start_primary_display(Mode::Instant, cx), + HotkeyAction::StartStudioRecording => start_from_hotkey(Mode::Studio, cx), + HotkeyAction::StartInstantRecording => start_from_hotkey(Mode::Instant, cx), HotkeyAction::StopRecording => { RecordingSession::global(cx).update(cx, |session, cx| session.stop(cx)); } @@ -253,17 +284,174 @@ fn dispatch(action: HotkeyAction, cx: &mut App) { } } -/// `RequestStartRecording`'s listener with no armed target records the -/// primary display (`lib.rs:5517-5538` over there); mic/camera come off the -/// main window's current selections through its ordinary start path. -fn start_primary_display(mode: Mode, cx: &mut App) { +/// `start_recording_from_hotkey` + the `RequestStartRecording` listener +/// (`hotkeys.rs:158-177`, `lib.rs:5529-5551` over there): confirm a start +/// without a working microphone, then record the store's +/// `recording_settings.target` -- whatever either app recorded last -- with +/// the primary display as the fallback. Mic/camera come off the main window's +/// current selections through its ordinary start path. +/// +/// The mode is set through [`app_windows::set_recording_mode`] first, so the +/// pill, the tray tick and the shared store all follow the hotkey; the Tauri +/// listener hands the mode straight to the engine and leaves the setting +/// where it was. Deliberate: this app's start path reads the window's mode, +/// and a recording visibly in Studio mode with the pill saying Instant is the +/// worse mismatch. +fn start_from_hotkey(mode: Mode, cx: &mut App) { app_windows::set_recording_mode(mode, cx); - let target = ScreenCaptureTarget::Display { + let target = stored_target().unwrap_or_else(|| ScreenCaptureTarget::Display { id: scap_targets::Display::primary().id(), - }; + }); let main = cx.global::().main; + + // `is_recording_active_or_pending` (`hotkeys.rs:162-170` over there): an + // active session skips the confirm -- the re-start is a no-op either way, + // `start_recording_with_target`'s phase check here and `start_recording`'s + // over there. + let idle = RecordingSession::global(cx).read(cx).phase == Phase::Idle; + let confirm_enabled = store::GeneralSettings::load().confirm_without_microphone; + if !idle || !confirm_enabled { + start_now(main, target, cx); + return; + } + let mic_name = main.read(cx).ok().and_then(|view| { + view.microphone_selection() + .map(|microphone| microphone.name.clone()) + }); + + cx.spawn(async move |cx| { + // `MicrophoneFeed::list().contains_key(name)` -- the exact + // availability probe the Tauri confirm runs (`hotkeys.rs:126-135` + // over there), on the background executor because it enumerates + // CoreAudio devices. No selection means no microphone, which needs no + // enumeration to know. + let available = match mic_name { + Some(name) => { + cx.background_executor() + .spawn(async move { + cap_recording::feeds::microphone::MicrophoneFeed::list().contains_key(&name) + }) + .await + } + None => false, + }; + if available { + cx.update(|cx| start_now(main, target, cx)); + return; + } + // The Tauri dialog, word for word + // (`confirm_direct_recording_without_microphone`). gpui prompts are + // window sheets rather than free-standing alerts, so the main window + // comes forward to host it. + let receiver = cx.update(|cx| { + app_windows::show_main_window(cx); + cx.activate(true); + main.update(cx, |_, window, cx| { + window.prompt( + PromptLevel::Warning, + "No microphone detected", + Some("This recording will not include your voice."), + &[ + PromptButton::ok("Record without microphone"), + PromptButton::cancel("Cancel"), + ], + cx, + ) + }) + }); + let Ok(receiver) = receiver else { + return; + }; + // `receiver.await.unwrap_or(false)`: a dismissed sheet records + // nothing. + if receiver.await == Ok(0) { + cx.update(|cx| start_now(main, target, cx)); + } + }) + .detach(); +} + +fn start_now( + main: gpui::WindowHandle, + target: ScreenCaptureTarget, + cx: &mut App, +) { main.update(cx, |view, window, cx| { view.start_recording_with_target(target, Vec::new(), window, cx) }) .ok(); } + +/// `RecordingSettingsStore.target`, straight off the shared store's JSON. A +/// missing key, a `null`, or a shape this build's `ScreenCaptureTarget` does +/// not know is `None` -- the primary-display fallback. +fn stored_target() -> Option { + let value = store::store_section(store::RECORDING_SETTINGS).remove("target")?; + serde_json::from_value(value).ok() +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Every store key the Tauri `HotkeyAction`'s camelCase serde produces + /// maps to a variant here, and an unknown key registers nothing. + #[test] + fn action_keys_match_the_tauri_serde_names() { + for key in [ + "startStudioRecording", + "startInstantRecording", + "stopRecording", + "restartRecording", + "togglePauseRecording", + "cycleRecordingMode", + "openRecordingPicker", + "openRecordingPickerDisplay", + "openRecordingPickerWindow", + "openRecordingPickerArea", + "screenshotDisplay", + "screenshotWindow", + "screenshotArea", + ] { + assert!( + HotkeyAction::from_store_key(key).is_some(), + "{key} should map to an action" + ); + } + assert_eq!(HotkeyAction::from_store_key("someFutureAction"), None); + } + + /// Two actions on the same combo both fire on one press -- the Tauri + /// handler loops the whole map (`hotkeys.rs:201-205` over there). + #[test] + fn a_shared_binding_fires_every_action_on_it() { + let stop = HotKey::new(Some(Modifiers::META | Modifiers::SHIFT), Code::KeyS); + let restart = HotKey::new(Some(Modifiers::META | Modifiers::SHIFT), Code::KeyS); + let pause = HotKey::new(Some(Modifiers::META), Code::KeyP); + assert_eq!(stop.id(), restart.id()); + + let bindings = vec![ + (stop, HotkeyAction::StopRecording), + (pause, HotkeyAction::TogglePauseRecording), + (restart, HotkeyAction::RestartRecording), + ]; + assert_eq!( + actions_for(&bindings, stop.id()), + vec![HotkeyAction::StopRecording, HotkeyAction::RestartRecording] + ); + assert_eq!( + actions_for(&bindings, pause.id()), + vec![HotkeyAction::TogglePauseRecording] + ); + } + + /// Why the Tauri handler's ⌘, special case is not reproduced (see + /// [`dispatch_id`]): registration rewrites META into SUPER, so its + /// `mods == Modifiers::META` guard can never hold. + #[test] + fn registration_never_leaves_meta_in_the_modifiers() { + let settings = HotKey::new(Some(Modifiers::META), Code::Comma); + assert!(!settings.mods.contains(Modifiers::META)); + assert!(settings.mods.contains(Modifiers::SUPER)); + } +} diff --git a/apps/desktop-gpui/src/menus.rs b/apps/desktop-gpui/src/menus.rs index 9f93a8b2a6..c2b750b95c 100644 --- a/apps/desktop-gpui/src/menus.rs +++ b/apps/desktop-gpui/src/menus.rs @@ -262,6 +262,10 @@ pub fn close_window_by_handle(handle: gpui::AnyWindowHandle, cx: &mut App) { /// quit waits for the session to come back to `Idle` (bounded, so a wedged /// finalize cannot make the app unquittable). pub fn quit(cx: &mut App) { + // Reaching a deliberate quit is proof enough that this build came up, even + // if it happens inside the marker's ten-second window. + crate::store::clear_handoff_marker(); + let session = RecordingSession::global(cx); if session.read(cx).phase == Phase::Idle { cx.quit(); diff --git a/apps/desktop-gpui/src/settings_pages.rs b/apps/desktop-gpui/src/settings_pages.rs index dd134269dc..389b92f2e3 100644 --- a/apps/desktop-gpui/src/settings_pages.rs +++ b/apps/desktop-gpui/src/settings_pages.rs @@ -9,7 +9,7 @@ //! two apps keep reading each other's state. Web calls run on the gpui_tokio //! runtime (reqwest needs tokio); persistence goes through [`crate::store`]. -use std::{collections::HashMap, time::Duration}; +use std::{collections::HashMap, str::FromStr as _, time::Duration}; use gpui::{ AppContext as _, Context, Entity, FontWeight, Hsla, InteractiveElement, IntoElement, @@ -273,6 +273,12 @@ pub(crate) struct PagesState { s3: S3Page, gdrive: GDrivePage, + // Experimental (experimental.tsx). The Native app row's hand-back takes the + // whole settings window over, so its clock and the ticker that repaints the + // sequence live here and the overlay renders from the window root. + switch_back: Option, + switch_back_ticker: Option>, + // Automations (automations.tsx) automations: AutomationsStore, automations_loaded: bool, @@ -377,6 +383,8 @@ impl PagesState { error: None, poll: None, }, + switch_back: None, + switch_back_ticker: None, automations: AutomationsStore::default(), automations_loaded: false, expanded_rule: None, @@ -933,6 +941,13 @@ impl SettingsWindow { let Some(code) = hotkey_code_for_key(&keystroke.key) else { return true; }; + // A code the Tauri app cannot parse must never reach the store: its + // `HotkeysStore` deserializes the whole `hotkeys` map strictly + // (`serde_json::from_value` in `HotkeysStore::get`), so one bad entry + // would silently drop every binding over there. + if global_hotkey::hotkey::Code::from_str(&code).is_err() { + return true; + } let Some(listening) = self.pages.listening.as_ref() else { return true; }; @@ -946,23 +961,35 @@ impl SettingsWindow { }; let value = serde_json::to_value(&hotkey).unwrap_or(Value::Null); self.pages.hotkeys.insert(action.to_string(), value); - // `createEffect` persists on every store change, captures included. - self.shortcuts_persist(cx); + // `createEffect` persists on every store change, captures included -- + // but the OS registration waits for the confirm, exactly as + // `commands.setHotkey` only runs from the buttons over there. Pressing + // the candidate combo again keeps re-capturing it instead of firing + // the action. + self.shortcuts_save(); cx.notify(); true } - fn shortcuts_persist(&self, cx: &mut Context) { + /// The `createEffect` half: write the map to the shared store, touch + /// nothing at the OS. + fn shortcuts_save(&self) { if !store::set_hotkeys_raw(&self.pages.hotkeys) { tracing::warn!("saving the hotkeys store failed"); } - // `commands.setHotkey` re-registers the OS shortcut as it persists; - // deferred because the registry swap reads the store this just wrote. + } + + /// The `commands.setHotkey` half: persist and swap the OS registrations. + /// Deferred because the registry swap reads the store this just wrote. + fn shortcuts_commit(&self, cx: &mut Context) { + self.shortcuts_save(); cx.defer(crate::hotkeys::reload); } /// The window click listener: an abandoned capture puts the previous - /// binding back. + /// binding back. Store only -- the capture never touched the OS + /// registration, so there is nothing to swap back (the TSX cancel path + /// never calls `setHotkey` either). fn shortcuts_restore_prev(&mut self, cx: &mut Context) { let Some(listening) = self.pages.listening.take() else { return; @@ -976,7 +1003,7 @@ impl SettingsWindow { self.pages.hotkeys.remove(action); } } - self.shortcuts_persist(cx); + self.shortcuts_save(); cx.notify(); } @@ -1139,7 +1166,7 @@ impl SettingsWindow { .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) .on_click(cx.listener(move |this, _, _window, cx| { this.pages.listening = None; - this.shortcuts_persist(cx); + this.shortcuts_commit(cx); cx.notify(); })), ); @@ -1160,7 +1187,7 @@ impl SettingsWindow { .on_click(cx.listener(move |this, _, _window, cx| { this.pages.listening = None; this.pages.hotkeys.remove(HOTKEY_ACTIONS[index].0); - this.shortcuts_persist(cx); + this.shortcuts_commit(cx); cx.notify(); })), ); @@ -1805,6 +1832,104 @@ impl SettingsWindow { // Experimental (experimental.tsx) // --------------------------------------------------------------------------- +/// The hand-back takeover, mirroring `experimental.tsx`'s overlay: the toggle +/// is the confirmation, and the sequence is what the user reads while Cancel is +/// on screen. +pub(crate) enum SwitchBack { + /// The sequence, timed from its start. One clock drives the sentence, its + /// fade and the countdown, so nothing can drift apart. + Running(std::time::Instant), + /// Dev only: the switch is committed and the supervisor is rebuilding the + /// classic app; this app stays up until the classic one deletes the + /// pending file to say it is on screen (`store::classic_pending_path`). + WaitingForClassic, + /// The switch was refused; the overlay stays up with the reason and the + /// toggle goes back to on, because nothing was switched. + Failed(String), +} + +/// A cold dev build can genuinely take this long; past it, assume the build +/// failed and hand the user back their app with a pointer at the terminal. +const CLASSIC_WAIT_TIMEOUT: Duration = Duration::from_secs(10 * 60); + +/// One line at a time, fading between them, underneath the countdown rather +/// than ahead of it: the numeral is on screen from the first frame. +const SWITCH_SENTENCES: &[&str] = &[ + "Switching back to the classic Cap app.", + "Your recordings and settings stay exactly where they are.", +]; +const SWITCH_SENTENCE_MS: u64 = 2000; +const SWITCH_FADE_MS: f32 = 400.; +const SWITCH_COUNTDOWN_FROM: u32 = 5; +const SWITCH_TAKEOVER_MS: u64 = SWITCH_COUNTDOWN_FROM as u64 * 1000; + +/// The overlay's whole appearance at one instant: which sentence, how opaque, +/// and the number. +fn takeover_frame(elapsed_ms: f32) -> (usize, f32, u32) { + let last = SWITCH_SENTENCES.len() - 1; + let index = ((elapsed_ms / SWITCH_SENTENCE_MS as f32) as usize).min(last); + let within = elapsed_ms - index as f32 * SWITCH_SENTENCE_MS as f32; + let fade_in = (within / SWITCH_FADE_MS).clamp(0., 1.); + // The last line holds rather than fading out; blanking the column while the + // countdown finishes would read as a stall. + let alpha = if index == last { + fade_in + } else { + fade_in.min(((SWITCH_SENTENCE_MS as f32 - within) / SWITCH_FADE_MS).clamp(0., 1.)) + }; + // Five on the first frame down to one in the last second; the switch fires + // rather than ever showing zero. + let left = SWITCH_TAKEOVER_MS as f32 - elapsed_ms; + let remaining = (left / 1000.) + .ceil() + .clamp(1., SWITCH_COUNTDOWN_FROM as f32) as u32; + (index, alpha, remaining) +} + +/// Where the classic app lives when this build was not started from inside its +/// bundle and is not a dev build either. +const CLASSIC_APP_FALLBACK: &str = "/Applications/Cap.app"; + +/// What "the classic app" means for this process -- decided from where its +/// binary lives, so dev sessions reopen the dev app and installed ones the +/// installed app. +#[derive(Debug, PartialEq)] +enum ClassicTarget { + /// `open` this bundle: the shipped layout is + /// `.../Cap.app/Contents/Resources/gpui/cap-gpui`, so the nearest `.app` + /// ancestor is the classic app this binary shipped inside. + Bundle(std::path::PathBuf), + /// A cargo-built binary (an ancestor directory literally named `target`): + /// the classic app here is the `tauri dev` harness, which cannot be + /// `open`ed -- ask the dev-session supervisor to restart it instead + /// (`store::request_classic_reopen`). + DevSupervisor, +} + +fn classic_target_for_exe(exe: &std::path::Path) -> Option { + if let Some(bundle) = exe + .ancestors() + .find(|path| path.extension().is_some_and(|extension| extension == "app")) + { + return Some(ClassicTarget::Bundle(bundle.to_path_buf())); + } + if exe + .components() + .any(|component| component.as_os_str() == "target") + { + return Some(ClassicTarget::DevSupervisor); + } + let fallback = std::path::PathBuf::from(CLASSIC_APP_FALLBACK); + fallback.is_dir().then_some(ClassicTarget::Bundle(fallback)) +} + +fn classic_target() -> Option { + std::env::current_exe() + .ok() + .as_deref() + .and_then(classic_target_for_exe) +} + impl SettingsWindow { pub(crate) fn render_experimental(&self, cx: &mut Context) -> Vec { let theme = self.theme; @@ -1887,8 +2012,277 @@ impl SettingsWindow { ], ) .into_any_element(), + self.section( + "Native app", + None, + None, + vec![self.rows(vec![self.native_app_row(cx)]).into_any_element()], + ) + .into_any_element(), ] } + + /// The mirror of the Tauri page's Native app row: it hands the session over + /// to this app, this one hands it back. + fn native_app_row(&self, cx: &mut Context) -> gpui::AnyElement { + // Being in this app is what the toggle means, so it reads on even when + // the stored flag does not -- a `cargo run` or `dev.sh` launch never + // went through the hand-off. The flag only routes the Tauri app's + // startup. + let checked = !matches!(self.pages.switch_back, Some(SwitchBack::Running(_))); + self.setting_row( + "Cap GPUI", + Some( + "You are using the native version of Cap. Turning this off closes it and reopens \ + the classic app. Your recordings and settings are shared.", + ), + self.toggle("enable-gpui-app", checked, cx, |this, cx| { + match this.pages.switch_back { + Some(SwitchBack::Running(_)) => this.cancel_switch_back(cx), + // Committed: the classic app is already being brought up. + Some(SwitchBack::WaitingForClassic) => {} + _ => this.start_switch_back(cx), + } + }) + .into_any_element(), + ) + } + + /// The full-window takeover, painted from `SettingsWindow::render` so it + /// covers the sidebar and the menus too. + pub(crate) fn render_switch_overlay(&self, cx: &mut Context) -> Option { + let switch = self.pages.switch_back.as_ref()?; + let white = gpui::white(); + let mut column = div() + .absolute() + .top_0() + .left_0() + .size_full() + // Nothing behind the takeover is clickable while it runs. + .occlude() + .flex() + .flex_col() + .items_center() + .justify_center() + .gap(px(28.)) + .bg(gpui::hsla(0., 0., 0., 0.92)); + + match switch { + SwitchBack::Running(started) => { + let (index, alpha, remaining) = + takeover_frame(started.elapsed().as_secs_f32() * 1000.); + column = column + .child( + div() + .text_size(px(72.)) + .line_height(px(76.)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(white) + .child(SharedString::from(remaining.to_string())), + ) + .child( + div() + .h(px(40.)) + .max_w(px(380.)) + .flex() + .items_center() + .text_size(px(15.)) + .text_center() + .text_color(gpui::hsla(0., 0., 1., alpha)) + .child(SWITCH_SENTENCES[index]), + ); + } + SwitchBack::WaitingForClassic => { + column = column + .child( + div() + .max_w(px(380.)) + .text_size(px(17.)) + .font_weight(FontWeight::SEMIBOLD) + .text_center() + .text_color(white) + .child("Opening the classic Cap app"), + ) + .child( + div() + .max_w(px(380.)) + .text_size(px(14.)) + .text_center() + .text_color(gpui::hsla(0., 0., 1., 0.7)) + .child( + "The dev build is compiling. This window closes by itself when \ + the classic app is on screen; a cold build can take a few \ + minutes.", + ), + ); + } + SwitchBack::Failed(error) => { + column = column.child( + div() + .max_w(px(380.)) + .text_size(px(14.)) + .text_center() + .text_color(rgb(0xf87171)) + .child(SharedString::from(error.clone())), + ); + } + } + + // Committed states have nothing to cancel. + if matches!(switch, SwitchBack::WaitingForClassic) { + return Some(column.into_any_element()); + } + + Some( + column + .child( + div() + .id("switch-back-cancel") + .flex() + .items_center() + .px(px(16.)) + .h(px(32.)) + .rounded(px(8.)) + .border_1() + .border_color(gpui::hsla(0., 0., 1., 0.25)) + .text_size(px(13.)) + .text_color(white) + .cursor_pointer() + .hover(|style| style.bg(gpui::hsla(0., 0., 1., 0.12))) + .on_click(cx.listener(|this, _, _window, cx| this.cancel_switch_back(cx))) + .child("Cancel"), + ) + .into_any_element(), + ) + } + + fn start_switch_back(&mut self, cx: &mut Context) { + self.pages.switch_back = Some(SwitchBack::Running(std::time::Instant::now())); + // gpui only renders on invalidation, so the fades and the countdown + // need a pulse to run at all -- the `toggle_placeholders` shape. + // Assigning over the previous task drops it, which is how Cancel and a + // restart stop the one already in flight. + self.pages.switch_back_ticker = Some(cx.spawn(async move |this, cx| { + loop { + cx.background_executor() + .timer(Duration::from_millis(16)) + .await; + let running = this + .update(cx, |this, cx| { + let started = match &this.pages.switch_back { + Some(SwitchBack::Running(started)) => *started, + _ => return false, + }; + if started.elapsed() >= Duration::from_millis(SWITCH_TAKEOVER_MS) { + this.finish_switch_back(cx); + return false; + } + cx.notify(); + true + }) + .unwrap_or(false); + if !running { + return; + } + } + })); + cx.notify(); + } + + fn cancel_switch_back(&mut self, cx: &mut Context) { + self.pages.switch_back = None; + self.pages.switch_back_ticker = None; + cx.notify(); + } + + /// Write the flag, start the classic app, then quit -- in that order, so + /// the app that comes up already owns the session. Nothing is written when + /// there is no way to start one: the flag would strand the user in an app + /// that redirects to one that is not installed. + fn finish_switch_back(&mut self, cx: &mut Context) { + let Some(target) = classic_target() else { + self.pages.switch_back = Some(SwitchBack::Failed( + "Couldn't find the Cap app to switch back to.".to_string(), + )); + cx.notify(); + return; + }; + + self.settings.enable_gpui_app = false; + self.write_bool("enableGpuiApp", false, cx); + + let started = match &target { + ClassicTarget::Bundle(bundle) => std::process::Command::new("/usr/bin/open") + .arg(bundle) + .spawn() + .map(drop) + .map_err(|error| format!("Couldn't open the Cap app: {error}")), + ClassicTarget::DevSupervisor => store::mark_classic_pending() + .and_then(|()| store::request_classic_reopen()) + .map_err(|error| format!("Couldn't request the dev app restart: {error}")), + }; + + match (started, target) { + // An installed bundle opens in a moment; quit right away. + (Ok(()), ClassicTarget::Bundle(_)) => { + tracing::info!("handing back to the classic app"); + crate::menus::quit(cx); + } + // The dev harness has to rebuild first, which can take minutes. + // Stay up until the classic app deletes the pending file to say + // it is on screen, so the user is never staring at no app at all. + (Ok(()), ClassicTarget::DevSupervisor) => { + tracing::info!("handing back to the classic app; waiting for the dev build"); + self.pages.switch_back = Some(SwitchBack::WaitingForClassic); + cx.notify(); + // Occupies the ticker slot so starting or cancelling another + // sequence drops this waiter with it. + self.pages.switch_back_ticker = Some(cx.spawn(async move |this, cx| { + let started = std::time::Instant::now(); + loop { + cx.background_executor() + .timer(Duration::from_millis(500)) + .await; + let waiting = this + .update(cx, |this, _| { + matches!( + this.pages.switch_back, + Some(SwitchBack::WaitingForClassic) + ) + }) + .unwrap_or(false); + if !waiting { + break; + } + if !store::classic_pending_path().exists() { + tracing::info!("classic app is up; quitting"); + cx.update(|cx| crate::menus::quit(cx)); + break; + } + if started.elapsed() > CLASSIC_WAIT_TIMEOUT { + this.update(cx, |this, cx| { + this.pages.switch_back = Some(SwitchBack::Failed( + "The classic app hasn't come up. Check the dev terminal for \ + build errors, then toggle again." + .to_string(), + )); + cx.notify(); + }) + .ok(); + break; + } + } + })); + } + (Err(message), _) => { + tracing::error!("{message}"); + self.settings.enable_gpui_app = true; + self.write_bool("enableGpuiApp", true, cx); + self.pages.switch_back = Some(SwitchBack::Failed(message)); + cx.notify(); + } + } + } } // --------------------------------------------------------------------------- @@ -7319,6 +7713,56 @@ mod tests { assert_eq!(hotkey_code_for_key("escape"), None); } + /// Every code the capture can produce parses as a `global_hotkey` `Code`. + /// This is the shared-store guarantee: the Tauri `HotkeysStore` fails to + /// deserialize wholesale on one unknown code, losing every binding, so a + /// code this page writes must always be one that side can read back. + #[test] + fn every_producible_code_parses_for_the_tauri_app() { + let mut keys: Vec = Vec::new(); + for c in 'a'..='z' { + keys.push(c.to_string()); + } + for c in '0'..='9' { + keys.push(c.to_string()); + } + for c in [',', '.', '/', ';', '\'', '[', ']', '\\', '-', '=', '`'] { + keys.push(c.to_string()); + } + // gpui function keys stop at f19 on Apple keyboards; f20 is the + // largest either side can see. + for n in 1..=20 { + keys.push(format!("f{n}")); + } + for named in [ + "space", + "enter", + "tab", + "backspace", + "delete", + "up", + "down", + "left", + "right", + "home", + "end", + "pageup", + "pagedown", + ] { + keys.push(named.to_string()); + } + + for key in keys { + let Some(code) = hotkey_code_for_key(&key) else { + panic!("{key} should map to a code"); + }; + assert!( + global_hotkey::hotkey::Code::from_str(&code).is_ok(), + "{key} produced {code}, which global_hotkey cannot parse" + ); + } + } + #[test] fn hotkey_chips_render_in_macos_modifier_order() { let hotkey = Hotkey { @@ -7334,6 +7778,59 @@ mod tests { ); } + /// The switch-back target matches how this binary was started: the `.app` + /// it is staged inside beats everything, a cargo `target/` path means the + /// dev harness (never the installed app), and only a bare binary from + /// neither falls through to `/Applications`. + #[test] + fn the_classic_target_matches_the_launch_context() { + assert_eq!( + classic_target_for_exe(std::path::Path::new( + "/Applications/Cap.app/Contents/Resources/gpui/cap-gpui" + )), + Some(ClassicTarget::Bundle(std::path::PathBuf::from( + "/Applications/Cap.app" + ))) + ); + assert_eq!( + classic_target_for_exe(std::path::Path::new( + "/Users/x/Cap/apps/desktop-gpui/target/debug/cap-gpui" + )), + Some(ClassicTarget::DevSupervisor) + ); + // A dev binary staged inside a bundle is still that bundle's. + assert_eq!( + classic_target_for_exe(std::path::Path::new( + "/Users/x/Cap/target/debug/bundle/osx/Cap.app/Contents/Resources/gpui/cap-gpui" + )), + Some(ClassicTarget::Bundle(std::path::PathBuf::from( + "/Users/x/Cap/target/debug/bundle/osx/Cap.app" + ))) + ); + } + + /// The takeover's whole timeline, read off its one clock. + #[test] + fn the_takeover_counts_down_while_the_lines_fade() { + // The number is up from the first frame and never reaches zero. + assert_eq!(takeover_frame(0.).2, SWITCH_COUNTDOWN_FROM); + assert_eq!(takeover_frame(1000.).2, SWITCH_COUNTDOWN_FROM - 1); + assert_eq!(takeover_frame(SWITCH_TAKEOVER_MS as f32 - 1.).2, 1); + assert_eq!(takeover_frame(SWITCH_TAKEOVER_MS as f32).2, 1); + + // First line, faded in over its lead, and out again as its slot ends. + let (index, alpha, _) = takeover_frame(0.); + assert_eq!(index, 0); + assert_eq!(alpha, 0.); + assert_eq!(takeover_frame(SWITCH_FADE_MS).1, 1.); + assert_eq!(takeover_frame(SWITCH_SENTENCE_MS as f32 - 1.).1.round(), 0.); + + // The last line holds rather than blanking while the number finishes. + let (index, alpha, _) = takeover_frame(SWITCH_TAKEOVER_MS as f32 - 1.); + assert_eq!(index, SWITCH_SENTENCES.len() - 1); + assert_eq!(alpha, 1.); + } + /// `conditionAppliesToTrigger` / `actionAppliesToTrigger` against the /// tables in `utils/automations.ts`. #[test] diff --git a/apps/desktop-gpui/src/settings_window.rs b/apps/desktop-gpui/src/settings_window.rs index aaf1394edf..4a0b93dd22 100644 --- a/apps/desktop-gpui/src/settings_window.rs +++ b/apps/desktop-gpui/src/settings_window.rs @@ -26,8 +26,8 @@ use crate::{ library::{self, RecordingItem, RecordingMode, ScreenshotItem}, store::{ self, AppTheme, DEFAULT_PROJECT_NAME_TEMPLATE, DEFAULT_SERVER_URL, GENERAL_SETTINGS, - GeneralSettings, MainWindowStartBehaviour, PostDeletionBehaviour, PostStudioBehaviour, - RECORDING_START_SAFETY, SettingsEnum, StudioQuality, UpdateChannel, WindowExclusion, + GeneralSettings, MainWindowStartBehaviour, PostDeletionBehaviour, RECORDING_START_SAFETY, + SettingsEnum, StudioQuality, UpdateChannel, WindowExclusion, }, theme::Theme, ui, @@ -184,7 +184,6 @@ impl Page { pub(crate) enum MenuKind { Countdown, MainWindowStart, - PostStudio, PostDeletion, MaxFps, /// The excluded-windows card's Add button, whose menu is the live window @@ -1185,7 +1184,6 @@ impl SettingsWindow { MenuKind::MainWindowStart => { enum_items(self.settings.main_window_recording_start_behaviour) } - MenuKind::PostStudio => enum_items(self.settings.post_studio_recording_behaviour), MenuKind::PostDeletion => enum_items(self.settings.post_deletion_behaviour), MenuKind::AddWindow => self .available_windows() @@ -1223,12 +1221,6 @@ impl SettingsWindow { self.write_enum("mainWindowRecordingStartBehaviour", *value, cx); } } - MenuKind::PostStudio => { - if let Some(value) = PostStudioBehaviour::ALL.get(index) { - self.settings.post_studio_recording_behaviour = *value; - self.write_enum("postStudioRecordingBehaviour", *value, cx); - } - } MenuKind::PostDeletion => { if let Some(value) = PostDeletionBehaviour::ALL.get(index) { self.settings.post_deletion_behaviour = *value; @@ -1388,6 +1380,9 @@ impl Render for SettingsWindow { // the drag layer the zoom slider needs while the button is held. .children(self.render_menu(cx)) .children(self.render_slider_drag_layer(cx)) + // Over everything, including the menus: the Experimental page's + // hand-back takes the whole window while it counts down. + .children(self.render_switch_overlay(cx)) } } @@ -2868,17 +2863,16 @@ impl SettingsWindow { ) .into_any_element(), ), - self.setting_row( - "After a Studio recording", - Some("What happens once you stop a Studio recording."), - self.select( - "post-studio", - settings.post_studio_recording_behaviour.label(), - MenuKind::PostStudio, - cx, - ) - .into_any_element(), - ), + // Deviation: general.tsx puts an "After a Studio + // recording" select here (Open editor / Show in overlay). + // The post-capture recordings overlay does not exist in + // this app -- a product decision, not a gap -- so a + // one-option select would be noise and the row is omitted. + // The stored `postStudioRecordingBehaviour` is left + // exactly as the Tauri app wrote it (this page never + // writes the key), and the stop flow treats a stored + // `showOverlay` as "reshow the main window" + // (`app_windows.rs`). self.setting_row( "After deleting a recording", Some("Whether the recording window should reopen."), From 5a993f4325b2975c6b81e2c6316fd664a6df52c4 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:04:21 +0100 Subject: [PATCH 145/146] feat(desktop-gpui): shared wiring for the parity units app_windows gains the editor-recording lifecycle (hidden-for-picker, finish routing, deep-link executor init), upload.rs the screenshot share path (create-or-get with isScreenshot, presigned PUT, the exact Tauri auth gates), the store the screenshot editor's persisted panel and crop-snap flags, and Cargo the tiny-skia/cosmic-text/sha2 direct deps the export compositor uses. README parity tables refreshed to the delivered state. --- apps/desktop-gpui/Cargo.lock | 5 + apps/desktop-gpui/Cargo.toml | 18 ++ apps/desktop-gpui/README.md | 60 +++-- apps/desktop-gpui/dev.sh | 25 +- apps/desktop-gpui/src/app_windows.rs | 328 ++++++++++++++++++++++++--- apps/desktop-gpui/src/main.rs | 18 ++ apps/desktop-gpui/src/store.rs | 108 ++++++++- apps/desktop-gpui/src/upload.rs | 106 ++++++++- 8 files changed, 611 insertions(+), 57 deletions(-) diff --git a/apps/desktop-gpui/Cargo.lock b/apps/desktop-gpui/Cargo.lock index 85f1b28dc2..b194b2dbb4 100644 --- a/apps/desktop-gpui/Cargo.lock +++ b/apps/desktop-gpui/Cargo.lock @@ -1478,6 +1478,7 @@ dependencies = [ "anyhow", "base64 0.22.1", "cap-camera", + "cap-camera-effects", "cap-editor", "cap-export", "cap-project", @@ -1489,6 +1490,7 @@ dependencies = [ "cidre", "core-foundation 0.10.1", "core-video", + "cosmic-text 0.14.2", "cpal", "dirs 6.0.0", "ffmpeg-next", @@ -1512,11 +1514,14 @@ dependencies = [ "scap-targets", "serde", "serde_json", + "sha2", "smallvec", + "tiny-skia", "tokio", "tracing", "tracing-subscriber", "unicode-segmentation", + "wgpu 25.0.2", "whisper-rs", "windows-sys 0.59.0", "x11rb", diff --git a/apps/desktop-gpui/Cargo.toml b/apps/desktop-gpui/Cargo.toml index d4caa797b9..5a3aa46e7a 100644 --- a/apps/desktop-gpui/Cargo.toml +++ b/apps/desktop-gpui/Cargo.toml @@ -64,6 +64,15 @@ cap-project = { path = "../../crates/project" } # playback, and hands frames back through a plain `FnMut` callback. It carries # no tauri types -- the websocket lives in the Tauri binary, not in the crate. cap-rendering = { path = "../../crates/rendering" } +# The camera bubble's live background blur (`src/camera_blur.rs`): the same +# `BlurProcessor` the Tauri preview and the editor's camera layer run. +# Already in this lockfile through cap-rendering, so naming it only makes the +# edge direct. +cap-camera-effects = { path = "../../crates/camera-effects" } +# The blur worker's device/queue/texture types. Must stay on cap-rendering's +# wgpu 25 line (the vendored wgpu-hal `[patch.crates-io]` below applies to +# it); gpui's own wgpu 29 is a separate tree and never touches these types. +wgpu = "25.0.0" cap-editor = { path = "../../crates/editor" } # In-process export. Only the `ExporterBase` + settings surface is used; the # export *page* can also drive the `cap-exporter` subprocess protocol, so this @@ -107,6 +116,15 @@ serde_json = "1" serde = { version = "1", features = ["derive"] } base64 = "0.22" rfd = "0.15" +# The screenshot export compositor (`src/screenshot_export.rs`): the canvas-2d +# work `renderScreenshotExportCanvas` does in the webview -- shape rasterizing, +# text, and the share fingerprint. All three are already in this lockfile +# (tiny-skia through resvg, cosmic-text through glyphon, sha2 through half the +# tree), so these lines make existing build units direct rather than adding +# dependency trees. +tiny-skia = "0.11" +cosmic-text = "0.14" +sha2 = "0.10" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/apps/desktop-gpui/README.md b/apps/desktop-gpui/README.md index 122850c764..be082f716e 100644 --- a/apps/desktop-gpui/README.md +++ b/apps/desktop-gpui/README.md @@ -35,6 +35,10 @@ one (see the editor's measurements below). ./dev.sh ``` +`pnpm dev:desktop` (from the repo root) starts this loop automatically next +to the Tauri app, output prefixed `[gpui]`; set `CAP_GPUI_DEV=0` to opt out +(`scripts/dev-desktop.mjs`). + Native Rust cannot hot-reload, so this is the Solid-dev-server feel rebuilt from its observable parts. The script watches the app's sources, the `cap-*` crates the editor depends on and the local gpui checkout, rebuilds on save, @@ -541,7 +545,7 @@ Sizes are the Tauri app's, from `apps/desktop/src-tauri/src/windows.rs`. | Window | Size | Status | |---|---|---| | Main | 330×395 / 600×660 | **Done** — layout, devices, pickers, modes, recording, Recents with real thumbnails, level-100 panel behavior, native Liquid Glass / vibrancy material | -| Camera preview | size×(size+56), 150–600 | **Done** — live frames, round/square/full shapes, S/L sizes, hover toolbar, corner resize, drag, persisted chrome state, capture-excluded in studio / included in instant | +| Camera preview | size×(size+56), 150–600 | **Done** — live frames, round/square/full shapes, S/L sizes, hover toolbar with all five controls live (close, size, shape, mirror, blur), live background-blur preview via `camera_blur.rs`, hardware mirroring, corner resize with the bracket visuals, drag, camera-issue overlay, position persisted to the shared store per monitor, persisted chrome state, always-dark like `camera.tsx`, capture-excluded in studio / included in instant | | Recording controls | 320×150 | **Done** — live timer, pause/resume, restart, delete, live mic level, instant-mode mute, drag; capture-excluded, non-activating | | Target select overlay | per display | **Done** — all four variants (display / window / area / camera-only), one transparent non-activating panel per display at the Tauri-verbatim level 7, cursor-following highlight, click-to-pin windows with app icons, draw/move/resize area selection with min-size validation, the real Start Recording flow (overlays close, bar opens, overlays excluded from capture), Escape/close dismiss | | Window capture occluder | per display | Not started | @@ -553,7 +557,7 @@ Sizes are the Tauri app's, from `apps/desktop/src-tauri/src/windows.rs`. | Onboarding | dynamic, 860–1080 wide | Not started | | Teleprompter | 560×320 | **Done, with deviations** — resizable to the 420×220 floor, level 101 on all Spaces, the `"teleprompter"` material at radius 22, traffic lights at (14, 11) — the Tauri `(14, 14)` is a `position_window_controls` inset, not a top-left, auto-scroll from the ported `teleprompter-utils` maths, the full footer and settings popover, native window opacity, the `teleprompter` store section, capture exclusion + content protection. The script editor is a real multi-line field and Mirror is inert — see below | | Editor | 1275×800 | **E1–E5 done — window, shell, playback, timeline, editing, config sidebar.** The real 1275×800 window with the traffic lights at (20, 20) — the Tauri `(20, 32)` is a `position_window_controls` inset, not a top-left, the header, the letterboxed player, the 260px timeline strip and the 416px config sidebar with its six-tab rail; a real project through `EditorInstance`; play/pause (button + Space), a 60fps live playhead and clock, real audio, click/drag seeking, end-of-media stop; the timeline at 1:1 — all nine track types from the project's own config, waveforms, the ruler's resolution ladder, minimap, edge fade, hover ghost, zoom (keys, buttons, slider, wheel, pinch) and pan; and **timeline editing**: selection (single, ⌘-multi, ⇧-range, ⌘A), trim, move, split (S + C, with snapping), zoom-segment create/resize/move, delete, undo/redo and the debounced write back to `project-config.json`. and **the config sidebar in full**: the six-tab rail, the scroll body, selection routing, all six tabs at 1:1, the shared colour-correction section on both its targets, and the eight per-segment panels with multi-select where the source has it. Crop and export are the remaining units | -| Screenshot editor | 1240×800 (min 800×600) | **Done, with deviations** — the real window (native traffic lights, resizable to the 800×600 floor), one editor per bundle keyed like the video editor's registry, opened from capture, Recents, the settings Screenshots page, the tray's Previous and image import. The still renders through the same GPU path the Tauri instance uses (`FrameRenderer::render_immediate`, `preserve_screen_alpha`, no camera) on a config watch, painted with `paint_image` instead of shipped over the frame websocket. Styling panel: background source tabs (color swatches, gradient presets, wallpaper grid with theme tabs and cached thumbnails, image picker), blur/padding/rounding/shadow sliders at the popovers' ranges, squircle/rounded corner style, border toggle + width/opacity, aspect-ratio menu; edits publish to the renderer live and ride a 250ms debounced `project-config.json` write. Header: copy (GPU export → clipboard), save PNG (the `render_screenshot_png` upscale + unpad + encode, on the live renderer's device), reveal, delete behind a native confirm. Deviations: no annotations/OCR/layers, no share/upload, no crop dialog, no undo history, no border color picker, no advanced-shadow or gradient angle/noise controls, no `PendingScreenshots` in-memory hand-off (the PNG is on disk before the editor opens) | +| Screenshot editor | 1240×800 (min 800×600) | **Done, with deviations** — the real window (native traffic lights, resizable to the 800×600 floor), one editor per bundle keyed like the video editor's registry, opened from capture, Recents, the settings Screenshots page, the tray's Previous and image import. The still renders through the same GPU path the Tauri instance uses (`FrameRenderer::render_immediate`, `preserve_screen_alpha`, no camera) on a config watch, painted with `paint_image` instead of shipped over the frame websocket; edits publish to the renderer live, ride the 1000ms debounced `project-config.json` write, and walk a real undo history (⌘Z / ⌘⇧Z / ⌘Y, a drag's sixty intermediate values collapsed into one entry). Styling: background source tabs (color swatches, gradient presets, wallpaper grid with theme tabs and cached thumbnails, image picker), hex fields, blur/padding/rounding/shadow sliders at the popovers' ranges plus the advanced-shadow collapsible, squircle/rounded corner style, border toggle + width/color/opacity, aspect-ratio menu. Annotations: the full engine — all seven tools with their single-key shortcuts, the frame-space geometry of `layout.ts`/`arrow.ts` verbatim, selection handles, the per-type config bar (stroke/fill swatches with the colour popover, width/opacity/mask-level/text-size sliders), the layers panel with drag reorder, blur/pixelate masks resampled live off the rendered frame, inline text editing, ⌘C/⌘V duplicate, delete. Crop dialog: the `editor_crop` engine wholesale over `original.png` (`screenshot_crop.rs`) — Size/Position number boxes, the round ratio button and its options menu (right-click opens the same one at the cursor) with the snap-to-ratio toggle persisted in the shared store, Full/Reset, arrow-key nudges, one history entry on Save, Escape/backdrop cancel. Export: Copy/Save/Share all bake the annotations in (`screenshot_export.rs` — the `renderScreenshotExportCanvas` pass: masks re-filtered at export scale from an unmodified copy, shapes via tiny-skia, text via cosmic-text at the preview's Helvetica baseline, the output expanded to the union of image and annotation boxes with the source's white-fill rules); Copy composites over white and lands PNG on the clipboard, Save writes PNG, Share fingerprints the config (`sha256` over recursively key-sorted JSON), short-circuits to the stored link on a hash match, and otherwise uploads JPEG-0.9 (PNG when transparency is actually needed) through create-or-get + presigned PUT — reusing `sharing.id` so the link survives edits — then persists `SharingMeta { id, link, content_hash }`, with Tauri's exact auth gates and toast ladder; reveal, delete behind a native confirm. Deviations: no OCR text-selection overlay (Vision OCR feeding an invisible DOM-style selectable text layer has no gpui equivalent yet; the layer is invisible in the Tauri editor too, so zero visual impact); the colour swatches are inert — no native colour panel is wired to this window, the hex fields are the input; the skeleton's Cap logo is drawn still (the app ships no bare glyph to spin, only the full lockup); the crop options menu has no separator row (`ui::Menu` has none, so its two groups sit adjacent); no `PendingScreenshots` in-memory hand-off (the PNG is on disk before the editor opens) | | Tray (status item) | menu bar | **Done, with deviations** — a native `NSStatusItem` (no gpui API exists for one; built on AppKit with the color-panel channel discipline), menu byte-identical to `build_tray_menu`: Open Main Window, Record Display/Window/Area (screenshot-mode relabels), Select Mode ▸ with the ✓, Previous ▸ with 32px cover-cropped thumbnails and the 🎬⚡📷 prefixes, View all recordings / screenshots, Settings, version row, Quit Cap. The icon follows the mode and becomes the stop icon while recording, when clicking the item stops the capture (the menu is detached so the button's action can fire). Select Mode writes the same `recording_settings.mode` store key the Tauri app reads. Take a Screenshot captures the display under the cursor and Import Media… routes video/image imports; a Previous screenshot opens the screenshot editor. Deviations: Upload Logs renders disabled (no log-upload infrastructure yet) | | App menu + shortcuts | menu bar | **Done** — the `build_macos_app_menu` structure (Cap / File / Edit / View / Window / Help) via `cx.set_menus`, with ⌘W closing the key window through each window's own close path (main hides, Tauri's `CloseRequested` arm transcribed: camera bubble closed, overlays closed, feeds released when idle), ⌘M / Zoom / ⌃⌘F on the key window, ⌘H / ⌥⌘H, ⌘Q, and Edit items dispatching the text-input actions. Dock-icon policy (Regular ↔ Accessory per `hideDockIcon` + visible windows) synced on every window show/hide seam | @@ -705,20 +709,50 @@ uploads on the frame path (`camera-preview-convert-benchmark` measures the conversion at 137µs vs the old path's 196µs per 720p frame, byte-identical output, before counting the atlas upload the new path deletes; the fork originally hard-asserted `420f` and had no surface clipping, which is what the -`cap/bgra-surface` patches add). Camera-window deviations: no mirroring (the -toolbar button is present but disabled), background blur does not process -*preview* frames yet (the `cap-camera-effects` segmentation pipeline needs a -`wgpu::Device` this app does not have — it is its own unit), the window -position is not persisted per-monitor, and chrome state persists to -`gpui-state.json` next to the Tauri store rather than `localStorage`. - -The blur toggle is no longer preview-only state, though: its value is copied -into every studio recording's `project-config.json` at finalize time, the way +`cap/bgra-surface` patches add). + +Mirroring is a second VideoToolbox pass on the same path: a +`VTPixelRotationSession` with `FlipHorizontalOrientation` flips the converted +BGRA surface into a sibling ring — hardware, no CPU pixels — which is the +preview-only mirror both Tauri paths have (the native shader flips the +sampling UV, the legacy page flips the canvas with `scaleX(-1)`; neither app +ever mirrors the recorded camera track). + +Background blur runs live on preview frames through `src/camera_blur.rs`: a +dedicated `camera-blur` thread owns its own wgpu device (requested the way the +Tauri preview requests one — LowPower, downlevel limits) and runs the exact +`cap_camera_effects::BlurProcessor` the recording's project config points the +editor at, with the Tauri preview's 640×360 input cap and 150ms inference +interval. The converter downscales to the cap inside the same hardware +transfer, the worker imports the surface zero-copy through `cap-rendering`'s +`iosurface_texture` seam, and the blurred output comes back as a BGRA +IOSurface CVPixelBuffer (via `RgbaToBgraSurfaceConverter`) that the window +paints on the normal surface path. Blur off never touches any of this — the +direct-IOSurface fast path is byte-identical to before — and a failed +bring-up (no ONNX runtime, no device) degrades to raw frames with the toggle +still cycling, the `ensure_blur_processor` contract. Low-spec machines (≤8GB, +the Tauri RAM gate) never start the worker at all. + +The window position is persisted the Tauri way too: moves land, debounced, +in the shared store's `cameraWindowPosition` / +`cameraWindowPositionsByMonitorName` keys, and opening restores the saved +spot when it is still on the preferred monitor — so the two apps remember one +bubble position between them. Remaining camera-window deviations: no 150ms +opacity/translate transitions on the toolbar and resize brackets (no +animation pass in this port), no `backdrop-blur-xs` behind the camera-issue +overlay (no per-element backdrop blur hook), no "Camera disconnected" overlay +on recording input loss (the Tauri `recordingEvent` InputLost seam has no +counterpart here yet), and chrome state persists to `gpui-state.json` next to +the Tauri store rather than `localStorage`. + +The blur toggle's value is also copied into every studio recording's +`project-config.json` at finalize time, the way `project_config_from_recording` copies the Tauri preview's (see Recording above). Blur was always non-destructive in both apps — the recorded camera track is raw and the editor re-runs the pipeline over it from that field — so a project recorded here with the bubble set to Light opens Light in the -shipping editor, whether or not the bubble itself ever painted the blur. +shipping editor, and now the bubble shows the same blur those frames will +open with. ### The macOS 26 display-link fix @@ -940,7 +974,7 @@ Teleprompter-specific deviations: | | | |---|---| | **The script editor keeps its own selection wash** | It is `ui::TextInput` in its multi-line shape now (see [Text input](#text-input)) — wrapping, click-to-position, arrow and word motion, selection, the clipboard, undo. The one deliberate difference from the rest of the app is the selection colour: bare glass takes `gray-12` at 25 % rather than the macOS-blue accent the settings fields use, because there is no accent token on this surface. `state.script` stays the mirror the word count, the auto-scroll maths and the debounced store write read. | -| **Mirror is persisted but inert** | `scale-x-[-1]` needs a flip transform, and this gpui rev has none — the same finding that leaves the camera bubble's mirror button disabled. The toggle stores `mirror` so the setting survives for the shipping app; nothing on screen changes. | +| **Mirror is persisted but inert** | `scale-x-[-1]` needs a flip transform on painted *text*, and this gpui rev has none. (The camera bubble solved its version of this with a `VTPixelRotationSession` on the pixel buffers, but the teleprompter mirrors glyphs, not a surface.) The toggle stores `mirror` so the setting survives for the shipping app; nothing on screen changes. | | **The vignette is a wash, not a mask** | The script area's `mask-image` fades the *glyphs'* alpha to 0.4 at the top and bottom. With no mask hook, two `gray-1` gradient layers over the same 34% / 66% stops stand in. Over vibrancy that is nearly the same picture; over Liquid Glass it tints the backdrop instead of the text. | | **No backdrop blur** | The settings popover is `bg-gray-1/80` *plus* `backdrop-blur-2xl`, and the footer pills add their own `backdrop-blur-xl`. The washes are here, the blur is not — the same missing hook as the header's `backdrop-filter` and the recording overlay's `backdrop-blur-xs`. | | **No letter-spacing** | The script is `tracking-[-0.025em]` in the TSX; this gpui rev exposes no letter-spacing, so it renders at the font's own tracking. | diff --git a/apps/desktop-gpui/dev.sh b/apps/desktop-gpui/dev.sh index d68635caf8..8dff625127 100755 --- a/apps/desktop-gpui/dev.sh +++ b/apps/desktop-gpui/dev.sh @@ -9,7 +9,6 @@ cd "$(dirname "$0")" STATE_FILE="$PWD/target/dev-restore.json" BIN="$PWD/target/debug/cap-gpui" BUILD=cargo -command -v cinder >/dev/null 2>&1 && BUILD=cinder # Cargo.toml turns incremental off to keep the dep tree's caches off a full # disk, but deps never rebuild in this loop -- the cache this creates is the @@ -33,6 +32,22 @@ fingerprint() { APP_PID="" +SHARED_DIR="$HOME/Library/Application Support/so.cap.desktop" + +# Launching unconditionally would put this app on screen next to the classic +# one every session: under the experimental switch the two share ownership of +# the session (`enableGpuiApp` in the shared store), so a build only launches +# when this app owns it or an instance is already running (a rebuild swap). +gpui_owns_session() { + grep -Eq '"enableGpuiApp":[[:space:]]*true' "$SHARED_DIR/store" 2>/dev/null +} + +instance_live() { + local pid + pid=$(cat "$SHARED_DIR/cap-gpui.pid" 2>/dev/null) || return 1 + [ -n "$pid" ] && ps -p "$pid" -o comm= 2>/dev/null | grep -q cap-gpui +} + stop_app() { [ -n "$APP_PID" ] || return 0 if ! kill -0 "$APP_PID" 2>/dev/null; then @@ -70,8 +85,12 @@ while true; do LAST="$CURRENT" echo "[dev] building..." if "$BUILD" build; then - stop_app - start_app + if [ -n "$APP_PID" ] || gpui_owns_session || instance_live; then + stop_app + start_app + else + echo "[dev] built; not launching (the classic app owns the session)" + fi else echo "[dev] build failed; keeping the previous app running" fi diff --git a/apps/desktop-gpui/src/app_windows.rs b/apps/desktop-gpui/src/app_windows.rs index d004c3a598..adf0f4fcbf 100644 --- a/apps/desktop-gpui/src/app_windows.rs +++ b/apps/desktop-gpui/src/app_windows.rs @@ -70,6 +70,14 @@ pub struct AppWindows { /// tray path): the Tauri effect does the same, which is why cancelling a /// tray-opened picker reveals the main window. pub main_hidden_for_picker: bool, + /// `hiddenForPicker` in the editor's record modal + /// (`ClipsSidebar.tsx:311-341, 413-426`): the editor window that hid + /// itself for an editor-owned target picker ("Record a new clip"). A + /// cancelled dismissal reveals *that* editor -- never the main window -- + /// and clears the session's editor recording target; a recording start + /// clears the flag without revealing, because the editor stays hidden + /// until the finish path hands it the foreground. + pub editor_hidden_for_picker: Option, } impl Global for AppWindows {} @@ -89,6 +97,7 @@ pub fn init(main: WindowHandle, session: Entity, c editors: Vec::new(), screenshot_editors: Vec::new(), main_hidden_for_picker: false, + editor_hidden_for_picker: None, }); // The global-Escape drain: the Carbon hotkey (registered only while the @@ -114,20 +123,33 @@ pub fn init(main: WindowHandle, session: Entity, c } if phase == Phase::Idle && last_phase != Phase::Idle { close_controls(&session, cx); - // `postStudioRecordingBehaviour` (`openEditor` is the default): - // a cleanly-stopped studio recording goes straight to the editor, - // main window staying hidden -- `editor_closed` brings it back - // once the last editor goes away. Every other path (instant, - // failures, `showOverlay` until an overlay exists) reshows the - // main window as before. let finished_studio = session.update(cx, |session, _| session.finished_studio.take()); - let editor_project = finished_studio.filter(|_| { - crate::store::GeneralSettings::load().post_studio_recording_behaviour - == crate::store::PostStudioBehaviour::OpenEditor - }); - match editor_project { - Some(project_dir) => open_editor(project_dir, cx), - None => show_main_window(cx), + // The in-editor re-record target is consumed first, before + // `postStudioRecordingBehaviour` is even consulted -- the order + // `apply_post_studio_editor_behaviour` checks them + // (`src-tauri/src/recording.rs:3268-3287`). `take` on every path, + // clean or not: the fallback at `recording.rs:3225-3237` does the + // same so a cancelled or failed recording still restores the + // editor and cannot leak its target into the next session. + let editor_target = + session.update(cx, |session, _| session.take_editor_recording_target()); + if let Some(editor_path) = editor_target { + editor_recording_finished(editor_path, finished_studio, cx); + } else { + // `postStudioRecordingBehaviour` (`openEditor` is the + // default): a cleanly-stopped studio recording goes straight + // to the editor, main window staying hidden -- + // `editor_closed` brings it back once the last editor goes + // away. Every other path (instant, failures, `showOverlay` + // until an overlay exists) reshows the main window as before. + let editor_project = finished_studio.filter(|_| { + crate::store::GeneralSettings::load().post_studio_recording_behaviour + == crate::store::PostStudioBehaviour::OpenEditor + }); + match editor_project { + Some(project_dir) => open_editor(project_dir, cx), + None => show_main_window(cx), + } } // `NewStudioRecordingAdded` -> `add_new_item_to_cache` + // `refresh_tray_menu`. The reshow is where the main window's own @@ -141,6 +163,12 @@ pub fn init(main: WindowHandle, session: Entity, c last_phase = phase; }) .detach(); + + // The `cap-desktop://action` executor (see `crate::deeplink`): started + // here because the actions dispatch into this registry, which now exists. + // The Tauri app orders it the same way -- `DeepLinkActionExecutor::new` in + // `setup`, before `on_open_url` is wired (`lib.rs:5449`). + crate::deeplink::init(cx); } /// `createThemeListener` + `commands.setTheme`: persist is already done; this @@ -1018,6 +1046,45 @@ fn resolve_window(id: &scap_targets::WindowId) -> Option { /// cheaper to reason about and what the Tauri flow does (the webviews are /// recreated with a new `targetMode` query parameter). pub fn open_target_overlays(request: OverlayRequest, cx: &mut App) { + open_overlays_core(request, cx); + + // `pickerActive && !hasHidden && !recording` -> `getCurrentWindow().hide()` + // (`new-main/index.tsx:2024-2028`): the picker owns the screen; the main + // window would otherwise float above the overlays at level 100. + if RecordingSession::global(cx).read(cx).phase == Phase::Idle { + cx.global_mut::().main_hidden_for_picker = true; + hide_main_window(cx); + } +} + +/// The editor's record modal opening the same picker +/// (`openTargetMode` / `selectDisplayTarget` / `selectWindowTarget`, +/// `ClipsSidebar.tsx:447-501`): identical overlays, but the window that hides +/// for them is the *editor* -- `hideEditorForPicker` -- and the dismissal +/// bookkeeping runs against [`AppWindows::editor_hidden_for_picker`] instead +/// of the main window's flag, so cancelling reveals the editor and starting +/// hands the foreground to the bar. +/// +/// Must be reached through `cx.defer` from anything inside an entity update, +/// same as [`open_target_overlays`]. +pub fn open_editor_target_overlays(editor_path: PathBuf, request: OverlayRequest, cx: &mut App) { + // A live recording keeps its own picker semantics; the modal's actions + // are guarded on Idle already, so this is the belt-and-braces the Tauri + // backend has in `set_pending_recording`. + if RecordingSession::global(cx).read(cx).phase != Phase::Idle { + return; + } + + open_overlays_core(request, cx); + + let key = editor_key(&editor_path); + if cx.global::().editor_hidden_for_picker.as_ref() != Some(&key) { + cx.global_mut::().editor_hidden_for_picker = Some(key.clone()); + hide_editor_window(&key, cx); + } +} + +fn open_overlays_core(request: OverlayRequest, cx: &mut App) { let select = TargetSelect::global(cx); let mode_changed = select.read(cx).mode != Some(request.mode); if mode_changed { @@ -1087,14 +1154,6 @@ pub fn open_target_overlays(request: OverlayRequest, cx: &mut App) { // and the overlays non-activating, a plain key handler has nothing to be // delivered to. platform::register_escape_hotkey(); - - // `pickerActive && !hasHidden && !recording` -> `getCurrentWindow().hide()` - // (`new-main/index.tsx:2024-2028`): the picker owns the screen; the main - // window would otherwise float above the overlays at level 100. - if RecordingSession::global(cx).read(cx).phase == Phase::Idle { - cx.global_mut::().main_hidden_for_picker = true; - hide_main_window(cx); - } } /// Close the overlays and clear the main window's armed target -- Escape, the @@ -1109,6 +1168,32 @@ pub fn dismiss_target_overlays(cx: &mut App) { main.update(cx, |view, _window, cx| view.clear_target(cx)) .ok(); + // An editor-owned picker cancelled: clear the editor recording target and + // reveal that editor with focus, record modal closed -- the non- + // "editorRecording" branch of the `targetMode == null && hiddenForPicker` + // effect (`ClipsSidebar.tsx:413-426`). The main window stays wherever it + // was; the editor owns the foreground again. + if let Some(editor_path) = cx + .global_mut::() + .editor_hidden_for_picker + .take() + { + RecordingSession::global(cx) + .update(cx, |session, _| session.set_editor_recording_target(None)); + tracing::info!(path = %editor_path.display(), "editor picker dismissed"); + if let Some(handle) = editor_window_handle(&editor_path, cx) { + handle + .update(cx, |view, _window, cx| view.editor_picker_dismissed(cx)) + .ok(); + reveal_editor_window(&editor_path, cx); + return; + } + // The editor went away under the picker; reveal the main window so + // the app is not left with no visible window at all. + show_main_window(cx); + return; + } + let hidden = std::mem::take(&mut cx.global_mut::().main_hidden_for_picker); let idle = RecordingSession::global(cx).read(cx).phase == Phase::Idle; tracing::info!(hidden, idle, "picker dismissed"); @@ -1178,6 +1263,12 @@ pub fn start_recording_from_overlay(target: ScreenCaptureTarget, cx: &mut App) { // A "recordingStudio" dismissal does not reveal the main window -- the bar // owns the foreground now, and the stop flow's reshow brings it back. cx.global_mut::().main_hidden_for_picker = false; + // Same for an editor-owned picker (`targetModeSource === "editorRecording"` + // in the `ClipsSidebar` effect, `:416-419`): the editor stays hidden for + // the whole recording, and the session observer's finish path reveals it. + // The session's editor recording target survives -- that is what routes + // the finished capture back into the project. + cx.global_mut::().editor_hidden_for_picker = None; let main = cx.global::().main; main.update(cx, |view, window, cx| { @@ -1340,6 +1431,24 @@ pub fn begin_recording(config: StartConfig, cx: &mut App) { return; } + // `start_recording`'s first act (`src-tauri/src/recording.rs:1492-1494`): + // an armed editor recording target forces Studio, whatever mode the main + // window is in -- the finished capture has to append as an editable clip. + let mut config = config; + let editor_target = session.read(cx).editor_recording_target(); + if editor_target.is_some() { + config.mode = RecordingMode::Studio; + } + // `recording.rs:1775-1780`: the target editor gets content protection for + // the recording's duration (it is hidden-for-picker already, but a dock + // click can bring it back mid-capture, and a protected window stays out of + // the frame when it does). The Tauri arm also `minimize()`s it; here the + // picker's hide already took it off screen, and the finish path's reveal + // is the same un-hiding either way. + if let Some(editor_path) = &editor_target { + set_editor_content_protection(editor_path, true, cx); + } + let mut excluded: Vec = config.excluded_windows.clone(); excluded.extend(open_controls(&config, session.clone(), cx)); // The camera bubble is excluded from studio captures (the camera is its own @@ -1379,10 +1488,12 @@ pub fn begin_recording(config: StartConfig, cx: &mut App) { session.update(cx, |session, cx| session.start(config, cx)); } -/// Open the camera preview bubble (idempotent). Placement mirrors the Tauri -/// default: bottom-right of the main window's display, 100px in from the -/// corner. Position is not persisted yet (deviation; the Tauri app remembers it -/// per monitor). +/// Open the camera preview bubble (idempotent). Placement is the Tauri +/// decision (`windows.rs:2278-2345`): the persisted position from the shared +/// store's `cameraWindowPosition` keys when it is still on the preferred +/// monitor -- the main window's, falling back to the cursor's, like +/// `CursorMonitorInfo::from_window(main)` over there -- else the default +/// bottom-right slot (see `camera_window::opening_position`). pub fn open_camera_window(cx: &mut App) { if cx.global::().camera.is_some() { return; @@ -1391,15 +1502,19 @@ pub fn open_camera_window(cx: &mut App) { let state = crate::store::load().camera_window.unwrap_or_default(); let (width, height) = camera_window::window_size(&state, None); - let display = scap_targets::Display::get_containing_cursor() + let main = cx.global::().main; + let display = main + .update(cx, |_, window, _| window.bounds()) + .ok() + .and_then(|bounds| { + camera_window::display_for_point( + f64::from(f32::from(bounds.origin.x + bounds.size.width / 2.)), + f64::from(f32::from(bounds.origin.y + bounds.size.height / 2.)), + ) + }) + .or_else(scap_targets::Display::get_containing_cursor) .unwrap_or_else(scap_targets::Display::primary); - let (x, y) = match display.raw_handle().logical_bounds() { - Some(bounds) => ( - (bounds.position().x() + bounds.size().width() - width as f64 - 100.) as f32, - (bounds.position().y() + bounds.size().height() - height as f64 - 100.) as f32, - ), - None => (100., 100.), - }; + let (x, y) = camera_window::opening_position(&display); let handle = cx.open_window( WindowOptions { @@ -2243,6 +2358,153 @@ pub fn reload_editor(project_path: &Path, cx: &mut App) { open_editor(key, cx); } +/// `editor_window_for_path` (`src-tauri/src/windows.rs:3699-3706`): the live +/// editor window for a `.cap` bundle, by the same path identity the registry +/// keys on. +fn editor_window_handle(project_path: &Path, cx: &App) -> Option> { + let key = editor_key(project_path); + cx.global::() + .editors + .iter() + .find(|(path, _)| path == &key) + .map(|(_, handle)| *handle) +} + +/// `hideEditorForPicker` (`ClipsSidebar.tsx:337-341`) -- +/// `getCurrentWindow().hide()` on the editor, with the same detached-task rule +/// every other native show/hide in this file follows. +fn hide_editor_window(project_path: &Path, cx: &mut App) { + let Some(handle) = editor_window_handle(project_path, cx) else { + return; + }; + let native = handle + .update(cx, |_, window, _| platform::native_window(window)) + .ok() + .flatten(); + cx.spawn(async move |cx| { + if let Some(native) = &native { + platform::hide_native(native); + tracing::info!("editor window hidden for picker"); + } + cx.update(crate::menus::schedule_dock_sync); + }) + .detach(); +} + +/// The `unminimize()/show()/set_focus()` triple both Tauri finish paths run on +/// the target editor (`src-tauri/src/recording.rs:3231-3237, 3268-3274`) -- +/// `makeKeyAndOrderFront:` covers all three here, plus the display-link kick +/// every reshow in this app needs. +fn reveal_editor_window(project_path: &Path, cx: &mut App) { + let Some(handle) = editor_window_handle(project_path, cx) else { + return; + }; + let native = handle + .update(cx, |_, window, _| platform::native_window(window)) + .ok() + .flatten(); + cx.spawn(async move |cx| { + if let Some(native) = &native { + platform::show_native(native); + } + cx.update(|cx| { + handle + .update(cx, |_, window, cx| { + platform::kick_display_link(window); + cx.notify(); + window.refresh(); + }) + .ok(); + crate::menus::schedule_dock_sync(cx); + }); + }) + .detach(); +} + +/// `editor_window.set_content_protected(...)` around a recording +/// (`src-tauri/src/recording.rs:1775-1780`), released with the same +/// end-of-recording timing as [`set_teleprompter_content_protection`] and for +/// the same reason: a permanently excluded window is invisible on +/// capture-based displays. +fn set_editor_content_protection(project_path: &Path, hidden: bool, cx: &mut App) { + let Some(handle) = editor_window_handle(project_path, cx) else { + return; + }; + let native = handle + .update(cx, |_, window, _| platform::native_window(window)) + .ok() + .flatten(); + cx.spawn(async move |_| { + if let Some(native) = &native { + let sharing = platform::set_window_capture_hidden(native, hidden); + tracing::info!(hidden, sharing, "editor content protection"); + } + }) + .detach(); +} + +/// A recording that carried an editor target came to rest -- +/// `apply_post_studio_editor_behaviour`'s editor arm plus the cancelled/failed +/// fallback (`src-tauri/src/recording.rs:3225-3287`), fused because the gpui +/// session observer is the one place both outcomes land. +/// +/// * Clean studio stop (`recording` is `Some`): reveal the target editor and +/// hand it the finished bundle -- the native spelling of the +/// `EditorRecordingAdded` event whose editor-side listener runs +/// `addExistingRecordingToEditor` + `deleteRecordingDirectory` + reload +/// (`Editor.tsx:312-335`). +/// * Cancelled, deleted or failed (`None`): just reveal the editor, exactly +/// what the stop-cleanup fallback does. +/// * Editor window already closed: the Tauri event fires into a torn-down +/// webview and the capture stays in the library as its own project; same +/// here, except the main window is reshown (rescanning Recents on the way, +/// so the orphaned capture is visible) rather than left suppressed -- over +/// there the window merely stays minimized, here it was hidden outright and +/// leaving it hidden would leave the app with no window at all. +fn editor_recording_finished(editor_path: PathBuf, recording: Option, cx: &mut App) { + set_editor_content_protection(&editor_path, false, cx); + + let Some(handle) = editor_window_handle(&editor_path, cx) else { + tracing::warn!( + editor = %editor_path.display(), + recording = ?recording.as_ref().map(|path| path.display().to_string()), + "editor recording finished but its editor window is gone; \ + the capture stays in the library" + ); + show_main_window(cx); + return; + }; + + reveal_editor_window(&editor_path, cx); + + if let Some(recording_dir) = recording { + handle + .update(cx, |view, window, cx| { + view.append_recorded_clip(recording_dir, window, cx) + }) + .ok(); + } +} + +/// An editor-flow start that never reached the session (the pre-flight bails +/// in `start_recording_with_target`): put everything back the way a cancelled +/// picker would -- target cleared, editor revealed. Without this the editor +/// would stay hidden forever, since no phase transition is coming. +pub fn abort_editor_recording_flow(cx: &mut App) { + let session = RecordingSession::global(cx); + let Some(editor_path) = session.update(cx, |session, _| session.take_editor_recording_target()) + else { + return; + }; + cx.global_mut::().editor_hidden_for_picker = None; + tracing::info!(path = %editor_path.display(), "editor recording flow aborted before start"); + if editor_window_handle(&editor_path, cx).is_some() { + reveal_editor_window(&editor_path, cx); + } else { + show_main_window(cx); + } +} + /// The tray's Record Display / Record Window / Record Area. /// /// `crate::open_target_picker(&app, RecordingTargetMode::*)` over there, which diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index f88d75c1a7..02b355e7eb 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -8,8 +8,11 @@ mod assets; mod auth; #[cfg(target_os = "macos")] mod camera_bench; +#[cfg(target_os = "macos")] +mod camera_blur; mod camera_window; mod controls_window; +mod deeplink; mod dev_restore; mod devices; mod editor_audio; @@ -37,7 +40,10 @@ mod platform; mod presets; mod recording; mod screenshot; +mod screenshot_annotations; +mod screenshot_crop; mod screenshot_editor; +mod screenshot_export; mod session; mod settings_pages; mod settings_window; @@ -228,6 +234,18 @@ fn main() { } }) .detach(); + // The other half of the Tauri app's hand-off protocol + // (`store::handoff_marker_path`): staying up this long is what proves + // to it that redirecting here was not a mistake. + cx.spawn(async move |cx| { + cx.background_executor() + .timer(std::time::Duration::from_secs(10)) + .await; + cx.background_executor() + .spawn(async { crate::store::clear_handoff_marker() }) + .await; + }) + .detach(); // `CAP_GPUI_AUTO_TRAY` / `CAP_GPUI_TRAY_DUMP`: the tray's harness path. tray::drive_from_env(cx); // `CAP_GPUI_AUTO_CLOSE=settings|main:`: run the ⌘W body against diff --git a/apps/desktop-gpui/src/store.rs b/apps/desktop-gpui/src/store.rs index 8157a8379a..b99f206f96 100644 --- a/apps/desktop-gpui/src/store.rs +++ b/apps/desktop-gpui/src/store.rs @@ -29,8 +29,8 @@ pub enum CameraShape { Full, } -/// `BackgroundBlurMode`. Cycled and persisted for parity; the effects pipeline -/// itself is not wired yet. +/// `BackgroundBlurMode`. Cycled and persisted like the Tauri bubble's, and +/// processed live by the preview (`camera_blur.rs`). #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] pub enum BlurMode { @@ -60,8 +60,7 @@ impl BlurMode { } } -/// `CameraWindowState` from `CameraPreviewChrome.tsx`, minus `mirrored` (no -/// flip transform exists in this gpui rev; see README). +/// `CameraWindowState` from `CameraPreviewChrome.tsx`. #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub struct CameraWindowState { pub size: f32, @@ -86,6 +85,16 @@ pub struct PersistedState { pub camera_window: Option, #[serde(default)] pub export: Option, + /// `screenshotEditorLayersPanelOpen` -- the Tauri screenshot editor keeps + /// it in `localStorage` (`makePersisted` in + /// `screenshot-editor/context.tsx`); no webview here, so it lives in this + /// file. + #[serde(default)] + pub screenshot_layers_panel_open: Option, + /// `editorCropSnapToRatio` -- the crop dialog's persisted snap toggle, + /// also `localStorage` over there. + #[serde(default)] + pub screenshot_crop_snap_to_ratio: Option, } /// `export_settings` in the Tauri editor (`ExportPage.tsx` localStorage). @@ -145,6 +154,66 @@ pub fn app_data_dir() -> PathBuf { } } +/// The Tauri app's hand-off marker (`gpui_app.rs`). +/// +/// It writes this file immediately before spawning this app and never deletes +/// it; this app deletes it once it has been alive long enough to count as +/// healthy (`main.rs`) or on a clean quit (`menus::quit`). A marker the Tauri +/// app still finds at startup therefore means the build it handed off to never +/// came up, so it clears `enableGpuiApp` and takes the session back rather than +/// bouncing the user into an app that does not start. +pub fn handoff_marker_path() -> PathBuf { + app_data_dir().join("cap-gpui.handoff") +} + +/// Best-effort: a marker that cannot be removed only costs one fallback to the +/// Tauri app on the next launch. +pub fn clear_handoff_marker() { + let path = handoff_marker_path(); + match std::fs::remove_file(&path) { + Ok(()) => tracing::info!("cleared the hand-off marker"), + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => tracing::warn!("clearing the hand-off marker: {error}"), + } +} + +/// A dev-checkout switch-back has no `Cap.app` to `open`: the classic app only +/// runs inside its `tauri dev` harness, which exited with the hand-off. This +/// sentinel asks whatever supervises the dev session (`scripts/dev-desktop.mjs` +/// watches for it) to start that harness again; with no supervisor listening it +/// is inert, and the flag written alongside it still routes the next +/// `pnpm dev:desktop` to the classic app. +pub fn classic_reopen_path() -> PathBuf { + app_data_dir().join("cap-classic.reopen") +} + +pub fn request_classic_reopen() -> std::io::Result<()> { + let path = classic_reopen_path(); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::write(&path, std::process::id().to_string()) +} + +/// The dev switch-back's readiness handshake. A dev rebuild of the classic app +/// takes anywhere from seconds to many minutes, and quitting immediately +/// leaves the user with no app and no feedback for all of it. So the +/// switch-back writes this file next to the reopen sentinel and stays up +/// "waiting for the classic app"; the Tauri app deletes it when it starts and +/// decides to keep the session (`gpui_app.rs`), and only then does this app +/// quit. +pub fn classic_pending_path() -> PathBuf { + app_data_dir().join("cap-classic.pending") +} + +pub fn mark_classic_pending() -> std::io::Result<()> { + let path = classic_pending_path(); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::write(&path, std::process::id().to_string()) +} + fn state_path() -> PathBuf { app_data_dir().join("gpui-state.json") } @@ -696,6 +765,10 @@ pub struct GeneralSettings { /// `!!settings.outOfProcessMuxer` in experimental.tsx). pub enable_native_camera_preview: bool, pub out_of_process_muxer: bool, + /// Whether this app, rather than the Tauri one, owns the session. Reads + /// true for anyone who arrived here through the Tauri app's hand-off; the + /// Experimental page's Native app row turns it back off. + pub enable_gpui_app: bool, /// Not part of `general_settings` at all: the mic-confirmation toggle /// lives in the store's own `recording_start_safety` section /// (`RECORDING_START_SAFETY_DEFAULTS`), and the page renders it in the @@ -833,6 +906,7 @@ impl GeneralSettings { editor_preview_quality: enum_at(general, "editorPreviewQuality"), enable_native_camera_preview: bool_at(general, "enableNativeCameraPreview", false), out_of_process_muxer: bool_at(general, "outOfProcessMuxer", false), + enable_gpui_app: bool_at(general, "enableGpuiApp", false), confirm_without_microphone: bool_at( safety, "confirmBeforeRecordingWithoutMicrophone", @@ -1688,6 +1762,32 @@ mod tests { assert!(!GeneralSettings::load().enable_telemetry); } + /// The Tauri app hands off by setting `enableGpuiApp`, so this app has to + /// read the same key -- and default it off, since the vast majority of + /// stores predate it. + #[test] + fn the_hand_off_flag_reads_from_general_settings() { + let _store = TempStore::new("gpui-flag", None); + assert!(!GeneralSettings::load().enable_gpui_app); + + assert!(super::set_store_setting( + GENERAL_SETTINGS, + "enableGpuiApp", + Value::Bool(true) + )); + assert!(GeneralSettings::load().enable_gpui_app); + } + + /// Both apps have to name the same file, and it has to sit next to the + /// store rather than in a per-app dir. + #[test] + fn the_hand_off_marker_sits_beside_the_store() { + assert_eq!( + super::handoff_marker_path(), + super::app_data_dir().join("cap-gpui.handoff") + ); + } + /// `normalizeTranscriptionHints`: NULs stripped, whitespace trimmed, /// empties and duplicates dropped, first occurrence wins. #[test] diff --git a/apps/desktop-gpui/src/upload.rs b/apps/desktop-gpui/src/upload.rs index c311db96c0..6cd20c70fb 100644 --- a/apps/desktop-gpui/src/upload.rs +++ b/apps/desktop-gpui/src/upload.rs @@ -86,6 +86,7 @@ pub async fn upload_exported_video( } let s3_config = match create_or_get_video( + false, matches!(mode, UploadMode::Reupload) .then(|| { meta.sharing @@ -166,7 +167,12 @@ fn store_auth_missing() -> bool { !crate::store::auth_snapshot().signed_in() } +/// `create_or_get_video` (`src-tauri/upload.rs:340-436`): passing an existing +/// `video_id` re-uses that server record (which is what keeps a share link +/// stable across re-uploads), and `is_screenshot` marks the record the way the +/// screenshot share flow needs (`isScreenshot=true` on the create URL). async fn create_or_get_video( + is_screenshot: bool, video_id: Option, name: Option, meta: Option<&VideoMeta>, @@ -176,6 +182,9 @@ async fn create_or_get_video( if let Some(id) = video_id { path.push_str(&format!("&videoId={id}")); } + if is_screenshot { + path.push_str("&isScreenshot=true"); + } if let Some(name) = name { path.push_str(&format!("&name={}", urlencoding(&name))); } @@ -530,6 +539,19 @@ async fn put_part( } async fn upload_screenshot(video_id: &str, path: &Path) -> Result<(), AuthApiError> { + let bytes = compress_image(path)?; + presigned_put_bytes(video_id, "screenshot/screen-capture.jpg", bytes).await +} + +/// One presigned single-part PUT under a video's subpath -- `/api/upload/ +/// signed` for the URL, then the raw body. The thumbnail upload and the +/// rendered-screenshot upload are both this shape (`singlepart_uploader` + +/// `PresignedS3PutRequest` in the Tauri binary). +async fn presigned_put_bytes( + video_id: &str, + subpath: &str, + bytes: Vec, +) -> Result<(), AuthApiError> { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct Response { @@ -542,13 +564,12 @@ async fn upload_screenshot(video_id: &str, path: &Path) -> Result<(), AuthApiErr headers: HashMap, } - let bytes = compress_image(path)?; let response = auth::authed_request( reqwest::Method::POST, "/api/upload/signed", Some(json!({ "videoId": video_id, - "subpath": "screenshot/screen-capture.jpg", + "subpath": subpath, "method": "put", })), ) @@ -578,13 +599,86 @@ async fn upload_screenshot(video_id: &str, path: &Path) -> Result<(), AuthApiErr .map_err(|error| AuthApiError::Other(error.to_string()))?; if !response.status().is_success() { return Err(AuthApiError::Other(format!( - "thumbnail upload failed: {}", + "upload failed: {}", response.status() ))); } Ok(()) } +// --------------------------------------------------------------------------- +// Screenshot share upload +// --------------------------------------------------------------------------- + +/// A created-or-reused server video record, with the share link derived from +/// its id -- `UploadedItem` in the Tauri binary. +pub struct UploadedItem { + pub link: String, + pub id: String, +} + +/// What the screenshot share flow's upload step resolved to. The two +/// non-success arms carry the exact toasts `shareLinkFromUploadResult` maps +/// them to on the window side. +pub enum ScreenshotShareOutcome { + Uploaded(UploadedItem), + NotAuthenticated, + UpgradeRequired, +} + +/// `screenshot_upload_subpath` (`src-tauri/upload.rs:259-265`): the composited +/// image lands under the record's screenshot key, extension by content type. +fn screenshot_upload_subpath(content_type: &str) -> &'static str { + if content_type.eq_ignore_ascii_case("image/png") { + "screenshot/screen-capture.png" + } else { + "screenshot/screen-capture.jpg" + } +} + +/// `upload_rendered_screenshot` (`src-tauri/lib.rs:3761-3796`), minus the +/// clipboard/meta halves the window owns: gate on auth exactly as the Tauri +/// command does, create-or-get the screenshot video record (re-using the +/// sharing id when the caller has one, so the link survives re-uploads), and +/// presigned-PUT the encoded bytes to the screenshot subpath. +pub async fn upload_rendered_screenshot( + image_bytes: Vec, + content_type: &str, + video_id: Option, +) -> Result { + let auth = crate::store::auth_snapshot(); + if !auth.signed_in() { + // The Tauri command resets a corrupt/absent auth store on this path. + let _ = crate::store::set_auth(None); + return Ok(ScreenshotShareOutcome::NotAuthenticated); + } + if !auth.is_upgraded() { + return Ok(ScreenshotShareOutcome::UpgradeRequired); + } + + match upload_screenshot_bytes(image_bytes, content_type, video_id).await { + Ok(item) => Ok(ScreenshotShareOutcome::Uploaded(item)), + Err(AuthApiError::InvalidAuthentication) => Ok(ScreenshotShareOutcome::NotAuthenticated), + Err(AuthApiError::UpgradeRequired) => Ok(ScreenshotShareOutcome::UpgradeRequired), + Err(error) => Err(error.to_string()), + } +} + +/// `upload_screenshot_bytes` (`src-tauri/upload.rs:279-307`). +async fn upload_screenshot_bytes( + image_bytes: Vec, + content_type: &str, + video_id: Option, +) -> Result { + let s3_config = create_or_get_video(true, video_id, None, None, None).await?; + let subpath = screenshot_upload_subpath(content_type); + presigned_put_bytes(&s3_config.id, subpath, image_bytes).await?; + Ok(UploadedItem { + link: format!("{}/s/{}", auth::server_url(), s3_config.id), + id: s3_config.id, + }) +} + fn compress_image(path: &Path) -> Result, AuthApiError> { let img = image::ImageReader::open(path) .map_err(|error| AuthApiError::Other(format!("Failed to open image: {error}")))? @@ -704,7 +798,11 @@ mod tests { #[test] fn chunk_size_clamps() { assert_eq!(chunk_size_for(1), MIN_CHUNK_SIZE); - assert_eq!(chunk_size_for(MIN_CHUNK_SIZE * 200), MAX_CHUNK_SIZE); + // A hundredth of the file, clamped: the upper assertion needs a file + // whose hundredth actually exceeds the cap (1.5 GiB does; the old + // `MIN_CHUNK_SIZE * 200` = 1000 MiB sat below it and asserted the + // wrong side of the clamp). + assert_eq!(chunk_size_for(MAX_CHUNK_SIZE * 200), MAX_CHUNK_SIZE); assert_eq!(chunk_size_for(MIN_CHUNK_SIZE * 50), MIN_CHUNK_SIZE); } From 1da2659581a53e7dbd10c7d20e436a670c77ce1b Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:04:21 +0100 Subject: [PATCH 146/146] chore(desktop): dev-loop and hand-off updates for the gpui app The desktop dev script goes through scripts/dev-desktop.mjs (which supervises the gpui/classic hand-off), the cinder indirection is retired along with scripts/local-cargo.mjs, and the Tauri side's hand-off plumbing and experimental toggle pick up the current gpui-app behaviour. --- AGENTS.md | 8 +- README.md | 2 - apps/desktop/package.json | 2 +- .../desktop/src-tauri/src/general_settings.rs | 5 +- apps/desktop/src-tauri/src/gpui_app.rs | 366 ++++++++++++++++-- apps/desktop/src-tauri/src/lib.rs | 16 +- .../(window-chrome)/settings/experimental.tsx | 166 ++++++-- apps/desktop/src/utils/tauri.ts | 19 +- package.json | 3 +- scripts/bundle-gpui-mac | 6 +- scripts/dev-desktop.mjs | 130 +++++++ scripts/setup.js | 10 - 12 files changed, 641 insertions(+), 92 deletions(-) create mode 100644 scripts/dev-desktop.mjs diff --git a/AGENTS.md b/AGENTS.md index d061d4984a..d007508e08 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ These rules are enforced by CI (`cargo clippy -D warnings`, Biome). Fixing them ### Post-edit checks (run before you say "done") - Prefer scoped, fast checks over full workspace gates. Do not run long full-repo checks by default. -- Touched any Rust file → `cargo fmt --all` and `cargo check -p `. On macOS, if `cinder` is on `PATH`, use `cinder` instead of `cargo` for `check`, `build`, `test`, and `run` (not `fmt` or `clippy`). Add `--all-targets`, `--workspace`, or clippy only when explicitly requested, when preparing CI/PR final validation, or when the change needs broader coverage. Never use Cinder in CI or release builds. +- Touched any Rust file → `cargo fmt --all` and `cargo check -p `. Add `--all-targets`, `--workspace`, or clippy only when explicitly requested, when preparing CI/PR final validation, or when the change needs broader coverage. - Touched any TS / JS / JSON / CSS / MD file → run the narrowest applicable formatter/linter on touched files first, such as `pnpm exec biome check --write `. Use full `pnpm format`, `pnpm lint`, and `pnpm typecheck` only when explicitly requested or when the change spans shared types/packages. - Touched DB schema → `pnpm db:generate` before relying on it. @@ -61,7 +61,7 @@ Additionally, `unused_must_use = "deny"` applies to all Rust code: every `Result - Build: `pnpm build` (Turbo). Desktop release: `pnpm tauri:build`. - DB: `pnpm db:generate` → `pnpm db:push` → `pnpm db:studio`. - Docker: `pnpm docker:up | docker:stop | docker:clean`. -- Quality: `pnpm lint`, `pnpm format`, `pnpm typecheck`. Rust: `cargo build -p `, `cargo test -p `. On macOS, prefer `cinder` over `cargo` for those local `build` / `test` / `check` / `run` commands when it is installed. +- Quality: `pnpm lint`, `pnpm format`, `pnpm typecheck`. Rust: `cargo build -p `, `cargo test -p `. ## Coding Style & Naming - TypeScript / JS / JSON / CSS: **tab indent** and **double-quoted** strings, enforced by Biome (see `biome.json`). Do not configure per-file overrides. @@ -73,7 +73,7 @@ Additionally, `unused_must_use = "deny"` applies to all Rust code: every `Result ## Testing - TS/JS: Vitest where present (e.g., desktop). Name tests `*.test.ts(x)` near sources. -- Rust: `cargo test` per crate; tests in `src` or `tests`. On macOS, use `cinder test` when Cinder is installed. +- Rust: `cargo test` per crate; tests in `src` or `tests`. - Prefer unit tests for logic and light smoke tests for flows; no strict coverage yet. ## Commits & PRs @@ -111,6 +111,6 @@ Prefer the smallest correct fix, but only after checking whether the narrow fix ## Code Formatting & Lint Checks Before declaring any task complete, the agent should run the fastest useful check for every file type it touched and report anything skipped. -- **Rust**: `cargo fmt --all` and `cargo check -p ` for the touched crate. On macOS, if `cinder` is on `PATH`, run that check as `cinder check -p `. Add `--all-targets`, `--workspace`, or `cargo clippy -p --all-targets -- -D warnings` only for explicit requests, CI/PR final validation, or changes that need broader coverage. +- **Rust**: `cargo fmt --all` and `cargo check -p ` for the touched crate. Add `--all-targets`, `--workspace`, or `cargo clippy -p --all-targets -- -D warnings` only for explicit requests, CI/PR final validation, or changes that need broader coverage. - **TS / JS / JSON / CSS / MD**: prefer scoped checks such as `pnpm exec biome check --write `. Use full `pnpm format`, `pnpm lint`, and `pnpm typecheck` only when explicitly requested or when the change is broad enough to justify it. - If a scoped check fails, fix the violation in the source (do NOT suppress with `#[allow(...)]`, `// biome-ignore`, or `any` unless explicitly approved). The Pre-Generation Invariants show the correct form for every denied lint. diff --git a/README.md b/README.md index 5face40ac8..f8943348fb 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,6 @@ Requirements: - pnpm 10.5.2 - Rust 1.88 or newer - Docker for MySQL, MinIO, and local services -- Optional on macOS: [Cinder](https://github.com/CapSoftware/cinder) for faster local `check` / `build` / `test` / `run` and desktop watch. `pnpm dev:desktop` uses it automatically when `cinder` is on `PATH`. Install with `pnpm cinder:install`. Set `CAP_USE_CINDER=0` to force stock Cargo. Install and set up the repo: @@ -143,7 +142,6 @@ Common commands: | `pnpm format` | Format with Biome | | `pnpm typecheck` | Run TypeScript project references | | `cargo test -p ` | Run Rust tests for a crate | -| `pnpm cinder:install` | Install Cinder for faster local macOS Rust commands | Database commands: diff --git a/apps/desktop/package.json b/apps/desktop/package.json index d8dcb03460..e2e03c5c87 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -2,7 +2,7 @@ "name": "@cap/desktop", "type": "module", "scripts": { - "dev": "pnpm -w cap-setup && pnpm build:sidecar && dotenv -e ../../.env -- pnpm run preparescript && dotenv -e ../../.env -- node ../../scripts/local-cargo.mjs pnpm tauri dev", + "dev": "pnpm -w cap-setup && pnpm build:sidecar && dotenv -e ../../.env -- pnpm run preparescript && dotenv -e ../../.env -- node ../../scripts/dev-desktop.mjs", "build:tauri": "pnpm build:sidecar && dotenv -e ../../.env -- pnpm run preparescript && dotenv -e ../../.env -- pnpm tauri build", "build:sidecar": "node ../../scripts/build-desktop-binaries.mjs", "preparescript": "node scripts/prepare.js", diff --git a/apps/desktop/src-tauri/src/general_settings.rs b/apps/desktop/src-tauri/src/general_settings.rs index 75de506ae3..11458df561 100644 --- a/apps/desktop/src-tauri/src/general_settings.rs +++ b/apps/desktop/src-tauri/src/general_settings.rs @@ -249,8 +249,9 @@ pub struct GeneralSettingsStore { pub camera_blur_disabled_by_crash: Option, #[serde(default)] pub update_channel: UpdateChannel, - /// Launch the experimental gpui-native app (`cap-gpui`) alongside this - /// one at startup, when its binary is bundled or discoverable. + /// Run the experimental gpui-native app (`cap-gpui`) *instead of* this one: + /// while enabled, startup hands off to it and exits, and the native app's + /// own Experimental page hands back. See `gpui_app.rs`. #[serde(default)] pub enable_gpui_app: bool, } diff --git a/apps/desktop/src-tauri/src/gpui_app.rs b/apps/desktop/src-tauri/src/gpui_app.rs index 2c36144ecf..b64b8ebf02 100644 --- a/apps/desktop/src-tauri/src/gpui_app.rs +++ b/apps/desktop/src-tauri/src/gpui_app.rs @@ -1,19 +1,34 @@ -//! Launching the experimental gpui-native app (`cap-gpui`) alongside this -//! one. The two apps already share one settings store and recordings library -//! (`apps/desktop-gpui/src/store.rs` reads this app's), so "enabling" the -//! native app is just making sure its process exists. +//! Handing this app off to the experimental gpui-native app (`cap-gpui`). +//! +//! The two apps already share one settings store and recordings library +//! (`apps/desktop-gpui/src/store.rs` reads this app's), so the `enable_gpui_app` +//! setting is not "run both": whichever app is enabled owns the session. Turning +//! it on spawns `cap-gpui` and exits this app; turning it off inside `cap-gpui` +//! reopens this one. Startup honours the same flag, so the handoff survives a +//! relaunch. //! //! The binary is discovered in order: `CAP_GPUI_BIN` (explicit override), the //! bundled `gpui/` resource dir (staged by the release pipeline; absent in //! builds that don't ship it), and -- in debug builds only -- the sibling //! `apps/desktop-gpui` target dir, so the toggle works from a source checkout. +//! +//! ## The handoff marker +//! +//! A handoff that spawns a `cap-gpui` which then dies immediately would leave +//! the user with no app at all, and with a setting that keeps redirecting away +//! from the app that does work. So this side writes `cap-gpui.handoff` next to +//! the shared store before spawning, and `cap-gpui` deletes it once it has been +//! alive for ~10 seconds (`store::handoff_marker_path`, `main.rs`). A marker +//! still present at startup therefore means the last handoff never reached a +//! healthy instance: clear the flag, tell the user, and start normally. use std::path::PathBuf; use tauri::{AppHandle, Manager}; +use tauri_plugin_dialog::DialogExt; use tracing::{info, warn}; -use crate::general_settings::GeneralSettingsStore; +use crate::{App, MutableState, general_settings::GeneralSettingsStore, request_app_exit}; #[cfg(windows)] const BINARY_NAME: &str = "cap-gpui.exe"; @@ -39,22 +54,32 @@ fn binary_path(app: &AppHandle) -> Option { #[cfg(debug_assertions)] { + // Whichever profile was built most recently: a fixed preference here + // hands off to a days-old binary the moment the other profile is the + // one being actively rebuilt. let target = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../desktop-gpui/target"); - for profile in ["release", "debug"] { - if let Some(path) = existing_file(target.join(profile).join(BINARY_NAME)) { - return Some(path); - } + let newest = ["release", "debug"] + .into_iter() + .filter_map(|profile| { + let path = target.join(profile).join(BINARY_NAME); + let modified = std::fs::metadata(&path).ok()?.modified().ok()?; + Some((modified, path)) + }) + .max_by_key(|(modified, _)| *modified) + .map(|(_, path)| path); + if newest.is_some() { + return newest; } } None } -/// Mirror of `store::app_data_dir` in `apps/desktop-gpui`: the pidfile its -/// new-instance-wins guard writes lives under the shared production -/// identifier (`so.cap.desktop`), not this app's possibly-`.dev` one. -fn gpui_pidfile() -> PathBuf { +/// Mirror of `store::app_data_dir` in `apps/desktop-gpui`: the pidfile and the +/// handoff marker live under the shared production identifier +/// (`so.cap.desktop`), not this app's possibly-`.dev` one. +fn shared_data_dir() -> PathBuf { #[cfg(target_os = "macos")] let base = PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".into())) .join("Library/Application Support/so.cap.desktop"); @@ -71,38 +96,186 @@ fn gpui_pidfile() -> PathBuf { .join(".local/share") }) .join("so.cap.desktop"); - base.join("cap-gpui.pid") + base +} + +fn gpui_pidfile() -> PathBuf { + shared_data_dir().join("cap-gpui.pid") +} + +/// Whether this app's own store IS the shared one. It is for the production +/// bundle (`so.cap.desktop`), but a dev build stores under a `.dev` identifier +/// while `cap-gpui` always reads and writes the shared file -- two stores that +/// silently disagree. The handoff flag therefore lives in the shared store, +/// and this app's own copy of the setting is just what its settings page +/// displays. +fn own_store_is_shared(app: &AppHandle) -> bool { + app.path() + .app_data_dir() + .is_ok_and(|dir| dir == shared_data_dir()) +} + +fn shared_store_path() -> PathBuf { + // The store plugin's file for the production identifier -- the same file + // `store::set_store_setting` in apps/desktop-gpui operates on. + shared_data_dir().join("store") +} + +fn shared_store_flag() -> Option { + let raw = std::fs::read_to_string(shared_store_path()).ok()?; + let doc: serde_json::Value = serde_json::from_str(&raw).ok()?; + doc.get("general_settings")?.get("enableGpuiApp")?.as_bool() } -/// Whether a live `cap-gpui` owns the pidfile. Launching over a live instance -/// would not stack: its single-instance guard kills the previous process, so -/// an unconditional startup launch would restart a session the user may be -/// recording in. -fn instance_running() -> bool { +/// Single-key replace with everything else preserved, temp+rename atomic -- +/// the same contract as the gpui app's `set_store_setting`. A missing file is +/// created with just this key; an unparseable one is refused rather than +/// replaced. Only ever called when the shared store is NOT this app's own +/// (`own_store_is_shared` false): writing the file directly under the store +/// plugin's feet would race its in-memory copy. +fn write_shared_store_flag(value: bool) { + let path = shared_store_path(); + let mut doc = match std::fs::read_to_string(&path) { + Ok(raw) => match serde_json::from_str::(&raw) { + Ok(doc) => doc, + Err(error) => { + warn!(%error, "shared store is unparseable; leaving it untouched"); + return; + } + }, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => serde_json::json!({}), + Err(error) => { + warn!(%error, "could not read the shared store"); + return; + } + }; + let Some(root) = doc.as_object_mut() else { + warn!("shared store is not a JSON object; leaving it untouched"); + return; + }; + root.entry("general_settings") + .or_insert_with(|| serde_json::json!({})) + .as_object_mut() + .map(|section| section.insert("enableGpuiApp".into(), serde_json::Value::Bool(value))); + let pretty = serde_json::to_string_pretty(&doc).unwrap_or_else(|_| doc.to_string()); + let tmp = path.with_extension("tmp"); + let write = std::fs::create_dir_all(path.parent().unwrap_or(&path)) + .and_then(|()| std::fs::write(&tmp, pretty)) + .and_then(|()| std::fs::rename(&tmp, &path)); + if let Err(error) = write { + warn!(%error, "could not write the shared store flag"); + } +} + +fn handoff_marker() -> PathBuf { + shared_data_dir().join("cap-gpui.handoff") +} + +/// The dev switch-back's readiness handshake (`store::classic_pending_path` +/// in apps/desktop-gpui): `cap-gpui` writes this and stays on screen until the +/// classic app deletes it, so a minutes-long dev rebuild never leaves the user +/// with no app at all. +fn classic_pending() -> PathBuf { + shared_data_dir().join("cap-classic.pending") +} + +/// The pid of the live `cap-gpui` that owns the pidfile, if there is one. +/// Spawning over a live instance would not stack: its single-instance guard +/// kills the previous process, so a handoff that relaunched unconditionally +/// would restart a session the user may be recording in. +fn running_instance_pid() -> Option { #[cfg(unix)] { - let Ok(raw) = std::fs::read_to_string(gpui_pidfile()) else { - return false; - }; - let Ok(pid) = raw.trim().parse::() else { - return false; - }; - std::process::Command::new("ps") + let pid = std::fs::read_to_string(gpui_pidfile()) + .ok()? + .trim() + .parse::() + .ok()?; + let alive = std::process::Command::new("ps") .args(["-p", &pid.to_string(), "-o", "comm="]) .output() .is_ok_and(|output| { output.status.success() && String::from_utf8_lossy(&output.stdout).contains("cap-gpui") - }) + }); + alive.then_some(pid) } #[cfg(not(unix))] { - false + None + } +} + +#[cfg(target_os = "macos")] +fn activate_instance(pid: u32) { + use objc2_app_kit::{NSApplicationActivationOptions, NSRunningApplication}; + + if let Some(instance) = + unsafe { NSRunningApplication::runningApplicationWithProcessIdentifier(pid as _) } + { + unsafe { + instance.activateWithOptions(NSApplicationActivationOptions::ActivateIgnoringOtherApps); + } + } +} + +#[cfg(not(target_os = "macos"))] +fn activate_instance(_pid: u32) {} + +fn write_handoff_marker() { + let path = handoff_marker(); + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + let stamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|since| since.as_secs()) + .unwrap_or_default(); + if let Err(error) = std::fs::write(&path, stamp.to_string()) { + warn!(%error, "could not write the Cap GPUI handoff marker"); } } +/// The child must never inherit this process's stdio. The handoff exits this +/// app, and whatever was reading those pipes (`pnpm tauri dev`'s tooling chain) +/// goes with it; `cap-gpui`'s next log line then hits EPIPE, the write panics +/// inside an ObjC run-loop callback, and the process aborts about a tenth of a +/// second after launch. Its output goes to a file instead of `/dev/null` +/// because a handoff launch has no terminal, and a startup failure there is +/// only ever diagnosable from that file. fn spawn_detached(path: &std::path::Path) -> Result<(), String> { - let mut child = std::process::Command::new(path) + use std::process::Stdio; + + let log_path = shared_data_dir().join("cap-gpui.log"); + if let Some(parent) = log_path.parent() { + let _ = std::fs::create_dir_all(parent); + } + let (stdout, stderr) = match std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&log_path) + { + Ok(file) => match file.try_clone() { + Ok(clone) => (Stdio::from(file), Stdio::from(clone)), + Err(_) => (Stdio::from(file), Stdio::null()), + }, + Err(error) => { + warn!(%error, "could not open the Cap GPUI log; discarding its output"); + (Stdio::null(), Stdio::null()) + } + }; + + let mut command = std::process::Command::new(path); + command.stdin(Stdio::null()).stdout(stdout).stderr(stderr); + // Its own process group, so signals aimed at the terminal this app was + // started from never reach the app that outlives it. + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + command.process_group(0); + } + + let mut child = command .spawn() .map_err(|error| format!("Failed to launch Cap GPUI: {error}"))?; // Reap the child when it exits so it never lingers as a zombie. @@ -118,33 +291,144 @@ pub async fn gpui_app_available(app: AppHandle) -> bool { binary_path(&app).is_some() } +/// Close this app and open the native one. The setting has already been written +/// by the caller, so a failure here has to be reported rather than swallowed: +/// the page reverts it. #[tauri::command] #[specta::specta] -pub async fn launch_gpui_app(app: AppHandle) -> Result<(), String> { +pub async fn switch_to_gpui_app( + app: AppHandle, + state: MutableState<'_, App>, +) -> Result<(), String> { + if state.read().await.is_recording_active_or_pending() { + return Err("Stop your recording before switching to the native app.".to_string()); + } + if crate::export::export_session_active() { + return Err( + "Wait for your export to finish before switching to the native app.".to_string(), + ); + } + let path = binary_path(&app).ok_or_else(|| "Cap GPUI isn't included in this build".to_string())?; - info!(path = %path.display(), "launching Cap GPUI"); - spawn_detached(&path) + + // The caller wrote this app's own store; a dev build must also set the + // flag where `cap-gpui` and the next startup's redirect actually read it. + if !own_store_is_shared(&app) { + write_shared_store_flag(true); + } + + match running_instance_pid() { + Some(pid) => { + info!(pid, "Cap GPUI is already running; bringing it forward"); + activate_instance(pid); + } + None => { + write_handoff_marker(); + info!(path = %path.display(), "handing off to Cap GPUI"); + if let Err(error) = spawn_detached(&path) { + let _ = std::fs::remove_file(handoff_marker()); + if !own_store_is_shared(&app) { + write_shared_store_flag(false); + } + return Err(error); + } + } + } + + request_app_exit(app).await; + Ok(()) +} + +/// Whether this app should hand over to `cap-gpui` instead of starting. +/// +/// `true` means the caller must exit before any window is created. +pub fn redirect_at_startup_if_enabled(app: &AppHandle) -> bool { + let redirect = redirect_decision(app); + if !redirect { + // Staying up IS the readiness signal the waiting `cap-gpui` needs -- + // and the wait can begin while this app is ALREADY running (switching + // back with both apps up), so the signal has to keep firing, not just + // fire once at startup. + tauri::async_runtime::spawn(async { + loop { + let _ = std::fs::remove_file(classic_pending()); + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + }); + } + redirect } -pub fn launch_at_startup_if_enabled(app: &AppHandle) { - let enabled = GeneralSettingsStore::get(app) +fn redirect_decision(app: &AppHandle) -> bool { + let own = GeneralSettingsStore::get(app) .ok() .flatten() .is_some_and(|settings| settings.enable_gpui_app); + // In a dev build the shared store is the authority: `cap-gpui`'s + // switch-back writes false THERE, and honouring this app's own `.dev` + // copy instead would bounce the user straight back after every + // switch-back (the loop this replaced). + let enabled = if own_store_is_shared(app) { + own + } else { + shared_store_flag().unwrap_or(false) + }; if !enabled { - return; + if own { + // The stale display copy: without this the Experimental page + // shows the toggle on while the classic app owns the session. + if let Err(error) = GeneralSettingsStore::update(app, |settings| { + settings.enable_gpui_app = false; + }) { + warn!(%error, "could not reconcile the Cap GPUI setting"); + } + } + return false; } - if instance_running() { - info!("Cap GPUI is already running; not relaunching it"); - return; + + // A live instance is checked before the marker: within ten seconds of a + // successful handoff the marker is still legitimately on disk, and healing + // then would clear the flag and open this app next to a healthy native one. + // The marker's lifecycle stays with `cap-gpui` -- if that instance dies + // before proving itself, the marker survives it and the next launch heals. + if let Some(pid) = running_instance_pid() { + info!(pid, "Cap GPUI is already running; handing over to it"); + activate_instance(pid); + return true; } + + let marker = handoff_marker(); + if marker.exists() { + warn!("the last hand-off to Cap GPUI never reported a healthy instance; taking back over"); + let _ = std::fs::remove_file(&marker); + if let Err(error) = GeneralSettingsStore::update(app, |settings| { + settings.enable_gpui_app = false; + }) { + warn!(%error, "could not clear the Cap GPUI setting"); + } + if !own_store_is_shared(app) { + write_shared_store_flag(false); + } + app.dialog() + .message( + "The native Cap app didn't start correctly last time, so the classic app has been restored.", + ) + .show(|_| {}); + return false; + } + let Some(path) = binary_path(app) else { - warn!("Cap GPUI is enabled but its binary was not found"); - return; + warn!("Cap GPUI is enabled but its binary was not found; starting this app instead"); + return false; }; - info!(path = %path.display(), "launching Cap GPUI at startup"); + + write_handoff_marker(); + info!(path = %path.display(), "handing off to Cap GPUI at startup"); if let Err(error) = spawn_detached(&path) { warn!("{error}"); + let _ = std::fs::remove_file(handoff_marker()); + return false; } + true } diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 248c711b06..5ae0b64e3e 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -4883,7 +4883,7 @@ fn specta_builder() -> tauri_specta::Builder { set_camera_input, set_native_camera_preview_enabled, gpui_app::gpui_app_available, - gpui_app::launch_gpui_app, + gpui_app::switch_to_gpui_app, recording_settings::set_recording_mode, upload_logs, get_system_diagnostics, @@ -5298,7 +5298,19 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { specta_builder.mount_events(&app); hotkeys::init(&app); general_settings::init(&app); - gpui_app::launch_at_startup_if_enabled(&app); + // Before anything shows a window or initialises further state: when + // the native app owns the session, this one only exists to start it. + if gpui_app::redirect_at_startup_if_enabled(&app) { + // Nothing is managed yet, so `ExitRequested` would otherwise + // spawn a cleanup with no `AppExitState` to guard it. Marking + // the exit as already begun takes the plain runtime-exit path. + let exit_state = AppExitState::default(); + exit_state.begin(); + app.manage(exit_state); + crash_sentinel::mark_clean_exit(); + app.exit(0); + return Ok(()); + } configure_camera_blur_recovery(&app, previous_termination); fake_window::init(&app); app.manage(target_select_overlay::WindowFocusManager::default()); diff --git a/apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx b/apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx index 54785f8e0f..f21fcc5a34 100644 --- a/apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx +++ b/apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx @@ -1,7 +1,6 @@ -import { Button } from "@cap/ui-solid"; import { invoke } from "@tauri-apps/api/core"; import { type } from "@tauri-apps/plugin-os"; -import { createResource, Show } from "solid-js"; +import { createResource, createSignal, For, onCleanup, Show } from "solid-js"; import { createStore } from "solid-js/store"; import { generalSettingsStore } from "~/store"; @@ -12,11 +11,31 @@ import { import { Section, SectionRows, - SettingItem, SettingsPageContent, ToggleSettingItem, } from "./Setting"; +// The explainer runs alongside the countdown rather than before it: the numeral +// is on screen from the first frame, and the lines fade through underneath it. +// The mirror of this sequence lives in `render_switch_overlay` in the native +// app's `settings_pages.rs`. +const SWITCH_SENTENCES = [ + "Switching to the native Cap app.", + "It will look almost identical. That is the point.", + "Same Cap, rebuilt fully native for performance.", + "Experimental. Your recordings and settings come with you.", +]; +const SENTENCE_MS = 1300; +const COUNTDOWN_FROM = 5; + +type Takeover = { + sentence: number; + /** Counts 5 down to 1; the switch fires rather than ever showing zero. */ + remaining: number; + /** Set when the switch was refused; the overlay stays up with the reason. */ + error: string | null; +}; + export default function ExperimentalSettings() { const [store] = createResource(() => generalSettingsStore.get()); const osType = type(); @@ -40,14 +59,80 @@ function Inner(props: { invoke("gpui_app_available").catch(() => false), ); - const launchGpuiApp = async () => { + const [takeover, setTakeover] = createSignal(null); + const timers = new Set>(); + + const clearTimers = () => { + for (const timer of timers) clearTimeout(timer); + timers.clear(); + }; + onCleanup(clearTimers); + + const cancelTakeover = () => { + clearTimers(); + setTakeover(null); + }; + + // A failed takeover leaves the overlay up but puts the toggle back where + // it was, since nothing was switched. + const takeoverActive = () => { + const state = takeover(); + return state !== null && state.error === null; + }; + + const performSwitch = async () => { try { - await invoke("launch_gpui_app"); + await generalSettingsStore.set({ enableGpuiApp: true }); + await invoke("switch_to_gpui_app"); } catch (error) { - console.error("Failed to launch Cap GPUI", error); + // The setting is written first so the native app comes up already + // owning the session; a refused handoff has to put it back. + await generalSettingsStore + .set({ enableGpuiApp: false }) + .catch(() => undefined); + setTakeover((state) => + state + ? { + ...state, + error: + typeof error === "string" + ? error + : "Couldn't open the native app.", + } + : state, + ); } }; + // The toggle is the confirmation: flipping it starts the takeover, and + // Cancel is on screen for the whole sequence. + const startTakeover = () => { + clearTimers(); + setTakeover({ sentence: 0, remaining: COUNTDOWN_FROM, error: null }); + + for (let index = 1; index < SWITCH_SENTENCES.length; index += 1) { + timers.add( + setTimeout(() => { + setTakeover((state) => + state ? { ...state, sentence: index } : state, + ); + }, index * SENTENCE_MS), + ); + } + + for (let tick = 1; tick < COUNTDOWN_FROM; tick += 1) { + timers.add( + setTimeout(() => { + setTakeover((state) => + state ? { ...state, remaining: COUNTDOWN_FROM - tick } : state, + ); + }, tick * 1000), + ); + } + + timers.add(setTimeout(() => void performSwitch(), COUNTDOWN_FROM * 1000)); + }; + const handleChange = async ( key: K, value: (typeof settings)[K], @@ -63,9 +148,6 @@ function Inner(props: { } else { await generalSettingsStore.set({ [key]: value }); } - if (key === "enableGpuiApp" && value === true) { - await launchGpuiApp(); - } } catch (error) { setSettings(key as keyof GeneralSettingsStore, previousValue); console.error(`Failed to update ${key}`, error); @@ -113,24 +195,64 @@ function Inner(props: { handleChange("enableGpuiApp", value)} + description="Close this app and reopen the experimental fully-native version of Cap. It is unfinished, so expect missing features. Your recordings and settings are shared, and you can switch back from its Experimental settings." + value={!!settings.enableGpuiApp || takeoverActive()} + onChange={(value) => { + if (value) startTakeover(); + else if (takeover()) cancelTakeover(); + else void handleChange("enableGpuiApp", false); + }} /> - - - - - + + + {(state) => ( +
    + +

    + {state().remaining} +

    +
    + +
    + + {(sentence, index) => ( +

    + {sentence} +

    + )} +
    +
    + + + {(message) => ( +

    + {message()} +

    + )} +
    + + +
    + )} +
    ); } diff --git a/apps/desktop/src/utils/tauri.ts b/apps/desktop/src/utils/tauri.ts index f4705e44fc..34ffe6ee20 100644 --- a/apps/desktop/src/utils/tauri.ts +++ b/apps/desktop/src/utils/tauri.ts @@ -14,6 +14,17 @@ async setCameraInput(id: DeviceOrModelID | null, skipCameraWindow: boolean | nul async setNativeCameraPreviewEnabled(enabled: boolean) : Promise { return await TAURI_INVOKE("set_native_camera_preview_enabled", { enabled }); }, +async gpuiAppAvailable() : Promise { + return await TAURI_INVOKE("gpui_app_available"); +}, +/** + * Close this app and open the native one. The setting has already been written + * by the caller, so a failure here has to be reported rather than swallowed: + * the page reverts it. + */ +async switchToGpuiApp() : Promise { + return await TAURI_INVOKE("switch_to_gpui_app"); +}, async setRecordingMode(mode: RecordingMode) : Promise { return await TAURI_INVOKE("set_recording_mode", { mode }); }, @@ -976,7 +987,13 @@ previousRecordingsPaths?: string[]; * Cleared automatically when the app version changes (one retry per * update, since a new ort/wgpu/driver stack may have fixed the crash). */ -cameraBlurDisabledByCrash?: string | null; updateChannel?: UpdateChannel } +cameraBlurDisabledByCrash?: string | null; updateChannel?: UpdateChannel; +/** + * Run the experimental gpui-native app (`cap-gpui`) *instead of* this one: + * while enabled, startup hands off to it and exits, and the native app's + * own Experimental page hands back. See `gpui_app.rs`. + */ +enableGpuiApp?: boolean } export type GifExportSettings = { fps: number; resolution_base: XY; quality: GifQuality | null } export type GifQuality = { /** diff --git a/package.json b/package.json index 197862c7d8..55c4e1879e 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,7 @@ "check-tauri-versions": "node scripts/check-tauri-plugin-versions.js", "clean": "find . -name node_modules -o -name .next -o -name .output -o -name .turbo -o -name dist -type d -prune | xargs rm -rf", "lgtm-otel": "docker run -p 3010:3000 -p 4317:4317 -p 4318:4318 --rm -it docker.io/grafana/otel-lgtm", - "cinder:install": "cargo install --git https://github.com/CapSoftware/cinder --locked", - "with-env": "dotenv -e .env -- node scripts/local-cargo.mjs" + "with-env": "dotenv -e .env --" }, "devDependencies": { "@biomejs/biome": "2.2.0", diff --git a/scripts/bundle-gpui-mac b/scripts/bundle-gpui-mac index f6c55cf39d..6b41c87f74 100755 --- a/scripts/bundle-gpui-mac +++ b/scripts/bundle-gpui-mac @@ -43,11 +43,7 @@ done if [[ "${CAP_GPUI_BUNDLE_SKIP_BUILD:-0}" != "1" ]]; then pushd "$gpui_dir" >/dev/null - if command -v cinder >/dev/null 2>&1; then - cinder build --release - else - cargo build --release - fi + cargo build --release popd >/dev/null fi diff --git a/scripts/dev-desktop.mjs b/scripts/dev-desktop.mjs new file mode 100644 index 0000000000..1e048a3b94 --- /dev/null +++ b/scripts/dev-desktop.mjs @@ -0,0 +1,130 @@ +// The desktop dev entry: `pnpm tauri dev` plus the gpui app's save-to-relaunch +// loop (`apps/desktop-gpui/dev.sh`), so working on either app under +// `pnpm dev:desktop` gets hot reload without a second terminal. The gpui loop +// is macOS-only (dev.sh uses BSD stat/md5), skipped when the workspace is +// absent (main without the gpui branch), and opt-out via CAP_GPUI_DEV=0. Its +// output is prefixed [gpui] so the two cargo streams stay tellable apart. +// +// This script is also the dev half of the experimental app-switch: handing off +// to the gpui app exits the Tauri process by design, so the session stays up +// while the loop runs, and switching back inside the gpui app writes a reopen +// sentinel (`store::request_classic_reopen`) that this supervisor answers by +// starting `tauri dev` again -- the dev classic app only exists inside that +// harness, so nothing else could reopen it. +import { spawn } from "node:child_process"; +import { existsSync, rmSync } from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import readline from "node:readline"; +import { fileURLToPath } from "node:url"; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const gpuiDir = path.join(repoRoot, "apps", "desktop-gpui"); +const gpuiDevScript = path.join(gpuiDir, "dev.sh"); +const reopenSentinel = path.join( + os.homedir(), + "Library/Application Support/so.cap.desktop/cap-classic.reopen", +); + +let tauri = null; +let gpui = null; +let lastTauriCode = 0; +let exiting = false; + +function startTauri() { + tauri = spawn("pnpm", ["tauri", "dev"], { stdio: "inherit" }); + tauri.on("exit", (code, signal) => { + tauri = null; + lastTauriCode = signal ? 1 : (code ?? 1); + if (exiting || !gpui) { + finish(signal); + return; + } + // The experimental toggle hands the session off to the gpui app: this + // exit is by design, and the loop keeps the app the user switched to + // alive. The sentinel watcher below brings `tauri dev` back when the + // gpui app asks to switch back again. + console.error( + "[gpui] Tauri app exited; the gpui dev loop is still running (Ctrl-C to stop)", + ); + }); +} + +function finish(signal) { + exiting = true; + stopGpui(); + if (signal) { + // Re-raise with the handler gone, or it would swallow its own signal. + process.removeAllListeners(signal); + process.kill(process.pid, signal); + } else process.exit(lastTauriCode); +} + +// dev.sh traps TERM and shuts the running app down with it; give that a +// moment before giving up on a stuck loop. +function stopGpui() { + const child = gpui; + if (!child) return; + child.kill("SIGTERM"); + setTimeout(() => { + if (gpui === child) child.kill("SIGKILL"); + }, 5000).unref(); +} + +if ( + process.platform === "darwin" && + process.env.CAP_GPUI_DEV !== "0" && + existsSync(gpuiDevScript) +) { + gpui = spawn("bash", [gpuiDevScript], { + cwd: gpuiDir, + stdio: ["ignore", "pipe", "pipe"], + env: process.env, + }); + const prefix = (stream, out) => { + readline + .createInterface({ input: stream }) + .on("line", (line) => out.write(`[gpui] ${line}\n`)); + }; + prefix(gpui.stdout, process.stdout); + prefix(gpui.stderr, process.stderr); + gpui.on("exit", (code, signal) => { + gpui = null; + // With no Tauri child either, the loop was the session's last process: + // exit whether this is a shutdown or a crash in detached mode. + if (!tauri) { + finish(null); + return; + } + if (exiting) return; + console.error( + `[gpui] dev loop exited (${signal ?? code}); restart with: cd apps/desktop-gpui && ./dev.sh`, + ); + }); + + // A sentinel from before this session is stale, not a request. + rmSync(reopenSentinel, { force: true }); + setInterval(() => { + if (exiting || !existsSync(reopenSentinel)) return; + // Always consumed once seen: with the Tauri side already up (or still + // starting, e.g. mid-compile) the request is satisfied, and leaving + // the file would fire it spuriously after the next hand-off. + rmSync(reopenSentinel, { force: true }); + if (tauri) return; + console.error("[gpui] switch-back requested; starting the Tauri app again"); + startTauri(); + }, 500).unref(); +} + +for (const signal of ["SIGINT", "SIGTERM"]) { + process.on(signal, () => { + exiting = true; + stopGpui(); + // Ctrl-C already reaches the children through the process group; + // forward for the direct-signal case, then let the exit handlers run. + if (tauri) tauri.kill(signal); + else if (!gpui) process.exit(lastTauriCode); + }); +} + +startTauri(); diff --git a/scripts/setup.js b/scripts/setup.js index ca7b116f1a..9b42839ea7 100644 --- a/scripts/setup.js +++ b/scripts/setup.js @@ -6,7 +6,6 @@ import * as path from "node:path"; import { env } from "node:process"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; -import { resolveLocalCargo, usesCinder } from "./local-cargo.mjs"; const exec = promisify(execCb); const execFile = promisify(execFileCb); @@ -48,15 +47,6 @@ async function main() { ); if (process.platform === "darwin") { - const localCargo = resolveLocalCargo(); - if (usesCinder(localCargo)) - console.log( - `Using Cinder at ${localCargo} for local macOS Cargo commands`, - ); - else if (env.CAP_USE_CINDER === "0") - console.log("Cinder disabled (CAP_USE_CINDER=0), using Cargo"); - else console.log("Cinder not found. Install with: pnpm cinder:install"); - cargoConfigContents += FFMPEG_CARGO_ENV; const NATIVE_DEPS_VERSION = "v0.25";

    n{a5{DL!Ulj2S0gSl3&PnJ1jZ^j!e0UK)V$ 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 literal 0 HcmV?d00001 From ce383cb29114b2f4888bceffd0fda9d966969136 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:32:30 +0100 Subject: [PATCH 014/146] chore(desktop-gpui): disable incremental compilation to cap disk growth --- apps/desktop-gpui/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/desktop-gpui/Cargo.toml b/apps/desktop-gpui/Cargo.toml index 08ea8b3ff3..3662e0351d 100644 --- a/apps/desktop-gpui/Cargo.toml +++ b/apps/desktop-gpui/Cargo.toml @@ -85,6 +85,10 @@ wgpu-hal = { path = "../../vendor/wgpu-hal" } # gpui is unusably slow when its dependencies are unoptimized; zed/comet both # build deps at opt-level 2 and keep the app crate itself at 0. opt-level = 0 +# Incremental caches for this dep tree run to gigabytes on a disk that has +# already been filled to zero once; the app crate is small enough that full +# recompiles cost seconds. +incremental = false [profile.dev.package."*"] opt-level = 2 From 085d56596098cb45129432c4f08ad1c09e663d94 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:56:07 +0100 Subject: [PATCH 015/146] feat(desktop-gpui): window-management layer and the recording controls bar The platform module reaches the NSWindow behind a gpui window through raw-window-handle and applies what gpui does not expose: window level, Spaces collection behavior, shadow. The main window gets its level-100 all-Spaces treatment from windows.rs; the 320x150 controls bar is a non-activating WindowKind::PopUp panel at the level the Tauri bar actually runs at (CGWindowLevelForKey(10) = 8 -- the constant windows.rs names kCGMaximumWindowLevelKey is really the modal-panel key, reproduced verbatim for parity). The recording lifecycle moves off MainWindow into a RecordingSession entity both windows observe. Starting opens the bar first and hides the main window, passing the bar's window number to the actors as a capture exclusion; stop, delete, restart, and failed starts tear it back down. Pause/resume produce real segment boundaries. A microphone that enumerates but fails to open degrades to a no-mic recording instead of failing the start. --- apps/desktop-gpui/Cargo.lock | 3 + apps/desktop-gpui/Cargo.toml | 5 + apps/desktop-gpui/README.md | 34 +- .../assets/icons/more-vertical.svg | 5 + .../assets/icons/pause-circle.svg | 5 + apps/desktop-gpui/assets/icons/restart.svg | 3 + apps/desktop-gpui/assets/icons/trash.svg | 3 + .../docs/recording-controls-bar.png | Bin 0 -> 21660 bytes .../docs/recording-controls-starting.png | Bin 0 -> 21241 bytes apps/desktop-gpui/src/app_windows.rs | 190 +++++++++++ apps/desktop-gpui/src/assets.rs | 19 +- apps/desktop-gpui/src/controls_window.rs | 304 ++++++++++++++++++ apps/desktop-gpui/src/main.rs | 42 ++- apps/desktop-gpui/src/main_window.rs | 222 ++++++------- apps/desktop-gpui/src/platform.rs | 145 +++++++++ apps/desktop-gpui/src/recording.rs | 147 +++++++-- apps/desktop-gpui/src/session.rs | 266 +++++++++++++++ apps/desktop-gpui/src/theme.rs | 13 + 18 files changed, 1239 insertions(+), 167 deletions(-) create mode 100644 apps/desktop-gpui/assets/icons/more-vertical.svg create mode 100644 apps/desktop-gpui/assets/icons/pause-circle.svg create mode 100644 apps/desktop-gpui/assets/icons/restart.svg create mode 100644 apps/desktop-gpui/assets/icons/trash.svg create mode 100644 apps/desktop-gpui/docs/recording-controls-bar.png create mode 100644 apps/desktop-gpui/docs/recording-controls-starting.png create mode 100644 apps/desktop-gpui/src/app_windows.rs create mode 100644 apps/desktop-gpui/src/controls_window.rs create mode 100644 apps/desktop-gpui/src/platform.rs create mode 100644 apps/desktop-gpui/src/session.rs diff --git a/apps/desktop-gpui/Cargo.lock b/apps/desktop-gpui/Cargo.lock index 89c1c9a69d..09d575b16f 100644 --- a/apps/desktop-gpui/Cargo.lock +++ b/apps/desktop-gpui/Cargo.lock @@ -1294,6 +1294,9 @@ dependencies = [ "gpui_platform", "gpui_tokio", "kameo", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "raw-window-handle", "scap-targets", "serde_json", "tokio", diff --git a/apps/desktop-gpui/Cargo.toml b/apps/desktop-gpui/Cargo.toml index 3662e0351d..72a6ca73aa 100644 --- a/apps/desktop-gpui/Cargo.toml +++ b/apps/desktop-gpui/Cargo.toml @@ -57,6 +57,11 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } [target.'cfg(target_os = "macos")'.dependencies] +# Native window treatment (panel levels, Spaces) gpui does not expose. Same +# versions gpui's own tree compiles, so no second objc2 universe gets built. +objc2 = "0.5.2" +objc2-app-kit = { version = "0.2.2", features = ["NSWindow", "NSView", "NSResponder", "NSGraphics", "NSPasteboard"] } +raw-window-handle = "0.6" # ScreenCaptureKit shareable content, acquired the same way recording.rs in # src-tauri does. Same fork/rev/features as the root workspace. cidre = { git = "https://github.com/CapSoftware/cidre", rev = "bf84b67079a8", features = [ diff --git a/apps/desktop-gpui/README.md b/apps/desktop-gpui/README.md index 4729d1e720..0eb690e209 100644 --- a/apps/desktop-gpui/README.md +++ b/apps/desktop-gpui/README.md @@ -58,6 +58,16 @@ Everything below is real, not mocked. - **Mode selector**, with the info panel behind its dot. - **Light and dark**, following the system appearance, from the app's real resolved Radix values. +- **Native panel behavior.** The main window runs at window level 100 on all + Spaces, exactly as `windows.rs` configures it — applied through a small + `platform` module that reaches the `NSWindow` behind a gpui window via + `raw-window-handle` (gpui exposes no level/Spaces API). +- **Recording controls bar.** The 320×150 always-on-screen panel from + `in-progress-recording.tsx`: stop with a live timer, pause/resume, restart, + delete, mic indicator, drag handle. A non-activating panel + (`WindowKind::PopUp`), so its buttons work without stealing focus from the + app being recorded. While it is up the main window hides, and the bar's own + window is excluded from the capture. ### Layout fidelity @@ -78,7 +88,8 @@ Things that are deliberately different, and why. |---|---| | **Traffic lights are hand-drawn** | The Tauri main window returns `None` from `traffic_lights_position`, which routes it to `decorations(false)`; the lights are HTML there too. `titlebar: None` is the gpui equivalent. Minimize is not drawn, and zoom toggles expand/collapse. | | **No vibrancy** | The real macOS shell is a translucent material, not `bg-gray-1`. This is the single largest visual gap. | -| **Not always-on-top** | `WindowKind::Floating` is *not* the fix: it floats, but it allocates an `NSPanel`, and a panel hides itself when the app deactivates — the window would vanish the moment you click another app. This needs the AppKit calls `windows.rs` makes, on a normal window. Same for `visible_on_all_workspaces` and the NSPanel level-100 treatment. | +| **NSWindow, not NSPanel** | The Tauri app class-swizzles its windows into `NSPanel`s via `tauri_nspanel`. Here the main window stays a normal `NSWindow` and gets the observable parts — level 100, `CanJoinAllSpaces \| FullScreenPrimary` — from the `platform` module. (`WindowKind::Floating` is *not* a shortcut to this: its panel hides on app deactivation.) The controls bar *is* a real panel via `WindowKind::PopUp`, whose non-activating behavior it genuinely needs. | +| **Controls bar level 8, faithfully** | `windows.rs` raises the bar with `CGWindowLevelForKey(10)` under a constant named `kCGMaximumWindowLevelKey` — but key 10 is `kCGModalPanelWindowLevelKey` (maximum is 14), so the shipping bar actually runs at level 8. Reproduced verbatim rather than "fixed" from over here. | | **Resize does not re-clamp** | Expand/collapse animates over 180ms with an ease-out cubic, as the Tauri app does, but does not re-clamp the window into the monitor work area afterwards — expanding near a screen edge can push the window off it. | | **Panels instead of windows** | Mode info is the 580×340 ModeSelect *window* in the Tauri app; here it is a body panel, because there is only one window so far. The device and target pickers are body panels in both. | | **No target thumbnails** | Display and window cards render the icon fallback the real card falls back to before its thumbnail arrives. Live previews need the capture pipeline. | @@ -103,9 +114,9 @@ Sizes are the Tauri app's, from `apps/desktop/src-tauri/src/windows.rs`. | Window | Size | Status | |---|---|---| -| Main | 330×395 / 600×660 | **Done** — layout, devices, pickers, modes, recording | +| Main | 330×395 / 600×660 | **Done** — layout, devices, pickers, modes, recording, level-100 panel behavior | | Camera preview | 460×460 | Not started — needs the camera feed | -| Recording controls | 320×150 | Not started — needs a recording session | +| Recording controls | 320×150 | **Done** — timer, pause/resume, restart, delete, mic indicator, drag; capture-excluded, non-activating | | Target select overlay | per display | Not started — one transparent window per display | | Window capture occluder | per display | Not started | | Capture area | per display | Not started | @@ -130,11 +141,24 @@ instant projects get `content/output.mp4` plus the `finalize_completed` — a project recorded here exports cleanly with `cap export`. +The flow matches the real app: starting opens the controls bar first (in its +"Starting" state) and hides the main window; the bar's window number is passed +to the recording actors as an excluded window so the bar never appears in the +capture; stop, delete, and a failed start all close the bar and bring the main +window back. Pause closes the live segment and resume opens the next one — +a paused recording produces exactly the multi-segment `.cap` the editor +expects. A microphone that enumerates but fails to open (Bluetooth profile +switches, Continuity devices) degrades to a no-mic recording instead of +failing the start. + Recording-specific deviations: | | | |---|---| | **TEMP start button** | The real app starts recording from the fullscreen target-select overlay, which does not exist yet. An armed target (Display selected, or a concrete window picked) shows a pinned blue Start Recording footer instead. | +| **Bar timer, no countdown** | The countdown variant (`window.COUNTDOWN`) needs the settings store; the bar goes straight from Starting to the timer. | +| **Mic level is static** | The level track under the mic icon renders, but live levels need the app-scoped feed actors (same work as the camera preview). Mute toggle likewise. | +| **Settings button is inert** | The bar's recording-settings popover menu is not built. | | **Area does not arm** | Area needs the selection overlay. | | **Per-recording feed actors** | The Tauri app keeps app-wide `MicrophoneFeed`/`CameraFeed` actors for previews and level meters; here they are spawned per recording and dropped after. Moves to app scope with the camera preview window. | | **No overlay blur** | The recording overlay is `bg-gray-1/80` without `backdrop-blur-xs`; this gpui rev has no per-element backdrop blur hook. | @@ -143,7 +167,9 @@ Recording-specific deviations: `CAP_GPUI_AUTO_RECORD=studio:5` (or `instant:4`) arms the primary display and drives a start/stop through the button code paths — the end-to-end check uses -it because unprivileged synthetic clicks are dropped. +it because unprivileged synthetic clicks are dropped. Add +`CAP_GPUI_AUTO_PAUSE=1` to pause for the middle third: two segments whose +summed duration is ~⅔ of wall time is the proof the pause reached the engine. Running from a dev build needs the ffmpeg dylibs the binary's install names point at (`@executable_path/../Frameworks/Spacedrive.framework/...`): diff --git a/apps/desktop-gpui/assets/icons/more-vertical.svg b/apps/desktop-gpui/assets/icons/more-vertical.svg new file mode 100644 index 0000000000..f045c01828 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/more-vertical.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/desktop-gpui/assets/icons/pause-circle.svg b/apps/desktop-gpui/assets/icons/pause-circle.svg new file mode 100644 index 0000000000..9c1412c604 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/pause-circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/desktop-gpui/assets/icons/restart.svg b/apps/desktop-gpui/assets/icons/restart.svg new file mode 100644 index 0000000000..49b9015377 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/restart.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/trash.svg b/apps/desktop-gpui/assets/icons/trash.svg new file mode 100644 index 0000000000..fd943094cf --- /dev/null +++ b/apps/desktop-gpui/assets/icons/trash.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/docs/recording-controls-bar.png b/apps/desktop-gpui/docs/recording-controls-bar.png new file mode 100644 index 0000000000000000000000000000000000000000..6b7d05e482a5e7e4d8ae6a8e9ca7a9481125f292 GIT binary patch literal 21660 zcmeFY1zTH9*C-r9fZ$Nvwb0^LTuY(FDH_~c+}#}t6e;esxI=MwDPFutumZ)ckl{&K5YgRs}D!<0YAjbdz0N8SGUa0{9z(4>1i4Ba3C{e!! zvH}2DidNFns&dlOw5rYy@2qSs0Dw0ilC{ww>feZS^wj7;;8&9Jzm)J2o=D1r*#m#m zN6NnehCVTSsal&qTD86;17dB(__BzEPjQtjQ`?q;f=^^&pik0v&IFpWFuI0bcecQ~ z9<#1@vs`vlkOH9-sq!wWN&x4lUSHFU1Lg|~%!&7eBm=3t1I4&@Fn_8kYiI}t^1D8? zb+n*#d^YeH0gr-4Y+D`pKvhd^*QSrSCK+^2SvY`w>(+MA{=^e1Q^ zV>l@yrAi-V5~wEM?wKv6(?j#bcOda(sbFrR%|KtLm!N)fyw}5ZW__rBY8X5%_|oD$ zGBG?Pn2{Fray4D@G^|RgC<9nGClkrI+p*argctOvXASLR&3KxXHABD$$vZBR3T?)X ze%!stpNYK@&iGM4pk~XKc><%j*1(qcwCE%09Arf~gW$!lxZ7yw;N*6)ey8)h#m}|Z zUXD&>zt8Bc3^lS?l-ot4X~+ws(AIE!kw`!QV%6>x)S?hJTKl8YlOXM~9NKO)o_M5J z-6rv5Re*oQ8_>_btvfVt8Csh2+}Abo%A#YXu{gATH7cc_uyZ7{Fnc9& z+O@Er$%Qk#0mRWUJiLD8y9nZnpj=G4f6SpV?c$*v4#nk(`b?(tn@+`)mOGI2B^LeC zCTcV8aacrOK8@{!AO(zcWUE|a1O4;I(@4j}DdlDnCH-d<_}j0ha4tho4-&8J{2WW+pJa zIONlh&`fH0XcRAl%!D6|Qyr5DNx(AqF*k&43z)Bv-N9{v*@GM=N)Rz)6Xsgf3KWs5 z{yx^@v+gL04EA@lIkqLd&f~rvbtCd4+16RD+2W-)=dbF{lW-Y4T3s6zvl7yWHoG~) zBL@<}ltVRH*;%UqRsR5u>ZXP7$ZJHQKhBf&?kE6I=G)ubIKSs?H~|{@b>T zr!!wry9e@tVQfYN8zaa_okG7(_h!%ryD1mDeY1inPJ#Zkc%i{>@X$VWYmoXe>F@Gz z5saZkOX5F6V(k%D#c2#Y%ELHBL6Z`*1Qz#rIg;t42=s_Gd$)FDPGLd=Dpk*e~Ga)K`f(Q*qpdMW4= zh8cAURyXR>UX9UD&~Y%>(;qYB&^O|XeDGs@BA08fa?T|430@$ohHr&zg}cYNM_wBJ zJGMfWpH3t(WcZsodP}^%JXA?ss#Dfq{^8B{0*IRE^8`XV3w{d}3$(%n=dfv+>o;>> zbk#dboo3ng>8;5cQoLimb8mE&l*SdurP+0rDjHQQ745o~WEyZ9VEaq^?)&-+^gC)kudLXhqM5`N+g8IMb4;5ODU&#pX<`EtvrznUmOKi}JDcjs1$S!3<%c85km0opT)nOk7UPZSMzaZ|?Rb%^+lAdzF&I##k z_}VC6e*-Op?x#28yqIk5ZXLYo#2z19co(izIF7CNg?*WS**)<{Q)G5xj$@{(_c%(- z`s5vFm5mS&X^jcG@z|J`QDsZv=S+&g&wxX0y*`9b2Qd#Ab;@RpdS$@O8s3Op zkKV<>ySBZZcb!yFjFEbf29PR)mV(ZLwu5j(#)5f*t%JQo3_^B6xfpbzp=!P*vEJ*XQ?rvwfz0BYo6i%VDFv6wF@;q?xPIJtca1UOstCgHG?2U_(#yO6T=> zq%g5kOl(wDr2i9Mj(7HBVG!BF`o+~+ht0b!t^U>MrVrZv0?bncYEQ}tpiHQYRm!%x zeD2fja;b?}pZevnzZLup{h9sEFA-fs`Ls^JEm)>D`CP?Cj9fM-PBP!N6_b%$rgO}hPbw=R}1)g$?#?`~Zx?Exq z(H$RJE<@Uk=z{C#`GepT%@GjG7+_I&kg zPOD*UX9rt*g-OUjcrt}}^81vviqq=Frtgkh(;poK&;<;Z51U%tqrazoVy9`0YJ&bC zZm+DGF8k^#;^gyi#Vqo~T_678Uj0%zB>Sm|#L1^a>*}adVZ9?mgN|iKSQL4^?Aw_R zW{b(W4kRB6YbxueBeP@HAGlYnR}vEO-s|%{H6h~>ixJkidel(QP^W@soYG8#1-)FI zHE8_olsn&VOP74@tpu~!A@>*Q{F;;tVx60y%l=OU_sp2eSJ|I(_Jy*YTI;-YFFJS= zCsqV0{2DLy7h9rtLnd-tx?3F2zrY5H6mn8yAI2V99=e@X;QauTEmBWxeM$bmm6IK+Ku~`5oCG4T%7Sb*Tr$TNj@H@;!q*z||K%G1oH}swm}XNx1mW z&r;3$U0(m(HQ+4Z6u%<8ue!V)Oy%dqecXV(RyZkuR%w6Lz7OE=seJTaFyaV!0h75D z^G&?n*;?N8xH&0<)~y-AVHiucG{~W? zPZ|KGJ^_ro{TST7S_cRkM_Q|h1a=4XmWllg?2$;-g{#o4B0t*OBi96=0kl+ebAXbB z*nxt$fw43aE8$np$hXRc_#05;gV{-|7OVBOG;5U`r4@!r$Hl0Q5JEG9vxsibp(uwD~JV{uB;CL%cmfJf3;Lf1rVZdC33dk=PJ*04a58IXOhC zZsu%ZVeew;;M$Qf`vy^f?)XN>1ppv^`p1JLr^a}Sz&~%L0da*WDGHl8*m0PcJD6H< zc-lGsfd?S!DU8V4S-6_edfM6Ay9j%V(f@o@6+ zup=PYUA*jFO+4A{T^RmC@(-R@7A|JaR*tS#4)(Nv;59LIaB~%-r~jj)f3Ls3)56p0 zKRwyI{5>qh06G7t;pFDv;`|pkqN(T~rNXLKo))$`udM74G(+?u&dbFm`X~JVQS+Z3 z|EneBKP~zB{(mk1tLA^U)O4|MmUgg1bm=PoA9nrS?SEJP-B6VCkCFcuCH^AwpQQ*o zi(`m#{>w9Q47Ku|2Lu}_tzM~UAX0>o{duJzexD(pKT^bl`NGV)$Q+R($-R=&@I*RX zRkA0LBSke9X(R}JKV*6pB*O?2Fy?s4=x$OKPYI~vGLJ~oH*c20QzMQS!*n-$wYivc z6T|-UNfW!8=J524+KiLO%;n`wrnct&jJ;+SQ{p#SYmS`>+v6gOv09FlTK~>TmeKlK z*GEVO7>`ym5P$@101eiKo3`wW%HJAJT*dtXx8u~dnnS~T&nD8KK;=(-ML%^p-7b{L z@H73NMj(5PSG4VE<9+p%b68;lqqX4(OrU>j|1Zc?q)N7FwW#EZAK{{gtjvkmjcgQf2R~cit+ep}qg{IWuadue zOq5IjkkYYSt{)YDUIRNQ9dWJ_cLZ5I6FkOeqXy2cTF?D9^gcWKQ!YSYLi}NBfEk<* zr7@q+)o1rqKw=C0)aqGFGH~0zbi>$fvY{)Ydm1XCdqMIS^Hud|Z2(zff@eb5tM2}8 ziyrurSZKv&v1-kCys!|u{mxRu>DzZU^M6a;3zV;@-4VDnopQLohlLRWQCZT$?xLj5 z@V%MhR3Qpg*ilAdyMx2H?q8CXmm=8+AUzqqy*gSBcndc;xz%$$=O(0_>F|L)3}5C* zx^YfFh!bWl`#9M&{wv&HQMzZNuXrv=@72p-4mdmq$u}aGz|QpQwo$d!aqpInOuf3~ zzl67hE}j?gkakP@P$jeu5%F-NjHJ+?JIlB5vIRC=M&;{(hNWI zd|-ZE9k9Uw^Xm6O*+Sp?ZP?A$P~LWpfdIB2*ZLbcTrvfZC`{b{&hyhTQpH=B739U6 z(+%mArMZr~Pusr@p&4R-DNWEcQ0Juv;q}jNiE?|lk2|DJMoYJ_le*ce+7eZWCincJ}saAgX9M=@AK?-D$B0weyca9_E)sOS)@y= z2l&k;b5S^Q&o$8MEx`Ii{%>q}!^D6N<-%*{ZU5|%|4~B*=m58FL~Z+jK5y;pZVej# zZzB9kw2rP$Q(0thtPT5{lAQ7E0NK4#?R};Fy(QL5;WQo2C6gPB;n6IO{Z50rB-l}< zj>5mAAP0E3yHjID^iA&hHz^;h^o}2YrnvO(kWf)k>G}y$gDH)Ff(q!>{cU7Qp2xoc z$ZjkzFHfq!5s8{?_$Zstw);f7^*WpI4o}dPUi2Ph!OI< z1)64+H?*^}J1XvW-^C*}VMH03zrMac_vzQNi%wLK+P>900y#Lu1p@>{^_ z&lL}cZcqH(1O>zChPIqShc`DIlnahqp)MW@fAcs;vKByEjMrCY_3O&cJ1sBK`&8=~ zJI1hNHe^vAH!)^f^*mawuQNJr&fr~|UqaL6-&$#aumfDm7FU5Xzfq56O4{1nvnIaR zdALR8^x8LxWWP5#0OJIIS+Bo3|G&*_|E<^m`|i&NV!84zq}W!Nat}EWz)-1QUnzIg~1Ne_`;zn)cuO8{HTm;84N8Uw+YP_Vt2@0thGMA#jM_cVen;tPoe>Mj@>bfEh9&>l z5>i^Ie~FPZx$}aG$Y;nKc6^^W_>m%)wff;Bbg7RJgM-FacRCBIi?(B^LSBA#{q@qVRRVBh}SFLV(v7E z7S~2qWVw@1>zs;a#6>w(P=^{F%va{xLffyosHTZ7$_a+;XNr{UM^o78ip}u<MfnhN^GmKf>rWbIXmIKu9J-aqOT<=u|l%+iq>*6tvCnKjaaxk zr&=bH?2Ko^48E16Nmp=c)NS*}@dZ@*z_mvM1fNkyQTUdQ+LvRg9htlffb5hnL7WdF4B`z zbi!c3Y;;eJV+0P>-RBLrgZX>S9j1p=O6~9OQF4{?F~@B@dfHRo6O{u-%M#-Ud;DKG zm=&;Y;|i_^mYT{^1wU}jwmEOhbNmDcjne;vIA{cfAqUtTsqri@8;XTaah>xpiXKQj zUQdGMUQU&k%+!zPx9%j!f_Fw0>flBw5Ml!jEAm723w~K$qhEQ@?jU56;Ti{Y9#*Xi z$1f=f=Ks=yHmDwip#<1R87(PV_BrY3sK7!?oyZnxiZj=kqHE-wts9D8$lK1CWYKz? zqRAXI*r=kkOu8pu-4lWqz__)Oy*_PmeZFmfu`?dTk^b~QiZBbp&;)EoWxlZ4X?5M( zY>CA`d1_JjeiEt@Re4%x#rL+hxRza7_#m&B?I4y#7<^THEgxL{#oq6O$@MK>iUj;P z@###CM3=q+)-ByXXrJLAPLBa+INbqx&5akn7+MhEtvojfs!W+`ER%;J@p07VkT^V} z+Pwl;xkQx~-qXofy@{*&;$w5?*tHL)%Y*SXD?583oR=$|@gJO#DtS{Vw8tR&nu3D< zI*`^Fx~O6l9iS2}tvPN(UK^hGZ1z0Hl*o$gh5XweSA|v-3`bo*KYD8n4s?Zd(mn_t zW(KoF|7!%NpS%eM^eY()lG5Wz&5MY#mxpw5$`SeU%Mua!kT3HTlv8QsX|{z#h4=XL z#Wjy$`adi+IXsfoZwJR=1jqb4vy>^(J_q6nSdxTS7D$eCLv8T(0S*XmDGy!+q_jk`< z4E{T5%`wIEA%P`=#Jqo>X30cL*;d$6c*L%>Bece!enxzZ8x%u3_}9S3OELx(U`YM|_aSt6{Aj5GbC4T?WJ0H2OA^!59VMO_`;$Pl2b+V`(%1fZM0ru_y~ zEgBLcd+xUUwEgzD%`P;QDBul)I9WC<*k54dA3M=5zI13l#8hy{pZs-+bz!W-gQW2Up4SOO+UC zSW6tEHHB2aHEQ-muw=+qL`$@@ugzEJY-v^M68#GQFAF#vD>;o~jIe(CZSF^0^1=$l zA;!^y2rhs@j7fUm-_HnAvqJDm%B5tUE-TMO_8C5$a*_m?2dBBob+;g9RgrY@jgEB| z01`)%d1SXzt~gZ{auhMaqU#BC)um{u*6cM}t}a-_P0WkW;%`K+Q)_-FUbOA&h`D;c0R(| zF=t}pb}H$%<_BwW+8PX_s!FuL^FpjWoFgcmBM)YZ<2Y~BR@wU3gHbS7mL0l|cE-}B zhb~GbiA;p3X(xnsDNim4-EZOTqL;JkGWP&F5%-o5T=7Mzv_$|XH9T{v)vcVp>e6)@ zg%&W9B!u>z9k(`L;^tTWo*H32faxW`yRXad_Nt>CG4537ONYuwyHntkSL-BQ@US-c zX0(~X*-5~oj|XhkW2h|ir+m$HmiK;XXCc#!PmslK7>VI&g-CiMd;JYI1-~naq336T z_m&7Z@G(L}%AG&dSV}$Xb7pJf6uPE%$0p}HCEnQM&#H0#78lcZcW8=%7oct6mfRPw zr_r5Mh?p^&p2^VaH-04L?lp0G3F$VSC{-`^u2SfYU69o=zzb|8_e3ZUt{;3OVgchd zxRVPX+&{mM5Q7x{l1mP|0ZEi)W%huQFIEOf`dz)(7oXwh$=GqotF!j*)v`+ZO-17d z7pNcGF@MyXwdvh#Gr_K}dcDg^4dg|Aln9y+zmBd<20UEI7Y`6L*%JIr{^lN~nKK-4 zk-654M&nzfmf89#hzOWM4(_fAq@nk18urhCn#?+^b^49aa70o=-0S@zxnYK=#3Tl; z2_EUBo1Mq42X_i79rLUBuPHa|xVK4U8EGF31Mb~UlFga$n;_d$i15LC`4kDR{#gtV z_#OBab`3}z!JQ2D4AM2;r%#_g+;5&>eZj+tevy=ta`h(~cs;`JaIyAbkM<5R z5Dr*BfePH>O_m4L>v+kgx{?v3BD9?pe8W@a+A}xS{SsNha!M@(ZKMh6J=$jEiywjF z`!-~^#-y(df^G7{3&AvkwrAKWVv(lUsYd=S@3%z`D(bS}`g5BxrjcE{xdBj$rO^_T zUKX>7T%xhXl zI=mfz)mZc5p?R8)h}8ph5T}Na-)g$wq~2j5iYUi|EcT-f&rfwg@4o-dUh(bPQ#ejf z|I)4E<2ncR;=PJ=a-@iWTP1C1Yxqz7_cHBP#baqskViZV=j z-0dbv0vk^D4MSE+v#;EIh9kt4;kx4OoJoG0$2xq>4q817%Wm!rOGignt932Qo$5@# zM;rWFy%H(9_SFS^wlV}$$=Z}pb}CoCQMI+$)B6VHS>21hTDNyUsnC16tH+f4)iXC& z_Hw_CKscp2XL_x3T(`M{Lcp!O(Qhwh#4P|m;0>EDE5tb@S{=?rE$fQ-r$oG2I6tyh1U2l)tC+8Fe_V7f$FfHdr4@)gMx+=_Jj;=4;bzT zDI7c{Y@M!Amlxcrdyi`Ra1?|j2y@H*V5u5KX`#_iX#e z9&}s=xn(F}07=?fakR*x*blzW2-*kQ-*~iGbihlWq4@?YUuX@al1bwAHDTZvi{HO;9uJzwrfAWFN%T|An9!UkVU$07OHn!w*y-3Rs>Ez6vdlDFKG~`|;q7;`YrWl5 zjkChS+#kPq-&$&;ZHd1{3Ob!-SMhxyImmsyd+#yI-t?-meXl69y~VgB)PxO81;I|F zrZul>-i$)nhjiR6otKvp&t`o3aY@*U@x;kb2?HMQVa)b|g3kbF4AC^M*fsA=3~BuX z^TXZfQj~@F3gi{1(89l+aj7LrK)sKC3_jkkJyNmQ)*T6yiSTb8LGL>DQju6x(MFAo25 zPVzddaX=Ss^CBT6kYrtYVgs(>Y%p?yWHZCP{)JsPXKnry^+CYdd=###)v;?h%wl3NUj zff0Wse=$PwRmKi$*)$0U_j8RT-8vmmx}`K+kee~;vl&DIpQ|?_LIqOonnOFnq?GZK zs`|sDMxIpKwy_HK?nv$9 zDi0sX6lhg1x)-@}vk~c!qw!3lm~Zr*W;t(;XrY*Ih6z-;7-Ssp;aIOi8~7H!8iX#N zIduL^L8*upyRzLI#F3bIkzitmZFC49QGztAR4%p-G&rpEQ?1q7Jbl~ts{iVP`lDv$ z!MqEhx^FK!v#0bY?@2t#y$UaQZ+PJM(|nry;SGi7-;iLHn3Rq*HFqD$c$sX@iWN^$ ze7-$R)%fZy1<;MvZBpI(EFIw;%}4_otCEP6qLw49Dj%GG_ckMNXS z&0H%Z=84TEZY(ST4RB4&{#u51eBa}5ar0kdPQWRWfZlvVJ9J4%ry;phu*9LyLBvVR zka%SdO)qBamAk+d)qY0@DUP_+epk5|^XZ_t^eW2Jw%rt8diuRP4!PN?^*iviRF;qI6t% zosh%LQGBycHI_of zvSr!6jr~16ul7QES?>DFaE;3x{~N1G6VI}(9_L6&wrAg4LTl(vEqMuSOlu~W#>@;E zD+&?BGM%vHr(3wUaAE(^D0V)~a1SHvtH1JL%#9qIsoRleIeW!< z(+~miw8R=7tn_P$yMyrpJ%&}c$#~2^07lpBFh_HvI)bh5Jfu%PcRoF($z?y=*|As} z&+*}NJfU<8w}F_fF2m>CJ+dIf^P$bzd&}(&J|^(9hpdjt&XrBsj_b)7bz;v?+n-8svG{^rS7d4xg4IM1X1^Tw;U4`buun==-qjt z{p=#JZ~}*~ywJbM-8&8=%C$^!0wG~Y?gpo@-x+U3h|tgp#@xWS;&zWdh|?_KvNGwr zy+S!8M#fK*>yDem4S-8`q3;NqG?+b+u0qkPx-C~Kf&hZ;hVa%48In;b`&vA?x-F4` zvQKNysrv>^kcfR(8^E|)FNB`Tgb|<^FN&x)V09g(qb{RbjoY-&qtNPiv^y`#LGd_t z?y{3YOonB7eqXEL%Q;eon9OcLy`P(raHna*Ma&Wum=$TroC`!utCb&aU4mDJUh`1K z0Jyy}*$$^_)OIkS9DU2=#y>K-_g%eQM%Jh+Chm8B&lbTIS}$F+VH^T>`Ef?$J_}+E zzO@Sm)$b}rZOX3hSH8xre!S=lNW!(6=@IjqvaH;td{4C?@qa>6TQt6BxE?+9KIFBv2 zS+0pUGruVB3SldoWedY3A|R($!HU@L!dHdIUI}#TYKi@U9J8PZV6KV>#7g zN`f;G+q+exag1{HRw8$6?ohbT)sOcbM^K*Y3QCtT?wrV%6l2@HIBbw>$RQGOmQ7X5 z;7}k@40|OgqhglNSkVTupH7fmn2!5nc+C0hA zESW7>7n3h=UJ42Pet`65OVuo= zv1joBTaP9T?yp<%5Mq#U*RWA@YIQ62_L+XC&@uTQGTDVxEmK`{uwt$vuovlYQ|ze` z{?(zHbVQjMz;l{E$mL2($`AFp* zD$)N@xNpr<+_glo!ml=!s14Ac+Jq=4RowomBf;8ZvZAQnZEA`6MWv7C;S}v;Nd97g zvTwf_^O-YuKovhk7M;JsbinroYK~G~up&y#mD@S!y6CuW|G3yJCC8I%#IHlbUCY z_VC-!LoLWG#CmK5Xk++%NCM);t}|MwZdq_9&NQ#Lui1Ay285csPrD&?BGAH7b=PzOd|t*v17sUyf#s~!IyTO zMbtdrT<-kyS;1d9D7ZQ`D=51YOPcL?HgFNOvtk%3J~*Kyr8^9h#5`SegFp1_*x1^OKa_*2G9Hx1h7;3Yu3_v55k=r8XJaNIVRT&TW;s#Z-VS3exSXMsa?ku0?{(InK}WB^NZEeLU3M9)lp zf6&7dc{I@r?5uXBXwfK-g?-c1IFNI@^zZfHw{N-VXh9aA*tyN?ekowdgfqY~}{W0*#M z2ij$C^lR57VO!L^@#fZQFfnVSsaZSF9?#0cY_8yn&o|BZUgKqilHU??@KY4K63CL@OS8n*6D7v0h7#04QB6VTrj(B1cdr0g&zpNQiQYrE32qZlkkYsav|H;dqHNuxj zp}{n=GlAi?w;lH#@NLpa9C1H28wm?3Gs2M&-D92IopvohyW z+!j!E*Y7}+ObRW^_c_eVzhl|!H|xR}npvV9Tgc6REV+F5O@GkF!bIK;#xn0uno1C8 zwnk#K(jf>YC_eey6Q%Le73#HnO)z)a{k%A96LFkZ{G)$BAN1sry)RA`Aq!#)RM$&p z9aWsb=Z$p6qR&uCf+YKmMe1CmCAoM$({mlu<3Zvz*)|f#WoXYHJD&r#>dMr32?V%K z9p3k~i?#-lO@Gby*_m3+l+!cvxgzJQgazGkTnpGvFD{nH*1iF?#db%~0C%>i&IU1` zlLW7HzpRc*UihAa;RLllo6ZuzWs4DoFWj!lEba~}^0JQA_w%pQ%y$I!c5zi5db zggt&8<#2(d86$U!YOY8zE+zYxM4%Ahio^5PKo_2~-f2md8>fY@1-Ksc(s+>2=KpY( zHZ22*M@4^6Nqhb1ooEdzortA>?vRIyyJ&?nAdZH&xRHTU?sC+Ng{bPmH6K%d3m60u zX^jg9Km5pQGIv;3=_|9)f7c|W@cu340i{2vII_-y>fHbr7x5=WLp`m+o9o-(?p=pX zK493YTluQcNvBq!p$6iFj&etNv8~)_8%8tf0`ucrm_)4o!81d+)JGY^_RQc#UeC}- zB{tA^JbjH|l;;vDD9_2p1?MJ6+n^mjZ2^zBYt-oQh2G$}LWR^D^P1vnBI$>qc#!Wx zk9^5zy(W8mgr_m5izCUzjazGv$h!pl_0($`zxt4ELb~U%d9~#Dn~KQ9ql;)@_DLoV z)Ynr3?~rCEk*TCuKlFY~WCBR3?cUGrQ%dT;`D~98VIxxRg6m=PxT@EDgm>`caV&Gs z&`@W;6Mn<0Lh0|3n{)jB7F*Iy0<=rfdo+SoM=%^!skf4#{;;?3`b*mw-Q9u-^G%yi z-r{Yv42>@Y&`5+*Zy_rC}&Z_ovc{C{!Vp`IZwiEHmxewG47I>Y9vz6#0 ziSz)c%DEpHIDNX1Qc~CVK@VSEsx<}oxk&>M=l}C<9nBmrUm&2kAC>Fttj7o!T#UXP zm95b;FI|9o7Xf58uiT}DNZ(qy!HF>(Fl*8j3_-z?)q-2K4z70p%5;l>_lV^8;u^M! zc{C_#*pf)R5=x8RM;=-iIjM$Pue&FgnMT4`Yr(zOE?mYXm#w%XRFVg^uCaLy&=+E8 zEi|qt#X1s$y&uJX>Q-#MqtySpjp-33*c(nGiCv>VA$C(C-`C~MX5fo(@z{?|U+ zZ956(irpU8-TUMUI(=adtjO0GTPEYTO%u?lps48E1`>xGLpUHLK0jiq#g#1a1Ss!7IqOnPsgbkA#ZZ$Z_fn@mon$uV;$NjwPvx(B0VAVWt!#fe*PhT^bV`T-Pr17GPWjmb?oXJNw@-B zKMeJljxNPxDAMw9C8>P#0a(bu{h>8<1>;SQPlv}{3d zv=3W6P44;E@<02S|4QgoNUIUny_$|vbAaJOFzg~(0iXLF_FeKsnhH|ws51=ft@7K9 zcU~dR7b=ru`#wcp2h5=p29}m?U=m$i7-P={einuF)O4sZA+aLfCh`mu4fHt$!Cf_az(Ejt20_Dz(>=7u~tI2FI=8RLU zOrlHe4xdKp629njmed#~;pltd%a~y`_s||V2`DiP> z`g=^)b?X3|z_|Dht063^J=~z)Ctv}V`PMM+(w7YB1Rf2LDUL&}2jqLVfvk?_bS_tF zmkDK*7e^dyds`eUO>s7?KA338cc&lmn^-14oOET=@g8krNFKp{#;*2r8V4+tdnvG= zZz+j%o`;9iBp9gkA1@xK+HA)2Q>`ST9W}C8d1b-1FQ9HJ>_(Ku)^F!~{4>+47driX z9v^Dyab0eV-99)Q$DrZ?7sat%Q0Jbrj%ZgK9*wh(X}tMjXNMLE!IHA>P8Dk08kw`! zj#Ly3n5V(aVy3zH_50?62zIiz)`ojT<08jRQ{PIDw|TM|&FLQbLiGOkOirQBgSb1O~)+BK_xJcaKnaB)7$dAs|~ zNqHf!51H1i{%96bXj2<#?S`0)&$b2)VF=vm0Yl!+%Y7Y?eIOG&0>G{{eqvEF4FG< zw?l0L7ECI}j|C@_#8x-={HETnh#Q&(9oK8IH~nryXXG#9e(YS>Yu2-aZRLUf?YDjf zO)elOjm|OH<(?K~Lf4On5pMZAf=apv{%HUYCp^AZs0}-f0vr_b@i3A6kdki&xzm;g z;kB&jlBRv&XkG_CDp2|jxe0%44I{1zM8VYITMt6YC9CipXIw|yy}Ou%vA?v$9K~*I zm3JO)FdlUaX!zznTk;lKW~`4)DJ^|yssUsT!>5fp1E6bjn;-$nacl5K!4Rbwp)tHU z;U+wbL?)&4*oFRpKbLwwSBHC@XCECjT0fp`4YdtKyV$IEduZf}`-w8IB4Ud|070tX z>5M;Ww$I++aRo~1{Zgn5G-SK5BzG~ZkT*MOiRj%gB6Szbht+F} zh1bq+=?qlu5iCegl`z(tsk>!eSEkj5b6H_BMo*Rj;Bk8+OuKx#1ZgU z#9<&JwA`3;JWZc6mE^MaP`Mpv2);wn<3P-9e)%Ex7S@~o&R($y_qvd%uPlnFYByTm zB|+p3sa>~t7imx=h9`KyAwVh27emrlz~<3g0uCf`_V={%bGT}I%$qa=WT5%y!fFjfXIOAIq5ip*6v13s7t7q4-Hxxb0eYCdv z^&^Mt3$Ll%P5oaB~f8E}Hk`+lNc zMyJ>$D2zx`yxK^9EsY}S84{T8SG1&;JcRMxwlW;po9bbQhSiX>Y9ZmFIOL}vC#Dx_ zh!g&Sxk7&@$KWA>HF*Pq`lLh-2)V{wV8>&F_tt(HFhqOEIYkMjp@$RJvj*FBFc#AR zxhZYggPLjBmf|CSW<8noNz1-`|~*`nWz7C;`%i}u_zM{V7NKK6a4Wk{x9D=NxxwZ(EmNoVSeQTkOm zLaAc-Jh^&Rx!88dRZ;OHxlH>b<#8?W!bxw!T&yBVu$aw$kaKfWavbu1>QuRgxtxTT))4Yf*#7}a{KNBF%6r=2BHHmmgK|7eyV#qODq3`Da- zh)W}<5nv>B+UC4RkkDu`jGryc#4xg(7@ORy2_D847zpn>qf&q0d!RBnBeTRE)3?VK(Um-DSpDxD3Xh zB(&~{^#TF?QM-9BI62Vn*3B#lfHhTUcd8phcmxX@gp~7S4UB!x6$E!ZKH1fWhE^VR-fR)2Z;{{2l z`#KN0ad1Cxy*z_}) zV5o1kxqx^TnsBQn42jIEia;JDA0-C#w31j{97NQV4<7}DI`mG4XwSz~YV z@FZNR#v2C-ii(I-xQMSguY>qkbY$)ZB!IY>@lt*&Ru0$P_?`BkeaO8aL1!M2io|!E zd5%&;ZFX!%HzB0l3h0N4t_0bo(qdZH7(kLPaeJq9?`(w!+H_hLFCf z&|#(RCX4~}50t=X7D<9OXLg3pL;VGI(tj>l(JPPr6I{)xx&r#d0pNa5QvT5bv_*cF z3-3C&FZ(rv2>H>y*j!#oLH~pt2ZEes5TRCh zm<{V^bX<3sZ%LT`kV&jiek#n;s3u6V(jO5_TJRtB%)o)YUlRS-zXY1e7gf!_PT2dT$>=u3Q`P7W7_NAc*ncRk_x55CuR z|8QUT^|?QX>;3tB-k1dqR0HmK4Tz=80vjcNAYny3Rh9R z{pk+VyUBUQC;lo?E`hw`OZ&TzL00=up1}gGRgEFJPRm z7q`r<43_K!0-*OmTC)noQsa1Skhxcj-z7#|B-_Vj1_b@oLo0_sZcT_Q_XBCr!l-*n z=)`h9EmQ9(H0VO(%ib-DaR@sZwfE0O%!NSi@|-Rt%5U8ZkzzLMlX|HS1B4CPDFC=V zC?cK|4k>)v6?-ye*x=T4e(ksefdApLj;6P?xCqk2gW>-bFyzf6c<7y~Oj-OY!8rw2 zZGy1pSwf!dI*di+`6HHRCI;3-odk~%@5&ehXI*59k=lAw=M~*6wQnAT&)s6Q-9Gjh zyUF_OBs7xUE6*i1p8psTVIB_a|79l-r%<@SHEI5XhQ3V}UQ6}#zQg9=8PaxI@phwI zOWAk7i@SH>=2O039E?Dp+PE7@^IH(rWoApWU$Kt?^9xUbL}ackVyq$44xRj4i^J3D zGCQMONZZ|tw?HqqoxGmn45NH$K`O8kXvfH#n*h^cC#Z5q^dT(1mHetRSMRH?^3GM^ zXN$ZlIQI&kT++0%oJtt;ZOD_9wK|U!LhKW4__XS*jGvY2b$@Iu^(YDI#uZ21BfJLl zakMKLQhZ6kOzQ4wiBK~T-ZFMonccX;_&Q>*5VhrrW0Tqw6V|$K8hbN(9uqhv2rCT| zWJ|9Jnt|bodqYv(@eioz;GPL=Nzu3)V7||C`oUo0!cD_TE%yE3nXG1(xbi3~Qo;dh z(pJLUE9KCy%p#?3!6CJV*l@WU))!EtwOO`5;E78qP$LhmP%tylg`6(S@}0sa9boT0 z=hyw}w1lD^A`ktP+R%aZ29!DrSe)D~6*l(Vxu9o(f&c3HQyrJ*gvL8akR>ag-7F?%_F< z*a`{wgZIgs{{Y;k>RW1Watg zs3oYnFoxmzpA~OA{dpcuh|O(d}4&V00v_t<+@QxbM3}Do_9DbiL^jCO^PXf3xSg zUHwtl9WZ;dc%P#dpCBqWo$)SxG{i97j2^jaqxEWJ>s({LyU_H_)z$9TZu5iX@j;WL zZ`w~w?4Q+dErQ1ita;~uh8)(1y$Oa=0u9NZR)nCgkdW?zhs0iFuanvevzEzn=bm8;~_@Q*b!r_joLsVzt{MD@0a+S1z)1`Rv z2qRO0yov1VFzK~ZiRd51fOT}OI0PA7;?=VXT$n`7v)kC)DEzD4`a{1gF2P=E$7&Cp zvAJmnJQV$0J5H$v7iE-1i*qL$Q?Ks#?Y~4@o(Rn0ZJczk14nj$n8K9#d>O`F9u<~^ zWwJiWueA3~1~kTeK&ediP>#J@PSt~bgZBBW=9M2@werh-b`JhdPV5J+4Y1OF42!tM zzJTn@W4rq{ZCKDUKDc~*bFq+7(VaucX;!mWW52rZtr%nfkDG58=Fhc`Jpq>Sp+1_R PO$ymhK4f2I=a=#~TKW2E literal 0 HcmV?d00001 diff --git a/apps/desktop-gpui/docs/recording-controls-starting.png b/apps/desktop-gpui/docs/recording-controls-starting.png new file mode 100644 index 0000000000000000000000000000000000000000..24274d919d7f75badfe50bb73146c20e90fed69d GIT binary patch literal 21241 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 Wqy%)s-!O6WM;;DH7u!1PAkyFF-?MH2 literal 0 HcmV?d00001 diff --git a/apps/desktop-gpui/src/app_windows.rs b/apps/desktop-gpui/src/app_windows.rs new file mode 100644 index 0000000000..89869e08aa --- /dev/null +++ b/apps/desktop-gpui/src/app_windows.rs @@ -0,0 +1,190 @@ +//! Multi-window orchestration -- the `windows.rs` of the gpui app. +//! +//! The registry lives in a global; the recording flow is driven by observing +//! the [`RecordingSession`]: the caller opens the bar and hides the main +//! window *before* starting the engine (the bar's window number has to exist +//! to be excluded from capture, and the real app shows the bar in its +//! "Starting" state from t=0), and the observer closes the bar and reshows the +//! main window whenever the session comes back to rest. + +use gpui::{ + App, AppContext as _, Bounds, Entity, Global, WindowBounds, WindowHandle, WindowKind, + WindowOptions, point, px, size, +}; + +use crate::{ + controls_window::ControlsWindow, + main_window::MainWindow, + platform, + recording::StartConfig, + session::{Phase, RecordingSession}, +}; + +/// Matches the Tauri `InProgressRecording` window and +/// `fake_window.rs` position math. +pub const CONTROLS_WIDTH: f32 = 320.; +pub const CONTROLS_HEIGHT: f32 = 150.; +const CONTROLS_BOTTOM_OFFSET: f64 = 120.; +const TARGET_CONTROLS_OFFSET_Y: f64 = 48.; + +pub struct AppWindows { + pub main: WindowHandle, + pub controls: Option>, +} + +impl Global for AppWindows {} + +/// Install the registry and wire the session observer that tears the bar down +/// when a recording ends (stop, delete, or a failed start). +pub fn init(main: WindowHandle, session: Entity, cx: &mut App) { + cx.set_global(AppWindows { + main, + controls: None, + }); + + let mut last_phase = Phase::Idle; + cx.observe(&session, move |session, cx| { + let phase = session.read(cx).phase; + if phase == Phase::Idle && last_phase != Phase::Idle { + close_controls(&session, cx); + let main = cx.global::().main; + main.update(cx, |_, window, _| platform::show_window(window)) + .ok(); + } + last_phase = phase; + }) + .detach(); +} + +/// The whole start flow: bar up (in its Starting state), main window hidden, +/// engine started with the bar excluded from capture. +pub fn begin_recording(config: StartConfig, cx: &mut App) { + let session = RecordingSession::global(cx); + if session.read(cx).phase != Phase::Idle { + return; + } + + let excluded = open_controls(&config, session.clone(), cx); + let bar_open = cx.global::().controls.is_some(); + session.update(cx, |session, cx| { + session.set_controls_open(bar_open, cx); + }); + if bar_open { + let main = cx.global::().main; + main.update(cx, |_, window, _| platform::hide_window(window)) + .ok(); + } + + let config = StartConfig { + excluded_windows: excluded.into_iter().collect(), + ..config + }; + session.update(cx, |session, cx| session.start(config, cx)); +} + +/// Where the bar goes, from `fake_window.rs`: centered under a window target +/// (48px up from its bottom edge), bottom-center of the recorded display +/// otherwise (120px up). +fn controls_origin(config: &StartConfig) -> (f64, f64) { + use cap_recording::sources::screen_capture::ScreenCaptureTarget; + + let width = CONTROLS_WIDTH as f64; + let height = CONTROLS_HEIGHT as f64; + + if let ScreenCaptureTarget::Window { id } = &config.target + && let Some(window) = scap_targets::Window::from_id(id) + && let Some(bounds) = window.raw_handle().logical_bounds() + { + let x = bounds.position().x() + (bounds.size().width() - width) / 2.; + let y = bounds.position().y() + bounds.size().height() - height - TARGET_CONTROLS_OFFSET_Y; + return (x, y); + } + + let display = match &config.target { + ScreenCaptureTarget::Display { id } => scap_targets::Display::from_id(id), + _ => scap_targets::Display::get_containing_cursor(), + } + .unwrap_or_else(scap_targets::Display::primary); + + match display.raw_handle().logical_bounds() { + Some(bounds) => ( + bounds.position().x() + (bounds.size().width() - width) / 2., + bounds.position().y() + bounds.size().height() - height - CONTROLS_BOTTOM_OFFSET, + ), + None => (0., 0.), + } +} + +/// Open the bar. Returns the native window number for capture exclusion. +fn open_controls( + config: &StartConfig, + session: Entity, + cx: &mut App, +) -> Option { + let (x, y) = controls_origin(config); + let has_microphone = config.microphone.is_some(); + + let handle = cx.open_window( + WindowOptions { + window_bounds: Some(WindowBounds::Windowed(Bounds { + origin: point(px(x as f32), px(y as f32)), + size: size(px(CONTROLS_WIDTH), px(CONTROLS_HEIGHT)), + })), + // No titlebar at all: with one, the panel still draws standard + // window buttons floating in the transparent top of the window. + titlebar: None, + // Non-activating panel: clickable without pulling focus from the + // app being recorded -- the tauri_nspanel behavior. + kind: WindowKind::PopUp, + focus: false, + show: true, + is_resizable: false, + is_minimizable: false, + window_background: gpui::WindowBackgroundAppearance::Transparent, + ..Default::default() + }, + |window, cx| cx.new(|cx| ControlsWindow::new(session, has_microphone, window, cx)), + ); + + match handle { + Ok(handle) => { + cx.global_mut::().controls = Some(handle); + // Panel treatment AFTER open_window returns: inside the builder + // closure the platform window is not finished and gpui's own + // PopUp setup would override the level. + let number = handle + .update(cx, |_, window, _| { + platform::apply_panel_behavior( + window, + platform::PanelBehavior { + level: platform::recording_controls_level(), + join_all_spaces: true, + // `.shadow(false)` in the Tauri builder: the bar + // draws its own shadow-free card. + shadow: false, + }, + ); + // `order_front_regardless` in the Tauri flow: front without + // taking key status from the app being recorded. + platform::show_window_without_focus(window); + platform::window_number(window) + }) + .ok() + .flatten()?; + number.to_string().parse().ok() + } + Err(error) => { + tracing::error!("recording controls window failed to open: {error:#}"); + None + } + } +} + +fn close_controls(session: &Entity, cx: &mut App) { + if let Some(handle) = cx.global_mut::().controls.take() { + handle + .update(cx, |_, window, _| window.remove_window()) + .ok(); + } + session.update(cx, |session, cx| session.set_controls_open(false, cx)); +} diff --git a/apps/desktop-gpui/src/assets.rs b/apps/desktop-gpui/src/assets.rs index 90e960a028..5356984322 100644 --- a/apps/desktop-gpui/src/assets.rs +++ b/apps/desktop-gpui/src/assets.rs @@ -49,8 +49,12 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": "logo-full.svg", "microphone.svg", "minimize.svg", + "more-vertical.svg", "move-left.svg", + "pause-circle.svg", "play-circle.svg", + "restart.svg", + "trash.svg", "scan-text.svg", "screen.svg", "screenshot.svg", @@ -103,12 +107,20 @@ impl Assets { mod tests { use super::*; - /// Every icon `main_window` asks for must be in the table. gpui draws + /// Every source file that draws icons; both directions of the asset checks + /// scan the same list. + const ICON_SOURCES: &[&str] = &[ + include_str!("main_window.rs"), + include_str!("controls_window.rs"), + ]; + + /// Every icon a window asks for must be in the table. gpui draws /// nothing at all for a path it cannot resolve, and does not log, so a typo /// here is otherwise only visible by looking at the window. #[test] fn every_referenced_icon_is_embedded() { - let source = include_str!("main_window.rs"); + let source = ICON_SOURCES.concat(); + let source = source.as_str(); let referenced: Vec<&str> = source .match_indices("\"icons/") @@ -140,7 +152,8 @@ mod tests { /// refactor is dead weight in the binary, and nothing else would notice. #[test] fn every_embedded_icon_is_referenced() { - let source = include_str!("main_window.rs"); + let source = ICON_SOURCES.concat(); + let source = source.as_str(); let unused: Vec<&str> = ICONS .iter() diff --git a/apps/desktop-gpui/src/controls_window.rs b/apps/desktop-gpui/src/controls_window.rs new file mode 100644 index 0000000000..b4a589f56d --- /dev/null +++ b/apps/desktop-gpui/src/controls_window.rs @@ -0,0 +1,304 @@ +//! The in-progress recording bar -- `in-progress-recording.tsx`, natively. +//! +//! A 320x150 transparent panel whose visible content is a 40px card hugging the +//! bottom: stop button with the elapsed timer on the left, mic indicator and +//! action buttons on the right, a drag handle on the trailing edge. The window +//! itself is a non-activating panel at the maximum window level (see +//! `app_windows`), so the buttons work without pulling focus from whatever is +//! being recorded. + +use std::time::Duration; + +use gpui::{ + Context, Entity, InteractiveElement as _, IntoElement, MouseButton, ParentElement as _, Render, + SharedString, StatefulInteractiveElement as _, Styled, Window, div, + prelude::FluentBuilder as _, px, svg, +}; + +use crate::{ + session::{Phase, RecordingSession}, + theme::{Appearance, Theme}, +}; + +pub struct ControlsWindow { + session: Entity, + theme: Theme, + has_microphone: bool, + /// Repaints the timer. An inactive window is repainted lazily by the + /// platform, so the tick both notifies and asks for a frame explicitly. + _tick: gpui::Task<()>, +} + +impl ControlsWindow { + pub fn new( + session: Entity, + has_microphone: bool, + window: &mut Window, + cx: &mut Context, + ) -> Self { + let theme = Theme::new(Appearance::from_window(window.appearance())); + cx.observe(&session, |_, _, cx| cx.notify()).detach(); + + // 4 ticks a second keeps the seconds display honest without burning a + // frame budget; `refresh` (not just `notify`) because this window is + // usually not the active one while a recording runs. + let tick = cx.spawn_in(window, async move |this, cx| { + loop { + cx.background_executor() + .timer(Duration::from_millis(250)) + .await; + if this + .update_in(cx, |_, window, cx| { + cx.notify(); + window.refresh(); + }) + .is_err() + { + return; + } + } + }); + + Self { + session, + theme, + has_microphone, + _tick: tick, + } + } + + /// `formatTime`: `M:SS`, hours folded into minutes the same way. + fn format_elapsed(elapsed: Duration) -> String { + let total = elapsed.as_secs(); + format!("{}:{:02}", total / 60, total % 60) + } + + fn action_button( + &self, + id: &'static str, + icon: &'static str, + disabled: bool, + ) -> gpui::Stateful { + let theme = self.theme; + div() + .id(id) + .size(px(32.)) + .flex() + .items_center() + .justify_center() + .rounded(px(8.)) + .when(!disabled, |this| { + this.hover(|style| style.bg(Theme::with_alpha(theme.gray_12, 0.06))) + .active(|style| style.bg(Theme::with_alpha(theme.gray_12, 0.10))) + }) + .child( + svg() + .path(icon) + .size(px(20.)) + .text_color(if disabled { + Theme::with_alpha(theme.gray_11, 0.5) + } else { + theme.gray_11.into() + }), + ) + } + + fn render_stop(&self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let session = self.session.read(cx); + let starting = session.phase == Phase::Starting; + let stopping = session.phase == Phase::Stopping; + let label: SharedString = if starting { + "Starting".into() + } else { + Self::format_elapsed(session.elapsed()).into() + }; + + div() + .id("stop") + .flex() + .flex_row() + .items_center() + .gap(px(4.)) + .rounded(px(8.)) + .py(px(4.)) + .px(px(8.)) + .text_color(theme.red_300) + .when(!starting && !stopping, |this| { + this.hover(|style| style.bg(Theme::with_alpha(theme.red_300, 0.08))) + .active(|style| style.bg(Theme::with_alpha(theme.red_300, 0.12))) + .on_click(cx.listener(|this, _, _, cx| { + this.session.update(cx, |session, cx| session.stop(cx)); + })) + }) + .when(stopping, |this| this.opacity(0.6)) + .child( + svg() + .path("icons/stop-circle.svg") + .size(px(16.)) + .text_color(theme.red_300), + ) + .child( + div() + .text_size(px(14.)) + .font_weight(gpui::FontWeight::MEDIUM) + .child(label), + ) + } + + /// The mic presence indicator: icon plus the level track underneath. The + /// track is static for now -- the live level channel comes with the + /// app-scoped feeds. + fn render_microphone(&self) -> impl IntoElement { + let theme = self.theme; + div() + .size(px(32.)) + .relative() + .flex() + .items_center() + .justify_center() + .child( + svg() + .path("icons/microphone.svg") + .size(px(20.)) + .text_color(if self.has_microphone { + theme.gray_12.into() + } else { + // `IconLucideMicOff text-gray-7` in the bar; same + // glyph dimmed until a mic-off asset exists. + Theme::with_alpha(theme.gray_12, 0.35) + }), + ) + .when(self.has_microphone, |this| { + this.child( + div() + .absolute() + .bottom(px(4.)) + .left(px(4.)) + .right(px(4.)) + .h(px(2.)) + .rounded_full() + .bg(theme.gray_10), + ) + }) + } + + fn render_bar(&mut self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let session = self.session.read(cx); + let paused = session.is_paused(); + let busy = !matches!(session.phase, Phase::Recording { .. }); + + div() + .h(px(40.)) + .w_full() + .flex() + .flex_row() + .items_stretch() + .overflow_hidden() + .rounded(px(16.)) + .bg(theme.gray_1) + .border_1() + .border_color(theme.gray_5) + .shadow(vec![gpui::BoxShadow { + color: gpui::hsla(0., 0., 0., 0.1), + offset: gpui::point(px(0.), px(1.)), + blur_radius: px(3.), + spread_radius: px(0.), + inset: false, + }]) + .child( + div() + .flex() + .flex_1() + .flex_row() + .items_center() + .justify_between() + .p(px(4.)) + .child(self.render_stop(cx)) + .child( + div() + .flex() + .flex_row() + .items_center() + .gap(px(4.)) + .child(self.render_microphone()) + .child( + self.action_button( + "pause", + if paused { + "icons/play-circle.svg" + } else { + "icons/pause-circle.svg" + }, + busy, + ) + .when(!busy, |this| { + this.on_click(cx.listener(|this, _, _, cx| { + this.session + .update(cx, |session, cx| session.toggle_pause(cx)); + })) + }), + ) + .child(self.action_button("restart", "icons/restart.svg", busy).when( + !busy, + |this| { + this.on_click(cx.listener(|this, _, _, cx| { + this.session + .update(cx, |session, cx| session.restart(cx)); + })) + }, + )) + .child(self.action_button("delete", "icons/trash.svg", busy).when( + !busy, + |this| { + this.on_click(cx.listener(|this, _, _, cx| { + this.session.update(cx, |session, cx| session.delete(cx)); + })) + }, + )) + // TODO: the recording settings popover menu. + .child(self.action_button("settings", "icons/settings.svg", true)), + ), + ) + .child( + div() + .id("drag") + .flex() + .items_center() + .justify_center() + .border_l_1() + .border_color(theme.gray_5) + .p(px(4.)) + .hover(|style| style.bg(Theme::with_alpha(theme.gray_12, 0.04))) + .on_mouse_down(MouseButton::Left, |_, window, _| { + window.start_window_move(); + }) + .child( + svg() + .path("icons/more-vertical.svg") + .size(px(16.)) + .text_color(theme.gray_10), + ), + ) + } +} + +impl Render for ControlsWindow { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let appearance = Appearance::from_window(window.appearance()); + if appearance != self.theme.appearance { + self.theme = Theme::new(appearance); + } + + div() + .size_full() + .flex() + .flex_col() + .justify_end() + .px(px(12.)) + .pb(px(12.)) + .font_family("Geist") + .child(self.render_bar(cx)) + } +} diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index 45de4cf367..93508545f7 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -3,15 +3,19 @@ //! Milestone 1 is the main recording window (compact + expanded) with real //! device enumeration. No tauri, no webview: the whole UI is gpui. +mod app_windows; mod assets; +mod controls_window; mod devices; mod main_window; +mod platform; mod recording; +mod session; mod theme; use gpui::{App, AppContext as _, Bounds, WindowBounds, WindowOptions, px, size}; -use crate::{assets::Assets, main_window::MainWindow}; +use crate::{assets::Assets, main_window::MainWindow, session::RecordingSession}; /// Matches the Tauri main window exactly (`CapWindowId::Main`). const MAIN_WINDOW_WIDTH: f32 = 330.; @@ -48,6 +52,8 @@ fn main() { // 330x395 is what `CapWindowId::Main::min_size` uses in the Tauri app, // and the window is fixed at that size there too. + let session = RecordingSession::init(cx); + let bounds = Bounds::centered( None, size(px(MAIN_WINDOW_WIDTH), px(MAIN_WINDOW_HEIGHT)), @@ -65,15 +71,11 @@ fn main() { // `None` titlebar drops NSClosable/NSMiniaturizable/NSResizable // from the style mask, which is the equivalent. titlebar: None, - // TODO: always-on-top, `visible_on_all_workspaces(true)` - // and the NSPanel level-100 treatment the Tauri window - // gets. `WindowKind::Floating` is not the answer: it does - // float at NSFloatingWindowLevel, but it allocates an + // Stays `Normal` and gets its panel treatment (level 100, + // all Spaces) from `platform::apply_panel_behavior` below. + // `WindowKind::Floating` is not the answer: it allocates an // NSPanel, and a panel hides itself when the application - // deactivates -- the window vanishes the moment you click - // another app, which is exactly wrong for a recorder. This - // needs the AppKit calls `windows.rs` makes, on a normal - // window. + // deactivates -- exactly wrong for a recorder. kind: gpui::WindowKind::Normal, // The header is dragged by the app via `start_window_move` // rather than by AppKit, so mark the content view as app-owned @@ -84,14 +86,32 @@ fn main() { is_minimizable: false, ..Default::default() }, - |window, cx| cx.new(|cx| MainWindow::new(window, cx)), + { + let session = session.clone(); + move |window, cx| cx.new(|cx| MainWindow::new(session, window, cx)) + }, ) .expect("failed to open the main window"); + app_windows::init(window_handle, session, cx); + // Enumeration is started here rather than in `MainWindow::new`, which // runs before the window is fully built -- see `start_enumeration`. + // The panel behavior (`MAIN_PANEL_LEVEL`, all Spaces -- what the Tauri + // app does via tauri_nspanel) is applied here for the same reason: the + // NSWindow does not exist yet inside the builder closure. window_handle - .update(cx, |view, window, cx| view.start_enumeration(window, cx)) + .update(cx, |view, window, cx| { + platform::apply_panel_behavior( + window, + platform::PanelBehavior { + level: platform::MAIN_WINDOW_LEVEL, + join_all_spaces: true, + shadow: true, + }, + ); + view.start_enumeration(window, cx) + }) .expect("failed to start device enumeration"); // `CAP_GPUI_AUTO_RECORD=studio:5` / `instant:4`: arm the primary diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index c0bfa85453..976949682a 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -14,11 +14,13 @@ use gpui::{ }; use crate::{ - MAIN_WINDOW_HEIGHT, MAIN_WINDOW_WIDTH, + MAIN_WINDOW_HEIGHT, MAIN_WINDOW_WIDTH, app_windows, devices::{CameraOption, DeviceSnapshot, DisplayOption, MicrophoneOption, WindowOption}, - recording::{self, ActiveRecording}, + recording, + session::{Phase, RecordingSession}, theme::{Appearance, Theme}, }; +use gpui::Entity; /// `MAIN_WINDOW_SIZE.expanded` in index.tsx. const EXPANDED_WIDTH: f32 = 600.; @@ -203,27 +205,19 @@ pub struct MainWindow { /// True until the background enumeration has reported back, so the panel can /// say "Loading..." rather than "No cameras found". enumerating: bool, - recording_phase: RecordingPhase, - /// The live recording while `recording_phase` is `Recording`. Taken (moved - /// into the stop future) the moment stopping starts. - active_recording: Option, - /// Why the last start attempt failed, shown above the start button. - recording_error: Option, -} - -/// `Idle -> Starting -> Recording -> Stopping -> Idle`, with failed starts -/// falling straight back to `Idle` + `recording_error`. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum RecordingPhase { - Idle, - Starting, - Recording, - Stopping, + /// The app-wide recording session; the lifecycle itself lives there so the + /// controls bar window can drive the same recording. + session: Entity, } impl MainWindow { - pub fn new(window: &mut Window, cx: &mut Context) -> Self { + pub fn new( + session: Entity, + window: &mut Window, + cx: &mut Context, + ) -> Self { let theme = Theme::new(Appearance::from_window(window.appearance())); + cx.observe(&session, |_, _, cx| cx.notify()).detach(); // Enumeration hits AVFoundation and the window server, so it must not // run on the main thread -- doing it inline here costs ~180ms of a @@ -246,9 +240,7 @@ impl MainWindow { search: String::new(), search_focus: cx.focus_handle(), enumerating: true, - recording_phase: RecordingPhase::Idle, - active_recording: None, - recording_error: None, + session, } } @@ -399,6 +391,10 @@ impl MainWindow { self.target = Some(TargetType::Display); cx.notify(); + // `CAP_GPUI_AUTO_PAUSE=1`: wiggle pause/resume in the middle third, so + // ffprobe duration < wall time proves the pause reached the engine. + let pause_wiggle = std::env::var("CAP_GPUI_AUTO_PAUSE").is_ok_and(|v| v == "1"); + cx.spawn_in(window, async move |this, cx| { // Give enumeration and the first paint a moment; the recorder // itself does not depend on it, but the screenshots should show @@ -406,28 +402,72 @@ impl MainWindow { cx.background_executor() .timer(std::time::Duration::from_secs(2)) .await; + // Exercise the microphone path too: record with the system default + // input. "First in the list" is wrong on a machine with a + // Continuity iPhone mic -- it enumerates but dies on stream start, + // which kills the whole recording. + let default_mic = cx + .background_executor() + .spawn(async { + cap_recording::feeds::microphone::MicrophoneFeed::default_device() + .map(|(name, _, _)| name) + }) + .await; if this .update_in(cx, |this, window, cx| { - // Exercise the microphone path too: record with the default - // input when the machine has one. - this.microphone = this.devices.microphones.first().cloned(); + this.microphone = default_mic + .and_then(|name| { + this.devices + .microphones + .iter() + .find(|mic| mic.name == name) + .cloned() + }) + .or_else(|| this.devices.microphones.first().cloned()); + if let Some(mic) = &this.microphone { + tracing::info!(mic = %mic.name, "auto-record microphone"); + } this.start_recording(window, cx) }) .is_err() { return; } - cx.background_executor() - .timer(std::time::Duration::from_secs(record_secs)) - .await; - this.update_in(cx, |this, window, cx| this.stop_recording(window, cx)) + + let third = std::time::Duration::from_secs(record_secs.div_ceil(3)); + let toggle = |this: &gpui::WeakEntity, + cx: &mut gpui::AsyncWindowContext| { + this.update_in(cx, |this, _, cx| { + this.session + .update(cx, |session, cx| session.toggle_pause(cx)); + }) .ok(); + }; + if pause_wiggle { + cx.background_executor().timer(third).await; + toggle(&this, cx); + cx.background_executor().timer(third).await; + toggle(&this, cx); + cx.background_executor().timer(third).await; + } else { + cx.background_executor() + .timer(std::time::Duration::from_secs(record_secs)) + .await; + } + this.update_in(cx, |this, _, cx| { + this.session.update(cx, |session, cx| session.stop(cx)); + }) + .ok(); }) .detach(); } - pub fn start_recording(&mut self, window: &mut Window, cx: &mut Context) { - if self.recording_phase != RecordingPhase::Idle { + /// Collect the UI state into a start config and hand it to the + /// orchestrator, which opens the controls bar, hides this window, and + /// starts the engine. Deferred because the orchestrator updates this very + /// window (hide), which would re-enter the update we are inside of. + pub fn start_recording(&mut self, _window: &mut Window, cx: &mut Context) { + if self.session.read(cx).phase != Phase::Idle { return; } let Some(target) = self.armed_target() else { @@ -437,9 +477,10 @@ impl MainWindow { return; }; if matches!(target, ScreenCaptureTarget::CameraOnly) && self.camera.is_none() { - self.recording_error = - Some("Camera-only recording requires a selected camera.".into()); - cx.notify(); + self.session.update(cx, |session, cx| { + session.error = Some("Camera-only recording requires a selected camera.".into()); + cx.notify(); + }); return; } @@ -452,84 +493,10 @@ impl MainWindow { .as_ref() .map(|camera| recording::DeviceOrModelID::DeviceID(camera.device_id.clone())), system_audio: self.system_audio, + excluded_windows: Vec::new(), }; - self.recording_phase = RecordingPhase::Starting; - self.recording_error = None; - cx.notify(); - - // The engine lives on tokio (kameo actors + the capture pipeline); - // gpui_tokio bridges its completion back into this entity. - let task = gpui_tokio::Tokio::spawn(cx, recording::start(config)); - cx.spawn_in(window, async move |this, cx| { - let result = task.await; - this.update_in(cx, |this, _window, cx| { - match result { - Ok(Ok(active)) => { - tracing::info!(dir = %active.project_dir.display(), "recording started"); - this.active_recording = Some(active); - this.recording_phase = RecordingPhase::Recording; - } - Ok(Err(error)) => { - tracing::error!("recording failed to start: {error:#}"); - this.recording_error = Some(format!("{error:#}")); - this.recording_phase = RecordingPhase::Idle; - } - Err(join_error) => { - tracing::error!("recording start task died: {join_error}"); - this.recording_error = Some("Recording task failed.".into()); - this.recording_phase = RecordingPhase::Idle; - } - } - cx.notify(); - }) - .ok(); - }) - .detach(); - } - - pub fn stop_recording(&mut self, window: &mut Window, cx: &mut Context) { - if self.recording_phase != RecordingPhase::Recording { - return; - } - let Some(active) = self.active_recording.take() else { - return; - }; - self.recording_phase = RecordingPhase::Stopping; - cx.notify(); - - let task = gpui_tokio::Tokio::spawn(cx, active.stop()); - cx.spawn_in(window, async move |this, cx| { - let result = task.await; - this.update_in(cx, |this, _window, cx| { - match result { - Ok(Ok(project_dir)) => { - tracing::info!(dir = %project_dir.display(), "recording finished"); - // v1 completion affordance until the editor window and - // Recents thumbnails exist: reveal the project. - #[cfg(target_os = "macos")] - { - let _ = std::process::Command::new("open") - .arg("-R") - .arg(&project_dir) - .spawn(); - } - } - Ok(Err(error)) => { - tracing::error!("recording failed to stop cleanly: {error:#}"); - this.recording_error = Some(format!("{error:#}")); - } - Err(join_error) => { - tracing::error!("recording stop task died: {join_error}"); - this.recording_error = Some("Stop task failed.".into()); - } - } - this.recording_phase = RecordingPhase::Idle; - cx.notify(); - }) - .ok(); - }) - .detach(); + cx.defer(move |cx: &mut gpui::App| app_windows::begin_recording(config, cx)); } } @@ -551,9 +518,15 @@ impl Render for MainWindow { .text_color(theme.text_primary) .child(self.render_header(window, cx)) .child(self.render_body(cx)) - .when(self.recording_phase != RecordingPhase::Idle, |this| { - this.child(self.render_recording_overlay(cx)) - }) + .when( + // The controls bar owns the live-recording UI; this overlay is + // the fallback for when the bar window failed to open. + { + let session = self.session.read(cx); + session.phase != Phase::Idle && !session.controls_open + }, + |this| this.child(self.render_recording_overlay(cx)), + ) } } @@ -725,7 +698,7 @@ impl MainWindow { .child(self.render_base_controls(cx)) .when(self.expanded, |this| this.child(self.render_recents())), ) - .when(self.can_start_recording(), |this| { + .when(self.can_start_recording(cx), |this| { this.child(self.render_start_footer(cx)) }), } @@ -735,8 +708,8 @@ impl MainWindow { /// target tile opens the fullscreen target-select overlay, which owns /// "Start Recording". Until that overlay window exists, an armed target /// gets a pinned footer button here so the recorder is drivable at all. - fn can_start_recording(&self) -> bool { - self.recording_phase == RecordingPhase::Idle + fn can_start_recording(&self, cx: &Context) -> bool { + self.session.read(cx).phase == Phase::Idle && self.recording_mode().is_some() && self.armed_target().is_some() } @@ -749,7 +722,7 @@ impl MainWindow { .flex_col() .gap(px(4.)) .flex_shrink_0() - .when_some(self.recording_error.clone(), |this, error| { + .when_some(self.session.read(cx).error.clone(), |this, error| { this.child( div() .text_size(px(11.)) @@ -794,8 +767,9 @@ impl MainWindow { /// to the bottom (`px-6 pb-8`, `h-11 rounded-xl bg-red-9`). fn render_recording_overlay(&self, cx: &mut Context) -> impl IntoElement { let theme = self.theme; - let stopping = self.recording_phase == RecordingPhase::Stopping; - let starting = self.recording_phase == RecordingPhase::Starting; + let phase = self.session.read(cx).phase; + let stopping = phase == Phase::Stopping; + let starting = phase == Phase::Starting; let mut wash: Hsla = theme.gray_1.into(); wash.a = 0.8; @@ -829,8 +803,8 @@ impl MainWindow { .when(stopping || starting, |this| this.opacity(0.6)) .when(!stopping && !starting, |this| { this.hover(|style| style.bg(theme.red_10)).on_click(cx.listener( - |this, _, window, cx| { - this.stop_recording(window, cx); + |this, _, _window, cx| { + this.session.update(cx, |session, cx| session.stop(cx)); }, )) }) @@ -840,9 +814,9 @@ impl MainWindow { .size(px(16.)) .text_color(gpui::white()), ) - .child(match self.recording_phase { - RecordingPhase::Starting => "Starting...", - RecordingPhase::Stopping => "Stopping...", + .child(match phase { + Phase::Starting => "Starting...", + Phase::Stopping => "Stopping...", _ => "Stop Recording", }), ) diff --git a/apps/desktop-gpui/src/platform.rs b/apps/desktop-gpui/src/platform.rs new file mode 100644 index 0000000000..0beeca8ee2 --- /dev/null +++ b/apps/desktop-gpui/src/platform.rs @@ -0,0 +1,145 @@ +//! Native window treatment gpui does not expose. +//! +//! The Tauri app gives its windows their panel behavior in +//! `apps/desktop/src-tauri/src/windows.rs`: converted to NSPanel via +//! `tauri_nspanel`, raised to a specific window level, joined to all Spaces. +//! gpui exposes none of that, but its `Window` implements +//! `raw_window_handle::HasWindowHandle`, and on macOS the AppKit handle's +//! `ns_view` reaches the `NSWindow`, where the same AppKit calls apply. We skip +//! the NSPanel class swizzle -- level + collection behavior covers the +//! observable behavior (always-on-top, follows Spaces); the difference is noted +//! in the README. +//! +//! Everything here must run on the main thread. gpui's foreground executor is +//! the main thread, and every caller sits inside a `Window` update, so that +//! holds by construction. + +/// `MAIN_PANEL_LEVEL` in `windows.rs`: above normal windows and the Dock's +/// auto-hide reveal, below context menus. +pub const MAIN_WINDOW_LEVEL: isize = 100; + +#[derive(Debug, Clone, Copy)] +pub struct PanelBehavior { + pub level: isize, + /// `CanJoinAllSpaces | FullScreenPrimary`, the combination every Cap panel + /// window uses. + pub join_all_spaces: bool, + /// Borderless NSWindows get a system shadow; the recording controls bar is + /// drawn shadowless (`.shadow(false)` in the Tauri builder). + pub shadow: bool, +} + +#[cfg(target_os = "macos")] +pub use mac::*; + +#[cfg(target_os = "macos")] +mod mac { + use gpui::Window; + use objc2::rc::Id; + use objc2_app_kit::{NSView, NSWindow, NSWindowCollectionBehavior}; + use raw_window_handle::{HasWindowHandle, RawWindowHandle}; + + use super::PanelBehavior; + + #[link(name = "CoreGraphics", kind = "framework")] + unsafe extern "C" { + fn CGWindowLevelForKey(key: i32) -> i32; + } + + /// The level the recording controls bar runs at. + /// + /// `windows.rs` computes this as `CGWindowLevelForKey(10)` under a constant + /// it names `kCGMaximumWindowLevelKey` -- but key 10 is actually + /// `kCGModalPanelWindowLevelKey` (maximum is 14), so the Tauri bar really + /// runs at level 8, not the maximum. Reproduced verbatim: parity with the + /// shipping app beats fixing its constant from over here. + pub fn recording_controls_level() -> isize { + unsafe { CGWindowLevelForKey(10) as isize } + } + + /// The `NSWindow` behind a gpui window. + pub fn ns_window(window: &Window) -> Option> { + // Fully qualified: `Window` also has an inherent `window_handle()` + // returning gpui's own `AnyWindowHandle`. + let handle = HasWindowHandle::window_handle(window).ok()?; + let RawWindowHandle::AppKit(appkit) = handle.as_raw() else { + return None; + }; + let view = appkit.ns_view.as_ptr() as *const NSView; + // The view pointer is valid for the lifetime of the gpui window, and we + // are inside a live `&Window`. + unsafe { (*view).window() } + } + + pub fn apply_panel_behavior(window: &Window, behavior: PanelBehavior) { + let Some(ns_window) = ns_window(window) else { + tracing::error!("no NSWindow behind gpui window; panel behavior not applied"); + return; + }; + ns_window.setLevel(behavior.level); + tracing::debug!( + requested = behavior.level, + actual = unsafe { ns_window.level() }, + "panel level applied" + ); + if behavior.join_all_spaces { + unsafe { + ns_window.setCollectionBehavior( + NSWindowCollectionBehavior::CanJoinAllSpaces + | NSWindowCollectionBehavior::FullScreenPrimary, + ); + } + } + ns_window.setHasShadow(behavior.shadow); + } + + /// `orderOut:` -- hide without closing, the way the Tauri main window hides + /// while the recording controls bar is up. + pub fn hide_window(window: &Window) { + if let Some(ns_window) = ns_window(window) { + ns_window.orderOut(None); + } + } + + /// Reverse of [`hide_window`]. + pub fn show_window(window: &Window) { + if let Some(ns_window) = ns_window(window) { + ns_window.makeKeyAndOrderFront(None); + } + } + + /// Show without stealing key status -- `orderFrontRegardless`, what the + /// Tauri app calls on the recording controls panel. + pub fn show_window_without_focus(window: &Window) { + if let Some(ns_window) = ns_window(window) { + unsafe { ns_window.orderFrontRegardless() }; + } + } + + /// The CGWindowID, which is also what `scap_targets::WindowId` wraps on + /// macOS -- used to exclude our own controls bar from the capture. + pub fn window_number(window: &Window) -> Option { + ns_window(window).map(|w| unsafe { w.windowNumber() }) + } +} + +#[cfg(not(target_os = "macos"))] +mod stub { + use gpui::Window; + + use super::PanelBehavior; + + pub fn recording_controls_level() -> isize { + 0 + } + pub fn apply_panel_behavior(_window: &Window, _behavior: PanelBehavior) {} + pub fn hide_window(_window: &Window) {} + pub fn show_window(_window: &Window) {} + pub fn show_window_without_focus(_window: &Window) {} + pub fn window_number(_window: &Window) -> Option { + None + } +} + +#[cfg(not(target_os = "macos"))] +pub use stub::*; diff --git a/apps/desktop-gpui/src/recording.rs b/apps/desktop-gpui/src/recording.rs index 27d4c52eec..dfc91ddc12 100644 --- a/apps/desktop-gpui/src/recording.rs +++ b/apps/desktop-gpui/src/recording.rs @@ -28,7 +28,9 @@ pub enum RecordingMode { } /// Everything `start` needs, captured from UI state up front so the future is -/// `'static` and owns its inputs. +/// `'static` and owns its inputs. `Clone` because the session keeps the last +/// config around for the bar's restart button. +#[derive(Clone)] pub struct StartConfig { pub mode: RecordingMode, pub target: ScreenCaptureTarget, @@ -36,11 +38,16 @@ pub struct StartConfig { pub microphone: Option, pub camera: Option, pub system_audio: bool, + /// Our own windows (the recording controls bar), excluded from capture the + /// way the Tauri app excludes its bar. + pub excluded_windows: Vec, } enum Handle { + // Studio's handle is `Clone`; instant's is not, so it rides in an `Arc`. + // Both give the owned handles pause/resume need for `'static` futures. Studio(studio_recording::ActorHandle), - Instant(instant_recording::ActorHandle), + Instant(Arc), } /// A live recording. Stopping consumes it; dropping it without stopping leaves @@ -65,6 +72,57 @@ impl ActiveRecording { /// mp4s), instant projects get their DASH segments muxed into /// `content/output.mp4` plus a `recording-meta.json`/`project-config.json` /// pair so the library and share flows recognize the project. + /// An owned future pausing the recording; `'static` so the session can + /// spawn it on tokio without borrowing itself. + pub fn pause_handle( + &self, + ) -> std::pin::Pin> + Send>> { + match &self.handle { + Handle::Studio(handle) => { + let handle = handle.clone(); + Box::pin(async move { handle.pause().await }) + } + Handle::Instant(handle) => { + let handle = handle.clone(); + Box::pin(async move { handle.pause().await }) + } + } + } + + /// Owned resume future; see [`Self::pause_handle`]. + pub fn resume_handle( + &self, + ) -> std::pin::Pin> + Send>> { + match &self.handle { + Handle::Studio(handle) => { + let handle = handle.clone(); + Box::pin(async move { handle.resume().await }) + } + Handle::Instant(handle) => { + let handle = handle.clone(); + Box::pin(async move { handle.resume().await }) + } + } + } + + /// Cancel without finalizing and delete the project directory -- the + /// delete and restart flows. Deleting a directory this app just created is + /// app behavior, same as the Tauri delete button. + pub async fn cancel_and_delete(self) -> anyhow::Result<()> { + match &self.handle { + Handle::Studio(handle) => handle.cancel().await?, + Handle::Instant(handle) => handle.cancel().await?, + } + tokio::task::spawn_blocking({ + let dir = self.project_dir.clone(); + move || std::fs::remove_dir_all(&dir) + }) + .await + .context("delete task")? + .with_context(|| format!("deleting {}", self.project_dir.display()))?; + Ok(()) + } + pub async fn stop(self) -> anyhow::Result { match self.handle { Handle::Studio(handle) => { @@ -144,32 +202,45 @@ fn persist_instant_meta( } pub async fn start(config: StartConfig) -> anyhow::Result { + match start_attempt(config.clone()).await { + Ok(active) => Ok(active), + // The mic actor can die between our health checks and the recording + // actor's own audio setup (flaky Bluetooth/Continuity devices, or + // CoreAudio still tearing down a previous session). One retry without + // the mic keeps the screen recording alive; the real fix is app-scoped + // feeds with reconnect, which arrive with the camera preview window. + Err(error) if config.microphone.is_some() && format!("{error:#}").contains("microphone") => + { + tracing::warn!("start failed on the microphone path, retrying without: {error:#}"); + start_attempt(StartConfig { + microphone: None, + ..config + }) + .await + } + Err(error) => Err(error), + } +} + +async fn start_attempt(config: StartConfig) -> anyhow::Result { let project_dir = create_project_dir(&config.target)?; tracing::info!(dir = %project_dir.display(), "starting recording"); // Feed actors are per-recording here, where the Tauri app keeps app-wide // ones (it needs them for previews and level meters between recordings). // Once the camera preview window exists this moves to app scope too. + // A microphone that enumerates but fails to open (Bluetooth profile + // switch, a Continuity iPhone that wandered off) must not kill the whole + // recording -- degrade to no-mic, the way the Tauri app's app-scoped feed + // surfaces "Not connected" and records on. let (mic_feed, mic_lock, mic_errors) = match &config.microphone { - Some(label) => { - let (error_tx, error_rx) = flume::unbounded(); - let feed = MicrophoneFeed::spawn(MicrophoneFeed::new(error_tx)); - let ready = feed - .ask(microphone::SetInput { - label: label.clone(), - settings: None, - }) - .await - .map_err(|e| anyhow!("microphone '{label}' setup: {e}"))?; - ready - .await - .map_err(|e| anyhow!("microphone '{label}' init: {e}"))?; - let lock = feed - .ask(microphone::Lock) - .await - .map_err(|e| anyhow!("microphone '{label}' lock: {e}"))?; - (Some(feed), Some(Arc::new(lock)), Some(error_rx)) - } + Some(label) => match setup_microphone(label).await { + Ok((feed, lock, error_rx)) => (Some(feed), Some(lock), Some(error_rx)), + Err(error) => { + tracing::warn!("microphone '{label}' unavailable, recording without: {error:#}"); + (None, None, None) + } + }, None => (None, None, None), }; @@ -213,7 +284,8 @@ pub async fn start(config: StartConfig) -> anyhow::Result { RecordingMode::Studio => { let mut builder = studio_recording::Actor::builder(project_dir.clone(), config.target.clone()) - .with_system_audio(config.system_audio); + .with_system_audio(config.system_audio) + .with_excluded_windows(config.excluded_windows.clone()); if let Some(lock) = camera_lock.clone() { builder = builder.with_camera_feed(lock); } @@ -233,14 +305,15 @@ pub async fn start(config: StartConfig) -> anyhow::Result { RecordingMode::Instant => { let mut builder = instant_recording::Actor::builder(project_dir.clone(), config.target.clone()) - .with_system_audio(config.system_audio); + .with_system_audio(config.system_audio) + .with_excluded_windows(config.excluded_windows.clone()); if let Some(lock) = camera_lock.clone() { builder = builder.with_camera_feed(lock); } if let Some(lock) = mic_lock.clone() { builder = builder.with_mic_feed(lock); } - Handle::Instant( + Handle::Instant(Arc::new( builder .build( #[cfg(target_os = "macos")] @@ -248,7 +321,7 @@ pub async fn start(config: StartConfig) -> anyhow::Result { ) .await .context("instant recording actor")?, - ) + )) } }; @@ -261,6 +334,30 @@ pub async fn start(config: StartConfig) -> anyhow::Result { }) } +async fn setup_microphone( + label: &str, +) -> anyhow::Result<( + ActorRef, + Arc, + flume::Receiver, +)> { + let (error_tx, error_rx) = flume::unbounded(); + let feed = MicrophoneFeed::spawn(MicrophoneFeed::new(error_tx)); + let ready = feed + .ask(microphone::SetInput { + label: label.to_string(), + settings: None, + }) + .await + .map_err(|e| anyhow!("setup: {e}"))?; + ready.await.map_err(|e| anyhow!("init: {e}"))?; + let lock = feed + .ask(microphone::Lock) + .await + .map_err(|e| anyhow!("lock: {e}"))?; + Ok((feed, Arc::new(lock), error_rx)) +} + #[cfg(target_os = "macos")] async fn read_shareable_content() -> anyhow::Result { let content = cidre::sc::ShareableContent::current() diff --git a/apps/desktop-gpui/src/session.rs b/apps/desktop-gpui/src/session.rs new file mode 100644 index 0000000000..4fcb5c498d --- /dev/null +++ b/apps/desktop-gpui/src/session.rs @@ -0,0 +1,266 @@ +//! The app-wide recording session. +//! +//! Unit 1 kept the recording lifecycle on `MainWindow`; the controls bar makes +//! that untenable -- two windows now read and drive the same state. The session +//! is a plain gpui entity installed as a global, observed by both windows; all +//! engine work runs on tokio via `gpui_tokio` and lands back here with +//! `cx.notify()`. + +use std::time::{Duration, Instant}; + +use gpui::{App, AppContext as _, Context, Entity, Global}; + +use crate::recording::{self, ActiveRecording, StartConfig}; + +/// `Idle -> Starting -> Recording -> Stopping -> Idle`; pause is a flag on +/// `Recording`, matching the `recording`/`paused` variants of the Tauri bar. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Phase { + Idle, + Starting, + Recording { paused: bool }, + Stopping, +} + +pub struct RecordingSession { + pub phase: Phase, + active: Option, + /// Why the last start attempt failed, for the main window to surface. + pub error: Option, + /// The config of the live (or last) recording, kept for restart. + last_config: Option, + /// True while the controls bar window is open, so the main window knows to + /// fall back to its in-window overlay when the bar failed to open. + pub controls_open: bool, + started_at: Option, + paused_accum: Duration, + paused_since: Option, +} + +struct SessionGlobal(Entity); +impl Global for SessionGlobal {} + +impl RecordingSession { + pub fn init(cx: &mut App) -> Entity { + let session = cx.new(|_| Self { + phase: Phase::Idle, + active: None, + error: None, + last_config: None, + controls_open: false, + started_at: None, + paused_accum: Duration::ZERO, + paused_since: None, + }); + cx.set_global(SessionGlobal(session.clone())); + session + } + + pub fn global(cx: &App) -> Entity { + cx.global::().0.clone() + } + + /// Elapsed recording time, excluding paused stretches -- what the bar's + /// timer shows. + pub fn elapsed(&self) -> Duration { + let Some(started_at) = self.started_at else { + return Duration::ZERO; + }; + let paused = self.paused_accum + + self + .paused_since + .map(|since| since.elapsed()) + .unwrap_or_default(); + started_at.elapsed().saturating_sub(paused) + } + + pub fn is_paused(&self) -> bool { + matches!(self.phase, Phase::Recording { paused: true }) + } + + pub fn set_controls_open(&mut self, open: bool, cx: &mut Context) { + if self.controls_open != open { + self.controls_open = open; + cx.notify(); + } + } + + pub fn start(&mut self, config: StartConfig, cx: &mut Context) { + if self.phase != Phase::Idle { + return; + } + self.phase = Phase::Starting; + self.error = None; + self.last_config = Some(config.clone()); + cx.notify(); + + let task = gpui_tokio::Tokio::spawn(cx, recording::start(config)); + cx.spawn(async move |this, cx| { + let result = task.await; + this.update(cx, |this, cx| { + match result { + Ok(Ok(active)) => { + tracing::info!(dir = %active.project_dir.display(), "recording started"); + this.active = Some(active); + this.phase = Phase::Recording { paused: false }; + this.started_at = Some(Instant::now()); + this.paused_accum = Duration::ZERO; + this.paused_since = None; + } + Ok(Err(error)) => { + tracing::error!("recording failed to start: {error:#}"); + this.error = Some(format!("{error:#}")); + this.phase = Phase::Idle; + } + Err(join_error) => { + tracing::error!("recording start task died: {join_error}"); + this.error = Some("Recording task failed.".into()); + this.phase = Phase::Idle; + } + } + cx.notify(); + }) + .ok(); + }) + .detach(); + } + + pub fn stop(&mut self, cx: &mut Context) { + if !matches!(self.phase, Phase::Recording { .. }) { + return; + } + let Some(active) = self.active.take() else { + return; + }; + self.phase = Phase::Stopping; + cx.notify(); + + let task = gpui_tokio::Tokio::spawn(cx, active.stop()); + cx.spawn(async move |this, cx| { + let result = task.await; + this.update(cx, |this, cx| { + match result { + Ok(Ok(project_dir)) => { + tracing::info!(dir = %project_dir.display(), "recording finished"); + // v1 completion affordance until the editor window and + // Recents thumbnails exist: reveal the project. + #[cfg(target_os = "macos")] + { + let _ = std::process::Command::new("open") + .arg("-R") + .arg(&project_dir) + .spawn(); + } + } + Ok(Err(error)) => { + tracing::error!("recording failed to stop cleanly: {error:#}"); + this.error = Some(format!("{error:#}")); + } + Err(join_error) => { + tracing::error!("recording stop task died: {join_error}"); + this.error = Some("Stop task failed.".into()); + } + } + this.finish(cx); + }) + .ok(); + }) + .detach(); + } + + pub fn toggle_pause(&mut self, cx: &mut Context) { + let Phase::Recording { paused } = self.phase else { + return; + }; + let Some(active) = self.active.as_ref() else { + return; + }; + + // Optimistic flip; the actor call is fire-and-logged. The Tauri app + // does the same through its mutation -- there is no rollback UI. + self.phase = Phase::Recording { paused: !paused }; + if paused { + if let Some(since) = self.paused_since.take() { + self.paused_accum += since.elapsed(); + } + } else { + self.paused_since = Some(Instant::now()); + } + cx.notify(); + + let control = if paused { + active.resume_handle() + } else { + active.pause_handle() + }; + gpui_tokio::Tokio::spawn(cx, async move { + if let Err(error) = control.await { + tracing::error!("pause/resume failed: {error:#}"); + } + }) + .detach(); + } + + /// Cancel the live recording and delete its project directory (the bar's + /// trash button). + pub fn delete(&mut self, cx: &mut Context) { + if !matches!(self.phase, Phase::Recording { .. }) { + return; + } + let Some(active) = self.active.take() else { + return; + }; + self.phase = Phase::Stopping; + cx.notify(); + + let task = gpui_tokio::Tokio::spawn(cx, active.cancel_and_delete()); + cx.spawn(async move |this, cx| { + if let Ok(Err(error)) = task.await { + tracing::error!("delete recording: {error:#}"); + } + this.update(cx, |this, cx| this.finish(cx)).ok(); + }) + .detach(); + } + + /// The bar's restart button: throw away the live recording and immediately + /// start a new one with the same config. + pub fn restart(&mut self, cx: &mut Context) { + if !matches!(self.phase, Phase::Recording { .. }) { + return; + } + let Some(active) = self.active.take() else { + return; + }; + let Some(config) = self.last_config.clone() else { + return; + }; + self.phase = Phase::Starting; + self.started_at = None; + cx.notify(); + + let task = gpui_tokio::Tokio::spawn(cx, active.cancel_and_delete()); + cx.spawn(async move |this, cx| { + if let Ok(Err(error)) = task.await { + tracing::error!("restart: discarding old recording: {error:#}"); + } + this.update(cx, |this, cx| { + // Restart bypasses the Idle guard in `start` by construction: + // we are in `Starting` and `start` would bail, so inline the + // same transition. + this.phase = Phase::Idle; + this.start(config, cx); + }) + .ok(); + }) + .detach(); + } + + fn finish(&mut self, cx: &mut Context) { + self.phase = Phase::Idle; + self.started_at = None; + self.paused_accum = Duration::ZERO; + self.paused_since = None; + cx.notify(); + } +} diff --git a/apps/desktop-gpui/src/theme.rs b/apps/desktop-gpui/src/theme.rs index 75c906035b..38dd47a183 100644 --- a/apps/desktop-gpui/src/theme.rs +++ b/apps/desktop-gpui/src/theme.rs @@ -62,6 +62,9 @@ pub struct Theme { /// Distinct from `blue-9`; the two are not interchangeable. pub blue_500: Rgba, + /// `--red-300`, the recording bar's stop button. Same value in both themes. + pub red_300: Rgba, + /// `--text-primary`, the root text colour. Slightly translucent by design. pub text_primary: Rgba, } @@ -103,6 +106,7 @@ impl Theme { red_10: rgb(0xdc3e42), blue_500: rgb(0x3666c5), + red_300: rgb(0xff4766), text_primary: rgba(0x12161ff2), } @@ -138,6 +142,7 @@ impl Theme { red_10: rgb(0xec5d5e), blue_500: rgb(0x0a84ff), + red_300: rgb(0xff4766), text_primary: rgba(0xfffffff2), } @@ -159,6 +164,14 @@ impl Theme { } } + /// A colour at an alpha, for Tailwind's `/N` overlay fills + /// (`hover:bg-gray-12/6`, `hover:bg-red-500/8`). + pub fn with_alpha(color: Rgba, alpha: f32) -> Hsla { + let mut color: Hsla = color.into(); + color.a = alpha; + color + } + pub fn tile_selected_hover_bg(&self) -> Hsla { if self.is_dark() { let mut color: Hsla = self.blue_4.into(); From 3e8e4eedc263eb0831b0a172e0b69c38ff0609a9 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:42:50 +0100 Subject: [PATCH 016/146] fix(desktop-gpui): repair gpui display links on macOS 26 occlusionState reports visible windows with an undocumented 0x2000 bit on macOS 26 and no longer sets NSWindowOcclusionStateVisible (0x2), which is the only bit gpui's display-link gate checks -- so no window ever received frame callbacks after its first paint. The bar timer sat frozen on Starting through whole recordings and every prior refresh-tick workaround was painting nothing. Shim occlusionState on gpui's own window classes (GPUIWindow/GPUIPanel) to OR the documented bit back in whenever AppKit reports any visibility, and re-fire the occlusion handler from apply_panel_behavior so the link starts. Drop when the gpui pin understands the macOS 26 bit. --- apps/desktop-gpui/src/platform.rs | 101 ++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/apps/desktop-gpui/src/platform.rs b/apps/desktop-gpui/src/platform.rs index 0beeca8ee2..a832df8a76 100644 --- a/apps/desktop-gpui/src/platform.rs +++ b/apps/desktop-gpui/src/platform.rs @@ -36,11 +36,89 @@ pub use mac::*; mod mac { use gpui::Window; use objc2::rc::Id; + use objc2::runtime::{AnyObject, Sel}; use objc2_app_kit::{NSView, NSWindow, NSWindowCollectionBehavior}; use raw_window_handle::{HasWindowHandle, RawWindowHandle}; use super::PanelBehavior; + /// Repair gpui's per-window display link on macOS 26. + /// + /// gpui only starts a window's CVDisplayLink when `occlusionState()` + /// contains the documented `NSWindowOcclusionStateVisible` bit (`0x2`). + /// On macOS 26 AppKit reports visible windows with a new undocumented bit + /// (`0x2000`) and, for windows like ours, no longer sets `0x2` -- so no + /// window in this app ever got a frame callback: first paint only, then + /// frozen (bar timer stuck on "Starting", camera preview never showing a + /// frame). Verified empirically via `window_diagnostics`. + /// + /// The shim overrides `occlusionState` on gpui's own window subclasses + /// (`GPUIWindow`, `GPUIPanel`) to OR the documented bit back in whenever + /// AppKit reports any visibility at all. gpui's gate then passes and its + /// own occlusion handling keeps working (a truly occluded window still + /// reports 0 and stops its link). Remove when the gpui pin understands the + /// macOS 26 bit. + /// + /// Call once, before any window opens; [`kick_display_link`] re-fires the + /// (self-delegating) occlusion handler for windows that opened before the + /// state ever changed. + pub fn install_occlusion_shim() { + use objc2::ffi::{class_addMethod, objc_msgSendSuper, objc_super}; + + unsafe extern "C" fn occlusion_state_shim(this: *mut AnyObject, sel: Sel) -> usize { + unsafe { + let class = (*this).class(); + let Some(superclass) = class.superclass() else { + return 0; + }; + let mut sup = objc_super { + receiver: this.cast(), + super_class: (superclass as *const objc2::runtime::AnyClass).cast(), + }; + let send: unsafe extern "C" fn(*mut objc_super, Sel) -> usize = + std::mem::transmute(objc_msgSendSuper as unsafe extern "C" fn()); + let raw = send(&mut sup, sel); + if raw != 0 { raw | 0x2 } else { raw } + } + } + + for name in ["GPUIWindow", "GPUIPanel"] { + let Some(class) = objc2::runtime::AnyClass::get(name) else { + // The class registers lazily with the first window; the caller + // runs before that only if nothing was opened -- harmless, the + // second call from `kick_display_link` retries. + continue; + }; + let added = unsafe { + class_addMethod( + (class as *const objc2::runtime::AnyClass as *mut objc2::ffi::objc_class) + .cast(), + objc2::sel!(occlusionState).as_ptr(), + Some(std::mem::transmute::< + unsafe extern "C" fn(*mut AnyObject, Sel) -> usize, + unsafe extern "C" fn(), + >(occlusion_state_shim)), + c"Q@:".as_ptr(), + ) + }; + if added { + tracing::info!("installed macOS 26 occlusion shim on {name}"); + } + } + } + + /// Re-run gpui's occlusion handler so a window whose display link never + /// started (see [`install_occlusion_shim`]) evaluates the gate again. gpui + /// windows are their own delegate, so the selector lives on the window. + pub fn kick_display_link(window: &Window) { + install_occlusion_shim(); + if let Some(ns_window) = ns_window(window) { + unsafe { + let _: () = objc2::msg_send![&*ns_window, windowDidChangeOcclusionState: std::ptr::null_mut::()]; + } + } + } + #[link(name = "CoreGraphics", kind = "framework")] unsafe extern "C" { fn CGWindowLevelForKey(key: i32) -> i32; @@ -72,6 +150,10 @@ mod mac { } pub fn apply_panel_behavior(window: &Window, behavior: PanelBehavior) { + // Every Cap window comes through here right after opening, which makes + // it the one reliable place to repair the display link (see + // `install_occlusion_shim`). + kick_display_link(window); let Some(ns_window) = ns_window(window) else { tracing::error!("no NSWindow behind gpui window; panel behavior not applied"); return; @@ -121,6 +203,23 @@ mod mac { pub fn window_number(window: &Window) -> Option { ns_window(window).map(|w| unsafe { w.windowNumber() }) } + + /// One-line diagnostic of everything AppKit weighs into occlusion + /// visibility -- how the macOS 26 display-link failure was diagnosed; keep + /// it for the next platform mystery. + #[allow(dead_code)] + pub fn window_diagnostics(window: &Window) -> Option { + let w = ns_window(window)?; + Some(format!( + "occlusion_raw={:#x} visible={} on_active_space={} alpha={} level={} number={}", + w.occlusionState().bits(), + w.isVisible(), + unsafe { w.isOnActiveSpace() }, + unsafe { w.alphaValue() }, + unsafe { w.level() }, + unsafe { w.windowNumber() }, + )) + } } #[cfg(not(target_os = "macos"))] @@ -132,6 +231,8 @@ mod stub { pub fn recording_controls_level() -> isize { 0 } + pub fn install_occlusion_shim() {} + pub fn kick_display_link(_window: &Window) {} pub fn apply_panel_behavior(_window: &Window, _behavior: PanelBehavior) {} pub fn hide_window(_window: &Window) {} pub fn show_window(_window: &Window) {} From e11c58b40a27a063084d8049d4b521d5e4ce4b0b Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:43:00 +0100 Subject: [PATCH 017/146] feat(desktop-gpui): camera preview window with app-scoped feeds Selecting a camera spins up an app-scoped CameraFeed and opens the preview bubble immediately, the Tauri model; the mic feed is app-scoped too, feeding live levels to the picker rows and the recording bar, whose mute button now flips the recording-scoped payload-zeroing mute in instant mode. Recordings lock the running feeds instead of spawning their own (per-recording spawn kept as fallback), and the bubble is excluded from studio captures but included in instant ones, mirroring filter_for_instant_mode. Frames go VideoToolbox 420v->BGRA into gpui RenderImages -- the surface() element hard-asserts 420f on this rev and ignores rounded clipping, and the default bubble is a circle. Chrome state (size/shape/blur) persists to gpui-state.json; round/square/full shapes, S/L presets, hover toolbar, corner resize, and drag-to-move all match CameraPreviewChrome.tsx; mirroring is present-but-disabled (no flip transform in this gpui rev). --- apps/desktop-gpui/Cargo.lock | 3 + apps/desktop-gpui/Cargo.toml | 6 + apps/desktop-gpui/assets/icons/arrows.svg | 3 + apps/desktop-gpui/assets/icons/circle.svg | 3 + apps/desktop-gpui/assets/icons/mic-off.svg | 8 + .../assets/icons/person-standing.svg | 6 + .../assets/icons/rectangle-horizontal.svg | 3 + apps/desktop-gpui/assets/icons/square.svg | 3 + apps/desktop-gpui/src/app_windows.rs | 128 +++- apps/desktop-gpui/src/assets.rs | 7 + apps/desktop-gpui/src/camera_window.rs | 645 ++++++++++++++++++ apps/desktop-gpui/src/controls_window.rs | 72 +- apps/desktop-gpui/src/feeds.rs | 379 ++++++++++ apps/desktop-gpui/src/main.rs | 4 + apps/desktop-gpui/src/main_window.rs | 115 +++- apps/desktop-gpui/src/recording.rs | 97 ++- apps/desktop-gpui/src/session.rs | 36 + apps/desktop-gpui/src/store.rs | 110 +++ 18 files changed, 1579 insertions(+), 49 deletions(-) create mode 100644 apps/desktop-gpui/assets/icons/arrows.svg create mode 100644 apps/desktop-gpui/assets/icons/circle.svg create mode 100644 apps/desktop-gpui/assets/icons/mic-off.svg create mode 100644 apps/desktop-gpui/assets/icons/person-standing.svg create mode 100644 apps/desktop-gpui/assets/icons/rectangle-horizontal.svg create mode 100644 apps/desktop-gpui/assets/icons/square.svg create mode 100644 apps/desktop-gpui/src/camera_window.rs create mode 100644 apps/desktop-gpui/src/feeds.rs create mode 100644 apps/desktop-gpui/src/store.rs diff --git a/apps/desktop-gpui/Cargo.lock b/apps/desktop-gpui/Cargo.lock index 09d575b16f..530f6f5403 100644 --- a/apps/desktop-gpui/Cargo.lock +++ b/apps/desktop-gpui/Cargo.lock @@ -1293,12 +1293,15 @@ dependencies = [ "gpui", "gpui_platform", "gpui_tokio", + "image 0.25.10", "kameo", "objc2 0.5.2", "objc2-app-kit 0.2.2", "raw-window-handle", "scap-targets", + "serde", "serde_json", + "smallvec", "tokio", "tracing", "tracing-subscriber", diff --git a/apps/desktop-gpui/Cargo.toml b/apps/desktop-gpui/Cargo.toml index 72a6ca73aa..fa87f0e670 100644 --- a/apps/desktop-gpui/Cargo.toml +++ b/apps/desktop-gpui/Cargo.toml @@ -52,6 +52,7 @@ anyhow = "1" chrono = "0.4" # Read the Tauri app's settings store so both apps share one recordings library. serde_json = "1" +serde = { version = "1", features = ["derive"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } @@ -62,6 +63,11 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } objc2 = "0.5.2" objc2-app-kit = { version = "0.2.2", features = ["NSWindow", "NSView", "NSResponder", "NSGraphics", "NSPasteboard"] } raw-window-handle = "0.6" +# Camera preview frames become gpui `RenderImage`s; the frame container type +# must match gpui's image crate version exactly (`image = "0.25.1"` in the zed +# fork's workspace) or `Frame` would be two different types. +image = { version = "0.25.1", default-features = false } +smallvec = "1" # ScreenCaptureKit shareable content, acquired the same way recording.rs in # src-tauri does. Same fork/rev/features as the root workspace. cidre = { git = "https://github.com/CapSoftware/cidre", rev = "bf84b67079a8", features = [ diff --git a/apps/desktop-gpui/assets/icons/arrows.svg b/apps/desktop-gpui/assets/icons/arrows.svg new file mode 100644 index 0000000000..ba8fa16133 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/arrows.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/circle.svg b/apps/desktop-gpui/assets/icons/circle.svg new file mode 100644 index 0000000000..9703881374 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/mic-off.svg b/apps/desktop-gpui/assets/icons/mic-off.svg new file mode 100644 index 0000000000..50f0449340 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/mic-off.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/apps/desktop-gpui/assets/icons/person-standing.svg b/apps/desktop-gpui/assets/icons/person-standing.svg new file mode 100644 index 0000000000..da2fc8edae --- /dev/null +++ b/apps/desktop-gpui/assets/icons/person-standing.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/desktop-gpui/assets/icons/rectangle-horizontal.svg b/apps/desktop-gpui/assets/icons/rectangle-horizontal.svg new file mode 100644 index 0000000000..76789fd515 --- /dev/null +++ b/apps/desktop-gpui/assets/icons/rectangle-horizontal.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/assets/icons/square.svg b/apps/desktop-gpui/assets/icons/square.svg new file mode 100644 index 0000000000..978299fc2c --- /dev/null +++ b/apps/desktop-gpui/assets/icons/square.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/desktop-gpui/src/app_windows.rs b/apps/desktop-gpui/src/app_windows.rs index 89869e08aa..51f78a6cc6 100644 --- a/apps/desktop-gpui/src/app_windows.rs +++ b/apps/desktop-gpui/src/app_windows.rs @@ -13,10 +13,11 @@ use gpui::{ }; use crate::{ + camera_window::{self, CameraWindow}, controls_window::ControlsWindow, main_window::MainWindow, platform, - recording::StartConfig, + recording::{RecordingMode, StartConfig}, session::{Phase, RecordingSession}, }; @@ -30,6 +31,7 @@ const TARGET_CONTROLS_OFFSET_Y: f64 = 48.; pub struct AppWindows { pub main: WindowHandle, pub controls: Option>, + pub camera: Option>, } impl Global for AppWindows {} @@ -40,6 +42,7 @@ pub fn init(main: WindowHandle, session: Entity, c cx.set_global(AppWindows { main, controls: None, + camera: None, }); let mut last_phase = Phase::Idle; @@ -64,7 +67,19 @@ pub fn begin_recording(config: StartConfig, cx: &mut App) { return; } - let excluded = open_controls(&config, session.clone(), cx); + let mut excluded: Vec = + open_controls(&config, session.clone(), cx).into_iter().collect(); + // The camera bubble is excluded from studio captures (the camera is its own + // track, composited in the editor) but *included* in instant captures -- + // `filter_for_instant_mode` in the Tauri app strips the camera exclusion + // there because instant has no compositing step. + if config.mode == RecordingMode::Studio + && let Some(number) = camera_window_number(cx) + && let Ok(id) = number.to_string().parse() + { + excluded.push(id); + } + let bar_open = cx.global::().controls.is_some(); session.update(cx, |session, cx| { session.set_controls_open(bar_open, cx); @@ -76,12 +91,119 @@ pub fn begin_recording(config: StartConfig, cx: &mut App) { } let config = StartConfig { - excluded_windows: excluded.into_iter().collect(), + excluded_windows: excluded, ..config }; session.update(cx, |session, cx| session.start(config, cx)); } +/// Open the camera preview bubble (idempotent). Placement mirrors the Tauri +/// default: bottom-right of the main window's display, 100px in from the +/// corner. Position is not persisted yet (deviation; the Tauri app remembers it +/// per monitor). +pub fn open_camera_window(cx: &mut App) { + if cx.global::().camera.is_some() { + return; + } + + let state = crate::store::load().camera_window.unwrap_or_default(); + let (width, height) = camera_window::window_size(&state, None); + + let display = scap_targets::Display::get_containing_cursor() + .unwrap_or_else(scap_targets::Display::primary); + let (x, y) = match display.raw_handle().logical_bounds() { + Some(bounds) => ( + (bounds.position().x() + bounds.size().width() - width as f64 - 100.) as f32, + (bounds.position().y() + bounds.size().height() - height as f64 - 100.) as f32, + ), + None => (100., 100.), + }; + + let handle = cx.open_window( + WindowOptions { + window_bounds: Some(WindowBounds::Windowed(Bounds { + origin: point(px(x), px(y)), + size: size(px(width), px(height)), + })), + titlebar: None, + // Non-activating panel, same as the bar: the bubble is clickable + // without stealing focus from what is being recorded. + kind: WindowKind::PopUp, + focus: false, + show: true, + is_resizable: false, + is_minimizable: false, + window_background: gpui::WindowBackgroundAppearance::Transparent, + ..Default::default() + }, + |window, cx| cx.new(|cx| CameraWindow::new(window, cx)), + ); + + match handle { + Ok(handle) => { + cx.global_mut::().camera = Some(handle); + handle + .update(cx, |_, window, _| { + platform::apply_panel_behavior( + window, + platform::PanelBehavior { + // `set_level(max_level)` in windows.rs -- the same + // `CGWindowLevelForKey(10)` the bar uses. + level: platform::recording_controls_level(), + join_all_spaces: true, + // `.shadow(false)` in the Tauri builder; the shape + // container draws its own look. + shadow: false, + }, + ); + platform::show_window_without_focus(window); + }) + .ok(); + } + Err(error) => tracing::error!("camera window failed to open: {error:#}"), + } +} + +pub fn close_camera_window(cx: &mut App) { + if let Some(handle) = cx.global_mut::().camera.take() { + handle + .update(cx, |_, window, _| window.remove_window()) + .ok(); + } +} + +/// Hand a camera frame to the preview window. Returns false when no window is +/// open (the pump drops the frame and keeps draining). +pub fn deliver_camera_frame(frame: cap_recording::NativeCameraFrame, cx: &mut App) -> bool { + let Some(handle) = cx.global::().camera else { + return false; + }; + handle + .update(cx, |view, window, cx| view.frame_arrived(frame, window, cx)) + .is_ok() +} + +/// Repaint the bar between its own 250ms ticks -- the mic meter updates at +/// ~20Hz and the bar is never the active window. +pub fn refresh_controls_window(cx: &mut App) { + if let Some(handle) = cx.global::().controls { + handle + .update(cx, |_, window, cx| { + cx.notify(); + window.refresh(); + }) + .ok(); + } +} + +fn camera_window_number(cx: &mut App) -> Option { + let handle = cx.global::().camera?; + handle + .update(cx, |_, window, _| platform::window_number(window)) + .ok() + .flatten() +} + /// Where the bar goes, from `fake_window.rs`: centered under a window target /// (48px up from its bottom edge), bottom-center of the recorded display /// otherwise (120px up). diff --git a/apps/desktop-gpui/src/assets.rs b/apps/desktop-gpui/src/assets.rs index 5356984322..56969fc07b 100644 --- a/apps/desktop-gpui/src/assets.rs +++ b/apps/desktop-gpui/src/assets.rs @@ -33,8 +33,10 @@ const FONTS: &[(&str, &[u8])] = assets!("fonts": /// illustration would come out as a filled silhouette. const ICONS: &[(&str, &[u8])] = assets!("icons": "area.svg", + "arrows.svg", "bell.svg", "camera.svg", + "circle.svg", "check.svg", "chevron-down.svg", "circle-help.svg", @@ -47,13 +49,17 @@ const ICONS: &[(&str, &[u8])] = assets!("icons": "instant.svg", "logo-full-dark.svg", "logo-full.svg", + "mic-off.svg", "microphone.svg", "minimize.svg", "more-vertical.svg", "move-left.svg", "pause-circle.svg", + "person-standing.svg", "play-circle.svg", + "rectangle-horizontal.svg", "restart.svg", + "square.svg", "trash.svg", "scan-text.svg", "screen.svg", @@ -112,6 +118,7 @@ mod tests { const ICON_SOURCES: &[&str] = &[ include_str!("main_window.rs"), include_str!("controls_window.rs"), + include_str!("camera_window.rs"), ]; /// Every icon a window asks for must be in the table. gpui draws diff --git a/apps/desktop-gpui/src/camera_window.rs b/apps/desktop-gpui/src/camera_window.rs new file mode 100644 index 0000000000..63cb0e22e0 --- /dev/null +++ b/apps/desktop-gpui/src/camera_window.rs @@ -0,0 +1,645 @@ +//! The camera preview bubble -- `camera.tsx` + `CameraPreviewChrome.tsx`, +//! natively. +//! +//! Layout parity: a transparent window, 56px toolbar strip on top (chrome +//! appears on hover), preview container below it clipped to the shape +//! (round / square / full), four corner resize handles below the toolbar, +//! drag-anywhere to move the window. Frames arrive as CoreVideo pixel buffers +//! from the app-scoped [`CameraFeed`] and are painted zero-copy through gpui's +//! `surface()` element with the same manual cover-fit math the web Canvas uses. +//! +//! Known deviations (also in the README): no mirroring (this gpui rev has no +//! flip transform for surfaces -- the toolbar button is present but disabled), +//! background blur cycles/persists but does not process frames yet, and the +//! window position is not persisted per-monitor. + +use std::time::{Duration, Instant}; + +use gpui::{ + Context, InteractiveElement as _, IntoElement, MouseButton, MouseMoveEvent, MouseUpEvent, + ParentElement as _, Render, StatefulInteractiveElement as _, Styled, Window, div, + prelude::FluentBuilder as _, px, size, svg, +}; + +use crate::{ + feeds::Feeds, + store::{self, BlurMode, CameraShape, CameraWindowState}, + theme::{Appearance, Theme}, +}; + +pub const CAMERA_MIN_SIZE: f32 = 150.; +pub const CAMERA_MAX_SIZE: f32 = 600.; +pub const CAMERA_DEFAULT_SIZE: f32 = 230.; +pub const CAMERA_PRESET_SMALL: f32 = 230.; +pub const CAMERA_PRESET_LARGE: f32 = 400.; +pub const CAMERA_TOOLBAR_HEIGHT: f32 = 56.; +/// `CAMERA_WIDE_ASPECT_RATIO`. +const WIDE_ASPECT: f32 = 16. / 9.; + +pub fn clamp_size(scale: f32) -> f32 { + scale.clamp(CAMERA_MIN_SIZE, CAMERA_MAX_SIZE) +} + +/// `cameraPreviewDimensions`: height is the size; width follows the aspect +/// ratio (1 unless the shape is `full`). +pub fn preview_dimensions(state: &CameraWindowState, frame_aspect: Option) -> (f32, f32) { + let base = clamp_size(state.size); + let aspect = match state.shape { + CameraShape::Full => frame_aspect + .filter(|aspect| aspect.is_finite() && *aspect > 0.) + .map_or(WIDE_ASPECT, |aspect| aspect.max(WIDE_ASPECT)), + _ => 1., + }; + (base * aspect, base) +} + +pub fn window_size(state: &CameraWindowState, frame_aspect: Option) -> (f32, f32) { + let (width, height) = preview_dimensions(state, frame_aspect); + (width, height + CAMERA_TOOLBAR_HEIGHT) +} + +#[cfg(target_os = "macos")] +mod frame { + use std::sync::Arc; + + use cidre::{arc, cv}; + use gpui::RenderImage; + use image::Frame; + use smallvec::smallvec; + + /// Converts camera frames into gpui images. + /// + /// Why not gpui's zero-copy `surface()` element: on this rev its Metal path + /// hard-asserts `420f` (the camera vends `420v`), and surfaces ignore + /// rounded-corner clipping -- fatal for a preview whose default shape is a + /// circle. Images go through the sprite atlas, where the fork explicitly + /// fixed cover-crop corner radii. So: VideoToolbox converts `420v` -> BGRA + /// in hardware, one CPU row-copy lifts it into a [`RenderImage`], and the + /// caller drops the previous image from the atlas each frame. + pub struct FrameConverter { + session: arc::R, + dst: arc::R, + dims: (usize, usize), + } + + // Only touched from the main thread; the CF objects have atomic refcounts. + unsafe impl Send for FrameConverter {} + + unsafe extern "C-unwind" { + // cidre only exposes the planar variants; BGRA is non-planar. + fn CVPixelBufferGetBaseAddress(pixel_buf: *const cv::PixelBuf) -> *const u8; + fn CVPixelBufferGetBytesPerRow(pixel_buf: *const cv::PixelBuf) -> usize; + } + + impl FrameConverter { + fn new(width: usize, height: usize) -> Option { + let mut session = cidre::vt::PixelTransferSession::new().ok()?; + session.set_realtime(true).ok()?; + let dst = cv::PixelBuf::new(width, height, cv::PixelFormat::_32_BGRA, None).ok()?; + Some(Self { + session, + dst, + dims: (width, height), + }) + } + + /// The frame as a BGRA [`RenderImage`]. Recreates the destination + /// buffer when the camera's frame size changes. + pub fn convert( + this: &mut Option, + frame: &cap_recording::NativeCameraFrame, + ) -> Option> { + let src = frame.sample_buf.image_buf()?; + let dims = (src.width(), src.height()); + if this.as_ref().is_none_or(|converter| converter.dims != dims) { + *this = Self::new(dims.0, dims.1); + } + let converter = this.as_mut()?; + + converter.session.transfer(src, &converter.dst).ok()?; + + let (width, height) = dims; + let mut data = vec![0u8; width * height * 4]; + { + // Raw pointer taken before the lock guard: the guard mutably + // borrows `dst`, and the getters only need the CF reference. + let dst_ptr = converter.dst.as_ref() as *const cv::PixelBuf; + let _guard = converter + .dst + .base_address_lock(cv::pixel_buffer::LockFlags::READ_ONLY) + .ok()?; + let base = unsafe { CVPixelBufferGetBaseAddress(dst_ptr) }; + let stride = unsafe { CVPixelBufferGetBytesPerRow(dst_ptr) }; + if base.is_null() || stride < width * 4 { + return None; + } + for row in 0..height { + let src_row = unsafe { std::slice::from_raw_parts(base.add(row * stride), width * 4) }; + data[row * width * 4..(row + 1) * width * 4].copy_from_slice(src_row); + } + } + + // gpui's atlas expects BGRA bytes; `image`'s type is just the + // container. + let buffer = image::RgbaImage::from_raw(width as u32, height as u32, data)?; + Some(Arc::new(RenderImage::new(smallvec![Frame::new(buffer)]))) + } + } +} + +#[derive(Debug, Clone, Copy)] +enum ResizeCorner { + NorthWest, + NorthEast, + SouthWest, + SouthEast, +} + +impl ResizeCorner { + const ALL: [Self; 4] = [ + Self::NorthWest, + Self::NorthEast, + Self::SouthWest, + Self::SouthEast, + ]; + + fn east(self) -> bool { + matches!(self, Self::NorthEast | Self::SouthEast) + } + + fn south(self) -> bool { + matches!(self, Self::SouthWest | Self::SouthEast) + } +} + +struct ResizeDrag { + corner: ResizeCorner, + start_size: f32, + start_position: gpui::Point, +} + +pub struct CameraWindow { + theme: Theme, + state: CameraWindowState, + chrome_visible: bool, + resizing: Option, + #[cfg(target_os = "macos")] + converter: Option, + latest_frame: Option>, + frame_dims: Option<(usize, usize)>, + // Cadence instrumentation: proves the preview stays live while the app is + // inactive (the whole point of the per-frame `window.refresh()`). + frames_in_window: u32, + cadence_window_start: Instant, +} + +impl CameraWindow { + pub fn new(window: &mut Window, _cx: &mut Context) -> Self { + let theme = Theme::new(Appearance::from_window(window.appearance())); + let state = store::load().camera_window.unwrap_or_default(); + Self { + theme, + state, + chrome_visible: false, + resizing: None, + #[cfg(target_os = "macos")] + converter: None, + latest_frame: None, + frame_dims: None, + frames_in_window: 0, + cadence_window_start: Instant::now(), + } + } + + /// Called by the feed pump for every camera frame. `refresh` and not just + /// `notify`: this window is essentially never the active one, and an + /// inactive window repaints only when explicitly asked (unit-2 finding). + pub fn frame_arrived( + &mut self, + frame: cap_recording::NativeCameraFrame, + window: &mut Window, + cx: &mut Context, + ) { + #[cfg(target_os = "macos")] + { + if let Some(image) = frame::FrameConverter::convert(&mut self.converter, &frame) { + let dims = (image.size(0).width.0 as usize, image.size(0).height.0 as usize); + let dims_changed = self.frame_dims != Some(dims); + self.frame_dims = Some(dims); + // Freed explicitly: nothing else evicts per-frame images from + // the sprite atlas, and a 3.7MB 720p frame 25x a second would + // otherwise fill it in seconds. + if let Some(old) = self.latest_frame.replace(image) { + let _ = window.drop_image(old); + } + if dims_changed { + self.apply_window_size(window); + } + } else if self.frame_dims.is_none() && self.frames_in_window == 0 { + tracing::warn!("camera frame could not be converted for preview"); + } + } + #[cfg(not(target_os = "macos"))] + let _ = frame; + + self.frames_in_window += 1; + let elapsed = self.cadence_window_start.elapsed(); + if elapsed >= Duration::from_secs(5) { + tracing::info!( + fps = format!("{:.1}", self.frames_in_window as f64 / elapsed.as_secs_f64()), + "camera preview cadence" + ); + self.frames_in_window = 0; + self.cadence_window_start = Instant::now(); + } + + cx.notify(); + window.refresh(); + } + + fn frame_aspect(&self) -> Option { + self.frame_dims + .map(|(width, height)| width as f32 / height.max(1) as f32) + } + + /// 0..1 across the 150..600 size range -- drives the toolbar scale and the + /// square-shape corner radius, like `cameraToolbarScale` / + /// `cameraBorderRadius`. + fn normalized_size(&self) -> f32 { + (clamp_size(self.state.size) - CAMERA_MIN_SIZE) / (CAMERA_MAX_SIZE - CAMERA_MIN_SIZE) + } + + fn toolbar_scale(&self) -> f32 { + 0.7 + self.normalized_size() * 0.3 + } + + fn preview_radius(&self, preview_height: f32) -> f32 { + match self.state.shape { + CameraShape::Round => preview_height / 2., + // 3rem + normalized * 1.5rem. + _ => 48. + self.normalized_size() * 24., + } + } + + fn mutate_state( + &mut self, + window: &mut Window, + cx: &mut Context, + mutate: impl FnOnce(&mut CameraWindowState), + ) { + mutate(&mut self.state); + self.state.size = clamp_size(self.state.size); + self.apply_window_size(window); + self.persist(cx); + cx.notify(); + } + + fn apply_window_size(&self, window: &mut Window) { + let (width, height) = window_size(&self.state, self.frame_aspect()); + window.resize(size(px(width), px(height))); + } + + fn persist(&self, cx: &mut Context) { + let state = self.state; + cx.background_executor() + .spawn(async move { + store::update(|persisted| persisted.camera_window = Some(state)); + }) + .detach(); + } + + fn close(&mut self, cx: &mut Context) { + // Deselecting the camera is what closes the window (`Feeds` observes + // the selection); mirrors the Tauri close button closing the webview + // and the main window's selection following it. + Feeds::global(cx).update(cx, |feeds, cx| feeds.set_camera(None, cx)); + } + + // Mirrors `ControlButton`'s prop list; a config struct would just rename + // the same nine things. + #[allow(clippy::too_many_arguments)] + fn toolbar_button( + &self, + id: &'static str, + icon: &'static str, + pressed: bool, + enabled: bool, + scale: f32, + label: Option<&'static str>, + cx: &mut Context, + on_click: impl Fn(&mut Self, &mut Window, &mut Context) + 'static, + ) -> impl IntoElement { + let theme = self.theme; + div() + .id(id) + .p(px(8. * scale)) + .rounded(px(8. * scale)) + .when(pressed, |this| { + this.bg(theme.gray_3).text_color(theme.gray_12) + }) + .when(!enabled, |this| this.opacity(0.5)) + .when(enabled, |this| { + this.hover(|style| style.bg(theme.gray_3)) + .on_mouse_down(MouseButton::Left, |_, window, _| { + window.prevent_default(); + }) + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + on_click(this, window, cx); + })) + }) + .occlude() + .child( + div() + .relative() + .child( + svg() + .path(icon) + .size(px(22. * scale)) + .text_color(if pressed { + theme.gray_12.into() + } else { + gpui::Hsla::from(theme.gray_10) + }), + ) + .when_some(label, |this, label| { + this.child( + div() + .absolute() + .bottom(px(-4. * scale)) + .left_0() + .right_0() + .flex() + .justify_center() + .text_size(px(7. * scale)) + .font_weight(gpui::FontWeight::BOLD) + .text_color(theme.gray_12) + .child(label), + ) + }), + ) + } + + fn render_toolbar(&self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let scale = self.toolbar_scale(); + let shape_icon = match self.state.shape { + CameraShape::Round => "icons/circle.svg", + CameraShape::Square => "icons/square.svg", + CameraShape::Full => "icons/rectangle-horizontal.svg", + }; + + div() + .h(px(CAMERA_TOOLBAR_HEIGHT)) + .w_full() + .flex_none() + .flex() + .flex_row() + .justify_center() + .items_center() + .child( + div() + .flex() + .flex_row() + .gap(px(4. * scale)) + .p(px(4. * scale)) + .rounded(px(12. * scale)) + .bg(theme.gray_1) + .border_1() + .border_color(gpui::hsla(0., 0., 1., 0.2)) + .text_color(theme.gray_10) + .when(!self.chrome_visible && self.resizing.is_none(), |this| { + this.invisible() + }) + .child(self.toolbar_button( + "close", + "icons/circle-x.svg", + false, + true, + scale, + None, + cx, + |this, _, cx| this.close(cx), + )) + .child(self.toolbar_button( + "size", + "icons/enlarge.svg", + self.state.size >= CAMERA_PRESET_LARGE, + true, + scale, + None, + cx, + |this, window, cx| { + this.mutate_state(window, cx, |state| { + state.size = if state.size < CAMERA_PRESET_LARGE { + CAMERA_PRESET_LARGE + } else { + CAMERA_PRESET_SMALL + }; + }) + }, + )) + .child(self.toolbar_button( + "shape", + shape_icon, + self.state.shape != CameraShape::Round, + true, + scale, + None, + cx, + |this, window, cx| { + this.mutate_state(window, cx, |state| { + state.shape = match state.shape { + CameraShape::Round => CameraShape::Square, + CameraShape::Square => CameraShape::Full, + CameraShape::Full => CameraShape::Round, + }; + }) + }, + )) + // Mirroring needs a horizontal flip on the painted surface; + // this gpui rev has no such transform. Present but disabled. + .child(self.toolbar_button( + "mirror", + "icons/arrows.svg", + false, + false, + scale, + None, + cx, + |_, _, _| {}, + )) + .child(self.toolbar_button( + "blur", + "icons/person-standing.svg", + self.state.background_blur != BlurMode::Off, + true, + scale, + self.state.background_blur.label(), + cx, + |this, window, cx| { + this.mutate_state(window, cx, |state| { + state.background_blur = state.background_blur.cycle(); + }) + }, + )), + ) + } + + fn render_preview(&self, cx: &mut Context) -> impl IntoElement { + let theme = self.theme; + let (_preview_width, preview_height) = preview_dimensions(&self.state, self.frame_aspect()); + let radius = self.preview_radius(preview_height); + + let mut container = div() + .relative() + .flex_1() + .overflow_hidden() + .rounded(px(radius)) + .bg(theme.gray_1) + .text_color(theme.gray_12); + + if let Some(image) = self.latest_frame.clone() { + use gpui::StyledImage as _; + // Cover-fit through the sprite atlas; the image element's own + // rounding holds under cover-crop (fork-fixed via atlas-tile UVs), + // which is what makes the circular shape actually clip the video. + container = container.child( + gpui::img(image) + .object_fit(gpui::ObjectFit::Cover) + .size_full() + .rounded(px(radius)), + ); + } + + let showing_frame = self.latest_frame.is_some(); + + if !showing_frame { + let message = Feeds::global(cx) + .read(cx) + .camera_error + .clone() + .unwrap_or_else(|| "Loading camera...".into()); + container = container.child( + div() + .size_full() + .flex() + .items_center() + .justify_center() + .text_size(px(14.)) + .text_color(theme.gray_11) + .child(message), + ); + } + + container + } + + fn render_resize_handles(&self, cx: &mut Context) -> impl IntoElement { + let mut layer = div() + .absolute() + .top(px(CAMERA_TOOLBAR_HEIGHT)) + .bottom_0() + .left_0() + .right_0(); + + for corner in ResizeCorner::ALL { + let id: &'static str = match corner { + ResizeCorner::NorthWest => "resize-nw", + ResizeCorner::NorthEast => "resize-ne", + ResizeCorner::SouthWest => "resize-sw", + ResizeCorner::SouthEast => "resize-se", + }; + let mut handle = div().id(id).absolute().size(px(16.)).occlude(); + handle = match corner { + ResizeCorner::NorthWest => handle.top_0().left_0(), + ResizeCorner::NorthEast => handle.top_0().right_0(), + ResizeCorner::SouthWest => handle.bottom_0().left_0(), + ResizeCorner::SouthEast => handle.bottom_0().right_0(), + }; + layer = layer.child(handle.on_mouse_down( + MouseButton::Left, + cx.listener(move |this, event: &gpui::MouseDownEvent, window, cx| { + window.prevent_default(); + cx.stop_propagation(); + this.resizing = Some(ResizeDrag { + corner, + start_size: this.state.size, + start_position: event.position, + }); + cx.notify(); + }), + )); + } + layer + } + + fn handle_resize_move(&mut self, event: &MouseMoveEvent, window: &mut Window, cx: &mut Context) { + let Some(drag) = &self.resizing else { + return; + }; + // `CameraResizeHandles.handleResizeMove`: outward drag on either axis + // grows the square size, diagonal takes the larger delta. + let delta_x = f32::from(event.position.x - drag.start_position.x); + let delta_y = f32::from(event.position.y - drag.start_position.y); + let dx = if drag.corner.east() { delta_x } else { -delta_x }; + let dy = if drag.corner.south() { delta_y } else { -delta_y }; + let delta = dx.max(dy); + let next = clamp_size(drag.start_size + delta); + if (next - self.state.size).abs() > 0.5 { + self.state.size = next; + self.apply_window_size(window); + cx.notify(); + } + } + + fn end_resize(&mut self, cx: &mut Context) { + if self.resizing.take().is_some() { + self.persist(cx); + cx.notify(); + } + } +} + +impl Render for CameraWindow { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let appearance = Appearance::from_window(window.appearance()); + if appearance != self.theme.appearance { + self.theme = Theme::new(appearance); + } + let resizing = self.resizing.is_some(); + + div() + .id("camera-root") + .size_full() + .flex() + .flex_col() + .font_family("Geist") + .on_hover(cx.listener(|this, hovered: &bool, _, cx| { + if this.chrome_visible != *hovered { + this.chrome_visible = *hovered; + cx.notify(); + } + })) + .on_mouse_down( + MouseButton::Left, + cx.listener(|this, _, window, _| { + if this.resizing.is_none() { + window.start_window_move(); + } + }), + ) + .when(resizing, |this| { + this.on_mouse_move(cx.listener(|this, event: &MouseMoveEvent, window, cx| { + this.handle_resize_move(event, window, cx); + })) + .on_mouse_up( + MouseButton::Left, + cx.listener(|this, _: &MouseUpEvent, _, cx| this.end_resize(cx)), + ) + }) + .child(self.render_toolbar(cx)) + .child(self.render_preview(cx)) + .child(self.render_resize_handles(cx)) + } +} diff --git a/apps/desktop-gpui/src/controls_window.rs b/apps/desktop-gpui/src/controls_window.rs index b4a589f56d..560b50dfbb 100644 --- a/apps/desktop-gpui/src/controls_window.rs +++ b/apps/desktop-gpui/src/controls_window.rs @@ -16,6 +16,8 @@ use gpui::{ }; use crate::{ + feeds::{self, Feeds}, + recording::RecordingMode, session::{Phase, RecordingSession}, theme::{Appearance, Theme}, }; @@ -146,30 +148,52 @@ impl ControlsWindow { ) } - /// The mic presence indicator: icon plus the level track underneath. The - /// track is static for now -- the live level channel comes with the - /// app-scoped feeds. - fn render_microphone(&self) -> impl IntoElement { + /// The mic indicator: icon plus the live level track underneath + /// (`createAudioInputLevel` in the bar: -60..0 dB linear, blue-9 fill on a + /// gray-10 track). In instant mode with a live mic it doubles as the mute + /// button -- studio deliberately does not expose mute, since the mic is an + /// editable track and muted spans would bake zeros in. + fn render_microphone(&self, cx: &mut Context) -> impl IntoElement { let theme = self.theme; + let session = self.session.read(cx); + let muted = session.mic_muted; + let can_mute = session.mode() == Some(RecordingMode::Instant) + && session.has_microphone() + && matches!(session.phase, Phase::Recording { .. }); + let level = feeds::bar_level(Feeds::global(cx).read(cx).mic_level_db); + + let icon = if !self.has_microphone || muted { + "icons/mic-off.svg" + } else { + "icons/microphone.svg" + }; + let icon_color: gpui::Hsla = if muted { + theme.red_9.into() + } else if self.has_microphone { + theme.gray_12.into() + } else { + // `IconLucideMicOff text-gray-7`. + Theme::with_alpha(theme.gray_12, 0.35) + }; + div() + .id("microphone") .size(px(32.)) .relative() .flex() .items_center() .justify_center() - .child( - svg() - .path("icons/microphone.svg") - .size(px(20.)) - .text_color(if self.has_microphone { - theme.gray_12.into() - } else { - // `IconLucideMicOff text-gray-7` in the bar; same - // glyph dimmed until a mic-off asset exists. - Theme::with_alpha(theme.gray_12, 0.35) - }), - ) - .when(self.has_microphone, |this| { + .when(can_mute, |this| { + this.rounded(px(8.)) + .hover(|style| style.bg(Theme::with_alpha(theme.gray_12, 0.06))) + .active(|style| style.bg(Theme::with_alpha(theme.gray_12, 0.10))) + .on_click(cx.listener(|this, _, _, cx| { + this.session + .update(cx, |session, cx| session.toggle_mic_mute(cx)); + })) + }) + .child(svg().path(icon).size(px(20.)).text_color(icon_color)) + .when(self.has_microphone && !muted, |this| { this.child( div() .absolute() @@ -178,7 +202,17 @@ impl ControlsWindow { .right(px(4.)) .h(px(2.)) .rounded_full() - .bg(theme.gray_10), + .overflow_hidden() + .bg(theme.gray_10) + .child( + div() + .absolute() + .left_0() + .top_0() + .bottom_0() + .w(gpui::relative(level as f32)) + .bg(theme.blue_9), + ), ) }) } @@ -222,7 +256,7 @@ impl ControlsWindow { .flex_row() .items_center() .gap(px(4.)) - .child(self.render_microphone()) + .child(self.render_microphone(cx)) .child( self.action_button( "pause", diff --git a/apps/desktop-gpui/src/feeds.rs b/apps/desktop-gpui/src/feeds.rs new file mode 100644 index 0000000000..61f83b013b --- /dev/null +++ b/apps/desktop-gpui/src/feeds.rs @@ -0,0 +1,379 @@ +//! App-scoped device feeds -- the gpui counterpart of the Tauri `App` state's +//! `camera_feed` / `mic_feed`. +//! +//! Selecting a camera spins the feed up and opens the preview window +//! immediately, before any recording exists; selecting a microphone keeps a +//! meter running so the pickers and the recording bar can show a live level. +//! A recording then locks the *already running* feeds (`feeds::camera::Lock` / +//! `feeds::microphone::Lock`), exactly like `src-tauri/src/recording.rs`. +//! +//! Threading: kameo actors live on the tokio runtime, so every actor message +//! goes through `gpui_tokio::Tokio::spawn`. Frame and sample pumps await their +//! flume channels on the foreground executor -- each frame has to land on the +//! main thread anyway to repaint, so draining there avoids a second hop. + +use std::time::{Duration, Instant}; + +use cap_recording::feeds::{ + camera::{self, CameraFeed}, + microphone::{self, MicrophoneFeed, MicrophoneSamples}, +}; +use gpui::{App, AppContext as _, Context, Entity, Global}; +use kameo::{Actor as _, actor::ActorRef}; + +pub use cap_recording::feeds::camera::DeviceOrModelID; + +use crate::app_windows; + +/// How the pickers map dB to a 0..1 bar: `DeviceListPanel` in `index.tsx` +/// (`DB_SCALE = 40`, inverted, square-rooted). 1 = silence, 0 = full scale -- +/// the overlay's `right` offset, kept in the same orientation as the web app. +pub fn picker_level(db: f64) -> f64 { + (1.0 - ((db + 40.0).max(0.0) / 40.0)).max(0.0).sqrt() +} + +/// How the recording bar maps dB to its little track: `createAudioInputLevel` +/// in `in-progress-recording.tsx` (-60..0 dB, linear, 0 = silence). +pub fn bar_level(db: f64) -> f64 { + ((db + 60.0) / 60.0).clamp(0.0, 1.0) +} + +pub struct Feeds { + camera_actor: Option>, + mic_actor: Option>, + /// Selected camera; `Some` while the preview window should exist. + pub camera: Option, + /// Selected microphone name. + pub microphone: Option, + /// Rolling 200ms max of the mic level, in dB FS. `-96` when silent/absent. + pub mic_level_db: f64, + pub camera_error: Option, + /// Bumped on every camera/mic selection change; async completions from a + /// previous selection see a stale epoch and drop their result. + camera_epoch: u64, + mic_epoch: u64, + // Channel-holding tasks; dropping them ends the pumps. + _frame_pump: Option>, + _meter_pump: Option>, + // The mic error channel must outlive the stream (see recording.rs). + _mic_errors: Option>, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SelectedCamera { + pub id: DeviceOrModelID, + pub label: String, +} + +struct FeedsGlobal(Entity); +impl Global for FeedsGlobal {} + +impl Feeds { + pub fn init(cx: &mut App) -> Entity { + let feeds = cx.new(|_| Self { + camera_actor: None, + mic_actor: None, + camera: None, + microphone: None, + mic_level_db: -96.0, + camera_error: None, + camera_epoch: 0, + mic_epoch: 0, + _frame_pump: None, + _meter_pump: None, + _mic_errors: None, + }); + cx.set_global(FeedsGlobal(feeds.clone())); + feeds + } + + pub fn global(cx: &App) -> Entity { + cx.global::().0.clone() + } + + /// The live actors, for a recording to lock. `None` when nothing is + /// selected (or the actor died -- `recording::start` falls back to a + /// per-recording feed in that case). + pub fn camera_actor(&self) -> Option> { + self.camera.as_ref()?; + self.camera_actor.clone().filter(|actor| actor.is_alive()) + } + + pub fn mic_actor(&self) -> Option> { + self.microphone.as_ref()?; + self.mic_actor.clone().filter(|actor| actor.is_alive()) + } + + /// Select (or deselect) the camera. Opens/closes the preview window and + /// points the app-scoped feed at the device. + pub fn set_camera(&mut self, selection: Option, cx: &mut Context) { + if self.camera == selection { + return; + } + self.camera_epoch += 1; + let epoch = self.camera_epoch; + self.camera = selection.clone(); + self.camera_error = None; + cx.notify(); + + match selection { + Some(selection) => { + let actor = self.ensure_camera_actor(cx); + let set = gpui_tokio::Tokio::spawn(cx, async move { + let ready = actor + .ask(camera::SetInput { + id: selection.id, + settings: None, + }) + .await + .map_err(|e| e.to_string())?; + ready.await.map_err(|e| e.to_string()) + }); + cx.spawn(async move |this, cx| { + let result = match set.await { + Ok(result) => result.map(|_| ()), + Err(join_error) => Err(join_error.to_string()), + }; + this.update(cx, |this, cx| { + if this.camera_epoch != epoch { + return; + } + if let Err(error) = result { + tracing::error!("camera input failed: {error}"); + this.camera_error = Some(error); + } + cx.notify(); + }) + .ok(); + }) + .detach(); + // Deferred: `open_window` paints the new window's first frame + // synchronously, and that render reads this very entity -- + // opening inside the update is a double-lease panic. + cx.defer(app_windows::open_camera_window); + } + None => { + if let Some(actor) = self.camera_actor.clone() { + gpui_tokio::Tokio::spawn(cx, async move { + let _ = actor.ask(camera::RemoveInput).await; + }) + .detach(); + } + cx.defer(app_windows::close_camera_window); + } + } + } + + /// Select (or deselect) the microphone. The feed keeps running between + /// recordings so the pickers and bar have a live level. + pub fn set_microphone(&mut self, label: Option, cx: &mut Context) { + if self.microphone == label { + return; + } + self.mic_epoch += 1; + self.microphone = label.clone(); + self.mic_level_db = -96.0; + cx.notify(); + + let actor = self.ensure_mic_actor(cx); + match label { + Some(label) => { + gpui_tokio::Tokio::spawn(cx, async move { + match actor.ask(microphone::SetInput { + label: label.clone(), + settings: None, + }) + .await + { + Ok(ready) => { + if let Err(error) = ready.await { + tracing::warn!("microphone '{label}' failed to open: {error}"); + } + } + Err(error) => { + tracing::warn!("microphone '{label}' set-input failed: {error}") + } + } + }) + .detach(); + } + None => { + gpui_tokio::Tokio::spawn(cx, async move { + let _ = actor.ask(microphone::RemoveInput).await; + }) + .detach(); + } + } + } + + fn ensure_camera_actor(&mut self, cx: &mut Context) -> ActorRef { + if let Some(actor) = self.camera_actor.clone() + && actor.is_alive() + { + return actor; + } + + // kameo spawns onto the ambient tokio runtime; this method runs on + // gpui's main thread, so enter the gpui_tokio runtime first or the + // spawn panics (unwind across the objc frame aborts the process). + let actor = { + let _runtime = gpui_tokio::Tokio::handle(cx).enter(); + CameraFeed::spawn(CameraFeed::default()) + }; + + // The preview channel: bounded(4) so a stalled UI drops frames instead + // of ballooning; the pump drains on the main thread and hands each + // frame straight to the camera window. + let (frame_tx, frame_rx) = flume::bounded::(4); + { + let actor = actor.clone(); + gpui_tokio::Tokio::spawn(cx, async move { + if let Err(error) = actor.ask(camera::AddNativeSender(frame_tx)).await { + tracing::error!("attaching camera preview sender: {error}"); + } + }) + .detach(); + } + + let pump = cx.spawn(async move |_this, cx| { + while let Ok(frame) = frame_rx.recv_async().await { + // When no window is open (yet), the frame is dropped and the + // channel keeps draining. + cx.update(|cx| app_windows::deliver_camera_frame(frame, cx)); + } + }); + self._frame_pump = Some(pump); + self.camera_actor = Some(actor.clone()); + actor + } + + fn ensure_mic_actor(&mut self, cx: &mut Context) -> ActorRef { + if let Some(actor) = self.mic_actor.clone() + && actor.is_alive() + { + return actor; + } + + let (error_tx, error_rx) = flume::unbounded(); + self._mic_errors = Some(error_rx); + // Same runtime-entry requirement as the camera actor above. + let actor = { + let _runtime = gpui_tokio::Tokio::handle(cx).enter(); + MicrophoneFeed::spawn(MicrophoneFeed::new(error_tx)) + }; + + // Meter channel, attached once for the actor's lifetime -- the same + // shape as the Tauri `mic_meter_sender`. + let (meter_tx, meter_rx) = flume::bounded::(16); + { + let actor = actor.clone(); + gpui_tokio::Tokio::spawn(cx, async move { + if let Err(error) = actor.ask(microphone::AddSender(meter_tx)).await { + tracing::error!("attaching mic meter sender: {error}"); + } + }) + .detach(); + } + + let pump = cx.spawn(async move |this, cx| { + // Port of `audio_meter.rs`: a 200ms max-hold window, coalesced to + // ~20 updates/s so the meter does not repaint the window per audio + // callback. + let mut window_entries: Vec<(Instant, f64)> = Vec::new(); + let mut last_push = Instant::now() - Duration::from_secs(1); + while let Ok(samples) = meter_rx.recv_async().await { + let now = Instant::now(); + let db = db_fs(&samples); + window_entries.push((now, db)); + window_entries.retain(|(at, _)| now.duration_since(*at) <= Duration::from_millis(200)); + if now.duration_since(last_push) < Duration::from_millis(50) { + continue; + } + last_push = now; + let max = window_entries + .iter() + .map(|(_, db)| *db) + .fold(f64::MIN, f64::max); + if this + .update(cx, |this: &mut Feeds, cx| { + this.mic_level_db = max; + cx.notify(); + }) + .is_err() + { + return; + } + cx.update(app_windows::refresh_controls_window); + } + }); + self._meter_pump = Some(pump); + self.mic_actor = Some(actor.clone()); + actor + } +} + +/// `db_fs` from `src-tauri/src/audio_meter.rs`: peak of the batch as dB FS, +/// clamped to [-96, 0]. +fn db_fs(samples: &MicrophoneSamples) -> f64 { + use cpal::SampleFormat; + + let sample_size = samples.format.sample_size(); + if sample_size == 0 || samples.data.len() < sample_size { + return -96.0; + } + let peak = samples + .data + .chunks_exact(sample_size) + .map(|data| { + let value: f64 = match samples.format { + SampleFormat::I8 => i8::from_ne_bytes([data[0]]) as f64 / i8::MAX as f64, + SampleFormat::U8 => u8::from_ne_bytes([data[0]]) as f64 / u8::MAX as f64 - 0.5, + SampleFormat::I16 => { + i16::from_ne_bytes([data[0], data[1]]) as f64 / i16::MAX as f64 + } + SampleFormat::U16 => { + u16::from_ne_bytes([data[0], data[1]]) as f64 / u16::MAX as f64 - 0.5 + } + SampleFormat::I32 => { + i32::from_ne_bytes([data[0], data[1], data[2], data[3]]) as f64 + / i32::MAX as f64 + } + SampleFormat::U32 => { + u32::from_ne_bytes([data[0], data[1], data[2], data[3]]) as f64 + / u32::MAX as f64 + - 0.5 + } + SampleFormat::F32 => { + f32::from_ne_bytes([data[0], data[1], data[2], data[3]]) as f64 + } + SampleFormat::F64 => f64::from_ne_bytes([ + data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], + ]), + _ => 0.0, + }; + value.abs() + }) + .fold(0.0f64, f64::max); + + (20.0 * peak.log10()).clamp(-96.0, 0.0) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Both level mappings keep the web app's orientation: `picker_level` is 1 + /// at silence (it is the overlay's *right* offset), `bar_level` is 0 at + /// silence (it is the track's fill fraction). + #[test] + fn level_mappings_match_the_web_formulas() { + assert_eq!(picker_level(-96.0), 1.0); + assert_eq!(picker_level(0.0), 0.0); + assert!((picker_level(-20.0) - 0.5f64.sqrt()).abs() < 1e-9); + + assert_eq!(bar_level(-96.0), 0.0); + assert_eq!(bar_level(-60.0), 0.0); + assert_eq!(bar_level(0.0), 1.0); + assert!((bar_level(-30.0) - 0.5).abs() < 1e-9); + } +} diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index 93508545f7..bcb3dd9cf0 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -5,12 +5,15 @@ mod app_windows; mod assets; +mod camera_window; mod controls_window; mod devices; +mod feeds; mod main_window; mod platform; mod recording; mod session; +mod store; mod theme; use gpui::{App, AppContext as _, Bounds, WindowBounds, WindowOptions, px, size}; @@ -53,6 +56,7 @@ fn main() { // 330x395 is what `CapWindowId::Main::min_size` uses in the Tauri app, // and the window is fixed at that size there too. let session = RecordingSession::init(cx); + crate::feeds::Feeds::init(cx); let bounds = Bounds::centered( None, diff --git a/apps/desktop-gpui/src/main_window.rs b/apps/desktop-gpui/src/main_window.rs index 976949682a..6cef1903b6 100644 --- a/apps/desktop-gpui/src/main_window.rs +++ b/apps/desktop-gpui/src/main_window.rs @@ -16,6 +16,7 @@ use gpui::{ use crate::{ MAIN_WINDOW_HEIGHT, MAIN_WINDOW_WIDTH, app_windows, devices::{CameraOption, DeviceSnapshot, DisplayOption, MicrophoneOption, WindowOption}, + feeds::{self, Feeds}, recording, session::{Phase, RecordingSession}, theme::{Appearance, Theme}, @@ -219,6 +220,23 @@ impl MainWindow { let theme = Theme::new(Appearance::from_window(window.appearance())); cx.observe(&session, |_, _, cx| cx.notify()).detach(); + // Track the app-scoped feeds: the camera bubble's close button + // deselects the camera there, and this window's selection has to + // follow. Repaints are gated to what is actually visible -- the mic + // meter notifies at ~20Hz and would otherwise repaint the home view + // for a level bar only the microphone picker shows. + let feeds = Feeds::global(cx); + cx.observe(&feeds, |this: &mut Self, feeds, cx| { + let feeds = feeds.read(cx); + if this.camera.is_some() && feeds.camera.is_none() { + this.camera = None; + cx.notify(); + } else if matches!(this.panel, Some(Panel::Device(_))) { + cx.notify(); + } + }) + .detach(); + // Enumeration hits AVFoundation and the window server, so it must not // run on the main thread -- doing it inline here costs ~180ms of a // blank window on this machine, and more on a machine with more @@ -270,6 +288,26 @@ impl MainWindow { ); this.devices = snapshot; this.enumerating = false; + + // `CAP_GPUI_AUTO_CAMERA=1`: select the first camera the way a + // click would -- the automated check drives the preview window + // this way because synthetic clicks are dropped. + if std::env::var("CAP_GPUI_AUTO_CAMERA").is_ok_and(|v| v == "1") + && this.camera.is_none() + && let Some(first) = this.devices.cameras.first().cloned() + { + tracing::info!(camera = %first.label, "auto-selecting camera"); + this.camera = Some(first.clone()); + Feeds::global(cx).update(cx, |feeds, cx| { + feeds.set_camera( + Some(feeds::SelectedCamera { + id: recording::DeviceOrModelID::DeviceID(first.device_id.clone()), + label: first.label, + }), + cx, + ) + }); + } cx.notify(); }) .unwrap_or_else(|error| tracing::error!("device enumeration update failed: {error:#}")); @@ -414,7 +452,7 @@ impl MainWindow { }) .await; if this - .update_in(cx, |this, window, cx| { + .update_in(cx, |this, _window, cx| { this.microphone = default_mic .and_then(|name| { this.devices @@ -426,13 +464,29 @@ impl MainWindow { .or_else(|| this.devices.microphones.first().cloned()); if let Some(mic) = &this.microphone { tracing::info!(mic = %mic.name, "auto-record microphone"); + // Through the app-scoped feed, so the automated run + // exercises the same lock path a clicked selection uses. + let name = mic.name.clone(); + Feeds::global(cx) + .update(cx, |feeds, cx| feeds.set_microphone(Some(name), cx)); } - this.start_recording(window, cx) }) .is_err() { return; } + // Give the app-scoped feed a moment to connect its input; locking + // an input-less feed would fall back to a per-recording one and + // dodge the path this harness exists to exercise. + cx.background_executor() + .timer(std::time::Duration::from_millis(1500)) + .await; + if this + .update_in(cx, |this, window, cx| this.start_recording(window, cx)) + .is_err() + { + return; + } let third = std::time::Duration::from_secs(record_secs.div_ceil(3)); let toggle = |this: &gpui::WeakEntity, @@ -484,6 +538,11 @@ impl MainWindow { return; } + let (camera_feed, mic_feed) = { + let feeds = Feeds::global(cx); + let feeds = feeds.read(cx); + (feeds.camera_actor(), feeds.mic_actor()) + }; let config = recording::StartConfig { mode, target, @@ -494,6 +553,8 @@ impl MainWindow { .map(|camera| recording::DeviceOrModelID::DeviceID(camera.device_id.clone())), system_audio: self.system_audio, excluded_windows: Vec::new(), + camera_feed, + mic_feed, }; cx.defer(move |cx: &mut gpui::App| app_windows::begin_recording(config, cx)); @@ -1058,10 +1119,18 @@ impl MainWindow { DeviceMenu::Camera => self.camera.is_none(), DeviceMenu::Microphone => self.microphone.is_none(), }, + None, cx.listener(move |this, _, _window, cx| { match menu { - DeviceMenu::Camera => this.camera = None, - DeviceMenu::Microphone => this.microphone = None, + DeviceMenu::Camera => { + this.camera = None; + Feeds::global(cx).update(cx, |feeds, cx| feeds.set_camera(None, cx)); + } + DeviceMenu::Microphone => { + this.microphone = None; + Feeds::global(cx) + .update(cx, |feeds, cx| feeds.set_microphone(None, cx)); + } } this.close_panel(cx); }), @@ -1092,8 +1161,20 @@ impl MainWindow { camera.label.clone(), camera.best_format.map(|format| format.describe()), selected, + None, cx.listener(move |this, _, _window, cx| { this.camera = Some(chosen.clone()); + Feeds::global(cx).update(cx, |feeds, cx| { + feeds.set_camera( + Some(feeds::SelectedCamera { + id: recording::DeviceOrModelID::DeviceID( + chosen.device_id.clone(), + ), + label: chosen.label.clone(), + }), + cx, + ) + }); this.close_panel(cx); }), ) @@ -1122,8 +1203,14 @@ impl MainWindow { mic.name.clone(), mic.describe(), selected, + selected.then(|| { + feeds::picker_level(Feeds::global(cx).read(cx).mic_level_db) + }), cx.listener(move |this, _, _window, cx| { this.microphone = Some(chosen.clone()); + Feeds::global(cx).update(cx, |feeds, cx| { + feeds.set_microphone(Some(chosen.name.clone()), cx) + }); this.close_panel(cx); }), ) @@ -1433,6 +1520,8 @@ impl MainWindow { /// Selection is `bg-blue-500` with white text -- note that is the custom /// `--blue-500`, not `blue-9` used by the pills; the two are different /// colours. + // Mirrors the web list-item's prop list; a struct would rename, not reduce. + #[allow(clippy::too_many_arguments)] fn render_device_list_row( &self, id: SharedString, @@ -1440,6 +1529,10 @@ impl MainWindow { label: String, detail: Option, selected: bool, + // `MicrophoneListItem`'s live level wash: a white 25% overlay whose + // right edge sits at `level * 100%` (1 = silence, the web app's + // orientation). Only the selected microphone row gets one. + audio_level: Option, on_click: impl Fn(&gpui::ClickEvent, &mut Window, &mut gpui::App) + 'static, ) -> gpui::Stateful { let theme = self.theme; @@ -1459,6 +1552,8 @@ impl MainWindow { div() .id(id) + .relative() + .overflow_hidden() .flex() .flex_col() .gap(px(2.)) @@ -1470,6 +1565,18 @@ impl MainWindow { .text_color(foreground) .when(selected, |this| this.bg(theme.blue_500)) .when(!selected, |this| this.hover(|style| style.bg(theme.gray_4))) + .when_some(audio_level.filter(|_| selected), |this, level| { + this.child( + div() + .absolute() + .top_0() + .bottom_0() + .left_0() + .right(gpui::relative(level.clamp(0., 1.) as f32)) + .rounded(px(8.)) + .bg(gpui::hsla(0., 0., 1., 0.25)), + ) + }) .child( div() .flex() diff --git a/apps/desktop-gpui/src/recording.rs b/apps/desktop-gpui/src/recording.rs index dfc91ddc12..a82d934ba7 100644 --- a/apps/desktop-gpui/src/recording.rs +++ b/apps/desktop-gpui/src/recording.rs @@ -38,9 +38,13 @@ pub struct StartConfig { pub microphone: Option, pub camera: Option, pub system_audio: bool, - /// Our own windows (the recording controls bar), excluded from capture the - /// way the Tauri app excludes its bar. + /// Our own windows (the recording controls bar; the camera bubble in studio + /// mode), excluded from capture the way the Tauri app excludes them. pub excluded_windows: Vec, + /// The app-scoped feed actors (running previews/meters). When present a + /// recording locks these instead of spawning its own -- the Tauri model. + pub camera_feed: Option>, + pub mic_feed: Option>, } enum Handle { @@ -55,8 +59,12 @@ enum Handle { pub struct ActiveRecording { handle: Handle, pub project_dir: PathBuf, + /// Recording-scoped mic mute (payload zeroing at the consumer seam; the + /// stream cadence is unaffected). `None` when the recording has no mic. + pub mic_mute: Option>, // Held for the duration of the recording: dropping an ActorRef early would - // stop the feed under the pipeline. + // stop the feed under the pipeline. Only populated by the per-recording + // fallback path; app-scoped feeds are owned by `Feeds`. _mic_feed: Option>, _camera_feed: Option>, // The mic error channel must outlive the stream or error sends panic the @@ -214,6 +222,7 @@ pub async fn start(config: StartConfig) -> anyhow::Result { tracing::warn!("start failed on the microphone path, retrying without: {error:#}"); start_attempt(StartConfig { microphone: None, + mic_feed: None, ..config }) .await @@ -226,40 +235,55 @@ async fn start_attempt(config: StartConfig) -> anyhow::Result { let project_dir = create_project_dir(&config.target)?; tracing::info!(dir = %project_dir.display(), "starting recording"); - // Feed actors are per-recording here, where the Tauri app keeps app-wide - // ones (it needs them for previews and level meters between recordings). - // Once the camera preview window exists this moves to app scope too. + // The app-scoped feeds (running previews/meters, owned by `Feeds`) are + // locked in place when available -- the Tauri model. The per-recording + // spawn below is the fallback for a feed that died between selection and + // start. // A microphone that enumerates but fails to open (Bluetooth profile // switch, a Continuity iPhone that wandered off) must not kill the whole // recording -- degrade to no-mic, the way the Tauri app's app-scoped feed // surfaces "Not connected" and records on. - let (mic_feed, mic_lock, mic_errors) = match &config.microphone { - Some(label) => match setup_microphone(label).await { + let (mic_feed, mic_lock, mic_errors) = match (&config.mic_feed, &config.microphone) { + (Some(actor), Some(label)) => match actor.ask(microphone::Lock).await { + Ok(lock) => (None, Some(Arc::new(lock)), None), + Err(error) => { + tracing::warn!("app mic feed lock failed ({error}), spawning one for '{label}'"); + match setup_microphone(label).await { + Ok((feed, lock, error_rx)) => (Some(feed), Some(lock), Some(error_rx)), + Err(error) => { + tracing::warn!( + "microphone '{label}' unavailable, recording without: {error:#}" + ); + (None, None, None) + } + } + } + }, + (None, Some(label)) => match setup_microphone(label).await { Ok((feed, lock, error_rx)) => (Some(feed), Some(lock), Some(error_rx)), Err(error) => { tracing::warn!("microphone '{label}' unavailable, recording without: {error:#}"); (None, None, None) } }, - None => (None, None, None), + _ => (None, None, None), }; let (camera_feed, camera_lock) = match &config.camera { Some(id) => { - let feed = CameraFeed::spawn(CameraFeed::default()); - let ready = feed - .ask(camera::SetInput { - id: id.clone(), - settings: None, - }) - .await - .map_err(|e| anyhow!("camera setup: {e}"))?; - ready.await.map_err(|e| anyhow!("camera init: {e}"))?; - let lock = feed - .ask(camera::Lock) - .await - .map_err(|e| anyhow!("camera lock: {e}"))?; - (Some(feed), Some(Arc::new(lock))) + if let Some(actor) = &config.camera_feed { + match actor.ask(camera::Lock).await { + Ok(lock) => (None, Some(Arc::new(lock))), + Err(error) => { + tracing::warn!("app camera feed lock failed ({error}), spawning one"); + let (feed, lock) = setup_camera(id).await?; + (Some(feed), Some(Arc::new(lock))) + } + } + } else { + let (feed, lock) = setup_camera(id).await?; + (Some(feed), Some(Arc::new(lock))) + } } None => { if matches!(config.target, ScreenCaptureTarget::CameraOnly) { @@ -271,6 +295,10 @@ async fn start_attempt(config: StartConfig) -> anyhow::Result { } }; + let mic_mute = mic_lock + .as_ref() + .map(|lock| lock.recording_muted_handle()); + // ScreenCaptureKit content, exactly as `read_recording_shareable_content` // does it: the current-process fallback covers the sandboxed case where the // full query returns no displays. @@ -328,12 +356,35 @@ async fn start_attempt(config: StartConfig) -> anyhow::Result { Ok(ActiveRecording { handle, project_dir, + mic_mute, _mic_feed: mic_feed, _camera_feed: camera_feed, _mic_errors: mic_errors, }) } +async fn setup_camera( + id: &DeviceOrModelID, +) -> anyhow::Result<( + ActorRef, + cap_recording::feeds::camera::CameraFeedLock, +)> { + let feed = CameraFeed::spawn(CameraFeed::default()); + let ready = feed + .ask(camera::SetInput { + id: id.clone(), + settings: None, + }) + .await + .map_err(|e| anyhow!("camera setup: {e}"))?; + ready.await.map_err(|e| anyhow!("camera init: {e}"))?; + let lock = feed + .ask(camera::Lock) + .await + .map_err(|e| anyhow!("camera lock: {e}"))?; + Ok((feed, lock)) +} + async fn setup_microphone( label: &str, ) -> anyhow::Result<( diff --git a/apps/desktop-gpui/src/session.rs b/apps/desktop-gpui/src/session.rs index 4fcb5c498d..c77ba1a683 100644 --- a/apps/desktop-gpui/src/session.rs +++ b/apps/desktop-gpui/src/session.rs @@ -32,6 +32,10 @@ pub struct RecordingSession { /// True while the controls bar window is open, so the main window knows to /// fall back to its in-window overlay when the bar failed to open. pub controls_open: bool, + /// Mirror of the recording-scoped mic mute flag (the engine zeroes the + /// payloads; the flag itself lives on the live recording's mic lock and + /// resets with every new session). + pub mic_muted: bool, started_at: Option, paused_accum: Duration, paused_since: Option, @@ -48,6 +52,7 @@ impl RecordingSession { error: None, last_config: None, controls_open: false, + mic_muted: false, started_at: None, paused_accum: Duration::ZERO, paused_since: None, @@ -78,6 +83,34 @@ impl RecordingSession { matches!(self.phase, Phase::Recording { paused: true }) } + /// The live (or last) recording's mode -- the bar exposes mic mute for + /// instant recordings only (studio records the mic as an editable track, + /// where muted spans would silently bake zeros in). + pub fn mode(&self) -> Option { + self.last_config.as_ref().map(|config| config.mode) + } + + /// Whether the live recording actually has a microphone attached. + pub fn has_microphone(&self) -> bool { + self.active + .as_ref() + .is_some_and(|active| active.mic_mute.is_some()) + } + + /// Flip the recording-scoped mic mute. No-op without a live mic. + pub fn toggle_mic_mute(&mut self, cx: &mut Context) { + let Some(mute) = self + .active + .as_ref() + .and_then(|active| active.mic_mute.clone()) + else { + return; + }; + self.mic_muted = !self.mic_muted; + mute.store(self.mic_muted, std::sync::atomic::Ordering::Relaxed); + cx.notify(); + } + pub fn set_controls_open(&mut self, open: bool, cx: &mut Context) { if self.controls_open != open { self.controls_open = open; @@ -103,6 +136,8 @@ impl RecordingSession { tracing::info!(dir = %active.project_dir.display(), "recording started"); this.active = Some(active); this.phase = Phase::Recording { paused: false }; + // A fresh mic lock always starts unmuted. + this.mic_muted = false; this.started_at = Some(Instant::now()); this.paused_accum = Duration::ZERO; this.paused_since = None; @@ -258,6 +293,7 @@ impl RecordingSession { fn finish(&mut self, cx: &mut Context) { self.phase = Phase::Idle; + self.mic_muted = false; self.started_at = None; self.paused_accum = Duration::ZERO; self.paused_since = None; diff --git a/apps/desktop-gpui/src/store.rs b/apps/desktop-gpui/src/store.rs new file mode 100644 index 0000000000..09f01061c5 --- /dev/null +++ b/apps/desktop-gpui/src/store.rs @@ -0,0 +1,110 @@ +//! Tiny persisted state for this app. +//! +//! The Tauri app keeps the camera window's chrome state in the webview's +//! `localStorage` (`cameraWindowState`); there is no webview here, so the same +//! shape lives in a JSON file next to the Tauri store (`gpui-state.json` inside +//! `so.cap.desktop`'s app-data dir). Reads happen once at open; writes are +//! whole-file rewrites on a background thread -- the state is a handful of +//! scalars, atomicity beyond rename is not worth plumbing. + +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +/// `CameraPreviewShape` in the web app. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum CameraShape { + #[default] + Round, + Square, + Full, +} + +/// `BackgroundBlurMode`. Cycled and persisted for parity; the effects pipeline +/// itself is not wired yet. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum BlurMode { + #[default] + Off, + Light, + Heavy, +} + +impl BlurMode { + pub fn cycle(self) -> Self { + match self { + Self::Off => Self::Light, + Self::Light => Self::Heavy, + Self::Heavy => Self::Off, + } + } + + /// The tiny label under the person glyph: `Light` / `Heavy`, nothing when + /// off. + pub fn label(self) -> Option<&'static str> { + match self { + Self::Off => None, + Self::Light => Some("Light"), + Self::Heavy => Some("Heavy"), + } + } +} + +/// `CameraWindowState` from `CameraPreviewChrome.tsx`, minus `mirrored` (no +/// flip transform exists in this gpui rev; see README). +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct CameraWindowState { + pub size: f32, + pub shape: CameraShape, + pub mirrored: bool, + pub background_blur: BlurMode, +} + +impl Default for CameraWindowState { + fn default() -> Self { + Self { + size: crate::camera_window::CAMERA_DEFAULT_SIZE, + shape: CameraShape::Round, + mirrored: false, + background_blur: BlurMode::Off, + } + } +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct PersistedState { + pub camera_window: Option, +} + +fn state_path() -> PathBuf { + PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".into())) + .join("Library/Application Support/so.cap.desktop/gpui-state.json") +} + +pub fn load() -> PersistedState { + std::fs::read(state_path()) + .ok() + .and_then(|bytes| serde_json::from_slice(&bytes).ok()) + .unwrap_or_default() +} + +/// Read-modify-write on the caller's thread; callers hand this to the +/// background executor. +pub fn update(mutate: impl FnOnce(&mut PersistedState)) { + let path = state_path(); + let mut state = load(); + mutate(&mut state); + if let Some(parent) = path.parent() { + let _ = std::fs::create_dir_all(parent); + } + match serde_json::to_vec_pretty(&state) { + Ok(bytes) => { + if let Err(error) = std::fs::write(&path, bytes) { + tracing::warn!("persisting gpui state: {error}"); + } + } + Err(error) => tracing::warn!("serializing gpui state: {error}"), + } +} From b0554d2edf831771222cb1124e6eef4787924384 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:43:05 +0100 Subject: [PATCH 018/146] docs(desktop-gpui): camera preview, feeds, and the macOS 26 display-link finding --- apps/desktop-gpui/README.md | 50 ++++++++++++++++-- apps/desktop-gpui/docs/camera-full.png | Bin 0 -> 496454 bytes apps/desktop-gpui/docs/camera-round.png | Bin 0 -> 243790 bytes apps/desktop-gpui/docs/camera-square.png | Bin 0 -> 278822 bytes .../docs/controls-bar-live-timer.png | Bin 0 -> 20274 bytes 5 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 apps/desktop-gpui/docs/camera-full.png create mode 100644 apps/desktop-gpui/docs/camera-round.png create mode 100644 apps/desktop-gpui/docs/camera-square.png create mode 100644 apps/desktop-gpui/docs/controls-bar-live-timer.png diff --git a/apps/desktop-gpui/README.md b/apps/desktop-gpui/README.md index 0eb690e209..7c8bf1d766 100644 --- a/apps/desktop-gpui/README.md +++ b/apps/desktop-gpui/README.md @@ -115,8 +115,8 @@ Sizes are the Tauri app's, from `apps/desktop/src-tauri/src/windows.rs`. | Window | Size | Status | |---|---|---| | Main | 330×395 / 600×660 | **Done** — layout, devices, pickers, modes, recording, level-100 panel behavior | -| Camera preview | 460×460 | Not started — needs the camera feed | -| Recording controls | 320×150 | **Done** — timer, pause/resume, restart, delete, mic indicator, drag; capture-excluded, non-activating | +| Camera preview | size×(size+56), 150–600 | **Done** — live frames, round/square/full shapes, S/L sizes, hover toolbar, corner resize, drag, persisted chrome state, capture-excluded in studio / included in instant | +| Recording controls | 320×150 | **Done** — live timer, pause/resume, restart, delete, live mic level, instant-mode mute, drag; capture-excluded, non-activating | | Target select overlay | per display | Not started — one transparent window per display | | Window capture occluder | per display | Not started | | Capture area | per display | Not started | @@ -157,10 +157,8 @@ Recording-specific deviations: |---|---| | **TEMP start button** | The real app starts recording from the fullscreen target-select overlay, which does not exist yet. An armed target (Display selected, or a concrete window picked) shows a pinned blue Start Recording footer instead. | | **Bar timer, no countdown** | The countdown variant (`window.COUNTDOWN`) needs the settings store; the bar goes straight from Starting to the timer. | -| **Mic level is static** | The level track under the mic icon renders, but live levels need the app-scoped feed actors (same work as the camera preview). Mute toggle likewise. | | **Settings button is inert** | The bar's recording-settings popover menu is not built. | | **Area does not arm** | Area needs the selection overlay. | -| **Per-recording feed actors** | The Tauri app keeps app-wide `MicrophoneFeed`/`CameraFeed` actors for previews and level meters; here they are spawned per recording and dropped after. Moves to app scope with the camera preview window. | | **No overlay blur** | The recording overlay is `bg-gray-1/80` without `backdrop-blur-xs`; this gpui rev has no per-element backdrop blur hook. | | **Camera id is DeviceID-only** | The Tauri app persists `ModelID` when a camera advertises one, so the same camera survives re-plugging into a different port. | | **Defaults are the builder's** | `desktop_recording_defaults` (studio quality, fps caps, custom cursor) is not applied yet — it reads the Tauri settings store. | @@ -170,6 +168,50 @@ drives a start/stop through the button code paths — the end-to-end check uses it because unprivileged synthetic clicks are dropped. Add `CAP_GPUI_AUTO_PAUSE=1` to pause for the middle third: two segments whose summed duration is ~⅔ of wall time is the proof the pause reached the engine. +`CAP_GPUI_AUTO_CAMERA=1` selects the first camera at startup the way a click +would, opening the preview bubble; combined with `CAP_GPUI_AUTO_RECORD` it +verifies the camera track end to end. + +## Camera preview and app-scoped feeds + +Selecting a camera spins up an app-scoped `CameraFeed` actor and opens the +preview bubble immediately, before any recording — the Tauri model. The same +applies to the microphone: an app-scoped `MicrophoneFeed` keeps a meter +running, which is what feeds the live level bar in the mic picker rows and on +the recording bar. Starting a recording locks the already-running feeds +(`feeds::camera::Lock` / `feeds::microphone::Lock`), so the preview never +stutters across a start, and the bar's mute button (instant mode only, like +the real app) flips the recording-scoped payload-zeroing mute on the mic lock. + +Frames arrive as `420v` CoreMedia sample buffers on a bounded flume channel. +gpui's zero-copy `surface()` element turned out to be unusable for this on the +pinned rev — its Metal path hard-asserts `420f`, and surface primitives ignore +rounded-corner clipping (fatal when the default shape is a circle). Instead +VideoToolbox converts `420v` → BGRA in hardware, one row-copy lifts the frame +into a gpui `RenderImage`, and the previous frame's image is explicitly +dropped from the sprite atlas each frame. Cover-fit and the circular clip both +hold on the image path. Camera-window deviations: no mirroring (no flip +transform exists in this gpui rev — the toolbar button is present but +disabled), background blur cycles and persists but does not process frames +(the `cap-camera-effects` pipeline is its own unit), the window position is +not persisted per-monitor, and chrome state persists to `gpui-state.json` +next to the Tauri store rather than `localStorage`. + +### The macOS 26 display-link fix + +The single most important platform finding so far: on macOS 26, +`NSWindow.occlusionState` reports visible windows with an undocumented bit +(`0x2000`) instead of the documented `NSWindowOcclusionStateVisible` (`0x2`) +— and gpui only starts a window's CVDisplayLink when it sees `0x2`. Result: +**no window in this app ever received frame callbacks** — first paint, then +frozen. Every earlier "inactive repaint" workaround (250ms `refresh()` ticks) +was painting nothing; the bar's timer sat on "Starting" through entire +recordings. `platform::install_occlusion_shim` overrides `occlusionState` on +gpui's own window classes to OR the documented bit back in whenever AppKit +reports any visibility, and `apply_panel_behavior` re-fires the occlusion +handler so the link starts. With it, the bar timer ticks and the camera +renders at capture cadence while another app is frontmost. Remove the shim +when the gpui pin understands the macOS 26 bit. Running from a dev build needs the ffmpeg dylibs the binary's install names point at (`@executable_path/../Frameworks/Spacedrive.framework/...`): diff --git a/apps/desktop-gpui/docs/camera-full.png b/apps/desktop-gpui/docs/camera-full.png new file mode 100644 index 0000000000000000000000000000000000000000..f8cdd6beb94c525153a34088a5674b41d6641640 GIT binary patch literal 496454 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�q*do*C=RNz!6GFAg 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%0Al)9nR9}l|Hb8gPMH>^7 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