From 9cf98f70919448d4fc1694d9533b951c9c91d24b Mon Sep 17 00:00:00 2001 From: Changkun Ou Date: Mon, 20 Sep 2021 17:48:08 +0200 Subject: [PATCH] all: improve error message when CGO_ENABLED=0 --- .github/workflows/hotkey.yml | 15 +- go.mod | 2 +- go.sum | 4 +- hotkey.go | 1 + hotkey_darwin_test.go | 2 +- hotkey_linux.c | 8 +- hotkey_linux_test.go | 2 +- hotkey_nocgo.go | 31 ++++ hotkey_nocgo_test.go | 29 +++ vendor/golang.design/x/mainthread/.gitignore | 15 ++ vendor/golang.design/x/mainthread/LICENSE | 21 +++ vendor/golang.design/x/mainthread/README.md | 106 +++++++++++ .../golang.design/x/mainthread/mainthread.go | 170 ++++++++++++++++++ vendor/modules.txt | 5 + 14 files changed, 402 insertions(+), 9 deletions(-) create mode 100644 hotkey_nocgo.go create mode 100644 hotkey_nocgo_test.go create mode 100644 vendor/golang.design/x/mainthread/.gitignore create mode 100644 vendor/golang.design/x/mainthread/LICENSE create mode 100644 vendor/golang.design/x/mainthread/README.md create mode 100644 vendor/golang.design/x/mainthread/mainthread.go create mode 100644 vendor/modules.txt diff --git a/.github/workflows/hotkey.yml b/.github/workflows/hotkey.yml index 677a581..d97d750 100644 --- a/.github/workflows/hotkey.yml +++ b/.github/workflows/hotkey.yml @@ -48,6 +48,17 @@ jobs: stable: 'false' go-version: '1.17.x' - - name: Run Tests + - name: Run Tests with CGO_ENABLED=1 + if: ${{ runner.os == 'Linux' || runner.os == 'macOS'}} run: | - go test -v -covermode=atomic ./... \ No newline at end of file + CGO_ENABLED=1 go test -v -covermode=atomic . + + - name: Run Tests with CGO_ENABLED=0 + if: ${{ runner.os == 'Linux' || runner.os == 'macOS'}} + run: | + CGO_ENABLED=0 go test -v -covermode=atomic . + + - name: Run Tests on Windows + if: ${{ runner.os == 'Windows'}} + run: | + go test -v -covermode=atomic . \ No newline at end of file diff --git a/go.mod b/go.mod index 45afda4..c5ba9b1 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module golang.design/x/hotkey go 1.17 require ( - golang.design/x/mainthread v0.2.1 + golang.design/x/mainthread v0.3.0 golang.org/x/sys v0.0.0-20210122093101-04d7465088b8 // indirect ) diff --git a/go.sum b/go.sum index 2d810d3..357bfa8 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -golang.design/x/mainthread v0.2.1 h1:IUGVW1acDfKoQtFeeS/RD/YYiKK8jxwkJXIQuKuL+ig= -golang.design/x/mainthread v0.2.1/go.mod h1:vYX7cF2b3pTJMGM/hc13NmN6kblKnf4/IyvHeu259L0= +golang.design/x/mainthread v0.3.0 h1:UwFus0lcPodNpMOGoQMe87jSFwbSsEY//CA7yVmu4j8= +golang.design/x/mainthread v0.3.0/go.mod h1:vYX7cF2b3pTJMGM/hc13NmN6kblKnf4/IyvHeu259L0= golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210122093101-04d7465088b8 h1:de2yTH1xuxjmGB7i6Z5o2z3RCHVa0XlpSZzjd8Fe6bE= golang.org/x/sys v0.0.0-20210122093101-04d7465088b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/hotkey.go b/hotkey.go index e5849f5..945c7a5 100644 --- a/hotkey.go +++ b/hotkey.go @@ -111,6 +111,7 @@ func newEventChan() (chan<- Event, <-chan Event) { for len(q) > 0 { select { case out <- q[0]: + q[0] = Event{} q = q[1:] case e, ok := <-in: if ok { diff --git a/hotkey_darwin_test.go b/hotkey_darwin_test.go index 9573ea9..ed2ecce 100644 --- a/hotkey_darwin_test.go +++ b/hotkey_darwin_test.go @@ -4,7 +4,7 @@ // // Written by Changkun Ou -//go:build darwin +//go:build darwin && cgo package hotkey_test diff --git a/hotkey_linux.c b/hotkey_linux.c index 48509b3..69c9398 100644 --- a/hotkey_linux.c +++ b/hotkey_linux.c @@ -11,11 +11,15 @@ #include int displayTest() { - Display* d = XOpenDisplay(0); + Display* d = NULL; + for (int i = 0; i < 42; i++) { + d = XOpenDisplay(0); + if (d == NULL) continue; + break; + } if (d == NULL) { return -1; } - XCloseDisplay(d); return 0; } diff --git a/hotkey_linux_test.go b/hotkey_linux_test.go index 8929b17..cbe05cb 100644 --- a/hotkey_linux_test.go +++ b/hotkey_linux_test.go @@ -4,7 +4,7 @@ // // Written by Changkun Ou -//go:build linux +//go:build linux && cgo package hotkey_test diff --git a/hotkey_nocgo.go b/hotkey_nocgo.go new file mode 100644 index 0000000..c330271 --- /dev/null +++ b/hotkey_nocgo.go @@ -0,0 +1,31 @@ +// Copyright 2021 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 + +//go:build !windows && !cgo + +package hotkey + +import "context" + +// Modifier represents a modifier +type Modifier uint32 + +// Key represents a key. +type Key uint8 + +func (hk *Hotkey) register() error { + panic("hotkey: cannot use when CGO_ENABLED=0") +} + +// unregister deregisteres a system hotkey. +func (hk *Hotkey) unregister() { + panic("hotkey: cannot use when CGO_ENABLED=0") +} + +// handle handles the hotkey event loop. +func (hk *Hotkey) handle(ctx context.Context) { + panic("hotkey: cannot use when CGO_ENABLED=0") +} diff --git a/hotkey_nocgo_test.go b/hotkey_nocgo_test.go new file mode 100644 index 0000000..4a1b7ef --- /dev/null +++ b/hotkey_nocgo_test.go @@ -0,0 +1,29 @@ +// Copyright 2021 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 + +//go:build (linux || darwin) && !cgo + +package hotkey_test + +import ( + "testing" + + "golang.design/x/hotkey" +) + +// TestHotkey should always run success. +// This is a test to run and for manually testing, registered combination: +// Ctrl+Alt+A (Ctrl+Mod2+Mod4+A on Linux) +func TestHotkey(t *testing.T) { + defer func() { + if r := recover(); r != nil { + return + } + t.Fatalf("expect to fail when CGO_ENABLED=0") + }() + + hotkey.Register([]hotkey.Modifier{}, hotkey.Key(0)) +} diff --git a/vendor/golang.design/x/mainthread/.gitignore b/vendor/golang.design/x/mainthread/.gitignore new file mode 100644 index 0000000..66fd13c --- /dev/null +++ b/vendor/golang.design/x/mainthread/.gitignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/vendor/golang.design/x/mainthread/LICENSE b/vendor/golang.design/x/mainthread/LICENSE new file mode 100644 index 0000000..1ba3c18 --- /dev/null +++ b/vendor/golang.design/x/mainthread/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Changkun Ou + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/golang.design/x/mainthread/README.md b/vendor/golang.design/x/mainthread/README.md new file mode 100644 index 0000000..24a81e1 --- /dev/null +++ b/vendor/golang.design/x/mainthread/README.md @@ -0,0 +1,106 @@ +# mainthread [![PkgGoDev](https://pkg.go.dev/badge/golang.design/x/mainthread)](https://pkg.go.dev/golang.design/x/mainthread) ![mainthread](https://github.com/golang-design/mainthread/workflows/mainthread/badge.svg?branch=main) ![](https://changkun.de/urlstat?mode=github&repo=golang-design/mainthread) + +schedule functions to run on the main thread + +```go +import "golang.design/x/mainthread" +``` + +## Features + +- Main thread scheduling +- Schedule functions without memory allocation + +## API Usage + +Package mainthread offers facilities to schedule functions +on the main thread. To use this package properly, one must +call `mainthread.Init` from the main package. For example: + +```go +package main + +import "golang.design/x/mainthread" + +func main() { mainthread.Init(fn) } + +// fn is the actual main function +func fn() { + // ... do stuff ... + + // mainthread.Call returns when f1 returns. Note that if f1 blocks + // it will also block the execution of any subsequent calls on the + // main thread. + mainthread.Call(f1) + + // ... do stuff ... + + + // mainthread.Go returns immediately and f2 is scheduled to be + // executed in the future. + mainthread.Go(f2) + + // ... do stuff ... +} + +func f1() { ... } +func f2() { ... } +``` + +If the given function triggers a panic, and called via `mainthread.Call`, +then the panic will be propagated to the same goroutine. One can capture +that panic, when possible: + +```go +defer func() { + if r := recover(); r != nil { + println(r) + } +}() + +mainthread.Call(func() { ... }) // if panic +``` + +If the given function triggers a panic, and called via `mainthread.Go`, +then the panic will be cached internally, until a call to the `Error()` method: + +```go +mainthread.Go(func() { ... }) // if panics + +// ... do stuff ... + +if err := mainthread.Error(); err != nil { // can be captured here. + println(err) +} +``` + +Note that a panic happens before `mainthread.Error()` returning the +panicked error. If one needs to guarantee `mainthread.Error()` indeed +captured the panic, a dummy function can be used as synchornization: + +```go +mainthread.Go(func() { panic("die") }) // if panics +mainthread.Call(func() {}) // for execution synchronization +err := mainthread.Error() // err must be non-nil +``` + + +It is possible to cache up to a maximum of 42 panicked errors. +More errors are ignored. + +## When do you need this package? + +Read this to learn more about the design purpose of this package: +https://golang.design/research/zero-alloc-call-sched/ + +## Who is using this package? + +The initial purpose of building this package is to support writing +graphical applications in Go. To know projects that are using this +package, check our [wiki](https://github.com/golang-design/mainthread/wiki) +page. + + +## License + +MIT | © 2021 The golang.design Initiative Authors, written by [Changkun Ou](https://changkun.de). \ No newline at end of file diff --git a/vendor/golang.design/x/mainthread/mainthread.go b/vendor/golang.design/x/mainthread/mainthread.go new file mode 100644 index 0000000..b42c203 --- /dev/null +++ b/vendor/golang.design/x/mainthread/mainthread.go @@ -0,0 +1,170 @@ +// Copyright 2020-2021 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 + +// Package mainthread offers facilities to schedule functions +// on the main thread. To use this package properly, one must +// call `mainthread.Init` from the main package. For example: +// +// package main +// +// import "golang.design/x/mainthread" +// +// func main() { mainthread.Init(fn) } +// +// // fn is the actual main function +// func fn() { +// // ... do stuff ... +// +// // mainthread.Call returns when f1 returns. Note that if f1 blocks +// // it will also block the execution of any subsequent calls on the +// // main thread. +// mainthread.Call(f1) +// +// // ... do stuff ... +// +// +// // mainthread.Go returns immediately and f2 is scheduled to be +// // executed in the future. +// mainthread.Go(f2) +// +// // ... do stuff ... +// } +// +// func f1() { ... } +// func f2() { ... } +// +// If the given function triggers a panic, and called via `mainthread.Call`, +// then the panic will be propagated to the same goroutine. One can capture +// that panic, when possible: +// +// defer func() { +// if r := recover(); r != nil { +// println(r) +// } +// }() +// +// mainthread.Call(func() { ... }) // if panic +// +// If the given function triggers a panic, and called via `mainthread.Go`, +// then the panic will be cached internally, until a call to the `Error()` method: +// +// mainthread.Go(func() { ... }) // if panics +// +// // ... do stuff ... +// +// if err := mainthread.Error(); err != nil { // can be captured here. +// println(err) +// } +// +// Note that a panic happens before `mainthread.Error()` returning the +// panicked error. If one needs to guarantee `mainthread.Error()` indeed +// captured the panic, a dummy function can be used as synchornization: +// +// mainthread.Go(func() { panic("die") }) // if panics +// mainthread.Call(func() {}) // for execution synchronization +// err := mainthread.Error() // err must be non-nil +// +// It is possible to cache up to a maximum of 42 panicked errors. +// More errors are ignored. +package mainthread // import "golang.design/x/mainthread" + +import ( + "fmt" + "runtime" + "sync" +) + +func init() { + runtime.LockOSThread() +} + +// 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 + } + } +} + +// 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) + } +} + +// Go schedules f to be called on the main thread. +func Go(f func()) { + funcQ <- funcData{fn: f} +} + +// Error returns an error that is captured if there are any panics +// happened on the mainthread. +// +// It is possible to cache up to a maximum of 42 panicked errors. +// More errors are ignored. +func Error() error { + select { + case err := <-erroQ: + return err + default: + return nil + } +} + +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 +} diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 0000000..3b3d94d --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,5 @@ +# golang.design/x/mainthread v0.3.0 +## explicit; go 1.16 +golang.design/x/mainthread +# golang.org/x/sys v0.0.0-20210122093101-04d7465088b8 +## explicit; go 1.12