ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

DankMaterialShell Launcher 插件开发指南:构建可搜索的自定义启动器

DankMaterialShell Launcher 插件开发指南:构建可搜索的自定义启动器 桌面应用【免费下载链接】DankMaterialShellDesktop shell for wayland compositors built with Quickshell GO, optimized for niri, hyprland, sway, MangoWC, labwc, and MiracleWM.项目地址https://gitcode.com/gh_mirrors/da/DankMaterialShell点击查看免费下载本指南以 DankMaterialShellDMS的 Launcher 插件体系为核心讲解如何用 QML 为桌面启动器编写带触发词过滤、可搜索、可执行动作的扩展项并完整覆盖项目结构、清单文件、图标类型、动作解析、状态持久化与设置界面等实战内容。读完本文你将能够从零编写一个可发布到 DMS 插件目录的 launcher 型插件并将其接入应用抽屉app drawer与启动器搜索结果。一、Launcher 插件是什么Launcher 插件是 DankMaterialShell 六种插件类型之一它的职责是向 DMS 启动器launcher提供可搜索的条目items与可执行的动作actions。与在顶栏显示 pill 的 widget 插件、在后台静默运行的 daemon 插件不同launcher 插件完全依赖“触发词 查询文本”的方式工作用户在启动器中输入触发词如#、、!、img后插件条目才会出现随后输入的内容会被当作查询参数传给插件做过滤。从 SKILL.md 可知DMS 插件统一发现自~/.config/DankMaterialShell/plugins/目录launcher 插件的基本目录结构为~/.config/DankMaterialShell/plugins/YourPlugin/ plugin.json # 必需清单文件含触发词、组件路径等元数据 YourLauncher.qml # 必需主 QML 组件普通 Item YourSettings.qml # 可选设置界面 *.js # 可选JavaScript 工具与 widget/daemon 使用PluginComponent作为基类不同launcher 插件使用普通Item——这是本类型最核心的差异点也是新手最容易犯的错误详见后文常见错误清单。在 PluginService.qml 的实现中可以看到 launcher 插件的注册与实例化链路插件管理器维护pluginLauncherComponents注册表ensureLauncherInstance(pluginId)负责按需实例化组件PluginService.qml#L979-L1003getLauncherPlugins()则把已加载且含 launcher 表面的插件聚合返回PluginService.qml#L1192-L1203并可通过requestLauncherUpdate(pluginId)信号请求刷新启动器内容PluginService.qml#L57。二、最小可运行组件骨架官方模板位于 .agents/skills/dms-plugin-dev/assets/templates/launcher/Launcher.qml它给出了一个可直接复制改写的起点。launcher 插件基类是一个普通Item必需的导入只有QtQuick与qs.Servicesimport QtQuick import qs.Services Item { id: root property var pluginService: null property string trigger: # signal itemsChanged() function getItems(query) { // Return array of items return [] } function executeItem(item) { // Handle item selection } }关于模板中的Component.onCompleted片段trigger pluginService.loadPluginData(myLauncher, trigger, #)它会在组件加载完成时从插件设置中恢复用户自定义的触发词保证插件重启后触发词不丢失。必需接口一览launcher 插件与宿主交互的全部接口如下表缺一不可成员类型说明pluginServiceproperty宿主注入的 PluginService 引用声明为null由框架注入triggerproperty激活插件的触发词字符串itemsChangedsignal条目列表变化时发出触发启动器 UI 刷新getItems(query)function返回匹配查询的条目数组executeItem(item)function处理条目被选中时的动作其中pluginService若未声明或类型不符注入会失败SKILL.md 的常见错误清单第 2 条专门强调“缺失property var pluginService: null会导致注入失败”。三、插件清单plugin.json与触发词约束launcher 插件的plugin.json模板见 .agents/skills/dms-plugin-dev/assets/templates/launcher/plugin.json{ id: myLauncher, name: My Launcher, description: Custom launcher plugin with searchable items, version: 1.0.0, author: Your Name, type: launcher, capabilities: [launcher], component: ./Launcher.qml, trigger: #, icon: search, settings: ./Settings.qml, permissions: [settings_read, settings_write] }结合 plugin-schema.json 与 plugin-manifest-reference.md有几个对 launcher 型插件至关重要的校验规则trigger是硬性必填schema 中的条件规则allOf分支明确规定当type为launcher时trigger字段必须存在对于components中包含launcher键的 composite 插件同样适用plugin-schema.json#L211-L236。id必须为 camelCase匹配^[a-zA-Z][a-zA-Z0-9]*$version必须为 semver 格式如1.0.0。component路径必须以./开头、以.qml结尾。若插件带设置界面permissions中必须声明settings_write否则设置 UI 会报错。真实的仓库示例 quickshell/PLUGINS/LauncherImageExample/plugin.json 展示了自定义字段的用法它使用触发词img并额外携带viewMode: tile与viewModeEnforced: true两个字段schema 允许additionalProperties即插件可以携带自定义元数据。四、条目Item结构getItems(query)返回的每个条目是一个纯 JavaScript 对象字段约定如下{ name: Item Display Name, // 必填启动器中显示的名称 icon: material:star, // 可选图标规格 comment: Description text, // 必填副标题/描述文本 action: type:data, // 必填动作标识符见“动作执行”一节 categories: [MyPlugin], // 必填包含插件分类名的数组 imageUrl: https://... // 可选tile 视图下显示的图片 }其中categories在 SKILL.md 的常见错误清单第 8 条被特别点名“忘记在 launcher 条目中写categories条目将无法显示”。即使你的插件只服务一个分类也必须在数组里给出至少一个分类名。五、四种图标类型icon字段的取值决定了启动器如何渲染图标共四种形态1. Material Design 图标{ icon: material:lightbulb } { icon: material:terminal } { icon: material:translate }以material:为前缀使用 Material Symbols Rounded 字体渲染也是模板默认推荐的方式。2. Unicode / Emoji 图标{ icon: unicode:smile_face }以unicode:为前缀按图标尺寸的 70%80% 渲染并跟随主题配色。3. 桌面主题图标{ icon: firefox } { icon: folder }不带任何前缀直接写图标名称使用用户当前安装的桌面图标主题icon theme。4. 无图标省略icon字段即可。启动器会隐藏图标区域把整行宽度让给条目名称适合纯文本型条目。六、触发词系统Trigger System触发词决定了插件条目何时出现在启动器中两种模式对比自定义触发词仅在输入触发词后显示条目{ trigger: # }行为规则单独输入#显示该插件的全部条目输入# query用 query 过滤插件条目传给getItems(query)的 query不含触发词前缀——触发词被剥离开后剩余文本才是查询参数。无触发词条目始终与常规应用并列显示{ trigger: }即把触发词设为空字符串插件条目常驻启动器与普通应用混排。运行时把空触发词保存到插件数据的写法如下Component.onCompleted: { trigger pluginService?.loadPluginData(pluginId, trigger, #) ?? # }注意这里使用了可选链?.与空值合并??——SKILL.md 常见错误清单第 9 条明确要求“始终使用可选链或空值检查不要假设 pluginService 一定非空”。加载逻辑的语义是读取已保存的触发词若不存在则回退到默认值#。模板 Launcher.qml 中的写法与之对应Component.onCompleted: { if (pluginService) { trigger pluginService.loadPluginData(myLauncher, trigger, #) } }七、动作执行Action Execution每个条目通过action字段描述“选中后做什么”格式为type:data。executeItem(item)内解析动作字符串并分发function executeItem(item) { const actionParts item.action.split(:) const actionType actionParts[0] const actionData actionParts.slice(1).join(:) switch (actionType) { case toast: ToastService?.showInfo(actionData) break case copy: Quickshell.execDetached([dms, cl, copy, actionData]) ToastService?.showInfo(Copied to clipboard) break case exec: Quickshell.execDetached(actionData.split( )) break case url: Quickshell.execDetached([xdg-open, actionData]) break default: console.warn(Unknown action type:, actionType) } }解析要点action.split(:)以第一个冒号切分类型与数据actionParts.slice(1).join(:)把剩余部分重新拼接保证数据段自身包含冒号也不会被破坏例如copy:https://example.com/a:b.pngtoast经ToastService.showInfo弹出提示ToastService来自qs.Services同样使用可选链防御copy通过Quickshell.execDetached调用dms cl copy命令写入剪贴板并附带复制成功提示exec把数据按空格拆分成参数数组后分离执行——注意这种拆分方式不支持带空格的单个参数如需更稳健的参数传递应使用[sh, -c, ...]形式见 SKILL.md 的进程执行说明url交给xdg-open打开链接未知类型回退到console.warn告警。这里还隐含一个来自 SKILL.md 的关键事实QML 运行时不存在浏览器 JavaScript APIglobalThis.clipboard不可用剪贴板操作必须走Quickshell.execDetached([dms, cl, copy, text])。八、搜索与过滤Search / FilteringgetItems(query)收到的query是去掉触发词前缀后的用户搜索文本。典型实现是“空查询返回全部非空查询做大小写不敏感的子串匹配”function getItems(query) { const allItems [ { name: Calculator, icon: material:calculate, comment: Open calculator, action: exec:gnome-calculator, categories: [Tools] }, { name: Terminal, icon: material:terminal, comment: Open terminal, action: exec:alacritty, categories: [Tools] } ] if (!query || query.length 0) return allItems const q query.toLowerCase() return allItems.filter(item item.name.toLowerCase().includes(q) || item.comment.toLowerCase().includes(q) ) }过滤维度一般覆盖name与comment两个字段。模板 Launcher.qml 采用完全相同的模式先判空、统一转小写、includes子串匹配。这也是 SKILL.md 第 3 步给出的 launcher 组件示例的实现方式items.filter(i i.name.toLowerCase().includes(q))。九、右键菜单动作Context Menu Actionslauncher 条目支持通过getContextMenuActions(item)提供右键菜单返回值结构与条目动作一致function getContextMenuActions(item) { return [ { name: Copy, icon: material:content_copy, action: copy: item.name }, { name: Open in Browser, icon: material:open_in_new, action: url: item.url } ] }要点右键菜单动作与左键主动作共用同一个executeItem()处理器所以getContextMenuActions返回的动作也必须遵循type:data格式并且需要executeItem已支持对应的动作类型。十、图片磁贴视图Image Tile View对于以图片为核心的启动器GIF 搜索、贴纸选择器等可以把视图切换为磁贴网格模式通过清单中的两个自定义字段控制{ viewMode: tile, viewModeEnforced: true }条目中改用imageUrl提供图片{ name: Image Title, imageUrl: https://example.com/image.png, comment: Description, action: copy:https://example.com/image.png, categories: [MyPlugin] }仓库自带的 LauncherImageExample 就是这一模式的标准示范触发词为img清单同时声明viewMode: tile与viewModeEnforced: true把启动器强制锁定为图片网格。viewMode/viewModeEnforced属于 schema 允许的自定义附加字段plugin-manifest-reference.md 的 “Additional Properties” 一节将其列为生产插件常见字段。十一、状态持久化State Persistence对带持久状态的插件便签、历史记录、收藏等插件系统提供两套 APIsavePluginState(id, key, val)/loadPluginState(id, key, default)运行时数据便签内容、历史、缓存写入独立的 state 文件savePluginData(id, key, val)/loadPluginData(id, key, default)用户偏好与配置写入 settings.json。便签型插件的典型模式property var notes: [] Component.onCompleted: { const saved pluginService?.loadPluginState(pluginId, notes, []) if (saved) notes saved } function addNote(text) { notes.push({ text: text, timestamp: Date.now() }) pluginService?.savePluginState(pluginId, notes, notes) itemsChanged() }这里的关键细节是修改数据后必须发出itemsChanged()信号否则启动器 UI 不会感知到条目列表变化。这与前面“必需接口”一节中itemsChanged的语义触发 UI 刷新相呼应。SKILL.md 还补充了持久化的第三层PluginGlobalVar仅运行时、跨实例共享用于多显示器场景的同步以及pluginData作为 PluginComponent 上的响应式属性自动从设置加载。十二、触发词配置的设置界面为了让用户自定义触发词或切换为“始终可见”插件应提供PluginSettings设置组件。模板 Settings.qml 完整演示了两种设置项import QtQuick import qs.Common import qs.Widgets import qs.Modules.Plugins PluginSettings { pluginId: myLauncher StringSetting { settingKey: trigger label: Trigger description: Type this prefix in the launcher to activate the plugin placeholder: # defaultValue: # } ToggleSetting { settingKey: noTrigger label: Always Visible description: Show items alongside regular apps without needing a trigger defaultValue: false } }要点PluginSettings必须声明pluginId与清单中的id一致所有设置项自动保存、自动加载无需手写读写逻辑StringSetting的settingKey对应loadPluginData(pluginId, trigger, ...)中的 key用户在设置界面保存新触发词后主组件Component.onCompleted里的加载逻辑会读回新值ToggleSetting的noTrigger与运行时“把 trigger 置空实现常驻显示”的策略互相配合UI 层给用户开关运行时层把开关翻译成空触发词前置条件清单permissions必须包含settings_write否则设置界面直接报错。十三、完整实战示例快速命令启动器下面是一个完整的“快速命令”launcher 插件与指南原例一致触发词为!提供锁定屏幕、截图、打开文件管理器三条命令支持查询过滤、动作执行与触发词恢复import QtQuick import Quickshell import qs.Services Item { id: root property var pluginService: null property string trigger: ! signal itemsChanged() property var commands: [ { name: Lock Screen, icon: material:lock, comment: Lock the session, action: exec:loginctl lock-session }, { name: Screenshot, icon: material:screenshot_monitor, comment: Take a screenshot, action: exec:grim }, { name: File Manager, icon: material:folder, comment: Open file manager, action: exec:nautilus } ] function getItems(query) { if (!query) return commands const q query.toLowerCase() return commands.filter(c c.name.toLowerCase().includes(q) || c.comment.toLowerCase().includes(q) ) } function executeItem(item) { const [type, ...rest] item.action.split(:) const data rest.join(:) if (type exec) { Quickshell.execDetached(data.split( )) } } Component.onCompleted: { if (pluginService) { trigger pluginService.loadPluginData(quickCommands, trigger, !) } } }这段代码演示了解构式动作解析const [type, ...rest] item.action.split(:)注意本示例只处理了exec类型其他类型会被静默忽略——生产插件应像第七节那样提供完整的switch分发与default告警分支。对应的plugin.json只需按第二节的模板把id、component、trigger等字段替换为quickCommands/./Launcher.qml/!即可。十四、调试、验证与常见错误验证清单与运行时调试用jq . plugin.json检查清单语法SKILL.md 建议的排查第一步将插件放入~/.config/DankMaterialShell/plugins/在设置中触发 “Scan for Plugins” 扫描启用插件后打开启动器输入触发词测试条目显示与过滤运行时可通过 IPC 命令在不重启 shell 的情况下重扫与重载插件dms ipc plugin-scan scan全量重扫、dms ipc plugin-scan reload id强制重载、dms ipc plugin-scan status id查看加载状态与错误启动器组件实例化失败时PluginService会记录错误PluginService.qml#L979-L990 的ensureLauncherInstance中对comp.errorString()做了日志输出。launcher 插件高频错误清单提炼自 SKILL.md 的 Common Mistakes 章节用PluginComponent而不是普通Item——launcher 基类必须是Item条目缺少categories字段——条目将无法显示清单缺少trigger——schema 校验直接失败composite 插件含launcher表面时同样必填不处理 null pluginService——始终使用?.可选链或空值检查同时提供component与components——二者只能取其一误用globalThis.clipboard——QML 运行时没有浏览器 API剪贴板用Quickshell.execDetached([dms, cl, copy, text])使用import QtQuick时调用Quickshell.execDetached失败——Quickshell需要单独的import Quickshell清单类型与表面不匹配——launcher 表面需要type: launcher或components中带launcher键使用已弃用的requires字段——应使用dependencies有设置组件却未声明settings_write权限——设置 UI 会显示错误。十五、小结Launcher 插件是 DMS 插件体系中唯一以“普通Item 触发词 查询过滤”为核心交互模型的类型。本指南覆盖了从最小骨架、清单约束、条目结构、四种图标、触发词语义、动作解析、搜索过滤、右键菜单、图片磁贴、状态持久化到设置界面与调试排错的完整链路。直接复用 launcher 模板目录 的三个文件Launcher.qml、Settings.qml、plugin.json即可快速起步参考 LauncherImageExample 可学习磁贴视图等进阶形态完整的清单字段与 schema 校验规则见 plugin-manifest-reference.md 与 plugin-schema.json插件系统的服务端实现可深入阅读 PluginService.qml。赞分享桌面应用【免费下载链接】DankMaterialShellDesktop shell for wayland compositors built with Quickshell GO, optimized for niri, hyprland, sway, MangoWC, labwc, and MiracleWM.项目地址https://gitcode.com/gh_mirrors/da/DankMaterialShell点击查看免费下载相关推荐3步轻松掌握Umi-OCR免费离线批量文字识别完美解决方案3步轻松掌握Umi OCR免费离线批量文字识别完美解决方案 您是否曾为从海量图片中手动提取文字而烦恼无论是整理会议截图、处理扫描文档还是收集网页资料传统OCR桌面应用SearXNG插件开发入门创建自定义搜索功能的完整指南SearXNG插件开发入门创建自定义搜索功能的完整指南 引言为什么需要自定义搜索插件 在信息爆炸的时代传统的搜索引擎往往无法满足特定场景下的搜索需求。S后端搜索引擎Autocomplete插件开发终极指南从零创建自定义搜索体验Autocomplete插件开发终极指南从零创建自定义搜索体验 Autocomplete是Algolia开发的一个快速、功能丰富的JavaScript自动补全前端UI组件搜索引擎上一篇ADK Python 模型容错实战用 FallbackModel 构建跨模型故障转移的可靠 Agent下一篇Apache Iceberg Go 0.5.0 发布V3 表规范、视图支持与删除文件能力全面落地创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表