这两天写一个 linux only 的 application launcher, 用于替代 rofi. 在 linux 平台上的启动器都很简陋, KDE 上提供了 Krunner, 但是它只能在 KDE 上使用, 以及最近 raycast 大火, 我关注的各个 up 都有在用...
实现思路
还是使用 webview 来使用, 前面了解过 tauri, 但是它的开发体验不是很好, rust 的热加载太慢了, 所以选择了的 go 中的 wails.
ui 主要使用了 vue-command-palette 这个库, 可以说没有这个库,就没有这个项目了.
程序本体没有什么难点, 这两天主要费时间就是插件了
插件设计
插件目前是通过 iframe 来实现的, 但是 iframe 有很多限制, 比如无法跨域访问, input 文本框无法获取焦点等等
加载插件
实现了一个后端的文件服务器,加载插件的时候, 通过后端的文件服务器来加载插件
插件与主程序通信
由于跨域了,所以不能直接在 window
上挂载方法进行调用, 只能通过 window.postmessage
来进行通信.
这个也很难受, 子窗口调用方法后, 不能直接返回结果, 只能通过 postmessage
来返回结果, 例如这一段代码:
// https://github.com/fzdwx/launcher-api/blob/main/src/ext/api/index.ts#LL21C1-L34C2
function getClipText(callback: (text: string) => void) {
window.parent.postMessage(buildEvent(getClipTextAction), '*');
window.addEventListener('message', (event) => {
const {action, data} = event.data as ExtEvent<string>;
if (action === getClipTextAction) {
callback(data || '');
}
}, {once: true})
}
iframe 无法获取焦点
原本以为可以自动获取焦点, 这样 iframe 就可以自主渲染所以内容了, 但万万没想到, 怎样调用 focus
方法都无法获取焦点,
没办法, 只能强制在主窗口中渲染 input 框, iframe 渲染剩下的内容.
但这又会导致一个问题, 比如说用户按下 ArrowDown
这个时候 list 应该向下移动, 但是 input 和 list 不在同一个窗口中,
所以无法响应. 只能 hack 代码了
// https://github.com/fzdwx/launcher/blob/main/frontend/src/components/ExtensionFrame.vue#L33-L43
// iframe 中接收到 keydown 事件, 然后发送
const listener = (e: KeyboardEvent) => {
let event = {
code: e.code,
key: e.key,
ctrlKey: e.ctrlKey,
altKey: e.altKey,
metaKey: e.metaKey,
}
const data = JSON.stringify(event);
extensionFrame.value.contentWindow.postMessage(buildEvent(commandKeyDownAction, data), "*")
};
window.addEventListener('keydown', listener)
// 处理事件
//https://github.com/fzdwx/launcher-api/blob/main/src/ext/api/index.ts#L86
if (action === commandKeyDownAction) {
commandEvent.emitter.emit('keydown', JSON.parse(data || '{}'));
}
// https://github.com/fzdwx/launcher-api/blob/main/src/components/Command.vue#L230
emitter.on("keydown", (e) => {
handleKeyDown(e)
})
插件分发
- 提交一个 pr https://github.com/fzdwx/launcher-extension/blob/main/extensions.json 添加你的插件的信息
- 修改插件中的
package.json
中的launcher
字段, 需要与extensions.json
中的一致 - 插件仓库必须提交
dist
目录, 里面包含了插件的代码, 以及index.html
文件, 使用户免于运行npm run i && npm run build
的麻烦
一个插件开发示例: