插件开发教程

一句话概括:一个文件夹 = plugin.json + 一个(或零个)exe,放进插件目录就是一个 GFiles 插件。不需要 SDK、不写注册表、不用重启宿主——界面改完保存,重开窗口即生效。

一句话认识插件

GFiles 插件有三种形态,按需选择:

① 5 分钟写一个插件

目标:选中若干文件 → 右键 → 显示统计结果。无需编译任何 exe,用系统自带 PowerShell 即可体验全流程。

第 1 步:打开插件目录

设置 → 插件 → 外部插件 → 「打开插件目录」(默认 %APPDATA%\GFiles\plugins,不存在就手动创建)。

第 2 步:建一个 hello 文件夹,放两个文件

plugin.json:

{ "id": "fm.action.hello", "name": "Hello 统计", "version": "1.0.0", "type": "action", "cmd": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "%plugin%\\run.ps1"], "input": "stdin-json", "label": "统计选中的文件数" }

run.ps1:

$ctx = [Console]::In.ReadLine() | ConvertFrom-Json # 读宿主输入 "选中了 $($ctx.files.Count) 个文件,目录:$($ctx.cwd)"

第 3 步:刷新,右键试试

设置 → 插件 → 外部插件 → 「刷新」,列表出现「Hello 统计」即安装成功。选中几个文件 → 右键 → 外部插件 → 「统计选中的文件数」→ 弹出结果窗。

提示:体验完成后,正式分发的插件请把引擎换成 Go/.NET 独立 exe(见第 ⑫ 节)——PowerShell 拉脚本的模式会被杀软重点盯防。

② 安装位置与共享目录

多个插件共用的第三方工具放共享目录,一个工具一个子目录:

bin\plugins\public\7z\ ├─ 7z.exe # 主程序 ├─ 7z.dll # 必需:格式逻辑全在 dll,两者必须同目录 └─ 7zCon.sfx # 做自解压包时必需

动作插件用相对路径引用:"cmd": "../public/7z/7z.exe"。依赖同目录文件的程序必须走相对路径(exe + 依赖 DLL 一起放),只拷 exe 会起不来。

③ plugin.json 常用字段

字段说明
id必填,全局唯一,建议 fm.action.* / fm.codec.* 前缀
name / version必填。★升级插件必须改 version,否则更新器认为没有新版
typeaction(动作/界面)|codec(编解码);文件系统插件为 filesystem
cmdexe 路径,相对插件目录或绝对路径;可 ../ 借共享目录
cmdFallbackscmd 找不到时的候选路径(支持 %plugin% 与环境变量),提升换机存活率
inputstdin-json(默认,推荐)|argv
target / ext右键出现位置与扩展名过滤,见第 ④ 节
label必填,右键菜单文案
timeout秒,缺省 30,超时强杀
iconType / iconpng/ico 路径,或 svg 代码全文;缺省取 exe 自带图标
ui / uiMode自带界面入口;layer=主窗内遮罩卡片(默认)|window=独立窗口
frameless / width / heightwindow 模式:无边框自绘标题栏与启动尺寸
fileDropwindow 模式:允许从资源管理器拖文件进窗口,宿主把真实路径交给页面(见第 ⑭ 节)
alwaysOnTopwindow 模式:窗口置顶
toggleHotkey / hotkeywindow 模式:同一热键再按一次=收起(否则连按开两个窗);hotkey 为唤出键,可在设置→快捷键改
menus[]一个插件占多个右键项(每项可覆盖 label/target/ext/ui 等)
dblclicktrue=双击文件时按 ext 认领直接打开(右键"打开"不受影响)
codecExtsaction 插件附带缩略图能力(如视频抽帧)

完整字段与逐条注释见随包文档《外部插件开发教学》。

④ 出现位置:target

target出现位置
all目录 / 文件 / 空白处右键都出现
dir目录条目右键
files文件条目右键(可配 ext 过滤扩展名,如 "7z,zip,rar")
dirblank打开某目录后,其内部空白处右键
selecttext记事本里选中文字后的右键(text=选中文本)
twodir拆分窗格后右键:cwd=触发窗格,cwd2=另一窗格

可逗号多选:"target": "dir,dirblank"。扩展名统一不带点。

⑤ 宿主 → 插件的输入

stdin-json(默认):exe 从标准输入收到一行 JSON:

{ "cwd": "D:\\Photos", "cwd2": "E:\\Backup", "files": ["D:\\Photos\\a.jpg"], "text": "" }

argv:数据经命令行占位符传入,写在清单 args 里:%dir%、%dir2%、%files%(单独成元素时逐文件展开)、%text%、%plugin%(插件目录,用于锚定相对脚本)。

⑥ 插件 → 宿主的返回(stdout 契约)

纯文本(最简单):print 一行文字,宿主弹结果窗展示 + 一键复制。

JSON 结构化:最后一行输出 {"fm":...} 开头的 JSON:

{ "fm": { "action": "toast | open-text | open-html | open-file | copy", "title": "结果窗标题", "text": "……", // open-text / open-html 的内容 "file": "D:/out/result.png", // open-file:用系统默认程序打开 "clipboard": "……" // copy:写入剪贴板 } }

JSON 行前后的普通文本会被忽略,可以边打日志边在最后一行输出契约 JSON。

⑦ 自带 UI 与桥接 API

清单声明 "ui": "index.html" 后,触发插件改为加载你的页面,界面与交互全部由你定义。exe 只在你主动调 api/run 时才被拉起。每个插件界面想长什么样完全自定——这正是「插件界面百变」的来源。

桥接 API 常用表

API说明
/extplugin/api/sessionGET 触发上下文 {cwd, cwd2, files, cmd, dir, menu…}
/extplugin/api/runPOST 同步执行清单 cmd,返回 {code, stdout, stderr}
/extplugin/api/runstartPOST 后台运行,返回 {task}(长操作)
/extplugin/api/runpollGET 轮询累计输出 {done, code, stdout}(进度百分比就在 stdout)
/extplugin/api/runcancelPOST 取消后台任务
/extplugin/api/pick(pickfolder)POST 文件/目录选择对话框
/extplugin/api/statPOST 查询路径存在性与大小(预检依赖文件用)
/extplugin/api/notifyPOST 请求宿主刷新目录(新文件立刻现身)
/extplugin/api/iconPOST 批量取文件/文件夹的 shell 图标(16–256px,单批 ≤64 项)
/extplugin/api/launchPOST 启动 exe/lnk/bat/目录(CreateProcess,失败回退 ShellExecute——唯一能弹 UAC 的路)
/extplugin/api/dropGET 拿拖入文件的真实路径(长轮询,取出即清;需清单 fileDrop:true)
/extplugin/api/mediainfo / mediaGET 媒体元信息与视频流(播放器类插件)
/extplugin/api/closePOST 关闭自带 UI

页面凭 URL 上的 token 参数鉴权。window 模式最小骨架:

<!DOCTYPE html> <html><head><meta charset="utf-8"></head><body> <div id="app">…</div> <script> const token = new URLSearchParams(location.search).get('token'); async function api(path, body) { const opt = body !== undefined ? {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)} : {}; return (await fetch(path, opt)).json(); } (async () => { const ctx = await api('/extplugin/api/session'); // {cwd, files, ...} document.getElementById('app').textContent = ctx.cwd; })(); </script></body></html>
无边框窗口三件套:window + frameless 时,标题栏拖动调 api/drag、最大化调 api/maximize、边缘缩放宿主自动注入——页面只需自绘一条标题栏。范本见 7zip / videopreview。

⑧ 编解码插件(type=codec)

给宿主补齐系统不认识的格式:列表缩略图 + 查看器原图。exe 只需实现一个契约:

codec.exe --decode <输入文件> <输出.(jpg|png)>

⑨ 调试通道

不开主界面直接验证宿主识别与执行:

GFiles.exe --action-probe list [root] 枚举已识别的动作插件 GFiles.exe --action-probe run [root] <id> [cwd] [files] 无窗口试跑一次 GFiles.exe --action-probe uiserve [root] <dir> [files] UI 桥接冒烟

设置页「新增插件」时的试跑本身就是一次冒烟测试;被拒的插件看返回的 stderr 即可定位。

⑩ 范本对照表

内置插件源码全部公开(外加一份示例主题包),按想学的能力对号入座:

插件演示的能力
calc-clipboard选中文本触发 + 一行 stdout 契约(最小范本)
dircmp双窗格 twodir + argv 占位符 + 遮罩层 UI
filesync独立窗口 + 无边框 + 后台任务进度环 + 取消
7zip多菜单项 menus[] + 一份页面按 menu 字段分流 + 自解压预检
videopreview双击认领 + 视频抽帧缩略图 + 视频流播放
heifcodec--decode 契约 + livp 拆包
quick-launch启动器三件套:fileDrop 拖放 + alwaysOnTop + toggleHotkey 热键唤出(无 exe 纯页面)
flat-demo(示例主题)type=theme 图标包:槽位写法 / strong / 三形态图标值(docs/示例-图标主题/)

⑪ 常见坑

⑫ 引擎选型建议

需要引擎逻辑的插件(对比、同步、计算、转换…),建议用 Go(首选)或 .NET 编译成独立 exe: 单文件、毫秒启动、无运行时依赖,也不会触发杀软对「解释器拉脚本」的拦截。 PowerShell / BAT 脚本只适合一次性本机调试。教学文档 §14 有完整的选型论证与工程约定。

⑬ 图标主题插件(type=theme)

主题插件是什么都不做的插件:不声明 cmd/label/menus, 不进右键菜单、不执行命令、不需要 exe——只往文件列表的图标槽位里塞图标。 示例包见随包文档 docs/示例-图标主题/flat-demo/(4 个文件:清单 + 3 张 SVG)。

{ "type": "theme", "id": "fm.theme.demo-flat", "name": "扁平蓝(示例)", "version": "1.0.0", "author": "…", "desc": "…", "strong": false, "icons": { "folder": "folder.svg", # 所有目录 "file": "file.svg", # 只对"没有扩展名"的文件兜底 "ext:7z": "archive.svg", # 具体后缀(键必须带 ext: 前缀) "ext:zip": "archive.svg" } }

三条硬规则

提示:多个主题可同时启用各做一部分,顺序=启用先后(想调整就停用再启用); 被主题接管的目录还省一次系统图标查询,列表反而更快。冲突去 设置→插件→「图标冲突」区看三层诊断 (主题之间 / 主题 vs 动作插件 / 动作插件之间)。

⑭ 启动器型插件:拖放、置顶、热键唤出

「快捷启动」范本演示了独立窗口插件的三件套:把 exe / 桌面快捷方式 / 文件夹拖进窗口收录、 窗口置顶、全局热键随按随现。窗口行为全部由宿主承接,插件页只管界面(本范本甚至没有 exe)。

{ "ui": "index.html", "uiMode": "window", "frameless": true, "width": 800, "height": 600, "fileDrop": true, # 允许拖入文件,宿主把真实路径交给页面 "alwaysOnTop": true, # 唤出后不被其它窗口盖住 "toggleHotkey": true, # 同一热键再按一次=收起(否则连按开出两个窗) "hotkey": "Ctrl+Alt+Space" }

三个布尔字段都可在 menus[] 单项上覆盖(false 能盖掉顶层的 true)。 热键可在 设置→快捷键 里改,作用域可切「系统级」(任何程序前台都能按)。

页面侧接拖入的三行代码

<body data-file-drop-target> # ① 整条链路唯一静默失败点:缺了它拖入零反应且无报错 window.addEventListener('drop', consume) # ② 宿主已接管 preventDefault,页面不用管 const r = await fetch(`/extplugin/api/drop?token=${token}&wait=1500`) # ③ 长轮询拿真实路径,取出即清
启动别自己拼命令:用 /extplugin/api/launch——exe/lnk/bat 走 CreateProcess、失败回退 ShellExecute(唯一能弹 UAC 的路);目录→资源管理器;其它文件→默认程序。 自己 cmd /c start 会被 cmd 重新解析引号,带空格/中文/& 的路径会碎。 图标批量取 /extplugin/api/icon(与主窗口同一个 shell 缓存)。

下载 GFiles,动手试试 →