mirror of
https://github.com/ayangweb/BongoCat.git
synced 2026-03-12 17:51:48 +08:00
Compare commits
38 Commits
v0.5.0
...
linux-wayl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6ad482851 | ||
|
|
7d541a486e | ||
|
|
496852630b | ||
|
|
54b9b9f34f | ||
|
|
e666ae5399 | ||
|
|
d56f79676e | ||
|
|
b75562a6fc | ||
|
|
c6af9827ac | ||
|
|
a1806e87bd | ||
|
|
83eb9581db | ||
|
|
beeecb617f | ||
|
|
7df7aad81a | ||
|
|
7f3c103ef6 | ||
|
|
1dda390450 | ||
|
|
0a412dc827 | ||
|
|
d15376e9cb | ||
|
|
af895ce839 | ||
|
|
fa418e0d63 | ||
|
|
368480fd3d | ||
|
|
9e478eae34 | ||
|
|
03793b0cc8 | ||
|
|
b8ce3e4a39 | ||
|
|
2a190efa15 | ||
|
|
73bbd6a4fb | ||
|
|
3d1206cb2d | ||
|
|
ce34b12f96 | ||
|
|
0dbd97d205 | ||
|
|
a6ffa0c07f | ||
|
|
85fe938f25 | ||
|
|
9b5599712a | ||
|
|
64e043fb11 | ||
|
|
22c2d479b6 | ||
|
|
80d197d9ad | ||
|
|
806623327d | ||
|
|
0825e5ffa5 | ||
|
|
b2e7e0abf4 | ||
|
|
f7da48e5c0 | ||
|
|
d7b460524c |
2
.github/DOWNLOAD_GUIDE.md
vendored
2
.github/DOWNLOAD_GUIDE.md
vendored
@@ -24,7 +24,7 @@ brew tap ayangweb/BongoCat
|
||||
2. 安装:
|
||||
|
||||
```bash
|
||||
brew install bongo-cat
|
||||
brew install --no-quarantine bongo-cat
|
||||
```
|
||||
|
||||
3. 更新:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: 🐞 Bug 报告
|
||||
title: '[bug] '
|
||||
description: 报告一个 Bug
|
||||
labels: 🐞 bug
|
||||
labels: bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: 💡 功能请求
|
||||
title: '[feat] '
|
||||
description: 提出一个想法
|
||||
labels: 💡 feature request
|
||||
labels: feature request
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Release CI
|
||||
name: BongoCat Release
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
|
||||
- name: Install app dependencies and build web
|
||||
- name: Install front-end dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build the app
|
||||
|
||||
20
.github/workflows/upgradelink.yml
vendored
Normal file
20
.github/workflows/upgradelink.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Upload Release to UpgradeLink
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
upgradeLink-upload:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send a request to UpgradeLink
|
||||
uses: toolsetlink/upgradelink-action@v5
|
||||
with:
|
||||
source-url: 'https://github.com/ayangweb/BongoCat/releases/latest/download/latest.json'
|
||||
access-key: ${{ secrets.UPGRADE_LINK_ACCESS_KEY }}
|
||||
tauri-key: ${{ secrets.UPGRADE_LINK_TAURI_KEY }}
|
||||
github-token: ${{ secrets.RELEASE_TOKEN }}
|
||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -2,6 +2,8 @@
|
||||
// Disable the default formatter, use eslint instead
|
||||
"prettier.enable": false,
|
||||
|
||||
"eslint.format.enable": true,
|
||||
|
||||
// Auto fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
@@ -46,5 +48,8 @@
|
||||
"scss",
|
||||
"pcss",
|
||||
"postcss"
|
||||
]
|
||||
],
|
||||
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"typescript.tsdk": "./node_modules/typescript/lib"
|
||||
}
|
||||
|
||||
376
Cargo.lock
generated
376
Cargo.lock
generated
@@ -184,9 +184,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059"
|
||||
checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"cfg-if",
|
||||
@@ -195,7 +195,7 @@ dependencies = [
|
||||
"futures-lite",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix 0.38.44",
|
||||
"rustix 1.0.7",
|
||||
"slab",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -214,9 +214,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
|
||||
checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-io",
|
||||
@@ -227,7 +227,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"rustix 0.38.44",
|
||||
"rustix 1.0.7",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -244,9 +244,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-signal"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3"
|
||||
checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-lock",
|
||||
@@ -254,7 +254,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix 0.38.44",
|
||||
"rustix 1.0.7",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -450,9 +450,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bongo-cat"
|
||||
version = "0.5.0"
|
||||
version = "0.6.2"
|
||||
dependencies = [
|
||||
"fs_extra",
|
||||
"input",
|
||||
"nix",
|
||||
"rdev",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -464,6 +466,7 @@ dependencies = [
|
||||
"tauri-plugin-custom-window",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-fs",
|
||||
"tauri-plugin-global-shortcut",
|
||||
"tauri-plugin-log",
|
||||
"tauri-plugin-macos-permissions",
|
||||
"tauri-plugin-opener",
|
||||
@@ -612,9 +615,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.9"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
|
||||
checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -654,9 +657,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.24"
|
||||
version = "1.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7"
|
||||
checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
@@ -747,7 +750,7 @@ dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"block",
|
||||
"cocoa-foundation 0.2.1",
|
||||
"core-foundation 0.10.0",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics 0.24.0",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
@@ -776,7 +779,7 @@ checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"block",
|
||||
"core-foundation 0.10.0",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics-types 0.2.0",
|
||||
"objc",
|
||||
]
|
||||
@@ -828,9 +831,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",
|
||||
@@ -862,7 +865,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation 0.10.0",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics-types 0.2.0",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
@@ -886,7 +889,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation 0.10.0",
|
||||
"core-foundation 0.10.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -1157,9 +1160,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dlopen2_derive"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54"
|
||||
checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1216,16 +1219,16 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "embed-resource"
|
||||
version = "3.0.2"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fbc6e0d8e0c03a655b53ca813f0463d2c956bc4db8138dbc89f120b066551e3"
|
||||
checksum = "e8fe7d068ca6b3a5782ca5ec9afc244acd99dd441e4686a83b1c3973aba1d489"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"memchr",
|
||||
"rustc_version",
|
||||
"toml",
|
||||
"vswhom",
|
||||
"winreg 0.52.0",
|
||||
"winreg 0.55.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1864,6 +1867,24 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "global-hotkey"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"keyboard-types",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"windows-sys 0.59.0",
|
||||
"x11rb",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.18.0"
|
||||
@@ -1962,9 +1983,15 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.4.0"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
@@ -2065,22 +2092,26 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
"webpki-roots 1.0.0",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.12"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710"
|
||||
checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"ipnet",
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
@@ -2299,12 +2330,52 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "input"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbdc09524a91f9cacd26f16734ff63d7dc650daffadd2b6f84d17a285bd875a9"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"input-sys",
|
||||
"libc",
|
||||
"log",
|
||||
"udev",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "input-sys"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd4f5b4d1c00331c5245163aacfe5f20be75b564c7112d45893d4ae038119eb0"
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
|
||||
[[package]]
|
||||
name = "iri-string"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-docker"
|
||||
version = "0.2.0"
|
||||
@@ -2509,6 +2580,16 @@ dependencies = [
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libudev-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
@@ -2529,9 +2610,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
@@ -2654,13 +2735,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3160,9 +3241,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
@@ -3170,9 +3251,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.10"
|
||||
version = "0.9.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
@@ -3394,15 +3475,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.7.4"
|
||||
version = "3.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f"
|
||||
checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.5.1",
|
||||
"pin-project-lite",
|
||||
"rustix 0.38.44",
|
||||
"rustix 1.0.7",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
@@ -3439,9 +3520,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.32"
|
||||
version = "0.2.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
|
||||
checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.101",
|
||||
@@ -3839,9 +3920,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.15"
|
||||
version = "0.12.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
|
||||
checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
@@ -3862,7 +3943,6 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -3872,14 +3952,14 @@ dependencies = [
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots 0.26.11",
|
||||
"windows-registry",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4027,15 +4107,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.12.0"
|
||||
@@ -4361,9 +4432,9 @@ checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.9"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
|
||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -4589,7 +4660,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation 0.10.0",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics 0.24.0",
|
||||
"crossbeam-channel",
|
||||
"dispatch",
|
||||
@@ -4774,12 +4845,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tauri-nspanel"
|
||||
version = "2.0.1"
|
||||
source = "git+https://github.com/ahkohd/tauri-nspanel?branch=v2#d4b9df797959f8fa4701e8a20ff69d9605bb66e9"
|
||||
source = "git+https://github.com/ahkohd/tauri-nspanel?branch=v2#dc1563b2c71d3699dc67028080e43aade0575bcf"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"block",
|
||||
"cocoa 0.26.1",
|
||||
"core-foundation 0.10.0",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics 0.24.0",
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
@@ -4883,6 +4954,21 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-global-shortcut"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31919f3c07bcb585afef217c0c33cde80da9ebccf5b8e2c90e0e0a535b14ab47"
|
||||
dependencies = [
|
||||
"global-hotkey",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-log"
|
||||
version = "2.4.0"
|
||||
@@ -4962,9 +5048,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-pinia"
|
||||
version = "3.4.0"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e87882a5a390b91bd7f785c382cd374b4a1b32ce3429cec871b16ee1ff1f8cb8"
|
||||
checksum = "bec0cb197a26a9b468679887d85b38ad1860cf032d5bc2fd755d77f7e704b838"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"tauri",
|
||||
@@ -5095,9 +5181,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-store"
|
||||
version = "0.10.5"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d35f80125c58fcbc5d408d288f7bc7d89b585c4ceb474217c553d9c0afc3963"
|
||||
checksum = "f7981255eefa2868d49af173f27f0171e6e4a3c580530334f31a7be8b25446c8"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"futures",
|
||||
@@ -5113,9 +5199,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-store-macros"
|
||||
version = "0.10.5"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a1c7a0b908a8d874ac2c055d757273c102d386d06690f56fe6d7a93bbc4ea60"
|
||||
checksum = "fef3bf6f34307c6185436ea109c6fd6c01bfb9636c16902cd7e8abcc243fbecc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5124,12 +5210,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-store-utils"
|
||||
version = "0.4.2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bd3d928e03a75d07774ae2e4b754eb32cbf87e6154b3c7b7f1facd40a6e9497"
|
||||
checksum = "baed2bbdce143a0b6e8105a0c9c7d9bfaa9c5689c3632bff12085c7209365d3f"
|
||||
dependencies = [
|
||||
"bon",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
@@ -5327,14 +5412,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.45.0"
|
||||
version = "1.45.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
|
||||
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio 1.0.3",
|
||||
"mio 1.0.4",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
@@ -5455,6 +5540,24 @@ dependencies = [
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"iri-string",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.3"
|
||||
@@ -5551,6 +5654,18 @@ version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "udev"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af4e37e9ea4401fc841ff54b9ddfc9be1079b1e89434c1a6a865dd68980f7e9f"
|
||||
dependencies = [
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"libudev-sys",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
version = "1.1.0"
|
||||
@@ -5971,15 +6086,6 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
||||
dependencies = [
|
||||
"webpki-roots 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.0"
|
||||
@@ -6115,7 +6221,7 @@ dependencies = [
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings 0.4.2",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6167,17 +6273,6 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-strings 0.3.1",
|
||||
"windows-targets 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
@@ -6187,15 +6282,6 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
@@ -6280,29 +6366,13 @@ dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.53.0",
|
||||
"windows_aarch64_msvc 0.53.0",
|
||||
"windows_i686_gnu 0.53.0",
|
||||
"windows_i686_gnullvm 0.53.0",
|
||||
"windows_i686_msvc 0.53.0",
|
||||
"windows_x86_64_gnu 0.53.0",
|
||||
"windows_x86_64_gnullvm 0.53.0",
|
||||
"windows_x86_64_msvc 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.1.0"
|
||||
@@ -6339,12 +6409,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -6363,12 +6427,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
@@ -6387,24 +6445,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -6423,12 +6469,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
@@ -6447,12 +6487,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@@ -6471,12 +6505,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -6495,12 +6523,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.40"
|
||||
@@ -6530,12 +6552,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.52.0"
|
||||
version = "0.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
|
||||
checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6673,6 +6695,12 @@ dependencies = [
|
||||
"rustix 1.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xkeysym"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.0"
|
||||
|
||||
35
README.md
35
README.md
@@ -51,15 +51,15 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
| macOS | Window | Linux(x11) |
|
||||
| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||
|  |  |  |
|
||||
| macOS | Windows | Linux(x11) |
|
||||
| -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
|  |  |  |
|
||||
|
||||
## 开发背景
|
||||
|
||||
本项目的灵感来源于 [MMmmmoko](https://github.com/MMmmmoko) 大佬开发的 [Bongo-Cat-Mver](https://github.com/MMmmmoko/Bongo-Cat-Mver)。它以独特的猫咪互动功能深受用户喜爱,但仅支持 Windows 平台。作为一名深度 macOS 用户,我特别希望在自己的设备上也能使用这款可爱的猫咪,于是我决定开发一个适配 macOS 的版本。
|
||||
|
||||
同时,得益于 [Tauri](https://github.com/tauri-apps/tauri) 强大的跨平台能力,本项目不仅支持 macOS,还兼容 Windows 和 Linux(x11),让更多的用户都能与这只可爱的猫咪互动!
|
||||
同时,得益于 [Tauri](https://github.com/tauri-apps/tauri) 强大的跨平台能力,本项目不仅支持 macOS,还兼容 Windows 和 Linux,让更多的用户都能与这只可爱的猫咪互动!
|
||||
|
||||
## 下载
|
||||
|
||||
@@ -70,11 +70,32 @@
|
||||
|
||||
## 功能介绍
|
||||
|
||||
- 适配 macOS、Windows 和 Linux(x11)。
|
||||
- 适配 macOS、Windows 和 Linux。
|
||||
- 根据据键盘或鼠标操作,同步移动鼠标或敲击键盘。
|
||||
- 支持导入自定义模型,自由打造专属猫咪形象。
|
||||
- 完全开源,代码公开透明,绝不收集任何用户数据。
|
||||
- 支持离线运行,无需联网,保护用户隐私。
|
||||
|
||||
## 使用提示
|
||||
|
||||
- Linux 下需要用户系统安装 libinput 并加入 `input` 用户组,方可在 X11 和 Wayland 下正常使用。
|
||||
|
||||
## 更多模型
|
||||
|
||||
你可以在这个仓库中探索、下载更多猫咪模型,或提交你的创作,与大家一起分享:
|
||||
|
||||
📦 [Awesome-BongoCat](https://github.com/ayangweb/Awesome-BongoCat)
|
||||
|
||||
## 社区交流
|
||||
|
||||
<a href="https://qm.qq.com/q/AS3gNv2Vzy">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://i0.hdslb.com/bfs/openplatform/5ad8e4278c525cca6d3b4426c30b6d299d8a9654.png" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://i0.hdslb.com/bfs/openplatform/599680ad67bc9f9f876f76069c2239e9a85bb54d.png" />
|
||||
<img alt="QQ Group" src="https://i0.hdslb.com/bfs/openplatform/599680ad67bc9f9f876f76069c2239e9a85bb54d.png" height="250" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## 贡献指南
|
||||
|
||||
感谢大家为 BongoCat 做出的宝贵贡献!如果你也希望为 BongoCat 做出贡献,请查阅[贡献指南](.github/CONTRIBUTING.md)。
|
||||
@@ -92,3 +113,7 @@
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ayangweb/BongoCat&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## 致谢
|
||||
|
||||
- 特别感谢 [UpgradeLink](https://www.toolsetlink.com/) 提供高效稳定的自动更新服务,让本项目得以持续为用户带来最新版本的优质体验。
|
||||
|
||||
24
package.json
24
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "bongo-cat",
|
||||
"type": "module",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.2",
|
||||
"private": true,
|
||||
"author": {
|
||||
"name": "ayangweb",
|
||||
@@ -27,43 +27,45 @@
|
||||
"@tauri-apps/plugin-clipboard-manager": "~2.2.2",
|
||||
"@tauri-apps/plugin-dialog": "~2.2.2",
|
||||
"@tauri-apps/plugin-fs": "~2.3.0",
|
||||
"@tauri-apps/plugin-global-shortcut": "~2.2.1",
|
||||
"@tauri-apps/plugin-log": "~2.3.1",
|
||||
"@tauri-apps/plugin-opener": "~2.2.7",
|
||||
"@tauri-apps/plugin-os": "^2.2.1",
|
||||
"@tauri-apps/plugin-process": "^2.2.1",
|
||||
"@tauri-apps/plugin-updater": "~2.7.1",
|
||||
"@tauri-store/pinia": "^3.4.0",
|
||||
"@vueuse/core": "^13.2.0",
|
||||
"@tauri-store/pinia": "^3.7.0",
|
||||
"@vueuse/core": "^13.3.0",
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"dayjs": "^1.11.13",
|
||||
"es-toolkit": "^1.38.0",
|
||||
"is-url": "^1.2.4",
|
||||
"nanoid": "^5.1.5",
|
||||
"pinia": "^3.0.2",
|
||||
"pinia": "^3.0.3",
|
||||
"pixi-live2d-display": "^0.4.0",
|
||||
"pixi.js": "^6.5.10",
|
||||
"tauri-plugin-macos-permissions-api": "^2.3.0",
|
||||
"vue": "^3.5.14",
|
||||
"vue": "^3.5.16",
|
||||
"vue-markdown-render": "^2.2.1",
|
||||
"vue-router": "^4.5.1"
|
||||
"vue-router": "^4.5.1",
|
||||
"vue3-masonry-css": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^4.13.2",
|
||||
"@antfu/eslint-config": "^4.13.3",
|
||||
"@commitlint/cli": "^19.8.1",
|
||||
"@commitlint/config-conventional": "^19.8.1",
|
||||
"@iconify-json/iconamoon": "^1.2.2",
|
||||
"@iconify-json/solar": "^1.2.2",
|
||||
"@tauri-apps/cli": "^2.5.0",
|
||||
"@types/is-url": "^1.2.32",
|
||||
"@types/node": "^22.15.21",
|
||||
"@unocss/eslint-plugin": "^66.1.2",
|
||||
"@types/node": "^22.15.29",
|
||||
"@unocss/eslint-plugin": "^66.1.3",
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint-plugin-format": "^1.0.1",
|
||||
"lint-staged": "^15.5.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"release-it": "^18.1.2",
|
||||
"sass": "^1.89.0",
|
||||
"sass": "^1.89.1",
|
||||
"simple-git-hooks": "^2.13.0",
|
||||
"tsx": "^4.19.4",
|
||||
"typescript": "~5.6.3",
|
||||
|
||||
1759
pnpm-lock.yaml
generated
1759
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bongo-cat"
|
||||
version = "0.5.0"
|
||||
version = "0.6.2"
|
||||
description = "A Tauri App"
|
||||
authors = [ "ayangweb" ]
|
||||
edition = "2024"
|
||||
@@ -22,7 +22,6 @@ tauri = { workspace = true, features = ["tray-icon", "protocol-asset", "macos-pr
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
tauri-plugin-custom-window.workspace = true
|
||||
rdev = { git = "https://github.com/ayangweb/rdev" }
|
||||
tauri-plugin-os = "2"
|
||||
tauri-plugin-process = "2"
|
||||
tauri-plugin-opener = "2"
|
||||
@@ -37,9 +36,17 @@ tauri-plugin-dialog = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
fs_extra = "1"
|
||||
tauri-plugin-clipboard-manager = "2"
|
||||
tauri-plugin-global-shortcut = "2"
|
||||
|
||||
[target."cfg(target_os = \"macos\")".dependencies]
|
||||
tauri-nspanel.workspace = true
|
||||
|
||||
[target."cfg(not(target_os = \"linux\"))".dependencies]
|
||||
rdev = { git = "https://github.com/ayangweb/rdev" }
|
||||
|
||||
[target."cfg(target_os = \"linux\")".dependencies]
|
||||
nix = { version = "0.30", features = ["poll"] }
|
||||
input = "0.9"
|
||||
|
||||
[features]
|
||||
cargo-clippy = []
|
||||
|
||||
@@ -42,6 +42,9 @@
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
"clipboard-manager:allow-write-text"
|
||||
"clipboard-manager:allow-write-text",
|
||||
"global-shortcut:allow-is-registered",
|
||||
"global-shortcut:allow-register",
|
||||
"global-shortcut:allow-unregister"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,35 +1,22 @@
|
||||
use rdev::{Event, EventType, listen};
|
||||
use serde::Serialize;
|
||||
use serde_json::{Value, json};
|
||||
use serde_json::json;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tauri::{AppHandle, Emitter, Runtime, command};
|
||||
|
||||
use crate::core::{device::{DeviceEvent, DeviceKind}};
|
||||
|
||||
static IS_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub enum DeviceKind {
|
||||
MousePress,
|
||||
MouseRelease,
|
||||
MouseMove,
|
||||
KeyboardPress,
|
||||
KeyboardRelease,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct DeviceEvent {
|
||||
kind: DeviceKind,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
pub fn start_listening(app_handle: AppHandle) {
|
||||
#[command]
|
||||
pub async fn start_device_listening<R: Runtime>(app_handle: AppHandle<R>) -> Result<(), String> {
|
||||
if IS_RUNNING.load(Ordering::SeqCst) {
|
||||
return;
|
||||
return Err("Device is already listening".to_string());
|
||||
}
|
||||
|
||||
IS_RUNNING.store(true, Ordering::SeqCst);
|
||||
|
||||
let callback = move |event: Event| {
|
||||
let device = match event.event_type {
|
||||
let device_event = match event.event_type {
|
||||
EventType::ButtonPress(button) => DeviceEvent {
|
||||
kind: DeviceKind::MousePress,
|
||||
value: json!(format!("{:?}", button)),
|
||||
@@ -53,20 +40,10 @@ pub fn start_listening(app_handle: AppHandle) {
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if let Err(e) = app_handle.emit("device-changed", device) {
|
||||
eprintln!("Failed to emit event: {:?}", e);
|
||||
}
|
||||
let _ = app_handle.emit("device-changed", device_event);
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Err(e) = listen(callback) {
|
||||
eprintln!("Device listening error: {:?}", e);
|
||||
}
|
||||
listen(callback).map_err(|err| format!("Failed to listen device: {:?}", err))?;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
std::thread::spawn(move || {
|
||||
if let Err(e) = listen(callback) {
|
||||
eprintln!("Device listening error: {:?}", e);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
128
src-tauri/src/core/device/linux.rs
Normal file
128
src-tauri/src/core/device/linux.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
use input::{
|
||||
event::{
|
||||
keyboard::{KeyState, KeyboardEventTrait},
|
||||
pointer::ButtonState,
|
||||
PointerEvent,
|
||||
},
|
||||
Event, Libinput, LibinputInterface,
|
||||
};
|
||||
use nix::{
|
||||
libc::{O_RDONLY, O_RDWR, O_WRONLY},
|
||||
poll::{poll, PollFd, PollFlags, PollTimeout},
|
||||
};
|
||||
use std::{
|
||||
fs::{File, OpenOptions}, os::{fd::{AsFd, OwnedFd}, unix::prelude::OpenOptionsExt}, path::Path
|
||||
};
|
||||
|
||||
use serde_json::json;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tauri::{AppHandle, Emitter, Runtime, command};
|
||||
|
||||
use crate::core::{device::{DeviceEvent, DeviceKind}, setup::key_from_code};
|
||||
|
||||
static IS_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub struct Interface;
|
||||
|
||||
impl LibinputInterface for Interface {
|
||||
fn open_restricted(&mut self, path: &Path, flags: i32) -> Result<OwnedFd, i32> {
|
||||
OpenOptions::new()
|
||||
.custom_flags(flags)
|
||||
.read((flags & O_RDONLY != 0) | (flags & O_RDWR != 0))
|
||||
.write((flags & O_WRONLY != 0) | (flags & O_RDWR != 0))
|
||||
.open(path)
|
||||
.map(|file| file.into())
|
||||
.map_err(|err| err.raw_os_error().unwrap())
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
fn close_restricted(&mut self, fd: OwnedFd) {
|
||||
File::from(fd);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_device_event(event: &Event) -> Option<DeviceEvent> {
|
||||
match event {
|
||||
Event::Keyboard(ev) => {
|
||||
let key_code = ev.key();
|
||||
let key_name = match key_from_code(key_code) {
|
||||
Some(name) => name.to_string(),
|
||||
None => format!("Unknown({})", key_code),
|
||||
};
|
||||
match ev.key_state() {
|
||||
KeyState::Pressed => Some(DeviceEvent {
|
||||
kind: DeviceKind::KeyboardPress,
|
||||
value: json!(key_name),
|
||||
}),
|
||||
KeyState::Released => Some(DeviceEvent{
|
||||
kind: DeviceKind::KeyboardRelease,
|
||||
value: json!(key_name),
|
||||
})
|
||||
}
|
||||
},
|
||||
Event::Pointer(ev) => {
|
||||
match ev {
|
||||
PointerEvent::Button(e) => {
|
||||
let btn_code = e.button();
|
||||
let btn_name = match btn_code {
|
||||
0x110 => String::from("Left"),
|
||||
0x111 => String::from("Right"),
|
||||
0x112 => String::from("Middle"),
|
||||
_ => format!("Unknown({})", btn_code as u8),
|
||||
};
|
||||
match e.button_state() {
|
||||
ButtonState::Pressed => Some(DeviceEvent {
|
||||
kind: DeviceKind::MousePress,
|
||||
value: json!(btn_name),
|
||||
}),
|
||||
ButtonState::Released => Some(DeviceEvent {
|
||||
kind: DeviceKind::MouseRelease,
|
||||
value: json!(btn_name),
|
||||
})
|
||||
}
|
||||
},
|
||||
PointerEvent::Motion(e) => {
|
||||
Some(DeviceEvent {
|
||||
kind: DeviceKind::MouseMove,
|
||||
value: json!({
|
||||
"x": e.dx_unaccelerated(),
|
||||
"y": e.dy_unaccelerated()
|
||||
}),
|
||||
})
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[command]
|
||||
pub async fn start_device_listening<R: Runtime>(app_handle: AppHandle<R>) -> Result<(), String> {
|
||||
if IS_RUNNING.load(Ordering::SeqCst) {
|
||||
return Err("Device is already listening".to_string());
|
||||
}
|
||||
|
||||
IS_RUNNING.store(true, Ordering::SeqCst);
|
||||
|
||||
let mut input = Libinput::new_with_udev(Interface);
|
||||
match input.udev_assign_seat("seat0") {
|
||||
Ok(_) => {
|
||||
let input_clone = &input.clone();
|
||||
let mut pollfds = [PollFd::new(input_clone.as_fd(), PollFlags::POLLIN)];
|
||||
while poll(&mut pollfds, PollTimeout::NONE).is_ok() {
|
||||
input.dispatch().unwrap();
|
||||
for event in &mut input {
|
||||
let device_event = build_device_event(&event);
|
||||
if let Some(e) = device_event {
|
||||
app_handle.emit("device-changed", e).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => return Err("Failed to assign seat".to_string()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
29
src-tauri/src/core/device/mod.rs
Normal file
29
src-tauri/src/core/device/mod.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub mod common;
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub use common::*;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use linux::*;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub enum DeviceKind {
|
||||
MousePress,
|
||||
MouseRelease,
|
||||
MouseMove,
|
||||
KeyboardPress,
|
||||
KeyboardRelease,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct DeviceEvent {
|
||||
kind: DeviceKind,
|
||||
value: Value,
|
||||
}
|
||||
@@ -6,3 +6,149 @@ pub fn platform(
|
||||
_preference_window: WebviewWindow,
|
||||
) {
|
||||
}
|
||||
|
||||
pub fn key_from_code(code: u32) -> Option<&'static str> {
|
||||
match code {
|
||||
// Function key
|
||||
1 => Some("Escape"),
|
||||
28 => Some("Return"),
|
||||
14 => Some("Backspace"),
|
||||
15 => Some("Tab"),
|
||||
57 => Some("Space"),
|
||||
58 => Some("CapsLock"),
|
||||
99 => Some("PrintScreen"),
|
||||
70 => Some("ScrollLock"),
|
||||
119 => Some("Pause"),
|
||||
69 => Some("NumLock"),
|
||||
110 => Some("Insert"),
|
||||
102 => Some("Home"),
|
||||
107 => Some("End"),
|
||||
104 => Some("PageUp"),
|
||||
109 => Some("PageDown"),
|
||||
111 => Some("Delete"),
|
||||
|
||||
// Arrow key
|
||||
103 => Some("UpArrow"),
|
||||
108 => Some("DownArrow"),
|
||||
105 => Some("LeftArrow"),
|
||||
106 => Some("RightArrow"),
|
||||
|
||||
// F key
|
||||
59 => Some("F1"),
|
||||
60 => Some("F2"),
|
||||
61 => Some("F3"),
|
||||
62 => Some("F4"),
|
||||
63 => Some("F5"),
|
||||
64 => Some("F6"),
|
||||
65 => Some("F7"),
|
||||
66 => Some("F8"),
|
||||
67 => Some("F9"),
|
||||
68 => Some("F10"),
|
||||
87 => Some("F11"),
|
||||
88 => Some("F12"),
|
||||
|
||||
// Numeric
|
||||
2 => Some("Num1"),
|
||||
3 => Some("Num2"),
|
||||
4 => Some("Num3"),
|
||||
5 => Some("Num4"),
|
||||
6 => Some("Num5"),
|
||||
7 => Some("Num6"),
|
||||
8 => Some("Num7"),
|
||||
9 => Some("Num8"),
|
||||
10 => Some("Num9"),
|
||||
11 => Some("Num0"),
|
||||
|
||||
// Alphabetic
|
||||
16 => Some("KeyQ"),
|
||||
17 => Some("KeyW"),
|
||||
18 => Some("KeyE"),
|
||||
19 => Some("KeyR"),
|
||||
20 => Some("KeyT"),
|
||||
21 => Some("KeyY"),
|
||||
22 => Some("KeyU"),
|
||||
23 => Some("KeyI"),
|
||||
24 => Some("KeyO"),
|
||||
25 => Some("KeyP"),
|
||||
30 => Some("KeyA"),
|
||||
31 => Some("KeyS"),
|
||||
32 => Some("KeyD"),
|
||||
33 => Some("KeyF"),
|
||||
34 => Some("KeyG"),
|
||||
35 => Some("KeyH"),
|
||||
36 => Some("KeyJ"),
|
||||
37 => Some("KeyK"),
|
||||
38 => Some("KeyL"),
|
||||
44 => Some("KeyZ"),
|
||||
45 => Some("KeyX"),
|
||||
46 => Some("KeyC"),
|
||||
47 => Some("KeyV"),
|
||||
48 => Some("KeyB"),
|
||||
49 => Some("KeyN"),
|
||||
50 => Some("KeyM"),
|
||||
|
||||
// Symbolic
|
||||
41 => Some("BackQuote"),
|
||||
12 => Some("Minus"),
|
||||
13 => Some("Equal"),
|
||||
26 => Some("LeftBracket"),
|
||||
27 => Some("RightBracket"),
|
||||
39 => Some("SemiColon"),
|
||||
40 => Some("Quote"),
|
||||
43 => Some("BackSlash"),
|
||||
86 => Some("IntlBackslash"),
|
||||
89 => Some("IntlRo"),
|
||||
124 => Some("IntlYen"),
|
||||
101 => Some("KanaMode"),
|
||||
51 => Some("Comma"),
|
||||
52 => Some("Dot"),
|
||||
53 => Some("Slash"),
|
||||
|
||||
// Control key
|
||||
29 => Some("ControlLeft"),
|
||||
97 => Some("ControlRight"),
|
||||
42 => Some("ShiftLeft"),
|
||||
54 => Some("ShiftRight"),
|
||||
56 => Some("Alt"),
|
||||
100 => Some("AltGr"),
|
||||
125 => Some("MetaLeft"),
|
||||
126 => Some("MetaRight"),
|
||||
127 => Some("Apps"),
|
||||
|
||||
// NumPad
|
||||
55 => Some("KpMultiply"),
|
||||
78 => Some("KpMinus"),
|
||||
74 => Some("KpPlus"),
|
||||
98 => Some("KpDivide"),
|
||||
117 => Some("KpEqual"),
|
||||
121 => Some("KpComma"),
|
||||
96 => Some("KpReturn"),
|
||||
83 => Some("KpDecimal"),
|
||||
79 => Some("Kp1"),
|
||||
80 => Some("Kp2"),
|
||||
81 => Some("Kp3"),
|
||||
75 => Some("Kp4"),
|
||||
76 => Some("Kp5"),
|
||||
77 => Some("Kp6"),
|
||||
71 => Some("Kp7"),
|
||||
72 => Some("Kp8"),
|
||||
73 => Some("Kp9"),
|
||||
82 => Some("Kp0"),
|
||||
|
||||
// Media key
|
||||
115 => Some("VolumeUp"),
|
||||
114 => Some("VolumeDown"),
|
||||
113 => Some("VolumeMute"),
|
||||
|
||||
// Language key
|
||||
90 => Some("Lang1"),
|
||||
91 => Some("Lang2"),
|
||||
92 => Some("Lang3"),
|
||||
93 => Some("Lang4"),
|
||||
94 => Some("Lang5"),
|
||||
|
||||
// Other
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#![allow(deprecated)]
|
||||
use tauri::{AppHandle, Emitter, EventTarget, WebviewWindow};
|
||||
use tauri_nspanel::{
|
||||
WebviewWindowExt,
|
||||
cocoa::appkit::{NSMainMenuWindowLevel, NSWindowCollectionBehavior},
|
||||
panel_delegate,
|
||||
};
|
||||
use tauri_nspanel::{WebviewWindowExt, cocoa::appkit::NSWindowCollectionBehavior, panel_delegate};
|
||||
use tauri_plugin_custom_window::MAIN_WINDOW_LABEL;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
@@ -26,8 +23,6 @@ pub fn platform(
|
||||
|
||||
let panel = main_window.to_panel().unwrap();
|
||||
|
||||
panel.set_level(NSMainMenuWindowLevel);
|
||||
|
||||
panel.set_style_mask(NSWindowStyleMaskNonActivatingPanel | NSResizableWindowMask);
|
||||
|
||||
panel.set_collection_behaviour(
|
||||
|
||||
@@ -23,7 +23,7 @@ pub fn default(
|
||||
main_window: WebviewWindow,
|
||||
preference_window: WebviewWindow,
|
||||
) {
|
||||
#[cfg(any(dev, debug_assertions))]
|
||||
#[cfg(debug_assertions)]
|
||||
main_window.open_devtools();
|
||||
|
||||
platform(app_handle, main_window.clone(), preference_window.clone());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod core;
|
||||
mod utils;
|
||||
|
||||
use core::{device, prevent_default, setup};
|
||||
use core::{device::start_device_listening, prevent_default, setup};
|
||||
use tauri::{Manager, WindowEvent, generate_handler};
|
||||
use tauri_plugin_autostart::MacosLauncher;
|
||||
use tauri_plugin_custom_window::{
|
||||
@@ -21,11 +21,9 @@ pub fn run() {
|
||||
|
||||
setup::default(&app_handle, main_window.clone(), preference_window.clone());
|
||||
|
||||
device::start_listening(app_handle.clone());
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(generate_handler![copy_dir])
|
||||
.invoke_handler(generate_handler![copy_dir, start_device_listening])
|
||||
.plugin(tauri_plugin_custom_window::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
@@ -47,6 +45,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||
.on_window_event(|window, event| match event {
|
||||
WindowEvent::CloseRequested { api, .. } => {
|
||||
let _ = window.hide();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(deprecated)]
|
||||
use super::{is_main_window, shared_hide_window, shared_set_always_on_top, shared_show_window};
|
||||
use crate::MAIN_WINDOW_LABEL;
|
||||
use tauri::{AppHandle, Runtime, WebviewWindow, command};
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
"url": "index.html/#/preference",
|
||||
"visible": false,
|
||||
"titleBarStyle": "Overlay",
|
||||
"hiddenTitle": true
|
||||
"hiddenTitle": true,
|
||||
"minWidth": 800,
|
||||
"minHeight": 600
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
@@ -61,8 +63,10 @@
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"dangerousInsecureTransportProtocol": true,
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEVBRjJFMzE3MjEwMUZEMTAKUldRUS9RRWhGK1B5NmdkemhKcUFrVjZBQXlzdExpakdWVEJDeU9XckVsbzV2cFIycVJOempWa2UK",
|
||||
"endpoints": [
|
||||
"http://api.upgrade.toolsetlink.com/v1/tauri/upgrade?tauriKey=KtGlsZUVXmWfjkRKCuqpfw&versionName={{current_version}}&target={{target}}&arch={{arch}}&appointVersionName=&devModelKey=&devKey=",
|
||||
"https://gh-proxy.com/github.com/ayangweb/BongoCat/releases/latest/download/latest.json"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -19,12 +19,14 @@ import { useAppStore } from './stores/app'
|
||||
import { useCatStore } from './stores/cat'
|
||||
import { useGeneralStore } from './stores/general'
|
||||
import { useModelStore } from './stores/model'
|
||||
import { useShortcutStore } from './stores/shortcut.ts'
|
||||
|
||||
const { generateColorVars } = useThemeVars()
|
||||
const appStore = useAppStore()
|
||||
const modelStore = useModelStore()
|
||||
const catStore = useCatStore()
|
||||
const generalStore = useGeneralStore()
|
||||
const shortcutStore = useShortcutStore()
|
||||
const appWindow = getCurrentWebviewWindow()
|
||||
const { isRestored, restoreState } = useWindowState()
|
||||
|
||||
@@ -33,10 +35,12 @@ onMounted(async () => {
|
||||
|
||||
await appStore.$tauri.start()
|
||||
await modelStore.$tauri.start()
|
||||
await modelStore.init()
|
||||
await catStore.$tauri.start()
|
||||
await generalStore.$tauri.start()
|
||||
|
||||
restoreState()
|
||||
await shortcutStore.$tauri.start()
|
||||
await restoreState()
|
||||
catStore.init()
|
||||
})
|
||||
|
||||
useTauriListen(LISTEN_KEY.SHOW_WINDOW, ({ payload }) => {
|
||||
|
||||
@@ -2,18 +2,14 @@
|
||||
import { Flex } from 'ant-design-vue'
|
||||
import { computed, useSlots } from 'vue'
|
||||
|
||||
const { title, icon, description, vertical } = defineProps<{
|
||||
const { title, description, vertical } = defineProps<{
|
||||
title: string
|
||||
icon?: string
|
||||
description?: string
|
||||
vertical?: boolean
|
||||
}>()
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const hasIcon = computed(() => {
|
||||
return icon || slots.icon
|
||||
})
|
||||
const hasDescription = computed(() => {
|
||||
return description || slots.description
|
||||
})
|
||||
@@ -28,23 +24,13 @@ const hasDescription = computed(() => {
|
||||
:vertical="vertical"
|
||||
>
|
||||
<Flex align="center">
|
||||
<slot name="icon">
|
||||
<div
|
||||
class="text-4"
|
||||
:class="icon"
|
||||
/>
|
||||
</slot>
|
||||
|
||||
<Flex
|
||||
:class="{ 'ml-4': hasIcon }"
|
||||
vertical
|
||||
>
|
||||
<Flex vertical>
|
||||
<div class="text-sm font-medium">
|
||||
{{ title }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="text-xs [&_a]:(active:text-color-primary-7 hover:text-color-primary-5 text-color-3) text-color-3"
|
||||
class="break-all text-xs [&_a]:(active:text-color-primary-7 hover:text-color-primary-5 text-color-3) text-color-3"
|
||||
:class="{ 'mt-2': hasDescription }"
|
||||
>
|
||||
<slot name="description">
|
||||
|
||||
127
src/components/pro-shortcut/index.vue
Normal file
127
src/components/pro-shortcut/index.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<script setup lang="ts">
|
||||
import type { Key } from '@/utils/keyboard'
|
||||
|
||||
import { find, map, remove, some, split } from 'es-toolkit/compat'
|
||||
import { ref, useTemplateRef, watch } from 'vue'
|
||||
|
||||
import ProListItem from '@/components/pro-list-item/index.vue'
|
||||
import { keys, modifierKeys, standardKeys } from '@/utils/keyboard'
|
||||
|
||||
const props = defineProps<{
|
||||
title: string
|
||||
description?: string
|
||||
}>()
|
||||
|
||||
const modelValue = defineModel<string>()
|
||||
const shortcutInputRef = useTemplateRef('shortcutInput')
|
||||
const isFocusing = ref(false)
|
||||
const isHovering = ref(false)
|
||||
const pressedKeys = ref<Key[]>([])
|
||||
|
||||
watch(modelValue, () => {
|
||||
parseModelValue()
|
||||
}, { immediate: true })
|
||||
|
||||
function parseModelValue() {
|
||||
if (!modelValue.value) {
|
||||
return pressedKeys.value = []
|
||||
}
|
||||
|
||||
pressedKeys.value = split(modelValue.value, '+').map((tauriKey) => {
|
||||
return find(keys, { tauriKey })!
|
||||
})
|
||||
}
|
||||
|
||||
function getEventKey(event: KeyboardEvent) {
|
||||
const { key, code } = event
|
||||
|
||||
const eventKey = key.replace('Meta', 'Command')
|
||||
|
||||
const isModifierKey = some(modifierKeys, { eventKey })
|
||||
|
||||
return isModifierKey ? eventKey : code
|
||||
}
|
||||
|
||||
function isValidShortcut() {
|
||||
if (pressedKeys.value?.[0]?.eventKey?.startsWith('F')) {
|
||||
return true
|
||||
}
|
||||
|
||||
const hasModifierKey = some(pressedKeys.value, ({ eventKey }) => {
|
||||
return some(modifierKeys, { eventKey })
|
||||
})
|
||||
const hasStandardKey = some(pressedKeys.value, ({ eventKey }) => {
|
||||
return some(standardKeys, { eventKey })
|
||||
})
|
||||
|
||||
return hasModifierKey && hasStandardKey
|
||||
}
|
||||
|
||||
function handleFocus() {
|
||||
isFocusing.value = true
|
||||
|
||||
pressedKeys.value = []
|
||||
}
|
||||
|
||||
function handleBlur() {
|
||||
isFocusing.value = false
|
||||
|
||||
if (!isValidShortcut()) {
|
||||
return parseModelValue()
|
||||
}
|
||||
|
||||
modelValue.value = map(pressedKeys.value, 'tauriKey').join('+')
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
const eventKey = getEventKey(event)
|
||||
|
||||
const matched = find(keys, { eventKey })
|
||||
const isInvalid = !matched
|
||||
const isDuplicate = some(pressedKeys.value, { eventKey })
|
||||
|
||||
if (isInvalid || isDuplicate) return
|
||||
|
||||
pressedKeys.value.push(matched)
|
||||
|
||||
if (isValidShortcut()) {
|
||||
shortcutInputRef.value?.blur()
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
remove(pressedKeys.value, { eventKey: getEventKey(event) })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProListItem v-bind="props">
|
||||
<div
|
||||
ref="shortcutInput"
|
||||
align="center"
|
||||
class="relative h-8 min-w-32 flex cursor-text items-center justify-center b b-color-1 hover:b-primary-5 rounded-md b-solid px-2.5 text-color-3 outline-none transition focus:(b-primary shadow-[0_0_0_2px_rgba(5,145,255,0.1)])"
|
||||
justify="center"
|
||||
:tabindex="0"
|
||||
@blur="handleBlur"
|
||||
@focus="handleFocus"
|
||||
@keydown="handleKeyDown"
|
||||
@keyup="handleKeyUp"
|
||||
@mouseout="isHovering = false"
|
||||
@mouseover="isHovering = true"
|
||||
>
|
||||
<span v-if="pressedKeys.length === 0">
|
||||
{{ isFocusing ? '按下录制快捷键' : '点击录制快捷键' }}
|
||||
</span>
|
||||
|
||||
<span class="text-primary font-bold">
|
||||
{{ map(pressedKeys, 'symbol').join(' ') }}
|
||||
</span>
|
||||
|
||||
<div
|
||||
class="i-iconamoon:close-circle-1 absolute right-2 cursor-pointer text-4 transition hover:text-primary"
|
||||
:hidden="isFocusing || !isHovering || pressedKeys.length === 0"
|
||||
@mousedown.prevent="modelValue = ''"
|
||||
/>
|
||||
</div>
|
||||
</ProListItem>
|
||||
</template>
|
||||
@@ -11,7 +11,7 @@ import { computed, reactive, watch } from 'vue'
|
||||
import VueMarkdown from 'vue-markdown-render'
|
||||
|
||||
import { useTauriListen } from '@/composables/useTauriListen'
|
||||
import { GITHUB_LINK, LISTEN_KEY } from '@/constants'
|
||||
import { GITHUB_LINK, LISTEN_KEY, UPGRADE_LINK_ACCESS_KEY } from '@/constants'
|
||||
import { showWindow } from '@/plugins/window'
|
||||
import { useGeneralStore } from '@/stores/general'
|
||||
|
||||
@@ -67,7 +67,12 @@ const downloadProgress = computed(() => {
|
||||
|
||||
async function checkUpdate(visibleMessage = false) {
|
||||
try {
|
||||
const update = await check()
|
||||
const update = await check({
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
'X-AccessKey': UPGRADE_LINK_ACCESS_KEY,
|
||||
},
|
||||
})
|
||||
|
||||
if (update) {
|
||||
const { version, currentVersion, body = '', date, downloadAndInstall } = update
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { LogicalSize, PhysicalSize } from '@tauri-apps/api/dpi'
|
||||
import { resolveResource } from '@tauri-apps/api/path'
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { isNil, round } from 'es-toolkit'
|
||||
import { computed, watch } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import live2d from '../utils/live2d'
|
||||
import { getCursorMonitor } from '../utils/monitor'
|
||||
|
||||
import { useCatStore } from '@/stores/cat'
|
||||
import { useModelStore } from '@/stores/model'
|
||||
import { getImageSize } from '@/utils/dom'
|
||||
import { join } from '@/utils/path'
|
||||
|
||||
const appWindow = getCurrentWebviewWindow()
|
||||
|
||||
interface ModelSize {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export function useModel() {
|
||||
const modelStore = useModelStore()
|
||||
const catStore = useCatStore()
|
||||
|
||||
const backgroundImage = computed(() => {
|
||||
return convertFileSrc(join(modelStore.currentModel!.path, 'resources', 'background.png'))
|
||||
})
|
||||
const modelSize = ref<ModelSize>()
|
||||
|
||||
watch(() => modelStore.currentModel, handleLoad, { deep: true, immediate: true })
|
||||
|
||||
watch(() => catStore.scale, async () => {
|
||||
const { width, height } = await getImageSize(backgroundImage.value)
|
||||
watch([() => catStore.scale, modelSize], async () => {
|
||||
if (!modelSize.value) return
|
||||
|
||||
const { width, height } = modelSize.value
|
||||
|
||||
appWindow.setSize(
|
||||
new PhysicalSize({
|
||||
@@ -39,15 +40,19 @@ export function useModel() {
|
||||
|
||||
async function handleLoad() {
|
||||
try {
|
||||
const { path } = modelStore.currentModel!
|
||||
if (!modelStore.currentModel) return
|
||||
|
||||
const { path } = modelStore.currentModel
|
||||
|
||||
await resolveResource(path)
|
||||
|
||||
const data = await live2d.load(path)
|
||||
const { width, height, ...rest } = await live2d.load(path)
|
||||
|
||||
modelSize.value = { width, height }
|
||||
|
||||
handleResize()
|
||||
|
||||
Object.assign(modelStore, data)
|
||||
Object.assign(modelStore, rest)
|
||||
} catch (error) {
|
||||
message.error(String(error))
|
||||
}
|
||||
@@ -58,13 +63,11 @@ export function useModel() {
|
||||
}
|
||||
|
||||
async function handleResize() {
|
||||
if (!live2d.model) return
|
||||
if (!modelSize.value) return
|
||||
|
||||
const { innerWidth, innerHeight } = window
|
||||
live2d.fitModel()
|
||||
|
||||
const { width, height } = await getImageSize(backgroundImage.value)
|
||||
|
||||
live2d.model?.scale.set(innerWidth / width)
|
||||
const { width, height } = modelSize.value
|
||||
|
||||
if (round(innerWidth / innerHeight, 1) !== round(width / height, 1)) {
|
||||
await appWindow.setSize(
|
||||
@@ -130,7 +133,6 @@ export function useModel() {
|
||||
}
|
||||
|
||||
return {
|
||||
backgroundImage,
|
||||
handleLoad,
|
||||
handleDestroy,
|
||||
handleResize,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CheckMenuItem, MenuItem, PredefinedMenuItem, Submenu } from '@tauri-apps/api/menu'
|
||||
import { range } from 'es-toolkit'
|
||||
|
||||
import { hideWindow, showWindow } from '@/plugins/window'
|
||||
import { showWindow } from '@/plugins/window'
|
||||
import { useCatStore } from '@/stores/cat'
|
||||
import { isMac } from '@/utils/platform'
|
||||
|
||||
@@ -66,12 +66,6 @@ export function useSharedMenu() {
|
||||
MenuItem.new({
|
||||
text: catStore.visible ? '隐藏猫咪' : '显示猫咪',
|
||||
action: () => {
|
||||
if (catStore.visible) {
|
||||
hideWindow('main')
|
||||
} else {
|
||||
showWindow('main')
|
||||
}
|
||||
|
||||
catStore.visible = !catStore.visible
|
||||
},
|
||||
}),
|
||||
|
||||
33
src/composables/useTauriKeyPress.ts
Normal file
33
src/composables/useTauriKeyPress.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { ShortcutHandler } from '@tauri-apps/plugin-global-shortcut'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import {
|
||||
isRegistered,
|
||||
register,
|
||||
unregister,
|
||||
} from '@tauri-apps/plugin-global-shortcut'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
export function useTauriKeyPress(shortcut: Ref<string, string>, callback: ShortcutHandler) {
|
||||
const oldShortcut = ref(shortcut.value)
|
||||
|
||||
watch(shortcut, async (value) => {
|
||||
if (oldShortcut.value) {
|
||||
const registered = await isRegistered(oldShortcut.value)
|
||||
|
||||
if (registered) {
|
||||
await unregister(oldShortcut.value)
|
||||
}
|
||||
}
|
||||
|
||||
if (!value) return
|
||||
|
||||
await register(value, (event) => {
|
||||
if (event.state === 'Released') return
|
||||
|
||||
callback(event)
|
||||
})
|
||||
|
||||
oldShortcut.value = value
|
||||
}, { immediate: true })
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { TrayIcon } from '@tauri-apps/api/tray'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import { exit, relaunch } from '@tauri-apps/plugin-process'
|
||||
import { watchDebounced } from '@vueuse/core'
|
||||
import { watch } from 'vue'
|
||||
|
||||
import { GITHUB_LINK, LISTEN_KEY } from '../constants'
|
||||
import { showWindow } from '../plugins/window'
|
||||
@@ -23,9 +24,13 @@ export function useTray() {
|
||||
const catStore = useCatStore()
|
||||
const { getSharedMenu } = useSharedMenu()
|
||||
|
||||
watchDebounced(() => catStore, () => {
|
||||
watch([() => catStore.visible, () => catStore.penetrable], () => {
|
||||
updateTrayMenu()
|
||||
}, { deep: true, debounce: 500 })
|
||||
})
|
||||
|
||||
watchDebounced([() => catStore.scale, () => catStore.opacity], () => {
|
||||
updateTrayMenu()
|
||||
}, { debounce: 200 })
|
||||
|
||||
const createTray = async () => {
|
||||
const tray = await getTrayById()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export const GITHUB_LINK = 'https://github.com/ayangweb/BongoCat'
|
||||
|
||||
export const UPGRADE_LINK_ACCESS_KEY = 'xDbrq2rOoRThDqKOHL2ZRA'
|
||||
|
||||
export const LISTEN_KEY = {
|
||||
SHOW_WINDOW: 'show-window',
|
||||
HIDE_WINDOW: 'hide-window',
|
||||
@@ -9,4 +11,5 @@ export const LISTEN_KEY = {
|
||||
|
||||
export const INVOKE_KEY = {
|
||||
COPY_DIR: 'copy_dir',
|
||||
START_DEVICE_LISTENING: 'start_device_listening',
|
||||
}
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { convertFileSrc, invoke } from '@tauri-apps/api/core'
|
||||
import { Menu } from '@tauri-apps/api/menu'
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { exists } from '@tauri-apps/plugin-fs'
|
||||
import { useDebounceFn, useEventListener } from '@vueuse/core'
|
||||
import { onUnmounted, ref, watch } from 'vue'
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
import { useDevice } from '@/composables/useDevice'
|
||||
import { useModel } from '@/composables/useModel'
|
||||
import { useSharedMenu } from '@/composables/useSharedMenu'
|
||||
import { setAlwaysOnTop } from '@/plugins/window'
|
||||
import { INVOKE_KEY } from '@/constants'
|
||||
import { hideWindow, setAlwaysOnTop, showWindow } from '@/plugins/window'
|
||||
import { useCatStore } from '@/stores/cat'
|
||||
import { useModelStore } from '@/stores/model'
|
||||
import { join } from '@/utils/path'
|
||||
|
||||
const appWindow = getCurrentWebviewWindow()
|
||||
const { pressedMouses, mousePosition, pressedLeftKeys, pressedRightKeys } = useDevice()
|
||||
const { backgroundImage, handleDestroy, handleResize, handleMouseDown, handleMouseMove, handleKeyDown } = useModel()
|
||||
const { handleDestroy, handleResize, handleMouseDown, handleMouseMove, handleKeyDown } = useModel()
|
||||
const catStore = useCatStore()
|
||||
const { getSharedMenu } = useSharedMenu()
|
||||
const modelStore = useModelStore()
|
||||
|
||||
const resizing = ref(false)
|
||||
const backgroundImagePath = ref<string>()
|
||||
|
||||
onMounted(() => {
|
||||
invoke(INVOKE_KEY.START_DEVICE_LISTENING)
|
||||
})
|
||||
|
||||
onUnmounted(handleDestroy)
|
||||
|
||||
const handleDebounceResize = useDebounceFn(async () => {
|
||||
const debouncedResize = useDebounceFn(async () => {
|
||||
await handleResize()
|
||||
|
||||
resizing.value = false
|
||||
@@ -33,7 +39,7 @@ const handleDebounceResize = useDebounceFn(async () => {
|
||||
useEventListener('resize', () => {
|
||||
resizing.value = true
|
||||
|
||||
handleDebounceResize()
|
||||
debouncedResize()
|
||||
})
|
||||
|
||||
watch(pressedMouses, handleMouseDown)
|
||||
@@ -48,12 +54,26 @@ watch(pressedRightKeys, (keys) => {
|
||||
handleKeyDown('right', keys.length > 0)
|
||||
})
|
||||
|
||||
watch(() => catStore.visible, async (value) => {
|
||||
value ? showWindow() : hideWindow()
|
||||
})
|
||||
|
||||
watch(() => catStore.penetrable, (value) => {
|
||||
appWindow.setIgnoreCursorEvents(value)
|
||||
}, { immediate: true })
|
||||
|
||||
watch(() => catStore.alwaysOnTop, setAlwaysOnTop, { immediate: true })
|
||||
|
||||
watch(() => modelStore.currentModel, async (model) => {
|
||||
if (!model) return
|
||||
|
||||
const path = join(model.path, 'resources', 'background.png')
|
||||
|
||||
const existed = await exists(path)
|
||||
|
||||
backgroundImagePath.value = existed ? convertFileSrc(path) : void 0
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
function handleWindowDrag() {
|
||||
appWindow.startDragging()
|
||||
}
|
||||
@@ -68,7 +88,7 @@ async function handleContextmenu(event: MouseEvent) {
|
||||
menu.popup()
|
||||
}
|
||||
|
||||
function resolveImagePath(key: string, side: 'left' | 'right' = 'left') {
|
||||
function resolveKeyImagePath(key: string, side: 'left' | 'right' = 'left') {
|
||||
return convertFileSrc(join(modelStore.currentModel!.path, 'resources', `${side}-keys`, `${key}.png`))
|
||||
}
|
||||
</script>
|
||||
@@ -76,25 +96,28 @@ function resolveImagePath(key: string, side: 'left' | 'right' = 'left') {
|
||||
<template>
|
||||
<div
|
||||
class="relative size-screen overflow-hidden children:(absolute size-full)"
|
||||
:class="[catStore.mirrorMode ? '-scale-x-100' : 'scale-x-100']"
|
||||
:class="{ '-scale-x-100': catStore.mirrorMode }"
|
||||
:style="{ opacity: catStore.opacity / 100 }"
|
||||
@contextmenu="handleContextmenu"
|
||||
@mousedown="handleWindowDrag"
|
||||
>
|
||||
<img :src="backgroundImage">
|
||||
<img
|
||||
v-if="backgroundImagePath"
|
||||
:src="backgroundImagePath"
|
||||
>
|
||||
|
||||
<canvas id="live2dCanvas" />
|
||||
|
||||
<img
|
||||
v-for="key in pressedLeftKeys"
|
||||
:key="key"
|
||||
:src="resolveImagePath(key)"
|
||||
:src="resolveKeyImagePath(key)"
|
||||
>
|
||||
|
||||
<img
|
||||
v-for="key in pressedRightKeys"
|
||||
:key="key"
|
||||
:src="resolveImagePath(key, 'right')"
|
||||
:src="resolveKeyImagePath(key, 'right')"
|
||||
>
|
||||
|
||||
<div
|
||||
|
||||
@@ -40,7 +40,7 @@ async function copyInfo() {
|
||||
}
|
||||
|
||||
function feedbackIssue() {
|
||||
openUrl(`${GITHUB_LINK}/issues/new`)
|
||||
openUrl(`${GITHUB_LINK}/issues/new/choose`)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Slider, Switch } from 'ant-design-vue'
|
||||
import { InputNumber, Slider, Switch } from 'ant-design-vue'
|
||||
|
||||
import ProList from '@/components/pro-list/index.vue'
|
||||
import ProListItem from '@/components/pro-list-item/index.vue'
|
||||
@@ -7,10 +7,6 @@ import { useCatStore } from '@/stores/cat'
|
||||
|
||||
const catStore = useCatStore()
|
||||
|
||||
function scaleFormatter(value?: number) {
|
||||
return value === 100 ? '默认' : `${value}%`
|
||||
}
|
||||
|
||||
function opacityFormatter(value?: number) {
|
||||
return `${value}%`
|
||||
}
|
||||
@@ -58,15 +54,16 @@ function opacityFormatter(value?: number) {
|
||||
<ProListItem
|
||||
description="将鼠标移动到窗口边缘后,也可以拖动调整窗口尺寸"
|
||||
title="窗口尺寸"
|
||||
vertical
|
||||
>
|
||||
<Slider
|
||||
<InputNumber
|
||||
v-model:value="catStore.scale"
|
||||
class="m-0!"
|
||||
:max="150"
|
||||
:min="50"
|
||||
:tip-formatter="scaleFormatter"
|
||||
/>
|
||||
class="w-28"
|
||||
:min="1"
|
||||
>
|
||||
<template #addonAfter>
|
||||
%
|
||||
</template>
|
||||
</InputNumber>
|
||||
</ProListItem>
|
||||
|
||||
<ProListItem
|
||||
|
||||
@@ -86,7 +86,7 @@ watch(selectPaths, async (paths) => {
|
||||
<template>
|
||||
<div
|
||||
ref="drop"
|
||||
class="size-full flex flex-col cursor-pointer items-center justify-center gap-4 b b-color-1 rounded-lg b-dashed bg-color-8 transition hover:border-primary"
|
||||
class="w-full flex flex-col cursor-pointer items-center justify-center gap-4 b b-color-1 rounded-lg b-dashed bg-color-8 transition hover:border-primary"
|
||||
:class="{ 'border-primary': dragenter }"
|
||||
@click="handleUpload"
|
||||
>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import type { Model } from '@/stores/model'
|
||||
import type { ColProps } from 'ant-design-vue'
|
||||
import type { ComponentPublicInstance } from 'vue'
|
||||
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { remove } from '@tauri-apps/plugin-fs'
|
||||
import { revealItemInDir } from '@tauri-apps/plugin-opener'
|
||||
import { Card, Col, message, Popconfirm, Row } from 'ant-design-vue'
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
import { Card, message, Popconfirm } from 'ant-design-vue'
|
||||
import { ref } from 'vue'
|
||||
import { MasonryGrid, MasonryGridItem } from 'vue3-masonry-css'
|
||||
|
||||
import FloatMenu from './components/float-menu/index.vue'
|
||||
import Upload from './components/upload/index.vue'
|
||||
@@ -14,12 +17,20 @@ import { useModelStore } from '@/stores/model'
|
||||
import { join } from '@/utils/path'
|
||||
|
||||
const modelStore = useModelStore()
|
||||
const firstItemRef = ref<HTMLElement>()
|
||||
|
||||
const colProps: ColProps = {
|
||||
xs: 12,
|
||||
md: 8,
|
||||
lg: 6,
|
||||
xl: 4,
|
||||
const { height } = useElementSize(firstItemRef)
|
||||
|
||||
function setFirstItemRef(el: Element | ComponentPublicInstance | null, index: number) {
|
||||
if (!el || index > 0) return
|
||||
|
||||
if ('$el' in el) {
|
||||
return firstItemRef.value = el.$el
|
||||
}
|
||||
|
||||
if (el instanceof HTMLElement) {
|
||||
firstItemRef.value = el
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(item: Model) {
|
||||
@@ -42,19 +53,23 @@ async function handleDelete(item: Model) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Row :gutter="[16, 16]">
|
||||
<Col v-bind="colProps">
|
||||
<Upload />
|
||||
</Col>
|
||||
<MasonryGrid
|
||||
:columns="{ 992: 3, 1200: 4, 1600: 6, default: 8 }"
|
||||
:gutter="16"
|
||||
>
|
||||
<MasonryGridItem>
|
||||
<Upload :style="{ height: `${height}px` }" />
|
||||
</MasonryGridItem>
|
||||
|
||||
<Col
|
||||
v-for="item in modelStore.models"
|
||||
<MasonryGridItem
|
||||
v-for="(item, index) in modelStore.models"
|
||||
:key="item.id"
|
||||
v-bind="colProps"
|
||||
>
|
||||
<Card
|
||||
:ref="(el) => setFirstItemRef(el, index)"
|
||||
hoverable
|
||||
size="small"
|
||||
@click="modelStore.currentModel = item"
|
||||
>
|
||||
<template #cover>
|
||||
<img
|
||||
@@ -67,12 +82,11 @@ async function handleDelete(item: Model) {
|
||||
<i
|
||||
class="i-iconamoon:check-circle-1-bold text-4"
|
||||
:class="{ 'text-success': item.id === modelStore.currentModel?.id }"
|
||||
@click="modelStore.currentModel = item"
|
||||
/>
|
||||
|
||||
<i
|
||||
class="i-iconamoon:link-external-bold text-4"
|
||||
@click="revealItemInDir(item.path)"
|
||||
@click.stop="revealItemInDir(item.path)"
|
||||
/>
|
||||
|
||||
<template v-if="!item.isPreset">
|
||||
@@ -82,13 +96,16 @@ async function handleDelete(item: Model) {
|
||||
title="删除模型"
|
||||
@confirm="handleDelete(item)"
|
||||
>
|
||||
<i class="i-iconamoon:trash-simple-bold text-4" />
|
||||
<i
|
||||
class="i-iconamoon:trash-simple-bold text-4"
|
||||
@click.stop
|
||||
/>
|
||||
</Popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</MasonryGridItem>
|
||||
</MasonryGrid>
|
||||
|
||||
<FloatMenu />
|
||||
</template>
|
||||
|
||||
68
src/pages/preference/components/shortcut/index.vue
Normal file
68
src/pages/preference/components/shortcut/index.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import ProList from '@/components/pro-list/index.vue'
|
||||
import ProShortcut from '@/components/pro-shortcut/index.vue'
|
||||
import { useTauriKeyPress } from '@/composables/useTauriKeyPress'
|
||||
import { toggleWindowVisible } from '@/plugins/window'
|
||||
import { useCatStore } from '@/stores/cat'
|
||||
import { useShortcutStore } from '@/stores/shortcut.ts'
|
||||
|
||||
const shortcutStore = useShortcutStore()
|
||||
const { visibleCat, visiblePreference, mirrorMode, penetrable, alwaysOnTop } = storeToRefs(shortcutStore)
|
||||
const catStore = useCatStore()
|
||||
|
||||
useTauriKeyPress(visibleCat, () => {
|
||||
catStore.visible = !catStore.visible
|
||||
})
|
||||
|
||||
useTauriKeyPress(visiblePreference, () => {
|
||||
toggleWindowVisible('preference')
|
||||
})
|
||||
|
||||
useTauriKeyPress(mirrorMode, () => {
|
||||
catStore.mirrorMode = !catStore.mirrorMode
|
||||
})
|
||||
|
||||
useTauriKeyPress(penetrable, () => {
|
||||
catStore.penetrable = !catStore.penetrable
|
||||
})
|
||||
|
||||
useTauriKeyPress(alwaysOnTop, () => {
|
||||
catStore.alwaysOnTop = !catStore.alwaysOnTop
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProList title="快捷键">
|
||||
<ProShortcut
|
||||
v-model="shortcutStore.visibleCat"
|
||||
description="切换猫咪窗口的显示与隐藏"
|
||||
title="打开猫咪"
|
||||
/>
|
||||
|
||||
<ProShortcut
|
||||
v-model="shortcutStore.visiblePreference"
|
||||
description="切换偏好设置窗口的显示与隐藏"
|
||||
title="打开偏好设置"
|
||||
/>
|
||||
|
||||
<ProShortcut
|
||||
v-model="shortcutStore.mirrorMode"
|
||||
description="切换猫咪的镜像模式"
|
||||
title="镜像模式"
|
||||
/>
|
||||
|
||||
<ProShortcut
|
||||
v-model="shortcutStore.penetrable"
|
||||
description="切换猫咪窗口是否可穿透"
|
||||
title="窗口穿透"
|
||||
/>
|
||||
|
||||
<ProShortcut
|
||||
v-model="shortcutStore.alwaysOnTop"
|
||||
description="切换猫咪窗口是否置顶"
|
||||
title="窗口置顶"
|
||||
/>
|
||||
</ProList>
|
||||
</template>
|
||||
@@ -6,6 +6,7 @@ import About from './components/about/index.vue'
|
||||
import Cat from './components/cat/index.vue'
|
||||
import General from './components/general/index.vue'
|
||||
import Model from './components/model/index.vue'
|
||||
import Shortcut from './components/shortcut/index.vue'
|
||||
|
||||
import UpdateApp from '@/components/update-app/index.vue'
|
||||
import { useTray } from '@/composables/useTray'
|
||||
@@ -36,6 +37,11 @@ const menus = [
|
||||
icon: 'i-solar:magic-stick-3-bold',
|
||||
component: Model,
|
||||
},
|
||||
{
|
||||
label: '快捷键',
|
||||
icon: 'i-solar:keyboard-bold',
|
||||
component: Shortcut,
|
||||
},
|
||||
{
|
||||
label: '关于',
|
||||
icon: 'i-solar:info-circle-bold',
|
||||
@@ -47,7 +53,7 @@ const menus = [
|
||||
<template>
|
||||
<Flex class="h-screen">
|
||||
<div
|
||||
class="h-full w-30 flex flex-col items-center gap-4 bg-gradient-from-primary-1 bg-gradient-to-black/1 bg-gradient-linear"
|
||||
class="h-full w-30 flex flex-col items-center gap-4 overflow-auto bg-gradient-from-primary-1 bg-gradient-to-black/1 bg-gradient-linear"
|
||||
:class="[isMac ? 'pt-8' : 'pt-4']"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
@@ -55,6 +61,7 @@ const menus = [
|
||||
<div class="b b-color-2 rounded-2xl b-solid">
|
||||
<img
|
||||
class="size-15"
|
||||
data-tauri-drag-region
|
||||
src="/logo.png"
|
||||
>
|
||||
</div>
|
||||
@@ -68,7 +75,7 @@ const menus = [
|
||||
:key="item.label"
|
||||
class="size-20 flex flex-col cursor-pointer items-center justify-center gap-2 rounded-lg hover:bg-color-7 text-color-3 transition"
|
||||
:class="{ 'bg-white! text-primary-5 font-bold': current === index }"
|
||||
@mousedown="current = index"
|
||||
@click="current = index"
|
||||
>
|
||||
<div
|
||||
class="size-8"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { emit } from '@tauri-apps/api/event'
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
|
||||
import { LISTEN_KEY } from '../constants'
|
||||
|
||||
@@ -30,3 +31,17 @@ export function hideWindow(label?: WindowLabel) {
|
||||
export function setAlwaysOnTop(alwaysOnTop: boolean) {
|
||||
invoke(COMMAND.SET_ALWAYS_ON_TOP, { alwaysOnTop })
|
||||
}
|
||||
|
||||
export async function toggleWindowVisible(label?: WindowLabel) {
|
||||
const appWindow = getCurrentWebviewWindow()
|
||||
|
||||
if (appWindow.label !== label) return
|
||||
|
||||
const visible = await appWindow.isVisible()
|
||||
|
||||
if (visible) {
|
||||
return hideWindow(label)
|
||||
}
|
||||
|
||||
return showWindow(label)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useCatStore = defineStore('cat', () => {
|
||||
const visible = ref(true)
|
||||
const visible = ref(false)
|
||||
const mirrorMode = ref(false)
|
||||
const singleMode = ref(false)
|
||||
const mouseMirror = ref(false)
|
||||
@@ -11,6 +11,10 @@ export const useCatStore = defineStore('cat', () => {
|
||||
const scale = ref(100)
|
||||
const opacity = ref(100)
|
||||
|
||||
const init = () => {
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
return {
|
||||
visible,
|
||||
mirrorMode,
|
||||
@@ -20,5 +24,6 @@ export const useCatStore = defineStore('cat', () => {
|
||||
alwaysOnTop,
|
||||
scale,
|
||||
opacity,
|
||||
init,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { resolveResource } from '@tauri-apps/api/path'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { defineStore } from 'pinia'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { join } from '@/utils/path'
|
||||
|
||||
@@ -37,33 +37,47 @@ export const useModelStore = defineStore('model', () => {
|
||||
const motions = ref<MotionGroup>({})
|
||||
const expressions = ref<Expression[]>([])
|
||||
|
||||
onMounted(async () => {
|
||||
const modelsPath = await resolveResource('assets/models')
|
||||
const init = async () => {
|
||||
const presetModelsPath = await resolveResource('assets/models')
|
||||
|
||||
if (models.value.length === 0) {
|
||||
const modes: ModelMode[] = ['standard', 'keyboard']
|
||||
|
||||
for await (const mode of modes) {
|
||||
const path = join(modelsPath, mode)
|
||||
|
||||
for (const mode of modes) {
|
||||
models.value.push({
|
||||
id: nanoid(),
|
||||
path,
|
||||
mode,
|
||||
id: nanoid(),
|
||||
isPreset: true,
|
||||
path: join(presetModelsPath, mode),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
models.value = models.value.map((item) => {
|
||||
const { isPreset, mode } = item
|
||||
|
||||
if (!isPreset) return item
|
||||
|
||||
return {
|
||||
...item,
|
||||
path: join(presetModelsPath, mode),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (currentModel.value) return
|
||||
const matched = models.value.find(item => item.id === currentModel.value?.id)
|
||||
|
||||
if (matched) {
|
||||
return currentModel.value = matched
|
||||
}
|
||||
|
||||
currentModel.value = models.value[0]
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
models,
|
||||
currentModel,
|
||||
motions,
|
||||
expressions,
|
||||
init,
|
||||
}
|
||||
})
|
||||
|
||||
20
src/stores/shortcut.ts
Normal file
20
src/stores/shortcut.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export type HotKey = 'visibleCat' | 'mirrorMode' | 'penetrable' | 'alwaysOnTop'
|
||||
|
||||
export const useShortcutStore = defineStore('shortcut', () => {
|
||||
const visibleCat = ref('')
|
||||
const visiblePreference = ref('')
|
||||
const mirrorMode = ref('')
|
||||
const penetrable = ref('')
|
||||
const alwaysOnTop = ref('')
|
||||
|
||||
return {
|
||||
visibleCat,
|
||||
visiblePreference,
|
||||
mirrorMode,
|
||||
penetrable,
|
||||
alwaysOnTop,
|
||||
}
|
||||
})
|
||||
@@ -1,15 +0,0 @@
|
||||
export function getImageSize(src: string) {
|
||||
return new Promise<{ width: number, height: number }>((resolve, reject) => {
|
||||
const img = new Image()
|
||||
|
||||
img.src = src
|
||||
|
||||
img.onload = () => {
|
||||
const { naturalWidth, naturalHeight } = img
|
||||
|
||||
resolve({ width: naturalWidth, height: naturalHeight })
|
||||
}
|
||||
|
||||
img.onerror = reject
|
||||
})
|
||||
}
|
||||
276
src/utils/keyboard.ts
Normal file
276
src/utils/keyboard.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
import { isMac } from './platform'
|
||||
|
||||
export interface Key {
|
||||
eventKey: string
|
||||
tauriKey?: string
|
||||
symbol?: string
|
||||
}
|
||||
|
||||
export const modifierKeys: Key[] = [
|
||||
{
|
||||
eventKey: 'Shift',
|
||||
symbol: isMac ? '⇧' : 'Shift',
|
||||
},
|
||||
{
|
||||
eventKey: 'Control',
|
||||
symbol: isMac ? '⌃' : 'Ctrl',
|
||||
},
|
||||
{
|
||||
eventKey: 'Alt',
|
||||
symbol: isMac ? '⌥' : 'Alt',
|
||||
},
|
||||
{
|
||||
eventKey: 'Command',
|
||||
symbol: isMac ? '⌘' : 'Super',
|
||||
},
|
||||
].map((item) => {
|
||||
return { ...item, tauriKey: item.eventKey }
|
||||
})
|
||||
|
||||
export const standardKeys: Key[] = [
|
||||
// 第一排
|
||||
{
|
||||
eventKey: 'Escape',
|
||||
symbol: isMac ? '⎋' : 'Esc',
|
||||
},
|
||||
{
|
||||
eventKey: 'F1',
|
||||
},
|
||||
{
|
||||
eventKey: 'F2',
|
||||
},
|
||||
{
|
||||
eventKey: 'F3',
|
||||
},
|
||||
{
|
||||
eventKey: 'F4',
|
||||
},
|
||||
{
|
||||
eventKey: 'F5',
|
||||
},
|
||||
{
|
||||
eventKey: 'F6',
|
||||
},
|
||||
{
|
||||
eventKey: 'F7',
|
||||
},
|
||||
{
|
||||
eventKey: 'F8',
|
||||
},
|
||||
{
|
||||
eventKey: 'F9',
|
||||
},
|
||||
{
|
||||
eventKey: 'F10',
|
||||
},
|
||||
{
|
||||
eventKey: 'F11',
|
||||
},
|
||||
{
|
||||
eventKey: 'F12',
|
||||
}, // 第二排
|
||||
{
|
||||
eventKey: 'Backquote',
|
||||
symbol: '`',
|
||||
},
|
||||
{
|
||||
eventKey: 'Digit1',
|
||||
},
|
||||
{
|
||||
eventKey: 'Digit2',
|
||||
},
|
||||
{
|
||||
eventKey: 'Digit3',
|
||||
},
|
||||
{
|
||||
eventKey: 'Digit4',
|
||||
},
|
||||
{
|
||||
eventKey: 'Digit5',
|
||||
},
|
||||
{
|
||||
eventKey: 'Digit6',
|
||||
},
|
||||
{
|
||||
eventKey: 'Digit7',
|
||||
},
|
||||
{
|
||||
eventKey: 'Digit8',
|
||||
},
|
||||
{
|
||||
eventKey: 'Digit9',
|
||||
},
|
||||
{
|
||||
eventKey: 'Digit0',
|
||||
},
|
||||
{
|
||||
eventKey: 'Minus',
|
||||
tauriKey: '-',
|
||||
symbol: '-',
|
||||
},
|
||||
{
|
||||
eventKey: 'Equal',
|
||||
tauriKey: '=',
|
||||
symbol: '=',
|
||||
},
|
||||
{
|
||||
eventKey: 'Backspace',
|
||||
symbol: isMac ? '⌫' : void 0,
|
||||
},
|
||||
// 第三排
|
||||
{
|
||||
eventKey: 'Tab',
|
||||
symbol: isMac ? '⇥' : void 0,
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyQ',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyW',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyE',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyR',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyT',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyY',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyU',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyI',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyO',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyP',
|
||||
},
|
||||
{
|
||||
eventKey: 'BracketLeft',
|
||||
symbol: '[',
|
||||
},
|
||||
{
|
||||
eventKey: 'BracketRight',
|
||||
symbol: ']',
|
||||
},
|
||||
{
|
||||
eventKey: 'Backslash',
|
||||
symbol: '\\',
|
||||
},
|
||||
// 第四排
|
||||
{
|
||||
eventKey: 'KeyA',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyS',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyD',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyF',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyG',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyH',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyJ',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyK',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyL',
|
||||
},
|
||||
{
|
||||
eventKey: 'Semicolon',
|
||||
symbol: ';',
|
||||
},
|
||||
{
|
||||
eventKey: 'Quote',
|
||||
symbol: '\'',
|
||||
},
|
||||
{
|
||||
eventKey: 'Enter',
|
||||
symbol: isMac ? '↩︎' : void 0,
|
||||
},
|
||||
// 第五排
|
||||
{
|
||||
eventKey: 'KeyZ',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyX',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyC',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyV',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyB',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyN',
|
||||
},
|
||||
{
|
||||
eventKey: 'KeyM',
|
||||
},
|
||||
{
|
||||
eventKey: 'Comma',
|
||||
symbol: ',',
|
||||
},
|
||||
{
|
||||
eventKey: 'Period',
|
||||
symbol: '.',
|
||||
},
|
||||
{
|
||||
eventKey: 'Slash',
|
||||
symbol: '/',
|
||||
},
|
||||
// 第六排
|
||||
{
|
||||
eventKey: 'Space',
|
||||
symbol: isMac ? '␣' : void 0,
|
||||
},
|
||||
// 方向键
|
||||
{
|
||||
eventKey: 'ArrowUp',
|
||||
symbol: '↑',
|
||||
},
|
||||
{
|
||||
eventKey: 'ArrowDown',
|
||||
symbol: '↓',
|
||||
},
|
||||
{
|
||||
eventKey: 'ArrowLeft',
|
||||
symbol: '←',
|
||||
},
|
||||
{
|
||||
eventKey: 'ArrowRight',
|
||||
symbol: '→',
|
||||
},
|
||||
].map((item) => {
|
||||
const { eventKey } = item
|
||||
|
||||
item.symbol ??= eventKey
|
||||
item.tauriKey ??= eventKey
|
||||
|
||||
if (eventKey.startsWith('Digit') || eventKey.startsWith('Key')) {
|
||||
item.tauriKey = item.symbol = eventKey.slice(-1)
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
|
||||
export const keys = modifierKeys.concat(standardKeys)
|
||||
@@ -12,25 +12,26 @@ Live2DModel.registerTicker(Ticker)
|
||||
class Live2d {
|
||||
private app: Application | null = null
|
||||
public model: Live2DModel | null = null
|
||||
private modelWidth = 0
|
||||
private modelHeight = 0
|
||||
|
||||
constructor() { }
|
||||
|
||||
private mount() {
|
||||
private initApp() {
|
||||
if (this.app) return
|
||||
|
||||
const view = document.getElementById('live2dCanvas') as HTMLCanvasElement
|
||||
|
||||
this.app = new Application({
|
||||
view,
|
||||
resizeTo: window,
|
||||
backgroundAlpha: 0,
|
||||
autoDensity: true,
|
||||
resolution: devicePixelRatio,
|
||||
})
|
||||
}
|
||||
|
||||
public async load(path: string) {
|
||||
if (!this.app) {
|
||||
this.mount()
|
||||
}
|
||||
this.initApp()
|
||||
|
||||
this.destroy()
|
||||
|
||||
@@ -57,11 +58,17 @@ class Live2d {
|
||||
|
||||
this.model = await Live2DModel.from(modelSettings)
|
||||
|
||||
const { width, height } = this.model
|
||||
this.modelWidth = width
|
||||
this.modelHeight = height
|
||||
|
||||
this.app?.stage.addChild(this.model)
|
||||
|
||||
const { motions, expressions } = modelSettings
|
||||
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
motions,
|
||||
expressions,
|
||||
}
|
||||
@@ -71,6 +78,19 @@ class Live2d {
|
||||
this.model?.destroy()
|
||||
}
|
||||
|
||||
public fitModel() {
|
||||
if (!this.model) return
|
||||
|
||||
const scaleX = innerWidth / this.modelWidth
|
||||
const scaleY = innerHeight / this.modelHeight
|
||||
const scale = Math.min(scaleX, scaleY)
|
||||
|
||||
this.model.scale.set(scale)
|
||||
this.model.x = innerWidth / 2
|
||||
this.model.y = innerHeight / 2
|
||||
this.model.anchor.set(0.5)
|
||||
}
|
||||
|
||||
public playMotion(group: string, index: number) {
|
||||
return this.model?.motion(group, index)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user