🎨 Optimize hotkey service

This commit is contained in:
2025-11-06 00:08:26 +08:00
parent df79267e16
commit e0179b5838
25 changed files with 1642 additions and 1113 deletions

View File

@@ -0,0 +1,11 @@
// Copyright 2022 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
// Package mainthread provides facilities to schedule functions
// on the main thread. It includes platform-specific implementations
// for Windows, Linux, and macOS. The macOS implementation is specially
// designed to handle main thread events for the NSApplication.
package mainthread

View File

@@ -0,0 +1,87 @@
// Copyright 2022 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
//go:build windows || linux
package mainthread
import (
"fmt"
"runtime"
"sync"
)
func init() {
runtime.LockOSThread()
}
// Call calls f on the main thread and blocks until f finishes.
func Call(f func()) {
done := donePool.Get().(chan error)
defer donePool.Put(done)
data := funcData{fn: f, done: done}
funcQ <- data
if err := <-done; err != nil {
panic(err)
}
}
// Init initializes the functionality of running arbitrary subsequent functions be called on the main system thread.
//
// Init must be called in the main.main function.
func Init(main func()) {
done := donePool.Get().(chan error)
defer donePool.Put(done)
go func() {
defer func() {
done <- nil
}()
main()
}()
for {
select {
case f := <-funcQ:
func() {
defer func() {
r := recover()
if f.done != nil {
if r != nil {
f.done <- fmt.Errorf("%v", r)
} else {
f.done <- nil
}
} else {
if r != nil {
select {
case erroQ <- fmt.Errorf("%v", r):
default:
}
}
}
}()
f.fn()
}()
case <-done:
return
}
}
}
var (
funcQ = make(chan funcData, runtime.GOMAXPROCS(0))
erroQ = make(chan error, 42)
donePool = sync.Pool{New: func() interface{} {
return make(chan error)
}}
)
type funcData struct {
fn func()
done chan error
}

View File

@@ -0,0 +1,69 @@
// Copyright 2022 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
//go:build darwin
package mainthread
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Cocoa
#import <Cocoa/Cocoa.h>
#import <Dispatch/Dispatch.h>
extern void os_main(void);
extern void wakeupMainThread(void);
static bool isMainThread() {
return [NSThread isMainThread];
}
*/
import "C"
import (
"os"
"runtime"
)
func init() {
runtime.LockOSThread()
}
// Call calls f on the main thread and blocks until f finishes.
func Call(f func()) {
if C.isMainThread() {
f()
return
}
go func() {
mainFuncs <- f
C.wakeupMainThread()
}()
}
// Init initializes the functionality of running arbitrary subsequent functions be called on the main system thread.
//
// Init must be called in the main.main function.
func Init(f func()) {
go func() {
f()
os.Exit(0)
}()
C.os_main()
}
var mainFuncs = make(chan func(), 1)
//export dispatchMainFuncs
func dispatchMainFuncs() {
for {
select {
case f := <-mainFuncs:
f()
default:
return
}
}
}

View File

@@ -0,0 +1,28 @@
// Copyright 2022 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
//go:build darwin
#include <stdint.h>
#import <Cocoa/Cocoa.h>
extern void dispatchMainFuncs();
void wakeupMainThread(void) {
dispatch_async(dispatch_get_main_queue(), ^{
dispatchMainFuncs();
});
}
// The following three lines of code must run on the main thread.
// It must handle it using golang.design/x/mainthread.
//
// inspired from here: https://github.com/cehoffman/dotfiles/blob/4be8e893517e970d40746a9bdc67fe5832dd1c33/os/mac/iTerm2HotKey.m
void os_main(void) {
[NSApplication sharedApplication];
[NSApp disableRelaunchOnLogin];
[NSApp run];
}