From 486f2af11ffbf49ffc323b1ed5bbe381229bf7bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E6=A3=AE?= Date: Sun, 14 Jun 2026 15:06:16 +0800 Subject: [PATCH] feat: add screen annotation and rpa agent demo --- agent/config.example.toml | 13 + agent/config.go | 80 ++ agent/go.mod | 2 + agent/go.sum | 2 + agent/image.go | 15 + agent/llm.go | 70 ++ agent/logger.go | 43 + agent/main.go | 132 +- agent/planner.go | 87 ++ agent/regions.go | 204 +++ agent/task.go | 162 +++ agent/types.go | 140 +++ package-lock.json | 704 +++++++++++ package.json | 1 + src-tauri/Cargo.lock | 1786 ++++++++++++++++++++++++++- src-tauri/Cargo.toml | 5 +- src-tauri/capabilities/default.json | 1 + src-tauri/src/lib.rs | 388 +++++- src-tauri/tauri.conf.json | 8 +- src/App.jsx | 17 +- src/components/ui/select.jsx | 92 ++ src/pages/AnnotationPage.jsx | 525 ++++++++ src/pages/ClonePage.jsx | 129 +- 23 files changed, 4525 insertions(+), 81 deletions(-) create mode 100644 agent/config.example.toml create mode 100644 agent/config.go create mode 100644 agent/go.sum create mode 100644 agent/image.go create mode 100644 agent/llm.go create mode 100644 agent/logger.go create mode 100644 agent/planner.go create mode 100644 agent/regions.go create mode 100644 agent/task.go create mode 100644 agent/types.go create mode 100644 src/components/ui/select.jsx create mode 100644 src/pages/AnnotationPage.jsx diff --git a/agent/config.example.toml b/agent/config.example.toml new file mode 100644 index 0000000..f82ee6a --- /dev/null +++ b/agent/config.example.toml @@ -0,0 +1,13 @@ +# Copy this file to config.toml and fill in your API key. +[volcengine] +base_url = "https://ark.cn-beijing.volces.com/api/v3" +api_key = "YOUR_ARK_API_KEY" +model = "doubao-seed-2-0-lite-260428" + +[agent] +app_data_dir = "~/Library/Application Support/com.tauri.dev" +regions_path = "data/regions/wechat.json" +screenshot_path = "data/screenshots/WeChat.jpg" +task_output_path = "data/tasks/latest_task.json" +task_history_dir = "data/tasks/history" +dry_run = true diff --git a/agent/config.go b/agent/config.go new file mode 100644 index 0000000..b73b5a5 --- /dev/null +++ b/agent/config.go @@ -0,0 +1,80 @@ +package main + +import ( + "errors" + "os" + "path/filepath" + "strings" + + "github.com/pelletier/go-toml/v2" +) + +func loadConfig(path string) (Config, error) { + data, err := os.ReadFile(path) + if err != nil { + return Config{}, err + } + + var config Config + if err := toml.Unmarshal(data, &config); err != nil { + return Config{}, err + } + + applyConfigDefaults(&config) + if config.Volcengine.BaseURL == "" || config.Volcengine.APIKey == "" || config.Volcengine.Model == "" { + return Config{}, errors.New("missing volcengine base_url/api_key/model in config.toml") + } + + appDataDir, err := expandPath(config.Agent.AppDataDir) + if err != nil { + return Config{}, err + } + config.Agent.AppDataDir = appDataDir + + return config, nil +} + +func applyConfigDefaults(config *Config) { + if config.Agent.AppDataDir == "" { + config.Agent.AppDataDir = "~/Library/Application Support/com.tauri.dev" + } + if config.Agent.RegionsPath == "" { + config.Agent.RegionsPath = "data/regions/wechat.json" + } + if config.Agent.ScreenshotPath == "" { + config.Agent.ScreenshotPath = "data/screenshots/WeChat.jpg" + } + if config.Agent.TaskOutputPath == "" { + config.Agent.TaskOutputPath = "data/tasks/latest_task.json" + } + if config.Agent.TaskHistoryDir == "" { + config.Agent.TaskHistoryDir = "data/tasks/history" + } + config.Agent.DryRun = true +} + +func expandPath(path string) (string, error) { + if path == "" { + return "", nil + } + + if path == "~" || strings.HasPrefix(path, "~/") { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + if path == "~" { + return home, nil + } + return filepath.Join(home, path[2:]), nil + } + + return path, nil +} + +func appDataPath(config Config, relativePath string) string { + if filepath.IsAbs(relativePath) { + return relativePath + } + return filepath.Join(config.Agent.AppDataDir, relativePath) +} diff --git a/agent/go.mod b/agent/go.mod index edaf2d3..c53c487 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -1,3 +1,5 @@ module agent go 1.26.1 + +require github.com/pelletier/go-toml/v2 v2.2.4 diff --git a/agent/go.sum b/agent/go.sum new file mode 100644 index 0000000..3cf50e1 --- /dev/null +++ b/agent/go.sum @@ -0,0 +1,2 @@ +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= diff --git a/agent/image.go b/agent/image.go new file mode 100644 index 0000000..7665db8 --- /dev/null +++ b/agent/image.go @@ -0,0 +1,15 @@ +package main + +import ( + "encoding/base64" + "os" +) + +func loadImageDataURL(path string) (string, error) { + data, err := os.ReadFile(path) + if err != nil { + return "", err + } + + return "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(data), nil +} diff --git a/agent/llm.go b/agent/llm.go new file mode 100644 index 0000000..f38acc7 --- /dev/null +++ b/agent/llm.go @@ -0,0 +1,70 @@ +package main + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +func requestLLM(config Config, prompt string, imageDataURL string) (string, error) { + url := strings.TrimRight(config.Volcengine.BaseURL, "/") + "/chat/completions" + body := openAIRequest{ + Model: config.Volcengine.Model, + Messages: []openAIMessage{ + { + Role: "user", + Content: []openAIContent{ + {Type: "text", Text: prompt}, + {Type: "image_url", ImageURL: &openAIImageURL{URL: imageDataURL}}, + }, + }, + }, + Temperature: 0.2, + } + + data, err := json.Marshal(body) + if err != nil { + return "", err + } + + request, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data)) + if err != nil { + return "", err + } + request.Header.Set("Authorization", "Bearer "+config.Volcengine.APIKey) + request.Header.Set("Content-Type", "application/json") + + client := http.Client{Timeout: 90 * time.Second} + response, err := client.Do(request) + if err != nil { + return "", err + } + defer response.Body.Close() + + responseBody, err := io.ReadAll(response.Body) + if err != nil { + return "", err + } + + if response.StatusCode < 200 || response.StatusCode >= 300 { + return "", fmt.Errorf("LLM request failed: status=%d body=%s", response.StatusCode, string(responseBody)) + } + + var result openAIResponse + if err := json.Unmarshal(responseBody, &result); err != nil { + return "", err + } + if result.Error != nil && result.Error.Message != "" { + return "", errors.New(result.Error.Message) + } + if len(result.Choices) == 0 || result.Choices[0].Message.Content == "" { + return "", errors.New("LLM returned empty content") + } + + return result.Choices[0].Message.Content, nil +} diff --git a/agent/logger.go b/agent/logger.go new file mode 100644 index 0000000..3a6cf68 --- /dev/null +++ b/agent/logger.go @@ -0,0 +1,43 @@ +package main + +import ( + "encoding/json" + "fmt" + "time" +) + +func logJSON(level, message string) { + entry := LogEntry{ + Level: level, + Message: message, + Timestamp: time.Now().Format("15:04:05"), + } + + data, err := json.Marshal(entry) + if err != nil { + fmt.Printf(`{"level":"error","message":%q,"timestamp":"%s"}`+"\n", err.Error(), time.Now().Format("15:04:05")) + return + } + + fmt.Println(string(data)) +} + +func logInfo(message string) { + logJSON("info", message) +} + +func logThink(message string) { + logJSON("think", message) +} + +func logWarning(message string) { + logJSON("warning", message) +} + +func logError(message string) { + logJSON("error", message) +} + +func logTask(message string) { + logJSON("task", message) +} diff --git a/agent/main.go b/agent/main.go index bffde15..126a1a9 100644 --- a/agent/main.go +++ b/agent/main.go @@ -1,58 +1,90 @@ package main import ( - "encoding/json" "fmt" - "sync" - "time" + "os" ) -var waitGroup sync.WaitGroup - -type logEntry struct { - Level string `json:"level"` - Message string `json:"message"` - Timestamp string `json:"timestamp"` -} - -// 创建一个函数,启动一个协程,每隔600ms输出一次日志信息到命令行界面 -func logToConsole() { - defer waitGroup.Done() - for { - time.Sleep(600 * time.Millisecond) - fmt.Println(randomLogMsg()) - } -} - -// 生成随机日志信息,日志信息从以下列表中随机选择 日志格式为json格式,格式{"level": "info", "message": "正在处理用户请求..."},日志级别有info、debug、error,warning, think五种,日志信息有正在处理用户请求...、正在连接服务器...、正在加载数据...、正在分析用户输入...、正在生成回复...、正在优化模型...、正在更新知识库...等 -func randomLogMsg() string { - logMsgs := []logEntry{ - {Level: "info", Message: "正在处理用户请求..."}, - {Level: "debug", Message: "正在连接服务器..."}, - {Level: "error", Message: "正在加载数据..."}, - {Level: "warning", Message: "正在分析用户输入..."}, - {Level: "think", Message: "正在生成回复..."}, - {Level: "info", Message: "正在优化模型..."}, - {Level: "info", Message: "正在更新知识库..."}, - } - logMsg := logMsgs[time.Now().UnixNano()%int64(len(logMsgs))] - logMsg.Timestamp = time.Now().Format("15:04:05") - - data, err := json.Marshal(logMsg) - if err != nil { - return fmt.Sprintf(`{"level":"error","message":"%s","timestamp":"%s"}`, err.Error(), time.Now().Format("15:04:05")) - } - - return string(data) -} - func main() { - waitGroup = sync.WaitGroup{} - // 启动日志协程 - waitGroup.Add(1) - go logToConsole() - // 等待协程main执行完成 - time.Sleep(50 * time.Second) - waitGroup.Wait() - + if err := runOnce(); err != nil { + logError(err.Error()) + os.Exit(1) + } +} + +func runOnce() error { + logInfo("Go RPA agent demo 启动") + + config, err := loadConfig("config.toml") + if err != nil { + return fmt.Errorf("读取 config.toml 失败:%w", err) + } + logInfo("已读取 config.toml,模型=" + config.Volcengine.Model) + + regionsPath := appDataPath(config, config.Agent.RegionsPath) + screenshotPath := appDataPath(config, config.Agent.ScreenshotPath) + annotation, err := loadAnnotation(regionsPath) + if err != nil { + return fmt.Errorf("读取标注文件失败:%w", err) + } + summaries := summarizeRegions(annotation.Regions) + summaries, inferenceWarnings := inferMissingRegionTypes(summaries) + logInfo(fmt.Sprintf("已读取标注文件,regions=%d", len(summaries))) + for _, warning := range inferenceWarnings { + logWarning(warning) + } + if err := validateRequiredRegions(summaries); err != nil { + return err + } + + for _, summary := range summaries { + logThink(fmt.Sprintf("区域识别:%s center=[%.1f, %.1f]", summary.Type, summary.CenterScreen[0], summary.CenterScreen[1])) + } + + imageDataURL, err := loadImageDataURL(screenshotPath) + if err != nil { + return fmt.Errorf("读取截图失败:%w", err) + } + logInfo("已读取截图 WeChat.jpg") + + prompt, err := buildPrompt(summaries) + if err != nil { + return fmt.Errorf("构造 prompt 失败:%w", err) + } + + logThink("正在请求豆包多模态模型识别聊天内容并生成任务计划") + rawResponse, err := requestLLM(config, prompt, imageDataURL) + if err != nil { + return fmt.Errorf("请求 LLM 失败:%w", err) + } + + task, _, err := parseTaskFromLLM(rawResponse) + if err != nil { + _ = saveRawResponse(config, rawResponse) + return fmt.Errorf("解析 LLM task JSON 失败,已保存 latest_task_raw.txt:%w", err) + } + + task = finalizeTask(task, summaries, config, regionsPath, screenshotPath) + logJSON("summary", fmt.Sprintf( + "LLM 摘要:联系人=%s;最新消息=%s;回复=%s;confidence=%.2f", + task.LLMSummary.ContactName, + task.LLMSummary.LatestUserMessage, + task.LLMSummary.ReplyText, + task.Confidence, + )) + logTask(fmt.Sprintf("已生成任务 %s,type=%s,actions=%d,dry_run=%v", task.TaskID, task.TaskType, len(task.Actions), config.Agent.DryRun)) + + for _, action := range task.Actions { + logTask(fmt.Sprintf("动作记录:%s %s status=%s target=%s", action.ActionID, action.Name, action.Status, action.TargetRegion)) + } + + latestPath, historyPath, err := saveTaskFiles(task, config) + if err != nil { + return fmt.Errorf("保存 task 文件失败:%w", err) + } + logInfo("已保存 latest task: " + latestPath) + logInfo("已保存 history task: " + historyPath) + logInfo("Go RPA agent demo 完成,未真实执行鼠标键盘") + + return nil } diff --git a/agent/planner.go b/agent/planner.go new file mode 100644 index 0000000..0fd1cfe --- /dev/null +++ b/agent/planner.go @@ -0,0 +1,87 @@ +package main + +import ( + "encoding/json" + "errors" + "strings" +) + +func buildPrompt(summaries []RegionSummary) (string, error) { + regionsJSON, err := json.MarshalIndent(summaries, "", " ") + if err != nil { + return "", err + } + + return `你是微信 RPA 多模态任务规划器。 + +请根据图片和标注区域完成: +1. 识别当前聊天记录。 +2. 识别联系人或会话对象,如果无法识别则填 unknown。 +3. 判断是否需要回复。 +4. 如果需要回复,生成 reply_text。 +5. 生成 dry-run 动作计划。 + +限制: +- 只能返回合法 JSON,不要 Markdown,不要解释。 +- 不要编造坐标。 +- 点击坐标只能使用我提供的区域中心点。 +- 如果区域 type 是 custom,请重点参考 description 判断该区域的用途和可执行动作。 +- description 是用户标注时填写的用途说明,优先级高于模型自行猜测。 +- 微信发送消息使用键入回车键,不要规划点击发送按钮。 +- action.type 只能是 click、type_text、press_enter、scroll、wait、noop。 +- 当前是 dry_run,只生成计划,不要假设动作已执行。 +- 如果无需回复,task_type 使用 wechat_observe 或 noop,并给出 observations。 + +标注区域如下: +` + string(regionsJSON) + ` + +请严格返回如下 JSON 结构: +{ + "task_id": "", + "task_name": "识别微信聊天并生成回复", + "task_type": "wechat_reply", + "task_status": "planned", + "confidence": 0.0, + "llm_summary": { + "contact_name": "unknown", + "chat_summary": "", + "latest_user_message": "", + "reply_text": "" + }, + "actions": [ + {"action_id":"action_001","type":"click","name":"点击输入框","status":"pending","target_region":"input_box"}, + {"action_id":"action_002","type":"type_text","name":"输入回复内容","status":"pending","text":""}, + {"action_id":"action_003","type":"press_enter","name":"按回车发送","status":"pending"} + ] +}`, nil +} + +func extractJSON(text string) (string, error) { + trimmed := strings.TrimSpace(text) + trimmed = strings.TrimPrefix(trimmed, "```json") + trimmed = strings.TrimPrefix(trimmed, "```") + trimmed = strings.TrimSuffix(trimmed, "```") + trimmed = strings.TrimSpace(trimmed) + + start := strings.Index(trimmed, "{") + end := strings.LastIndex(trimmed, "}") + if start < 0 || end < start { + return "", errors.New("no JSON object found in LLM response") + } + + return trimmed[start : end+1], nil +} + +func parseTaskFromLLM(raw string) (Task, string, error) { + jsonText, err := extractJSON(raw) + if err != nil { + return Task{}, "", err + } + + var task Task + if err := json.Unmarshal([]byte(jsonText), &task); err != nil { + return Task{}, jsonText, err + } + + return task, jsonText, nil +} diff --git a/agent/regions.go b/agent/regions.go new file mode 100644 index 0000000..756b09b --- /dev/null +++ b/agent/regions.go @@ -0,0 +1,204 @@ +package main + +import ( + "encoding/json" + "fmt" + "math" + "os" + "strings" +) + +func loadAnnotation(path string) (AnnotationFile, error) { + data, err := os.ReadFile(path) + if err != nil { + return AnnotationFile{}, err + } + + var annotation AnnotationFile + if err := json.Unmarshal(data, &annotation); err != nil { + return AnnotationFile{}, err + } + + return annotation, nil +} + +func summarizeRegions(regions []Region) []RegionSummary { + summaries := make([]RegionSummary, 0, len(regions)) + for _, region := range regions { + bboxSource := region.BBoxSource + if isZeroBox(bboxSource) { + scaleFactor := region.ScaleFactor + if scaleFactor == 0 { + scaleFactor = 1 + } + bboxSource = [4]float64{ + region.BBoxImage[0] / scaleFactor, + region.BBoxImage[1] / scaleFactor, + region.BBoxImage[2] / scaleFactor, + region.BBoxImage[3] / scaleFactor, + } + } + + summaries = append(summaries, RegionSummary{ + Name: region.Name, + Description: region.Description, + Type: region.Type, + BBoxScreen: region.BBoxScreen, + BBoxSource: bboxSource, + CenterScreen: centerOf(region.BBoxScreen), + CenterSource: centerOf(bboxSource), + }) + } + return summaries +} + +func centerOf(box [4]float64) [2]float64 { + return [2]float64{(box[0] + box[2]) / 2, (box[1] + box[3]) / 2} +} + +func regionByType(summaries []RegionSummary) map[string]RegionSummary { + index := make(map[string]RegionSummary, len(summaries)) + for _, summary := range summaries { + if _, exists := index[summary.Type]; !exists { + index[summary.Type] = summary + } + } + return index +} + +func validateRequiredRegions(summaries []RegionSummary) error { + index := regionByType(summaries) + missing := make([]string, 0) + for _, regionType := range []string{"chat_content", "input_box"} { + if _, exists := index[regionType]; !exists { + missing = append(missing, regionType) + } + } + if len(missing) > 0 { + return fmt.Errorf("missing required regions: %v", missing) + } + return nil +} + +func inferMissingRegionTypes(summaries []RegionSummary) ([]RegionSummary, []string) { + index := regionByType(summaries) + warnings := make([]string, 0) + customs := make([]RegionSummary, 0) + for _, summary := range summaries { + if inferredType := inferTypeFromDescription(summary.Description); inferredType != "" { + if _, exists := index[inferredType]; !exists { + inferred := summary + inferred.Type = inferredType + inferred.Name = inferred.Name + "(按描述推断)" + summaries = append(summaries, inferred) + index[inferredType] = inferred + warnings = append(warnings, fmt.Sprintf("区域 %q 已按描述推断为 %s", summary.Name, inferredType)) + } + } + + if summary.Type == "custom" { + customs = append(customs, summary) + } + } + if len(customs) == 0 { + return summaries, warnings + } + + if _, exists := index["contact_list"]; !exists { + best := customs[0] + for _, candidate := range customs[1:] { + if candidate.CenterScreen[0] < best.CenterScreen[0] && boxHeight(candidate.BBoxScreen) > boxHeight(best.BBoxScreen)*0.5 { + best = candidate + } + } + best.Type = "contact_list" + best.Name = best.Name + "(推断联系人区域)" + summaries = append(summaries, best) + index[best.Type] = best + warnings = append(warnings, "未标注 contact_list,已按最左侧大区域推断") + } + + if _, exists := index["input_box"]; !exists { + best := customs[0] + for _, candidate := range customs[1:] { + if candidate.CenterScreen[1] > best.CenterScreen[1] || nearlyEqual(candidate.CenterScreen[1], best.CenterScreen[1]) && boxArea(candidate.BBoxScreen) > boxArea(best.BBoxScreen) { + best = candidate + } + } + best.Type = "input_box" + best.Name = best.Name + "(推断输入框区域)" + summaries = append(summaries, best) + index[best.Type] = best + warnings = append(warnings, "未标注 input_box,已按底部区域推断") + } + + if _, exists := index["chat_content"]; !exists { + input := index["input_box"] + contact := index["contact_list"] + best := customs[0] + bestScore := math.Inf(-1) + for _, candidate := range customs { + if sameBox(candidate.BBoxScreen, input.BBoxScreen) || sameBox(candidate.BBoxScreen, contact.BBoxScreen) { + continue + } + score := boxArea(candidate.BBoxScreen) - math.Abs(candidate.CenterScreen[1]-input.CenterScreen[1])*0.2 + if score > bestScore { + best = candidate + bestScore = score + } + } + best.Type = "chat_content" + best.Name = best.Name + "(推断聊天内容区域)" + summaries = append(summaries, best) + index[best.Type] = best + warnings = append(warnings, "未标注 chat_content,已按大面积内容区推断") + } + + return summaries, warnings +} + +func sameBox(a, b [4]float64) bool { + return nearlyEqual(a[0], b[0]) && nearlyEqual(a[1], b[1]) && nearlyEqual(a[2], b[2]) && nearlyEqual(a[3], b[3]) +} + +func isZeroBox(box [4]float64) bool { + return box[0] == 0 && box[1] == 0 && box[2] == 0 && box[3] == 0 +} + +func boxWidth(box [4]float64) float64 { + return math.Abs(box[2] - box[0]) +} + +func boxHeight(box [4]float64) float64 { + return math.Abs(box[3] - box[1]) +} + +func boxArea(box [4]float64) float64 { + return boxWidth(box) * boxHeight(box) +} + +func nearlyEqual(a, b float64) bool { + return math.Abs(a-b) < 8 +} + +func inferTypeFromDescription(description string) string { + text := strings.ToLower(description) + if text == "" { + return "" + } + + if strings.Contains(text, "chat_content") || strings.Contains(text, "聊天") || strings.Contains(text, "消息") || strings.Contains(text, "记录") { + return "chat_content" + } + if strings.Contains(text, "input_box") || strings.Contains(text, "输入") || strings.Contains(text, "文本框") || strings.Contains(text, "编辑框") { + return "input_box" + } + if strings.Contains(text, "send_button") || strings.Contains(text, "发送") || strings.Contains(text, "send") { + return "send_button" + } + if strings.Contains(text, "contact_list") || strings.Contains(text, "联系人") || strings.Contains(text, "会话列表") || strings.Contains(text, "好友") { + return "contact_list" + } + + return "" +} diff --git a/agent/task.go b/agent/task.go new file mode 100644 index 0000000..c97aa13 --- /dev/null +++ b/agent/task.go @@ -0,0 +1,162 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "time" +) + +var allowedTaskTypes = map[string]bool{ + "wechat_reply": true, + "wechat_observe": true, + "noop": true, +} + +var allowedActionTypes = map[string]bool{ + "click": true, + "type_text": true, + "press_enter": true, + "scroll": true, + "wait": true, + "noop": true, +} + +func finalizeTask(task Task, summaries []RegionSummary, config Config, regionsPath string, screenshotPath string) Task { + now := time.Now() + if task.TaskID == "" { + task.TaskID = "task_" + now.Format("20060102_150405") + } + if task.TaskName == "" { + task.TaskName = "识别微信聊天并生成回复" + } + if task.TaskType == "" || !allowedTaskTypes[task.TaskType] { + task.TaskType = "wechat_reply" + } + if task.TaskStatus == "" { + task.TaskStatus = "planned" + } + if config.Agent.DryRun { + task.TaskStatus = "dry_run" + } + if task.CreatedAt == "" { + task.CreatedAt = now.Format(time.RFC3339) + } + task.Source = TaskSource{RegionsPath: regionsPath, ScreenshotPath: screenshotPath} + task.Regions = summaries + + regionIndex := regionByType(summaries) + if len(task.Actions) == 0 { + task.Actions = defaultActions(task.LLMSummary.ReplyText) + } + + for index := range task.Actions { + action := &task.Actions[index] + if action.ActionID == "" { + action.ActionID = fmt.Sprintf("action_%03d", index+1) + } + if action.Name == "" { + action.Name = action.Type + } + if !allowedActionTypes[action.Type] { + action.Type = "noop" + action.Reason = "unsupported action type replaced by noop" + } + if action.Type == "click" || action.Type == "scroll" { + if summary, exists := regionIndex[action.TargetRegion]; exists { + point := summary.CenterScreen + action.Point = &point + } else if action.Type == "click" { + action.Type = "noop" + action.Reason = "target_region not found" + } + } + if action.Type == "type_text" && len([]rune(action.Text)) > 500 { + runes := []rune(action.Text) + action.Text = string(runes[:500]) + } + if config.Agent.DryRun { + action.Status = "skipped" + if action.Reason == "" { + action.Reason = "dry_run enabled" + } + } else if action.Status == "" { + action.Status = "pending" + } + + task.ExecutionLog = append(task.ExecutionLog, ExecutionLog{ + ActionID: action.ActionID, + Status: action.Status, + Message: executionMessage(*action), + Time: time.Now().Format(time.RFC3339), + }) + } + + return task +} + +func defaultActions(replyText string) []Action { + if strings.TrimSpace(replyText) == "" { + return []Action{{ActionID: "action_001", Type: "noop", Name: "无需回复", Status: "pending"}} + } + + return []Action{ + {ActionID: "action_001", Type: "click", Name: "点击输入框", Status: "pending", TargetRegion: "input_box"}, + {ActionID: "action_002", Type: "type_text", Name: "输入回复内容", Status: "pending", Text: replyText}, + {ActionID: "action_003", Type: "press_enter", Name: "按回车发送", Status: "pending"}, + } +} + +func executionMessage(action Action) string { + switch action.Type { + case "click": + return fmt.Sprintf("dry_run: 跳过点击 %s", action.TargetRegion) + case "type_text": + return "dry_run: 跳过输入回复内容" + case "press_enter": + return "dry_run: 跳过按回车发送" + case "scroll": + return fmt.Sprintf("dry_run: 跳过滑动 %s delta_y=%d", action.TargetRegion, action.DeltaY) + case "wait": + return fmt.Sprintf("dry_run: 跳过等待 %dms", action.DurationMs) + default: + return "dry_run: noop" + } +} + +func saveTaskFiles(task Task, config Config) (string, string, error) { + latestPath := appDataPath(config, config.Agent.TaskOutputPath) + historyDir := appDataPath(config, config.Agent.TaskHistoryDir) + if err := os.MkdirAll(filepath.Dir(latestPath), 0755); err != nil { + return "", "", err + } + if err := os.MkdirAll(historyDir, 0755); err != nil { + return "", "", err + } + + data, err := json.MarshalIndent(task, "", " ") + if err != nil { + return "", "", err + } + + if err := os.WriteFile(latestPath, data, 0644); err != nil { + return "", "", err + } + + historyPath := filepath.Join(historyDir, task.TaskID+".json") + if err := os.WriteFile(historyPath, data, 0644); err != nil { + return "", "", err + } + + return latestPath, historyPath, nil +} + +func saveRawResponse(config Config, raw string) error { + path := appDataPath(config, "data/tasks/latest_task_raw.txt") + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return err + } + return os.WriteFile(path, []byte(raw), 0644) +} diff --git a/agent/types.go b/agent/types.go new file mode 100644 index 0000000..dd70ed3 --- /dev/null +++ b/agent/types.go @@ -0,0 +1,140 @@ +package main + +type Config struct { + Volcengine VolcengineConfig `toml:"volcengine"` + Agent AgentConfig `toml:"agent"` +} + +type VolcengineConfig struct { + BaseURL string `toml:"base_url"` + APIKey string `toml:"api_key"` + Model string `toml:"model"` +} + +type AgentConfig struct { + AppDataDir string `toml:"app_data_dir"` + RegionsPath string `toml:"regions_path"` + ScreenshotPath string `toml:"screenshot_path"` + TaskOutputPath string `toml:"task_output_path"` + TaskHistoryDir string `toml:"task_history_dir"` + DryRun bool `toml:"dry_run"` +} + +type LogEntry struct { + Level string `json:"level"` + Message string `json:"message"` + Timestamp string `json:"timestamp"` +} + +type AnnotationFile struct { + App string `json:"app"` + ScreenshotPath string `json:"screenshotPath"` + ScreenshotWidth int `json:"screenshotWidth"` + ScreenshotHeight int `json:"screenshotHeight"` + ScaleFactor float64 `json:"scaleFactor"` + Source jsonRawOptional `json:"source,omitempty"` + Regions []Region `json:"regions"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + +type jsonRawOptional map[string]any + +type Region struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Type string `json:"type"` + BBoxImage [4]float64 `json:"bbox_image"` + BBoxSource [4]float64 `json:"bbox_source,omitempty"` + BBoxScreen [4]float64 `json:"bbox_screen"` + ScaleFactor float64 `json:"scaleFactor"` +} + +type RegionSummary struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Type string `json:"type"` + BBoxScreen [4]float64 `json:"bbox_screen"` + BBoxSource [4]float64 `json:"bbox_source,omitempty"` + CenterScreen [2]float64 `json:"center_screen"` + CenterSource [2]float64 `json:"center_source,omitempty"` +} + +type Task struct { + TaskID string `json:"task_id"` + TaskName string `json:"task_name"` + TaskType string `json:"task_type"` + TaskStatus string `json:"task_status"` + CreatedAt string `json:"created_at"` + Source TaskSource `json:"source"` + Confidence float64 `json:"confidence"` + LLMSummary LLMSummary `json:"llm_summary"` + Regions []RegionSummary `json:"regions"` + Actions []Action `json:"actions"` + ExecutionLog []ExecutionLog `json:"execution_log"` +} + +type TaskSource struct { + RegionsPath string `json:"regions_path"` + ScreenshotPath string `json:"screenshot_path"` +} + +type LLMSummary struct { + ContactName string `json:"contact_name"` + ChatSummary string `json:"chat_summary"` + LatestUserMessage string `json:"latest_user_message"` + ReplyText string `json:"reply_text"` +} + +type Action struct { + ActionID string `json:"action_id"` + Type string `json:"type"` + Name string `json:"name"` + Status string `json:"status"` + TargetRegion string `json:"target_region,omitempty"` + Point *[2]float64 `json:"point,omitempty"` + Text string `json:"text,omitempty"` + DeltaY int `json:"delta_y,omitempty"` + DurationMs int `json:"duration_ms,omitempty"` + Reason string `json:"reason,omitempty"` +} + +type ExecutionLog struct { + ActionID string `json:"action_id"` + Status string `json:"status"` + Message string `json:"message"` + Time string `json:"time"` +} + +type openAIRequest struct { + Model string `json:"model"` + Messages []openAIMessage `json:"messages"` + Temperature float64 `json:"temperature"` +} + +type openAIMessage struct { + Role string `json:"role"` + Content []openAIContent `json:"content"` +} + +type openAIContent struct { + Type string `json:"type"` + Text string `json:"text,omitempty"` + ImageURL *openAIImageURL `json:"image_url,omitempty"` +} + +type openAIImageURL struct { + URL string `json:"url"` +} + +type openAIResponse struct { + Choices []struct { + Message struct { + Content string `json:"content"` + } `json:"message"` + } `json:"choices"` + Error *struct { + Message string `json:"message"` + } `json:"error,omitempty"` +} diff --git a/package-lock.json b/package-lock.json index a046789..23f7657 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "wechat-ai-ui", "version": "0.1.0", "dependencies": { + "@radix-ui/react-select": "^2.3.0", "@tauri-apps/api": "^2.11.0", "@vitejs/plugin-react": "^5.0.0", "autoprefixer": "^10.4.20", @@ -713,6 +714,44 @@ "node": ">=18" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -793,6 +832,526 @@ "node": ">= 8" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz", + "integrity": "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.4.tgz", + "integrity": "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.9.tgz", + "integrity": "sha512-yqHW5WQ/cTpU/un7dqqIKNy2iRU8BC0JB78PEzTfCCYvZu1U6W9KwObAniMk9nhSfyotKPQTYaUD/HB0f5muig==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.9.tgz", + "integrity": "sha512-zuSVi7ziP7uQRqc+yGxsKJfNkdyHv3ZKDaHe0gzg4dRgws96TPKWIiz84tVHP4GEcEl8bC0mdt17NkcxaJHmaQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-slot": "1.2.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", + "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.4.tgz", + "integrity": "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", + "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.12.tgz", + "integrity": "sha512-MhoruH6xEzsbvOmo4TNgMfmtvRGyDZw4MDSdf4ybMHfezjqwzv6hyd4lsMzBp8K9Sn6sGzCF62x1I7BYUECXOg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-escape-keydown": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", + "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.9.tgz", + "integrity": "sha512-9Se8t+Zry+1rEOL7Y6l/4ANYU/TOtAtf8O2fKdwLltcaMcm6kOqYGbzO4tMFQ0bvzO920pRAoHpFZ4W85S3keQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", + "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.0.tgz", + "integrity": "sha512-9PB589e1aWZbrlFUHdz6WiPCL+xLZHQFX7oibqG/6Q0SwOkxDyQX9W/cyPa+sAPPKuC8cpLCpRczE5a/1DiwVQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.9", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-rect": "1.1.2", + "@radix-ui/react-use-size": "1.1.2", + "@radix-ui/rect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.11.tgz", + "integrity": "sha512-UEytdjgEh2tJGgD/gZK4FUx6t1rNIlM3U0DENhSrG7I75FGm1DnaDuVUWF1pWAWUwGmn1sCJ1VGHn8LhN1aTOw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.6.tgz", + "integrity": "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.5.tgz", + "integrity": "sha512-zifXeB8Y88qCYx8PLZ5oQb32KwZub+s925mMoZsBBq9KUQqWKkREubTfs6ASjRPPBe7Jt9O8OHH89+95VG+grA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.0.tgz", + "integrity": "sha512-mENc7WpJvJcW8hlMpzfFcHcEhTvYS5JMBmi9HVC1Q00uhBwML086MHYUV8QQdQv6lcu0Wg8dzd1RB8AFADcG/g==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.2", + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.9", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.12", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.9", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.0", + "@radix-ui/react-portal": "1.1.11", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-slot": "1.2.5", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-visually-hidden": "1.2.5", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.5.tgz", + "integrity": "sha512-rCMO3QsIVKv5JTY5CVbo2MvO77SpEqqYc8AvRE7OWqRDOIqAKjsp+DrmnY9uc8NPdxB5E2z47HTYGeE2+NTptg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", + "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", + "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", + "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.2.tgz", + "integrity": "sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", + "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.2.tgz", + "integrity": "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz", + "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz", + "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.5.tgz", + "integrity": "sha512-tPcHNI3FajdDBFpl/Ez1m2WL0ufJqBKyHxMDBvKitopamK36WwBGOMicuMEZKkM5Wce41QxUyv6BsiqfrWBiGg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz", + "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==", + "license": "MIT" + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.3", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", @@ -1443,6 +2002,18 @@ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "license": "MIT" }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/autoprefixer": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", @@ -1657,6 +2228,12 @@ } } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -1828,6 +2405,15 @@ "node": ">=6.9.0" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2325,6 +2911,75 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -2623,6 +3278,12 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -2653,6 +3314,49 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 676ef16..ed06d91 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview --host 127.0.0.1" }, "dependencies": { + "@radix-ui/react-select": "^2.3.0", "@tauri-apps/api": "^2.11.0", "@vitejs/plugin-react": "^5.0.0", "autoprefixer": "^10.4.20", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 7d9e77c..b6a581d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -69,6 +69,16 @@ dependencies = [ "libc", ] +[[package]] +name = "annotate-snippets" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" +dependencies = [ + "unicode-width", + "yansi-term", +] + [[package]] name = "anyhow" version = "1.0.102" @@ -79,13 +89,16 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" name = "app" version = "0.1.0" dependencies = [ + "image 0.25.10", "log", + "screenshots", "serde", "serde_json", "tauri", "tauri-build", "tauri-plugin-log", "tauri-plugin-shell", + "xcap", ] [[package]] @@ -94,6 +107,137 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.1.4", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.4", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "atk" version = "0.18.2" @@ -141,6 +285,27 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "annotate-snippets", + "bitflags 2.13.0", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex 1.3.0", + "syn 2.0.117", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -156,6 +321,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -201,6 +372,19 @@ dependencies = [ "objc2", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "borsh" version = "1.6.1" @@ -300,6 +484,20 @@ name = "bytemuck" version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "byteorder" @@ -307,6 +505,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.1" @@ -390,7 +594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -399,6 +603,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfb" version = "0.7.3" @@ -444,6 +657,23 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading 0.8.9", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "combine" version = "4.6.7" @@ -454,6 +684,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie" version = "0.18.1" @@ -464,6 +712,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -480,6 +747,32 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", + "libc", +] + [[package]] name = "core-graphics" version = "0.25.0" @@ -487,9 +780,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ "bitflags 2.13.0", - "core-foundation", - "core-graphics-types", - "foreign-types", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", "libc", ] @@ -500,7 +804,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.13.0", - "core-foundation", + "core-foundation 0.10.1", "libc", ] @@ -531,12 +835,37 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -705,6 +1034,20 @@ dependencies = [ "objc2", ] +[[package]] +name = "display-info" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba4b5ddb26d674c9cd40b7a747e42658ffe1289843615b838532f660e0e3dd0" +dependencies = [ + "anyhow", + "core-graphics 0.23.2", + "fxhash", + "widestring", + "windows 0.52.0", + "xcb", +] + [[package]] name = "displaydoc" version = "0.2.6" @@ -716,6 +1059,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading 0.7.4", +] + [[package]] name = "dlopen2" version = "0.8.2" @@ -754,6 +1106,12 @@ dependencies = [ "tendril", ] +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "dpi" version = "0.1.2" @@ -763,6 +1121,46 @@ dependencies = [ "serde", ] +[[package]] +name = "drm" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" +dependencies = [ + "bitflags 2.13.0", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "libc", + "rustix 0.38.44", +] + +[[package]] +name = "drm-ffi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51a91c9b32ac4e8105dec255e849e0d66e27d7c34d184364fb93e469db08f690" +dependencies = [ + "drm-sys", + "rustix 1.1.4", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8e1361066d91f5ffccff060a3c3be9c3ecde15be2959c1937595f7a82a9f8" +dependencies = [ + "libc", + "linux-raw-sys 0.9.4", +] + [[package]] name = "dtoa" version = "1.0.11" @@ -805,6 +1203,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + [[package]] name = "embed-resource" version = "3.0.9" @@ -834,6 +1238,33 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "env_filter" version = "0.1.4" @@ -871,6 +1302,42 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -901,7 +1368,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ - "memoffset", + "memoffset 0.9.1", "rustc_version", ] @@ -939,6 +1406,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -946,7 +1422,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -960,6 +1436,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -981,6 +1463,21 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -988,6 +1485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1013,6 +1511,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.32" @@ -1042,6 +1553,7 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1052,6 +1564,39 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gbm" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce852e998d3ca5e4a97014fb31c940dc5ef344ec7d364984525fd11e8a547e6a" +dependencies = [ + "bitflags 2.13.0", + "drm", + "drm-fourcc", + "gbm-sys", + "libc", + "wayland-backend 0.3.15", + "wayland-server", +] + +[[package]] +name = "gbm-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13a5f2acc785d8fb6bf6b7ab6bfb0ef5dad4f4d97e8e70bb8e470722312f76f" +dependencies = [ + "libc", +] + [[package]] name = "gdk" version = "0.18.2" @@ -1197,6 +1742,16 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gio" version = "0.18.4" @@ -1229,6 +1784,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "gl" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94edab108827d67608095e269cf862e60d920f144a5026d3dbcfd8b877fb404" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + [[package]] name = "glib" version = "0.18.5" @@ -1345,6 +1920,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1381,6 +1967,18 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -1430,6 +2028,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + [[package]] name = "httparse" version = "1.10.1" @@ -1628,6 +2232,39 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-traits", + "png 0.17.16", + "qoi", + "tiff", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png 0.18.1", + "zune-core", + "zune-jpeg", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1660,6 +2297,17 @@ dependencies = [ "cfb", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1685,6 +2333,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -1758,6 +2415,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" +dependencies = [ + "rayon", +] + [[package]] name = "js-sys" version = "0.3.100" @@ -1802,12 +2468,46 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "leb128fmt" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libappindicator" version = "0.9.0" @@ -1828,7 +2528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ "gtk-sys", - "libloading", + "libloading 0.7.4", "once_cell", ] @@ -1844,6 +2544,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" dependencies = [ + "cc", "pkg-config", ] @@ -1857,6 +2558,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + [[package]] name = "libredox" version = "0.1.17" @@ -1866,6 +2577,89 @@ dependencies = [ "libc", ] +[[package]] +name = "libspa" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65f3a4b81b2a2d8c7f300643676202debd1b7c929dbf5c9bb89402ea11d19810" +dependencies = [ + "bitflags 2.13.0", + "cc", + "convert_case", + "cookie-factory", + "libc", + "libspa-sys", + "nix 0.27.1", + "nom", + "system-deps", +] + +[[package]] +name = "libspa-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f" +dependencies = [ + "bindgen", + "cc", + "system-deps", +] + +[[package]] +name = "libwayshot" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "896d0e594158b7f5188034836a6c4886492078352c39760786e54f1b796caaea" +dependencies = [ + "image 0.24.9", + "log", + "memmap2 0.7.1", + "nix 0.26.4", + "thiserror 1.0.69", + "wayland-client 0.30.2", + "wayland-protocols 0.30.1", + "wayland-protocols-wlr 0.1.0", +] + +[[package]] +name = "libwayshot-xcap" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558a3a7ca16a17a14adf8f051b3adcd7766d397532f5f6d6a48034db11e54c22" +dependencies = [ + "drm", + "gbm", + "gl", + "image 0.25.10", + "khronos-egl", + "memmap2 0.9.10", + "rustix 1.1.4", + "thiserror 2.0.18", + "tracing", + "wayland-backend 0.3.15", + "wayland-client 0.31.14", + "wayland-protocols 0.32.12", + "wayland-protocols-wlr 0.3.12", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.2" @@ -1907,6 +2701,33 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +[[package]] +name = "memmap2" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -1922,6 +2743,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1943,6 +2770,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "muda" version = "0.19.2" @@ -1994,6 +2831,40 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", + "pin-utils", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-conv" version = "0.2.2" @@ -2058,8 +2929,48 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.13.0", "block2", + "libc", "objc2", + "objc2-cloud-kit", + "objc2-core-data", "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-av-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478ae33fcac9df0a18db8302387c666b8ef08a3e2d62b510ca4fc278a384b6c0" +dependencies = [ + "bitflags 2.13.0", + "block2", + "dispatch2", + "objc2", + "objc2-avf-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-video", + "objc2-foundation", + "objc2-image-io", + "objc2-media-toolbox", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-avf-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" +dependencies = [ + "objc2", "objc2-foundation", ] @@ -2074,12 +2985,35 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc2-core-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" +dependencies = [ + "dispatch2", + "objc2", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-core-audio-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" +dependencies = [ + "bitflags 2.13.0", + "objc2", +] + [[package]] name = "objc2-core-data" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ + "bitflags 2.13.0", "objc2", "objc2-foundation", ] @@ -2091,7 +3025,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.13.0", + "block2", "dispatch2", + "libc", "objc2", ] @@ -2102,10 +3038,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.13.0", + "block2", "dispatch2", + "libc", "objc2", "objc2-core-foundation", "objc2-io-surface", + "objc2-metal", ] [[package]] @@ -2128,6 +3067,22 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc2-core-media" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d" +dependencies = [ + "bitflags 2.13.0", + "block2", + "dispatch2", + "objc2", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-video", +] + [[package]] name = "objc2-core-text" version = "0.3.2" @@ -2140,6 +3095,21 @@ dependencies = [ "objc2-core-graphics", ] +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", + "objc2-metal", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -2163,10 +3133,22 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.13.0", "block2", + "libc", "objc2", "objc2-core-foundation", ] +[[package]] +name = "objc2-image-io" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b0446e98cf4a784cc7a0177715ff317eeaa8463841c616cfc78aa4f953c4ea" +dependencies = [ + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + [[package]] name = "objc2-io-surface" version = "0.3.2" @@ -2178,6 +3160,29 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-media-toolbox" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd9fdde720df3da7046bb9097811000c1e7ab5cd579fa89d96b27d56781fb30" +dependencies = [ + "objc2", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-media", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.13.0", + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-quartz-core" version = "0.3.2" @@ -2259,6 +3264,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "os_pipe" version = "1.2.3" @@ -2294,6 +3309,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -2388,6 +3409,51 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pipewire" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08e645ba5c45109106d56610b3ee60eb13a6f2beb8b74f8dc8186cf261788dda" +dependencies = [ + "anyhow", + "bitflags 2.13.0", + "libc", + "libspa", + "libspa-sys", + "nix 0.27.1", + "once_cell", + "pipewire-sys", + "thiserror 1.0.69", +] + +[[package]] +name = "pipewire-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112" +dependencies = [ + "bindgen", + "libspa-sys", + "system-deps", +] + [[package]] name = "pkg-config" version = "0.3.33" @@ -2402,7 +3468,7 @@ checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" dependencies = [ "base64 0.22.1", "indexmap 2.14.0", - "quick-xml", + "quick-xml 0.39.4", "serde", "time", ] @@ -2433,6 +3499,20 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -2555,6 +3635,39 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-xml" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", +] + [[package]] name = "quick-xml" version = "0.39.4" @@ -2598,8 +3711,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -2609,7 +3732,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -2621,12 +3754,41 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -2778,13 +3940,19 @@ dependencies = [ "borsh", "bytes", "num-traits", - "rand", + "rand 0.8.6", "rkyv", "serde", "serde_json", "wasm-bindgen", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.2" @@ -2800,6 +3968,32 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -2866,12 +4060,37 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "screenshots" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038df8746dbf7d8b70715d638470db956794e0f3d08608e4197f4053c2da1620" +dependencies = [ + "anyhow", + "core-graphics 0.22.3", + "dbus", + "display-info", + "fxhash", + "image 0.24.9", + "libwayshot", + "percent-encoding", + "widestring", + "windows 0.51.1", + "xcb", +] + [[package]] name = "seahash" version = "4.1.0" @@ -2892,7 +4111,7 @@ dependencies = [ "phf", "phf_codegen", "precomputed-hash", - "rustc-hash", + "rustc-hash 2.1.2", "servo_arc", "smallvec", ] @@ -3087,6 +4306,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "shlex" version = "2.0.1" @@ -3322,8 +4547,8 @@ checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" dependencies = [ "bitflags 2.13.0", "block2", - "core-foundation", - "core-graphics", + "core-foundation 0.10.1", + "core-graphics 0.25.0", "crossbeam-channel", "dbus", "dispatch2", @@ -3348,7 +4573,7 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", @@ -3394,6 +4619,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", + "http-range", "jni", "libc", "log", @@ -3425,7 +4651,7 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows", + "windows 0.61.3", ] [[package]] @@ -3571,7 +4797,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", ] [[package]] @@ -3596,7 +4822,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", "wry", ] @@ -3649,6 +4875,19 @@ dependencies = [ "toml 1.1.2+spec-1.1.0", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + [[package]] name = "tendril" version = "0.5.0" @@ -3699,6 +4938,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.47" @@ -3956,9 +5206,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -4008,6 +5270,17 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "windows-sys 0.61.2", +] + [[package]] name = "unic-char-property" version = "0.9.0" @@ -4061,6 +5334,12 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -4305,6 +5584,168 @@ dependencies = [ "semver", ] +[[package]] +name = "wayland-backend" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8" +dependencies = [ + "cc", + "downcast-rs", + "io-lifetimes", + "nix 0.26.4", + "scoped-tls", + "smallvec", + "wayland-sys 0.30.1", +] + +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys 0.31.11", +] + +[[package]] +name = "wayland-client" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489c9654770f674fc7e266b3c579f4053d7551df0ceb392f153adb1f9ed06ac8" +dependencies = [ + "bitflags 1.3.2", + "nix 0.26.4", + "wayland-backend 0.1.2", + "wayland-scanner 0.30.1", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.13.0", + "rustix 1.1.4", + "wayland-backend 0.3.15", + "wayland-scanner 0.31.10", +] + +[[package]] +name = "wayland-protocols" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b28101e5ca94f70461a6c2d610f76d85ad223d042dd76585ab23d3422dd9b4d" +dependencies = [ + "bitflags 1.3.2", + "wayland-backend 0.1.2", + "wayland-client 0.30.2", + "wayland-scanner 0.30.1", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend 0.3.15", + "wayland-client 0.31.14", + "wayland-scanner 0.31.10", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce991093320e4a6a525876e6b629ab24da25f9baef0c2e0080ad173ec89588a" +dependencies = [ + "bitflags 1.3.2", + "wayland-backend 0.1.2", + "wayland-client 0.30.2", + "wayland-protocols 0.30.1", + "wayland-scanner 0.30.1", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend 0.3.15", + "wayland-client 0.31.14", + "wayland-protocols 0.32.12", + "wayland-scanner 0.31.10", +] + +[[package]] +name = "wayland-scanner" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b873b257fbc32ec909c0eb80dea312076a67014e65e245f5eb69a6b8ab330e" +dependencies = [ + "proc-macro2", + "quick-xml 0.28.2", + "quote", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml 0.39.4", + "quote", +] + +[[package]] +name = "wayland-server" +version = "0.31.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1846eb04c49182e04f4a099e2a830a2b745610bbc1d61246e206f29c7000a0" +dependencies = [ + "bitflags 2.13.0", + "downcast-rs", + "rustix 1.1.4", + "wayland-backend 0.3.15", + "wayland-scanner 0.31.10", +] + +[[package]] +name = "wayland-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "libc", + "log", + "memoffset 0.9.1", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.100" @@ -4379,7 +5820,7 @@ checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-implement", "windows-interface", @@ -4403,10 +5844,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ "thiserror 2.0.18", - "windows", + "windows 0.61.3", "windows-core 0.61.2", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -4453,6 +5906,26 @@ dependencies = [ "windows-version", ] +[[package]] +name = "windows" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core 0.51.1", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.61.3" @@ -4475,6 +5948,24 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.61.2" @@ -4601,6 +6092,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -4643,6 +6143,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -4700,6 +6215,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4718,6 +6239,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4736,6 +6263,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4766,6 +6299,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4784,6 +6323,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4802,6 +6347,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4820,6 +6371,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -5004,7 +6561,7 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", @@ -5040,6 +6597,64 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "xcap" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7921260094351cd497514d5fccba8066316cacbdb15b3b3bda6e7345447c67a7" +dependencies = [ + "dispatch2", + "image 0.25.10", + "lazy_static", + "libwayshot-xcap", + "log", + "objc2", + "objc2-app-kit", + "objc2-av-foundation", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-media", + "objc2-core-video", + "objc2-foundation", + "percent-encoding", + "pipewire", + "rand 0.9.4", + "scopeguard", + "serde", + "thiserror 2.0.18", + "url", + "widestring", + "windows 0.61.3", + "xcb", + "zbus", +] + +[[package]] +name = "xcb" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4c580d8205abb0a5cf4eb7e927bd664e425b6c3263f9c5310583da96970cf6" +dependencies = [ + "bitflags 1.3.2", + "libc", + "quick-xml 0.30.0", +] + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "yansi-term" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" +dependencies = [ + "winapi", +] + [[package]] name = "yoke" version = "0.8.3" @@ -5063,6 +6678,67 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "5.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.4", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.3", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" +dependencies = [ + "serde", + "winnow 1.0.3", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.8.52" @@ -5142,3 +6818,67 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] + +[[package]] +name = "zvariant" +version = "5.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.3", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.117", + "winnow 1.0.3", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1f23f7a..196f0bd 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -21,6 +21,9 @@ tauri-build = { version = "2.6.2", features = [] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } log = "0.4" -tauri = { version = "2.11.2", features = ["macos-private-api"] } +image = { version = "0.25", default-features = false, features = ["jpeg"] } +screenshots = "0.8" +tauri = { version = "2.11.2", features = ["protocol-asset", "macos-private-api"] } tauri-plugin-log = "2" tauri-plugin-shell = "2.3.5" +xcap = "0.6" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 1f51888..af7af97 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -9,6 +9,7 @@ "permissions": [ "core:default", "core:window:allow-close", + "core:window:allow-set-fullscreen", "core:window:allow-start-dragging" ] } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 8265087..5ef0295 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,10 +1,15 @@ +use screenshots::Screen; use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; use std::sync::Mutex; +use std::time::Instant; use tauri::{Emitter, Manager, WebviewUrl, WebviewWindowBuilder}; use tauri_plugin_shell::{ process::{CommandChild, CommandEvent}, ShellExt, }; +use xcap::{Monitor, Window}; struct AgentProcess(Mutex>); @@ -15,6 +20,131 @@ struct AgentLog { timestamp: Option, } +#[derive(Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct CaptureResult { + screenshot_path: String, + screenshot_width: u32, + screenshot_height: u32, + scale_factor: f64, + source: Option, + screen_list_ms: u128, + capture_ms: u128, + save_ms: u128, + total_ms: u128, +} + +#[derive(Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +struct CaptureSource { + id: String, + kind: String, + label: String, + app_name: Option, + title: Option, + pid: Option, + x: i32, + y: i32, + width: u32, + height: u32, + scale_factor: f64, +} + +#[derive(Clone, Deserialize, Serialize)] +struct Region { + id: String, + name: String, + description: Option, + #[serde(rename = "type")] + region_type: String, + bbox_image: [f64; 4], + bbox_source: Option<[f64; 4]>, + bbox_screen: [f64; 4], + #[serde(rename = "scaleFactor")] + scale_factor: f64, +} + +#[derive(Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +struct AnnotationFile { + app: String, + screenshot_path: String, + screenshot_width: u32, + screenshot_height: u32, + scale_factor: f64, + source: Option, + regions: Vec, + created_at: String, + updated_at: String, +} + +fn data_dir(app: &tauri::AppHandle) -> Result { + let dir = app + .path() + .app_data_dir() + .map_err(|error| error.to_string())? + .join("data"); + fs::create_dir_all(&dir).map_err(|error| error.to_string())?; + Ok(dir) +} + +fn regions_path(app: &tauri::AppHandle) -> Result { + let dir = data_dir(app)?.join("regions"); + fs::create_dir_all(&dir).map_err(|error| error.to_string())?; + Ok(dir.join("wechat.json")) +} + +fn screenshot_path(app: &tauri::AppHandle) -> Result { + let screenshot_dir = data_dir(app)?.join("screenshots"); + fs::create_dir_all(&screenshot_dir).map_err(|error| error.to_string())?; + Ok(screenshot_dir.join("WeChat.jpg")) +} + +fn save_capture_image( + width: u32, + height: u32, + rgba_bytes: Vec, + screenshot_path: PathBuf, + source: Option, + default_scale_factor: f64, + screen_list_ms: u128, + capture_ms: u128, + total_started_at: Instant, +) -> Result { + let save_started_at = Instant::now(); + let rgba_image = image::RgbaImage::from_raw(width, height, rgba_bytes) + .ok_or_else(|| "invalid screenshot buffer".to_string())?; + let rgb_image = image::DynamicImage::ImageRgba8(rgba_image).to_rgb8(); + let mut file = fs::File::create(&screenshot_path).map_err(|error| error.to_string())?; + let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut file, 88); + encoder + .encode_image(&rgb_image) + .map_err(|error| error.to_string())?; + let save_ms = save_started_at.elapsed().as_millis(); + let total_ms = total_started_at.elapsed().as_millis(); + let scale_factor = source + .as_ref() + .map(|item| item.scale_factor) + .unwrap_or(default_scale_factor); + + println!( + "capture_source timing: screen_list={}ms capture={}ms save={}ms total={}ms size={}x{} scale={}", + screen_list_ms, capture_ms, save_ms, total_ms, width, height, scale_factor + ); + + Ok(CaptureResult { + screenshot_path: screenshot_path.to_string_lossy().to_string(), + screenshot_width: width, + screenshot_height: height, + scale_factor, + source, + screen_list_ms, + capture_ms, + save_ms, + total_ms, + }) +} + fn emit_agent_log(app: &tauri::AppHandle, level: &str, message: impl Into) { let _ = app.emit( "agent-log", @@ -110,6 +240,246 @@ fn stop_agent(app: tauri::AppHandle, process: tauri::State) -> Res Ok(()) } +#[tauri::command] +async fn capture_screen(app: tauri::AppHandle) -> Result { + let screenshot_path = screenshot_path(&app)?; + + tauri::async_runtime::spawn_blocking(move || { + let total_started_at = Instant::now(); + let screen_list_started_at = Instant::now(); + let screen = Screen::all() + .map_err(|error| error.to_string())? + .into_iter() + .next() + .ok_or_else(|| "no screen found".to_string())?; + let screen_list_ms = screen_list_started_at.elapsed().as_millis(); + + let capture_started_at = Instant::now(); + let image = screen.capture().map_err(|error| error.to_string())?; + let capture_ms = capture_started_at.elapsed().as_millis(); + let width = image.width(); + let height = image.height(); + save_capture_image( + width, + height, + image.into_raw(), + screenshot_path, + None, + screen.display_info.scale_factor as f64, + screen_list_ms, + capture_ms, + total_started_at, + ) + }) + .await + .map_err(|error| error.to_string())? +} + +#[tauri::command] +async fn list_capture_sources() -> Result, String> { + tauri::async_runtime::spawn_blocking(move || { + let mut sources = Vec::new(); + + for (index, monitor) in Monitor::all() + .map_err(|error| error.to_string())? + .into_iter() + .enumerate() + { + let width = monitor.width().map_err(|error| error.to_string())?; + let height = monitor.height().map_err(|error| error.to_string())?; + let x = monitor.x().map_err(|error| error.to_string())?; + let y = monitor.y().map_err(|error| error.to_string())?; + let scale_factor = monitor.scale_factor().map_err(|error| error.to_string())? as f64; + + sources.push(CaptureSource { + id: format!("display:{index}"), + kind: "display".to_string(), + label: format!("桌面 {} · {}x{}", index + 1, width, height), + app_name: None, + title: None, + pid: None, + x, + y, + width, + height, + scale_factor, + }); + } + + if let Ok(windows) = Window::all() { + for window in windows { + let width = match window.width() { + Ok(width) => width, + Err(_) => continue, + }; + let height = match window.height() { + Ok(height) => height, + Err(_) => continue, + }; + + if window.is_minimized().unwrap_or(true) || width < 80 || height < 80 { + continue; + } + + let app_name = window.app_name().unwrap_or_default(); + let title = window.title().unwrap_or_default(); + if app_name.is_empty() && title.is_empty() { + continue; + } + + sources.push(CaptureSource { + id: format!("window:{}", window.id().map_err(|error| error.to_string())?), + kind: "window".to_string(), + label: format!("{} · {}", app_name, title), + app_name: Some(app_name), + title: Some(title), + pid: window.pid().ok(), + x: window.x().unwrap_or(0), + y: window.y().unwrap_or(0), + width, + height, + scale_factor: 1.0, + }); + } + } + + Ok(sources) + }) + .await + .map_err(|error| error.to_string())? +} + +#[tauri::command] +async fn capture_source(app: tauri::AppHandle, source_id: String) -> Result { + let screenshot_path = screenshot_path(&app)?; + + tauri::async_runtime::spawn_blocking(move || { + let total_started_at = Instant::now(); + let screen_list_started_at = Instant::now(); + + if let Some(index_text) = source_id.strip_prefix("display:") { + let index = index_text + .parse::() + .map_err(|error| error.to_string())?; + let monitors = Monitor::all().map_err(|error| error.to_string())?; + let monitor = monitors + .into_iter() + .nth(index) + .ok_or_else(|| "display source not found".to_string())?; + let screen_list_ms = screen_list_started_at.elapsed().as_millis(); + let width = monitor.width().map_err(|error| error.to_string())?; + let height = monitor.height().map_err(|error| error.to_string())?; + let scale_factor = monitor.scale_factor().map_err(|error| error.to_string())? as f64; + let source = CaptureSource { + id: source_id, + kind: "display".to_string(), + label: format!("桌面 {} · {}x{}", index + 1, width, height), + app_name: None, + title: None, + pid: None, + x: monitor.x().map_err(|error| error.to_string())?, + y: monitor.y().map_err(|error| error.to_string())?, + width, + height, + scale_factor, + }; + + let capture_started_at = Instant::now(); + let image = monitor.capture_image().map_err(|error| error.to_string())?; + let capture_ms = capture_started_at.elapsed().as_millis(); + let image_width = image.width(); + let image_height = image.height(); + return save_capture_image( + image_width, + image_height, + image.into_raw(), + screenshot_path, + Some(source.clone()), + source.scale_factor, + screen_list_ms, + capture_ms, + total_started_at, + ); + } + + if let Some(window_id_text) = source_id.strip_prefix("window:") { + let window_id = window_id_text + .parse::() + .map_err(|error| error.to_string())?; + let windows = Window::all().map_err(|error| error.to_string())?; + let window = windows + .into_iter() + .find(|item| item.id().ok() == Some(window_id)) + .ok_or_else(|| "window source not found".to_string())?; + let screen_list_ms = screen_list_started_at.elapsed().as_millis(); + let window_width = window.width().map_err(|error| error.to_string())?; + let window_height = window.height().map_err(|error| error.to_string())?; + let app_name = window.app_name().unwrap_or_default(); + let title = window.title().unwrap_or_default(); + let capture_started_at = Instant::now(); + let image = window.capture_image().map_err(|error| error.to_string())?; + let capture_ms = capture_started_at.elapsed().as_millis(); + let image_width = image.width(); + let image_height = image.height(); + let scale_factor = if window_width > 0 { + image_width as f64 / window_width as f64 + } else { + 1.0 + }; + let source = CaptureSource { + id: source_id, + kind: "window".to_string(), + label: format!("{} · {}", app_name, title), + app_name: Some(app_name), + title: Some(title), + pid: window.pid().ok(), + x: window.x().unwrap_or(0), + y: window.y().unwrap_or(0), + width: window_width, + height: window_height, + scale_factor, + }; + + return save_capture_image( + image_width, + image_height, + image.into_raw(), + screenshot_path, + Some(source.clone()), + source.scale_factor, + screen_list_ms, + capture_ms, + total_started_at, + ); + } + + Err("unsupported capture source".to_string()) + }) + .await + .map_err(|error| error.to_string())? +} + +#[tauri::command] +fn save_regions(app: tauri::AppHandle, annotation: AnnotationFile) -> Result { + let path = regions_path(&app)?; + let json = serde_json::to_string_pretty(&annotation).map_err(|error| error.to_string())?; + fs::write(&path, json).map_err(|error| error.to_string())?; + Ok(path.to_string_lossy().to_string()) +} + +#[tauri::command] +fn load_regions(app: tauri::AppHandle) -> Result, String> { + let path = regions_path(&app)?; + if !path.exists() { + return Ok(None); + } + + let json = fs::read_to_string(&path).map_err(|error| error.to_string())?; + serde_json::from_str(&json) + .map(Some) + .map_err(|error| error.to_string()) +} + #[tauri::command] fn open_popup_window(app: tauri::AppHandle, route: String) -> Result<(), String> { let route = if route.starts_with('/') { @@ -125,6 +495,7 @@ fn open_popup_window(app: tauri::AppHandle, route: String) -> Result<(), String> } let title = match route.as_str() { + "/annotate" => "屏幕区域标注", "/window/settings" => "设置", "/window/engine-logs" => "分身引擎日志", "/window/knowledge-create" => "新增知识库", @@ -135,7 +506,7 @@ fn open_popup_window(app: tauri::AppHandle, route: String) -> Result<(), String> _ => "二级窗口", }; - WebviewWindowBuilder::new( + let mut builder = WebviewWindowBuilder::new( &app, label, WebviewUrl::App(format!("index.html#{route}").into()), @@ -144,9 +515,13 @@ fn open_popup_window(app: tauri::AppHandle, route: String) -> Result<(), String> .inner_size(1080.0, 800.0) .decorations(false) .transparent(true) - .resizable(true) - .build() - .map_err(|error| error.to_string())?; + .resizable(true); + + if route == "/annotate" { + builder = builder.maximized(true).fullscreen(true); + } + + builder.build().map_err(|error| error.to_string())?; Ok(()) } @@ -167,7 +542,12 @@ pub fn run() { Ok(()) }) .invoke_handler(tauri::generate_handler![ + capture_source, + capture_screen, + list_capture_sources, + load_regions, open_popup_window, + save_regions, start_agent, stop_agent ]) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 4fb46b5..b6809c6 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -25,7 +25,13 @@ } ], "security": { - "csp": null + "csp": null, + "assetProtocol": { + "enable": true, + "scope": [ + "$APPDATA/data/screenshots/**" + ] + } } }, "bundle": { diff --git a/src/App.jsx b/src/App.jsx index 1b137c7..e69e3c7 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; import MainShell from "./components/MainShell"; +import AnnotationPage from "./pages/AnnotationPage"; import SecondaryWindow from "./windows/SecondaryWindow"; function App() { @@ -15,6 +16,17 @@ function App() { localStorage.setItem("theme", theme); }, [theme]); + useEffect(() => { + const onStorage = (event) => { + if (event.key === "theme" && event.newValue) { + setTheme(event.newValue); + } + }; + + window.addEventListener("storage", onStorage); + return () => window.removeEventListener("storage", onStorage); + }, []); + useEffect(() => { const onHashChange = () => setRoute(window.location.hash.replace("#", "") || "/"); window.addEventListener("hashchange", onHashChange); @@ -22,10 +34,13 @@ function App() { }, []); const isWindowRoute = route.startsWith("/window/"); + const isAnnotationRoute = route === "/annotate"; return (
- {isWindowRoute ? ( + {isAnnotationRoute ? ( + + ) : isWindowRoute ? ( + {children} + + + + + ); +} + +function SelectScrollUpButton({ className, ...props }) { + return ( + + + + ); +} + +function SelectScrollDownButton({ className, ...props }) { + return ( + + + + ); +} + +function SelectContent({ className, children, position = "popper", ...props }) { + return ( + + + + + {children} + + + + + ); +} + +function SelectItem({ className, children, ...props }) { + return ( + + + + + + + {children} + + ); +} + +export { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue }; diff --git a/src/pages/AnnotationPage.jsx b/src/pages/AnnotationPage.jsx new file mode 100644 index 0000000..a558693 --- /dev/null +++ b/src/pages/AnnotationPage.jsx @@ -0,0 +1,525 @@ +import { useEffect, useMemo, useRef, useState } from "react"; +import { convertFileSrc, invoke } from "@tauri-apps/api/core"; +import { getCurrentWindow } from "@tauri-apps/api/window"; +import { Check, MousePointer2, Trash2, X } from "lucide-react"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select"; + +const regionTypes = [ + { value: "contact_list", label: "联系人区域" }, + { value: "chat_content", label: "聊天内容区域" }, + { value: "input_box", label: "输入框区域" }, + { value: "send_button", label: "发送按钮区域" }, + { value: "custom", label: "自定义区域" }, +]; + +const emptyCapture = { + screenshotPath: "", + screenshotWidth: 0, + screenshotHeight: 0, + scaleFactor: window.devicePixelRatio || 1, +}; + +function clamp(value, min, max) { + return Math.min(Math.max(value, min), max); +} + +function normalizeBox(start, end) { + return [ + Math.min(start.x, end.x), + Math.min(start.y, end.y), + Math.max(start.x, end.x), + Math.max(start.y, end.y), + ]; +} + +function roundCoord(value) { + return Math.round(value * 100) / 100; +} + +function toScreenBox(box, capture) { + const scaleFactor = capture.scaleFactor || 1; + const sourceX = capture.source?.x || 0; + const sourceY = capture.source?.y || 0; + const sourceBox = toSourceBox(box, scaleFactor); + + return [ + roundCoord(sourceX + sourceBox[0]), + roundCoord(sourceY + sourceBox[1]), + roundCoord(sourceX + sourceBox[2]), + roundCoord(sourceY + sourceBox[3]), + ]; +} + +function toSourceBox(box, scaleFactor) { + return box.map((value) => roundCoord(value / scaleFactor)); +} + +function createRegion(box, capture) { + const bboxImage = box.map(roundCoord); + const type = "custom"; + + return { + id: crypto.randomUUID(), + name: `区域 ${new Date().toLocaleTimeString("zh-CN", { hour12: false })}`, + type, + description: "", + bbox_image: bboxImage, + bbox_source: toSourceBox(bboxImage, capture.scaleFactor || 1), + bbox_screen: toScreenBox(bboxImage, capture), + scaleFactor: capture.scaleFactor || 1, + }; +} + +function normalizeRegion(region) { + return { + ...region, + description: region.description || "", + bbox_source: region.bbox_source || region.bbox_image.map((value) => roundCoord(value / (region.scaleFactor || 1))), + }; +} + +function formatCaptureTiming(capture, frontendMs) { + const totalMs = capture.totalMs ?? Math.round(frontendMs); + + return [ + "拖拽截图区域创建矩形框", + `总耗时 ${totalMs}ms`, + `枚举屏幕 ${capture.screenListMs ?? "-"}ms`, + `截屏 ${capture.captureMs ?? "-"}ms`, + `保存 ${capture.saveMs ?? "-"}ms`, + `前端等待 ${Math.round(frontendMs)}ms`, + ].join(" · "); +} + +export default function AnnotationPage() { + const [capture, setCapture] = useState(emptyCapture); + const [regions, setRegions] = useState([]); + const [selectedId, setSelectedId] = useState(null); + const [drawStart, setDrawStart] = useState(null); + const [drawEnd, setDrawEnd] = useState(null); + const [status, setStatus] = useState("正在准备截图..."); + const [saving, setSaving] = useState(false); + const imageRef = useRef(null); + + const selectedRegion = regions.find((region) => region.id === selectedId) || null; + const screenshotSrc = capture.screenshotPath ? convertFileSrc(capture.screenshotPath) : ""; + const tempBox = drawStart && drawEnd ? normalizeBox(drawStart, drawEnd) : null; + + const typeLabelMap = useMemo( + () => Object.fromEntries(regionTypes.map((type) => [type.value, type.label])), + [], + ); + + useEffect(() => { + let cancelled = false; + + async function init() { + const stored = sessionStorage.getItem("pendingAnnotationCapture"); + if (stored) { + const nextCapture = JSON.parse(stored); + if (!cancelled) { + setCapture(nextCapture); + setStatus("拖拽截图区域创建矩形框"); + if (window.__TAURI_INTERNALS__) { + await getCurrentWindow().setFullscreen(true); + } + } + return; + } + + const sharedCapture = localStorage.getItem("annotationCaptureResult"); + if (sharedCapture) { + const nextCapture = JSON.parse(sharedCapture); + if (!cancelled) { + setCapture(nextCapture); + setStatus("拖拽截图区域创建矩形框"); + } + return; + } + + if (localStorage.getItem("annotationCaptureStatus") === "pending") { + setStatus("正在截取屏幕,请稍候..."); + const startedAt = performance.now(); + const nextCapture = await new Promise((resolve, reject) => { + const readResult = () => { + const result = localStorage.getItem("annotationCaptureResult"); + if (result) { + resolve(JSON.parse(result)); + return true; + } + + const error = localStorage.getItem("annotationCaptureError"); + if (error) { + reject(new Error(error)); + return true; + } + + return false; + }; + + if (readResult()) return; + + const timer = window.setInterval(() => { + if (readResult()) window.clearInterval(timer); + }, 100); + + window.addEventListener("storage", () => readResult(), { once: true }); + }); + + if (!cancelled) { + sessionStorage.setItem("pendingAnnotationCapture", JSON.stringify(nextCapture)); + setCapture(nextCapture); + setStatus(formatCaptureTiming(nextCapture, performance.now() - startedAt)); + } + return; + } + + if (!window.__TAURI_INTERNALS__) { + setStatus("浏览器预览模式无法截屏,请在 Tauri 应用中使用"); + return; + } + + setStatus("正在截取屏幕,请稍候..."); + const startedAt = performance.now(); + const nextCapture = await invoke("capture_screen"); + if (!cancelled) { + sessionStorage.setItem("pendingAnnotationCapture", JSON.stringify(nextCapture)); + setCapture(nextCapture); + setStatus(formatCaptureTiming(nextCapture, performance.now() - startedAt)); + await getCurrentWindow().setFullscreen(true); + } + } + + init().catch((error) => { + setStatus(`截屏失败:${String(error)}`); + }); + + return () => { + cancelled = true; + }; + }, []); + + useEffect(() => { + async function loadSaved() { + if (!window.__TAURI_INTERNALS__) return; + + const saved = await invoke("load_regions"); + if (saved?.regions?.length) { + setRegions(saved.regions.map(normalizeRegion)); + setSelectedId(saved.regions[0].id); + } + } + + loadSaved().catch(() => {}); + }, []); + + function getImagePoint(event) { + const rect = imageRef.current?.getBoundingClientRect(); + if (!rect || !capture.screenshotWidth || !capture.screenshotHeight) return null; + + const displayX = clamp(event.clientX - rect.left, 0, rect.width); + const displayY = clamp(event.clientY - rect.top, 0, rect.height); + + return { + x: (displayX * capture.screenshotWidth) / rect.width, + y: (displayY * capture.screenshotHeight) / rect.height, + }; + } + + function handlePointerDown(event) { + if (event.button !== 0) return; + const point = getImagePoint(event); + if (!point) return; + + event.currentTarget.setPointerCapture(event.pointerId); + setDrawStart(point); + setDrawEnd(point); + setSelectedId(null); + } + + function handlePointerMove(event) { + if (!drawStart) return; + const point = getImagePoint(event); + if (point) setDrawEnd(point); + } + + function handlePointerUp(event) { + if (!drawStart || !drawEnd) return; + + if (event.currentTarget.hasPointerCapture(event.pointerId)) { + event.currentTarget.releasePointerCapture(event.pointerId); + } + + const box = normalizeBox(drawStart, drawEnd); + const width = box[2] - box[0]; + const height = box[3] - box[1]; + setDrawStart(null); + setDrawEnd(null); + + if (width < 6 || height < 6) return; + + const region = createRegion(box, capture); + setRegions((current) => [...current, region]); + setSelectedId(region.id); + } + + function handlePointerCancel(event) { + if (event.currentTarget.hasPointerCapture(event.pointerId)) { + event.currentTarget.releasePointerCapture(event.pointerId); + } + setDrawStart(null); + setDrawEnd(null); + } + + function updateSelected(patch) { + setRegions((current) => + current.map((region) => (region.id === selectedId ? { ...region, ...patch } : region)), + ); + } + + function deleteSelected() { + if (!selectedId) return; + setRegions((current) => current.filter((region) => region.id !== selectedId)); + setSelectedId(null); + } + + async function exitAnnotation() { + sessionStorage.removeItem("pendingAnnotationCapture"); + localStorage.removeItem("annotationCaptureStatus"); + localStorage.removeItem("annotationCaptureResult"); + localStorage.removeItem("annotationCaptureError"); + localStorage.removeItem("annotationCaptureSource"); + if (window.__TAURI_INTERNALS__) { + const currentWindow = getCurrentWindow(); + if (currentWindow.label !== "main") { + await currentWindow.close(); + return; + } + + await currentWindow.setFullscreen(false); + } + window.location.hash = "/"; + } + + async function save() { + if (!window.__TAURI_INTERNALS__) { + setStatus("浏览器预览模式无法保存到本地文件"); + return; + } + + setSaving(true); + const now = new Date().toISOString(); + const existing = await invoke("load_regions").catch(() => null); + const annotation = { + app: "wechat", + screenshotPath: capture.screenshotPath, + screenshotWidth: capture.screenshotWidth, + screenshotHeight: capture.screenshotHeight, + scaleFactor: capture.scaleFactor || 1, + source: capture.source || null, + regions, + createdAt: existing?.createdAt || now, + updatedAt: now, + }; + + try { + const path = await invoke("save_regions", { annotation }); + sessionStorage.setItem("lastRegionsPath", path); + setStatus(`已保存:${path}`); + await exitAnnotation(); + } catch (error) { + setStatus(`保存失败:${String(error)}`); + } finally { + setSaving(false); + } + } + + return ( +
+