fix: resolve app show/hide behavior and tray icon handling (#129)

This commit is contained in:
Medcl
2025-02-09 21:51:03 +08:00
committed by GitHub
parent 17b3b97bec
commit bab38b3226
19 changed files with 1267 additions and 642 deletions

View File

@@ -11,16 +11,17 @@
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^2.1.10", "@headlessui/react": "^2.1.10",
"@react-oauth/google": "^0.12.1",
"@tauri-apps/api": "^2.2.0", "@tauri-apps/api": "^2.2.0",
"@tauri-apps/plugin-autostart": "~2", "@tauri-apps/plugin-autostart": "~2",
"@tauri-apps/plugin-deep-link": "^2.2.0", "@tauri-apps/plugin-deep-link": "^2.2.0",
"@tauri-apps/plugin-dialog": "^2.2.0",
"@tauri-apps/plugin-global-shortcut": "~2.0.0", "@tauri-apps/plugin-global-shortcut": "~2.0.0",
"@tauri-apps/plugin-http": "~2.0.1", "@tauri-apps/plugin-http": "~2.0.1",
"@tauri-apps/plugin-os": "^2.2.0", "@tauri-apps/plugin-os": "^2.2.0",
"@tauri-apps/plugin-shell": ">=2.0.0", "@tauri-apps/plugin-shell": ">=2.0.0",
"@tauri-apps/plugin-websocket": "~2", "@tauri-apps/plugin-websocket": "~2",
"@tauri-apps/plugin-window": "2.0.0-alpha.1", "@tauri-apps/plugin-window": "2.0.0-alpha.1",
"@tauri-apps/plugin-updater": "^2.3.0",
"ahooks": "^3.8.4", "ahooks": "^3.8.4",
"axios": "^1.7.7", "axios": "^1.7.7",
"clsx": "^2.1.1", "clsx": "^2.1.1",

47
pnpm-lock.yaml generated
View File

@@ -11,9 +11,6 @@ importers:
'@headlessui/react': '@headlessui/react':
specifier: ^2.1.10 specifier: ^2.1.10
version: 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@react-oauth/google':
specifier: ^0.12.1
version: 0.12.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tauri-apps/api': '@tauri-apps/api':
specifier: ^2.2.0 specifier: ^2.2.0
version: 2.2.0 version: 2.2.0
@@ -23,6 +20,9 @@ importers:
'@tauri-apps/plugin-deep-link': '@tauri-apps/plugin-deep-link':
specifier: ^2.2.0 specifier: ^2.2.0
version: 2.2.0 version: 2.2.0
'@tauri-apps/plugin-dialog':
specifier: ^2.2.0
version: 2.2.0
'@tauri-apps/plugin-global-shortcut': '@tauri-apps/plugin-global-shortcut':
specifier: ~2.0.0 specifier: ~2.0.0
version: 2.0.0 version: 2.0.0
@@ -35,6 +35,9 @@ importers:
'@tauri-apps/plugin-shell': '@tauri-apps/plugin-shell':
specifier: '>=2.0.0' specifier: '>=2.0.0'
version: 2.0.0 version: 2.0.0
'@tauri-apps/plugin-updater':
specifier: ^2.3.0
version: 2.5.0
'@tauri-apps/plugin-websocket': '@tauri-apps/plugin-websocket':
specifier: ~2 specifier: ~2
version: 2.0.0 version: 2.0.0
@@ -513,12 +516,6 @@ packages:
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
'@react-oauth/google@0.12.1':
resolution: {integrity: sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
'@react-stately/utils@3.10.4': '@react-stately/utils@3.10.4':
resolution: {integrity: sha512-gBEQEIMRh5f60KCm7QKQ2WfvhB2gLUr9b72sqUdIZ2EG+xuPgaIlCBeSicvjmjBvYZwOjoOEnmIkcx2GHp/HWw==} resolution: {integrity: sha512-gBEQEIMRh5f60KCm7QKQ2WfvhB2gLUr9b72sqUdIZ2EG+xuPgaIlCBeSicvjmjBvYZwOjoOEnmIkcx2GHp/HWw==}
peerDependencies: peerDependencies:
@@ -557,55 +554,46 @@ packages:
resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.24.0': '@rollup/rollup-linux-arm-musleabihf@4.24.0':
resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.24.0': '@rollup/rollup-linux-arm64-gnu@4.24.0':
resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.24.0': '@rollup/rollup-linux-arm64-musl@4.24.0':
resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-powerpc64le-gnu@4.24.0': '@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.24.0': '@rollup/rollup-linux-riscv64-gnu@4.24.0':
resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.24.0': '@rollup/rollup-linux-s390x-gnu@4.24.0':
resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.24.0': '@rollup/rollup-linux-x64-gnu@4.24.0':
resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.24.0': '@rollup/rollup-linux-x64-musl@4.24.0':
resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.24.0': '@rollup/rollup-win32-arm64-msvc@4.24.0':
resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==}
@@ -664,28 +652,24 @@ packages:
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@tauri-apps/cli-linux-arm64-musl@2.2.7': '@tauri-apps/cli-linux-arm64-musl@2.2.7':
resolution: {integrity: sha512-+8HZ+txff/Y3YjAh80XcLXcX8kpGXVdr1P8AfjLHxHdS6QD4Md+acSxGTTNbplmHuBaSHJvuTvZf9tU1eDCTDg==} resolution: {integrity: sha512-+8HZ+txff/Y3YjAh80XcLXcX8kpGXVdr1P8AfjLHxHdS6QD4Md+acSxGTTNbplmHuBaSHJvuTvZf9tU1eDCTDg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@tauri-apps/cli-linux-x64-gnu@2.2.7': '@tauri-apps/cli-linux-x64-gnu@2.2.7':
resolution: {integrity: sha512-ahlSnuCnUntblp9dG7/w5ZWZOdzRFi3zl0oScgt7GF4KNAOEa7duADsxPA4/FT2hLRa0SvpqtD4IYFvCxoVv3Q==} resolution: {integrity: sha512-ahlSnuCnUntblp9dG7/w5ZWZOdzRFi3zl0oScgt7GF4KNAOEa7duADsxPA4/FT2hLRa0SvpqtD4IYFvCxoVv3Q==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@tauri-apps/cli-linux-x64-musl@2.2.7': '@tauri-apps/cli-linux-x64-musl@2.2.7':
resolution: {integrity: sha512-+qKAWnJRSX+pjjRbKAQgTdFY8ecdcu8UdJ69i7wn3ZcRn2nMMzOO2LOMOTQV42B7/Q64D1pIpmZj9yblTMvadA==} resolution: {integrity: sha512-+qKAWnJRSX+pjjRbKAQgTdFY8ecdcu8UdJ69i7wn3ZcRn2nMMzOO2LOMOTQV42B7/Q64D1pIpmZj9yblTMvadA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@tauri-apps/cli-win32-arm64-msvc@2.2.7': '@tauri-apps/cli-win32-arm64-msvc@2.2.7':
resolution: {integrity: sha512-aa86nRnrwT04u9D9fhf5JVssuAZlUCCc8AjqQjqODQjMd4BMA2+d4K9qBMpEG/1kVh95vZaNsLogjEaqSTTw4A==} resolution: {integrity: sha512-aa86nRnrwT04u9D9fhf5JVssuAZlUCCc8AjqQjqODQjMd4BMA2+d4K9qBMpEG/1kVh95vZaNsLogjEaqSTTw4A==}
@@ -716,6 +700,9 @@ packages:
'@tauri-apps/plugin-deep-link@2.2.0': '@tauri-apps/plugin-deep-link@2.2.0':
resolution: {integrity: sha512-H6mkxr2KZ3XJcKL44tiq6cOjCw9DL8OgU1xjn3j26Qsn+H/roPFiyhR7CHuB8Ar+sQFj4YVlfmJwtBajK2FETQ==} resolution: {integrity: sha512-H6mkxr2KZ3XJcKL44tiq6cOjCw9DL8OgU1xjn3j26Qsn+H/roPFiyhR7CHuB8Ar+sQFj4YVlfmJwtBajK2FETQ==}
'@tauri-apps/plugin-dialog@2.2.0':
resolution: {integrity: sha512-6bLkYK68zyK31418AK5fNccCdVuRnNpbxquCl8IqgFByOgWFivbiIlvb79wpSXi0O+8k8RCSsIpOquebusRVSg==}
'@tauri-apps/plugin-global-shortcut@2.0.0': '@tauri-apps/plugin-global-shortcut@2.0.0':
resolution: {integrity: sha512-pnB4CUwFVjg4twtBSxoLJ4uLFTYxsvOdC1zIbG581pYzhYatOl6mjB+ijD5SSXgiS/jNoqMcfkOF9PWAisurew==} resolution: {integrity: sha512-pnB4CUwFVjg4twtBSxoLJ4uLFTYxsvOdC1zIbG581pYzhYatOl6mjB+ijD5SSXgiS/jNoqMcfkOF9PWAisurew==}
@@ -728,6 +715,9 @@ packages:
'@tauri-apps/plugin-shell@2.0.0': '@tauri-apps/plugin-shell@2.0.0':
resolution: {integrity: sha512-OpW2+ycgJLrEoZityWeWYk+6ZWP9VyiAfbO+N/O8VfLkqyOym8kXh7odKDfINx9RAotkSGBtQM4abyKfJDkcUg==} resolution: {integrity: sha512-OpW2+ycgJLrEoZityWeWYk+6ZWP9VyiAfbO+N/O8VfLkqyOym8kXh7odKDfINx9RAotkSGBtQM4abyKfJDkcUg==}
'@tauri-apps/plugin-updater@2.5.0':
resolution: {integrity: sha512-CWpwrkgpMESDPgJ0EuXgQTI/U9zeZQ9NLUvMyuWVrsuRez7tJ/3c7y73LAkvkI6+ekUxVaRJrxYrSfd8W+DRvQ==}
'@tauri-apps/plugin-websocket@2.0.0': '@tauri-apps/plugin-websocket@2.0.0':
resolution: {integrity: sha512-O2qRxZCljd4g+ceJhW7LfgQr+fg0fBBiAaLiMopoKL6TXKMnhBHOenp4nZ5/MoVTr77OQIDNO6Jp/c1YwiRVtQ==} resolution: {integrity: sha512-O2qRxZCljd4g+ceJhW7LfgQr+fg0fBBiAaLiMopoKL6TXKMnhBHOenp4nZ5/MoVTr77OQIDNO6Jp/c1YwiRVtQ==}
@@ -2649,11 +2639,6 @@ snapshots:
clsx: 2.1.1 clsx: 2.1.1
react: 18.3.1 react: 18.3.1
'@react-oauth/google@0.12.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@react-stately/utils@3.10.4(react@18.3.1)': '@react-stately/utils@3.10.4(react@18.3.1)':
dependencies: dependencies:
'@swc/helpers': 0.5.13 '@swc/helpers': 0.5.13
@@ -2780,6 +2765,10 @@ snapshots:
dependencies: dependencies:
'@tauri-apps/api': 2.2.0 '@tauri-apps/api': 2.2.0
'@tauri-apps/plugin-dialog@2.2.0':
dependencies:
'@tauri-apps/api': 2.2.0
'@tauri-apps/plugin-global-shortcut@2.0.0': '@tauri-apps/plugin-global-shortcut@2.0.0':
dependencies: dependencies:
'@tauri-apps/api': 2.2.0 '@tauri-apps/api': 2.2.0
@@ -2796,6 +2785,10 @@ snapshots:
dependencies: dependencies:
'@tauri-apps/api': 2.2.0 '@tauri-apps/api': 2.2.0
'@tauri-apps/plugin-updater@2.5.0':
dependencies:
'@tauri-apps/api': 2.2.0
'@tauri-apps/plugin-websocket@2.0.0': '@tauri-apps/plugin-websocket@2.0.0':
dependencies: dependencies:
'@tauri-apps/api': 2.2.0 '@tauri-apps/api': 2.2.0

597
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,23 +14,31 @@ name = "coco_lib"
crate-type = ["staticlib", "cdylib", "rlib"] crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies] [build-dependencies]
tauri-build = { version = "2.0.0", features = [] } tauri-build = { version = "2", features = [] }
[dependencies] [dependencies]
pizza-common = { git = "https://github.com/infinilabs/pizza-common", branch = "main" } pizza-common = { git = "https://github.com/infinilabs/pizza-common", branch = "main" }
tauri = { version = "2.0.6", features = ["macos-private-api", "tray-icon", "image-png", "unstable"] } tauri = { version = "2", features = ["protocol-asset", "macos-private-api", "tray-icon", "image-ico", "image-png", "unstable"] }
tauri-plugin-shell = "2.0.0" tauri-plugin-shell = "2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
tauri-plugin-http = "2" tauri-plugin-http = "2"
tauri-plugin-websocket = "2" tauri-plugin-websocket = "2"
tauri-plugin-theme = "2.1.2" tauri-plugin-theme = "2.1.2"
tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" }
tauri-plugin-deep-link = "2.0.0" tauri-plugin-deep-link = "2.0.0"
tauri-plugin-single-instance = "2.0.0" tauri-plugin-single-instance = "2.0.0"
tauri-plugin-store = "2.2.0" tauri-plugin-store = "2.2.0"
tauri-plugin-os = "2"
tauri-plugin-dialog = "2"
tauri-plugin-fs = "2"
tauri-plugin-updater = "2"
tauri-plugin-process = "2"
tauri-plugin-drag = "2"
[target."cfg(target_os = \"macos\")".dependencies]
tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" }
reqwest = "0.12.12" reqwest = "0.12.12"
futures = "0.3.31" futures = "0.3.31"
ordered-float = { version = "4.6.0", default-features = false } ordered-float = { version = "4.6.0", default-features = false }

8
src-tauri/Coco.desktop Normal file
View File

@@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name={{{name}}}
Exec={{{exec}}}
Icon={{{icon}}}
Categories={{{categories}}}
Comment={{{comment}}}
Terminal=false

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
src-tauri/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src-tauri/assets/tray.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -55,9 +55,6 @@
{ {
"identifier": "http:default", "identifier": "http:default",
"allow": [ "allow": [
{
"url": "http://localhost:9000"
},
{ {
"url": "https://coco.infini.cloud" "url": "https://coco.infini.cloud"
} }

View File

@@ -18,7 +18,7 @@ use reqwest::Client;
use std::path::PathBuf; use std::path::PathBuf;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use tauri::ActivationPolicy; use tauri::ActivationPolicy;
use tauri::{AppHandle, Emitter, Listener, Manager, Runtime, WebviewWindow}; use tauri::{AppHandle, Emitter, Listener, Manager, Runtime, WebviewWindow, WindowEvent};
use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt; use tauri_plugin_deep_link::DeepLinkExt;
use tokio::runtime::Runtime as RT; use tokio::runtime::Runtime as RT;
@@ -73,7 +73,7 @@ struct Payload {
pub fn run() { pub fn run() {
let mut ctx = tauri::generate_context!(); let mut ctx = tauri::generate_context!();
tauri::Builder::default() let app = tauri::Builder::default()
.plugin(tauri_nspanel::init()) .plugin(tauri_nspanel::init())
.plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_shell::init())
@@ -96,7 +96,7 @@ pub fn run() {
shortcut::get_current_shortcut, shortcut::get_current_shortcut,
change_autostart, change_autostart,
hide_coco, hide_coco,
switch_tray_icon, // switch_tray_icon,
server::servers::add_coco_server, server::servers::add_coco_server,
server::servers::remove_coco_server, server::servers::remove_coco_server,
server::servers::list_coco_servers, server::servers::list_coco_servers,
@@ -158,9 +158,31 @@ pub fn run() {
setup::default(app, main_window.clone(), settings_window.clone()); setup::default(app, main_window.clone(), settings_window.clone());
Ok(()) Ok(())
}) }).on_window_event(|window, event| match event {
.run(ctx) WindowEvent::CloseRequested { api, .. } => {
dbg!("Close requested event received");
window.hide().unwrap();
api.prevent_close();
}
_ => {}
}).build(ctx)
.expect("error while running tauri application"); .expect("error while running tauri application");
app.run(|app_handle, event| match event {
#[cfg(target_os = "macos")]
tauri::RunEvent::Reopen {
has_visible_windows,
..
} => {
dbg!("Reopen event received: has_visible_windows = {}", has_visible_windows);
if has_visible_windows {
return;
}
}
_ => {
let _ = app_handle;
}
});
} }
pub async fn init<R: Runtime>(app_handle: &AppHandle<R>) { pub async fn init<R: Runtime>(app_handle: &AppHandle<R>) {
@@ -253,7 +275,7 @@ fn hide_coco(app: tauri::AppHandle) {
} }
fn handle_open_coco(app: &AppHandle) { fn handle_open_coco(app: &AppHandle) {
// println!("Open Coco menu clicked!"); println!("Open Coco menu clicked!");
if let Some(window) = app.get_window(MAIN_WINDOW_LABEL) { if let Some(window) = app.get_window(MAIN_WINDOW_LABEL) {
window.show().unwrap(); window.show().unwrap();
@@ -279,36 +301,36 @@ fn handle_hide_coco(app: &AppHandle) {
} }
} }
#[tauri::command] // #[tauri::command]
fn switch_tray_icon(app: tauri::AppHandle, is_dark_mode: bool) { // fn switch_tray_icon(app: tauri::AppHandle, is_dark_mode: bool) {
let app_handle = app.app_handle(); // let app_handle = app.app_handle();
//
// println!("is_dark_mode: {}", is_dark_mode); // // println!("is_dark_mode: {}", is_dark_mode);
//
const DARK_ICON_PATH: &[u8] = include_bytes!("../icons/dark@2x.png"); // const DARK_ICON_PATH: &[u8] = include_bytes!("../icons/dark@2x.png");
const LIGHT_ICON_PATH: &[u8] = include_bytes!("../icons/light@2x.png"); // const LIGHT_ICON_PATH: &[u8] = include_bytes!("../icons/light@2x.png");
//
let icon_path: &[u8] = if is_dark_mode { // let icon_path: &[u8] = if is_dark_mode {
DARK_ICON_PATH // DARK_ICON_PATH
} else { // } else {
LIGHT_ICON_PATH // LIGHT_ICON_PATH
}; // };
//
let tray = match app_handle.tray_by_id("tray") { // let tray = match app_handle.tray_by_id("tray") {
Some(tray) => tray, // Some(tray) => tray,
None => { // None => {
eprintln!("Tray with ID 'tray' not found"); // eprintln!("Tray with ID 'tray' not found");
return; // return;
} // }
}; // };
//
if let Err(e) = tray.set_icon(Some( // if let Err(e) = tray.set_icon(Some(
tauri::image::Image::from_bytes(icon_path) // tauri::image::Image::from_bytes(icon_path)
.unwrap_or_else(|e| panic!("Failed to load icon from bytes: {}", e)), // .unwrap_or_else(|e| panic!("Failed to load icon from bytes: {}", e)),
)) { // )) {
eprintln!("Failed to set tray icon: {}", e); // eprintln!("Failed to set tray icon: {}", e);
} // }
} // }
fn enable_tray(app: &mut tauri::App) { fn enable_tray(app: &mut tauri::App) {
use tauri::{ use tauri::{
@@ -335,8 +357,9 @@ fn enable_tray(app: &mut tauri::App) {
.unwrap(); .unwrap();
let _tray = TrayIconBuilder::with_id("tray") let _tray = TrayIconBuilder::with_id("tray")
.icon_as_template(true)
// .icon(app.default_window_icon().unwrap().clone()) // .icon(app.default_window_icon().unwrap().clone())
.icon(Image::from_bytes(include_bytes!("../icons/light@2x.png")).expect("REASON")) .icon(Image::from_bytes(include_bytes!("../assets/tray-mac.ico")).expect("Failed to load icon"))
.menu(&menu) .menu(&menu)
.on_menu_event(|app, event| match event.id.as_ref() { .on_menu_event(|app, event| match event.id.as_ref() {
"open" => { "open" => {
@@ -351,7 +374,7 @@ fn enable_tray(app: &mut tauri::App) {
"settings" => { "settings" => {
// windows failed to open second window, issue: https://github.com/tauri-apps/tauri/issues/11144 https://github.com/tauri-apps/tauri/issues/8196 // windows failed to open second window, issue: https://github.com/tauri-apps/tauri/issues/11144 https://github.com/tauri-apps/tauri/issues/8196
//#[cfg(windows)] //#[cfg(windows)]
let _ = app.emit("open_settings", ""); let _ = app.emit("open_settings", "settings");
// #[cfg(not(windows))] // #[cfg(not(windows))]
// open_settings(&app); // open_settings(&app);

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2.0.0", "$schema": "https://schema.tauri.app/config/2.0.0",
"productName": "Coco AI", "productName": "Coco AI",
"version": "0.1.0", "version": "../package.json",
"identifier": "rs.coco.app", "identifier": "rs.coco.app",
"build": { "build": {
"beforeDevCommand": "pnpm dev", "beforeDevCommand": "pnpm dev",
@@ -23,12 +23,13 @@
"maximizable": false, "maximizable": false,
"skipTaskbar": true, "skipTaskbar": true,
"resizable": false, "resizable": false,
"alwaysOnTop": false, "alwaysOnTop": true,
"acceptFirstMouse": true, "acceptFirstMouse": true,
"shadow": true, "shadow": true,
"transparent": true, "transparent": true,
"fullscreen": false, "fullscreen": false,
"center": false, "center": false,
"visible": false,
"windowEffects": { "windowEffects": {
"effects": [], "effects": [],
"radius": 12 "radius": 12
@@ -43,7 +44,8 @@
"transparent": true, "transparent": true,
"maximizable": false, "maximizable": false,
"skipTaskbar": false, "skipTaskbar": false,
"dragDropEnabled": true, "dragDropEnabled": false,
"hiddenTitle": true,
"visible": false, "visible": false,
"windowEffects": { "windowEffects": {
"effects": [ "effects": [
@@ -54,17 +56,30 @@
} }
], ],
"security": { "security": {
"csp": null "csp": null,
"dangerousDisableAssetCspModification": true,
"assetProtocol": {
"enable": true,
"scope": {
"allow": [
"**/*"
],
"requireLiteralLeadingDot": false
}
}
} }
}, },
"bundle": { "bundle": {
"active": true, "active": true,
"targets": "all", "targets": [
"windows": { "nsis",
"certificateThumbprint": null, "dmg",
"digestAlgorithm": "sha256", "app",
"timestampUrl": "" "appimage",
}, "deb",
"rpm"
],
"shortDescription": "Coco AI",
"icon": [ "icon": [
"icons/32x32.png", "icons/32x32.png",
"icons/128x128.png", "icons/128x128.png",
@@ -108,6 +123,12 @@
] ]
}, },
"window": {}, "window": {},
"updater": {
"pubkey": "",
"endpoints": [
"https://api.coco.rs/update"
]
},
"websocket": {}, "websocket": {},
"shell": {}, "shell": {},
"globalShortcut": {}, "globalShortcut": {},

View File

@@ -0,0 +1,15 @@
{
"identifier": "rs.coco.app",
"bundle": {
"linux": {
"deb": {
"depends": [],
"desktopTemplate": "./Coco.desktop"
},
"rpm": {
"depends": [],
"desktopTemplate": "./Coco.desktop"
}
}
}
}

View File

@@ -0,0 +1,11 @@
{
"identifier": "rs.coco.app",
"bundle": {
"externalBin": [
],
"resources": [
"assets/tray-mac.ico",
"assets/drag-icon.png"
]
}
}

View File

@@ -0,0 +1,15 @@
{
"identifier": "rs.coco.app",
"bundle": {
"externalBin": [
],
"windows": {
"digestAlgorithm": "sha256",
"nsis": {
"languages": [
],
"installMode": "both"
}
}
}
}

View File

@@ -1,24 +1,16 @@
import { import {ArrowBigLeft, AudioLines, Image, Library, Mic, Plus, Search, Send,} from "lucide-react";
Library, import {useCallback, useEffect, useRef, useState} from "react";
Mic, import {listen} from "@tauri-apps/api/event";
Send, import {invoke, isTauri} from "@tauri-apps/api/core";
Plus,
AudioLines,
Image,
ArrowBigLeft,
Search,
} from "lucide-react";
import { useRef, useState, useEffect, useCallback } from "react";
import { listen } from "@tauri-apps/api/event";
import { isTauri } from "@tauri-apps/api/core";
import ChatSwitch from "@/components/Common/ChatSwitch"; import ChatSwitch from "@/components/Common/ChatSwitch";
import AutoResizeTextarea from "./AutoResizeTextarea"; import AutoResizeTextarea from "./AutoResizeTextarea";
import { useChatStore } from "@/stores/chatStore"; import {useChatStore} from "@/stores/chatStore";
import StopIcon from "@/icons/Stop"; import StopIcon from "@/icons/Stop";
import { useAppStore } from "@/stores/appStore"; import {useAppStore} from "@/stores/appStore";
import { useSearchStore } from "@/stores/searchStore"; import {useSearchStore} from "@/stores/searchStore";
import { metaOrCtrlKey } from "@/utils/keyboardUtils"; import {metaOrCtrlKey} from "@/utils/keyboardUtils";
interface ChatInputProps { interface ChatInputProps {
onSend: (message: string) => void; onSend: (message: string) => void;
disabled: boolean; disabled: boolean;
@@ -39,7 +31,7 @@ export default function ChatInput({
changeInput, changeInput,
disabledChange, disabledChange,
reconnect, reconnect,
}: ChatInputProps) { }: ChatInputProps) {
const showTooltip = useAppStore((state: { showTooltip: boolean }) => state.showTooltip); const showTooltip = useAppStore((state: { showTooltip: boolean }) => state.showTooltip);
const sourceData = useSearchStore((state: { sourceData: any; }) => state.sourceData); const sourceData = useSearchStore((state: { sourceData: any; }) => state.sourceData);
@@ -52,7 +44,7 @@ export default function ChatInput({
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const textareaRef = useRef<{ reset: () => void; focus: () => void }>(null); const textareaRef = useRef<{ reset: () => void; focus: () => void }>(null);
const { curChatEnd, connected } = useChatStore(); const {curChatEnd, connected} = useChatStore();
const [isCommandPressed, setIsCommandPressed] = useState(false); const [isCommandPressed, setIsCommandPressed] = useState(false);
@@ -76,6 +68,23 @@ export default function ChatInput({
const handleKeyDown = useCallback( const handleKeyDown = useCallback(
(e: KeyboardEvent) => { (e: KeyboardEvent) => {
console.log("handleKeyDown", e.code, e.key); console.log("handleKeyDown", e.code, e.key);
if (e.key === "Escape") {
console.log("Escape:" + inputValue);
if (inputValue) {
changeInput("");
return;
} else {
console.log("empty value, but Escape key pressed.");
invoke("hide_coco").then(() => {
console.log("Hide Coco");
}).finally(() => {
console.log("Hide Coco");
})
}
}
pressedKeys.add(e.key); pressedKeys.add(e.key);
if (e.key === metaOrCtrlKey()) { if (e.key === metaOrCtrlKey()) {
@@ -198,7 +207,8 @@ export default function ChatInput({
return ( return (
<div className="w-full relative"> <div className="w-full relative">
<div className="p-2 flex items-center dark:text-[#D8D8D8] bg-[#ededed] dark:bg-[#202126] rounded transition-all relative"> <div
className="p-2 flex items-center dark:text-[#D8D8D8] bg-[#ededed] dark:bg-[#202126] rounded transition-all relative">
<div className="flex flex-wrap gap-2 flex-1 items-center relative"> <div className="flex flex-wrap gap-2 flex-1 items-center relative">
{!isChatMode && !sourceData ? ( {!isChatMode && !sourceData ? (
<Search <Search
@@ -264,7 +274,7 @@ export default function ChatInput({
className="p-1 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-full transition-colors" className="p-1 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-full transition-colors"
type="button" type="button"
> >
<Mic className="w-4 h-4 text-[#999] dark:text-[#999]" /> <Mic className="w-4 h-4 text-[#999] dark:text-[#999]"/>
</button> </button>
) : null} ) : null}
@@ -277,7 +287,7 @@ export default function ChatInput({
type="submit" type="submit"
onClick={() => onSend(inputValue.trim())} onClick={() => onSend(inputValue.trim())}
> >
<Send className="w-4 h-4 text-white" /> <Send className="w-4 h-4 text-white"/>
</button> </button>
) : null} ) : null}
{isChatMode && !curChatEnd ? ( {isChatMode && !curChatEnd ? (
@@ -311,7 +321,8 @@ export default function ChatInput({
) : null} ) : null}
{!connected && isChatMode ? ( {!connected && isChatMode ? (
<div className="absolute top-0 right-0 bottom-0 left-0 px-2 py-4 bg-red-500/10 rounded-md font-normal text-xs text-gray-400 flex items-center gap-4"> <div
className="absolute top-0 right-0 bottom-0 left-0 px-2 py-4 bg-red-500/10 rounded-md font-normal text-xs text-gray-400 flex items-center gap-4">
Unable to connect to the server Unable to connect to the server
<div <div
className="w-[96px] h-[24px] bg-[#0061FF] rounded-[12px] font-normal text-xs text-white flex items-center justify-center cursor-pointer" className="w-[96px] h-[24px] bg-[#0061FF] rounded-[12px] font-normal text-xs text-white flex items-center justify-center cursor-pointer"
@@ -333,11 +344,12 @@ export default function ChatInput({
className="inline-flex items-center rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors relative" className="inline-flex items-center rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors relative"
onClick={openChatAI} onClick={openChatAI}
> >
<Library className="w-4 h-4 mr-1 text-[#000] dark:text-[#d8d8d8]" /> <Library className="w-4 h-4 mr-1 text-[#000] dark:text-[#d8d8d8]"/>
Coco Coco
</button> </button>
<button className="inline-flex items-center rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-color relative"> <button
<Plus className="w-4 h-4 mr-1 text-[#000] dark:text-[#d8d8d8]" /> className="inline-flex items-center rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-color relative">
<Plus className="w-4 h-4 mr-1 text-[#000] dark:text-[#d8d8d8]"/>
Upload Upload
</button> </button>
{showTooltip && isCommandPressed ? ( {showTooltip && isCommandPressed ? (
@@ -361,10 +373,11 @@ export default function ChatInput({
className="inline-flex items-center rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors relative" className="inline-flex items-center rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors relative"
onClick={openChatAI} onClick={openChatAI}
> >
<AudioLines className="w-4 h-4 text-[#000] dark:text-[#d8d8d8]" /> <AudioLines className="w-4 h-4 text-[#000] dark:text-[#d8d8d8]"/>
</button> </button>
<button className="inline-flex items-center rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-color relative"> <button
<Image className="w-4 h-4 text-[#000] dark:text-[#d8d8d8]" /> className="inline-flex items-center rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-color relative">
<Image className="w-4 h-4 text-[#000] dark:text-[#d8d8d8]"/>
</button> </button>
{showTooltip && isCommandPressed ? ( {showTooltip && isCommandPressed ? (
<div <div

View File

@@ -1,19 +1,23 @@
import { useEffect } from "react"; import {useEffect} from "react";
import { isTauri, invoke } from "@tauri-apps/api/core"; import {invoke, isTauri} from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event"; import {listen} from "@tauri-apps/api/event";
const useEscape = () => { const useEscape = () => {
const handleEscape = async (event: KeyboardEvent) => { const handleEscape = async (event: KeyboardEvent) => {
if (event.key === "Escape") { if (event.key === "Escape") {
console.log("Escape key pressed.");
event.preventDefault(); event.preventDefault();
// Hide the Tauri app window when 'Esc' is pressed // Hide the Tauri app window when 'Esc' is pressed
await invoke("hide_coco"); await invoke("hide_coco");
console.log("App window hidden successfully."); console.log("App window hidden successfully.");
} }
}; };
useEffect(() => { useEffect(() => {
if(!isTauri()) return; if (!isTauri()) return;
const unlisten = listen("tauri://focus", () => { const unlisten = listen("tauri://focus", () => {
// Add event listener for keydown // Add event listener for keydown

View File

@@ -1,13 +1,13 @@
import { useState, useRef, useEffect } from "react"; import {useEffect, useRef, useState} from "react";
import { isTauri } from "@tauri-apps/api/core"; import {invoke, isTauri} from "@tauri-apps/api/core";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; import {getCurrentWebviewWindow} from "@tauri-apps/api/webviewWindow";
import { LogicalSize } from "@tauri-apps/api/dpi"; import {LogicalSize} from "@tauri-apps/api/dpi";
import InputBox from "@/components/Search/InputBox"; import InputBox from "@/components/Search/InputBox";
import Search from "@/components/Search/Search"; import Search from "@/components/Search/Search";
import ChatAI, { ChatAIRef } from "@/components/Assistant/Chat"; import ChatAI, {ChatAIRef} from "@/components/Assistant/Chat";
import { useAppStore } from "@/stores/appStore"; import {useAppStore} from "@/stores/appStore";
import { useAuthStore } from "@/stores/authStore"; import {useAuthStore} from "@/stores/authStore";
import ApiDetails from "@/components/Common/ApiDetails"; import ApiDetails from "@/components/Common/ApiDetails";
export default function DesktopApp() { export default function DesktopApp() {
@@ -19,6 +19,31 @@ export default function DesktopApp() {
useEffect(() => { useEffect(() => {
initializeListeners(); initializeListeners();
initializeListeners_auth(); initializeListeners_auth();
// Listen for window focus and blur events
const handleBlur = () => {
console.log("Window blurred");
invoke('hide_coco').then(() => {
console.log("Hide Coco");
}).finally(() => {
console.log("Hide Coco");
});
};
const handleFocus = () => {
// Optionally, show the window if needed when focus is regained
console.log("Window focused");
};
window.addEventListener('blur', handleBlur);
window.addEventListener('focus', handleFocus);
// Clean up event listeners on component unmount
return () => {
window.removeEventListener('blur', handleBlur);
window.removeEventListener('focus', handleFocus);
};
}, []); }, []);
const chatAIRef = useRef<ChatAIRef>(null); const chatAIRef = useRef<ChatAIRef>(null);
@@ -118,7 +143,7 @@ export default function DesktopApp() {
) : null} ) : null}
</div> </div>
<ApiDetails /> <ApiDetails/>
</div> </div>
); );
} }