From 470bb10e30cf173432ba21df78a222234a5e15ad Mon Sep 17 00:00:00 2001 From: Chiziaruhoma Ogbonda Date: Wed, 1 Apr 2026 10:19:45 +0100 Subject: [PATCH] feat: add device rotation functionality and updater plugin - Implemented device rotation feature in the control module, allowing users to change the screen orientation. - Integrated the Tauri updater plugin for seamless application updates. - Updated UI components to include a rotate button in the MirrorScreen. - Enhanced the release workflow to support the updater configuration in tauri.conf.json. --- .github/workflows/release.yml | 86 +++- Cargo.lock | 654 +++++++++++++++++++++++++++- bun.lock | 3 + crates/another-core/src/control.rs | 8 + crates/another-mcp/src/server.rs | 13 + package.json | 1 + src-tauri/Cargo.toml | 2 + src-tauri/capabilities/default.json | 5 +- src-tauri/src/commands.rs | 18 + src-tauri/src/lib.rs | 11 + src-tauri/tauri.conf.json | 11 +- src/App.css | 18 + src/App.tsx | 12 +- src/components/MirrorScreen.tsx | 11 + src/hooks/useConnection.ts | 27 +- src/hooks/useUpdater.ts | 42 ++ testfile.txt | 0 testfile.txt.sig | 1 + 18 files changed, 906 insertions(+), 17 deletions(-) create mode 100644 src/hooks/useUpdater.ts create mode 100644 testfile.txt create mode 100644 testfile.txt.sig diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c25d44a..186d4c3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,76 @@ permissions: contents: write jobs: + release-notes: + runs-on: ubuntu-latest + outputs: + notes: ${{ steps.ai.outputs.notes || steps.fallback.outputs.notes }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get commit log + id: commits + run: | + PREV_TAG=$(git tag --sort=-v:refname | grep '^v' | sed -n '2p') + if [ -z "$PREV_TAG" ]; then + LOG=$(git log --pretty=format:"- %s" --no-merges HEAD) + else + LOG=$(git log --pretty=format:"- %s" --no-merges "$PREV_TAG"..HEAD) + fi + if [ -z "$LOG" ]; then + LOG="- Bug fixes and improvements" + fi + { + echo "log<> "$GITHUB_OUTPUT" + + - name: Generate AI release notes + id: ai + if: ${{ secrets.GEMINI_API_KEY != '' }} + uses: google-github-actions/run-gemini-cli@v1 + with: + prompt: | + You are writing release notes for "Another", an Android screen mirroring and control desktop app. + Given these git commits, write concise, user-friendly release notes in markdown. + Group changes into sections like "New Features", "Improvements", "Bug Fixes" as needed. + Skip merge commits and internal refactors. Keep it short and clear. + Do not use em dashes. Do not add any preamble. + Start directly with ## What's New + + Commits: + ${{ steps.commits.outputs.log }} + env: + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + continue-on-error: true + + - name: Save AI notes + if: steps.ai.outcome == 'success' && steps.ai.outputs.text != '' + id: save-ai + run: | + { + echo "notes<> "$GITHUB_OUTPUT" + + - name: Fallback release notes + id: fallback + if: steps.ai.outcome != 'success' || steps.ai.outputs.text == '' + run: | + { + echo "notes<> "$GITHUB_OUTPUT" + build-linux: + needs: release-notes if: >- github.event_name == 'push' || inputs.platforms == 'all' || @@ -60,14 +129,18 @@ jobs: uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} with: tagName: ${{ github.ref_name }} releaseName: "Another ${{ github.ref_name }}" - releaseBody: "See the assets for download links." + releaseBody: ${{ needs.release-notes.outputs.notes }} releaseDraft: false prerelease: false + updaterJsonKeepUniversal: true build-macos: + needs: release-notes if: >- github.event_name == 'push' || inputs.platforms == 'all' || @@ -113,15 +186,19 @@ jobs: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} with: tagName: ${{ github.ref_name }} releaseName: "Another ${{ github.ref_name }}" - releaseBody: "See the assets for download links." + releaseBody: ${{ needs.release-notes.outputs.notes }} releaseDraft: false prerelease: false + updaterJsonKeepUniversal: true args: --target ${{ matrix.arch }} build-windows: + needs: release-notes if: >- github.event_name == 'push' || inputs.platforms == 'all' || @@ -156,9 +233,12 @@ jobs: uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} with: tagName: ${{ github.ref_name }} releaseName: "Another ${{ github.ref_name }}" - releaseBody: "See the assets for download links." + releaseBody: ${{ needs.release-notes.outputs.notes }} releaseDraft: false prerelease: false + updaterJsonKeepUniversal: true diff --git a/Cargo.lock b/Cargo.lock index 2ca9344..d049f76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,8 +15,10 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tauri-plugin-decorum", "tauri-plugin-dialog", "tauri-plugin-opener", + "tauri-plugin-updater", "tokio", "tokio-util", ] @@ -27,6 +29,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "ahash" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0453232ace82dee0dd0b4c87a59bd90f7b53b314f3e0f61fe2ee7c8a16482289" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -170,6 +178,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -442,6 +459,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -642,6 +665,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha20" version = "0.10.0" @@ -718,6 +747,36 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + [[package]] name = "colorchoice" version = "1.0.5" @@ -759,6 +818,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -775,6 +844,19 @@ 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", + "libc", +] + [[package]] name = "core-graphics" version = "0.25.0" @@ -782,12 +864,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ "bitflags 2.11.0", - "core-foundation", - "core-graphics-types", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", "foreign-types", "libc", ] +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + [[package]] name = "core-graphics-types" version = "0.2.0" @@ -795,7 +888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.11.0", - "core-foundation", + "core-foundation 0.10.1", "libc", ] @@ -994,6 +1087,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -1105,6 +1209,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dlv-list" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68df3f2b690c1b86e65ef7830956aededf3cb0a16f898f79b9a6f421a7b6211b" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "dom_query" version = "0.27.0" @@ -1188,6 +1301,19 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" +[[package]] +name = "enigo" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "802e4b2ae123615659085369b453cba87c5562e46ed8050a909fee18a9bc3157" +dependencies = [ + "core-graphics 0.23.2", + "libc", + "objc", + "pkg-config", + "windows 0.51.1", +] + [[package]] name = "enumflags2" version = "0.7.12" @@ -1282,6 +1408,26 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "file-locker" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ae8b5984a4863d8a32109a848d038bd6d914f20f010cc141375f7a183c41cf" +dependencies = [ + "nix", +] + +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1352,6 +1498,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "freedesktop_entry_parser" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db9c27b72f19a99a895f8ca89e2d26e4ef31013376e56fdafef697627306c3e4" +dependencies = [ + "nom", + "thiserror 1.0.69", +] + [[package]] name = "futf" version = "0.1.5" @@ -1777,6 +1933,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1911,6 +2076,22 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -2372,7 +2553,33 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ + "bitflags 2.11.0", "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "linicon" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee8c5653188a809616c97296180a0547a61dba205bcdcbdd261dbd022a25fd9" +dependencies = [ + "file-locker", + "freedesktop_entry_parser", + "linicon-theme", + "memmap2", + "thiserror 1.0.69", +] + +[[package]] +name = "linicon-theme" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f8240c33bb08c5d8b8cdea87b683b05e61037aa76ff26bef40672cc6ecbb80" +dependencies = [ + "freedesktop_entry_parser", + "rust-ini", ] [[package]] @@ -2417,6 +2624,15 @@ dependencies = [ "libc", ] +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "markup5ever" version = "0.14.1" @@ -2480,6 +2696,15 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -2501,6 +2726,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "minisign-verify" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2602,6 +2833,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -2675,6 +2918,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + [[package]] name = "objc2" version = "0.6.4" @@ -2745,6 +2997,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.11.0", "block2", + "libc", "objc2", "objc2-core-foundation", ] @@ -2760,6 +3013,18 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-osa-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + [[package]] name = "objc2-quartz-core" version = "0.3.2" @@ -2845,12 +3110,28 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-multimap" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485" +dependencies = [ + "dlv-list", + "hashbrown 0.9.1", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -2861,6 +3142,20 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "osakit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" +dependencies = [ + "objc2", + "objc2-foundation", + "objc2-osa-kit", + "serde", + "serde_json", + "thiserror 2.0.18", +] + [[package]] name = "pango" version = "0.18.3" @@ -2910,7 +3205,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link 0.2.1", ] @@ -3149,6 +3444,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "plist" version = "1.8.0" @@ -3440,6 +3741,15 @@ dependencies = [ "bitflags 2.11.0", ] +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -3514,15 +3824,20 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", "sync_wrapper", "tokio", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -3558,6 +3873,20 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rmcp" version = "1.3.0" @@ -3618,6 +3947,16 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rust-ini" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63471c4aa97a1cf8332a5f97709a79a4234698de6a1f5087faf66f2dae810e22" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -3646,6 +3985,79 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -3661,6 +4073,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.8.22" @@ -3732,6 +4153,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.24.0" @@ -4047,7 +4491,7 @@ dependencies = [ "objc2-foundation", "objc2-quartz-core", "raw-window-handle", - "redox_syscall", + "redox_syscall 0.5.18", "tracing", "wasm-bindgen", "web-sys", @@ -4154,6 +4598,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "swift-rs" version = "1.0.7" @@ -4228,8 +4678,8 @@ checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" dependencies = [ "bitflags 2.11.0", "block2", - "core-foundation", - "core-graphics", + "core-foundation 0.10.1", + "core-graphics 0.25.0", "crossbeam-channel", "dispatch2", "dlopen2", @@ -4269,6 +4719,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tar" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -4406,6 +4867,23 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-decorum" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db925c61a04a937028bc91ad8ae64a93b84a1715b964530925a54e793d494999" +dependencies = [ + "anyhow", + "cocoa", + "enigo", + "linicon", + "objc", + "rand 0.8.5", + "serde", + "tauri", + "tauri-plugin", +] + [[package]] name = "tauri-plugin-dialog" version = "2.6.0" @@ -4468,6 +4946,39 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-updater" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fe8e9bebd88fc222938ffdfbdcfa0307081423bd01e3252fc337d8bde81fc61" +dependencies = [ + "base64 0.22.1", + "dirs", + "flate2", + "futures-util", + "http", + "infer", + "log", + "minisign-verify", + "osakit", + "percent-encoding", + "reqwest", + "rustls", + "semver", + "serde", + "serde_json", + "tar", + "tauri", + "tauri-plugin", + "tempfile", + "thiserror 2.0.18", + "time", + "tokio", + "url", + "windows-sys 0.60.2", + "zip", +] + [[package]] name = "tauri-runtime" version = "2.10.1" @@ -4720,6 +5231,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.18" @@ -5065,6 +5586,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -5375,6 +5902,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webview2-com" version = "0.38.2" @@ -5457,6 +5993,16 @@ dependencies = [ "windows-version", ] +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core 0.51.1", + "windows-targets 0.48.5", +] + [[package]] name = "windows" version = "0.54.0" @@ -5489,6 +6035,15 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-core" version = "0.54.0" @@ -5685,6 +6240,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -5742,6 +6312,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -5760,6 +6336,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -5778,6 +6360,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -5808,6 +6396,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -5826,6 +6420,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -5844,6 +6444,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -5862,6 +6468,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -6070,6 +6682,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "yoke" version = "0.8.1" @@ -6195,6 +6817,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerotrie" version = "0.2.3" @@ -6228,6 +6856,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zip" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" +dependencies = [ + "arbitrary", + "crc32fast", + "indexmap 2.13.0", + "memchr", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/bun.lock b/bun.lock index 5bb9b17..f5152e7 100644 --- a/bun.lock +++ b/bun.lock @@ -10,6 +10,7 @@ "@tauri-apps/api": "^2", "@tauri-apps/plugin-dialog": "^2.6.0", "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-updater": "^2.10.0", "react": "^19.1.0", "react-dom": "^19.1.0", }, @@ -223,6 +224,8 @@ "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ=="], + "@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.10.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-ljN8jPlnT0aSn8ecYhuBib84alxfMx6Hc8vJSKMJyzGbTPFZAC44T2I1QNFZssgWKrAlofvJqCC6Rr472JWfkQ=="], + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], diff --git a/crates/another-core/src/control.rs b/crates/another-core/src/control.rs index 9b00df0..31e5ab5 100644 --- a/crates/another-core/src/control.rs +++ b/crates/another-core/src/control.rs @@ -10,6 +10,7 @@ const MSG_TYPE_INJECT_TOUCH: u8 = 2; const MSG_TYPE_INJECT_SCROLL: u8 = 3; #[allow(dead_code)] const MSG_TYPE_BACK_OR_SCREEN_ON: u8 = 4; +const MSG_TYPE_ROTATE_DEVICE: u8 = 11; pub const KEYCODE_HOME: u32 = 3; pub const KEYCODE_BACK: u32 = 4; @@ -93,6 +94,13 @@ pub async fn inject_text(socket: &Mutex, text: &str) -> Result<()> { Ok(()) } +pub async fn rotate_device(socket: &Mutex) -> Result<()> { + let buf = vec![MSG_TYPE_ROTATE_DEVICE]; + let mut stream = socket.lock().await; + stream.write_all(&buf).await?; + Ok(()) +} + pub async fn inject_scroll( socket: &Mutex, x: u32, diff --git a/crates/another-mcp/src/server.rs b/crates/another-mcp/src/server.rs index e6da973..69984fc 100644 --- a/crates/another-mcp/src/server.rs +++ b/crates/another-mcp/src/server.rs @@ -750,6 +750,19 @@ impl AnotherMcp { } } + #[tool(description = "Rotate the device screen orientation")] + async fn another_rotate_device(&self) -> String { + let session = self.session.lock().await; + let session = match session.as_ref() { + Some(s) => s, + None => return "No device connected".to_string(), + }; + match control::rotate_device(&session.control_socket).await { + Ok(_) => "Device rotated".to_string(), + Err(e) => format!("Error: {}", e), + } + } + #[tool(description = "Delete a recorded macro")] async fn another_macro_delete(&self, params: Parameters) -> String { if self.macros.lock().await.remove(¶ms.0.name).is_some() { diff --git a/package.json b/package.json index 4237562..64d65c1 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@tauri-apps/api": "^2", "@tauri-apps/plugin-dialog": "^2.6.0", "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-updater": "^2.10.0", "react": "^19.1.0", "react-dom": "^19.1.0" }, diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 10895f9..f8e4615 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -25,3 +25,5 @@ tokio = { version = "1", features = ["full"] } base64 = "0.22" anyhow = "1" rodio = { version = "0.19", default-features = false } +tauri-plugin-decorum = "1.1.1" +tauri-plugin-updater = "2.10.0" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index f4bcba4..e0453af 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -7,7 +7,10 @@ "core:default", "core:window:allow-set-size", "core:window:allow-set-min-size", + "core:window:allow-start-dragging", + "core:window:allow-start-resize-dragging", "opener:default", - "dialog:default" + "dialog:default", + "updater:default" ] } diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 5007273..9204d8c 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -230,6 +230,24 @@ pub async fn press_button(button: String, state: State<'_, AppState>) -> Result< .map_err(|e| e.to_string()) } +#[tauri::command] +pub async fn update_screen_size(width: u32, height: u32, state: State<'_, AppState>) -> Result<(), String> { + let mut session = state.session.lock().await; + let session = session.as_mut().ok_or("Not connected")?; + session.screen_width = width; + session.screen_height = height; + Ok(()) +} + +#[tauri::command] +pub async fn rotate_device(state: State<'_, AppState>) -> Result<(), String> { + let session = state.session.lock().await; + let session = session.as_ref().ok_or("Not connected")?; + control::rotate_device(&session.control_socket) + .await + .map_err(|e| e.to_string()) +} + #[tauri::command] pub async fn wake_screen(state: State<'_, AppState>) -> Result<(), String> { let session = state.session.lock().await; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 6bc1823..8667386 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -12,12 +12,21 @@ pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_decorum::init()) + .plugin(tauri_plugin_updater::Builder::new().build()) .manage(AppState::new()) .setup(|app| { if let Ok(dir) = app.path().resource_dir() { another_core::adb::set_resource_dir(dir); } + #[cfg(target_os = "macos")] + { + use tauri_plugin_decorum::WebviewWindowExt; + let main_window = app.get_webview_window("main").unwrap(); + main_window.create_overlay_titlebar().unwrap(); + } + let device_menu = SubmenuBuilder::new(app, "Device") .text("disconnect", "Disconnect") .separator() @@ -59,6 +68,8 @@ pub fn run() { commands::send_scroll, commands::take_screenshot, commands::press_button, + commands::rotate_device, + commands::update_screen_size, commands::set_muted, commands::wake_screen, commands::play_macro, diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 332be3f..20c71f4 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -19,13 +19,22 @@ "minWidth": 280, "minHeight": 400, "titleBarStyle": "Overlay", - "hiddenTitle": true + "hiddenTitle": true, + "acceptFirstMouse": true } ], "security": { "csp": null } }, + "plugins": { + "updater": { + "endpoints": [ + "https://github.com/Zfinix/another/releases/latest/download/latest.json" + ], + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEMwOUM2QkMzM0RFNDdCRjkKUldUNWUrUTl3MnVjd1Azb0k5Rm9MQXphVGJzaHdRNC9IZVhQazZTN2xUay8wQkdBZjBjL1o3R2kK" + } + }, "bundle": { "active": true, "targets": "all", diff --git a/src/App.css b/src/App.css index aefc8f7..e4d63e0 100644 --- a/src/App.css +++ b/src/App.css @@ -472,6 +472,13 @@ body { } /* Titlebar */ +.mirror-drag-bar { + height: 12px; + flex-shrink: 0; + -webkit-app-region: drag; + background: var(--surface); +} + .titlebar { height: 52px; display: flex; @@ -557,6 +564,12 @@ body { height: 16px; } +@media (max-width: 520px) { + .titlebar-btn-extra { + display: none; + } +} + /* Buttons */ .btn { padding: 5px 14px; @@ -1460,6 +1473,11 @@ body { justify-content: space-between; width: 100%; padding-left: 78px; + pointer-events: none; +} + +.toolbar-actions-split > * { + pointer-events: auto; } .toolbar-right { diff --git a/src/App.tsx b/src/App.tsx index 18da1fb..ac45871 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import { useDevices } from "./hooks/useDevices"; import { useConnection } from "./hooks/useConnection"; import { useAdaptiveBitrate } from "./hooks/useAdaptiveBitrate"; import { useMacro } from "./hooks/useMacro"; +import { useUpdater } from "./hooks/useUpdater"; import { WelcomeScreen } from "./components/WelcomeScreen"; import { MirrorScreen } from "./components/MirrorScreen"; import { SettingsDialog } from "./components/SettingsDialog"; @@ -40,6 +41,7 @@ function App() { const { toasts, showToast } = useToasts(); const { devices, refreshDevices } = useDevices(showToast); const macro = useMacro({ showToast, onRecordingStopped: () => setShowMacros(true) }); + const updater = useUpdater(showToast); const takeScreenshot = useCallback(async () => { try { @@ -55,8 +57,8 @@ function App() { }, [showToast]); const adaptiveRef = useRef<{ frameReceived: () => void; disableAdaptive: () => void }>({ - frameReceived: () => {}, - disableAdaptive: () => {}, + frameReceived: () => { }, + disableAdaptive: () => { }, }); const settingsRef = useRef(settings); @@ -154,7 +156,8 @@ function App() { { id: "macro-manage", label: "Manage Macros", keys: [MOD, "⇧", "L"], key: "l", shift: true, section: "Macros", action: () => setShowMacros(true) }, { id: "macro-export", label: "Export All Macros", keys: [MOD, "⇧", "E"], key: "e", shift: true, section: "Macros", action: macro.exportAllMacros }, { id: "macro-import", label: "Import Macros", keys: [MOD, "⇧", "I"], key: "i", shift: true, section: "Macros", action: macro.importMacros }, - ], [muted, recording, setMuted, toggleRecording, pressButton, takeScreenshot, cycleTheme, disconnect, macro.macroRecording, macro.toggleRecording, macro.macros, macro.playMacro, macro.exportAllMacros, macro.importMacros]); + { id: "check-updates", label: "Check for Updates", keys: [MOD, "⇧", "U"], key: "u", shift: true, section: "Actions", action: () => updater.checkForUpdates() }, + ], [muted, recording, setMuted, toggleRecording, pressButton, takeScreenshot, cycleTheme, disconnect, macro.macroRecording, macro.toggleRecording, macro.macros, macro.playMacro, macro.exportAllMacros, macro.importMacros, updater.checkForUpdates]); const commandsRef = useRef(commands); commandsRef.current = commands; @@ -163,7 +166,7 @@ function App() { const mcpEnabled = localStorage.getItem("mcp_enabled") !== "false"; if (mcpEnabled) { const port = parseInt(localStorage.getItem("mcp_port") || "7070", 10); - invoke("start_mcp_server", { port }).catch(() => {}); + invoke("start_mcp_server", { port }).catch(() => { }); } }, []); @@ -229,6 +232,7 @@ function App() { onToggleRecording={toggleRecording} onToggleMacroRecording={macro.toggleRecording} onPressButton={pressButton} + onRotateDevice={() => invoke("rotate_device")} onTakeScreenshot={takeScreenshot} onToggleSettings={() => setShowSettings((s) => !s)} onOpenCommandBar={() => setShowCommandBar(true)} diff --git a/src/components/MirrorScreen.tsx b/src/components/MirrorScreen.tsx index 6676147..43e164c 100644 --- a/src/components/MirrorScreen.tsx +++ b/src/components/MirrorScreen.tsx @@ -9,6 +9,8 @@ import { Square2StackIcon, CommandLineIcon, StopIcon, + PowerIcon, + ArrowPathIcon, } from "@heroicons/react/24/outline"; import type { Device } from "../types"; import { getDeviceDisplayName } from "../types"; @@ -25,6 +27,7 @@ interface MirrorScreenProps { onToggleRecording: () => void; onToggleMacroRecording: () => void; onPressButton: (button: string) => void; + onRotateDevice: () => void; onTakeScreenshot: () => void; onToggleSettings: () => void; onOpenCommandBar: () => void; @@ -52,6 +55,7 @@ export function MirrorScreen({ onToggleRecording, onToggleMacroRecording, onPressButton, + onRotateDevice, onTakeScreenshot, onToggleSettings, onOpenCommandBar, @@ -89,6 +93,7 @@ export function MirrorScreen({ return (
+
{getDeviceDisplayName(connectedDevice)} @@ -105,6 +110,12 @@ export function MirrorScreen({ + +
diff --git a/src/hooks/useConnection.ts b/src/hooks/useConnection.ts index 3c769cf..4ac89d2 100644 --- a/src/hooks/useConnection.ts +++ b/src/hooks/useConnection.ts @@ -41,6 +41,7 @@ export function useConnection(opts: UseConnectionOptions) { const isReconnecting = useRef(false); const recorderRef = useRef(null); const recordedChunks = useRef([]); + const nativeSize = useRef({ width: 1080, height: 1920 }); const cleanupDecoder = useCallback(() => { if (rafId.current) { @@ -91,7 +92,30 @@ export function useConnection(opts: UseConnectionOptions) { if (canvas.width !== f.displayWidth || canvas.height !== f.displayHeight) { canvas.width = f.displayWidth; canvas.height = f.displayHeight; - setDeviceSize({ width: f.displayWidth, height: f.displayHeight }); + setDeviceSize((prev) => { + if (prev.width === f.displayWidth && prev.height === f.displayHeight) return prev; + const prevLandscape = prev.width > prev.height; + const nowLandscape = f.displayWidth > f.displayHeight; + if (prevLandscape !== nowLandscape) { + nativeSize.current = { width: nativeSize.current.height, height: nativeSize.current.width }; + } + invoke("update_screen_size", { width: nativeSize.current.width, height: nativeSize.current.height }); + const chromeH = 52; + const aspect = f.displayWidth / f.displayHeight; + const isLandscape = aspect > 1; + let viewW: number, viewH: number; + if (isLandscape) { + const maxViewW = 860; + viewW = maxViewW; + viewH = Math.round(maxViewW / aspect); + } else { + const maxViewH = 860; + viewH = maxViewH; + viewW = Math.round(maxViewH * aspect); + } + getCurrentWindow().setSize(new LogicalSize(Math.max(viewW, 280), viewH + chromeH)); + return { width: f.displayWidth, height: f.displayHeight }; + }); } const ctx = canvas.getContext("2d"); if (ctx) ctx.drawImage(f, 0, 0); @@ -140,6 +164,7 @@ export function useConnection(opts: UseConnectionOptions) { onFrame: channel, settings: streamSettings, }); + nativeSize.current = { width, height }; setDeviceSize({ width, height }); setConnectedDevice(device); setScreen("another"); diff --git a/src/hooks/useUpdater.ts b/src/hooks/useUpdater.ts new file mode 100644 index 0000000..18a1c62 --- /dev/null +++ b/src/hooks/useUpdater.ts @@ -0,0 +1,42 @@ +import { useState, useCallback } from "react"; +import { check, type Update } from "@tauri-apps/plugin-updater"; + +export function useUpdater(showToast: (msg: string, type?: "error" | "info") => void) { + const [checking, setChecking] = useState(false); + const [updating, setUpdating] = useState(false); + + const checkForUpdates = useCallback( + async (silent = false) => { + if (checking || updating) return; + setChecking(true); + try { + let update: Update | null = null; + try { + update = await check(); + } catch { + if (!silent) showToast("No updates available right now", "info"); + return; + } + if (update) { + showToast(`Update ${update.version} available, downloading...`, "info"); + setUpdating(true); + await update.downloadAndInstall((event) => { + if (event.event === "Finished") { + showToast("Update installed. Restart the app to apply.", "info"); + } + }); + } else if (!silent) { + showToast("You're on the latest version", "info"); + } + } catch (e) { + if (!silent) showToast(`Update failed: ${e}`); + } finally { + setChecking(false); + setUpdating(false); + } + }, + [checking, updating, showToast], + ); + + return { checking, updating, checkForUpdates }; +} diff --git a/testfile.txt b/testfile.txt new file mode 100644 index 0000000..e69de29 diff --git a/testfile.txt.sig b/testfile.txt.sig new file mode 100644 index 0000000..fa88e43 --- /dev/null +++ b/testfile.txt.sig @@ -0,0 +1 @@ +dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUNWUrUTl3MnVjd0VJcEdKenRSUmZmZTRMZjRBaDI2KzNzYjdLVnN4dGNRam9pLzRsNW04eFNENi9hamFFaFJGOWdxcVJRTk1oZHpxOWZ3aXJLZ0VDWHM1TnV6b0MreVE0PQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzc1MDM0NTEzCWZpbGU6dGVzdGZpbGUudHh0CjNuOVc1c0V4VzJJREVFNmVYbjJuWmErcFZENktKV2IrUDgwRklwSXBxdTF4Z0hjaU1DOXViby9TdHFpenJ0Z3I4ZEl2b3UwUk1NVHIzNkVkT3dHcUNnPT0K \ No newline at end of file