diff --git a/Cargo.lock b/Cargo.lock index 97bceb9bfa..7af6d4b4da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -329,11 +329,11 @@ dependencies = [ "clipboard-win", "image 0.25.8", "log", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", "windows-sys 0.60.2", @@ -861,11 +861,11 @@ dependencies = [ [[package]] name = "block2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "objc2 0.6.2", + "objc2 0.6.3", ] [[package]] @@ -1167,7 +1167,7 @@ name = "cap-cursor-info" version = "0.0.0" dependencies = [ "hex", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "serde", "sha2", @@ -1186,6 +1186,7 @@ dependencies = [ "async-stream", "axum", "base64 0.22.1", + "block2 0.6.2", "bytemuck", "bytes", "cap-audio", @@ -1202,12 +1203,10 @@ dependencies = [ "chrono", "cidre", "clipboard-rs", - "cocoa", - "core-foundation 0.10.1", - "core-graphics 0.24.0", "cpal 0.15.3 (git+https://github.com/CapSoftware/cpal?rev=3cc779a7b4ca)", "device_query", "dirs", + "dispatch2", "dotenvy_macro", "ffmpeg-next", "flume", @@ -1222,8 +1221,12 @@ dependencies = [ "lz4_flex", "md5", "nix 0.29.0", - "objc", + "objc2 0.6.3", "objc2-app-kit", + "objc2-application-services", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", "opentelemetry", "opentelemetry-otlp", "opentelemetry_sdk", @@ -1592,7 +1595,7 @@ dependencies = [ "image 0.25.8", "log", "metal 0.31.0", - "objc2 0.6.2", + "objc2 0.6.3", "pretty_assertions", "rayon", "reactive_graph", @@ -1873,9 +1876,9 @@ checksum = "afede46921767868c5c7f8f55202bdd8bec0bab6bc9605174200f45924f93c62" dependencies = [ "clipboard-win", "image 0.25.8", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "windows 0.59.0", "x11rb", ] @@ -2648,9 +2651,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ "bitflags 2.9.4", - "block2 0.6.1", + "block2 0.6.2", "libc", - "objc2 0.6.2", + "objc2 0.6.3", ] [[package]] @@ -3670,7 +3673,7 @@ checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7" dependencies = [ "crossbeam-channel", "keyboard-types", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "once_cell", "serde", @@ -5002,8 +5005,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "119c8490084af61b44c9eda9d626475847a186737c0378c85e32d77c33a01cd4" dependencies = [ "cc", - "objc2 0.6.2", - "objc2-foundation 0.3.1", + "objc2 0.6.3", + "objc2-foundation 0.3.2", "time", ] @@ -5316,10 +5319,10 @@ dependencies = [ "dpi", "gtk", "keyboard-types", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "once_cell", "png 0.17.16", "serde", @@ -5663,9 +5666,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" dependencies = [ "objc2-encode", "objc2-exception-helper", @@ -5673,21 +5676,38 @@ dependencies = [ [[package]] name = "objc2-app-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.9.4", - "block2 0.6.1", + "block2 0.6.2", "libc", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-cloud-kit", "objc2-core-data", "objc2-core-foundation", "objc2-core-graphics", "objc2-core-image", - "objc2-foundation 0.3.1", - "objc2-quartz-core 0.3.1", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "objc2-application-services" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69282c2b5bc58fba07cb9de2113619532eb551e98efe3d8d695509ef45fbd53b" +dependencies = [ + "bitflags 2.9.4", + "libc", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-services", + "objc2-foundation 0.3.2", ] [[package]] @@ -5697,17 +5717,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e085a2e16c61dadbad7a808fc9d5b5f8472b1b825b53d529c9f64ccac78e722" dependencies = [ "bitflags 2.9.4", - "block2 0.6.1", + "block2 0.6.2", "dispatch2", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-avf-audio", "objc2-core-foundation", "objc2-core-graphics", "objc2-core-image", "objc2-core-media", "objc2-core-video", - "objc2-foundation 0.3.1", - "objc2-quartz-core 0.3.1", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", ] [[package]] @@ -5716,19 +5736,19 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfc1d11521c211a7ebe17739fc806719da41f56c6b3f949d9861b459188ce910" dependencies = [ - "objc2 0.6.2", - "objc2-foundation 0.3.1", + "objc2 0.6.3", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-cloud-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-foundation 0.3.1", + "objc2 0.6.3", + "objc2-foundation 0.3.2", ] [[package]] @@ -5738,7 +5758,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca44961e888e19313b808f23497073e3f6b3c22bb485056674c8b49f3b025c82" dependencies = [ "dispatch2", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-audio-types", "objc2-core-foundation", ] @@ -5750,52 +5770,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0f1cc99bb07ad2ddb6527ddf83db6a15271bb036b3eb94b801cd44fdc666ee1" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", + "objc2 0.6.3", ] [[package]] name = "objc2-core-data" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-foundation 0.3.1", + "objc2 0.6.3", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-core-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.9.4", + "block2 0.6.2", "dispatch2", - "objc2 0.6.2", + "libc", + "objc2 0.6.3", ] [[package]] name = "objc2-core-graphics" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.9.4", + "block2 0.6.2", "dispatch2", - "objc2 0.6.2", + "libc", + "objc2 0.6.3", "objc2-core-foundation", "objc2-io-surface", + "objc2-metal 0.3.2", ] [[package]] name = "objc2-core-image" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" dependencies = [ - "objc2 0.6.2", - "objc2-foundation 0.3.1", + "objc2 0.6.3", + "objc2-foundation 0.3.2", ] [[package]] @@ -5806,21 +5831,45 @@ checksum = "f0b7afa6822e2fa20dfc88d10186b2432bf8560b5ed73ec9d31efd78277bc878" dependencies = [ "bitflags 2.9.4", "dispatch2", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-audio", "objc2-core-audio-types", "objc2-core-foundation", "objc2-core-video", ] +[[package]] +name = "objc2-core-services" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583300ad934cba24ff5292aee751ecc070f7ca6b39a574cc21b7b5e588e06a0b" +dependencies = [ + "dispatch2", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-security", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", +] + [[package]] name = "objc2-core-video" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1989c3e76c7e978cab0ba9e6f4961cd00ed14ca21121444cc26877403bfb6303" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-foundation", "objc2-core-graphics", "objc2-io-surface", @@ -5855,25 +5904,25 @@ dependencies = [ [[package]] name = "objc2-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.9.4", - "block2 0.6.1", + "block2 0.6.2", "libc", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-foundation", ] [[package]] name = "objc2-io-surface" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-foundation", ] @@ -5883,7 +5932,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9052cb1bb50a4c161d934befcf879526fb87ae9a68858f241e693ca46225cf5a" dependencies = [ - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-foundation", ] @@ -5899,6 +5948,17 @@ dependencies = [ "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.9.4", + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-osa-kit" version = "0.3.1" @@ -5906,9 +5966,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26bb88504b5a050dbba515d2414607bf5e57dd56b107bc5f0351197a3e7bdc5d" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", ] [[package]] @@ -5921,28 +5981,28 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal", + "objc2-metal 0.2.2", ] [[package]] name = "objc2-quartz-core" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", - "objc2-foundation 0.3.1", + "objc2 0.6.3", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-security" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f8e0ef3ab66b08c42644dcb34dba6ec0a574bbd8adbb8bdbdc7a2779731a44" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-foundation", ] @@ -5953,9 +6013,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", ] [[package]] @@ -5965,11 +6025,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" dependencies = [ "bitflags 2.9.4", - "block2 0.6.1", - "objc2 0.6.2", + "block2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "objc2-javascript-core", "objc2-security", ] @@ -6253,8 +6313,8 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" dependencies = [ - "objc2 0.6.2", - "objc2-foundation 0.3.1", + "objc2 0.6.3", + "objc2-foundation 0.3.2", "objc2-osa-kit", "serde", "serde_json", @@ -7433,17 +7493,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" dependencies = [ "ashpd", - "block2 0.6.1", + "block2 0.6.2", "dispatch2", "glib-sys", "gobject-sys", "gtk-sys", "js-sys", "log", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "raw-window-handle", "wasm-bindgen", "wasm-bindgen-futures", @@ -7754,9 +7814,9 @@ dependencies = [ "clap", "futures", "inquire", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "scap-targets", "tokio", "tracing", @@ -7859,7 +7919,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c62ae379564f834110c5b020cc731a7c14b8ec4879b4367579a467a0a704c21" dependencies = [ - "block2 0.6.1", + "block2 0.6.2", "core-foundation 0.10.1", "core-graphics 0.25.0", "core-media-rs", @@ -8927,12 +8987,12 @@ dependencies = [ [[package]] name = "tao" -version = "0.34.3" +version = "0.34.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7" +checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" dependencies = [ "bitflags 2.9.4", - "block2 0.6.1", + "block2 0.6.2", "core-foundation 0.10.1", "core-graphics 0.24.0", "crossbeam-channel", @@ -8949,9 +9009,9 @@ dependencies = [ "ndk 0.9.0", "ndk-context", "ndk-sys 0.6.0+11769913", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "once_cell", "parking_lot", "raw-window-handle", @@ -8995,9 +9055,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.8.5" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d1d3b3dc4c101ac989fd7db77e045cc6d91a25349cd410455cb5c57d510c1c" +checksum = "15524fc7959bfcaa051ba6d0b3fb1ef18e978de2176c7c6acb977f7fd14d35c7" dependencies = [ "anyhow", "bytes", @@ -9017,9 +9077,9 @@ dependencies = [ "log", "mime", "muda", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "objc2-ui-kit", "objc2-web-kit", "percent-encoding", @@ -9041,7 +9101,6 @@ dependencies = [ "tokio", "tray-icon", "url", - "urlpattern", "webkit2gtk", "webview2-com", "window-vibrancy", @@ -9050,9 +9109,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.4.1" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c432ccc9ff661803dab74c6cd78de11026a578a9307610bbc39d3c55be7943f" +checksum = "17fcb8819fd16463512a12f531d44826ce566f486d7ccd211c9c8cebdaec4e08" dependencies = [ "anyhow", "cargo_toml", @@ -9072,9 +9131,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.4.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a" +checksum = "9fa9844cefcf99554a16e0a278156ae73b0d8680bbc0e2ad1e4287aadd8489cf" dependencies = [ "base64 0.22.1", "brotli", @@ -9099,9 +9158,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.4.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e" +checksum = "3764a12f886d8245e66b7ee9b43ccc47883399be2019a61d80cf0f4117446fde" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -9146,9 +9205,9 @@ dependencies = [ [[package]] name = "tauri-plugin-clipboard-manager" -version = "2.3.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adddd9e9275b20e77af3061d100a25a884cced3c4c9ef680bd94dd0f7e26c1ca" +checksum = "206dc20af4ed210748ba945c2774e60fd0acd52b9a73a028402caf809e9b6ecf" dependencies = [ "arboard", "log", @@ -9161,9 +9220,9 @@ dependencies = [ [[package]] name = "tauri-plugin-deep-link" -version = "2.4.3" +version = "2.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd67112fb1131834c2a7398ffcba520dbbf62c17de3b10329acd1a3554b1a9bb" +checksum = "6e82759f7c7d51de3cbde51c04b3f2332de52436ed84541182cd8944b04e9e73" dependencies = [ "dunce", "plist", @@ -9182,9 +9241,9 @@ dependencies = [ [[package]] name = "tauri-plugin-dialog" -version = "2.4.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beee42a4002bc695550599b011728d9dfabf82f767f134754ed6655e434824e" +checksum = "313f8138692ddc4a2127c4c9607d616a46f5c042e77b3722450866da0aad2f19" dependencies = [ "log", "raw-window-handle", @@ -9200,9 +9259,9 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.4.2" +version = "2.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "315784ec4be45e90a987687bae7235e6be3d6e9e350d2b75c16b8a4bf22c1db7" +checksum = "47df422695255ecbe7bac7012440eddaeefd026656171eac9559f5243d3230d9" dependencies = [ "anyhow", "dunce", @@ -9222,9 +9281,9 @@ dependencies = [ [[package]] name = "tauri-plugin-global-shortcut" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df9f0f7bf2fe768b85fee4951c2505a35b72c44df1f6403e74e110bc13c5f58" +checksum = "424af23c7e88d05e4a1a6fc2c7be077912f8c76bd7900fd50aa2b7cbf5a2c405" dependencies = [ "global-hotkey", "log", @@ -9237,9 +9296,9 @@ dependencies = [ [[package]] name = "tauri-plugin-http" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938a3d7051c9a82b431e3a0f3468f85715b3442b3c3a3913095e9fa509e2652c" +checksum = "c00685aceab12643cf024f712ab0448ba8fcadf86f2391d49d2e5aa732aacc70" dependencies = [ "bytes", "cookie_store", @@ -9261,9 +9320,9 @@ dependencies = [ [[package]] name = "tauri-plugin-notification" -version = "2.3.1" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fbc86b929b5376ab84b25c060f966d146b2fbd59b6af8264027b343c82c219" +checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc" dependencies = [ "log", "notify-rust", @@ -9294,14 +9353,14 @@ dependencies = [ [[package]] name = "tauri-plugin-opener" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5" +checksum = "c26b72571d25dee25667940027114e60f569fc3974f8cefbe50c2cbc5fd65e3b" dependencies = [ "dunce", "glob", "objc2-app-kit", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "open", "schemars 0.8.22", "serde", @@ -9316,9 +9375,9 @@ dependencies = [ [[package]] name = "tauri-plugin-os" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a1c77ebf6f20417ab2a74e8c310820ba52151406d0c80fbcea7df232e3f6ba" +checksum = "d8f08346c8deb39e96f86973da0e2d76cbb933d7ac9b750f6dc4daf955a6f997" dependencies = [ "gethostname", "log", @@ -9334,9 +9393,9 @@ dependencies = [ [[package]] name = "tauri-plugin-positioner" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a01e373ea3f3f5f46d40f434ba13bd12fa4833aabab50dfc09f6362bad27c95" +checksum = "02dcd56dd4797bd4d6c4c658daed40ce563176f92df90fbd2c904ce145de17ef" dependencies = [ "log", "serde", @@ -9375,9 +9434,9 @@ dependencies = [ [[package]] name = "tauri-plugin-shell" -version = "2.3.1" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54777d0c0d8add34eea3ced84378619ef5b97996bd967d3038c668feefd21071" +checksum = "c374b6db45f2a8a304f0273a15080d98c70cde86178855fc24653ba657a1144c" dependencies = [ "encoding_rs", "log", @@ -9396,9 +9455,9 @@ dependencies = [ [[package]] name = "tauri-plugin-single-instance" -version = "2.3.4" +version = "2.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb9cac815bf11c4a80fb498666bcdad66d65b89e3ae24669e47806febb76389c" +checksum = "dd707f8c86b4e3004e2c141fa24351f1909ba40ce1b8437e30d5ed5277dd3710" dependencies = [ "serde", "serde_json", @@ -9412,9 +9471,9 @@ dependencies = [ [[package]] name = "tauri-plugin-store" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85dd80d60a76ee2c2fdce09e9ef30877b239c2a6bb76e6d7d03708aa5f13a19" +checksum = "59a77036340a97eb5bbe1b3209c31e5f27f75e6f92a52fd9dd4b211ef08bf310" dependencies = [ "dunce", "serde", @@ -9460,9 +9519,9 @@ dependencies = [ [[package]] name = "tauri-plugin-window-state" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5f6fe3291bfa609c7e0b0ee3bedac294d94c7018934086ce782c1d0f2a468e" +checksum = "73736611e14142408d15353e21e3cca2f12a3cfb523ad0ce85999b6d2ef1a704" dependencies = [ "bitflags 2.9.4", "log", @@ -9475,16 +9534,16 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.8.0" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846" +checksum = "87f766fe9f3d1efc4b59b17e7a891ad5ed195fa8d23582abb02e6c9a01137892" dependencies = [ "cookie", "dpi", "gtk", "http 1.3.1", "jni", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-ui-kit", "objc2-web-kit", "raw-window-handle", @@ -9500,17 +9559,17 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.8.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807" +checksum = "7950f3bde6bcca6655bc5e76d3d6ec587ceb81032851ab4ddbe1f508bdea2729" dependencies = [ "gtk", "http 1.3.1", "jni", "log", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "once_cell", "percent-encoding", "raw-window-handle", @@ -9555,9 +9614,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.7.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212" +checksum = "76a423c51176eb3616ee9b516a9fa67fed5f0e78baaba680e44eb5dd2cc37490" dependencies = [ "anyhow", "brotli", @@ -10198,11 +10257,11 @@ dependencies = [ "dirs", "libappindicator", "muda", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "once_cell", "png 0.17.16", "serde", @@ -11180,10 +11239,10 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" dependencies = [ - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "raw-window-handle", "windows-sys 0.59.0", "windows-version", @@ -12003,12 +12062,12 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wry" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f0e9642a0d061f6236c54ccae64c2722a7879ad4ec7dff59bd376d446d8e90" +checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2" dependencies = [ "base64 0.22.1", - "block2 0.6.1", + "block2 0.6.2", "cookie", "crossbeam-channel", "dirs", @@ -12023,10 +12082,10 @@ dependencies = [ "kuchikiki", "libc", "ndk 0.9.0", - "objc2 0.6.2", + "objc2 0.6.3", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.1", + "objc2-foundation 0.3.2", "objc2-ui-kit", "objc2-web-kit", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 61e8ab20f9..8782e12e28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,12 +22,12 @@ tokio = { version = "1.39.3", features = [ "rt-multi-thread", "time", ] } -tauri = { version = "2.5.0", features = ["specta"] } +tauri = { version = "2.9.4", features = ["specta"] } specta = { version = "=2.0.0-rc.20", features = [ "derive", "serde_json", "uuid", - "chrono" + "chrono", ] } serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -65,6 +65,7 @@ cidre = { git = "https://github.com/CapSoftware/cidre", rev = "bf84b67079a8", fe "io_surface", "mtl", ], default-features = false } +objc2 = "0.6.3" windows = "0.60.0" windows-core = "0.60" diff --git a/apps/desktop/package.json b/apps/desktop/package.json index aa83e5edee..d7f205c956 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -17,7 +17,7 @@ "@cap/web-api-contract": "workspace:*", "@corvu/tooltip": "^0.2.1", "@intercom/messenger-js-sdk": "^0.0.14", - "@kobalte/core": "^0.13.7", + "@kobalte/core": "^0.13.11", "@radix-ui/colors": "^3.0.0", "@rive-app/canvas": "^2.26.7", "@solid-primitives/bounds": "^0.0.122", @@ -39,7 +39,7 @@ "@solidjs/router": "^0.14.2", "@solidjs/start": "^1.1.3", "@tanstack/solid-query": "^5.51.21", - "@tauri-apps/api": "2.5.0", + "@tauri-apps/api": "2.9.1", "@tauri-apps/plugin-clipboard-manager": "^2.3.0", "@tauri-apps/plugin-deep-link": "^2.4.1", "@tauri-apps/plugin-dialog": "^2.4.0", @@ -49,7 +49,7 @@ "@tauri-apps/plugin-opener": "^2.5.0", "@tauri-apps/plugin-os": "^2.3.0", "@tauri-apps/plugin-process": "2.3.0", - "@tauri-apps/plugin-shell": "^2.3.0", + "@tauri-apps/plugin-shell": "^2.3.1", "@tauri-apps/plugin-store": "^2.4.0", "@tauri-apps/plugin-updater": "^2.9.0", "@ts-rest/core": "^3.52.1", @@ -75,7 +75,7 @@ "@fontsource/geist-sans": "^5.0.3", "@webgpu/types": "^0.1.44", "@iconify/json": "^2.2.239", - "@tauri-apps/cli": ">=2.1.0", + "@tauri-apps/cli": "^2.9.5", "@total-typescript/ts-reset": "^0.6.1", "@types/dom-webcodecs": "^0.1.11", "@types/uuid": "^9.0.8", @@ -85,6 +85,8 @@ "vite-plugin-top-level-await": "^1.4.4", "vite-plugin-wasm": "^3.4.1", "vite-tsconfig-paths": "^5.0.1", - "vitest": "~2.1.9" + "vitest": "~2.1.9", + "@tailwindcss/typography": "^0.5.19", + "tailwind-scrollbar": "^4.0.2" } } diff --git a/apps/desktop/postcss.config.cjs b/apps/desktop/postcss.config.cjs index a1d89157e4..9ebd1c8c20 100644 --- a/apps/desktop/postcss.config.cjs +++ b/apps/desktop/postcss.config.cjs @@ -1 +1,8 @@ -module.exports = require("@cap/ui/postcss"); +// module.exports = require("@cap/ui/postcss"); + +module.exports = { + plugins: { + "@tailwindcss/postcss": {}, + autoprefixer: {}, + }, +}; diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 184bb073d1..c3780cc3cb 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -27,23 +27,23 @@ tauri = { workspace = true, features = [ "devtools", ] } tauri-specta = { version = "=2.0.0-rc.20", features = ["derive", "typescript"] } -tauri-plugin-dialog = "2.2.0" -tauri-plugin-fs = "2.2.0" -tauri-plugin-global-shortcut = "2.2.0" -tauri-plugin-http = "2.2.0" -tauri-plugin-notification = "2.2.0" -tauri-plugin-os = "2.2.0" -tauri-plugin-process = "2.2.0" -tauri-plugin-shell = "2.2.0" -tauri-plugin-single-instance = { version = "2.2.0", features = ["deep-link"] } -tauri-plugin-store = "2.2.0" +tauri-plugin-dialog = "2.4.2" +tauri-plugin-fs = "2.4.4" +tauri-plugin-global-shortcut = "2.3.1" +tauri-plugin-http = "2.5.4" +tauri-plugin-notification = "2.3.3" +tauri-plugin-os = "2.3.2" +tauri-plugin-process = "2.3.0" +tauri-plugin-shell = "2.3.3" +tauri-plugin-single-instance = { version = "2.3.6", features = ["deep-link"] } +tauri-plugin-store = "2.4.1" tauri-plugin-updater = "2.9.0" tauri-plugin-oauth = { git = "https://github.com/FabianLars/tauri-plugin-oauth", branch = "v2" } -tauri-plugin-window-state = "2.2.0" -tauri-plugin-positioner = "2.2.0" -tauri-plugin-deep-link = "2.2.0" -tauri-plugin-clipboard-manager = "2.2.1" -tauri-plugin-opener = "2.2.6" +tauri-plugin-window-state = "2.4.1" +tauri-plugin-positioner = "2.3.1" +tauri-plugin-deep-link = "2.4.5" +tauri-plugin-clipboard-manager = "2.3.2" +tauri-plugin-opener = "2.5.2" serde = { workspace = true } serde_json = "1.0.111" @@ -120,16 +120,18 @@ aho-corasick.workspace = true [target.'cfg(target_os = "macos")'.dependencies] -core-graphics = "0.24.0" -core-foundation = "0.10.0" -objc2-app-kit = { version = "0.3.0", features = [ +objc2.workspace = true +dispatch2 = "0.3.0" +block2 = "0.6.2" +objc2-foundation = "0.3.2" +objc2-application-services = "0.3.2" +objc2-core-foundation = "0.3.2" +objc2-core-graphics = "0.3.2" +objc2-app-kit = { version = "0.3.2", features = [ "NSWindow", "NSResponder", "NSHapticFeedback", ] } -cocoa = "0.26.0" -objc = "0.2.7" -swift-rs = "1.0.6" tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" } cidre = { workspace = true } diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index dbd90f667f..240205c982 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -6,7 +6,7 @@ use std::path::{Path, PathBuf}; use tauri::{AppHandle, Manager, Url}; use tracing::trace; -use crate::{App, ArcLock, recording::StartRecordingInputs, windows::ShowCapWindow}; +use crate::{App, ArcLock, recording::StartRecordingInputs, windows::CapWindow}; #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -150,7 +150,7 @@ impl DeepLinkAction { crate::open_project_from_path(Path::new(&project_path), app.clone()) } DeepLinkAction::OpenSettings { page } => { - crate::show_window(app.clone(), ShowCapWindow::Settings { page }).await + crate::show_window(app.clone(), CapWindow::Settings { page }).await } } } diff --git a/apps/desktop/src-tauri/src/hotkeys.rs b/apps/desktop/src-tauri/src/hotkeys.rs index 52df0540af..5b7be24fac 100644 --- a/apps/desktop/src-tauri/src/hotkeys.rs +++ b/apps/desktop/src-tauri/src/hotkeys.rs @@ -2,7 +2,7 @@ use crate::{ RequestOpenRecordingPicker, RequestStartRecording, recording, recording_settings::{RecordingSettingsStore, RecordingTargetMode}, tray, - windows::ShowCapWindow, + windows::CapWindow, }; use global_hotkey::HotKeyState; use serde::{Deserialize, Serialize}; @@ -97,7 +97,7 @@ pub fn init(app: &AppHandle) { if shortcut.key == Code::Comma && shortcut.mods == Modifiers::META { let app = app.clone(); tokio::spawn(async move { - let _ = ShowCapWindow::Settings { page: None }.show(&app).await; + let _ = CapWindow::Settings { page: None }.show(&app).await; }); } diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index f5c19546d3..359d6f3792 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -96,7 +96,7 @@ use upload::{create_or_get_video, upload_image, upload_video}; use web_api::AuthedApiError; use web_api::ManagerExt as WebManagerExt; use windows::{ - CapWindowId, EditorWindowIds, ScreenshotEditorWindowIds, ShowCapWindow, set_window_transparent, + CapWindow, CapWindowDef, EditorWindowIds, ScreenshotEditorWindowIds, set_window_transparent, }; use crate::{ @@ -503,7 +503,7 @@ async fn set_camera_input( return Err(e); } - ShowCapWindow::Camera + CapWindow::Camera .show(&app_handle) .await .map_err(|err| error!("Failed to show camera preview window: {err}")) @@ -1314,7 +1314,7 @@ struct SerializedEditorInstance { #[specta::specta] #[instrument(skip(window))] async fn create_editor_instance(window: Window) -> Result { - let CapWindowId::Editor { id } = CapWindowId::from_str(window.label()).unwrap() else { + let Ok(CapWindowDef::Editor { id }) = CapWindowDef::from_str(window.label()) else { return Err("Invalid window".to_string()); }; @@ -1466,14 +1466,14 @@ fn close_recordings_overlay_window(app: AppHandle) { #[cfg(target_os = "macos")] { use tauri_nspanel::ManagerExt; - if let Ok(panel) = app.get_webview_panel(&CapWindowId::RecordingsOverlay.label()) { + if let Ok(panel) = app.get_webview_panel(&CapWindowDef::RecordingsOverlay.label()) { panel.released_when_closed(true); panel.close(); } } if !cfg!(target_os = "macos") - && let Some(window) = CapWindowId::RecordingsOverlay.get(&app) + && let Some(window) = CapWindowDef::RecordingsOverlay.get(&app) { let _ = window.close(); } @@ -1486,7 +1486,7 @@ fn focus_captures_panel(_app: AppHandle) { #[cfg(target_os = "macos")] { use tauri_nspanel::ManagerExt; - if let Ok(panel) = _app.get_webview_panel(&CapWindowId::RecordingsOverlay.label()) { + if let Ok(panel) = _app.get_webview_panel(&CapWindowDef::RecordingsOverlay.label()) { panel.make_key_window(); } } @@ -1722,7 +1722,7 @@ async fn upload_screenshot( }; if !auth.is_upgraded() { - ShowCapWindow::Upgrade.show(&app).await.ok(); + CapWindow::Upgrade.show(&app).await.ok(); return Ok(UploadResult::UpgradeRequired); } @@ -2107,7 +2107,7 @@ async fn reset_microphone_permissions(_app: AppHandle) -> Result<(), ()> { #[specta::specta] #[instrument(skip(app))] async fn is_camera_window_open(app: AppHandle) -> bool { - CapWindowId::Camera.get(&app).is_some() + CapWindowDef::Camera.get(&app).is_some() } #[tauri::command] @@ -2185,7 +2185,7 @@ async fn editor_delete_project( #[tauri::command] #[specta::specta] #[instrument(skip(app))] -async fn show_window(app: AppHandle, window: ShowCapWindow) -> Result<(), String> { +async fn show_window(app: AppHandle, window: CapWindow) -> Result<(), String> { window.show(&app).await.map_err(|e| e.to_string())?; Ok(()) } @@ -2392,7 +2392,6 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { reset_microphone_permissions, is_camera_window_open, seek_to, - windows::position_traffic_lights, windows::set_theme, global_message_dialog, show_window, @@ -2509,7 +2508,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { else { let app = app.clone(); tokio::spawn(async move { - ShowCapWindow::Main { + CapWindow::Main { init_target_mode: None, } .show(&app) @@ -2550,14 +2549,14 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { flags }) .with_denylist(&[ - CapWindowId::Setup.label().as_str(), + CapWindowDef::Setup.label().as_str(), "window-capture-occluder", "target-select-overlay", - CapWindowId::CaptureArea.label().as_str(), - CapWindowId::Camera.label().as_str(), - CapWindowId::RecordingsOverlay.label().as_str(), - CapWindowId::RecordingControls.label().as_str(), - CapWindowId::Upgrade.label().as_str(), + CapWindowDef::CaptureArea.label().as_str(), + CapWindowDef::Camera.label().as_str(), + CapWindowDef::RecordingsOverlay.label().as_str(), + CapWindowDef::RecordingControls.label().as_str(), + CapWindowDef::Upgrade.label().as_str(), ]) .map_label(|label| match label { label if label.starts_with("editor-") => "editor", @@ -2600,7 +2599,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { camera_feed .tell(feeds::camera::OnFeedDisconnect(Box::new({ move || { - if let Some(win) = CapWindowId::Camera.get(&app) { + if let Some(win) = CapWindowDef::Camera.get(&app) { win.close().ok(); } } @@ -2707,11 +2706,11 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { .map(|s| !s.has_completed_startup) .unwrap_or(false) { - let _ = ShowCapWindow::Setup.show(&app).await; + let _ = CapWindow::Setup.show(&app).await; } else { println!("Permissions granted, showing main window"); - let _ = ShowCapWindow::Main { + let _ = CapWindow::Main { init_target_mode: None, } .show(&app) @@ -2749,7 +2748,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { }); RequestOpenRecordingPicker::listen_any_spawn(&app, async |event, app| { - let _ = ShowCapWindow::Main { + let _ = CapWindow::Main { init_target_mode: event.target_mode, } .show(&app) @@ -2757,7 +2756,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { }); RequestOpenSettings::listen_any_spawn(&app, async |payload, app| { - let _ = ShowCapWindow::Settings { + let _ = CapWindow::Settings { page: Some(payload.page), } .show(&app) @@ -2783,19 +2782,19 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { match event { WindowEvent::CloseRequested { .. } => { - if let Ok(CapWindowId::Camera) = CapWindowId::from_str(label) { + if let Ok(CapWindowDef::Camera) = CapWindowDef::from_str(label) { tokio::spawn(cleanup_camera_window(app.clone())); } } WindowEvent::Destroyed => { - if let Ok(window_id) = CapWindowId::from_str(label) { + if let Ok(window_id) = CapWindowDef::from_str(label) { match window_id { - CapWindowId::Main => { + CapWindowDef::Main => { let app = app.clone(); for (id, window) in app.webview_windows() { - if let Ok(CapWindowId::TargetSelectOverlay { .. }) = - CapWindowId::from_str(&id) + if let Ok(CapWindowDef::TargetSelectOverlay { .. }) = + CapWindowDef::from_str(&id) { let _ = window.close(); } @@ -2806,7 +2805,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { let app_state = &mut *state.write().await; let camera_window_open = - CapWindowId::Camera.get(&app).is_some(); + CapWindowDef::Camera.get(&app).is_some(); if !app_state.is_recording_active_or_pending() && !camera_window_open @@ -2824,18 +2823,18 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { } }); } - CapWindowId::Editor { id } => { + CapWindowDef::Editor { id } => { let window_ids = EditorWindowIds::get(window.app_handle()); window_ids.ids.lock().unwrap().retain(|(_, _id)| *_id != id); tokio::spawn(EditorInstances::remove(window.clone())); #[cfg(target_os = "windows")] - if CapWindowId::Settings.get(app).is_none() { + if CapWindowDef::Settings.get(app).is_none() { reopen_main_window(app); } } - CapWindowId::ScreenshotEditor { id } => { + CapWindowDef::ScreenshotEditor { id } => { let window_ids = ScreenshotEditorWindowIds::get(window.app_handle()); window_ids.ids.lock().unwrap().retain(|(_, _id)| *_id != id); @@ -2843,18 +2842,18 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { tokio::spawn(ScreenshotEditorInstances::remove(window.clone())); #[cfg(target_os = "windows")] - if CapWindowId::Settings.get(app).is_none() { + if CapWindowDef::Settings.get(app).is_none() { reopen_main_window(app); } } - CapWindowId::Settings => { + CapWindowDef::Settings => { for (label, window) in app.webview_windows() { - if let Ok(id) = CapWindowId::from_str(&label) + if let Ok(id) = CapWindowDef::from_str(&label) && matches!( id, - CapWindowId::TargetSelectOverlay { .. } - | CapWindowId::Main - | CapWindowId::Camera + CapWindowDef::TargetSelectOverlay { .. } + | CapWindowDef::Main + | CapWindowDef::Camera ) { let _ = window.show(); @@ -2868,14 +2867,14 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { return; } - CapWindowId::Upgrade | CapWindowId::ModeSelect => { + CapWindowDef::Upgrade | CapWindowDef::ModeSelect => { for (label, window) in app.webview_windows() { - if let Ok(id) = CapWindowId::from_str(&label) + if let Ok(id) = CapWindowDef::from_str(&label) && matches!( id, - CapWindowId::TargetSelectOverlay { .. } - | CapWindowId::Main - | CapWindowId::Camera + CapWindowDef::TargetSelectOverlay { .. } + | CapWindowDef::Main + | CapWindowDef::Camera ) { let _ = window.show(); @@ -2883,12 +2882,26 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { } return; } - CapWindowId::TargetSelectOverlay { display_id } => { + CapWindowDef::TargetSelectOverlay { display_id } => { app.state::() .destroy(&display_id, app.global_shortcut()); } - CapWindowId::Camera => { - tokio::spawn(cleanup_camera_window(app.clone())); + CapWindowDef::Camera => { + let app = app.clone(); + tokio::spawn(async move { + let state = app.state::>(); + let mut app_state = state.write().await; + + app_state.camera_preview.on_window_close(); + + if !app_state.is_recording_active_or_pending() { + let _ = app_state + .camera_feed + .ask(feeds::camera::RemoveInput) + .await; + app_state.camera_in_use = false; + } + }); } _ => {} }; @@ -2899,7 +2912,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { && app .webview_windows() .keys() - .all(|label| !CapWindowId::from_str(label).unwrap().activates_dock()) + .all(|label| !CapWindowDef::from_str(label).unwrap().activates_dock()) { #[cfg(target_os = "macos")] app.set_activation_policy(tauri::ActivationPolicy::Accessory) @@ -2908,12 +2921,12 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { } #[cfg(target_os = "macos")] WindowEvent::Focused(focused) => { - let window_id = CapWindowId::from_str(label); + let window_id = CapWindowDef::from_str(label); - if matches!(window_id, Ok(CapWindowId::Upgrade)) { + if matches!(window_id, Ok(CapWindowDef::Upgrade)) { for (label, window) in app.webview_windows() { - if let Ok(id) = CapWindowId::from_str(&label) - && matches!(id, CapWindowId::TargetSelectOverlay { .. }) + if let Ok(id) = CapWindowDef::from_str(&label) + && matches!(id, CapWindowDef::TargetSelectOverlay { .. }) { let _ = window.hide(); } @@ -2965,7 +2978,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { } else { let handle = _handle.clone(); tokio::spawn(async move { - let _ = ShowCapWindow::Main { + let _ = CapWindow::Main { init_target_mode: None, } .show(&handle) @@ -2984,20 +2997,23 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { #[cfg(target_os = "windows")] fn has_open_editor_window(app: &AppHandle) -> bool { - app.webview_windows() - .keys() - .any(|label| matches!(CapWindowId::from_str(label), Ok(CapWindowId::Editor { .. }))) + app.webview_windows().keys().any(|label| { + matches!( + CapWindowDef::from_str(label), + Ok(CapWindowDef::Editor { .. }) + ) + }) } #[cfg(target_os = "windows")] fn reopen_main_window(app: &AppHandle) { - if let Some(main) = CapWindowId::Main.get(app) { + if let Some(main) = CapWindowDef::Main.get(app) { let _ = main.show(); let _ = main.set_focus(); } else { let handle = app.clone(); tokio::spawn(async move { - let _ = ShowCapWindow::Main { + let _ = CapWindow::Main { init_target_mode: None, } .show(&handle) @@ -3258,7 +3274,7 @@ fn open_project_from_path(path: &Path, app: AppHandle) -> Result<(), String> { } let project_path = path.to_path_buf(); - tokio::spawn(async move { ShowCapWindow::Editor { project_path }.show(&app).await }); + tokio::spawn(async move { CapWindow::Editor { project_path }.show(&app).await }); } RecordingMetaInner::Instant(_) => { let mp4_path = path.join("content/output.mp4"); @@ -3267,7 +3283,7 @@ fn open_project_from_path(path: &Path, app: AppHandle) -> Result<(), String> { let _ = app .opener() .open_path(mp4_path.to_str().unwrap_or_default(), None::); - if let Some(main_window) = CapWindowId::Main.get(&app) { + if let Some(main_window) = CapWindowDef::Main.get(&app) { main_window.close().ok(); } } diff --git a/apps/desktop/src-tauri/src/permissions.rs b/apps/desktop/src-tauri/src/permissions.rs index 9e6ad00f20..f1983de1d4 100644 --- a/apps/desktop/src-tauri/src/permissions.rs +++ b/apps/desktop/src-tauri/src/permissions.rs @@ -4,14 +4,6 @@ use serde::{Deserialize, Serialize}; use cidre::av; use tracing::instrument; -#[cfg(target_os = "macos")] -#[link(name = "ApplicationServices", kind = "framework")] -unsafe extern "C" { - fn AXIsProcessTrusted() -> bool; - fn AXIsProcessTrustedWithOptions(options: core_foundation::dictionary::CFDictionaryRef) - -> bool; -} - #[derive(Debug, Serialize, Deserialize, specta::Type)] #[serde(rename_all = "camelCase")] pub enum OSPermission { @@ -91,20 +83,17 @@ pub async fn request_permission(_permission: OSPermission) { }); } OSPermission::Accessibility => { - use core_foundation::base::TCFType; - use core_foundation::dictionary::CFDictionary; // Import CFDictionaryRef - use core_foundation::string::CFString; - - let prompt_key = CFString::new("AXTrustedCheckOptionPrompt"); - let prompt_value = core_foundation::boolean::CFBoolean::true_value(); - - let options = CFDictionary::from_CFType_pairs(&[( - prompt_key.as_CFType(), - prompt_value.as_CFType(), - )]); - + let options = objc2_core_foundation::CFDictionary::from_slices( + &[&*objc2_core_foundation::CFString::from_static_str( + "AXTrustedCheckOptionPrompt", + )], + &[objc2_core_foundation::CFBoolean::new(true)], + ); + // SAFETY: The AXIsProcessTrustedWithOptions function is safe to call with a valid CFDictionaryRef. unsafe { - AXIsProcessTrustedWithOptions(options.as_concrete_TypeRef()); + objc2_application_services::AXIsProcessTrustedWithOptions(Some( + options.as_opaque(), + )); } } } @@ -173,7 +162,7 @@ pub fn do_permissions_check(_initial_check: bool) -> OSPermissionsCheck { }, microphone: check_av_permission(MediaType::audio()), camera: check_av_permission(MediaType::video()), - accessibility: if unsafe { AXIsProcessTrusted() } { + accessibility: if unsafe { objc2_application_services::AXIsProcessTrusted() } { OSPermissionStatus::Granted } else { OSPermissionStatus::Denied diff --git a/apps/desktop/src-tauri/src/platform/macos/delegates.rs b/apps/desktop/src-tauri/src/platform/macos/delegates.rs deleted file mode 100644 index d8933c3b84..0000000000 --- a/apps/desktop/src-tauri/src/platform/macos/delegates.rs +++ /dev/null @@ -1,361 +0,0 @@ -// TODO(Ilya): Re-write all macos code to use `objc2` crates n -/// -/// Credit to @haasal, @charrondev, Hoppscotch app, Electron, Zed Editor -/// -/// https://github.com/haasal -/// https://gist.github.com/charrondev -/// https://github.com/hoppscotch/hoppscotch -/// https://github.com/clearlysid/tauri-plugin-decorum/ -/// (Issue) https://github.com/tauri-apps/tauri/issues/4789 -/// (Gist) https://gist.github.com/charrondev/43150e940bd2771b1ea88256d491c7a9 -/// (Hoppscotch) https://github.com/hoppscotch/hoppscotch/blob/286fcd2bb08a84f027b10308d1e18da368f95ebf/packages/hoppscotch-selfhost-desktop/src-tauri/src/mac/window.rs -/// (Electron) https://github.com/electron/electron/blob/38512efd25a159ddc64a54c22ef9eb6dd60064ec/shell/browser/native_window_mac.mm#L1454 -/// -use objc::{msg_send, sel, sel_impl}; -use rand::{Rng, distributions::Alphanumeric}; -use tauri::{Emitter, LogicalPosition, Runtime, Window}; - -pub struct UnsafeWindowHandle(pub *mut std::ffi::c_void); -unsafe impl Send for UnsafeWindowHandle {} -unsafe impl Sync for UnsafeWindowHandle {} - -#[derive(Debug)] -struct WindowState { - window: Window, - controls_inset: LogicalPosition, -} - -// TODO: Respect RTL display language -// TODO: Update Height, consider supporting the scenario where the buttons are hidden by the system due to screen sharing of the window -// https://developer.apple.com/documentation/appkit/nsapplication/1428556-userinterfacelayoutdirection?language=objc -pub fn position_window_controls( - ns_window_handle: UnsafeWindowHandle, - inset: &LogicalPosition, -) { - use cocoa::{ - appkit::{NSView, NSWindow, NSWindowButton}, - base::id, - foundation::NSRect, - }; - - let ns_window = ns_window_handle.0 as id; - unsafe { - let close = ns_window.standardWindowButton_(NSWindowButton::NSWindowCloseButton); - let minimize = ns_window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton); - let zoom = ns_window.standardWindowButton_(NSWindowButton::NSWindowZoomButton); - - let title_bar_container_view = close.superview().superview(); - - let close_rect: NSRect = msg_send![close, frame]; - let button_height = close_rect.size.height; - - let title_bar_frame_height = button_height + inset.y; - let mut title_bar_rect = NSView::frame(title_bar_container_view); - title_bar_rect.size.height = title_bar_frame_height; - title_bar_rect.origin.y = NSView::frame(ns_window).size.height - title_bar_frame_height; - let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect]; - - let window_buttons = vec![close, minimize, zoom]; - let space_between = NSView::frame(minimize).origin.x - NSView::frame(close).origin.x; - let vertical_offset = 4.0; // Adjust this value to push buttons down - - for (i, button) in window_buttons.into_iter().enumerate() { - let mut rect: NSRect = NSView::frame(button); - rect.origin.x = inset.x + (i as f64 * space_between); - rect.origin.y = ((title_bar_frame_height - button_height) / 2.0) - vertical_offset; - button.setFrameOrigin(rect.origin); - } - } -} - -pub fn setup(window: Window, controls_inset: LogicalPosition) { - use cocoa::appkit::NSWindow; - use cocoa::base::{BOOL, id}; - use cocoa::foundation::NSUInteger; - use objc::runtime::{Object, Sel}; - use std::ffi::c_void; - - let Ok(ns_win) = window.ns_window() else { - tracing::warn!("Failed to get window handle for delegate setup"); - return; - }; - - // Do the initial positioning - position_window_controls(UnsafeWindowHandle(ns_win), &controls_inset); - - // Ensure they stay in place while resizing the window. - fn with_window_state) -> T, T>( - this: &Object, - func: F, - ) { - let ptr = unsafe { - let x: *mut c_void = *this.get_ivar("app_box"); - &mut *(x as *mut WindowState) - }; - func(ptr); - } - - unsafe { - let ns_win_id = ns_win as id; - let current_delegate: id = ns_win_id.delegate(); - - extern "C" fn on_window_should_close(this: &Object, _cmd: Sel, sender: id) -> BOOL { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - msg_send![super_del, windowShouldClose: sender] - } - } - extern "C" fn on_window_will_close(this: &Object, _cmd: Sel, notification: id) { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, windowWillClose: notification]; - } - } - extern "C" fn on_window_did_resize(this: &Object, _cmd: Sel, notification: id) { - unsafe { - with_window_state(this, |state: &mut WindowState| { - position_window_controls( - UnsafeWindowHandle( - state - .window - .ns_window() - .expect("Failed to get handle to NSWindow"), - ), - &state.controls_inset, - ); - }); - - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, windowDidResize: notification]; - } - } - extern "C" fn on_window_did_move(this: &Object, _cmd: Sel, notification: id) { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, windowDidMove: notification]; - } - } - extern "C" fn on_window_did_change_backing_properties( - this: &Object, - _cmd: Sel, - notification: id, - ) { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, windowDidChangeBackingProperties: notification]; - } - } - extern "C" fn on_window_did_become_key(this: &Object, _cmd: Sel, notification: id) { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, windowDidBecomeKey: notification]; - } - } - extern "C" fn on_window_did_resign_key(this: &Object, _cmd: Sel, notification: id) { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, windowDidResignKey: notification]; - } - } - extern "C" fn on_dragging_entered(this: &Object, _cmd: Sel, notification: id) -> BOOL { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - msg_send![super_del, draggingEntered: notification] - } - } - extern "C" fn on_prepare_for_drag_operation( - this: &Object, - _cmd: Sel, - notification: id, - ) -> BOOL { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - msg_send![super_del, prepareForDragOperation: notification] - } - } - extern "C" fn on_perform_drag_operation(this: &Object, _cmd: Sel, sender: id) -> BOOL { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - msg_send![super_del, performDragOperation: sender] - } - } - extern "C" fn on_conclude_drag_operation(this: &Object, _cmd: Sel, notification: id) { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, concludeDragOperation: notification]; - } - } - extern "C" fn on_dragging_exited(this: &Object, _cmd: Sel, notification: id) { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, draggingExited: notification]; - } - } - extern "C" fn on_window_will_use_full_screen_presentation_options( - this: &Object, - _cmd: Sel, - window: id, - proposed_options: NSUInteger, - ) -> NSUInteger { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options] - } - } - extern "C" fn on_window_did_enter_full_screen( - this: &Object, - _cmd: Sel, - notification: id, - ) { - unsafe { - with_window_state(this, |state: &mut WindowState| { - state - .window - .emit("did-enter-fullscreen", ()) - .expect("Failed to emit event"); - }); - - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, windowDidEnterFullScreen: notification]; - } - } - extern "C" fn on_window_will_enter_full_screen( - this: &Object, - _cmd: Sel, - notification: id, - ) { - unsafe { - with_window_state(this, |state: &mut WindowState| { - state - .window - .emit("will-enter-fullscreen", ()) - .expect("Failed to emit event"); - }); - - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, windowWillEnterFullScreen: notification]; - } - } - extern "C" fn on_window_did_exit_full_screen( - this: &Object, - _cmd: Sel, - notification: id, - ) { - unsafe { - with_window_state(this, |state: &mut WindowState| { - state - .window - .emit("did-exit-fullscreen", ()) - .expect("Failed to emit event"); - - position_window_controls( - UnsafeWindowHandle( - state - .window - .ns_window() - .expect("Failed to get handle to NSWindow"), - ), - &state.controls_inset, - ); - }); - - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, windowDidExitFullScreen: notification]; - } - } - extern "C" fn on_window_will_exit_full_screen( - this: &Object, - _cmd: Sel, - notification: id, - ) { - unsafe { - with_window_state(this, |state: &mut WindowState| { - state - .window - .emit("will-exit-fullscreen", ()) - .expect("Failed to emit event"); - }); - - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, windowWillExitFullScreen: notification]; - } - } - extern "C" fn on_window_did_fail_to_enter_full_screen( - this: &Object, - _cmd: Sel, - window: id, - ) { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, windowDidFailToEnterFullScreen: window]; - } - } - extern "C" fn on_effective_appearance_did_change( - this: &Object, - _cmd: Sel, - notification: id, - ) { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![super_del, effectiveAppearanceDidChange: notification]; - } - } - extern "C" fn on_effective_appearance_did_changed_on_main_thread( - this: &Object, - _cmd: Sel, - notification: id, - ) { - unsafe { - let super_del: id = *this.get_ivar("super_delegate"); - let _: () = msg_send![ - super_del, - effectiveAppearanceDidChangedOnMainThread: notification - ]; - } - } - - let window_label = window.label().to_string(); - - let app_state = WindowState { - window, - controls_inset, - }; - let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void; - let random_str: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(20) - .map(char::from) - .collect(); - - // We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate - // delegate with the same name. - let delegate_name = format!("windowDelegate_cap_{window_label}_{random_str}"); - - ns_win_id.setDelegate_(cocoa::delegate!(&delegate_name, { - window: id = ns_win_id, - app_box: *mut c_void = app_box, - toolbar: id = cocoa::base::nil, - super_delegate: id = current_delegate, - (windowShouldClose:) => on_window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL, - (windowWillClose:) => on_window_will_close as extern "C" fn(&Object, Sel, id), - (windowDidResize:) => on_window_did_resize:: as extern "C" fn(&Object, Sel, id), - (windowDidMove:) => on_window_did_move as extern "C" fn(&Object, Sel, id), - (windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern "C" fn(&Object, Sel, id), - (windowDidBecomeKey:) => on_window_did_become_key as extern "C" fn(&Object, Sel, id), - (windowDidResignKey:) => on_window_did_resign_key as extern "C" fn(&Object, Sel, id), - (draggingEntered:) => on_dragging_entered as extern "C" fn(&Object, Sel, id) -> BOOL, - (prepareForDragOperation:) => on_prepare_for_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL, - (performDragOperation:) => on_perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL, - (concludeDragOperation:) => on_conclude_drag_operation as extern "C" fn(&Object, Sel, id), - (draggingExited:) => on_dragging_exited as extern "C" fn(&Object, Sel, id), - (window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern "C" fn(&Object, Sel, id, NSUInteger) -> NSUInteger, - (windowDidEnterFullScreen:) => on_window_did_enter_full_screen:: as extern "C" fn(&Object, Sel, id), - (windowWillEnterFullScreen:) => on_window_will_enter_full_screen:: as extern "C" fn(&Object, Sel, id), - (windowDidExitFullScreen:) => on_window_did_exit_full_screen:: as extern "C" fn(&Object, Sel, id), - (windowWillExitFullScreen:) => on_window_will_exit_full_screen:: as extern "C" fn(&Object, Sel, id), - (windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern "C" fn(&Object, Sel, id), - (effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern "C" fn(&Object, Sel, id), - (effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern "C" fn(&Object, Sel, id) - })) - } -} diff --git a/apps/desktop/src-tauri/src/platform/macos/mod.rs b/apps/desktop/src-tauri/src/platform/macos/mod.rs index 14a3158583..1c60e46604 100644 --- a/apps/desktop/src-tauri/src/platform/macos/mod.rs +++ b/apps/desktop/src-tauri/src/platform/macos/mod.rs @@ -1,53 +1,40 @@ -// use std::ffi::c_void; +use objc2::{Message, rc::Retained}; +use objc2_app_kit::{NSWindow, NSWindowButton}; +use tauri::WebviewWindow; -// use cocoa::{ -// base::{id, nil}, -// foundation::NSString, -// }; -// use core_graphics::{ -// base::boolean_t, -// display::{CFDictionaryRef, CGRect}, -// }; -// use objc::{class, msg_send, sel, sel_impl}; - -pub mod delegates; mod sc_shareable_content; pub use sc_shareable_content::*; -pub fn set_window_level(window: tauri::Window, level: objc2_app_kit::NSWindowLevel) { - let c_window = window.clone(); - _ = window.run_on_main_thread(move || unsafe { - let Ok(ns_win) = c_window.ns_window() else { - return; - }; - let ns_win = ns_win as *const objc2_app_kit::NSWindow; - (*ns_win).setLevel(level); - }); -} - -// pub fn get_ns_window_number(ns_window: *mut c_void) -> isize { -// let ns_window = ns_window as *const objc2_app_kit::NSWindow; +pub trait WebviewWindowExt { + fn objc2_nswindow(&self) -> Retained; -// unsafe { (*ns_window).windowNumber() } -// } - -// #[link(name = "CoreGraphics", kind = "framework")] -// unsafe extern "C" { -// pub fn CGRectMakeWithDictionaryRepresentation( -// dict: CFDictionaryRef, -// rect: *mut CGRect, -// ) -> boolean_t; -// } + fn set_traffic_lights_visible(&self, visible: bool); +} -// /// Makes the background of the WKWebView layer transparent. -// /// This differs from Tauri's implementation as it does not change the window background which causes performance performance issues and artifacts when shadows are enabled on the window. -// /// Use Tauri's implementation to make the window itself transparent. -// pub fn make_webview_transparent(target: &tauri::WebviewWindow) -> tauri::Result<()> { -// target.with_webview(|webview| unsafe { -// let wkwebview = webview.inner() as id; -// let no: id = msg_send![class!(NSNumber), numberWithBool:0]; -// // [https://developer.apple.com/documentation/webkit/webview/1408486-drawsbackground] -// let _: id = msg_send![wkwebview, setValue:no forKey: NSString::alloc(nil).init_str("drawsBackground")]; -// }) -// } +impl WebviewWindowExt for WebviewWindow { + #[inline] + fn objc2_nswindow(&self) -> Retained { + // SAFETY: This cast is safe as the existence of the WebviewWindow means it's attached to an NSWindow + unsafe { + (&*self + .ns_window() + .expect("WebviewWindow is always backed by NSWindow") + .cast::()) + .retain() + } + } + + fn set_traffic_lights_visible(&self, visible: bool) { + let nswindow = self.objc2_nswindow(); + for btn in [ + NSWindowButton::CloseButton, + NSWindowButton::MiniaturizeButton, + NSWindowButton::ZoomButton, + ] { + if let Some(btn) = nswindow.standardWindowButton(btn) { + btn.setHidden(!visible); + } + } + } +} diff --git a/apps/desktop/src-tauri/src/platform/mod.rs b/apps/desktop/src-tauri/src/platform/mod.rs index e51d3e709a..48324c5b96 100644 --- a/apps/desktop/src-tauri/src/platform/mod.rs +++ b/apps/desktop/src-tauri/src/platform/mod.rs @@ -38,7 +38,7 @@ pub fn perform_haptic_feedback( _time: Option, ) -> Result<(), String> { #[cfg(target_os = "macos")] - unsafe { + { use objc2_app_kit::{ NSHapticFeedbackManager, NSHapticFeedbackPattern, NSHapticFeedbackPerformanceTime, NSHapticFeedbackPerformer, diff --git a/apps/desktop/src-tauri/src/recording.rs b/apps/desktop/src-tauri/src/recording.rs index a796749932..a36aaea8f0 100644 --- a/apps/desktop/src-tauri/src/recording.rs +++ b/apps/desktop/src-tauri/src/recording.rs @@ -66,7 +66,7 @@ use crate::{ InstantMultipartUpload, build_video_meta, compress_image, create_or_get_video, upload_video, }, web_api::ManagerExt, - windows::{CapWindowId, ShowCapWindow}, + windows::{CapWindow, CapWindowDef}, }; #[derive(Clone)] @@ -478,7 +478,7 @@ pub async fn start_recording( .add_recording_logging_handle(&project_file_path.join("recording-logs.log")) .await?; - if let Some(window) = CapWindowId::Camera.get(&app) { + if let Some(window) = CapWindowDef::Camera.get(&app) { let _ = window.set_content_protected(matches!(inputs.mode, RecordingMode::Studio)); } @@ -563,13 +563,13 @@ pub async fn start_recording( if let Some(show) = inputs .capture_target .display() - .map(|d| ShowCapWindow::WindowCaptureOccluder { screen_id: d.id() }) + .map(|d| CapWindow::WindowCaptureOccluder { screen_id: d.id() }) { let _ = show.show(&app).await; } } ScreenCaptureTarget::Area { screen, .. } => { - let _ = ShowCapWindow::WindowCaptureOccluder { + let _ = CapWindow::WindowCaptureOccluder { screen_id: screen.clone(), } .show(&app) @@ -588,17 +588,17 @@ pub async fn start_recording( for (id, win) in app .webview_windows() .iter() - .filter_map(|(label, win)| CapWindowId::from_str(label).ok().map(|id| (id, win))) + .filter_map(|(label, win)| CapWindowDef::from_str(label).ok().map(|id| (id, win))) { - if matches!(id, CapWindowId::TargetSelectOverlay { .. }) { + if matches!(id, CapWindowDef::TargetSelectOverlay { .. }) { win.close().ok(); } } - let _ = ShowCapWindow::InProgressRecording { countdown } + let _ = CapWindow::InProgressRecording { countdown } .show(&app) .await; - if let Some(window) = CapWindowId::Main.get(&app) { + if let Some(window) = CapWindowDef::Main.get(&app) { let _ = general_settings .map(|v| v.main_window_recording_start_behaviour) .unwrap_or_default() @@ -906,7 +906,7 @@ pub async fn start_recording( ) .kind(tauri_plugin_dialog::MessageDialogKind::Error); - if let Some(window) = CapWindowId::RecordingControls.get(&app) { + if let Some(window) = CapWindowDef::RecordingControls.get(&app) { dialog = dialog.parent(&window); } @@ -970,7 +970,7 @@ async fn handle_spawn_failure( ) .kind(tauri_plugin_dialog::MessageDialogKind::Error); - if let Some(window) = CapWindowId::RecordingControls.get(app) { + if let Some(window) = CapWindowDef::RecordingControls.get(app) { dialog = dialog.parent(&window); } @@ -1097,14 +1097,14 @@ pub async fn delete_recording(app: AppHandle, state: MutableState<'_, App>) -> R .flatten() .unwrap_or_default(); - if let Some(window) = CapWindowId::RecordingControls.get(&app) { + if let Some(window) = CapWindowDef::RecordingControls.get(&app) { let _ = window.close(); } match settings.post_deletion_behaviour { PostDeletionBehaviour::DoNothing => {} PostDeletionBehaviour::ReopenRecordingWindow => { - let _ = ShowCapWindow::Main { + let _ = CapWindow::Main { init_target_mode: None, } .show(&app) @@ -1327,14 +1327,14 @@ async fn handle_recording_end( let _ = app.recording_logging_handle.reload(None); - if let Some(window) = CapWindowId::RecordingControls.get(&handle) { + if let Some(window) = CapWindowDef::RecordingControls.get(&handle) { let _ = window.close(); } - if let Some(window) = CapWindowId::Main.get(&handle) { + if let Some(window) = CapWindowDef::Main.get(&handle) { window.unminimize().ok(); } else { - if let Some(v) = CapWindowId::Camera.get(&handle) { + if let Some(v) = CapWindowDef::Camera.get(&handle) { let _ = v.close(); } let _ = app.mic_feed.ask(microphone::RemoveInput).await; @@ -1342,7 +1342,7 @@ async fn handle_recording_end( app.selected_mic_label = None; app.selected_camera_id = None; app.camera_in_use = false; - if let Some(win) = CapWindowId::Camera.get(&handle) { + if let Some(win) = CapWindowDef::Camera.get(&handle) { win.close().ok(); } } @@ -1560,14 +1560,14 @@ async fn handle_recording_finish( .unwrap_or(PostStudioRecordingBehaviour::OpenEditor) { PostStudioRecordingBehaviour::OpenEditor => { - let _ = ShowCapWindow::Editor { + let _ = CapWindow::Editor { project_path: recording_dir, } .show(app) .await; } PostStudioRecordingBehaviour::ShowOverlay => { - let _ = ShowCapWindow::RecordingsOverlay.show(app).await; + let _ = CapWindow::RecordingsOverlay.show(app).await; let app = AppHandle::clone(app); tokio::spawn(async move { diff --git a/apps/desktop/src-tauri/src/screenshot_editor.rs b/apps/desktop/src-tauri/src/screenshot_editor.rs index 58f7a5f8f6..fa2c4b7a22 100644 --- a/apps/desktop/src-tauri/src/screenshot_editor.rs +++ b/apps/desktop/src-tauri/src/screenshot_editor.rs @@ -1,7 +1,7 @@ use crate::PendingScreenshots; use crate::frame_ws::{WSFrame, create_watch_frame_ws}; use crate::gpu_context; -use crate::windows::{CapWindowId, ScreenshotEditorWindowIds}; +use crate::windows::{CapWindowDef, ScreenshotEditorWindowIds}; use cap_project::{ ProjectConfiguration, RecordingMeta, RecordingMetaInner, SingleSegment, StudioRecordingMeta, VideoMeta, @@ -423,8 +423,8 @@ pub struct SerializedScreenshotEditorInstance { pub async fn create_screenshot_editor_instance( window: Window, ) -> Result { - let CapWindowId::ScreenshotEditor { id } = - CapWindowId::from_str(window.label()).map_err(|e| e.to_string())? + let CapWindowDef::ScreenshotEditor { id } = + CapWindowDef::from_str(window.label()).map_err(|e| e.to_string())? else { return Err("Invalid window".to_string()); }; diff --git a/apps/desktop/src-tauri/src/target_select_overlay.rs b/apps/desktop/src-tauri/src/target_select_overlay.rs index e41efe5e91..a9ebbb8161 100644 --- a/apps/desktop/src-tauri/src/target_select_overlay.rs +++ b/apps/desktop/src-tauri/src/target_select_overlay.rs @@ -11,7 +11,7 @@ use cap_recording::screen_capture::ScreenCaptureTarget; use crate::{ general_settings, window_exclusion::WindowExclusion, - windows::{CapWindowId, ShowCapWindow}, + windows::{CapWindow, CapWindowDef}, }; use scap_targets::{ Display, DisplayId, Window, WindowId, @@ -59,7 +59,7 @@ pub async fn open_target_select_overlays( .map(|d| d.id()) .collect::>(); for display_id in displays { - let _ = ShowCapWindow::TargetSelectOverlay { display_id } + let _ = CapWindow::TargetSelectOverlay { display_id } .show(&app) .await; } @@ -184,7 +184,7 @@ pub async fn update_camera_overlay_bounds( #[instrument(skip(app))] pub async fn close_target_select_overlays(app: AppHandle) -> Result<(), String> { for (id, window) in app.webview_windows() { - if let Ok(CapWindowId::TargetSelectOverlay { .. }) = CapWindowId::from_str(&id) { + if let Ok(CapWindowDef::TargetSelectOverlay { .. }) = CapWindowDef::from_str(&id) { let _ = window.close(); } } @@ -238,12 +238,8 @@ pub async fn focus_window(window_id: WindowId) -> Result<(), String> { .owner_pid() .ok_or("Could not get window owner PID")?; - if let Some(app) = - unsafe { NSRunningApplication::runningApplicationWithProcessIdentifier(pid) } - { - unsafe { - app.activateWithOptions(NSApplicationActivationOptions::ActivateIgnoringOtherApps); - } + if let Some(app) = NSRunningApplication::runningApplicationWithProcessIdentifier(pid) { + app.activateWithOptions(NSApplicationActivationOptions::ActivateIgnoringOtherApps); } } @@ -299,8 +295,8 @@ impl WindowFocusManager { tokio::spawn(async move { let app = window.app_handle(); loop { - let cap_main = CapWindowId::Main.get(app); - let cap_settings = CapWindowId::Settings.get(app); + let cap_main = CapWindowDef::Main.get(app); + let cap_settings = CapWindowDef::Settings.get(app); let main_window_available = cap_main.is_some(); let settings_window_available = cap_settings.is_some(); diff --git a/apps/desktop/src-tauri/src/tray.rs b/apps/desktop/src-tauri/src/tray.rs index 8b2e1efa9c..2d7bb19517 100644 --- a/apps/desktop/src-tauri/src/tray.rs +++ b/apps/desktop/src-tauri/src/tray.rs @@ -2,7 +2,7 @@ use crate::{ NewScreenshotAdded, NewStudioRecordingAdded, RecordingStarted, RecordingStopped, RequestOpenRecordingPicker, RequestOpenSettings, recording, recording_settings::{RecordingSettingsStore, RecordingTargetMode}, - windows::ShowCapWindow, + windows::CapWindow, }; use cap_recording::RecordingMode; @@ -456,7 +456,7 @@ fn handle_previous_item_click(app: &AppHandle, path_str: &str) { let app = app.clone(); let screenshot_path = path; tokio::spawn(async move { - let _ = ShowCapWindow::ScreenshotEditor { + let _ = CapWindow::ScreenshotEditor { path: screenshot_path, } .show(&app) @@ -478,7 +478,7 @@ fn handle_previous_item_click(app: &AppHandle, path_str: &str) { let app = app.clone(); let project_path = path.clone(); tokio::spawn(async move { - let _ = ShowCapWindow::Editor { project_path }.show(&app).await; + let _ = CapWindow::Editor { project_path }.show(&app).await; }); } RecordingMetaInner::Instant(_) => { @@ -553,7 +553,7 @@ pub fn create_tray(app: &AppHandle) -> tauri::Result<()> { Ok(TrayItem::OpenCap) => { let app = app.clone(); tokio::spawn(async move { - let _ = ShowCapWindow::Main { + let _ = CapWindow::Main { init_target_mode: None, } .show(&app) @@ -593,7 +593,7 @@ pub fn create_tray(app: &AppHandle) -> tauri::Result<()> { Ok(TrayItem::OpenSettings) => { let app = app.clone(); tokio::spawn( - async move { ShowCapWindow::Settings { page: None }.show(&app).await }, + async move { CapWindow::Settings { page: None }.show(&app).await }, ); } Ok(TrayItem::UploadLogs) => { diff --git a/apps/desktop/src-tauri/src/windows.rs b/apps/desktop/src-tauri/src/windows.rs index d09d54d0dd..cdbb789bd8 100644 --- a/apps/desktop/src-tauri/src/windows.rs +++ b/apps/desktop/src-tauri/src/windows.rs @@ -33,10 +33,10 @@ use crate::{ use cap_recording::feeds; #[cfg(target_os = "macos")] -const DEFAULT_TRAFFIC_LIGHTS_INSET: LogicalPosition = LogicalPosition::new(12.0, 12.0); +use crate::platform::{self, WebviewWindowExt}; #[derive(Clone, Deserialize, Type)] -pub enum CapWindowId { +pub enum CapWindowDef { // Contains onboarding + permissions Setup, Main, @@ -54,7 +54,7 @@ pub enum CapWindowId { ScreenshotEditor { id: u32 }, } -impl FromStr for CapWindowId { +impl FromStr for CapWindowDef { type Err = String; fn from_str(s: &str) -> Result { @@ -99,7 +99,7 @@ impl FromStr for CapWindowId { } } -impl std::fmt::Display for CapWindowId { +impl std::fmt::Display for CapWindowDef { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Setup => write!(f, "setup"), @@ -124,28 +124,28 @@ impl std::fmt::Display for CapWindowId { } } -impl CapWindowId { +impl CapWindowDef { pub fn label(&self) -> String { self.to_string() } - pub fn title(&self) -> String { + pub const fn title(&self) -> &str { match self { - Self::Setup => "Cap Setup".to_string(), - Self::Settings => "Cap Settings".to_string(), - Self::WindowCaptureOccluder { .. } => "Cap Window Capture Occluder".to_string(), - Self::CaptureArea => "Cap Capture Area".to_string(), - Self::RecordingControls => "Cap Recording Controls".to_string(), - Self::Editor { .. } => "Cap Editor".to_string(), - Self::ScreenshotEditor { .. } => "Cap Screenshot Editor".to_string(), - Self::ModeSelect => "Cap Mode Selection".to_string(), - Self::Camera => "Cap Camera".to_string(), - Self::RecordingsOverlay => "Cap Recordings Overlay".to_string(), - _ => "Cap".to_string(), + Self::Setup => "Cap Setup", + Self::Settings => "Cap Settings", + Self::WindowCaptureOccluder { .. } => "Cap Window Capture Occluder", + Self::CaptureArea => "Cap Capture Area", + Self::RecordingControls => "Cap Recording Controls", + Self::Editor { .. } => "Cap Editor", + Self::ScreenshotEditor { .. } => "Cap Screenshot Editor", + Self::ModeSelect => "Cap Mode Selection", + Self::Camera => "Cap Camera", + Self::RecordingsOverlay => "Cap Recordings Overlay", + _ => "Cap", } } - pub fn activates_dock(&self) -> bool { + pub const fn activates_dock(&self) -> bool { matches!( self, Self::Setup @@ -158,28 +158,57 @@ impl CapWindowId { ) } + #[cfg(target_os = "macos")] + pub const fn pre_solarium_traffic_lights_position(&self) -> LogicalPosition { + match self { + Self::Editor { .. } | Self::ScreenshotEditor { .. } => LogicalPosition::new(20.0, 25.0), + _ => LogicalPosition::new(12.0, 16.0), + } + } + pub fn get(&self, app: &AppHandle) -> Option { let label = self.label(); app.get_webview_window(&label) } #[cfg(target_os = "macos")] - pub fn traffic_lights_position(&self) -> Option>> { + pub const fn undecorated(&self) -> bool { + matches!( + self, + Self::Camera + | Self::WindowCaptureOccluder { .. } + | Self::CaptureArea + | Self::RecordingsOverlay + | Self::TargetSelectOverlay { .. } + ) + } + + #[cfg(target_os = "macos")] + pub const fn disables_window_buttons(&self) -> bool { + matches!(self, Self::RecordingControls) + } + + #[cfg(target_os = "macos")] + pub const fn disables_fullscreen(&self) -> bool { + matches!(self, Self::Settings) + } + + #[cfg(target_os = "macos")] + pub const fn window_level(&self) -> Option { + use objc2_app_kit::{ + NSMainMenuWindowLevel, NSPopUpMenuWindowLevel, NSScreenSaverWindowLevel, + }; + match self { - Self::Editor { .. } | Self::ScreenshotEditor { .. } => { - Some(Some(LogicalPosition::new(20.0, 32.0))) + Self::RecordingControls => Some(NSMainMenuWindowLevel), + Self::TargetSelectOverlay { .. } | Self::CaptureArea => Some(45), + Self::RecordingsOverlay | Self::WindowCaptureOccluder { .. } => { + Some(NSScreenSaverWindowLevel) } - Self::RecordingControls => Some(Some(LogicalPosition::new(-100.0, -100.0))), - Self::Camera - | Self::WindowCaptureOccluder { .. } - | Self::CaptureArea - | Self::RecordingsOverlay - | Self::TargetSelectOverlay { .. } => None, - _ => Some(None), + _ => None, } } - - pub fn min_size(&self) -> Option<(f64, f64)> { + pub const fn min_size(&self) -> Option<(f64, f64)> { Some(match self { Self::Setup => (600.0, 600.0), Self::Main => (300.0, 360.0), @@ -195,7 +224,7 @@ impl CapWindowId { } #[derive(Debug, Clone, Type, Deserialize)] -pub enum ShowCapWindow { +pub enum CapWindow { Setup, Main { init_target_mode: Option, @@ -227,7 +256,7 @@ pub enum ShowCapWindow { }, } -impl ShowCapWindow { +impl CapWindow { pub async fn show(&self, app: &AppHandle) -> tauri::Result { if let Self::Editor { project_path } = &self { let state = app.state::(); @@ -244,7 +273,7 @@ impl ShowCapWindow { } }; - let window_label = CapWindowId::Editor { id: window_id }.label(); + let window_label = CapWindowDef::Editor { id: window_id }.label(); PendingEditorInstances::start_prewarm(app, window_label, project_path.clone()).await; } @@ -261,14 +290,14 @@ impl ShowCapWindow { } } - if let Some(window) = self.id(app).get(app) { + let def = self.def(app); + if let Some(window) = def.get(app) { window.show().ok(); window.unminimize().ok(); window.set_focus().ok(); return Ok(window); } - let _id = self.id(app); let monitor = app.primary_monitor()?.unwrap(); let window = match self { @@ -292,9 +321,6 @@ impl ShowCapWindow { .map(|s| s.enable_new_recording_flow) .unwrap_or_default(); - let title = CapWindowId::Main.title(); - let should_protect = should_protect_window(app, &title); - let window = self .window_builder(app, if new_recording_flow { "/new-main" } else { "/" }) .resizable(false) @@ -303,7 +329,6 @@ impl ShowCapWindow { .minimizable(false) .always_on_top(true) .visible_on_all_workspaces(true) - .content_protected(should_protect) .center() .initialization_script(format!( " @@ -315,13 +340,15 @@ impl ShowCapWindow { )) .build()?; - if new_recording_flow { - #[cfg(target_os = "macos")] - crate::platform::set_window_level(window.as_ref().window(), 50); - } - #[cfg(target_os = "macos")] { + if new_recording_flow { + let _ = window.run_on_main_thread({ + let window = window.clone(); + move || window.objc2_nswindow().setLevel(50) + }); + } + let app_handle = app.clone(); tauri::async_runtime::spawn(async move { let prewarmer = @@ -344,13 +371,7 @@ impl ShowCapWindow { .map(|d| d.id()) == Some(display.id()); - let title = CapWindowId::TargetSelectOverlay { - display_id: display_id.clone(), - } - .title(); - let should_protect = should_protect_window(app, &title); - - let mut window_builder = self + let mut builder = self .window_builder( app, format!("/target-select-overlay?displayId={display_id}&isHoveredDisplay={is_hovered_display}"), @@ -359,29 +380,27 @@ impl ShowCapWindow { .resizable(false) .fullscreen(false) .shadow(false) - .content_protected(should_protect) .always_on_top(true) .visible_on_all_workspaces(true) .skip_taskbar(true) - .transparent(true) - .visible(false); + .transparent(true); #[cfg(target_os = "macos")] { let position = display.raw_handle().logical_position(); let size = display.logical_size().unwrap(); - window_builder = window_builder + builder = builder .inner_size(size.width(), size.height()) .position(position.x(), position.y()); } #[cfg(windows)] { - window_builder = window_builder.inner_size(100.0, 100.0).position(0.0, 0.0); + builder = builder.inner_size(100.0, 100.0).position(0.0, 0.0); } - let window = window_builder.build()?; + let window = builder.build()?; #[cfg(windows)] { @@ -409,42 +428,39 @@ impl ShowCapWindow { app.state::() .spawn(display_id, window.clone()); - #[cfg(target_os = "macos")] - { - crate::platform::set_window_level(window.as_ref().window(), 45); - } - window } Self::Settings { page } => { // Hide main window and target select overlays when settings window opens for (label, window) in app.webview_windows() { - if let Ok(id) = CapWindowId::from_str(&label) + if let Ok(id) = CapWindowDef::from_str(&label) && matches!( id, - CapWindowId::TargetSelectOverlay { .. } - | CapWindowId::Main - | CapWindowId::Camera + CapWindowDef::TargetSelectOverlay { .. } + | CapWindowDef::Main + | CapWindowDef::Camera ) { let _ = window.hide(); } } - self.window_builder( - app, - format!("/settings/{}", page.clone().unwrap_or_default()), - ) - .resizable(true) - .maximized(false) - .center() - .build()? + let mut builder = self + .window_builder( + app, + format!("/settings/{}", page.clone().unwrap_or_default()), + ) + .resizable(true) + .maximized(false) + .center(); + + builder.build()? } Self::Editor { .. } => { - if let Some(main) = CapWindowId::Main.get(app) { + if let Some(main) = CapWindowDef::Main.get(app) { let _ = main.close(); }; - if let Some(camera) = CapWindowId::Camera.get(app) { + if let Some(camera) = CapWindowDef::Camera.get(app) { let _ = camera.close(); }; @@ -455,10 +471,10 @@ impl ShowCapWindow { .build()? } Self::ScreenshotEditor { path: _ } => { - if let Some(main) = CapWindowId::Main.get(app) { + if let Some(main) = CapWindowDef::Main.get(app) { let _ = main.close(); }; - if let Some(camera) = CapWindowId::Camera.get(app) { + if let Some(camera) = CapWindowDef::Camera.get(app) { let _ = camera.close(); }; @@ -470,28 +486,26 @@ impl ShowCapWindow { } Self::Upgrade => { // Hide main window when upgrade window opens - if let Some(main) = CapWindowId::Main.get(app) { + if let Some(main) = CapWindowDef::Main.get(app) { let _ = main.hide(); } - let mut builder = self - .window_builder(app, "/upgrade") + self.window_builder(app, "/upgrade") .resizable(false) .focused(true) .always_on_top(true) .maximized(false) .shadow(true) - .center(); - - builder.build()? + .center() + .build()? } Self::ModeSelect => { - if let Some(main) = CapWindowId::Main.get(app) { + // Hide main window when mode select window opens + if let Some(main) = CapWindowDef::Main.get(app) { let _ = main.hide(); } - let mut builder = self - .window_builder(app, "/mode-select") + self.window_builder(app, "/mode-select") .inner_size(580.0, 340.0) .min_inner_size(580.0, 340.0) .resizable(false) @@ -499,9 +513,8 @@ impl ShowCapWindow { .maximizable(false) .center() .focused(true) - .shadow(true); - - builder.build()? + .shadow(true) + .build()? } Self::Camera => { const WINDOW_SIZE: f64 = 230.0 * 2.0; @@ -517,7 +530,7 @@ impl ShowCapWindow { if enable_native_camera_preview && state.camera_preview.is_initialized() { error!("Unable to initialize camera preview as one already exists!"); - if let Some(window) = CapWindowId::Camera.get(app) { + if let Some(window) = CapWindowDef::Camera.get(app) { window.show().ok(); } return Err(anyhow!( @@ -526,7 +539,7 @@ impl ShowCapWindow { .into()); } - let mut window_builder = self + let mut builder = self .window_builder(app, "/camera") .maximized(false) .resizable(false) @@ -551,7 +564,7 @@ impl ShowCapWindow { .transparent(true) .visible(false); // We set this true in `CameraWindowState::init_window` - let window = window_builder.build()?; + let window = builder.build()?; if enable_native_camera_preview { if let Some(id) = state.selected_camera_id.clone() @@ -582,22 +595,13 @@ impl ShowCapWindow { } #[cfg(target_os = "macos")] - { - crate::platform::set_window_level(window.as_ref().window(), 60); - - _ = window.run_on_main_thread({ - let window = window.as_ref().window(); - move || unsafe { - let Ok(win) = window.ns_window() else { - return; - }; - let win = win as *const objc2_app_kit::NSWindow; - (*win).setCollectionBehavior( - (*win).collectionBehavior() | objc2_app_kit::NSWindowCollectionBehavior::FullScreenAuxiliary, - ); - } - }); - } + dispatch2::run_on_main(|_| { + let nswindow = window.objc2_nswindow(); + nswindow.setCollectionBehavior( + nswindow.collectionBehavior() + | objc2_app_kit::NSWindowCollectionBehavior::FullScreenAuxiliary, + ); + }); window } @@ -607,12 +611,6 @@ impl ShowCapWindow { return Err(tauri::Error::WindowNotFound); }; - let title = CapWindowId::WindowCaptureOccluder { - screen_id: screen_id.clone(), - } - .title(); - let should_protect = should_protect_window(app, &title); - #[cfg(target_os = "macos")] let position = display.raw_handle().logical_position(); @@ -621,7 +619,7 @@ impl ShowCapWindow { let bounds = display.physical_size().unwrap(); - let mut window_builder = self + let mut builder = self .window_builder(app, "/window-capture-occluder") .maximized(false) .resizable(false) @@ -629,35 +627,22 @@ impl ShowCapWindow { .shadow(false) .always_on_top(true) .visible_on_all_workspaces(true) - .content_protected(should_protect) .skip_taskbar(true) .inner_size(bounds.width(), bounds.height()) .position(position.x(), position.y()) .transparent(true); - let window = window_builder.build()?; - + let window = builder.build()?; window.set_ignore_cursor_events(true).unwrap(); - - #[cfg(target_os = "macos")] - { - crate::platform::set_window_level(window.as_ref().window(), 900); - } - window } Self::CaptureArea { screen_id } => { - let title = CapWindowId::CaptureArea.title(); - let should_protect = should_protect_window(app, &title); - - let mut window_builder = self + let mut builder = self .window_builder(app, "/capture-area") .maximized(false) .fullscreen(false) .shadow(false) - .resizable(false) .always_on_top(true) - .content_protected(should_protect) .skip_taskbar(true) .closable(true) .decorations(false) @@ -669,28 +654,20 @@ impl ShowCapWindow { #[cfg(target_os = "macos")] if let Some(bounds) = display.raw_handle().logical_bounds() { - window_builder = window_builder + builder = builder .inner_size(bounds.size().width(), bounds.size().height()) .position(bounds.position().x(), bounds.position().y()); } #[cfg(windows)] if let Some(bounds) = display.raw_handle().physical_bounds() { - window_builder = window_builder + builder = builder .inner_size(bounds.size().width(), bounds.size().height()) .position(bounds.position().x(), bounds.position().y()); } - let window = window_builder.build()?; - - #[cfg(target_os = "macos")] - crate::platform::set_window_level( - window.as_ref().window(), - objc2_app_kit::NSPopUpMenuWindowLevel, - ); - // Hide the main window if the target monitor is the same - if let Some(main_window) = CapWindowId::Main.get(app) + if let Some(main_window) = CapWindowDef::Main.get(app) && let (Ok(outer_pos), Ok(outer_size)) = (main_window.outer_position(), main_window.outer_size()) && let Ok(scale_factor) = main_window.scale_factor() @@ -699,14 +676,14 @@ impl ShowCapWindow { let _ = main_window.minimize(); }; - window + builder.build()? } Self::InProgressRecording { countdown } => { let width = 320.0; let height = 150.0; - let title = CapWindowId::RecordingControls.title(); - let should_protect = should_protect_window(app, &title); + let title = CapWindowDef::RecordingControls.title(); + let should_protect = should_protect_window(app, title); let pos_x = ((monitor.size().width as f64) / monitor.scale_factor() - width) / 2.0; let pos_y = @@ -753,54 +730,22 @@ impl ShowCapWindow { .visible_on_all_workspaces(true) .content_protected(should_protect) .inner_size(width, height) - .position(pos_x, pos_y) - .skip_taskbar(false) + .position( + ((monitor.size().width as f64) / monitor.scale_factor() - width) / 2.0, + (monitor.size().height as f64) / monitor.scale_factor() - height - 120.0, + ) + .skip_taskbar(true) .initialization_script(format!( "window.COUNTDOWN = {};", countdown.unwrap_or_default() )) .build()?; - debug!( - "InProgressRecording window created: label={}, inner_size={:?}, outer_position={:?}", - window.label(), - window.inner_size(), - window.outer_position() - ); - - #[cfg(target_os = "macos")] - { - crate::platform::set_window_level(window.as_ref().window(), 1000); - } - - #[cfg(target_os = "macos")] - { - let show_result = window.show(); - debug!( - "InProgressRecording window.show() result: {:?}", - show_result - ); - fake_window::spawn_fake_window_listener(app.clone(), window.clone()); - } - - #[cfg(windows)] - { - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - let show_result = window.show(); - debug!( - "InProgressRecording window.show() result: {:?}", - show_result - ); - window.set_focus().ok(); - fake_window::spawn_fake_window_listener(app.clone(), window.clone()); - } + fake_window::spawn_fake_window_listener(app.clone(), window.clone()); window } Self::RecordingsOverlay => { - let title = CapWindowId::RecordingsOverlay.title(); - let should_protect = should_protect_window(app, &title); - let window = self .window_builder(app, "/recordings-overlay") .maximized(false) @@ -810,7 +755,6 @@ impl ShowCapWindow { .always_on_top(true) .visible_on_all_workspaces(true) .accept_first_mouse(true) - .content_protected(should_protect) .inner_size( (monitor.size().width as f64) / monitor.scale_factor(), (monitor.size().height as f64) / monitor.scale_factor(), @@ -830,7 +774,7 @@ impl ShowCapWindow { let panel = window.to_panel().unwrap(); - panel.set_level(cocoa::appkit::NSMainMenuWindowLevel); + panel.set_level(objc2_app_kit::NSMainMenuWindowLevel as i32); panel.set_collection_behaviour( NSWindowCollectionBehavior::NSWindowCollectionBehaviorTransient @@ -854,13 +798,28 @@ impl ShowCapWindow { } }; - // removing this for now as it causes windows to just stay hidden sometimes -_- - // window.hide().ok(); - #[cfg(target_os = "macos")] - if let Some(position) = _id.traffic_lights_position() { - add_traffic_lights(&window, position); - } + let _ = window.run_on_main_thread({ + let window = window.clone(); + move || { + if def.disables_window_buttons() { + window.set_traffic_lights_visible(false); + } + + let nswindow = window.objc2_nswindow(); + + if def.disables_fullscreen() { + nswindow.setCollectionBehavior( + nswindow.collectionBehavior() + | objc2_app_kit::NSWindowCollectionBehavior::FullScreenNone, + ); + } + + if let Some(level) = def.window_level() { + nswindow.setLevel(level) + } + } + }); Ok(window) } @@ -870,29 +829,30 @@ impl ShowCapWindow { app: &'a AppHandle, url: impl Into, ) -> WebviewWindowBuilder<'a, Wry, AppHandle> { - let id = self.id(app); + let def = self.def(app); + let should_protect = should_protect_window(app, def.title()); - let mut builder = WebviewWindow::builder(app, id.label(), WebviewUrl::App(url.into())) - .title(id.title()) + let mut builder = WebviewWindow::builder(app, def.label(), WebviewUrl::App(url.into())) + .title(def.title()) .visible(false) .accept_first_mouse(true) - .shadow(true); + .shadow(true) + .content_protected(should_protect); - if let Some(min) = id.min_size() { + if let Some(min) = def.min_size() { builder = builder .inner_size(min.0, min.1) .min_inner_size(min.0, min.1); } #[cfg(target_os = "macos")] - { - if id.traffic_lights_position().is_some() { - builder = builder - .hidden_title(true) - .title_bar_style(tauri::TitleBarStyle::Overlay); - } else { - builder = builder.decorations(false) - } + if def.undecorated() { + builder = builder.decorations(false); + } else { + builder = builder + .hidden_title(true) + .title_bar_style(tauri::TitleBarStyle::Overlay) + .traffic_light_position(def.pre_solarium_traffic_lights_position()); } #[cfg(windows)] @@ -903,64 +863,39 @@ impl ShowCapWindow { builder } - pub fn id(&self, app: &AppHandle) -> CapWindowId { + pub fn def(&self, app: &AppHandle) -> CapWindowDef { match self { - ShowCapWindow::Setup => CapWindowId::Setup, - ShowCapWindow::Main { .. } => CapWindowId::Main, - ShowCapWindow::Settings { .. } => CapWindowId::Settings, - ShowCapWindow::Editor { project_path } => { + CapWindow::Setup => CapWindowDef::Setup, + CapWindow::Main { .. } => CapWindowDef::Main, + CapWindow::Settings { .. } => CapWindowDef::Settings, + CapWindow::Editor { project_path } => { let state = app.state::(); let s = state.ids.lock().unwrap(); let id = s.iter().find(|(path, _)| path == project_path).unwrap().1; - CapWindowId::Editor { id } + CapWindowDef::Editor { id } } - ShowCapWindow::RecordingsOverlay => CapWindowId::RecordingsOverlay, - ShowCapWindow::TargetSelectOverlay { display_id } => CapWindowId::TargetSelectOverlay { + CapWindow::RecordingsOverlay => CapWindowDef::RecordingsOverlay, + CapWindow::TargetSelectOverlay { display_id } => CapWindowDef::TargetSelectOverlay { display_id: display_id.clone(), }, - ShowCapWindow::WindowCaptureOccluder { screen_id } => { - CapWindowId::WindowCaptureOccluder { - screen_id: screen_id.clone(), - } - } - ShowCapWindow::CaptureArea { .. } => CapWindowId::CaptureArea, - ShowCapWindow::Camera => CapWindowId::Camera, - ShowCapWindow::InProgressRecording { .. } => CapWindowId::RecordingControls, - ShowCapWindow::Upgrade => CapWindowId::Upgrade, - ShowCapWindow::ModeSelect => CapWindowId::ModeSelect, - ShowCapWindow::ScreenshotEditor { path } => { + CapWindow::WindowCaptureOccluder { screen_id } => CapWindowDef::WindowCaptureOccluder { + screen_id: screen_id.clone(), + }, + CapWindow::CaptureArea { .. } => CapWindowDef::CaptureArea, + CapWindow::Camera => CapWindowDef::Camera, + CapWindow::InProgressRecording { .. } => CapWindowDef::RecordingControls, + CapWindow::Upgrade => CapWindowDef::Upgrade, + CapWindow::ModeSelect => CapWindowDef::ModeSelect, + CapWindow::ScreenshotEditor { path } => { let state = app.state::(); let s = state.ids.lock().unwrap(); let id = s.iter().find(|(p, _)| p == path).unwrap().1; - CapWindowId::ScreenshotEditor { id } + CapWindowDef::ScreenshotEditor { id } } } } } -#[cfg(target_os = "macos")] -fn add_traffic_lights(window: &WebviewWindow, controls_inset: Option>) { - use crate::platform::delegates; - - let target_window = window.clone(); - window - .run_on_main_thread(move || { - delegates::setup( - target_window.as_ref().window(), - controls_inset.unwrap_or(DEFAULT_TRAFFIC_LIGHTS_INSET), - ); - - let c_win = target_window.clone(); - target_window.on_window_event(move |event| match event { - tauri::WindowEvent::ThemeChanged(..) | tauri::WindowEvent::Focused(..) => { - position_traffic_lights_impl(&c_win.as_ref().window(), controls_inset); - } - _ => {} - }); - }) - .ok(); -} - #[tauri::command] #[specta::specta] #[instrument(skip(window))] @@ -970,49 +905,6 @@ pub fn set_theme(window: tauri::Window, theme: AppTheme) { AppTheme::Light => Some(tauri::Theme::Light), AppTheme::Dark => Some(tauri::Theme::Dark), }); - - #[cfg(target_os = "macos")] - match CapWindowId::from_str(window.label()) { - Ok(win) if win.traffic_lights_position().is_some() => position_traffic_lights(window, None), - Ok(_) | Err(_) => {} - } -} - -#[tauri::command] -#[specta::specta] -#[instrument(skip(_window))] -pub fn position_traffic_lights(_window: tauri::Window, _controls_inset: Option<(f64, f64)>) { - #[cfg(target_os = "macos")] - position_traffic_lights_impl( - &_window, - _controls_inset.map(LogicalPosition::from).or_else(|| { - // Attempt to get the default inset from the window's traffic lights position - CapWindowId::from_str(_window.label()) - .ok() - .and_then(|id| id.traffic_lights_position().flatten()) - }), - ); -} - -#[cfg(target_os = "macos")] -fn position_traffic_lights_impl( - window: &tauri::Window, - controls_inset: Option>, -) { - use crate::platform::delegates::{UnsafeWindowHandle, position_window_controls}; - let c_win = window.clone(); - window - .run_on_main_thread(move || { - let ns_window = match c_win.ns_window() { - Ok(handle) => handle, - Err(_) => return, - }; - position_window_controls( - UnsafeWindowHandle(ns_window), - &controls_inset.unwrap_or(DEFAULT_TRAFFIC_LIGHTS_INSET), - ); - }) - .ok(); } fn should_protect_window(app: &AppHandle, window_title: &str) -> bool { @@ -1033,10 +925,10 @@ fn should_protect_window(app: &AppHandle, window_title: &str) -> bool { #[instrument(skip(app))] pub fn refresh_window_content_protection(app: AppHandle) -> Result<(), String> { for (label, window) in app.webview_windows() { - if let Ok(id) = CapWindowId::from_str(&label) { + if let Ok(id) = CapWindowDef::from_str(&label) { let title = id.title(); window - .set_content_protected(should_protect_window(&app, &title)) + .set_content_protected(should_protect_window(&app, title)) .map_err(|e| e.to_string())?; } } @@ -1113,7 +1005,6 @@ impl MonitorExt for Display { #[specta::specta] #[tauri::command(async)] -#[instrument(skip(_window))] pub fn set_window_transparent(_window: tauri::Window, _value: bool) { #[cfg(target_os = "macos")] { diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 5f2a676ebd..5b7777b1b7 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -54,6 +54,7 @@ "../src/assets/rive/*.riv": "assets/rive/" }, "macOS": { + "minimumSystemVersion": "13.1", "dmg": { "background": "assets/dmg-background.png", "appPosition": { @@ -84,7 +85,13 @@ "ext": ["cap"], "name": "Cap Project", "mimeType": "application/x-cap-project", - "role": "Editor" + "role": "Editor", + "rank": "Owner", + "description": "Cap Project", + "exportedType": { + "identifier": "so.cap.desktop.project", + "conformsTo": ["com.apple.package"] + } } ] } diff --git a/apps/desktop/src/components/CapErrorBoundary.tsx b/apps/desktop/src/components/CapErrorBoundary.tsx index 5bf7fc132e..2dca68d1d4 100644 --- a/apps/desktop/src/components/CapErrorBoundary.tsx +++ b/apps/desktop/src/components/CapErrorBoundary.tsx @@ -9,9 +9,9 @@ export function CapErrorBoundary(props: ParentProps) { fallback={(e: Error) => { console.error(e); return ( -
+
-

+

An Error Occured

diff --git a/apps/desktop/src/components/Loader.tsx b/apps/desktop/src/components/Loader.tsx index f88661a0e0..b8da544d80 100644 --- a/apps/desktop/src/components/Loader.tsx +++ b/apps/desktop/src/components/Loader.tsx @@ -2,7 +2,7 @@ export function AbsoluteInsetLoader() { return (

- +
); diff --git a/apps/desktop/src/components/SignInButton.tsx b/apps/desktop/src/components/SignInButton.tsx index 08fc3f12a9..2c9f0b834e 100644 --- a/apps/desktop/src/components/SignInButton.tsx +++ b/apps/desktop/src/components/SignInButton.tsx @@ -11,7 +11,7 @@ export function SignInButton( return (
-
+
Commercial License -

+

For commercial use

@@ -261,7 +261,7 @@ function CommercialLicensePurchase() { onClick={() => openCommercialCheckout.mutate()} disabled={openCommercialCheckout.isPending} variant="dark" - class="w-full !rounded-full mt-10 !h-[48px] text-lg font-medium" + class="w-full rounded-full! mt-10 h-[48px]! text-lg font-medium" size="lg" > {openCommercialCheckout.isPending @@ -281,9 +281,9 @@ function CommercialLicensePurchase() { ].map((feature) => (
  • - +
    - + {feature}
  • diff --git a/apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx b/apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx index 3c694f45d3..2229d7bac9 100644 --- a/apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx +++ b/apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx @@ -150,7 +150,7 @@ export default function Recordings() { 0} fallback={ -

    +

    No recordings found

    } @@ -176,11 +176,11 @@ export default function Recordings() {
    -

    +

    No {activeTab()} recordings

    -
      +
        {(recording) => ( 0} fallback={ -

        +

        No screenshots found

        } @@ -131,7 +131,7 @@ export default function Screenshots() {
    -
      +
        {(screenshot) => (
        +
        {showStartup() && ( { @@ -112,7 +112,7 @@ export default function () {
        -

        +

        Permissions Required

        Cap needs permissions to run properly.

        @@ -126,11 +126,11 @@ export default function () { return (
      • -
        - +
        + {permission.name} Permission - + {permission.description}
        @@ -172,7 +172,7 @@ export default function () {
        -

        +

        Select Recording Mode

        @@ -314,7 +314,7 @@ function Startup(props: { onClose: () => void }) { >

        void }) { }`} > Cloud One @@ -457,7 +457,7 @@ function Startup(props: { onClose: () => void }) { }`} > Cloud Two diff --git a/apps/desktop/src/routes/(window-chrome)/update.tsx b/apps/desktop/src/routes/(window-chrome)/update.tsx index 2dddd8a402..7c694ad2aa 100644 --- a/apps/desktop/src/routes/(window-chrome)/update.tsx +++ b/apps/desktop/src/routes/(window-chrome)/update.tsx @@ -16,11 +16,11 @@ export default function () { }); return ( -
        +
        No update available + No update available } keyed > @@ -69,12 +69,12 @@ export default function () {
        + } >
        -

        +

        Update has been installed. Restart Cap to finish updating.

        @@ -93,7 +93,7 @@ export default function () { > {(status) => ( <> -

        +

        Installing Update

        diff --git a/apps/desktop/src/routes/(window-chrome)/upgrade.tsx b/apps/desktop/src/routes/(window-chrome)/upgrade.tsx index bf160cde30..2de6e359f1 100644 --- a/apps/desktop/src/routes/(window-chrome)/upgrade.tsx +++ b/apps/desktop/src/routes/(window-chrome)/upgrade.tsx @@ -328,7 +328,7 @@ export default function Page() { ) : ( <>
        -

        +

        Early Adopter Pricing

        @@ -361,7 +361,7 @@ export default function Page() {

        Commercial License

        -

        +

        For commercial use

        @@ -402,9 +402,9 @@ export default function Page() { ].map((feature) => (
      • - +
        - + {feature}
      • @@ -421,7 +421,7 @@ export default function Page() { onClick={() => openCommercialCheckout.mutate()} disabled={openCommercialCheckout.isPending} variant="dark" - class="w-full !rounded-full !h-[48px] text-lg font-medium" + class="w-full rounded-full! h-[48px]! text-lg font-medium" size="lg" > {openCommercialCheckout.isPending @@ -457,7 +457,7 @@ export default function Page() { riveInstance.play("items-coming-in"); } }} - class="flex-grow p-3 h-[700px] flex-1 bg-gray-12 rounded-2xl border shadow-sm text-card-foreground md:p-3" + class="grow p-3 h-[700px] flex-1 bg-gray-12 rounded-2xl border shadow-sm text-card-foreground md:p-3" >
        @@ -512,7 +512,7 @@ export default function Page() {
        -
        +
        -
        +