diff --git a/crates/client-core/src/signal.rs b/crates/client-core/src/signal.rs
index e84a0f4..55f4235 100644
--- a/crates/client-core/src/signal.rs
+++ b/crates/client-core/src/signal.rs
@@ -118,6 +118,16 @@ pub enum SignalMessage {
key: String,
event_type: String, // "down", "up"
},
+ /// 流媒体设置
+ #[serde(rename = "stream_settings")]
+ StreamSettings {
+ session_id: String,
+ from_device: String,
+ to_device: String,
+ resolution: f64,
+ fps: u32,
+ quality: u32,
+ },
}
/// 信令客户端
diff --git a/crates/client-tauri/Cargo.toml b/crates/client-tauri/Cargo.toml
index 936bd18..c581560 100644
--- a/crates/client-tauri/Cargo.toml
+++ b/crates/client-tauri/Cargo.toml
@@ -11,7 +11,7 @@ easyremote-common = { path = "../common" }
easyremote-client-core = { path = "../client-core" }
# Tauri
-tauri = { version = "1.5", features = ["shell-open", "dialog-all", "clipboard-all", "window-all"] }
+tauri = { version = "1.5", features = ["shell-open", "dialog-all", "clipboard-all", "window-all", "global-shortcut-all", "process-all", "system-tray"] }
# Windows single instance
[target.'cfg(windows)'.dependencies]
@@ -21,6 +21,7 @@ windows = { version = "0.58", features = [
"Win32_Security",
"Win32_UI_WindowsAndMessaging"
] }
+winreg = "0.52"
# Async
tokio = { workspace = true }
diff --git a/crates/client-tauri/app-icon.png b/crates/client-tauri/app-icon.png
index e16bb73..dcc3d28 100644
Binary files a/crates/client-tauri/app-icon.png and b/crates/client-tauri/app-icon.png differ
diff --git a/crates/client-tauri/app-icon.svg b/crates/client-tauri/app-icon.svg
new file mode 100644
index 0000000..04fb6fc
--- /dev/null
+++ b/crates/client-tauri/app-icon.svg
@@ -0,0 +1,45 @@
+
diff --git a/crates/client-tauri/generate-icons.js b/crates/client-tauri/generate-icons.js
new file mode 100644
index 0000000..f667fc4
--- /dev/null
+++ b/crates/client-tauri/generate-icons.js
@@ -0,0 +1,79 @@
+const sharp = require('sharp');
+const fs = require('fs');
+const path = require('path');
+
+const svgPath = path.join(__dirname, 'app-icon.svg');
+const iconsDir = path.join(__dirname, 'icons');
+
+// 确保icons目录存在
+if (!fs.existsSync(iconsDir)) {
+ fs.mkdirSync(iconsDir, { recursive: true });
+}
+
+const sizes = [
+ { name: '32x32.png', size: 32 },
+ { name: '128x128.png', size: 128 },
+ { name: '128x128@2x.png', size: 256 },
+ { name: 'icon.png', size: 512 },
+ { name: 'Square30x30Logo.png', size: 30 },
+ { name: 'Square44x44Logo.png', size: 44 },
+ { name: 'Square71x71Logo.png', size: 71 },
+ { name: 'Square89x89Logo.png', size: 89 },
+ { name: 'Square107x107Logo.png', size: 107 },
+ { name: 'Square142x142Logo.png', size: 142 },
+ { name: 'Square150x150Logo.png', size: 150 },
+ { name: 'Square284x284Logo.png', size: 284 },
+ { name: 'Square310x310Logo.png', size: 310 },
+ { name: 'StoreLogo.png', size: 50 },
+];
+
+async function generateIcons() {
+ const svgBuffer = fs.readFileSync(svgPath);
+
+ for (const { name, size } of sizes) {
+ const outputPath = path.join(iconsDir, name);
+ await sharp(svgBuffer)
+ .resize(size, size)
+ .png()
+ .toFile(outputPath);
+ console.log(`Generated: ${name} (${size}x${size})`);
+ }
+
+ // 生成 ICO 文件 (Windows)
+ // 为了简单起见,我们用256x256的PNG作为ICO的基础
+ const icoSizes = [16, 32, 48, 64, 128, 256];
+ const icoPngs = [];
+
+ for (const size of icoSizes) {
+ const buffer = await sharp(svgBuffer)
+ .resize(size, size)
+ .png()
+ .toBuffer();
+ icoPngs.push({ size, buffer });
+ }
+
+ // 生成简单的ICO (使用256x256作为主图标)
+ const ico256 = await sharp(svgBuffer)
+ .resize(256, 256)
+ .png()
+ .toFile(path.join(iconsDir, 'icon.ico.png'));
+
+ // 复制一个PNG作为ICO的替代(Tauri会处理)
+ await sharp(svgBuffer)
+ .resize(256, 256)
+ .png()
+ .toFile(path.join(iconsDir, 'icon.ico'));
+
+ console.log('Generated: icon.ico');
+
+ // 复制到根目录作为app-icon.png
+ await sharp(svgBuffer)
+ .resize(512, 512)
+ .png()
+ .toFile(path.join(__dirname, 'app-icon.png'));
+ console.log('Generated: app-icon.png');
+
+ console.log('\nAll icons generated successfully!');
+}
+
+generateIcons().catch(console.error);
diff --git a/crates/client-tauri/icons/128x128.png b/crates/client-tauri/icons/128x128.png
index 4e52c99..6693362 100644
Binary files a/crates/client-tauri/icons/128x128.png and b/crates/client-tauri/icons/128x128.png differ
diff --git a/crates/client-tauri/icons/128x128@2x.png b/crates/client-tauri/icons/128x128@2x.png
index 4722767..f03b77e 100644
Binary files a/crates/client-tauri/icons/128x128@2x.png and b/crates/client-tauri/icons/128x128@2x.png differ
diff --git a/crates/client-tauri/icons/32x32.png b/crates/client-tauri/icons/32x32.png
index 68bbdd2..2aeacdd 100644
Binary files a/crates/client-tauri/icons/32x32.png and b/crates/client-tauri/icons/32x32.png differ
diff --git a/crates/client-tauri/icons/Square107x107Logo.png b/crates/client-tauri/icons/Square107x107Logo.png
index 1b1d574..25ccc37 100644
Binary files a/crates/client-tauri/icons/Square107x107Logo.png and b/crates/client-tauri/icons/Square107x107Logo.png differ
diff --git a/crates/client-tauri/icons/Square142x142Logo.png b/crates/client-tauri/icons/Square142x142Logo.png
index 83c1051..2082e43 100644
Binary files a/crates/client-tauri/icons/Square142x142Logo.png and b/crates/client-tauri/icons/Square142x142Logo.png differ
diff --git a/crates/client-tauri/icons/Square150x150Logo.png b/crates/client-tauri/icons/Square150x150Logo.png
index 6918690..43c173f 100644
Binary files a/crates/client-tauri/icons/Square150x150Logo.png and b/crates/client-tauri/icons/Square150x150Logo.png differ
diff --git a/crates/client-tauri/icons/Square284x284Logo.png b/crates/client-tauri/icons/Square284x284Logo.png
index 6bfbdca..caec866 100644
Binary files a/crates/client-tauri/icons/Square284x284Logo.png and b/crates/client-tauri/icons/Square284x284Logo.png differ
diff --git a/crates/client-tauri/icons/Square30x30Logo.png b/crates/client-tauri/icons/Square30x30Logo.png
index 156d42f..866968d 100644
Binary files a/crates/client-tauri/icons/Square30x30Logo.png and b/crates/client-tauri/icons/Square30x30Logo.png differ
diff --git a/crates/client-tauri/icons/Square310x310Logo.png b/crates/client-tauri/icons/Square310x310Logo.png
index 659d284..0aa4348 100644
Binary files a/crates/client-tauri/icons/Square310x310Logo.png and b/crates/client-tauri/icons/Square310x310Logo.png differ
diff --git a/crates/client-tauri/icons/Square44x44Logo.png b/crates/client-tauri/icons/Square44x44Logo.png
index a3699ed..67e4807 100644
Binary files a/crates/client-tauri/icons/Square44x44Logo.png and b/crates/client-tauri/icons/Square44x44Logo.png differ
diff --git a/crates/client-tauri/icons/Square71x71Logo.png b/crates/client-tauri/icons/Square71x71Logo.png
index 6157922..f6bac78 100644
Binary files a/crates/client-tauri/icons/Square71x71Logo.png and b/crates/client-tauri/icons/Square71x71Logo.png differ
diff --git a/crates/client-tauri/icons/Square89x89Logo.png b/crates/client-tauri/icons/Square89x89Logo.png
index b3a8eee..382aac9 100644
Binary files a/crates/client-tauri/icons/Square89x89Logo.png and b/crates/client-tauri/icons/Square89x89Logo.png differ
diff --git a/crates/client-tauri/icons/StoreLogo.png b/crates/client-tauri/icons/StoreLogo.png
index 35a059f..9b1a4ef 100644
Binary files a/crates/client-tauri/icons/StoreLogo.png and b/crates/client-tauri/icons/StoreLogo.png differ
diff --git a/crates/client-tauri/icons/icon.icns b/crates/client-tauri/icons/icon.icns
index 7b53a41..c062f59 100644
Binary files a/crates/client-tauri/icons/icon.icns and b/crates/client-tauri/icons/icon.icns differ
diff --git a/crates/client-tauri/icons/icon.ico b/crates/client-tauri/icons/icon.ico
index 2caf0fb..c392c54 100644
Binary files a/crates/client-tauri/icons/icon.ico and b/crates/client-tauri/icons/icon.ico differ
diff --git a/crates/client-tauri/icons/icon.ico.png b/crates/client-tauri/icons/icon.ico.png
new file mode 100644
index 0000000..004183d
Binary files /dev/null and b/crates/client-tauri/icons/icon.ico.png differ
diff --git a/crates/client-tauri/icons/icon.png b/crates/client-tauri/icons/icon.png
index daf89f6..95f9e68 100644
Binary files a/crates/client-tauri/icons/icon.png and b/crates/client-tauri/icons/icon.png differ
diff --git a/crates/client-tauri/package-lock.json b/crates/client-tauri/package-lock.json
new file mode 100644
index 0000000..022f3f9
--- /dev/null
+++ b/crates/client-tauri/package-lock.json
@@ -0,0 +1,589 @@
+{
+ "name": "client-tauri",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "devDependencies": {
+ "sharp": "^0.34.5"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.8.1.tgz",
+ "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@img/colour": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/@img/colour/-/colour-1.0.0.tgz",
+ "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.7.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sharp": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmmirror.com/sharp/-/sharp-0.34.5.tgz",
+ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@img/colour": "^1.0.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.34.5",
+ "@img/sharp-darwin-x64": "0.34.5",
+ "@img/sharp-libvips-darwin-arm64": "1.2.4",
+ "@img/sharp-libvips-darwin-x64": "1.2.4",
+ "@img/sharp-libvips-linux-arm": "1.2.4",
+ "@img/sharp-libvips-linux-arm64": "1.2.4",
+ "@img/sharp-libvips-linux-ppc64": "1.2.4",
+ "@img/sharp-libvips-linux-riscv64": "1.2.4",
+ "@img/sharp-libvips-linux-s390x": "1.2.4",
+ "@img/sharp-libvips-linux-x64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
+ "@img/sharp-linux-arm": "0.34.5",
+ "@img/sharp-linux-arm64": "0.34.5",
+ "@img/sharp-linux-ppc64": "0.34.5",
+ "@img/sharp-linux-riscv64": "0.34.5",
+ "@img/sharp-linux-s390x": "0.34.5",
+ "@img/sharp-linux-x64": "0.34.5",
+ "@img/sharp-linuxmusl-arm64": "0.34.5",
+ "@img/sharp-linuxmusl-x64": "0.34.5",
+ "@img/sharp-wasm32": "0.34.5",
+ "@img/sharp-win32-arm64": "0.34.5",
+ "@img/sharp-win32-ia32": "0.34.5",
+ "@img/sharp-win32-x64": "0.34.5"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ }
+ }
+}
diff --git a/crates/client-tauri/package.json b/crates/client-tauri/package.json
new file mode 100644
index 0000000..c5d3646
--- /dev/null
+++ b/crates/client-tauri/package.json
@@ -0,0 +1,5 @@
+{
+ "devDependencies": {
+ "sharp": "^0.34.5"
+ }
+}
diff --git a/crates/client-tauri/src/commands.rs b/crates/client-tauri/src/commands.rs
index d78f9d4..5dbc673 100644
--- a/crates/client-tauri/src/commands.rs
+++ b/crates/client-tauri/src/commands.rs
@@ -18,6 +18,33 @@ static FORCE_OFFLINE_FLAG: AtomicBool = AtomicBool::new(false);
/// 当前活跃的屏幕流会话
static ACTIVE_SESSION: tokio::sync::OnceCell>>> = tokio::sync::OnceCell::const_new();
+/// 流媒体设置
+#[derive(Clone)]
+struct StreamSettings {
+ resolution: f64,
+ fps: u32,
+ quality: u32,
+}
+
+impl Default for StreamSettings {
+ fn default() -> Self {
+ Self {
+ resolution: 0.5,
+ fps: 15,
+ quality: 70,
+ }
+ }
+}
+
+/// 全局流媒体设置
+static STREAM_SETTINGS: tokio::sync::OnceCell>> = tokio::sync::OnceCell::const_new();
+
+async fn get_stream_settings() -> &'static Arc> {
+ STREAM_SETTINGS.get_or_init(|| async {
+ Arc::new(RwLock::new(StreamSettings::default()))
+ }).await
+}
+
/// 活跃屏幕会话
struct ActiveScreenSession {
session_id: String,
@@ -89,6 +116,8 @@ async fn start_screen_streaming(session_id: String, controller_device: String, m
let signal_client_clone = signal_client.clone();
let active_session_clone = active_session.clone();
+ let stream_settings = get_stream_settings().await.clone();
+
tokio::task::spawn_blocking(move || {
// 创建屏幕捕获器
let mut capturer = match ScreenCapturer::new(0) {
@@ -107,6 +136,18 @@ async fn start_screen_streaming(session_id: String, controller_device: String, m
let start_time = std::time::Instant::now();
loop {
+ // 获取当前设置
+ let (resolution, fps, _quality) = {
+ let rt = tokio::runtime::Handle::current();
+ rt.block_on(async {
+ let s = stream_settings.read().await;
+ (s.resolution, s.fps, s.quality)
+ })
+ };
+
+ // 计算帧间隔
+ let frame_interval = std::time::Duration::from_millis(1000 / fps as u64);
+
// 检查是否需要停止
let should_stop = {
let rt = tokio::runtime::Handle::current();
@@ -130,8 +171,10 @@ async fn start_screen_streaming(session_id: String, controller_device: String, m
let mut jpeg_data = Vec::new();
let mut cursor = std::io::Cursor::new(&mut jpeg_data);
- // 降低分辨率和质量以减少带宽
- let scaled = image::imageops::resize(&img, width / 2, height / 2, image::imageops::FilterType::Nearest);
+ // 根据设置调整分辨率
+ let scaled_width = ((width as f64) * resolution) as u32;
+ let scaled_height = ((height as f64) * resolution) as u32;
+ let scaled = image::imageops::resize(&img, scaled_width, scaled_height, image::imageops::FilterType::Nearest);
if scaled.write_to(&mut cursor, image::ImageFormat::Jpeg).is_ok() {
let jpeg_len = jpeg_data.len();
@@ -141,8 +184,6 @@ async fn start_screen_streaming(session_id: String, controller_device: String, m
let session_id_send = session_id_capture.clone();
let controller_device_send = controller_device_capture.clone();
let signal_client_send = signal_client_clone.clone();
- let scaled_width = width / 2;
- let scaled_height = height / 2;
rt.block_on(async move {
let client = signal_client_send.read().await;
@@ -159,8 +200,9 @@ async fn start_screen_streaming(session_id: String, controller_device: String, m
frame_count += 1;
if frame_count % 30 == 0 {
- let fps = frame_count as f64 / start_time.elapsed().as_secs_f64();
- tracing::debug!("屏幕流帧率: {:.1} fps, 帧大小: {} KB", fps, jpeg_len / 1024);
+ let actual_fps = frame_count as f64 / start_time.elapsed().as_secs_f64();
+ tracing::debug!("屏幕流: {:.1} fps, 分辨率: {}x{}, 帧大小: {} KB",
+ actual_fps, scaled_width, scaled_height, jpeg_len / 1024);
}
}
}
@@ -174,8 +216,8 @@ async fn start_screen_streaming(session_id: String, controller_device: String, m
}
}
- // 控制帧率 (约 15 fps)
- std::thread::sleep(std::time::Duration::from_millis(66));
+ // 根据设置控制帧率
+ std::thread::sleep(frame_interval);
}
// 清理会话
@@ -191,8 +233,15 @@ async fn start_screen_streaming(session_id: String, controller_device: String, m
fn handle_mouse_input(x: f64, y: f64, event_type: &str, button: Option, delta: Option) {
use easyremote_client_core::InputController;
- // 获取屏幕缩放比例 (因为我们发送的是缩放后的帧)
- let scale = 2.0;
+ // 获取当前分辨率设置以计算缩放比例
+ let scale = {
+ let rt = tokio::runtime::Handle::current();
+ rt.block_on(async {
+ let settings = get_stream_settings().await;
+ let s = settings.read().await;
+ 1.0 / s.resolution
+ })
+ };
let actual_x = (x * scale) as i32;
let actual_y = (y * scale) as i32;
@@ -351,6 +400,17 @@ async fn connect_to_signal_server(device_id: String, server_url: String) -> Resu
// 处理键盘事件
handle_keyboard_input(&key, &event_type);
}
+ SignalMessage::StreamSettings { resolution, fps, quality, .. } => {
+ // 更新流媒体设置
+ tracing::info!("更新流媒体设置: resolution={}, fps={}, quality={}", resolution, fps, quality);
+ tokio::spawn(async move {
+ let settings = get_stream_settings().await;
+ let mut s = settings.write().await;
+ s.resolution = resolution;
+ s.fps = fps;
+ s.quality = quality;
+ });
+ }
_ => {
tracing::debug!("收到信令消息: {:?}", msg);
}
@@ -758,3 +818,125 @@ pub async fn save_config(
config.save().map_err(|e| e.to_string())?;
Ok(())
}
+
+/// 获取开机启动状态
+#[tauri::command]
+pub fn get_autostart() -> Result {
+ #[cfg(windows)]
+ {
+ use winreg::enums::*;
+ use winreg::RegKey;
+
+ let hkcu = RegKey::predef(HKEY_CURRENT_USER);
+ if let Ok(run_key) = hkcu.open_subkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run") {
+ return Ok(run_key.get_value::("EasyRemote").is_ok());
+ }
+ Ok(false)
+ }
+
+ #[cfg(target_os = "macos")]
+ {
+ let plist_path = dirs::home_dir()
+ .map(|p| p.join("Library/LaunchAgents/com.easyremote.app.plist"))
+ .unwrap_or_default();
+ Ok(plist_path.exists())
+ }
+
+ #[cfg(target_os = "linux")]
+ {
+ let autostart_path = dirs::config_dir()
+ .map(|p| p.join("autostart/easyremote.desktop"))
+ .unwrap_or_default();
+ Ok(autostart_path.exists())
+ }
+}
+
+/// 设置开机启动
+#[tauri::command]
+pub fn set_autostart(enable: bool) -> Result<(), String> {
+ let exe_path = std::env::current_exe().map_err(|e| e.to_string())?;
+
+ #[cfg(windows)]
+ {
+ use winreg::enums::*;
+ use winreg::RegKey;
+
+ let hkcu = RegKey::predef(HKEY_CURRENT_USER);
+ let run_key = hkcu
+ .open_subkey_with_flags("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", KEY_WRITE)
+ .map_err(|e| e.to_string())?;
+
+ if enable {
+ run_key
+ .set_value("EasyRemote", &exe_path.to_string_lossy().to_string())
+ .map_err(|e| e.to_string())?;
+ } else {
+ let _ = run_key.delete_value("EasyRemote");
+ }
+ return Ok(());
+ }
+
+ #[cfg(target_os = "macos")]
+ {
+ let plist_path = dirs::home_dir()
+ .ok_or("找不到用户目录")?
+ .join("Library/LaunchAgents/com.easyremote.app.plist");
+
+ if enable {
+ let plist_content = format!(
+ r#"
+
+
+
+ Label
+ com.easyremote.app
+ ProgramArguments
+
+ {}
+
+ RunAtLoad
+
+
+"#,
+ exe_path.to_string_lossy()
+ );
+
+ if let Some(parent) = plist_path.parent() {
+ std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
+ }
+ std::fs::write(&plist_path, plist_content).map_err(|e| e.to_string())?;
+ } else {
+ let _ = std::fs::remove_file(&plist_path);
+ }
+ return Ok(());
+ }
+
+ #[cfg(target_os = "linux")]
+ {
+ let autostart_dir = dirs::config_dir()
+ .ok_or("找不到配置目录")?
+ .join("autostart");
+ let desktop_path = autostart_dir.join("easyremote.desktop");
+
+ if enable {
+ std::fs::create_dir_all(&autostart_dir).map_err(|e| e.to_string())?;
+
+ let desktop_content = format!(
+ r#"[Desktop Entry]
+Type=Application
+Name=EasyRemote
+Exec={}
+Hidden=false
+NoDisplay=false
+X-GNOME-Autostart-enabled=true
+"#,
+ exe_path.to_string_lossy()
+ );
+
+ std::fs::write(&desktop_path, desktop_content).map_err(|e| e.to_string())?;
+ } else {
+ let _ = std::fs::remove_file(&desktop_path);
+ }
+ return Ok(());
+ }
+}
diff --git a/crates/client-tauri/src/main.rs b/crates/client-tauri/src/main.rs
index 705ea30..292f1de 100644
--- a/crates/client-tauri/src/main.rs
+++ b/crates/client-tauri/src/main.rs
@@ -8,7 +8,10 @@ mod state;
use state::AppState;
use std::sync::Arc;
-use tauri::Manager;
+use tauri::{
+ CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem,
+ WindowEvent,
+};
use tokio::sync::RwLock;
#[cfg(windows)]
@@ -140,7 +143,45 @@ fn main() {
// 创建应用状态
let state = Arc::new(RwLock::new(AppState::new()));
+ // 创建系统托盘菜单
+ let tray_menu = SystemTrayMenu::new()
+ .add_item(CustomMenuItem::new("show", "显示主窗口"))
+ .add_native_item(SystemTrayMenuItem::Separator)
+ .add_item(CustomMenuItem::new("quit", "退出"));
+
+ let system_tray = SystemTray::new().with_menu(tray_menu);
+
tauri::Builder::default()
+ .system_tray(system_tray)
+ .on_system_tray_event(|app, event| match event {
+ SystemTrayEvent::LeftClick { .. } => {
+ // 左键点击显示窗口
+ if let Some(window) = app.get_window("main") {
+ let _ = window.show();
+ let _ = window.set_focus();
+ }
+ }
+ SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
+ "show" => {
+ if let Some(window) = app.get_window("main") {
+ let _ = window.show();
+ let _ = window.set_focus();
+ }
+ }
+ "quit" => {
+ std::process::exit(0);
+ }
+ _ => {}
+ },
+ _ => {}
+ })
+ .on_window_event(|event| {
+ // 关闭窗口时最小化到托盘而不是退出
+ if let WindowEvent::CloseRequested { api, .. } = event.event() {
+ event.window().hide().unwrap();
+ api.prevent_close();
+ }
+ })
.setup(|app| {
// 设置窗口标题以便单例检测
if let Some(window) = app.get_window("main") {
@@ -189,6 +230,9 @@ fn main() {
// 配置
commands::get_config,
commands::save_config,
+ // 开机启动
+ commands::get_autostart,
+ commands::set_autostart,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
diff --git a/crates/client-tauri/tauri.conf.json b/crates/client-tauri/tauri.conf.json
index c9100dc..2a7b632 100644
--- a/crates/client-tauri/tauri.conf.json
+++ b/crates/client-tauri/tauri.conf.json
@@ -23,8 +23,18 @@
},
"clipboard": {
"all": true
+ },
+ "process": {
+ "all": true
+ },
+ "globalShortcut": {
+ "all": true
}
},
+ "systemTray": {
+ "iconPath": "icons/icon.png",
+ "iconAsTemplate": true
+ },
"bundle": {
"active": true,
"category": "Utility",
diff --git a/crates/client-tauri/ui/src/App.vue b/crates/client-tauri/ui/src/App.vue
index 429e88e..d9a9332 100644
--- a/crates/client-tauri/ui/src/App.vue
+++ b/crates/client-tauri/ui/src/App.vue
@@ -12,6 +12,7 @@ const connectionState = ref({ type: 'Disconnected' })
const loading = ref(false)
const error = ref('')
const successMsg = ref('')
+const autostart = ref(false)
// 配置
const config = ref({
@@ -48,6 +49,13 @@ const connectForm = ref({
verification_code: ''
})
+// 流媒体设置
+const streamSettings = ref({
+ resolution: '0.5',
+ fps: '15',
+ quality: '70'
+})
+
// 计算属性
const isLoggedIn = computed(() => currentUser.value !== null)
const formattedDeviceId = computed(() => deviceInfo.value?.device_id_formatted || '')
@@ -56,13 +64,9 @@ const onlineDevices = computed(() => devices.value.filter(d => d.is_online && d.
// 初始化
onMounted(async () => {
try {
- // 加载配置
await loadConfig()
-
- // 初始化(验证 token 并注册设备)
+ await loadAutostart()
await invoke('initialize')
-
- // 获取设备信息和用户
deviceInfo.value = await invoke('get_device_info')
currentUser.value = await invoke('get_current_user')
@@ -75,7 +79,27 @@ onMounted(async () => {
}
})
-// 加载配置
+// 加载开机启动状态
+async function loadAutostart() {
+ try {
+ autostart.value = await invoke('get_autostart')
+ } catch (e) {
+ console.error('加载开机启动状态失败:', e)
+ }
+}
+
+// 切换开机启动
+async function toggleAutostart() {
+ try {
+ const newValue = !autostart.value
+ await invoke('set_autostart', { enable: newValue })
+ autostart.value = newValue
+ showSuccess(newValue ? '已设置开机自动启动' : '已取消开机自动启动')
+ } catch (e: any) {
+ showError('设置开机启动失败: ' + e)
+ }
+}
+
async function loadConfig() {
try {
config.value = await invoke('get_config')
@@ -85,7 +109,6 @@ async function loadConfig() {
}
}
-// 保存配置
async function saveConfig() {
loading.value = true
try {
@@ -104,25 +127,26 @@ async function saveConfig() {
}
}
-// 开始编辑配置
function startEditConfig() {
configForm.value.server_url = config.value.server_url
configEditing.value = true
}
-// 取消编辑
function cancelEditConfig() {
configForm.value.server_url = config.value.server_url
configEditing.value = false
}
-// 显示成功消息
function showSuccess(msg: string) {
successMsg.value = msg
- setTimeout(() => { successMsg.value = '' }, 2000)
+ setTimeout(() => { successMsg.value = '' }, 3000)
+}
+
+function showError(msg: string) {
+ error.value = msg
+ setTimeout(() => { error.value = '' }, 3000)
}
-// 刷新验证码
async function refreshCode() {
try {
const newCode = await invoke('refresh_verification_code')
@@ -135,7 +159,6 @@ async function refreshCode() {
}
}
-// 切换允许远程
async function toggleAllowRemote() {
if (!deviceInfo.value) return
try {
@@ -147,7 +170,6 @@ async function toggleAllowRemote() {
}
}
-// 登录/注册
async function handleAuth() {
loading.value = true
error.value = ''
@@ -175,7 +197,6 @@ async function handleAuth() {
}
}
-// 退出登录
async function handleLogout() {
try {
await invoke('logout')
@@ -188,7 +209,6 @@ async function handleLogout() {
}
}
-// 加载设备列表
async function loadDevices() {
try {
devices.value = await invoke('get_devices')
@@ -197,7 +217,6 @@ async function loadDevices() {
}
}
-// 加载历史记录
async function loadHistory() {
try {
history.value = await invoke('get_history', { page: 1, limit: 20 })
@@ -206,7 +225,6 @@ async function loadHistory() {
}
}
-// 连接设备
async function connectToDevice(targetDeviceId?: string, targetCode?: string) {
const deviceId = targetDeviceId || connectForm.value.device_id
const code = targetCode || connectForm.value.verification_code
@@ -233,7 +251,6 @@ async function connectToDevice(targetDeviceId?: string, targetCode?: string) {
}
}
-// 快速连接已登录设备
async function quickConnect(device: Device) {
loading.value = true
error.value = ''
@@ -250,7 +267,6 @@ async function quickConnect(device: Device) {
}
}
-// 断开连接
async function disconnect() {
try {
await invoke('disconnect')
@@ -260,7 +276,6 @@ async function disconnect() {
}
}
-// 复制设备信息
async function copyDeviceInfo() {
if (!deviceInfo.value) return
const text = `设备ID: ${deviceInfo.value.device_id_formatted}\n验证码: ${deviceInfo.value.verification_code}`
@@ -268,13 +283,11 @@ async function copyDeviceInfo() {
showSuccess('已复制到剪贴板')
}
-// 格式化时间
function formatTime(timeStr: string): string {
const date = new Date(timeStr)
return date.toLocaleString('zh-CN')
}
-// 获取设备图标
function getDeviceIcon(osType: string): string {
const os = osType.toLowerCase()
if (os.includes('windows')) return '💻'
@@ -291,7 +304,7 @@ function getDeviceIcon(osType: string): string {
-