🎨 Optimize hotkey service
This commit is contained in:
11
internal/common/hotkey/mainthread/doc.go
Normal file
11
internal/common/hotkey/mainthread/doc.go
Normal 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
|
||||
87
internal/common/hotkey/mainthread/os.go
Normal file
87
internal/common/hotkey/mainthread/os.go
Normal 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
|
||||
}
|
||||
69
internal/common/hotkey/mainthread/os_darwin.go
Normal file
69
internal/common/hotkey/mainthread/os_darwin.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
28
internal/common/hotkey/mainthread/os_darwin.m
Normal file
28
internal/common/hotkey/mainthread/os_darwin.m
Normal 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];
|
||||
}
|
||||
Reference in New Issue
Block a user