diff --git a/.vscode/launch.json b/.vscode/launch.json index eaccc38..71643e6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "go", "request": "launch", "mode": "auto", - "program": "${workspaceFolder}/main.go", + "program": "${workspaceFolder}", "preLaunchTask": "build frontend" } ] diff --git a/frontend/bindings/mesh-drop/internal/config/config.ts b/frontend/bindings/mesh-drop/internal/config/config.ts index 51b16c0..6483ab8 100644 --- a/frontend/bindings/mesh-drop/internal/config/config.ts +++ b/frontend/bindings/mesh-drop/internal/config/config.ts @@ -9,8 +9,8 @@ import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Cr // @ts-ignore: Unused imports import * as $models from "./models.js"; -export function AddTrustedPeer(peerID: string, publicKey: string): $CancellablePromise { - return $Call.ByID(2866399505, peerID, publicKey); +export function AddTrust(peerID: string, publicKey: string): $CancellablePromise { + return $Call.ByID(2986105628, peerID, publicKey); } export function GetAutoAccept(): $CancellablePromise { @@ -41,8 +41,8 @@ export function GetSavePath(): $CancellablePromise { return $Call.ByID(4081533263); } -export function GetTrustedPeer(): $CancellablePromise<{ [_: string]: string }> { - return $Call.ByID(1253442080).then(($result: any) => { +export function GetTrusted(): $CancellablePromise<{ [_: string]: string }> { + return $Call.ByID(800326956).then(($result: any) => { return $$createType0($result); }); } @@ -57,12 +57,12 @@ export function GetWindowState(): $CancellablePromise<$models.WindowState> { }); } -export function IsTrustedPeer(peerID: string): $CancellablePromise { - return $Call.ByID(3452062706, peerID); +export function IsTrusted(peerID: string): $CancellablePromise { + return $Call.ByID(1255607538, peerID); } -export function RemoveTrustedPeer(peerID: string): $CancellablePromise { - return $Call.ByID(909233322, peerID); +export function RemoveTrust(peerID: string): $CancellablePromise { + return $Call.ByID(732981195, peerID); } /** diff --git a/frontend/src/components/PeerCard.vue b/frontend/src/components/PeerCard.vue index 9d909e9..f0eade7 100644 --- a/frontend/src/components/PeerCard.vue +++ b/frontend/src/components/PeerCard.vue @@ -15,15 +15,15 @@ import { } from "../../bindings/mesh-drop/internal/transfer/service"; import { Peer } from "../../bindings/mesh-drop/internal/discovery/models"; import { - IsTrustedPeer, - AddTrustedPeer, - RemoveTrustedPeer, + IsTrusted, + AddTrust, + RemoveTrust, } from "../../bindings/mesh-drop/internal/config/config"; // --- 生命周期 --- onMounted(async () => { try { - isTrusted.value = await IsTrustedPeer(props.peer.id); + isTrusted.value = await IsTrusted(props.peer.id); } catch (err) { console.error("Failed to check trusted peer status:", err); } @@ -136,7 +136,7 @@ const handleSendFolder = async () => { SendFolder(props.peer, selectedIp.value, folderPath as string).catch((e) => { console.error(e); - alert("Failed to send folder: " + e); + alert(t("discover.sendFolderFailed", { error: e })); }); emit("transferStarted"); }; @@ -150,19 +150,21 @@ const handleSendClipboard = async () => { } SendText(props.peer, selectedIp.value, text).catch((e) => { console.error(e); - alert("Failed to send clipboard: " + e); + alert(t("discover.sendClipboardFailed", { error: e })); }); emit("transferStarted"); }; const handleTrust = () => { - AddTrustedPeer(props.peer.id, props.peer.pk); + AddTrust(props.peer.id, props.peer.pk); isTrusted.value = true; }; const handleUntrust = () => { - RemoveTrustedPeer(props.peer.id); + RemoveTrust(props.peer.id); isTrusted.value = false; + + props.peer.trust_mismatch = false; }; @@ -226,9 +228,9 @@ const handleUntrust = () => { variant="tonal" prepend-icon="mdi-alert" :ripple="false" - style="pointer-events: none" + style="pointer-events: none; min-width: 0" > - {{ t("discover.mismatch") }} + {{ t("discover.mismatch") }} diff --git a/frontend/src/components/SettingsView.vue b/frontend/src/components/SettingsView.vue index c80f172..8e3fae1 100644 --- a/frontend/src/components/SettingsView.vue +++ b/frontend/src/components/SettingsView.vue @@ -51,7 +51,7 @@ onMounted(async () => { // --- 方法 --- const changeSavePath = async () => { const opts: Dialogs.OpenFileDialogOptions = { - Title: "Select Save Path", + Title: t("settings.selectSavePath"), CanChooseDirectories: true, CanChooseFiles: false, AllowsMultipleSelection: false, diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 2c88069..5f7ffb0 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -7,7 +7,10 @@ "edit": "Edit", "loading": "Loading...", "success": "Success", - "error": "Error" + "error": "Error", + "accept": "Accept", + "reject": "Reject", + "copy": "Copy" }, "menu": { "discover": "Discover", @@ -17,7 +20,20 @@ "discover": { "scanning": "Scanning for peers...", "noPeers": "No peers found", - "send": "Send" + "send": "Send", + "sendFiles": "Send Files", + "sendFolder": "Send Folder", + "sendText": "Send Text", + "sendClipboard": "Send Clipboard", + "selectFolder": "Select Folder", + "clipboardEmpty": "Clipboard is empty", + "noRoute": "No Route", + "mismatch": "Trust Mismatch", + "resetTrust": "Reset Trust", + "trustPeer": "Trust Peer", + "untrustPeer": "Untrust Peer", + "sendFolderFailed": "Failed to send folder: {error}", + "sendClipboardFailed": "Failed to send clipboard: {error}" }, "transfers": { "noTransfers": "No transfers yet", @@ -26,7 +42,16 @@ "transferring": "Transferring", "completed": "Completed", "failed": "Failed", - "cancelled": "Cancelled" + "cancelled": "Cancelled", + "selectSavePath": "Select Save Path", + "text": "Text", + "folder": "Folder", + "securityAlert": "Security Alert", + "rejected": "Rejected", + "waitingForAccept": "Waiting for accept", + "saveToFolder": "Save to Folder", + "viewContent": "View Content", + "textContent": "Text Content" }, "settings": { "savePath": "Save Path", @@ -35,6 +60,7 @@ "saveHistory": "Save History", "autoAccept": "Auto Accept", "version": "Version", - "language": "Language" + "language": "Language", + "selectSavePath": "Select Save Path" } } \ No newline at end of file diff --git a/frontend/src/locales/zh-Hans.json b/frontend/src/locales/zh-Hans.json index 7a1c68b..25ffa7f 100644 --- a/frontend/src/locales/zh-Hans.json +++ b/frontend/src/locales/zh-Hans.json @@ -7,7 +7,10 @@ "edit": "编辑", "loading": "加载中...", "success": "成功", - "error": "错误" + "error": "错误", + "accept": "接收", + "reject": "拒绝", + "copy": "复制" }, "menu": { "discover": "发现", @@ -17,7 +20,20 @@ "discover": { "scanning": "正在扫描设备...", "noPeers": "未发现设备", - "send": "发送" + "send": "发送", + "sendFiles": "发送文件", + "sendFolder": "发送文件夹", + "sendText": "发送文本", + "sendClipboard": "发送剪贴板", + "selectFolder": "选择文件夹", + "clipboardEmpty": "剪贴板为空", + "noRoute": "不可达", + "mismatch": "信任不匹配", + "resetTrust": "重置信任", + "trustPeer": "信任设备", + "untrustPeer": "取消信任", + "sendFolderFailed": "发送文件夹失败: {error}", + "sendClipboardFailed": "发送剪贴板失败: {error}" }, "transfers": { "noTransfers": "暂无传输记录", @@ -26,7 +42,16 @@ "transferring": "传输中", "completed": "已完成", "failed": "失败", - "cancelled": "已取消" + "cancelled": "已取消", + "selectSavePath": "选择保存路径", + "text": "文本", + "folder": "文件夹", + "securityAlert": "安全警告", + "rejected": "已拒绝", + "waitingForAccept": "等待接收", + "saveToFolder": "保存到文件夹", + "viewContent": "查看内容", + "textContent": "文本内容" }, "settings": { "savePath": "保存路径", @@ -35,6 +60,7 @@ "saveHistory": "保存历史记录", "autoAccept": "自动接收", "version": "版本", - "language": "语言" + "language": "语言", + "selectSavePath": "选择保存路径" } } \ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go index c8b50ca..d563515 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -254,7 +254,7 @@ func (c *Config) GetWindowState() WindowState { return c.WindowState } -func (c *Config) AddTrustedPeer(peerID string, publicKey string) { +func (c *Config) AddTrust(peerID string, publicKey string) { c.mu.Lock() defer c.mu.Unlock() if c.TrustedPeer == nil { @@ -265,13 +265,13 @@ func (c *Config) AddTrustedPeer(peerID string, publicKey string) { _ = c.save() } -func (c *Config) GetTrustedPeer() map[string]string { +func (c *Config) GetTrusted() map[string]string { c.mu.RLock() defer c.mu.RUnlock() return c.TrustedPeer } -func (c *Config) RemoveTrustedPeer(peerID string) { +func (c *Config) RemoveTrust(peerID string) { c.mu.Lock() defer c.mu.Unlock() delete(c.TrustedPeer, peerID) @@ -279,7 +279,7 @@ func (c *Config) RemoveTrustedPeer(peerID string) { _ = c.save() } -func (c *Config) IsTrustedPeer(peerID string) bool { +func (c *Config) IsTrusted(peerID string) bool { c.mu.RLock() defer c.mu.RUnlock() _, exists := c.TrustedPeer[peerID] diff --git a/internal/discovery/service.go b/internal/discovery/service.go index a379321..b3cda8e 100644 --- a/internal/discovery/service.go +++ b/internal/discovery/service.go @@ -228,7 +228,7 @@ func (s *Service) startListening() { // 验证身份一致性 (防止 ID 欺骗) trustMismatch := false - trustedKeys := s.config.GetTrustedPeer() + trustedKeys := s.config.GetTrusted() if knownKey, ok := trustedKeys[packet.ID]; ok { if knownKey != packet.PublicKey { slog.Warn("SECURITY ALERT: Peer ID mismatch with known public key (Spoofing attempt?)", "id", packet.ID, "known_key", knownKey, "received_key", packet.PublicKey) @@ -236,6 +236,13 @@ func (s *Service) startListening() { // 当发现 ID 欺骗时,不更新 peer,而是标记为 trustMismatch // 用户可以手动重新添加信任 } + } else { + // 不存在于信任列表 + // 存在之前在信任列表,但是不匹配被用户手动重置了,此时需要将 peer.TrustMismatch 标记为 false + // 否则在 handleHeartbeat 里会一直标记为不匹配 + if peer, ok := s.peers[packet.ID]; ok { + peer.TrustMismatch = false + } } s.handleHeartbeat(packet, remoteAddr.IP.String(), trustMismatch) diff --git a/internal/transfer/server.go b/internal/transfer/server.go index cc75ec6..ba522f7 100644 --- a/internal/transfer/server.go +++ b/internal/transfer/server.go @@ -49,7 +49,7 @@ func (s *Service) handleAsk(c *gin.Context) { task.Sender.TrustMismatch = peer.TrustMismatch } - if s.config.GetAutoAccept() || (s.config.IsTrustedPeer(task.Sender.ID) && !task.Sender.TrustMismatch) { + if s.config.GetAutoAccept() || (s.config.IsTrusted(task.Sender.ID) && !task.Sender.TrustMismatch) { task.DecisionChan <- Decision{ ID: task.ID, Accepted: true, diff --git a/main.go b/main.go index 31749a7..efca95b 100644 --- a/main.go +++ b/main.go @@ -81,6 +81,9 @@ func main() { }, }) + // 设置系统托盘 + setupSystray(app, window) + // 窗口文件拖拽事件 window.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { files := event.Context().DroppedFiles() @@ -91,10 +94,6 @@ func main() { }) }) - // 窗口关闭事件 - // window.OnWindowEvent(events.Common.WindowClosing, func(event *application.WindowEvent) { - // }) - // 应用关闭事件 app.OnShutdown(func() { x, y := window.Position() diff --git a/systray.go b/systray.go new file mode 100644 index 0000000..6b73924 --- /dev/null +++ b/systray.go @@ -0,0 +1,7 @@ +//go:build !windows + +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +func setupSystray(app *application.App, window *application.WebviewWindow) {} diff --git a/systray_windows.go b/systray_windows.go new file mode 100644 index 0000000..6b1c1cb --- /dev/null +++ b/systray_windows.go @@ -0,0 +1,35 @@ +//go:build windows + +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func setupSystray(app *application.App, window *application.WebviewWindow) { + systray := app.SystemTray.New() + systray.SetIcon(icon) + systray.SetLabel("Mesh Drop") + + menu := app.NewMenu() + menu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + systray.OnClick(func() { + if window.IsVisible() { + window.Hide() + } else { + window.Show() + window.Focus() + } + }) + + systray.SetMenu(menu) + + window.OnWindowEvent(events.Common.WindowClosing, func(event *application.WindowEvent) { + event.Cancel() + window.Hide() + }) +}