win自动化分类新增监控剪贴板和文件夹,支持等待剪贴板变化和文件夹变化,并返回变化内容

This commit is contained in:
fofolee 2025-01-14 01:20:51 +08:00
parent 0ff5c4b49f
commit 55be953eef
5 changed files with 443 additions and 0 deletions

View File

@ -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 <bool> 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<string>()));
}
}
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));
}
}
}

49
plugin/lib/js/monitor.js Normal file
View File

@ -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,
};

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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,
},
},
],
},
],
},
],
};