diff --git a/.github/actions/pnpm_install/action.yaml b/.github/actions/pnpm_install/action.yaml index 6cfc30a264..8af858366e 100644 --- a/.github/actions/pnpm_install/action.yaml +++ b/.github/actions/pnpm_install/action.yaml @@ -3,6 +3,7 @@ runs: steps: - uses: pnpm/action-setup@v4 with: + version: "10.26.0" run_install: false - uses: actions/setup-node@v4 diff --git a/.github/actions/rust_install/action.yaml b/.github/actions/rust_install/action.yaml index d31debfe58..3406f97030 100644 --- a/.github/actions/rust_install/action.yaml +++ b/.github/actions/rust_install/action.yaml @@ -10,15 +10,6 @@ runs: echo "channel=$(grep 'channel' rust-toolchain.toml | cut -d'"' -f2)" >> $GITHUB_OUTPUT echo "components=$(grep 'components' rust-toolchain.toml | sed 's/.*\[//' | sed 's/\].*//' | tr -d '"' | tr -d ' ')" >> $GITHUB_OUTPUT shell: bash - - id: cache-key - run: | - if command -v ldd &> /dev/null; then - GLIBC_VER=$(ldd --version 2>/dev/null | head -n1 | awk '{print $NF}') - echo "suffix=-glibc-${GLIBC_VER}" >> $GITHUB_OUTPUT - else - echo "suffix=" >> $GITHUB_OUTPUT - fi - shell: bash - if: inputs.platform == 'macos' uses: dtolnay/rust-toolchain@master with: @@ -67,4 +58,3 @@ runs: - uses: Swatinem/rust-cache@v2 with: cache-targets: "true" - prefix-key: v0-rust${{ steps.cache-key.outputs.suffix }} diff --git a/.github/workflows/desktop_cd.yaml b/.github/workflows/desktop_cd.yaml index d14f4aaae5..8faae95b81 100644 --- a/.github/workflows/desktop_cd.yaml +++ b/.github/workflows/desktop_cd.yaml @@ -111,7 +111,7 @@ jobs: run: | chmod +x ./apps/desktop/src-tauri/binaries/stt-${{ matrix.target }} ./scripts/sidecar.sh "./apps/desktop/${{ env.TAURI_CONF_PATH }}" "binaries/stt" - # - run: ./scripts/resource.sh "./apps/desktop/${{ env.TAURI_CONF_PATH }}" "resources/libonnxruntime.dylib" + - run: ./scripts/resource.sh "./apps/desktop/${{ env.TAURI_CONF_PATH }}" "resources/libonnxruntime.dylib" - uses: ./.github/actions/apple_cert id: apple-cert with: @@ -145,7 +145,7 @@ jobs: APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - APPLE_SIGNING_IDENTITY: ${{ steps.apple-cert.outputs.cert-id }} + APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }} @@ -212,7 +212,7 @@ jobs: - uses: ./.github/actions/pnpm_install # - uses: ./.github/actions/setup_flathub - run: pnpm -F ui build - # - run: ./scripts/resource.sh "./apps/desktop/${{ env.TAURI_CONF_PATH }}" "resources/libonnxruntime.so" + - run: ./scripts/resource.sh "./apps/desktop/${{ env.TAURI_CONF_PATH }}" "resources/libonnxruntime.so" - run: | FEATURES_FLAG="" if [[ "${{ inputs.channel }}" == "staging" ]]; then @@ -377,3 +377,10 @@ jobs: name: desktop_v${{ needs.compute-version.outputs.version }} body: "https://hyprnote.com/changelog/${{ needs.compute-version.outputs.version }}" artifacts: ${{ steps.artifacts.outputs.list }} + - run: | + set -e + curl -f -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/dispatches" \ + -d '{"event_type": "desktop_release", "client_payload": {"tag": "desktop_v${{ needs.compute-version.outputs.version }}"}}' diff --git a/.github/workflows/devin_changelog.yaml b/.github/workflows/devin_changelog.yaml new file mode 100644 index 0000000000..fb8e93fe3c --- /dev/null +++ b/.github/workflows/devin_changelog.yaml @@ -0,0 +1,17 @@ +name: devin_desktop_changelog + +on: + repository_dispatch: + types: [desktop_release] + +jobs: + run: + if: startsWith(github.event.client_payload.tag, 'desktop_v1') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/devin + with: + api_key: ${{ secrets.DEVIN_API_KEY }} + prompt: Create new changelog entry for tag {{ tag }}, following guide at apps/web/content/changelog/AGENTS.md. + vars: '{"tag": "${{ github.event.client_payload.tag }}"}' diff --git a/.github/workflows/fmt.yaml b/.github/workflows/fmt.yaml index a61db22097..b9d055094d 100644 --- a/.github/workflows/fmt.yaml +++ b/.github/workflows/fmt.yaml @@ -10,16 +10,11 @@ on: - main jobs: fmt: - runs-on: depot-ubuntu-24.04-4 + runs-on: depot-ubuntu-24.04-8 steps: - uses: actions/checkout@v4 - uses: ./.github/actions/pnpm_install - uses: ./.github/actions/rust_install - - uses: actions/cache@v4 - with: - path: ~/.cache/dprint/cache/plugins - key: dprint-${{ hashFiles('dprint.json') }} - - run: pnpm fmt:check - uses: dprint/check@v2.3 with: config-path: dprint.json diff --git a/Cargo.lock b/Cargo.lock index 0a161ab06a..30d251b3d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,7 +206,6 @@ dependencies = [ "cfg-if", "cipher", "cpufeatures", - "zeroize", ] [[package]] @@ -228,7 +227,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "const-random", "getrandom 0.3.4", "once_cell", "serde", @@ -586,24 +584,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -[[package]] -name = "as-raw-xcb-connection" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" - [[package]] name = "as-slice" version = "0.2.1" @@ -613,34 +599,12 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "ash" -version = "0.38.0+1.3.281" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" -dependencies = [ - "libloading 0.8.9", -] - -[[package]] -name = "ash-window" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52bca67b61cb81e5553babde81b8211f713cb6db79766f80168f3e5f40ea6c82" -dependencies = [ - "ash", - "raw-window-handle", - "raw-window-metal", -] - [[package]] name = "ashpd" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" dependencies = [ - "async-fs", - "async-net", "enumflags2", "futures-channel", "futures-util", @@ -652,25 +616,7 @@ dependencies = [ "url", "wayland-backend", "wayland-client", - "wayland-protocols 0.32.9", - "zbus", -] - -[[package]] -name = "ashpd" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0986d5b4f0802160191ad75f8d33ada000558757db3defb70299ca95d9fcbd" -dependencies = [ - "async-fs", - "async-net", - "enumflags2", - "futures-channel", - "futures-util", - "rand 0.9.2", - "serde", - "serde_repr", - "url", + "wayland-protocols", "zbus", ] @@ -743,19 +689,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-compression" -version = "0.4.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" -dependencies = [ - "compression-codecs", - "compression-core", - "futures-core", - "futures-io", - "pin-project-lite", -] - [[package]] name = "async-executor" version = "1.13.3" @@ -770,32 +703,6 @@ dependencies = [ "slab", ] -[[package]] -name = "async-fs" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" -dependencies = [ - "async-lock", - "blocking", - "futures-lite 2.6.1", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.5.0", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite 2.6.1", - "once_cell", -] - [[package]] name = "async-io" version = "2.6.0" @@ -825,17 +732,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-net" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" -dependencies = [ - "async-io", - "blocking", - "futures-lite 2.6.1", -] - [[package]] name = "async-openai" version = "0.27.2" @@ -922,33 +818,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "async-std" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" -dependencies = [ - "async-channel 1.9.0", - "async-global-executor", - "async-io", - "async-lock", - "async-process", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite 2.6.1", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -986,7 +855,7 @@ dependencies = [ "serde_path_to_error", "serde_qs 0.10.1", "smart-default 0.6.0", - "smol_str 0.1.24", + "smol_str", "thiserror 1.0.69", "tokio", ] @@ -1008,19 +877,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "async_zip" -version = "0.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b9f7252833d5ed4b00aa9604b563529dd5e11de9c23615de2dcdf91eb87b52" -dependencies = [ - "async-compression", - "crc32fast", - "futures-lite 2.6.1", - "pin-project", - "thiserror 1.0.69", -] - [[package]] name = "atk" version = "0.18.2" @@ -1044,12 +900,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - [[package]] name = "atomic-waker" version = "1.1.2" @@ -2076,16 +1926,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ - "bit-vec 0.6.3", -] - -[[package]] -name = "bit-set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec 0.8.0", + "bit-vec", ] [[package]] @@ -2094,12 +1935,6 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" -[[package]] -name = "bit-vec" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" - [[package]] name = "bit_field" version = "0.10.3" @@ -2142,64 +1977,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "blade-graphics" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4deb8f595ce7f00dee3543ebf6fd9a20ea86fc421ab79600dac30876250bdae" -dependencies = [ - "ash", - "ash-window", - "bitflags 2.10.0", - "bytemuck", - "codespan-reporting", - "glow", - "gpu-alloc", - "gpu-alloc-ash", - "hidden-trait", - "js-sys", - "khronos-egl", - "libloading 0.8.9", - "log", - "mint", - "naga", - "objc2 0.6.3", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-metal 0.3.2", - "objc2-quartz-core 0.3.2", - "objc2-ui-kit", - "once_cell", - "raw-window-handle", - "slab", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "blade-macros" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27142319e2f4c264581067eaccb9f80acccdde60d8b4bf57cc50cd3152f109ca" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "blade-util" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6be3a82c001ba7a17b6f8e413ede5d1004e6047213f8efaf0ffc15b5c4904c" -dependencies = [ - "blade-graphics", - "bytemuck", - "log", - "profiling", -] - [[package]] name = "block" version = "0.1.6" @@ -2569,32 +2346,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "calloop" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" -dependencies = [ - "bitflags 2.10.0", - "log", - "polling", - "rustix 0.38.44", - "slab", - "thiserror 1.0.69", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" -dependencies = [ - "calloop", - "rustix 0.38.44", - "wayland-backend", - "wayland-client", -] - [[package]] name = "camino" version = "1.2.1" @@ -2768,24 +2519,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "cbindgen" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" -dependencies = [ - "heck 0.4.1", - "indexmap 2.12.1", - "log", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn 2.0.111", - "tempfile", - "toml 0.8.23", -] - [[package]] name = "cc" version = "1.2.49" @@ -2852,15 +2585,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "cgl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" -dependencies = [ - "libc", -] - [[package]] name = "chardetng" version = "0.1.17" @@ -2961,7 +2685,6 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", - "zeroize", ] [[package]] @@ -3049,39 +2772,23 @@ checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59" [[package]] name = "cmake" -version = "0.1.55" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49d74c227b6cc9f3c51a2c7c667a05b6453f7f0f952a5f8e4493bb9e731d68e" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" dependencies = [ "cc", ] [[package]] name = "cocoa" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation 0.1.2", - "core-foundation 0.9.4", - "core-graphics 0.23.2", - "foreign-types 0.5.0", - "libc", - "objc", -] - -[[package]] -name = "cocoa" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" +checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" dependencies = [ "bitflags 2.10.0", "block", - "cocoa-foundation 0.2.0", - "core-foundation 0.10.0", + "cocoa-foundation", + "core-foundation 0.10.1", "core-graphics 0.24.0", "foreign-types 0.5.0", "libc", @@ -3090,29 +2797,14 @@ dependencies = [ [[package]] name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" +checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" dependencies = [ "bitflags 2.10.0", "block", - "core-foundation 0.10.0", + "core-foundation 0.10.1", "core-graphics-types 0.2.0", - "libc", "objc", ] @@ -3150,17 +2842,6 @@ dependencies = [ "tera", ] -[[package]] -name = "codespan-reporting" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" -dependencies = [ - "serde", - "termcolor", - "unicode-width 0.2.0", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -3193,20 +2874,10 @@ dependencies = [ ] [[package]] -name = "command-fds" -version = "0.3.2" +name = "compact_str" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f849b92c694fe237ecd8fafd1ba0df7ae0d45c1df6daeb7f68ed4220d51640bd" -dependencies = [ - "nix 0.30.1", - "thiserror 2.0.17", -] - -[[package]] -name = "compact_str" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" dependencies = [ "castaway", "cfg-if", @@ -3231,24 +2902,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "compression-codecs" -version = "0.4.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" -dependencies = [ - "compression-core", - "deflate64", - "flate2", - "memchr", -] - -[[package]] -name = "compression-core" -version = "0.4.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -3418,9 +3071,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -3432,19 +3085,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core-graphics" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "foreign-types 0.5.0", - "libc", -] - [[package]] name = "core-graphics" version = "0.24.0" @@ -3452,21 +3092,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ "bitflags 2.10.0", - "core-foundation 0.10.0", + "core-foundation 0.10.1", "core-graphics-types 0.2.0", "foreign-types 0.5.0", "libc", ] [[package]] -name = "core-graphics-helmer-fork" -version = "0.24.0" +name = "core-graphics" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ "bitflags 2.10.0", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", "foreign-types 0.5.0", "libc", ] @@ -3489,47 +3129,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.10.0", - "core-foundation 0.10.0", - "libc", -] - -[[package]] -name = "core-graphics2" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4583956b9806b69f73fcb23aee05eb3620efc282972f08f6a6db7504f8334d" -dependencies = [ - "bitflags 2.10.0", - "block", - "cfg-if", - "core-foundation 0.10.0", - "libc", -] - -[[package]] -name = "core-text" -version = "21.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130" -dependencies = [ - "core-foundation 0.10.0", - "core-graphics 0.24.0", - "foreign-types 0.5.0", - "libc", -] - -[[package]] -name = "core-video" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d45e71d5be22206bed53c3c3cb99315fc4c3d31b8963808c6bc4538168c4f8ef" -dependencies = [ - "block", - "core-foundation 0.10.0", - "core-graphics2", - "io-surface", + "core-foundation 0.10.1", "libc", - "metal 0.29.0", ] [[package]] @@ -3541,15 +3142,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "core_maths" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" -dependencies = [ - "libm", -] - [[package]] name = "coreaudio-rs" version = "0.11.3" @@ -3570,29 +3162,6 @@ dependencies = [ "bindgen 0.72.1", ] -[[package]] -name = "cosmic-text" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da46a9d5a8905cc538a4a5bceb6a4510de7a51049c5588c0114efce102bcbbe8" -dependencies = [ - "bitflags 2.10.0", - "fontdb 0.16.2", - "log", - "rangemap", - "rustc-hash 1.1.0", - "rustybuzz 0.14.1", - "self_cell", - "smol_str 0.2.2", - "swash", - "sys-locale", - "ttf-parser 0.21.1", - "unicode-bidi", - "unicode-linebreak", - "unicode-script", - "unicode-segmentation", -] - [[package]] name = "cpal" version = "0.15.3" @@ -3757,15 +3326,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -3832,7 +3392,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] @@ -3894,22 +3453,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "ctor" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec09e802f5081de6157da9a75701d6c713d8dc3ba52571fd4bd25f412644e8a6" -dependencies = [ - "ctor-proc-macro", - "dtor", -] - -[[package]] -name = "ctor-proc-macro" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" - [[package]] name = "cudarc" version = "0.16.6" @@ -4276,12 +3819,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "deflate64" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" - [[package]] name = "deno_core" version = "0.338.0" @@ -4291,8 +3828,8 @@ dependencies = [ "anyhow", "az", "bincode", - "bit-set 0.5.3", - "bit-vec 0.6.3", + "bit-set", + "bit-vec", "bytes", "capacity_builder", "cooked-waker", @@ -4518,7 +4055,6 @@ dependencies = [ "tauri-build", "tauri-plugin-analytics", "tauri-plugin-apple-calendar", - "tauri-plugin-auth", "tauri-plugin-autostart", "tauri-plugin-cli", "tauri-plugin-cli2", @@ -4532,7 +4068,6 @@ dependencies = [ "tauri-plugin-fs", "tauri-plugin-hooks", "tauri-plugin-http", - "tauri-plugin-icon", "tauri-plugin-importer", "tauri-plugin-listener", "tauri-plugin-listener2", @@ -4546,7 +4081,6 @@ dependencies = [ "tauri-plugin-prevent-default", "tauri-plugin-process", "tauri-plugin-sentry", - "tauri-plugin-settings", "tauri-plugin-shell", "tauri-plugin-single-instance", "tauri-plugin-store", @@ -4555,8 +4089,6 @@ dependencies = [ "tauri-plugin-tracing", "tauri-plugin-tray", "tauri-plugin-updater", - "tauri-plugin-updater2", - "tauri-plugin-window-state", "tauri-plugin-windows", "tauri-specta", "tokio", @@ -4579,7 +4111,6 @@ dependencies = [ "regex", "serde", "specta", - "sysinfo 0.37.2", "thiserror 2.0.17", "tokio", "tracing", @@ -4624,15 +4155,6 @@ dependencies = [ "dirs-sys 0.3.7", ] -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", -] - [[package]] name = "dirs" version = "6.0.0" @@ -4653,18 +4175,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.4.6", - "windows-sys 0.48.0", -] - [[package]] name = "dirs-sys" version = "0.5.0" @@ -4855,39 +4365,12 @@ dependencies = [ "dtoa", ] -[[package]] -name = "dtor" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" -dependencies = [ - "dtor-proc-macro", -] - -[[package]] -name = "dtor-proc-macro" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" - [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "dwrote" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" -dependencies = [ - "lazy_static", - "libc", - "winapi", - "wio", -] - [[package]] name = "dyn-clone" version = "1.0.20" @@ -5153,16 +4636,6 @@ dependencies = [ "cc", ] -[[package]] -name = "etagere" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" -dependencies = [ - "euclid", - "svg_fmt", -] - [[package]] name = "etcetera" version = "0.8.0" @@ -5185,15 +4658,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "euclid" -version = "0.22.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" -dependencies = [ - "num-traits", -] - [[package]] name = "event-listener" version = "2.5.3" @@ -5293,7 +4757,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" dependencies = [ - "bit-set 0.5.3", + "bit-set", "regex-automata", "regex-syntax", ] @@ -5381,15 +4845,6 @@ dependencies = [ "rustc_version 0.4.1", ] -[[package]] -name = "figma_squircle" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "400cd5cde6560b07cd6c3d5e6c869870f9304cb38820d24ac2bdd89535cdec5b" -dependencies = [ - "phf 0.13.1", -] - [[package]] name = "file" version = "0.1.0" @@ -5420,17 +4875,6 @@ dependencies = [ "flate2", ] -[[package]] -name = "filedescriptor" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" -dependencies = [ - "libc", - "thiserror 1.0.69", - "winapi", -] - [[package]] name = "filetime" version = "0.2.26" @@ -5486,36 +4930,6 @@ dependencies = [ "miniz_oxide 0.8.9", ] -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" - -[[package]] -name = "float-ord" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" - -[[package]] -name = "float_next_after" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "spin", -] - [[package]] name = "fnv" version = "1.0.7" @@ -5534,52 +4948,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "font-types" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "fontconfig-parser" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" -dependencies = [ - "roxmltree", -] - -[[package]] -name = "fontdb" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" -dependencies = [ - "fontconfig-parser", - "log", - "memmap2", - "slotmap", - "tinyvec", - "ttf-parser 0.20.0", -] - -[[package]] -name = "fontdb" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" -dependencies = [ - "fontconfig-parser", - "log", - "memmap2", - "slotmap", - "tinyvec", - "ttf-parser 0.25.1", -] - [[package]] name = "foreign-types" version = "0.3.2" @@ -5631,17 +4999,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "freetype-sys" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "from_variant" version = "2.0.2" @@ -7279,18 +6636,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "glow" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" -dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "gobject-sys" version = "0.18.0" @@ -7482,362 +6827,28 @@ dependencies = [ ] [[package]] -name = "gpu-alloc" -version = "0.6.0" +name = "grep-cli" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +checksum = "cf32d263c5d5cc2a23ce587097f5ddafdb188492ba2e6fb638eaccdc22453631" dependencies = [ - "bitflags 2.10.0", - "gpu-alloc-types", + "bstr", + "globset", + "libc", + "log", + "termcolor", + "winapi-util", ] [[package]] -name = "gpu-alloc-ash" -version = "0.7.0" +name = "group" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbda7a18a29bc98c2e0de0435c347df935bf59489935d0cbd0b73f1679b6f79a" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ash", - "gpu-alloc-types", - "tinyvec", -] - -[[package]] -name = "gpu-alloc-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" -dependencies = [ - "bitflags 2.10.0", -] - -[[package]] -name = "gpui" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "979b45cfa6ec723b6f42330915a1b3769b930d02b2d505f9697f8ca602bee707" -dependencies = [ - "anyhow", - "as-raw-xcb-connection", - "ashpd 0.11.0", - "async-task", - "bindgen 0.71.1", - "blade-graphics", - "blade-macros", - "blade-util", - "block", - "bytemuck", - "calloop", - "calloop-wayland-source", - "cbindgen", - "cocoa 0.26.0", - "cocoa-foundation 0.2.0", - "core-foundation 0.10.0", - "core-foundation-sys", - "core-graphics 0.24.0", - "core-text", - "core-video", - "cosmic-text", - "ctor 0.4.3", - "derive_more 0.99.20", - "embed-resource", - "etagere", - "filedescriptor", - "flume", - "foreign-types 0.5.0", - "futures", - "gpui-macros", - "gpui_collections", - "gpui_http_client", - "gpui_media", - "gpui_refineable", - "gpui_semantic_version", - "gpui_sum_tree", - "gpui_util", - "gpui_util_macros", - "image 0.25.9", - "inventory", - "itertools 0.14.0", - "libc", - "log", - "lyon", - "metal 0.29.0", - "naga", - "num_cpus", - "objc", - "oo7", - "open", - "parking", - "parking_lot 0.12.5", - "pathfinder_geometry", - "pin-project", - "postage", - "profiling", - "rand 0.9.2", - "raw-window-handle", - "resvg", - "schemars 1.1.0", - "seahash", - "serde", - "serde_json", - "slotmap", - "smallvec 1.15.1", - "smol", - "stacksafe", - "strum 0.27.2", - "taffy", - "thiserror 2.0.17", - "usvg", - "uuid", - "waker-fn", - "wayland-backend", - "wayland-client", - "wayland-cursor", - "wayland-protocols 0.31.2", - "wayland-protocols-plasma", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-numerics", - "windows-registry 0.5.3", - "x11-clipboard", - "x11rb", - "xkbcommon", - "zed-font-kit", - "zed-scap", - "zed-xim", -] - -[[package]] -name = "gpui-macros" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcb02dd63a2859714ac7b6b476937617c3c744157af1b49f7c904023a79039be" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "gpui_collections" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae39dc6d3d201be97e4bc08d96dbef2bc5b5c3d5734e05786e8cc3043342351c" -dependencies = [ - "indexmap 2.12.1", - "rustc-hash 2.1.1", -] - -[[package]] -name = "gpui_derive_refineable" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644de174341a87b3478bd65b66bca38af868bcf2b2e865700523734f83cfc664" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "gpui_http_client" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23822b0a6d2c5e6a42507980a0ab3848610ea908942c8ef98187f646f690335e" -dependencies = [ - "anyhow", - "async-compression", - "async-fs", - "bytes", - "derive_more 0.99.20", - "futures", - "gpui_util", - "http 1.4.0", - "http-body 1.0.1", - "log", - "parking_lot 0.12.5", - "serde", - "serde_json", - "sha2", - "tempfile", - "url", - "zed-async-tar", - "zed-reqwest", -] - -[[package]] -name = "gpui_media" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cb8912ae17371725132d2b7eec6797a255accc95d58ee5c1134b529810f14b" -dependencies = [ - "anyhow", - "bindgen 0.71.1", - "core-foundation 0.10.0", - "core-video", - "ctor 0.4.3", - "foreign-types 0.5.0", - "metal 0.29.0", - "objc", -] - -[[package]] -name = "gpui_perf" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40a0961dcf598955130e867f4b731150a20546427b41b1a63767c1037a86d77" -dependencies = [ - "gpui_collections", - "serde", - "serde_json", -] - -[[package]] -name = "gpui_refineable" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258cb099254e9468181aee5614410fba61db4ae115fc1d51b4a0b985f60d6641" -dependencies = [ - "gpui_derive_refineable", -] - -[[package]] -name = "gpui_semantic_version" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201e45eff7b695528fb3af6560a534943fbc2db5323d755b9d198bd743948e35" -dependencies = [ - "anyhow", - "serde", -] - -[[package]] -name = "gpui_squircle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a53f6ce49ac783ed2952ed2b6f918e03f964bcacd3bcbf23de19a7bd4860e621" -dependencies = [ - "anyhow", - "figma_squircle", - "gpui", - "gpui-macros", - "lyon", - "smallvec 1.15.1", -] - -[[package]] -name = "gpui_sum_tree" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4f3bedd573fafafa13d1200b356c588cf094fb2786e3684bb3f5ea59b549fa9" -dependencies = [ - "arrayvec", - "log", - "rayon", -] - -[[package]] -name = "gpui_util" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68faea25903ae524de9af83990b9aa51bcbc8dd085929ac0aea7fd41905e05c3" -dependencies = [ - "anyhow", - "async-fs", - "async_zip", - "command-fds", - "dirs 4.0.0", - "dunce", - "futures", - "futures-lite 1.13.0", - "globset", - "gpui_collections", - "itertools 0.14.0", - "libc", - "log", - "nix 0.29.0", - "regex", - "rust-embed", - "schemars 1.1.0", - "serde", - "serde_json", - "serde_json_lenient", - "shlex", - "smol", - "take-until", - "tempfile", - "tendril", - "unicase", - "walkdir", - "which 6.0.3", -] - -[[package]] -name = "gpui_util_macros" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c28f65ef47fb97e21e82fd4dd75ccc2506eda010c846dc8054015ea234f1a22" -dependencies = [ - "gpui_perf", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "granola" -version = "0.1.0" -dependencies = [ - "chrono", - "dirs 6.0.0", - "regex", - "reqwest", - "serde", - "serde_json", - "serde_yaml", - "tempfile", - "thiserror 2.0.17", - "tokio", -] - -[[package]] -name = "granola-cli" -version = "0.1.0" -dependencies = [ - "clap", - "granola", - "tokio", -] - -[[package]] -name = "grep-cli" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf32d263c5d5cc2a23ce587097f5ddafdb188492ba2e6fb638eaccdc22453631" -dependencies = [ - "bstr", - "globset", - "libc", - "log", - "termcolor", - "winapi-util", -] - -[[package]] -name = "grid" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12101ecc8225ea6d675bc70263074eab6169079621c2186fe0c66590b2df9681" - -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", + "ff", + "rand_core 0.6.4", + "subtle", ] [[package]] @@ -8077,12 +7088,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hexf-parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" - [[package]] name = "hf-hub" version = "0.4.3" @@ -8107,26 +7112,6 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "hidden-trait" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ed9e850438ac849bec07e7d09fbe9309cbd396a5988c30b010580ce08860df" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - [[package]] name = "hmac" version = "0.12.1" @@ -8739,12 +7724,6 @@ dependencies = [ "quick-error", ] -[[package]] -name = "imagesize" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" - [[package]] name = "imara-diff" version = "0.1.8" @@ -8909,27 +7888,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "inventory" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" -dependencies = [ - "rustversion", -] - -[[package]] -name = "io-surface" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e" -dependencies = [ - "cgl", - "core-foundation 0.10.0", - "core-foundation-sys", - "leaky-cow", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -9076,9 +8034,9 @@ dependencies = [ [[package]] name = "jiff-tzdb" -version = "0.1.5" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" +checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" [[package]] name = "jiff-tzdb-platform" @@ -9213,7 +8171,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.8.9", "pkg-config", ] @@ -9263,26 +8220,6 @@ dependencies = [ "selectors", ] -[[package]] -name = "kurbo" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" -dependencies = [ - "arrayvec", - "euclid", - "smallvec 1.15.1", -] - -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "kyutai" version = "0.1.0" @@ -9334,9 +8271,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "lazycell" @@ -9344,21 +8278,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "leak" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73" - -[[package]] -name = "leaky-cow" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc" -dependencies = [ - "leak", -] - [[package]] name = "lebe" version = "0.5.3" @@ -9673,7 +8592,7 @@ dependencies = [ "tracing", "wayland-backend", "wayland-client", - "wayland-protocols 0.32.9", + "wayland-protocols", "wayland-protocols-wlr", ] @@ -9823,10 +8742,6 @@ name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -dependencies = [ - "serde_core", - "value-bag", -] [[package]] name = "loop9" @@ -9864,69 +8779,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "lyon" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcb7d54d54c8937364c9d41902d066656817dce1e03a44e5533afebd1ef4352" -dependencies = [ - "lyon_algorithms", - "lyon_extra", - "lyon_tessellation", -] - -[[package]] -name = "lyon_algorithms" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c0829e28c4f336396f250d850c3987e16ce6db057ffe047ce0dd54aab6b647" -dependencies = [ - "lyon_path", - "num-traits", -] - -[[package]] -name = "lyon_extra" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca94c7bf1e2557c2798989c43416822c12fc5dcc5e17cc3307ef0e71894a955" -dependencies = [ - "lyon_path", - "thiserror 1.0.69", -] - -[[package]] -name = "lyon_geom" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e260b6de923e6e47adfedf6243013a7a874684165a6a277594ee3906021b2343" -dependencies = [ - "arrayvec", - "euclid", - "num-traits", -] - -[[package]] -name = "lyon_path" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aeca86bcfd632a15984ba029b539ffb811e0a70bf55e814ef8b0f54f506fdeb" -dependencies = [ - "lyon_geom", - "num-traits", -] - -[[package]] -name = "lyon_tessellation" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f586142e1280335b1bc89539f7c97dd80f08fc43e9ab1b74ef0a42b04aa353" -dependencies = [ - "float_next_after", - "lyon_path", - "num-traits", -] - [[package]] name = "mac" version = "0.1.0" @@ -10300,9 +9152,9 @@ dependencies = [ [[package]] name = "minijinja" -version = "2.14.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ea9ac0a51fb5112607099560fdf0f90366ab088a2a9e6e8ae176794e9806aa" +checksum = "0adbe6e92a6ce0fd6c4aac593fdfd3e3950b0f61b1a63aa9731eb6fd85776fa3" dependencies = [ "indexmap 2.12.1", "memo-map", @@ -10313,9 +9165,9 @@ dependencies = [ [[package]] name = "minijinja-contrib" -version = "2.14.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be6ad8bbc21c256d5f2f5494699d5d69d519b8510d672a0e43b7bfa3a56c388a" +checksum = "a915f5cc17b954d5252b6373cc5fd97eb86d75e29b30ac2ee7932126451eef24" dependencies = [ "minijinja", "serde", @@ -10371,12 +9223,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "mint" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" - [[package]] name = "mio" version = "1.1.1" @@ -10469,31 +9315,6 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "naga" -version = "25.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" -dependencies = [ - "arrayvec", - "bit-set 0.8.0", - "bitflags 2.10.0", - "cfg_aliases 0.2.1", - "codespan-reporting", - "half", - "hashbrown 0.15.5", - "hexf-parse", - "indexmap 2.12.1", - "log", - "num-traits", - "once_cell", - "rustc-hash 1.1.0", - "spirv", - "strum 0.26.3", - "thiserror 2.0.17", - "unicode-ident", -] - [[package]] name = "nango" version = "0.1.0" @@ -10509,15 +9330,6 @@ dependencies = [ "url", ] -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom 0.2.16", -] - [[package]] name = "native-tls" version = "0.2.14" @@ -10633,18 +9445,6 @@ dependencies = [ "memoffset", ] -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases 0.2.1", - "libc", -] - [[package]] name = "nix" version = "0.30.1" @@ -10703,7 +9503,6 @@ dependencies = [ name = "notification" version = "0.1.0" dependencies = [ - "notification-gpui", "notification-interface", "notification-linux", "notification-macos", @@ -10711,15 +9510,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "notification-gpui" -version = "0.1.0" -dependencies = [ - "gpui", - "gpui_squircle", - "notification-interface", -] - [[package]] name = "notification-interface" version = "0.1.0" @@ -10799,23 +9589,6 @@ dependencies = [ "serde", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "serde", - "smallvec 1.15.1", - "zeroize", -] - [[package]] name = "num-complex" version = "0.4.6" @@ -11355,7 +10128,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" dependencies = [ "bitflags 2.10.0", - "block2 0.6.2", "objc2 0.6.3", "objc2-foundation 0.3.2", ] @@ -11395,7 +10167,6 @@ dependencies = [ "objc2 0.6.3", "objc2-core-foundation", "objc2-foundation 0.3.2", - "objc2-metal 0.3.2", ] [[package]] @@ -11576,41 +10347,6 @@ dependencies = [ "thiserror 2.0.17", ] -[[package]] -name = "oo7" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3299dd401feaf1d45afd8fd1c0586f10fcfb22f244bb9afa942cec73503b89d" -dependencies = [ - "aes", - "ashpd 0.12.0", - "async-fs", - "async-io", - "async-lock", - "blocking", - "cbc", - "cipher", - "digest", - "endi", - "futures-lite 2.6.1", - "futures-util", - "getrandom 0.3.4", - "hkdf", - "hmac", - "md-5", - "num", - "num-bigint-dig", - "pbkdf2", - "rand 0.9.2", - "serde", - "sha2", - "subtle", - "zbus", - "zbus_macros", - "zeroize", - "zvariant", -] - [[package]] name = "oorandom" version = "11.1.5" @@ -12079,35 +10815,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" -[[package]] -name = "pathfinder_geometry" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" -dependencies = [ - "log", - "pathfinder_simd", -] - -[[package]] -name = "pathfinder_simd" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf9027960355bf3afff9841918474a81a5f972ac6d226d518060bba758b5ad57" -dependencies = [ - "rustc_version 0.4.1", -] - -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest", - "hmac", -] - [[package]] name = "peeking_take_while" version = "0.1.2" @@ -12219,7 +10926,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros 0.13.1", "phf_shared 0.13.1", "serde", ] @@ -12274,16 +10980,6 @@ dependencies = [ "rand 0.8.5", ] -[[package]] -name = "phf_generator" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" -dependencies = [ - "fastrand 2.3.0", - "phf_shared 0.13.1", -] - [[package]] name = "phf_macros" version = "0.10.0" @@ -12311,19 +11007,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "phf_macros" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" -dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "phf_shared" version = "0.8.0" @@ -12361,12 +11044,6 @@ dependencies = [ "siphasher 1.0.1", ] -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - [[package]] name = "pin-project" version = "1.1.10" @@ -12562,12 +11239,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "pollster" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" - [[package]] name = "port-killer" version = "0.1.0" @@ -12595,23 +11266,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "postage" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" -dependencies = [ - "atomic", - "crossbeam-queue", - "futures", - "log", - "parking_lot 0.12.5", - "pin-project", - "pollster", - "static_assertions", - "thiserror 1.0.69", -] - [[package]] name = "postgres-protocol" version = "0.6.9" @@ -12755,28 +11409,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -13267,12 +11899,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "rangemap" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbbbbea733ec66275512d0b9694f34102e7d5406fdbe2ad8d21b28dce92887c" - [[package]] name = "ratatui" version = "0.29.0" @@ -13368,18 +11994,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" -[[package]] -name = "raw-window-metal" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e8caa82e31bb98fee12fa8f051c94a6aa36b07cddb03f0d4fc558988360ff1" -dependencies = [ - "cocoa 0.25.0", - "core-graphics 0.23.2", - "objc", - "raw-window-handle", -] - [[package]] name = "rawpointer" version = "0.2.1" @@ -13417,16 +12031,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "read-fonts" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" -dependencies = [ - "bytemuck", - "font-types", -] - [[package]] name = "realfft" version = "3.5.0" @@ -13692,20 +12296,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "resvg" -version = "0.45.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" -dependencies = [ - "log", - "pico-args", - "rgb", - "svgtypes", - "tiny-skia", - "usvg", -] - [[package]] name = "retain_mut" version = "0.1.9" @@ -13738,7 +12328,7 @@ version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" dependencies = [ - "ashpd 0.11.0", + "ashpd", "block2 0.6.2", "dispatch2", "glib-sys", @@ -13819,12 +12409,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "roxmltree" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" - [[package]] name = "rquickjs" version = "0.10.0" @@ -13866,41 +12450,6 @@ dependencies = [ "realfft", ] -[[package]] -name = "rust-embed" -version = "8.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca" -dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", -] - -[[package]] -name = "rust-embed-impl" -version = "8.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2" -dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn 2.0.111", - "walkdir", -] - -[[package]] -name = "rust-embed-utils" -version = "8.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475" -dependencies = [ - "globset", - "sha2", - "walkdir", -] - [[package]] name = "rust-ini" version = "0.21.3" @@ -14112,41 +12661,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "rustybuzz" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" -dependencies = [ - "bitflags 2.10.0", - "bytemuck", - "libm", - "smallvec 1.15.1", - "ttf-parser 0.21.1", - "unicode-bidi-mirroring 0.2.0", - "unicode-ccc 0.2.0", - "unicode-properties", - "unicode-script", -] - -[[package]] -name = "rustybuzz" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" -dependencies = [ - "bitflags 2.10.0", - "bytemuck", - "core_maths", - "log", - "smallvec 1.15.1", - "ttf-parser 0.25.1", - "unicode-bidi-mirroring 0.4.0", - "unicode-ccc 0.4.0", - "unicode-properties", - "unicode-script", -] - [[package]] name = "ryu" version = "1.0.20" @@ -14239,9 +12753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" dependencies = [ "dyn-clone", - "indexmap 2.12.1", "ref-cast", - "schemars_derive 1.1.0", "serde", "serde_json", ] @@ -14270,18 +12782,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "schemars_derive" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.111", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -14300,29 +12800,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "screencapturekit" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e" -dependencies = [ - "screencapturekit-sys", -] - -[[package]] -name = "screencapturekit-sys" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60" -dependencies = [ - "block", - "dispatch", - "objc", - "objc-foundation", - "objc_id", - "once_cell", -] - [[package]] name = "scroll" version = "0.12.0" @@ -14359,12 +12836,6 @@ version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - [[package]] name = "sec1" version = "0.3.0" @@ -14409,7 +12880,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags 2.10.0", - "core-foundation 0.10.0", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -14701,15 +13172,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "serde_fmt" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e497af288b3b95d067a23a4f749f2861121ffcb2f6d8379310dcda040c345ed" -dependencies = [ - "serde_core", -] - [[package]] name = "serde_html_form" version = "0.2.8" @@ -14737,19 +13199,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_json_lenient" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e033097bf0d2b59a62b42c18ebbb797503839b26afdda2c4e1415cb6c813540" -dependencies = [ - "indexmap 2.12.1", - "itoa", - "memchr", - "ryu", - "serde", -] - [[package]] name = "serde_path_to_error" version = "0.1.20" @@ -15172,15 +13621,6 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" -[[package]] -name = "simplecss" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" -dependencies = [ - "log", -] - [[package]] name = "simsimd" version = "6.5.5" @@ -15202,31 +13642,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" -[[package]] -name = "skrifa" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" -dependencies = [ - "bytemuck", - "read-fonts", -] - [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" -[[package]] -name = "slotmap" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" -dependencies = [ - "version_check", -] - [[package]] name = "slug" version = "0.1.6" @@ -15282,23 +13703,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "smol" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" -dependencies = [ - "async-channel 2.5.0", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-net", - "async-process", - "blocking", - "futures-lite 2.6.1", -] - [[package]] name = "smol_str" version = "0.1.24" @@ -15308,12 +13712,6 @@ dependencies = [ "serde", ] -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" - [[package]] name = "socket2" version = "0.5.10" @@ -15458,24 +13856,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spirv" -version = "0.3.0+sdk-1.3.268.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" -dependencies = [ - "bitflags 2.10.0", -] - [[package]] name = "spki" version = "0.6.0" @@ -15523,27 +13903,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "stacksafe" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9c1172965d317e87ddb6d364a040d958b40a1db82b6ef97da26253a8b3d090" -dependencies = [ - "stacker", - "stacksafe-macro", -] - -[[package]] -name = "stacksafe-macro" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172175341049678163e979d9107ca3508046d4d2a7c6682bee46ac541b17db69" -dependencies = [ - "proc-macro-error2", - "quote", - "syn 2.0.111", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -15572,15 +13931,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" -[[package]] -name = "strict-num" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -dependencies = [ - "float-cmp", -] - [[package]] name = "string_cache" version = "0.8.9" @@ -15687,157 +14037,52 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros 0.27.2", -] - -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.111", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.111", -] - -[[package]] -name = "strum_macros" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "sval" -version = "2.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502b8906c4736190684646827fbab1e954357dfe541013bbd7994d033d53a1ca" - -[[package]] -name = "sval_buffer" -version = "2.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4b854348b15b6c441bdd27ce9053569b016a0723eab2d015b1fd8e6abe4f708" -dependencies = [ - "sval", - "sval_ref", -] - -[[package]] -name = "sval_dynamic" -version = "2.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bd9e8b74410ddad37c6962587c5f9801a2caadba9e11f3f916ee3f31ae4a1f" -dependencies = [ - "sval", -] - -[[package]] -name = "sval_fmt" -version = "2.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe17b8deb33a9441280b4266c2d257e166bafbaea6e66b4b34ca139c91766d9" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_json" -version = "2.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854addb048a5bafb1f496c98e0ab5b9b581c3843f03ca07c034ae110d3b7c623" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_nested" -version = "2.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf068f482108ff44ae8013477cb047a1665d5f1a635ad7cf79582c1845dce9" -dependencies = [ - "sval", - "sval_buffer", - "sval_ref", + "strum_macros 0.27.2", ] [[package]] -name = "sval_ref" -version = "2.16.0" +name = "strum_macros" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed02126365ffe5ab8faa0abd9be54fbe68d03d607cd623725b0a71541f8aaa6f" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ - "sval", + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.111", ] [[package]] -name = "sval_serde" -version = "2.16.0" +name = "strum_macros" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a263383c6aa2076c4ef6011d3bae1b356edf6ea2613e3d8e8ebaa7b57dd707d5" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "serde_core", - "sval", - "sval_nested", + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.111", ] [[package]] -name = "svg_fmt" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" - -[[package]] -name = "svgtypes" -version = "0.15.3" +name = "strum_macros" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ - "kurbo", - "siphasher 1.0.1", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] -name = "swash" -version = "0.2.6" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47846491253e976bdd07d0f9cc24b7daf24720d11309302ccbbc6e6b6e53550a" -dependencies = [ - "skrifa", - "yazi", - "zeno", -] +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "swc_atoms" @@ -16144,20 +14389,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "sysinfo" -version = "0.31.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" -dependencies = [ - "core-foundation-sys", - "libc", - "memchr", - "ntapi", - "rayon", - "windows 0.57.0", -] - [[package]] name = "sysinfo" version = "0.33.1" @@ -16220,24 +14451,6 @@ dependencies = [ "version-compare", ] -[[package]] -name = "taffy" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13e5d13f79d558b5d353a98072ca8ca0e99da429467804de959aa8c83c9a004" -dependencies = [ - "arrayvec", - "grid", - "serde", - "slotmap", -] - -[[package]] -name = "take-until" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb" - [[package]] name = "tao" version = "0.34.5" @@ -16246,7 +14459,7 @@ checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" dependencies = [ "bitflags 2.10.0", "block2 0.6.2", - "core-foundation 0.10.0", + "core-foundation 0.10.1", "core-graphics 0.24.0", "crossbeam-channel", "dispatch", @@ -16278,18 +14491,6 @@ dependencies = [ "x11-dl", ] -[[package]] -name = "tao-core-video-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "objc", -] - [[package]] name = "tao-macros" version = "0.1.3" @@ -16315,7 +14516,7 @@ checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" dependencies = [ "filetime", "libc", - "xattr 1.6.1", + "xattr", ] [[package]] @@ -16441,6 +14642,22 @@ dependencies = [ "tauri-utils", ] +[[package]] +name = "tauri-nspanel" +version = "2.0.1" +source = "git+https://github.com/ahkohd/tauri-nspanel?branch=v2#18ffb9a201fbf6fedfaa382fd4b92315ea30ab1a" +dependencies = [ + "bitflags 2.10.0", + "block", + "cocoa", + "core-foundation 0.10.1", + "core-graphics 0.25.0", + "objc", + "objc-foundation", + "objc_id", + "tauri", +] + [[package]] name = "tauri-plugin" version = "2.5.2" @@ -16486,6 +14703,7 @@ dependencies = [ "itertools 0.14.0", "objc2 0.6.3", "objc2-contacts", + "objc2-core-graphics", "objc2-event-kit", "objc2-foundation 0.3.2", "serde", @@ -16501,24 +14719,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "tauri-plugin-auth" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "specta", - "specta-typescript", - "strum 0.26.3", - "tauri", - "tauri-plugin", - "tauri-plugin-store", - "tauri-plugin-store2", - "tauri-specta", - "thiserror 2.0.17", - "tokio", -] - [[package]] name = "tauri-plugin-autostart" version = "2.5.1" @@ -16560,7 +14760,6 @@ dependencies = [ "tauri", "tauri-plugin", "tauri-plugin-cli", - "tauri-plugin-updater", "tauri-specta", "thiserror 2.0.17", "tracing", @@ -16786,40 +14985,15 @@ dependencies = [ "urlpattern", ] -[[package]] -name = "tauri-plugin-icon" -version = "0.1.0" -dependencies = [ - "objc2 0.6.3", - "objc2-app-kit", - "objc2-foundation 0.3.2", - "serde", - "specta", - "specta-typescript", - "tauri", - "tauri-plugin", - "tauri-specta", - "thiserror 2.0.17", - "tokio", -] - [[package]] name = "tauri-plugin-importer" version = "0.1.0" dependencies = [ - "db-core", - "db-user", - "dirs 6.0.0", - "granola", - "owhisper-interface", - "serde", "specta", "specta-typescript", "tauri", "tauri-plugin", "tauri-specta", - "thiserror 2.0.17", - "tokio", ] [[package]] @@ -16839,7 +15013,6 @@ dependencies = [ "futures-util", "hound", "insta", - "intercept", "language", "llm", "ordered-float", @@ -17110,6 +15283,7 @@ dependencies = [ "tauri-specta", "tcc", "thiserror 2.0.17", + "tokio", ] [[package]] @@ -17152,21 +15326,6 @@ dependencies = [ "thiserror 2.0.17", ] -[[package]] -name = "tauri-plugin-settings" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "specta", - "specta-typescript", - "tauri", - "tauri-plugin", - "tauri-specta", - "thiserror 2.0.17", - "tokio", -] - [[package]] name = "tauri-plugin-sfx" version = "0.1.0" @@ -17304,11 +15463,8 @@ dependencies = [ "tauri-plugin-clipboard-manager", "tauri-plugin-dialog", "tauri-plugin-misc", - "tauri-plugin-updater", - "tauri-plugin-updater2", "tauri-plugin-windows", "tauri-specta", - "tracing", ] [[package]] @@ -17343,24 +15499,6 @@ dependencies = [ "zip 4.6.1", ] -[[package]] -name = "tauri-plugin-updater2" -version = "0.1.0" -dependencies = [ - "serde", - "specta", - "specta-typescript", - "strum 0.26.3", - "tauri", - "tauri-plugin", - "tauri-plugin-store2", - "tauri-plugin-updater", - "tauri-specta", - "thiserror 2.0.17", - "tokio", - "tracing", -] - [[package]] name = "tauri-plugin-webhook" version = "0.1.0" @@ -17378,21 +15516,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "tauri-plugin-window-state" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73736611e14142408d15353e21e3cca2f12a3cfb523ad0ce85999b6d2ef1a704" -dependencies = [ - "bitflags 2.10.0", - "log", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.17", -] - [[package]] name = "tauri-plugin-windows" version = "0.1.0" @@ -17407,10 +15530,9 @@ dependencies = [ "specta-typescript", "strum 0.26.3", "tauri", + "tauri-nspanel", "tauri-plugin", "tauri-plugin-analytics", - "tauri-plugin-os", - "tauri-plugin-window-state", "tauri-specta", "thiserror 2.0.17", "tokio", @@ -17508,7 +15630,7 @@ dependencies = [ "anyhow", "brotli", "cargo_metadata", - "ctor 0.2.9", + "ctor", "dunce", "glob", "html5ever 0.29.1", @@ -17840,32 +15962,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "tiny-skia" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "png 0.17.16", - "tiny-skia-path", -] - -[[package]] -name = "tiny-skia-path" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - [[package]] name = "tinystr" version = "0.8.2" @@ -18047,18 +16143,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-socks" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" -dependencies = [ - "either", - "futures-util", - "thiserror 1.0.69", - "tokio", -] - [[package]] name = "tokio-stream" version = "0.1.17" @@ -18083,7 +16167,7 @@ dependencies = [ "redox_syscall 0.3.5", "tokio", "tokio-stream", - "xattr 1.6.1", + "xattr", ] [[package]] @@ -18760,27 +16844,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "ttf-parser" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" - -[[package]] -name = "ttf-parser" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" - -[[package]] -name = "ttf-parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" -dependencies = [ - "core_maths", -] - [[package]] name = "tungstenite" version = "0.26.2" @@ -19014,36 +17077,12 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" -[[package]] -name = "unicode-bidi-mirroring" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" - -[[package]] -name = "unicode-bidi-mirroring" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" - [[package]] name = "unicode-bom" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" -[[package]] -name = "unicode-ccc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" - -[[package]] -name = "unicode-ccc" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" - [[package]] name = "unicode-id" version = "0.3.6" @@ -19062,12 +17101,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" -[[package]] -name = "unicode-linebreak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" - [[package]] name = "unicode-normalization" version = "0.1.25" @@ -19092,12 +17125,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" -[[package]] -name = "unicode-script" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" - [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -19115,12 +17142,6 @@ dependencies = [ "unicode-width 0.1.14", ] -[[package]] -name = "unicode-vo" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" - [[package]] name = "unicode-width" version = "0.1.14" @@ -19238,36 +17259,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" dependencies = [ "regex", - "serde", - "unic-ucd-ident", - "url", -] - -[[package]] -name = "usvg" -version = "0.45.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" -dependencies = [ - "base64 0.22.1", - "data-url", - "flate2", - "fontdb 0.23.0", - "imagesize", - "kurbo", - "log", - "pico-args", - "roxmltree", - "rustybuzz 0.20.1", - "simplecss", - "siphasher 1.0.1", - "strict-num", - "svgtypes", - "tiny-skia-path", - "unicode-bidi", - "unicode-script", - "unicode-vo", - "xmlwriter", + "serde", + "unic-ucd-ident", + "url", ] [[package]] @@ -19321,7 +17315,6 @@ dependencies = [ "js-sys", "rand 0.9.2", "serde_core", - "sha1_smol", "wasm-bindgen", ] @@ -19395,42 +17388,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "value-bag" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" -dependencies = [ - "value-bag-serde1", - "value-bag-sval2", -] - -[[package]] -name = "value-bag-serde1" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16530907bfe2999a1773ca5900a65101e092c70f642f25cc23ca0c43573262c5" -dependencies = [ - "erased-serde", - "serde_core", - "serde_fmt", -] - -[[package]] -name = "value-bag-sval2" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00ae130edd690eaa877e4f40605d534790d1cf1d651e7685bd6a144521b251f" -dependencies = [ - "sval", - "sval_buffer", - "sval_dynamic", - "sval_fmt", - "sval_json", - "sval_ref", - "sval_serde", -] - [[package]] name = "vcpkg" version = "0.2.15" @@ -19724,29 +17681,6 @@ dependencies = [ "wayland-scanner", ] -[[package]] -name = "wayland-cursor" -version = "0.31.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" -dependencies = [ - "rustix 1.1.2", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" -dependencies = [ - "bitflags 2.10.0", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - [[package]] name = "wayland-protocols" version = "0.32.9" @@ -19759,19 +17693,6 @@ dependencies = [ "wayland-scanner", ] -[[package]] -name = "wayland-protocols-plasma" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" -dependencies = [ - "bitflags 2.10.0", - "wayland-backend", - "wayland-client", - "wayland-protocols 0.31.2", - "wayland-scanner", -] - [[package]] name = "wayland-protocols-wlr" version = "0.3.9" @@ -19781,7 +17702,7 @@ dependencies = [ "bitflags 2.10.0", "wayland-backend", "wayland-client", - "wayland-protocols 0.32.9", + "wayland-protocols", "wayland-scanner", ] @@ -19819,7 +17740,6 @@ dependencies = [ "libc", "log", "memoffset", - "once_cell", "pkg-config", ] @@ -20169,19 +18089,6 @@ dependencies = [ "windows-numerics", ] -[[package]] -name = "windows-capture" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4df73e95feddb9ec1a7e9c2ca6323b8c97d5eeeff78d28f1eccdf19c882b24" -dependencies = [ - "parking_lot 0.12.5", - "rayon", - "thiserror 2.0.17", - "windows 0.61.3", - "windows-future", -] - [[package]] name = "windows-collections" version = "0.2.0" @@ -20350,17 +18257,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows-registry" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" -dependencies = [ - "windows-result 0.3.4", - "windows-strings 0.3.1", - "windows-targets 0.53.5", -] - [[package]] name = "windows-registry" version = "0.5.3" @@ -20410,15 +18306,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-strings" version = "0.4.2" @@ -20804,15 +18691,6 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" -[[package]] -name = "wio" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" -dependencies = [ - "winapi", -] - [[package]] name = "wiremock" version = "0.5.22" @@ -20856,7 +18734,7 @@ dependencies = [ "tree_magic_mini", "wayland-backend", "wayland-client", - "wayland-protocols 0.32.9", + "wayland-protocols", "wayland-protocols-wlr", ] @@ -20960,16 +18838,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "x11-clipboard" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" -dependencies = [ - "libc", - "x11rb", -] - [[package]] name = "x11-dl" version = "2.21.0" @@ -20987,12 +18855,9 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ - "as-raw-xcb-connection", "gethostname", - "libc", "rustix 1.1.2", "x11rb-protocol", - "xcursor", ] [[package]] @@ -21001,15 +18866,6 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" -[[package]] -name = "xattr" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" -dependencies = [ - "libc", -] - [[package]] name = "xattr" version = "1.6.1" @@ -21061,51 +18917,8 @@ dependencies = [ "bitflags 1.3.2", "libc", "quick-xml 0.30.0", - "x11", -] - -[[package]] -name = "xcursor" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" - -[[package]] -name = "xim-ctext" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac61a7062c40f3c37b6e82eeeef835d5cc7824b632a72784a89b3963c33284c" -dependencies = [ - "encoding_rs", -] - -[[package]] -name = "xim-parser" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dcee45f89572d5a65180af3a84e7ddb24f5ea690a6d3aa9de231281544dd7b7" -dependencies = [ - "bitflags 2.10.0", -] - -[[package]] -name = "xkbcommon" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" -dependencies = [ - "as-raw-xcb-connection", - "libc", - "memmap2", - "xkeysym", ] -[[package]] -name = "xkeysym" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" - [[package]] name = "xml-rs" version = "0.8.28" @@ -21128,12 +18941,6 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" -[[package]] -name = "xmlwriter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" - [[package]] name = "xterm-color" version = "1.0.1" @@ -21175,23 +18982,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "yazi" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" - -[[package]] -name = "yeslogic-fontconfig-sys" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" -dependencies = [ - "dlib", - "once_cell", - "pkg-config", -] - [[package]] name = "yoke" version = "0.7.5" @@ -21301,137 +19091,6 @@ dependencies = [ "zvariant", ] -[[package]] -name = "zed-async-tar" -version = "0.5.0-zed" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cf4b5f655e29700e473cb1acd914ab112b37b62f96f7e642d5fc6a0c02eb881" -dependencies = [ - "async-std", - "filetime", - "libc", - "pin-project", - "redox_syscall 0.2.16", - "xattr 0.2.3", -] - -[[package]] -name = "zed-font-kit" -version = "0.14.1-zed" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3898e450f36f852edda72e3f985c34426042c4951790b23b107f93394f9bff5" -dependencies = [ - "bitflags 2.10.0", - "byteorder", - "core-foundation 0.10.0", - "core-graphics 0.24.0", - "core-text", - "dirs 5.0.1", - "dwrote", - "float-ord", - "freetype-sys", - "lazy_static", - "libc", - "log", - "pathfinder_geometry", - "pathfinder_simd", - "walkdir", - "winapi", - "yeslogic-fontconfig-sys", -] - -[[package]] -name = "zed-reqwest" -version = "0.12.15-zed" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2d05756ff48539950c3282ad7acf3817ad3f08797c205ad1c34a2ce03b9970" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.4.12", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.8.1", - "hyper-rustls 0.27.7", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "mime_guess", - "once_cell", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls 0.23.35", - "rustls-native-certs 0.8.2", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 1.0.2", - "system-configuration", - "tokio", - "tokio-rustls 0.26.4", - "tokio-socks", - "tokio-util", - "tower 0.5.2", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "windows-registry 0.4.0", -] - -[[package]] -name = "zed-scap" -version = "0.0.8-zed" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b338d705ae33a43ca00287c11129303a7a0aa57b101b72a1c08c863f698ac8" -dependencies = [ - "anyhow", - "cocoa 0.25.0", - "core-graphics-helmer-fork", - "log", - "objc", - "rand 0.8.5", - "screencapturekit", - "screencapturekit-sys", - "sysinfo 0.31.4", - "tao-core-video-sys", - "windows 0.61.3", - "windows-capture", - "x11", - "xcb", -] - -[[package]] -name = "zed-xim" -version = "0.4.0-zed" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0b46ed118eba34d9ba53d94ddc0b665e0e06a2cf874cfa2dd5dec278148642" -dependencies = [ - "ahash", - "hashbrown 0.14.5", - "log", - "x11rb", - "xim-ctext", - "xim-parser", -] - -[[package]] -name = "zeno" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" - [[package]] name = "zerocopy" version = "0.7.35" @@ -21499,20 +19158,6 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] [[package]] name = "zerotrie" diff --git a/Cargo.toml b/Cargo.toml index 8d1cd006ff..181234072c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ debug = false resolver = "2" members = [ "apps/desktop/src-tauri", - "apps/granola", "crates/*", "owhisper/*", "plugins/*", @@ -27,6 +26,7 @@ hypr-analytics = { path = "crates/analytics", package = "analytics" } hypr-audio = { path = "crates/audio", package = "audio" } hypr-audio-interface = { path = "crates/audio-interface", package = "audio-interface" } hypr-audio-utils = { path = "crates/audio-utils", package = "audio-utils" } +hypr-auth-interface = { path = "plugins/auth-interface", package = "auth-interface" } hypr-buffer = { path = "crates/buffer", package = "buffer" } hypr-data = { path = "crates/data", package = "data" } hypr-db-admin = { path = "crates/db-admin", package = "db-admin" } @@ -81,8 +81,6 @@ hypr-whisper-local-model = { path = "crates/whisper-local-model", package = "whi hypr-ws = { path = "crates/ws", package = "ws" } hypr-ws-utils = { path = "crates/ws-utils", package = "ws-utils" } -hypr-granola = { path = "crates/granola", package = "granola" } - owhisper-client = { path = "owhisper/owhisper-client", package = "owhisper-client" } owhisper-config = { path = "owhisper/owhisper-config", package = "owhisper-config" } owhisper-interface = { path = "owhisper/owhisper-interface", package = "owhisper-interface" } @@ -90,6 +88,7 @@ owhisper-model = { path = "owhisper/owhisper-model", package = "owhisper-model" tauri = "2.9" tauri-build = "2.5" +tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" } tauri-plugin = "2.5" tauri-plugin-autostart = "2.5" tauri-plugin-cli = "2.4" @@ -107,11 +106,9 @@ tauri-plugin-shell = "2.3" tauri-plugin-single-instance = "2" tauri-plugin-store = "2.4" tauri-plugin-updater = "2.9" -tauri-plugin-window-state = "2.4" tauri-plugin-analytics = { path = "plugins/analytics" } tauri-plugin-apple-calendar = { path = "plugins/apple-calendar" } -tauri-plugin-auth = { path = "plugins/auth" } tauri-plugin-cli2 = { path = "plugins/cli2" } tauri-plugin-db = { path = "plugins/db" } tauri-plugin-db2 = { path = "plugins/db2" } @@ -119,7 +116,6 @@ tauri-plugin-deeplink2 = { path = "plugins/deeplink2" } tauri-plugin-detect = { path = "plugins/detect" } tauri-plugin-extensions = { path = "plugins/extensions" } tauri-plugin-hooks = { path = "plugins/hooks" } -tauri-plugin-icon = { path = "plugins/icon" } tauri-plugin-importer = { path = "plugins/importer" } tauri-plugin-listener = { path = "plugins/listener" } tauri-plugin-listener2 = { path = "plugins/listener2" } @@ -129,13 +125,11 @@ tauri-plugin-misc = { path = "plugins/misc" } tauri-plugin-network = { path = "plugins/network" } tauri-plugin-notification = { path = "plugins/notification" } tauri-plugin-permissions = { path = "plugins/permissions" } -tauri-plugin-settings = { path = "plugins/settings" } tauri-plugin-sfx = { path = "plugins/sfx" } tauri-plugin-store2 = { path = "plugins/store2" } tauri-plugin-template = { path = "plugins/template" } tauri-plugin-tracing = { path = "plugins/tracing" } tauri-plugin-tray = { path = "plugins/tray" } -tauri-plugin-updater2 = { path = "plugins/updater2" } tauri-plugin-webhook = { path = "plugins/webhook" } tauri-plugin-windows = { path = "plugins/windows" } @@ -246,6 +240,7 @@ objc2-application-services = "0.3" objc2-av-foundation = "0.3" objc2-contacts = "0.3" objc2-core-foundation = "0.3" +objc2-core-graphics = "0.3" objc2-event-kit = "0.3" objc2-foundation = "0.3" objc2-user-notifications = "0.3" diff --git a/crates/am/src/client.rs b/crates/am/src/client.rs index fb19acf47a..e290add927 100644 --- a/crates/am/src/client.rs +++ b/crates/am/src/client.rs @@ -1,6 +1,4 @@ -use crate::{ - AmModel, Error, ErrorResponse, GenericResponse, InitRequest, InitResponse, Secret, ServerStatus, -}; +use crate::{Error, ErrorResponse, GenericResponse, InitRequest, InitResponse, ServerStatus}; use reqwest::{Response, StatusCode}; #[derive(Clone)] @@ -62,12 +60,13 @@ impl Client { } pub async fn init(&self, request: InitRequest) -> Result { - if !request.api_key.expose().starts_with("ax_") { + if !request.api_key.starts_with("ax_") { return Err(Error::InvalidApiKey); } let url = format!("{}/init", self.base_url); let response = self.client.post(&url).json(&request).send().await?; + println!("{:?}", request); match response.status() { StatusCode::OK => Ok(response.json().await?), @@ -126,7 +125,7 @@ impl Client { impl InitRequest { pub fn new(api_key: impl Into) -> Self { Self { - api_key: Secret::new(api_key), + api_key: api_key.into(), model: None, model_token: None, download_base: None, @@ -143,7 +142,11 @@ impl InitRequest { } } - pub fn with_model(mut self, model: AmModel, base_dir: impl AsRef) -> Self { + pub fn with_model( + mut self, + model: crate::AmModel, + base_dir: impl AsRef, + ) -> Self { self.model = Some(model.model_dir().to_string()); self.model_repo = Some(model.repo_name().to_string()); self.model_folder = Some( diff --git a/crates/am/src/types.rs b/crates/am/src/types.rs index 67a0eb50cc..1f3fb2e2b0 100644 --- a/crates/am/src/types.rs +++ b/crates/am/src/types.rs @@ -5,26 +5,6 @@ macro_rules! common_derives { }; } -#[derive(Clone, serde::Serialize, serde::Deserialize)] -#[serde(transparent)] -pub struct Secret(String); - -impl Secret { - pub fn new(value: impl Into) -> Self { - Self(value.into()) - } - - pub fn expose(&self) -> &str { - &self.0 - } -} - -impl std::fmt::Debug for Secret { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("[REDACTED]") - } -} - common_derives! { #[serde(rename_all = "camelCase")] pub struct ServerStatus { @@ -77,7 +57,7 @@ common_derives! { common_derives! { #[serde(rename_all = "camelCase")] pub struct InitRequest { - pub api_key: Secret, + pub api_key: String, #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/audio/src/norm.rs b/crates/audio/src/norm.rs index 9e6e2ec5b5..552fe96899 100644 --- a/crates/audio/src/norm.rs +++ b/crates/audio/src/norm.rs @@ -95,12 +95,11 @@ impl Stream for NormalizedSource 1 - { - let children = node.children().cloned().unwrap_or_default(); + if let markdown::mdast::Node::Heading(heading) = node { + if heading.depth > 1 { + let children = node.children().cloned().unwrap_or_default(); - let strong_node = markdown::mdast::Node::Strong(markdown::mdast::Strong { - children, - position: None, - }); + let strong_node = markdown::mdast::Node::Strong(markdown::mdast::Strong { + children, + position: None, + }); - *node = markdown::mdast::Node::Paragraph(markdown::mdast::Paragraph { - children: vec![strong_node], - position: None, - }); + *node = markdown::mdast::Node::Paragraph(markdown::mdast::Paragraph { + children: vec![strong_node], + position: None, + }); + } } if let Some(children) = node.children_mut() { @@ -208,12 +208,13 @@ fn remove_empty_headings(node: &mut markdown::mdast::Node) { if let Some(children) = node.children_mut() { let mut i = 0; while i < children.len() { - if let Some(next) = children.get(i + 1) - && matches!(&children[i], markdown::mdast::Node::Heading(_)) - && matches!(next, markdown::mdast::Node::Heading(_)) - { - children.remove(i); - continue; + if let Some(next) = children.get(i + 1) { + if matches!(&children[i], markdown::mdast::Node::Heading(_)) + && matches!(next, markdown::mdast::Node::Heading(_)) + { + children.remove(i); + continue; + } } i += 1; } diff --git a/crates/detect/Cargo.toml b/crates/detect/Cargo.toml index 3b2b99072d..27eb56065c 100644 --- a/crates/detect/Cargo.toml +++ b/crates/detect/Cargo.toml @@ -6,7 +6,6 @@ edition = "2024" [dependencies] lazy_static = { workspace = true } regex = { workspace = true } -sysinfo = { workspace = true } thiserror = { workspace = true } url = { workspace = true } diff --git a/crates/detect/src/app/linux.rs b/crates/detect/src/app/linux.rs index edc235d9b9..616bb6a2d1 100644 --- a/crates/detect/src/app/linux.rs +++ b/crates/detect/src/app/linux.rs @@ -66,32 +66,33 @@ fn get_running_meeting_apps() -> Result, std::io::Error> { let entry = entry?; let path = entry.path(); - if let Some(pid_str) = path.file_name().and_then(|n| n.to_str()) - && pid_str.chars().all(|c| c.is_ascii_digit()) - { - let comm_path = path.join("comm"); - if let Ok(comm) = fs::read_to_string(&comm_path) { - let process_name = comm.trim(); - - for &meeting_app in &MEETING_APP_LIST { - if process_name.to_lowercase().contains(meeting_app) { - apps.insert(process_name.to_string()); - break; + if let Some(pid_str) = path.file_name().and_then(|n| n.to_str()) { + if pid_str.chars().all(|c| c.is_ascii_digit()) { + let comm_path = path.join("comm"); + if let Ok(comm) = fs::read_to_string(&comm_path) { + let process_name = comm.trim(); + + for &meeting_app in &MEETING_APP_LIST { + if process_name.to_lowercase().contains(meeting_app) { + apps.insert(process_name.to_string()); + break; + } } } - } - let cmdline_path = path.join("cmdline"); - if let Ok(cmdline) = fs::read_to_string(&cmdline_path) { - let cmdline_lower = cmdline.to_lowercase(); + let cmdline_path = path.join("cmdline"); + if let Ok(cmdline) = fs::read_to_string(&cmdline_path) { + let cmdline_lower = cmdline.to_lowercase(); - for &meeting_app in &MEETING_APP_LIST { - if cmdline_lower.contains(meeting_app) - && let Some(process_name) = cmdline.split('\0').next() - && let Some(basename) = process_name.split('/').next_back() - { - apps.insert(basename.to_string()); - break; + for &meeting_app in &MEETING_APP_LIST { + if cmdline_lower.contains(meeting_app) { + if let Some(process_name) = cmdline.split('\0').next() { + if let Some(basename) = process_name.split('/').next_back() { + apps.insert(basename.to_string()); + break; + } + } + } } } } diff --git a/crates/detect/src/lib.rs b/crates/detect/src/lib.rs index d605167603..8c07186f86 100644 --- a/crates/detect/src/lib.rs +++ b/crates/detect/src/lib.rs @@ -18,7 +18,7 @@ use utils::*; #[derive(Debug, Clone)] pub enum DetectEvent { MicStarted(Vec), - MicStopped(Vec), + MicStopped, #[cfg(target_os = "macos")] ZoomMuteStateChanged { value: bool, diff --git a/crates/detect/src/list/linux.rs b/crates/detect/src/list/linux.rs index 0c2574b580..a0ce339a39 100644 --- a/crates/detect/src/list/linux.rs +++ b/crates/detect/src/list/linux.rs @@ -11,10 +11,10 @@ pub fn list_installed_apps() -> Vec { if let Ok(entries) = fs::read_dir(&dir) { for entry in entries.flatten() { let path = entry.path(); - if path.extension().and_then(|s| s.to_str()) == Some("desktop") - && let Some(app) = parse_desktop_file(&path) - { - apps.entry(app.id.clone()).or_insert(app); + if path.extension().and_then(|s| s.to_str()) == Some("desktop") { + if let Some(app) = parse_desktop_file(&path) { + apps.entry(app.id.clone()).or_insert(app); + } } } } @@ -150,17 +150,16 @@ fn parse_desktop_file(path: &std::path::Path) -> Option { id = Some(line.strip_prefix("Icon=")?.to_string()); } - if line.starts_with("Exec=") - && id.is_none() - && let Some(exec) = line.strip_prefix("Exec=") - { - let binary = exec - .split_whitespace() - .next()? - .split('/') - .next_back()? - .to_string(); - id = Some(binary); + if line.starts_with("Exec=") && id.is_none() { + if let Some(exec) = line.strip_prefix("Exec=") { + let binary = exec + .split_whitespace() + .next()? + .split('/') + .next_back()? + .to_string(); + id = Some(binary); + } } } diff --git a/crates/detect/src/list/macos.rs b/crates/detect/src/list/macos.rs index 823677da05..1c09d50b4d 100644 --- a/crates/detect/src/list/macos.rs +++ b/crates/detect/src/list/macos.rs @@ -1,43 +1,36 @@ -use std::path::{Path, PathBuf}; - -use cidre::core_audio as ca; -use sysinfo::{Pid, System}; - use super::InstalledApp; +use cidre::core_audio as ca; #[cfg(target_os = "macos")] pub fn list_installed_apps() -> Vec { + use std::path::PathBuf; + let app_dirs = [ - "/Applications".to_string(), - format!("{}/Applications", std::env::var("HOME").unwrap_or_default()), + "/Applications", + &format!("{}/Applications", std::env::var("HOME").unwrap_or_default()), ]; let mut apps = Vec::new(); - for dir in app_dirs { + for dir in &app_dirs { let path = PathBuf::from(dir); - if !path.exists() { - continue; - } - - let mut stack = vec![path]; - while let Some(current) = stack.pop() { - let Ok(entries) = std::fs::read_dir(¤t) else { - continue; - }; - - for entry in entries.flatten() { - let path = entry.path(); - if !path.is_dir() { - continue; - } - - if is_app_bundle(&path) { - if let Some(app) = read_app_info(&path) { - apps.push(app); + if path.exists() { + let mut stack = vec![path]; + + while let Some(current) = stack.pop() { + if let Ok(entries) = std::fs::read_dir(¤t) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + if path.extension().and_then(|s| s.to_str()) == Some("app") { + if let Some(app) = get_app_info(&path) { + apps.push(app); + } + } else { + stack.push(path); + } + } } - } else { - stack.push(path); } } } @@ -54,93 +47,77 @@ pub fn list_installed_apps() -> Vec { #[cfg(target_os = "macos")] pub fn list_mic_using_apps() -> Vec { - let Ok(processes) = ca::System::processes() else { + let Some(processes) = ca::System::processes().ok() else { return Vec::new(); }; - processes - .into_iter() - .filter(|p| p.is_running_input().unwrap_or(false)) - .filter_map(|p| p.pid().ok()) - .filter_map(resolve_to_app) - .collect() -} - -fn resolve_to_app(pid: i32) -> Option { - resolve_via_nsrunningapp(pid).or_else(|| resolve_via_sysinfo(pid)) -} - -fn resolve_via_nsrunningapp(pid: i32) -> Option { - let running_app = cidre::ns::RunningApp::with_pid(pid)?; - - if let Some(bundle_url) = running_app.bundle_url() { - if let Some(path_ns) = bundle_url.path() { - let path_str = path_ns.to_string(); - if let Some(app) = find_outermost_app(Path::new(&path_str)) { - return Some(app); - } + let mut out = Vec::::new(); + for p in processes { + if let Ok(false) = p.is_running_input() { + continue; } - } - let bundle_id = running_app.bundle_id()?.to_string(); - let name = running_app - .localized_name() - .map(|s| s.to_string()) - .unwrap_or_else(|| bundle_id.clone()); + let bundle_id = p + .bundle_id() + .ok() + .map(|s| s.to_string()) + .unwrap_or_default(); + if bundle_id.is_empty() { + continue; + } - Some(InstalledApp { - id: bundle_id, - name, - }) -} + let (resolved_bundle_id, name) = resolve_to_parent_app(&bundle_id); -fn resolve_via_sysinfo(pid: i32) -> Option { - let mut sys = System::new(); - let pid = Pid::from_u32(pid as u32); - sys.refresh_processes(sysinfo::ProcessesToUpdate::Some(&[pid]), true); + out.push(InstalledApp { + id: resolved_bundle_id, + name, + }); + } - let exe_path = sys.process(pid)?.exe()?; - find_outermost_app(exe_path) + out } -fn find_outermost_app(path: &Path) -> Option { - let mut outermost: Option<&Path> = None; - let mut current = Some(path); +fn resolve_to_parent_app(bundle_id: &str) -> (String, String) { + let try_resolve = |id: &str| -> Option { + cidre::ns::RunningApp::with_bundle_id(&cidre::ns::String::with_str(id)) + .first() + .and_then(|app| app.localized_name().map(|s| s.to_string())) + }; - while let Some(p) = current { - if is_app_bundle(p) { - outermost = Some(p); + if let Some((parent, _)) = bundle_id.rsplit_once('.') { + if let Some(name) = try_resolve(parent) { + return (parent.to_string(), name); } - current = p.parent(); } - outermost.and_then(read_app_info) + let name = try_resolve(bundle_id).unwrap_or_else(|| bundle_id.to_string()); + (bundle_id.to_string(), name) } -fn is_app_bundle(path: &Path) -> bool { - path.extension().and_then(|s| s.to_str()) == Some("app") -} +fn get_app_info(app_path: &std::path::Path) -> Option { + let info_plist_path = app_path.join("Contents/Info.plist"); + + if let Ok(plist_data) = std::fs::read(&info_plist_path) { + if let Ok(plist) = plist::from_bytes::(&plist_data) { + let bundle_id = plist + .get("CFBundleIdentifier") + .and_then(|v| v.as_string())? + .to_string(); + + let localized_name = plist + .get("CFBundleDisplayName") + .and_then(|v| v.as_string()) + .or_else(|| plist.get("CFBundleName").and_then(|v| v.as_string()))? + .to_string(); + + return Some(InstalledApp { + id: bundle_id, + name: localized_name, + }); + } + } -fn read_app_info(app_path: &Path) -> Option { - let plist_path = app_path.join("Contents/Info.plist"); - let plist_data = std::fs::read(&plist_path).ok()?; - let plist: plist::Dictionary = plist::from_bytes(&plist_data).ok()?; - - let bundle_id = plist - .get("CFBundleIdentifier") - .and_then(|v| v.as_string())? - .to_string(); - - let name = plist - .get("CFBundleDisplayName") - .or_else(|| plist.get("CFBundleName")) - .and_then(|v| v.as_string())? - .to_string(); - - Some(InstalledApp { - id: bundle_id, - name, - }) + None } #[cfg(test)] @@ -151,19 +128,27 @@ mod tests { #[ignore] fn test_list_installed_apps() { let apps = list_installed_apps(); - println!("Got {} apps", apps.len()); - for app in &apps { - println!("- {} ({})", app.name, app.id); - } + println!("Got {} apps\n---", apps.len()); + println!( + "{}", + apps.iter() + .map(|a| format!("- {} ({})", a.name, a.id)) + .collect::>() + .join("\n") + ); } #[test] #[ignore] fn test_list_mic_using_apps() { let apps = list_mic_using_apps(); - println!("Got {} apps", apps.len()); - for app in &apps { - println!("- {} ({})", app.name, app.id); - } + println!("Got {} apps\n---", apps.len()); + println!( + "{}", + apps.iter() + .map(|a| format!("- {} ({})", a.name, a.id)) + .collect::>() + .join("\n") + ); } } diff --git a/crates/detect/src/mic/linux.rs b/crates/detect/src/mic/linux.rs index 889c2562ac..bcb2116f32 100644 --- a/crates/detect/src/mic/linux.rs +++ b/crates/detect/src/mic/linux.rs @@ -72,29 +72,30 @@ impl crate::Observer for Detector { context.set_subscribe_callback(Some(Box::new( move |facility, operation, _index| { - if let Some(pulse::context::subscribe::Facility::Source) = facility - && let Some(pulse::context::subscribe::Operation::Changed) = operation - && let Ok(mut state) = detector_state_for_subscribe.lock() - { - let mic_in_use = check_mic_in_use(); - - if state.should_trigger(mic_in_use) { - if mic_in_use { - let cb = callback_for_subscribe.clone(); - std::thread::spawn(move || { - let apps = crate::list_mic_using_apps(); - tracing::info!("mic_started_detected: {:?}", apps); - - if let Ok(guard) = cb.lock() { - let event = DetectEvent::MicStarted(apps); + if let Some(pulse::context::subscribe::Facility::Source) = facility { + if let Some(pulse::context::subscribe::Operation::Changed) = operation { + if let Ok(mut state) = detector_state_for_subscribe.lock() { + let mic_in_use = check_mic_in_use(); + + if state.should_trigger(mic_in_use) { + if mic_in_use { + let cb = callback_for_subscribe.clone(); + std::thread::spawn(move || { + let apps = crate::list_mic_using_apps(); + tracing::info!("mic_started_detected: {:?}", apps); + + if let Ok(guard) = cb.lock() { + let event = DetectEvent::MicStarted(apps); + tracing::info!(event = ?event, "detected"); + (*guard)(event); + } + }); + } else if let Ok(guard) = callback_for_subscribe.lock() { + let event = DetectEvent::MicStopped; tracing::info!(event = ?event, "detected"); (*guard)(event); } - }); - } else if let Ok(guard) = callback_for_subscribe.lock() { - let event = DetectEvent::MicStopped(vec![]); - tracing::info!(event = ?event, "detected"); - (*guard)(event); + } } } } @@ -223,10 +224,10 @@ fn check_mic_in_use() -> bool { introspector.get_source_output_info_list(move |list_result| match list_result { pulse::callbacks::ListResult::Item(info) => { - if !info.corked - && let Ok(mut r) = result_clone.lock() - { - *r = true; + if !info.corked { + if let Ok(mut r) = result_clone.lock() { + *r = true; + } } } pulse::callbacks::ListResult::End => { @@ -242,10 +243,10 @@ fn check_mic_in_use() -> bool { }); for _ in 0..100 { - if let Ok(d) = done.lock() - && *d - { - break; + if let Ok(d) = done.lock() { + if *d { + break; + } } std::thread::sleep(Duration::from_millis(10)); } diff --git a/crates/detect/src/mic/macos.rs b/crates/detect/src/mic/macos.rs index a03f5e839b..1b22761712 100644 --- a/crates/detect/src/mic/macos.rs +++ b/crates/detect/src/mic/macos.rs @@ -1,18 +1,7 @@ use cidre::{core_audio as ca, os}; -use std::collections::HashSet; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; -use crate::{BackgroundTask, DetectEvent, InstalledApp}; - -const DEVICE_IS_RUNNING_SOMEWHERE: ca::PropAddr = ca::PropAddr { - selector: ca::PropSelector::DEVICE_IS_RUNNING_SOMEWHERE, - scope: ca::PropScope::GLOBAL, - element: ca::PropElement::MAIN, -}; - -const POLL_INTERVAL: Duration = Duration::from_secs(1); +use crate::{BackgroundTask, DetectEvent}; pub struct Detector { background: BackgroundTask, @@ -26,11 +15,16 @@ impl Default for Detector { } } +const DEVICE_IS_RUNNING_SOMEWHERE: ca::PropAddr = ca::PropAddr { + selector: ca::PropSelector::DEVICE_IS_RUNNING_SOMEWHERE, + scope: ca::PropScope::GLOBAL, + element: ca::PropElement::MAIN, +}; + struct DetectorState { last_state: bool, last_change: Instant, debounce_duration: Duration, - active_apps: Vec, } impl DetectorState { @@ -39,241 +33,198 @@ impl DetectorState { last_state: false, last_change: Instant::now(), debounce_duration: Duration::from_millis(500), - active_apps: Vec::new(), } } fn should_trigger(&mut self, new_state: bool) -> bool { let now = Instant::now(); + if new_state == self.last_state { return false; } if now.duration_since(self.last_change) < self.debounce_duration { return false; } + self.last_state = new_state; self.last_change = now; true } } -struct SharedContext { - callback: Arc>, - current_device: Arc>>, - state: Arc>, - polling_active: Arc, -} - -impl SharedContext { - fn new(callback: crate::DetectCallback) -> Self { - Self { - callback: Arc::new(Mutex::new(callback)), - current_device: Arc::new(Mutex::new(None)), - state: Arc::new(Mutex::new(DetectorState::new())), - polling_active: Arc::new(AtomicBool::new(false)), - } - } - - fn clone_shared(&self) -> Self { - Self { - callback: self.callback.clone(), - current_device: self.current_device.clone(), - state: self.state.clone(), - polling_active: self.polling_active.clone(), - } - } - - fn emit(&self, event: DetectEvent) { - tracing::info!(?event, "detected"); - if let Ok(guard) = self.callback.lock() { - (*guard)(event); - } - } - - fn handle_mic_change(&self, mic_in_use: bool) { - let Ok(mut state_guard) = self.state.lock() else { - return; - }; - - if !state_guard.should_trigger(mic_in_use) { - return; - } - - if mic_in_use { - let apps = crate::list_mic_using_apps(); - state_guard.active_apps = apps.clone(); - self.polling_active.store(true, Ordering::SeqCst); - drop(state_guard); - self.emit(DetectEvent::MicStarted(apps)); - } else { - self.polling_active.store(false, Ordering::SeqCst); - let stopped_apps = std::mem::take(&mut state_guard.active_apps); - drop(state_guard); - self.emit(DetectEvent::MicStopped(stopped_apps)); - } - } -} - -fn is_mic_running(device: &ca::Device) -> bool { - device - .prop::(&DEVICE_IS_RUNNING_SOMEWHERE) - .map(|v| v != 0) - .unwrap_or(false) -} - -fn diff_apps( - previous: &[InstalledApp], - current: &[InstalledApp], -) -> (Vec, Vec) { - let previous_ids: HashSet<_> = previous.iter().map(|app| &app.id).collect(); - let current_ids: HashSet<_> = current.iter().map(|app| &app.id).collect(); - - let started = current - .iter() - .filter(|app| !previous_ids.contains(&app.id)) - .cloned() - .collect(); - - let stopped = previous - .iter() - .filter(|app| !current_ids.contains(&app.id)) - .cloned() - .collect(); - - (started, stopped) -} - -struct ListenerData { - ctx: SharedContext, - device_listener_ptr: *mut (), -} - -fn spawn_polling_thread(ctx: SharedContext) { - std::thread::spawn(move || { - loop { - std::thread::sleep(POLL_INTERVAL); - - if !ctx.polling_active.load(Ordering::SeqCst) { - continue; - } - - let current_apps = crate::list_mic_using_apps(); - let Ok(mut state_guard) = ctx.state.lock() else { - continue; - }; - - let (started, stopped) = diff_apps(&state_guard.active_apps, ¤t_apps); - state_guard.active_apps = current_apps; - drop(state_guard); - - if !started.is_empty() { - let event = DetectEvent::MicStarted(started); - tracing::info!(?event, "detected_via_polling"); - if let Ok(guard) = ctx.callback.lock() { - (*guard)(event); - } - } - - if !stopped.is_empty() { - let event = DetectEvent::MicStopped(stopped); - tracing::info!(?event, "detected_via_polling"); - if let Ok(guard) = ctx.callback.lock() { - (*guard)(event); - } - } - } - }); -} - -extern "C-unwind" fn device_listener( - _obj_id: ca::Obj, - number_addresses: u32, - addresses: *const ca::PropAddr, - client_data: *mut (), -) -> os::Status { - let data = unsafe { &*(client_data as *const ListenerData) }; - let addresses = unsafe { std::slice::from_raw_parts(addresses, number_addresses as usize) }; - - for addr in addresses { - if addr.selector != ca::PropSelector::DEVICE_IS_RUNNING_SOMEWHERE { - continue; - } - if let Ok(device) = ca::System::default_input_device() { - data.ctx.handle_mic_change(is_mic_running(&device)); - } - } - - os::Status::NO_ERR -} - -extern "C-unwind" fn system_listener( - _obj_id: ca::Obj, - number_addresses: u32, - addresses: *const ca::PropAddr, - client_data: *mut (), -) -> os::Status { - let data = unsafe { &*(client_data as *const ListenerData) }; - let addresses = unsafe { std::slice::from_raw_parts(addresses, number_addresses as usize) }; - - for addr in addresses { - if addr.selector != ca::PropSelector::HW_DEFAULT_INPUT_DEVICE { - continue; - } - - let Ok(mut device_guard) = data.ctx.current_device.lock() else { - continue; - }; - - if let Some(old_device) = device_guard.take() { - let _ = old_device.remove_prop_listener( - &DEVICE_IS_RUNNING_SOMEWHERE, - device_listener, - data.device_listener_ptr, - ); - } - - let Ok(new_device) = ca::System::default_input_device() else { - continue; - }; - - if new_device - .add_prop_listener( - &DEVICE_IS_RUNNING_SOMEWHERE, - device_listener, - data.device_listener_ptr, - ) - .is_ok() - { - let mic_in_use = is_mic_running(&new_device); - *device_guard = Some(new_device); - drop(device_guard); - data.ctx.handle_mic_change(mic_in_use); - } - } - - os::Status::NO_ERR -} - impl crate::Observer for Detector { fn start(&mut self, f: crate::DetectCallback) { self.background.start(|running, mut rx| async move { let (tx, mut notify_rx) = tokio::sync::mpsc::channel(1); std::thread::spawn(move || { - let ctx = SharedContext::new(f); + let callback = std::sync::Arc::new(std::sync::Mutex::new(f)); + let current_device = std::sync::Arc::new(std::sync::Mutex::new(None::)); + let detector_state = + std::sync::Arc::new(std::sync::Mutex::new(DetectorState::new())); + + let callback_for_device = callback.clone(); + let current_device_for_device = current_device.clone(); + let detector_state_for_device = detector_state.clone(); + + extern "C-unwind" fn device_listener( + _obj_id: ca::Obj, + number_addresses: u32, + addresses: *const ca::PropAddr, + client_data: *mut (), + ) -> os::Status { + let data = unsafe { + &*(client_data + as *const ( + std::sync::Arc>, + std::sync::Arc>>, + std::sync::Arc>, + )) + }; + let callback = &data.0; + let state = &data.2; + + let addresses = + unsafe { std::slice::from_raw_parts(addresses, number_addresses as usize) }; + + for addr in addresses { + if addr.selector == ca::PropSelector::DEVICE_IS_RUNNING_SOMEWHERE { + if let Ok(device) = ca::System::default_input_device() { + if let Ok(is_running) = + device.prop::(&DEVICE_IS_RUNNING_SOMEWHERE) + { + let mic_in_use = is_running != 0; + + if let Ok(mut state_guard) = state.lock() { + if state_guard.should_trigger(mic_in_use) { + if mic_in_use { + let cb = callback.clone(); + + std::thread::spawn(move || { + let apps = crate::list_mic_using_apps(); + tracing::info!( + "detect_device_listener: {:?}", + apps + ); + + if let Ok(guard) = cb.lock() { + let event = DetectEvent::MicStarted(apps); + tracing::info!(event = ?event, "detected"); + (*guard)(event); + } + }); + } else { + if let Ok(guard) = callback.lock() { + let event = DetectEvent::MicStopped; + tracing::info!(event = ?event, "detected"); + (*guard)(event); + } + } + } + } + } + } + } + } + + os::Status::NO_ERR + } - spawn_polling_thread(ctx.clone_shared()); + extern "C-unwind" fn system_listener( + _obj_id: ca::Obj, + number_addresses: u32, + addresses: *const ca::PropAddr, + client_data: *mut (), + ) -> os::Status { + let data = unsafe { + &*(client_data + as *const ( + std::sync::Arc>, + std::sync::Arc>>, + std::sync::Arc>, + *mut (), + )) + }; + let current_device = &data.1; + let state = &data.2; + let device_listener_data = data.3; + + let addresses = + unsafe { std::slice::from_raw_parts(addresses, number_addresses as usize) }; + + for addr in addresses { + if addr.selector == ca::PropSelector::HW_DEFAULT_INPUT_DEVICE { + if let Ok(mut device_guard) = current_device.lock() { + if let Some(old_device) = device_guard.take() { + let _ = old_device.remove_prop_listener( + &DEVICE_IS_RUNNING_SOMEWHERE, + device_listener, + device_listener_data, + ); + } - let device_listener_data = Box::new(ListenerData { - ctx: ctx.clone_shared(), - device_listener_ptr: std::ptr::null_mut(), - }); + if let Ok(new_device) = ca::System::default_input_device() { + let mic_in_use = if let Ok(is_running) = + new_device.prop::(&DEVICE_IS_RUNNING_SOMEWHERE) + { + is_running != 0 + } else { + false + }; + + if new_device + .add_prop_listener( + &DEVICE_IS_RUNNING_SOMEWHERE, + device_listener, + device_listener_data, + ) + .is_ok() + { + *device_guard = Some(new_device); + + if let Ok(mut state_guard) = state.lock() { + if state_guard.should_trigger(mic_in_use) { + if mic_in_use { + let cb = data.0.clone(); + + std::thread::spawn(move || { + let apps = crate::list_mic_using_apps(); + tracing::info!( + "detect_system_listener: {:?}", + apps + ); + + if let Ok(callback_guard) = cb.lock() { + (*callback_guard)( + DetectEvent::MicStarted(apps), + ); + } + }); + } + } + } + } + } + } + } + } + + os::Status::NO_ERR + } + + let device_listener_data = Box::new(( + callback_for_device.clone(), + current_device_for_device.clone(), + detector_state_for_device.clone(), + )); let device_listener_ptr = Box::into_raw(device_listener_data) as *mut (); - let system_listener_data = Box::new(ListenerData { - ctx, + let system_listener_data = Box::new(( + callback.clone(), + current_device.clone(), + detector_state.clone(), device_listener_ptr, - }); + )); let system_listener_ptr = Box::into_raw(system_listener_data) as *mut (); if let Err(e) = ca::System::OBJ.add_prop_listener( @@ -286,38 +237,40 @@ impl crate::Observer for Detector { tracing::info!("adding_system_listener_success"); } - match ca::System::default_input_device() { - Ok(device) => { - let mic_in_use = is_mic_running(&device); - if device - .add_prop_listener( - &DEVICE_IS_RUNNING_SOMEWHERE, - device_listener, - device_listener_ptr, - ) - .is_ok() - { - tracing::info!("adding_device_listener_success"); - - let data = unsafe { &*(system_listener_ptr as *const ListenerData) }; - if let Ok(mut device_guard) = data.ctx.current_device.lock() { - *device_guard = Some(device); - } - if let Ok(mut state_guard) = data.ctx.state.lock() { - state_guard.last_state = mic_in_use; - if mic_in_use { - state_guard.active_apps = crate::list_mic_using_apps(); - data.ctx.polling_active.store(true, Ordering::SeqCst); - } - } + if let Ok(device) = ca::System::default_input_device() { + let mic_in_use = + if let Ok(is_running) = device.prop::(&DEVICE_IS_RUNNING_SOMEWHERE) { + is_running != 0 } else { - tracing::error!("adding_device_listener_failed"); + false + }; + + if device + .add_prop_listener( + &DEVICE_IS_RUNNING_SOMEWHERE, + device_listener, + device_listener_ptr, + ) + .is_ok() + { + tracing::info!("adding_device_listener_success"); + + if let Ok(mut device_guard) = current_device.lock() { + *device_guard = Some(device); } + + if let Ok(mut state_guard) = detector_state.lock() { + state_guard.last_state = mic_in_use; + } + } else { + tracing::error!("adding_device_listener_failed"); } - Err(_) => tracing::warn!("no_default_input_device_found"), + } else { + tracing::warn!("no_default_input_device_found"); } let _ = tx.blocking_send(()); + loop { std::thread::park(); } @@ -327,9 +280,11 @@ impl crate::Observer for Detector { loop { tokio::select! { - _ = &mut rx => break, - _ = tokio::time::sleep(Duration::from_millis(500)) => { - if !running.load(Ordering::SeqCst) { + _ = &mut rx => { + break; + } + _ = tokio::time::sleep(tokio::time::Duration::from_millis(500)) => { + if !running.load(std::sync::atomic::Ordering::SeqCst) { break; } } @@ -355,7 +310,7 @@ mod tests { println!("{:?}", v); })); - tokio::time::sleep(Duration::from_secs(60)).await; + tokio::time::sleep(tokio::time::Duration::from_secs(60)).await; detector.stop(); } } diff --git a/crates/device-heuristic/src/linux.rs b/crates/device-heuristic/src/linux.rs index c0392adc27..6ed7058a93 100644 --- a/crates/device-heuristic/src/linux.rs +++ b/crates/device-heuristic/src/linux.rs @@ -53,15 +53,16 @@ pub fn is_headphone_from_default_output_device() -> bool { mainloop.lock(); let introspector = context.introspect(); introspector.get_sink_info_by_name("@DEFAULT_SINK@", move |list_result| { - if let pulse::callbacks::ListResult::Item(sink_info) = list_result - && let Some(active_port) = &sink_info.active_port - && let Some(name) = active_port.name.as_ref() - { - let name_lower = name.to_lowercase(); - let is_headphone = - name_lower.contains("headphone") || name_lower.contains("headset"); - if let Ok(mut r) = result.lock() { - *r = is_headphone; + if let pulse::callbacks::ListResult::Item(sink_info) = list_result { + if let Some(active_port) = &sink_info.active_port { + if let Some(name) = active_port.name.as_ref() { + let name_lower = name.to_lowercase(); + let is_headphone = + name_lower.contains("headphone") || name_lower.contains("headset"); + if let Ok(mut r) = result.lock() { + *r = is_headphone; + } + } } } done.store(true, Ordering::Release); diff --git a/crates/file/src/lib.rs b/crates/file/src/lib.rs index 8a8376ca22..d7aa7a1b6b 100644 --- a/crates/file/src/lib.rs +++ b/crates/file/src/lib.rs @@ -178,21 +178,21 @@ pub async fn download_file_with_callback_cancellable( loop { // Check for cancellation - if let Some(ref token) = cancellation_token - && token.is_cancelled() - { - // Flush any buffered data before exiting - if !write_buffer.is_empty() { - file.write_all(&write_buffer)?; - write_buffer.clear(); + if let Some(ref token) = cancellation_token { + if token.is_cancelled() { + // Flush any buffered data before exiting + if !write_buffer.is_empty() { + file.write_all(&write_buffer)?; + write_buffer.clear(); + } + file.flush()?; + file.sync_all()?; + tracing::info!( + "Download cancelled, partial file saved at: {:?}", + output_path.as_ref() + ); + return Err(crate::Error::Cancelled); } - file.flush()?; - file.sync_all()?; - tracing::info!( - "Download cancelled, partial file saved at: {:?}", - output_path.as_ref() - ); - return Err(crate::Error::Cancelled); } match stream.next().await { @@ -387,26 +387,26 @@ pub async fn download_file_parallel_cancellable= MAX_CONCURRENT_CHUNKS - && let Some(result) = tasks.next().await - { - process_task_result(result, &file, &pending_writes, &next_write_offset)?; + if tasks.len() >= MAX_CONCURRENT_CHUNKS { + if let Some(result) = tasks.next().await { + process_task_result(result, &file, &pending_writes, &next_write_offset)?; + } } } diff --git a/crates/granola/Cargo.toml b/crates/granola/Cargo.toml deleted file mode 100644 index 5e869512c9..0000000000 --- a/crates/granola/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "granola" -version = "0.1.0" -edition = "2024" - -[dependencies] -chrono = { workspace = true, features = ["serde"] } -dirs = { workspace = true } -regex = { workspace = true } -reqwest = { workspace = true, features = ["json"] } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -serde_yaml = { workspace = true } -thiserror = { workspace = true } - -[dev-dependencies] -tempfile = { workspace = true } -tokio = { workspace = true, features = ["rt", "macros"] } diff --git a/crates/granola/src/api/client.rs b/crates/granola/src/api/client.rs deleted file mode 100644 index ae56e005cf..0000000000 --- a/crates/granola/src/api/client.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::api::models::{Document, GranolaResponse}; -use crate::api::token::extract_access_token; -use crate::error::{Error, Result}; -use serde_json::json; -use std::time::Duration; - -const API_URL: &str = "https://api.granola.ai/v2/get-documents"; -const USER_AGENT: &str = "Granola/5.354.0"; -const CLIENT_VERSION: &str = "5.354.0"; -const PAGE_LIMIT: usize = 100; - -pub struct GranolaClient { - client: reqwest::Client, - access_token: String, -} - -impl GranolaClient { - pub fn new(supabase_content: &[u8], timeout: Duration) -> Result { - let access_token = extract_access_token(supabase_content)?; - let client = reqwest::Client::builder().timeout(timeout).build()?; - - Ok(Self { - client, - access_token, - }) - } - - pub async fn get_documents(&self) -> Result> { - let mut all_documents = Vec::new(); - let mut offset = 0; - - loop { - let request_body = json!({ - "limit": PAGE_LIMIT, - "offset": offset, - "include_last_viewed_panel": true - }); - - let response = self - .client - .post(API_URL) - .header("Authorization", format!("Bearer {}", self.access_token)) - .header("User-Agent", USER_AGENT) - .header("X-Client-Version", CLIENT_VERSION) - .header("Content-Type", "application/json") - .header("Accept", "*/*") - .json(&request_body) - .send() - .await?; - - let status = response.status(); - if !status.is_success() { - let body = response.text().await.unwrap_or_default(); - let preview = if body.chars().count() > 200 { - let truncate_idx = body - .char_indices() - .nth(200) - .map(|(i, _)| i) - .unwrap_or(body.len()); - format!("{}...", &body[..truncate_idx]) - } else { - body - }; - return Err(Error::ApiStatus { - status: status.as_u16(), - body: preview, - }); - } - - let response_text = response.text().await?; - let granola_response: GranolaResponse = - serde_json::from_str(&response_text).map_err(Error::ApiResponseParse)?; - - let docs_count = granola_response.docs.len(); - all_documents.extend(granola_response.docs); - - if docs_count < PAGE_LIMIT { - break; - } - - offset += PAGE_LIMIT; - } - - Ok(all_documents) - } -} diff --git a/crates/granola/src/api/mod.rs b/crates/granola/src/api/mod.rs deleted file mode 100644 index 075a923e01..0000000000 --- a/crates/granola/src/api/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod client; -mod models; -mod token; - -pub use client::*; -pub use models::*; -pub use token::*; diff --git a/crates/granola/src/api/models.rs b/crates/granola/src/api/models.rs deleted file mode 100644 index da99e64f5d..0000000000 --- a/crates/granola/src/api/models.rs +++ /dev/null @@ -1,201 +0,0 @@ -use serde::{Deserialize, Deserializer}; -use serde_json::Value; - -#[derive(Debug, Clone, Deserialize)] -pub struct GranolaResponse { - pub docs: Vec, -} - -#[derive(Debug, Clone)] -pub struct Document { - pub id: String, - pub title: String, - pub content: String, - pub created_at: String, - pub updated_at: String, - pub tags: Vec, - pub notes: Option, - pub notes_plain: Option, - pub last_viewed_panel: Option, -} - -impl<'de> Deserialize<'de> for Document { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - struct RawDocument { - id: String, - title: String, - #[serde(default)] - content: String, - created_at: String, - updated_at: String, - #[serde(default)] - tags: Vec, - notes: Option, - notes_plain: Option, - last_viewed_panel: Option, - } - - let raw = RawDocument::deserialize(deserializer)?; - - let notes = raw.notes.and_then(|v| parse_maybe_stringified_json(&v)); - - Ok(Document { - id: raw.id, - title: raw.title, - content: raw.content, - created_at: raw.created_at, - updated_at: raw.updated_at, - tags: raw.tags, - notes, - notes_plain: raw.notes_plain, - last_viewed_panel: raw.last_viewed_panel, - }) - } -} - -#[derive(Debug, Clone)] -pub struct LastViewedPanel { - pub document_id: Option, - pub id: Option, - pub created_at: Option, - pub title: Option, - pub content: Option, - pub deleted_at: Option, - pub template_slug: Option, - pub last_viewed_at: Option, - pub updated_at: Option, - pub content_updated_at: Option, - pub original_content: String, -} - -impl<'de> Deserialize<'de> for LastViewedPanel { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - struct RawLastViewedPanel { - document_id: Option, - id: Option, - created_at: Option, - title: Option, - content: Option, - deleted_at: Option, - template_slug: Option, - last_viewed_at: Option, - updated_at: Option, - content_updated_at: Option, - #[serde(default)] - original_content: String, - } - - let raw = RawLastViewedPanel::deserialize(deserializer)?; - - let content = raw.content.and_then(|v| parse_maybe_stringified_json(&v)); - - Ok(LastViewedPanel { - document_id: raw.document_id, - id: raw.id, - created_at: raw.created_at, - title: raw.title, - content, - deleted_at: raw.deleted_at, - template_slug: raw.template_slug, - last_viewed_at: raw.last_viewed_at, - updated_at: raw.updated_at, - content_updated_at: raw.content_updated_at, - original_content: raw.original_content, - }) - } -} - -#[derive(Debug, Clone, Deserialize)] -pub struct ProseMirrorDoc { - #[serde(rename = "type")] - pub doc_type: String, - #[serde(default)] - pub content: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct ProseMirrorNode { - #[serde(rename = "type")] - pub node_type: String, - #[serde(default)] - pub content: Vec, - #[serde(default)] - pub text: String, - #[serde(default)] - pub attrs: Option>, -} - -fn parse_maybe_stringified_json(value: &Value) -> Option { - match value { - Value::Null => None, - Value::Object(_) => serde_json::from_value(value.clone()).ok(), - Value::String(s) => { - let trimmed = s.trim_start(); - if trimmed.starts_with('<') { - return None; - } - serde_json::from_str(s).ok() - } - _ => None, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_document_with_notes_object() { - let json = r#"{ - "id": "doc-1", - "title": "Test", - "content": "", - "created_at": "2024-01-01T00:00:00Z", - "updated_at": "2024-01-01T00:00:00Z", - "tags": [], - "notes": {"type": "doc", "content": []} - }"#; - let doc: Document = serde_json::from_str(json).unwrap(); - assert!(doc.notes.is_some()); - assert_eq!(doc.notes.unwrap().doc_type, "doc"); - } - - #[test] - fn test_parse_document_with_notes_string() { - let json = r#"{ - "id": "doc-1", - "title": "Test", - "content": "", - "created_at": "2024-01-01T00:00:00Z", - "updated_at": "2024-01-01T00:00:00Z", - "tags": [], - "notes": "{\"type\": \"doc\", \"content\": []}" - }"#; - let doc: Document = serde_json::from_str(json).unwrap(); - assert!(doc.notes.is_some()); - assert_eq!(doc.notes.unwrap().doc_type, "doc"); - } - - #[test] - fn test_parse_document_with_html_content_skipped() { - let json = r#"{ - "id": "doc-1", - "title": "Test", - "content": "", - "created_at": "2024-01-01T00:00:00Z", - "updated_at": "2024-01-01T00:00:00Z", - "tags": [], - "notes": "content" - }"#; - let doc: Document = serde_json::from_str(json).unwrap(); - assert!(doc.notes.is_none()); - } -} diff --git a/crates/granola/src/api/token.rs b/crates/granola/src/api/token.rs deleted file mode 100644 index 676f3197ad..0000000000 --- a/crates/granola/src/api/token.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::error::{Error, Result}; -use serde::Deserialize; - -#[derive(Deserialize)] -struct SupabaseWrapper { - workos_tokens: String, -} - -#[derive(Deserialize)] -struct WorkosTokens { - access_token: String, -} - -pub fn extract_access_token(content: &[u8]) -> Result { - let wrapper: SupabaseWrapper = - serde_json::from_slice(content).map_err(Error::SupabaseJsonParse)?; - - let tokens: WorkosTokens = - serde_json::from_str(&wrapper.workos_tokens).map_err(Error::TokenJsonParse)?; - - let access_token = tokens.access_token.trim().to_string(); - if access_token.is_empty() { - return Err(Error::AccessTokenNotFound); - } - - Ok(access_token) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_extract_access_token_success() { - let json = r#"{"workos_tokens": "{\"access_token\":\"test_token_123\"}"}"#; - let token = extract_access_token(json.as_bytes()).unwrap(); - assert_eq!(token, "test_token_123"); - } - - #[test] - fn test_extract_access_token_empty() { - let json = r#"{"workos_tokens": "{\"access_token\":\" \"}"}"#; - let result = extract_access_token(json.as_bytes()); - assert!(matches!(result, Err(Error::AccessTokenNotFound))); - } - - #[test] - fn test_extract_access_token_invalid_wrapper() { - let json = r#"{"invalid": "json"}"#; - let result = extract_access_token(json.as_bytes()); - assert!(matches!(result, Err(Error::SupabaseJsonParse(_)))); - } -} diff --git a/crates/granola/src/cache/mod.rs b/crates/granola/src/cache/mod.rs deleted file mode 100644 index dfb56dd721..0000000000 --- a/crates/granola/src/cache/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod reader; - -pub use reader::*; diff --git a/crates/granola/src/cache/reader.rs b/crates/granola/src/cache/reader.rs deleted file mode 100644 index 624adadb78..0000000000 --- a/crates/granola/src/cache/reader.rs +++ /dev/null @@ -1,159 +0,0 @@ -use crate::error::{Error, Result}; -use serde::Deserialize; -use serde_json::Value; -use std::collections::HashMap; -use std::path::Path; - -#[derive(Debug, Clone)] -pub struct TranscriptSegment { - pub id: String, - pub document_id: String, - pub start_timestamp: String, - pub end_timestamp: String, - pub text: String, - pub source: String, - pub is_final: bool, -} - -#[derive(Debug, Clone)] -pub struct CacheDocument { - pub id: String, - pub title: String, - pub created_at: String, - pub updated_at: String, -} - -#[derive(Debug)] -pub struct CacheData { - pub documents: HashMap, - pub transcripts: HashMap>, -} - -#[derive(Deserialize)] -struct OuterCache { - cache: String, -} - -#[derive(Deserialize)] -struct InnerCache { - state: CacheState, -} - -#[derive(Deserialize)] -struct CacheState { - documents: HashMap, - transcripts: HashMap, -} - -pub fn read_cache(path: &Path) -> Result { - let content = std::fs::read_to_string(path).map_err(Error::CacheFileRead)?; - - let outer: OuterCache = serde_json::from_str(&content).map_err(Error::CacheJsonParse)?; - - let inner: InnerCache = serde_json::from_str(&outer.cache).map_err(Error::CacheJsonParse)?; - - let mut documents = HashMap::new(); - for (id, value) in inner.state.documents { - if let Some(doc) = parse_cache_document(&id, &value) { - documents.insert(id, doc); - } - } - - let mut transcripts = HashMap::new(); - for (id, value) in inner.state.transcripts { - if let Some(segments) = parse_transcript_segments(&value) { - transcripts.insert(id, segments); - } - } - - Ok(CacheData { - documents, - transcripts, - }) -} - -fn parse_cache_document(id: &str, value: &Value) -> Option { - #[derive(Deserialize)] - struct RawDoc { - title: Option, - created_at: Option, - updated_at: Option, - } - - let raw: RawDoc = serde_json::from_value(value.clone()).ok()?; - - Some(CacheDocument { - id: id.to_string(), - title: raw.title.unwrap_or_default(), - created_at: raw.created_at.unwrap_or_default(), - updated_at: raw.updated_at.unwrap_or_default(), - }) -} - -fn parse_transcript_segments(value: &Value) -> Option> { - #[derive(Deserialize)] - struct RawSegment { - id: String, - document_id: String, - start_timestamp: String, - end_timestamp: String, - text: String, - source: String, - is_final: bool, - } - - let raw_segments: Vec = serde_json::from_value(value.clone()).ok()?; - - Some( - raw_segments - .into_iter() - .map(|s| TranscriptSegment { - id: s.id, - document_id: s.document_id, - start_timestamp: s.start_timestamp, - end_timestamp: s.end_timestamp, - text: s.text, - source: s.source, - is_final: s.is_final, - }) - .collect(), - ) -} - -pub fn default_cache_path() -> std::path::PathBuf { - if let Some(home) = dirs::home_dir() { - #[cfg(target_os = "macos")] - return home.join("Library/Application Support/Granola/cache-v3.json"); - - #[cfg(target_os = "linux")] - return home.join(".config/Granola/cache-v3.json"); - - #[cfg(target_os = "windows")] - return home.join("AppData/Roaming/Granola/cache-v3.json"); - - #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] - return home.join("cache-v3.json"); - } - std::path::PathBuf::from("cache-v3.json") -} - -#[cfg(test)] -mod tests { - use super::*; - use std::io::Write; - use tempfile::NamedTempFile; - - #[test] - fn test_read_cache_success() { - let cache_content = r#"{"cache": "{\"state\":{\"documents\":{\"doc-1\":{\"title\":\"Test\",\"created_at\":\"2024-01-01T00:00:00Z\",\"updated_at\":\"2024-01-01T00:00:00Z\"}},\"transcripts\":{\"doc-1\":[{\"id\":\"seg-1\",\"document_id\":\"doc-1\",\"start_timestamp\":\"2024-01-01T14:00:00Z\",\"end_timestamp\":\"2024-01-01T14:00:05Z\",\"text\":\"Hello\",\"source\":\"system\",\"is_final\":true}]}}}"}"#; - - let mut file = NamedTempFile::new().unwrap(); - file.write_all(cache_content.as_bytes()).unwrap(); - - let cache_data = read_cache(file.path()).unwrap(); - - assert_eq!(cache_data.documents.len(), 1); - assert_eq!(cache_data.transcripts.len(), 1); - assert_eq!(cache_data.documents["doc-1"].title, "Test"); - } -} diff --git a/crates/granola/src/error.rs b/crates/granola/src/error.rs deleted file mode 100644 index d399f2de37..0000000000 --- a/crates/granola/src/error.rs +++ /dev/null @@ -1,45 +0,0 @@ -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum Error { - #[error("failed to read supabase file: {0}")] - SupabaseFileRead(#[from] std::io::Error), - - #[error("failed to parse supabase JSON: {0}")] - SupabaseJsonParse(#[source] serde_json::Error), - - #[error("access token not found in supabase.json")] - AccessTokenNotFound, - - #[error("failed to parse token JSON: {0}")] - TokenJsonParse(#[source] serde_json::Error), - - #[error("API request failed: {0}")] - ApiRequest(#[from] reqwest::Error), - - #[error("API returned error status {status}: {body}")] - ApiStatus { status: u16, body: String }, - - #[error("failed to parse API response: {0}")] - ApiResponseParse(#[source] serde_json::Error), - - #[error("failed to read cache file: {0}")] - CacheFileRead(std::io::Error), - - #[error("failed to parse cache JSON: {0}")] - CacheJsonParse(#[source] serde_json::Error), - - #[error("failed to create output directory: {0}")] - CreateDirectory(std::io::Error), - - #[error("failed to write file {path}: {source}")] - WriteFile { - path: String, - source: std::io::Error, - }, - - #[error("failed to serialize YAML frontmatter: {0}")] - YamlSerialize(#[from] serde_yaml::Error), -} - -pub type Result = std::result::Result; diff --git a/crates/granola/src/fs/mod.rs b/crates/granola/src/fs/mod.rs deleted file mode 100644 index 548cb397d7..0000000000 --- a/crates/granola/src/fs/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod writer; - -pub use writer::*; diff --git a/crates/granola/src/fs/writer.rs b/crates/granola/src/fs/writer.rs deleted file mode 100644 index 7bdd31b001..0000000000 --- a/crates/granola/src/fs/writer.rs +++ /dev/null @@ -1,237 +0,0 @@ -use crate::api::Document; -use crate::cache::{CacheDocument, TranscriptSegment}; -use crate::error::{Error, Result}; -use crate::markdown::document_to_markdown; -use crate::transcript::format_transcript; -use chrono::{DateTime, Utc}; -use regex::Regex; -use std::collections::HashMap; -use std::fs; -use std::path::Path; - -pub fn write_notes(documents: &[Document], output_dir: &Path) -> Result { - fs::create_dir_all(output_dir).map_err(Error::CreateDirectory)?; - - let mut used_filenames: HashMap = HashMap::new(); - let mut written_count = 0; - - let invalid_chars = Regex::new(r#"[<>:"/\\|?*\x00-\x1f]"#).unwrap(); - let multiple_underscores = Regex::new(r"_+").unwrap(); - - for doc in documents { - let filename = - sanitize_filename(&doc.title, &doc.id, &invalid_chars, &multiple_underscores); - let filename = make_unique(&filename, &mut used_filenames); - *used_filenames.entry(filename.clone()).or_insert(0) += 1; - - let file_path = output_dir.join(format!("{}.md", filename)); - - if !should_update_file(&doc.updated_at, &file_path) { - continue; - } - - let markdown = document_to_markdown(doc)?; - - fs::write(&file_path, markdown).map_err(|e| Error::WriteFile { - path: file_path.display().to_string(), - source: e, - })?; - - written_count += 1; - } - - Ok(written_count) -} - -pub fn write_transcripts( - documents: &HashMap, - transcripts: &HashMap>, - output_dir: &Path, -) -> Result { - fs::create_dir_all(output_dir).map_err(Error::CreateDirectory)?; - - let mut used_filenames: HashMap = HashMap::new(); - let mut written_count = 0; - - let invalid_chars = Regex::new(r#"[<>:"/\\|?*\x00-\x1f]"#).unwrap(); - let multiple_underscores = Regex::new(r"_+").unwrap(); - - for (doc_id, segments) in transcripts { - if segments.is_empty() { - continue; - } - - let doc = documents - .get(doc_id) - .cloned() - .unwrap_or_else(|| CacheDocument { - id: doc_id.clone(), - title: doc_id.clone(), - created_at: String::new(), - updated_at: String::new(), - }); - - let filename = - sanitize_filename(&doc.title, &doc.id, &invalid_chars, &multiple_underscores); - let filename = make_unique(&filename, &mut used_filenames); - *used_filenames.entry(filename.clone()).or_insert(0) += 1; - - let file_path = output_dir.join(format!("{}.txt", filename)); - - if !should_update_file(&doc.updated_at, &file_path) { - continue; - } - - let content = format_transcript(&doc, segments); - if content.is_empty() { - continue; - } - - fs::write(&file_path, content).map_err(|e| Error::WriteFile { - path: file_path.display().to_string(), - source: e, - })?; - - written_count += 1; - } - - Ok(written_count) -} - -fn sanitize_filename( - title: &str, - id: &str, - invalid_chars: &Regex, - multiple_underscores: &Regex, -) -> String { - let name = if title.trim().is_empty() { - id - } else { - title.trim() - }; - - let name = invalid_chars.replace_all(name, "_"); - let name = multiple_underscores.replace_all(&name, "_"); - let name = name.trim_matches('_'); - - let name = if name.is_empty() { "untitled" } else { name }; - - if name.chars().count() > 100 { - name.chars().take(100).collect() - } else { - name.to_string() - } -} - -fn make_unique(filename: &str, used: &mut HashMap) -> String { - if let Some(&count) = used.get(filename) { - format!("{}_{}", filename, count + 1) - } else { - filename.to_string() - } -} - -fn should_update_file(updated_at: &str, file_path: &Path) -> bool { - let metadata = match fs::metadata(file_path) { - Ok(m) => m, - Err(_) => return true, - }; - - let doc_updated = match DateTime::parse_from_rfc3339(updated_at) { - Ok(dt) => dt, - Err(_) => return true, - }; - - let file_modified = match metadata.modified() { - Ok(t) => t, - Err(_) => return true, - }; - - let file_modified: DateTime = file_modified.into(); - - doc_updated > file_modified -} - -#[cfg(test)] -mod tests { - use super::*; - use tempfile::TempDir; - - #[test] - fn test_sanitize_filename() { - let invalid_chars = Regex::new(r#"[<>:"/\\|?*\x00-\x1f]"#).unwrap(); - let multiple_underscores = Regex::new(r"_+").unwrap(); - - assert_eq!( - sanitize_filename( - "Simple Title", - "id-1", - &invalid_chars, - &multiple_underscores - ), - "Simple Title" - ); - assert_eq!( - sanitize_filename( - "Title: With Colon", - "id-2", - &invalid_chars, - &multiple_underscores - ), - "Title_ With Colon" - ); - assert_eq!( - sanitize_filename( - "Title/With/Slashes", - "id-3", - &invalid_chars, - &multiple_underscores - ), - "Title_With_Slashes" - ); - assert_eq!( - sanitize_filename("", "id-4", &invalid_chars, &multiple_underscores), - "id-4" - ); - assert_eq!( - sanitize_filename(" ", "id-5", &invalid_chars, &multiple_underscores), - "id-5" - ); - } - - #[test] - fn test_make_unique() { - let mut used = HashMap::new(); - - assert_eq!(make_unique("test", &mut used), "test"); - used.insert("test".to_string(), 1); - - assert_eq!(make_unique("test", &mut used), "test_2"); - } - - #[test] - fn test_write_notes() { - let temp_dir = TempDir::new().unwrap(); - - let documents = vec![Document { - id: "doc-1".to_string(), - title: "Test Meeting".to_string(), - content: "Content".to_string(), - created_at: "2024-01-01T00:00:00Z".to_string(), - updated_at: "2024-01-01T00:00:00Z".to_string(), - tags: vec![], - notes: None, - notes_plain: None, - last_viewed_panel: None, - }]; - - let count = write_notes(&documents, temp_dir.path()).unwrap(); - assert_eq!(count, 1); - - let file_path = temp_dir.path().join("Test Meeting.md"); - assert!(file_path.exists()); - - let content = fs::read_to_string(file_path).unwrap(); - assert!(content.contains("# Test Meeting")); - } -} diff --git a/crates/granola/src/lib.rs b/crates/granola/src/lib.rs deleted file mode 100644 index 93ee8dc89b..0000000000 --- a/crates/granola/src/lib.rs +++ /dev/null @@ -1,52 +0,0 @@ -pub mod api; -pub mod cache; -pub mod error; -pub mod fs; -pub mod markdown; -pub mod prosemirror; -pub mod transcript; - -use crate::api::GranolaClient; -use crate::cache::read_cache; -use crate::error::{Error, Result}; -use crate::fs::{write_notes, write_transcripts}; -use std::path::PathBuf; -use std::time::Duration; - -#[derive(Debug, Clone)] -pub struct NotesConfig { - pub supabase_path: PathBuf, - pub output_dir: PathBuf, - pub timeout: Duration, -} - -#[derive(Debug, Clone)] -pub struct TranscriptsConfig { - pub cache_path: PathBuf, - pub output_dir: PathBuf, -} - -pub fn default_supabase_path() -> PathBuf { - dirs::config_dir() - .map(|config| config.join("Granola/supabase.json")) - .unwrap_or_else(|| PathBuf::from("supabase.json")) -} - -pub async fn export_notes(config: &NotesConfig) -> Result { - let supabase_content = std::fs::read(&config.supabase_path).map_err(Error::SupabaseFileRead)?; - - let client = GranolaClient::new(&supabase_content, config.timeout)?; - let documents = client.get_documents().await?; - - write_notes(&documents, &config.output_dir) -} - -pub fn export_transcripts(config: &TranscriptsConfig) -> Result { - let cache_data = read_cache(&config.cache_path)?; - - write_transcripts( - &cache_data.documents, - &cache_data.transcripts, - &config.output_dir, - ) -} diff --git a/crates/granola/src/markdown/mod.rs b/crates/granola/src/markdown/mod.rs deleted file mode 100644 index 548cb397d7..0000000000 --- a/crates/granola/src/markdown/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod writer; - -pub use writer::*; diff --git a/crates/granola/src/markdown/writer.rs b/crates/granola/src/markdown/writer.rs deleted file mode 100644 index f3c43145ba..0000000000 --- a/crates/granola/src/markdown/writer.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::api::Document; -use crate::error::Result; -use crate::prosemirror::convert_to_markdown; -use serde::Serialize; - -#[derive(Serialize)] -struct Metadata { - id: String, - created: String, - updated: String, - #[serde(skip_serializing_if = "Vec::is_empty")] - tags: Vec, -} - -pub fn document_to_markdown(doc: &Document) -> Result { - let metadata = Metadata { - id: doc.id.clone(), - created: doc.created_at.clone(), - updated: doc.updated_at.clone(), - tags: doc.tags.clone(), - }; - - let yaml = serde_yaml::to_string(&metadata)?; - - let mut output = String::new(); - output.push_str("---\n"); - output.push_str(&yaml); - output.push_str("---\n\n"); - - if !doc.title.is_empty() { - output.push_str(&format!("# {}\n\n", doc.title)); - } - - let content = get_document_content(doc); - if !content.is_empty() { - output.push_str(&content); - if !content.ends_with('\n') { - output.push('\n'); - } - } - - Ok(output) -} - -fn get_document_content(doc: &Document) -> String { - if let Some(ref notes) = doc.notes { - let content = convert_to_markdown(notes).trim().to_string(); - if !content.is_empty() { - return content; - } - } - - if let Some(ref panel) = doc.last_viewed_panel { - if let Some(ref content) = panel.content { - let md = convert_to_markdown(content).trim().to_string(); - if !md.is_empty() { - return md; - } - } - - if !panel.original_content.is_empty() { - return panel.original_content.clone(); - } - } - - doc.content.clone() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_document_to_markdown() { - let doc = Document { - id: "test-123".to_string(), - title: "Test Meeting".to_string(), - content: "Meeting content".to_string(), - created_at: "2024-01-01T00:00:00Z".to_string(), - updated_at: "2024-01-02T00:00:00Z".to_string(), - tags: vec!["work".to_string(), "planning".to_string()], - notes: None, - notes_plain: None, - last_viewed_panel: None, - }; - - let result = document_to_markdown(&doc).unwrap(); - - assert!(result.contains("---")); - assert!(result.contains("id: test-123")); - assert!(result.contains("# Test Meeting")); - assert!(result.contains("Meeting content")); - } -} diff --git a/crates/granola/src/prosemirror/converter.rs b/crates/granola/src/prosemirror/converter.rs deleted file mode 100644 index 80d9844ab9..0000000000 --- a/crates/granola/src/prosemirror/converter.rs +++ /dev/null @@ -1,255 +0,0 @@ -use crate::api::{ProseMirrorDoc, ProseMirrorNode}; -use regex::Regex; -use std::sync::OnceLock; - -static NEWLINE_REGEX: OnceLock = OnceLock::new(); - -fn get_newline_regex() -> &'static Regex { - NEWLINE_REGEX.get_or_init(|| Regex::new(r"\n{3,}").unwrap()) -} - -pub fn convert_to_markdown(doc: &ProseMirrorDoc) -> String { - if doc.doc_type != "doc" || doc.content.is_empty() { - return String::new(); - } - - let mut output = Vec::new(); - for node in &doc.content { - output.push(process_node(node, 0, true)); - } - - let result = output.join(""); - let result = get_newline_regex().replace_all(&result, "\n\n"); - - format!("{}\n", result.trim()) -} - -fn process_node(node: &ProseMirrorNode, indent_level: usize, is_top_level: bool) -> String { - let text_content = if !node.content.is_empty() { - match node.node_type.as_str() { - "bulletList" => node - .content - .iter() - .map(|child| process_node(child, indent_level, false)) - .collect::>() - .join(""), - "listItem" => node - .content - .iter() - .map(|child| { - if child.node_type == "bulletList" { - process_node(child, indent_level + 1, false) - } else { - process_node(child, indent_level, false) - } - }) - .collect::>() - .join(""), - _ => node - .content - .iter() - .map(|child| process_node(child, indent_level, false)) - .collect::>() - .join(""), - } - } else if !node.text.is_empty() { - node.text.clone() - } else { - String::new() - }; - - match node.node_type.as_str() { - "heading" => { - let level = node - .attrs - .as_ref() - .and_then(|attrs: &serde_json::Map| attrs.get("level")) - .and_then(|v: &serde_json::Value| v.as_f64()) - .map(|v: f64| v as usize) - .unwrap_or(1); - - let suffix = if is_top_level { "\n\n" } else { "\n" }; - format!("{} {}{}", "#".repeat(level), text_content.trim(), suffix) - } - "paragraph" => { - let suffix = if is_top_level { "\n\n" } else { "" }; - format!("{}{}", text_content, suffix) - } - "bulletList" => { - let mut items = Vec::new(); - for item_node in &node.content { - if item_node.node_type == "listItem" { - let mut child_contents = Vec::new(); - let mut nested_lists = Vec::new(); - - for child in &item_node.content { - if child.node_type == "bulletList" { - nested_lists.push(format!( - "\n{}", - process_node(child, indent_level + 1, false) - )); - } else { - child_contents.push(process_node(child, indent_level, false)); - } - } - - let first_text = child_contents - .iter() - .find(|c| !c.starts_with('\n')) - .cloned() - .unwrap_or_default(); - - let indent = "\t".repeat(indent_level); - let rest = nested_lists.join(""); - items.push(format!("{}- {}{}", indent, first_text.trim(), rest)); - } - } - - let suffix = if is_top_level { "\n\n" } else { "" }; - format!("{}{}", items.join("\n"), suffix) - } - "text" => node.text.clone(), - _ => text_content, - } -} - -pub fn convert_to_plain_text(doc: &ProseMirrorDoc) -> String { - if doc.doc_type != "doc" || doc.content.is_empty() { - return String::new(); - } - - let mut output = Vec::new(); - for node in &doc.content { - let text = extract_text(node); - if !text.is_empty() { - output.push(text); - } - } - - output.join("\n\n").trim().to_string() -} - -fn extract_text(node: &ProseMirrorNode) -> String { - if !node.text.is_empty() { - return node.text.clone(); - } - - if node.content.is_empty() { - return String::new(); - } - - let texts: Vec = node - .content - .iter() - .map(extract_text) - .filter(|t: &String| !t.is_empty()) - .collect(); - - let separator = match node.node_type.as_str() { - "paragraph" | "heading" | "listItem" => "\n", - _ => " ", - }; - - texts.join(separator) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::api::{ProseMirrorDoc, ProseMirrorNode}; - use serde_json::json; - - #[test] - fn test_convert_heading_and_paragraph() { - let doc = ProseMirrorDoc { - doc_type: "doc".to_string(), - content: vec![ - ProseMirrorNode { - node_type: "heading".to_string(), - content: vec![ProseMirrorNode { - node_type: "text".to_string(), - text: "Meeting Notes".to_string(), - content: vec![], - attrs: None, - }], - text: String::new(), - attrs: Some(serde_json::from_value(json!({"level": 1})).unwrap()), - }, - ProseMirrorNode { - node_type: "paragraph".to_string(), - content: vec![ProseMirrorNode { - node_type: "text".to_string(), - text: "This is a paragraph.".to_string(), - content: vec![], - attrs: None, - }], - text: String::new(), - attrs: None, - }, - ], - }; - - let result = convert_to_markdown(&doc); - assert!(result.contains("# Meeting Notes")); - assert!(result.contains("This is a paragraph.")); - } - - #[test] - fn test_convert_bullet_list() { - let doc = ProseMirrorDoc { - doc_type: "doc".to_string(), - content: vec![ProseMirrorNode { - node_type: "bulletList".to_string(), - content: vec![ - ProseMirrorNode { - node_type: "listItem".to_string(), - content: vec![ProseMirrorNode { - node_type: "paragraph".to_string(), - content: vec![ProseMirrorNode { - node_type: "text".to_string(), - text: "First item".to_string(), - content: vec![], - attrs: None, - }], - text: String::new(), - attrs: None, - }], - text: String::new(), - attrs: None, - }, - ProseMirrorNode { - node_type: "listItem".to_string(), - content: vec![ProseMirrorNode { - node_type: "paragraph".to_string(), - content: vec![ProseMirrorNode { - node_type: "text".to_string(), - text: "Second item".to_string(), - content: vec![], - attrs: None, - }], - text: String::new(), - attrs: None, - }], - text: String::new(), - attrs: None, - }, - ], - text: String::new(), - attrs: None, - }], - }; - - let result = convert_to_markdown(&doc); - assert!(result.contains("- First item")); - assert!(result.contains("- Second item")); - } - - #[test] - fn test_empty_doc() { - let doc = ProseMirrorDoc { - doc_type: "doc".to_string(), - content: vec![], - }; - assert_eq!(convert_to_markdown(&doc), ""); - } -} diff --git a/crates/granola/src/prosemirror/mod.rs b/crates/granola/src/prosemirror/mod.rs deleted file mode 100644 index 11bc94497f..0000000000 --- a/crates/granola/src/prosemirror/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod converter; - -pub use converter::*; diff --git a/crates/granola/src/transcript/formatter.rs b/crates/granola/src/transcript/formatter.rs deleted file mode 100644 index db5074c914..0000000000 --- a/crates/granola/src/transcript/formatter.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::cache::{CacheDocument, TranscriptSegment}; -use chrono::DateTime; - -pub fn format_transcript(doc: &CacheDocument, segments: &[TranscriptSegment]) -> String { - if segments.is_empty() { - return String::new(); - } - - let mut output = String::new(); - - output.push_str(&"=".repeat(80)); - output.push('\n'); - - if !doc.title.is_empty() { - output.push_str(&doc.title); - output.push('\n'); - } - - output.push_str(&format!("ID: {}\n", doc.id)); - - if !doc.created_at.is_empty() { - output.push_str(&format!("Created: {}\n", doc.created_at)); - } - - if !doc.updated_at.is_empty() { - output.push_str(&format!("Updated: {}\n", doc.updated_at)); - } - - output.push_str(&format!("Segments: {}\n", segments.len())); - - output.push_str(&"=".repeat(80)); - output.push_str("\n\n"); - - for segment in segments { - let time = parse_timestamp(&segment.start_timestamp); - let speaker = match segment.source.as_str() { - "microphone" => "You", - _ => "System", - }; - - output.push_str(&format!("[{}] {}: {}\n", time, speaker, segment.text)); - } - - output -} - -fn parse_timestamp(timestamp: &str) -> String { - DateTime::parse_from_rfc3339(timestamp) - .map(|dt| dt.format("%H:%M:%S").to_string()) - .unwrap_or_else(|_| timestamp.to_string()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_format_transcript() { - let doc = CacheDocument { - id: "doc-1".to_string(), - title: "Test Meeting".to_string(), - created_at: "2024-01-01T14:00:00Z".to_string(), - updated_at: "2024-01-01T15:00:00Z".to_string(), - }; - - let segments = vec![ - TranscriptSegment { - id: "seg-1".to_string(), - document_id: "doc-1".to_string(), - start_timestamp: "2024-01-01T14:00:04Z".to_string(), - end_timestamp: "2024-01-01T14:00:06Z".to_string(), - text: "Hello everyone".to_string(), - source: "system".to_string(), - is_final: true, - }, - TranscriptSegment { - id: "seg-2".to_string(), - document_id: "doc-1".to_string(), - start_timestamp: "2024-01-01T14:00:06Z".to_string(), - end_timestamp: "2024-01-01T14:00:08Z".to_string(), - text: "Hi there".to_string(), - source: "microphone".to_string(), - is_final: true, - }, - ]; - - let result = format_transcript(&doc, &segments); - - assert!(result.contains("Test Meeting")); - assert!(result.contains("ID: doc-1")); - assert!(result.contains("Segments: 2")); - assert!(result.contains("[14:00:04] System: Hello everyone")); - assert!(result.contains("[14:00:06] You: Hi there")); - } - - #[test] - fn test_parse_timestamp() { - assert_eq!(parse_timestamp("2024-01-01T14:30:45Z"), "14:30:45"); - assert_eq!(parse_timestamp("invalid"), "invalid"); - } -} diff --git a/crates/granola/src/transcript/mod.rs b/crates/granola/src/transcript/mod.rs deleted file mode 100644 index 6fe7d98e0c..0000000000 --- a/crates/granola/src/transcript/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod formatter; - -pub use formatter::*; diff --git a/crates/intercept/src/lib.rs b/crates/intercept/src/lib.rs index 5411618467..3c412b1da0 100644 --- a/crates/intercept/src/lib.rs +++ b/crates/intercept/src/lib.rs @@ -1,43 +1,38 @@ -use std::{collections::HashMap, sync::LazyLock, sync::Mutex}; +use std::sync::Mutex; #[cfg(target_os = "macos")] use swift_rs::swift; -static QUIT_HANDLERS: LazyLock bool + Send + Sync>>>> = - LazyLock::new(|| Mutex::new(HashMap::new())); +static QUIT_CALLBACK: Mutex bool + Send + Sync>>> = Mutex::new(None); #[cfg(target_os = "macos")] swift!(fn _setup_quit_handler()); -#[cfg(target_os = "macos")] -static SWIFT_HANDLER_INITIALIZED: std::sync::atomic::AtomicBool = - std::sync::atomic::AtomicBool::new(false); - -pub fn register_quit_handler(id: &'static str, callback: F) +pub fn setup_quit_handler(_callback: F) where F: Fn() -> bool + Send + Sync + 'static, { - QUIT_HANDLERS.lock().unwrap().insert(id, Box::new(callback)); - #[cfg(target_os = "macos")] { - if !SWIFT_HANDLER_INITIALIZED.swap(true, std::sync::atomic::Ordering::SeqCst) { - unsafe { - _setup_quit_handler(); - } + *QUIT_CALLBACK.lock().unwrap() = Some(Box::new(_callback)); + unsafe { + _setup_quit_handler(); } } } -pub fn unregister_quit_handler(id: &'static str) { - QUIT_HANDLERS.lock().unwrap().remove(id); +pub fn reset_quit_handler() { + #[cfg(target_os = "macos")] + { + *QUIT_CALLBACK.lock().unwrap() = None; + } } #[unsafe(no_mangle)] #[cfg(target_os = "macos")] pub extern "C" fn rust_should_quit() -> bool { - QUIT_HANDLERS + QUIT_CALLBACK .lock() .unwrap() - .values() - .all(|callback| callback()) + .as_ref() + .map_or(true, |callback| callback()) } diff --git a/crates/llama/src/lib.rs b/crates/llama/src/lib.rs index 5f5c6c75e4..861e83dc10 100644 --- a/crates/llama/src/lib.rs +++ b/crates/llama/src/lib.rs @@ -187,14 +187,14 @@ impl Llama { let rounded_progress_int = (progress * 100.0).round() as i32; let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - if let Ok(mut last_reported) = progress_data.last_reported.lock() - && *last_reported != rounded_progress_int - { - *last_reported = rounded_progress_int; - let rounded_progress = rounded_progress_int as f64 / 100.0; - - if let Ok(mut cb) = progress_data.callback.lock() { - (cb)(rounded_progress); + if let Ok(mut last_reported) = progress_data.last_reported.lock() { + if *last_reported != rounded_progress_int { + *last_reported = rounded_progress_int; + let rounded_progress = rounded_progress_int as f64 / 100.0; + + if let Ok(mut cb) = progress_data.callback.lock() { + (cb)(rounded_progress); + } } } })); diff --git a/crates/llama/src/parser.rs b/crates/llama/src/parser.rs index 10b74c6bfb..043ed5ace8 100644 --- a/crates/llama/src/parser.rs +++ b/crates/llama/src/parser.rs @@ -65,12 +65,12 @@ impl StreamingParser { return Some(Response::ToolCall { name, arguments }); } - if let Some(pos) = self.find_next_block_start() - && pos > 0 - { - let text = self.buffer[..pos].to_string(); - self.buffer = self.buffer[pos..].to_string(); - return Some(Response::TextDelta(text)); + if let Some(pos) = self.find_next_block_start() { + if pos > 0 { + let text = self.buffer[..pos].to_string(); + self.buffer = self.buffer[pos..].to_string(); + return Some(Response::TextDelta(text)); + } } None diff --git a/crates/notification-gpui/Cargo.toml b/crates/notification-gpui/Cargo.toml deleted file mode 100644 index d346800d0d..0000000000 --- a/crates/notification-gpui/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "notification-gpui" -version = "0.1.0" -edition = "2024" - -[features] -default = [] - -[[example]] -name = "test_notification" - -[dependencies] -gpui = { version = "0.2", features = ["runtime_shaders"] } -gpui_squircle = "1" -hypr-notification-interface = { workspace = true } - -[dev-dependencies] diff --git a/crates/notification-gpui/examples/test_notification.rs b/crates/notification-gpui/examples/test_notification.rs deleted file mode 100644 index 66f68f8a63..0000000000 --- a/crates/notification-gpui/examples/test_notification.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::time::Duration; - -use gpui::{App, Application}; -use notification_gpui::Notification; - -fn main() { - Application::new().run(|cx: &mut App| { - let notification = Notification::builder() - .title("Test Notification") - .message("This notification will auto-dismiss in 5 seconds") - .timeout(Duration::from_secs(5)) - .build(); - - notification_gpui::show(¬ification, cx); - }); -} diff --git a/crates/notification-gpui/src/constants.rs b/crates/notification-gpui/src/constants.rs deleted file mode 100644 index 5632179711..0000000000 --- a/crates/notification-gpui/src/constants.rs +++ /dev/null @@ -1,7 +0,0 @@ -use gpui::{Pixels, px}; - -pub(crate) const NOTIFICATION_WIDTH: Pixels = px(344.); -pub(crate) const NOTIFICATION_HEIGHT: Pixels = px(64.); -pub(crate) const NOTIFICATION_MARGIN_RIGHT: Pixels = px(15.); -pub(crate) const NOTIFICATION_MARGIN_TOP: Pixels = px(15.); -pub(crate) const NOTIFICATION_CORNER_RADIUS: Pixels = px(11.); diff --git a/crates/notification-gpui/src/lib.rs b/crates/notification-gpui/src/lib.rs deleted file mode 100644 index c1f7178ebb..0000000000 --- a/crates/notification-gpui/src/lib.rs +++ /dev/null @@ -1,118 +0,0 @@ -mod constants; -mod theme; -mod toast; - -use std::sync::Mutex; - -use gpui::{App, AppContext, Entity, WindowHandle, WindowId}; - -pub use gpui::PlatformDisplay; -pub use hypr_notification_interface::*; - -pub use theme::NotificationTheme; -pub use toast::StatusToast; - -static ACTIVE_WINDOWS: Mutex>> = Mutex::new(Vec::new()); -static CONFIRM_CB: Mutex>> = Mutex::new(None); -static ACCEPT_CB: Mutex>> = Mutex::new(None); -static DISMISS_CB: Mutex>> = Mutex::new(None); -static TIMEOUT_CB: Mutex>> = Mutex::new(None); - -pub fn setup_notification_dismiss_handler(f: F) -where - F: Fn(String) + Send + Sync + 'static, -{ - *DISMISS_CB.lock().unwrap() = Some(Box::new(f)); -} - -pub fn setup_notification_confirm_handler(f: F) -where - F: Fn(String) + Send + Sync + 'static, -{ - *CONFIRM_CB.lock().unwrap() = Some(Box::new(f)); -} - -pub fn setup_notification_accept_handler(f: F) -where - F: Fn(String) + Send + Sync + 'static, -{ - *ACCEPT_CB.lock().unwrap() = Some(Box::new(f)); -} - -pub fn setup_notification_timeout_handler(f: F) -where - F: Fn(String) + Send + Sync + 'static, -{ - *TIMEOUT_CB.lock().unwrap() = Some(Box::new(f)); -} - -fn close_window(cx: &mut App, window_id: WindowId) { - let mut windows = ACTIVE_WINDOWS.lock().unwrap(); - windows.retain(|w| { - if w.window_id() == window_id { - w.update(cx, |_, window, _cx| { - window.remove_window(); - }) - .ok(); - false - } else { - true - } - }); -} - -pub fn show(notification: &Notification, cx: &mut App) { - let screen = match cx.primary_display() { - Some(screen) => screen, - None => return, - }; - - let toast_entity: Entity = - cx.new(|_cx| StatusToast::new(¬ification.title, ¬ification.message)); - - if let Ok(window) = cx.open_window(StatusToast::window_options(screen, cx), |_window, _cx| { - toast_entity.clone() - }) { - let window_id = window.window_id(); - - cx.subscribe( - &toast_entity, - move |_, event: &NotificationEvent, cx| match event { - NotificationEvent::Accept - | NotificationEvent::Dismiss - | NotificationEvent::Confirm - | NotificationEvent::Timeout => { - close_window(cx, window_id); - } - }, - ) - .detach(); - - ACTIVE_WINDOWS.lock().unwrap().push(window); - - if let Some(timeout) = notification.timeout { - let toast_entity = toast_entity.clone(); - cx.spawn(async move |cx| { - cx.background_executor().timer(timeout).await; - cx.update(|cx| { - toast_entity.update(cx, |_, cx| { - cx.emit(NotificationEvent::Timeout); - }); - }) - .ok(); - }) - .detach(); - } - } -} - -pub fn dismiss_all(cx: &mut App) { - let windows: Vec<_> = ACTIVE_WINDOWS.lock().unwrap().drain(..).collect(); - for window in windows { - window - .update(cx, |_, window, _cx| { - window.remove_window(); - }) - .ok(); - } -} diff --git a/crates/notification-gpui/src/theme.rs b/crates/notification-gpui/src/theme.rs deleted file mode 100644 index e8a8aadf7e..0000000000 --- a/crates/notification-gpui/src/theme.rs +++ /dev/null @@ -1,64 +0,0 @@ -use gpui::{Hsla, hsla}; - -#[derive(Clone, Copy, Default, PartialEq)] -pub enum NotificationTheme { - #[default] - System, - Light, - Dark, -} - -pub struct NotificationColors { - pub bg: Hsla, - pub text_primary: Hsla, - pub text_secondary: Hsla, - pub border_color: Hsla, - pub close_button_bg: Hsla, - pub close_button_bg_hover: Hsla, - pub action_button_bg: Hsla, - pub action_button_bg_hover: Hsla, - pub action_button_border: Hsla, - pub action_button_text: Hsla, -} - -impl NotificationTheme { - pub fn is_light(&self) -> bool { - match self { - NotificationTheme::System | NotificationTheme::Light => true, - NotificationTheme::Dark => false, - } - } - - pub fn colors(&self) -> NotificationColors { - let is_light = self.is_light(); - - let (bg, text_primary, text_secondary, border_color) = if is_light { - ( - hsla(0., 0., 0.85, 0.95), - hsla(0., 0., 0., 1.), - hsla(0., 0., 0., 0.55), - hsla(0., 0., 1., 0.10), - ) - } else { - ( - hsla(0., 0., 0.24, 0.95), - hsla(0., 0., 1., 1.), - hsla(0., 0., 1., 0.6), - hsla(0., 0., 1., 0.10), - ) - }; - - NotificationColors { - bg, - text_primary, - text_secondary, - border_color, - close_button_bg: hsla(0., 0., 0., 0.5), - close_button_bg_hover: hsla(0., 0., 0., 0.6), - action_button_bg: hsla(0., 0., 0.95, 0.9), - action_button_bg_hover: hsla(0., 0., 0.90, 0.9), - action_button_border: hsla(0., 0., 0.7, 0.5), - action_button_text: hsla(0., 0., 0.1, 1.), - } - } -} diff --git a/crates/notification-gpui/src/toast.rs b/crates/notification-gpui/src/toast.rs deleted file mode 100644 index bc21186798..0000000000 --- a/crates/notification-gpui/src/toast.rs +++ /dev/null @@ -1,237 +0,0 @@ -use std::rc::Rc; - -use gpui::{prelude::*, *}; -use gpui_squircle::{SquircleStyled, squircle}; -use hypr_notification_interface::NotificationEvent; - -use crate::constants::{ - NOTIFICATION_CORNER_RADIUS, NOTIFICATION_HEIGHT, NOTIFICATION_MARGIN_RIGHT, - NOTIFICATION_MARGIN_TOP, NOTIFICATION_WIDTH, -}; -use crate::theme::NotificationTheme; - -pub struct StatusToast { - title: SharedString, - message: SharedString, - action_label: Option, - theme: NotificationTheme, -} - -impl StatusToast { - pub fn new(title: impl Into, message: impl Into) -> Self { - Self { - title: title.into(), - message: message.into(), - action_label: None, - theme: NotificationTheme::default(), - } - } - - pub fn theme(mut self, theme: NotificationTheme) -> Self { - self.theme = theme; - self - } - - pub fn action_label(mut self, label: impl Into) -> Self { - self.action_label = Some(label.into()); - self - } - - fn native_shadow() -> Vec { - vec![BoxShadow { - color: hsla(0., 0., 0., 0.22), - offset: point(px(0.), px(2.)), - blur_radius: px(12.), - spread_radius: px(0.), - }] - } - - pub fn window_options(screen: Rc, _cx: &App) -> WindowOptions { - let size = Size { - width: NOTIFICATION_WIDTH, - height: NOTIFICATION_HEIGHT, - }; - - let screen_bounds = screen.bounds(); - let bounds = Bounds { - origin: point( - screen_bounds.origin.x + screen_bounds.size.width - - size.width - - NOTIFICATION_MARGIN_RIGHT, - screen_bounds.origin.y + NOTIFICATION_MARGIN_TOP, - ), - size, - }; - - WindowOptions { - window_bounds: Some(WindowBounds::Windowed(bounds)), - titlebar: None, - focus: false, - show: true, - kind: WindowKind::PopUp, - is_movable: false, - display_id: Some(screen.id()), - window_background: WindowBackgroundAppearance::Transparent, - window_decorations: Some(WindowDecorations::Client), - ..Default::default() - } - } -} - -impl EventEmitter for StatusToast {} - -impl Render for StatusToast { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let colors = self.theme.colors(); - let has_action = self.action_label.is_some(); - - div() - .id("notification-container") - .group("toast") - .size_full() - .shadow(Self::native_shadow()) - .overflow_hidden() - .relative() - .child( - squircle() - .absolute() - .inset_0() - .rounded(NOTIFICATION_CORNER_RADIUS) - .bg(colors.bg) - .border(px(0.5)) - .border_color(colors.border_color), - ) - .child( - div() - .id("close-button") - .absolute() - .top(px(5.)) - .left(px(4.)) - .size(px(15.)) - .rounded_full() - .bg(colors.close_button_bg) - .hover(|s| s.bg(colors.close_button_bg_hover)) - .cursor_pointer() - .flex() - .items_center() - .justify_center() - .opacity(0.0) - .group_hover("toast", |s| s.opacity(1.0)) - .child( - div() - .text_size(px(10.)) - .font_weight(FontWeight::SEMIBOLD) - .text_color(white()) - .child("×"), - ) - .on_click(cx.listener(|_, _, _, cx| { - cx.emit(NotificationEvent::Dismiss); - })), - ) - .child( - div() - .size_full() - .px(px(12.)) - .py(px(9.)) - .flex() - .flex_row() - .items_center() - .gap(px(8.)) - .child(self.render_icon()) - .child(self.render_text_content(colors.text_primary, colors.text_secondary)) - .when_some(self.action_label.clone(), |el, label| { - el.child(self.render_action_button( - label, - colors.action_button_bg, - colors.action_button_bg_hover, - colors.action_button_border, - colors.action_button_text, - cx, - )) - }) - .when(!has_action, |el| el.pr(px(35.))), - ) - } -} - -impl StatusToast { - fn render_icon(&self) -> impl IntoElement { - div() - .size(px(32.)) - .flex_shrink_0() - .flex() - .items_center() - .justify_center() - .child( - div() - .size(px(24.)) - .rounded(px(6.)) - .bg(rgb(0x5AC8FA)) - .flex() - .items_center() - .justify_center() - .child( - svg() - .path("icons/folder.svg") - .size(px(16.)) - .text_color(white()), - ), - ) - } - - fn render_text_content(&self, text_primary: Hsla, text_secondary: Hsla) -> impl IntoElement { - div() - .flex() - .flex_col() - .flex_1() - .min_w_0() - .gap(px(2.)) - .child( - div() - .text_size(px(14.)) - .font_weight(FontWeight::SEMIBOLD) - .text_color(text_primary) - .truncate() - .child(self.title.clone()), - ) - .child( - div() - .text_size(px(11.)) - .text_color(text_secondary) - .truncate() - .child(self.message.clone()), - ) - } - - fn render_action_button( - &self, - label: SharedString, - bg: Hsla, - bg_hover: Hsla, - border: Hsla, - text: Hsla, - cx: &mut Context, - ) -> impl IntoElement { - div() - .id("action-button") - .flex_shrink_0() - .px(px(11.)) - .py(px(6.)) - .bg(bg) - .hover(|s| s.bg(bg_hover)) - .rounded(px(10.)) - .border_1() - .border_color(border) - .cursor_pointer() - .child( - div() - .text_size(px(14.)) - .font_weight(FontWeight::SEMIBOLD) - .text_color(text) - .child(label), - ) - .on_click(cx.listener(|_, _, _, cx| { - cx.emit(NotificationEvent::Accept); - })) - } -} diff --git a/crates/notification-interface/src/lib.rs b/crates/notification-interface/src/lib.rs index a6b92dad43..a2f4cbdb57 100644 --- a/crates/notification-interface/src/lib.rs +++ b/crates/notification-interface/src/lib.rs @@ -1,16 +1,9 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum NotificationEvent { - Confirm, - Accept, - Dismiss, - Timeout, -} - #[derive(Debug, serde::Serialize, serde::Deserialize, specta::Type)] pub struct Notification { pub key: Option, pub title: String, pub message: String, + pub url: Option, pub timeout: Option, } @@ -25,6 +18,7 @@ pub struct NotificationBuilder { key: Option, title: Option, message: Option, + url: Option, timeout: Option, } @@ -44,6 +38,11 @@ impl NotificationBuilder { self } + pub fn url(mut self, url: impl Into) -> Self { + self.url = Some(url.into()); + self + } + pub fn timeout(mut self, timeout: std::time::Duration) -> Self { self.timeout = Some(timeout); self @@ -53,12 +52,14 @@ impl NotificationBuilder { let key = self.key.clone(); let title = self.title.unwrap(); let message = self.message.unwrap(); + let url = self.url.clone(); let timeout = self.timeout; Notification { key, title, message, + url, timeout, } } diff --git a/crates/notification-linux/src/impl.rs b/crates/notification-linux/src/impl.rs index 11738edab1..c06c042866 100644 --- a/crates/notification-linux/src/impl.rs +++ b/crates/notification-linux/src/impl.rs @@ -14,9 +14,7 @@ thread_local! { } static CONFIRM_CB: Mutex>> = Mutex::new(None); -static ACCEPT_CB: Mutex>> = Mutex::new(None); static DISMISS_CB: Mutex>> = Mutex::new(None); -static TIMEOUT_CB: Mutex>> = Mutex::new(None); pub fn setup_notification_dismiss_handler(f: F) where @@ -32,44 +30,18 @@ where *CONFIRM_CB.lock().unwrap() = Some(Box::new(f)); } -pub fn setup_notification_accept_handler(f: F) -where - F: Fn(String) + Send + Sync + 'static, -{ - *ACCEPT_CB.lock().unwrap() = Some(Box::new(f)); -} - -pub fn setup_notification_timeout_handler(f: F) -where - F: Fn(String) + Send + Sync + 'static, -{ - *TIMEOUT_CB.lock().unwrap() = Some(Box::new(f)); -} - fn call_confirm_handler(id: String) { if let Some(cb) = CONFIRM_CB.lock().unwrap().as_ref() { cb(id); } } -fn call_accept_handler(id: String) { - if let Some(cb) = ACCEPT_CB.lock().unwrap().as_ref() { - cb(id); - } -} - fn call_dismiss_handler(id: String) { if let Some(cb) = DISMISS_CB.lock().unwrap().as_ref() { cb(id); } } -fn call_timeout_handler(id: String) { - if let Some(cb) = TIMEOUT_CB.lock().unwrap().as_ref() { - cb(id); - } -} - struct NotificationInstance { id: String, window: Window, @@ -93,7 +65,6 @@ impl NotificationInstance { let id = self.id.clone(); let window = self.window.clone(); let source = glib::timeout_add_seconds_local_once(timeout_seconds as u32, move || { - call_timeout_handler(id.clone()); Self::dismiss_window(&window, &id, false); }); self.timeout_source = Some(source); @@ -142,7 +113,7 @@ impl NotificationManager { } } - fn show(&mut self, title: String, message: String, timeout_seconds: f64) { + fn show(&mut self, title: String, message: String, url: Option, timeout_seconds: f64) { if !self.ensure_gtk() { return; } @@ -167,7 +138,7 @@ impl NotificationManager { window.set_keep_above(true); self.setup_window_style(&window); - self.create_notification_content(&window, &title, &message, &id); + self.create_notification_content(&window, &title, &message, url.as_deref(), &id); self.position_window(&window); window.show_all(); @@ -236,7 +207,14 @@ impl NotificationManager { } } - fn create_notification_content(&self, window: &Window, title: &str, message: &str, id: &str) { + fn create_notification_content( + &self, + window: &Window, + title: &str, + message: &str, + url: Option<&str>, + id: &str, + ) { let main_box = GtkBox::new(Orientation::Horizontal, 8); main_box.set_margin_start(12); main_box.set_margin_end(12); @@ -266,6 +244,24 @@ impl NotificationManager { main_box.pack_start(&text_box, true, true, 0); + if let Some(url_str) = url { + if !url_str.is_empty() { + let action_button = Button::with_label("Take Notes"); + action_button.style_context().add_class("action-button"); + + let id_clone = id.to_string(); + let url_clone = url_str.to_string(); + let window_clone = window.clone(); + action_button.connect_clicked(move |_| { + call_confirm_handler(id_clone.clone()); + let _ = open::that(&url_clone); + NotificationInstance::dismiss_window(&window_clone, &id_clone, false); + }); + + main_box.pack_start(&action_button, false, false, 0); + } + } + let close_button = Button::new(); close_button.set_label("×"); close_button.style_context().add_class("close-button"); @@ -290,13 +286,13 @@ impl NotificationManager { // Use the default width we set (360) since window.size() returns 0 before realization const DEFAULT_WINDOW_WIDTH: i32 = 360; - if let Some(screen) = gdk::Screen::default() - && let Some(root_window) = screen.root_window() - { - let screen_width = root_window.width(); - let x = screen_width - DEFAULT_WINDOW_WIDTH - 20; - let y = 50; - window.move_(x, y); + if let Some(screen) = gdk::Screen::default() { + if let Some(root_window) = screen.root_window() { + let screen_width = root_window.width(); + let x = screen_width - DEFAULT_WINDOW_WIDTH - 20; + let y = 50; + window.move_(x, y); + } } } @@ -330,11 +326,14 @@ impl NotificationManager { pub fn show(notification: &hypr_notification_interface::Notification) { let title = notification.title.clone(); let message = notification.message.clone(); + let url = notification.url.clone(); let timeout_seconds = notification.timeout.map(|d| d.as_secs_f64()).unwrap_or(5.0); glib::MainContext::default().invoke(move || { NOTIFICATION_MANAGER.with(|manager| { - manager.borrow_mut().show(title, message, timeout_seconds); + manager + .borrow_mut() + .show(title, message, url, timeout_seconds); }); }); } @@ -356,6 +355,7 @@ mod tests { let notification = hypr_notification_interface::Notification::builder() .title("Test Title") .message("Test message content") + .url("https://example.com") .timeout(std::time::Duration::from_secs(3)) .build(); diff --git a/crates/notification-linux/src/lib.rs b/crates/notification-linux/src/lib.rs index 3c3532684a..57c41d948f 100644 --- a/crates/notification-linux/src/lib.rs +++ b/crates/notification-linux/src/lib.rs @@ -5,6 +5,5 @@ mod r#impl; #[cfg(target_os = "linux")] pub use r#impl::{ - dismiss_all, setup_notification_accept_handler, setup_notification_confirm_handler, - setup_notification_dismiss_handler, setup_notification_timeout_handler, show, + dismiss_all, setup_notification_confirm_handler, setup_notification_dismiss_handler, show, }; diff --git a/crates/notification-macos/src/lib.rs b/crates/notification-macos/src/lib.rs index 3dcf3d51bd..b2438c7fb1 100644 --- a/crates/notification-macos/src/lib.rs +++ b/crates/notification-macos/src/lib.rs @@ -9,15 +9,14 @@ pub use hypr_notification_interface::*; swift!(fn _show_notification( title: &SRString, message: &SRString, + url: &SRString, timeout_seconds: f64 ) -> Bool); swift!(fn _dismiss_all_notifications() -> Bool); static CONFIRM_CB: Mutex>> = Mutex::new(None); -static ACCEPT_CB: Mutex>> = Mutex::new(None); static DISMISS_CB: Mutex>> = Mutex::new(None); -static TIMEOUT_CB: Mutex>> = Mutex::new(None); pub fn setup_notification_dismiss_handler(f: F) where @@ -33,20 +32,6 @@ where *CONFIRM_CB.lock().unwrap() = Some(Box::new(f)); } -pub fn setup_notification_accept_handler(f: F) -where - F: Fn(String) + Send + Sync + 'static, -{ - *ACCEPT_CB.lock().unwrap() = Some(Box::new(f)); -} - -pub fn setup_notification_timeout_handler(f: F) -where - F: Fn(String) + Send + Sync + 'static, -{ - *TIMEOUT_CB.lock().unwrap() = Some(Box::new(f)); -} - #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_on_notification_confirm(id_ptr: *const c_char) { if let Some(cb) = CONFIRM_CB.lock().unwrap().as_ref() { @@ -58,17 +43,6 @@ pub unsafe extern "C" fn rust_on_notification_confirm(id_ptr: *const c_char) { } } -#[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_on_notification_accept(id_ptr: *const c_char) { - if let Some(cb) = ACCEPT_CB.lock().unwrap().as_ref() { - let id = unsafe { CStr::from_ptr(id_ptr) } - .to_str() - .unwrap() - .to_string(); - cb(id); - } -} - #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_on_notification_dismiss(id_ptr: *const c_char) { if let Some(cb) = DISMISS_CB.lock().unwrap().as_ref() { @@ -80,24 +54,18 @@ pub unsafe extern "C" fn rust_on_notification_dismiss(id_ptr: *const c_char) { } } -#[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_on_notification_timeout(id_ptr: *const c_char) { - if let Some(cb) = TIMEOUT_CB.lock().unwrap().as_ref() { - let id = unsafe { CStr::from_ptr(id_ptr) } - .to_str() - .unwrap() - .to_string(); - cb(id); - } -} - pub fn show(notification: &hypr_notification_interface::Notification) { unsafe { let title = SRString::from(notification.title.as_str()); let message = SRString::from(notification.message.as_str()); + let url = notification + .url + .as_ref() + .map(|u| SRString::from(u.as_str())) + .unwrap_or_else(|| SRString::from("")); let timeout_seconds = notification.timeout.map(|d| d.as_secs_f64()).unwrap_or(5.0); - _show_notification(&title, &message, timeout_seconds); + _show_notification(&title, &message, &url, timeout_seconds); } } @@ -116,6 +84,7 @@ mod tests { let notification = hypr_notification_interface::Notification::builder() .title("Test Title") .message("Test message content") + .url("https://example.com") .timeout(std::time::Duration::from_secs(3)) .build(); diff --git a/crates/notification-macos/swift-lib/src/lib.swift b/crates/notification-macos/swift-lib/src/lib.swift index ab641903df..af048831bf 100644 --- a/crates/notification-macos/swift-lib/src/lib.swift +++ b/crates/notification-macos/swift-lib/src/lib.swift @@ -6,17 +6,19 @@ class NotificationInstance { let id = UUID() let panel: NSPanel let clickableView: ClickableView + let url: String? private var dismissTimer: DispatchWorkItem? - init(panel: NSPanel, clickableView: ClickableView) { + init(panel: NSPanel, clickableView: ClickableView, url: String?) { self.panel = panel self.clickableView = clickableView + self.url = url } func startDismissTimer(timeoutSeconds: Double) { dismissTimer?.cancel() let timer = DispatchWorkItem { [weak self] in - self?.dismissWithTimeout() + self?.dismiss() } dismissTimer = timer DispatchQueue.main.asyncAfter(deadline: .now() + timeoutSeconds, execute: timer) @@ -43,13 +45,6 @@ class NotificationInstance { dismiss() } - func dismissWithTimeout() { - self.id.uuidString.withCString { idPtr in - rustOnNotificationTimeout(idPtr) - } - dismiss() - } - deinit { dismissTimer?.cancel() } @@ -99,6 +94,11 @@ class ClickableView: NSView { let inside = bounds.contains(local) if inside != isHovering { isHovering = inside + if inside && notification?.url != nil { + NSCursor.pointingHand.set() + } else { + NSCursor.arrow.set() + } onHover?(inside) } } @@ -106,6 +106,7 @@ class ClickableView: NSView { override func mouseEntered(with event: NSEvent) { super.mouseEntered(with: event) isHovering = true + if notification?.url != nil { NSCursor.pointingHand.set() } onHover?(true) } @@ -122,6 +123,11 @@ class ClickableView: NSView { let isInside = bounds.contains(location) if isInside != isHovering { isHovering = isInside + if isInside && notification?.url != nil { + NSCursor.pointingHand.set() + } else { + NSCursor.arrow.set() + } onHover?(isInside) } } @@ -133,6 +139,9 @@ class ClickableView: NSView { notification.id.uuidString.withCString { idPtr in rustOnNotificationConfirm(idPtr) } + if let urlString = notification.url, let url = URL(string: urlString) { + NSWorkspace.shared.open(url) + } notification.dismissWithUserAction() } } @@ -264,19 +273,6 @@ class ActionButton: NSButton { s.height = max(28, s.height + 4) return s } - - override func mouseDown(with event: NSEvent) { - layer?.backgroundColor = NSColor(calibratedWhite: 0.85, alpha: 0.9).cgColor - DispatchQueue.main.asyncAfter(deadline: .now() + 0.08) { - self.layer?.backgroundColor = NSColor(calibratedWhite: 0.95, alpha: 0.9).cgColor - } - if let notification = notification { - notification.id.uuidString.withCString { idPtr in - rustOnNotificationAccept(idPtr) - } - notification.dismiss() - } - } } class NotificationManager { @@ -348,13 +344,14 @@ class NotificationManager { return NSScreen.main ?? NSScreen.screens.first } - func show(title: String, message: String, timeoutSeconds: Double) { + func show(title: String, message: String, url: String?, timeoutSeconds: Double) { DispatchQueue.main.async { [weak self] in guard let self else { return } self.setupApplicationIfNeeded() self.createAndShowNotification( title: title, message: message, + url: url, timeoutSeconds: timeoutSeconds ) } @@ -397,7 +394,7 @@ class NotificationManager { } private func createAndShowNotification( - title: String, message: String, timeoutSeconds: Double + title: String, message: String, url: String?, timeoutSeconds: Double ) { guard let screen = getTargetScreen() else { return } @@ -409,11 +406,11 @@ class NotificationManager { let container = createContainer(clickableView: clickableView) let effectView = createEffectView(container: container) - let notification = NotificationInstance(panel: panel, clickableView: clickableView) + let notification = NotificationInstance(panel: panel, clickableView: clickableView, url: url) clickableView.notification = notification setupContent( - effectView: effectView, title: title, message: message, notification: notification) + effectView: effectView, title: title, message: message, url: url, notification: notification) clickableView.addSubview(container) panel.contentView = clickableView @@ -533,20 +530,26 @@ class NotificationManager { effectView: NSVisualEffectView, title: String, message: String, + url: String?, notification: NotificationInstance ) { + let hasUrl = (url != nil && !url!.isEmpty) + let contentView = createNotificationView( title: title, body: message, + buttonTitle: hasUrl ? "Take Notes" : nil, notification: notification ) contentView.translatesAutoresizingMaskIntoConstraints = false effectView.addSubview(contentView) + let trailingConstant: CGFloat = hasUrl ? -10 : -35 + NSLayoutConstraint.activate([ contentView.leadingAnchor.constraint(equalTo: effectView.leadingAnchor, constant: 12), contentView.trailingAnchor.constraint( - equalTo: effectView.trailingAnchor, constant: -35), + equalTo: effectView.trailingAnchor, constant: trailingConstant), contentView.topAnchor.constraint(equalTo: effectView.topAnchor, constant: 9), contentView.bottomAnchor.constraint(equalTo: effectView.bottomAnchor, constant: -9), ]) @@ -558,6 +561,7 @@ class NotificationManager { private func createNotificationView( title: String, body: String, + buttonTitle: String? = nil, notification: NotificationInstance ) -> NSView { let container = NSStackView() @@ -618,9 +622,39 @@ class NotificationManager { container.addArrangedSubview(iconContainer) container.addArrangedSubview(textStack) + if let buttonTitle { + let gap = NSView() + gap.translatesAutoresizingMaskIntoConstraints = false + gap.widthAnchor.constraint(equalToConstant: 8).isActive = true + gap.setContentHuggingPriority(.required, for: .horizontal) + gap.setContentCompressionResistancePriority(.required, for: .horizontal) + container.addArrangedSubview(gap) + + let btn = ActionButton( + title: buttonTitle, + target: self, + action: #selector(handleActionButtonPress(_:)) + ) + btn.setContentHuggingPriority(.required, for: .horizontal) + btn.setContentCompressionResistancePriority(.required, for: .horizontal) + btn.notification = notification + container.addArrangedSubview(btn) + } + return container } + @objc private func handleActionButtonPress(_ sender: NSButton) { + guard let btn = sender as? ActionButton, let notification = btn.notification else { return } + notification.id.uuidString.withCString { idPtr in + rustOnNotificationConfirm(idPtr) + } + if let urlString = notification.url, let url = URL(string: urlString) { + NSWorkspace.shared.open(url) + } + notification.dismissWithUserAction() + } + private func createAppIconView() -> NSImageView { let imageView = NSImageView() if let appIcon = NSApp.applicationIconImage { @@ -765,27 +799,25 @@ class NotificationManager { @_silgen_name("rust_on_notification_confirm") func rustOnNotificationConfirm(_ idPtr: UnsafePointer) -@_silgen_name("rust_on_notification_accept") -func rustOnNotificationAccept(_ idPtr: UnsafePointer) - @_silgen_name("rust_on_notification_dismiss") func rustOnNotificationDismiss(_ idPtr: UnsafePointer) -@_silgen_name("rust_on_notification_timeout") -func rustOnNotificationTimeout(_ idPtr: UnsafePointer) - @_cdecl("_show_notification") public func _showNotification( title: SRString, message: SRString, + url: SRString, timeoutSeconds: Double ) -> Bool { let titleStr = title.toString() let messageStr = message.toString() + let urlStr = url.toString() + let finalUrl = urlStr.isEmpty ? nil : urlStr NotificationManager.shared.show( title: titleStr, message: messageStr, + url: finalUrl, timeoutSeconds: timeoutSeconds ) diff --git a/crates/notification/Cargo.toml b/crates/notification/Cargo.toml index 23d61a25e4..142d354f74 100644 --- a/crates/notification/Cargo.toml +++ b/crates/notification/Cargo.toml @@ -3,20 +3,13 @@ name = "notification" version = "0.1.0" edition = "2024" -[features] -default = ["legacy"] -legacy = ["dep:hypr-notification-macos", "dep:hypr-notification-linux"] -new = ["dep:hypr-notification-gpui"] - [dependencies] +hypr-notification-interface = { workspace = true } serde = { workspace = true, features = ["derive"] } tracing = { workspace = true } -hypr-notification-gpui = { path = "../notification-gpui", package = "notification-gpui", optional = true } -hypr-notification-interface = { workspace = true } - [target.'cfg(target_os = "macos")'.dependencies] -hypr-notification-macos = { path = "../notification-macos", package = "notification-macos", optional = true } +hypr-notification-macos = { path = "../notification-macos", package = "notification-macos" } [target.'cfg(target_os = "linux")'.dependencies] -hypr-notification-linux = { path = "../notification-linux", package = "notification-linux", optional = true } +hypr-notification-linux = { path = "../notification-linux", package = "notification-linux" } diff --git a/crates/notification/src/lib.rs b/crates/notification/src/lib.rs index a15293280a..368b9abc00 100644 --- a/crates/notification/src/lib.rs +++ b/crates/notification/src/lib.rs @@ -13,20 +13,41 @@ pub enum NotificationMutation { Dismiss, } -fn show_inner(notification: &hypr_notification_interface::Notification) { - #[cfg(feature = "new")] - hypr_notification_gpui::show(notification); +#[cfg(target_os = "macos")] +pub fn show(notification: &hypr_notification_interface::Notification) { + let Some(key) = ¬ification.key else { + hypr_notification_macos::show(notification); + return; + }; - #[cfg(all(feature = "legacy", target_os = "macos"))] - hypr_notification_macos::show(notification); + let recent_map = RECENT_NOTIFICATIONS.get_or_init(|| Mutex::new(HashMap::new())); - #[cfg(all(feature = "legacy", target_os = "linux"))] - hypr_notification_linux::show(notification); + { + let mut recent_notifications = recent_map.lock().unwrap(); + let now = Instant::now(); + + recent_notifications + .retain(|_, &mut timestamp| now.duration_since(timestamp) < DEDUPE_WINDOW); + + if let Some(&last_shown) = recent_notifications.get(key) { + let duration = now.duration_since(last_shown); + + if duration < DEDUPE_WINDOW { + tracing::info!(key = key, duration = ?duration, "skipping_notification"); + return; + } + } + + recent_notifications.insert(key.clone(), now); + } + + hypr_notification_macos::show(notification); } +#[cfg(target_os = "linux")] pub fn show(notification: &hypr_notification_interface::Notification) { let Some(key) = ¬ification.key else { - show_inner(notification); + hypr_notification_linux::show(notification); return; }; @@ -51,17 +72,17 @@ pub fn show(notification: &hypr_notification_interface::Notification) { recent_notifications.insert(key.clone(), now); } - show_inner(notification); + hypr_notification_linux::show(notification); } -pub fn clear() { - #[cfg(feature = "new")] - hypr_notification_gpui::dismiss_all(); +#[cfg(not(any(target_os = "macos", target_os = "linux")))] +pub fn show(notification: &hypr_notification_interface::Notification) {} - #[cfg(all(feature = "legacy", target_os = "macos"))] +pub fn clear() { + #[cfg(target_os = "macos")] hypr_notification_macos::dismiss_all(); - #[cfg(all(feature = "legacy", target_os = "linux"))] + #[cfg(target_os = "linux")] hypr_notification_linux::dismiss_all(); } @@ -69,13 +90,10 @@ pub fn setup_notification_dismiss_handler(f: F) where F: Fn(String) + Send + Sync + 'static, { - #[cfg(feature = "new")] - hypr_notification_gpui::setup_notification_dismiss_handler(f); - - #[cfg(all(feature = "legacy", target_os = "macos"))] + #[cfg(target_os = "macos")] hypr_notification_macos::setup_notification_dismiss_handler(f); - #[cfg(all(feature = "legacy", target_os = "linux"))] + #[cfg(target_os = "linux")] hypr_notification_linux::setup_notification_dismiss_handler(f); } @@ -83,40 +101,9 @@ pub fn setup_notification_confirm_handler(f: F) where F: Fn(String) + Send + Sync + 'static, { - #[cfg(feature = "new")] - hypr_notification_gpui::setup_notification_confirm_handler(f); - - #[cfg(all(feature = "legacy", target_os = "macos"))] + #[cfg(target_os = "macos")] hypr_notification_macos::setup_notification_confirm_handler(f); - #[cfg(all(feature = "legacy", target_os = "linux"))] + #[cfg(target_os = "linux")] hypr_notification_linux::setup_notification_confirm_handler(f); } - -pub fn setup_notification_accept_handler(f: F) -where - F: Fn(String) + Send + Sync + 'static, -{ - #[cfg(feature = "new")] - hypr_notification_gpui::setup_notification_accept_handler(f); - - #[cfg(all(feature = "legacy", target_os = "macos"))] - hypr_notification_macos::setup_notification_accept_handler(f); - - #[cfg(all(feature = "legacy", target_os = "linux"))] - hypr_notification_linux::setup_notification_accept_handler(f); -} - -pub fn setup_notification_timeout_handler(f: F) -where - F: Fn(String) + Send + Sync + 'static, -{ - #[cfg(feature = "new")] - hypr_notification_gpui::setup_notification_timeout_handler(f); - - #[cfg(all(feature = "legacy", target_os = "macos"))] - hypr_notification_macos::setup_notification_timeout_handler(f); - - #[cfg(all(feature = "legacy", target_os = "linux"))] - hypr_notification_linux::setup_notification_timeout_handler(f); -} diff --git a/crates/template/src/lib.rs b/crates/template/src/lib.rs index a4cdeefce7..13bc5c7ee7 100644 --- a/crates/template/src/lib.rs +++ b/crates/template/src/lib.rs @@ -48,12 +48,6 @@ pub enum Template { #[strum(serialize = "postprocess_transcript.user")] #[serde(rename = "postprocess_transcript.user")] PostprocessTranscriptUser, - #[strum(serialize = "highlight.system")] - #[serde(rename = "highlight.system")] - HighlightSystem, - #[strum(serialize = "highlight.user")] - #[serde(rename = "highlight.user")] - HighlightUser, } #[cfg(not(debug_assertions))] @@ -81,10 +75,6 @@ pub const POSTPROCESS_TRANSCRIPT_SYSTEM_TPL: &str = pub const POSTPROCESS_TRANSCRIPT_USER_TPL: &str = include_str!("../assets/postprocess_transcript.user.jinja"); #[cfg(not(debug_assertions))] -pub const HIGHLIGHT_SYSTEM_TPL: &str = include_str!("../assets/highlight.system.jinja"); -#[cfg(not(debug_assertions))] -pub const HIGHLIGHT_USER_TPL: &str = include_str!("../assets/highlight.user.jinja"); -#[cfg(not(debug_assertions))] pub const LANGUAGE_PARTIAL_TPL: &str = include_str!("../assets/_language.partial.jinja"); static GLOBAL_ENV: OnceLock> = OnceLock::new(); @@ -140,10 +130,6 @@ fn init_environment() -> minijinja::Environment<'static> { POSTPROCESS_TRANSCRIPT_USER_TPL, ) .unwrap(); - env.add_template(Template::HighlightSystem.as_ref(), HIGHLIGHT_SYSTEM_TPL) - .unwrap(); - env.add_template(Template::HighlightUser.as_ref(), HIGHLIGHT_USER_TPL) - .unwrap(); env.add_template("_language.partial", LANGUAGE_PARTIAL_TPL) .unwrap(); } diff --git a/crates/whisper-local/src/model/actual.rs b/crates/whisper-local/src/model/actual.rs index bc1577707b..d5c63985c8 100644 --- a/crates/whisper-local/src/model/actual.rs +++ b/crates/whisper-local/src/model/actual.rs @@ -272,25 +272,25 @@ impl Whisper { } fn debug(&mut self, audio: &[f32]) { - if let Ok(v) = std::env::var("HYPR_WHISPER_DEBUG") - && v == "1" - { - let mut writer = hound::WavWriter::create( - format!("./whisper_{}_{}.wav", self.id, self.index), - hound::WavSpec { - channels: 1, - sample_rate: 16000, - bits_per_sample: 32, - sample_format: hound::SampleFormat::Float, - }, - ) - .unwrap(); - self.index += 1; - - for sample in audio { - writer.write_sample(*sample).unwrap(); + if let Ok(v) = std::env::var("HYPR_WHISPER_DEBUG") { + if v == "1" { + let mut writer = hound::WavWriter::create( + format!("./whisper_{}_{}.wav", self.id, self.index), + hound::WavSpec { + channels: 1, + sample_rate: 16000, + bits_per_sample: 32, + sample_format: hound::SampleFormat::Float, + }, + ) + .unwrap(); + self.index += 1; + + for sample in audio { + writer.write_sample(*sample).unwrap(); + } + writer.finalize().unwrap(); } - writer.finalize().unwrap(); } } } diff --git a/crates/ws/src/client.rs b/crates/ws/src/client.rs index d57d8426bd..67d62c70bd 100644 --- a/crates/ws/src/client.rs +++ b/crates/ws/src/client.rs @@ -97,12 +97,12 @@ impl WebSocketClient { let handle = WebSocketHandle { control_tx }; let _send_task = tokio::spawn(async move { - if let Some(msg) = initial_message - && let Err(e) = ws_sender.send(msg).await - { - tracing::error!("ws_initial_message_failed: {:?}", e); - let _ = error_tx.send(e.into()); - return; + if let Some(msg) = initial_message { + if let Err(e) = ws_sender.send(msg).await { + tracing::error!("ws_initial_message_failed: {:?}", e); + let _ = error_tx.send(e.into()); + return; + } } let mut last_outbound_at = tokio::time::Instant::now(); diff --git a/extensions/shared/API.md b/extensions/shared/API.md index 32eabd8fe3..765d4e60de 100644 --- a/extensions/shared/API.md +++ b/extensions/shared/API.md @@ -6,67 +6,67 @@ TinyBase store with app data (sessions, events, humans, etc.) -| Export | Description | -| --------------- | -------------------------------- | -| `STORE_ID` | The main store identifier | -| `UI` | TinyBase UI hooks | -| `INDEXES` | Available TinyBase indexes | -| `QUERIES` | Available TinyBase queries | -| `METRICS` | Available TinyBase metrics | +| Export | Description | +|--------|-------------| +| `STORE_ID` | The main store identifier | +| `UI` | TinyBase UI hooks | +| `INDEXES` | Available TinyBase indexes | +| `QUERIES` | Available TinyBase queries | +| `METRICS` | Available TinyBase metrics | | `RELATIONSHIPS` | Available TinyBase relationships | ## @hypr/tabs Tab navigation (open sessions, events, etc.) -| Export | Description | -| --------- | ----------------------------- | +| Export | Description | +|--------|-------------| | `useTabs` | Hook to access tab navigation | ## @hypr/ui UI components (shadcn-style) -| Component Path | Exports | -| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `@hypr/ui/components/icons/outlook` | OutlookIcon | -| `@hypr/ui/components/ui/accordion` | Accordion, AccordionContent, AccordionItem, AccordionTrigger | -| `@hypr/ui/components/ui/avatar` | Avatar, AvatarFallback, AvatarImage | -| `@hypr/ui/components/ui/badge` | Badge, badgeVariants | -| `@hypr/ui/components/ui/bottom-sheet` | BottomSheet, BottomSheetContent, BottomSheetTrigger | -| `@hypr/ui/components/ui/breadcrumb` | Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator | -| `@hypr/ui/components/ui/button` | Button, buttonVariants | -| `@hypr/ui/components/ui/button-group` | ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants | -| `@hypr/ui/components/ui/card` | Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter | -| `@hypr/ui/components/ui/carousel` | Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious | -| `@hypr/ui/components/ui/checkbox` | Checkbox | -| `@hypr/ui/components/ui/command` | Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut | -| `@hypr/ui/components/ui/context-menu` | ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger | -| `@hypr/ui/components/ui/dancing-sticks` | DancingSticks | -| `@hypr/ui/components/ui/dialog` | Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger | -| `@hypr/ui/components/ui/dropdown-menu` | DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger | -| `@hypr/ui/components/ui/form` | Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField | -| `@hypr/ui/components/ui/hover-card` | HoverCard, HoverCardContent, HoverCardTrigger | -| `@hypr/ui/components/ui/input` | Input | -| `@hypr/ui/components/ui/input-group` | InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea | -| `@hypr/ui/components/ui/kbd` | Kbd, KbdGroup | -| `@hypr/ui/components/ui/label` | Label | -| `@hypr/ui/components/ui/marquee` | Marquee | -| `@hypr/ui/components/ui/modal` | Modal, ModalHeader, ModalBody, ModalFooter, ModalTitle, ModalDescription | -| `@hypr/ui/components/ui/popover` | Popover, PopoverAnchor, PopoverContent, PopoverTrigger | -| `@hypr/ui/components/ui/progress` | Progress | -| `@hypr/ui/components/ui/progressive-blur` | GRADIENT_ANGLES, ProgressiveBlur | -| `@hypr/ui/components/ui/radio-group` | RadioGroup, RadioGroupItem | -| `@hypr/ui/components/ui/resizable` | ResizableHandle, ResizablePanel, ResizablePanelGroup | -| `@hypr/ui/components/ui/select` | Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue | -| `@hypr/ui/components/ui/separator` | Separator | -| `@hypr/ui/components/ui/slider` | Slider | -| `@hypr/ui/components/ui/spinner` | Spinner | -| `@hypr/ui/components/ui/splash` | SplashLoader, SplashScreen, Splash | -| `@hypr/ui/components/ui/switch` | Switch | -| `@hypr/ui/components/ui/tabs` | Tabs, TabsContent, TabsList, TabsTrigger | -| `@hypr/ui/components/ui/text-animate` | TextAnimate | -| `@hypr/ui/components/ui/textarea` | Textarea | -| `@hypr/ui/components/ui/toast` | sonnerToast, CustomToast, toast | -| `@hypr/ui/components/ui/tooltip` | Tooltip, TooltipContent, TooltipProvider, TooltipTrigger | -| `@hypr/ui/components/ui/typewriter` | Typewriter | +| Component Path | Exports | +|----------------|---------| +| `@hypr/ui/components/icons/outlook` | OutlookIcon | +| `@hypr/ui/components/ui/accordion` | Accordion, AccordionContent, AccordionItem, AccordionTrigger | +| `@hypr/ui/components/ui/avatar` | Avatar, AvatarFallback, AvatarImage | +| `@hypr/ui/components/ui/badge` | Badge, badgeVariants | +| `@hypr/ui/components/ui/bottom-sheet` | BottomSheet, BottomSheetContent, BottomSheetTrigger | +| `@hypr/ui/components/ui/breadcrumb` | Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator | +| `@hypr/ui/components/ui/button` | Button, buttonVariants | +| `@hypr/ui/components/ui/button-group` | ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants | +| `@hypr/ui/components/ui/card` | Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter | +| `@hypr/ui/components/ui/carousel` | Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious | +| `@hypr/ui/components/ui/checkbox` | Checkbox | +| `@hypr/ui/components/ui/command` | Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut | +| `@hypr/ui/components/ui/context-menu` | ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger | +| `@hypr/ui/components/ui/dancing-sticks` | DancingSticks | +| `@hypr/ui/components/ui/dialog` | Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger | +| `@hypr/ui/components/ui/dropdown-menu` | DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger | +| `@hypr/ui/components/ui/form` | Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField | +| `@hypr/ui/components/ui/hover-card` | HoverCard, HoverCardContent, HoverCardTrigger | +| `@hypr/ui/components/ui/input` | Input | +| `@hypr/ui/components/ui/input-group` | InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea | +| `@hypr/ui/components/ui/kbd` | Kbd, KbdGroup | +| `@hypr/ui/components/ui/label` | Label | +| `@hypr/ui/components/ui/marquee` | Marquee | +| `@hypr/ui/components/ui/modal` | Modal, ModalHeader, ModalBody, ModalFooter, ModalTitle, ModalDescription | +| `@hypr/ui/components/ui/popover` | Popover, PopoverAnchor, PopoverContent, PopoverTrigger | +| `@hypr/ui/components/ui/progress` | Progress | +| `@hypr/ui/components/ui/progressive-blur` | GRADIENT_ANGLES, ProgressiveBlur | +| `@hypr/ui/components/ui/radio-group` | RadioGroup, RadioGroupItem | +| `@hypr/ui/components/ui/resizable` | ResizableHandle, ResizablePanel, ResizablePanelGroup | +| `@hypr/ui/components/ui/select` | Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue | +| `@hypr/ui/components/ui/separator` | Separator | +| `@hypr/ui/components/ui/slider` | Slider | +| `@hypr/ui/components/ui/spinner` | Spinner | +| `@hypr/ui/components/ui/splash` | SplashLoader, SplashScreen, Splash | +| `@hypr/ui/components/ui/switch` | Switch | +| `@hypr/ui/components/ui/tabs` | Tabs, TabsContent, TabsList, TabsTrigger | +| `@hypr/ui/components/ui/text-animate` | TextAnimate | +| `@hypr/ui/components/ui/textarea` | Textarea | +| `@hypr/ui/components/ui/toast` | sonnerToast, CustomToast, toast | +| `@hypr/ui/components/ui/tooltip` | Tooltip, TooltipContent, TooltipProvider, TooltipTrigger | +| `@hypr/ui/components/ui/typewriter` | Typewriter | diff --git a/extensions/shared/build.ts b/extensions/shared/build.ts index 433d0f1210..e228607d41 100644 --- a/extensions/shared/build.ts +++ b/extensions/shared/build.ts @@ -404,7 +404,7 @@ async function main() { } } - void esbuild.stop(); + esbuild.stop(); } main().catch((err) => { diff --git a/extensions/shared/generate.ts b/extensions/shared/generate.ts index dcfe6f1bf4..9491c77b76 100644 --- a/extensions/shared/generate.ts +++ b/extensions/shared/generate.ts @@ -116,4 +116,4 @@ async function main() { printGlobalsChecklist(uiModules); } -void main(); +main(); diff --git a/extensions/shared/runtime.ts b/extensions/shared/runtime.ts index b9cd3dc57c..013092e8fa 100644 --- a/extensions/shared/runtime.ts +++ b/extensions/shared/runtime.ts @@ -60,10 +60,7 @@ export const HYPR_MODULES = { description: "TinyBase store with app data (sessions, events, humans, etc.)", exports: { - STORE_ID: { - type: '"main"', - description: "The main store identifier", - }, + STORE_ID: { type: '"main"', description: "The main store identifier" }, UI: { type: `_UI.WithSchemas`, description: "TinyBase UI hooks", diff --git a/owhisper/schema.json b/owhisper/schema.json index b6436337d2..970a5f2704 100644 --- a/owhisper/schema.json +++ b/owhisper/schema.json @@ -2,7 +2,9 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Config", "type": "object", - "required": ["models"], + "required": [ + "models" + ], "properties": { "general": { "anyOf": [ @@ -26,7 +28,10 @@ "type": "object", "properties": { "api_key": { - "type": ["string", "null"] + "type": [ + "string", + "null" + ] } } }, @@ -34,11 +39,19 @@ "oneOf": [ { "type": "object", - "required": ["access_key_id", "id", "region", "secret_access_key", "type"], + "required": [ + "access_key_id", + "id", + "region", + "secret_access_key", + "type" + ], "properties": { "type": { "type": "string", - "enum": ["aws"] + "enum": [ + "aws" + ] }, "id": { "type": "string" @@ -56,30 +69,47 @@ }, { "type": "object", - "required": ["id", "type"], + "required": [ + "id", + "type" + ], "properties": { "type": { "type": "string", - "enum": ["deepgram"] + "enum": [ + "deepgram" + ] }, "id": { "type": "string" }, "api_key": { - "type": ["string", "null"] + "type": [ + "string", + "null" + ] }, "base_url": { - "type": ["string", "null"] + "type": [ + "string", + "null" + ] } } }, { "type": "object", - "required": ["assets_dir", "id", "type"], + "required": [ + "assets_dir", + "id", + "type" + ], "properties": { "type": { "type": "string", - "enum": ["whisper-cpp"] + "enum": [ + "whisper-cpp" + ] }, "id": { "type": "string" @@ -91,11 +121,18 @@ }, { "type": "object", - "required": ["assets_dir", "id", "size", "type"], + "required": [ + "assets_dir", + "id", + "size", + "type" + ], "properties": { "type": { "type": "string", - "enum": ["moonshine"] + "enum": [ + "moonshine" + ] }, "id": { "type": "string" @@ -112,7 +149,10 @@ }, "MoonshineModelSize": { "type": "string", - "enum": ["tiny", "base"] + "enum": [ + "tiny", + "base" + ] } } -} +} \ No newline at end of file diff --git a/plugins/analytics/js/bindings.gen.ts b/plugins/analytics/js/bindings.gen.ts index 74a5c2fcf3..40af8a4f2e 100644 --- a/plugins/analytics/js/bindings.gen.ts +++ b/plugins/analytics/js/bindings.gen.ts @@ -1,122 +1,116 @@ // @ts-nocheck + // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. /** user-defined commands **/ + export const commands = { - async event(payload: AnalyticsPayload): Promise> { +async event(payload: AnalyticsPayload) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:analytics|event", { payload }) }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async setProperties(payload: PropertiesPayload): Promise> { + return { status: "ok", data: await TAURI_INVOKE("plugin:analytics|event", { payload }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async setProperties(payload: PropertiesPayload) : Promise> { try { - return { - status: "ok", - data: await TAURI_INVOKE("plugin:analytics|set_properties", { payload }), - }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async setDisabled(disabled: boolean): Promise> { + return { status: "ok", data: await TAURI_INVOKE("plugin:analytics|set_properties", { payload }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async setDisabled(disabled: boolean) : Promise> { try { - return { - status: "ok", - data: await TAURI_INVOKE("plugin:analytics|set_disabled", { disabled }), - }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async isDisabled(): Promise> { + return { status: "ok", data: await TAURI_INVOKE("plugin:analytics|set_disabled", { disabled }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async isDisabled() : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:analytics|is_disabled") }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, -}; + return { status: "ok", data: await TAURI_INVOKE("plugin:analytics|is_disabled") }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +} +} /** user-defined events **/ + + /** user-defined constants **/ + + /** user-defined types **/ -export type AnalyticsPayload = Partial<{ - [key in string]: - | null - | boolean - | number - | string - | JsonValue[] - | Partial<{ [key in string]: JsonValue }>; -}> & { event: string }; -export type JsonValue = - | null - | boolean - | number - | string - | JsonValue[] - | Partial<{ [key in string]: JsonValue }>; -export type PropertiesPayload = { - set?: Partial<{ [key in string]: JsonValue }>; - set_once?: Partial<{ [key in string]: JsonValue }>; -}; +export type AnalyticsPayload = (Partial<{ [key in string]: null | boolean | number | string | JsonValue[] | Partial<{ [key in string]: JsonValue }> }>) & { event: string } +export type JsonValue = null | boolean | number | string | JsonValue[] | Partial<{ [key in string]: JsonValue }> +export type PropertiesPayload = { set?: Partial<{ [key in string]: JsonValue }>; set_once?: Partial<{ [key in string]: JsonValue }> } /** tauri-specta globals **/ -import { invoke as TAURI_INVOKE, Channel as TAURI_CHANNEL } from "@tauri-apps/api/core"; +import { + invoke as TAURI_INVOKE, + Channel as TAURI_CHANNEL, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - emit: null extends T - ? (payload?: T) => ReturnType - : (payload: T) => ReturnType; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + emit: null extends T + ? (payload?: T) => ReturnType + : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; - -function __makeEvents__>(mappings: Record) { - return new Proxy( - {} as unknown as { - [K in keyof T]: __EventObj__ & { - (handle: __WebviewWindow__): __EventObj__; - }; - }, - { - get: (_, event) => { - const name = mappings[event as keyof T]; - - return new Proxy((() => {}) as any, { - apply: (_, __, [window]: [__WebviewWindow__]) => ({ - listen: (arg: any) => window.listen(name, arg), - once: (arg: any) => window.once(name, arg), - emit: (arg: any) => window.emit(name, arg), - }), - get: (_, command: keyof __EventObj__) => { - switch (command) { - case "listen": - return (arg: any) => TAURI_API_EVENT.listen(name, arg); - case "once": - return (arg: any) => TAURI_API_EVENT.once(name, arg); - case "emit": - return (arg: any) => TAURI_API_EVENT.emit(name, arg); - } - }, - }); - }, - }, - ); +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; + +function __makeEvents__>( + mappings: Record, +) { + return new Proxy( + {} as unknown as { + [K in keyof T]: __EventObj__ & { + (handle: __WebviewWindow__): __EventObj__; + }; + }, + { + get: (_, event) => { + const name = mappings[event as keyof T]; + + return new Proxy((() => {}) as any, { + apply: (_, __, [window]: [__WebviewWindow__]) => ({ + listen: (arg: any) => window.listen(name, arg), + once: (arg: any) => window.once(name, arg), + emit: (arg: any) => window.emit(name, arg), + }), + get: (_, command: keyof __EventObj__) => { + switch (command) { + case "listen": + return (arg: any) => TAURI_API_EVENT.listen(name, arg); + case "once": + return (arg: any) => TAURI_API_EVENT.once(name, arg); + case "emit": + return (arg: any) => TAURI_API_EVENT.emit(name, arg); + } + }, + }); + }, + }, + ); } diff --git a/plugins/analytics/src/commands.rs b/plugins/analytics/src/commands.rs index e949569cf0..17e8bda52d 100644 --- a/plugins/analytics/src/commands.rs +++ b/plugins/analytics/src/commands.rs @@ -6,10 +6,7 @@ pub(crate) async fn event( app: tauri::AppHandle, payload: hypr_analytics::AnalyticsPayload, ) -> Result<(), String> { - app.analytics() - .event(payload) - .await - .map_err(|e| e.to_string()) + app.event(payload).await.map_err(|e| e.to_string()) } #[tauri::command] @@ -18,10 +15,7 @@ pub(crate) async fn set_properties( app: tauri::AppHandle, payload: hypr_analytics::PropertiesPayload, ) -> Result<(), String> { - app.analytics() - .set_properties(payload) - .await - .map_err(|e| e.to_string()) + app.set_properties(payload).await.map_err(|e| e.to_string()) } #[tauri::command] @@ -30,9 +24,7 @@ pub(crate) async fn set_disabled( app: tauri::AppHandle, disabled: bool, ) -> Result<(), String> { - app.analytics() - .set_disabled(disabled) - .map_err(|e| e.to_string()) + app.set_disabled(disabled).map_err(|e| e.to_string()) } #[tauri::command] @@ -40,5 +32,5 @@ pub(crate) async fn set_disabled( pub(crate) async fn is_disabled( app: tauri::AppHandle, ) -> Result { - app.analytics().is_disabled().map_err(|e| e.to_string()) + app.is_disabled().map_err(|e| e.to_string()) } diff --git a/plugins/analytics/src/ext.rs b/plugins/analytics/src/ext.rs index 01d335f5ce..a1ce6d2367 100644 --- a/plugins/analytics/src/ext.rs +++ b/plugins/analytics/src/ext.rs @@ -1,52 +1,31 @@ +use std::future::Future; + use tauri_plugin_misc::MiscPluginExt; use tauri_plugin_store2::StorePluginExt; -pub struct Analytics<'a, R: tauri::Runtime, M: tauri::Manager> { - manager: &'a M, - _runtime: std::marker::PhantomData R>, +pub trait AnalyticsPluginExt { + fn set_disabled(&self, disabled: bool) -> Result<(), crate::Error>; + fn is_disabled(&self) -> Result; + fn event( + &self, + payload: hypr_analytics::AnalyticsPayload, + ) -> impl Future>; + fn set_properties( + &self, + payload: hypr_analytics::PropertiesPayload, + ) -> impl Future>; } -impl<'a, R: tauri::Runtime, M: tauri::Manager> Analytics<'a, R, M> { - pub async fn event( +impl> crate::AnalyticsPluginExt for T { + async fn event( &self, mut payload: hypr_analytics::AnalyticsPayload, ) -> Result<(), crate::Error> { - Self::enrich_payload(self.manager, &mut payload); - - if self.is_disabled().unwrap_or(true) { - return Ok(()); - } - - let machine_id = hypr_host::fingerprint(); - let client = self.manager.state::(); - client - .event(machine_id, payload) - .await - .map_err(crate::Error::HyprAnalytics)?; - - Ok(()) - } - - pub fn event_fire_and_forget(&self, mut payload: hypr_analytics::AnalyticsPayload) { - Self::enrich_payload(self.manager, &mut payload); - - if self.is_disabled().unwrap_or(true) { - return; - } - - let machine_id = hypr_host::fingerprint(); - let client = self.manager.state::().inner().clone(); - - tauri::async_runtime::spawn(async move { - let _ = client.event(machine_id, payload).await; - }); - } - - fn enrich_payload(manager: &M, payload: &mut hypr_analytics::AnalyticsPayload) { let app_version = env!("APP_VERSION"); - let app_identifier = manager.config().identifier.clone(); - let git_hash = manager.get_git_hash(); - let bundle_id = manager.config().identifier.clone(); + let app_identifier = self.config().identifier.clone(); + let git_hash = self.get_git_hash(); + let bundle_id = self.config().identifier.clone(); + let machine_id = hypr_host::fingerprint(); payload .props @@ -67,30 +46,40 @@ impl<'a, R: tauri::Runtime, M: tauri::Manager> Analytics<'a, R, M> { .props .entry("bundle_id".into()) .or_insert(bundle_id.into()); + + if !self.is_disabled()? { + let client = self.state::(); + client + .event(machine_id, payload) + .await + .map_err(crate::Error::HyprAnalytics)?; + } + + Ok(()) } - pub fn set_disabled(&self, disabled: bool) -> Result<(), crate::Error> { + fn set_disabled(&self, disabled: bool) -> Result<(), crate::Error> { { - let store = self.manager.scoped_store(crate::PLUGIN_NAME)?; + let store = self.scoped_store(crate::PLUGIN_NAME)?; store.set(crate::StoreKey::Disabled, disabled)?; } Ok(()) } - pub fn is_disabled(&self) -> Result { - let store = self.manager.scoped_store(crate::PLUGIN_NAME)?; + fn is_disabled(&self) -> Result { + let store = self.scoped_store(crate::PLUGIN_NAME)?; let v = store.get(crate::StoreKey::Disabled)?.unwrap_or(false); Ok(v) } - pub async fn set_properties( + async fn set_properties( &self, payload: hypr_analytics::PropertiesPayload, ) -> Result<(), crate::Error> { if !self.is_disabled()? { let machine_id = hypr_host::fingerprint(); - let client = self.manager.state::(); + let client = self.state::(); client .set_properties(machine_id, payload) .await @@ -100,21 +89,3 @@ impl<'a, R: tauri::Runtime, M: tauri::Manager> Analytics<'a, R, M> { Ok(()) } } - -pub trait AnalyticsPluginExt { - fn analytics(&self) -> Analytics<'_, R, Self> - where - Self: tauri::Manager + Sized; -} - -impl> AnalyticsPluginExt for T { - fn analytics(&self) -> Analytics<'_, R, Self> - where - Self: Sized, - { - Analytics { - manager: self, - _runtime: std::marker::PhantomData, - } - } -} diff --git a/plugins/analytics/src/lib.rs b/plugins/analytics/src/lib.rs index fd22a9290c..8f7fad3a59 100644 --- a/plugins/analytics/src/lib.rs +++ b/plugins/analytics/src/lib.rs @@ -87,7 +87,6 @@ mod test { async fn test_analytics() { let app = create_app(tauri::test::mock_builder()); let result = app - .analytics() .event(hypr_analytics::AnalyticsPayload::builder("test_event").build()) .await; assert!(result.is_ok()); diff --git a/plugins/apple-calendar/Cargo.toml b/plugins/apple-calendar/Cargo.toml index 5a6e4a614a..2adf2fe887 100644 --- a/plugins/apple-calendar/Cargo.toml +++ b/plugins/apple-calendar/Cargo.toml @@ -33,5 +33,6 @@ tracing = { workspace = true } block2 = { workspace = true } objc2 = { workspace = true } objc2-contacts = { workspace = true, features = ["CNContactStore", "CNContact", "CNLabeledValue", "CNPhoneNumber"] } -objc2-event-kit = { workspace = true, features = ["EKEventStore", "EKCalendarItem", "EKCalendar", "EKParticipant", "EKObject", "EKEvent", "EKSource", "EKTypes", "EKRecurrenceRule", "EKRecurrenceEnd", "EKAlarm", "EKStructuredLocation", "EKRecurrenceDayOfWeek"] } +objc2-core-graphics = { workspace = true } +objc2-event-kit = { workspace = true, features = ["EKEventStore", "EKCalendarItem", "EKCalendar", "EKParticipant", "EKObject", "EKEvent", "EKSource", "EKTypes", "EKRecurrenceRule", "EKRecurrenceEnd", "EKAlarm", "EKStructuredLocation", "EKRecurrenceDayOfWeek", "objc2-core-graphics"] } objc2-foundation = { workspace = true, features = ["NSDate", "NSEnumerator", "NSPredicate", "NSError", "NSNotification"] } diff --git a/plugins/apple-calendar/js/bindings.gen.ts b/plugins/apple-calendar/js/bindings.gen.ts index 05b43355b6..082dec7de7 100644 --- a/plugins/apple-calendar/js/bindings.gen.ts +++ b/plugins/apple-calendar/js/bindings.gen.ts @@ -1,272 +1,139 @@ // @ts-nocheck -/** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; -import * as TAURI_API_EVENT from "@tauri-apps/api/event"; -import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. /** user-defined commands **/ + export const commands = { - async openCalendar(): Promise> { +async openCalendar() : Promise> { try { - return { - status: "ok", - data: await TAURI_INVOKE("plugin:apple-calendar|open_calendar"), - }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async listCalendars(): Promise> { + return { status: "ok", data: await TAURI_INVOKE("plugin:apple-calendar|open_calendar") }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async listCalendars() : Promise> { try { - return { - status: "ok", - data: await TAURI_INVOKE("plugin:apple-calendar|list_calendars"), - }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async listEvents(filter: EventFilter): Promise> { + return { status: "ok", data: await TAURI_INVOKE("plugin:apple-calendar|list_calendars") }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async listEvents(filter: EventFilter) : Promise> { try { - return { - status: "ok", - data: await TAURI_INVOKE("plugin:apple-calendar|list_events", { - filter, - }), - }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, -}; + return { status: "ok", data: await TAURI_INVOKE("plugin:apple-calendar|list_events", { filter }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +} +} /** user-defined events **/ + export const events = __makeEvents__<{ - calendarChangedEvent: CalendarChangedEvent; +calendarChangedEvent: CalendarChangedEvent }>({ - calendarChangedEvent: "plugin:apple-calendar:calendar-changed-event", -}); +calendarChangedEvent: "plugin:apple-calendar:calendar-changed-event" +}) /** user-defined constants **/ + + /** user-defined types **/ -export type Alarm = { - absolute_date: string | null; - relative_offset: number | null; - proximity: AlarmProximity | null; - alarm_type: AlarmType | null; - email_address: string | null; - sound_name: string | null; - url: string | null; - structured_location: StructuredLocation | null; -}; -export type AlarmProximity = "None" | "Enter" | "Leave"; -export type AlarmType = "Display" | "Audio" | "Procedure" | "Email"; -export type AppleCalendar = { - id: string; - title: string; - calendar_type: CalendarType; - color: CalendarColor | null; - allows_content_modifications: boolean; - is_immutable: boolean; - is_subscribed: boolean; - supported_event_availabilities: EventAvailability[]; - allowed_entity_types: CalendarEntityType[]; - source: CalendarSource; -}; -export type AppleEvent = { - event_identifier: string; - calendar_item_identifier: string; - external_identifier: string; - calendar: CalendarRef; - title: string; - location: string | null; - url: string | null; - notes: string | null; - creation_date: string | null; - last_modified_date: string | null; - time_zone: string | null; - start_date: string; - end_date: string; - is_all_day: boolean; - availability: EventAvailability; - status: EventStatus; - has_alarms: boolean; - has_attendees: boolean; - has_notes: boolean; - has_recurrence_rules: boolean; - organizer: Participant | null; - attendees: Participant[]; - structured_location: StructuredLocation | null; - recurrence: RecurrenceInfo | null; - occurrence_date: string | null; - is_detached: boolean; - alarms: Alarm[]; - birthday_contact_identifier: string | null; - is_birthday: boolean; -}; -export type CalendarChangedEvent = null; -export type CalendarColor = { - red: number; - green: number; - blue: number; - alpha: number; -}; -export type CalendarEntityType = "Event" | "Reminder"; -export type CalendarRef = { id: string; title: string }; -export type CalendarSource = { - identifier: string; - title: string; - source_type: CalendarSourceType; -}; -export type CalendarSourceType = - | "Local" - | "Exchange" - | "CalDav" - | "MobileMe" - | "Subscribed" - | "Birthdays"; -export type CalendarType = "Local" | "CalDav" | "Exchange" | "Subscription" | "Birthday"; -export type EventAvailability = "NotSupported" | "Busy" | "Free" | "Tentative" | "Unavailable"; -export type EventFilter = { - from: string; - to: string; - calendar_tracking_id: string; -}; -export type EventStatus = "None" | "Confirmed" | "Tentative" | "Canceled"; -export type GeoLocation = { latitude: number; longitude: number }; -export type Participant = { - name: string | null; - email: string | null; - is_current_user: boolean; - role: ParticipantRole; - status: ParticipantStatus; - participant_type: ParticipantType; - schedule_status: ParticipantScheduleStatus | null; - url: string | null; - contact: ParticipantContact | null; -}; -export type ParticipantContact = { - identifier: string; - given_name: string | null; - family_name: string | null; - middle_name: string | null; - organization_name: string | null; - job_title: string | null; - email_addresses: string[]; - phone_numbers: string[]; - url_addresses: string[]; - image_available: boolean; -}; -export type ParticipantRole = "Unknown" | "Required" | "Optional" | "Chair" | "NonParticipant"; -export type ParticipantScheduleStatus = - | "None" - | "Pending" - | "Sent" - | "Delivered" - | "RecipientNotRecognized" - | "NoPrivileges" - | "DeliveryFailed" - | "CannotDeliver"; -export type ParticipantStatus = - | "Unknown" - | "Pending" - | "Accepted" - | "Declined" - | "Tentative" - | "Delegated" - | "Completed" - | "InProgress"; -export type ParticipantType = "Unknown" | "Person" | "Room" | "Resource" | "Group"; -export type RecurrenceDayOfWeek = { - weekday: Weekday; - week_number: number | null; -}; -export type RecurrenceEnd = { Count: number } | { Until: string }; -export type RecurrenceFrequency = "Daily" | "Weekly" | "Monthly" | "Yearly"; -export type RecurrenceInfo = { - series_identifier: string; - has_recurrence_rules: boolean; - occurrence: RecurrenceOccurrence | null; - rules: RecurrenceRule[]; -}; -export type RecurrenceOccurrence = { - original_start: string; - is_detached: boolean; -}; -export type RecurrenceRule = { - frequency: RecurrenceFrequency; - interval: number; - days_of_week: RecurrenceDayOfWeek[]; - days_of_month: number[]; - months_of_year: number[]; - weeks_of_year: number[]; - days_of_year: number[]; - set_positions: number[]; - first_day_of_week: Weekday | null; - end: RecurrenceEnd | null; -}; -export type StructuredLocation = { - title: string; - geo: GeoLocation | null; - radius: number | null; -}; -export type Weekday = - | "Sunday" - | "Monday" - | "Tuesday" - | "Wednesday" - | "Thursday" - | "Friday" - | "Saturday"; +export type Alarm = { absolute_date: string | null; relative_offset: number | null; proximity: AlarmProximity | null; alarm_type: AlarmType | null; email_address: string | null; sound_name: string | null; url: string | null; structured_location: StructuredLocation | null } +export type AlarmProximity = "None" | "Enter" | "Leave" +export type AlarmType = "Display" | "Audio" | "Procedure" | "Email" +export type AppleCalendar = { id: string; title: string; calendar_type: CalendarType; color: CalendarColor | null; allows_content_modifications: boolean; is_immutable: boolean; is_subscribed: boolean; supported_event_availabilities: EventAvailability[]; allowed_entity_types: CalendarEntityType[]; source: CalendarSource } +export type AppleEvent = { event_identifier: string; calendar_item_identifier: string; external_identifier: string; calendar: CalendarRef; title: string; location: string | null; url: string | null; notes: string | null; creation_date: string | null; last_modified_date: string | null; time_zone: string | null; start_date: string; end_date: string; is_all_day: boolean; availability: EventAvailability; status: EventStatus; has_alarms: boolean; has_attendees: boolean; has_notes: boolean; has_recurrence_rules: boolean; organizer: Participant | null; attendees: Participant[]; structured_location: StructuredLocation | null; recurrence: RecurrenceInfo | null; occurrence_date: string | null; is_detached: boolean; alarms: Alarm[]; birthday_contact_identifier: string | null; is_birthday: boolean } +export type CalendarChangedEvent = null +export type CalendarColor = { red: number; green: number; blue: number; alpha: number } +export type CalendarEntityType = "Event" | "Reminder" +export type CalendarRef = { id: string; title: string } +export type CalendarSource = { identifier: string; title: string; source_type: CalendarSourceType } +export type CalendarSourceType = "Local" | "Exchange" | "CalDav" | "MobileMe" | "Subscribed" | "Birthdays" +export type CalendarType = "Local" | "CalDav" | "Exchange" | "Subscription" | "Birthday" +export type EventAvailability = "NotSupported" | "Busy" | "Free" | "Tentative" | "Unavailable" +export type EventFilter = { from: string; to: string; calendar_tracking_id: string } +export type EventStatus = "None" | "Confirmed" | "Tentative" | "Canceled" +export type GeoLocation = { latitude: number; longitude: number } +export type Participant = { name: string | null; email: string | null; is_current_user: boolean; role: ParticipantRole; status: ParticipantStatus; participant_type: ParticipantType; schedule_status: ParticipantScheduleStatus | null; url: string | null; contact: ParticipantContact | null } +export type ParticipantContact = { identifier: string; given_name: string | null; family_name: string | null; middle_name: string | null; organization_name: string | null; job_title: string | null; email_addresses: string[]; phone_numbers: string[]; url_addresses: string[]; image_available: boolean } +export type ParticipantRole = "Unknown" | "Required" | "Optional" | "Chair" | "NonParticipant" +export type ParticipantScheduleStatus = "None" | "Pending" | "Sent" | "Delivered" | "RecipientNotRecognized" | "NoPrivileges" | "DeliveryFailed" | "CannotDeliver" +export type ParticipantStatus = "Unknown" | "Pending" | "Accepted" | "Declined" | "Tentative" | "Delegated" | "Completed" | "InProgress" +export type ParticipantType = "Unknown" | "Person" | "Room" | "Resource" | "Group" +export type RecurrenceDayOfWeek = { weekday: Weekday; week_number: number | null } +export type RecurrenceEnd = { Count: number } | { Until: string } +export type RecurrenceFrequency = "Daily" | "Weekly" | "Monthly" | "Yearly" +export type RecurrenceInfo = { series_identifier: string; has_recurrence_rules: boolean; occurrence: RecurrenceOccurrence | null; rules: RecurrenceRule[] } +export type RecurrenceOccurrence = { original_start: string; is_detached: boolean } +export type RecurrenceRule = { frequency: RecurrenceFrequency; interval: number; days_of_week: RecurrenceDayOfWeek[]; days_of_month: number[]; months_of_year: number[]; weeks_of_year: number[]; days_of_year: number[]; set_positions: number[]; first_day_of_week: Weekday | null; end: RecurrenceEnd | null } +export type StructuredLocation = { title: string; geo: GeoLocation | null; radius: number | null } +export type Weekday = "Sunday" | "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" + +/** tauri-specta globals **/ + +import { + invoke as TAURI_INVOKE, + Channel as TAURI_CHANNEL, +} from "@tauri-apps/api/core"; +import * as TAURI_API_EVENT from "@tauri-apps/api/event"; +import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - emit: null extends T - ? (payload?: T) => ReturnType - : (payload: T) => ReturnType; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + emit: null extends T + ? (payload?: T) => ReturnType + : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { - return new Proxy( - {} as unknown as { - [K in keyof T]: __EventObj__ & { - (handle: __WebviewWindow__): __EventObj__; - }; - }, - { - get: (_, event) => { - const name = mappings[event as keyof T]; +function __makeEvents__>( + mappings: Record, +) { + return new Proxy( + {} as unknown as { + [K in keyof T]: __EventObj__ & { + (handle: __WebviewWindow__): __EventObj__; + }; + }, + { + get: (_, event) => { + const name = mappings[event as keyof T]; - return new Proxy((() => {}) as any, { - apply: (_, __, [window]: [__WebviewWindow__]) => ({ - listen: (arg: any) => window.listen(name, arg), - once: (arg: any) => window.once(name, arg), - emit: (arg: any) => window.emit(name, arg), - }), - get: (_, command: keyof __EventObj__) => { - switch (command) { - case "listen": - return (arg: any) => TAURI_API_EVENT.listen(name, arg); - case "once": - return (arg: any) => TAURI_API_EVENT.once(name, arg); - case "emit": - return (arg: any) => TAURI_API_EVENT.emit(name, arg); - } - }, - }); - }, - }, - ); + return new Proxy((() => {}) as any, { + apply: (_, __, [window]: [__WebviewWindow__]) => ({ + listen: (arg: any) => window.listen(name, arg), + once: (arg: any) => window.once(name, arg), + emit: (arg: any) => window.emit(name, arg), + }), + get: (_, command: keyof __EventObj__) => { + switch (command) { + case "listen": + return (arg: any) => TAURI_API_EVENT.listen(name, arg); + case "once": + return (arg: any) => TAURI_API_EVENT.once(name, arg); + case "emit": + return (arg: any) => TAURI_API_EVENT.emit(name, arg); + } + }, + }); + }, + }, + ); } diff --git a/plugins/apple-calendar/src/apple.rs b/plugins/apple-calendar/src/apple.rs index a9e07756bc..4cbeaa8cc5 100644 --- a/plugins/apple-calendar/src/apple.rs +++ b/plugins/apple-calendar/src/apple.rs @@ -4,23 +4,23 @@ use block2::RcBlock; use itertools::Itertools; use objc2::{AllocAnyThread, msg_send, rc::Retained, runtime::Bool}; -use objc2_contacts::{CNContactStore, CNEntityType}; +use objc2_core_graphics::CGColor; use objc2_event_kit::{ EKAlarm, EKAuthorizationStatus, EKCalendar, EKCalendarType, EKEntityType, EKEvent, EKEventAvailability, EKEventStatus, EKEventStore, EKParticipant, EKParticipantRole, EKParticipantStatus, EKParticipantType, EKSourceType, EKStructuredLocation, }; use objc2_foundation::{ - NSArray, NSDate, NSInteger, NSNotification, NSNotificationCenter, NSObject, NSPredicate, - NSString, NSTimeZone, NSURL, + NSArray, NSDate, NSInteger, NSNotification, NSNotificationCenter, NSObject, NSString, + NSTimeZone, NSURL, }; +use crate::contact_resolver; use crate::error::Error; use crate::model::{ Alarm, AlarmProximity, AlarmType, AppleCalendar, AppleEvent, CalendarColor, CalendarEntityType, CalendarRef, CalendarSource, CalendarSourceType, CalendarType, EventAvailability, EventStatus, - Participant, ParticipantContact, ParticipantRole, ParticipantScheduleStatus, ParticipantStatus, - ParticipantType, StructuredLocation, + Participant, ParticipantRole, ParticipantStatus, ParticipantType, StructuredLocation, }; use crate::recurrence::{offset_date_time_from, parse_recurrence_info}; use crate::types::EventFilter; @@ -77,19 +77,20 @@ pub struct Handle { event_store: Retained, } -#[allow(clippy::new_without_default)] -impl Handle { - pub fn new() -> Self { +impl Default for Handle { + fn default() -> Self { let event_store = unsafe { EKEventStore::new() }; Self { event_store } } +} +impl Handle { fn has_calendar_access(&self) -> bool { let status = unsafe { EKEventStore::authorizationStatusForEntityType(EKEntityType::Event) }; matches!(status, EKAuthorizationStatus::FullAccess) } - fn fetch_events(&self, filter: &EventFilter) -> Retained> { + fn fetch_events(&self, filter: &EventFilter) -> Result>, Error> { let calendars: Retained> = unsafe { self.event_store.calendars() } .into_iter() .filter(|c| { @@ -99,8 +100,11 @@ impl Handle { .collect(); if calendars.is_empty() { - let empty_array: Retained> = NSArray::new(); - return empty_array; + return Err(Error::CalendarNotFound); + } + + if filter.from > filter.to { + return Err(Error::InvalidDateRange); } let (start_date, end_date) = [filter.from, filter.to] @@ -108,7 +112,7 @@ impl Handle { .sorted_by(|a, b| a.cmp(b)) .map(|v| NSDate::initWithTimeIntervalSince1970(NSDate::alloc(), v.timestamp() as f64)) .collect_tuple() - .unwrap(); + .ok_or_else(|| Error::InvalidDateRange)?; let predicate = unsafe { self.event_store @@ -119,7 +123,7 @@ impl Handle { ) }; - unsafe { self.event_store.eventsMatchingPredicate(&predicate) } + Ok(unsafe { self.event_store.eventsMatchingPredicate(&predicate) }) } pub fn list_calendars(&self) -> Result, Error> { @@ -143,8 +147,9 @@ impl Handle { return Err(Error::CalendarAccessDenied); } - let events = self - .fetch_events(&filter) + let events_array = self.fetch_events(&filter)?; + + let events: Result, _> = events_array .iter() .filter_map(|event| { let calendar = unsafe { event.calendar() }?; @@ -156,9 +161,11 @@ impl Handle { Some(transform_event(&event)) }) - .sorted_by(|a, b| a.start_date.cmp(&b.start_date)) .collect(); + let mut events = events?; + events.sort_by(|a, b| a.start_date.cmp(&b.start_date)); + Ok(events) } } @@ -167,24 +174,43 @@ fn transform_calendar(calendar: &EKCalendar) -> AppleCalendar { let id = unsafe { calendar.calendarIdentifier() }.to_string(); let title = unsafe { calendar.title() }.to_string(); let calendar_type = transform_calendar_type(unsafe { calendar.r#type() }); + let color = unsafe { calendar.CGColor() }.map(|cg_color| extract_color_components(&cg_color)); - let color = unsafe { - let cg_color: *const std::ffi::c_void = msg_send![calendar, CGColor]; - if cg_color.is_null() { - None - } else { - extract_color_components(cg_color) - } - }; + let properties = extract_calendar_properties(calendar); + AppleCalendar { + id, + title, + calendar_type, + color, + ..properties + } +} + +fn extract_calendar_properties(calendar: &EKCalendar) -> AppleCalendar { let allows_content_modifications = unsafe { calendar.allowsContentModifications() }; let is_immutable = unsafe { calendar.isImmutable() }; let is_subscribed = unsafe { calendar.isSubscribed() }; - let supported_event_availabilities = extract_supported_availabilities(calendar); let allowed_entity_types = extract_allowed_entity_types(calendar); + let source = extract_calendar_source(calendar); + + AppleCalendar { + allows_content_modifications, + is_immutable, + is_subscribed, + supported_event_availabilities, + allowed_entity_types, + source, + id: String::new(), // Will be overridden + title: String::new(), // Will be overridden + calendar_type: CalendarType::Local, // Will be overridden + color: None, // Will be overridden + } +} - let source = if let Some(src) = unsafe { calendar.source() } { +fn extract_calendar_source(calendar: &EKCalendar) -> CalendarSource { + if let Some(src) = unsafe { calendar.source() } { let source_identifier = unsafe { src.sourceIdentifier() }.to_string(); let source_title = unsafe { src.title() }.to_string(); let source_type = transform_source_type(unsafe { src.sourceType() }); @@ -194,145 +220,241 @@ fn transform_calendar(calendar: &EKCalendar) -> AppleCalendar { source_type, } } else { - CalendarSource { - identifier: String::new(), - title: String::new(), - source_type: CalendarSourceType::Local, - } - }; - - AppleCalendar { - id, - title, - calendar_type, - color, - allows_content_modifications, - is_immutable, - is_subscribed, - supported_event_availabilities, - allowed_entity_types, - source, + CalendarSource::default() } } -fn transform_event(event: &EKEvent) -> AppleEvent { - let event_identifier = unsafe { event.eventIdentifier() } - .map(|s| s.to_string()) - .unwrap_or_default(); - let calendar_item_identifier = unsafe { event.calendarItemIdentifier() }.to_string(); - let external_identifier = unsafe { event.calendarItemExternalIdentifier() } - .map(|s| s.to_string()) - .unwrap_or_default(); +fn transform_event(event: &EKEvent) -> Result { + let identifiers = extract_event_identifiers(event); + let calendar_ref = extract_event_calendar_ref(event); + let basic_info = extract_event_basic_info(event); + let dates = extract_event_dates(event); + let status_info = extract_event_status_info(event); + let flags = extract_event_flags(event); + let participants = extract_event_participants(event); + let location_info = extract_event_location_info(event); + let recurrence_info = extract_event_recurrence_info(event, flags.has_recurrence_rules); + let alarm_info = extract_event_alarm_info(event); + let birthday_info = extract_event_birthday_info(event, &calendar_ref); + + Ok(AppleEvent { + event_identifier: identifiers.event_identifier, + calendar_item_identifier: identifiers.calendar_item_identifier, + external_identifier: identifiers.external_identifier, + calendar: calendar_ref, + title: basic_info.title, + location: basic_info.location, + url: basic_info.url, + notes: basic_info.notes, + creation_date: basic_info.creation_date, + last_modified_date: basic_info.last_modified_date, + time_zone: basic_info.time_zone, + start_date: dates.start_date, + end_date: dates.end_date, + is_all_day: dates.is_all_day, + availability: status_info.availability, + status: status_info.status, + has_alarms: flags.has_alarms, + has_attendees: flags.has_attendees, + has_notes: flags.has_notes, + has_recurrence_rules: flags.has_recurrence_rules, + organizer: participants.organizer, + attendees: participants.attendees, + structured_location: location_info.structured_location, + recurrence: recurrence_info.recurrence, + occurrence_date: recurrence_info.occurrence_date, + is_detached: recurrence_info.is_detached, + alarms: alarm_info.alarms, + birthday_contact_identifier: birthday_info.birthday_contact_identifier, + is_birthday: birthday_info.is_birthday, + }) +} +struct EventIdentifiers { + event_identifier: String, + calendar_item_identifier: String, + external_identifier: String, +} + +fn extract_event_identifiers(event: &EKEvent) -> EventIdentifiers { + EventIdentifiers { + event_identifier: unsafe { event.eventIdentifier() } + .map(|s| s.to_string()) + .unwrap_or_default(), + calendar_item_identifier: unsafe { event.calendarItemIdentifier() }.to_string(), + external_identifier: unsafe { event.calendarItemExternalIdentifier() } + .map(|s| s.to_string()) + .unwrap_or_default(), + } +} + +fn extract_event_calendar_ref(event: &EKEvent) -> CalendarRef { let calendar = unsafe { event.calendar() }.unwrap(); - let calendar_ref = CalendarRef { + CalendarRef { id: unsafe { calendar.calendarIdentifier() }.to_string(), title: unsafe { calendar.title() }.to_string(), - }; + } +} - let title = unsafe { event.title() }.to_string(); - let location = unsafe { event.location() }.map(|s| s.to_string()); - let url = unsafe { - let url_obj: Option> = msg_send![event, URL]; - url_obj.and_then(|u| u.absoluteString().map(|s| s.to_string())) - }; - let notes = unsafe { event.notes() }.map(|s| s.to_string()); +struct EventBasicInfo { + title: String, + location: Option, + url: Option, + notes: Option, + creation_date: Option>, + last_modified_date: Option>, + time_zone: Option, +} - let creation_date = unsafe { - let date: Option> = msg_send![event, creationDate]; - date.map(offset_date_time_from) - }; - let last_modified_date = unsafe { - let date: Option> = msg_send![event, lastModifiedDate]; - date.map(offset_date_time_from) - }; +fn extract_event_basic_info(event: &EKEvent) -> EventBasicInfo { + EventBasicInfo { + title: unsafe { event.title() }.to_string(), + location: unsafe { event.location() }.map(|s| s.to_string()), + url: get_url_string(event, "URL"), + notes: unsafe { event.notes() }.map(|s| s.to_string()), + creation_date: unsafe { + let date: Option> = msg_send![event, creationDate]; + date.map(offset_date_time_from) + }, + last_modified_date: unsafe { + let date: Option> = msg_send![event, lastModifiedDate]; + date.map(offset_date_time_from) + }, + time_zone: unsafe { + let tz: Option> = msg_send![event, timeZone]; + tz.map(|t| t.name().to_string()) + }, + } +} - let time_zone = unsafe { - let tz: Option> = msg_send![event, timeZone]; - tz.map(|t| t.name().to_string()) - }; +struct EventDates { + start_date: chrono::DateTime, + end_date: chrono::DateTime, + is_all_day: bool, +} - let start_date = unsafe { event.startDate() }; - let end_date = unsafe { event.endDate() }; - let is_all_day = unsafe { event.isAllDay() }; +fn extract_event_dates(event: &EKEvent) -> EventDates { + EventDates { + start_date: offset_date_time_from(unsafe { event.startDate() }), + end_date: offset_date_time_from(unsafe { event.endDate() }), + is_all_day: unsafe { event.isAllDay() }, + } +} - let availability = transform_event_availability(unsafe { event.availability() }); - let status = transform_event_status(unsafe { event.status() }); +struct EventStatusInfo { + availability: EventAvailability, + status: EventStatus, +} - let has_alarms: bool = unsafe { - let b: Bool = msg_send![event, hasAlarms]; - b.as_bool() - }; - let has_attendees: bool = unsafe { - let b: Bool = msg_send![event, hasAttendees]; - b.as_bool() - }; - let has_notes: bool = unsafe { - let b: Bool = msg_send![event, hasNotes]; - b.as_bool() - }; - let has_recurrence_rules: bool = unsafe { - let b: Bool = msg_send![event, hasRecurrenceRules]; - b.as_bool() - }; +fn extract_event_status_info(event: &EKEvent) -> EventStatusInfo { + EventStatusInfo { + availability: transform_event_availability(unsafe { event.availability() }), + status: transform_event_status(unsafe { event.status() }), + } +} - let organizer = unsafe { event.organizer() }.map(|p| transform_participant(&p)); - let attendees = unsafe { event.attendees() } - .map(|arr| arr.iter().map(|p| transform_participant(&p)).collect()) - .unwrap_or_default(); +struct EventFlags { + has_alarms: bool, + has_attendees: bool, + has_notes: bool, + has_recurrence_rules: bool, +} - let structured_location = unsafe { - let loc: Option> = msg_send![event, structuredLocation]; - loc.map(|l| transform_structured_location(&l)) - }; +fn extract_event_flags(event: &EKEvent) -> EventFlags { + EventFlags { + has_alarms: unsafe { + let b: Bool = msg_send![event, hasAlarms]; + b.as_bool() + }, + has_attendees: unsafe { + let b: Bool = msg_send![event, hasAttendees]; + b.as_bool() + }, + has_notes: unsafe { + let b: Bool = msg_send![event, hasNotes]; + b.as_bool() + }, + has_recurrence_rules: unsafe { + let b: Bool = msg_send![event, hasRecurrenceRules]; + b.as_bool() + }, + } +} - let recurrence = parse_recurrence_info(event, has_recurrence_rules); +struct EventParticipants { + organizer: Option, + attendees: Vec, +} - let occurrence_date = unsafe { event.occurrenceDate() }.map(offset_date_time_from); - let is_detached = unsafe { event.isDetached() }; +fn extract_event_participants(event: &EKEvent) -> EventParticipants { + EventParticipants { + organizer: unsafe { event.organizer() }.map(|p| transform_participant(&p)), + attendees: unsafe { event.attendees() } + .map(|arr| arr.iter().map(|p| transform_participant(&p)).collect()) + .unwrap_or_default(), + } +} - let alarms = unsafe { - let alarm_arr: Option>> = msg_send![event, alarms]; - alarm_arr - .map(|arr| arr.iter().map(|a| transform_alarm(&a)).collect()) - .unwrap_or_default() - }; +struct EventLocationInfo { + structured_location: Option, +} + +fn extract_event_location_info(event: &EKEvent) -> EventLocationInfo { + EventLocationInfo { + structured_location: unsafe { + let loc: Option> = msg_send![event, structuredLocation]; + loc.map(|l| transform_structured_location(&l)) + }, + } +} + +struct EventRecurrenceInfo { + recurrence: Option, + occurrence_date: Option>, + is_detached: bool, +} + +fn extract_event_recurrence_info( + event: &EKEvent, + has_recurrence_rules: bool, +) -> EventRecurrenceInfo { + EventRecurrenceInfo { + recurrence: parse_recurrence_info(event, has_recurrence_rules), + occurrence_date: unsafe { event.occurrenceDate() }.map(offset_date_time_from), + is_detached: unsafe { event.isDetached() }, + } +} + +struct EventAlarmInfo { + alarms: Vec, +} + +fn extract_event_alarm_info(event: &EKEvent) -> EventAlarmInfo { + EventAlarmInfo { + alarms: unsafe { + let alarm_arr: Option>> = msg_send![event, alarms]; + alarm_arr + .map(|arr| arr.iter().map(|a| transform_alarm(&a)).collect()) + .unwrap_or_default() + }, + } +} + +struct EventBirthdayInfo { + birthday_contact_identifier: Option, + is_birthday: bool, +} +fn extract_event_birthday_info(event: &EKEvent, _calendar_ref: &CalendarRef) -> EventBirthdayInfo { let birthday_contact_identifier = unsafe { let id: Option> = msg_send![event, birthdayContactIdentifier]; id.map(|s| s.to_string()) }; + let is_birthday = birthday_contact_identifier.is_some() - || unsafe { calendar.r#type() } == EKCalendarType::Birthday; + || unsafe { event.calendar().unwrap().r#type() } == EKCalendarType::Birthday; - AppleEvent { - event_identifier, - calendar_item_identifier, - external_identifier, - calendar: calendar_ref, - title, - location, - url, - notes, - creation_date, - last_modified_date, - time_zone, - start_date: offset_date_time_from(start_date), - end_date: offset_date_time_from(end_date), - is_all_day, - availability, - status, - has_alarms, - has_attendees, - has_notes, - has_recurrence_rules, - organizer, - attendees, - structured_location, - recurrence, - occurrence_date, - is_detached, - alarms, + EventBirthdayInfo { birthday_contact_identifier, is_birthday, } @@ -345,17 +467,15 @@ fn transform_participant(participant: &EKParticipant) -> Participant { let role = transform_participant_role(unsafe { participant.participantRole() }); let status = transform_participant_status(unsafe { participant.participantStatus() }); let participant_type = transform_participant_type(unsafe { participant.participantType() }); - let schedule_status = unsafe { - let status: NSInteger = msg_send![participant, participantScheduleStatus]; - transform_participant_schedule_status(status) - }; + let schedule_status = contact_resolver::safe_participant_schedule_status(participant); let url = unsafe { let url_obj: Option> = msg_send![participant, URL]; url_obj.and_then(|u| u.absoluteString().map(|s| s.to_string())) }; - let (email, contact) = resolve_participant_contact(participant, url.as_deref()); + let (email, contact) = + contact_resolver::resolve_participant_contact(participant, url.as_deref()); Participant { name, @@ -370,150 +490,6 @@ fn transform_participant(participant: &EKParticipant) -> Participant { } } -fn resolve_participant_contact( - participant: &EKParticipant, - url: Option<&str>, -) -> (Option, Option) { - if let Some(contact) = try_fetch_contact(participant) { - let email = contact.email_addresses.first().cloned(); - return (email, Some(contact)); - } - - let email = parse_email_from_url(url); - (email, None) -} - -fn try_fetch_contact(participant: &EKParticipant) -> Option { - if !has_contacts_access() { - return None; - } - - let predicate: Retained = unsafe { participant.contactPredicate() }; - let contact_store = unsafe { CNContactStore::new() }; - - let keys_to_fetch: Retained> = NSArray::from_slice(&[ - &*NSString::from_str("identifier"), - &*NSString::from_str("givenName"), - &*NSString::from_str("familyName"), - &*NSString::from_str("middleName"), - &*NSString::from_str("organizationName"), - &*NSString::from_str("jobTitle"), - &*NSString::from_str("emailAddresses"), - &*NSString::from_str("phoneNumbers"), - &*NSString::from_str("urlAddresses"), - &*NSString::from_str("imageDataAvailable"), - ]); - - let contacts: Option>> = unsafe { - msg_send![ - &*contact_store, - unifiedContactsMatchingPredicate: &*predicate, - keysToFetch: &*keys_to_fetch, - error: std::ptr::null_mut::<*mut objc2_foundation::NSError>() - ] - }; - - let contacts = contacts?; - let contact = contacts.iter().next()?; - - let identifier = unsafe { - let id: Retained = msg_send![&*contact, identifier]; - id.to_string() - }; - - let given_name = get_optional_string(&contact, "givenName"); - let family_name = get_optional_string(&contact, "familyName"); - let middle_name = get_optional_string(&contact, "middleName"); - let organization_name = get_optional_string(&contact, "organizationName"); - let job_title = get_optional_string(&contact, "jobTitle"); - - let email_addresses = extract_labeled_string_values(&contact, "emailAddresses"); - let phone_numbers = extract_phone_numbers(&contact); - let url_addresses = extract_labeled_string_values(&contact, "urlAddresses"); - - let image_available: bool = unsafe { - let b: Bool = msg_send![&*contact, imageDataAvailable]; - b.as_bool() - }; - - Some(ParticipantContact { - identifier, - given_name, - family_name, - middle_name, - organization_name, - job_title, - email_addresses, - phone_numbers, - url_addresses, - image_available, - }) -} - -fn has_contacts_access() -> bool { - let status = - unsafe { CNContactStore::authorizationStatusForEntityType(CNEntityType::Contacts) }; - status.0 == 3 // CNAuthorizationStatus::Authorized -} - -fn get_optional_string(contact: &Retained, key: &str) -> Option { - unsafe { - let value: Option> = - msg_send![&**contact, valueForKey: &*NSString::from_str(key)]; - value.filter(|s| !s.is_empty()).map(|s| s.to_string()) - } -} - -fn extract_labeled_string_values( - contact: &Retained, - key: &str, -) -> Vec { - unsafe { - let labeled_values: Option> = - msg_send![&**contact, valueForKey: &*NSString::from_str(key)]; - labeled_values - .map(|arr| { - arr.iter() - .filter_map(|lv| { - let value: Option> = msg_send![&*lv, value]; - value.map(|s| s.to_string()) - }) - .collect() - }) - .unwrap_or_default() - } -} - -fn extract_phone_numbers(contact: &Retained) -> Vec { - unsafe { - let labeled_values: Option> = - msg_send![&**contact, valueForKey: &*NSString::from_str("phoneNumbers")]; - labeled_values - .map(|arr| { - arr.iter() - .filter_map(|lv| { - let phone: Option> = - msg_send![&*lv, value]; - phone.and_then(|p| { - let digits: Option> = msg_send![&*p, stringValue]; - digits.map(|s| s.to_string()) - }) - }) - .collect() - }) - .unwrap_or_default() - } -} - -fn parse_email_from_url(url: Option<&str>) -> Option { - let url = url?; - if url.starts_with("mailto:") { - Some(url.trim_start_matches("mailto:").to_string()) - } else { - None - } -} - fn transform_alarm(alarm: &EKAlarm) -> Alarm { let absolute_date = unsafe { let date: Option> = msg_send![alarm, absoluteDate]; @@ -679,23 +655,46 @@ fn transform_participant_type(t: EKParticipantType) -> ParticipantType { } } -fn transform_participant_schedule_status(status: NSInteger) -> Option { - match status { - 0 => Some(ParticipantScheduleStatus::None), - 1 => Some(ParticipantScheduleStatus::Pending), - 2 => Some(ParticipantScheduleStatus::Sent), - 3 => Some(ParticipantScheduleStatus::Delivered), - 4 => Some(ParticipantScheduleStatus::RecipientNotRecognized), - 5 => Some(ParticipantScheduleStatus::NoPrivileges), - 6 => Some(ParticipantScheduleStatus::DeliveryFailed), - 7 => Some(ParticipantScheduleStatus::CannotDeliver), - _ => None, +#[allow(unused_variables)] +fn extract_color_components(cg_color: &CGColor) -> CalendarColor { + let num_components = CGColor::number_of_components(Some(cg_color)); + let components_ptr = CGColor::components(Some(cg_color)); + let alpha = CGColor::alpha(Some(cg_color)) as f32; + + if components_ptr.is_null() || num_components < 1 { + return CalendarColor { + red: 0.5, + green: 0.5, + blue: 0.5, + alpha: 1.0, + }; } -} -#[allow(unused_variables)] -fn extract_color_components(cg_color: *const std::ffi::c_void) -> Option { - None + let components = unsafe { std::slice::from_raw_parts(components_ptr, num_components) }; + + match num_components { + 2 => { + let gray = components[0] as f32; + CalendarColor { + red: gray, + green: gray, + blue: gray, + alpha, + } + } + 3 | 4 => CalendarColor { + red: components[0] as f32, + green: components[1] as f32, + blue: components[2] as f32, + alpha, + }, + _ => CalendarColor { + red: 0.5, + green: 0.5, + blue: 0.5, + alpha: 1.0, + }, + } } fn extract_supported_availabilities(calendar: &EKCalendar) -> Vec { @@ -734,3 +733,15 @@ fn extract_allowed_entity_types(calendar: &EKCalendar) -> Vec(obj: &T, selector: &str) -> Option +where + T: objc2::Message + ?Sized, +{ + unsafe { + let sel = objc2::sel!(selector); + let url_obj: Option> = msg_send![obj, sel]; + url_obj.and_then(|u| u.absoluteString().map(|s| s.to_string())) + } +} diff --git a/plugins/apple-calendar/src/contact_resolver.rs b/plugins/apple-calendar/src/contact_resolver.rs new file mode 100644 index 0000000000..2e1bbdca3a --- /dev/null +++ b/plugins/apple-calendar/src/contact_resolver.rs @@ -0,0 +1,184 @@ +use std::panic::AssertUnwindSafe; + +use objc2::{msg_send, rc::Retained, runtime::Bool}; +use objc2_contacts::{CNContact, CNContactStore, CNEntityType, CNPhoneNumber}; +use objc2_foundation::{NSArray, NSError, NSString}; + +use crate::model::{ParticipantContact, ParticipantScheduleStatus}; + +pub fn resolve_participant_contact( + participant: &objc2_event_kit::EKParticipant, + url: Option<&str>, +) -> (Option, Option) { + if let Some(contact) = try_fetch_contact(participant) { + let email = contact.email_addresses.first().cloned(); + return (email, Some(contact)); + } + + let email = parse_email_from_url(url); + (email, None) +} + +fn try_fetch_contact(participant: &objc2_event_kit::EKParticipant) -> Option { + if !has_contacts_access() { + return None; + } + + let participant = AssertUnwindSafe(participant); + let predicate: Retained = + match unsafe { objc2::exception::catch(|| participant.contactPredicate()) } { + Ok(p) => p, + Err(_) => return None, + }; + + let contact_store = unsafe { CNContactStore::new() }; + + let keys_to_fetch: Retained> = NSArray::from_slice(&[ + &*NSString::from_str("identifier"), + &*NSString::from_str("givenName"), + &*NSString::from_str("familyName"), + &*NSString::from_str("middleName"), + &*NSString::from_str("organizationName"), + &*NSString::from_str("jobTitle"), + &*NSString::from_str("emailAddresses"), + &*NSString::from_str("phoneNumbers"), + &*NSString::from_str("urlAddresses"), + &*NSString::from_str("imageDataAvailable"), + ]); + + let contacts: Option>> = unsafe { + msg_send![ + &*contact_store, + unifiedContactsMatchingPredicate: &*predicate, + keysToFetch: &*keys_to_fetch, + error: std::ptr::null_mut::<*mut NSError>() + ] + }; + + let contacts = contacts?; + let contact = contacts.iter().next()?; + + let identifier = unsafe { + let id: Retained = msg_send![&*contact, identifier]; + id.to_string() + }; + + let given_name = get_optional_string(&contact, "givenName"); + let family_name = get_optional_string(&contact, "familyName"); + let middle_name = get_optional_string(&contact, "middleName"); + let organization_name = get_optional_string(&contact, "organizationName"); + let job_title = get_optional_string(&contact, "jobTitle"); + + let email_addresses = extract_labeled_string_values(&contact, "emailAddresses"); + let phone_numbers = extract_phone_numbers(&contact); + let url_addresses = extract_labeled_string_values(&contact, "urlAddresses"); + + let image_available: bool = unsafe { + let b: Bool = msg_send![&*contact, imageDataAvailable]; + b.as_bool() + }; + + Some(ParticipantContact { + identifier, + given_name, + family_name, + middle_name, + organization_name, + job_title, + email_addresses, + phone_numbers, + url_addresses, + image_available, + }) +} + +fn has_contacts_access() -> bool { + let status = + unsafe { CNContactStore::authorizationStatusForEntityType(CNEntityType::Contacts) }; + status.0 == 3 // CNAuthorizationStatus::Authorized +} + +fn get_optional_string(contact: &Retained, key: &str) -> Option { + unsafe { + let value: Option> = + msg_send![&**contact, valueForKey: &*NSString::from_str(key)]; + value.filter(|s| !s.is_empty()).map(|s| s.to_string()) + } +} + +fn extract_labeled_string_values(contact: &Retained, key: &str) -> Vec { + unsafe { + let labeled_values: Option> = + msg_send![&**contact, valueForKey: &*NSString::from_str(key)]; + labeled_values + .map(|arr| { + arr.iter() + .filter_map(|lv| { + let value: Option> = msg_send![&*lv, value]; + value.map(|s| s.to_string()) + }) + .collect() + }) + .unwrap_or_default() + } +} + +fn extract_phone_numbers(contact: &Retained) -> Vec { + unsafe { + let labeled_values: Option> = + msg_send![&**contact, valueForKey: &*NSString::from_str("phoneNumbers")]; + labeled_values + .map(|arr| { + arr.iter() + .filter_map(|lv| { + let phone: Option> = msg_send![&*lv, value]; + phone.and_then(|p| { + let digits: Option> = msg_send![&*p, stringValue]; + digits.map(|s| s.to_string()) + }) + }) + .collect() + }) + .unwrap_or_default() + } +} + +fn parse_email_from_url(url: Option<&str>) -> Option { + let url = url?; + if url.starts_with("mailto:") { + Some(url.trim_start_matches("mailto:").to_string()) + } else { + None + } +} + +pub fn safe_participant_schedule_status( + participant: &objc2_event_kit::EKParticipant, +) -> Option { + let participant = AssertUnwindSafe(participant); + let result = objc2::exception::catch(|| unsafe { + let raw: objc2_foundation::NSInteger = msg_send![*participant, participantScheduleStatus]; + raw + }); + + match result { + Ok(raw) => transform_participant_schedule_status(raw), + Err(_) => None, + } +} + +fn transform_participant_schedule_status( + status: objc2_foundation::NSInteger, +) -> Option { + match status { + 0 => Some(ParticipantScheduleStatus::None), + 1 => Some(ParticipantScheduleStatus::Pending), + 2 => Some(ParticipantScheduleStatus::Sent), + 3 => Some(ParticipantScheduleStatus::Delivered), + 4 => Some(ParticipantScheduleStatus::RecipientNotRecognized), + 5 => Some(ParticipantScheduleStatus::NoPrivileges), + 6 => Some(ParticipantScheduleStatus::DeliveryFailed), + 7 => Some(ParticipantScheduleStatus::CannotDeliver), + _ => None, + } +} diff --git a/plugins/apple-calendar/src/error.rs b/plugins/apple-calendar/src/error.rs index 791615e524..70ecad49c1 100644 --- a/plugins/apple-calendar/src/error.rs +++ b/plugins/apple-calendar/src/error.rs @@ -8,6 +8,22 @@ pub enum Error { CalendarAccessDenied, #[error("contacts access denied")] ContactsAccessDenied, + #[error("event not found")] + EventNotFound, + #[error("calendar not found")] + CalendarNotFound, + #[error("invalid date range")] + InvalidDateRange, + #[error("objective-c exception: {0}")] + ObjectiveCException(String), + #[error("transform error: {0}")] + TransformError(String), + #[error("permission denied: {0}")] + PermissionDenied(String), + #[error("io error: {0}")] + IoError(#[from] std::io::Error), + #[error("serde error: {0}")] + SerdeError(#[from] serde_json::Error), } impl Serialize for Error { diff --git a/plugins/apple-calendar/src/ext.rs b/plugins/apple-calendar/src/ext.rs index f92fb24a3c..20596c64ef 100644 --- a/plugins/apple-calendar/src/ext.rs +++ b/plugins/apple-calendar/src/ext.rs @@ -34,13 +34,13 @@ impl> crate::AppleCalendarPluginExt f #[tracing::instrument(skip_all)] fn list_calendars(&self) -> Result, String> { - let handle = crate::apple::Handle::new(); + let handle = crate::apple::Handle::default(); handle.list_calendars().map_err(|e| e.to_string()) } #[tracing::instrument(skip_all)] fn list_events(&self, filter: EventFilter) -> Result, String> { - let handle = crate::apple::Handle::new(); + let handle = crate::apple::Handle::default(); handle.list_events(filter).map_err(|e| e.to_string()) } } diff --git a/plugins/apple-calendar/src/lib.rs b/plugins/apple-calendar/src/lib.rs index bf06ba8936..68cde1e7a7 100644 --- a/plugins/apple-calendar/src/lib.rs +++ b/plugins/apple-calendar/src/lib.rs @@ -3,6 +3,8 @@ use tauri::Manager; #[cfg(target_os = "macos")] mod apple; #[cfg(target_os = "macos")] +mod contact_resolver; +#[cfg(target_os = "macos")] mod recurrence; mod commands; @@ -84,7 +86,38 @@ mod test { } #[test] - fn test_apple_calendar() { - let _app = create_app(tauri::test::mock_builder()); + fn test_list_calendars() { + let app = create_app(tauri::test::mock_builder()); + + let calendars = app.list_calendars(); + println!("calendars: {:?}", calendars); + } + + #[test] + fn test_list_events() { + let app = create_app(tauri::test::mock_builder()); + + // First test with a simple calendar that should exist + match app.list_calendars() { + Ok(calendars) => { + if let Some(calendar) = calendars.first() { + println!( + "Testing with calendar: {} ({})", + calendar.title, calendar.id + ); + let events = app.list_events(EventFilter { + from: chrono::Utc::now(), + to: chrono::Utc::now() + chrono::Duration::days(7), + calendar_tracking_id: calendar.id.clone(), + }); + println!("events: {:?}", events); + } else { + println!("No calendars found"); + } + } + Err(e) => { + println!("Error listing calendars: {:?}", e); + } + } } } diff --git a/plugins/apple-calendar/src/model.rs b/plugins/apple-calendar/src/model.rs index e3e347cba1..da77214f87 100644 --- a/plugins/apple-calendar/src/model.rs +++ b/plugins/apple-calendar/src/model.rs @@ -66,6 +66,16 @@ common_derives! { } } +impl Default for CalendarSource { + fn default() -> Self { + Self { + identifier: String::new(), + title: String::new(), + source_type: CalendarSourceType::Local, + } + } +} + common_derives! { pub struct AppleCalendar { pub id: String, diff --git a/plugins/auth/.gitignore b/plugins/auth/.gitignore deleted file mode 100644 index 50d8e32e89..0000000000 --- a/plugins/auth/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -/.vs -.DS_Store -.Thumbs.db -*.sublime* -.idea/ -debug.log -package-lock.json -.vscode/settings.json -yarn.lock - -/.tauri -/target -Cargo.lock -node_modules/ - -dist-js -dist diff --git a/plugins/auth/Cargo.toml b/plugins/auth/Cargo.toml deleted file mode 100644 index 84c66d7cf9..0000000000 --- a/plugins/auth/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "tauri-plugin-auth" -version = "0.1.0" -authors = ["You"] -edition = "2024" -exclude = ["/js", "/node_modules"] -links = "tauri-plugin-auth" -description = "" - -[build-dependencies] -tauri-plugin = { workspace = true, features = ["build"] } - -[dev-dependencies] -specta-typescript = { workspace = true } -tauri-plugin-store = { workspace = true } -tokio = { workspace = true, features = ["macros"] } - -[dependencies] -tauri-plugin-store2 = { workspace = true } - -tauri = { workspace = true, features = ["test"] } -tauri-specta = { workspace = true, features = ["derive", "typescript"] } - -serde = { workspace = true } -serde_json = { workspace = true } -specta = { workspace = true } -strum = { workspace = true, features = ["derive"] } - -thiserror = { workspace = true } diff --git a/plugins/auth/build.rs b/plugins/auth/build.rs deleted file mode 100644 index 72a2d54857..0000000000 --- a/plugins/auth/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -const COMMANDS: &[&str] = &["get_item", "set_item", "remove_item", "clear"]; - -fn main() { - tauri_plugin::Builder::new(COMMANDS).build(); -} diff --git a/plugins/auth/js/bindings.gen.ts b/plugins/auth/js/bindings.gen.ts deleted file mode 100644 index fd063cab02..0000000000 --- a/plugins/auth/js/bindings.gen.ts +++ /dev/null @@ -1,99 +0,0 @@ -// @ts-nocheck -/** user-defined events **/ -/** user-defined constants **/ -/** user-defined types **/ -/** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; -import * as TAURI_API_EVENT from "@tauri-apps/api/event"; -import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; - -// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. - -/** user-defined commands **/ - -export const commands = { - async getItem(key: string): Promise> { - try { - return { - status: "ok", - data: await TAURI_INVOKE("plugin:auth|get_item", { key }), - }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async setItem(key: string, value: string): Promise> { - try { - return { - status: "ok", - data: await TAURI_INVOKE("plugin:auth|set_item", { key, value }), - }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async removeItem(key: string): Promise> { - try { - return { - status: "ok", - data: await TAURI_INVOKE("plugin:auth|remove_item", { key }), - }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async clear(): Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("plugin:auth|clear") }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, -}; - -type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - emit: null extends T - ? (payload?: T) => ReturnType - : (payload: T) => ReturnType; -}; - -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; - -function __makeEvents__>(mappings: Record) { - return new Proxy( - {} as unknown as { - [K in keyof T]: __EventObj__ & { - (handle: __WebviewWindow__): __EventObj__; - }; - }, - { - get: (_, event) => { - const name = mappings[event as keyof T]; - - return new Proxy((() => {}) as any, { - apply: (_, __, [window]: [__WebviewWindow__]) => ({ - listen: (arg: any) => window.listen(name, arg), - once: (arg: any) => window.once(name, arg), - emit: (arg: any) => window.emit(name, arg), - }), - get: (_, command: keyof __EventObj__) => { - switch (command) { - case "listen": - return (arg: any) => TAURI_API_EVENT.listen(name, arg); - case "once": - return (arg: any) => TAURI_API_EVENT.once(name, arg); - case "emit": - return (arg: any) => TAURI_API_EVENT.emit(name, arg); - } - }, - }); - }, - }, - ); -} diff --git a/plugins/auth/js/index.ts b/plugins/auth/js/index.ts deleted file mode 100644 index a96e122f03..0000000000 --- a/plugins/auth/js/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./bindings.gen"; diff --git a/plugins/auth/package.json b/plugins/auth/package.json deleted file mode 100644 index c8c9c88402..0000000000 --- a/plugins/auth/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@hypr/plugin-auth", - "private": true, - "main": "./js/index.ts", - "scripts": { - "codegen": "cargo test -p tauri-plugin-auth" - }, - "dependencies": { - "@tauri-apps/api": "^2.9.1" - } -} diff --git a/plugins/auth/permissions/autogenerated/commands/clear.toml b/plugins/auth/permissions/autogenerated/commands/clear.toml deleted file mode 100644 index 83de181923..0000000000 --- a/plugins/auth/permissions/autogenerated/commands/clear.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-clear" -description = "Enables the clear command without any pre-configured scope." -commands.allow = ["clear"] - -[[permission]] -identifier = "deny-clear" -description = "Denies the clear command without any pre-configured scope." -commands.deny = ["clear"] diff --git a/plugins/auth/permissions/autogenerated/commands/get_item.toml b/plugins/auth/permissions/autogenerated/commands/get_item.toml deleted file mode 100644 index 71d5241e7b..0000000000 --- a/plugins/auth/permissions/autogenerated/commands/get_item.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-get-item" -description = "Enables the get_item command without any pre-configured scope." -commands.allow = ["get_item"] - -[[permission]] -identifier = "deny-get-item" -description = "Denies the get_item command without any pre-configured scope." -commands.deny = ["get_item"] diff --git a/plugins/auth/permissions/autogenerated/commands/remove_item.toml b/plugins/auth/permissions/autogenerated/commands/remove_item.toml deleted file mode 100644 index 2337773adc..0000000000 --- a/plugins/auth/permissions/autogenerated/commands/remove_item.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-remove-item" -description = "Enables the remove_item command without any pre-configured scope." -commands.allow = ["remove_item"] - -[[permission]] -identifier = "deny-remove-item" -description = "Denies the remove_item command without any pre-configured scope." -commands.deny = ["remove_item"] diff --git a/plugins/auth/permissions/autogenerated/commands/set_item.toml b/plugins/auth/permissions/autogenerated/commands/set_item.toml deleted file mode 100644 index 709323e069..0000000000 --- a/plugins/auth/permissions/autogenerated/commands/set_item.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-set-item" -description = "Enables the set_item command without any pre-configured scope." -commands.allow = ["set_item"] - -[[permission]] -identifier = "deny-set-item" -description = "Denies the set_item command without any pre-configured scope." -commands.deny = ["set_item"] diff --git a/plugins/auth/permissions/autogenerated/reference.md b/plugins/auth/permissions/autogenerated/reference.md deleted file mode 100644 index 7b8a030db7..0000000000 --- a/plugins/auth/permissions/autogenerated/reference.md +++ /dev/null @@ -1,124 +0,0 @@ -## Default Permission - -Default permissions for the plugin - -#### This default permission set includes the following: - -- `allow-get-item` -- `allow-set-item` -- `allow-remove-item` -- `allow-clear` - -## Permission Table - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IdentifierDescription
- -`auth:allow-clear` - - - -Enables the clear command without any pre-configured scope. - -
- -`auth:deny-clear` - - - -Denies the clear command without any pre-configured scope. - -
- -`auth:allow-get-item` - - - -Enables the get_item command without any pre-configured scope. - -
- -`auth:deny-get-item` - - - -Denies the get_item command without any pre-configured scope. - -
- -`auth:allow-remove-item` - - - -Enables the remove_item command without any pre-configured scope. - -
- -`auth:deny-remove-item` - - - -Denies the remove_item command without any pre-configured scope. - -
- -`auth:allow-set-item` - - - -Enables the set_item command without any pre-configured scope. - -
- -`auth:deny-set-item` - - - -Denies the set_item command without any pre-configured scope. - -
diff --git a/plugins/auth/permissions/default.toml b/plugins/auth/permissions/default.toml deleted file mode 100644 index 8dc06417c1..0000000000 --- a/plugins/auth/permissions/default.toml +++ /dev/null @@ -1,3 +0,0 @@ -[default] -description = "Default permissions for the plugin" -permissions = ["allow-get-item", "allow-set-item", "allow-remove-item", "allow-clear"] diff --git a/plugins/auth/permissions/schemas/schema.json b/plugins/auth/permissions/schemas/schema.json deleted file mode 100644 index c1633411bf..0000000000 --- a/plugins/auth/permissions/schemas/schema.json +++ /dev/null @@ -1,354 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PermissionFile", - "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", - "type": "object", - "properties": { - "default": { - "description": "The default permission set for the plugin", - "anyOf": [ - { - "$ref": "#/definitions/DefaultPermission" - }, - { - "type": "null" - } - ] - }, - "set": { - "description": "A list of permissions sets defined", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionSet" - } - }, - "permission": { - "description": "A list of inlined permissions", - "default": [], - "type": "array", - "items": { - "$ref": "#/definitions/Permission" - } - } - }, - "definitions": { - "DefaultPermission": { - "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", - "type": "object", - "required": [ - "permissions" - ], - "properties": { - "version": { - "description": "The version of the permission.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 1.0 - }, - "description": { - "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", - "type": [ - "string", - "null" - ] - }, - "permissions": { - "description": "All permissions this set contains.", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PermissionSet": { - "description": "A set of direct permissions grouped together under a new name.", - "type": "object", - "required": [ - "description", - "identifier", - "permissions" - ], - "properties": { - "identifier": { - "description": "A unique identifier for the permission.", - "type": "string" - }, - "description": { - "description": "Human-readable description of what the permission does.", - "type": "string" - }, - "permissions": { - "description": "All permissions this set contains.", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionKind" - } - } - } - }, - "Permission": { - "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", - "type": "object", - "required": [ - "identifier" - ], - "properties": { - "version": { - "description": "The version of the permission.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 1.0 - }, - "identifier": { - "description": "A unique identifier for the permission.", - "type": "string" - }, - "description": { - "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", - "type": [ - "string", - "null" - ] - }, - "commands": { - "description": "Allowed or denied commands when using this permission.", - "default": { - "allow": [], - "deny": [] - }, - "allOf": [ - { - "$ref": "#/definitions/Commands" - } - ] - }, - "scope": { - "description": "Allowed or denied scoped when using this permission.", - "allOf": [ - { - "$ref": "#/definitions/Scopes" - } - ] - }, - "platforms": { - "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Target" - } - } - } - }, - "Commands": { - "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", - "type": "object", - "properties": { - "allow": { - "description": "Allowed command.", - "default": [], - "type": "array", - "items": { - "type": "string" - } - }, - "deny": { - "description": "Denied command, which takes priority.", - "default": [], - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "Scopes": { - "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", - "type": "object", - "properties": { - "allow": { - "description": "Data that defines what is allowed by the scope.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - }, - "deny": { - "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - } - } - }, - "Value": { - "description": "All supported ACL values.", - "anyOf": [ - { - "description": "Represents a null JSON value.", - "type": "null" - }, - { - "description": "Represents a [`bool`].", - "type": "boolean" - }, - { - "description": "Represents a valid ACL [`Number`].", - "allOf": [ - { - "$ref": "#/definitions/Number" - } - ] - }, - { - "description": "Represents a [`String`].", - "type": "string" - }, - { - "description": "Represents a list of other [`Value`]s.", - "type": "array", - "items": { - "$ref": "#/definitions/Value" - } - }, - { - "description": "Represents a map of [`String`] keys to [`Value`]s.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Value" - } - } - ] - }, - "Number": { - "description": "A valid ACL number.", - "anyOf": [ - { - "description": "Represents an [`i64`].", - "type": "integer", - "format": "int64" - }, - { - "description": "Represents a [`f64`].", - "type": "number", - "format": "double" - } - ] - }, - "Target": { - "description": "Platform target.", - "oneOf": [ - { - "description": "MacOS.", - "type": "string", - "enum": [ - "macOS" - ] - }, - { - "description": "Windows.", - "type": "string", - "enum": [ - "windows" - ] - }, - { - "description": "Linux.", - "type": "string", - "enum": [ - "linux" - ] - }, - { - "description": "Android.", - "type": "string", - "enum": [ - "android" - ] - }, - { - "description": "iOS.", - "type": "string", - "enum": [ - "iOS" - ] - } - ] - }, - "PermissionKind": { - "type": "string", - "oneOf": [ - { - "description": "Enables the clear command without any pre-configured scope.", - "type": "string", - "const": "allow-clear", - "markdownDescription": "Enables the clear command without any pre-configured scope." - }, - { - "description": "Denies the clear command without any pre-configured scope.", - "type": "string", - "const": "deny-clear", - "markdownDescription": "Denies the clear command without any pre-configured scope." - }, - { - "description": "Enables the get_item command without any pre-configured scope.", - "type": "string", - "const": "allow-get-item", - "markdownDescription": "Enables the get_item command without any pre-configured scope." - }, - { - "description": "Denies the get_item command without any pre-configured scope.", - "type": "string", - "const": "deny-get-item", - "markdownDescription": "Denies the get_item command without any pre-configured scope." - }, - { - "description": "Enables the remove_item command without any pre-configured scope.", - "type": "string", - "const": "allow-remove-item", - "markdownDescription": "Enables the remove_item command without any pre-configured scope." - }, - { - "description": "Denies the remove_item command without any pre-configured scope.", - "type": "string", - "const": "deny-remove-item", - "markdownDescription": "Denies the remove_item command without any pre-configured scope." - }, - { - "description": "Enables the set_item command without any pre-configured scope.", - "type": "string", - "const": "allow-set-item", - "markdownDescription": "Enables the set_item command without any pre-configured scope." - }, - { - "description": "Denies the set_item command without any pre-configured scope.", - "type": "string", - "const": "deny-set-item", - "markdownDescription": "Denies the set_item command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-item`\n- `allow-set-item`\n- `allow-remove-item`\n- `allow-clear`", - "type": "string", - "const": "default", - "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-item`\n- `allow-set-item`\n- `allow-remove-item`\n- `allow-clear`" - } - ] - } - } -} \ No newline at end of file diff --git a/plugins/auth/src/commands.rs b/plugins/auth/src/commands.rs deleted file mode 100644 index 3c640dc54a..0000000000 --- a/plugins/auth/src/commands.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::AuthPluginExt; - -#[tauri::command] -#[specta::specta] -pub(crate) async fn get_item( - app: tauri::AppHandle, - key: String, -) -> Result, String> { - app.get_item(key).map_err(|e| e.to_string()) -} - -#[tauri::command] -#[specta::specta] -pub(crate) async fn set_item( - app: tauri::AppHandle, - key: String, - value: String, -) -> Result<(), String> { - app.set_item(key, value).map_err(|e| e.to_string()) -} - -#[tauri::command] -#[specta::specta] -pub(crate) async fn remove_item( - app: tauri::AppHandle, - key: String, -) -> Result<(), String> { - app.remove_item(key).map_err(|e| e.to_string()) -} - -#[tauri::command] -#[specta::specta] -pub(crate) async fn clear(app: tauri::AppHandle) -> Result<(), String> { - app.clear_auth().map_err(|e| e.to_string()) -} diff --git a/plugins/auth/src/error.rs b/plugins/auth/src/error.rs deleted file mode 100644 index 34325848f6..0000000000 --- a/plugins/auth/src/error.rs +++ /dev/null @@ -1,20 +0,0 @@ -use serde::{Serialize, ser::Serializer}; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error(transparent)] - Store2Error(#[from] tauri_plugin_store2::Error), - #[error(transparent)] - SerdeJsonError(#[from] serde_json::Error), -} - -impl Serialize for Error { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serializer.serialize_str(self.to_string().as_ref()) - } -} - -pub type Result = std::result::Result; diff --git a/plugins/auth/src/ext.rs b/plugins/auth/src/ext.rs deleted file mode 100644 index 33a35b709d..0000000000 --- a/plugins/auth/src/ext.rs +++ /dev/null @@ -1,37 +0,0 @@ -use tauri_plugin_store2::StorePluginExt; - -pub trait AuthPluginExt { - fn get_item(&self, key: String) -> Result, crate::Error>; - fn set_item(&self, key: String, value: String) -> Result<(), crate::Error>; - fn remove_item(&self, key: String) -> Result<(), crate::Error>; - fn clear_auth(&self) -> Result<(), crate::Error>; -} - -impl> crate::AuthPluginExt for T { - fn get_item(&self, key: String) -> Result, crate::Error> { - let store = self.scoped_store(crate::PLUGIN_NAME)?; - store.get::(key).map_err(Into::into) - } - - fn set_item(&self, key: String, value: String) -> Result<(), crate::Error> { - let store = self.scoped_store(crate::PLUGIN_NAME)?; - store.set(key, value)?; - store.save()?; - Ok(()) - } - - fn remove_item(&self, key: String) -> Result<(), crate::Error> { - let store = self.scoped_store(crate::PLUGIN_NAME)?; - store.set(key, serde_json::Value::Null)?; - store.save()?; - Ok(()) - } - - fn clear_auth(&self) -> Result<(), crate::Error> { - let store = self.scoped_store(crate::PLUGIN_NAME)?; - store.set("access_token".to_string(), serde_json::Value::Null)?; - store.set("refresh_token".to_string(), serde_json::Value::Null)?; - store.save()?; - Ok(()) - } -} diff --git a/plugins/auth/src/lib.rs b/plugins/auth/src/lib.rs deleted file mode 100644 index 580f1ab0c1..0000000000 --- a/plugins/auth/src/lib.rs +++ /dev/null @@ -1,69 +0,0 @@ -mod commands; -mod error; -mod ext; -mod store; - -pub use error::{Error, Result}; -pub use ext::*; - -const PLUGIN_NAME: &str = "auth"; - -fn make_specta_builder() -> tauri_specta::Builder { - tauri_specta::Builder::::new() - .plugin_name(PLUGIN_NAME) - .commands(tauri_specta::collect_commands![ - commands::get_item::, - commands::set_item::, - commands::remove_item::, - commands::clear::, - ]) - .error_handling(tauri_specta::ErrorHandlingMode::Result) -} - -pub fn init() -> tauri::plugin::TauriPlugin { - let specta_builder = make_specta_builder(); - - tauri::plugin::Builder::new(PLUGIN_NAME) - .invoke_handler(specta_builder.invoke_handler()) - .setup(|_app, _api| Ok(())) - .build() -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn export_types() { - const OUTPUT_FILE: &str = "./js/bindings.gen.ts"; - - make_specta_builder::() - .export( - specta_typescript::Typescript::default() - .formatter(specta_typescript::formatter::prettier) - .bigint(specta_typescript::BigIntExportBehavior::Number), - OUTPUT_FILE, - ) - .unwrap(); - - let content = std::fs::read_to_string(OUTPUT_FILE).unwrap(); - std::fs::write(OUTPUT_FILE, format!("// @ts-nocheck\n{content}")).unwrap(); - } - - fn create_app(builder: tauri::Builder) -> tauri::App { - builder - .plugin(tauri_plugin_store::Builder::new().build()) - .plugin(tauri_plugin_store2::init()) - .plugin(init()) - .build(tauri::test::mock_context(tauri::test::noop_assets())) - .unwrap() - } - - #[tokio::test] - async fn test_auth() { - let app = create_app(tauri::test::mock_builder()); - - let _ = app.set_item("test_key".to_string(), "test_value".to_string()); - let _ = app.get_item("test_key".to_string()); - } -} diff --git a/plugins/auth/src/store.rs b/plugins/auth/src/store.rs deleted file mode 100644 index 04c9a6fa25..0000000000 --- a/plugins/auth/src/store.rs +++ /dev/null @@ -1,11 +0,0 @@ -use tauri_plugin_store2::ScopedStoreKey; - -#[derive(serde::Deserialize, specta::Type, PartialEq, Eq, Hash, strum::Display)] -pub enum StoreKey { - #[strum(serialize = "access_token")] - AccessToken, - #[strum(serialize = "refresh_token")] - RefreshToken, -} - -impl ScopedStoreKey for StoreKey {} diff --git a/plugins/auth/tsconfig.json b/plugins/auth/tsconfig.json deleted file mode 100644 index 13b985325d..0000000000 --- a/plugins/auth/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "include": ["./js/*.ts"], - "exclude": ["node_modules"] -} diff --git a/plugins/cli2/Cargo.toml b/plugins/cli2/Cargo.toml index 7ba9aa4789..2987e5fcf4 100644 --- a/plugins/cli2/Cargo.toml +++ b/plugins/cli2/Cargo.toml @@ -16,7 +16,6 @@ specta-typescript = { workspace = true } [dependencies] tauri = { workspace = true, features = ["test"] } tauri-plugin-cli = { workspace = true } -tauri-plugin-updater = { workspace = true } tauri-specta = { workspace = true, features = ["derive", "typescript"] } serde = { workspace = true } diff --git a/plugins/cli2/js/bindings.gen.ts b/plugins/cli2/js/bindings.gen.ts index 963216bffc..e761c0d0a1 100644 --- a/plugins/cli2/js/bindings.gen.ts +++ b/plugins/cli2/js/bindings.gen.ts @@ -1,6 +1,9 @@ // @ts-nocheck /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -57,16 +60,24 @@ export type CliStatus = { }; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/cli2/src/ext.rs b/plugins/cli2/src/ext.rs index 53c1812346..c62744f8af 100644 --- a/plugins/cli2/src/ext.rs +++ b/plugins/cli2/src/ext.rs @@ -54,10 +54,10 @@ impl PluginCli { let exe_path = self.get_cli_executable_path()?; let symlink_path = self.get_cli_symlink_path(); - if let Some(parent) = symlink_path.parent() - && !parent.exists() - { - std::fs::create_dir_all(parent)?; + if let Some(parent) = symlink_path.parent() { + if !parent.exists() { + std::fs::create_dir_all(parent)?; + } } #[cfg(unix)] diff --git a/plugins/cli2/src/handler.rs b/plugins/cli2/src/handler.rs index 52c1e96248..87745a5c84 100644 --- a/plugins/cli2/src/handler.rs +++ b/plugins/cli2/src/handler.rs @@ -1,7 +1,6 @@ use serde_json::Value; use tauri::AppHandle; use tauri_plugin_cli::Matches; -use tauri_plugin_updater::UpdaterExt; pub fn entrypoint(app: &AppHandle, matches: Matches) { if let Some(arg) = matches.args.get("help") { @@ -27,7 +26,6 @@ pub fn entrypoint(app: &AppHandle, matches: Matches) { ), "web" => url(app, "https://hyprnote.com"), "changelog" => url(app, "https://hyprnote.com/changelog"), - "update" => update(app), _ => { tracing::warn!("unknown_subcommand: {}", subcommand_matches.name); std::process::exit(1); @@ -45,47 +43,3 @@ fn url(_app: &AppHandle, url: impl Into) { } } } - -fn update(app: &AppHandle) { - let app_clone = app.clone(); - tauri::async_runtime::block_on(async move { - let updater = match app_clone.updater() { - Ok(updater) => updater, - Err(e) => { - eprintln!("Failed to initialize updater: {e}"); - std::process::exit(1); - } - }; - - println!("Checking for updates..."); - - match updater.check().await { - Ok(Some(update)) => { - println!("Update available: v{}", update.version); - if let Some(body) = &update.body { - println!("\nRelease notes:\n{body}"); - } - - println!("\nDownloading update..."); - match update.download_and_install(|_, _| {}, || {}).await { - Ok(_) => { - println!("Update installed successfully. Please restart the application."); - std::process::exit(0); - } - Err(e) => { - eprintln!("Failed to install update: {e}"); - std::process::exit(1); - } - } - } - Ok(None) => { - println!("Already up to date."); - std::process::exit(0); - } - Err(e) => { - eprintln!("Failed to check for updates: {e}"); - std::process::exit(1); - } - } - }); -} diff --git a/plugins/db/js/bindings.gen.ts b/plugins/db/js/bindings.gen.ts index 98e462d5c7..93ea2eb848 100644 --- a/plugins/db/js/bindings.gen.ts +++ b/plugins/db/js/bindings.gen.ts @@ -1,6 +1,9 @@ // @ts-nocheck /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -20,7 +23,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async listEvents(filter: ListEventFilter | null): Promise> { + async listEvents( + filter: ListEventFilter | null, + ): Promise> { try { return { status: "ok", @@ -31,7 +36,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async getCalendar(calendarId: string): Promise> { + async getCalendar( + calendarId: string, + ): Promise> { try { return { status: "ok", @@ -64,7 +71,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async toggleCalendarSelected(trackingId: string): Promise> { + async toggleCalendarSelected( + trackingId: string, + ): Promise> { try { return { status: "ok", @@ -154,7 +163,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async listSessions(filter: ListSessionFilter | null): Promise> { + async listSessions( + filter: ListSessionFilter | null, + ): Promise> { try { return { status: "ok", @@ -176,7 +187,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async getSession(filter: GetSessionFilter): Promise> { + async getSession( + filter: GetSessionFilter, + ): Promise> { try { return { status: "ok", @@ -187,7 +200,10 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async setSessionEvent(sessionId: string, eventId: string | null): Promise> { + async setSessionEvent( + sessionId: string, + eventId: string | null, + ): Promise> { try { return { status: "ok", @@ -201,7 +217,10 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async sessionAddParticipant(sessionId: string, humanId: string): Promise> { + async sessionAddParticipant( + sessionId: string, + humanId: string, + ): Promise> { try { return { status: "ok", @@ -215,11 +234,16 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async sessionListDeletedParticipantIds(sessionId: string): Promise> { + async sessionListDeletedParticipantIds( + sessionId: string, + ): Promise> { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:db|session_list_deleted_participant_ids", { sessionId }), + data: await TAURI_INVOKE( + "plugin:db|session_list_deleted_participant_ids", + { sessionId }, + ), }; } catch (e) { if (e instanceof Error) throw e; @@ -243,7 +267,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async sessionListParticipants(sessionId: string): Promise> { + async sessionListParticipants( + sessionId: string, + ): Promise> { try { return { status: "ok", @@ -256,7 +282,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async sessionGetEvent(sessionId: string): Promise> { + async sessionGetEvent( + sessionId: string, + ): Promise> { try { return { status: "ok", @@ -330,7 +358,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async listHumans(filter: ListHumanFilter | null): Promise> { + async listHumans( + filter: ListHumanFilter | null, + ): Promise> { try { return { status: "ok", @@ -352,7 +382,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async getOrganization(id: string): Promise> { + async getOrganization( + id: string, + ): Promise> { try { return { status: "ok", @@ -374,7 +406,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async getOrganizationByUserId(userId: string): Promise> { + async getOrganizationByUserId( + userId: string, + ): Promise> { try { return { status: "ok", @@ -387,7 +421,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async upsertOrganization(organization: Organization): Promise> { + async upsertOrganization( + organization: Organization, + ): Promise> { try { return { status: "ok", @@ -413,7 +449,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async listOrganizationMembers(organizationId: string): Promise> { + async listOrganizationMembers( + organizationId: string, + ): Promise> { try { return { status: "ok", @@ -426,7 +464,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async listChatGroups(sessionId: string): Promise> { + async listChatGroups( + sessionId: string, + ): Promise> { try { return { status: "ok", @@ -437,7 +477,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async listChatMessages(groupId: string): Promise> { + async listChatMessages( + groupId: string, + ): Promise> { try { return { status: "ok", @@ -459,7 +501,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async upsertChatMessage(message: ChatMessage): Promise> { + async upsertChatMessage( + message: ChatMessage, + ): Promise> { try { return { status: "ok", @@ -503,7 +547,10 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async assignTagToSession(tagId: string, sessionId: string): Promise> { + async assignTagToSession( + tagId: string, + sessionId: string, + ): Promise> { try { return { status: "ok", @@ -517,7 +564,10 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async unassignTagFromSession(tagId: string, sessionId: string): Promise> { + async unassignTagFromSession( + tagId: string, + sessionId: string, + ): Promise> { try { return { status: "ok", @@ -568,7 +618,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async listConversations(sessionId: string): Promise> { + async listConversations( + sessionId: string, + ): Promise> { try { return { status: "ok", @@ -579,7 +631,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async createMessageV2(message: ChatMessageV2): Promise> { + async createMessageV2( + message: ChatMessageV2, + ): Promise> { try { return { status: "ok", @@ -590,7 +644,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async listMessagesV2(conversationId: string): Promise> { + async listMessagesV2( + conversationId: string, + ): Promise> { try { return { status: "ok", @@ -603,7 +659,10 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async updateMessageV2Parts(id: string, parts: string): Promise> { + async updateMessageV2Parts( + id: string, + parts: string, + ): Promise> { try { return { status: "ok", @@ -659,7 +718,11 @@ export type ChatMessage = { tool_details: string | null; }; export type ChatMessageRole = "User" | "Assistant"; -export type ChatMessageType = "text-delta" | "tool-start" | "tool-result" | "tool-error"; +export type ChatMessageType = + | "text-delta" + | "tool-start" + | "tool-result" + | "tool-error"; export type ChatMessageV2 = { id: string; conversation_id: string; @@ -711,7 +774,10 @@ export type Event = { participants: string | null; is_recurring: boolean; }; -export type GetSessionFilter = { id: string } | { calendarEventId: string } | { tagId: string }; +export type GetSessionFilter = + | { id: string } + | { calendarEventId: string } + | { tagId: string }; export type Human = { id: string; organization_id: string | null; @@ -778,16 +844,24 @@ export type Word2 = { }; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/db2/js/bindings.gen.ts b/plugins/db2/js/bindings.gen.ts index 08655330d2..ff8b6ba6aa 100644 --- a/plugins/db2/js/bindings.gen.ts +++ b/plugins/db2/js/bindings.gen.ts @@ -1,6 +1,9 @@ // @ts-nocheck /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -9,7 +12,10 @@ import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webview /** user-defined commands **/ export const commands = { - async executeLocal(sql: string, args: string[]): Promise> { + async executeLocal( + sql: string, + args: string[], + ): Promise> { try { return { status: "ok", @@ -20,7 +26,10 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async executeCloud(sql: string, args: string[]): Promise> { + async executeCloud( + sql: string, + args: string[], + ): Promise> { try { return { status: "ok", @@ -48,16 +57,24 @@ export type JsonValue = | Partial<{ [key in string]: JsonValue }>; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/deeplink2/js/bindings.gen.ts b/plugins/deeplink2/js/bindings.gen.ts index 46181051e1..2360ebbd6a 100644 --- a/plugins/deeplink2/js/bindings.gen.ts +++ b/plugins/deeplink2/js/bindings.gen.ts @@ -1,4 +1,11 @@ // @ts-nocheck +/** tauri-specta globals **/ +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; +import * as TAURI_API_EVENT from "@tauri-apps/api/event"; +import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. @@ -18,27 +25,29 @@ export const events = __makeEvents__<{ /** user-defined types **/ -export type AuthCallbackSearch = { access_token: string; refresh_token: string }; -export type DeepLink = { to: "/auth/callback"; search: AuthCallbackSearch }; +export type DeepLink = { to: "/notification"; search: NotificationSearch }; export type DeepLinkEvent = DeepLink; - -/** tauri-specta globals **/ - -import { invoke as TAURI_INVOKE, Channel as TAURI_CHANNEL } from "@tauri-apps/api/core"; -import * as TAURI_API_EVENT from "@tauri-apps/api/event"; -import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; +export type NotificationSearch = { key: string }; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/deeplink2/permissions/autogenerated/commands/ping.toml b/plugins/deeplink2/permissions/autogenerated/commands/ping.toml new file mode 100644 index 0000000000..1d1358807e --- /dev/null +++ b/plugins/deeplink2/permissions/autogenerated/commands/ping.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-ping" +description = "Enables the ping command without any pre-configured scope." +commands.allow = ["ping"] + +[[permission]] +identifier = "deny-ping" +description = "Denies the ping command without any pre-configured scope." +commands.deny = ["ping"] diff --git a/plugins/deeplink2/permissions/autogenerated/reference.md b/plugins/deeplink2/permissions/autogenerated/reference.md index cd349a13fc..725d9ccebd 100644 --- a/plugins/deeplink2/permissions/autogenerated/reference.md +++ b/plugins/deeplink2/permissions/autogenerated/reference.md @@ -4,6 +4,7 @@ Default permissions for the plugin #### This default permission set includes the following: +- `allow-ping` - `allow-get-available-deep-links` ## Permission Table @@ -38,6 +39,32 @@ Enables the get_available_deep_links command without any pre-configured scope. Denies the get_available_deep_links command without any pre-configured scope. + + + + + + +`deeplink2:allow-ping` + + + + +Enables the ping command without any pre-configured scope. + + + + + + + +`deeplink2:deny-ping` + + + + +Denies the ping command without any pre-configured scope. + diff --git a/plugins/deeplink2/permissions/default.toml b/plugins/deeplink2/permissions/default.toml index 0e2769ed7e..3c41fd2bba 100644 --- a/plugins/deeplink2/permissions/default.toml +++ b/plugins/deeplink2/permissions/default.toml @@ -1,3 +1,3 @@ [default] description = "Default permissions for the plugin" -permissions = ["allow-get-available-deep-links"] +permissions = ["allow-ping", "allow-get-available-deep-links"] diff --git a/plugins/deeplink2/permissions/schemas/schema.json b/plugins/deeplink2/permissions/schemas/schema.json index b5b01ddf9e..c0f9bc4205 100644 --- a/plugins/deeplink2/permissions/schemas/schema.json +++ b/plugins/deeplink2/permissions/schemas/schema.json @@ -307,10 +307,22 @@ "markdownDescription": "Denies the get_available_deep_links command without any pre-configured scope." }, { - "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-available-deep-links`", + "description": "Enables the ping command without any pre-configured scope.", + "type": "string", + "const": "allow-ping", + "markdownDescription": "Enables the ping command without any pre-configured scope." + }, + { + "description": "Denies the ping command without any pre-configured scope.", + "type": "string", + "const": "deny-ping", + "markdownDescription": "Denies the ping command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-ping`\n- `allow-get-available-deep-links`", "type": "string", "const": "default", - "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-available-deep-links`" + "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-ping`\n- `allow-get-available-deep-links`" } ] } diff --git a/plugins/deeplink2/src/lib.rs b/plugins/deeplink2/src/lib.rs index 3f9d237753..1fba1ab6ff 100644 --- a/plugins/deeplink2/src/lib.rs +++ b/plugins/deeplink2/src/lib.rs @@ -14,18 +14,6 @@ use tauri_specta::Event; const PLUGIN_NAME: &str = "deeplink2"; -fn redact_url(url_str: &str) -> String { - match url::Url::parse(url_str) { - Ok(parsed) => { - let scheme = parsed.scheme(); - let host = parsed.host_str().unwrap_or(""); - let path = parsed.path(); - format!("{}://{}{}", scheme, host, path) - } - Err(_) => "[invalid_url]".to_string(), - } -} - fn make_specta_builder() -> tauri_specta::Builder { tauri_specta::Builder::::new() .plugin_name(PLUGIN_NAME) @@ -40,26 +28,23 @@ pub fn init() -> tauri::plugin::TauriPlugin { tauri::plugin::Builder::new(PLUGIN_NAME) .invoke_handler(specta_builder.invoke_handler()) - .setup(move |app, _api| { - specta_builder.mount_events(app); - + .setup(|app, _api| { let app_handle = app.clone(); app.deep_link().on_open_url(move |event| { for url in event.urls() { let url_str = url.as_str(); - let redacted = redact_url(url_str); - tracing::info!(url = %redacted, "deeplink_received"); + tracing::info!(url = url_str, "deeplink_received"); match DeepLink::from_str(url_str) { Ok(deep_link) => { - tracing::info!(path = deep_link.path(), "deeplink_parsed"); + tracing::info!(deep_link = ?deep_link, "deeplink_parsed"); if let Err(e) = DeepLinkEvent(deep_link).emit(&app_handle) { tracing::error!(error = ?e, "deeplink_event_emit_failed"); } } Err(e) => { - tracing::debug!(error = ?e, url = %redacted, "deeplink_parse_failed"); + tracing::warn!(error = ?e, url = url_str, "deeplink_parse_failed"); } } } diff --git a/plugins/deeplink2/src/types/mod.rs b/plugins/deeplink2/src/types/mod.rs index 9a8ae5bdd9..c411d32347 100644 --- a/plugins/deeplink2/src/types/mod.rs +++ b/plugins/deeplink2/src/types/mod.rs @@ -1,6 +1,5 @@ -mod auth_callback; - -pub use auth_callback::*; +mod notification; +pub use notification::*; use serde::{Deserialize, Serialize}; use specta::Type; @@ -13,16 +12,8 @@ pub struct DeepLinkEvent(pub DeepLink); #[derive(Debug, Clone, Serialize, Deserialize, Type)] #[serde(tag = "to", content = "search")] pub enum DeepLink { - #[serde(rename = "/auth/callback")] - AuthCallback(AuthCallbackSearch), -} - -impl DeepLink { - pub fn path(&self) -> &'static str { - match self { - DeepLink::AuthCallback(_) => "/auth/callback", - } - } + #[serde(rename = "/notification")] + Notification(NotificationSearch), } impl FromStr for DeepLink { @@ -42,8 +33,8 @@ impl FromStr for DeepLink { let query_params: HashMap = parsed.query_pairs().into_owned().collect(); match full_path.as_str() { - "auth/callback" => Ok(DeepLink::AuthCallback( - AuthCallbackSearch::from_query_params(&query_params)?, + "notification" => Ok(DeepLink::Notification( + NotificationSearch::from_query_params(&query_params)?, )), _ => Err(crate::Error::UnknownPath(full_path)), } diff --git a/plugins/deeplink2/src/types/notification.rs b/plugins/deeplink2/src/types/notification.rs new file mode 100644 index 0000000000..c92b545786 --- /dev/null +++ b/plugins/deeplink2/src/types/notification.rs @@ -0,0 +1,21 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use specta::Type; + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct NotificationSearch { + pub key: String, +} + +impl NotificationSearch { + pub fn from_query_params(query_params: &HashMap) -> crate::Result { + let key = query_params + .get("key") + .ok_or(crate::Error::MissingQueryParam("key".to_string()))?; + + Ok(Self { + key: key.to_string(), + }) + } +} diff --git a/plugins/detect/js/bindings.gen.ts b/plugins/detect/js/bindings.gen.ts index 8cdd520f42..237f16d6f2 100644 --- a/plugins/detect/js/bindings.gen.ts +++ b/plugins/detect/js/bindings.gen.ts @@ -1,4 +1,11 @@ // @ts-nocheck +/** tauri-specta globals **/ +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; +import * as TAURI_API_EVENT from "@tauri-apps/api/event"; +import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. @@ -9,7 +16,9 @@ export const commands = { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:detect|set_quit_handler", { reallyQuit }), + data: await TAURI_INVOKE("plugin:detect|set_quit_handler", { + reallyQuit, + }), }; } catch (e) { if (e instanceof Error) throw e; @@ -18,7 +27,10 @@ export const commands = { }, async resetQuitHandler(): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:detect|reset_quit_handler") }; + return { + status: "ok", + data: await TAURI_INVOKE("plugin:detect|reset_quit_handler"), + }; } catch (e) { if (e instanceof Error) throw e; else return { status: "error", error: e as any }; @@ -46,22 +58,30 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async setRespectDoNotDisturb(enabled: boolean): Promise> { + async setRespectDoNotDisturb( + enabled: boolean, + ): Promise> { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:detect|set_respect_do_not_disturb", { enabled }), + data: await TAURI_INVOKE("plugin:detect|set_respect_do_not_disturb", { + enabled, + }), }; } catch (e) { if (e instanceof Error) throw e; else return { status: "error", error: e as any }; } }, - async setIgnoredBundleIds(bundleIds: string[]): Promise> { + async setIgnoredBundleIds( + bundleIds: string[], + ): Promise> { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:detect|set_ignored_bundle_ids", { bundleIds }), + data: await TAURI_INVOKE("plugin:detect|set_ignored_bundle_ids", { + bundleIds, + }), }; } catch (e) { if (e instanceof Error) throw e; @@ -72,7 +92,9 @@ export const commands = { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:detect|list_default_ignored_bundle_ids"), + data: await TAURI_INVOKE( + "plugin:detect|list_default_ignored_bundle_ids", + ), }; } catch (e) { if (e instanceof Error) throw e; @@ -95,27 +117,29 @@ export const events = __makeEvents__<{ export type DetectEvent = | { type: "micStarted"; key: string; apps: InstalledApp[] } - | { type: "micStopped"; apps: InstalledApp[] } + | { type: "micStopped" } | { type: "micMuted"; value: boolean }; export type InstalledApp = { id: string; name: string }; -/** tauri-specta globals **/ - -import { invoke as TAURI_INVOKE, Channel as TAURI_CHANNEL } from "@tauri-apps/api/core"; -import * as TAURI_API_EVENT from "@tauri-apps/api/event"; -import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; - type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/detect/src/commands.rs b/plugins/detect/src/commands.rs index 5d41313164..b8bd4f0195 100644 --- a/plugins/detect/src/commands.rs +++ b/plugins/detect/src/commands.rs @@ -1,14 +1,12 @@ use tauri::Manager; -const QUIT_HANDLER_ID: &str = "detect"; - #[tauri::command] #[specta::specta] pub(crate) async fn set_quit_handler( app: tauri::AppHandle, really_quit: bool, ) -> Result<(), String> { - hypr_intercept::register_quit_handler(QUIT_HANDLER_ID, move || { + hypr_intercept::setup_quit_handler(move || { hypr_host::kill_processes_by_matcher(hypr_host::ProcessMatcher::Sidecar); for (_, window) in app.webview_windows() { @@ -31,7 +29,7 @@ pub(crate) async fn set_quit_handler( pub(crate) async fn reset_quit_handler( _app: tauri::AppHandle, ) -> Result<(), String> { - hypr_intercept::unregister_quit_handler(QUIT_HANDLER_ID); + hypr_intercept::reset_quit_handler(); Ok(()) } diff --git a/plugins/detect/src/events.rs b/plugins/detect/src/events.rs index 01fb59c6a8..5b6ad4a6b8 100644 --- a/plugins/detect/src/events.rs +++ b/plugins/detect/src/events.rs @@ -15,9 +15,7 @@ common_event_derives! { apps: Vec, }, #[serde(rename = "micStopped")] - MicStopped { - apps: Vec, - }, + MicStopped {}, #[serde(rename = "micMuted")] MicMuteStateChanged { value: bool }, } @@ -30,7 +28,7 @@ impl From for DetectEvent { key: uuid::Uuid::new_v4().to_string(), apps, }, - hypr_detect::DetectEvent::MicStopped(apps) => Self::MicStopped { apps }, + hypr_detect::DetectEvent::MicStopped => Self::MicStopped {}, #[cfg(target_os = "macos")] hypr_detect::DetectEvent::ZoomMuteStateChanged { value } => { Self::MicMuteStateChanged { value } diff --git a/plugins/detect/src/handler.rs b/plugins/detect/src/handler.rs index 30dd41b063..fac8b726e9 100644 --- a/plugins/detect/src/handler.rs +++ b/plugins/detect/src/handler.rs @@ -4,6 +4,64 @@ use tauri_specta::Event; use crate::{DetectEvent, SharedState, dnd}; +pub async fn setup(app: &AppHandle) -> Result<(), Box> { + let app_handle = app.app_handle().clone(); + let callback = hypr_detect::new_callback(move |event| { + let state = app_handle.state::(); + + match event { + hypr_detect::DetectEvent::MicStarted(apps) => { + let state_guard = state.blocking_lock(); + + if state_guard.respect_do_not_disturb && dnd::is_do_not_disturb() { + tracing::info!(reason = "respect_do_not_disturb", "skip_notification"); + return; + } + + let filtered_apps: Vec<_> = apps + .into_iter() + .filter(|app| !state_guard.ignored_bundle_ids.contains(&app.id)) + .filter(|app| !default_ignored_bundle_ids().contains(&app.id)) + .collect(); + + if filtered_apps.is_empty() { + tracing::info!(reason = "all_apps_filtered", "skip_notification"); + return; + } + + drop(state_guard); + + let detect_event = DetectEvent::MicStarted { + key: uuid::Uuid::new_v4().to_string(), + apps: filtered_apps, + }; + let _ = detect_event.emit_to( + &app_handle, + EventTarget::AnyLabel { + label: tauri_plugin_windows::AppWindow::Main.label(), + }, + ); + } + other_event => { + let detect_event = DetectEvent::from(other_event); + let _ = detect_event.emit_to( + &app_handle, + EventTarget::AnyLabel { + label: tauri_plugin_windows::AppWindow::Main.label(), + }, + ); + } + } + }); + + let state = app.state::(); + let mut state_guard = state.lock().await; + state_guard.detector.start(callback); + drop(state_guard); + + Ok(()) +} + pub(crate) fn default_ignored_bundle_ids() -> Vec { let hyprnote = [ "com.hyprnote.dev", @@ -51,90 +109,3 @@ pub(crate) fn default_ignored_bundle_ids() -> Vec { .map(String::from) .collect() } - -pub async fn setup(app: &AppHandle) -> Result<(), Box> { - let app_handle = app.app_handle().clone(); - let callback = hypr_detect::new_callback(move |event| { - let state = app_handle.state::(); - - match event { - hypr_detect::DetectEvent::MicStarted(apps) => { - let state_guard = state.blocking_lock(); - - if state_guard.respect_do_not_disturb && dnd::is_do_not_disturb() { - tracing::info!(reason = "respect_do_not_disturb", "skip_notification"); - return; - } - - let filtered_apps = filter_apps(apps, &state_guard.ignored_bundle_ids); - drop(state_guard); - - if filtered_apps.is_empty() { - tracing::info!(reason = "all_apps_filtered", "skip_notification"); - return; - } - - emit_to_main( - &app_handle, - DetectEvent::MicStarted { - key: uuid::Uuid::new_v4().to_string(), - apps: filtered_apps, - }, - ); - } - hypr_detect::DetectEvent::MicStopped(apps) => { - let state_guard = state.blocking_lock(); - - if state_guard.respect_do_not_disturb && dnd::is_do_not_disturb() { - tracing::info!(reason = "respect_do_not_disturb", "skip_mic_stopped"); - return; - } - - let filtered_apps = filter_apps(apps, &state_guard.ignored_bundle_ids); - drop(state_guard); - - if filtered_apps.is_empty() { - tracing::info!(reason = "all_apps_filtered", "skip_mic_stopped"); - return; - } - - emit_to_main( - &app_handle, - DetectEvent::MicStopped { - apps: filtered_apps, - }, - ); - } - other_event => { - emit_to_main(&app_handle, DetectEvent::from(other_event)); - } - } - }); - - let state = app.state::(); - let mut state_guard = state.lock().await; - state_guard.detector.start(callback); - drop(state_guard); - - Ok(()) -} - -fn filter_apps( - apps: Vec, - ignored_bundle_ids: &[String], -) -> Vec { - let default_ignored = default_ignored_bundle_ids(); - apps.into_iter() - .filter(|app| !ignored_bundle_ids.contains(&app.id)) - .filter(|app| !default_ignored.contains(&app.id)) - .collect() -} - -fn emit_to_main(app_handle: &AppHandle, event: DetectEvent) { - let _ = event.emit_to( - app_handle, - EventTarget::AnyLabel { - label: tauri_plugin_windows::AppWindow::Main.label(), - }, - ); -} diff --git a/plugins/detect/src/lib.rs b/plugins/detect/src/lib.rs index 91e41bfcbb..b21ef163db 100644 --- a/plugins/detect/src/lib.rs +++ b/plugins/detect/src/lib.rs @@ -41,7 +41,7 @@ fn make_specta_builder() -> tauri_specta::Builder { .error_handling(tauri_specta::ErrorHandlingMode::Result) } -pub fn init() -> tauri::plugin::TauriPlugin { +pub fn init() -> tauri::plugin::TauriPlugin { let specta_builder = make_specta_builder(); tauri::plugin::Builder::new(PLUGIN_NAME) @@ -82,17 +82,4 @@ mod test { let content = std::fs::read_to_string(OUTPUT_FILE).unwrap(); std::fs::write(OUTPUT_FILE, format!("// @ts-nocheck\n{content}")).unwrap(); } - - fn create_app(builder: tauri::Builder) -> tauri::App { - builder - .plugin(init()) - .build(tauri::test::mock_context(tauri::test::noop_assets())) - .unwrap() - } - - #[test] - fn test_detect() { - let _app = create_app(tauri::test::mock_builder()); - std::thread::sleep(std::time::Duration::from_secs(10)); - } } diff --git a/plugins/extensions/js/bindings.gen.ts b/plugins/extensions/js/bindings.gen.ts index 21f804d85b..a50de60e12 100644 --- a/plugins/extensions/js/bindings.gen.ts +++ b/plugins/extensions/js/bindings.gen.ts @@ -1,6 +1,9 @@ // @ts-nocheck /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -39,7 +42,10 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async executeCode(extensionId: string, code: string): Promise> { + async executeCode( + extensionId: string, + code: string, + ): Promise> { try { return { status: "ok", @@ -75,7 +81,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async getExtension(extensionId: string): Promise> { + async getExtension( + extensionId: string, + ): Promise> { try { return { status: "ok", @@ -120,16 +128,24 @@ export type PanelInfo = { }; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/extensions/src/commands.rs b/plugins/extensions/src/commands.rs index c08dbef290..a7efcec6b3 100644 --- a/plugins/extensions/src/commands.rs +++ b/plugins/extensions/src/commands.rs @@ -42,7 +42,7 @@ pub async fn list_extensions( ) -> Result, Error> { let extensions_dir = app .path() - .data_dir() + .app_data_dir() .map_err(|e| Error::Io(e.to_string()))? .join("extensions"); @@ -86,7 +86,7 @@ pub async fn get_extensions_dir( ) -> Result { let extensions_dir = app .path() - .data_dir() + .app_data_dir() .map_err(|e| Error::Io(e.to_string()))? .join("extensions"); @@ -105,7 +105,7 @@ pub async fn get_extension( ) -> Result { let extensions_dir = app .path() - .data_dir() + .app_data_dir() .map_err(|e| Error::Io(e.to_string()))? .join("extensions"); diff --git a/plugins/hooks/js/bindings.gen.ts b/plugins/hooks/js/bindings.gen.ts index 53e30c8ea9..1a27bbfa69 100644 --- a/plugins/hooks/js/bindings.gen.ts +++ b/plugins/hooks/js/bindings.gen.ts @@ -1,6 +1,9 @@ // @ts-nocheck /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -96,16 +99,24 @@ export type HooksConfig = { }; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/hooks/src/commands.rs b/plugins/hooks/src/commands.rs index 6ab634635b..3b67652eab 100644 --- a/plugins/hooks/src/commands.rs +++ b/plugins/hooks/src/commands.rs @@ -6,8 +6,5 @@ pub(crate) async fn run_event_hooks( app: tauri::AppHandle, event: HookEvent, ) -> Result, String> { - app.hooks() - .handle_event(event) - .await - .map_err(|e| e.to_string()) + app.run_hooks(event).await.map_err(|e| e.to_string()) } diff --git a/plugins/hooks/src/config.rs b/plugins/hooks/src/config.rs index e62b3748e0..57e2078225 100644 --- a/plugins/hooks/src/config.rs +++ b/plugins/hooks/src/config.rs @@ -9,7 +9,7 @@ pub struct HooksConfig { pub version: u8, /// Map of event names to their associated hook definitions. #[serde(default)] - pub on: HashMap>, + pub hooks: HashMap>, } /// Defines a single hook to be executed on an event. @@ -30,16 +30,9 @@ impl HooksConfig { let content = std::fs::read_to_string(&path).map_err(|e| crate::Error::ConfigLoad(e.to_string()))?; - let settings: serde_json::Value = + let config: HooksConfig = serde_json::from_str(&content).map_err(|e| crate::Error::ConfigParse(e.to_string()))?; - let Some(hooks_value) = settings.get("hooks").cloned() else { - return Ok(Self::empty()); - }; - - let config: HooksConfig = serde_json::from_value(hooks_value) - .map_err(|e| crate::Error::ConfigParse(e.to_string()))?; - if config.version != 0 { return Err(crate::Error::UnsupportedVersion(config.version)); } @@ -47,19 +40,17 @@ impl HooksConfig { Ok(config) } - fn config_path(app: &impl tauri::Manager) -> crate::Result { - let data_dir = app - .path() - .data_dir() - .map_err(|e| crate::Error::ConfigLoad(e.to_string()))?; + fn config_path(_app: &impl tauri::Manager) -> crate::Result { + let data_dir = + dirs::data_dir().ok_or_else(|| crate::Error::ConfigLoad("no data dir".to_string()))?; - Ok(data_dir.join("hyprnote").join("settings.json")) + Ok(data_dir.join("hyprnote").join("hooks.json")) } fn empty() -> Self { Self { version: 0, - on: HashMap::new(), + hooks: HashMap::new(), } } } diff --git a/plugins/hooks/src/ext.rs b/plugins/hooks/src/ext.rs index 3731aae7db..263291ba29 100644 --- a/plugins/hooks/src/ext.rs +++ b/plugins/hooks/src/ext.rs @@ -3,31 +3,19 @@ use crate::{ runner::{HookResult, run_hooks_for_event}, }; -pub struct Hooks<'a, R: tauri::Runtime, M: tauri::Manager> { - manager: &'a M, - _runtime: std::marker::PhantomData R>, -} - -impl<'a, R: tauri::Runtime, M: tauri::Manager> Hooks<'a, R, M> { - pub async fn handle_event(&self, event: HookEvent) -> crate::Result> { - run_hooks_for_event(self.manager, event).await - } -} - pub trait HooksPluginExt { - fn hooks(&self) -> Hooks<'_, R, Self> - where - Self: tauri::Manager + Sized; + fn run_hooks( + &self, + event: HookEvent, + ) -> impl std::future::Future>> + Send; } -impl> HooksPluginExt for T { - fn hooks(&self) -> Hooks<'_, R, Self> - where - Self: Sized, - { - Hooks { - manager: self, - _runtime: std::marker::PhantomData, - } +impl HooksPluginExt for T +where + R: tauri::Runtime, + T: tauri::Manager + Send + Sync, +{ + async fn run_hooks(&self, event: HookEvent) -> crate::Result> { + run_hooks_for_event(self, event).await } } diff --git a/plugins/hooks/src/runner.rs b/plugins/hooks/src/runner.rs index 90112cc9fb..94d46875b3 100644 --- a/plugins/hooks/src/runner.rs +++ b/plugins/hooks/src/runner.rs @@ -19,7 +19,7 @@ pub async fn run_hooks_for_event( let condition_key = event.condition_key(); let cli_args = event.cli_args(); - let Some(hooks) = config.on.get(condition_key) else { + let Some(hooks) = config.hooks.get(condition_key) else { return Ok(vec![]); }; diff --git a/plugins/icon/.gitignore b/plugins/icon/.gitignore deleted file mode 100644 index 50d8e32e89..0000000000 --- a/plugins/icon/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -/.vs -.DS_Store -.Thumbs.db -*.sublime* -.idea/ -debug.log -package-lock.json -.vscode/settings.json -yarn.lock - -/.tauri -/target -Cargo.lock -node_modules/ - -dist-js -dist diff --git a/plugins/icon/Cargo.toml b/plugins/icon/Cargo.toml deleted file mode 100644 index c869b83a8f..0000000000 --- a/plugins/icon/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tauri-plugin-icon" -version = "0.1.0" -authors = ["You"] -edition = "2024" -exclude = ["/js", "/node_modules"] -links = "tauri-plugin-icon" -description = "" - -[build-dependencies] -tauri-plugin = { workspace = true, features = ["build"] } - -[dev-dependencies] -specta-typescript = { workspace = true } -tokio = { workspace = true, features = ["macros"] } - -[dependencies] -tauri = { workspace = true, features = ["test"] } -tauri-specta = { workspace = true, features = ["derive", "typescript"] } - -serde = { workspace = true } -specta = { workspace = true } -thiserror = { workspace = true } - -[target."cfg(target_os = \"macos\")".dependencies] -objc2 = { workspace = true } -objc2-app-kit = { workspace = true, features = ["NSApplication", "NSImage"] } -objc2-foundation = { workspace = true, features = ["NSString"] } diff --git a/plugins/icon/build.rs b/plugins/icon/build.rs deleted file mode 100644 index f45db05fcb..0000000000 --- a/plugins/icon/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -const COMMANDS: &[&str] = &["set_dock_icon", "reset_dock_icon"]; - -fn main() { - tauri_plugin::Builder::new(COMMANDS).build(); -} diff --git a/plugins/icon/js/bindings.gen.ts b/plugins/icon/js/bindings.gen.ts deleted file mode 100644 index ba7474c307..0000000000 --- a/plugins/icon/js/bindings.gen.ts +++ /dev/null @@ -1,80 +0,0 @@ -// @ts-nocheck -/** user-defined events **/ -/** user-defined constants **/ -/** user-defined types **/ -/** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; -import * as TAURI_API_EVENT from "@tauri-apps/api/event"; -import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; - -// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. - -/** user-defined commands **/ - -export const commands = { - async setDockIcon(name: string): Promise> { - try { - return { - status: "ok", - data: await TAURI_INVOKE("plugin:icon|set_dock_icon", { name }), - }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async resetDockIcon(): Promise> { - try { - return { - status: "ok", - data: await TAURI_INVOKE("plugin:icon|reset_dock_icon"), - }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, -}; - -type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - emit: null extends T - ? (payload?: T) => ReturnType - : (payload: T) => ReturnType; -}; - -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; - -function __makeEvents__>(mappings: Record) { - return new Proxy( - {} as unknown as { - [K in keyof T]: __EventObj__ & { - (handle: __WebviewWindow__): __EventObj__; - }; - }, - { - get: (_, event) => { - const name = mappings[event as keyof T]; - - return new Proxy((() => {}) as any, { - apply: (_, __, [window]: [__WebviewWindow__]) => ({ - listen: (arg: any) => window.listen(name, arg), - once: (arg: any) => window.once(name, arg), - emit: (arg: any) => window.emit(name, arg), - }), - get: (_, command: keyof __EventObj__) => { - switch (command) { - case "listen": - return (arg: any) => TAURI_API_EVENT.listen(name, arg); - case "once": - return (arg: any) => TAURI_API_EVENT.once(name, arg); - case "emit": - return (arg: any) => TAURI_API_EVENT.emit(name, arg); - } - }, - }); - }, - }, - ); -} diff --git a/plugins/icon/js/index.ts b/plugins/icon/js/index.ts deleted file mode 100644 index a96e122f03..0000000000 --- a/plugins/icon/js/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./bindings.gen"; diff --git a/plugins/icon/package.json b/plugins/icon/package.json deleted file mode 100644 index 6d5b1c887d..0000000000 --- a/plugins/icon/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@hypr/plugin-icon", - "private": true, - "main": "./js/index.ts", - "scripts": { - "codegen": "cargo test -p tauri-plugin-icon" - }, - "dependencies": { - "@tauri-apps/api": "^2.9.1" - } -} diff --git a/plugins/icon/permissions/autogenerated/commands/reset_dock_icon.toml b/plugins/icon/permissions/autogenerated/commands/reset_dock_icon.toml deleted file mode 100644 index 6bc5080bba..0000000000 --- a/plugins/icon/permissions/autogenerated/commands/reset_dock_icon.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-reset-dock-icon" -description = "Enables the reset_dock_icon command without any pre-configured scope." -commands.allow = ["reset_dock_icon"] - -[[permission]] -identifier = "deny-reset-dock-icon" -description = "Denies the reset_dock_icon command without any pre-configured scope." -commands.deny = ["reset_dock_icon"] diff --git a/plugins/icon/permissions/autogenerated/commands/set_dock_icon.toml b/plugins/icon/permissions/autogenerated/commands/set_dock_icon.toml deleted file mode 100644 index 3cc50f2a15..0000000000 --- a/plugins/icon/permissions/autogenerated/commands/set_dock_icon.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-set-dock-icon" -description = "Enables the set_dock_icon command without any pre-configured scope." -commands.allow = ["set_dock_icon"] - -[[permission]] -identifier = "deny-set-dock-icon" -description = "Denies the set_dock_icon command without any pre-configured scope." -commands.deny = ["set_dock_icon"] diff --git a/plugins/icon/permissions/autogenerated/reference.md b/plugins/icon/permissions/autogenerated/reference.md deleted file mode 100644 index 8a83973e3a..0000000000 --- a/plugins/icon/permissions/autogenerated/reference.md +++ /dev/null @@ -1,70 +0,0 @@ -## Default Permission - -Default permissions for the plugin - -#### This default permission set includes the following: - -- `allow-set-dock-icon` -- `allow-reset-dock-icon` - -## Permission Table - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IdentifierDescription
- -`icon:allow-reset-dock-icon` - - - -Enables the reset_dock_icon command without any pre-configured scope. - -
- -`icon:deny-reset-dock-icon` - - - -Denies the reset_dock_icon command without any pre-configured scope. - -
- -`icon:allow-set-dock-icon` - - - -Enables the set_dock_icon command without any pre-configured scope. - -
- -`icon:deny-set-dock-icon` - - - -Denies the set_dock_icon command without any pre-configured scope. - -
diff --git a/plugins/icon/permissions/default.toml b/plugins/icon/permissions/default.toml deleted file mode 100644 index 5e24e9b8c4..0000000000 --- a/plugins/icon/permissions/default.toml +++ /dev/null @@ -1,3 +0,0 @@ -[default] -description = "Default permissions for the plugin" -permissions = ["allow-set-dock-icon", "allow-reset-dock-icon"] diff --git a/plugins/icon/permissions/schemas/schema.json b/plugins/icon/permissions/schemas/schema.json deleted file mode 100644 index 34a4fe7601..0000000000 --- a/plugins/icon/permissions/schemas/schema.json +++ /dev/null @@ -1,330 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PermissionFile", - "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", - "type": "object", - "properties": { - "default": { - "description": "The default permission set for the plugin", - "anyOf": [ - { - "$ref": "#/definitions/DefaultPermission" - }, - { - "type": "null" - } - ] - }, - "set": { - "description": "A list of permissions sets defined", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionSet" - } - }, - "permission": { - "description": "A list of inlined permissions", - "default": [], - "type": "array", - "items": { - "$ref": "#/definitions/Permission" - } - } - }, - "definitions": { - "DefaultPermission": { - "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", - "type": "object", - "required": [ - "permissions" - ], - "properties": { - "version": { - "description": "The version of the permission.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 1.0 - }, - "description": { - "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", - "type": [ - "string", - "null" - ] - }, - "permissions": { - "description": "All permissions this set contains.", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PermissionSet": { - "description": "A set of direct permissions grouped together under a new name.", - "type": "object", - "required": [ - "description", - "identifier", - "permissions" - ], - "properties": { - "identifier": { - "description": "A unique identifier for the permission.", - "type": "string" - }, - "description": { - "description": "Human-readable description of what the permission does.", - "type": "string" - }, - "permissions": { - "description": "All permissions this set contains.", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionKind" - } - } - } - }, - "Permission": { - "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", - "type": "object", - "required": [ - "identifier" - ], - "properties": { - "version": { - "description": "The version of the permission.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 1.0 - }, - "identifier": { - "description": "A unique identifier for the permission.", - "type": "string" - }, - "description": { - "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", - "type": [ - "string", - "null" - ] - }, - "commands": { - "description": "Allowed or denied commands when using this permission.", - "default": { - "allow": [], - "deny": [] - }, - "allOf": [ - { - "$ref": "#/definitions/Commands" - } - ] - }, - "scope": { - "description": "Allowed or denied scoped when using this permission.", - "allOf": [ - { - "$ref": "#/definitions/Scopes" - } - ] - }, - "platforms": { - "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Target" - } - } - } - }, - "Commands": { - "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", - "type": "object", - "properties": { - "allow": { - "description": "Allowed command.", - "default": [], - "type": "array", - "items": { - "type": "string" - } - }, - "deny": { - "description": "Denied command, which takes priority.", - "default": [], - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "Scopes": { - "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", - "type": "object", - "properties": { - "allow": { - "description": "Data that defines what is allowed by the scope.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - }, - "deny": { - "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - } - } - }, - "Value": { - "description": "All supported ACL values.", - "anyOf": [ - { - "description": "Represents a null JSON value.", - "type": "null" - }, - { - "description": "Represents a [`bool`].", - "type": "boolean" - }, - { - "description": "Represents a valid ACL [`Number`].", - "allOf": [ - { - "$ref": "#/definitions/Number" - } - ] - }, - { - "description": "Represents a [`String`].", - "type": "string" - }, - { - "description": "Represents a list of other [`Value`]s.", - "type": "array", - "items": { - "$ref": "#/definitions/Value" - } - }, - { - "description": "Represents a map of [`String`] keys to [`Value`]s.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Value" - } - } - ] - }, - "Number": { - "description": "A valid ACL number.", - "anyOf": [ - { - "description": "Represents an [`i64`].", - "type": "integer", - "format": "int64" - }, - { - "description": "Represents a [`f64`].", - "type": "number", - "format": "double" - } - ] - }, - "Target": { - "description": "Platform target.", - "oneOf": [ - { - "description": "MacOS.", - "type": "string", - "enum": [ - "macOS" - ] - }, - { - "description": "Windows.", - "type": "string", - "enum": [ - "windows" - ] - }, - { - "description": "Linux.", - "type": "string", - "enum": [ - "linux" - ] - }, - { - "description": "Android.", - "type": "string", - "enum": [ - "android" - ] - }, - { - "description": "iOS.", - "type": "string", - "enum": [ - "iOS" - ] - } - ] - }, - "PermissionKind": { - "type": "string", - "oneOf": [ - { - "description": "Enables the reset_dock_icon command without any pre-configured scope.", - "type": "string", - "const": "allow-reset-dock-icon", - "markdownDescription": "Enables the reset_dock_icon command without any pre-configured scope." - }, - { - "description": "Denies the reset_dock_icon command without any pre-configured scope.", - "type": "string", - "const": "deny-reset-dock-icon", - "markdownDescription": "Denies the reset_dock_icon command without any pre-configured scope." - }, - { - "description": "Enables the set_dock_icon command without any pre-configured scope.", - "type": "string", - "const": "allow-set-dock-icon", - "markdownDescription": "Enables the set_dock_icon command without any pre-configured scope." - }, - { - "description": "Denies the set_dock_icon command without any pre-configured scope.", - "type": "string", - "const": "deny-set-dock-icon", - "markdownDescription": "Denies the set_dock_icon command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-set-dock-icon`\n- `allow-reset-dock-icon`", - "type": "string", - "const": "default", - "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-set-dock-icon`\n- `allow-reset-dock-icon`" - } - ] - } - } -} \ No newline at end of file diff --git a/plugins/icon/src/commands.rs b/plugins/icon/src/commands.rs deleted file mode 100644 index 54e85232bd..0000000000 --- a/plugins/icon/src/commands.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::IconPluginExt; - -#[tauri::command] -#[specta::specta] -pub(crate) async fn set_dock_icon( - app: tauri::AppHandle, - name: String, -) -> Result<(), String> { - app.set_dock_icon(name).map_err(|e| e.to_string()) -} - -#[tauri::command] -#[specta::specta] -pub(crate) async fn reset_dock_icon( - app: tauri::AppHandle, -) -> Result<(), String> { - app.reset_dock_icon().map_err(|e| e.to_string()) -} diff --git a/plugins/icon/src/error.rs b/plugins/icon/src/error.rs deleted file mode 100644 index dbbd459267..0000000000 --- a/plugins/icon/src/error.rs +++ /dev/null @@ -1,20 +0,0 @@ -use serde::{Serialize, ser::Serializer}; - -pub type Result = std::result::Result; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error(transparent)] - Tauri(#[from] tauri::Error), - #[error("{0}")] - Custom(String), -} - -impl Serialize for Error { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serializer.serialize_str(self.to_string().as_ref()) - } -} diff --git a/plugins/icon/src/ext.rs b/plugins/icon/src/ext.rs deleted file mode 100644 index c7c9fdefd4..0000000000 --- a/plugins/icon/src/ext.rs +++ /dev/null @@ -1,95 +0,0 @@ -pub trait IconPluginExt { - fn set_dock_icon(&self, name: String) -> Result<(), crate::Error>; - fn reset_dock_icon(&self) -> Result<(), crate::Error>; -} - -impl> IconPluginExt for T { - fn set_dock_icon(&self, name: String) -> Result<(), crate::Error> { - #[cfg(target_os = "macos")] - { - use std::path::PathBuf; - use tauri::path::BaseDirectory; - - let icon_path = if cfg!(debug_assertions) { - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .parent() - .unwrap() - .join("apps") - .join("desktop") - .join("src-tauri") - .join("icons") - .join(&name) - .join("icon.icns") - } else { - self.path() - .resolve(format!("icons/{}.icns", name), BaseDirectory::Resource) - .map_err(crate::Error::Tauri)? - }; - - if !icon_path.exists() { - return Err(crate::Error::Custom(format!( - "Icon file not found: {}", - icon_path.display() - ))); - } - - let icon_path_str = icon_path.to_string_lossy().to_string(); - - let app_handle = self.app_handle(); - app_handle - .run_on_main_thread(move || { - use objc2::AnyThread; - use objc2_app_kit::{NSApplication, NSImage}; - use objc2_foundation::{MainThreadMarker, NSString}; - - let mtm = - MainThreadMarker::new().expect("run_on_main_thread guarantees main thread"); - let ns_app = NSApplication::sharedApplication(mtm); - - let path_str = NSString::from_str(&icon_path_str); - let image = NSImage::initWithContentsOfFile(NSImage::alloc(), &path_str); - - if let Some(image) = image { - unsafe { ns_app.setApplicationIconImage(Some(&image)) }; - } - }) - .map_err(crate::Error::Tauri)?; - - Ok(()) - } - - #[cfg(not(target_os = "macos"))] - { - let _ = name; - Ok(()) - } - } - - fn reset_dock_icon(&self) -> Result<(), crate::Error> { - #[cfg(target_os = "macos")] - { - let app_handle = self.app_handle(); - app_handle - .run_on_main_thread(move || { - use objc2_app_kit::NSApplication; - use objc2_foundation::MainThreadMarker; - - let mtm = - MainThreadMarker::new().expect("run_on_main_thread guarantees main thread"); - let ns_app = NSApplication::sharedApplication(mtm); - - unsafe { ns_app.setApplicationIconImage(None) }; - }) - .map_err(crate::Error::Tauri)?; - - Ok(()) - } - - #[cfg(not(target_os = "macos"))] - { - Ok(()) - } - } -} diff --git a/plugins/icon/src/lib.rs b/plugins/icon/src/lib.rs deleted file mode 100644 index f9f60864f6..0000000000 --- a/plugins/icon/src/lib.rs +++ /dev/null @@ -1,48 +0,0 @@ -mod commands; -mod error; -mod ext; - -pub use error::{Error, Result}; -pub use ext::*; - -const PLUGIN_NAME: &str = "icon"; - -fn make_specta_builder() -> tauri_specta::Builder { - tauri_specta::Builder::::new() - .plugin_name(PLUGIN_NAME) - .commands(tauri_specta::collect_commands![ - commands::set_dock_icon::, - commands::reset_dock_icon::, - ]) - .error_handling(tauri_specta::ErrorHandlingMode::Result) -} - -pub fn init() -> tauri::plugin::TauriPlugin { - let specta_builder = make_specta_builder(); - - tauri::plugin::Builder::new(PLUGIN_NAME) - .invoke_handler(specta_builder.invoke_handler()) - .build() -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn export_types() { - const OUTPUT_FILE: &str = "./js/bindings.gen.ts"; - - make_specta_builder::() - .export( - specta_typescript::Typescript::default() - .formatter(specta_typescript::formatter::prettier) - .bigint(specta_typescript::BigIntExportBehavior::Number), - OUTPUT_FILE, - ) - .unwrap(); - - let content = std::fs::read_to_string(OUTPUT_FILE).unwrap(); - std::fs::write(OUTPUT_FILE, format!("// @ts-nocheck\n{content}")).unwrap(); - } -} diff --git a/plugins/icon/tsconfig.json b/plugins/icon/tsconfig.json deleted file mode 100644 index 13b985325d..0000000000 --- a/plugins/icon/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "include": ["./js/*.ts"], - "exclude": ["node_modules"] -} diff --git a/plugins/importer/Cargo.toml b/plugins/importer/Cargo.toml index d8a7e146de..8d34f404fe 100644 --- a/plugins/importer/Cargo.toml +++ b/plugins/importer/Cargo.toml @@ -14,14 +14,6 @@ tauri-plugin = { workspace = true, features = ["build"] } specta-typescript = { workspace = true } [dependencies] -dirs = { workspace = true } -hypr-db-core = { workspace = true } -hypr-db-user = { workspace = true } -hypr-granola = { workspace = true } -owhisper-interface = { workspace = true } -serde = { workspace = true, features = ["derive"] } specta = { workspace = true } tauri = { workspace = true, features = ["test"] } tauri-specta = { workspace = true, features = ["derive", "typescript"] } -thiserror = { workspace = true } -tokio = { workspace = true } diff --git a/plugins/importer/build.rs b/plugins/importer/build.rs index fc3265552c..029861396b 100644 --- a/plugins/importer/build.rs +++ b/plugins/importer/build.rs @@ -1,4 +1,4 @@ -const COMMANDS: &[&str] = &["list_available_sources", "run_import", "run_import_dry"]; +const COMMANDS: &[&str] = &["ping"]; fn main() { tauri_plugin::Builder::new(COMMANDS).build(); diff --git a/plugins/importer/js/bindings.gen.ts b/plugins/importer/js/bindings.gen.ts index 0762b7eaf3..1a5456dd1a 100644 --- a/plugins/importer/js/bindings.gen.ts +++ b/plugins/importer/js/bindings.gen.ts @@ -1,106 +1,77 @@ // @ts-nocheck - -// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. - -/** user-defined commands **/ - - -export const commands = { -async listAvailableSources() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("plugin:importer|list_available_sources") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async runImport(source: ImportSourceKind) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("plugin:importer|run_import", { source }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async runImportDry(source: ImportSourceKind) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("plugin:importer|run_import_dry", { source }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -} -} - /** user-defined events **/ - - - /** user-defined constants **/ - - - /** user-defined types **/ - -export type ImportSourceInfo = { kind: ImportSourceKind; name: string; description: string } -export type ImportSourceKind = "granola" | "hyprnote_v0_stable" | "hyprnote_v0_nightly" - /** tauri-specta globals **/ - import { - invoke as TAURI_INVOKE, - Channel as TAURI_CHANNEL, + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, } from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; +// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. + +/** user-defined commands **/ + +export const commands = { + async ping(): Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("plugin:importer|ping") }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, +}; + type __EventObj__ = { - listen: ( - cb: TAURI_API_EVENT.EventCallback, - ) => ReturnType>; - once: ( - cb: TAURI_API_EVENT.EventCallback, - ) => ReturnType>; - emit: null extends T - ? (payload?: T) => ReturnType - : (payload: T) => ReturnType; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + emit: null extends T + ? (payload?: T) => ReturnType + : (payload: T) => ReturnType; }; export type Result = - | { status: "ok"; data: T } - | { status: "error"; error: E }; + | { status: "ok"; data: T } + | { status: "error"; error: E }; function __makeEvents__>( - mappings: Record, + mappings: Record, ) { - return new Proxy( - {} as unknown as { - [K in keyof T]: __EventObj__ & { - (handle: __WebviewWindow__): __EventObj__; - }; - }, - { - get: (_, event) => { - const name = mappings[event as keyof T]; - - return new Proxy((() => {}) as any, { - apply: (_, __, [window]: [__WebviewWindow__]) => ({ - listen: (arg: any) => window.listen(name, arg), - once: (arg: any) => window.once(name, arg), - emit: (arg: any) => window.emit(name, arg), - }), - get: (_, command: keyof __EventObj__) => { - switch (command) { - case "listen": - return (arg: any) => TAURI_API_EVENT.listen(name, arg); - case "once": - return (arg: any) => TAURI_API_EVENT.once(name, arg); - case "emit": - return (arg: any) => TAURI_API_EVENT.emit(name, arg); - } - }, - }); - }, - }, - ); + return new Proxy( + {} as unknown as { + [K in keyof T]: __EventObj__ & { + (handle: __WebviewWindow__): __EventObj__; + }; + }, + { + get: (_, event) => { + const name = mappings[event as keyof T]; + + return new Proxy((() => {}) as any, { + apply: (_, __, [window]: [__WebviewWindow__]) => ({ + listen: (arg: any) => window.listen(name, arg), + once: (arg: any) => window.once(name, arg), + emit: (arg: any) => window.emit(name, arg), + }), + get: (_, command: keyof __EventObj__) => { + switch (command) { + case "listen": + return (arg: any) => TAURI_API_EVENT.listen(name, arg); + case "once": + return (arg: any) => TAURI_API_EVENT.once(name, arg); + case "emit": + return (arg: any) => TAURI_API_EVENT.emit(name, arg); + } + }, + }); + }, + }, + ); } diff --git a/plugins/importer/permissions/autogenerated/commands/ping.toml b/plugins/importer/permissions/autogenerated/commands/ping.toml new file mode 100644 index 0000000000..1d1358807e --- /dev/null +++ b/plugins/importer/permissions/autogenerated/commands/ping.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-ping" +description = "Enables the ping command without any pre-configured scope." +commands.allow = ["ping"] + +[[permission]] +identifier = "deny-ping" +description = "Denies the ping command without any pre-configured scope." +commands.deny = ["ping"] diff --git a/plugins/importer/permissions/autogenerated/reference.md b/plugins/importer/permissions/autogenerated/reference.md index 4bd0bbc82f..1e61d121eb 100644 --- a/plugins/importer/permissions/autogenerated/reference.md +++ b/plugins/importer/permissions/autogenerated/reference.md @@ -4,9 +4,7 @@ Default permissions for the plugin #### This default permission set includes the following: -- `allow-list-available-sources` -- `allow-run-import` -- `allow-run-import-dry` +- `allow-ping` ## Permission Table @@ -20,12 +18,12 @@ Default permissions for the plugin -`importer:allow-list-available-sources` +`importer:allow-ping` -Enables the list_available_sources command without any pre-configured scope. +Enables the ping command without any pre-configured scope. @@ -33,64 +31,12 @@ Enables the list_available_sources command without any pre-configured scope. -`importer:deny-list-available-sources` +`importer:deny-ping` -Denies the list_available_sources command without any pre-configured scope. - - - - - - - -`importer:allow-run-import` - - - - -Enables the run_import command without any pre-configured scope. - - - - - - - -`importer:deny-run-import` - - - - -Denies the run_import command without any pre-configured scope. - - - - - - - -`importer:allow-run-import-dry` - - - - -Enables the run_import_dry command without any pre-configured scope. - - - - - - - -`importer:deny-run-import-dry` - - - - -Denies the run_import_dry command without any pre-configured scope. +Denies the ping command without any pre-configured scope. diff --git a/plugins/importer/permissions/default.toml b/plugins/importer/permissions/default.toml index d77daf0c63..cc5a76f22e 100644 --- a/plugins/importer/permissions/default.toml +++ b/plugins/importer/permissions/default.toml @@ -1,7 +1,3 @@ [default] description = "Default permissions for the plugin" -permissions = [ - "allow-list-available-sources", - "allow-run-import", - "allow-run-import-dry", -] +permissions = ["allow-ping"] diff --git a/plugins/importer/permissions/schemas/schema.json b/plugins/importer/permissions/schemas/schema.json index 15793f777d..ac68e129e2 100644 --- a/plugins/importer/permissions/schemas/schema.json +++ b/plugins/importer/permissions/schemas/schema.json @@ -295,46 +295,22 @@ "type": "string", "oneOf": [ { - "description": "Enables the list_available_sources command without any pre-configured scope.", + "description": "Enables the ping command without any pre-configured scope.", "type": "string", - "const": "allow-list-available-sources", - "markdownDescription": "Enables the list_available_sources command without any pre-configured scope." + "const": "allow-ping", + "markdownDescription": "Enables the ping command without any pre-configured scope." }, { - "description": "Denies the list_available_sources command without any pre-configured scope.", + "description": "Denies the ping command without any pre-configured scope.", "type": "string", - "const": "deny-list-available-sources", - "markdownDescription": "Denies the list_available_sources command without any pre-configured scope." + "const": "deny-ping", + "markdownDescription": "Denies the ping command without any pre-configured scope." }, { - "description": "Enables the run_import command without any pre-configured scope.", - "type": "string", - "const": "allow-run-import", - "markdownDescription": "Enables the run_import command without any pre-configured scope." - }, - { - "description": "Denies the run_import command without any pre-configured scope.", - "type": "string", - "const": "deny-run-import", - "markdownDescription": "Denies the run_import command without any pre-configured scope." - }, - { - "description": "Enables the run_import_dry command without any pre-configured scope.", - "type": "string", - "const": "allow-run-import-dry", - "markdownDescription": "Enables the run_import_dry command without any pre-configured scope." - }, - { - "description": "Denies the run_import_dry command without any pre-configured scope.", - "type": "string", - "const": "deny-run-import-dry", - "markdownDescription": "Denies the run_import_dry command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-list-available-sources`\n- `allow-run-import`\n- `allow-run-import-dry`", + "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-ping`", "type": "string", "const": "default", - "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-list-available-sources`\n- `allow-run-import`\n- `allow-run-import-dry`" + "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-ping`" } ] } diff --git a/plugins/importer/src/commands.rs b/plugins/importer/src/commands.rs index d7d1e50089..e2d350e221 100644 --- a/plugins/importer/src/commands.rs +++ b/plugins/importer/src/commands.rs @@ -1,28 +1,7 @@ -use crate::types::{ImportSourceInfo, ImportSourceKind}; +use crate::ImporterPluginExt; #[tauri::command] #[specta::specta] -pub async fn list_available_sources( - _app: tauri::AppHandle, -) -> Result, String> { - let sources = crate::sources::list_available_sources(); - Ok(sources) -} - -#[tauri::command] -#[specta::specta] -pub async fn run_import( - _app: tauri::AppHandle, - _source: ImportSourceKind, -) -> Result<(), String> { - Ok(()) -} - -#[tauri::command] -#[specta::specta] -pub async fn run_import_dry( - _app: tauri::AppHandle, - _source: ImportSourceKind, -) -> Result<(), String> { - Ok(()) +pub async fn ping(app: tauri::AppHandle) -> Result { + app.ping() } diff --git a/plugins/importer/src/ext.rs b/plugins/importer/src/ext.rs index d31858bbde..82cc15c966 100644 --- a/plugins/importer/src/ext.rs +++ b/plugins/importer/src/ext.rs @@ -1,3 +1,9 @@ -pub trait ImporterPluginExt {} +pub trait ImporterPluginExt { + fn ping(&self) -> Result; +} -impl + Sync> ImporterPluginExt for T {} +impl> ImporterPluginExt for T { + fn ping(&self) -> Result { + Ok("pong".to_string()) + } +} diff --git a/plugins/importer/src/lib.rs b/plugins/importer/src/lib.rs index e400857b22..a7d729d346 100644 --- a/plugins/importer/src/lib.rs +++ b/plugins/importer/src/lib.rs @@ -1,26 +1,16 @@ use tauri::Wry; mod commands; -mod error; mod ext; -mod sources; -mod types; -pub use error::*; pub use ext::ImporterPluginExt; -pub use sources::{ImportSource, ImportSourceDyn, all_sources, get_source, list_available_sources}; -pub use types::*; const PLUGIN_NAME: &str = "importer"; fn make_specta_builder() -> tauri_specta::Builder { tauri_specta::Builder::::new() .plugin_name(PLUGIN_NAME) - .commands(tauri_specta::collect_commands![ - commands::list_available_sources::, - commands::run_import::, - commands::run_import_dry::, - ]) + .commands(tauri_specta::collect_commands![commands::ping::,]) .error_handling(tauri_specta::ErrorHandlingMode::Result) } diff --git a/plugins/listener/Cargo.toml b/plugins/listener/Cargo.toml index 4927ad3385..e5d07f8bc3 100644 --- a/plugins/listener/Cargo.toml +++ b/plugins/listener/Cargo.toml @@ -18,13 +18,12 @@ specta-typescript = { workspace = true } uuid = { workspace = true } [dependencies] -hypr-aec = { workspace = true } +hypr-aec = { workspace = true, features = ["load-dynamic"] } hypr-agc = { workspace = true } hypr-audio = { workspace = true } hypr-audio-utils = { workspace = true } hypr-data = { workspace = true } hypr-device-heuristic = { workspace = true } -hypr-intercept = { workspace = true } hypr-language = { workspace = true } hypr-llm = { workspace = true } hypr-vad-ext = { workspace = true } diff --git a/plugins/listener/assets/viewer.html b/plugins/listener/assets/viewer.html index 87bba4a9f7..aecdff5eef 100644 --- a/plugins/listener/assets/viewer.html +++ b/plugins/listener/assets/viewer.html @@ -1,183 +1,181 @@ - + - - - - Diff Viewer - - - -
- - + + +
+ + - + render(html`<${App} />`, document.getElementById('app')); + + diff --git a/plugins/listener/js/bindings.gen.ts b/plugins/listener/js/bindings.gen.ts index fa09b18f41..fab4615e3f 100644 --- a/plugins/listener/js/bindings.gen.ts +++ b/plugins/listener/js/bindings.gen.ts @@ -1,150 +1,220 @@ // @ts-nocheck +/** tauri-specta globals **/ +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; +import * as TAURI_API_EVENT from "@tauri-apps/api/event"; +import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. /** user-defined commands **/ - export const commands = { -async listMicrophoneDevices() : Promise> { + async listMicrophoneDevices(): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:listener|list_microphone_devices") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getCurrentMicrophoneDevice() : Promise> { + return { + status: "ok", + data: await TAURI_INVOKE("plugin:listener|list_microphone_devices"), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, + async getCurrentMicrophoneDevice(): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:listener|get_current_microphone_device") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getMicMuted() : Promise> { + return { + status: "ok", + data: await TAURI_INVOKE( + "plugin:listener|get_current_microphone_device", + ), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, + async getMicMuted(): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:listener|get_mic_muted") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async setMicMuted(muted: boolean) : Promise> { + return { + status: "ok", + data: await TAURI_INVOKE("plugin:listener|get_mic_muted"), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, + async setMicMuted(muted: boolean): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:listener|set_mic_muted", { muted }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async startSession(params: SessionParams) : Promise> { + return { + status: "ok", + data: await TAURI_INVOKE("plugin:listener|set_mic_muted", { muted }), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, + async startSession(params: SessionParams): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:listener|start_session", { params }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async stopSession() : Promise> { + return { + status: "ok", + data: await TAURI_INVOKE("plugin:listener|start_session", { params }), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, + async stopSession(): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:listener|stop_session") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getState() : Promise> { + return { + status: "ok", + data: await TAURI_INVOKE("plugin:listener|stop_session"), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, + async getState(): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:listener|get_state") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -} -} + return { + status: "ok", + data: await TAURI_INVOKE("plugin:listener|get_state"), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, +}; /** user-defined events **/ - export const events = __makeEvents__<{ -sessionEvent: SessionEvent + sessionEvent: SessionEvent; }>({ -sessionEvent: "plugin:listener:session-event" -}) + sessionEvent: "plugin:listener:session-event", +}); /** user-defined constants **/ - - /** user-defined types **/ -export type SessionEvent = { type: "inactive"; session_id: string } | { type: "running_active"; session_id: string } | { type: "finalizing"; session_id: string } | { type: "audioAmplitude"; session_id: string; mic: number; speaker: number } | { type: "micMuted"; session_id: string; value: boolean } | { type: "streamResponse"; session_id: string; response: StreamResponse } | { type: "ExitRequested" } -export type SessionParams = { session_id: string; languages: string[]; onboarding: boolean; record_enabled: boolean; model: string; base_url: string; api_key: string; keywords: string[] } -export type StreamAlternatives = { transcript: string; words: StreamWord[]; confidence: number; languages?: string[] } -export type StreamChannel = { alternatives: StreamAlternatives[] } -export type StreamExtra = { started_unix_millis: number } -export type StreamMetadata = { request_id: string; model_info: StreamModelInfo; model_uuid: string; extra?: StreamExtra } -export type StreamModelInfo = { name: string; version: string; arch: string } -export type StreamResponse = { type: "Results"; start: number; duration: number; is_final: boolean; speech_final: boolean; from_finalize: boolean; channel: StreamChannel; metadata: StreamMetadata; channel_index: number[] } | { type: "Metadata"; request_id: string; created: string; duration: number; channels: number } | { type: "SpeechStarted"; channel: number[]; timestamp: number } | { type: "UtteranceEnd"; channel: number[]; last_word_end: number } -export type StreamWord = { word: string; start: number; end: number; confidence: number; speaker: number | null; punctuated_word: string | null; language: string | null } - -/** tauri-specta globals **/ - -import { - invoke as TAURI_INVOKE, - Channel as TAURI_CHANNEL, -} from "@tauri-apps/api/core"; -import * as TAURI_API_EVENT from "@tauri-apps/api/event"; -import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; +export type SessionEvent = + | { type: "inactive"; session_id: string } + | { type: "running_active"; session_id: string } + | { type: "finalizing"; session_id: string } + | { type: "audioAmplitude"; session_id: string; mic: number; speaker: number } + | { type: "micMuted"; session_id: string; value: boolean } + | { type: "streamResponse"; session_id: string; response: StreamResponse }; +export type SessionParams = { + session_id: string; + languages: string[]; + onboarding: boolean; + record_enabled: boolean; + model: string; + base_url: string; + api_key: string; + keywords: string[]; +}; +export type StreamAlternatives = { + transcript: string; + words: StreamWord[]; + confidence: number; + languages?: string[]; +}; +export type StreamChannel = { alternatives: StreamAlternatives[] }; +export type StreamExtra = { started_unix_millis: number }; +export type StreamMetadata = { + request_id: string; + model_info: StreamModelInfo; + model_uuid: string; + extra?: StreamExtra; +}; +export type StreamModelInfo = { name: string; version: string; arch: string }; +export type StreamResponse = + | { + type: "Results"; + start: number; + duration: number; + is_final: boolean; + speech_final: boolean; + from_finalize: boolean; + channel: StreamChannel; + metadata: StreamMetadata; + channel_index: number[]; + } + | { + type: "Metadata"; + request_id: string; + created: string; + duration: number; + channels: number; + } + | { type: "SpeechStarted"; channel: number[]; timestamp: number } + | { type: "UtteranceEnd"; channel: number[]; last_word_end: number }; +export type StreamWord = { + word: string; + start: number; + end: number; + confidence: number; + speaker: number | null; + punctuated_word: string | null; + language: string | null; +}; type __EventObj__ = { - listen: ( - cb: TAURI_API_EVENT.EventCallback, - ) => ReturnType>; - once: ( - cb: TAURI_API_EVENT.EventCallback, - ) => ReturnType>; - emit: null extends T - ? (payload?: T) => ReturnType - : (payload: T) => ReturnType; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + emit: null extends T + ? (payload?: T) => ReturnType + : (payload: T) => ReturnType; }; export type Result = - | { status: "ok"; data: T } - | { status: "error"; error: E }; + | { status: "ok"; data: T } + | { status: "error"; error: E }; function __makeEvents__>( - mappings: Record, + mappings: Record, ) { - return new Proxy( - {} as unknown as { - [K in keyof T]: __EventObj__ & { - (handle: __WebviewWindow__): __EventObj__; - }; - }, - { - get: (_, event) => { - const name = mappings[event as keyof T]; - - return new Proxy((() => {}) as any, { - apply: (_, __, [window]: [__WebviewWindow__]) => ({ - listen: (arg: any) => window.listen(name, arg), - once: (arg: any) => window.once(name, arg), - emit: (arg: any) => window.emit(name, arg), - }), - get: (_, command: keyof __EventObj__) => { - switch (command) { - case "listen": - return (arg: any) => TAURI_API_EVENT.listen(name, arg); - case "once": - return (arg: any) => TAURI_API_EVENT.once(name, arg); - case "emit": - return (arg: any) => TAURI_API_EVENT.emit(name, arg); - } - }, - }); - }, - }, - ); + return new Proxy( + {} as unknown as { + [K in keyof T]: __EventObj__ & { + (handle: __WebviewWindow__): __EventObj__; + }; + }, + { + get: (_, event) => { + const name = mappings[event as keyof T]; + + return new Proxy((() => {}) as any, { + apply: (_, __, [window]: [__WebviewWindow__]) => ({ + listen: (arg: any) => window.listen(name, arg), + once: (arg: any) => window.once(name, arg), + emit: (arg: any) => window.emit(name, arg), + }), + get: (_, command: keyof __EventObj__) => { + switch (command) { + case "listen": + return (arg: any) => TAURI_API_EVENT.listen(name, arg); + case "once": + return (arg: any) => TAURI_API_EVENT.once(name, arg); + case "emit": + return (arg: any) => TAURI_API_EVENT.emit(name, arg); + } + }, + }); + }, + }, + ); } diff --git a/plugins/listener/src/events.rs b/plugins/listener/src/events.rs index 4a1cdab6cf..77e3e171fd 100644 --- a/plugins/listener/src/events.rs +++ b/plugins/listener/src/events.rs @@ -30,6 +30,5 @@ common_event_derives! { session_id: String, response: Box, }, - ExitRequested } } diff --git a/plugins/listener/src/lib.rs b/plugins/listener/src/lib.rs index 56572e64c5..720d5bb17f 100644 --- a/plugins/listener/src/lib.rs +++ b/plugins/listener/src/lib.rs @@ -68,21 +68,6 @@ pub fn init() -> tauri::plugin::TauriPlugin { Ok(()) }) - .on_event(move |app, event| { - if let tauri::RunEvent::Ready { .. } = event { - let app_handle = app.clone(); - hypr_intercept::register_quit_handler(PLUGIN_NAME, move || { - let state = app_handle.state::(); - match state.try_lock() { - Ok(guard) => guard.session_supervisor.is_none(), - Err(_) => false, - } - }); - } - }) - .on_drop(|_app| { - hypr_intercept::unregister_quit_handler(PLUGIN_NAME); - }) .build() } diff --git a/plugins/listener2/js/bindings.gen.ts b/plugins/listener2/js/bindings.gen.ts index 2029aa0fd1..3bbb9da024 100644 --- a/plugins/listener2/js/bindings.gen.ts +++ b/plugins/listener2/js/bindings.gen.ts @@ -1,6 +1,9 @@ // @ts-nocheck /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -31,7 +34,10 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async exportToVtt(sessionId: string, words: VttWord[]): Promise> { + async exportToVtt( + sessionId: string, + words: VttWord[], + ): Promise> { try { return { status: "ok", @@ -153,16 +159,24 @@ export type Token = { text: string; start_time: number; end_time: number }; export type VttWord = { text: string; start_ms: number; end_ms: number }; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/local-llm/js/bindings.gen.ts b/plugins/local-llm/js/bindings.gen.ts index 8a35ed5781..8b47a142cc 100644 --- a/plugins/local-llm/js/bindings.gen.ts +++ b/plugins/local-llm/js/bindings.gen.ts @@ -1,6 +1,9 @@ // @ts-nocheck /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -34,7 +37,9 @@ export const commands = { async isServerRunning(): Promise { return await TAURI_INVOKE("plugin:local-llm|is_server_running"); }, - async isModelDownloaded(model: SupportedModel): Promise> { + async isModelDownloaded( + model: SupportedModel, + ): Promise> { try { return { status: "ok", @@ -47,7 +52,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async isModelDownloading(model: SupportedModel): Promise> { + async isModelDownloading( + model: SupportedModel, + ): Promise> { try { return { status: "ok", @@ -160,18 +167,25 @@ export const commands = { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:local-llm|get_current_model_selection"), + data: await TAURI_INVOKE( + "plugin:local-llm|get_current_model_selection", + ), }; } catch (e) { if (e instanceof Error) throw e; else return { status: "error", error: e as any }; } }, - async setCurrentModelSelection(model: ModelSelection): Promise> { + async setCurrentModelSelection( + model: ModelSelection, + ): Promise> { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:local-llm|set_current_model_selection", { model }), + data: await TAURI_INVOKE( + "plugin:local-llm|set_current_model_selection", + { model }, + ), }; } catch (e) { if (e instanceof Error) throw e; @@ -207,16 +221,24 @@ export type SupportedModel = "Llama3p2_3bQ4" | "Gemma3_4bQ4" | "HyprLLM"; export type TAURI_CHANNEL = null; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/local-stt/js/bindings.gen.ts b/plugins/local-stt/js/bindings.gen.ts index 9c315a09cc..19019a9781 100644 --- a/plugins/local-stt/js/bindings.gen.ts +++ b/plugins/local-stt/js/bindings.gen.ts @@ -1,6 +1,9 @@ // @ts-nocheck /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -20,7 +23,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async isModelDownloaded(model: SupportedSttModel): Promise> { + async isModelDownloaded( + model: SupportedSttModel, + ): Promise> { try { return { status: "ok", @@ -33,7 +38,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async isModelDownloading(model: SupportedSttModel): Promise> { + async isModelDownloading( + model: SupportedSttModel, + ): Promise> { try { return { status: "ok", @@ -60,7 +67,9 @@ export const commands = { async cancelDownload(model: SupportedSttModel): Promise { return await TAURI_INVOKE("plugin:local-stt|cancel_download", { model }); }, - async getServers(): Promise, string>> { + async getServers(): Promise< + Result, string> + > { try { return { status: "ok", @@ -82,7 +91,9 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async stopServer(serverType: ServerType | null): Promise> { + async stopServer( + serverType: ServerType | null, + ): Promise> { try { return { status: "ok", @@ -125,7 +136,10 @@ export const events = __makeEvents__<{ /** user-defined types **/ -export type AmModel = "am-parakeet-v2" | "am-parakeet-v3" | "am-whisper-large-v3"; +export type AmModel = + | "am-parakeet-v2" + | "am-parakeet-v3" + | "am-whisper-large-v3"; export type DownloadProgressPayload = { model: SupportedSttModel; progress: number; @@ -153,16 +167,24 @@ export type WhisperModel = | "QuantizedLargeTurbo"; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/local-stt/src/ext.rs b/plugins/local-stt/src/ext.rs index df2a7104b0..dc742427f7 100644 --- a/plugins/local-stt/src/ext.rs +++ b/plugins/local-stt/src/ext.rs @@ -106,16 +106,16 @@ impl> LocalSttPluginExt for T { ServerType::External => external_health().await, }; - if let Some(info) = current_info.as_ref() - && info.model.as_ref() == Some(&model) - { - if let Some(url) = info.url.clone() { - return Ok(url); - } + if let Some(info) = current_info.as_ref() { + if info.model.as_ref() == Some(&model) { + if let Some(url) = info.url.clone() { + return Ok(url); + } - return Err(crate::Error::ServerStartFailed( - "missing_health_url".to_string(), - )); + return Err(crate::Error::ServerStartFailed( + "missing_health_url".to_string(), + )); + } } if matches!(server_type, ServerType::Internal) && !self.is_model_downloaded(&model).await? { @@ -514,7 +514,10 @@ async fn internal_health() -> Option { match registry::where_is(internal::InternalSTTActor::name()) { Some(cell) => { let actor: ActorRef = cell.into(); - call_t!(actor, internal::InternalSTTMessage::GetHealth, 10 * 1000).ok() + match call_t!(actor, internal::InternalSTTMessage::GetHealth, 10 * 1000) { + Ok(info) => Some(info), + Err(_) => None, + } } None => None, } @@ -524,7 +527,10 @@ async fn external_health() -> Option { match registry::where_is(external::ExternalSTTActor::name()) { Some(cell) => { let actor: ActorRef = cell.into(); - call_t!(actor, external::ExternalSTTMessage::GetHealth, 10 * 1000).ok() + match call_t!(actor, external::ExternalSTTMessage::GetHealth, 10 * 1000) { + Ok(info) => Some(info), + Err(_) => None, + } } None => None, } diff --git a/plugins/local-stt/src/server/external.rs b/plugins/local-stt/src/server/external.rs index 4e9e09a645..a8b2767646 100644 --- a/plugins/local-stt/src/server/external.rs +++ b/plugins/local-stt/src/server/external.rs @@ -77,20 +77,20 @@ impl ExternalSTTActor { fn cleanup_state(state: &mut ExternalSTTState) { let mut kill_failed = false; - if let Some(process) = state.process_handle.take() - && let Err(e) = process.kill() - { - if let tauri_plugin_shell::Error::Io(io_err) = &e { - match io_err.kind() { - io::ErrorKind::InvalidInput | io::ErrorKind::NotFound => {} - _ => { - tracing::error!("failed_to_kill_process: {:?}", e); - kill_failed = true; + if let Some(process) = state.process_handle.take() { + if let Err(e) = process.kill() { + if let tauri_plugin_shell::Error::Io(io_err) = &e { + match io_err.kind() { + io::ErrorKind::InvalidInput | io::ErrorKind::NotFound => {} + _ => { + tracing::error!("failed_to_kill_process: {:?}", e); + kill_failed = true; + } } + } else { + tracing::error!("failed_to_kill_process: {:?}", e); + kill_failed = true; } - } else { - tracing::error!("failed_to_kill_process: {:?}", e); - kill_failed = true; } } diff --git a/plugins/local-stt/src/server/supervisor.rs b/plugins/local-stt/src/server/supervisor.rs index f6e881138a..791d04bcda 100644 --- a/plugins/local-stt/src/server/supervisor.rs +++ b/plugins/local-stt/src/server/supervisor.rs @@ -112,10 +112,10 @@ pub async fn stop_stt_server( let result = DynamicSupervisor::terminate_child(supervisor.clone(), child_id.to_string()).await; if let Err(e) = result { - if let Some(supervisor_error) = e.downcast_ref::() - && matches!(supervisor_error, SupervisorError::ChildNotFound { .. }) - { - return Ok(()); + if let Some(supervisor_error) = e.downcast_ref::() { + if matches!(supervisor_error, SupervisorError::ChildNotFound { .. }) { + return Ok(()); + } } return Err(e); } diff --git a/plugins/misc/js/bindings.gen.ts b/plugins/misc/js/bindings.gen.ts index 0aaf8397de..bbc4687dcf 100644 --- a/plugins/misc/js/bindings.gen.ts +++ b/plugins/misc/js/bindings.gen.ts @@ -3,7 +3,10 @@ /** user-defined constants **/ /** user-defined types **/ /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -107,7 +110,10 @@ export const commands = { else return { status: "error", error: e as any }; } }, - async audioImport(sessionId: string, sourcePath: string): Promise> { + async audioImport( + sessionId: string, + sourcePath: string, + ): Promise> { try { return { status: "ok", @@ -124,16 +130,24 @@ export const commands = { }; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/misc/src/ext.rs b/plugins/misc/src/ext.rs index a309e9a0b1..3f53bf5668 100644 --- a/plugins/misc/src/ext.rs +++ b/plugins/misc/src/ext.rs @@ -30,10 +30,10 @@ impl> MiscPluginExt for T { } let url_pattern = r"https?://[^\s]+"; - if let Ok(regex) = regex::Regex::new(url_pattern) - && let Some(capture) = regex.find(text) - { - return Some(capture.as_str().to_string()); + if let Ok(regex) = regex::Regex::new(url_pattern) { + if let Some(capture) = regex.find(text) { + return Some(capture.as_str().to_string()); + } } None diff --git a/plugins/network/js/bindings.gen.ts b/plugins/network/js/bindings.gen.ts index 40643386b2..1d9b04d00b 100644 --- a/plugins/network/js/bindings.gen.ts +++ b/plugins/network/js/bindings.gen.ts @@ -1,6 +1,9 @@ // @ts-nocheck /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -25,16 +28,24 @@ export const events = __makeEvents__<{ export type NetworkStatusEvent = { is_online: boolean }; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/network/src/actor.rs b/plugins/network/src/actor.rs index a4819d4f1b..f4d375d726 100644 --- a/plugins/network/src/actor.rs +++ b/plugins/network/src/actor.rs @@ -81,7 +81,9 @@ fn schedule_check(actor: ActorRef) { } async fn check_network() -> bool { - let client = reqwest::Client::builder().timeout(REQUEST_TIMEOUT).build(); + let client = reqwest::Client::builder() + .timeout(REQUEST_TIMEOUT.into()) + .build(); let client = match client { Ok(c) => c, diff --git a/plugins/notification/Cargo.toml b/plugins/notification/Cargo.toml index c80b7b6072..373ce02b41 100644 --- a/plugins/notification/Cargo.toml +++ b/plugins/notification/Cargo.toml @@ -18,7 +18,7 @@ hypr-db-user = { workspace = true } hypr-detect = { workspace = true } hypr-host = { workspace = true } hypr-intercept = { workspace = true } -hypr-notification = { workspace = true, features = ["legacy"] } +hypr-notification = { workspace = true } tauri-plugin-analytics = { workspace = true } tauri-plugin-db = { workspace = true } diff --git a/plugins/notification/js/bindings.gen.ts b/plugins/notification/js/bindings.gen.ts index a26f65b7de..beec216499 100644 --- a/plugins/notification/js/bindings.gen.ts +++ b/plugins/notification/js/bindings.gen.ts @@ -1,6 +1,9 @@ // @ts-nocheck /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -51,16 +54,24 @@ export type Notification = { }; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/notification/src/lib.rs b/plugins/notification/src/lib.rs index c5a7300283..d74a1cfa64 100644 --- a/plugins/notification/src/lib.rs +++ b/plugins/notification/src/lib.rs @@ -3,7 +3,6 @@ use std::str::FromStr; mod commands; mod error; mod ext; -mod handler; pub use error::*; pub use ext::*; @@ -25,10 +24,7 @@ pub fn init() -> tauri::plugin::TauriPlugin { tauri::plugin::Builder::new(PLUGIN_NAME) .invoke_handler(specta_builder.invoke_handler()) - .setup(|app, _api| { - handler::init(app.clone()); - Ok(()) - }) + .setup(|_app, _api| Ok(())) .on_event(|app, event| match event { tauri::RunEvent::MainEventsCleared => {} tauri::RunEvent::Ready => {} diff --git a/plugins/permissions/Cargo.toml b/plugins/permissions/Cargo.toml index 81b926685d..90ed984c33 100644 --- a/plugins/permissions/Cargo.toml +++ b/plugins/permissions/Cargo.toml @@ -12,6 +12,8 @@ tauri-plugin = { workspace = true, features = ["build"] } [dev-dependencies] specta-typescript = { workspace = true } +tokio = { workspace = true, features = ["macros"] } +tauri-plugin-shell = { workspace = true } [dependencies] hypr-audio = { workspace = true } diff --git a/plugins/permissions/js/bindings.gen.ts b/plugins/permissions/js/bindings.gen.ts index 4711aca7e4..dade488c84 100644 --- a/plugins/permissions/js/bindings.gen.ts +++ b/plugins/permissions/js/bindings.gen.ts @@ -1,6 +1,9 @@ // @ts-nocheck /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -13,7 +16,9 @@ export const commands = { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:permissions|check_microphone_permission"), + data: await TAURI_INVOKE( + "plugin:permissions|check_microphone_permission", + ), }; } catch (e) { if (e instanceof Error) throw e; @@ -24,18 +29,24 @@ export const commands = { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:permissions|request_microphone_permission"), + data: await TAURI_INVOKE( + "plugin:permissions|request_microphone_permission", + ), }; } catch (e) { if (e instanceof Error) throw e; else return { status: "error", error: e as any }; } }, - async checkSystemAudioPermission(): Promise> { + async checkSystemAudioPermission(): Promise< + Result + > { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:permissions|check_system_audio_permission"), + data: await TAURI_INVOKE( + "plugin:permissions|check_system_audio_permission", + ), }; } catch (e) { if (e instanceof Error) throw e; @@ -46,18 +57,24 @@ export const commands = { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:permissions|request_system_audio_permission"), + data: await TAURI_INVOKE( + "plugin:permissions|request_system_audio_permission", + ), }; } catch (e) { if (e instanceof Error) throw e; else return { status: "error", error: e as any }; } }, - async checkAccessibilityPermission(): Promise> { + async checkAccessibilityPermission(): Promise< + Result + > { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:permissions|check_accessibility_permission"), + data: await TAURI_INVOKE( + "plugin:permissions|check_accessibility_permission", + ), }; } catch (e) { if (e instanceof Error) throw e; @@ -68,7 +85,9 @@ export const commands = { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:permissions|request_accessibility_permission"), + data: await TAURI_INVOKE( + "plugin:permissions|request_accessibility_permission", + ), }; } catch (e) { if (e instanceof Error) throw e; @@ -79,7 +98,9 @@ export const commands = { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:permissions|check_calendar_permission"), + data: await TAURI_INVOKE( + "plugin:permissions|check_calendar_permission", + ), }; } catch (e) { if (e instanceof Error) throw e; @@ -90,7 +111,9 @@ export const commands = { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:permissions|request_calendar_permission"), + data: await TAURI_INVOKE( + "plugin:permissions|request_calendar_permission", + ), }; } catch (e) { if (e instanceof Error) throw e; @@ -101,7 +124,9 @@ export const commands = { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:permissions|check_contacts_permission"), + data: await TAURI_INVOKE( + "plugin:permissions|check_contacts_permission", + ), }; } catch (e) { if (e instanceof Error) throw e; @@ -112,7 +137,9 @@ export const commands = { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:permissions|request_contacts_permission"), + data: await TAURI_INVOKE( + "plugin:permissions|request_contacts_permission", + ), }; } catch (e) { if (e instanceof Error) throw e; @@ -152,16 +179,24 @@ export const commands = { export type PermissionStatus = "neverRequested" | "denied" | "authorized"; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/permissions/js/index.ts b/plugins/permissions/js/index.ts index a96e122f03..c1e919e037 100644 --- a/plugins/permissions/js/index.ts +++ b/plugins/permissions/js/index.ts @@ -1 +1 @@ -export * from "./bindings.gen"; +export * from './bindings.gen'; \ No newline at end of file diff --git a/plugins/permissions/src/commands.rs b/plugins/permissions/src/commands.rs index c13a863436..a6bd137936 100644 --- a/plugins/permissions/src/commands.rs +++ b/plugins/permissions/src/commands.rs @@ -1,288 +1,121 @@ -use futures_util::StreamExt; - -use crate::models::PermissionStatus; - -#[cfg(target_os = "macos")] -use block2::StackBlock; -#[cfg(target_os = "macos")] -use objc2_av_foundation::{AVCaptureDevice, AVMediaTypeAudio}; -#[cfg(target_os = "macos")] -use objc2_contacts::{CNContactStore, CNEntityType}; -#[cfg(target_os = "macos")] -use objc2_event_kit::{EKEntityType, EKEventStore}; +use crate::PermissionsPluginExt; #[tauri::command] #[specta::specta] -pub async fn check_microphone_permission( - _app: tauri::AppHandle, -) -> Result { - #[cfg(target_os = "macos")] - { - let status = unsafe { - let media_type = AVMediaTypeAudio.unwrap(); - AVCaptureDevice::authorizationStatusForMediaType(media_type) - }; - Ok(status.into()) - } - - #[cfg(not(target_os = "macos"))] - { - let mut mic_sample_stream = hypr_audio::AudioInput::from_mic(None) - .map_err(|e| e.to_string())? - .stream(); - let sample = mic_sample_stream.next().await; - Ok(if sample.is_some() { - PermissionStatus::Authorized - } else { - PermissionStatus::Denied - }) - } +pub(crate) async fn check_microphone_permission( + app: tauri::AppHandle, +) -> Result { + app.check_microphone_permission() + .await + .map_err(|e| e.to_string()) } #[tauri::command] #[specta::specta] -pub async fn request_microphone_permission( - _app: tauri::AppHandle, +pub(crate) async fn request_microphone_permission( + app: tauri::AppHandle, ) -> Result<(), String> { - #[cfg(target_os = "macos")] - { - unsafe { - let media_type = AVMediaTypeAudio.unwrap(); - let block = StackBlock::new(|_granted| {}); - AVCaptureDevice::requestAccessForMediaType_completionHandler(media_type, &block); - } - } - - #[cfg(not(target_os = "macos"))] - { - let mut mic_sample_stream = hypr_audio::AudioInput::from_mic(None) - .map_err(|e| e.to_string())? - .stream(); - mic_sample_stream.next().await; - } - - Ok(()) + app.request_microphone_permission() + .await + .map_err(|e| e.to_string()) } #[tauri::command] #[specta::specta] -pub async fn check_system_audio_permission( - _app: tauri::AppHandle, -) -> Result { - #[cfg(target_os = "macos")] - { - let status = hypr_tcc::audio_capture_permission_status(); - Ok(status.into()) - } - - #[cfg(not(target_os = "macos"))] - { - let mut speaker_sample_stream = hypr_audio::AudioInput::from_speaker().stream(); - let sample = speaker_sample_stream.next().await; - Ok(if sample.is_some() { - PermissionStatus::Authorized - } else { - PermissionStatus::Denied - }) - } +pub(crate) async fn check_system_audio_permission( + app: tauri::AppHandle, +) -> Result { + app.check_system_audio_permission() + .await + .map_err(|e| e.to_string()) } #[tauri::command] #[specta::specta] -pub async fn request_system_audio_permission( - #[allow(unused_variables)] app: tauri::AppHandle, +pub(crate) async fn request_system_audio_permission( + app: tauri::AppHandle, ) -> Result<(), String> { - #[cfg(target_os = "macos")] - { - use tauri_plugin_shell::ShellExt; - - let bundle_id = app.config().identifier.clone(); - app.shell() - .command("tccutil") - .args(["reset", "AudioCapture", &bundle_id]) - .spawn() - .ok(); - } - - let stop = hypr_audio::AudioOutput::silence(); - - let mut speaker_sample_stream = hypr_audio::AudioInput::from_speaker().stream(); - speaker_sample_stream.next().await; - - let _ = stop.send(()); - Ok(()) + app.request_system_audio_permission() + .await + .map_err(|e| e.to_string()) } #[tauri::command] #[specta::specta] -pub async fn check_accessibility_permission( - _app: tauri::AppHandle, -) -> Result { - #[cfg(target_os = "macos")] - { - let is_trusted = macos_accessibility_client::accessibility::application_is_trusted(); - Ok(if is_trusted { - PermissionStatus::Authorized - } else { - PermissionStatus::Denied - }) - } - - #[cfg(not(target_os = "macos"))] - { - Ok(PermissionStatus::Denied) - } +pub(crate) async fn check_accessibility_permission( + app: tauri::AppHandle, +) -> Result { + app.check_accessibility_permission() + .await + .map_err(|e| e.to_string()) } #[tauri::command] #[specta::specta] -pub async fn request_accessibility_permission( - _app: tauri::AppHandle, +pub(crate) async fn request_accessibility_permission( + app: tauri::AppHandle, ) -> Result<(), String> { - #[cfg(target_os = "macos")] - { - macos_accessibility_client::accessibility::application_is_trusted_with_prompt(); - } - - Ok(()) + app.request_accessibility_permission() + .await + .map_err(|e| e.to_string()) } #[tauri::command] #[specta::specta] -pub async fn check_calendar_permission( - _app: tauri::AppHandle, -) -> Result { - #[cfg(target_os = "macos")] - { - let status = unsafe { EKEventStore::authorizationStatusForEntityType(EKEntityType::Event) }; - Ok(status.into()) - } - - #[cfg(not(target_os = "macos"))] - { - Ok(PermissionStatus::Denied) - } +pub(crate) async fn check_calendar_permission( + app: tauri::AppHandle, +) -> Result { + app.check_calendar_permission() + .await + .map_err(|e| e.to_string()) } #[tauri::command] #[specta::specta] -pub async fn request_calendar_permission( - #[allow(unused_variables)] app: tauri::AppHandle, +pub(crate) async fn request_calendar_permission( + app: tauri::AppHandle, ) -> Result<(), String> { - #[cfg(target_os = "macos")] - { - use objc2_foundation::NSError; - use tauri_plugin_shell::ShellExt; - - let bundle_id = app.config().identifier.clone(); - app.shell() - .command("tccutil") - .args(["reset", "Calendar", &bundle_id]) - .spawn() - .ok(); - - let event_store = unsafe { EKEventStore::new() }; - let (tx, rx) = std::sync::mpsc::channel::(); - let completion = - block2::RcBlock::new(move |granted: objc2::runtime::Bool, _error: *mut NSError| { - let _ = tx.send(granted.as_bool()); - }); - - unsafe { - event_store.requestFullAccessToEventsWithCompletion(&*completion as *const _ as *mut _) - }; - - let _ = rx.recv_timeout(std::time::Duration::from_secs(60)); - } - - Ok(()) + app.request_calendar_permission() + .await + .map_err(|e| e.to_string()) } #[tauri::command] #[specta::specta] -pub async fn check_contacts_permission( - _app: tauri::AppHandle, -) -> Result { - #[cfg(target_os = "macos")] - { - let status = - unsafe { CNContactStore::authorizationStatusForEntityType(CNEntityType::Contacts) }; - Ok(status.into()) - } - - #[cfg(not(target_os = "macos"))] - { - Ok(PermissionStatus::Denied) - } +pub(crate) async fn check_contacts_permission( + app: tauri::AppHandle, +) -> Result { + app.check_contacts_permission() + .await + .map_err(|e| e.to_string()) } #[tauri::command] #[specta::specta] -pub async fn request_contacts_permission( - #[allow(unused_variables)] app: tauri::AppHandle, +pub(crate) async fn request_contacts_permission( + app: tauri::AppHandle, ) -> Result<(), String> { - #[cfg(target_os = "macos")] - { - use objc2_foundation::NSError; - use tauri_plugin_shell::ShellExt; - - let bundle_id = app.config().identifier.clone(); - app.shell() - .command("tccutil") - .args(["reset", "AddressBook", &bundle_id]) - .spawn() - .ok(); - - let contacts_store = unsafe { CNContactStore::new() }; - let (tx, rx) = std::sync::mpsc::channel::(); - let completion = - block2::RcBlock::new(move |granted: objc2::runtime::Bool, _error: *mut NSError| { - let _ = tx.send(granted.as_bool()); - }); - - unsafe { - contacts_store - .requestAccessForEntityType_completionHandler(CNEntityType::Contacts, &completion); - }; - - let _ = rx.recv_timeout(std::time::Duration::from_secs(60)); - } - - Ok(()) + app.request_contacts_permission() + .await + .map_err(|e| e.to_string()) } #[tauri::command] #[specta::specta] -pub async fn open_calendar_settings( - _app: tauri::AppHandle, +pub(crate) async fn open_calendar_settings( + app: tauri::AppHandle, ) -> Result<(), String> { - #[cfg(target_os = "macos")] - { - std::process::Command::new("open") - .arg("x-apple.systempreferences:com.apple.preference.security?Privacy_Calendars") - .spawn() - .map_err(|e| e.to_string())? - .wait() - .map_err(|e| e.to_string())?; - } - - Ok(()) + app.open_calendar_settings() + .await + .map_err(|e| e.to_string()) } #[tauri::command] #[specta::specta] -pub async fn open_contacts_settings( - _app: tauri::AppHandle, +pub(crate) async fn open_contacts_settings( + app: tauri::AppHandle, ) -> Result<(), String> { - #[cfg(target_os = "macos")] - { - std::process::Command::new("open") - .arg("x-apple.systempreferences:com.apple.preference.security?Privacy_Contacts") - .spawn() - .map_err(|e| e.to_string())? - .wait() - .map_err(|e| e.to_string())?; - } - - Ok(()) + app.open_contacts_settings() + .await + .map_err(|e| e.to_string()) } diff --git a/plugins/permissions/src/error.rs b/plugins/permissions/src/error.rs index fb57ca5726..2065de6dee 100644 --- a/plugins/permissions/src/error.rs +++ b/plugins/permissions/src/error.rs @@ -1,4 +1,4 @@ -use serde::{Serialize, ser::Serializer}; +use serde::{ser::Serializer, Serialize}; pub type Result = std::result::Result; @@ -6,6 +6,8 @@ pub type Result = std::result::Result; pub enum Error { #[error(transparent)] Io(#[from] std::io::Error), + #[error(transparent)] + Audio(#[from] hypr_audio::Error), #[cfg(mobile)] #[error(transparent)] PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), diff --git a/plugins/permissions/src/ext.rs b/plugins/permissions/src/ext.rs new file mode 100644 index 0000000000..24a3da43ef --- /dev/null +++ b/plugins/permissions/src/ext.rs @@ -0,0 +1,271 @@ +use std::future::Future; + +use crate::models::PermissionStatus; + +#[cfg(target_os = "macos")] +use block2::StackBlock; +#[cfg(target_os = "macos")] +use objc2_av_foundation::{AVCaptureDevice, AVMediaTypeAudio}; +#[cfg(target_os = "macos")] +use objc2_contacts::{CNContactStore, CNEntityType}; +#[cfg(target_os = "macos")] +use objc2_event_kit::{EKEntityType, EKEventStore}; + +pub trait PermissionsPluginExt { + fn check_microphone_permission( + &self, + ) -> impl Future>; + fn request_microphone_permission(&self) -> impl Future>; + fn check_system_audio_permission( + &self, + ) -> impl Future>; + fn request_system_audio_permission(&self) -> impl Future>; + fn check_accessibility_permission( + &self, + ) -> impl Future>; + fn request_accessibility_permission(&self) -> impl Future>; + fn check_calendar_permission( + &self, + ) -> impl Future>; + fn request_calendar_permission(&self) -> impl Future>; + fn check_contacts_permission( + &self, + ) -> impl Future>; + fn request_contacts_permission(&self) -> impl Future>; + fn open_calendar_settings(&self) -> impl Future>; + fn open_contacts_settings(&self) -> impl Future>; +} + +impl> crate::PermissionsPluginExt for T { + async fn check_microphone_permission(&self) -> Result { + #[cfg(target_os = "macos")] + { + let status = unsafe { + let media_type = AVMediaTypeAudio.unwrap(); + AVCaptureDevice::authorizationStatusForMediaType(media_type) + }; + Ok(status.into()) + } + + #[cfg(not(target_os = "macos"))] + { + use futures_util::StreamExt; + let mut mic_sample_stream = + hypr_audio::AudioInput::from_mic(None)?.stream(); + let sample = mic_sample_stream.next().await; + Ok(if sample.is_some() { + PermissionStatus::Authorized + } else { + PermissionStatus::Denied + }) + } + } + + async fn request_microphone_permission(&self) -> Result<(), crate::Error> { + #[cfg(target_os = "macos")] + { + unsafe { + let media_type = AVMediaTypeAudio.unwrap(); + let block = StackBlock::new(|_granted| {}); + AVCaptureDevice::requestAccessForMediaType_completionHandler(media_type, &block); + } + } + + #[cfg(not(target_os = "macos"))] + { + use futures_util::StreamExt; + let mut mic_sample_stream = + hypr_audio::AudioInput::from_mic(None)?.stream(); + mic_sample_stream.next().await; + } + + Ok(()) + } + + async fn check_system_audio_permission(&self) -> Result { + #[cfg(target_os = "macos")] + { + let status = hypr_tcc::audio_capture_permission_status(); + Ok(status.into()) + } + + #[cfg(not(target_os = "macos"))] + { + use futures_util::StreamExt; + let mut speaker_sample_stream = hypr_audio::AudioInput::from_speaker().stream(); + let sample = speaker_sample_stream.next().await; + Ok(if sample.is_some() { + PermissionStatus::Authorized + } else { + PermissionStatus::Denied + }) + } + } + + async fn request_system_audio_permission(&self) -> Result<(), crate::Error> { + #[cfg(target_os = "macos")] + { + use tauri_plugin_shell::ShellExt; + + let bundle_id = self.config().identifier.clone(); + self.shell() + .command("tccutil") + .args(["reset", "AudioCapture", &bundle_id]) + .spawn() + .ok(); + } + + let stop = hypr_audio::AudioOutput::silence(); + + use futures_util::StreamExt; + let mut speaker_sample_stream = hypr_audio::AudioInput::from_speaker().stream(); + speaker_sample_stream.next().await; + + let _ = stop.send(()); + Ok(()) + } + + async fn check_accessibility_permission(&self) -> Result { + #[cfg(target_os = "macos")] + { + let is_trusted = + macos_accessibility_client::accessibility::application_is_trusted(); + Ok(if is_trusted { + PermissionStatus::Authorized + } else { + PermissionStatus::Denied + }) + } + + #[cfg(not(target_os = "macos"))] + { + Ok(PermissionStatus::Denied) + } + } + + async fn request_accessibility_permission(&self) -> Result<(), crate::Error> { + #[cfg(target_os = "macos")] + { + macos_accessibility_client::accessibility::application_is_trusted_with_prompt(); + } + + Ok(()) + } + + async fn check_calendar_permission(&self) -> Result { + #[cfg(target_os = "macos")] + { + let status = + unsafe { EKEventStore::authorizationStatusForEntityType(EKEntityType::Event) }; + Ok(status.into()) + } + + #[cfg(not(target_os = "macos"))] + { + Ok(PermissionStatus::Denied) + } + } + + async fn request_calendar_permission(&self) -> Result<(), crate::Error> { + #[cfg(target_os = "macos")] + { + use objc2_foundation::NSError; + use tauri_plugin_shell::ShellExt; + + let bundle_id = self.config().identifier.clone(); + self.shell() + .command("tccutil") + .args(["reset", "Calendar", &bundle_id]) + .spawn() + .ok(); + + let event_store = unsafe { EKEventStore::new() }; + let (tx, rx) = std::sync::mpsc::channel::(); + let completion = + block2::RcBlock::new(move |granted: objc2::runtime::Bool, _error: *mut NSError| { + let _ = tx.send(granted.as_bool()); + }); + + unsafe { + event_store + .requestFullAccessToEventsWithCompletion(&*completion as *const _ as *mut _) + }; + + let _ = rx.recv_timeout(std::time::Duration::from_secs(60)); + } + + Ok(()) + } + + async fn check_contacts_permission(&self) -> Result { + #[cfg(target_os = "macos")] + { + let status = unsafe { + CNContactStore::authorizationStatusForEntityType(CNEntityType::Contacts) + }; + Ok(status.into()) + } + + #[cfg(not(target_os = "macos"))] + { + Ok(PermissionStatus::Denied) + } + } + + async fn request_contacts_permission(&self) -> Result<(), crate::Error> { + #[cfg(target_os = "macos")] + { + use objc2_foundation::NSError; + use tauri_plugin_shell::ShellExt; + + let bundle_id = self.config().identifier.clone(); + self.shell() + .command("tccutil") + .args(["reset", "AddressBook", &bundle_id]) + .spawn() + .ok(); + + let contacts_store = unsafe { CNContactStore::new() }; + let (tx, rx) = std::sync::mpsc::channel::(); + let completion = + block2::RcBlock::new(move |granted: objc2::runtime::Bool, _error: *mut NSError| { + let _ = tx.send(granted.as_bool()); + }); + + unsafe { + contacts_store.requestAccessForEntityType_completionHandler( + CNEntityType::Contacts, + &completion, + ); + }; + + let _ = rx.recv_timeout(std::time::Duration::from_secs(60)); + } + + Ok(()) + } + + async fn open_calendar_settings(&self) -> Result<(), crate::Error> { + #[cfg(target_os = "macos")] + { + std::process::Command::new("open") + .arg("x-apple.systempreferences:com.apple.preference.security?Privacy_Calendars") + .spawn()? + .wait()?; + } + + Ok(()) + } + + async fn open_contacts_settings(&self) -> Result<(), crate::Error> { + #[cfg(target_os = "macos")] + { + std::process::Command::new("open") + .arg("x-apple.systempreferences:com.apple.preference.security?Privacy_Contacts") + .spawn()? + .wait()?; + } + + Ok(()) + } +} diff --git a/plugins/permissions/src/lib.rs b/plugins/permissions/src/lib.rs index fce8ee01ab..01328ebc09 100644 --- a/plugins/permissions/src/lib.rs +++ b/plugins/permissions/src/lib.rs @@ -1,8 +1,10 @@ mod commands; mod error; +mod ext; mod models; pub use error::{Error, Result}; +pub use ext::*; pub use models::PermissionStatus; const PLUGIN_NAME: &str = "permissions"; @@ -27,7 +29,7 @@ fn make_specta_builder() -> tauri_specta::Builder { .error_handling(tauri_specta::ErrorHandlingMode::Result) } -pub fn init() -> tauri::plugin::TauriPlugin { +pub fn init() -> tauri::plugin::TauriPlugin { let specta_builder = make_specta_builder(); tauri::plugin::Builder::new(PLUGIN_NAME) @@ -56,4 +58,19 @@ mod test { let content = std::fs::read_to_string(OUTPUT_FILE).unwrap(); std::fs::write(OUTPUT_FILE, format!("// @ts-nocheck\n{content}")).unwrap(); } + + fn create_app(builder: tauri::Builder) -> tauri::App { + builder + .plugin(tauri_plugin_shell::init()) + .plugin(init()) + .build(tauri::test::mock_context(tauri::test::noop_assets())) + .unwrap() + } + + #[tokio::test] + async fn test_permissions() { + let app = create_app(tauri::test::mock_builder()); + let status = app.check_calendar_permission().await; + println!("status: {:?}", status); + } } diff --git a/plugins/settings/.gitignore b/plugins/settings/.gitignore deleted file mode 100644 index 50d8e32e89..0000000000 --- a/plugins/settings/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -/.vs -.DS_Store -.Thumbs.db -*.sublime* -.idea/ -debug.log -package-lock.json -.vscode/settings.json -yarn.lock - -/.tauri -/target -Cargo.lock -node_modules/ - -dist-js -dist diff --git a/plugins/settings/Cargo.toml b/plugins/settings/Cargo.toml deleted file mode 100644 index ee5f896d24..0000000000 --- a/plugins/settings/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "tauri-plugin-settings" -version = "0.1.0" -authors = ["You"] -edition = "2024" -exclude = ["/js", "/node_modules"] -links = "tauri-plugin-settings" -description = "" - -[build-dependencies] -tauri-plugin = { workspace = true, features = ["build"] } - -[dev-dependencies] -specta-typescript = { workspace = true } -tokio = { workspace = true, features = ["macros"] } - -[dependencies] -tauri = { workspace = true, features = ["test"] } -tauri-specta = { workspace = true, features = ["derive", "typescript"] } - -serde = { workspace = true } -serde_json = { workspace = true } -specta = { workspace = true, features = ["derive", "serde_json"] } - -thiserror = { workspace = true } -tokio = { workspace = true, features = ["fs"] } diff --git a/plugins/settings/build.rs b/plugins/settings/build.rs deleted file mode 100644 index 9d8acf313d..0000000000 --- a/plugins/settings/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -const COMMANDS: &[&str] = &["path", "load", "save"]; - -fn main() { - tauri_plugin::Builder::new(COMMANDS).build(); -} diff --git a/plugins/settings/js/bindings.gen.ts b/plugins/settings/js/bindings.gen.ts deleted file mode 100644 index 8e1daafdd8..0000000000 --- a/plugins/settings/js/bindings.gen.ts +++ /dev/null @@ -1,90 +0,0 @@ -// @ts-nocheck - -// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. - -/** user-defined commands **/ - -export const commands = { - async path(): Promise { - return await TAURI_INVOKE("plugin:settings|path"); - }, - async load(): Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("plugin:settings|load") }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async save(settings: JsonValue): Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("plugin:settings|save", { settings }) }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, -}; - -/** user-defined events **/ - -/** user-defined constants **/ - -/** user-defined types **/ - -export type JsonValue = - | null - | boolean - | number - | string - | JsonValue[] - | Partial<{ [key in string]: JsonValue }>; - -/** tauri-specta globals **/ - -import { invoke as TAURI_INVOKE, Channel as TAURI_CHANNEL } from "@tauri-apps/api/core"; -import * as TAURI_API_EVENT from "@tauri-apps/api/event"; -import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; - -type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - emit: null extends T - ? (payload?: T) => ReturnType - : (payload: T) => ReturnType; -}; - -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; - -function __makeEvents__>(mappings: Record) { - return new Proxy( - {} as unknown as { - [K in keyof T]: __EventObj__ & { - (handle: __WebviewWindow__): __EventObj__; - }; - }, - { - get: (_, event) => { - const name = mappings[event as keyof T]; - - return new Proxy((() => {}) as any, { - apply: (_, __, [window]: [__WebviewWindow__]) => ({ - listen: (arg: any) => window.listen(name, arg), - once: (arg: any) => window.once(name, arg), - emit: (arg: any) => window.emit(name, arg), - }), - get: (_, command: keyof __EventObj__) => { - switch (command) { - case "listen": - return (arg: any) => TAURI_API_EVENT.listen(name, arg); - case "once": - return (arg: any) => TAURI_API_EVENT.once(name, arg); - case "emit": - return (arg: any) => TAURI_API_EVENT.emit(name, arg); - } - }, - }); - }, - }, - ); -} diff --git a/plugins/settings/js/index.ts b/plugins/settings/js/index.ts deleted file mode 100644 index a96e122f03..0000000000 --- a/plugins/settings/js/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./bindings.gen"; diff --git a/plugins/settings/package.json b/plugins/settings/package.json deleted file mode 100644 index 99b3c6218e..0000000000 --- a/plugins/settings/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@hypr/plugin-settings", - "private": true, - "main": "./js/index.ts", - "scripts": { - "codegen": "cargo test -p tauri-plugin-settings" - }, - "dependencies": { - "@tauri-apps/api": "^2.9.1" - } -} diff --git a/plugins/settings/permissions/autogenerated/commands/load.toml b/plugins/settings/permissions/autogenerated/commands/load.toml deleted file mode 100644 index f6e47ad8e0..0000000000 --- a/plugins/settings/permissions/autogenerated/commands/load.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-load" -description = "Enables the load command without any pre-configured scope." -commands.allow = ["load"] - -[[permission]] -identifier = "deny-load" -description = "Denies the load command without any pre-configured scope." -commands.deny = ["load"] diff --git a/plugins/settings/permissions/autogenerated/commands/path.toml b/plugins/settings/permissions/autogenerated/commands/path.toml deleted file mode 100644 index 564212541d..0000000000 --- a/plugins/settings/permissions/autogenerated/commands/path.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-path" -description = "Enables the path command without any pre-configured scope." -commands.allow = ["path"] - -[[permission]] -identifier = "deny-path" -description = "Denies the path command without any pre-configured scope." -commands.deny = ["path"] diff --git a/plugins/settings/permissions/autogenerated/commands/save.toml b/plugins/settings/permissions/autogenerated/commands/save.toml deleted file mode 100644 index d3e8422016..0000000000 --- a/plugins/settings/permissions/autogenerated/commands/save.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-save" -description = "Enables the save command without any pre-configured scope." -commands.allow = ["save"] - -[[permission]] -identifier = "deny-save" -description = "Denies the save command without any pre-configured scope." -commands.deny = ["save"] diff --git a/plugins/settings/permissions/autogenerated/reference.md b/plugins/settings/permissions/autogenerated/reference.md deleted file mode 100644 index b3c8e6226b..0000000000 --- a/plugins/settings/permissions/autogenerated/reference.md +++ /dev/null @@ -1,97 +0,0 @@ -## Default Permission - -Default permissions for the plugin - -#### This default permission set includes the following: - -- `allow-path` -- `allow-load` -- `allow-save` - -## Permission Table - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IdentifierDescription
- -`settings:allow-load` - - - -Enables the load command without any pre-configured scope. - -
- -`settings:deny-load` - - - -Denies the load command without any pre-configured scope. - -
- -`settings:allow-path` - - - -Enables the path command without any pre-configured scope. - -
- -`settings:deny-path` - - - -Denies the path command without any pre-configured scope. - -
- -`settings:allow-save` - - - -Enables the save command without any pre-configured scope. - -
- -`settings:deny-save` - - - -Denies the save command without any pre-configured scope. - -
diff --git a/plugins/settings/permissions/default.toml b/plugins/settings/permissions/default.toml deleted file mode 100644 index a59a0ffec3..0000000000 --- a/plugins/settings/permissions/default.toml +++ /dev/null @@ -1,3 +0,0 @@ -[default] -description = "Default permissions for the plugin" -permissions = ["allow-path", "allow-load", "allow-save"] diff --git a/plugins/settings/permissions/schemas/schema.json b/plugins/settings/permissions/schemas/schema.json deleted file mode 100644 index 1c7d76c88f..0000000000 --- a/plugins/settings/permissions/schemas/schema.json +++ /dev/null @@ -1,342 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PermissionFile", - "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", - "type": "object", - "properties": { - "default": { - "description": "The default permission set for the plugin", - "anyOf": [ - { - "$ref": "#/definitions/DefaultPermission" - }, - { - "type": "null" - } - ] - }, - "set": { - "description": "A list of permissions sets defined", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionSet" - } - }, - "permission": { - "description": "A list of inlined permissions", - "default": [], - "type": "array", - "items": { - "$ref": "#/definitions/Permission" - } - } - }, - "definitions": { - "DefaultPermission": { - "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", - "type": "object", - "required": [ - "permissions" - ], - "properties": { - "version": { - "description": "The version of the permission.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 1.0 - }, - "description": { - "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", - "type": [ - "string", - "null" - ] - }, - "permissions": { - "description": "All permissions this set contains.", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PermissionSet": { - "description": "A set of direct permissions grouped together under a new name.", - "type": "object", - "required": [ - "description", - "identifier", - "permissions" - ], - "properties": { - "identifier": { - "description": "A unique identifier for the permission.", - "type": "string" - }, - "description": { - "description": "Human-readable description of what the permission does.", - "type": "string" - }, - "permissions": { - "description": "All permissions this set contains.", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionKind" - } - } - } - }, - "Permission": { - "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", - "type": "object", - "required": [ - "identifier" - ], - "properties": { - "version": { - "description": "The version of the permission.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 1.0 - }, - "identifier": { - "description": "A unique identifier for the permission.", - "type": "string" - }, - "description": { - "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", - "type": [ - "string", - "null" - ] - }, - "commands": { - "description": "Allowed or denied commands when using this permission.", - "default": { - "allow": [], - "deny": [] - }, - "allOf": [ - { - "$ref": "#/definitions/Commands" - } - ] - }, - "scope": { - "description": "Allowed or denied scoped when using this permission.", - "allOf": [ - { - "$ref": "#/definitions/Scopes" - } - ] - }, - "platforms": { - "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Target" - } - } - } - }, - "Commands": { - "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", - "type": "object", - "properties": { - "allow": { - "description": "Allowed command.", - "default": [], - "type": "array", - "items": { - "type": "string" - } - }, - "deny": { - "description": "Denied command, which takes priority.", - "default": [], - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "Scopes": { - "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", - "type": "object", - "properties": { - "allow": { - "description": "Data that defines what is allowed by the scope.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - }, - "deny": { - "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - } - } - }, - "Value": { - "description": "All supported ACL values.", - "anyOf": [ - { - "description": "Represents a null JSON value.", - "type": "null" - }, - { - "description": "Represents a [`bool`].", - "type": "boolean" - }, - { - "description": "Represents a valid ACL [`Number`].", - "allOf": [ - { - "$ref": "#/definitions/Number" - } - ] - }, - { - "description": "Represents a [`String`].", - "type": "string" - }, - { - "description": "Represents a list of other [`Value`]s.", - "type": "array", - "items": { - "$ref": "#/definitions/Value" - } - }, - { - "description": "Represents a map of [`String`] keys to [`Value`]s.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Value" - } - } - ] - }, - "Number": { - "description": "A valid ACL number.", - "anyOf": [ - { - "description": "Represents an [`i64`].", - "type": "integer", - "format": "int64" - }, - { - "description": "Represents a [`f64`].", - "type": "number", - "format": "double" - } - ] - }, - "Target": { - "description": "Platform target.", - "oneOf": [ - { - "description": "MacOS.", - "type": "string", - "enum": [ - "macOS" - ] - }, - { - "description": "Windows.", - "type": "string", - "enum": [ - "windows" - ] - }, - { - "description": "Linux.", - "type": "string", - "enum": [ - "linux" - ] - }, - { - "description": "Android.", - "type": "string", - "enum": [ - "android" - ] - }, - { - "description": "iOS.", - "type": "string", - "enum": [ - "iOS" - ] - } - ] - }, - "PermissionKind": { - "type": "string", - "oneOf": [ - { - "description": "Enables the load command without any pre-configured scope.", - "type": "string", - "const": "allow-load", - "markdownDescription": "Enables the load command without any pre-configured scope." - }, - { - "description": "Denies the load command without any pre-configured scope.", - "type": "string", - "const": "deny-load", - "markdownDescription": "Denies the load command without any pre-configured scope." - }, - { - "description": "Enables the path command without any pre-configured scope.", - "type": "string", - "const": "allow-path", - "markdownDescription": "Enables the path command without any pre-configured scope." - }, - { - "description": "Denies the path command without any pre-configured scope.", - "type": "string", - "const": "deny-path", - "markdownDescription": "Denies the path command without any pre-configured scope." - }, - { - "description": "Enables the save command without any pre-configured scope.", - "type": "string", - "const": "allow-save", - "markdownDescription": "Enables the save command without any pre-configured scope." - }, - { - "description": "Denies the save command without any pre-configured scope.", - "type": "string", - "const": "deny-save", - "markdownDescription": "Denies the save command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-path`\n- `allow-load`\n- `allow-save`", - "type": "string", - "const": "default", - "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-path`\n- `allow-load`\n- `allow-save`" - } - ] - } - } -} \ No newline at end of file diff --git a/plugins/settings/src/commands.rs b/plugins/settings/src/commands.rs deleted file mode 100644 index b04b3685c5..0000000000 --- a/plugins/settings/src/commands.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::SettingsPluginExt; - -#[tauri::command] -#[specta::specta] -pub(crate) fn path(app: tauri::AppHandle) -> String { - app.settings().path().to_string_lossy().to_string() -} - -#[tauri::command] -#[specta::specta] -pub(crate) async fn load( - app: tauri::AppHandle, -) -> Result { - app.settings().load().await.map_err(|e| e.to_string()) -} - -#[tauri::command] -#[specta::specta] -pub(crate) async fn save( - app: tauri::AppHandle, - settings: serde_json::Value, -) -> Result<(), String> { - app.settings() - .save(settings) - .await - .map_err(|e| e.to_string()) -} diff --git a/plugins/settings/src/error.rs b/plugins/settings/src/error.rs deleted file mode 100644 index d0b0868dd1..0000000000 --- a/plugins/settings/src/error.rs +++ /dev/null @@ -1,18 +0,0 @@ -use serde::{Serialize, ser::Serializer}; - -pub type Result = std::result::Result; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("settings: {0}")] - Settings(String), -} - -impl Serialize for Error { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serializer.serialize_str(self.to_string().as_ref()) - } -} diff --git a/plugins/settings/src/ext.rs b/plugins/settings/src/ext.rs deleted file mode 100644 index 00119d5a9f..0000000000 --- a/plugins/settings/src/ext.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::path::PathBuf; - -use tauri::Manager; - -pub struct Settings<'a, R: tauri::Runtime, M: tauri::Manager> { - manager: &'a M, - _runtime: std::marker::PhantomData R>, -} - -impl<'a, R: tauri::Runtime, M: tauri::Manager> Settings<'a, R, M> { - pub fn path(&self) -> PathBuf { - let state = self.manager.state::(); - state.path().clone() - } - - pub async fn load(&self) -> crate::Result { - let state = self.manager.state::(); - state.load().await - } - - pub async fn save(&self, settings: serde_json::Value) -> crate::Result<()> { - let state = self.manager.state::(); - state.save(settings).await - } -} - -pub trait SettingsPluginExt { - fn settings(&self) -> Settings<'_, R, Self> - where - Self: tauri::Manager + Sized; -} - -impl> SettingsPluginExt for T { - fn settings(&self) -> Settings<'_, R, Self> - where - Self: Sized, - { - Settings { - manager: self, - _runtime: std::marker::PhantomData, - } - } -} diff --git a/plugins/settings/src/lib.rs b/plugins/settings/src/lib.rs deleted file mode 100644 index 323d641201..0000000000 --- a/plugins/settings/src/lib.rs +++ /dev/null @@ -1,59 +0,0 @@ -use tauri::Manager; - -mod commands; -mod error; -mod ext; -mod state; - -pub use error::{Error, Result}; -pub use ext::*; -use state::*; - -const PLUGIN_NAME: &str = "settings"; - -fn make_specta_builder() -> tauri_specta::Builder { - tauri_specta::Builder::::new() - .plugin_name(PLUGIN_NAME) - .commands(tauri_specta::collect_commands![ - commands::path::, - commands::load::, - commands::save::, - ]) - .error_handling(tauri_specta::ErrorHandlingMode::Result) -} - -pub fn init() -> tauri::plugin::TauriPlugin { - let specta_builder = make_specta_builder(); - - tauri::plugin::Builder::new(PLUGIN_NAME) - .invoke_handler(specta_builder.invoke_handler()) - .setup(|app, _api| { - let base = app.path().data_dir().unwrap().join("hyprnote"); - let state = SettingsState::new(base); - assert!(app.manage(state)); - Ok(()) - }) - .build() -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn export_types() { - const OUTPUT_FILE: &str = "./js/bindings.gen.ts"; - - make_specta_builder::() - .export( - specta_typescript::Typescript::default() - .formatter(specta_typescript::formatter::prettier) - .bigint(specta_typescript::BigIntExportBehavior::Number), - OUTPUT_FILE, - ) - .unwrap(); - - let content = std::fs::read_to_string(OUTPUT_FILE).unwrap(); - std::fs::write(OUTPUT_FILE, format!("// @ts-nocheck\n{content}")).unwrap(); - } -} diff --git a/plugins/settings/src/state.rs b/plugins/settings/src/state.rs deleted file mode 100644 index aa4825bf8a..0000000000 --- a/plugins/settings/src/state.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::path::PathBuf; -use tokio::sync::RwLock; - -use crate::Error; - -pub struct SettingsState { - path: PathBuf, - lock: RwLock<()>, -} - -impl SettingsState { - pub fn new(base: PathBuf) -> Self { - let path = base.join("settings.json"); - Self { - path, - lock: RwLock::new(()), - } - } - - pub fn path(&self) -> &PathBuf { - &self.path - } - - async fn read_or_default(&self) -> crate::Result { - match tokio::fs::read_to_string(&self.path).await { - Ok(content) => { - serde_json::from_str(&content).map_err(|e| Error::Settings(format!("parse: {}", e))) - } - Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(serde_json::json!({})), - Err(e) => Err(Error::Settings(format!("read: {}", e))), - } - } - - pub async fn load(&self) -> crate::Result { - let _guard = self.lock.read().await; - self.read_or_default().await - } - - pub async fn save(&self, settings: serde_json::Value) -> crate::Result<()> { - let _guard = self.lock.write().await; - - if let Some(parent) = self.path.parent() { - tokio::fs::create_dir_all(parent) - .await - .map_err(|e| Error::Settings(format!("create dir: {}", e)))?; - } - - let existing = self.read_or_default().await?; - - let merged = match (existing, settings) { - (serde_json::Value::Object(mut existing_map), serde_json::Value::Object(new_map)) => { - for (key, value) in new_map { - existing_map.insert(key, value); - } - serde_json::Value::Object(existing_map) - } - (_, new) => new, - }; - - let tmp_path = self.path.with_extension("json.tmp"); - let content = serde_json::to_string_pretty(&merged) - .map_err(|e| Error::Settings(format!("serialize: {}", e)))?; - - tokio::fs::write(&tmp_path, &content) - .await - .map_err(|e| Error::Settings(format!("write tmp: {}", e)))?; - - tokio::fs::rename(&tmp_path, &self.path) - .await - .map_err(|e| Error::Settings(format!("rename: {}", e)))?; - - Ok(()) - } -} diff --git a/plugins/settings/tsconfig.json b/plugins/settings/tsconfig.json deleted file mode 100644 index 13b985325d..0000000000 --- a/plugins/settings/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "include": ["./js/*.ts"], - "exclude": ["node_modules"] -} diff --git a/plugins/sfx/js/bindings.gen.ts b/plugins/sfx/js/bindings.gen.ts index 59fc3fbd36..6abf34df52 100644 --- a/plugins/sfx/js/bindings.gen.ts +++ b/plugins/sfx/js/bindings.gen.ts @@ -1,6 +1,9 @@ // @ts-nocheck /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -26,16 +29,24 @@ export const commands = { export type AppSounds = "StartRecording" | "StopRecording" | "BGM"; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/store2/js/bindings.gen.ts b/plugins/store2/js/bindings.gen.ts index 53ff37d605..4f8a57631a 100644 --- a/plugins/store2/js/bindings.gen.ts +++ b/plugins/store2/js/bindings.gen.ts @@ -1,120 +1,130 @@ // @ts-nocheck + // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. /** user-defined commands **/ + export const commands = { - async getStr(scope: string, key: string): Promise> { +async getStr(scope: string, key: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:store2|get_str", { scope, key }) }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async setStr(scope: string, key: string, value: string): Promise> { + return { status: "ok", data: await TAURI_INVOKE("plugin:store2|get_str", { scope, key }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async setStr(scope: string, key: string, value: string) : Promise> { try { - return { - status: "ok", - data: await TAURI_INVOKE("plugin:store2|set_str", { scope, key, value }), - }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async getBool(scope: string, key: string): Promise> { + return { status: "ok", data: await TAURI_INVOKE("plugin:store2|set_str", { scope, key, value }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async getBool(scope: string, key: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:store2|get_bool", { scope, key }) }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async setBool(scope: string, key: string, value: boolean): Promise> { + return { status: "ok", data: await TAURI_INVOKE("plugin:store2|get_bool", { scope, key }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async setBool(scope: string, key: string, value: boolean) : Promise> { try { - return { - status: "ok", - data: await TAURI_INVOKE("plugin:store2|set_bool", { scope, key, value }), - }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async getNumber(scope: string, key: string): Promise> { + return { status: "ok", data: await TAURI_INVOKE("plugin:store2|set_bool", { scope, key, value }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async getNumber(scope: string, key: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:store2|get_number", { scope, key }) }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, - async setNumber(scope: string, key: string, value: number): Promise> { + return { status: "ok", data: await TAURI_INVOKE("plugin:store2|get_number", { scope, key }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, +async setNumber(scope: string, key: string, value: number) : Promise> { try { - return { - status: "ok", - data: await TAURI_INVOKE("plugin:store2|set_number", { scope, key, value }), - }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, -}; + return { status: "ok", data: await TAURI_INVOKE("plugin:store2|set_number", { scope, key, value }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +} +} /** user-defined events **/ + + /** user-defined constants **/ + + /** user-defined types **/ + + /** tauri-specta globals **/ -import { invoke as TAURI_INVOKE, Channel as TAURI_CHANNEL } from "@tauri-apps/api/core"; +import { + invoke as TAURI_INVOKE, + Channel as TAURI_CHANNEL, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - emit: null extends T - ? (payload?: T) => ReturnType - : (payload: T) => ReturnType; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + emit: null extends T + ? (payload?: T) => ReturnType + : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; - -function __makeEvents__>(mappings: Record) { - return new Proxy( - {} as unknown as { - [K in keyof T]: __EventObj__ & { - (handle: __WebviewWindow__): __EventObj__; - }; - }, - { - get: (_, event) => { - const name = mappings[event as keyof T]; - - return new Proxy((() => {}) as any, { - apply: (_, __, [window]: [__WebviewWindow__]) => ({ - listen: (arg: any) => window.listen(name, arg), - once: (arg: any) => window.once(name, arg), - emit: (arg: any) => window.emit(name, arg), - }), - get: (_, command: keyof __EventObj__) => { - switch (command) { - case "listen": - return (arg: any) => TAURI_API_EVENT.listen(name, arg); - case "once": - return (arg: any) => TAURI_API_EVENT.once(name, arg); - case "emit": - return (arg: any) => TAURI_API_EVENT.emit(name, arg); - } - }, - }); - }, - }, - ); +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; + +function __makeEvents__>( + mappings: Record, +) { + return new Proxy( + {} as unknown as { + [K in keyof T]: __EventObj__ & { + (handle: __WebviewWindow__): __EventObj__; + }; + }, + { + get: (_, event) => { + const name = mappings[event as keyof T]; + + return new Proxy((() => {}) as any, { + apply: (_, __, [window]: [__WebviewWindow__]) => ({ + listen: (arg: any) => window.listen(name, arg), + once: (arg: any) => window.once(name, arg), + emit: (arg: any) => window.emit(name, arg), + }), + get: (_, command: keyof __EventObj__) => { + switch (command) { + case "listen": + return (arg: any) => TAURI_API_EVENT.listen(name, arg); + case "once": + return (arg: any) => TAURI_API_EVENT.once(name, arg); + case "emit": + return (arg: any) => TAURI_API_EVENT.emit(name, arg); + } + }, + }); + }, + }, + ); } diff --git a/plugins/store2/src/error.rs b/plugins/store2/src/error.rs index 943eeb13c7..0f00846647 100644 --- a/plugins/store2/src/error.rs +++ b/plugins/store2/src/error.rs @@ -6,10 +6,6 @@ pub enum Error { StorePluginError(#[from] tauri_plugin_store::Error), #[error(transparent)] SerdeJsonError(#[from] serde_json::Error), - #[error(transparent)] - TauriError(#[from] tauri::Error), - #[error(transparent)] - IoError(#[from] std::io::Error), } impl Serialize for Error { diff --git a/plugins/store2/src/ext.rs b/plugins/store2/src/ext.rs index 001e140d2f..623bb76f87 100644 --- a/plugins/store2/src/ext.rs +++ b/plugins/store2/src/ext.rs @@ -1,15 +1,6 @@ -use std::path::PathBuf; use std::sync::Arc; -use tauri::Manager; - -pub const STORE_FILENAME: &str = "store.json"; - -pub fn store_path(app: &tauri::AppHandle) -> Result { - let store_dir = app.path().data_dir()?.join("hyprnote"); - std::fs::create_dir_all(&store_dir).ok(); - Ok(store_dir.join(STORE_FILENAME)) -} +const STORE_FILENAME: &str = "store.json"; pub trait StorePluginExt { fn store(&self) -> Result>, crate::Error>; @@ -22,8 +13,7 @@ pub trait StorePluginExt { impl> StorePluginExt for T { fn store(&self) -> Result>, crate::Error> { let app = self.app_handle(); - let store_path = store_path(app)?; - as tauri_plugin_store::StoreExt>::store(app, &store_path) + as tauri_plugin_store::StoreExt>::store(app, STORE_FILENAME) .map_err(Into::into) } diff --git a/plugins/store2/src/lib.rs b/plugins/store2/src/lib.rs index 39fa74e895..5c7c0ea5cb 100644 --- a/plugins/store2/src/lib.rs +++ b/plugins/store2/src/lib.rs @@ -5,8 +5,6 @@ mod ext; pub use error::*; pub use ext::*; -use tauri::Manager; - const PLUGIN_NAME: &str = "store2"; fn make_specta_builder() -> tauri_specta::Builder { @@ -28,28 +26,9 @@ pub fn init() -> tauri::plugin::TauriPlugin { tauri::plugin::Builder::new(PLUGIN_NAME) .invoke_handler(specta_builder.invoke_handler()) - .setup(|app, _| { - migrate(app).ok(); - Ok(()) - }) .build() } -fn migrate(app: &tauri::AppHandle) -> Result<(), Error> { - let old_path = app.path().data_dir()?.join(STORE_FILENAME); - if !old_path.exists() { - return Ok(()); - } - - let new_path = store_path(app)?; - if new_path.exists() { - return Ok(()); - } - - std::fs::rename(&old_path, &new_path)?; - Ok(()) -} - #[cfg(test)] mod test { use super::*; diff --git a/plugins/template/js/bindings.gen.ts b/plugins/template/js/bindings.gen.ts index 84a94db89e..bcf1c4cede 100644 --- a/plugins/template/js/bindings.gen.ts +++ b/plugins/template/js/bindings.gen.ts @@ -1,4 +1,11 @@ // @ts-nocheck +/** tauri-specta globals **/ +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; +import * as TAURI_API_EVENT from "@tauri-apps/api/event"; +import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. @@ -10,7 +17,10 @@ export const commands = { ctx: Partial<{ [key in string]: JsonValue }>, ): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:template|render", { name, ctx }) }; + return { + status: "ok", + data: await TAURI_INVOKE("plugin:template|render", { name, ctx }), + }; } catch (e) { if (e instanceof Error) throw e; else return { status: "error", error: e as any }; @@ -23,7 +33,10 @@ export const commands = { try { return { status: "ok", - data: await TAURI_INVOKE("plugin:template|render_custom", { templateContent, ctx }), + data: await TAURI_INVOKE("plugin:template|render_custom", { + templateContent, + ctx, + }), }; } catch (e) { if (e instanceof Error) throw e; @@ -62,27 +75,27 @@ export type Template = | "auto_generate_tags.system" | "auto_generate_tags.user" | "postprocess_transcript.system" - | "postprocess_transcript.user" - | "highlight.system" - | "highlight.user"; - -/** tauri-specta globals **/ - -import { invoke as TAURI_INVOKE, Channel as TAURI_CHANNEL } from "@tauri-apps/api/core"; -import * as TAURI_API_EVENT from "@tauri-apps/api/event"; -import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; + | "postprocess_transcript.user"; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/tracing/js/bindings.gen.ts b/plugins/tracing/js/bindings.gen.ts index e4b8e99e36..559ed01bfb 100644 --- a/plugins/tracing/js/bindings.gen.ts +++ b/plugins/tracing/js/bindings.gen.ts @@ -1,6 +1,9 @@ // @ts-nocheck /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -49,16 +52,24 @@ export type JsonValue = export type Level = "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR"; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/tray/Cargo.toml b/plugins/tray/Cargo.toml index 302b451a1e..dc202d5e4c 100644 --- a/plugins/tray/Cargo.toml +++ b/plugins/tray/Cargo.toml @@ -23,10 +23,7 @@ tauri-plugin-cli2 = { workspace = true } tauri-plugin-clipboard-manager = { workspace = true } tauri-plugin-dialog = { workspace = true } tauri-plugin-misc = { workspace = true } -tauri-plugin-updater = { workspace = true } -tauri-plugin-updater2 = { workspace = true } tauri-plugin-windows = { workspace = true } serde_json = { workspace = true } specta = { workspace = true } -tracing = { workspace = true } diff --git a/plugins/tray/js/bindings.gen.ts b/plugins/tray/js/bindings.gen.ts index 224b48fb56..8d36914c19 100644 --- a/plugins/tray/js/bindings.gen.ts +++ b/plugins/tray/js/bindings.gen.ts @@ -3,7 +3,10 @@ /** user-defined constants **/ /** user-defined types **/ /** tauri-specta globals **/ -import { Channel as TAURI_CHANNEL, invoke as TAURI_INVOKE } from "@tauri-apps/api/core"; +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; @@ -14,16 +17,24 @@ import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webview export const commands = {}; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; emit: null extends T ? (payload?: T) => ReturnType : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; -function __makeEvents__>(mappings: Record) { +function __makeEvents__>( + mappings: Record, +) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { diff --git a/plugins/tray/src/ext.rs b/plugins/tray/src/ext.rs index f3b1f52f86..86f37cebb4 100644 --- a/plugins/tray/src/ext.rs +++ b/plugins/tray/src/ext.rs @@ -1,17 +1,57 @@ use tauri::{ AppHandle, Result, image::Image, - menu::{Menu, MenuItemKind, PredefinedMenuItem, Submenu}, + menu::{Menu, MenuId, MenuItem, MenuItemKind, PredefinedMenuItem}, tray::TrayIconBuilder, }; -use crate::menu_items::{ - AppInfo, AppNew, HyprMenuItem, MenuItemHandler, TrayCheckUpdate, TrayOpen, TrayQuit, - TraySettings, TrayStart, app_cli_menu, -}; +use tauri_plugin_clipboard_manager::ClipboardExt; +use tauri_plugin_dialog::{DialogExt, MessageDialogButtons}; +use tauri_plugin_misc::MiscPluginExt; const TRAY_ID: &str = "hypr-tray"; +pub enum HyprMenuItem { + TrayOpen, + TrayStart, + TrayQuit, + AppInfo, + AppCliInstall, + AppCliUninstall, + AppNew, +} + +impl From for MenuId { + fn from(value: HyprMenuItem) -> Self { + match value { + HyprMenuItem::TrayOpen => "hypr_tray_open", + HyprMenuItem::TrayStart => "hypr_tray_start", + HyprMenuItem::TrayQuit => "hypr_tray_quit", + HyprMenuItem::AppInfo => "hypr_app_info", + HyprMenuItem::AppCliInstall => "hypr_app_cli_install", + HyprMenuItem::AppCliUninstall => "hypr_app_cli_uninstall", + HyprMenuItem::AppNew => "hypr_app_new", + } + .into() + } +} + +impl From for HyprMenuItem { + fn from(id: MenuId) -> Self { + let id = id.0.as_str(); + match id { + "hypr_tray_open" => HyprMenuItem::TrayOpen, + "hypr_tray_start" => HyprMenuItem::TrayStart, + "hypr_tray_quit" => HyprMenuItem::TrayQuit, + "hypr_app_info" => HyprMenuItem::AppInfo, + "hypr_app_cli_install" => HyprMenuItem::AppCliInstall, + "hypr_app_cli_uninstall" => HyprMenuItem::AppCliUninstall, + "hypr_app_new" => HyprMenuItem::AppNew, + _ => unreachable!(), + } + } +} + pub trait TrayPluginExt { fn create_app_menu(&self) -> Result<()>; fn create_tray_menu(&self) -> Result<()>; @@ -22,50 +62,28 @@ impl> TrayPluginExt for T { fn create_app_menu(&self) -> Result<()> { let app = self.app_handle(); - let info_item = AppInfo::build(app)?; - let check_update_item = TrayCheckUpdate::build(app)?; - let settings_item = TraySettings::build(app)?; + let info_item = app_info_menu(app)?; let cli_item = app_cli_menu(app)?; - let new_item = AppNew::build(app)?; - - if cfg!(target_os = "macos") - && let Some(menu) = app.menu() - { - let items = menu.items()?; - - if !items.is_empty() - && let MenuItemKind::Submenu(old_submenu) = &items[0] - { - let app_name = old_submenu.text()?; - - let new_app_submenu = Submenu::with_items( - app, - &app_name, - true, - &[ - &info_item, - &check_update_item, - &settings_item, - &cli_item, - &PredefinedMenuItem::separator(app)?, - &PredefinedMenuItem::services(app, None)?, - &PredefinedMenuItem::separator(app)?, - &PredefinedMenuItem::hide(app, None)?, - &PredefinedMenuItem::hide_others(app, None)?, - &PredefinedMenuItem::show_all(app, None)?, - &PredefinedMenuItem::separator(app)?, - &PredefinedMenuItem::quit(app, None)?, - ], - )?; - - menu.remove(old_submenu)?; - menu.prepend(&new_app_submenu)?; - } + let new_item = app_new_menu(app)?; + + if cfg!(target_os = "macos") { + if let Some(menu) = app.menu() { + let items = menu.items()?; + + if !items.is_empty() { + if let MenuItemKind::Submenu(submenu) = &items[0] { + submenu.remove_at(0)?; + submenu.remove_at(0)?; + submenu.prepend(&cli_item)?; + submenu.prepend(&info_item)?; + } + } - if items.len() > 1 - && let MenuItemKind::Submenu(submenu) = &items[1] - { - submenu.prepend(&new_item)?; + if items.len() > 1 { + if let MenuItemKind::Submenu(submenu) = &items[1] { + submenu.prepend(&new_item)?; + } + } } } @@ -78,12 +96,10 @@ impl> TrayPluginExt for T { let menu = Menu::with_items( app, &[ - &TrayOpen::build(app)?, - &TrayStart::build_with_disabled(app, false)?, + &tray_open_menu(app)?, + &tray_start_menu(app, false)?, &PredefinedMenuItem::separator(app)?, - &TrayCheckUpdate::build(app)?, - &PredefinedMenuItem::separator(app)?, - &TrayQuit::build(app)?, + &tray_quit_menu(app)?, ], )?; @@ -94,8 +110,83 @@ impl> TrayPluginExt for T { .icon_as_template(true) .menu(&menu) .show_menu_on_left_click(true) - .on_menu_event(move |app: &AppHandle, event| { - HyprMenuItem::from(event.id.clone()).handle(app); + .on_menu_event({ + move |app: &AppHandle, event| match HyprMenuItem::from(event.id.clone()) { + HyprMenuItem::TrayOpen => { + use tauri_plugin_windows::AppWindow; + let _ = AppWindow::Main.show(app); + } + HyprMenuItem::TrayStart => { + use tauri_plugin_windows::{AppWindow, Navigate, WindowsPluginExt}; + if app.window_show(AppWindow::Main).is_ok() { + let _ = app.window_emit_navigate( + AppWindow::Main, + Navigate { + path: "/app/new".to_string(), + search: Some( + serde_json::json!({ "record": true }) + .as_object() + .cloned() + .unwrap(), + ), + }, + ); + } + } + HyprMenuItem::TrayQuit => { + hypr_host::kill_processes_by_matcher(hypr_host::ProcessMatcher::Sidecar); + app.exit(0); + } + HyprMenuItem::AppInfo => { + let app_name = app.package_info().name.clone(); + let app_version = app.package_info().version.to_string(); + let app_commit = app.get_git_hash(); + + let message = format!( + "• App Name: {}\n• App Version: {}\n• SHA:\n {}", + app_name, app_version, app_commit + ); + + let app_clone = app.clone(); + + app.dialog() + .message(&message) + .title("About Hyprnote") + .buttons(MessageDialogButtons::OkCancelCustom( + "Copy".to_string(), + "Cancel".to_string(), + )) + .show(move |result| { + if result { + let _ = app_clone.clipboard().write_text(&message); + } + }); + } + HyprMenuItem::AppCliInstall => { + use tauri_plugin_cli2::CliPluginExt; + if app.plugin_cli().install_cli_to_path().is_ok() { + let _ = app.create_app_menu(); + } + } + HyprMenuItem::AppCliUninstall => { + use tauri_plugin_cli2::CliPluginExt; + if app.plugin_cli().uninstall_cli_from_path().is_ok() { + let _ = app.create_app_menu(); + } + } + HyprMenuItem::AppNew => { + use tauri_plugin_windows::{AppWindow, Navigate, WindowsPluginExt}; + if app.window_show(AppWindow::Main).is_ok() { + let _ = app.window_emit_navigate( + AppWindow::Main, + Navigate { + path: "/app/new".to_string(), + search: None, + }, + ); + } + } + } }) .build(app)?; @@ -109,12 +200,10 @@ impl> TrayPluginExt for T { let menu = Menu::with_items( app, &[ - &TrayOpen::build(app)?, - &TrayStart::build_with_disabled(app, disabled)?, + &tray_open_menu(app)?, + &tray_start_menu(app, disabled)?, &PredefinedMenuItem::separator(app)?, - &TrayCheckUpdate::build(app)?, - &PredefinedMenuItem::separator(app)?, - &TrayQuit::build(app)?, + &tray_quit_menu(app)?, ], )?; @@ -124,3 +213,81 @@ impl> TrayPluginExt for T { Ok(()) } } + +fn app_info_menu(app: &AppHandle) -> Result> { + MenuItem::with_id( + app, + HyprMenuItem::AppInfo, + "About Hyprnote", + true, + None::<&str>, + ) +} + +fn app_cli_menu(app: &AppHandle) -> Result> { + use tauri_plugin_cli2::CliPluginExt; + + let is_installed = app + .plugin_cli() + .check_cli_status() + .map(|status| status.is_installed) + .unwrap_or(false); + + if is_installed { + MenuItem::with_id( + app, + HyprMenuItem::AppCliUninstall, + "Uninstall CLI", + true, + None::<&str>, + ) + } else { + MenuItem::with_id( + app, + HyprMenuItem::AppCliInstall, + "Install CLI", + true, + None::<&str>, + ) + } +} + +fn app_new_menu(app: &AppHandle) -> Result> { + MenuItem::with_id( + app, + HyprMenuItem::AppNew, + "New Note", + true, + Some("CmdOrCtrl+N"), + ) +} + +fn tray_open_menu(app: &AppHandle) -> Result> { + MenuItem::with_id( + app, + HyprMenuItem::TrayOpen, + "Open Hyprnote", + true, + None::<&str>, + ) +} + +fn tray_start_menu(app: &AppHandle, disabled: bool) -> Result> { + MenuItem::with_id( + app, + HyprMenuItem::TrayStart, + "Start a new recording", + !disabled, + None::<&str>, + ) +} + +fn tray_quit_menu(app: &AppHandle) -> Result> { + MenuItem::with_id( + app, + HyprMenuItem::TrayQuit, + "Quit Completely", + true, + Some("cmd+q"), + ) +} diff --git a/plugins/tray/src/lib.rs b/plugins/tray/src/lib.rs index 325e2ebbf3..1384b7a084 100644 --- a/plugins/tray/src/lib.rs +++ b/plugins/tray/src/lib.rs @@ -1,8 +1,5 @@ mod ext; -mod menu_items; - pub use ext::*; -pub use menu_items::{HyprMenuItem, UpdateMenuState}; const PLUGIN_NAME: &str = "tray"; @@ -13,33 +10,11 @@ fn make_specta_builder() -> tauri_specta::Builder { .error_handling(tauri_specta::ErrorHandlingMode::Result) } -pub fn init() -> tauri::plugin::TauriPlugin { - use tauri_specta::Event; - +pub fn init() -> tauri::plugin::TauriPlugin { let specta_builder = make_specta_builder(); - tauri::plugin::Builder::::new(PLUGIN_NAME) + tauri::plugin::Builder::new(PLUGIN_NAME) .invoke_handler(specta_builder.invoke_handler()) - .setup(|app, _api| { - let handle = app.clone(); - - tauri_plugin_updater2::UpdateReadyEvent::listen(app, move |_event| { - let _ = menu_items::TrayCheckUpdate::set_state( - &handle, - UpdateMenuState::RestartToApply, - ); - }); - - let handle = app.clone(); - tauri_plugin_updater2::UpdatedEvent::listen(app, move |_event| { - let _ = menu_items::TrayCheckUpdate::set_state( - &handle, - UpdateMenuState::CheckForUpdate, - ); - }); - - Ok(()) - }) .build() } @@ -63,4 +38,16 @@ mod test { let content = std::fs::read_to_string(OUTPUT_FILE).unwrap(); std::fs::write(OUTPUT_FILE, format!("// @ts-nocheck\n{content}")).unwrap(); } + + fn create_app(builder: tauri::Builder) -> tauri::App { + builder + .plugin(init()) + .build(tauri::test::mock_context(tauri::test::noop_assets())) + .unwrap() + } + + #[test] + fn test_tray() { + let _app = create_app(tauri::test::mock_builder()); + } } diff --git a/plugins/updater2/.gitignore b/plugins/updater2/.gitignore deleted file mode 100644 index 50d8e32e89..0000000000 --- a/plugins/updater2/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -/.vs -.DS_Store -.Thumbs.db -*.sublime* -.idea/ -debug.log -package-lock.json -.vscode/settings.json -yarn.lock - -/.tauri -/target -Cargo.lock -node_modules/ - -dist-js -dist diff --git a/plugins/updater2/Cargo.toml b/plugins/updater2/Cargo.toml deleted file mode 100644 index 97a3589878..0000000000 --- a/plugins/updater2/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "tauri-plugin-updater2" -version = "0.1.0" -authors = ["You"] -edition = "2024" -exclude = ["/js", "/node_modules"] -links = "tauri-plugin-updater2" -description = "" - -[build-dependencies] -tauri-plugin = { workspace = true, features = ["build"] } - -[dev-dependencies] -specta-typescript = { workspace = true } -tokio = { workspace = true, features = ["macros"] } - -[dependencies] -tauri-plugin-store2 = { workspace = true } -tauri-plugin-updater = { workspace = true } - -tauri = { workspace = true, features = ["test"] } -tauri-specta = { workspace = true, features = ["derive", "typescript"] } - -serde = { workspace = true } -specta = { workspace = true } -strum = { workspace = true, features = ["derive"] } - -thiserror = { workspace = true } -tokio = { workspace = true, features = ["time"] } -tracing = { workspace = true } diff --git a/plugins/updater2/build.rs b/plugins/updater2/build.rs deleted file mode 100644 index f07a0a4186..0000000000 --- a/plugins/updater2/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -const COMMANDS: &[&str] = &["get_pending_update"]; - -fn main() { - tauri_plugin::Builder::new(COMMANDS).build(); -} diff --git a/plugins/updater2/js/bindings.gen.ts b/plugins/updater2/js/bindings.gen.ts deleted file mode 100644 index fa921dc5ba..0000000000 --- a/plugins/updater2/js/bindings.gen.ts +++ /dev/null @@ -1,82 +0,0 @@ -// @ts-nocheck - -// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. - -/** user-defined commands **/ - -export const commands = { - async getPendingUpdate(): Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("plugin:updater2|get_pending_update") }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, -}; - -/** user-defined events **/ - -export const events = __makeEvents__<{ - updateReadyEvent: UpdateReadyEvent; - updatedEvent: UpdatedEvent; -}>({ - updateReadyEvent: "plugin:updater2:update-ready-event", - updatedEvent: "plugin:updater2:updated-event", -}); - -/** user-defined constants **/ - -/** user-defined types **/ - -export type UpdateReadyEvent = { version: string }; -export type UpdatedEvent = { previous: string | null; current: string }; - -/** tauri-specta globals **/ - -import { invoke as TAURI_INVOKE, Channel as TAURI_CHANNEL } from "@tauri-apps/api/core"; -import * as TAURI_API_EVENT from "@tauri-apps/api/event"; -import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; - -type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - emit: null extends T - ? (payload?: T) => ReturnType - : (payload: T) => ReturnType; -}; - -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; - -function __makeEvents__>(mappings: Record) { - return new Proxy( - {} as unknown as { - [K in keyof T]: __EventObj__ & { - (handle: __WebviewWindow__): __EventObj__; - }; - }, - { - get: (_, event) => { - const name = mappings[event as keyof T]; - - return new Proxy((() => {}) as any, { - apply: (_, __, [window]: [__WebviewWindow__]) => ({ - listen: (arg: any) => window.listen(name, arg), - once: (arg: any) => window.once(name, arg), - emit: (arg: any) => window.emit(name, arg), - }), - get: (_, command: keyof __EventObj__) => { - switch (command) { - case "listen": - return (arg: any) => TAURI_API_EVENT.listen(name, arg); - case "once": - return (arg: any) => TAURI_API_EVENT.once(name, arg); - case "emit": - return (arg: any) => TAURI_API_EVENT.emit(name, arg); - } - }, - }); - }, - }, - ); -} diff --git a/plugins/updater2/js/index.ts b/plugins/updater2/js/index.ts deleted file mode 100644 index a96e122f03..0000000000 --- a/plugins/updater2/js/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./bindings.gen"; diff --git a/plugins/updater2/package.json b/plugins/updater2/package.json deleted file mode 100644 index 88909a73b0..0000000000 --- a/plugins/updater2/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@hypr/plugin-updater2", - "private": true, - "main": "./js/index.ts", - "scripts": { - "codegen": "cargo test -p tauri-plugin-updater2" - }, - "dependencies": { - "@tauri-apps/api": "^2.9.1" - } -} diff --git a/plugins/updater2/permissions/autogenerated/commands/get_pending_update.toml b/plugins/updater2/permissions/autogenerated/commands/get_pending_update.toml deleted file mode 100644 index f25e06be8f..0000000000 --- a/plugins/updater2/permissions/autogenerated/commands/get_pending_update.toml +++ /dev/null @@ -1,13 +0,0 @@ -# Automatically generated - DO NOT EDIT! - -"$schema" = "../../schemas/schema.json" - -[[permission]] -identifier = "allow-get-pending-update" -description = "Enables the get_pending_update command without any pre-configured scope." -commands.allow = ["get_pending_update"] - -[[permission]] -identifier = "deny-get-pending-update" -description = "Denies the get_pending_update command without any pre-configured scope." -commands.deny = ["get_pending_update"] diff --git a/plugins/updater2/permissions/autogenerated/reference.md b/plugins/updater2/permissions/autogenerated/reference.md deleted file mode 100644 index 22e7140090..0000000000 --- a/plugins/updater2/permissions/autogenerated/reference.md +++ /dev/null @@ -1,43 +0,0 @@ -## Default Permission - -Default permissions for the plugin - -#### This default permission set includes the following: - -- `allow-get-pending-update` - -## Permission Table - - - - - - - - - - - - - - - - - -
IdentifierDescription
- -`updater2:allow-get-pending-update` - - - -Enables the get_pending_update command without any pre-configured scope. - -
- -`updater2:deny-get-pending-update` - - - -Denies the get_pending_update command without any pre-configured scope. - -
diff --git a/plugins/updater2/permissions/default.toml b/plugins/updater2/permissions/default.toml deleted file mode 100644 index 05611f48b6..0000000000 --- a/plugins/updater2/permissions/default.toml +++ /dev/null @@ -1,3 +0,0 @@ -[default] -description = "Default permissions for the plugin" -permissions = ["allow-get-pending-update"] diff --git a/plugins/updater2/permissions/schemas/schema.json b/plugins/updater2/permissions/schemas/schema.json deleted file mode 100644 index cbd31f7e9d..0000000000 --- a/plugins/updater2/permissions/schemas/schema.json +++ /dev/null @@ -1,318 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PermissionFile", - "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", - "type": "object", - "properties": { - "default": { - "description": "The default permission set for the plugin", - "anyOf": [ - { - "$ref": "#/definitions/DefaultPermission" - }, - { - "type": "null" - } - ] - }, - "set": { - "description": "A list of permissions sets defined", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionSet" - } - }, - "permission": { - "description": "A list of inlined permissions", - "default": [], - "type": "array", - "items": { - "$ref": "#/definitions/Permission" - } - } - }, - "definitions": { - "DefaultPermission": { - "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", - "type": "object", - "required": [ - "permissions" - ], - "properties": { - "version": { - "description": "The version of the permission.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 1.0 - }, - "description": { - "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", - "type": [ - "string", - "null" - ] - }, - "permissions": { - "description": "All permissions this set contains.", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PermissionSet": { - "description": "A set of direct permissions grouped together under a new name.", - "type": "object", - "required": [ - "description", - "identifier", - "permissions" - ], - "properties": { - "identifier": { - "description": "A unique identifier for the permission.", - "type": "string" - }, - "description": { - "description": "Human-readable description of what the permission does.", - "type": "string" - }, - "permissions": { - "description": "All permissions this set contains.", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionKind" - } - } - } - }, - "Permission": { - "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", - "type": "object", - "required": [ - "identifier" - ], - "properties": { - "version": { - "description": "The version of the permission.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 1.0 - }, - "identifier": { - "description": "A unique identifier for the permission.", - "type": "string" - }, - "description": { - "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", - "type": [ - "string", - "null" - ] - }, - "commands": { - "description": "Allowed or denied commands when using this permission.", - "default": { - "allow": [], - "deny": [] - }, - "allOf": [ - { - "$ref": "#/definitions/Commands" - } - ] - }, - "scope": { - "description": "Allowed or denied scoped when using this permission.", - "allOf": [ - { - "$ref": "#/definitions/Scopes" - } - ] - }, - "platforms": { - "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Target" - } - } - } - }, - "Commands": { - "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", - "type": "object", - "properties": { - "allow": { - "description": "Allowed command.", - "default": [], - "type": "array", - "items": { - "type": "string" - } - }, - "deny": { - "description": "Denied command, which takes priority.", - "default": [], - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "Scopes": { - "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", - "type": "object", - "properties": { - "allow": { - "description": "Data that defines what is allowed by the scope.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - }, - "deny": { - "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - } - } - }, - "Value": { - "description": "All supported ACL values.", - "anyOf": [ - { - "description": "Represents a null JSON value.", - "type": "null" - }, - { - "description": "Represents a [`bool`].", - "type": "boolean" - }, - { - "description": "Represents a valid ACL [`Number`].", - "allOf": [ - { - "$ref": "#/definitions/Number" - } - ] - }, - { - "description": "Represents a [`String`].", - "type": "string" - }, - { - "description": "Represents a list of other [`Value`]s.", - "type": "array", - "items": { - "$ref": "#/definitions/Value" - } - }, - { - "description": "Represents a map of [`String`] keys to [`Value`]s.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Value" - } - } - ] - }, - "Number": { - "description": "A valid ACL number.", - "anyOf": [ - { - "description": "Represents an [`i64`].", - "type": "integer", - "format": "int64" - }, - { - "description": "Represents a [`f64`].", - "type": "number", - "format": "double" - } - ] - }, - "Target": { - "description": "Platform target.", - "oneOf": [ - { - "description": "MacOS.", - "type": "string", - "enum": [ - "macOS" - ] - }, - { - "description": "Windows.", - "type": "string", - "enum": [ - "windows" - ] - }, - { - "description": "Linux.", - "type": "string", - "enum": [ - "linux" - ] - }, - { - "description": "Android.", - "type": "string", - "enum": [ - "android" - ] - }, - { - "description": "iOS.", - "type": "string", - "enum": [ - "iOS" - ] - } - ] - }, - "PermissionKind": { - "type": "string", - "oneOf": [ - { - "description": "Enables the get_pending_update command without any pre-configured scope.", - "type": "string", - "const": "allow-get-pending-update", - "markdownDescription": "Enables the get_pending_update command without any pre-configured scope." - }, - { - "description": "Denies the get_pending_update command without any pre-configured scope.", - "type": "string", - "const": "deny-get-pending-update", - "markdownDescription": "Denies the get_pending_update command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-pending-update`", - "type": "string", - "const": "default", - "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-pending-update`" - } - ] - } - } -} \ No newline at end of file diff --git a/plugins/updater2/src/commands.rs b/plugins/updater2/src/commands.rs deleted file mode 100644 index 4214ca3a2c..0000000000 --- a/plugins/updater2/src/commands.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::Updater2PluginExt; - -#[tauri::command] -#[specta::specta] -pub(crate) async fn get_pending_update( - app: tauri::AppHandle, -) -> Result, String> { - app.get_pending_update_version().map_err(|e| e.to_string()) -} diff --git a/plugins/updater2/src/error.rs b/plugins/updater2/src/error.rs deleted file mode 100644 index 99fe9a5f68..0000000000 --- a/plugins/updater2/src/error.rs +++ /dev/null @@ -1,20 +0,0 @@ -use serde::{Serialize, ser::Serializer}; - -pub type Result = std::result::Result; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error(transparent)] - Store2(#[from] tauri_plugin_store2::Error), - #[error(transparent)] - Updater(#[from] tauri_plugin_updater::Error), -} - -impl Serialize for Error { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - serializer.serialize_str(self.to_string().as_ref()) - } -} diff --git a/plugins/updater2/src/events.rs b/plugins/updater2/src/events.rs deleted file mode 100644 index ab4f3854ca..0000000000 --- a/plugins/updater2/src/events.rs +++ /dev/null @@ -1,10 +0,0 @@ -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, specta::Type, tauri_specta::Event)] -pub struct UpdatedEvent { - pub previous: Option, - pub current: String, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, specta::Type, tauri_specta::Event)] -pub struct UpdateReadyEvent { - pub version: String, -} diff --git a/plugins/updater2/src/ext.rs b/plugins/updater2/src/ext.rs deleted file mode 100644 index 72e4aec797..0000000000 --- a/plugins/updater2/src/ext.rs +++ /dev/null @@ -1,82 +0,0 @@ -use tauri::Manager; -use tauri_plugin_store2::StorePluginExt; -use tauri_specta::Event; - -use crate::events::UpdatedEvent; - -pub trait Updater2PluginExt { - fn get_last_seen_version(&self) -> Result, crate::Error>; - fn set_last_seen_version(&self, version: String) -> Result<(), crate::Error>; - fn get_pending_update_version(&self) -> Result, crate::Error>; - fn set_pending_update_version(&self, version: Option) -> Result<(), crate::Error>; - fn maybe_emit_updated(&self); -} - -impl> crate::Updater2PluginExt for T { - fn get_last_seen_version(&self) -> Result, crate::Error> { - let store = self.scoped_store(crate::PLUGIN_NAME)?; - let v = store.get(crate::StoreKey::LastSeenVersion)?; - Ok(v) - } - - fn set_last_seen_version(&self, version: String) -> Result<(), crate::Error> { - let store = self.scoped_store(crate::PLUGIN_NAME)?; - store.set(crate::StoreKey::LastSeenVersion, version)?; - Ok(()) - } - - fn get_pending_update_version(&self) -> Result, crate::Error> { - let store = self.scoped_store(crate::PLUGIN_NAME)?; - let v: Option = store.get(crate::StoreKey::PendingUpdateVersion)?; - Ok(v.filter(|s| !s.is_empty())) - } - - fn set_pending_update_version(&self, version: Option) -> Result<(), crate::Error> { - let store = self.scoped_store(crate::PLUGIN_NAME)?; - store.set( - crate::StoreKey::PendingUpdateVersion, - version.unwrap_or_default(), - )?; - Ok(()) - } - - fn maybe_emit_updated(&self) { - let current_version = match self.config().version.as_ref() { - Some(v) => v.clone(), - None => { - tracing::warn!("no_version_in_config"); - return; - } - }; - - if let Err(e) = self.set_pending_update_version(None) { - tracing::warn!("failed_to_clear_pending_update: {}", e); - } - - let (should_emit, previous) = match self.get_last_seen_version() { - Ok(Some(last_version)) if !last_version.is_empty() => { - (last_version != current_version, Some(last_version)) - } - Ok(_) => (true, None), - Err(e) => { - tracing::error!("failed_to_get_last_seen_version: {}", e); - (false, None) - } - }; - - if should_emit { - let payload = UpdatedEvent { - previous, - current: current_version.clone(), - }; - - if let Err(e) = payload.emit(self.app_handle()) { - tracing::error!("failed_to_emit_updated_event: {}", e); - } - } - - if let Err(e) = self.set_last_seen_version(current_version) { - tracing::error!("failed_to_update_version: {}", e); - } - } -} diff --git a/plugins/updater2/src/job.rs b/plugins/updater2/src/job.rs deleted file mode 100644 index 783d9cbc62..0000000000 --- a/plugins/updater2/src/job.rs +++ /dev/null @@ -1,38 +0,0 @@ -use tauri_plugin_updater::UpdaterExt; -use tauri_specta::Event; - -use crate::Updater2PluginExt; -use crate::events::UpdateReadyEvent; - -pub async fn check_and_download_update(app: &tauri::AppHandle) { - if cfg!(debug_assertions) { - return; - } - - let Ok(updater) = app.updater() else { - tracing::error!("failed_to_get_updater"); - return; - }; - - let update = match updater.check().await { - Ok(Some(update)) => update, - Ok(None) => { - return; - } - Err(e) => { - tracing::error!("failed_to_check_for_updates: {}", e); - return; - } - }; - - let version = update.version.clone(); - let _bytes = update.download(|_, _| {}, || {}).await; - - if let Err(e) = app.set_pending_update_version(Some(version.clone())) { - tracing::error!("failed_to_set_pending_update_version: {}", e); - } - - if let Err(e) = (UpdateReadyEvent { version }).emit(app) { - tracing::error!("failed_to_emit_update_ready_event: {}", e); - } -} diff --git a/plugins/updater2/src/lib.rs b/plugins/updater2/src/lib.rs deleted file mode 100644 index 3f48e1ddec..0000000000 --- a/plugins/updater2/src/lib.rs +++ /dev/null @@ -1,69 +0,0 @@ -mod commands; -mod error; -mod events; -mod ext; -mod job; -mod store; - -pub use error::{Error, Result}; -pub use events::*; -pub use ext::*; -pub use job::*; -pub(crate) use store::*; - -const PLUGIN_NAME: &str = "updater2"; - -fn make_specta_builder() -> tauri_specta::Builder { - tauri_specta::Builder::::new() - .plugin_name(PLUGIN_NAME) - .commands(tauri_specta::collect_commands![ - commands::get_pending_update::, - ]) - .events(tauri_specta::collect_events![ - events::UpdatedEvent, - events::UpdateReadyEvent - ]) - .error_handling(tauri_specta::ErrorHandlingMode::Result) -} - -pub fn init() -> tauri::plugin::TauriPlugin { - let specta_builder = make_specta_builder(); - - tauri::plugin::Builder::new(PLUGIN_NAME) - .invoke_handler(specta_builder.invoke_handler()) - .setup(move |app, _api| { - specta_builder.mount_events(app); - - let handle = app.clone(); - tauri::async_runtime::spawn(async move { - loop { - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - job::check_and_download_update(&handle).await; - } - }); - Ok(()) - }) - .build() -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn export_types() { - const OUTPUT_FILE: &str = "./js/bindings.gen.ts"; - - make_specta_builder::() - .export( - specta_typescript::Typescript::default() - .formatter(specta_typescript::formatter::prettier) - .bigint(specta_typescript::BigIntExportBehavior::Number), - OUTPUT_FILE, - ) - .unwrap(); - - let content = std::fs::read_to_string(OUTPUT_FILE).unwrap(); - std::fs::write(OUTPUT_FILE, format!("// @ts-nocheck\n{content}")).unwrap(); - } -} diff --git a/plugins/updater2/src/store.rs b/plugins/updater2/src/store.rs deleted file mode 100644 index e3b487f212..0000000000 --- a/plugins/updater2/src/store.rs +++ /dev/null @@ -1,9 +0,0 @@ -use tauri_plugin_store2::ScopedStoreKey; - -#[derive(serde::Deserialize, specta::Type, PartialEq, Eq, Hash, strum::Display)] -pub enum StoreKey { - LastSeenVersion, - PendingUpdateVersion, -} - -impl ScopedStoreKey for StoreKey {} diff --git a/plugins/updater2/tsconfig.json b/plugins/updater2/tsconfig.json deleted file mode 100644 index 13b985325d..0000000000 --- a/plugins/updater2/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "include": ["./js/*.ts"], - "exclude": ["node_modules"] -} diff --git a/plugins/webhook/build.rs b/plugins/webhook/build.rs index a220cd4b25..363f89e3e6 100644 --- a/plugins/webhook/build.rs +++ b/plugins/webhook/build.rs @@ -1,4 +1,4 @@ -const COMMANDS: &[&str] = &[]; +const COMMANDS: &[&str] = &["todo"]; fn main() { tauri_plugin::Builder::new(COMMANDS).build(); diff --git a/plugins/webhook/docs/webhook-openapi.json b/plugins/webhook/docs/webhook-openapi.json index 812597c2bd..2b67b937b4 100644 --- a/plugins/webhook/docs/webhook-openapi.json +++ b/plugins/webhook/docs/webhook-openapi.json @@ -25,7 +25,12 @@ "schemas": { "NoteEventData": { "type": "object", - "required": ["note_id", "title", "tags", "created_at"], + "required": [ + "note_id", + "title", + "tags", + "created_at" + ], "properties": { "note_id": { "type": "string", @@ -38,7 +43,10 @@ "example": "Meeting Notes - Q1 Planning" }, "content": { - "type": ["string", "null"], + "type": [ + "string", + "null" + ], "description": "Content of the note (may be truncated for large notes)", "example": "Today we discussed..." }, @@ -48,7 +56,11 @@ "type": "string" }, "description": "Tags associated with the note", - "example": ["meeting", "planning", "q1"] + "example": [ + "meeting", + "planning", + "q1" + ] }, "created_at": { "type": "string", @@ -56,7 +68,10 @@ "example": "2024-01-10T10:00:00Z" }, "updated_at": { - "type": ["string", "null"], + "type": [ + "string", + "null" + ], "description": "ISO 8601 timestamp when the note was last updated", "example": "2024-01-10T10:30:00Z" } @@ -64,7 +79,11 @@ }, "RecordingEventData": { "type": "object", - "required": ["recording_id", "status", "started_at"], + "required": [ + "recording_id", + "status", + "started_at" + ], "properties": { "recording_id": { "type": "string", @@ -72,14 +91,20 @@ "example": "rec_xyz789" }, "duration_seconds": { - "type": ["integer", "null"], + "type": [ + "integer", + "null" + ], "format": "int32", "description": "Duration of the recording in seconds", "example": 300, "minimum": 0 }, "file_size_bytes": { - "type": ["integer", "null"], + "type": [ + "integer", + "null" + ], "format": "int64", "description": "File size in bytes", "example": 1048576, @@ -95,7 +120,10 @@ "example": "2024-01-10T10:00:00Z" }, "completed_at": { - "type": ["string", "null"], + "type": [ + "string", + "null" + ], "description": "ISO 8601 timestamp when recording completed", "example": "2024-01-10T10:05:00Z" } @@ -103,11 +131,21 @@ }, "RecordingStatus": { "type": "string", - "enum": ["started", "in_progress", "completed", "failed"] + "enum": [ + "started", + "in_progress", + "completed", + "failed" + ] }, "TranscriptionEventData": { "type": "object", - "required": ["recording_id", "transcription_id", "text", "processing_time_ms"], + "required": [ + "recording_id", + "transcription_id", + "text", + "processing_time_ms" + ], "properties": { "recording_id": { "type": "string", @@ -125,12 +163,18 @@ "example": "Hello, this is the transcribed text from the recording." }, "language": { - "type": ["string", "null"], + "type": [ + "string", + "null" + ], "description": "Language detected in the transcription", "example": "en" }, "confidence": { - "type": ["number", "null"], + "type": [ + "number", + "null" + ], "format": "float", "description": "Confidence score of the transcription (0.0 to 1.0)", "example": 0.95 @@ -146,7 +190,10 @@ }, "WebhookDeliveryRequest": { "type": "object", - "required": ["event", "signature"], + "required": [ + "event", + "signature" + ], "properties": { "event": { "$ref": "#/components/schemas/WebhookEvent", @@ -161,21 +208,32 @@ }, "WebhookDeliveryResponse": { "type": "object", - "required": ["success"], + "required": [ + "success" + ], "properties": { "success": { "type": "boolean", "description": "Whether the webhook was successfully processed" }, "message": { - "type": ["string", "null"], + "type": [ + "string", + "null" + ], "description": "Optional message from the receiver" } } }, "WebhookEvent": { "type": "object", - "required": ["id", "event_type", "timestamp", "api_version", "data"], + "required": [ + "id", + "event_type", + "timestamp", + "api_version", + "data" + ], "properties": { "id": { "type": "string", diff --git a/plugins/webhook/js/bindings.gen.ts b/plugins/webhook/js/bindings.gen.ts index 719b805d6d..36a741f96b 100644 --- a/plugins/webhook/js/bindings.gen.ts +++ b/plugins/webhook/js/bindings.gen.ts @@ -1,71 +1,90 @@ // @ts-nocheck + // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. /** user-defined commands **/ + export const commands = { - async todo(): Promise> { +async todo() : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:webhook|todo") }; - } catch (e) { - if (e instanceof Error) throw e; - else return { status: "error", error: e as any }; - } - }, -}; + return { status: "ok", data: await TAURI_INVOKE("plugin:webhook|todo") }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +} +} /** user-defined events **/ + + /** user-defined constants **/ + + /** user-defined types **/ + + /** tauri-specta globals **/ -import { invoke as TAURI_INVOKE, Channel as TAURI_CHANNEL } from "@tauri-apps/api/core"; +import { + invoke as TAURI_INVOKE, + Channel as TAURI_CHANNEL, +} from "@tauri-apps/api/core"; import * as TAURI_API_EVENT from "@tauri-apps/api/event"; import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; type __EventObj__ = { - listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType>; - emit: null extends T - ? (payload?: T) => ReturnType - : (payload: T) => ReturnType; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + emit: null extends T + ? (payload?: T) => ReturnType + : (payload: T) => ReturnType; }; -export type Result = { status: "ok"; data: T } | { status: "error"; error: E }; - -function __makeEvents__>(mappings: Record) { - return new Proxy( - {} as unknown as { - [K in keyof T]: __EventObj__ & { - (handle: __WebviewWindow__): __EventObj__; - }; - }, - { - get: (_, event) => { - const name = mappings[event as keyof T]; - - return new Proxy((() => {}) as any, { - apply: (_, __, [window]: [__WebviewWindow__]) => ({ - listen: (arg: any) => window.listen(name, arg), - once: (arg: any) => window.once(name, arg), - emit: (arg: any) => window.emit(name, arg), - }), - get: (_, command: keyof __EventObj__) => { - switch (command) { - case "listen": - return (arg: any) => TAURI_API_EVENT.listen(name, arg); - case "once": - return (arg: any) => TAURI_API_EVENT.once(name, arg); - case "emit": - return (arg: any) => TAURI_API_EVENT.emit(name, arg); - } - }, - }); - }, - }, - ); +export type Result = + | { status: "ok"; data: T } + | { status: "error"; error: E }; + +function __makeEvents__>( + mappings: Record, +) { + return new Proxy( + {} as unknown as { + [K in keyof T]: __EventObj__ & { + (handle: __WebviewWindow__): __EventObj__; + }; + }, + { + get: (_, event) => { + const name = mappings[event as keyof T]; + + return new Proxy((() => {}) as any, { + apply: (_, __, [window]: [__WebviewWindow__]) => ({ + listen: (arg: any) => window.listen(name, arg), + once: (arg: any) => window.once(name, arg), + emit: (arg: any) => window.emit(name, arg), + }), + get: (_, command: keyof __EventObj__) => { + switch (command) { + case "listen": + return (arg: any) => TAURI_API_EVENT.listen(name, arg); + case "once": + return (arg: any) => TAURI_API_EVENT.once(name, arg); + case "emit": + return (arg: any) => TAURI_API_EVENT.emit(name, arg); + } + }, + }); + }, + }, + ); } diff --git a/plugins/webhook/openapi.gen.json b/plugins/webhook/openapi.gen.json index a5195578b6..bc33a26426 100644 --- a/plugins/webhook/openapi.gen.json +++ b/plugins/webhook/openapi.gen.json @@ -15,7 +15,9 @@ "paths": { "/api/webhooks": { "get": { - "tags": ["Webhooks"], + "tags": [ + "Webhooks" + ], "operationId": "list_webhooks", "responses": { "200": { @@ -36,7 +38,9 @@ ] }, "post": { - "tags": ["Webhooks"], + "tags": [ + "Webhooks" + ], "operationId": "create_webhook", "requestBody": { "content": { @@ -72,7 +76,9 @@ }, "/api/webhooks/{id}": { "delete": { - "tags": ["Webhooks"], + "tags": [ + "Webhooks" + ], "operationId": "delete_webhook", "parameters": [ { @@ -102,7 +108,9 @@ }, "/api/webhooks/{id}/test": { "post": { - "tags": ["Webhooks"], + "tags": [ + "Webhooks" + ], "operationId": "test_webhook", "parameters": [ { @@ -132,7 +140,9 @@ }, "/your-webhook-endpoint": { "post": { - "tags": ["Events"], + "tags": [ + "Events" + ], "operationId": "webhook_receiver_example", "requestBody": { "content": { @@ -167,7 +177,9 @@ "schemas": { "CreateWebhookRequest": { "type": "object", - "required": ["config"], + "required": [ + "config" + ], "properties": { "config": { "$ref": "#/components/schemas/WebhookConfig" @@ -176,7 +188,11 @@ }, "NoteEvent": { "type": "object", - "required": ["note_id", "title", "content"], + "required": [ + "note_id", + "title", + "content" + ], "properties": { "content": { "type": "string", @@ -194,7 +210,11 @@ }, "RecordingEvent": { "type": "object", - "required": ["recording_id", "duration_seconds", "status"], + "required": [ + "recording_id", + "duration_seconds", + "status" + ], "properties": { "duration_seconds": { "type": "integer", @@ -214,7 +234,11 @@ }, "TranscriptionEvent": { "type": "object", - "required": ["recording_id", "transcription_id", "text"], + "required": [ + "recording_id", + "transcription_id", + "text" + ], "properties": { "recording_id": { "type": "string", @@ -232,7 +256,11 @@ }, "WebhookConfig": { "type": "object", - "required": ["url", "events", "active"], + "required": [ + "url", + "events", + "active" + ], "properties": { "active": { "type": "boolean", @@ -245,7 +273,11 @@ "type": "string" }, "description": "Events to subscribe to", - "example": ["note.created", "note.updated", "recording.completed"] + "example": [ + "note.created", + "note.updated", + "recording.completed" + ] }, "url": { "type": "string", @@ -256,7 +288,12 @@ }, "WebhookEvent": { "type": "object", - "required": ["id", "event_type", "timestamp", "data"], + "required": [ + "id", + "event_type", + "timestamp", + "data" + ], "properties": { "data": { "description": "Event payload" @@ -280,7 +317,10 @@ }, "WebhookListResponse": { "type": "object", - "required": ["webhooks", "total"], + "required": [ + "webhooks", + "total" + ], "properties": { "total": { "type": "integer", @@ -296,7 +336,12 @@ }, "WebhookResponse": { "type": "object", - "required": ["id", "config", "secret", "created_at"], + "required": [ + "id", + "config", + "secret", + "created_at" + ], "properties": { "config": { "$ref": "#/components/schemas/WebhookConfig" @@ -318,7 +363,10 @@ }, "WebhookVerification": { "type": "object", - "required": ["signature", "timestamp"], + "required": [ + "signature", + "timestamp" + ], "properties": { "signature": { "type": "string", diff --git a/plugins/webhook/permissions/autogenerated/commands/ping.toml b/plugins/webhook/permissions/autogenerated/commands/ping.toml new file mode 100644 index 0000000000..1d1358807e --- /dev/null +++ b/plugins/webhook/permissions/autogenerated/commands/ping.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-ping" +description = "Enables the ping command without any pre-configured scope." +commands.allow = ["ping"] + +[[permission]] +identifier = "deny-ping" +description = "Denies the ping command without any pre-configured scope." +commands.deny = ["ping"] diff --git a/plugins/webhook/permissions/autogenerated/reference.md b/plugins/webhook/permissions/autogenerated/reference.md index cdacbae4cb..e0728245be 100644 --- a/plugins/webhook/permissions/autogenerated/reference.md +++ b/plugins/webhook/permissions/autogenerated/reference.md @@ -2,6 +2,10 @@ Default permissions for the plugin +#### This default permission set includes the following: + +- `allow-ping` + ## Permission Table @@ -11,6 +15,32 @@ Default permissions for the plugin + + + + + + + + + +
+ +`webhook:allow-ping` + + + +Enables the ping command without any pre-configured scope. + +
+ +`webhook:deny-ping` + + + +Denies the ping command without any pre-configured scope. + +
diff --git a/plugins/webhook/permissions/default.toml b/plugins/webhook/permissions/default.toml index 8ea0a5f96e..cc5a76f22e 100644 --- a/plugins/webhook/permissions/default.toml +++ b/plugins/webhook/permissions/default.toml @@ -1,3 +1,3 @@ [default] description = "Default permissions for the plugin" -permissions = [] +permissions = ["allow-ping"] diff --git a/plugins/webhook/permissions/schemas/schema.json b/plugins/webhook/permissions/schemas/schema.json index dda73b0416..ff4242e939 100644 --- a/plugins/webhook/permissions/schemas/schema.json +++ b/plugins/webhook/permissions/schemas/schema.json @@ -294,6 +294,18 @@ "PermissionKind": { "type": "string", "oneOf": [ + { + "description": "Enables the ping command without any pre-configured scope.", + "type": "string", + "const": "allow-ping", + "markdownDescription": "Enables the ping command without any pre-configured scope." + }, + { + "description": "Denies the ping command without any pre-configured scope.", + "type": "string", + "const": "deny-ping", + "markdownDescription": "Denies the ping command without any pre-configured scope." + }, { "description": "Enables the todo command without any pre-configured scope.", "type": "string", @@ -307,10 +319,10 @@ "markdownDescription": "Denies the todo command without any pre-configured scope." }, { - "description": "Default permissions for the plugin", + "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-ping`", "type": "string", "const": "default", - "markdownDescription": "Default permissions for the plugin" + "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-ping`" } ] } diff --git a/plugins/windows/Cargo.toml b/plugins/windows/Cargo.toml index fbdda6fa45..175225cd51 100644 --- a/plugins/windows/Cargo.toml +++ b/plugins/windows/Cargo.toml @@ -14,6 +14,7 @@ tauri-plugin = { workspace = true, features = ["build"] } specta-typescript = { workspace = true } [target.'cfg(target_os = "macos")'.dependencies] +tauri-nspanel = { workspace = true } objc2 = { workspace = true } objc2-app-kit = { workspace = true } objc2-foundation = { workspace = true } @@ -31,8 +32,6 @@ tauri = { workspace = true, features = ["test", "macos-private-api"] } tauri-specta = { workspace = true, features = ["derive", "typescript"] } tauri-plugin-analytics = { workspace = true } -tauri-plugin-os = { workspace = true } -tauri-plugin-window-state = { workspace = true } once_cell = { workspace = true } thiserror = { workspace = true } diff --git a/plugins/windows/js/bindings.gen.ts b/plugins/windows/js/bindings.gen.ts index 3c9a33d213..cd3a7b8412 100644 --- a/plugins/windows/js/bindings.gen.ts +++ b/plugins/windows/js/bindings.gen.ts @@ -1,167 +1,208 @@ // @ts-nocheck +// @ts-nocheck +/** tauri-specta globals **/ +import { + Channel as TAURI_CHANNEL, + invoke as TAURI_INVOKE, +} from "@tauri-apps/api/core"; +import * as TAURI_API_EVENT from "@tauri-apps/api/event"; +import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. /** user-defined commands **/ - export const commands = { -async windowShow(window: AppWindow) : Promise> { + async windowShow(window: AppWindow): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:windows|window_show", { window }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async windowDestroy(window: AppWindow) : Promise> { + return { + status: "ok", + data: await TAURI_INVOKE("plugin:windows|window_show", { window }), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, + async windowDestroy(window: AppWindow): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:windows|window_destroy", { window }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async windowNavigate(window: AppWindow, path: string) : Promise> { + return { + status: "ok", + data: await TAURI_INVOKE("plugin:windows|window_destroy", { window }), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, + async windowNavigate( + window: AppWindow, + path: string, + ): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:windows|window_navigate", { window, path }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async windowEmitNavigate(window: AppWindow, event: Navigate) : Promise> { + return { + status: "ok", + data: await TAURI_INVOKE("plugin:windows|window_navigate", { + window, + path, + }), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, + async windowEmitNavigate( + window: AppWindow, + event: Navigate, + ): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:windows|window_emit_navigate", { window, event }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async windowIsExists(window: AppWindow) : Promise> { + return { + status: "ok", + data: await TAURI_INVOKE("plugin:windows|window_emit_navigate", { + window, + event, + }), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, + async windowIsExists(window: AppWindow): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:windows|window_is_exists", { window }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async setFakeWindowBounds(name: string, bounds: OverlayBound) : Promise> { + return { + status: "ok", + data: await TAURI_INVOKE("plugin:windows|window_is_exists", { window }), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, + async setFakeWindowBounds( + name: string, + bounds: OverlayBound, + ): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:windows|set_fake_window_bounds", { name, bounds }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async removeFakeWindow(name: string) : Promise> { + return { + status: "ok", + data: await TAURI_INVOKE("plugin:windows|set_fake_window_bounds", { + name, + bounds, + }), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, + async removeFakeWindow(name: string): Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("plugin:windows|remove_fake_window", { name }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -} -} + return { + status: "ok", + data: await TAURI_INVOKE("plugin:windows|remove_fake_window", { name }), + }; + } catch (e) { + if (e instanceof Error) throw e; + else return { status: "error", error: e as any }; + } + }, +}; /** user-defined events **/ - export const events = __makeEvents__<{ -mainWindowState: MainWindowState, -navigate: Navigate, -openTab: OpenTab, -windowDestroyed: WindowDestroyed + mainWindowState: MainWindowState; + navigate: Navigate; + windowDestroyed: WindowDestroyed; }>({ -mainWindowState: "plugin:windows:main-window-state", -navigate: "plugin:windows:navigate", -openTab: "plugin:windows:open-tab", -windowDestroyed: "plugin:windows:window-destroyed" -}) + mainWindowState: "plugin:windows:main-window-state", + navigate: "plugin:windows:navigate", + windowDestroyed: "plugin:windows:window-destroyed", +}); /** user-defined constants **/ - - /** user-defined types **/ -export type AiState = { tab: AiTab | null } -export type AiTab = "transcription" | "intelligence" -export type AppWindow = { type: "onboarding" } | { type: "main" } | { type: "control" } -export type ChangelogState = { previous: string | null; current: string } -export type ChatShortcutsState = { isWebMode: boolean | null; selectedMineId: string | null; selectedWebIndex: number | null } -export type ContactsState = { selectedOrganization: string | null; selectedPerson: string | null } -export type DataState = { tab: DataTab | null } -export type DataTab = "import" | "export" -export type EditorView = { type: "raw" } | { type: "transcript" } | { type: "enhanced"; id: string } -export type ExtensionsState = { selectedExtension: string | null } -export type JsonValue = null | boolean | number | string | JsonValue[] | Partial<{ [key in string]: JsonValue }> -export type MainWindowState = { left_sidebar_expanded: boolean | null; right_panel_expanded: boolean | null } -export type Navigate = { path: string; search: Partial<{ [key in string]: JsonValue }> | null } -export type OpenTab = { tab: TabInput } -export type OverlayBound = { x: number; y: number; width: number; height: number } -export type PromptsState = { selectedTask: string | null } -export type SessionsState = { editor: EditorView | null } -export type TabInput = { type: "sessions"; id: string; state?: SessionsState | null } | { type: "contacts"; state?: ContactsState | null } | { type: "templates"; state?: TemplatesState | null } | { type: "prompts"; state?: PromptsState | null } | { type: "chat_shortcuts"; state?: ChatShortcutsState | null } | { type: "extensions"; state?: ExtensionsState | null } | { type: "events"; id: string } | { type: "humans"; id: string } | { type: "organizations"; id: string } | { type: "folders"; id: string | null } | { type: "empty" } | { type: "extension"; extensionId: string; state?: Partial<{ [key in string]: JsonValue }> | null } | { type: "calendar" } | { type: "changelog"; state: ChangelogState } | { type: "settings" } | { type: "ai"; state?: AiState | null } | { type: "data"; state?: DataState | null } -export type TemplatesState = { isWebMode: boolean | null; selectedMineId: string | null; selectedWebIndex: number | null } -export type WindowDestroyed = { window: AppWindow } - -/** tauri-specta globals **/ - -import { - invoke as TAURI_INVOKE, - Channel as TAURI_CHANNEL, -} from "@tauri-apps/api/core"; -import * as TAURI_API_EVENT from "@tauri-apps/api/event"; -import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; +export type AppWindow = + | { type: "onboarding" } + | { type: "main" } + | { type: "settings" } + | { type: "auth" } + | { type: "chat" } + | { type: "control" }; +export type JsonValue = + | null + | boolean + | number + | string + | JsonValue[] + | Partial<{ [key in string]: JsonValue }>; +export type MainWindowState = { + left_sidebar_expanded: boolean | null; + right_panel_expanded: boolean | null; +}; +export type Navigate = { + path: string; + search: Partial<{ [key in string]: JsonValue }> | null; +}; +export type OverlayBound = { + x: number; + y: number; + width: number; + height: number; +}; +export type WindowDestroyed = { window: AppWindow }; type __EventObj__ = { - listen: ( - cb: TAURI_API_EVENT.EventCallback, - ) => ReturnType>; - once: ( - cb: TAURI_API_EVENT.EventCallback, - ) => ReturnType>; - emit: null extends T - ? (payload?: T) => ReturnType - : (payload: T) => ReturnType; + listen: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + once: ( + cb: TAURI_API_EVENT.EventCallback, + ) => ReturnType>; + emit: null extends T + ? (payload?: T) => ReturnType + : (payload: T) => ReturnType; }; export type Result = - | { status: "ok"; data: T } - | { status: "error"; error: E }; + | { status: "ok"; data: T } + | { status: "error"; error: E }; function __makeEvents__>( - mappings: Record, + mappings: Record, ) { - return new Proxy( - {} as unknown as { - [K in keyof T]: __EventObj__ & { - (handle: __WebviewWindow__): __EventObj__; - }; - }, - { - get: (_, event) => { - const name = mappings[event as keyof T]; - - return new Proxy((() => {}) as any, { - apply: (_, __, [window]: [__WebviewWindow__]) => ({ - listen: (arg: any) => window.listen(name, arg), - once: (arg: any) => window.once(name, arg), - emit: (arg: any) => window.emit(name, arg), - }), - get: (_, command: keyof __EventObj__) => { - switch (command) { - case "listen": - return (arg: any) => TAURI_API_EVENT.listen(name, arg); - case "once": - return (arg: any) => TAURI_API_EVENT.once(name, arg); - case "emit": - return (arg: any) => TAURI_API_EVENT.emit(name, arg); - } - }, - }); - }, - }, - ); + return new Proxy( + {} as unknown as { + [K in keyof T]: __EventObj__ & { + (handle: __WebviewWindow__): __EventObj__; + }; + }, + { + get: (_, event) => { + const name = mappings[event as keyof T]; + + return new Proxy((() => {}) as any, { + apply: (_, __, [window]: [__WebviewWindow__]) => ({ + listen: (arg: any) => window.listen(name, arg), + once: (arg: any) => window.once(name, arg), + emit: (arg: any) => window.emit(name, arg), + }), + get: (_, command: keyof __EventObj__) => { + switch (command) { + case "listen": + return (arg: any) => TAURI_API_EVENT.listen(name, arg); + case "once": + return (arg: any) => TAURI_API_EVENT.once(name, arg); + case "emit": + return (arg: any) => TAURI_API_EVENT.emit(name, arg); + } + }, + }); + }, + }, + ); } diff --git a/plugins/windows/src/commands.rs b/plugins/windows/src/commands.rs index e0caadf270..20beb96fb1 100644 --- a/plugins/windows/src/commands.rs +++ b/plugins/windows/src/commands.rs @@ -6,7 +6,7 @@ pub async fn window_show( app: tauri::AppHandle, window: AppWindow, ) -> Result<(), String> { - app.windows().show(window).map_err(|e| e.to_string())?; + app.window_show(window).map_err(|e| e.to_string())?; Ok(()) } @@ -16,7 +16,7 @@ pub async fn window_destroy( app: tauri::AppHandle, window: AppWindow, ) -> Result<(), String> { - app.windows().destroy(window).map_err(|e| e.to_string())?; + app.window_destroy(window).map_err(|e| e.to_string())?; Ok(()) } @@ -27,8 +27,7 @@ pub async fn window_navigate( window: AppWindow, path: String, ) -> Result<(), String> { - app.windows() - .navigate(window, path) + app.window_navigate(window, path) .map_err(|e| e.to_string())?; Ok(()) } @@ -40,8 +39,7 @@ pub async fn window_emit_navigate( window: AppWindow, event: events::Navigate, ) -> Result<(), String> { - app.windows() - .emit_navigate(window, event) + app.window_emit_navigate(window, event) .map_err(|e| e.to_string())?; Ok(()) } @@ -52,7 +50,7 @@ pub async fn window_is_exists( app: tauri::AppHandle, window: AppWindow, ) -> Result { - let exists = app.windows().is_exists(window).map_err(|e| e.to_string())?; + let exists = app.window_is_exists(window).map_err(|e| e.to_string())?; Ok(exists) } diff --git a/plugins/windows/src/events.rs b/plugins/windows/src/events.rs index 97195875bb..fb58092805 100644 --- a/plugins/windows/src/events.rs +++ b/plugins/windows/src/events.rs @@ -3,11 +3,11 @@ use std::str::FromStr; use tauri::Manager; use tauri_specta::Event; -use crate::AppWindow; +use crate::{AppWindow, WindowsPluginExt}; // TODO: https://github.com/fastrepl/hyprnote/commit/150c8a1 this not worked. webview_window not found. pub fn on_window_event(window: &tauri::Window, event: &tauri::WindowEvent) { - let _app = window.app_handle(); + let app = window.app_handle(); match event { tauri::WindowEvent::CloseRequested { api, .. } => { @@ -97,12 +97,6 @@ common_event_derives! { } } -common_event_derives! { - pub struct OpenTab { - pub tab: crate::TabInput, - } -} - #[cfg(test)] mod test { use super::*; diff --git a/plugins/windows/src/ext.rs b/plugins/windows/src/ext.rs index 3df7620572..b7fc999150 100644 --- a/plugins/windows/src/ext.rs +++ b/plugins/windows/src/ext.rs @@ -1,5 +1,6 @@ use tauri::{AppHandle, Manager, WebviewWindow}; use tauri_specta::Event; +use uuid::Uuid; use crate::{AppWindow, WindowImpl, events}; @@ -75,11 +76,17 @@ impl AppWindow { #[cfg(target_os = "macos")] let _ = app.set_activation_policy(tauri::ActivationPolicy::Regular); - if matches!(self, Self::Main) { + if self.label() == "main" { use tauri_plugin_analytics::{AnalyticsPayload, AnalyticsPluginExt}; let e = AnalyticsPayload::builder("show_main_window").build(); - app.analytics().event_fire_and_forget(e); + + let app_clone = app.clone(); + tauri::async_runtime::spawn(async move { + if let Err(e) = app_clone.event(e).await { + tracing::error!("failed_to_send_analytics: {:?}", e); + } + }); } if let Some(window) = self.get(app) { @@ -90,76 +97,81 @@ impl AppWindow { let window = self.build_window(app)?; - if matches!(self, Self::Main) { - use tauri_plugin_window_state::{StateFlags, WindowExt}; - let _ = window.restore_state(StateFlags::SIZE); - } - - window.show()?; window.set_focus()?; + window.show()?; Ok(window) } } -pub struct Windows<'a> { - app: &'a AppHandle, +pub trait WindowsPluginExt { + fn close_all_windows(&self) -> Result<(), crate::Error>; + + fn window_show(&self, window: AppWindow) -> Result; + fn window_hide(&self, window: AppWindow) -> Result<(), crate::Error>; + fn window_close(&self, window: AppWindow) -> Result<(), crate::Error>; + fn window_destroy(&self, window: AppWindow) -> Result<(), crate::Error>; + fn window_is_focused(&self, window: AppWindow) -> Result; + fn window_is_exists(&self, window: AppWindow) -> Result; + + fn window_emit_navigate( + &self, + window: AppWindow, + event: events::Navigate, + ) -> Result<(), crate::Error>; + + fn window_navigate(&self, window: AppWindow, path: impl AsRef) + -> Result<(), crate::Error>; } -impl<'a> Windows<'a> { - pub fn show(&self, window: AppWindow) -> Result { - window.show(self.app) +impl WindowsPluginExt for AppHandle { + fn close_all_windows(&self) -> Result<(), crate::Error> { + for (_, window) in self.webview_windows() { + let _ = window.close(); + } + Ok(()) + } + + fn window_show(&self, window: AppWindow) -> Result { + window.show(self) } - pub fn hide(&self, window: AppWindow) -> Result<(), crate::Error> { - window.hide(self.app) + fn window_close(&self, window: AppWindow) -> Result<(), crate::Error> { + window.close(self) } - pub fn close(&self, window: AppWindow) -> Result<(), crate::Error> { - window.close(self.app) + fn window_hide(&self, window: AppWindow) -> Result<(), crate::Error> { + window.hide(self) } - pub fn destroy(&self, window: AppWindow) -> Result<(), crate::Error> { - window.destroy(self.app) + fn window_destroy(&self, window: AppWindow) -> Result<(), crate::Error> { + window.destroy(self) } - pub fn is_focused(&self, window: AppWindow) -> Result { + fn window_is_focused(&self, window: AppWindow) -> Result { Ok(window - .get(self.app) + .get(self) .and_then(|w| w.is_focused().ok()) .unwrap_or(false)) } - pub fn is_exists(&self, window: AppWindow) -> Result { - Ok(window.get(self.app).is_some()) - } - - pub fn emit_navigate( + fn window_emit_navigate( &self, window: AppWindow, event: events::Navigate, ) -> Result<(), crate::Error> { - window.emit_navigate(self.app, event) - } - - pub fn navigate(&self, window: AppWindow, path: impl AsRef) -> Result<(), crate::Error> { - window.navigate(self.app, path) + window.emit_navigate(self, event) } - pub fn close_all(&self) -> Result<(), crate::Error> { - for (_, window) in self.app.webview_windows() { - let _ = window.close(); - } - Ok(()) + fn window_navigate( + &self, + window: AppWindow, + path: impl AsRef, + ) -> Result<(), crate::Error> { + window.navigate(self, path) } -} - -pub trait WindowsPluginExt { - fn windows(&self) -> Windows<'_>; -} -impl WindowsPluginExt for AppHandle { - fn windows(&self) -> Windows<'_> { - Windows { app: self } + fn window_is_exists(&self, window: AppWindow) -> Result { + Ok(window.get(self).is_some()) } } diff --git a/plugins/windows/src/lib.rs b/plugins/windows/src/lib.rs index 2fd82da8f5..052f844b7b 100644 --- a/plugins/windows/src/lib.rs +++ b/plugins/windows/src/lib.rs @@ -3,13 +3,11 @@ mod errors; mod events; mod ext; mod overlay; -mod tab; mod window; pub use errors::*; pub use events::*; -pub use ext::{Windows, WindowsPluginExt}; -pub use tab::*; +pub use ext::*; pub use window::*; pub use overlay::{FakeWindowBounds, OverlayBound}; @@ -47,7 +45,6 @@ fn make_specta_builder() -> tauri_specta::Builder { events::Navigate, events::WindowDestroyed, events::MainWindowState, - events::OpenTab, ]) .commands(tauri_specta::collect_commands![ commands::window_show, @@ -81,13 +78,6 @@ pub fn init() -> tauri::plugin::TauriPlugin { Ok(()) }) - .on_event(move |app, event| match event { - tauri::RunEvent::ExitRequested { .. } => { - use tauri_plugin_window_state::{AppHandleExt, StateFlags}; - let _ = app.save_window_state(StateFlags::SIZE); - } - _ => {} - }) .build() } @@ -111,15 +101,4 @@ mod test { let content = std::fs::read_to_string(OUTPUT_FILE).unwrap(); std::fs::write(OUTPUT_FILE, format!("// @ts-nocheck\n{content}")).unwrap(); } - - #[test] - fn test_version() { - let version = tauri_plugin_os::version() - .to_string() - .split('.') - .next() - .and_then(|v| v.parse::().ok()) - .unwrap_or(0); - println!("version: {}", version); - } } diff --git a/plugins/windows/src/overlay.rs b/plugins/windows/src/overlay.rs index a444455d42..21be0482d2 100644 --- a/plugins/windows/src/overlay.rs +++ b/plugins/windows/src/overlay.rs @@ -23,10 +23,10 @@ static OVERLAY_JOIN_HANDLE: Lazy>>> = Lazy::new(|| Mutex::new(None)); pub fn abort_overlay_join_handle() { - if let Ok(mut guard) = OVERLAY_JOIN_HANDLE.lock() - && let Some(handle) = guard.take() - { - handle.abort(); + if let Ok(mut guard) = OVERLAY_JOIN_HANDLE.lock() { + if let Some(handle) = guard.take() { + handle.abort(); + } } } diff --git a/plugins/windows/src/window/v1.rs b/plugins/windows/src/window/v1.rs index c4b3722df5..b4e76002b8 100644 --- a/plugins/windows/src/window/v1.rs +++ b/plugins/windows/src/window/v1.rs @@ -7,6 +7,12 @@ pub enum AppWindow { Onboarding, #[serde(rename = "main")] Main, + #[serde(rename = "settings")] + Settings, + #[serde(rename = "auth")] + Auth, + #[serde(rename = "chat")] + Chat, #[serde(rename = "control")] Control, } @@ -16,6 +22,9 @@ impl std::fmt::Display for AppWindow { match self { Self::Onboarding => write!(f, "onboarding"), Self::Main => write!(f, "main"), + Self::Settings => write!(f, "settings"), + Self::Auth => write!(f, "auth"), + Self::Chat => write!(f, "chat"), Self::Control => write!(f, "control"), } } @@ -28,6 +37,9 @@ impl std::str::FromStr for AppWindow { match s { "onboarding" => return Ok(Self::Onboarding), "main" => return Ok(Self::Main), + "settings" => return Ok(Self::Settings), + "auth" => return Ok(Self::Auth), + "chat" => return Ok(Self::Chat), "control" => return Ok(Self::Control), _ => {} } @@ -51,27 +63,11 @@ impl AppWindow { #[cfg(target_os = "macos")] { - let traffic_light_y = { - use tauri_plugin_os::{Version, version}; - let major = match version() { - Version::Semantic(major, _, _) => major, - Version::Custom(s) => s - .split('.') - .next() - .and_then(|v| v.parse::().ok()) - .unwrap_or(0), - _ => 0, - }; - - if major >= 26 { 24.0 } else { 18.0 } - }; - builder = builder - .visible(false) .decorations(true) .hidden_title(true) .theme(Some(tauri::Theme::Light)) - .traffic_light_position(tauri::LogicalPosition::new(12.0, traffic_light_y)) + .traffic_light_position(tauri::LogicalPosition::new(12.0, 18.0)) .title_bar_style(tauri::TitleBarStyle::Overlay); } @@ -89,44 +85,14 @@ impl AppWindow { } } -const MAX_MAIN_WIDTH: f64 = 1600.0; -const MAX_MAIN_HEIGHT: f64 = 1000.0; -const MAX_ONBOARDING_WIDTH: f64 = 900.0; -const MAX_ONBOARDING_HEIGHT: f64 = 700.0; - -fn window_size_with_ratio( - monitor_width: f64, - monitor_height: f64, - aspect_ratio: f64, - scale: f64, - max_width: f64, - max_height: f64, -) -> (f64, f64) { - let monitor_ratio = monitor_width / monitor_height; - - let (width, height) = if aspect_ratio > monitor_ratio { - let width = monitor_width * scale; - (width, width / aspect_ratio) - } else { - let height = monitor_height * scale; - (height * aspect_ratio, height) - }; - - if width > max_width || height > max_height { - let scale_w = max_width / width; - let scale_h = max_height / height; - let scale = scale_w.min(scale_h); - (width * scale, height * scale) - } else { - (width, height) - } -} - impl WindowImpl for AppWindow { fn title(&self) -> String { match self { Self::Onboarding => "Onboarding".into(), Self::Main => "Main".into(), + Self::Settings => "Settings".into(), + Self::Auth => "Auth".into(), + Self::Chat => "Chat".into(), Self::Control => "Control".into(), } } @@ -135,74 +101,83 @@ impl WindowImpl for AppWindow { &self, app: &tauri::AppHandle, ) -> Result { - let margin = tauri::Size::Logical(tauri::LogicalSize::new(24.0, 24.0)); - - let monitor = app.primary_monitor().ok().flatten(); - - let (monitor_width, monitor_height) = monitor - .map(|m| { - let work_area = m.work_area(); - let scale = m.scale_factor(); - ( - work_area.size.width as f64 / scale, - work_area.size.height as f64 / scale, - ) - }) - .unwrap_or((1920.0, 1080.0)); + use tauri::LogicalSize; let window = match self { Self::Onboarding => { - let (width, height) = window_size_with_ratio( - monitor_width, - monitor_height, - 2.0 / 3.0, - 0.7, - MAX_ONBOARDING_WIDTH, - MAX_ONBOARDING_HEIGHT, - ); - - self.window_builder(app, "/app/onboarding") + let builder = self + .window_builder(app, "/app/onboarding") .resizable(false) - .inner_size(width, height) - .prevent_overflow_with_margin(margin) - .center() - .build()? + .min_inner_size(400.0, 600.0); + let window = builder.build()?; + window.set_size(LogicalSize::new(400.0, 600.0))?; + window } Self::Main => { - let (width, height) = window_size_with_ratio( - monitor_width, - monitor_height, - 4.0 / 3.0, - 0.8, - MAX_MAIN_WIDTH, - MAX_MAIN_HEIGHT, - ); - let (min_w, min_h) = window_size_with_ratio( - monitor_width, - monitor_height, - 4.0 / 3.0, - 0.4, - MAX_MAIN_WIDTH * 0.5, - MAX_MAIN_HEIGHT * 0.5, - ); - - self.window_builder(app, "/app/main") + let builder = self + .window_builder(app, "/app/main") .maximizable(true) .minimizable(true) - .inner_size(width, height) - .min_inner_size(min_w, min_h) - .prevent_overflow_with_margin(margin) - .center() - .build()? + .min_inner_size(620.0, 500.0); + let window = builder.build()?; + window.set_size(LogicalSize::new(910.0, 600.0))?; + window + } + Self::Settings => { + let window = self + .window_builder(app, "/app/settings") + .resizable(true) + .minimizable(true) + .maximizable(true) + .min_inner_size(800.0, 600.0) + .build()?; + + let desired_size = LogicalSize::new(800.0, 600.0); + window.set_size(LogicalSize::new(1.0, 1.0))?; + std::thread::sleep(std::time::Duration::from_millis(10)); + window.set_size(desired_size)?; + window + } + Self::Auth => { + let window = self + .window_builder(app, "/app/auth") + .resizable(false) + .min_inner_size(400.0, 600.0) + .build()?; + + let desired_size = LogicalSize::new(400.0, 600.0); + window.set_size(LogicalSize::new(1.0, 1.0))?; + std::thread::sleep(std::time::Duration::from_millis(10)); + window.set_size(desired_size)?; + window + } + Self::Chat => { + let window = self + .window_builder(app, "/app/chat") + .resizable(true) + .min_inner_size(400.0, 500.0) + .build()?; + + let desired_size = LogicalSize::new(400.0, 600.0); + window.set_size(LogicalSize::new(1.0, 1.0))?; + std::thread::sleep(std::time::Duration::from_millis(10)); + window.set_size(desired_size)?; + window + } + Self::Control => { + let window = self + .window_builder(app, "/app/control") + .transparent(true) + .resizable(true) + .min_inner_size(300.0, 200.0) + .build()?; + + let desired_size = LogicalSize::new(300.0, 200.0); + window.set_size(LogicalSize::new(1.0, 1.0))?; + std::thread::sleep(std::time::Duration::from_millis(10)); + window.set_size(desired_size)?; + window } - Self::Control => self - .window_builder(app, "/app/control") - .transparent(true) - .resizable(true) - .inner_size(300.0, 200.0) - .min_inner_size(300.0, 200.0) - .prevent_overflow() - .build()?, }; Ok(window)