From 55be953eef4c95f9f3cd665e5457523d27d34a20 Mon Sep 17 00:00:00 2001 From: fofolee Date: Tue, 14 Jan 2025 01:20:51 +0800 Subject: [PATCH] =?UTF-8?q?win=E8=87=AA=E5=8A=A8=E5=8C=96=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E6=96=B0=E5=A2=9E=E7=9B=91=E6=8E=A7=E5=89=AA=E8=B4=B4?= =?UTF-8?q?=E6=9D=BF=E5=92=8C=E6=96=87=E4=BB=B6=E5=A4=B9=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E7=AD=89=E5=BE=85=E5=89=AA=E8=B4=B4=E6=9D=BF=E5=8F=98?= =?UTF-8?q?=E5=8C=96=E5=92=8C=E6=96=87=E4=BB=B6=E5=A4=B9=E5=8F=98=E5=8C=96?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E8=BF=94=E5=9B=9E=E5=8F=98=E5=8C=96=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin/lib/csharp/monitor.cs | 279 ++++++++++++++++++++ plugin/lib/js/monitor.js | 49 ++++ plugin/lib/quickcomposer/windows/index.js | 2 + plugin/lib/quickcomposer/windows/monitor.js | 48 ++++ src/js/composer/commands/windowsCommands.js | 65 +++++ 5 files changed, 443 insertions(+) create mode 100644 plugin/lib/csharp/monitor.cs create mode 100644 plugin/lib/js/monitor.js create mode 100644 plugin/lib/quickcomposer/windows/monitor.js diff --git a/plugin/lib/csharp/monitor.cs b/plugin/lib/csharp/monitor.cs new file mode 100644 index 0000000..5db5adc --- /dev/null +++ b/plugin/lib/csharp/monitor.cs @@ -0,0 +1,279 @@ +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Windows.Forms; +using System.Runtime.InteropServices; +using System.Collections.Generic; +using System.Linq; +using System.Drawing; + +class Monitor { + // Win32 API + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext); + + private static FileSystemWatcher fsWatcher; + private static IntPtr nextClipboardViewer; + private static ClipboardForm clipboardForm; + private static bool running = true; + private static bool listenOnce = false; + private static bool isFirstClipboardEvent = true; + + private class ClipboardForm : Form { + public ClipboardForm() { + this.ShowInTaskbar = false; + this.Visible = false; + this.WindowState = FormWindowState.Minimized; + this.FormBorderStyle = FormBorderStyle.None; + this.Size = new System.Drawing.Size(1, 1); + this.Load += (sender, e) => { + nextClipboardViewer = SetClipboardViewer(this.Handle); + }; + this.FormClosing += (sender, e) => { + ChangeClipboardChain(this.Handle, nextClipboardViewer); + }; + } + + protected override CreateParams CreateParams { + get { + CreateParams cp = base.CreateParams; + cp.ExStyle |= 0x80; // WS_EX_TOOLWINDOW + return cp; + } + } + + protected override void WndProc(ref Message m) { + if (m.Msg == 0x308) { // WM_DRAWCLIPBOARD + if (isFirstClipboardEvent) { + isFirstClipboardEvent = false; + } else { + HandleClipboardChanged(); + if (listenOnce) { + running = false; + this.BeginInvoke(new Action(this.Close)); + } + } + SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam); + } + else if (m.Msg == 0x30D) { // WM_CHANGECBCHAIN + if (m.WParam == nextClipboardViewer) + nextClipboardViewer = m.LParam; + else if (nextClipboardViewer != IntPtr.Zero) + SendMessage(nextClipboardViewer, m.Msg, m.WParam, m.LParam); + } + base.WndProc(ref m); + } + } + + public static void ShowHelp() { + Console.WriteLine(@"Windows 监控工具使用说明 +用法: monitor.exe -type <监控类型> [参数...] + +监控类型: + clipboard - 剪贴板监控 + 参数: + -once 只监听一次变化就停止(可选) + 示例: + monitor.exe -type clipboard -once + + filesystem - 文件系统监控 + 参数: + -path <路径> 要监控的文件夹路径 + -filter <过滤器> 文件过滤器,如 *.txt(可选,默认监控所有文件) + -recursive 是否监控子文件夹(可选,默认为 true) + -once 只监听一次变化就停止(可选) + 示例: + monitor.exe -type filesystem -path C:\MyFolder -filter *.txt -recursive true -once + +返回值: + 监控到的变化会实时输出 JSON 格式的事件信息: + 剪贴板事件: + {""type"": ""clipboard"", ""format"": ""文本/图片/文件"", ""content"": ""变化内容""} + 文件系统事件: + {""type"": ""filesystem"", ""event"": ""created/changed/deleted/renamed"", ""path"": ""文件路径"", ""oldPath"": ""重命名前的路径""} + +注意事项: + 1. 按 Ctrl+C 停止监控 + 2. 剪贴板监控支持文本、图片和文件格式 + 3. 文件系统监控支持文件和文件夹的创建、修改、删除和重命名事件 + 4. 使用 -once 参数可以在监听到第一次变化后自动停止"); + } + + private static string GetArgumentValue(string[] args, string key) { + for (int i = 0; i < args.Length - 1; i++) { + if (args[i].Equals(key, StringComparison.OrdinalIgnoreCase)) { + return args[i + 1]; + } + } + return null; + } + + private static string EscapeJsonString(string str) { + if (str == null) return "null"; + StringBuilder sb = new StringBuilder(); + foreach (char c in str) { + switch (c) { + case '\"': sb.Append("\\\""); break; + case '\\': sb.Append("\\\\"); break; + case '\b': sb.Append("\\b"); break; + case '\f': sb.Append("\\f"); break; + case '\n': sb.Append("\\n"); break; + case '\r': sb.Append("\\r"); break; + case '\t': sb.Append("\\t"); break; + default: + if (c < ' ') { + sb.Append(string.Format("\\u{0:X4}", (int)c)); + } + else { + sb.Append(c); + } + break; + } + } + return string.Format("\"{0}\"", sb.ToString()); + } + + private static void OutputEvent(string type, string format, string content) { + string json = string.Format("{{\"type\": {0}, \"format\": {1}, \"content\": {2}}}", + EscapeJsonString(type), + EscapeJsonString(format), + EscapeJsonString(content)); + Console.WriteLine(json); + } + + private static void OutputFileSystemEvent(string type, string eventType, string path, string oldPath = null) { + StringBuilder json = new StringBuilder(); + json.Append("{\"type\": ").Append(EscapeJsonString(type)); + json.Append(", \"event\": ").Append(EscapeJsonString(eventType)); + json.Append(", \"path\": ").Append(EscapeJsonString(path)); + if (oldPath != null) { + json.Append(", \"oldPath\": ").Append(EscapeJsonString(oldPath)); + } + json.Append("}"); + Console.WriteLine(json.ToString()); + } + + private static void StartClipboardMonitor() { + Thread thread = new Thread(() => { + clipboardForm = new ClipboardForm(); + Application.Run(clipboardForm); + }); + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + } + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); + + private static void HandleClipboardChanged() { + if (Clipboard.ContainsText()) { + OutputEvent("clipboard", "text", Clipboard.GetText()); + } + else if (Clipboard.ContainsImage()) { + OutputEvent("clipboard", "image", "image_data"); + } + else if (Clipboard.ContainsFileDropList()) { + OutputEvent("clipboard", "files", string.Join(", ", Clipboard.GetFileDropList().Cast())); + } + } + + private static void StartFileSystemMonitor(string path, string fileFilter, bool includeSubdirectories) { + fsWatcher = new FileSystemWatcher(path); + fsWatcher.Filter = fileFilter ?? "*.*"; + fsWatcher.IncludeSubdirectories = includeSubdirectories; + + FileSystemEventHandler handler = (s, e) => { + OutputFileSystemEvent("filesystem", e.ChangeType.ToString().ToLower(), e.FullPath); + if (listenOnce) { + running = false; + fsWatcher.Dispose(); + } + }; + + RenamedEventHandler renameHandler = (s, e) => { + OutputFileSystemEvent("filesystem", "renamed", e.FullPath, e.OldFullPath); + if (listenOnce) { + running = false; + fsWatcher.Dispose(); + } + }; + + fsWatcher.Created += handler; + fsWatcher.Changed += handler; + fsWatcher.Deleted += handler; + fsWatcher.Renamed += renameHandler; + + fsWatcher.EnableRaisingEvents = true; + } + + private static bool HasArgument(string[] args, string key) { + return Array.Exists(args, arg => arg.Equals(key, StringComparison.OrdinalIgnoreCase)); + } + + public static void Main(string[] args) { + if (args.Length == 0 || args[0] == "-h" || args[0] == "--help") { + ShowHelp(); + return; + } + + string type = GetArgumentValue(args, "-type"); + if (string.IsNullOrEmpty(type)) { + Console.Error.WriteLine("Error: 必须指定监控类型 (-type)"); + return; + } + + listenOnce = HasArgument(args, "-once"); + + Console.CancelKeyPress += (s, e) => { + running = false; + if (fsWatcher != null) { + fsWatcher.Dispose(); + } + if (clipboardForm != null) { + clipboardForm.Invoke(new Action(() => clipboardForm.Close())); + } + Environment.Exit(0); + }; + + try { + switch (type.ToLower()) { + case "clipboard": + StartClipboardMonitor(); + break; + + case "filesystem": + string path = GetArgumentValue(args, "-path"); + if (string.IsNullOrEmpty(path)) { + Console.Error.WriteLine("Error: 必须指定监控路径 (-path)"); + return; + } + + string filter = GetArgumentValue(args, "-filter"); + bool recursive = true; + string recursiveArg = GetArgumentValue(args, "-recursive"); + if (!string.IsNullOrEmpty(recursiveArg)) { + recursive = bool.Parse(recursiveArg); + } + + StartFileSystemMonitor(path, filter, recursive); + break; + + default: + Console.Error.WriteLine(string.Format("Error: 不支持的监控类型: {0}", type)); + return; + } + + while (running) { + Thread.Sleep(100); + } + } + catch (Exception ex) { + Console.Error.WriteLine(string.Format("Error: {0}", ex.Message)); + } + } +} diff --git a/plugin/lib/js/monitor.js b/plugin/lib/js/monitor.js new file mode 100644 index 0000000..c897c02 --- /dev/null +++ b/plugin/lib/js/monitor.js @@ -0,0 +1,49 @@ +const fs = require("fs"); +const path = require("path"); +const quickcommand = require("../../quickcommand"); + +// 读取 monitor.cs 模板 +const monitorTemplate = fs.readFileSync( + path.join(__dirname, "..", "csharp", "monitor.cs"), + "utf8" +); + +// 监控剪贴板变化 +const watchClipboard = async function () { + const args = ["-type", "clipboard"]; + const result = await quickcommand.runCsharp(monitorTemplate, args); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + return true; +}; + +// 监控文件系统变化 +const watchFileSystem = async function (options = {}) { + const { path: watchPath, filter = "*.*", recursive = true } = options; + + if (!watchPath) { + throw new Error("必须指定监控路径"); + } + + const args = ["-type", "filesystem", "-path", watchPath]; + + if (filter !== "*.*") { + args.push("-filter", filter); + } + + if (!recursive) { + args.push("-recursive", "false"); + } + + const result = await quickcommand.runCsharp(monitorTemplate, args); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + return true; +}; + +module.exports = { + watchClipboard, + watchFileSystem, +}; diff --git a/plugin/lib/quickcomposer/windows/index.js b/plugin/lib/quickcomposer/windows/index.js index 2435ddf..7393a2d 100644 --- a/plugin/lib/quickcomposer/windows/index.js +++ b/plugin/lib/quickcomposer/windows/index.js @@ -1,9 +1,11 @@ const window = require("./window"); const message = require("./message"); const automation = require("./automation"); +const monitor = require("./monitor"); module.exports = { window, message, automation, + monitor, }; diff --git a/plugin/lib/quickcomposer/windows/monitor.js b/plugin/lib/quickcomposer/windows/monitor.js new file mode 100644 index 0000000..caa72a7 --- /dev/null +++ b/plugin/lib/quickcomposer/windows/monitor.js @@ -0,0 +1,48 @@ +const fs = require("fs"); +const path = require("path"); + +// 读取 monitor.cs 模板 +const monitorTemplate = fs.readFileSync( + path.join(__dirname, "..", "..", "csharp", "monitor.cs"), + "utf8" +); + +// 监控剪贴板变化 +const watchClipboard = async function () { + const args = ["-type", "clipboard", "-once"]; + const result = await quickcommand.runCsharp(monitorTemplate, args); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + return JSON.parse(result); +}; + +// 监控文件系统变化 +const watchFileSystem = async function (watchPath, options = {}) { + const { filter = "*.*", recursive = true } = options; + + if (!watchPath) { + throw new Error("必须指定监控路径"); + } + + const args = ["-type", "filesystem", "-path", watchPath, "-once"]; + + if (filter !== "*.*") { + args.push("-filter", filter); + } + + if (!recursive) { + args.push("-recursive", "false"); + } + + const result = await quickcommand.runCsharp(monitorTemplate, args); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + return JSON.parse(result); +}; + +module.exports = { + watchClipboard, + watchFileSystem, +}; diff --git a/src/js/composer/commands/windowsCommands.js b/src/js/composer/commands/windowsCommands.js index 98d0b69..48924f3 100644 --- a/src/js/composer/commands/windowsCommands.js +++ b/src/js/composer/commands/windowsCommands.js @@ -455,5 +455,70 @@ export const windowsCommands = { }, ], }, + { + value: "quickcomposer.windows.monitor.watchClipboard", + label: "剪贴板/文件监控", + desc: "监控系统变化", + icon: "monitor_heart", + isAsync: true, + outputVariable: "monitorEvent", + saveOutput: true, + showLoading: true, + subCommands: [ + { + value: "quickcomposer.windows.monitor.watchClipboard", + label: "等待剪贴板变化", + icon: "content_paste", + }, + { + value: "quickcomposer.windows.monitor.watchFileSystem", + label: "等待文件夹变化", + icon: "folder", + config: [ + { + label: "监控路径", + component: "VariableInput", + icon: "folder", + width: 12, + options: { + dialog: { + type: "open", + options: { + title: "选择文件夹", + properties: ["openDirectory"], + }, + }, + }, + placeholder: "要监控的文件夹路径", + required: true, + }, + { + key: "options", + component: "OptionEditor", + width: 12, + options: { + filter: { + label: "文件过滤", + component: "VariableInput", + icon: "filter_alt", + width: 6, + placeholder: "如: *.txt, *.docx", + }, + recursive: { + label: "包含子文件夹", + component: "CheckButton", + icon: "subdirectory_arrow_right", + width: 6, + defaultValue: true, + }, + }, + defaultValue: { + recursive: false, + }, + }, + ], + }, + ], + }, ], };