Skip to content

启动器 ​

启动器——如 Spotlight、Raycast、Alfred、PowerToys Run 或 Ulauncher——可以 在任何地方用快捷键唤起,在用户当前的应用之上接收其输入,并且来得快去得也 快。MyGo 具备各个部件:用于显示它的面板、用于唤起它的 全局快捷键、可供搜索的 已安装应用及其图标,以及一个没有 Dock 图标、栖身于 托盘的应用。 examples/launcher 用原生 UI 把它们组合在一起:

sh
go run ./examples/launcher

后台应用 ​

启动器没有 Dock 图标,也不出现在应用切换器中,无窗口显示地运行,并且只 运行一个实例:

go
func main() {
	if !mygo.App.RequestSingleInstanceLock() {
		return // the launcher that runs shows itself
	}
	mygo.App.SetActivationPolicy(mygo.ActivationPolicyAccessory)
	mygo.App.OnWindowAllClosed(func() {})
	mygo.App.OnSecondInstance(func([]string, string) { show() })
	mygo.App.WhenReady(ready)
	if err := mygo.App.Run(); err != nil {
		log.Fatal(err)
	}
}

打包后的应用从一开始就声明这一点,使 macOS 永不把它显示在 Dock 中,方法是 在 mygo.json 中设置 LSUIElement:

json
{
  "macos": { "infoPlist": { "LSUIElement": true } }
}

托盘图标给了用户一条除快捷键之外的入口,也用于退出; App.SetOpenAtLogin 让启动器随会话启动,而 App.WasOpenedAtLogin 会告诉它此时应保持隐藏。

面板 ​

面板悬浮在所有应用之上,包括全屏应用,并跟随用户所在的任意 Space 或虚拟 桌面;在 macOS 上,它获得键盘而无需激活启动器:用户原来的应用保留其菜单 栏,面板隐藏后键盘即归还。创建它时就隐藏,只创建一次,并且隐藏它而不是 关闭它,这样它能原样立即再次显示:

go
panel := mygo.NewWindow(mygo.WindowOptions{
	Panel:         true,
	Hidden:        true,
	Frameless:     true,
	Transparent:   true,
	Vibrancy:      mygo.VibrancyGlass, // Liquid Glass on macOS 26, Acrylic on Windows 11
	CornerRadius:  16,
	Width:         680,
	Height:        420,
	DisableResize: true,
	Content:       ui.View(app.view), // or URL: "/launcher"
})
panel.OnClose(func(e *mygo.CloseEvent) { // Escape, Cmd-W, Alt-F4
	e.PreventDefault()
	panel.Hide()
})
panel.OnBlur(panel.Hide) // the user went elsewhere

页面或原生 UI 未处理的 Escape 键会关闭面板,OnClose 监听器把关闭变成 隐藏。若 Escape 需要先做别的事,就自己处理它,如示例中先清空查询。

把它显示在用户正在查看的显示器上——指针的位置可以说明这一点——按 Spotlight 的方式放在自上而下三分之一处,并让搜索框获得键盘:

go
func show() {
	d := mygo.Screen.DisplayNearestPoint(mygo.Screen.CursorScreenPoint())
	w, h := panel.Size()
	panel.SetPosition(d.WorkArea.X+(d.WorkArea.Width-w)/2, d.WorkArea.Y+(d.WorkArea.Height-h)/3)
	app.query = ""
	app.input.Focus()
	panel.Show()
}

快捷键 ​

go
err := mygo.GlobalShortcut.Register("Alt+Shift+Space", func() {
	if panel.IsVisible() {
		panel.Hide()
	} else {
		show()
	}
})

选一个其他启动器没有占用的:Raycast 和 PowerToys Run 占用 Alt+Space, Spotlight 占用 Cmd+Space,而 macOS 会把快捷键分配给每个注册它的应用,那样 两者都会打开。让用户在启动器的设置中另选一个:Unregister 旧的并 Register 新的,当 Register 因快捷键被占用而失败时告知用户。在 Windows 上,按键使面板获得前台;在 Wayland 上由桌面绑定快捷键,见 全局快捷键。

查找应用 ​

Shell.InstalledApps 列出计算机上安装的应用程序,按名称排序,并使用系统 以用户语言显示的名称。它会读取磁盘,耗时几十毫秒:在面板显示时于一个 goroutine 上列出它们,并通过 Window.Update 交给视图(或通过绑定的服务 交给页面):

go
go func() {
	apps, err := mygo.Shell.InstalledApps()
	if err != nil {
		log.Print(err)
	}
	panel.Update(func() {
		app.apps = apps
		app.filter()
	})
}()

Shell.FileIcon(app.Path, size) 将应用图标绘制为 PNG,以像素计:即 DIP 尺寸乘以显示器的缩放比例。它耗时几毫秒:只绘制可见行的图标,并按路径缓存 它们。Shell.LaunchApp(app) 启动所选应用,或将其带到前台;先隐藏面板, 让用户原来的应用持有键盘,直到被启动的应用接管。

平台 ​

macOSWindowsLinux
无 Dock 图标、无任务栏按钮LSUIElement、ActivationPolicyAccessory面板没有面板没有
面板不激活的 NSPanel,位于普通应用和附属应用的全屏应用之上置顶的工具窗口X11 上保持置顶的实用窗口;在 Wayland 合成器支持时使用 layer surface(需 libgtk-layer-shell.so.0)
快捷键Carbon 热键RegisterHotKeyX11 抓取,Wayland 下的 XDG 桌面门户
应用"应用程序"文件夹开始菜单的"所有应用"桌面入口

在 Wayland 上,作为 layer surface 的面板在显示期间持有键盘,当地启动器都 如此,因此它只在隐藏时才失焦:在 Escape 时关闭它。