Compare commits

1 Commits

Author SHA1 Message Date
ae0ab09b48 add: .golangci.yml 2026-02-11 04:21:54 +08:00
10 changed files with 247 additions and 66 deletions

23
.golangci.yml Normal file
View File

@@ -0,0 +1,23 @@
version: "2"
linters:
default: standard
enable:
- staticcheck
- gosec
exclusions:
rules:
- linters:
- gosec
text: "G304:"
- linters:
- errcheck
text: "is not checked"
formatters:
enable:
- gofmt
- gofumpt
- goimports
- gci
- golines
output:
path-mode: abs

View File

@@ -3,12 +3,12 @@ package config
import ( import (
"encoding/json" "encoding/json"
"log/slog" "log/slog"
"mesh-drop/internal/security"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
"github.com/google/uuid" "github.com/google/uuid"
"mesh-drop/internal/security"
) )
// WindowState 定义窗口状态 // WindowState 定义窗口状态
@@ -66,7 +66,7 @@ func GetUserHomeDir() string {
// New 读取配置 // New 读取配置
func Load(defaultState WindowState) *Config { func Load(defaultState WindowState) *Config {
configDir := GetConfigDir() configDir := GetConfigDir()
_ = os.MkdirAll(configDir, 0755) _ = os.MkdirAll(configDir, 0o750)
configFile := filepath.Join(configDir, "config.json") configFile := filepath.Join(configDir, "config.json")
// 设置默认值 // 设置默认值
@@ -88,7 +88,9 @@ func Load(defaultState WindowState) *Config {
TrustedPeer: make(map[string]string), TrustedPeer: make(map[string]string),
} }
fileBytes, err := os.ReadFile(configFile) fileBytes, err := os.ReadFile(
configFile,
)
if err != nil { if err != nil {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
slog.Error("Failed to read config file", "error", err) slog.Error("Failed to read config file", "error", err)
@@ -107,7 +109,7 @@ func Load(defaultState WindowState) *Config {
} }
// 确保默认保存路径存在 // 确保默认保存路径存在
err = os.MkdirAll(defaultSavePath, 0755) err = os.MkdirAll(defaultSavePath, 0o750)
if err != nil { if err != nil {
slog.Error("Failed to create default save path", "path", defaultSavePath, "error", err) slog.Error("Failed to create default save path", "path", defaultSavePath, "error", err)
} }
@@ -145,7 +147,7 @@ func (c *Config) Save() error {
func (c *Config) save() error { func (c *Config) save() error {
dir := filepath.Dir(c.configPath) dir := filepath.Dir(c.configPath)
if err := os.MkdirAll(dir, 0755); err != nil { if err := os.MkdirAll(dir, 0o750); err != nil {
return err return err
} }
@@ -156,7 +158,7 @@ func (c *Config) save() error {
// 设置配置文件权限为 0600 (仅所有者读写) // 设置配置文件权限为 0600 (仅所有者读写)
if c.configPath != "" { if c.configPath != "" {
if err := os.WriteFile(c.configPath, jsonData, 0600); err != nil { if err := os.WriteFile(c.configPath, jsonData, 0o600); err != nil {
slog.Warn("Failed to write config file", "error", err) slog.Warn("Failed to write config file", "error", err)
return err return err
} }
@@ -181,7 +183,7 @@ func (c *Config) update(fn func()) {
func (c *Config) SetSavePath(savePath string) { func (c *Config) SetSavePath(savePath string) {
c.update(func() { c.update(func() {
c.data.SavePath = savePath c.data.SavePath = savePath
_ = os.MkdirAll(savePath, 0755) _ = os.MkdirAll(savePath, 0o750)
}) })
} }

View File

@@ -4,8 +4,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log/slog" "log/slog"
"mesh-drop/internal/config"
"mesh-drop/internal/security"
"net" "net"
"runtime" "runtime"
"sort" "sort"
@@ -13,6 +11,8 @@ import (
"time" "time"
"github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/application"
"mesh-drop/internal/config"
"mesh-drop/internal/security"
) )
const ( const (
@@ -112,7 +112,13 @@ func (s *Service) GetLocalIPInSameSubnet(receiverIP string) (string, bool) {
} }
} }
} }
slog.Error("Failed to get local IP in same subnet", "receiverIP", receiverIP, "component", "discovery") slog.Error(
"Failed to get local IP in same subnet",
"receiverIP",
receiverIP,
"component",
"discovery",
)
return "", false return "", false
} }
@@ -222,7 +228,13 @@ func (s *Service) startListening() {
sigData := packet.SignPayload() sigData := packet.SignPayload()
valid, err := security.Verify(packet.PublicKey, sigData, sig) valid, err := security.Verify(packet.PublicKey, sigData, sig)
if err != nil || !valid { if err != nil || !valid {
slog.Warn("Received invalid discovery packet signature", "id", packet.ID, "ip", remoteAddr.IP.String()) slog.Warn(
"Received invalid discovery packet signature",
"id",
packet.ID,
"ip",
remoteAddr.IP.String(),
)
continue continue
} }
@@ -231,7 +243,15 @@ func (s *Service) startListening() {
trustedKeys := s.config.GetTrusted() trustedKeys := s.config.GetTrusted()
if knownKey, ok := trustedKeys[packet.ID]; ok { if knownKey, ok := trustedKeys[packet.ID]; ok {
if knownKey != packet.PublicKey { 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) slog.Warn(
"SECURITY ALERT: Peer ID mismatch with known public key (Spoofing attempt?)",
"id",
packet.ID,
"known_key",
knownKey,
"received_key",
packet.PublicKey,
)
trustMismatch = true trustMismatch = true
// 当发现 ID 欺骗时,不更新 peer而是标记为 trustMismatch // 当发现 ID 欺骗时,不更新 peer而是标记为 trustMismatch
// 用户可以手动重新添加信任 // 用户可以手动重新添加信任

View File

@@ -52,7 +52,13 @@ func generateSelfSignedCert(certPath, keyPath string) error {
// 在实际的动态环境中,我们可能希望添加所有当前接口的 IP 地址 // 在实际的动态环境中,我们可能希望添加所有当前接口的 IP 地址
// 实际上,在客户端跳过 IP 验证对于本地 P2P 来说是很常见的。 // 实际上,在客户端跳过 IP 验证对于本地 P2P 来说是很常见的。
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) derBytes, err := x509.CreateCertificate(
rand.Reader,
&template,
&template,
&priv.PublicKey,
priv,
)
if err != nil { if err != nil {
return err return err
} }
@@ -73,7 +79,10 @@ func generateSelfSignedCert(certPath, keyPath string) error {
return err return err
} }
defer keyOut.Close() defer keyOut.Close()
if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil { if err := pem.Encode(
keyOut,
&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)},
); err != nil {
return err return err
} }

View File

@@ -10,13 +10,13 @@ import (
"io" "io"
"log/slog" "log/slog"
"math" "math"
"mesh-drop/internal/discovery"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"github.com/google/uuid" "github.com/google/uuid"
"mesh-drop/internal/discovery"
) )
func (s *Service) SendFiles(target *discovery.Peer, targetIP string, filePaths []string) { func (s *Service) SendFiles(target *discovery.Peer, targetIP string, filePaths []string) {
@@ -32,7 +32,15 @@ func (s *Service) SendFile(target *discovery.Peer, targetIP string, filePath str
file, err := os.Open(filePath) file, err := os.Open(filePath)
if err != nil { if err != nil {
slog.Error("Failed to open file", "path", filePath, "error", err, "component", "transfer-client") slog.Error(
"Failed to open file",
"path",
filePath,
"error",
err,
"component",
"transfer-client",
)
return return
} }
@@ -101,7 +109,15 @@ func (s *Service) SendFolder(target *discovery.Peer, targetIP string, folderPath
size, err := calculateTarSize(ctx, folderPath) size, err := calculateTarSize(ctx, folderPath)
if err != nil { if err != nil {
slog.Error("Failed to calculate folder size", "path", folderPath, "error", err, "component", "transfer-client") slog.Error(
"Failed to calculate folder size",
"path",
folderPath,
"error",
err,
"component",
"transfer-client",
)
return return
} }
@@ -137,7 +153,13 @@ func (s *Service) SendFolder(target *discovery.Peer, targetIP string, folderPath
go func(ctx context.Context) { go func(ctx context.Context) {
defer w.Close() defer w.Close()
if err := streamFolderToTar(ctx, w, folderPath); err != nil { if err := streamFolderToTar(ctx, w, folderPath); err != nil {
slog.Error("Failed to stream folder to tar", "error", err, "component", "transfer-client") slog.Error(
"Failed to stream folder to tar",
"error",
err,
"component",
"transfer-client",
)
w.CloseWithError(err) w.CloseWithError(err)
} }
}(ctx) }(ctx)
@@ -199,7 +221,12 @@ func (s *Service) SendText(target *discovery.Peer, targetIP string, text string)
} }
// ask 向接收端发送传输请求 // ask 向接收端发送传输请求
func (s *Service) ask(ctx context.Context, target *discovery.Peer, targetIP string, task *Transfer) (TransferAskResponse, error) { func (s *Service) ask(
ctx context.Context,
target *discovery.Peer,
targetIP string,
task *Transfer,
) (TransferAskResponse, error) {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return TransferAskResponse{}, err return TransferAskResponse{}, err
} }
@@ -232,7 +259,14 @@ func (s *Service) ask(ctx context.Context, target *discovery.Peer, targetIP stri
} }
// processTransfer 传输数据 // processTransfer 传输数据
func (s *Service) processTransfer(ctx context.Context, askResp TransferAskResponse, target *discovery.Peer, targetIP string, task *Transfer, payload io.Reader) { func (s *Service) processTransfer(
ctx context.Context,
askResp TransferAskResponse,
target *discovery.Peer,
targetIP string,
task *Transfer,
payload io.Reader,
) {
defer func() { defer func() {
s.NotifyTransferListUpdate() s.NotifyTransferListUpdate()
}() }()
@@ -240,7 +274,9 @@ func (s *Service) processTransfer(ctx context.Context, askResp TransferAskRespon
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return return
} }
uploadUrl, _ := url.Parse(fmt.Sprintf("https://%s:%d/transfer/upload/%s", targetIP, target.Port, task.ID)) uploadUrl, _ := url.Parse(
fmt.Sprintf("https://%s:%d/transfer/upload/%s", targetIP, target.Port, task.ID),
)
query := uploadUrl.Query() query := uploadUrl.Query()
query.Add("token", askResp.Token) query.Add("token", askResp.Token)
uploadUrl.RawQuery = query.Encode() uploadUrl.RawQuery = query.Encode()
@@ -273,7 +309,15 @@ func (s *Service) processTransfer(ctx context.Context, askResp TransferAskRespon
} else { } else {
task.Status = TransferStatusError task.Status = TransferStatusError
task.ErrorMsg = fmt.Sprintf("Failed to upload file: %v", err) task.ErrorMsg = fmt.Sprintf("Failed to upload file: %v", err)
slog.Error("Failed to upload file", "url", uploadUrl.String(), "error", err, "component", "transfer-client") slog.Error(
"Failed to upload file",
"url",
uploadUrl.String(),
"error",
err,
"component",
"transfer-client",
)
} }
return return
} }
@@ -384,7 +428,15 @@ func streamFolderToTar(ctx context.Context, w io.Writer, srcPath string) error {
if relPath == "." { if relPath == "." {
return nil return nil
} }
slog.Debug("Processing file", "path", path, "relPath", relPath, "component", "transfer-client") slog.Debug(
"Processing file",
"path",
path,
"relPath",
relPath,
"component",
"transfer-client",
)
header, err := tar.FileInfoHeader(info, "") header, err := tar.FileInfoHeader(info, "")
if err != nil { if err != nil {

View File

@@ -3,9 +3,10 @@ package transfer
import ( import (
"encoding/json" "encoding/json"
"log/slog" "log/slog"
"mesh-drop/internal/config"
"os" "os"
"path/filepath" "path/filepath"
"mesh-drop/internal/config"
) )
func (s *Service) SaveHistory() { func (s *Service) SaveHistory() {
@@ -24,7 +25,7 @@ func (s *Service) SaveHistory() {
} }
// 写入临时文件 // 写入临时文件
if err := os.WriteFile(tempPath, historyJson, 0644); err != nil { if err := os.WriteFile(tempPath, historyJson, 0o600); err != nil {
slog.Error("Failed to write temp history file", "error", err, "component", "transfer") slog.Error("Failed to write temp history file", "error", err, "component", "transfer")
return return
} }

View File

@@ -1,8 +1,9 @@
package transfer package transfer
import ( import (
"mesh-drop/internal/discovery"
"time" "time"
"mesh-drop/internal/discovery"
) )
type TransferStatus string type TransferStatus string
@@ -34,9 +35,9 @@ const (
// Transfer // Transfer
type Transfer struct { type Transfer struct {
ID string `json:"id" binding:"required"` // 传输会话 ID ID string `json:"id" binding:"required"` // 传输会话 ID
CreateTime int64 `json:"create_time"` // 创建时间 CreateTime int64 `json:"create_time"` // 创建时间
Sender discovery.Peer `json:"sender" binding:"required"` // 发送者 Sender discovery.Peer `json:"sender" binding:"required"` // 发送者
// FileName 如果 ContentType 为 file文件名如果 ContentType 为 folder文件夹名如果 ContentType 为 text // FileName 如果 ContentType 为 file文件名如果 ContentType 为 folder文件夹名如果 ContentType 为 text
FileName string `json:"file_name"` // 文件名 FileName string `json:"file_name"` // 文件名
FileSize int64 `json:"file_size"` // 文件大小 (字节) FileSize int64 `json:"file_size"` // 文件大小 (字节)

View File

@@ -49,7 +49,8 @@ func (s *Service) handleAsk(c *gin.Context) {
task.Sender.TrustMismatch = peer.TrustMismatch task.Sender.TrustMismatch = peer.TrustMismatch
} }
if s.config.GetAutoAccept() || (s.config.IsTrusted(task.Sender.ID) && !task.Sender.TrustMismatch) { if s.config.GetAutoAccept() ||
(s.config.IsTrusted(task.Sender.ID) && !task.Sender.TrustMismatch) {
task.DecisionChan <- Decision{ task.DecisionChan <- Decision{
ID: task.ID, ID: task.ID,
Accepted: true, Accepted: true,
@@ -179,7 +180,15 @@ func (s *Service) handleUpload(c *gin.Context) {
_, err := os.Stat(destPath) _, err := os.Stat(destPath)
counter := 1 counter := 1
for err == nil { for err == nil {
destPath = filepath.Join(savePath, fmt.Sprintf("%s (%d)%s", strings.TrimSuffix(task.FileName, filepath.Ext(task.FileName)), counter, filepath.Ext(task.FileName))) destPath = filepath.Join(
savePath,
fmt.Sprintf(
"%s (%d)%s",
strings.TrimSuffix(task.FileName, filepath.Ext(task.FileName)),
counter,
filepath.Ext(task.FileName),
),
)
counter++ counter++
_, err = os.Stat(destPath) _, err = os.Stat(destPath)
} }
@@ -227,7 +236,13 @@ func (s *Service) receive(c *gin.Context, task *Transfer, writer Writer, ctxRead
if err != nil { if err != nil {
// 发送端断线,任务取消 // 发送端断线,任务取消
if c.Request.Context().Err() != nil { if c.Request.Context().Err() != nil {
slog.Info("Sender canceled transfer (Network/Context disconnected)", "id", task.ID, "raw_err", err) slog.Info(
"Sender canceled transfer (Network/Context disconnected)",
"id",
task.ID,
"raw_err",
err,
)
task.ErrorMsg = "Sender disconnected" task.ErrorMsg = "Sender disconnected"
task.Status = TransferStatusCanceled task.Status = TransferStatusCanceled
return return
@@ -273,7 +288,12 @@ func (s *Service) receive(c *gin.Context, task *Transfer, writer Writer, ctxRead
task.Status = TransferStatusCompleted task.Status = TransferStatusCompleted
} }
func (s *Service) receiveFolder(c *gin.Context, savePath string, task *Transfer, ctxReader io.Reader) { func (s *Service) receiveFolder(
c *gin.Context,
savePath string,
task *Transfer,
ctxReader io.Reader,
) {
defer s.NotifyTransferListUpdate() defer s.NotifyTransferListUpdate()
// 创建根目录 // 创建根目录
@@ -286,7 +306,7 @@ func (s *Service) receiveFolder(c *gin.Context, savePath string, task *Transfer,
counter++ counter++
_, err = os.Stat(destPath) _, err = os.Stat(destPath)
} }
if err := os.MkdirAll(destPath, 0755); err != nil { if err := os.MkdirAll(destPath, 0o750); err != nil {
c.JSON(http.StatusInternalServerError, TransferUploadResponse{ c.JSON(http.StatusInternalServerError, TransferUploadResponse{
ID: task.ID, ID: task.ID,
Message: "Receiver failed to create folder", Message: "Receiver failed to create folder",
@@ -318,7 +338,13 @@ func (s *Service) receiveFolder(c *gin.Context, savePath string, task *Transfer,
return false return false
} }
if c.Request.Context().Err() != nil { if c.Request.Context().Err() != nil {
slog.Info("Transfer canceled by sender (Network disconnect)", "id", task.ID, "stage", stage) slog.Info(
"Transfer canceled by sender (Network disconnect)",
"id",
task.ID,
"stage",
stage,
)
task.Status = TransferStatusCanceled task.Status = TransferStatusCanceled
task.ErrorMsg = "Sender disconnected" task.ErrorMsg = "Sender disconnected"
// 发送端已断开,无需也不应再发送 c.JSON // 发送端已断开,无需也不应再发送 c.JSON
@@ -350,6 +376,14 @@ func (s *Service) receiveFolder(c *gin.Context, savePath string, task *Transfer,
return true return true
} }
// 获取绝对路径以防止 Zip Slip (G305)
// 必须先转换成绝对路径再判断
absDestPath, err := filepath.Abs(destPath)
if err != nil {
handleError(err, "resolve_abs_path")
return
}
tr := tar.NewReader(reader) tr := tar.NewReader(reader)
for { for {
header, err := tr.Next() header, err := tr.Next()
@@ -360,32 +394,52 @@ func (s *Service) receiveFolder(c *gin.Context, savePath string, task *Transfer,
return return
} }
target := filepath.Join(destPath, header.Name) target := filepath.Join(destPath, filepath.Clean(header.Name))
// 确保路径没有越界 absTarget, err := filepath.Abs(target)
if !strings.HasPrefix(target, filepath.Clean(destPath)+string(os.PathSeparator)) { if err != nil {
// 非法路径 slog.Error("Failed to resolve absolute path", "path", target, "error", err)
continue continue
} }
// 确保路径在目标目录内
if !strings.HasPrefix(absTarget, absDestPath+string(os.PathSeparator)) {
slog.Warn(
"Zip Slip attempt detected",
"header_name",
header.Name,
"resolved_path",
absTarget,
)
continue
}
// 使用安全的绝对路径
target = absTarget
switch header.Typeflag { switch header.Typeflag {
case tar.TypeDir: case tar.TypeDir:
if err := os.MkdirAll(target, 0755); err != nil { if err := os.MkdirAll(target, 0o750); err != nil {
slog.Error("Failed to create dir", "path", target, "error", err) slog.Error("Failed to create dir", "path", target, "error", err)
} }
case tar.TypeReg: case tar.TypeReg:
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) f, err := os.OpenFile(
target,
os.O_CREATE|os.O_RDWR,
os.FileMode(header.Mode),
) //nolint:gosec
if err != nil { if err != nil {
slog.Error("Failed to create file", "path", target, "error", err) slog.Error("Failed to create file", "path", target, "error", err)
continue continue
} }
// nolint: gosec
if _, err := io.Copy(f, tr); err != nil { if _, err := io.Copy(f, tr); err != nil {
f.Close() _ = f.Close()
if handleError(err, "write_file_content") { if handleError(err, "write_file_content") {
return return
} }
} }
f.Close() _ = f.Close()
} }
} }

View File

@@ -5,9 +5,6 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"log/slog" "log/slog"
"mesh-drop/internal/config"
"mesh-drop/internal/discovery"
"mesh-drop/internal/security"
"net/http" "net/http"
"path/filepath" "path/filepath"
"sort" "sort"
@@ -16,6 +13,9 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/services/notifications" "github.com/wailsapp/wails/v3/pkg/services/notifications"
"mesh-drop/internal/config"
"mesh-drop/internal/discovery"
"mesh-drop/internal/security"
) )
type Service struct { type Service struct {
@@ -37,12 +37,18 @@ type Service struct {
httpClient *http.Client httpClient *http.Client
} }
func NewService(config *config.Config, app *application.App, notifier *notifications.NotificationService, port int, discoveryService *discovery.Service) *Service { func NewService(
config *config.Config,
app *application.App,
notifier *notifications.NotificationService,
port int,
discoveryService *discovery.Service,
) *Service {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
// 配置自定义 HTTP 客户端以跳过自签名证书验证 // 配置自定义 HTTP 客户端以跳过自签名证书验证
tr := &http.Transport{ tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec
} }
httpClient := &http.Client{ httpClient := &http.Client{
Transport: tr, Transport: tr,
@@ -95,7 +101,7 @@ func (s *Service) GetTransferSyncMap() *sync.Map {
} }
func (s *Service) GetTransferList() []*Transfer { func (s *Service) GetTransferList() []*Transfer {
var requests []*Transfer = make([]*Transfer, 0) requests := make([]*Transfer, 0)
s.transfers.Range(func(key, value any) bool { s.transfers.Range(func(key, value any) bool {
transfer := value.(*Transfer) transfer := value.(*Transfer)
requests = append(requests, transfer) requests = append(requests, transfer)

47
main.go
View File

@@ -3,15 +3,15 @@ package main
import ( import (
"embed" "embed"
"log/slog" "log/slog"
"mesh-drop/internal/config"
"mesh-drop/internal/discovery"
"mesh-drop/internal/transfer"
"os" "os"
"path/filepath" "path/filepath"
"github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events" "github.com/wailsapp/wails/v3/pkg/events"
"github.com/wailsapp/wails/v3/pkg/services/notifications" "github.com/wailsapp/wails/v3/pkg/services/notifications"
"mesh-drop/internal/config"
"mesh-drop/internal/discovery"
"mesh-drop/internal/transfer"
) )
//go:embed all:frontend/dist //go:embed all:frontend/dist
@@ -67,7 +67,17 @@ func NewApp() *App {
if screen != nil { if screen != nil {
defaultWidth = int(float64(screen.Size.Width) * 0.8) defaultWidth = int(float64(screen.Size.Width) * 0.8)
defaultHeight = int(float64(screen.Size.Height) * 0.8) defaultHeight = int(float64(screen.Size.Height) * 0.8)
slog.Info("Primary screen found", "width", screen.Size.Width, "height", screen.Size.Height, "defaultWidth", defaultWidth, "defaultHeight", defaultHeight) slog.Info(
"Primary screen found",
"width",
screen.Size.Width,
"height",
screen.Size.Height,
"defaultWidth",
defaultWidth,
"defaultHeight",
defaultHeight,
)
} else { } else {
slog.Info("No primary screen found, using defaults") slog.Info("No primary screen found, using defaults")
} }
@@ -137,20 +147,23 @@ func (a *App) registerCustomEvents() {
func (a *App) setupEvents() { func (a *App) setupEvents() {
// 窗口文件拖拽事件 // 窗口文件拖拽事件
a.mainWindows.OnWindowEvent(events.Common.WindowFilesDropped, func(event *application.WindowEvent) { a.mainWindows.OnWindowEvent(
files := make([]File, 0) events.Common.WindowFilesDropped,
for _, file := range event.Context().DroppedFiles() { func(event *application.WindowEvent) {
files = append(files, File{ files := make([]File, 0)
Name: filepath.Base(file), for _, file := range event.Context().DroppedFiles() {
Path: file, files = append(files, File{
Name: filepath.Base(file),
Path: file,
})
}
details := event.Context().DropTargetDetails()
a.app.Event.Emit("files-dropped", FilesDroppedEvent{
Files: files,
Target: details.ElementID,
}) })
} },
details := event.Context().DropTargetDetails() )
a.app.Event.Emit("files-dropped", FilesDroppedEvent{
Files: files,
Target: details.ElementID,
})
})
// 窗口关闭事件 // 窗口关闭事件
a.mainWindows.OnWindowEvent(events.Common.WindowClosing, func(event *application.WindowEvent) { a.mainWindows.OnWindowEvent(events.Common.WindowClosing, func(event *application.WindowEvent) {