master
This commit is contained in:
parent
159932ff3e
commit
c7953f8ee1
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2021 Liam Galvin
|
|
||||||
|
|
||||||
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.
|
|
8
Makefile
8
Makefile
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
|
|
||||||
default: build
|
darktile: cmd/**/* internal/**/*
|
||||||
|
go build -o darktile ./cmd/darktile
|
||||||
build:
|
|
||||||
./scripts/build.sh
|
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm darktile
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -18,4 +18,5 @@ require (
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
mvdan.cc/xurls v1.1.0
|
mvdan.cc/xurls v1.1.0
|
||||||
|
sigs.k8s.io/yaml v1.1.0
|
||||||
)
|
)
|
||||||
|
|
1
go.sum
1
go.sum
|
@ -573,5 +573,6 @@ honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
|
||||||
mvdan.cc/xurls v1.1.0 h1:kj0j2lonKseISJCiq1Tfk+iTv65dDGCl0rTbanXJGGc=
|
mvdan.cc/xurls v1.1.0 h1:kj0j2lonKseISJCiq1Tfk+iTv65dDGCl0rTbanXJGGc=
|
||||||
mvdan.cc/xurls v1.1.0/go.mod h1:TNWuhvo+IqbUCmtUIb/3LJSQdrzel8loVpgFm0HikbI=
|
mvdan.cc/xurls v1.1.0/go.mod h1:TNWuhvo+IqbUCmtUIb/3LJSQdrzel8loVpgFm0HikbI=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||||
|
|
|
@ -6,28 +6,28 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Opacity float64
|
Opacity float64 `json:"opacity"`
|
||||||
Font Font
|
Font Font `json:"font"`
|
||||||
Cursor Cursor
|
Cursor Cursor `json:"cursor"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Font struct {
|
type Font struct {
|
||||||
Family string
|
Family string `json:"family"`
|
||||||
Size float64
|
Size float64 `json:"size"`
|
||||||
DPI float64
|
DPI float64 `json:"dpi"`
|
||||||
Ligatures bool
|
Ligatures bool `json:"ligatures"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Cursor struct {
|
type Cursor struct {
|
||||||
Image string
|
Image string `json:"image"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrorFileNotFound struct {
|
type ErrorFileNotFound struct {
|
||||||
Path string
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ErrorFileNotFound) Error() string {
|
func (e *ErrorFileNotFound) Error() string {
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
version=$(git describe --exact-match --tags 2>/dev/null || git describe 2>/dev/null || echo "prerelease")
|
|
||||||
go build \
|
|
||||||
-mod=vendor\
|
|
||||||
-ldflags="-X github.com/liamg/darktile/internal/app/darktile/version.Version=${version}" \
|
|
||||||
./cmd/darktile
|
|
|
@ -1,23 +0,0 @@
|
||||||
# Clip
|
|
||||||
|
|
||||||
A tiny library to access the MacOS clipboard (a.k.a. NSPasteboard).
|
|
||||||
|
|
||||||
```go
|
|
||||||
go get git.wow.st/gmp/clip
|
|
||||||
```
|
|
||||||
|
|
||||||
## API:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package clip
|
|
||||||
|
|
||||||
// Clear clears the general pasteboard
|
|
||||||
func Clear()
|
|
||||||
|
|
||||||
// Set puts a string on the pasteboard, returning true if successful
|
|
||||||
func Set(string) bool
|
|
||||||
|
|
||||||
// Get retrieves the string currently on the pasteboard.
|
|
||||||
func Get() string
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
package clip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.wow.st/gmp/clip/ns"
|
|
||||||
)
|
|
||||||
|
|
||||||
var pb *ns.NSPasteboard
|
|
||||||
|
|
||||||
func Clear() {
|
|
||||||
if pb == nil {
|
|
||||||
pb = ns.NSPasteboardGeneralPasteboard()
|
|
||||||
}
|
|
||||||
pb.ClearContents()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Set(x string) bool {
|
|
||||||
if pb == nil {
|
|
||||||
pb = ns.NSPasteboardGeneralPasteboard()
|
|
||||||
}
|
|
||||||
pb.ClearContents()
|
|
||||||
return pb.SetString(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Get() string {
|
|
||||||
if pb == nil {
|
|
||||||
pb = ns.NSPasteboardGeneralPasteboard()
|
|
||||||
}
|
|
||||||
ret := pb.GetString()
|
|
||||||
if ret.Ptr() == nil {
|
|
||||||
return ""
|
|
||||||
} else {
|
|
||||||
return ret.String()
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,211 +0,0 @@
|
||||||
package ns
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -x objective-c -fno-objc-arc
|
|
||||||
#cgo LDFLAGS: -framework AppKit -framework Foundation
|
|
||||||
#pragma clang diagnostic ignored "-Wformat-security"
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <AppKit/NSPasteboard.h>
|
|
||||||
|
|
||||||
void
|
|
||||||
NSObject_inst_Release(void* o) {
|
|
||||||
@autoreleasepool {
|
|
||||||
[(NSObject*)o release];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
NSString_inst_Release(void* o) {
|
|
||||||
@autoreleasepool {
|
|
||||||
[(NSString*)o release];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const void* _Nullable
|
|
||||||
NSString_inst_UTF8String(void* o) {
|
|
||||||
const char* _Nullable ret;
|
|
||||||
@autoreleasepool {
|
|
||||||
ret = strdup([(NSString*)o UTF8String]);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
NSPasteboard_inst_Release(void* o) {
|
|
||||||
@autoreleasepool {
|
|
||||||
[(NSPasteboard*)o release];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void* _Nullable
|
|
||||||
NSString_StringWithUTF8String(void* nullTerminatedCString) {
|
|
||||||
NSString* _Nullable ret;
|
|
||||||
@autoreleasepool {
|
|
||||||
ret = [NSString stringWithUTF8String:nullTerminatedCString];
|
|
||||||
if(ret != nil) { [ret retain]; }
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void* _Nonnull
|
|
||||||
NSPasteboard_GeneralPasteboard() {
|
|
||||||
NSPasteboard* _Nonnull ret;
|
|
||||||
@autoreleasepool {
|
|
||||||
ret = [NSPasteboard generalPasteboard];
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
NSPasteboard_inst_ClearContents(void* o) {
|
|
||||||
@autoreleasepool {
|
|
||||||
[(NSPasteboard*)o clearContents];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL
|
|
||||||
NSPasteboard_inst_SetString(void* o, void* string) {
|
|
||||||
BOOL ret;
|
|
||||||
@autoreleasepool {
|
|
||||||
ret = [(NSPasteboard*)o setString:string forType:NSPasteboardTypeString];
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* _Nullable
|
|
||||||
NSPasteboard_inst_GetString(void* o) {
|
|
||||||
NSString* _Nullable ret;
|
|
||||||
@autoreleasepool {
|
|
||||||
ret = [(NSPasteboard*)o stringForType:NSPasteboardTypeString];
|
|
||||||
if (ret != nil && ret != o) { [ret retain]; }
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Id struct {
|
|
||||||
ptr unsafe.Pointer
|
|
||||||
}
|
|
||||||
func (o *Id) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr }
|
|
||||||
|
|
||||||
type NSObject interface {
|
|
||||||
Ptr() unsafe.Pointer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Id) Release() {
|
|
||||||
C.NSObject_inst_Release(o.Ptr())
|
|
||||||
runtime.KeepAlive(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *NSPasteboard) Release() {
|
|
||||||
C.NSPasteboard_inst_Release(o.Ptr())
|
|
||||||
runtime.KeepAlive(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *NSString) Release() {
|
|
||||||
C.NSString_inst_Release(o.Ptr())
|
|
||||||
runtime.KeepAlive(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Char) Free() {
|
|
||||||
C.free(unsafe.Pointer(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
type BOOL C.uchar
|
|
||||||
|
|
||||||
type NSString struct { Id }
|
|
||||||
func (o *NSString) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr }
|
|
||||||
func (o *Id) NSString() *NSString {
|
|
||||||
return (*NSString)(unsafe.Pointer(o))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *NSString) UTF8String() *Char {
|
|
||||||
ret := (*Char)(unsafe.Pointer(C.NSString_inst_UTF8String(o.Ptr())))
|
|
||||||
runtime.KeepAlive(o)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *NSString) String() string {
|
|
||||||
utf8 := o.UTF8String()
|
|
||||||
ret := utf8.String()
|
|
||||||
utf8.Free()
|
|
||||||
runtime.KeepAlive(o)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
type NSPasteboard struct { Id }
|
|
||||||
func (o *NSPasteboard) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr }
|
|
||||||
func (o *Id) NSPasteboard() *NSPasteboard {
|
|
||||||
return (*NSPasteboard)(unsafe.Pointer(o))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Char C.char
|
|
||||||
|
|
||||||
func CharWithGoString(s string) *Char {
|
|
||||||
return (*Char)(unsafe.Pointer(C.CString(s)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Char) String() string {
|
|
||||||
return C.GoString((*C.char)(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NSStringWithUTF8String(nullTerminatedCString *Char) *NSString {
|
|
||||||
ret := &NSString{}
|
|
||||||
ret.ptr = unsafe.Pointer(C.NSString_StringWithUTF8String(unsafe.Pointer(nullTerminatedCString)))
|
|
||||||
if ret.ptr == nil { return ret }
|
|
||||||
runtime.SetFinalizer(ret, func(o *NSString) {
|
|
||||||
o.Release()
|
|
||||||
})
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func NSStringWithGoString(string string) *NSString {
|
|
||||||
string_chr := CharWithGoString(string)
|
|
||||||
defer string_chr.Free()
|
|
||||||
ret := NSStringWithUTF8String(string_chr)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func NSPasteboardGeneralPasteboard() *NSPasteboard {
|
|
||||||
ret := &NSPasteboard{}
|
|
||||||
ret.ptr = unsafe.Pointer(C.NSPasteboard_GeneralPasteboard())
|
|
||||||
if ret.ptr == nil { return ret }
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *NSPasteboard) ClearContents() {
|
|
||||||
C.NSPasteboard_inst_ClearContents(o.Ptr())
|
|
||||||
runtime.KeepAlive(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *NSPasteboard) SetString(s string) bool {
|
|
||||||
string := NSStringWithGoString(s)
|
|
||||||
ret := (C.NSPasteboard_inst_SetString(o.Ptr(), string.Ptr())) != 0
|
|
||||||
runtime.KeepAlive(o)
|
|
||||||
runtime.KeepAlive(string)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *NSPasteboard) GetString() *NSString {
|
|
||||||
ret := &NSString{}
|
|
||||||
ret.ptr = unsafe.Pointer(C.NSPasteboard_inst_GetString(o.Ptr()))
|
|
||||||
if ret.ptr == nil { runtime.KeepAlive(o); return ret }
|
|
||||||
if ret.ptr == o.ptr { runtime.KeepAlive(o); return (*NSString)(unsafe.Pointer(o)) }
|
|
||||||
runtime.SetFinalizer(ret, func(o *NSString) {
|
|
||||||
o.Release()
|
|
||||||
})
|
|
||||||
runtime.KeepAlive(o)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
# binding generator for git.wow.st/gmp/nswrap
|
|
||||||
# original binding is in ns/main.go- and is not used.
|
|
||||||
inputfiles:
|
|
||||||
- /System/Library/Frameworks/AppKit.framework/Headers/NSPasteboard.h
|
|
||||||
classes:
|
|
||||||
- NSPasteboard
|
|
||||||
- NSString
|
|
||||||
enums:
|
|
||||||
- NSPasteboard.*
|
|
||||||
frameworks: [ AppKit, Foundation ]
|
|
||||||
pragma: [ clang diagnostic ignored "-Wformat-security" ]
|
|
|
@ -1,2 +0,0 @@
|
||||||
xgbgen/xgbgen
|
|
||||||
.*.swp
|
|
|
@ -1,18 +0,0 @@
|
||||||
Andrew Gallant is the maintainer of this fork. What follows is the original
|
|
||||||
list of authors for the x-go-binding.
|
|
||||||
|
|
||||||
# This is the official list of XGB authors for copyright purposes.
|
|
||||||
# This file is distinct from the CONTRIBUTORS files.
|
|
||||||
# See the latter for an explanation.
|
|
||||||
|
|
||||||
# Names should be added to this file as
|
|
||||||
# Name or Organization <email address>
|
|
||||||
# The email address is not required for organizations.
|
|
||||||
|
|
||||||
# Please keep the list sorted.
|
|
||||||
|
|
||||||
Anthony Martin <ality@pbrane.org>
|
|
||||||
Firmansyah Adiputra <frm.adiputra@gmail.com>
|
|
||||||
Google Inc.
|
|
||||||
Scott Lawrence <bytbox@gmail.com>
|
|
||||||
Tor Andersson <tor.andersson@gmail.com>
|
|
|
@ -1,39 +0,0 @@
|
||||||
Andrew Gallant is the maintainer of this fork. What follows is the original
|
|
||||||
list of contributors for the x-go-binding.
|
|
||||||
|
|
||||||
# This is the official list of people who can contribute
|
|
||||||
# (and typically have contributed) code to the XGB repository.
|
|
||||||
# The AUTHORS file lists the copyright holders; this file
|
|
||||||
# lists people. For example, Google employees are listed here
|
|
||||||
# but not in AUTHORS, because Google holds the copyright.
|
|
||||||
#
|
|
||||||
# The submission process automatically checks to make sure
|
|
||||||
# that people submitting code are listed in this file (by email address).
|
|
||||||
#
|
|
||||||
# Names should be added to this file only after verifying that
|
|
||||||
# the individual or the individual's organization has agreed to
|
|
||||||
# the appropriate Contributor License Agreement, found here:
|
|
||||||
#
|
|
||||||
# http://code.google.com/legal/individual-cla-v1.0.html
|
|
||||||
# http://code.google.com/legal/corporate-cla-v1.0.html
|
|
||||||
#
|
|
||||||
# The agreement for individuals can be filled out on the web.
|
|
||||||
#
|
|
||||||
# When adding J Random Contributor's name to this file,
|
|
||||||
# either J's name or J's organization's name should be
|
|
||||||
# added to the AUTHORS file, depending on whether the
|
|
||||||
# individual or corporate CLA was used.
|
|
||||||
|
|
||||||
# Names should be added to this file like so:
|
|
||||||
# Name <email address>
|
|
||||||
|
|
||||||
# Please keep the list sorted.
|
|
||||||
|
|
||||||
Anthony Martin <ality@pbrane.org>
|
|
||||||
Firmansyah Adiputra <frm.adiputra@gmail.com>
|
|
||||||
Ian Lance Taylor <iant@golang.org>
|
|
||||||
Nigel Tao <nigeltao@golang.org>
|
|
||||||
Robert Griesemer <gri@golang.org>
|
|
||||||
Russ Cox <rsc@golang.org>
|
|
||||||
Scott Lawrence <bytbox@gmail.com>
|
|
||||||
Tor Andersson <tor.andersson@gmail.com>
|
|
|
@ -1,42 +0,0 @@
|
||||||
// Copyright (c) 2009 The XGB Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are
|
|
||||||
// met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above
|
|
||||||
// copyright notice, this list of conditions and the following disclaimer
|
|
||||||
// in the documentation and/or other materials provided with the
|
|
||||||
// distribution.
|
|
||||||
// * Neither the name of Google Inc. nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived from
|
|
||||||
// this software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
//
|
|
||||||
// Subject to the terms and conditions of this License, Google hereby
|
|
||||||
// grants to You a perpetual, worldwide, non-exclusive, no-charge,
|
|
||||||
// royalty-free, irrevocable (except as stated in this section) patent
|
|
||||||
// license to make, have made, use, offer to sell, sell, import, and
|
|
||||||
// otherwise transfer this implementation of XGB, where such license
|
|
||||||
// applies only to those patent claims licensable by Google that are
|
|
||||||
// necessarily infringed by use of this implementation of XGB. If You
|
|
||||||
// institute patent litigation against any entity (including a
|
|
||||||
// cross-claim or counterclaim in a lawsuit) alleging that this
|
|
||||||
// implementation of XGB or a Contribution incorporated within this
|
|
||||||
// implementation of XGB constitutes direct or contributory patent
|
|
||||||
// infringement, then any patent licenses granted to You under this
|
|
||||||
// License for this implementation of XGB shall terminate as of the date
|
|
||||||
// such litigation is filed.
|
|
|
@ -1,78 +0,0 @@
|
||||||
# This Makefile is used by the developer. It is not needed in any way to build
|
|
||||||
# a checkout of the XGB repository.
|
|
||||||
# It will be useful, however, if you are hacking at the code generator.
|
|
||||||
# i.e., after making a change to the code generator, run 'make' in the
|
|
||||||
# xgb directory. This will build xgbgen and regenerate each sub-package.
|
|
||||||
# 'make test' will then run any appropriate tests (just tests xproto right now).
|
|
||||||
# 'make bench' will test a couple of benchmarks.
|
|
||||||
# 'make build-all' will then try to build each extension. This isn't strictly
|
|
||||||
# necessary, but it's a good idea to make sure each sub-package is a valid
|
|
||||||
# Go package.
|
|
||||||
|
|
||||||
# My path to the X protocol XML descriptions.
|
|
||||||
XPROTO=/usr/share/xcb
|
|
||||||
|
|
||||||
# All of the XML files in my /usr/share/xcb directory EXCEPT XKB. -_-
|
|
||||||
# This is intended to build xgbgen and generate Go code for each supported
|
|
||||||
# extension.
|
|
||||||
all: build-xgbgen \
|
|
||||||
bigreq.xml composite.xml damage.xml dpms.xml dri2.xml \
|
|
||||||
ge.xml glx.xml randr.xml record.xml render.xml res.xml \
|
|
||||||
screensaver.xml shape.xml shm.xml xc_misc.xml \
|
|
||||||
xevie.xml xf86dri.xml xf86vidmode.xml xfixes.xml xinerama.xml \
|
|
||||||
xprint.xml xproto.xml xselinux.xml xtest.xml \
|
|
||||||
xvmc.xml xv.xml
|
|
||||||
|
|
||||||
build-xgbgen:
|
|
||||||
(cd xgbgen && go build)
|
|
||||||
|
|
||||||
# Builds each individual sub-package to make sure its valid Go code.
|
|
||||||
build-all: bigreq.b composite.b damage.b dpms.b dri2.b ge.b glx.b randr.b \
|
|
||||||
record.b render.b res.b screensaver.b shape.b shm.b xcmisc.b \
|
|
||||||
xevie.b xf86dri.b xf86vidmode.b xfixes.b xinerama.b \
|
|
||||||
xprint.b xproto.b xselinux.b xtest.b xv.b xvmc.b
|
|
||||||
|
|
||||||
%.b:
|
|
||||||
(cd $* ; go build)
|
|
||||||
|
|
||||||
# Installs each individual sub-package.
|
|
||||||
install: bigreq.i composite.i damage.i dpms.i dri2.i ge.i glx.i randr.i \
|
|
||||||
record.i render.i res.i screensaver.i shape.i shm.i xcmisc.i \
|
|
||||||
xevie.i xf86dri.i xf86vidmode.i xfixes.i xinerama.i \
|
|
||||||
xprint.i xproto.i xselinux.i xtest.i xv.i xvmc.i
|
|
||||||
go install
|
|
||||||
|
|
||||||
%.i:
|
|
||||||
(cd $* ; go install)
|
|
||||||
|
|
||||||
# xc_misc is special because it has an underscore.
|
|
||||||
# There's probably a way to do this better, but Makefiles aren't my strong suit.
|
|
||||||
xc_misc.xml: build-xgbgen
|
|
||||||
mkdir -p xcmisc
|
|
||||||
xgbgen/xgbgen --proto-path $(XPROTO) $(XPROTO)/xc_misc.xml > xcmisc/xcmisc.go
|
|
||||||
|
|
||||||
%.xml: build-xgbgen
|
|
||||||
mkdir -p $*
|
|
||||||
xgbgen/xgbgen --proto-path $(XPROTO) $(XPROTO)/$*.xml > $*/$*.go
|
|
||||||
|
|
||||||
# Just test the xproto core protocol for now.
|
|
||||||
test:
|
|
||||||
(cd xproto ; go test)
|
|
||||||
|
|
||||||
# Force all xproto benchmarks to run and no tests.
|
|
||||||
bench:
|
|
||||||
(cd xproto ; go test -run 'nomatch' -bench '.*' -cpu 1,2,3,6)
|
|
||||||
|
|
||||||
# gofmt all non-auto-generated code.
|
|
||||||
# (auto-generated code is already gofmt'd.)
|
|
||||||
# Also do a column check (80 cols) after a gofmt.
|
|
||||||
# But don't check columns on auto-generated code, since I don't care if they
|
|
||||||
# break 80 cols.
|
|
||||||
gofmt:
|
|
||||||
gofmt -w *.go xgbgen/*.go examples/*.go examples/*/*.go xproto/xproto_test.go
|
|
||||||
colcheck *.go xgbgen/*.go examples/*.go examples/*/*.go xproto/xproto_test.go
|
|
||||||
|
|
||||||
push:
|
|
||||||
git push origin master
|
|
||||||
git push github master
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
XGB is the X Go Binding, which is a low-level API to communicate with the
|
|
||||||
core X protocol and many of the X extensions. It is closely modeled after
|
|
||||||
XCB and xpyb.
|
|
||||||
|
|
||||||
It is thread safe and gets immediate improvement from parallelism when
|
|
||||||
GOMAXPROCS > 1. (See the benchmarks in xproto/xproto_test.go for evidence.)
|
|
||||||
|
|
||||||
Please see doc.go for more info.
|
|
||||||
|
|
||||||
Note that unless you know you need XGB, you can probably make your life
|
|
||||||
easier by using a slightly higher level library: xgbutil.
|
|
||||||
|
|
||||||
Quick Usage
|
|
||||||
===========
|
|
||||||
go get github.com/BurntSushi/xgb
|
|
||||||
go run go/path/src/github.com/BurntSushi/xgb/examples/create-window/main.go
|
|
||||||
|
|
||||||
BurntSushi's Fork
|
|
||||||
=================
|
|
||||||
I've forked the XGB repository from Google Code due to inactivty upstream.
|
|
||||||
|
|
||||||
Godoc documentation can be found here:
|
|
||||||
https://godoc.org/github.com/BurntSushi/xgb
|
|
||||||
|
|
||||||
Much of the code has been rewritten in an effort to support thread safety
|
|
||||||
and multiple extensions. Namely, go_client.py has been thrown away in favor
|
|
||||||
of an xgbgen package.
|
|
||||||
|
|
||||||
The biggest parts that *haven't* been rewritten by me are the connection and
|
|
||||||
authentication handshakes. They're inherently messy, and there's really no
|
|
||||||
reason to re-work them. The rest of XGB has been completely rewritten.
|
|
||||||
|
|
||||||
I like to release my code under the WTFPL, but since I'm starting with someone
|
|
||||||
else's work, I'm leaving the original license/contributor/author information
|
|
||||||
in tact.
|
|
||||||
|
|
||||||
I suppose I can legitimately release xgbgen under the WTFPL. To be fair, it is
|
|
||||||
at least as complex as XGB itself. *sigh*
|
|
||||||
|
|
||||||
What follows is the original README:
|
|
||||||
|
|
||||||
XGB README
|
|
||||||
==========
|
|
||||||
XGB is the X protocol Go language Binding.
|
|
||||||
|
|
||||||
It is the Go equivalent of XCB, the X protocol C-language Binding
|
|
||||||
(http://xcb.freedesktop.org/).
|
|
||||||
|
|
||||||
Unless otherwise noted, the XGB source files are distributed
|
|
||||||
under the BSD-style license found in the LICENSE file.
|
|
||||||
|
|
||||||
Contributions should follow the same procedure as for the Go project:
|
|
||||||
http://golang.org/doc/contribute.html
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
I like to keep all my code to 80 columns or less. I have plenty of screen real
|
|
||||||
estate, but enjoy 80 columns so that I can have multiple code windows open side
|
|
||||||
to side and not be plagued by the ugly auto-wrapping of a text editor.
|
|
||||||
|
|
||||||
If you don't oblige me, I will fix any patch you submit to abide 80 columns.
|
|
||||||
|
|
||||||
Note that this style restriction does not preclude gofmt, but introduces a few
|
|
||||||
peculiarities. The first is that gofmt will occasionally add spacing (typically
|
|
||||||
to comments) that ends up going over 80 columns. Either shorten the comment or
|
|
||||||
put it on its own line.
|
|
||||||
|
|
||||||
The second and more common hiccup is when a function definition extends beyond
|
|
||||||
80 columns. If one adds line breaks to keep it below 80 columns, gofmt will
|
|
||||||
indent all subsequent lines in a function definition to the same indentation
|
|
||||||
level of the function body. This results in a less-than-ideal separation
|
|
||||||
between function definition and function body. To remedy this, simply add a
|
|
||||||
line break like so:
|
|
||||||
|
|
||||||
func RestackWindowExtra(xu *xgbutil.XUtil, win xproto.Window, stackMode int,
|
|
||||||
sibling xproto.Window, source int) error {
|
|
||||||
|
|
||||||
return ClientEvent(xu, win, "_NET_RESTACK_WINDOW", source, int(sibling),
|
|
||||||
stackMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
Something similar should also be applied to long 'if' or 'for' conditionals,
|
|
||||||
although it would probably be preferrable to break up the conditional to
|
|
||||||
smaller chunks with a few helper variables.
|
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
package xgb
|
|
||||||
|
|
||||||
/*
|
|
||||||
auth.go contains functions to facilitate the parsing of .Xauthority files.
|
|
||||||
|
|
||||||
It is largely unmodified from the original XGB package that I forked.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// readAuthority reads the X authority file for the DISPLAY.
|
|
||||||
// If hostname == "" or hostname == "localhost",
|
|
||||||
// then use the system's hostname (as returned by os.Hostname) instead.
|
|
||||||
func readAuthority(hostname, display string) (
|
|
||||||
name string, data []byte, err error) {
|
|
||||||
|
|
||||||
// b is a scratch buffer to use and should be at least 256 bytes long
|
|
||||||
// (i.e. it should be able to hold a hostname).
|
|
||||||
b := make([]byte, 256)
|
|
||||||
|
|
||||||
// As per /usr/include/X11/Xauth.h.
|
|
||||||
const familyLocal = 256
|
|
||||||
const familyWild = 65535
|
|
||||||
|
|
||||||
if len(hostname) == 0 || hostname == "localhost" {
|
|
||||||
hostname, err = os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fname := os.Getenv("XAUTHORITY")
|
|
||||||
if len(fname) == 0 {
|
|
||||||
home := os.Getenv("HOME")
|
|
||||||
if len(home) == 0 {
|
|
||||||
err = errors.New("Xauthority not found: $XAUTHORITY, $HOME not set")
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
fname = home + "/.Xauthority"
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := os.Open(fname)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
for {
|
|
||||||
var family uint16
|
|
||||||
if err := binary.Read(r, binary.BigEndian, &family); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addr, err := getString(r, b)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
disp, err := getString(r, b)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
name0, err := getString(r, b)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data0, err := getBytes(r, b)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrmatch := (family == familyWild) ||
|
|
||||||
(family == familyLocal && addr == hostname)
|
|
||||||
dispmatch := (disp == "") || (disp == display)
|
|
||||||
|
|
||||||
if addrmatch && dispmatch {
|
|
||||||
return name0, data0, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBytes(r io.Reader, b []byte) ([]byte, error) {
|
|
||||||
var n uint16
|
|
||||||
if err := binary.Read(r, binary.BigEndian, &n); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if n > uint16(len(b)) {
|
|
||||||
return nil, errors.New("bytes too long for buffer")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.ReadFull(r, b[0:n]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b[0:n], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getString(r io.Reader, b []byte) (string, error) {
|
|
||||||
b, err := getBytes(r, b)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(b), nil
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
package xgb
|
|
||||||
|
|
||||||
/*
|
|
||||||
conn.go contains a couple of functions that do some real dirty work related
|
|
||||||
to the initial connection handshake with X.
|
|
||||||
|
|
||||||
This code is largely unmodified from the original XGB package that I forked.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// connect connects to the X server given in the 'display' string,
|
|
||||||
// and does all the necessary setup handshaking.
|
|
||||||
// If 'display' is empty it will be taken from os.Getenv("DISPLAY").
|
|
||||||
// Note that you should read and understand the "Connection Setup" of the
|
|
||||||
// X Protocol Reference Manual before changing this function:
|
|
||||||
// http://goo.gl/4zGQg
|
|
||||||
func (c *Conn) connect(display string) error {
|
|
||||||
err := c.dial(display)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.postConnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// connect init from to the net.Conn,
|
|
||||||
func (c *Conn) connectNet(netConn net.Conn) error {
|
|
||||||
c.conn = netConn
|
|
||||||
return c.postConnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// do the postConnect action after Conn get it's underly net.Conn
|
|
||||||
func (c *Conn) postConnect() error {
|
|
||||||
// Get authentication data
|
|
||||||
authName, authData, err := readAuthority(c.host, c.display)
|
|
||||||
noauth := false
|
|
||||||
if err != nil {
|
|
||||||
Logger.Printf("Could not get authority info: %v", err)
|
|
||||||
Logger.Println("Trying connection without authority info...")
|
|
||||||
authName = ""
|
|
||||||
authData = []byte{}
|
|
||||||
noauth = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assume that the authentication protocol is "MIT-MAGIC-COOKIE-1".
|
|
||||||
if !noauth && (authName != "MIT-MAGIC-COOKIE-1" || len(authData) != 16) {
|
|
||||||
return errors.New("unsupported auth protocol " + authName)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 12+Pad(len(authName))+Pad(len(authData)))
|
|
||||||
buf[0] = 0x6c
|
|
||||||
buf[1] = 0
|
|
||||||
Put16(buf[2:], 11)
|
|
||||||
Put16(buf[4:], 0)
|
|
||||||
Put16(buf[6:], uint16(len(authName)))
|
|
||||||
Put16(buf[8:], uint16(len(authData)))
|
|
||||||
Put16(buf[10:], 0)
|
|
||||||
copy(buf[12:], []byte(authName))
|
|
||||||
copy(buf[12+Pad(len(authName)):], authData)
|
|
||||||
if _, err = c.conn.Write(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
head := make([]byte, 8)
|
|
||||||
if _, err = io.ReadFull(c.conn, head[0:8]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
code := head[0]
|
|
||||||
reasonLen := head[1]
|
|
||||||
major := Get16(head[2:])
|
|
||||||
minor := Get16(head[4:])
|
|
||||||
dataLen := Get16(head[6:])
|
|
||||||
|
|
||||||
if major != 11 || minor != 0 {
|
|
||||||
return fmt.Errorf("x protocol version mismatch: %d.%d", major, minor)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = make([]byte, int(dataLen)*4+8, int(dataLen)*4+8)
|
|
||||||
copy(buf, head)
|
|
||||||
if _, err = io.ReadFull(c.conn, buf[8:]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if code == 0 {
|
|
||||||
reason := buf[8 : 8+reasonLen]
|
|
||||||
return fmt.Errorf("x protocol authentication refused: %s",
|
|
||||||
string(reason))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unfortunately, it isn't really feasible to read the setup bytes here,
|
|
||||||
// since the code to do so is in a different package.
|
|
||||||
// Users must call 'xproto.Setup(X)' to get the setup info.
|
|
||||||
c.SetupBytes = buf
|
|
||||||
|
|
||||||
// But also read stuff that we *need* to get started.
|
|
||||||
c.setupResourceIdBase = Get32(buf[12:])
|
|
||||||
c.setupResourceIdMask = Get32(buf[16:])
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// dial initializes the actual net connection with X.
|
|
||||||
func (c *Conn) dial(display string) error {
|
|
||||||
if len(display) == 0 {
|
|
||||||
display = os.Getenv("DISPLAY")
|
|
||||||
}
|
|
||||||
|
|
||||||
display0 := display
|
|
||||||
if len(display) == 0 {
|
|
||||||
return errors.New("empty display string")
|
|
||||||
}
|
|
||||||
|
|
||||||
colonIdx := strings.LastIndex(display, ":")
|
|
||||||
if colonIdx < 0 {
|
|
||||||
return errors.New("bad display string: " + display0)
|
|
||||||
}
|
|
||||||
|
|
||||||
var protocol, socket string
|
|
||||||
|
|
||||||
if display[0] == '/' {
|
|
||||||
socket = display[0:colonIdx]
|
|
||||||
} else {
|
|
||||||
slashIdx := strings.LastIndex(display, "/")
|
|
||||||
if slashIdx >= 0 {
|
|
||||||
protocol = display[0:slashIdx]
|
|
||||||
c.host = display[slashIdx+1 : colonIdx]
|
|
||||||
} else {
|
|
||||||
c.host = display[0:colonIdx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
display = display[colonIdx+1 : len(display)]
|
|
||||||
if len(display) == 0 {
|
|
||||||
return errors.New("bad display string: " + display0)
|
|
||||||
}
|
|
||||||
|
|
||||||
var scr string
|
|
||||||
dotIdx := strings.LastIndex(display, ".")
|
|
||||||
if dotIdx < 0 {
|
|
||||||
c.display = display[0:]
|
|
||||||
} else {
|
|
||||||
c.display = display[0:dotIdx]
|
|
||||||
scr = display[dotIdx+1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
c.DisplayNumber, err = strconv.Atoi(c.display)
|
|
||||||
if err != nil || c.DisplayNumber < 0 {
|
|
||||||
return errors.New("bad display string: " + display0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(scr) != 0 {
|
|
||||||
c.DefaultScreen, err = strconv.Atoi(scr)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("bad display string: " + display0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to server
|
|
||||||
if len(socket) != 0 {
|
|
||||||
c.conn, err = net.Dial("unix", socket+":"+c.display)
|
|
||||||
} else if len(c.host) != 0 {
|
|
||||||
if protocol == "" {
|
|
||||||
protocol = "tcp"
|
|
||||||
}
|
|
||||||
c.conn, err = net.Dial(protocol,
|
|
||||||
c.host+":"+strconv.Itoa(6000+c.DisplayNumber))
|
|
||||||
} else {
|
|
||||||
c.conn, err = net.Dial("unix", "/tmp/.X11-unix/X"+c.display)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("cannot connect to " + display0 + ": " + err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,165 +0,0 @@
|
||||||
package xgb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Cookie is the internal representation of a cookie, where one is generated
|
|
||||||
// for *every* request sent by XGB.
|
|
||||||
// 'cookie' is most frequently used by embedding it into a more specific
|
|
||||||
// kind of cookie, i.e., 'GetInputFocusCookie'.
|
|
||||||
type Cookie struct {
|
|
||||||
conn *Conn
|
|
||||||
Sequence uint16
|
|
||||||
replyChan chan []byte
|
|
||||||
errorChan chan error
|
|
||||||
pingChan chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCookie creates a new cookie with the correct channels initialized
|
|
||||||
// depending upon the values of 'checked' and 'reply'. Together, there are
|
|
||||||
// four different kinds of cookies. (See more detailed comments in the
|
|
||||||
// function for more info on those.)
|
|
||||||
// Note that a sequence number is not set until just before the request
|
|
||||||
// corresponding to this cookie is sent over the wire.
|
|
||||||
//
|
|
||||||
// Unless you're building requests from bytes by hand, this method should
|
|
||||||
// not be used.
|
|
||||||
func (c *Conn) NewCookie(checked, reply bool) *Cookie {
|
|
||||||
cookie := &Cookie{
|
|
||||||
conn: c,
|
|
||||||
Sequence: 0, // we add the sequence id just before sending a request
|
|
||||||
replyChan: nil,
|
|
||||||
errorChan: nil,
|
|
||||||
pingChan: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
// There are four different kinds of cookies:
|
|
||||||
// Checked requests with replies get a reply channel and an error channel.
|
|
||||||
// Unchecked requests with replies get a reply channel and a ping channel.
|
|
||||||
// Checked requests w/o replies get a ping channel and an error channel.
|
|
||||||
// Unchecked requests w/o replies get no channels.
|
|
||||||
// The reply channel is used to send reply data.
|
|
||||||
// The error channel is used to send error data.
|
|
||||||
// The ping channel is used when one of the 'reply' or 'error' channels
|
|
||||||
// is missing but the other is present. The ping channel is way to force
|
|
||||||
// the blocking to stop and basically say "the error has been received
|
|
||||||
// in the main event loop" (when the ping channel is coupled with a reply
|
|
||||||
// channel) or "the request you made that has no reply was successful"
|
|
||||||
// (when the ping channel is coupled with an error channel).
|
|
||||||
if checked {
|
|
||||||
cookie.errorChan = make(chan error, 1)
|
|
||||||
if !reply {
|
|
||||||
cookie.pingChan = make(chan bool, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if reply {
|
|
||||||
cookie.replyChan = make(chan []byte, 1)
|
|
||||||
if !checked {
|
|
||||||
cookie.pingChan = make(chan bool, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cookie
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reply detects whether this is a checked or unchecked cookie, and calls
|
|
||||||
// 'replyChecked' or 'replyUnchecked' appropriately.
|
|
||||||
//
|
|
||||||
// Unless you're building requests from bytes by hand, this method should
|
|
||||||
// not be used.
|
|
||||||
func (c Cookie) Reply() ([]byte, error) {
|
|
||||||
// checked
|
|
||||||
if c.errorChan != nil {
|
|
||||||
return c.replyChecked()
|
|
||||||
}
|
|
||||||
return c.replyUnchecked()
|
|
||||||
}
|
|
||||||
|
|
||||||
// replyChecked waits for a response on either the replyChan or errorChan
|
|
||||||
// channels. If the former arrives, the bytes are returned with a nil error.
|
|
||||||
// If the latter arrives, no bytes are returned (nil) and the error received
|
|
||||||
// is returned.
|
|
||||||
//
|
|
||||||
// Unless you're building requests from bytes by hand, this method should
|
|
||||||
// not be used.
|
|
||||||
func (c Cookie) replyChecked() ([]byte, error) {
|
|
||||||
if c.replyChan == nil {
|
|
||||||
return nil, errors.New("Cannot call 'replyChecked' on a cookie that " +
|
|
||||||
"is not expecting a *reply* or an error.")
|
|
||||||
}
|
|
||||||
if c.errorChan == nil {
|
|
||||||
return nil, errors.New("Cannot call 'replyChecked' on a cookie that " +
|
|
||||||
"is not expecting a reply or an *error*.")
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case reply := <-c.replyChan:
|
|
||||||
return reply, nil
|
|
||||||
case err := <-c.errorChan:
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// replyUnchecked waits for a response on either the replyChan or pingChan
|
|
||||||
// channels. If the former arrives, the bytes are returned with a nil error.
|
|
||||||
// If the latter arrives, no bytes are returned (nil) and a nil error
|
|
||||||
// is returned. (In the latter case, the corresponding error can be retrieved
|
|
||||||
// from (Wait|Poll)ForEvent asynchronously.)
|
|
||||||
// In all honesty, you *probably* don't want to use this method.
|
|
||||||
//
|
|
||||||
// Unless you're building requests from bytes by hand, this method should
|
|
||||||
// not be used.
|
|
||||||
func (c Cookie) replyUnchecked() ([]byte, error) {
|
|
||||||
if c.replyChan == nil {
|
|
||||||
return nil, errors.New("Cannot call 'replyUnchecked' on a cookie " +
|
|
||||||
"that is not expecting a *reply*.")
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case reply := <-c.replyChan:
|
|
||||||
return reply, nil
|
|
||||||
case <-c.pingChan:
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check is used for checked requests that have no replies. It is a mechanism
|
|
||||||
// by which to report "success" or "error" in a synchronous fashion. (Therefore,
|
|
||||||
// unchecked requests without replies cannot use this method.)
|
|
||||||
// If the request causes an error, it is sent to this cookie's errorChan.
|
|
||||||
// If the request was successful, there is no response from the server.
|
|
||||||
// Thus, pingChan is sent a value when the *next* reply is read.
|
|
||||||
// If no more replies are being processed, we force a round trip request with
|
|
||||||
// GetInputFocus.
|
|
||||||
//
|
|
||||||
// Unless you're building requests from bytes by hand, this method should
|
|
||||||
// not be used.
|
|
||||||
func (c Cookie) Check() error {
|
|
||||||
if c.replyChan != nil {
|
|
||||||
return errors.New("Cannot call 'Check' on a cookie that is " +
|
|
||||||
"expecting a *reply*. Use 'Reply' instead.")
|
|
||||||
}
|
|
||||||
if c.errorChan == nil {
|
|
||||||
return errors.New("Cannot call 'Check' on a cookie that is " +
|
|
||||||
"not expecting a possible *error*.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// First do a quick non-blocking check to see if we've been pinged.
|
|
||||||
select {
|
|
||||||
case err := <-c.errorChan:
|
|
||||||
return err
|
|
||||||
case <-c.pingChan:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now force a round trip and try again, but block this time.
|
|
||||||
c.conn.Sync()
|
|
||||||
select {
|
|
||||||
case err := <-c.errorChan:
|
|
||||||
return err
|
|
||||||
case <-c.pingChan:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,146 +0,0 @@
|
||||||
/*
|
|
||||||
Package XGB provides the X Go Binding, which is a low-level API to communicate
|
|
||||||
with the core X protocol and many of the X extensions.
|
|
||||||
|
|
||||||
It is *very* closely modeled on XCB, so that experience with XCB (or xpyb) is
|
|
||||||
easily translatable to XGB. That is, it uses the same cookie/reply model
|
|
||||||
and is thread safe. There are otherwise no major differences (in the API).
|
|
||||||
|
|
||||||
Most uses of XGB typically fall under the realm of window manager and GUI kit
|
|
||||||
development, but other applications (like pagers, panels, tilers, etc.) may
|
|
||||||
also require XGB. Moreover, it is a near certainty that if you need to work
|
|
||||||
with X, xgbutil will be of great use to you as well:
|
|
||||||
https://github.com/BurntSushi/xgbutil
|
|
||||||
|
|
||||||
Example
|
|
||||||
|
|
||||||
This is an extremely terse example that demonstrates how to connect to X,
|
|
||||||
create a window, listen to StructureNotify events and Key{Press,Release}
|
|
||||||
events, map the window, and print out all events received. An example with
|
|
||||||
accompanying documentation can be found in examples/create-window.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/BurntSushi/xgb"
|
|
||||||
"github.com/BurntSushi/xgb/xproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
X, err := xgb.NewConn()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wid, _ := xproto.NewWindowId(X)
|
|
||||||
screen := xproto.Setup(X).DefaultScreen(X)
|
|
||||||
xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root,
|
|
||||||
0, 0, 500, 500, 0,
|
|
||||||
xproto.WindowClassInputOutput, screen.RootVisual,
|
|
||||||
xproto.CwBackPixel | xproto.CwEventMask,
|
|
||||||
[]uint32{ // values must be in the order defined by the protocol
|
|
||||||
0xffffffff,
|
|
||||||
xproto.EventMaskStructureNotify |
|
|
||||||
xproto.EventMaskKeyPress |
|
|
||||||
xproto.EventMaskKeyRelease})
|
|
||||||
|
|
||||||
xproto.MapWindow(X, wid)
|
|
||||||
for {
|
|
||||||
ev, xerr := X.WaitForEvent()
|
|
||||||
if ev == nil && xerr == nil {
|
|
||||||
fmt.Println("Both event and error are nil. Exiting...")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ev != nil {
|
|
||||||
fmt.Printf("Event: %s\n", ev)
|
|
||||||
}
|
|
||||||
if xerr != nil {
|
|
||||||
fmt.Printf("Error: %s\n", xerr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Xinerama Example
|
|
||||||
|
|
||||||
This is another small example that shows how to query Xinerama for geometry
|
|
||||||
information of each active head. Accompanying documentation for this example
|
|
||||||
can be found in examples/xinerama.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"github.com/BurntSushi/xgb"
|
|
||||||
"github.com/BurntSushi/xgb/xinerama"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
X, err := xgb.NewConn()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the Xinerama extension.
|
|
||||||
// The appropriate 'Init' function must be run for *every*
|
|
||||||
// extension before any of its requests can be used.
|
|
||||||
err = xinerama.Init(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reply, err := xinerama.QueryScreens(X).Reply()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Number of heads: %d\n", reply.Number)
|
|
||||||
for i, screen := range reply.ScreenInfo {
|
|
||||||
fmt.Printf("%d :: X: %d, Y: %d, Width: %d, Height: %d\n",
|
|
||||||
i, screen.XOrg, screen.YOrg, screen.Width, screen.Height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Parallelism
|
|
||||||
|
|
||||||
XGB can benefit greatly from parallelism due to its concurrent design. For
|
|
||||||
evidence of this claim, please see the benchmarks in xproto/xproto_test.go.
|
|
||||||
|
|
||||||
Tests
|
|
||||||
|
|
||||||
xproto/xproto_test.go contains a number of contrived tests that stress
|
|
||||||
particular corners of XGB that I presume could be problem areas. Namely:
|
|
||||||
requests with no replies, requests with replies, checked errors, unchecked
|
|
||||||
errors, sequence number wrapping, cookie buffer flushing (i.e., forcing a round
|
|
||||||
trip every N requests made that don't have a reply), getting/setting properties
|
|
||||||
and creating a window and listening to StructureNotify events.
|
|
||||||
|
|
||||||
Code Generator
|
|
||||||
|
|
||||||
Both XCB and xpyb use the same Python module (xcbgen) for a code generator. XGB
|
|
||||||
(before this fork) used the same code generator as well, but in my attempt to
|
|
||||||
add support for more extensions, I found the code generator extremely difficult
|
|
||||||
to work with. Therefore, I re-wrote the code generator in Go. It can be found
|
|
||||||
in its own sub-package, xgbgen, of xgb. My design of xgbgen includes a rough
|
|
||||||
consideration that it could be used for other languages.
|
|
||||||
|
|
||||||
What works
|
|
||||||
|
|
||||||
I am reasonably confident that the core X protocol is in full working form. I've
|
|
||||||
also tested the Xinerama and RandR extensions sparingly. Many of the other
|
|
||||||
existing extensions have Go source generated (and are compilable) and are
|
|
||||||
included in this package, but I am currently unsure of their status. They
|
|
||||||
*should* work.
|
|
||||||
|
|
||||||
What does not work
|
|
||||||
|
|
||||||
XKB is the only extension that intentionally does not work, although I suspect
|
|
||||||
that GLX also does not work (however, there is Go source code for GLX that
|
|
||||||
compiles, unlike XKB). I don't currently have any intention of getting XKB
|
|
||||||
working, due to its complexity and my current mental incapacity to test it.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package xgb
|
|
|
@ -1,105 +0,0 @@
|
||||||
package xgb
|
|
||||||
|
|
||||||
/*
|
|
||||||
help.go is meant to contain a rough hodge podge of functions that are mainly
|
|
||||||
used in the auto generated code. Indeed, several functions here are simple
|
|
||||||
wrappers so that the sub-packages don't need to be smart about which stdlib
|
|
||||||
packages to import.
|
|
||||||
|
|
||||||
Also, the 'Get..' and 'Put..' functions are used through the core xgb package
|
|
||||||
too. (xgbutil uses them too.)
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StringsJoin is an alias to strings.Join. It allows us to avoid having to
|
|
||||||
// import 'strings' in each of the generated Go files.
|
|
||||||
func StringsJoin(ss []string, sep string) string {
|
|
||||||
return strings.Join(ss, sep)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintf is so we don't need to import 'fmt' in the generated Go files.
|
|
||||||
func Sprintf(format string, v ...interface{}) string {
|
|
||||||
return fmt.Sprintf(format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf is just a wrapper for fmt.Errorf. Exists for the same reason
|
|
||||||
// that 'stringsJoin' and 'sprintf' exists.
|
|
||||||
func Errorf(format string, v ...interface{}) error {
|
|
||||||
return fmt.Errorf(format, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pad a length to align on 4 bytes.
|
|
||||||
func Pad(n int) int {
|
|
||||||
return (n + 3) & ^3
|
|
||||||
}
|
|
||||||
|
|
||||||
// PopCount counts the number of bits set in a value list mask.
|
|
||||||
func PopCount(mask0 int) int {
|
|
||||||
mask := uint32(mask0)
|
|
||||||
n := 0
|
|
||||||
for i := uint32(0); i < 32; i++ {
|
|
||||||
if mask&(1<<i) != 0 {
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put16 takes a 16 bit integer and copies it into a byte slice.
|
|
||||||
func Put16(buf []byte, v uint16) {
|
|
||||||
buf[0] = byte(v)
|
|
||||||
buf[1] = byte(v >> 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put32 takes a 32 bit integer and copies it into a byte slice.
|
|
||||||
func Put32(buf []byte, v uint32) {
|
|
||||||
buf[0] = byte(v)
|
|
||||||
buf[1] = byte(v >> 8)
|
|
||||||
buf[2] = byte(v >> 16)
|
|
||||||
buf[3] = byte(v >> 24)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put64 takes a 64 bit integer and copies it into a byte slice.
|
|
||||||
func Put64(buf []byte, v uint64) {
|
|
||||||
buf[0] = byte(v)
|
|
||||||
buf[1] = byte(v >> 8)
|
|
||||||
buf[2] = byte(v >> 16)
|
|
||||||
buf[3] = byte(v >> 24)
|
|
||||||
buf[4] = byte(v >> 32)
|
|
||||||
buf[5] = byte(v >> 40)
|
|
||||||
buf[6] = byte(v >> 48)
|
|
||||||
buf[7] = byte(v >> 56)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get16 constructs a 16 bit integer from the beginning of a byte slice.
|
|
||||||
func Get16(buf []byte) uint16 {
|
|
||||||
v := uint16(buf[0])
|
|
||||||
v |= uint16(buf[1]) << 8
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get32 constructs a 32 bit integer from the beginning of a byte slice.
|
|
||||||
func Get32(buf []byte) uint32 {
|
|
||||||
v := uint32(buf[0])
|
|
||||||
v |= uint32(buf[1]) << 8
|
|
||||||
v |= uint32(buf[2]) << 16
|
|
||||||
v |= uint32(buf[3]) << 24
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get64 constructs a 64 bit integer from the beginning of a byte slice.
|
|
||||||
func Get64(buf []byte) uint64 {
|
|
||||||
v := uint64(buf[0])
|
|
||||||
v |= uint64(buf[1]) << 8
|
|
||||||
v |= uint64(buf[2]) << 16
|
|
||||||
v |= uint64(buf[3]) << 24
|
|
||||||
v |= uint64(buf[4]) << 32
|
|
||||||
v |= uint64(buf[5]) << 40
|
|
||||||
v |= uint64(buf[6]) << 48
|
|
||||||
v |= uint64(buf[7]) << 56
|
|
||||||
return v
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package xgb
|
|
||||||
|
|
||||||
// Sync sends a round trip request and waits for the response.
|
|
||||||
// This forces all pending cookies to be dealt with.
|
|
||||||
// You actually shouldn't need to use this like you might with Xlib. Namely,
|
|
||||||
// buffers are automatically flushed using Go's channels and round trip requests
|
|
||||||
// are forced where appropriate automatically.
|
|
||||||
func (c *Conn) Sync() {
|
|
||||||
cookie := c.NewCookie(true, true)
|
|
||||||
c.NewRequest(c.getInputFocusRequest(), cookie)
|
|
||||||
cookie.Reply() // wait for the buffer to clear
|
|
||||||
}
|
|
||||||
|
|
||||||
// getInputFocusRequest writes the raw bytes to a buffer.
|
|
||||||
// It is duplicated from xproto/xproto.go.
|
|
||||||
func (c *Conn) getInputFocusRequest() []byte {
|
|
||||||
size := 4
|
|
||||||
b := 0
|
|
||||||
buf := make([]byte, size)
|
|
||||||
|
|
||||||
buf[b] = 43 // request opcode
|
|
||||||
b += 1
|
|
||||||
|
|
||||||
b += 1 // padding
|
|
||||||
Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
|
|
||||||
b += 2
|
|
||||||
|
|
||||||
return buf
|
|
||||||
}
|
|
|
@ -1,554 +0,0 @@
|
||||||
package xgb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Where to log error-messages. Defaults to stderr.
|
|
||||||
// To disable logging, just set this to log.New(ioutil.Discard, "", 0)
|
|
||||||
Logger = log.New(os.Stderr, "XGB: ", log.Lshortfile)
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// cookieBuffer represents the queue size of cookies existing at any
|
|
||||||
// point in time. The size of the buffer is really only important when
|
|
||||||
// there are many requests without replies made in sequence. Once the
|
|
||||||
// buffer fills, a round trip request is made to clear the buffer.
|
|
||||||
cookieBuffer = 1000
|
|
||||||
|
|
||||||
// xidBuffer represents the queue size of the xid channel.
|
|
||||||
// I don't think this value matters much, since xid generation is not
|
|
||||||
// that expensive.
|
|
||||||
xidBuffer = 5
|
|
||||||
|
|
||||||
// seqBuffer represents the queue size of the sequence number channel.
|
|
||||||
// I don't think this value matters much, since sequence number generation
|
|
||||||
// is not that expensive.
|
|
||||||
seqBuffer = 5
|
|
||||||
|
|
||||||
// reqBuffer represents the queue size of the number of requests that
|
|
||||||
// can be made until new ones block. This value seems OK.
|
|
||||||
reqBuffer = 100
|
|
||||||
|
|
||||||
// eventBuffer represents the queue size of the number of events or errors
|
|
||||||
// that can be loaded off the wire and not grabbed with WaitForEvent
|
|
||||||
// until reading an event blocks. This value should be big enough to handle
|
|
||||||
// bursts of events.
|
|
||||||
eventBuffer = 5000
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Conn represents a connection to an X server.
|
|
||||||
type Conn struct {
|
|
||||||
host string
|
|
||||||
conn net.Conn
|
|
||||||
display string
|
|
||||||
DisplayNumber int
|
|
||||||
DefaultScreen int
|
|
||||||
SetupBytes []byte
|
|
||||||
|
|
||||||
setupResourceIdBase uint32
|
|
||||||
setupResourceIdMask uint32
|
|
||||||
|
|
||||||
eventChan chan eventOrError
|
|
||||||
cookieChan chan *Cookie
|
|
||||||
xidChan chan xid
|
|
||||||
seqChan chan uint16
|
|
||||||
reqChan chan *request
|
|
||||||
closing chan chan struct{}
|
|
||||||
|
|
||||||
// ExtLock is a lock used whenever new extensions are initialized.
|
|
||||||
// It should not be used. It is exported for use in the extension
|
|
||||||
// sub-packages.
|
|
||||||
ExtLock sync.RWMutex
|
|
||||||
|
|
||||||
// Extensions is a map from extension name to major opcode. It should
|
|
||||||
// not be used. It is exported for use in the extension sub-packages.
|
|
||||||
Extensions map[string]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConn creates a new connection instance. It initializes locks, data
|
|
||||||
// structures, and performs the initial handshake. (The code for the handshake
|
|
||||||
// has been relegated to conn.go.)
|
|
||||||
func NewConn() (*Conn, error) {
|
|
||||||
return NewConnDisplay("")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConnDisplay is just like NewConn, but allows a specific DISPLAY
|
|
||||||
// string to be used.
|
|
||||||
// If 'display' is empty it will be taken from os.Getenv("DISPLAY").
|
|
||||||
//
|
|
||||||
// Examples:
|
|
||||||
// NewConn(":1") -> net.Dial("unix", "", "/tmp/.X11-unix/X1")
|
|
||||||
// NewConn("/tmp/launch-12/:0") -> net.Dial("unix", "", "/tmp/launch-12/:0")
|
|
||||||
// NewConn("hostname:2.1") -> net.Dial("tcp", "", "hostname:6002")
|
|
||||||
// NewConn("tcp/hostname:1.0") -> net.Dial("tcp", "", "hostname:6001")
|
|
||||||
func NewConnDisplay(display string) (*Conn, error) {
|
|
||||||
conn := &Conn{}
|
|
||||||
|
|
||||||
// First connect. This reads authority, checks DISPLAY environment
|
|
||||||
// variable, and loads the initial Setup info.
|
|
||||||
err := conn.connect(display)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return postNewConn(conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConnDisplay is just like NewConn, but allows a specific net.Conn
|
|
||||||
// to be used.
|
|
||||||
func NewConnNet(netConn net.Conn) (*Conn, error) {
|
|
||||||
conn := &Conn{}
|
|
||||||
|
|
||||||
// First connect. This reads authority, checks DISPLAY environment
|
|
||||||
// variable, and loads the initial Setup info.
|
|
||||||
err := conn.connectNet(netConn)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return postNewConn(conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func postNewConn(conn *Conn) (*Conn, error) {
|
|
||||||
conn.Extensions = make(map[string]byte)
|
|
||||||
|
|
||||||
conn.cookieChan = make(chan *Cookie, cookieBuffer)
|
|
||||||
conn.xidChan = make(chan xid, xidBuffer)
|
|
||||||
conn.seqChan = make(chan uint16, seqBuffer)
|
|
||||||
conn.reqChan = make(chan *request, reqBuffer)
|
|
||||||
conn.eventChan = make(chan eventOrError, eventBuffer)
|
|
||||||
conn.closing = make(chan chan struct{}, 1)
|
|
||||||
|
|
||||||
go conn.generateXIds()
|
|
||||||
go conn.generateSeqIds()
|
|
||||||
go conn.sendRequests()
|
|
||||||
go conn.readResponses()
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close gracefully closes the connection to the X server.
|
|
||||||
func (c *Conn) Close() {
|
|
||||||
close(c.reqChan)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event is an interface that can contain any of the events returned by the
|
|
||||||
// server. Use a type assertion switch to extract the Event structs.
|
|
||||||
type Event interface {
|
|
||||||
Bytes() []byte
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEventFun is the type of function use to construct events from raw bytes.
|
|
||||||
// It should not be used. It is exported for use in the extension sub-packages.
|
|
||||||
type NewEventFun func(buf []byte) Event
|
|
||||||
|
|
||||||
// NewEventFuncs is a map from event numbers to functions that create
|
|
||||||
// the corresponding event. It should not be used. It is exported for use
|
|
||||||
// in the extension sub-packages.
|
|
||||||
var NewEventFuncs = make(map[int]NewEventFun)
|
|
||||||
|
|
||||||
// NewExtEventFuncs is a temporary map that stores event constructor functions
|
|
||||||
// for each extension. When an extension is initialized, each event for that
|
|
||||||
// extension is added to the 'NewEventFuncs' map. It should not be used. It is
|
|
||||||
// exported for use in the extension sub-packages.
|
|
||||||
var NewExtEventFuncs = make(map[string]map[int]NewEventFun)
|
|
||||||
|
|
||||||
// Error is an interface that can contain any of the errors returned by
|
|
||||||
// the server. Use a type assertion switch to extract the Error structs.
|
|
||||||
type Error interface {
|
|
||||||
SequenceId() uint16
|
|
||||||
BadId() uint32
|
|
||||||
Error() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewErrorFun is the type of function use to construct errors from raw bytes.
|
|
||||||
// It should not be used. It is exported for use in the extension sub-packages.
|
|
||||||
type NewErrorFun func(buf []byte) Error
|
|
||||||
|
|
||||||
// NewErrorFuncs is a map from error numbers to functions that create
|
|
||||||
// the corresponding error. It should not be used. It is exported for use in
|
|
||||||
// the extension sub-packages.
|
|
||||||
var NewErrorFuncs = make(map[int]NewErrorFun)
|
|
||||||
|
|
||||||
// NewExtErrorFuncs is a temporary map that stores error constructor functions
|
|
||||||
// for each extension. When an extension is initialized, each error for that
|
|
||||||
// extension is added to the 'NewErrorFuncs' map. It should not be used. It is
|
|
||||||
// exported for use in the extension sub-packages.
|
|
||||||
var NewExtErrorFuncs = make(map[string]map[int]NewErrorFun)
|
|
||||||
|
|
||||||
// eventOrError corresponds to values that can be either an event or an
|
|
||||||
// error.
|
|
||||||
type eventOrError interface{}
|
|
||||||
|
|
||||||
// NewId generates a new unused ID for use with requests like CreateWindow.
|
|
||||||
// If no new ids can be generated, the id returned is 0 and error is non-nil.
|
|
||||||
// This shouldn't be used directly, and is exported for use in the extension
|
|
||||||
// sub-packages.
|
|
||||||
// If you need identifiers, use the appropriate constructor.
|
|
||||||
// e.g., For a window id, use xproto.NewWindowId. For
|
|
||||||
// a new pixmap id, use xproto.NewPixmapId. And so on.
|
|
||||||
func (c *Conn) NewId() (uint32, error) {
|
|
||||||
xid := <-c.xidChan
|
|
||||||
if xid.err != nil {
|
|
||||||
return 0, xid.err
|
|
||||||
}
|
|
||||||
return xid.id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// xid encapsulates a resource identifier being sent over the Conn.xidChan
|
|
||||||
// channel. If no new resource id can be generated, id is set to 0 and a
|
|
||||||
// non-nil error is set in xid.err.
|
|
||||||
type xid struct {
|
|
||||||
id uint32
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateXids sends new Ids down the channel for NewId to use.
|
|
||||||
// generateXids should be run in its own goroutine.
|
|
||||||
// This needs to be updated to use the XC Misc extension once we run out of
|
|
||||||
// new ids.
|
|
||||||
// Thanks to libxcb/src/xcb_xid.c. This code is greatly inspired by it.
|
|
||||||
func (conn *Conn) generateXIds() {
|
|
||||||
defer close(conn.xidChan)
|
|
||||||
|
|
||||||
// This requires some explanation. From the horse's mouth:
|
|
||||||
// "The resource-id-mask contains a single contiguous set of bits (at least
|
|
||||||
// 18). The client allocates resource IDs for types WINDOW, PIXMAP,
|
|
||||||
// CURSOR, FONT, GCONTEXT, and COLORMAP by choosing a value with only some
|
|
||||||
// subset of these bits set and ORing it with resource-id-base. Only values
|
|
||||||
// constructed in this way can be used to name newly created resources over
|
|
||||||
// this connection."
|
|
||||||
// So for example (using 8 bit integers), the mask might look like:
|
|
||||||
// 00111000
|
|
||||||
// So that valid values would be 00101000, 00110000, 00001000, and so on.
|
|
||||||
// Thus, the idea is to increment it by the place of the last least
|
|
||||||
// significant '1'. In this case, that value would be 00001000. To get
|
|
||||||
// that value, we can AND the original mask with its two's complement:
|
|
||||||
// 00111000 & 11001000 = 00001000.
|
|
||||||
// And we use that value to increment the last resource id to get a new one.
|
|
||||||
// (And then, of course, we OR it with resource-id-base.)
|
|
||||||
inc := conn.setupResourceIdMask & -conn.setupResourceIdMask
|
|
||||||
max := conn.setupResourceIdMask
|
|
||||||
last := uint32(0)
|
|
||||||
for {
|
|
||||||
// TODO: Use the XC Misc extension to look for released ids.
|
|
||||||
if last > 0 && last >= max-inc+1 {
|
|
||||||
conn.xidChan <- xid{
|
|
||||||
id: 0,
|
|
||||||
err: errors.New("There are no more available resource" +
|
|
||||||
"identifiers."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
last += inc
|
|
||||||
conn.xidChan <- xid{
|
|
||||||
id: last | conn.setupResourceIdBase,
|
|
||||||
err: nil,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// newSeqId fetches the next sequence id from the Conn.seqChan channel.
|
|
||||||
func (c *Conn) newSequenceId() uint16 {
|
|
||||||
return <-c.seqChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateSeqIds returns new sequence ids. It is meant to be run in its
|
|
||||||
// own goroutine.
|
|
||||||
// A sequence id is generated for *every* request. It's the identifier used
|
|
||||||
// to match up replies with requests.
|
|
||||||
// Since sequence ids can only be 16 bit integers we start over at zero when it
|
|
||||||
// comes time to wrap.
|
|
||||||
// N.B. As long as the cookie buffer is less than 2^16, there are no limitations
|
|
||||||
// on the number (or kind) of requests made in sequence.
|
|
||||||
func (c *Conn) generateSeqIds() {
|
|
||||||
defer close(c.seqChan)
|
|
||||||
|
|
||||||
seqid := uint16(1)
|
|
||||||
for {
|
|
||||||
c.seqChan <- seqid
|
|
||||||
if seqid == uint16((1<<16)-1) {
|
|
||||||
seqid = 0
|
|
||||||
} else {
|
|
||||||
seqid++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// request encapsulates a buffer of raw bytes (containing the request data)
|
|
||||||
// and a cookie, which when combined represents a single request.
|
|
||||||
// The cookie is used to match up the reply/error.
|
|
||||||
type request struct {
|
|
||||||
buf []byte
|
|
||||||
cookie *Cookie
|
|
||||||
|
|
||||||
// seq is closed when the request (cookie) has been sequenced by the Conn.
|
|
||||||
seq chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRequest takes the bytes and a cookie of a particular request, constructs
|
|
||||||
// a request type, and sends it over the Conn.reqChan channel.
|
|
||||||
// Note that the sequence number is added to the cookie after it is sent
|
|
||||||
// over the request channel, but before it is sent to X.
|
|
||||||
//
|
|
||||||
// Note that you may safely use NewRequest to send arbitrary byte requests
|
|
||||||
// to X. The resulting cookie can be used just like any normal cookie and
|
|
||||||
// abides by the same rules, except that for replies, you'll get back the
|
|
||||||
// raw byte data. This may be useful for performance critical sections where
|
|
||||||
// every allocation counts, since all X requests in XGB allocate a new byte
|
|
||||||
// slice. In contrast, NewRequest allocates one small request struct and
|
|
||||||
// nothing else. (Except when the cookie buffer is full and has to be flushed.)
|
|
||||||
//
|
|
||||||
// If you're using NewRequest manually, you'll need to use NewCookie to create
|
|
||||||
// a new cookie.
|
|
||||||
//
|
|
||||||
// In all likelihood, you should be able to copy and paste with some minor
|
|
||||||
// edits the generated code for the request you want to issue.
|
|
||||||
func (c *Conn) NewRequest(buf []byte, cookie *Cookie) {
|
|
||||||
seq := make(chan struct{})
|
|
||||||
c.reqChan <- &request{buf: buf, cookie: cookie, seq: seq}
|
|
||||||
<-seq
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendRequests is run as a single goroutine that takes requests and writes
|
|
||||||
// the bytes to the wire and adds the cookie to the cookie queue.
|
|
||||||
// It is meant to be run as its own goroutine.
|
|
||||||
func (c *Conn) sendRequests() {
|
|
||||||
defer close(c.cookieChan)
|
|
||||||
|
|
||||||
for req := range c.reqChan {
|
|
||||||
// ho there! if the cookie channel is nearly full, force a round
|
|
||||||
// trip to clear out the cookie buffer.
|
|
||||||
// Note that we circumvent the request channel, because we're *in*
|
|
||||||
// the request channel.
|
|
||||||
if len(c.cookieChan) == cookieBuffer-1 {
|
|
||||||
if err := c.noop(); err != nil {
|
|
||||||
// Shut everything down.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
req.cookie.Sequence = c.newSequenceId()
|
|
||||||
c.cookieChan <- req.cookie
|
|
||||||
c.writeBuffer(req.buf)
|
|
||||||
close(req.seq)
|
|
||||||
}
|
|
||||||
response := make(chan struct{})
|
|
||||||
c.closing <- response
|
|
||||||
c.noop() // Flush the response reading goroutine, ignore error.
|
|
||||||
<-response
|
|
||||||
c.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// noop circumvents the usual request sending goroutines and forces a round
|
|
||||||
// trip request manually.
|
|
||||||
func (c *Conn) noop() error {
|
|
||||||
cookie := c.NewCookie(true, true)
|
|
||||||
cookie.Sequence = c.newSequenceId()
|
|
||||||
c.cookieChan <- cookie
|
|
||||||
if err := c.writeBuffer(c.getInputFocusRequest()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cookie.Reply() // wait for the buffer to clear
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeBuffer is a convenience function for writing a byte slice to the wire.
|
|
||||||
func (c *Conn) writeBuffer(buf []byte) error {
|
|
||||||
if _, err := c.conn.Write(buf); err != nil {
|
|
||||||
Logger.Printf("A write error is unrecoverable: %s", err)
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// readResponses is a goroutine that reads events, errors and
|
|
||||||
// replies off the wire.
|
|
||||||
// When an event is read, it is always added to the event channel.
|
|
||||||
// When an error is read, if it corresponds to an existing checked cookie,
|
|
||||||
// it is sent to that cookie's error channel. Otherwise it is added to the
|
|
||||||
// event channel.
|
|
||||||
// When a reply is read, it is added to the corresponding cookie's reply
|
|
||||||
// channel. (It is an error if no such cookie exists in this case.)
|
|
||||||
// Finally, cookies that came "before" this reply are always cleaned up.
|
|
||||||
func (c *Conn) readResponses() {
|
|
||||||
defer close(c.eventChan)
|
|
||||||
|
|
||||||
var (
|
|
||||||
err Error
|
|
||||||
seq uint16
|
|
||||||
replyBytes []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case respond := <-c.closing:
|
|
||||||
respond <- struct{}{}
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 32)
|
|
||||||
err, seq = nil, 0
|
|
||||||
if _, err := io.ReadFull(c.conn, buf); err != nil {
|
|
||||||
Logger.Printf("A read error is unrecoverable: %s", err)
|
|
||||||
c.eventChan <- err
|
|
||||||
c.Close()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch buf[0] {
|
|
||||||
case 0: // This is an error
|
|
||||||
// Use the constructor function for this error (that is auto
|
|
||||||
// generated) by looking it up by the error number.
|
|
||||||
newErrFun, ok := NewErrorFuncs[int(buf[1])]
|
|
||||||
if !ok {
|
|
||||||
Logger.Printf("BUG: Could not find error constructor function "+
|
|
||||||
"for error with number %d.", buf[1])
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = newErrFun(buf)
|
|
||||||
seq = err.SequenceId()
|
|
||||||
|
|
||||||
// This error is either sent to the event channel or a specific
|
|
||||||
// cookie's error channel below.
|
|
||||||
case 1: // This is a reply
|
|
||||||
seq = Get16(buf[2:])
|
|
||||||
|
|
||||||
// check to see if this reply has more bytes to be read
|
|
||||||
size := Get32(buf[4:])
|
|
||||||
if size > 0 {
|
|
||||||
byteCount := 32 + size*4
|
|
||||||
biggerBuf := make([]byte, byteCount)
|
|
||||||
copy(biggerBuf[:32], buf)
|
|
||||||
if _, err := io.ReadFull(c.conn, biggerBuf[32:]); err != nil {
|
|
||||||
Logger.Printf("A read error is unrecoverable: %s", err)
|
|
||||||
c.eventChan <- err
|
|
||||||
c.Close()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
replyBytes = biggerBuf
|
|
||||||
} else {
|
|
||||||
replyBytes = buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// This reply is sent to its corresponding cookie below.
|
|
||||||
default: // This is an event
|
|
||||||
// Use the constructor function for this event (like for errors,
|
|
||||||
// and is also auto generated) by looking it up by the event number.
|
|
||||||
// Note that we AND the event number with 127 so that we ignore
|
|
||||||
// the most significant bit (which is set when it was sent from
|
|
||||||
// a SendEvent request).
|
|
||||||
evNum := int(buf[0] & 127)
|
|
||||||
newEventFun, ok := NewEventFuncs[evNum]
|
|
||||||
if !ok {
|
|
||||||
Logger.Printf("BUG: Could not find event construct function "+
|
|
||||||
"for event with number %d.", evNum)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c.eventChan <- newEventFun(buf)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point, we have a sequence number and we're either
|
|
||||||
// processing an error or a reply, which are both responses to
|
|
||||||
// requests. So all we have to do is find the cookie corresponding
|
|
||||||
// to this error/reply, and send the appropriate data to it.
|
|
||||||
// In doing so, we make sure that any cookies that came before it
|
|
||||||
// are marked as successful if they are void and checked.
|
|
||||||
// If there's a cookie that requires a reply that is before this
|
|
||||||
// reply, then something is wrong.
|
|
||||||
for cookie := range c.cookieChan {
|
|
||||||
// This is the cookie we're looking for. Process and break.
|
|
||||||
if cookie.Sequence == seq {
|
|
||||||
if err != nil { // this is an error to a request
|
|
||||||
// synchronous processing
|
|
||||||
if cookie.errorChan != nil {
|
|
||||||
cookie.errorChan <- err
|
|
||||||
} else { // asynchronous processing
|
|
||||||
c.eventChan <- err
|
|
||||||
// if this is an unchecked reply, ping the cookie too
|
|
||||||
if cookie.pingChan != nil {
|
|
||||||
cookie.pingChan <- true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // this is a reply
|
|
||||||
if cookie.replyChan == nil {
|
|
||||||
Logger.Printf("Reply with sequence id %d does not "+
|
|
||||||
"have a cookie with a valid reply channel.", seq)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
cookie.replyChan <- replyBytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
// Checked requests with replies
|
|
||||||
case cookie.replyChan != nil && cookie.errorChan != nil:
|
|
||||||
Logger.Printf("Found cookie with sequence id %d that is "+
|
|
||||||
"expecting a reply but will never get it. Currently "+
|
|
||||||
"on sequence number %d", cookie.Sequence, seq)
|
|
||||||
// Unchecked requests with replies
|
|
||||||
case cookie.replyChan != nil && cookie.pingChan != nil:
|
|
||||||
Logger.Printf("Found cookie with sequence id %d that is "+
|
|
||||||
"expecting a reply (and not an error) but will never "+
|
|
||||||
"get it. Currently on sequence number %d",
|
|
||||||
cookie.Sequence, seq)
|
|
||||||
// Checked requests without replies
|
|
||||||
case cookie.pingChan != nil && cookie.errorChan != nil:
|
|
||||||
cookie.pingChan <- true
|
|
||||||
// Unchecked requests without replies don't have any channels,
|
|
||||||
// so we can't do anything with them except let them pass by.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// processEventOrError takes an eventOrError, type switches on it,
|
|
||||||
// and returns it in Go idiomatic style.
|
|
||||||
func processEventOrError(everr eventOrError) (Event, Error) {
|
|
||||||
switch ee := everr.(type) {
|
|
||||||
case Event:
|
|
||||||
return ee, nil
|
|
||||||
case Error:
|
|
||||||
return nil, ee
|
|
||||||
default:
|
|
||||||
Logger.Printf("Invalid event/error type: %T", everr)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForEvent returns the next event from the server.
|
|
||||||
// It will block until an event is available.
|
|
||||||
// WaitForEvent returns either an Event or an Error. (Returning both
|
|
||||||
// is a bug.) Note than an Error here is an X error and not an XGB error. That
|
|
||||||
// is, X errors are sometimes completely expected (and you may want to ignore
|
|
||||||
// them in some cases).
|
|
||||||
//
|
|
||||||
// If both the event and error are nil, then the connection has been closed.
|
|
||||||
func (c *Conn) WaitForEvent() (Event, Error) {
|
|
||||||
return processEventOrError(<-c.eventChan)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PollForEvent returns the next event from the server if one is available in
|
|
||||||
// the internal queue without blocking. Note that unlike WaitForEvent, both
|
|
||||||
// Event and Error could be nil. Indeed, they are both nil when the event queue
|
|
||||||
// is empty.
|
|
||||||
func (c *Conn) PollForEvent() (Event, Error) {
|
|
||||||
select {
|
|
||||||
case everr := <-c.eventChan:
|
|
||||||
return processEventOrError(everr)
|
|
||||||
default:
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +0,0 @@
|
||||||
[568].out
|
|
||||||
_go*
|
|
||||||
_test*
|
|
||||||
_obj
|
|
|
@ -1,17 +0,0 @@
|
||||||
ARG GOVERSION=1.14
|
|
||||||
FROM golang:${GOVERSION}
|
|
||||||
|
|
||||||
# Set base env.
|
|
||||||
ARG GOOS=linux
|
|
||||||
ARG GOARCH=amd64
|
|
||||||
ENV GOOS=${GOOS} GOARCH=${GOARCH} CGO_ENABLED=0 GOFLAGS='-v -ldflags=-s -ldflags=-w'
|
|
||||||
|
|
||||||
# Pre compile the stdlib for 386/arm (32bits).
|
|
||||||
RUN go build -a std
|
|
||||||
|
|
||||||
# Add the code to the image.
|
|
||||||
WORKDIR pty
|
|
||||||
ADD . .
|
|
||||||
|
|
||||||
# Build the lib.
|
|
||||||
RUN go build
|
|
|
@ -1,23 +0,0 @@
|
||||||
# NOTE: Using 1.13 as a base to build the RISCV compiler, the resulting version is based on go1.6.
|
|
||||||
FROM golang:1.13
|
|
||||||
|
|
||||||
# Clone and complie a riscv compatible version of the go compiler.
|
|
||||||
RUN git clone https://review.gerrithub.io/riscv/riscv-go /riscv-go
|
|
||||||
# riscvdev branch HEAD as of 2019-06-29.
|
|
||||||
RUN cd /riscv-go && git checkout 04885fddd096d09d4450726064d06dd107e374bf
|
|
||||||
ENV PATH=/riscv-go/misc/riscv:/riscv-go/bin:$PATH
|
|
||||||
RUN cd /riscv-go/src && GOROOT_BOOTSTRAP=$(go env GOROOT) ./make.bash
|
|
||||||
ENV GOROOT=/riscv-go
|
|
||||||
|
|
||||||
# Set the base env.
|
|
||||||
ENV GOOS=linux GOARCH=riscv CGO_ENABLED=0 GOFLAGS='-v -ldflags=-s -ldflags=-w'
|
|
||||||
|
|
||||||
# Pre compile the stdlib.
|
|
||||||
RUN go build -a std
|
|
||||||
|
|
||||||
# Add the code to the image.
|
|
||||||
WORKDIR pty
|
|
||||||
ADD . .
|
|
||||||
|
|
||||||
# Build the lib.
|
|
||||||
RUN go build
|
|
|
@ -1,23 +0,0 @@
|
||||||
Copyright (c) 2011 Keith Rarick
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,107 +0,0 @@
|
||||||
# pty
|
|
||||||
|
|
||||||
Pty is a Go package for using unix pseudo-terminals.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
```sh
|
|
||||||
go get github.com/creack/pty
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
Note that those examples are for demonstration purpose only, to showcase how to use the library. They are not meant to be used in any kind of production environment.
|
|
||||||
|
|
||||||
### Command
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/creack/pty"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
c := exec.Command("grep", "--color=auto", "bar")
|
|
||||||
f, err := pty.Start(c)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
f.Write([]byte("foo\n"))
|
|
||||||
f.Write([]byte("bar\n"))
|
|
||||||
f.Write([]byte("baz\n"))
|
|
||||||
f.Write([]byte{4}) // EOT
|
|
||||||
}()
|
|
||||||
io.Copy(os.Stdout, f)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Shell
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/creack/pty"
|
|
||||||
"golang.org/x/term"
|
|
||||||
)
|
|
||||||
|
|
||||||
func test() error {
|
|
||||||
// Create arbitrary command.
|
|
||||||
c := exec.Command("bash")
|
|
||||||
|
|
||||||
// Start the command with a pty.
|
|
||||||
ptmx, err := pty.Start(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Make sure to close the pty at the end.
|
|
||||||
defer func() { _ = ptmx.Close() }() // Best effort.
|
|
||||||
|
|
||||||
// Handle pty size.
|
|
||||||
ch := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(ch, syscall.SIGWINCH)
|
|
||||||
go func() {
|
|
||||||
for range ch {
|
|
||||||
if err := pty.InheritSize(os.Stdin, ptmx); err != nil {
|
|
||||||
log.Printf("error resizing pty: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ch <- syscall.SIGWINCH // Initial resize.
|
|
||||||
defer func() { signal.Stop(ch); close(ch) }() // Cleanup signals when done.
|
|
||||||
|
|
||||||
// Set stdin in raw mode.
|
|
||||||
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer func() { _ = term.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
|
|
||||||
|
|
||||||
// Copy stdin to the pty and the pty to stdout.
|
|
||||||
// NOTE: The goroutine will keep reading until the next keystroke before returning.
|
|
||||||
go func() { _, _ = io.Copy(ptmx, os.Stdin) }()
|
|
||||||
_, _ = io.Copy(os.Stdout, ptmx)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := test(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,16 +0,0 @@
|
||||||
// Package pty provides functions for working with Unix terminals.
|
|
||||||
package pty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrUnsupported is returned if a function is not
|
|
||||||
// available on the current platform.
|
|
||||||
var ErrUnsupported = errors.New("unsupported")
|
|
||||||
|
|
||||||
// Open a pty and its corresponding tty.
|
|
||||||
func Open() (pty, tty *os.File, err error) {
|
|
||||||
return open()
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
module github.com/creack/pty
|
|
||||||
|
|
||||||
go 1.13
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
// +build !windows,!solaris
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
func ioctl(fd, cmd, ptr uintptr) error {
|
|
||||||
_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
|
|
||||||
if e != 0 {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
// +build darwin dragonfly freebsd netbsd openbsd
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
// from <sys/ioccom.h>
|
|
||||||
const (
|
|
||||||
_IOC_VOID uintptr = 0x20000000
|
|
||||||
_IOC_OUT uintptr = 0x40000000
|
|
||||||
_IOC_IN uintptr = 0x80000000
|
|
||||||
_IOC_IN_OUT uintptr = _IOC_OUT | _IOC_IN
|
|
||||||
_IOC_DIRMASK = _IOC_VOID | _IOC_OUT | _IOC_IN
|
|
||||||
|
|
||||||
_IOC_PARAM_SHIFT = 13
|
|
||||||
_IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
func _IOC_PARM_LEN(ioctl uintptr) uintptr {
|
|
||||||
return (ioctl >> 16) & _IOC_PARAM_MASK
|
|
||||||
}
|
|
||||||
|
|
||||||
func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
|
||||||
return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num
|
|
||||||
}
|
|
||||||
|
|
||||||
func _IO(group byte, ioctl_num uintptr) uintptr {
|
|
||||||
return _IOC(_IOC_VOID, group, ioctl_num, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
|
||||||
return _IOC(_IOC_OUT, group, ioctl_num, param_len)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
|
||||||
return _IOC(_IOC_IN, group, ioctl_num, param_len)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
|
||||||
return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len)
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package pty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// see /usr/include/sys/stropts.h
|
|
||||||
I_PUSH = uintptr((int32('S')<<8 | 002))
|
|
||||||
I_STR = uintptr((int32('S')<<8 | 010))
|
|
||||||
I_FIND = uintptr((int32('S')<<8 | 013))
|
|
||||||
// see /usr/include/sys/ptms.h
|
|
||||||
ISPTM = (int32('P') << 8) | 1
|
|
||||||
UNLKPT = (int32('P') << 8) | 2
|
|
||||||
PTSSTTY = (int32('P') << 8) | 3
|
|
||||||
ZONEPT = (int32('P') << 8) | 4
|
|
||||||
OWNERPT = (int32('P') << 8) | 5
|
|
||||||
)
|
|
||||||
|
|
||||||
type strioctl struct {
|
|
||||||
ic_cmd int32
|
|
||||||
ic_timout int32
|
|
||||||
ic_len int32
|
|
||||||
ic_dp unsafe.Pointer
|
|
||||||
}
|
|
||||||
|
|
||||||
func ioctl(fd, cmd, ptr uintptr) error {
|
|
||||||
return unix.IoctlSetInt(int(fd), uint(cmd), int(ptr))
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
GOOSARCH="${GOOS}_${GOARCH}"
|
|
||||||
case "$GOOSARCH" in
|
|
||||||
_* | *_ | _)
|
|
||||||
echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
GODEFS="go tool cgo -godefs"
|
|
||||||
|
|
||||||
$GODEFS types.go |gofmt > ztypes_$GOARCH.go
|
|
||||||
|
|
||||||
case $GOOS in
|
|
||||||
freebsd|dragonfly|netbsd|openbsd)
|
|
||||||
$GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go
|
|
||||||
;;
|
|
||||||
esac
|
|
|
@ -1,65 +0,0 @@
|
||||||
package pty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func open() (pty, tty *os.File, err error) {
|
|
||||||
pFD, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
p := os.NewFile(uintptr(pFD), "/dev/ptmx")
|
|
||||||
// In case of error after this point, make sure we close the ptmx fd.
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
_ = p.Close() // Best effort.
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
sname, err := ptsname(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := grantpt(p); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := unlockpt(p); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return p, t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptsname(f *os.File) (string, error) {
|
|
||||||
n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME))
|
|
||||||
|
|
||||||
err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, c := range n {
|
|
||||||
if c == 0 {
|
|
||||||
return string(n[:i]), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
|
|
||||||
}
|
|
||||||
|
|
||||||
func grantpt(f *os.File) error {
|
|
||||||
return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func unlockpt(f *os.File) error {
|
|
||||||
return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0)
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package pty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// same code as pty_darwin.go
|
|
||||||
func open() (pty, tty *os.File, err error) {
|
|
||||||
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// In case of error after this point, make sure we close the ptmx fd.
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
_ = p.Close() // Best effort.
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
sname, err := ptsname(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := grantpt(p); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := unlockpt(p); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := os.OpenFile(sname, os.O_RDWR, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return p, t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func grantpt(f *os.File) error {
|
|
||||||
_, err := isptmaster(f.Fd())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func unlockpt(f *os.File) error {
|
|
||||||
_, err := isptmaster(f.Fd())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func isptmaster(fd uintptr) (bool, error) {
|
|
||||||
err := ioctl(fd, syscall.TIOCISPTMASTER, 0)
|
|
||||||
return err == nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
emptyFiodgnameArg fiodgnameArg
|
|
||||||
ioctl_FIODNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
|
|
||||||
)
|
|
||||||
|
|
||||||
func ptsname(f *os.File) (string, error) {
|
|
||||||
name := make([]byte, _C_SPECNAMELEN)
|
|
||||||
fa := fiodgnameArg{Name: (*byte)(unsafe.Pointer(&name[0])), Len: _C_SPECNAMELEN, Pad_cgo_0: [4]byte{0, 0, 0, 0}}
|
|
||||||
|
|
||||||
err := ioctl(f.Fd(), ioctl_FIODNAME, uintptr(unsafe.Pointer(&fa)))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, c := range name {
|
|
||||||
if c == 0 {
|
|
||||||
s := "/dev/" + string(name[:i])
|
|
||||||
return strings.Replace(s, "ptm", "pts", -1), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
package pty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func posixOpenpt(oflag int) (fd int, err error) {
|
|
||||||
r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
|
|
||||||
fd = int(r0)
|
|
||||||
if e1 != 0 {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return fd, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func open() (pty, tty *os.File, err error) {
|
|
||||||
fd, err := posixOpenpt(syscall.O_RDWR | syscall.O_CLOEXEC)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
p := os.NewFile(uintptr(fd), "/dev/pts")
|
|
||||||
// In case of error after this point, make sure we close the pts fd.
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
_ = p.Close() // Best effort.
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
sname, err := ptsname(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := os.OpenFile("/dev/"+sname, os.O_RDWR, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return p, t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isptmaster(fd uintptr) (bool, error) {
|
|
||||||
err := ioctl(fd, syscall.TIOCPTMASTER, 0)
|
|
||||||
return err == nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
emptyFiodgnameArg fiodgnameArg
|
|
||||||
ioctlFIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
|
|
||||||
)
|
|
||||||
|
|
||||||
func ptsname(f *os.File) (string, error) {
|
|
||||||
master, err := isptmaster(f.Fd())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if !master {
|
|
||||||
return "", syscall.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
const n = _C_SPECNAMELEN + 1
|
|
||||||
var (
|
|
||||||
buf = make([]byte, n)
|
|
||||||
arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))}
|
|
||||||
)
|
|
||||||
if err := ioctl(f.Fd(), ioctlFIODGNAME, uintptr(unsafe.Pointer(&arg))); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, c := range buf {
|
|
||||||
if c == 0 {
|
|
||||||
return string(buf[:i]), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", errors.New("FIODGNAME string not NUL-terminated")
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package pty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func open() (pty, tty *os.File, err error) {
|
|
||||||
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// In case of error after this point, make sure we close the ptmx fd.
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
_ = p.Close() // Best effort.
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
sname, err := ptsname(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := unlockpt(p); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return p, t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptsname(f *os.File) (string, error) {
|
|
||||||
var n _C_uint
|
|
||||||
err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return "/dev/pts/" + strconv.Itoa(int(n)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unlockpt(f *os.File) error {
|
|
||||||
var u _C_int
|
|
||||||
// use TIOCSPTLCK with a pointer to zero to clear the lock
|
|
||||||
return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
package pty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func open() (pty, tty *os.File, err error) {
|
|
||||||
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// In case of error after this point, make sure we close the ptmx fd.
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
_ = p.Close() // Best effort.
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
sname, err := ptsname(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := grantpt(p); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// In NetBSD unlockpt() does nothing, so it isn't called here.
|
|
||||||
|
|
||||||
t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return p, t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptsname(f *os.File) (string, error) {
|
|
||||||
/*
|
|
||||||
* from ptsname(3): The ptsname() function is equivalent to:
|
|
||||||
* struct ptmget pm;
|
|
||||||
* ioctl(fd, TIOCPTSNAME, &pm) == -1 ? NULL : pm.sn;
|
|
||||||
*/
|
|
||||||
var ptm ptmget
|
|
||||||
if err := ioctl(f.Fd(), uintptr(ioctl_TIOCPTSNAME), uintptr(unsafe.Pointer(&ptm))); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
name := make([]byte, len(ptm.Sn))
|
|
||||||
for i, c := range ptm.Sn {
|
|
||||||
name[i] = byte(c)
|
|
||||||
if c == 0 {
|
|
||||||
return string(name[:i]), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", errors.New("TIOCPTSNAME string not NUL-terminated")
|
|
||||||
}
|
|
||||||
|
|
||||||
func grantpt(f *os.File) error {
|
|
||||||
/*
|
|
||||||
* from grantpt(3): Calling grantpt() is equivalent to:
|
|
||||||
* ioctl(fd, TIOCGRANTPT, 0);
|
|
||||||
*/
|
|
||||||
return ioctl(f.Fd(), uintptr(ioctl_TIOCGRANTPT), 0)
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package pty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func open() (pty, tty *os.File, err error) {
|
|
||||||
/*
|
|
||||||
* from ptm(4):
|
|
||||||
* The PTMGET command allocates a free pseudo terminal, changes its
|
|
||||||
* ownership to the caller, revokes the access privileges for all previous
|
|
||||||
* users, opens the file descriptors for the pty and tty devices and
|
|
||||||
* returns them to the caller in struct ptmget.
|
|
||||||
*/
|
|
||||||
|
|
||||||
p, err := os.OpenFile("/dev/ptm", os.O_RDWR|syscall.O_CLOEXEC, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer p.Close()
|
|
||||||
|
|
||||||
var ptm ptmget
|
|
||||||
if err := ioctl(p.Fd(), uintptr(ioctl_PTMGET), uintptr(unsafe.Pointer(&ptm))); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pty = os.NewFile(uintptr(ptm.Cfd), "/dev/ptm")
|
|
||||||
tty = os.NewFile(uintptr(ptm.Sfd), "/dev/ptm")
|
|
||||||
|
|
||||||
return pty, tty, nil
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
package pty
|
|
||||||
|
|
||||||
/* based on:
|
|
||||||
http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/pt.c
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
const NODEV = ^uint64(0)
|
|
||||||
|
|
||||||
func open() (pty, tty *os.File, err error) {
|
|
||||||
masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|unix.O_NOCTTY, 0)
|
|
||||||
//masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC|unix.O_NOCTTY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
p := os.NewFile(uintptr(masterfd), "/dev/ptmx")
|
|
||||||
|
|
||||||
sname, err := ptsname(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = grantpt(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = unlockpt(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
slavefd, err := syscall.Open(sname, os.O_RDWR|unix.O_NOCTTY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
t := os.NewFile(uintptr(slavefd), sname)
|
|
||||||
|
|
||||||
// pushing terminal driver STREAMS modules as per pts(7)
|
|
||||||
for _, mod := range []string{"ptem", "ldterm", "ttcompat"} {
|
|
||||||
err = streams_push(t, mod)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func minor(x uint64) uint64 {
|
|
||||||
return x & 0377
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptsdev(fd uintptr) uint64 {
|
|
||||||
istr := strioctl{ISPTM, 0, 0, nil}
|
|
||||||
err := ioctl(fd, I_STR, uintptr(unsafe.Pointer(&istr)))
|
|
||||||
if err != nil {
|
|
||||||
return NODEV
|
|
||||||
}
|
|
||||||
var status unix.Stat_t
|
|
||||||
err = unix.Fstat(int(fd), &status)
|
|
||||||
if err != nil {
|
|
||||||
return NODEV
|
|
||||||
}
|
|
||||||
return uint64(minor(status.Rdev))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptsname(f *os.File) (string, error) {
|
|
||||||
dev := ptsdev(f.Fd())
|
|
||||||
if dev == NODEV {
|
|
||||||
return "", errors.New("not a master pty")
|
|
||||||
}
|
|
||||||
fn := "/dev/pts/" + strconv.FormatInt(int64(dev), 10)
|
|
||||||
// access(2) creates the slave device (if the pty exists)
|
|
||||||
// F_OK == 0 (unistd.h)
|
|
||||||
err := unix.Access(fn, 0)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return fn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type pt_own struct {
|
|
||||||
pto_ruid int32
|
|
||||||
pto_rgid int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func grantpt(f *os.File) error {
|
|
||||||
if ptsdev(f.Fd()) == NODEV {
|
|
||||||
return errors.New("not a master pty")
|
|
||||||
}
|
|
||||||
var pto pt_own
|
|
||||||
pto.pto_ruid = int32(os.Getuid())
|
|
||||||
// XXX should first attempt to get gid of DEFAULT_TTY_GROUP="tty"
|
|
||||||
pto.pto_rgid = int32(os.Getgid())
|
|
||||||
var istr strioctl
|
|
||||||
istr.ic_cmd = OWNERPT
|
|
||||||
istr.ic_timout = 0
|
|
||||||
istr.ic_len = int32(unsafe.Sizeof(istr))
|
|
||||||
istr.ic_dp = unsafe.Pointer(&pto)
|
|
||||||
err := ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr)))
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("access denied")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unlockpt(f *os.File) error {
|
|
||||||
istr := strioctl{UNLKPT, 0, 0, nil}
|
|
||||||
return ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// push STREAMS modules if not already done so
|
|
||||||
func streams_push(f *os.File, mod string) error {
|
|
||||||
var err error
|
|
||||||
buf := []byte(mod)
|
|
||||||
// XXX I_FIND is not returning an error when the module
|
|
||||||
// is already pushed even though truss reports a return
|
|
||||||
// value of 1. A bug in the Go Solaris syscall interface?
|
|
||||||
// XXX without this we are at risk of the issue
|
|
||||||
// https://www.illumos.org/issues/9042
|
|
||||||
// but since we are not using libc or XPG4.2, we should not be
|
|
||||||
// double-pushing modules
|
|
||||||
|
|
||||||
err = ioctl(f.Fd(), I_FIND, uintptr(unsafe.Pointer(&buf[0])))
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err = ioctl(f.Fd(), I_PUSH, uintptr(unsafe.Pointer(&buf[0])))
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
// +build !linux,!darwin,!freebsd,!dragonfly,!netbsd,!openbsd,!solaris
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func open() (pty, tty *os.File, err error) {
|
|
||||||
return nil, nil, ErrUnsupported
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Start assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
|
|
||||||
// and c.Stderr, calls c.Start, and returns the File of the tty's
|
|
||||||
// corresponding pty.
|
|
||||||
//
|
|
||||||
// Starts the process in a new session and sets the controlling terminal.
|
|
||||||
func Start(c *exec.Cmd) (pty *os.File, err error) {
|
|
||||||
return StartWithSize(c, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartWithSize assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
|
|
||||||
// and c.Stderr, calls c.Start, and returns the File of the tty's
|
|
||||||
// corresponding pty.
|
|
||||||
//
|
|
||||||
// This will resize the pty to the specified size before starting the command.
|
|
||||||
// Starts the process in a new session and sets the controlling terminal.
|
|
||||||
func StartWithSize(c *exec.Cmd, sz *Winsize) (pty *os.File, err error) {
|
|
||||||
if c.SysProcAttr == nil {
|
|
||||||
c.SysProcAttr = &syscall.SysProcAttr{}
|
|
||||||
}
|
|
||||||
c.SysProcAttr.Setsid = true
|
|
||||||
c.SysProcAttr.Setctty = true
|
|
||||||
return StartWithAttrs(c, sz, c.SysProcAttr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartWithAttrs assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
|
|
||||||
// and c.Stderr, calls c.Start, and returns the File of the tty's
|
|
||||||
// corresponding pty.
|
|
||||||
//
|
|
||||||
// This will resize the pty to the specified size before starting the command if a size is provided.
|
|
||||||
// The `attrs` parameter overrides the one set in c.SysProcAttr.
|
|
||||||
//
|
|
||||||
// This should generally not be needed. Used in some edge cases where it is needed to create a pty
|
|
||||||
// without a controlling terminal.
|
|
||||||
func StartWithAttrs(c *exec.Cmd, sz *Winsize, attrs *syscall.SysProcAttr) (pty *os.File, err error) {
|
|
||||||
pty, tty, err := Open()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer tty.Close()
|
|
||||||
|
|
||||||
if sz != nil {
|
|
||||||
if err := Setsize(pty, sz); err != nil {
|
|
||||||
pty.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.Stdout == nil {
|
|
||||||
c.Stdout = tty
|
|
||||||
}
|
|
||||||
if c.Stderr == nil {
|
|
||||||
c.Stderr = tty
|
|
||||||
}
|
|
||||||
if c.Stdin == nil {
|
|
||||||
c.Stdin = tty
|
|
||||||
}
|
|
||||||
|
|
||||||
c.SysProcAttr = attrs
|
|
||||||
|
|
||||||
if err := c.Start(); err != nil {
|
|
||||||
_ = pty.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return pty, err
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
#!/usr/bin/env sh
|
|
||||||
|
|
||||||
# Test script checking that all expected os/arch compile properly.
|
|
||||||
# Does not actually test the logic, just the compilation so we make sure we don't break code depending on the lib.
|
|
||||||
|
|
||||||
echo2() {
|
|
||||||
echo $@ >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
trap end 0
|
|
||||||
end() {
|
|
||||||
[ "$?" = 0 ] && echo2 "Pass." || (echo2 "Fail."; exit 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
cross() {
|
|
||||||
os=$1
|
|
||||||
shift
|
|
||||||
echo2 "Build for $os."
|
|
||||||
for arch in $@; do
|
|
||||||
echo2 " - $os/$arch"
|
|
||||||
GOOS=$os GOARCH=$arch go build
|
|
||||||
done
|
|
||||||
echo2
|
|
||||||
}
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
cross linux amd64 386 arm arm64 ppc64 ppc64le s390x mips mipsle mips64 mips64le
|
|
||||||
cross darwin amd64 arm64
|
|
||||||
cross freebsd amd64 386 arm arm64
|
|
||||||
cross netbsd amd64 386 arm arm64
|
|
||||||
cross openbsd amd64 386 arm arm64
|
|
||||||
cross dragonfly amd64
|
|
||||||
cross solaris amd64
|
|
||||||
|
|
||||||
# Not expected to work but should still compile.
|
|
||||||
cross windows amd64 386 arm
|
|
||||||
|
|
||||||
# TODO: Fix compilation error on openbsd/arm.
|
|
||||||
# TODO: Merge the solaris PR.
|
|
||||||
|
|
||||||
# Some os/arch require a different compiler. Run in docker.
|
|
||||||
if ! hash docker; then
|
|
||||||
# If docker is not present, stop here.
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo2 "Build for linux."
|
|
||||||
echo2 " - linux/riscv"
|
|
||||||
docker build -t creack-pty-test -f Dockerfile.riscv .
|
|
||||||
|
|
||||||
# Golang dropped support for darwin 32bits since go1.15. Make sure the lib still compile with go1.14 on those archs.
|
|
||||||
echo2 "Build for darwin (32bits)."
|
|
||||||
echo2 " - darwin/386"
|
|
||||||
docker build -t creack-pty-test -f Dockerfile.golang --build-arg=GOVERSION=1.14 --build-arg=GOOS=darwin --build-arg=GOARCH=386 .
|
|
||||||
echo2 " - darwin/arm"
|
|
||||||
docker build -t creack-pty-test -f Dockerfile.golang --build-arg=GOVERSION=1.14 --build-arg=GOOS=darwin --build-arg=GOARCH=arm .
|
|
||||||
|
|
||||||
# Run a single test for an old go version. Would be best with go1.0, but not available on Dockerhub.
|
|
||||||
# Using 1.6 as it is the base version for the RISCV compiler.
|
|
||||||
# Would also be better to run all the tests, not just one, need to refactor this file to allow for specifc archs per version.
|
|
||||||
echo2 "Build for linux - go1.6."
|
|
||||||
echo2 " - linux/amd64"
|
|
||||||
docker build -t creack-pty-test -f Dockerfile.golang --build-arg=GOVERSION=1.6 --build-arg=GOOS=linux --build-arg=GOARCH=amd64 .
|
|
|
@ -1,64 +0,0 @@
|
||||||
// +build !windows,!solaris
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InheritSize applies the terminal size of pty to tty. This should be run
|
|
||||||
// in a signal handler for syscall.SIGWINCH to automatically resize the tty when
|
|
||||||
// the pty receives a window size change notification.
|
|
||||||
func InheritSize(pty, tty *os.File) error {
|
|
||||||
size, err := GetsizeFull(pty)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = Setsize(tty, size)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setsize resizes t to s.
|
|
||||||
func Setsize(t *os.File, ws *Winsize) error {
|
|
||||||
return windowRectCall(ws, t.Fd(), syscall.TIOCSWINSZ)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetsizeFull returns the full terminal size description.
|
|
||||||
func GetsizeFull(t *os.File) (size *Winsize, err error) {
|
|
||||||
var ws Winsize
|
|
||||||
err = windowRectCall(&ws, t.Fd(), syscall.TIOCGWINSZ)
|
|
||||||
return &ws, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getsize returns the number of rows (lines) and cols (positions
|
|
||||||
// in each line) in terminal t.
|
|
||||||
func Getsize(t *os.File) (rows, cols int, err error) {
|
|
||||||
ws, err := GetsizeFull(t)
|
|
||||||
return int(ws.Rows), int(ws.Cols), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Winsize describes the terminal size.
|
|
||||||
type Winsize struct {
|
|
||||||
Rows uint16 // ws_row: Number of rows (in cells)
|
|
||||||
Cols uint16 // ws_col: Number of columns (in cells)
|
|
||||||
X uint16 // ws_xpixel: Width in pixels
|
|
||||||
Y uint16 // ws_ypixel: Height in pixels
|
|
||||||
}
|
|
||||||
|
|
||||||
func windowRectCall(ws *Winsize, fd, a2 uintptr) error {
|
|
||||||
_, _, errno := syscall.Syscall(
|
|
||||||
syscall.SYS_IOCTL,
|
|
||||||
fd,
|
|
||||||
a2,
|
|
||||||
uintptr(unsafe.Pointer(ws)),
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
return syscall.Errno(errno)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
//
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
TIOCGWINSZ = 21608 // 'T' << 8 | 104
|
|
||||||
TIOCSWINSZ = 21607 // 'T' << 8 | 103
|
|
||||||
)
|
|
||||||
|
|
||||||
// Winsize describes the terminal size.
|
|
||||||
type Winsize struct {
|
|
||||||
Rows uint16 // ws_row: Number of rows (in cells)
|
|
||||||
Cols uint16 // ws_col: Number of columns (in cells)
|
|
||||||
X uint16 // ws_xpixel: Width in pixels
|
|
||||||
Y uint16 // ws_ypixel: Height in pixels
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetsizeFull returns the full terminal size description.
|
|
||||||
func GetsizeFull(t *os.File) (size *Winsize, err error) {
|
|
||||||
var wsz *unix.Winsize
|
|
||||||
wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return &Winsize{wsz.Row, wsz.Col, wsz.Xpixel, wsz.Ypixel}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get Windows Size
|
|
||||||
func Getsize(t *os.File) (rows, cols int, err error) {
|
|
||||||
var wsz *unix.Winsize
|
|
||||||
wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 80, 25, err
|
|
||||||
} else {
|
|
||||||
return int(wsz.Row), int(wsz.Col), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setsize resizes t to s.
|
|
||||||
func Setsize(t *os.File, ws *Winsize) error {
|
|
||||||
wsz := unix.Winsize{ws.Rows, ws.Cols, ws.X, ws.Y}
|
|
||||||
return unix.IoctlSetWinsize(int(t.Fd()), TIOCSWINSZ, &wsz)
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
// Created by cgo -godefs - DO NOT EDIT
|
|
||||||
// cgo -godefs types.go
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
type (
|
|
||||||
_C_int int32
|
|
||||||
_C_uint uint32
|
|
||||||
)
|
|
|
@ -1,9 +0,0 @@
|
||||||
// Created by cgo -godefs - DO NOT EDIT
|
|
||||||
// cgo -godefs types.go
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
type (
|
|
||||||
_C_int int32
|
|
||||||
_C_uint uint32
|
|
||||||
)
|
|
|
@ -1,9 +0,0 @@
|
||||||
// Created by cgo -godefs - DO NOT EDIT
|
|
||||||
// cgo -godefs types.go
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
type (
|
|
||||||
_C_int int32
|
|
||||||
_C_uint uint32
|
|
||||||
)
|
|
|
@ -1,11 +0,0 @@
|
||||||
// Created by cgo -godefs - DO NOT EDIT
|
|
||||||
// cgo -godefs types.go
|
|
||||||
|
|
||||||
// +build arm64
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
type (
|
|
||||||
_C_int int32
|
|
||||||
_C_uint uint32
|
|
||||||
)
|
|
|
@ -1,14 +0,0 @@
|
||||||
// Created by cgo -godefs - DO NOT EDIT
|
|
||||||
// cgo -godefs types_dragonfly.go
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
const (
|
|
||||||
_C_SPECNAMELEN = 0x3f
|
|
||||||
)
|
|
||||||
|
|
||||||
type fiodgnameArg struct {
|
|
||||||
Name *byte
|
|
||||||
Len uint32
|
|
||||||
Pad_cgo_0 [4]byte
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
// Created by cgo -godefs - DO NOT EDIT
|
|
||||||
// cgo -godefs types_freebsd.go
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
const (
|
|
||||||
_C_SPECNAMELEN = 0x3f
|
|
||||||
)
|
|
||||||
|
|
||||||
type fiodgnameArg struct {
|
|
||||||
Len int32
|
|
||||||
Buf *byte
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
// Created by cgo -godefs - DO NOT EDIT
|
|
||||||
// cgo -godefs types_freebsd.go
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
const (
|
|
||||||
_C_SPECNAMELEN = 0x3f
|
|
||||||
)
|
|
||||||
|
|
||||||
type fiodgnameArg struct {
|
|
||||||
Len int32
|
|
||||||
Pad_cgo_0 [4]byte
|
|
||||||
Buf *byte
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
// Created by cgo -godefs - DO NOT EDIT
|
|
||||||
// cgo -godefs types_freebsd.go
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
const (
|
|
||||||
_C_SPECNAMELEN = 0x3f
|
|
||||||
)
|
|
||||||
|
|
||||||
type fiodgnameArg struct {
|
|
||||||
Len int32
|
|
||||||
Buf *byte
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
|
||||||
// cgo -godefs types_freebsd.go
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
const (
|
|
||||||
_C_SPECNAMELEN = 0xff
|
|
||||||
)
|
|
||||||
|
|
||||||
type fiodgnameArg struct {
|
|
||||||
Len int32
|
|
||||||
Buf *byte
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
// Created by cgo -godefs - DO NOT EDIT
|
|
||||||
// cgo -godefs types.go
|
|
||||||
|
|
||||||
// +build linux
|
|
||||||
// +build loongarch32 loongarch64
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
type (
|
|
||||||
_C_int int32
|
|
||||||
_C_uint uint32
|
|
||||||
)
|
|
|
@ -1,12 +0,0 @@
|
||||||
// Created by cgo -godefs - DO NOT EDIT
|
|
||||||
// cgo -godefs types.go
|
|
||||||
|
|
||||||
// +build linux
|
|
||||||
// +build mips mipsle mips64 mips64le
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
type (
|
|
||||||
_C_int int32
|
|
||||||
_C_uint uint32
|
|
||||||
)
|
|
|
@ -1,14 +0,0 @@
|
||||||
// +build netbsd
|
|
||||||
// +build 386 amd64 arm arm64
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
type ptmget struct {
|
|
||||||
Cfd int32
|
|
||||||
Sfd int32
|
|
||||||
Cn [1024]int8
|
|
||||||
Sn [1024]int8
|
|
||||||
}
|
|
||||||
|
|
||||||
var ioctl_TIOCPTSNAME = 0x48087448
|
|
||||||
var ioctl_TIOCGRANTPT = 0x20007447
|
|
|
@ -1,13 +0,0 @@
|
||||||
// +build openbsd
|
|
||||||
// +build 386 amd64 arm arm64
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
type ptmget struct {
|
|
||||||
Cfd int32
|
|
||||||
Sfd int32
|
|
||||||
Cn [16]int8
|
|
||||||
Sn [16]int8
|
|
||||||
}
|
|
||||||
|
|
||||||
var ioctl_PTMGET = 0x40287401
|
|
|
@ -1,11 +0,0 @@
|
||||||
// +build ppc64
|
|
||||||
|
|
||||||
// Created by cgo -godefs - DO NOT EDIT
|
|
||||||
// cgo -godefs types.go
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
type (
|
|
||||||
_C_int int32
|
|
||||||
_C_uint uint32
|
|
||||||
)
|
|
|
@ -1,11 +0,0 @@
|
||||||
// +build ppc64le
|
|
||||||
|
|
||||||
// Created by cgo -godefs - DO NOT EDIT
|
|
||||||
// cgo -godefs types.go
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
type (
|
|
||||||
_C_int int32
|
|
||||||
_C_uint uint32
|
|
||||||
)
|
|
|
@ -1,11 +0,0 @@
|
||||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
|
||||||
// cgo -godefs types.go
|
|
||||||
|
|
||||||
// +build riscv riscv64
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
type (
|
|
||||||
_C_int int32
|
|
||||||
_C_uint uint32
|
|
||||||
)
|
|
|
@ -1,11 +0,0 @@
|
||||||
// +build s390x
|
|
||||||
|
|
||||||
// Created by cgo -godefs - DO NOT EDIT
|
|
||||||
// cgo -godefs types.go
|
|
||||||
|
|
||||||
package pty
|
|
||||||
|
|
||||||
type (
|
|
||||||
_C_int int32
|
|
||||||
_C_uint uint32
|
|
||||||
)
|
|
|
@ -1,25 +0,0 @@
|
||||||
# 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/
|
|
||||||
|
|
||||||
# GoLand
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# Executable Binary
|
|
||||||
clipboard
|
|
||||||
|
|
||||||
# Go test coverage
|
|
||||||
c.out
|
|
||||||
coverage.html
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2020 Tsuji Daishiro
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,33 +0,0 @@
|
||||||
.PHONY: all build test lint clean deps devel-deps
|
|
||||||
|
|
||||||
BIN := clipboard
|
|
||||||
BUILD_LDFLAGS := "-s -w"
|
|
||||||
GOBIN ?= $(shell go env GOPATH)/bin
|
|
||||||
export GO111MODULE=on
|
|
||||||
|
|
||||||
all: clean build
|
|
||||||
|
|
||||||
deps:
|
|
||||||
go mod tidy
|
|
||||||
|
|
||||||
devel-deps: deps
|
|
||||||
GO111MODULE=off go get -u \
|
|
||||||
golang.org/x/lint/golint
|
|
||||||
|
|
||||||
build: clean
|
|
||||||
go build -ldflags=$(BUILD_LDFLAGS) -o $(BIN)
|
|
||||||
|
|
||||||
test: deps
|
|
||||||
go test -v -race -count=1 ./...
|
|
||||||
|
|
||||||
test-cover: deps
|
|
||||||
go test -v -race -count=1 ./... -cover -coverprofile=c.out
|
|
||||||
go tool cover -html=c.out -o coverage.html
|
|
||||||
|
|
||||||
lint: devel-deps
|
|
||||||
go vet ./...
|
|
||||||
$(GOBIN)/golint -set_exit_status ./...
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf $(BIN)
|
|
||||||
go clean
|
|
|
@ -1,37 +0,0 @@
|
||||||
# clipboard
|
|
||||||
|
|
||||||
[![Actions Status](https://github.com/d-tsuji/clipboard/workflows/test/badge.svg)](https://github.com/d-tsuji/clipboard/actions)
|
|
||||||
[![Doc](https://img.shields.io/badge/doc-reference-blue.svg)](https://pkg.go.dev/github.com/d-tsuji/clipboard)
|
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/d-tsuji/clipboard)](https://goreportcard.com/report/github.com/d-tsuji/clipboard)
|
|
||||||
|
|
||||||
This is a multi-platform clipboard library in Go.
|
|
||||||
|
|
||||||
## Abstract
|
|
||||||
|
|
||||||
- This is clipboard library in Go, which runs on multiple platforms.
|
|
||||||
- External clipboard package is not required.
|
|
||||||
|
|
||||||
## Supported Platforms
|
|
||||||
|
|
||||||
- Windows
|
|
||||||
- macOS
|
|
||||||
- Linux, Unix (X11)
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```
|
|
||||||
go get github.com/d-tsuji/clipboard
|
|
||||||
```
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
```go
|
|
||||||
package clipboard
|
|
||||||
|
|
||||||
// Get returns the current text data of the clipboard.
|
|
||||||
func Get() (string, error)
|
|
||||||
|
|
||||||
// Set sets the current text data of the clipboard.
|
|
||||||
func Set(text string) error
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package clipboard
|
|
||||||
|
|
||||||
// Get returns the current text data of the clipboard.
|
|
||||||
func Get() (string, error) {
|
|
||||||
return get()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sets the current text data of the clipboard.
|
|
||||||
func Set(text string) error {
|
|
||||||
return set(text)
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
// +build darwin
|
|
||||||
|
|
||||||
package clipboard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.wow.st/gmp/clip"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func set(text string) error {
|
|
||||||
ok := clip.Set(text)
|
|
||||||
if !ok {
|
|
||||||
return xerrors.New("nothing to set string")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func get() (string, error) {
|
|
||||||
return clip.Get(), nil
|
|
||||||
}
|
|
|
@ -1,230 +0,0 @@
|
||||||
// The MIT License (MIT)
|
|
||||||
// Copyright (c) 2016 Alessandro Arzilli
|
|
||||||
// https://github.com/aarzilli/nucular/blob/master/LICENSE
|
|
||||||
|
|
||||||
// +build freebsd linux netbsd openbsd solaris dragonfly
|
|
||||||
|
|
||||||
package clipboard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/BurntSushi/xgb"
|
|
||||||
"github.com/BurntSushi/xgb/xproto"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const debugClipboardRequests = false
|
|
||||||
|
|
||||||
var (
|
|
||||||
x *xgb.Conn
|
|
||||||
win xproto.Window
|
|
||||||
clipboardText string
|
|
||||||
selnotify chan bool
|
|
||||||
|
|
||||||
clipboardAtom, primaryAtom, textAtom, targetsAtom, atomAtom xproto.Atom
|
|
||||||
targetAtoms []xproto.Atom
|
|
||||||
clipboardAtomCache = map[xproto.Atom]string{}
|
|
||||||
|
|
||||||
doneCh = make(chan interface{}, 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
func start() error {
|
|
||||||
var err error
|
|
||||||
xServer := os.Getenv("DISPLAY")
|
|
||||||
if xServer == "" {
|
|
||||||
return xerrors.New("could not identify xserver")
|
|
||||||
}
|
|
||||||
x, err = xgb.NewConnDisplay(xServer)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("%w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
selnotify = make(chan bool, 1)
|
|
||||||
|
|
||||||
win, err = xproto.NewWindowId(x)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("%w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
setup := xproto.Setup(x)
|
|
||||||
s := setup.DefaultScreen(x)
|
|
||||||
err = xproto.CreateWindowChecked(x, s.RootDepth, win, s.Root, 100, 100, 1, 1, 0, xproto.WindowClassInputOutput, s.RootVisual, 0, []uint32{}).Check()
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("%w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
clipboardAtom = internAtom(x, "CLIPBOARD")
|
|
||||||
primaryAtom = internAtom(x, "PRIMARY")
|
|
||||||
textAtom = internAtom(x, "UTF8_STRING")
|
|
||||||
targetsAtom = internAtom(x, "TARGETS")
|
|
||||||
atomAtom = internAtom(x, "ATOM")
|
|
||||||
|
|
||||||
targetAtoms = []xproto.Atom{targetsAtom, textAtom}
|
|
||||||
|
|
||||||
go eventLoop()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func set(text string) error {
|
|
||||||
if err := start(); err != nil {
|
|
||||||
return xerrors.Errorf("init clipboard: %w", err)
|
|
||||||
}
|
|
||||||
clipboardText = text
|
|
||||||
ssoc := xproto.SetSelectionOwnerChecked(x, win, clipboardAtom, xproto.TimeCurrentTime)
|
|
||||||
if err := ssoc.Check(); err != nil {
|
|
||||||
return xerrors.Errorf("setting clipboard: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func get() (string, error) {
|
|
||||||
if err := start(); err != nil {
|
|
||||||
return "", xerrors.Errorf("init clipboard: %w", err)
|
|
||||||
}
|
|
||||||
return getSelection(clipboardAtom)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSelection(selAtom xproto.Atom) (string, error) {
|
|
||||||
csc := xproto.ConvertSelectionChecked(x, win, selAtom, textAtom, selAtom, xproto.TimeCurrentTime)
|
|
||||||
err := csc.Check()
|
|
||||||
if err != nil {
|
|
||||||
return "", xerrors.Errorf("convert selection check: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case r := <-selnotify:
|
|
||||||
if !r {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
gpc := xproto.GetProperty(x, true, win, selAtom, textAtom, 0, 5*1024*1024)
|
|
||||||
gpr, err := gpc.Reply()
|
|
||||||
if err != nil {
|
|
||||||
return "", xerrors.Errorf("grp reply: %w", err)
|
|
||||||
}
|
|
||||||
if gpr.BytesAfter != 0 {
|
|
||||||
return "", xerrors.New("clipboard too large")
|
|
||||||
}
|
|
||||||
return string(gpr.Value[:gpr.ValueLen]), nil
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
return "", xerrors.New("clipboard retrieval failed, timeout")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pollForEvent(X *xgb.Conn, events chan<- xgb.Event) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-doneCh:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
ev, err := X.PollForEvent()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("wait for event:", err)
|
|
||||||
}
|
|
||||||
events <- ev
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func eventLoop() {
|
|
||||||
eventCh := make(chan xgb.Event, 1)
|
|
||||||
go pollForEvent(x, eventCh)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event := <-eventCh:
|
|
||||||
switch e := event.(type) {
|
|
||||||
case xproto.SelectionRequestEvent:
|
|
||||||
if debugClipboardRequests {
|
|
||||||
tgtname := lookupAtom(e.Target)
|
|
||||||
propname := lookupAtom(e.Property)
|
|
||||||
fmt.Println("SelectionRequest", e, textAtom, tgtname, propname, "isPrimary:", e.Selection == primaryAtom, "isClipboard:", e.Selection == clipboardAtom)
|
|
||||||
}
|
|
||||||
t := clipboardText
|
|
||||||
|
|
||||||
switch e.Target {
|
|
||||||
case textAtom:
|
|
||||||
if debugClipboardRequests {
|
|
||||||
fmt.Println("Sending as text")
|
|
||||||
}
|
|
||||||
cpc := xproto.ChangePropertyChecked(x, xproto.PropModeReplace, e.Requestor, e.Property, textAtom, 8, uint32(len(t)), []byte(t))
|
|
||||||
err := cpc.Check()
|
|
||||||
if err == nil {
|
|
||||||
sendSelectionNotify(e)
|
|
||||||
} else {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
case targetsAtom:
|
|
||||||
if debugClipboardRequests {
|
|
||||||
fmt.Println("Sending targets")
|
|
||||||
}
|
|
||||||
buf := make([]byte, len(targetAtoms)*4)
|
|
||||||
for i, atom := range targetAtoms {
|
|
||||||
xgb.Put32(buf[i*4:], uint32(atom))
|
|
||||||
}
|
|
||||||
|
|
||||||
err := xproto.ChangePropertyChecked(x, xproto.PropModeReplace, e.Requestor, e.Property, atomAtom, 32, uint32(len(targetAtoms)), buf).Check()
|
|
||||||
if err == nil {
|
|
||||||
sendSelectionNotify(e)
|
|
||||||
} else {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
if debugClipboardRequests {
|
|
||||||
fmt.Println("Skipping")
|
|
||||||
}
|
|
||||||
e.Property = 0
|
|
||||||
sendSelectionNotify(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
case xproto.SelectionNotifyEvent:
|
|
||||||
selnotify <- (e.Property == clipboardAtom) || (e.Property == primaryAtom)
|
|
||||||
}
|
|
||||||
case <-doneCh:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupAtom(at xproto.Atom) string {
|
|
||||||
if s, ok := clipboardAtomCache[at]; ok {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
reply, err := xproto.GetAtomName(x, at).Reply()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're here, it means we didn't have ths ATOM id cached. So cache it.
|
|
||||||
atomName := string(reply.Name)
|
|
||||||
clipboardAtomCache[at] = atomName
|
|
||||||
return atomName
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendSelectionNotify(e xproto.SelectionRequestEvent) {
|
|
||||||
sn := xproto.SelectionNotifyEvent{
|
|
||||||
Time: e.Time,
|
|
||||||
Requestor: e.Requestor,
|
|
||||||
Selection: e.Selection,
|
|
||||||
Target: e.Target,
|
|
||||||
Property: e.Property}
|
|
||||||
sec := xproto.SendEventChecked(x, false, e.Requestor, 0, string(sn.Bytes()))
|
|
||||||
err := sec.Check()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func internAtom(conn *xgb.Conn, n string) xproto.Atom {
|
|
||||||
iac := xproto.InternAtom(conn, true, uint16(len(n)), n)
|
|
||||||
iar, err := iac.Reply()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return iar.Atom
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package clipboard
|
|
||||||
|
|
||||||
import "github.com/lxn/walk"
|
|
||||||
|
|
||||||
func get() (string, error) {
|
|
||||||
c := walk.Clipboard()
|
|
||||||
return c.Text()
|
|
||||||
}
|
|
||||||
|
|
||||||
func set(text string) error {
|
|
||||||
c := walk.Clipboard()
|
|
||||||
return c.SetText(text)
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
module github.com/d-tsuji/clipboard
|
|
||||||
|
|
||||||
go 1.14
|
|
||||||
|
|
||||||
require (
|
|
||||||
git.wow.st/gmp/clip v0.0.0-20191001134149-1458ba6a7cf5
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20200324125942-20f126ea2843
|
|
||||||
github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1
|
|
||||||
github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4 // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f // indirect
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
|
|
||||||
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
|
|
||||||
)
|
|
|
@ -1,15 +0,0 @@
|
||||||
git.wow.st/gmp/clip v0.0.0-20191001134149-1458ba6a7cf5 h1:OKeTjZST+/TKvtdA258NXJH+/gIx/xwyZxKrAezNFvk=
|
|
||||||
git.wow.st/gmp/clip v0.0.0-20191001134149-1458ba6a7cf5/go.mod h1:NLdpaBoMQNFqncwP8OVRNWUDw1Kt9XWm3snfT7cXu24=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20200324125942-20f126ea2843 h1:3iF31c7rp7nGZVDv7YQ+VxOgpipVfPKotLXykjZmwM8=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20200324125942-20f126ea2843/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1 h1:/QwQcwWVOQXcoNuV9tHx30gQ3q7jCE/rKcGjwzsa5tg=
|
|
||||||
github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
|
||||||
github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4 h1:5BmtGkQbch91lglMHQ9JIDGiYCL3kBRBA0ItZTvOcEI=
|
|
||||||
github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA=
|
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f h1:mOhmO9WsBaJCNmaZHPtHs9wOcdqdKCjF6OPJlmDM3KI=
|
|
||||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc=
|
|
||||||
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
|
|
|
@ -1,15 +0,0 @@
|
||||||
ISC License
|
|
||||||
|
|
||||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
@ -1,145 +0,0 @@
|
||||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
|
||||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
|
||||||
// tag is deprecated and thus should not be used.
|
|
||||||
// Go versions prior to 1.4 are disabled because they use a different layout
|
|
||||||
// for interfaces which make the implementation of unsafeReflectValue more complex.
|
|
||||||
// +build !js,!appengine,!safe,!disableunsafe,go1.4
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
|
||||||
// not access to the unsafe package is available.
|
|
||||||
UnsafeDisabled = false
|
|
||||||
|
|
||||||
// ptrSize is the size of a pointer on the current arch.
|
|
||||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
|
||||||
)
|
|
||||||
|
|
||||||
type flag uintptr
|
|
||||||
|
|
||||||
var (
|
|
||||||
// flagRO indicates whether the value field of a reflect.Value
|
|
||||||
// is read-only.
|
|
||||||
flagRO flag
|
|
||||||
|
|
||||||
// flagAddr indicates whether the address of the reflect.Value's
|
|
||||||
// value may be taken.
|
|
||||||
flagAddr flag
|
|
||||||
)
|
|
||||||
|
|
||||||
// flagKindMask holds the bits that make up the kind
|
|
||||||
// part of the flags field. In all the supported versions,
|
|
||||||
// it is in the lower 5 bits.
|
|
||||||
const flagKindMask = flag(0x1f)
|
|
||||||
|
|
||||||
// Different versions of Go have used different
|
|
||||||
// bit layouts for the flags type. This table
|
|
||||||
// records the known combinations.
|
|
||||||
var okFlags = []struct {
|
|
||||||
ro, addr flag
|
|
||||||
}{{
|
|
||||||
// From Go 1.4 to 1.5
|
|
||||||
ro: 1 << 5,
|
|
||||||
addr: 1 << 7,
|
|
||||||
}, {
|
|
||||||
// Up to Go tip.
|
|
||||||
ro: 1<<5 | 1<<6,
|
|
||||||
addr: 1 << 8,
|
|
||||||
}}
|
|
||||||
|
|
||||||
var flagValOffset = func() uintptr {
|
|
||||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
|
||||||
if !ok {
|
|
||||||
panic("reflect.Value has no flag field")
|
|
||||||
}
|
|
||||||
return field.Offset
|
|
||||||
}()
|
|
||||||
|
|
||||||
// flagField returns a pointer to the flag field of a reflect.Value.
|
|
||||||
func flagField(v *reflect.Value) *flag {
|
|
||||||
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
|
||||||
// the typical safety restrictions preventing access to unaddressable and
|
|
||||||
// unexported data. It works by digging the raw pointer to the underlying
|
|
||||||
// value out of the protected value and generating a new unprotected (unsafe)
|
|
||||||
// reflect.Value to it.
|
|
||||||
//
|
|
||||||
// This allows us to check for implementations of the Stringer and error
|
|
||||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
|
||||||
// inaccessible values such as unexported struct fields.
|
|
||||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
|
||||||
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
flagFieldPtr := flagField(&v)
|
|
||||||
*flagFieldPtr &^= flagRO
|
|
||||||
*flagFieldPtr |= flagAddr
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanity checks against future reflect package changes
|
|
||||||
// to the type or semantics of the Value.flag field.
|
|
||||||
func init() {
|
|
||||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
|
||||||
if !ok {
|
|
||||||
panic("reflect.Value has no flag field")
|
|
||||||
}
|
|
||||||
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
|
|
||||||
panic("reflect.Value flag field has changed kind")
|
|
||||||
}
|
|
||||||
type t0 int
|
|
||||||
var t struct {
|
|
||||||
A t0
|
|
||||||
// t0 will have flagEmbedRO set.
|
|
||||||
t0
|
|
||||||
// a will have flagStickyRO set
|
|
||||||
a t0
|
|
||||||
}
|
|
||||||
vA := reflect.ValueOf(t).FieldByName("A")
|
|
||||||
va := reflect.ValueOf(t).FieldByName("a")
|
|
||||||
vt0 := reflect.ValueOf(t).FieldByName("t0")
|
|
||||||
|
|
||||||
// Infer flagRO from the difference between the flags
|
|
||||||
// for the (otherwise identical) fields in t.
|
|
||||||
flagPublic := *flagField(&vA)
|
|
||||||
flagWithRO := *flagField(&va) | *flagField(&vt0)
|
|
||||||
flagRO = flagPublic ^ flagWithRO
|
|
||||||
|
|
||||||
// Infer flagAddr from the difference between a value
|
|
||||||
// taken from a pointer and not.
|
|
||||||
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
|
|
||||||
flagNoPtr := *flagField(&vA)
|
|
||||||
flagPtr := *flagField(&vPtrA)
|
|
||||||
flagAddr = flagNoPtr ^ flagPtr
|
|
||||||
|
|
||||||
// Check that the inferred flags tally with one of the known versions.
|
|
||||||
for _, f := range okFlags {
|
|
||||||
if flagRO == f.ro && flagAddr == f.addr {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("reflect.Value read-only flag has changed semantics")
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
|
||||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
|
||||||
// tag is deprecated and thus should not be used.
|
|
||||||
// +build js appengine safe disableunsafe !go1.4
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import "reflect"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
|
||||||
// not access to the unsafe package is available.
|
|
||||||
UnsafeDisabled = true
|
|
||||||
)
|
|
||||||
|
|
||||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
|
||||||
// that bypasses the typical safety restrictions preventing access to
|
|
||||||
// unaddressable and unexported data. However, doing this relies on access to
|
|
||||||
// the unsafe package. This is a stub version which simply returns the passed
|
|
||||||
// reflect.Value when the unsafe package is not available.
|
|
||||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
|
||||||
return v
|
|
||||||
}
|
|
|
@ -1,341 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
|
||||||
// the technique used in the fmt package.
|
|
||||||
var (
|
|
||||||
panicBytes = []byte("(PANIC=")
|
|
||||||
plusBytes = []byte("+")
|
|
||||||
iBytes = []byte("i")
|
|
||||||
trueBytes = []byte("true")
|
|
||||||
falseBytes = []byte("false")
|
|
||||||
interfaceBytes = []byte("(interface {})")
|
|
||||||
commaNewlineBytes = []byte(",\n")
|
|
||||||
newlineBytes = []byte("\n")
|
|
||||||
openBraceBytes = []byte("{")
|
|
||||||
openBraceNewlineBytes = []byte("{\n")
|
|
||||||
closeBraceBytes = []byte("}")
|
|
||||||
asteriskBytes = []byte("*")
|
|
||||||
colonBytes = []byte(":")
|
|
||||||
colonSpaceBytes = []byte(": ")
|
|
||||||
openParenBytes = []byte("(")
|
|
||||||
closeParenBytes = []byte(")")
|
|
||||||
spaceBytes = []byte(" ")
|
|
||||||
pointerChainBytes = []byte("->")
|
|
||||||
nilAngleBytes = []byte("<nil>")
|
|
||||||
maxNewlineBytes = []byte("<max depth reached>\n")
|
|
||||||
maxShortBytes = []byte("<max>")
|
|
||||||
circularBytes = []byte("<already shown>")
|
|
||||||
circularShortBytes = []byte("<shown>")
|
|
||||||
invalidAngleBytes = []byte("<invalid>")
|
|
||||||
openBracketBytes = []byte("[")
|
|
||||||
closeBracketBytes = []byte("]")
|
|
||||||
percentBytes = []byte("%")
|
|
||||||
precisionBytes = []byte(".")
|
|
||||||
openAngleBytes = []byte("<")
|
|
||||||
closeAngleBytes = []byte(">")
|
|
||||||
openMapBytes = []byte("map[")
|
|
||||||
closeMapBytes = []byte("]")
|
|
||||||
lenEqualsBytes = []byte("len=")
|
|
||||||
capEqualsBytes = []byte("cap=")
|
|
||||||
)
|
|
||||||
|
|
||||||
// hexDigits is used to map a decimal value to a hex digit.
|
|
||||||
var hexDigits = "0123456789abcdef"
|
|
||||||
|
|
||||||
// catchPanic handles any panics that might occur during the handleMethods
|
|
||||||
// calls.
|
|
||||||
func catchPanic(w io.Writer, v reflect.Value) {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
w.Write(panicBytes)
|
|
||||||
fmt.Fprintf(w, "%v", err)
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleMethods attempts to call the Error and String methods on the underlying
|
|
||||||
// type the passed reflect.Value represents and outputes the result to Writer w.
|
|
||||||
//
|
|
||||||
// It handles panics in any called methods by catching and displaying the error
|
|
||||||
// as the formatted value.
|
|
||||||
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
|
||||||
// We need an interface to check if the type implements the error or
|
|
||||||
// Stringer interface. However, the reflect package won't give us an
|
|
||||||
// interface on certain things like unexported struct fields in order
|
|
||||||
// to enforce visibility rules. We use unsafe, when it's available,
|
|
||||||
// to bypass these restrictions since this package does not mutate the
|
|
||||||
// values.
|
|
||||||
if !v.CanInterface() {
|
|
||||||
if UnsafeDisabled {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
v = unsafeReflectValue(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose whether or not to do error and Stringer interface lookups against
|
|
||||||
// the base type or a pointer to the base type depending on settings.
|
|
||||||
// Technically calling one of these methods with a pointer receiver can
|
|
||||||
// mutate the value, however, types which choose to satisify an error or
|
|
||||||
// Stringer interface with a pointer receiver should not be mutating their
|
|
||||||
// state inside these interface methods.
|
|
||||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
|
||||||
v = unsafeReflectValue(v)
|
|
||||||
}
|
|
||||||
if v.CanAddr() {
|
|
||||||
v = v.Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is it an error or Stringer?
|
|
||||||
switch iface := v.Interface().(type) {
|
|
||||||
case error:
|
|
||||||
defer catchPanic(w, v)
|
|
||||||
if cs.ContinueOnMethod {
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(iface.Error()))
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte(iface.Error()))
|
|
||||||
return true
|
|
||||||
|
|
||||||
case fmt.Stringer:
|
|
||||||
defer catchPanic(w, v)
|
|
||||||
if cs.ContinueOnMethod {
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(iface.String()))
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
w.Write([]byte(iface.String()))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// printBool outputs a boolean value as true or false to Writer w.
|
|
||||||
func printBool(w io.Writer, val bool) {
|
|
||||||
if val {
|
|
||||||
w.Write(trueBytes)
|
|
||||||
} else {
|
|
||||||
w.Write(falseBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// printInt outputs a signed integer value to Writer w.
|
|
||||||
func printInt(w io.Writer, val int64, base int) {
|
|
||||||
w.Write([]byte(strconv.FormatInt(val, base)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printUint outputs an unsigned integer value to Writer w.
|
|
||||||
func printUint(w io.Writer, val uint64, base int) {
|
|
||||||
w.Write([]byte(strconv.FormatUint(val, base)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printFloat outputs a floating point value using the specified precision,
|
|
||||||
// which is expected to be 32 or 64bit, to Writer w.
|
|
||||||
func printFloat(w io.Writer, val float64, precision int) {
|
|
||||||
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printComplex outputs a complex value using the specified float precision
|
|
||||||
// for the real and imaginary parts to Writer w.
|
|
||||||
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
|
||||||
r := real(c)
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
|
||||||
i := imag(c)
|
|
||||||
if i >= 0 {
|
|
||||||
w.Write(plusBytes)
|
|
||||||
}
|
|
||||||
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
|
||||||
w.Write(iBytes)
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
|
|
||||||
// prefix to Writer w.
|
|
||||||
func printHexPtr(w io.Writer, p uintptr) {
|
|
||||||
// Null pointer.
|
|
||||||
num := uint64(p)
|
|
||||||
if num == 0 {
|
|
||||||
w.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
|
||||||
buf := make([]byte, 18)
|
|
||||||
|
|
||||||
// It's simpler to construct the hex string right to left.
|
|
||||||
base := uint64(16)
|
|
||||||
i := len(buf) - 1
|
|
||||||
for num >= base {
|
|
||||||
buf[i] = hexDigits[num%base]
|
|
||||||
num /= base
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
buf[i] = hexDigits[num]
|
|
||||||
|
|
||||||
// Add '0x' prefix.
|
|
||||||
i--
|
|
||||||
buf[i] = 'x'
|
|
||||||
i--
|
|
||||||
buf[i] = '0'
|
|
||||||
|
|
||||||
// Strip unused leading bytes.
|
|
||||||
buf = buf[i:]
|
|
||||||
w.Write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
|
||||||
// elements to be sorted.
|
|
||||||
type valuesSorter struct {
|
|
||||||
values []reflect.Value
|
|
||||||
strings []string // either nil or same len and values
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
|
||||||
// surrogate keys on which the data should be sorted. It uses flags in
|
|
||||||
// ConfigState to decide if and how to populate those surrogate keys.
|
|
||||||
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
|
||||||
vs := &valuesSorter{values: values, cs: cs}
|
|
||||||
if canSortSimply(vs.values[0].Kind()) {
|
|
||||||
return vs
|
|
||||||
}
|
|
||||||
if !cs.DisableMethods {
|
|
||||||
vs.strings = make([]string, len(values))
|
|
||||||
for i := range vs.values {
|
|
||||||
b := bytes.Buffer{}
|
|
||||||
if !handleMethods(cs, &b, vs.values[i]) {
|
|
||||||
vs.strings = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
vs.strings[i] = b.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if vs.strings == nil && cs.SpewKeys {
|
|
||||||
vs.strings = make([]string, len(values))
|
|
||||||
for i := range vs.values {
|
|
||||||
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vs
|
|
||||||
}
|
|
||||||
|
|
||||||
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
|
||||||
// directly, or whether it should be considered for sorting by surrogate keys
|
|
||||||
// (if the ConfigState allows it).
|
|
||||||
func canSortSimply(kind reflect.Kind) bool {
|
|
||||||
// This switch parallels valueSortLess, except for the default case.
|
|
||||||
switch kind {
|
|
||||||
case reflect.Bool:
|
|
||||||
return true
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
return true
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
return true
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return true
|
|
||||||
case reflect.String:
|
|
||||||
return true
|
|
||||||
case reflect.Uintptr:
|
|
||||||
return true
|
|
||||||
case reflect.Array:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of values in the slice. It is part of the
|
|
||||||
// sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Len() int {
|
|
||||||
return len(s.values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap swaps the values at the passed indices. It is part of the
|
|
||||||
// sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Swap(i, j int) {
|
|
||||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
|
||||||
if s.strings != nil {
|
|
||||||
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// valueSortLess returns whether the first value should sort before the second
|
|
||||||
// value. It is used by valueSorter.Less as part of the sort.Interface
|
|
||||||
// implementation.
|
|
||||||
func valueSortLess(a, b reflect.Value) bool {
|
|
||||||
switch a.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return !a.Bool() && b.Bool()
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
return a.Int() < b.Int()
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
return a.Uint() < b.Uint()
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return a.Float() < b.Float()
|
|
||||||
case reflect.String:
|
|
||||||
return a.String() < b.String()
|
|
||||||
case reflect.Uintptr:
|
|
||||||
return a.Uint() < b.Uint()
|
|
||||||
case reflect.Array:
|
|
||||||
// Compare the contents of both arrays.
|
|
||||||
l := a.Len()
|
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
av := a.Index(i)
|
|
||||||
bv := b.Index(i)
|
|
||||||
if av.Interface() == bv.Interface() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return valueSortLess(av, bv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return a.String() < b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Less returns whether the value at index i should sort before the
|
|
||||||
// value at index j. It is part of the sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Less(i, j int) bool {
|
|
||||||
if s.strings == nil {
|
|
||||||
return valueSortLess(s.values[i], s.values[j])
|
|
||||||
}
|
|
||||||
return s.strings[i] < s.strings[j]
|
|
||||||
}
|
|
||||||
|
|
||||||
// sortValues is a sort function that handles both native types and any type that
|
|
||||||
// can be converted to error or Stringer. Other inputs are sorted according to
|
|
||||||
// their Value.String() value to ensure display stability.
|
|
||||||
func sortValues(values []reflect.Value, cs *ConfigState) {
|
|
||||||
if len(values) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sort.Sort(newValuesSorter(values, cs))
|
|
||||||
}
|
|
|
@ -1,306 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConfigState houses the configuration options used by spew to format and
|
|
||||||
// display values. There is a global instance, Config, that is used to control
|
|
||||||
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
|
||||||
// provides methods equivalent to the top-level functions.
|
|
||||||
//
|
|
||||||
// The zero value for ConfigState provides no indentation. You would typically
|
|
||||||
// want to set it to a space or a tab.
|
|
||||||
//
|
|
||||||
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
|
||||||
// with default settings. See the documentation of NewDefaultConfig for default
|
|
||||||
// values.
|
|
||||||
type ConfigState struct {
|
|
||||||
// Indent specifies the string to use for each indentation level. The
|
|
||||||
// global config instance that all top-level functions use set this to a
|
|
||||||
// single space by default. If you would like more indentation, you might
|
|
||||||
// set this to a tab with "\t" or perhaps two spaces with " ".
|
|
||||||
Indent string
|
|
||||||
|
|
||||||
// MaxDepth controls the maximum number of levels to descend into nested
|
|
||||||
// data structures. The default, 0, means there is no limit.
|
|
||||||
//
|
|
||||||
// NOTE: Circular data structures are properly detected, so it is not
|
|
||||||
// necessary to set this value unless you specifically want to limit deeply
|
|
||||||
// nested data structures.
|
|
||||||
MaxDepth int
|
|
||||||
|
|
||||||
// DisableMethods specifies whether or not error and Stringer interfaces are
|
|
||||||
// invoked for types that implement them.
|
|
||||||
DisableMethods bool
|
|
||||||
|
|
||||||
// DisablePointerMethods specifies whether or not to check for and invoke
|
|
||||||
// error and Stringer interfaces on types which only accept a pointer
|
|
||||||
// receiver when the current type is not a pointer.
|
|
||||||
//
|
|
||||||
// NOTE: This might be an unsafe action since calling one of these methods
|
|
||||||
// with a pointer receiver could technically mutate the value, however,
|
|
||||||
// in practice, types which choose to satisify an error or Stringer
|
|
||||||
// interface with a pointer receiver should not be mutating their state
|
|
||||||
// inside these interface methods. As a result, this option relies on
|
|
||||||
// access to the unsafe package, so it will not have any effect when
|
|
||||||
// running in environments without access to the unsafe package such as
|
|
||||||
// Google App Engine or with the "safe" build tag specified.
|
|
||||||
DisablePointerMethods bool
|
|
||||||
|
|
||||||
// DisablePointerAddresses specifies whether to disable the printing of
|
|
||||||
// pointer addresses. This is useful when diffing data structures in tests.
|
|
||||||
DisablePointerAddresses bool
|
|
||||||
|
|
||||||
// DisableCapacities specifies whether to disable the printing of capacities
|
|
||||||
// for arrays, slices, maps and channels. This is useful when diffing
|
|
||||||
// data structures in tests.
|
|
||||||
DisableCapacities bool
|
|
||||||
|
|
||||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
|
||||||
// a custom error or Stringer interface is invoked. The default, false,
|
|
||||||
// means it will print the results of invoking the custom error or Stringer
|
|
||||||
// interface and return immediately instead of continuing to recurse into
|
|
||||||
// the internals of the data type.
|
|
||||||
//
|
|
||||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
|
||||||
// via the DisableMethods or DisablePointerMethods options.
|
|
||||||
ContinueOnMethod bool
|
|
||||||
|
|
||||||
// SortKeys specifies map keys should be sorted before being printed. Use
|
|
||||||
// this to have a more deterministic, diffable output. Note that only
|
|
||||||
// native types (bool, int, uint, floats, uintptr and string) and types
|
|
||||||
// that support the error or Stringer interfaces (if methods are
|
|
||||||
// enabled) are supported, with other types sorted according to the
|
|
||||||
// reflect.Value.String() output which guarantees display stability.
|
|
||||||
SortKeys bool
|
|
||||||
|
|
||||||
// SpewKeys specifies that, as a last resort attempt, map keys should
|
|
||||||
// be spewed to strings and sorted by those strings. This is only
|
|
||||||
// considered if SortKeys is true.
|
|
||||||
SpewKeys bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is the active configuration of the top-level functions.
|
|
||||||
// The configuration can be changed by modifying the contents of spew.Config.
|
|
||||||
var Config = ConfigState{Indent: " "}
|
|
||||||
|
|
||||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the formatted string as a value that satisfies error. See NewFormatter
|
|
||||||
// for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
|
||||||
return fmt.Errorf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprint(w, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintln(w, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Print(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Printf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Println(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprint(a ...interface{}) string {
|
|
||||||
return fmt.Sprint(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
|
||||||
return fmt.Sprintf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
|
||||||
// were passed with a Formatter interface returned by c.NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
|
||||||
return fmt.Sprintln(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
|
||||||
interface. As a result, it integrates cleanly with standard fmt package
|
|
||||||
printing functions. The formatter is useful for inline printing of smaller data
|
|
||||||
types similar to the standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Typically this function shouldn't be called directly. It is much easier to make
|
|
||||||
use of the custom formatter by calling one of the convenience functions such as
|
|
||||||
c.Printf, c.Println, or c.Printf.
|
|
||||||
*/
|
|
||||||
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
|
||||||
return newFormatter(c, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
|
||||||
// exactly the same as Dump.
|
|
||||||
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
|
||||||
fdump(c, w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Dump displays the passed parameters to standard out with newlines, customizable
|
|
||||||
indentation, and additional debug information such as complete types and all
|
|
||||||
pointer addresses used to indirect to the final value. It provides the
|
|
||||||
following features over the built-in printing facilities provided by the fmt
|
|
||||||
package:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output
|
|
||||||
|
|
||||||
The configuration options are controlled by modifying the public members
|
|
||||||
of c. See ConfigState for options documentation.
|
|
||||||
|
|
||||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
|
||||||
get the formatted result as a string.
|
|
||||||
*/
|
|
||||||
func (c *ConfigState) Dump(a ...interface{}) {
|
|
||||||
fdump(c, os.Stdout, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
|
||||||
// as Dump.
|
|
||||||
func (c *ConfigState) Sdump(a ...interface{}) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fdump(c, &buf, a...)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
|
||||||
// length with each argument converted to a spew Formatter interface using
|
|
||||||
// the ConfigState associated with s.
|
|
||||||
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
|
||||||
formatters = make([]interface{}, len(args))
|
|
||||||
for index, arg := range args {
|
|
||||||
formatters[index] = newFormatter(c, arg)
|
|
||||||
}
|
|
||||||
return formatters
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultConfig returns a ConfigState with the following default settings.
|
|
||||||
//
|
|
||||||
// Indent: " "
|
|
||||||
// MaxDepth: 0
|
|
||||||
// DisableMethods: false
|
|
||||||
// DisablePointerMethods: false
|
|
||||||
// ContinueOnMethod: false
|
|
||||||
// SortKeys: false
|
|
||||||
func NewDefaultConfig() *ConfigState {
|
|
||||||
return &ConfigState{Indent: " "}
|
|
||||||
}
|
|
|
@ -1,211 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package spew implements a deep pretty printer for Go data structures to aid in
|
|
||||||
debugging.
|
|
||||||
|
|
||||||
A quick overview of the additional features spew provides over the built-in
|
|
||||||
printing facilities for Go data types are as follows:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output (only when using
|
|
||||||
Dump style)
|
|
||||||
|
|
||||||
There are two different approaches spew allows for dumping Go data structures:
|
|
||||||
|
|
||||||
* Dump style which prints with newlines, customizable indentation,
|
|
||||||
and additional debug information such as types and all pointer addresses
|
|
||||||
used to indirect to the final value
|
|
||||||
* A custom Formatter interface that integrates cleanly with the standard fmt
|
|
||||||
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
|
||||||
similar to the default %v while providing the additional functionality
|
|
||||||
outlined above and passing unsupported format verbs such as %x and %q
|
|
||||||
along to fmt
|
|
||||||
|
|
||||||
Quick Start
|
|
||||||
|
|
||||||
This section demonstrates how to quickly get started with spew. See the
|
|
||||||
sections below for further details on formatting and configuration options.
|
|
||||||
|
|
||||||
To dump a variable with full newlines, indentation, type, and pointer
|
|
||||||
information use Dump, Fdump, or Sdump:
|
|
||||||
spew.Dump(myVar1, myVar2, ...)
|
|
||||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
|
||||||
str := spew.Sdump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
|
||||||
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
|
||||||
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
|
||||||
%#+v (adds types and pointer addresses):
|
|
||||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
|
|
||||||
Configuration Options
|
|
||||||
|
|
||||||
Configuration of spew is handled by fields in the ConfigState type. For
|
|
||||||
convenience, all of the top-level functions use a global state available
|
|
||||||
via the spew.Config global.
|
|
||||||
|
|
||||||
It is also possible to create a ConfigState instance that provides methods
|
|
||||||
equivalent to the top-level functions. This allows concurrent configuration
|
|
||||||
options. See the ConfigState documentation for more details.
|
|
||||||
|
|
||||||
The following configuration options are available:
|
|
||||||
* Indent
|
|
||||||
String to use for each indentation level for Dump functions.
|
|
||||||
It is a single space by default. A popular alternative is "\t".
|
|
||||||
|
|
||||||
* MaxDepth
|
|
||||||
Maximum number of levels to descend into nested data structures.
|
|
||||||
There is no limit by default.
|
|
||||||
|
|
||||||
* DisableMethods
|
|
||||||
Disables invocation of error and Stringer interface methods.
|
|
||||||
Method invocation is enabled by default.
|
|
||||||
|
|
||||||
* DisablePointerMethods
|
|
||||||
Disables invocation of error and Stringer interface methods on types
|
|
||||||
which only accept pointer receivers from non-pointer variables.
|
|
||||||
Pointer method invocation is enabled by default.
|
|
||||||
|
|
||||||
* DisablePointerAddresses
|
|
||||||
DisablePointerAddresses specifies whether to disable the printing of
|
|
||||||
pointer addresses. This is useful when diffing data structures in tests.
|
|
||||||
|
|
||||||
* DisableCapacities
|
|
||||||
DisableCapacities specifies whether to disable the printing of
|
|
||||||
capacities for arrays, slices, maps and channels. This is useful when
|
|
||||||
diffing data structures in tests.
|
|
||||||
|
|
||||||
* ContinueOnMethod
|
|
||||||
Enables recursion into types after invoking error and Stringer interface
|
|
||||||
methods. Recursion after method invocation is disabled by default.
|
|
||||||
|
|
||||||
* SortKeys
|
|
||||||
Specifies map keys should be sorted before being printed. Use
|
|
||||||
this to have a more deterministic, diffable output. Note that
|
|
||||||
only native types (bool, int, uint, floats, uintptr and string)
|
|
||||||
and types which implement error or Stringer interfaces are
|
|
||||||
supported with other types sorted according to the
|
|
||||||
reflect.Value.String() output which guarantees display
|
|
||||||
stability. Natural map order is used by default.
|
|
||||||
|
|
||||||
* SpewKeys
|
|
||||||
Specifies that, as a last resort attempt, map keys should be
|
|
||||||
spewed to strings and sorted by those strings. This is only
|
|
||||||
considered if SortKeys is true.
|
|
||||||
|
|
||||||
Dump Usage
|
|
||||||
|
|
||||||
Simply call spew.Dump with a list of variables you want to dump:
|
|
||||||
|
|
||||||
spew.Dump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
|
||||||
io.Writer. For example, to dump to standard error:
|
|
||||||
|
|
||||||
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
A third option is to call spew.Sdump to get the formatted output as a string:
|
|
||||||
|
|
||||||
str := spew.Sdump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
Sample Dump Output
|
|
||||||
|
|
||||||
See the Dump example for details on the setup of the types and variables being
|
|
||||||
shown here.
|
|
||||||
|
|
||||||
(main.Foo) {
|
|
||||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
|
||||||
flag: (main.Flag) flagTwo,
|
|
||||||
data: (uintptr) <nil>
|
|
||||||
}),
|
|
||||||
ExportedField: (map[interface {}]interface {}) (len=1) {
|
|
||||||
(string) (len=3) "one": (bool) true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
|
||||||
command as shown.
|
|
||||||
([]uint8) (len=32 cap=32) {
|
|
||||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
|
||||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
|
||||||
00000020 31 32 |12|
|
|
||||||
}
|
|
||||||
|
|
||||||
Custom Formatter
|
|
||||||
|
|
||||||
Spew provides a custom formatter that implements the fmt.Formatter interface
|
|
||||||
so that it integrates cleanly with standard fmt package printing functions. The
|
|
||||||
formatter is useful for inline printing of smaller data types similar to the
|
|
||||||
standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Custom Formatter Usage
|
|
||||||
|
|
||||||
The simplest way to make use of the spew custom formatter is to call one of the
|
|
||||||
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
|
||||||
functions have syntax you are most likely already familiar with:
|
|
||||||
|
|
||||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
spew.Println(myVar, myVar2)
|
|
||||||
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
|
|
||||||
See the Index for the full list convenience functions.
|
|
||||||
|
|
||||||
Sample Formatter Output
|
|
||||||
|
|
||||||
Double pointer to a uint8:
|
|
||||||
%v: <**>5
|
|
||||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
|
||||||
%#v: (**uint8)5
|
|
||||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
|
||||||
|
|
||||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
|
||||||
%v: <*>{1 <*><shown>}
|
|
||||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
|
||||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
|
||||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
|
||||||
|
|
||||||
See the Printf example for details on the setup of variables being shown
|
|
||||||
here.
|
|
||||||
|
|
||||||
Errors
|
|
||||||
|
|
||||||
Since it is possible for custom Stringer/error interfaces to panic, spew
|
|
||||||
detects them and handles them internally by printing the panic information
|
|
||||||
inline with the output. Since spew is intended to provide deep pretty printing
|
|
||||||
capabilities on structures, it intentionally does not return any errors.
|
|
||||||
*/
|
|
||||||
package spew
|
|
|
@ -1,509 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// uint8Type is a reflect.Type representing a uint8. It is used to
|
|
||||||
// convert cgo types to uint8 slices for hexdumping.
|
|
||||||
uint8Type = reflect.TypeOf(uint8(0))
|
|
||||||
|
|
||||||
// cCharRE is a regular expression that matches a cgo char.
|
|
||||||
// It is used to detect character arrays to hexdump them.
|
|
||||||
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
|
|
||||||
|
|
||||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
|
||||||
// char. It is used to detect unsigned character arrays to hexdump
|
|
||||||
// them.
|
|
||||||
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
|
|
||||||
|
|
||||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
|
||||||
// It is used to detect uint8_t arrays to hexdump them.
|
|
||||||
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// dumpState contains information about the state of a dump operation.
|
|
||||||
type dumpState struct {
|
|
||||||
w io.Writer
|
|
||||||
depth int
|
|
||||||
pointers map[uintptr]int
|
|
||||||
ignoreNextType bool
|
|
||||||
ignoreNextIndent bool
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// indent performs indentation according to the depth level and cs.Indent
|
|
||||||
// option.
|
|
||||||
func (d *dumpState) indent() {
|
|
||||||
if d.ignoreNextIndent {
|
|
||||||
d.ignoreNextIndent = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpackValue returns values inside of non-nil interfaces when possible.
|
|
||||||
// This is useful for data types like structs, arrays, slices, and maps which
|
|
||||||
// can contain varying types packed inside an interface.
|
|
||||||
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
|
||||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
|
||||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
|
||||||
// Remove pointers at or below the current depth from map used to detect
|
|
||||||
// circular refs.
|
|
||||||
for k, depth := range d.pointers {
|
|
||||||
if depth >= d.depth {
|
|
||||||
delete(d.pointers, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep list of all dereferenced pointers to show later.
|
|
||||||
pointerChain := make([]uintptr, 0)
|
|
||||||
|
|
||||||
// Figure out how many levels of indirection there are by dereferencing
|
|
||||||
// pointers and unpacking interfaces down the chain while detecting circular
|
|
||||||
// references.
|
|
||||||
nilFound := false
|
|
||||||
cycleFound := false
|
|
||||||
indirects := 0
|
|
||||||
ve := v
|
|
||||||
for ve.Kind() == reflect.Ptr {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
indirects++
|
|
||||||
addr := ve.Pointer()
|
|
||||||
pointerChain = append(pointerChain, addr)
|
|
||||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
|
||||||
cycleFound = true
|
|
||||||
indirects--
|
|
||||||
break
|
|
||||||
}
|
|
||||||
d.pointers[addr] = d.depth
|
|
||||||
|
|
||||||
ve = ve.Elem()
|
|
||||||
if ve.Kind() == reflect.Interface {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ve = ve.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display type information.
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
|
||||||
d.w.Write([]byte(ve.Type().String()))
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
|
|
||||||
// Display pointer information.
|
|
||||||
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
for i, addr := range pointerChain {
|
|
||||||
if i > 0 {
|
|
||||||
d.w.Write(pointerChainBytes)
|
|
||||||
}
|
|
||||||
printHexPtr(d.w, addr)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display dereferenced value.
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
switch {
|
|
||||||
case nilFound:
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
|
|
||||||
case cycleFound:
|
|
||||||
d.w.Write(circularBytes)
|
|
||||||
|
|
||||||
default:
|
|
||||||
d.ignoreNextType = true
|
|
||||||
d.dump(ve)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
|
||||||
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
|
||||||
func (d *dumpState) dumpSlice(v reflect.Value) {
|
|
||||||
// Determine whether this type should be hex dumped or not. Also,
|
|
||||||
// for types which should be hexdumped, try to use the underlying data
|
|
||||||
// first, then fall back to trying to convert them to a uint8 slice.
|
|
||||||
var buf []uint8
|
|
||||||
doConvert := false
|
|
||||||
doHexDump := false
|
|
||||||
numEntries := v.Len()
|
|
||||||
if numEntries > 0 {
|
|
||||||
vt := v.Index(0).Type()
|
|
||||||
vts := vt.String()
|
|
||||||
switch {
|
|
||||||
// C types that need to be converted.
|
|
||||||
case cCharRE.MatchString(vts):
|
|
||||||
fallthrough
|
|
||||||
case cUnsignedCharRE.MatchString(vts):
|
|
||||||
fallthrough
|
|
||||||
case cUint8tCharRE.MatchString(vts):
|
|
||||||
doConvert = true
|
|
||||||
|
|
||||||
// Try to use existing uint8 slices and fall back to converting
|
|
||||||
// and copying if that fails.
|
|
||||||
case vt.Kind() == reflect.Uint8:
|
|
||||||
// We need an addressable interface to convert the type
|
|
||||||
// to a byte slice. However, the reflect package won't
|
|
||||||
// give us an interface on certain things like
|
|
||||||
// unexported struct fields in order to enforce
|
|
||||||
// visibility rules. We use unsafe, when available, to
|
|
||||||
// bypass these restrictions since this package does not
|
|
||||||
// mutate the values.
|
|
||||||
vs := v
|
|
||||||
if !vs.CanInterface() || !vs.CanAddr() {
|
|
||||||
vs = unsafeReflectValue(vs)
|
|
||||||
}
|
|
||||||
if !UnsafeDisabled {
|
|
||||||
vs = vs.Slice(0, numEntries)
|
|
||||||
|
|
||||||
// Use the existing uint8 slice if it can be
|
|
||||||
// type asserted.
|
|
||||||
iface := vs.Interface()
|
|
||||||
if slice, ok := iface.([]uint8); ok {
|
|
||||||
buf = slice
|
|
||||||
doHexDump = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The underlying data needs to be converted if it can't
|
|
||||||
// be type asserted to a uint8 slice.
|
|
||||||
doConvert = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy and convert the underlying type if needed.
|
|
||||||
if doConvert && vt.ConvertibleTo(uint8Type) {
|
|
||||||
// Convert and copy each element into a uint8 byte
|
|
||||||
// slice.
|
|
||||||
buf = make([]uint8, numEntries)
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
vv := v.Index(i)
|
|
||||||
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
|
||||||
}
|
|
||||||
doHexDump = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hexdump the entire slice as needed.
|
|
||||||
if doHexDump {
|
|
||||||
indent := strings.Repeat(d.cs.Indent, d.depth)
|
|
||||||
str := indent + hex.Dump(buf)
|
|
||||||
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
|
||||||
str = strings.TrimRight(str, d.cs.Indent)
|
|
||||||
d.w.Write([]byte(str))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively call dump for each item.
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
d.dump(d.unpackValue(v.Index(i)))
|
|
||||||
if i < (numEntries - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
|
||||||
// value to figure out what kind of object we are dealing with and formats it
|
|
||||||
// appropriately. It is a recursive function, however circular data structures
|
|
||||||
// are detected and handled properly.
|
|
||||||
func (d *dumpState) dump(v reflect.Value) {
|
|
||||||
// Handle invalid reflect values immediately.
|
|
||||||
kind := v.Kind()
|
|
||||||
if kind == reflect.Invalid {
|
|
||||||
d.w.Write(invalidAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pointers specially.
|
|
||||||
if kind == reflect.Ptr {
|
|
||||||
d.indent()
|
|
||||||
d.dumpPtr(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print type information unless already handled elsewhere.
|
|
||||||
if !d.ignoreNextType {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
d.w.Write([]byte(v.Type().String()))
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
d.ignoreNextType = false
|
|
||||||
|
|
||||||
// Display length and capacity if the built-in len and cap functions
|
|
||||||
// work with the value's kind and the len/cap itself is non-zero.
|
|
||||||
valueLen, valueCap := 0, 0
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Array, reflect.Slice, reflect.Chan:
|
|
||||||
valueLen, valueCap = v.Len(), v.Cap()
|
|
||||||
case reflect.Map, reflect.String:
|
|
||||||
valueLen = v.Len()
|
|
||||||
}
|
|
||||||
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
if valueLen != 0 {
|
|
||||||
d.w.Write(lenEqualsBytes)
|
|
||||||
printInt(d.w, int64(valueLen), 10)
|
|
||||||
}
|
|
||||||
if !d.cs.DisableCapacities && valueCap != 0 {
|
|
||||||
if valueLen != 0 {
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
d.w.Write(capEqualsBytes)
|
|
||||||
printInt(d.w, int64(valueCap), 10)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call Stringer/error interfaces if they exist and the handle methods flag
|
|
||||||
// is enabled
|
|
||||||
if !d.cs.DisableMethods {
|
|
||||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
|
||||||
if handled := handleMethods(d.cs, d.w, v); handled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case reflect.Invalid:
|
|
||||||
// Do nothing. We should never get here since invalid has already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Bool:
|
|
||||||
printBool(d.w, v.Bool())
|
|
||||||
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
printInt(d.w, v.Int(), 10)
|
|
||||||
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
printUint(d.w, v.Uint(), 10)
|
|
||||||
|
|
||||||
case reflect.Float32:
|
|
||||||
printFloat(d.w, v.Float(), 32)
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
printFloat(d.w, v.Float(), 64)
|
|
||||||
|
|
||||||
case reflect.Complex64:
|
|
||||||
printComplex(d.w, v.Complex(), 32)
|
|
||||||
|
|
||||||
case reflect.Complex128:
|
|
||||||
printComplex(d.w, v.Complex(), 64)
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
case reflect.Array:
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.dumpSlice(v)
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
d.w.Write([]byte(strconv.Quote(v.String())))
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
// The only time we should get here is for nil interfaces due to
|
|
||||||
// unpackValue calls.
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Ptr:
|
|
||||||
// Do nothing. We should never get here since pointers have already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
// nil maps should be indicated as different than empty maps
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
numEntries := v.Len()
|
|
||||||
keys := v.MapKeys()
|
|
||||||
if d.cs.SortKeys {
|
|
||||||
sortValues(keys, d.cs)
|
|
||||||
}
|
|
||||||
for i, key := range keys {
|
|
||||||
d.dump(d.unpackValue(key))
|
|
||||||
d.w.Write(colonSpaceBytes)
|
|
||||||
d.ignoreNextIndent = true
|
|
||||||
d.dump(d.unpackValue(v.MapIndex(key)))
|
|
||||||
if i < (numEntries - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
vt := v.Type()
|
|
||||||
numFields := v.NumField()
|
|
||||||
for i := 0; i < numFields; i++ {
|
|
||||||
d.indent()
|
|
||||||
vtf := vt.Field(i)
|
|
||||||
d.w.Write([]byte(vtf.Name))
|
|
||||||
d.w.Write(colonSpaceBytes)
|
|
||||||
d.ignoreNextIndent = true
|
|
||||||
d.dump(d.unpackValue(v.Field(i)))
|
|
||||||
if i < (numFields - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Uintptr:
|
|
||||||
printHexPtr(d.w, uintptr(v.Uint()))
|
|
||||||
|
|
||||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
|
||||||
printHexPtr(d.w, v.Pointer())
|
|
||||||
|
|
||||||
// There were not any other types at the time this code was written, but
|
|
||||||
// fall back to letting the default fmt package handle it in case any new
|
|
||||||
// types are added.
|
|
||||||
default:
|
|
||||||
if v.CanInterface() {
|
|
||||||
fmt.Fprintf(d.w, "%v", v.Interface())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(d.w, "%v", v.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fdump is a helper function to consolidate the logic from the various public
|
|
||||||
// methods which take varying writers and config states.
|
|
||||||
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
|
||||||
for _, arg := range a {
|
|
||||||
if arg == nil {
|
|
||||||
w.Write(interfaceBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
w.Write(nilAngleBytes)
|
|
||||||
w.Write(newlineBytes)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
d := dumpState{w: w, cs: cs}
|
|
||||||
d.pointers = make(map[uintptr]int)
|
|
||||||
d.dump(reflect.ValueOf(arg))
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
|
||||||
// exactly the same as Dump.
|
|
||||||
func Fdump(w io.Writer, a ...interface{}) {
|
|
||||||
fdump(&Config, w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
|
||||||
// as Dump.
|
|
||||||
func Sdump(a ...interface{}) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fdump(&Config, &buf, a...)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Dump displays the passed parameters to standard out with newlines, customizable
|
|
||||||
indentation, and additional debug information such as complete types and all
|
|
||||||
pointer addresses used to indirect to the final value. It provides the
|
|
||||||
following features over the built-in printing facilities provided by the fmt
|
|
||||||
package:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output
|
|
||||||
|
|
||||||
The configuration options are controlled by an exported package global,
|
|
||||||
spew.Config. See ConfigState for options documentation.
|
|
||||||
|
|
||||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
|
||||||
get the formatted result as a string.
|
|
||||||
*/
|
|
||||||
func Dump(a ...interface{}) {
|
|
||||||
fdump(&Config, os.Stdout, a...)
|
|
||||||
}
|
|
|
@ -1,419 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// supportedFlags is a list of all the character flags supported by fmt package.
|
|
||||||
const supportedFlags = "0-+# "
|
|
||||||
|
|
||||||
// formatState implements the fmt.Formatter interface and contains information
|
|
||||||
// about the state of a formatting operation. The NewFormatter function can
|
|
||||||
// be used to get a new Formatter which can be used directly as arguments
|
|
||||||
// in standard fmt package printing calls.
|
|
||||||
type formatState struct {
|
|
||||||
value interface{}
|
|
||||||
fs fmt.State
|
|
||||||
depth int
|
|
||||||
pointers map[uintptr]int
|
|
||||||
ignoreNextType bool
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildDefaultFormat recreates the original format string without precision
|
|
||||||
// and width information to pass in to fmt.Sprintf in the case of an
|
|
||||||
// unrecognized type. Unless new types are added to the language, this
|
|
||||||
// function won't ever be called.
|
|
||||||
func (f *formatState) buildDefaultFormat() (format string) {
|
|
||||||
buf := bytes.NewBuffer(percentBytes)
|
|
||||||
|
|
||||||
for _, flag := range supportedFlags {
|
|
||||||
if f.fs.Flag(int(flag)) {
|
|
||||||
buf.WriteRune(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteRune('v')
|
|
||||||
|
|
||||||
format = buf.String()
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
|
|
||||||
// constructOrigFormat recreates the original format string including precision
|
|
||||||
// and width information to pass along to the standard fmt package. This allows
|
|
||||||
// automatic deferral of all format strings this package doesn't support.
|
|
||||||
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
|
||||||
buf := bytes.NewBuffer(percentBytes)
|
|
||||||
|
|
||||||
for _, flag := range supportedFlags {
|
|
||||||
if f.fs.Flag(int(flag)) {
|
|
||||||
buf.WriteRune(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if width, ok := f.fs.Width(); ok {
|
|
||||||
buf.WriteString(strconv.Itoa(width))
|
|
||||||
}
|
|
||||||
|
|
||||||
if precision, ok := f.fs.Precision(); ok {
|
|
||||||
buf.Write(precisionBytes)
|
|
||||||
buf.WriteString(strconv.Itoa(precision))
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteRune(verb)
|
|
||||||
|
|
||||||
format = buf.String()
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpackValue returns values inside of non-nil interfaces when possible and
|
|
||||||
// ensures that types for values which have been unpacked from an interface
|
|
||||||
// are displayed when the show types flag is also set.
|
|
||||||
// This is useful for data types like structs, arrays, slices, and maps which
|
|
||||||
// can contain varying types packed inside an interface.
|
|
||||||
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
|
||||||
if v.Kind() == reflect.Interface {
|
|
||||||
f.ignoreNextType = false
|
|
||||||
if !v.IsNil() {
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
|
||||||
func (f *formatState) formatPtr(v reflect.Value) {
|
|
||||||
// Display nil if top level pointer is nil.
|
|
||||||
showTypes := f.fs.Flag('#')
|
|
||||||
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove pointers at or below the current depth from map used to detect
|
|
||||||
// circular refs.
|
|
||||||
for k, depth := range f.pointers {
|
|
||||||
if depth >= f.depth {
|
|
||||||
delete(f.pointers, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep list of all dereferenced pointers to possibly show later.
|
|
||||||
pointerChain := make([]uintptr, 0)
|
|
||||||
|
|
||||||
// Figure out how many levels of indirection there are by derferencing
|
|
||||||
// pointers and unpacking interfaces down the chain while detecting circular
|
|
||||||
// references.
|
|
||||||
nilFound := false
|
|
||||||
cycleFound := false
|
|
||||||
indirects := 0
|
|
||||||
ve := v
|
|
||||||
for ve.Kind() == reflect.Ptr {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
indirects++
|
|
||||||
addr := ve.Pointer()
|
|
||||||
pointerChain = append(pointerChain, addr)
|
|
||||||
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
|
||||||
cycleFound = true
|
|
||||||
indirects--
|
|
||||||
break
|
|
||||||
}
|
|
||||||
f.pointers[addr] = f.depth
|
|
||||||
|
|
||||||
ve = ve.Elem()
|
|
||||||
if ve.Kind() == reflect.Interface {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ve = ve.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display type or indirection level depending on flags.
|
|
||||||
if showTypes && !f.ignoreNextType {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
|
||||||
f.fs.Write([]byte(ve.Type().String()))
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
} else {
|
|
||||||
if nilFound || cycleFound {
|
|
||||||
indirects += strings.Count(ve.Type().String(), "*")
|
|
||||||
}
|
|
||||||
f.fs.Write(openAngleBytes)
|
|
||||||
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
|
||||||
f.fs.Write(closeAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display pointer information depending on flags.
|
|
||||||
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
for i, addr := range pointerChain {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(pointerChainBytes)
|
|
||||||
}
|
|
||||||
printHexPtr(f.fs, addr)
|
|
||||||
}
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display dereferenced value.
|
|
||||||
switch {
|
|
||||||
case nilFound:
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
|
|
||||||
case cycleFound:
|
|
||||||
f.fs.Write(circularShortBytes)
|
|
||||||
|
|
||||||
default:
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(ve)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// format is the main workhorse for providing the Formatter interface. It
|
|
||||||
// uses the passed reflect value to figure out what kind of object we are
|
|
||||||
// dealing with and formats it appropriately. It is a recursive function,
|
|
||||||
// however circular data structures are detected and handled properly.
|
|
||||||
func (f *formatState) format(v reflect.Value) {
|
|
||||||
// Handle invalid reflect values immediately.
|
|
||||||
kind := v.Kind()
|
|
||||||
if kind == reflect.Invalid {
|
|
||||||
f.fs.Write(invalidAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pointers specially.
|
|
||||||
if kind == reflect.Ptr {
|
|
||||||
f.formatPtr(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print type information unless already handled elsewhere.
|
|
||||||
if !f.ignoreNextType && f.fs.Flag('#') {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
f.fs.Write([]byte(v.Type().String()))
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = false
|
|
||||||
|
|
||||||
// Call Stringer/error interfaces if they exist and the handle methods
|
|
||||||
// flag is enabled.
|
|
||||||
if !f.cs.DisableMethods {
|
|
||||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
|
||||||
if handled := handleMethods(f.cs, f.fs, v); handled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case reflect.Invalid:
|
|
||||||
// Do nothing. We should never get here since invalid has already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Bool:
|
|
||||||
printBool(f.fs, v.Bool())
|
|
||||||
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
printInt(f.fs, v.Int(), 10)
|
|
||||||
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
printUint(f.fs, v.Uint(), 10)
|
|
||||||
|
|
||||||
case reflect.Float32:
|
|
||||||
printFloat(f.fs, v.Float(), 32)
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
printFloat(f.fs, v.Float(), 64)
|
|
||||||
|
|
||||||
case reflect.Complex64:
|
|
||||||
printComplex(f.fs, v.Complex(), 32)
|
|
||||||
|
|
||||||
case reflect.Complex128:
|
|
||||||
printComplex(f.fs, v.Complex(), 64)
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
case reflect.Array:
|
|
||||||
f.fs.Write(openBracketBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
numEntries := v.Len()
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(v.Index(i)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeBracketBytes)
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
f.fs.Write([]byte(v.String()))
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
// The only time we should get here is for nil interfaces due to
|
|
||||||
// unpackValue calls.
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Ptr:
|
|
||||||
// Do nothing. We should never get here since pointers have already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
// nil maps should be indicated as different than empty maps
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
f.fs.Write(openMapBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
keys := v.MapKeys()
|
|
||||||
if f.cs.SortKeys {
|
|
||||||
sortValues(keys, f.cs)
|
|
||||||
}
|
|
||||||
for i, key := range keys {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(key))
|
|
||||||
f.fs.Write(colonBytes)
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(v.MapIndex(key)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeMapBytes)
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
numFields := v.NumField()
|
|
||||||
f.fs.Write(openBraceBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
vt := v.Type()
|
|
||||||
for i := 0; i < numFields; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
vtf := vt.Field(i)
|
|
||||||
if f.fs.Flag('+') || f.fs.Flag('#') {
|
|
||||||
f.fs.Write([]byte(vtf.Name))
|
|
||||||
f.fs.Write(colonBytes)
|
|
||||||
}
|
|
||||||
f.format(f.unpackValue(v.Field(i)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Uintptr:
|
|
||||||
printHexPtr(f.fs, uintptr(v.Uint()))
|
|
||||||
|
|
||||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
|
||||||
printHexPtr(f.fs, v.Pointer())
|
|
||||||
|
|
||||||
// There were not any other types at the time this code was written, but
|
|
||||||
// fall back to letting the default fmt package handle it if any get added.
|
|
||||||
default:
|
|
||||||
format := f.buildDefaultFormat()
|
|
||||||
if v.CanInterface() {
|
|
||||||
fmt.Fprintf(f.fs, format, v.Interface())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(f.fs, format, v.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
|
||||||
// details.
|
|
||||||
func (f *formatState) Format(fs fmt.State, verb rune) {
|
|
||||||
f.fs = fs
|
|
||||||
|
|
||||||
// Use standard formatting for verbs that are not v.
|
|
||||||
if verb != 'v' {
|
|
||||||
format := f.constructOrigFormat(verb)
|
|
||||||
fmt.Fprintf(fs, format, f.value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.value == nil {
|
|
||||||
if fs.Flag('#') {
|
|
||||||
fs.Write(interfaceBytes)
|
|
||||||
}
|
|
||||||
fs.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.format(reflect.ValueOf(f.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFormatter is a helper function to consolidate the logic from the various
|
|
||||||
// public methods which take varying config states.
|
|
||||||
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
|
||||||
fs := &formatState{value: v, cs: cs}
|
|
||||||
fs.pointers = make(map[uintptr]int)
|
|
||||||
return fs
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
|
||||||
interface. As a result, it integrates cleanly with standard fmt package
|
|
||||||
printing functions. The formatter is useful for inline printing of smaller data
|
|
||||||
types similar to the standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Typically this function shouldn't be called directly. It is much easier to make
|
|
||||||
use of the custom formatter by calling one of the convenience functions such as
|
|
||||||
Printf, Println, or Fprintf.
|
|
||||||
*/
|
|
||||||
func NewFormatter(v interface{}) fmt.Formatter {
|
|
||||||
return newFormatter(&Config, v)
|
|
||||||
}
|
|
|
@ -1,148 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the formatted string as a value that satisfies error. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Errorf(format string, a ...interface{}) (err error) {
|
|
||||||
return fmt.Errorf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprint(w, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintf(w, format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintln(w, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Print(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Print(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Printf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Println(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Println(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprint(a ...interface{}) string {
|
|
||||||
return fmt.Sprint(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprintf(format string, a ...interface{}) string {
|
|
||||||
return fmt.Sprintf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
|
||||||
// were passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprintln(a ...interface{}) string {
|
|
||||||
return fmt.Sprintln(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
|
||||||
// length with each argument converted to a default spew Formatter interface.
|
|
||||||
func convertArgs(args []interface{}) (formatters []interface{}) {
|
|
||||||
formatters = make([]interface{}, len(args))
|
|
||||||
for index, arg := range args {
|
|
||||||
formatters[index] = NewFormatter(arg)
|
|
||||||
}
|
|
||||||
return formatters
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
b3e0aae393ef6c5cda7dcad0cba06bef23a1dda9
|
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2012 The glfw3-go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -1,53 +0,0 @@
|
||||||
package glfw
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Windows Build Tags
|
|
||||||
// ----------------
|
|
||||||
// GLFW Options:
|
|
||||||
#cgo windows CFLAGS: -D_GLFW_WIN32 -Iglfw/deps/mingw
|
|
||||||
|
|
||||||
// Linker Options:
|
|
||||||
#cgo windows LDFLAGS: -lgdi32
|
|
||||||
|
|
||||||
#cgo !gles2,windows LDFLAGS: -lopengl32
|
|
||||||
#cgo gles2,windows LDFLAGS: -lGLESv2
|
|
||||||
|
|
||||||
// Darwin Build Tags
|
|
||||||
// ----------------
|
|
||||||
// GLFW Options:
|
|
||||||
#cgo darwin CFLAGS: -D_GLFW_COCOA -Wno-deprecated-declarations
|
|
||||||
|
|
||||||
// Linker Options:
|
|
||||||
#cgo darwin LDFLAGS: -framework Cocoa -framework IOKit -framework CoreVideo
|
|
||||||
|
|
||||||
#cgo !gles2,darwin LDFLAGS: -framework OpenGL
|
|
||||||
#cgo gles2,darwin LDFLAGS: -lGLESv2
|
|
||||||
|
|
||||||
// Linux Build Tags
|
|
||||||
// ----------------
|
|
||||||
// GLFW Options:
|
|
||||||
#cgo linux,!wayland CFLAGS: -D_GLFW_X11 -D_GNU_SOURCE
|
|
||||||
#cgo linux,wayland CFLAGS: -D_GLFW_WAYLAND -D_GNU_SOURCE
|
|
||||||
|
|
||||||
// Linker Options:
|
|
||||||
#cgo linux,!gles1,!gles2,!gles3,!vulkan LDFLAGS: -lGL
|
|
||||||
#cgo linux,gles1 LDFLAGS: -lGLESv1
|
|
||||||
#cgo linux,gles2 LDFLAGS: -lGLESv2
|
|
||||||
#cgo linux,gles3 LDFLAGS: -lGLESv3
|
|
||||||
#cgo linux,vulkan LDFLAGS: -lvulkan
|
|
||||||
#cgo linux,!wayland LDFLAGS: -lX11 -lXrandr -lXxf86vm -lXi -lXcursor -lm -lXinerama -ldl -lrt
|
|
||||||
#cgo linux,wayland LDFLAGS: -lwayland-client -lwayland-cursor -lwayland-egl -lxkbcommon -lm -ldl -lrt
|
|
||||||
|
|
||||||
// FreeBSD Build Tags
|
|
||||||
// ----------------
|
|
||||||
// GLFW Options:
|
|
||||||
#cgo freebsd pkg-config: glfw3
|
|
||||||
#cgo freebsd CFLAGS: -D_GLFW_HAS_DLOPEN
|
|
||||||
#cgo freebsd,!wayland CFLAGS: -D_GLFW_X11 -D_GLFW_HAS_GLXGETPROCADDRESSARB
|
|
||||||
#cgo freebsd,wayland CFLAGS: -D_GLFW_WAYLAND
|
|
||||||
|
|
||||||
// Linker Options:
|
|
||||||
#cgo freebsd,!wayland LDFLAGS: -lm -lGL -lX11 -lXrandr -lXxf86vm -lXi -lXcursor -lXinerama
|
|
||||||
#cgo freebsd,wayland LDFLAGS: -lm -lGL -lwayland-client -lwayland-cursor -lwayland-egl -lxkbcommon
|
|
||||||
*/
|
|
||||||
import "C"
|
|
|
@ -1,18 +0,0 @@
|
||||||
// +build required
|
|
||||||
|
|
||||||
package glfw
|
|
||||||
|
|
||||||
// This file exists purely to prevent the golang toolchain from stripping
|
|
||||||
// away the c source directories and files when `go mod vendor` is used
|
|
||||||
// to populate a `vendor/` directory of a project depending on `go-gl/glfw`.
|
|
||||||
//
|
|
||||||
// How it works:
|
|
||||||
// - every directory which only includes c source files receives a dummy.go file.
|
|
||||||
// - every directory we want to preserve is included here as a _ import.
|
|
||||||
// - this file is given a build to exclude it from the regular build.
|
|
||||||
import (
|
|
||||||
// Prevent go tooling from stripping out the c source files.
|
|
||||||
_ "github.com/go-gl/glfw/v3.3/glfw/glfw/deps"
|
|
||||||
_ "github.com/go-gl/glfw/v3.3/glfw/glfw/include/GLFW"
|
|
||||||
_ "github.com/go-gl/glfw/v3.3/glfw/glfw/src"
|
|
||||||
)
|
|
|
@ -1,12 +0,0 @@
|
||||||
package glfw
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include "glfw/src/context.c"
|
|
||||||
#include "glfw/src/init.c"
|
|
||||||
#include "glfw/src/input.c"
|
|
||||||
#include "glfw/src/monitor.c"
|
|
||||||
#include "glfw/src/vulkan.c"
|
|
||||||
#include "glfw/src/window.c"
|
|
||||||
#include "glfw/src/osmesa_context.c"
|
|
||||||
*/
|
|
||||||
import "C"
|
|
|
@ -1,14 +0,0 @@
|
||||||
package glfw
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -x objective-c
|
|
||||||
#include "glfw/src/cocoa_init.m"
|
|
||||||
#include "glfw/src/cocoa_joystick.m"
|
|
||||||
#include "glfw/src/cocoa_monitor.m"
|
|
||||||
#include "glfw/src/cocoa_window.m"
|
|
||||||
#include "glfw/src/cocoa_time.c"
|
|
||||||
#include "glfw/src/posix_thread.c"
|
|
||||||
#include "glfw/src/nsgl_context.m"
|
|
||||||
#include "glfw/src/egl_context.c"
|
|
||||||
*/
|
|
||||||
import "C"
|
|
|
@ -1,29 +0,0 @@
|
||||||
// +build freebsd
|
|
||||||
|
|
||||||
package glfw
|
|
||||||
|
|
||||||
/*
|
|
||||||
#ifdef _GLFW_WAYLAND
|
|
||||||
#include "glfw/src/wl_init.c"
|
|
||||||
#include "glfw/src/wl_monitor.c"
|
|
||||||
#include "glfw/src/wl_window.c"
|
|
||||||
#include "glfw/src/wayland-idle-inhibit-unstable-v1-client-protocol.c"
|
|
||||||
#include "glfw/src/wayland-pointer-constraints-unstable-v1-client-protocol.c"
|
|
||||||
#include "glfw/src/wayland-relative-pointer-unstable-v1-client-protocol.c"
|
|
||||||
#include "glfw/src/wayland-viewporter-client-protocol.c"
|
|
||||||
#include "glfw/src/wayland-xdg-decoration-unstable-v1-client-protocol.c"
|
|
||||||
#include "glfw/src/wayland-xdg-shell-client-protocol.c"
|
|
||||||
#endif
|
|
||||||
#ifdef _GLFW_X11
|
|
||||||
#include "glfw/src/x11_init.c"
|
|
||||||
#include "glfw/src/x11_monitor.c"
|
|
||||||
#include "glfw/src/x11_window.c"
|
|
||||||
#include "glfw/src/glx_context.c"
|
|
||||||
#endif
|
|
||||||
#include "glfw/src/null_joystick.c"
|
|
||||||
#include "glfw/src/posix_time.c"
|
|
||||||
#include "glfw/src/posix_thread.c"
|
|
||||||
#include "glfw/src/xkb_unicode.c"
|
|
||||||
#include "glfw/src/egl_context.c"
|
|
||||||
*/
|
|
||||||
import "C"
|
|
|
@ -1,29 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package glfw
|
|
||||||
|
|
||||||
/*
|
|
||||||
#ifdef _GLFW_WAYLAND
|
|
||||||
#include "glfw/src/wl_init.c"
|
|
||||||
#include "glfw/src/wl_monitor.c"
|
|
||||||
#include "glfw/src/wl_window.c"
|
|
||||||
#include "glfw/src/wayland-idle-inhibit-unstable-v1-client-protocol.c"
|
|
||||||
#include "glfw/src/wayland-pointer-constraints-unstable-v1-client-protocol.c"
|
|
||||||
#include "glfw/src/wayland-relative-pointer-unstable-v1-client-protocol.c"
|
|
||||||
#include "glfw/src/wayland-viewporter-client-protocol.c"
|
|
||||||
#include "glfw/src/wayland-xdg-decoration-unstable-v1-client-protocol.c"
|
|
||||||
#include "glfw/src/wayland-xdg-shell-client-protocol.c"
|
|
||||||
#endif
|
|
||||||
#ifdef _GLFW_X11
|
|
||||||
#include "glfw/src/x11_init.c"
|
|
||||||
#include "glfw/src/x11_monitor.c"
|
|
||||||
#include "glfw/src/x11_window.c"
|
|
||||||
#include "glfw/src/glx_context.c"
|
|
||||||
#endif
|
|
||||||
#include "glfw/src/linux_joystick.c"
|
|
||||||
#include "glfw/src/posix_time.c"
|
|
||||||
#include "glfw/src/posix_thread.c"
|
|
||||||
#include "glfw/src/xkb_unicode.c"
|
|
||||||
#include "glfw/src/egl_context.c"
|
|
||||||
*/
|
|
||||||
import "C"
|
|
|
@ -1,13 +0,0 @@
|
||||||
package glfw
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include "glfw/src/win32_init.c"
|
|
||||||
#include "glfw/src/win32_joystick.c"
|
|
||||||
#include "glfw/src/win32_monitor.c"
|
|
||||||
#include "glfw/src/win32_time.c"
|
|
||||||
#include "glfw/src/win32_thread.c"
|
|
||||||
#include "glfw/src/win32_window.c"
|
|
||||||
#include "glfw/src/wgl_context.c"
|
|
||||||
#include "glfw/src/egl_context.c"
|
|
||||||
*/
|
|
||||||
import "C"
|
|
|
@ -1,94 +0,0 @@
|
||||||
package glfw
|
|
||||||
|
|
||||||
//#include <stdlib.h>
|
|
||||||
//#define GLFW_INCLUDE_NONE
|
|
||||||
//#include "glfw/include/GLFW/glfw3.h"
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MakeContextCurrent makes the context of the window current.
|
|
||||||
// Originally GLFW 3 passes a null pointer to detach the context.
|
|
||||||
// But since we're using receievers, DetachCurrentContext should
|
|
||||||
// be used instead.
|
|
||||||
func (w *Window) MakeContextCurrent() {
|
|
||||||
C.glfwMakeContextCurrent(w.data)
|
|
||||||
panicError()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DetachCurrentContext detaches the current context.
|
|
||||||
func DetachCurrentContext() {
|
|
||||||
C.glfwMakeContextCurrent(nil)
|
|
||||||
panicError()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCurrentContext returns the window whose context is current.
|
|
||||||
func GetCurrentContext() *Window {
|
|
||||||
w := C.glfwGetCurrentContext()
|
|
||||||
panicError()
|
|
||||||
if w == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return windows.get(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SwapBuffers swaps the front and back buffers of the window. If the
|
|
||||||
// swap interval is greater than zero, the GPU driver waits the specified number
|
|
||||||
// of screen updates before swapping the buffers.
|
|
||||||
func (w *Window) SwapBuffers() {
|
|
||||||
C.glfwSwapBuffers(w.data)
|
|
||||||
panicError()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SwapInterval sets the swap interval for the current context, i.e. the number
|
|
||||||
// of screen updates to wait before swapping the buffers of a window and
|
|
||||||
// returning from SwapBuffers. This is sometimes called
|
|
||||||
// 'vertical synchronization', 'vertical retrace synchronization' or 'vsync'.
|
|
||||||
//
|
|
||||||
// Contexts that support either of the WGL_EXT_swap_control_tear and
|
|
||||||
// GLX_EXT_swap_control_tear extensions also accept negative swap intervals,
|
|
||||||
// which allow the driver to swap even if a frame arrives a little bit late.
|
|
||||||
// You can check for the presence of these extensions using
|
|
||||||
// ExtensionSupported. For more information about swap tearing,
|
|
||||||
// see the extension specifications.
|
|
||||||
//
|
|
||||||
// Some GPU drivers do not honor the requested swap interval, either because of
|
|
||||||
// user settings that override the request or due to bugs in the driver.
|
|
||||||
func SwapInterval(interval int) {
|
|
||||||
C.glfwSwapInterval(C.int(interval))
|
|
||||||
panicError()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtensionSupported reports whether the specified OpenGL or context creation
|
|
||||||
// API extension is supported by the current context. For example, on Windows
|
|
||||||
// both the OpenGL and WGL extension strings are checked.
|
|
||||||
//
|
|
||||||
// As this functions searches one or more extension strings on each call, it is
|
|
||||||
// recommended that you cache its results if it's going to be used frequently.
|
|
||||||
// The extension strings will not change during the lifetime of a context, so
|
|
||||||
// there is no danger in doing this.
|
|
||||||
func ExtensionSupported(extension string) bool {
|
|
||||||
e := C.CString(extension)
|
|
||||||
defer C.free(unsafe.Pointer(e))
|
|
||||||
ret := glfwbool(C.glfwExtensionSupported(e))
|
|
||||||
panicError()
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProcAddress returns the address of the specified OpenGL or OpenGL ES core
|
|
||||||
// or extension function, if it is supported by the current context.
|
|
||||||
//
|
|
||||||
// A context must be current on the calling thread. Calling this function
|
|
||||||
// without a current context will cause a GLFW_NO_CURRENT_CONTEXT error.
|
|
||||||
//
|
|
||||||
// This function is used to provide GL proc resolving capabilities to an
|
|
||||||
// external C library.
|
|
||||||
func GetProcAddress(procname string) unsafe.Pointer {
|
|
||||||
p := C.CString(procname)
|
|
||||||
defer C.free(unsafe.Pointer(p))
|
|
||||||
ret := unsafe.Pointer(C.glfwGetProcAddress(p))
|
|
||||||
panicError()
|
|
||||||
return ret
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
#include "_cgo_export.h"
|
|
||||||
|
|
||||||
void glfwSetErrorCallbackCB() { glfwSetErrorCallback((GLFWerrorfun)goErrorCB); }
|
|
|
@ -1,200 +0,0 @@
|
||||||
package glfw
|
|
||||||
|
|
||||||
//#define GLFW_INCLUDE_NONE
|
|
||||||
//#include "glfw/include/GLFW/glfw3.h"
|
|
||||||
//void glfwSetErrorCallbackCB();
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrorCode corresponds to an error code.
|
|
||||||
type ErrorCode int
|
|
||||||
|
|
||||||
// Error codes that are translated to panics and the programmer should not
|
|
||||||
// expect to handle.
|
|
||||||
const (
|
|
||||||
notInitialized ErrorCode = C.GLFW_NOT_INITIALIZED // GLFW has not been initialized.
|
|
||||||
noCurrentContext ErrorCode = C.GLFW_NO_CURRENT_CONTEXT // No context is current.
|
|
||||||
invalidEnum ErrorCode = C.GLFW_INVALID_ENUM // One of the enum parameters for the function was given an invalid enum.
|
|
||||||
invalidValue ErrorCode = C.GLFW_INVALID_VALUE // One of the parameters for the function was given an invalid value.
|
|
||||||
outOfMemory ErrorCode = C.GLFW_OUT_OF_MEMORY // A memory allocation failed.
|
|
||||||
platformError ErrorCode = C.GLFW_PLATFORM_ERROR // A platform-specific error occurred that does not match any of the more specific categories.
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// APIUnavailable is the error code used when GLFW could not find support
|
|
||||||
// for the requested client API on the system.
|
|
||||||
//
|
|
||||||
// The installed graphics driver does not support the requested client API,
|
|
||||||
// or does not support it via the chosen context creation backend. Below
|
|
||||||
// are a few examples.
|
|
||||||
//
|
|
||||||
// Some pre-installed Windows graphics drivers do not support OpenGL. AMD
|
|
||||||
// only supports OpenGL ES via EGL, while Nvidia and Intel only supports it
|
|
||||||
// via a WGL or GLX extension. OS X does not provide OpenGL ES at all. The
|
|
||||||
// Mesa EGL, OpenGL and OpenGL ES libraries do not interface with the
|
|
||||||
// Nvidia binary driver.
|
|
||||||
APIUnavailable ErrorCode = C.GLFW_API_UNAVAILABLE
|
|
||||||
|
|
||||||
// VersionUnavailable is the error code used when the requested OpenGL or
|
|
||||||
// OpenGL ES (including any requested profile or context option) is not
|
|
||||||
// available on this machine.
|
|
||||||
//
|
|
||||||
// The machine does not support your requirements. If your application is
|
|
||||||
// sufficiently flexible, downgrade your requirements and try again.
|
|
||||||
// Otherwise, inform the user that their machine does not match your
|
|
||||||
// requirements.
|
|
||||||
//
|
|
||||||
// Future invalid OpenGL and OpenGL ES versions, for example OpenGL 4.8 if
|
|
||||||
// 5.0 comes out before the 4.x series gets that far, also fail with this
|
|
||||||
// error and not GLFW_INVALID_VALUE, because GLFW cannot know what future
|
|
||||||
// versions will exist.
|
|
||||||
VersionUnavailable ErrorCode = C.GLFW_VERSION_UNAVAILABLE
|
|
||||||
|
|
||||||
// FormatUnavailable is the error code used for both window creation and
|
|
||||||
// clipboard querying format errors.
|
|
||||||
//
|
|
||||||
// If emitted during window creation, the requested pixel format is not
|
|
||||||
// supported. This means one or more hard constraints did not match any of
|
|
||||||
// the available pixel formats. If your application is sufficiently
|
|
||||||
// flexible, downgrade your requirements and try again. Otherwise, inform
|
|
||||||
// the user that their machine does not match your requirements.
|
|
||||||
//
|
|
||||||
// If emitted when querying the clipboard, the contents of the clipboard
|
|
||||||
// could not be converted to the requested format. You should ignore the
|
|
||||||
// error or report it to the user, as appropriate.
|
|
||||||
FormatUnavailable ErrorCode = C.GLFW_FORMAT_UNAVAILABLE
|
|
||||||
)
|
|
||||||
|
|
||||||
func (e ErrorCode) String() string {
|
|
||||||
switch e {
|
|
||||||
case notInitialized:
|
|
||||||
return "NotInitialized"
|
|
||||||
case noCurrentContext:
|
|
||||||
return "NoCurrentContext"
|
|
||||||
case invalidEnum:
|
|
||||||
return "InvalidEnum"
|
|
||||||
case invalidValue:
|
|
||||||
return "InvalidValue"
|
|
||||||
case outOfMemory:
|
|
||||||
return "OutOfMemory"
|
|
||||||
case platformError:
|
|
||||||
return "PlatformError"
|
|
||||||
case APIUnavailable:
|
|
||||||
return "APIUnavailable"
|
|
||||||
case VersionUnavailable:
|
|
||||||
return "VersionUnavailable"
|
|
||||||
case FormatUnavailable:
|
|
||||||
return "FormatUnavailable"
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("ErrorCode(%d)", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error holds error code and description.
|
|
||||||
type Error struct {
|
|
||||||
Code ErrorCode
|
|
||||||
Desc string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error prints the error code and description in a readable format.
|
|
||||||
func (e *Error) Error() string {
|
|
||||||
return fmt.Sprintf("%s: %s", e.Code.String(), e.Desc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: There are many cryptic caveats to proper error handling here.
|
|
||||||
// See: https://github.com/go-gl/glfw3/pull/86
|
|
||||||
|
|
||||||
// Holds the value of the last error.
|
|
||||||
var lastError = make(chan *Error, 1)
|
|
||||||
|
|
||||||
//export goErrorCB
|
|
||||||
func goErrorCB(code C.int, desc *C.char) {
|
|
||||||
flushErrors()
|
|
||||||
err := &Error{ErrorCode(code), C.GoString(desc)}
|
|
||||||
select {
|
|
||||||
case lastError <- err:
|
|
||||||
default:
|
|
||||||
fmt.Println("GLFW: An uncaught error has occurred:", err)
|
|
||||||
fmt.Println("GLFW: Please report this bug in the Go package immediately.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the glfw callback internally
|
|
||||||
func init() {
|
|
||||||
C.glfwSetErrorCallbackCB()
|
|
||||||
}
|
|
||||||
|
|
||||||
// flushErrors is called by Terminate before it actually calls C.glfwTerminate,
|
|
||||||
// this ensures that any uncaught errors buffered in lastError are printed
|
|
||||||
// before the program exits.
|
|
||||||
func flushErrors() {
|
|
||||||
err := fetchError()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("GLFW: An uncaught error has occurred:", err)
|
|
||||||
fmt.Println("GLFW: Please report this bug in the Go package immediately.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// acceptError fetches the next error from the error channel, it accepts only
|
|
||||||
// errors with one of the given error codes. If any other error is encountered,
|
|
||||||
// a panic will occur.
|
|
||||||
//
|
|
||||||
// Platform errors are always printed, for information why please see:
|
|
||||||
//
|
|
||||||
// https://github.com/go-gl/glfw/issues/127
|
|
||||||
//
|
|
||||||
func acceptError(codes ...ErrorCode) error {
|
|
||||||
// Grab the next error, if there is one.
|
|
||||||
err := fetchError()
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only if the error has the specific error code accepted by the caller, do
|
|
||||||
// we return the error.
|
|
||||||
for _, code := range codes {
|
|
||||||
if err.Code == code {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The error isn't accepted by the caller. If the error code is not a code
|
|
||||||
// defined in the GLFW C documentation as a programmer error, then the
|
|
||||||
// caller should have accepted it. This is effectively a bug in this
|
|
||||||
// package.
|
|
||||||
switch err.Code {
|
|
||||||
case platformError:
|
|
||||||
log.Println(err)
|
|
||||||
return nil
|
|
||||||
case notInitialized, noCurrentContext, invalidEnum, invalidValue, outOfMemory:
|
|
||||||
panic(err)
|
|
||||||
default:
|
|
||||||
fmt.Println("GLFW: An invalid error was not accepted by the caller:", err)
|
|
||||||
fmt.Println("GLFW: Please report this bug in the Go package immediately.")
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// panicError is a helper used by functions which expect no errors (except
|
|
||||||
// programmer errors) to occur. It will panic if it finds any such error.
|
|
||||||
func panicError() {
|
|
||||||
err := acceptError()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchError fetches the next error from the error channel, it does not block
|
|
||||||
// and returns nil if there is no error present.
|
|
||||||
func fetchError() *Error {
|
|
||||||
select {
|
|
||||||
case err := <-lastError:
|
|
||||||
return err
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,125 +0,0 @@
|
||||||
package glfw
|
|
||||||
|
|
||||||
//#include <stdlib.h>
|
|
||||||
//#define GLFW_INCLUDE_NONE
|
|
||||||
//#include "glfw/include/GLFW/glfw3.h"
|
|
||||||
import "C"
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
// Version constants.
|
|
||||||
const (
|
|
||||||
VersionMajor = C.GLFW_VERSION_MAJOR // This is incremented when the API is changed in non-compatible ways.
|
|
||||||
VersionMinor = C.GLFW_VERSION_MINOR // This is incremented when features are added to the API but it remains backward-compatible.
|
|
||||||
VersionRevision = C.GLFW_VERSION_REVISION // This is incremented when a bug fix release is made that does not contain any API changes.
|
|
||||||
)
|
|
||||||
|
|
||||||
// Init initializes the GLFW library. Before most GLFW functions can be used,
|
|
||||||
// GLFW must be initialized, and before a program terminates GLFW should be
|
|
||||||
// terminated in order to free any resources allocated during or after
|
|
||||||
// initialization.
|
|
||||||
//
|
|
||||||
// If this function fails, it calls Terminate before returning. If it succeeds,
|
|
||||||
// you should call Terminate before the program exits.
|
|
||||||
//
|
|
||||||
// Additional calls to this function after successful initialization but before
|
|
||||||
// termination will succeed but will do nothing.
|
|
||||||
//
|
|
||||||
// This function may take several seconds to complete on some systems, while on
|
|
||||||
// other systems it may take only a fraction of a second to complete.
|
|
||||||
//
|
|
||||||
// On Mac OS X, this function will change the current directory of the
|
|
||||||
// application to the Contents/Resources subdirectory of the application's
|
|
||||||
// bundle, if present.
|
|
||||||
//
|
|
||||||
// This function may only be called from the main thread.
|
|
||||||
func Init() error {
|
|
||||||
C.glfwInit()
|
|
||||||
// invalidValue can happen when specific joysticks are used. This issue
|
|
||||||
// will be fixed in GLFW 3.3.5. As a temporary fix, ignore this error.
|
|
||||||
// See go-gl/glfw#292, go-gl/glfw#324, and glfw/glfw#1763.
|
|
||||||
err := acceptError(APIUnavailable, invalidValue)
|
|
||||||
if e, ok := err.(*Error); ok && e.Code == invalidValue {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Terminate destroys all remaining windows, frees any allocated resources and
|
|
||||||
// sets the library to an uninitialized state. Once this is called, you must
|
|
||||||
// again call Init successfully before you will be able to use most GLFW
|
|
||||||
// functions.
|
|
||||||
//
|
|
||||||
// If GLFW has been successfully initialized, this function should be called
|
|
||||||
// before the program exits. If initialization fails, there is no need to call
|
|
||||||
// this function, as it is called by Init before it returns failure.
|
|
||||||
//
|
|
||||||
// This function may only be called from the main thread.
|
|
||||||
func Terminate() {
|
|
||||||
flushErrors()
|
|
||||||
C.glfwTerminate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitHint function sets hints for the next initialization of GLFW.
|
|
||||||
//
|
|
||||||
// The values you set hints to are never reset by GLFW, but they only take
|
|
||||||
// effect during initialization. Once GLFW has been initialized, any values you
|
|
||||||
// set will be ignored until the library is terminated and initialized again.
|
|
||||||
//
|
|
||||||
// Some hints are platform specific. These may be set on any platform but they
|
|
||||||
// will only affect their specific platform. Other platforms will ignore them.
|
|
||||||
// Setting these hints requires no platform specific headers or functions.
|
|
||||||
//
|
|
||||||
// This function must only be called from the main thread.
|
|
||||||
func InitHint(hint Hint, value int) {
|
|
||||||
C.glfwInitHint(C.int(hint), C.int(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVersion retrieves the major, minor and revision numbers of the GLFW
|
|
||||||
// library. It is intended for when you are using GLFW as a shared library and
|
|
||||||
// want to ensure that you are using the minimum required version.
|
|
||||||
//
|
|
||||||
// This function may be called before Init.
|
|
||||||
func GetVersion() (major, minor, revision int) {
|
|
||||||
var (
|
|
||||||
maj C.int
|
|
||||||
min C.int
|
|
||||||
rev C.int
|
|
||||||
)
|
|
||||||
|
|
||||||
C.glfwGetVersion(&maj, &min, &rev)
|
|
||||||
return int(maj), int(min), int(rev)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVersionString returns a static string generated at compile-time according
|
|
||||||
// to which configuration macros were defined. This is intended for use when
|
|
||||||
// submitting bug reports, to allow developers to see which code paths are
|
|
||||||
// enabled in a binary.
|
|
||||||
//
|
|
||||||
// This function may be called before Init.
|
|
||||||
func GetVersionString() string {
|
|
||||||
return C.GoString(C.glfwGetVersionString())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetClipboardString returns the contents of the system clipboard, if it
|
|
||||||
// contains or is convertible to a UTF-8 encoded string.
|
|
||||||
//
|
|
||||||
// This function may only be called from the main thread.
|
|
||||||
func GetClipboardString() string {
|
|
||||||
cs := C.glfwGetClipboardString(nil)
|
|
||||||
if cs == nil {
|
|
||||||
acceptError(FormatUnavailable)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return C.GoString(cs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetClipboardString sets the system clipboard to the specified UTF-8 encoded
|
|
||||||
// string.
|
|
||||||
//
|
|
||||||
// This function may only be called from the main thread.
|
|
||||||
func SetClipboardString(str string) {
|
|
||||||
cp := C.CString(str)
|
|
||||||
defer C.free(unsafe.Pointer(cp))
|
|
||||||
C.glfwSetClipboardString(nil, cp)
|
|
||||||
panicError()
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
Copyright (c) 2002-2006 Marcus Geelnard
|
|
||||||
|
|
||||||
Copyright (c) 2006-2019 Camilla Löwy
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the authors be held liable for any damages
|
|
||||||
arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software
|
|
||||||
in a product, an acknowledgment in the product documentation would
|
|
||||||
be appreciated but is not required.
|
|
||||||
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not
|
|
||||||
be misrepresented as being the original software.
|
|
||||||
|
|
||||||
3. This notice may not be removed or altered from any source
|
|
||||||
distribution.
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
// +build required
|
|
||||||
|
|
||||||
// Package dummy prevents go tooling from stripping the c dependencies.
|
|
||||||
package dummy
|
|
||||||
|
|
||||||
import (
|
|
||||||
// Prevent go tooling from stripping out the c source files.
|
|
||||||
_ "github.com/go-gl/glfw/v3.3/glfw/glfw/deps/glad"
|
|
||||||
_ "github.com/go-gl/glfw/v3.3/glfw/glfw/deps/mingw"
|
|
||||||
_ "github.com/go-gl/glfw/v3.3/glfw/glfw/deps/vs2008"
|
|
||||||
)
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue