pax_global_header 0000666 0000000 0000000 00000000064 12572222044 0014512 g ustar 00root root 0000000 0000000 52 comment=eb7c1fa4d21cad64754b3d847ab08550c4c68529
golang-golang-x-exp_eb7c1fa/ 0000775 0000000 0000000 00000000000 12572222044 0016121 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/.gitattributes 0000664 0000000 0000000 00000000531 12572222044 0021013 0 ustar 00root root 0000000 0000000 # Treat all files in this repo as binary, with no git magic updating
# line endings. Windows users contributing to Go will need to use a
# modern version of git and editors capable of LF line endings.
#
# We'll prevent accidental CRLF line endings from entering the repo
# via the git-review gofmt checks.
#
# See golang.org/issue/9281
* -text
golang-golang-x-exp_eb7c1fa/.gitignore 0000664 0000000 0000000 00000000124 12572222044 0020106 0 ustar 00root root 0000000 0000000 # Add no patterns to .hgignore except for files generated by the build.
last-change
golang-golang-x-exp_eb7c1fa/AUTHORS 0000664 0000000 0000000 00000000255 12572222044 0017173 0 ustar 00root root 0000000 0000000 # This source code refers to The Go Authors for copyright purposes.
# The master list of authors is in the main Go distribution,
# visible at http://tip.golang.org/AUTHORS.
golang-golang-x-exp_eb7c1fa/CONTRIBUTING.md 0000664 0000000 0000000 00000002007 12572222044 0020351 0 ustar 00root root 0000000 0000000 # Contributing to Go
Go is an open source project.
It is the work of hundreds of contributors. We appreciate your help!
## Filing issues
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
1. What version of Go are you using (`go version`)?
2. What operating system and processor architecture are you using?
3. What did you do?
4. What did you expect to see?
5. What did you see instead?
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
## Contributing code
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
before sending patches.
**We do not accept GitHub pull requests**
(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review).
Unless otherwise noted, the Go source files are distributed under
the BSD-style license found in the LICENSE file.
golang-golang-x-exp_eb7c1fa/CONTRIBUTORS 0000664 0000000 0000000 00000000252 12572222044 0020000 0 ustar 00root root 0000000 0000000 # This source code was written by the Go contributors.
# The master list of contributors is in the main Go distribution,
# visible at http://tip.golang.org/CONTRIBUTORS.
golang-golang-x-exp_eb7c1fa/LICENSE 0000664 0000000 0000000 00000002707 12572222044 0017134 0 ustar 00root root 0000000 0000000 Copyright (c) 2009 The 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.
golang-golang-x-exp_eb7c1fa/PATENTS 0000664 0000000 0000000 00000002427 12572222044 0017167 0 ustar 00root root 0000000 0000000 Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
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,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.
golang-golang-x-exp_eb7c1fa/README 0000664 0000000 0000000 00000001406 12572222044 0017002 0 ustar 00root root 0000000 0000000 This subrepository holds experimental and deprecated (in the "old"
directory) packages.
The idea for this subrepository originated as the "pkg/exp" directory
of the main repository, but its presence there made it unavailable
to users of the binary downloads of the Go installation. The
subrepository has therefore been created to make it possible to "go
get" these packages.
Warning: Packages here are experimental and unreliable. Some may
one day be promoted to the main repository or other subrepository,
or they may be modified arbitrarily or even disappear altogether.
In short, code in this subrepository is not subject to the Go 1
compatibility promise. (No subrepo is, but the promise is even more
likely to be violated by go.exp than the others.)
Caveat emptor.
golang-golang-x-exp_eb7c1fa/codereview.cfg 0000664 0000000 0000000 00000000025 12572222044 0020733 0 ustar 00root root 0000000 0000000 issuerepo: golang/go
golang-golang-x-exp_eb7c1fa/ebnf/ 0000775 0000000 0000000 00000000000 12572222044 0017033 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/ebnf/ebnf.go 0000664 0000000 0000000 00000016117 12572222044 0020302 0 ustar 00root root 0000000 0000000 // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package ebnf is a library for EBNF grammars. The input is text ([]byte)
// satisfying the following grammar (represented itself in EBNF):
//
// Production = name "=" [ Expression ] "." .
// Expression = Alternative { "|" Alternative } .
// Alternative = Term { Term } .
// Term = name | token [ "…" token ] | Group | Option | Repetition .
// Group = "(" Expression ")" .
// Option = "[" Expression "]" .
// Repetition = "{" Expression "}" .
//
// A name is a Go identifier, a token is a Go string, and comments
// and white space follow the same rules as for the Go language.
// Production names starting with an uppercase Unicode letter denote
// non-terminal productions (i.e., productions which allow white-space
// and comments between tokens); all other production names denote
// lexical productions.
//
package ebnf // import "golang.org/x/exp/ebnf"
import (
"errors"
"fmt"
"text/scanner"
"unicode"
"unicode/utf8"
)
// ----------------------------------------------------------------------------
// Error handling
type errorList []error
func (list errorList) Err() error {
if len(list) == 0 {
return nil
}
return list
}
func (list errorList) Error() string {
switch len(list) {
case 0:
return "no errors"
case 1:
return list[0].Error()
}
return fmt.Sprintf("%s (and %d more errors)", list[0], len(list)-1)
}
func newError(pos scanner.Position, msg string) error {
return errors.New(fmt.Sprintf("%s: %s", pos, msg))
}
// ----------------------------------------------------------------------------
// Internal representation
type (
// An Expression node represents a production expression.
Expression interface {
// Pos is the position of the first character of the syntactic construct
Pos() scanner.Position
}
// An Alternative node represents a non-empty list of alternative expressions.
Alternative []Expression // x | y | z
// A Sequence node represents a non-empty list of sequential expressions.
Sequence []Expression // x y z
// A Name node represents a production name.
Name struct {
StringPos scanner.Position
String string
}
// A Token node represents a literal.
Token struct {
StringPos scanner.Position
String string
}
// A List node represents a range of characters.
Range struct {
Begin, End *Token // begin ... end
}
// A Group node represents a grouped expression.
Group struct {
Lparen scanner.Position
Body Expression // (body)
}
// An Option node represents an optional expression.
Option struct {
Lbrack scanner.Position
Body Expression // [body]
}
// A Repetition node represents a repeated expression.
Repetition struct {
Lbrace scanner.Position
Body Expression // {body}
}
// A Production node represents an EBNF production.
Production struct {
Name *Name
Expr Expression
}
// A Bad node stands for pieces of source code that lead to a parse error.
Bad struct {
TokPos scanner.Position
Error string // parser error message
}
// A Grammar is a set of EBNF productions. The map
// is indexed by production name.
//
Grammar map[string]*Production
)
func (x Alternative) Pos() scanner.Position { return x[0].Pos() } // the parser always generates non-empty Alternative
func (x Sequence) Pos() scanner.Position { return x[0].Pos() } // the parser always generates non-empty Sequences
func (x *Name) Pos() scanner.Position { return x.StringPos }
func (x *Token) Pos() scanner.Position { return x.StringPos }
func (x *Range) Pos() scanner.Position { return x.Begin.Pos() }
func (x *Group) Pos() scanner.Position { return x.Lparen }
func (x *Option) Pos() scanner.Position { return x.Lbrack }
func (x *Repetition) Pos() scanner.Position { return x.Lbrace }
func (x *Production) Pos() scanner.Position { return x.Name.Pos() }
func (x *Bad) Pos() scanner.Position { return x.TokPos }
// ----------------------------------------------------------------------------
// Grammar verification
func isLexical(name string) bool {
ch, _ := utf8.DecodeRuneInString(name)
return !unicode.IsUpper(ch)
}
type verifier struct {
errors errorList
worklist []*Production
reached Grammar // set of productions reached from (and including) the root production
grammar Grammar
}
func (v *verifier) error(pos scanner.Position, msg string) {
v.errors = append(v.errors, newError(pos, msg))
}
func (v *verifier) push(prod *Production) {
name := prod.Name.String
if _, found := v.reached[name]; !found {
v.worklist = append(v.worklist, prod)
v.reached[name] = prod
}
}
func (v *verifier) verifyChar(x *Token) rune {
s := x.String
if utf8.RuneCountInString(s) != 1 {
v.error(x.Pos(), "single char expected, found "+s)
return 0
}
ch, _ := utf8.DecodeRuneInString(s)
return ch
}
func (v *verifier) verifyExpr(expr Expression, lexical bool) {
switch x := expr.(type) {
case nil:
// empty expression
case Alternative:
for _, e := range x {
v.verifyExpr(e, lexical)
}
case Sequence:
for _, e := range x {
v.verifyExpr(e, lexical)
}
case *Name:
// a production with this name must exist;
// add it to the worklist if not yet processed
if prod, found := v.grammar[x.String]; found {
v.push(prod)
} else {
v.error(x.Pos(), "missing production "+x.String)
}
// within a lexical production references
// to non-lexical productions are invalid
if lexical && !isLexical(x.String) {
v.error(x.Pos(), "reference to non-lexical production "+x.String)
}
case *Token:
// nothing to do for now
case *Range:
i := v.verifyChar(x.Begin)
j := v.verifyChar(x.End)
if i >= j {
v.error(x.Pos(), "decreasing character range")
}
case *Group:
v.verifyExpr(x.Body, lexical)
case *Option:
v.verifyExpr(x.Body, lexical)
case *Repetition:
v.verifyExpr(x.Body, lexical)
case *Bad:
v.error(x.Pos(), x.Error)
default:
panic(fmt.Sprintf("internal error: unexpected type %T", expr))
}
}
func (v *verifier) verify(grammar Grammar, start string) {
// find root production
root, found := grammar[start]
if !found {
var noPos scanner.Position
v.error(noPos, "no start production "+start)
return
}
// initialize verifier
v.worklist = v.worklist[0:0]
v.reached = make(Grammar)
v.grammar = grammar
// work through the worklist
v.push(root)
for {
n := len(v.worklist) - 1
if n < 0 {
break
}
prod := v.worklist[n]
v.worklist = v.worklist[0:n]
v.verifyExpr(prod.Expr, isLexical(prod.Name.String))
}
// check if all productions were reached
if len(v.reached) < len(v.grammar) {
for name, prod := range v.grammar {
if _, found := v.reached[name]; !found {
v.error(prod.Pos(), name+" is unreachable")
}
}
}
}
// Verify checks that:
// - all productions used are defined
// - all productions defined are used when beginning at start
// - lexical productions refer only to other lexical productions
//
// Position information is interpreted relative to the file set fset.
//
func Verify(grammar Grammar, start string) error {
var v verifier
v.verify(grammar, start)
return v.errors.Err()
}
golang-golang-x-exp_eb7c1fa/ebnf/ebnf_test.go 0000664 0000000 0000000 00000002465 12572222044 0021342 0 ustar 00root root 0000000 0000000 // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ebnf
import (
"bytes"
"testing"
)
var goodGrammars = []string{
`Program = .`,
`Program = foo .
foo = "foo" .`,
`Program = "a" | "b" "c" .`,
`Program = "a" … "z" .`,
`Program = Song .
Song = { Note } .
Note = Do | (Re | Mi | Fa | So | La) | Ti .
Do = "c" .
Re = "d" .
Mi = "e" .
Fa = "f" .
So = "g" .
La = "a" .
Ti = ti .
ti = "b" .`,
}
var badGrammars = []string{
`Program = | .`,
`Program = | b .`,
`Program = a … b .`,
`Program = "a" … .`,
`Program = … "b" .`,
`Program = () .`,
`Program = [] .`,
`Program = {} .`,
}
func checkGood(t *testing.T, src string) {
grammar, err := Parse("", bytes.NewBuffer([]byte(src)))
if err != nil {
t.Errorf("Parse(%s) failed: %v", src, err)
return
}
if err = Verify(grammar, "Program"); err != nil {
t.Errorf("Verify(%s) failed: %v", src, err)
}
}
func checkBad(t *testing.T, src string) {
_, err := Parse("", bytes.NewBuffer([]byte(src)))
if err == nil {
t.Errorf("Parse(%s) should have failed", src)
}
}
func TestGrammars(t *testing.T) {
for _, src := range goodGrammars {
checkGood(t, src)
}
for _, src := range badGrammars {
checkBad(t, src)
}
}
golang-golang-x-exp_eb7c1fa/ebnf/parser.go 0000664 0000000 0000000 00000007610 12572222044 0020662 0 ustar 00root root 0000000 0000000 // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ebnf
import (
"io"
"strconv"
"text/scanner"
)
type parser struct {
errors errorList
scanner scanner.Scanner
pos scanner.Position // token position
tok rune // one token look-ahead
lit string // token literal
}
func (p *parser) next() {
p.tok = p.scanner.Scan()
p.pos = p.scanner.Position
p.lit = p.scanner.TokenText()
}
func (p *parser) error(pos scanner.Position, msg string) {
p.errors = append(p.errors, newError(pos, msg))
}
func (p *parser) errorExpected(pos scanner.Position, msg string) {
msg = `expected "` + msg + `"`
if pos.Offset == p.pos.Offset {
// the error happened at the current position;
// make the error message more specific
msg += ", found " + scanner.TokenString(p.tok)
if p.tok < 0 {
msg += " " + p.lit
}
}
p.error(pos, msg)
}
func (p *parser) expect(tok rune) scanner.Position {
pos := p.pos
if p.tok != tok {
p.errorExpected(pos, scanner.TokenString(tok))
}
p.next() // make progress in any case
return pos
}
func (p *parser) parseIdentifier() *Name {
pos := p.pos
name := p.lit
p.expect(scanner.Ident)
return &Name{pos, name}
}
func (p *parser) parseToken() *Token {
pos := p.pos
value := ""
if p.tok == scanner.String {
value, _ = strconv.Unquote(p.lit)
// Unquote may fail with an error, but only if the scanner found
// an illegal string in the first place. In this case the error
// has already been reported.
p.next()
} else {
p.expect(scanner.String)
}
return &Token{pos, value}
}
// ParseTerm returns nil if no term was found.
func (p *parser) parseTerm() (x Expression) {
pos := p.pos
switch p.tok {
case scanner.Ident:
x = p.parseIdentifier()
case scanner.String:
tok := p.parseToken()
x = tok
const ellipsis = '…' // U+2026, the horizontal ellipsis character
if p.tok == ellipsis {
p.next()
x = &Range{tok, p.parseToken()}
}
case '(':
p.next()
x = &Group{pos, p.parseExpression()}
p.expect(')')
case '[':
p.next()
x = &Option{pos, p.parseExpression()}
p.expect(']')
case '{':
p.next()
x = &Repetition{pos, p.parseExpression()}
p.expect('}')
}
return x
}
func (p *parser) parseSequence() Expression {
var list Sequence
for x := p.parseTerm(); x != nil; x = p.parseTerm() {
list = append(list, x)
}
// no need for a sequence if list.Len() < 2
switch len(list) {
case 0:
p.errorExpected(p.pos, "term")
return &Bad{p.pos, "term expected"}
case 1:
return list[0]
}
return list
}
func (p *parser) parseExpression() Expression {
var list Alternative
for {
list = append(list, p.parseSequence())
if p.tok != '|' {
break
}
p.next()
}
// len(list) > 0
// no need for an Alternative node if list.Len() < 2
if len(list) == 1 {
return list[0]
}
return list
}
func (p *parser) parseProduction() *Production {
name := p.parseIdentifier()
p.expect('=')
var expr Expression
if p.tok != '.' {
expr = p.parseExpression()
}
p.expect('.')
return &Production{name, expr}
}
func (p *parser) parse(filename string, src io.Reader) Grammar {
p.scanner.Init(src)
p.scanner.Filename = filename
p.next() // initializes pos, tok, lit
grammar := make(Grammar)
for p.tok != scanner.EOF {
prod := p.parseProduction()
name := prod.Name.String
if _, found := grammar[name]; !found {
grammar[name] = prod
} else {
p.error(prod.Pos(), name+" declared already")
}
}
return grammar
}
// Parse parses a set of EBNF productions from source src.
// It returns a set of productions. Errors are reported
// for incorrect syntax and if a production is declared
// more than once; the filename is used only for error
// positions.
//
func Parse(filename string, src io.Reader) (Grammar, error) {
var p parser
grammar := p.parse(filename, src)
return grammar, p.errors.Err()
}
golang-golang-x-exp_eb7c1fa/ebnflint/ 0000775 0000000 0000000 00000000000 12572222044 0017722 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/ebnflint/doc.go 0000664 0000000 0000000 00000001162 12572222044 0021016 0 ustar 00root root 0000000 0000000 // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Ebnflint verifies that EBNF productions are consistent and grammatically correct.
It reads them from an HTML document such as the Go specification.
Grammar productions are grouped in boxes demarcated by the HTML elements
Usage:
go tool ebnflint [--start production] [file]
The --start flag specifies the name of the start production for
the grammar; it defaults to "Start".
*/
package main // import "golang.org/x/exp/ebnflint"
golang-golang-x-exp_eb7c1fa/ebnflint/ebnflint.go 0000664 0000000 0000000 00000004034 12572222044 0022053 0 ustar 00root root 0000000 0000000 // Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"flag"
"fmt"
"go/scanner"
"go/token"
"io"
"io/ioutil"
"os"
"path/filepath"
"golang.org/x/exp/ebnf"
)
var fset = token.NewFileSet()
var start = flag.String("start", "Start", "name of start production")
func usage() {
fmt.Fprintf(os.Stderr, "usage: go tool ebnflint [flags] [filename]\n")
flag.PrintDefaults()
os.Exit(1)
}
// Markers around EBNF sections in .html files
var (
open = []byte(``)
close = []byte(`
`)
)
func report(err error) {
scanner.PrintError(os.Stderr, err)
os.Exit(1)
}
func extractEBNF(src []byte) []byte {
var buf bytes.Buffer
for {
// i = beginning of EBNF text
i := bytes.Index(src, open)
if i < 0 {
break // no EBNF found - we are done
}
i += len(open)
// write as many newlines as found in the excluded text
// to maintain correct line numbers in error messages
for _, ch := range src[0:i] {
if ch == '\n' {
buf.WriteByte('\n')
}
}
// j = end of EBNF text (or end of source)
j := bytes.Index(src[i:], close) // close marker
if j < 0 {
j = len(src) - i
}
j += i
// copy EBNF text
buf.Write(src[i:j])
// advance
src = src[j:]
}
return buf.Bytes()
}
func main() {
flag.Parse()
var (
name string
r io.Reader
)
switch flag.NArg() {
case 0:
name, r = "", os.Stdin
case 1:
name = flag.Arg(0)
default:
usage()
}
if err := verify(name, *start, r); err != nil {
report(err)
}
}
func verify(name, start string, r io.Reader) error {
if r == nil {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
r = f
}
src, err := ioutil.ReadAll(r)
if err != nil {
return err
}
if filepath.Ext(name) == ".html" || bytes.Index(src, open) >= 0 {
src = extractEBNF(src)
}
grammar, err := ebnf.Parse(name, bytes.NewBuffer(src))
if err != nil {
return err
}
return ebnf.Verify(grammar, start)
}
golang-golang-x-exp_eb7c1fa/ebnflint/ebnflint_test.go 0000664 0000000 0000000 00000000534 12572222044 0023113 0 ustar 00root root 0000000 0000000 // Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"runtime"
"testing"
)
func TestSpec(t *testing.T) {
if err := verify(runtime.GOROOT()+"/doc/go_spec.html", "SourceFile", nil); err != nil {
t.Fatal(err)
}
}
golang-golang-x-exp_eb7c1fa/fsnotify/ 0000775 0000000 0000000 00000000000 12572222044 0017762 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/fsnotify/README.txt 0000664 0000000 0000000 00000000122 12572222044 0021453 0 ustar 00root root 0000000 0000000 Please use gopkg.in/fsnotify.v0 instead.
For updates, see: https://fsnotify.org/
golang-golang-x-exp_eb7c1fa/inotify/ 0000775 0000000 0000000 00000000000 12572222044 0017602 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/inotify/inotify_linux.go 0000664 0000000 0000000 00000020321 12572222044 0023027 0 ustar 00root root 0000000 0000000 // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package inotify implements a wrapper for the Linux inotify system.
Example:
watcher, err := inotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
err = watcher.Watch("/tmp")
if err != nil {
log.Fatal(err)
}
for {
select {
case ev := <-watcher.Event:
log.Println("event:", ev)
case err := <-watcher.Error:
log.Println("error:", err)
}
}
*/
package inotify // import "golang.org/x/exp/inotify"
import (
"errors"
"fmt"
"os"
"strings"
"sync"
"syscall"
"unsafe"
)
type Event struct {
Mask uint32 // Mask of events
Cookie uint32 // Unique cookie associating related events (for rename(2))
Name string // File name (optional)
}
type watch struct {
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}
type Watcher struct {
mu sync.Mutex
fd int // File descriptor (as returned by the inotify_init() syscall)
watches map[string]*watch // Map of inotify watches (key: path)
paths map[int]string // Map of watched paths (key: watch descriptor)
Error chan error // Errors are sent on this channel
Event chan *Event // Events are returned on this channel
done chan bool // Channel for sending a "quit message" to the reader goroutine
isClosed bool // Set to true when Close() is first called
}
// NewWatcher creates and returns a new inotify instance using inotify_init(2)
func NewWatcher() (*Watcher, error) {
fd, errno := syscall.InotifyInit()
if fd == -1 {
return nil, os.NewSyscallError("inotify_init", errno)
}
w := &Watcher{
fd: fd,
watches: make(map[string]*watch),
paths: make(map[int]string),
Event: make(chan *Event),
Error: make(chan error),
done: make(chan bool, 1),
}
go w.readEvents()
return w, nil
}
// Close closes an inotify watcher instance
// It sends a message to the reader goroutine to quit and removes all watches
// associated with the inotify instance
func (w *Watcher) Close() error {
if w.isClosed {
return nil
}
w.isClosed = true
// Send "quit" message to the reader goroutine
w.done <- true
for path := range w.watches {
w.RemoveWatch(path)
}
return nil
}
// AddWatch adds path to the watched file set.
// The flags are interpreted as described in inotify_add_watch(2).
func (w *Watcher) AddWatch(path string, flags uint32) error {
if w.isClosed {
return errors.New("inotify instance already closed")
}
watchEntry, found := w.watches[path]
if found {
watchEntry.flags |= flags
flags |= syscall.IN_MASK_ADD
}
w.mu.Lock() // synchronize with readEvents goroutine
wd, err := syscall.InotifyAddWatch(w.fd, path, flags)
if err != nil {
w.mu.Unlock()
return &os.PathError{
Op: "inotify_add_watch",
Path: path,
Err: err,
}
}
if !found {
w.watches[path] = &watch{wd: uint32(wd), flags: flags}
w.paths[wd] = path
}
w.mu.Unlock()
return nil
}
// Watch adds path to the watched file set, watching all events.
func (w *Watcher) Watch(path string) error {
return w.AddWatch(path, IN_ALL_EVENTS)
}
// RemoveWatch removes path from the watched file set.
func (w *Watcher) RemoveWatch(path string) error {
watch, ok := w.watches[path]
if !ok {
return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path))
}
success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
if success == -1 {
return os.NewSyscallError("inotify_rm_watch", errno)
}
delete(w.watches, path)
return nil
}
// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Event channel
func (w *Watcher) readEvents() {
var buf [syscall.SizeofInotifyEvent * 4096]byte
for {
n, err := syscall.Read(w.fd, buf[:])
// See if there is a message on the "done" channel
var done bool
select {
case done = <-w.done:
default:
}
// If EOF or a "done" message is received
if n == 0 || done {
// The syscall.Close can be slow. Close
// w.Event first.
close(w.Event)
err := syscall.Close(w.fd)
if err != nil {
w.Error <- os.NewSyscallError("close", err)
}
close(w.Error)
return
}
if n < 0 {
w.Error <- os.NewSyscallError("read", err)
continue
}
if n < syscall.SizeofInotifyEvent {
w.Error <- errors.New("inotify: short read in readEvents()")
continue
}
var offset uint32 = 0
// We don't know how many events we just read into the buffer
// While the offset points to at least one whole event...
for offset <= uint32(n-syscall.SizeofInotifyEvent) {
// Point "raw" to the event in the buffer
raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
event := new(Event)
event.Mask = uint32(raw.Mask)
event.Cookie = uint32(raw.Cookie)
nameLen := uint32(raw.Len)
// If the event happened to the watched directory or the watched file, the kernel
// doesn't append the filename to the event, but we would like to always fill the
// the "Name" field with a valid filename. We retrieve the path of the watch from
// the "paths" map.
w.mu.Lock()
event.Name = w.paths[int(raw.Wd)]
w.mu.Unlock()
if nameLen > 0 {
// Point "bytes" at the first byte of the filename
bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
// The filename is padded with NUL bytes. TrimRight() gets rid of those.
event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
}
// Send the event on the events channel
w.Event <- event
// Move to the next event in the buffer
offset += syscall.SizeofInotifyEvent + nameLen
}
}
}
// String formats the event e in the form
// "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..."
func (e *Event) String() string {
var events string = ""
m := e.Mask
for _, b := range eventBits {
if m&b.Value == b.Value {
m &^= b.Value
events += "|" + b.Name
}
}
if m != 0 {
events += fmt.Sprintf("|%#x", m)
}
if len(events) > 0 {
events = " == " + events[1:]
}
return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)
}
const (
// Options for inotify_init() are not exported
// IN_CLOEXEC uint32 = syscall.IN_CLOEXEC
// IN_NONBLOCK uint32 = syscall.IN_NONBLOCK
// Options for AddWatch
IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
IN_ONESHOT uint32 = syscall.IN_ONESHOT
IN_ONLYDIR uint32 = syscall.IN_ONLYDIR
// The "IN_MASK_ADD" option is not exported, as AddWatch
// adds it automatically, if there is already a watch for the given path
// IN_MASK_ADD uint32 = syscall.IN_MASK_ADD
// Events
IN_ACCESS uint32 = syscall.IN_ACCESS
IN_ALL_EVENTS uint32 = syscall.IN_ALL_EVENTS
IN_ATTRIB uint32 = syscall.IN_ATTRIB
IN_CLOSE uint32 = syscall.IN_CLOSE
IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
IN_CLOSE_WRITE uint32 = syscall.IN_CLOSE_WRITE
IN_CREATE uint32 = syscall.IN_CREATE
IN_DELETE uint32 = syscall.IN_DELETE
IN_DELETE_SELF uint32 = syscall.IN_DELETE_SELF
IN_MODIFY uint32 = syscall.IN_MODIFY
IN_MOVE uint32 = syscall.IN_MOVE
IN_MOVED_FROM uint32 = syscall.IN_MOVED_FROM
IN_MOVED_TO uint32 = syscall.IN_MOVED_TO
IN_MOVE_SELF uint32 = syscall.IN_MOVE_SELF
IN_OPEN uint32 = syscall.IN_OPEN
// Special events
IN_ISDIR uint32 = syscall.IN_ISDIR
IN_IGNORED uint32 = syscall.IN_IGNORED
IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
IN_UNMOUNT uint32 = syscall.IN_UNMOUNT
)
var eventBits = []struct {
Value uint32
Name string
}{
{IN_ACCESS, "IN_ACCESS"},
{IN_ATTRIB, "IN_ATTRIB"},
{IN_CLOSE, "IN_CLOSE"},
{IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"},
{IN_CLOSE_WRITE, "IN_CLOSE_WRITE"},
{IN_CREATE, "IN_CREATE"},
{IN_DELETE, "IN_DELETE"},
{IN_DELETE_SELF, "IN_DELETE_SELF"},
{IN_MODIFY, "IN_MODIFY"},
{IN_MOVE, "IN_MOVE"},
{IN_MOVED_FROM, "IN_MOVED_FROM"},
{IN_MOVED_TO, "IN_MOVED_TO"},
{IN_MOVE_SELF, "IN_MOVE_SELF"},
{IN_OPEN, "IN_OPEN"},
{IN_ISDIR, "IN_ISDIR"},
{IN_IGNORED, "IN_IGNORED"},
{IN_Q_OVERFLOW, "IN_Q_OVERFLOW"},
{IN_UNMOUNT, "IN_UNMOUNT"},
}
golang-golang-x-exp_eb7c1fa/inotify/inotify_linux_test.go 0000664 0000000 0000000 00000004677 12572222044 0024106 0 ustar 00root root 0000000 0000000 // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package inotify
import (
"io/ioutil"
"os"
"sync/atomic"
"testing"
"time"
)
func TestInotifyEvents(t *testing.T) {
// Create an inotify watcher instance and initialize it
watcher, err := NewWatcher()
if err != nil {
t.Fatalf("NewWatcher failed: %s", err)
}
dir, err := ioutil.TempDir("", "inotify")
if err != nil {
t.Fatalf("TempDir failed: %s", err)
}
defer os.RemoveAll(dir)
// Add a watch for "_test"
err = watcher.Watch(dir)
if err != nil {
t.Fatalf("Watch failed: %s", err)
}
// Receive errors on the error channel on a separate goroutine
go func() {
for err := range watcher.Error {
t.Fatalf("error received: %s", err)
}
}()
testFile := dir + "/TestInotifyEvents.testfile"
// Receive events on the event channel on a separate goroutine
eventstream := watcher.Event
var eventsReceived int32 = 0
done := make(chan bool)
go func() {
for event := range eventstream {
// Only count relevant events
if event.Name == testFile {
atomic.AddInt32(&eventsReceived, 1)
t.Logf("event received: %s", event)
} else {
t.Logf("unexpected event received: %s", event)
}
}
done <- true
}()
// Create a file
// This should add at least one event to the inotify event queue
_, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
t.Fatalf("creating test file: %s", err)
}
// We expect this event to be received almost immediately, but let's wait 1 s to be sure
time.Sleep(1 * time.Second)
if atomic.AddInt32(&eventsReceived, 0) == 0 {
t.Fatal("inotify event hasn't been received after 1 second")
}
// Try closing the inotify instance
t.Log("calling Close()")
watcher.Close()
t.Log("waiting for the event channel to become closed...")
select {
case <-done:
t.Log("event channel closed")
case <-time.After(1 * time.Second):
t.Fatal("event stream was not closed after 1 second")
}
}
func TestInotifyClose(t *testing.T) {
watcher, _ := NewWatcher()
watcher.Close()
done := make(chan bool)
go func() {
watcher.Close()
done <- true
}()
select {
case <-done:
case <-time.After(50 * time.Millisecond):
t.Fatal("double Close() test failed: second Close() call didn't return")
}
err := watcher.Watch(os.TempDir())
if err == nil {
t.Fatal("expected error on Watch() after Close(), got nil")
}
}
golang-golang-x-exp_eb7c1fa/mmap/ 0000775 0000000 0000000 00000000000 12572222044 0017053 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/mmap/manual_test_program.go 0000664 0000000 0000000 00000002264 12572222044 0023451 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
//
// This build tag means that "go build" does not build this file. Use "go run
// manual_test_program.go" to run it.
//
// You will also need to change "debug = false" to "debug = true" in mmap_*.go.
package main
import (
"log"
"math/rand"
"time"
"golang.org/x/exp/mmap"
)
var garbage []byte
func main() {
const filename = "manual_test_program.go"
for _, explicitClose := range []bool{false, true} {
r, err := mmap.Open(filename)
if err != nil {
log.Fatalf("Open: %v", err)
}
if explicitClose {
r.Close()
} else {
// Leak the *mmap.ReaderAt returned by mmap.Open. The finalizer
// should pick it up, if finalizers run at all.
}
}
println("Finished all explicit Close calls.")
println("Creating and collecting garbage.")
println("Look for two munmap log messages.")
println("Hit Ctrl-C to exit.")
rng := rand.New(rand.NewSource(1))
now := time.Now()
for {
garbage = make([]byte, rng.Intn(1<<20))
if time.Since(now) > 1*time.Second {
now = time.Now()
print(".")
}
}
}
golang-golang-x-exp_eb7c1fa/mmap/mmap_linux.go 0000664 0000000 0000000 00000005060 12572222044 0021554 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package mmap provides a way to memory-map a file.
package mmap
import (
"errors"
"fmt"
"io"
"os"
"runtime"
"syscall"
)
// debug is whether to print debugging messages for manual testing.
//
// The runtime.SetFinalizer documentation says that, "The finalizer for x is
// scheduled to run at some arbitrary time after x becomes unreachable. There
// is no guarantee that finalizers will run before a program exits", so we
// cannot automatically test that the finalizer runs. Instead, set this to true
// when running the manual test.
const debug = false
// ReaderAt reads a memory-mapped file.
//
// Like any io.ReaderAt, clients can execute parallel ReadAt calls, but it is
// not safe to call Close and reading methods concurrently.
type ReaderAt struct {
data []byte
}
// Close closes the reader.
func (r *ReaderAt) Close() error {
if r.data == nil {
return nil
}
data := r.data
r.data = nil
if debug {
var p *byte
if len(data) != 0 {
p = &data[0]
}
println("munmap", r, p)
}
runtime.SetFinalizer(r, nil)
return syscall.Munmap(data)
}
// Len returns the length of the underlying memory-mapped file.
func (r *ReaderAt) Len() int {
return len(r.data)
}
// At returns the byte at index i.
func (r *ReaderAt) At(i int) byte {
return r.data[i]
}
// ReadAt implements the io.ReaderAt interface.
func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
if r.data == nil {
return 0, errors.New("mmap: closed")
}
if off < 0 || int64(len(r.data)) < off {
return 0, fmt.Errorf("mmap: invalid ReadAt offset %d", off)
}
n := copy(p, r.data[off:])
if n < len(p) {
return n, io.EOF
}
return n, nil
}
// Open memory-maps the named file for reading.
func Open(filename string) (*ReaderAt, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, err
}
size := fi.Size()
if size == 0 {
return &ReaderAt{}, nil
}
if size < 0 {
return nil, fmt.Errorf("mmap: file %q has negative size", filename)
}
if size != int64(int(size)) {
return nil, fmt.Errorf("mmap: file %q is too large", filename)
}
data, err := syscall.Mmap(int(f.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
return nil, err
}
r := &ReaderAt{data}
if debug {
var p *byte
if len(data) != 0 {
p = &data[0]
}
println("mmap", r, p)
}
runtime.SetFinalizer(r, (*ReaderAt).Close)
return r, nil
}
golang-golang-x-exp_eb7c1fa/mmap/mmap_other.go 0000664 0000000 0000000 00000003021 12572222044 0021531 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !linux
// Package mmap provides a way to memory-map a file.
package mmap
import (
"fmt"
"os"
)
// ReaderAt reads a memory-mapped file.
//
// Like any io.ReaderAt, clients can execute parallel ReadAt calls, but it is
// not safe to call Close and reading methods concurrently.
type ReaderAt struct {
f *os.File
len int
}
// Close closes the reader.
func (r *ReaderAt) Close() error {
return r.f.Close()
}
// Len returns the length of the underlying memory-mapped file.
func (r *ReaderAt) Len() int {
return r.len
}
// At returns the byte at index i.
func (r *ReaderAt) At(i int) byte {
if i < 0 || r.len <= i {
panic("index out of range")
}
var b [1]byte
r.ReadAt(b[:], int64(i))
return b[0]
}
// ReadAt implements the io.ReaderAt interface.
func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
return r.f.ReadAt(p, off)
}
// Open memory-maps the named file for reading.
func Open(filename string) (*ReaderAt, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
fi, err := f.Stat()
if err != nil {
f.Close()
return nil, err
}
size := fi.Size()
if size < 0 {
f.Close()
return nil, fmt.Errorf("mmap: file %q has negative size", filename)
}
if size != int64(int(size)) {
f.Close()
return nil, fmt.Errorf("mmap: file %q is too large", filename)
}
return &ReaderAt{
f: f,
len: int(fi.Size()),
}, nil
}
golang-golang-x-exp_eb7c1fa/mmap/mmap_test.go 0000664 0000000 0000000 00000001401 12572222044 0021367 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mmap
import (
"bytes"
"io"
"io/ioutil"
"testing"
)
func TestOpen(t *testing.T) {
const filename = "mmap_test.go"
r, err := Open(filename)
if err != nil {
t.Fatalf("Open: %v", err)
}
got := make([]byte, r.Len())
if _, err := r.ReadAt(got, 0); err != nil && err != io.EOF {
t.Fatalf("ReadAt: %v", err)
}
want, err := ioutil.ReadFile(filename)
if err != nil {
t.Fatalf("ioutil.ReadFile: %v", err)
}
if len(got) != len(want) {
t.Fatalf("got %d bytes, want %d", len(got), len(want))
}
if !bytes.Equal(got, want) {
t.Fatalf("\ngot %q\nwant %q", string(got), string(want))
}
}
golang-golang-x-exp_eb7c1fa/old/ 0000775 0000000 0000000 00000000000 12572222044 0016677 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/old/netchan/ 0000775 0000000 0000000 00000000000 12572222044 0020317 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/old/netchan/common.go 0000664 0000000 0000000 00000017765 12572222044 0022156 0 ustar 00root root 0000000 0000000 // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package netchan
import (
"encoding/gob"
"errors"
"io"
"reflect"
"sync"
"time"
)
// The direction of a connection from the client's perspective.
type Dir int
const (
Recv Dir = iota
Send
)
func (dir Dir) String() string {
switch dir {
case Recv:
return "Recv"
case Send:
return "Send"
}
return "???"
}
// Payload types
const (
payRequest = iota // request structure follows
payError // error structure follows
payData // user payload follows
payAck // acknowledgement; no payload
payClosed // channel is now closed
payAckSend // payload has been delivered.
)
// A header is sent as a prefix to every transmission. It will be followed by
// a request structure, an error structure, or an arbitrary user payload structure.
type header struct {
Id int
PayloadType int
SeqNum int64
}
// Sent with a header once per channel from importer to exporter to report
// that it wants to bind to a channel with the specified direction for count
// messages, with space for size buffered values. If count is -1, it means unlimited.
type request struct {
Name string
Count int64
Size int
Dir Dir
}
// Sent with a header to report an error.
type error_ struct {
Error string
}
// Used to unify management of acknowledgements for import and export.
type unackedCounter interface {
unackedCount() int64
ack() int64
seq() int64
}
// A channel and its direction.
type chanDir struct {
ch reflect.Value
dir Dir
}
// clientSet contains the objects and methods needed for tracking
// clients of an exporter and draining outstanding messages.
type clientSet struct {
mu sync.Mutex // protects access to channel and client maps
names map[string]*chanDir
clients map[unackedCounter]bool
}
// Mutex-protected encoder and decoder pair.
type encDec struct {
decLock sync.Mutex
dec *gob.Decoder
encLock sync.Mutex
enc *gob.Encoder
}
func newEncDec(conn io.ReadWriter) *encDec {
return &encDec{
dec: gob.NewDecoder(conn),
enc: gob.NewEncoder(conn),
}
}
// Decode an item from the connection.
func (ed *encDec) decode(value reflect.Value) error {
ed.decLock.Lock()
err := ed.dec.DecodeValue(value)
if err != nil {
// TODO: tear down connection?
}
ed.decLock.Unlock()
return err
}
// Encode a header and payload onto the connection.
func (ed *encDec) encode(hdr *header, payloadType int, payload interface{}) error {
ed.encLock.Lock()
hdr.PayloadType = payloadType
err := ed.enc.Encode(hdr)
if err == nil {
if payload != nil {
err = ed.enc.Encode(payload)
}
}
if err != nil {
// TODO: tear down connection if there is an error?
}
ed.encLock.Unlock()
return err
}
// See the comment for Exporter.Drain.
func (cs *clientSet) drain(timeout time.Duration) error {
deadline := time.Now().Add(timeout)
for {
pending := false
cs.mu.Lock()
// Any messages waiting for a client?
for _, chDir := range cs.names {
if chDir.ch.Len() > 0 {
pending = true
}
}
// Any unacknowledged messages?
for client := range cs.clients {
n := client.unackedCount()
if n > 0 { // Check for > rather than != just to be safe.
pending = true
break
}
}
cs.mu.Unlock()
if !pending {
break
}
if timeout > 0 && time.Now().After(deadline) {
return errors.New("timeout")
}
time.Sleep(100 * time.Millisecond)
}
return nil
}
// See the comment for Exporter.Sync.
func (cs *clientSet) sync(timeout time.Duration) error {
deadline := time.Now().Add(timeout)
// seq remembers the clients and their seqNum at point of entry.
seq := make(map[unackedCounter]int64)
cs.mu.Lock()
for client := range cs.clients {
seq[client] = client.seq()
}
cs.mu.Unlock()
for {
pending := false
cs.mu.Lock()
// Any unacknowledged messages? Look only at clients that existed
// when we started and are still in this client set.
for client := range seq {
if _, ok := cs.clients[client]; ok {
if client.ack() < seq[client] {
pending = true
break
}
}
}
cs.mu.Unlock()
if !pending {
break
}
if timeout > 0 && time.Now().After(deadline) {
return errors.New("timeout")
}
time.Sleep(100 * time.Millisecond)
}
return nil
}
// A netChan represents a channel imported or exported
// on a single connection. Flow is controlled by the receiving
// side by sending payAckSend messages when values
// are delivered into the local channel.
type netChan struct {
*chanDir
name string
id int
size int // buffer size of channel.
closed bool
// sender-specific state
ackCh chan bool // buffered with space for all the acks we need
space int // available space.
// receiver-specific state
sendCh chan reflect.Value // buffered channel of values received from other end.
ed *encDec // so that we can send acks.
count int64 // number of values still to receive.
}
// Create a new netChan with the given name (only used for
// messages), id, direction, buffer size, and count.
// The connection to the other side is represented by ed.
func newNetChan(name string, id int, ch *chanDir, ed *encDec, size int, count int64) *netChan {
c := &netChan{chanDir: ch, name: name, id: id, size: size, ed: ed, count: count}
if c.dir == Send {
c.ackCh = make(chan bool, size)
c.space = size
}
return c
}
// Close the channel.
func (nch *netChan) close() {
if nch.closed {
return
}
if nch.dir == Recv {
if nch.sendCh != nil {
// If the sender goroutine is active, close the channel to it.
// It will close nch.ch when it can.
close(nch.sendCh)
} else {
nch.ch.Close()
}
} else {
nch.ch.Close()
close(nch.ackCh)
}
nch.closed = true
}
// Send message from remote side to local receiver.
func (nch *netChan) send(val reflect.Value) {
if nch.dir != Recv {
panic("send on wrong direction of channel")
}
if nch.sendCh == nil {
// If possible, do local send directly and ack immediately.
if nch.ch.TrySend(val) {
nch.sendAck()
return
}
// Start sender goroutine to manage delayed delivery of values.
nch.sendCh = make(chan reflect.Value, nch.size)
go nch.sender()
}
select {
case nch.sendCh <- val:
// ok
default:
// TODO: should this be more resilient?
panic("netchan: remote sender sent more values than allowed")
}
}
// sendAck sends an acknowledgment that a message has left
// the channel's buffer. If the messages remaining to be sent
// will fit in the channel's buffer, then we don't
// need to send an ack.
func (nch *netChan) sendAck() {
if nch.count < 0 || nch.count > int64(nch.size) {
nch.ed.encode(&header{Id: nch.id}, payAckSend, nil)
}
if nch.count > 0 {
nch.count--
}
}
// The sender process forwards items from the sending queue
// to the destination channel, acknowledging each item.
func (nch *netChan) sender() {
if nch.dir != Recv {
panic("sender on wrong direction of channel")
}
// When Exporter.Hangup is called, the underlying channel is closed,
// and so we may get a "too many operations on closed channel" error
// if there are outstanding messages in sendCh.
// Make sure that this doesn't panic the whole program.
defer func() {
if r := recover(); r != nil {
// TODO check that r is "too many operations", otherwise re-panic.
}
}()
for v := range nch.sendCh {
nch.ch.Send(v)
nch.sendAck()
}
nch.ch.Close()
}
// Receive value from local side for sending to remote side.
func (nch *netChan) recv() (val reflect.Value, ok bool) {
if nch.dir != Send {
panic("recv on wrong direction of channel")
}
if nch.space == 0 {
// Wait for buffer space.
<-nch.ackCh
nch.space++
}
nch.space--
return nch.ch.Recv()
}
// acked is called when the remote side indicates that
// a value has been delivered.
func (nch *netChan) acked() {
if nch.dir != Send {
panic("recv on wrong direction of channel")
}
select {
case nch.ackCh <- true:
// ok
default:
// TODO: should this be more resilient?
panic("netchan: remote receiver sent too many acks")
}
}
golang-golang-x-exp_eb7c1fa/old/netchan/export.go 0000664 0000000 0000000 00000027273 12572222044 0022202 0 ustar 00root root 0000000 0000000 // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package netchan implements type-safe networked channels:
it allows the two ends of a channel to appear on different
computers connected by a network. It does this by transporting
data sent to a channel on one machine so it can be recovered
by a receive of a channel of the same type on the other.
An exporter publishes a set of channels by name. An importer
connects to the exporting machine and imports the channels
by name. After importing the channels, the two machines can
use the channels in the usual way.
Networked channels are not synchronized; they always behave
as if they are buffered channels of at least one element.
*/
package netchan // import "golang.org/x/exp/old/netchan"
// BUG: can't use range clause to receive when using ImportNValues to limit the count.
import (
"errors"
"io"
"log"
"net"
"reflect"
"strconv"
"sync"
"time"
)
// Export
// expLog is a logging convenience function. The first argument must be a string.
func expLog(args ...interface{}) {
args[0] = "netchan export: " + args[0].(string)
log.Print(args...)
}
// An Exporter allows a set of channels to be published on a single
// network port. A single machine may have multiple Exporters
// but they must use different ports.
type Exporter struct {
*clientSet
}
type expClient struct {
*encDec
exp *Exporter
chans map[int]*netChan // channels in use by client
mu sync.Mutex // protects remaining fields
errored bool // client has been sent an error
seqNum int64 // sequences messages sent to client; has value of highest sent
ackNum int64 // highest sequence number acknowledged
seqLock sync.Mutex // guarantees messages are in sequence, only locked under mu
}
func newClient(exp *Exporter, conn io.ReadWriter) *expClient {
client := new(expClient)
client.exp = exp
client.encDec = newEncDec(conn)
client.seqNum = 0
client.ackNum = 0
client.chans = make(map[int]*netChan)
return client
}
func (client *expClient) sendError(hdr *header, err string) {
error := &error_{err}
expLog("sending error to client:", error.Error)
client.encode(hdr, payError, error) // ignore any encode error, hope client gets it
client.mu.Lock()
client.errored = true
client.mu.Unlock()
}
func (client *expClient) newChan(hdr *header, dir Dir, name string, size int, count int64) *netChan {
exp := client.exp
exp.mu.Lock()
ech, ok := exp.names[name]
exp.mu.Unlock()
if !ok {
client.sendError(hdr, "no such channel: "+name)
return nil
}
if ech.dir != dir {
client.sendError(hdr, "wrong direction for channel: "+name)
return nil
}
nch := newNetChan(name, hdr.Id, ech, client.encDec, size, count)
client.chans[hdr.Id] = nch
return nch
}
func (client *expClient) getChan(hdr *header, dir Dir) *netChan {
nch := client.chans[hdr.Id]
if nch == nil {
return nil
}
if nch.dir != dir {
client.sendError(hdr, "wrong direction for channel: "+nch.name)
}
return nch
}
// The function run manages sends and receives for a single client. For each
// (client Recv) request, this will launch a serveRecv goroutine to deliver
// the data for that channel, while (client Send) requests are handled as
// data arrives from the client.
func (client *expClient) run() {
hdr := new(header)
hdrValue := reflect.ValueOf(hdr)
req := new(request)
reqValue := reflect.ValueOf(req)
error := new(error_)
for {
*hdr = header{}
if err := client.decode(hdrValue); err != nil {
if err != io.EOF {
expLog("error decoding client header:", err)
}
break
}
switch hdr.PayloadType {
case payRequest:
*req = request{}
if err := client.decode(reqValue); err != nil {
expLog("error decoding client request:", err)
break
}
if req.Size < 1 {
panic("netchan: remote requested " + strconv.Itoa(req.Size) + " values")
}
switch req.Dir {
case Recv:
// look up channel before calling serveRecv to
// avoid a lock around client.chans.
if nch := client.newChan(hdr, Send, req.Name, req.Size, req.Count); nch != nil {
go client.serveRecv(nch, *hdr, req.Count)
}
case Send:
client.newChan(hdr, Recv, req.Name, req.Size, req.Count)
// The actual sends will have payload type payData.
// TODO: manage the count?
default:
error.Error = "request: can't handle channel direction"
expLog(error.Error, req.Dir)
client.encode(hdr, payError, error)
}
case payData:
client.serveSend(*hdr)
case payClosed:
client.serveClosed(*hdr)
case payAck:
client.mu.Lock()
if client.ackNum != hdr.SeqNum-1 {
// Since the sequence number is incremented and the message is sent
// in a single instance of locking client.mu, the messages are guaranteed
// to be sent in order. Therefore receipt of acknowledgement N means
// all messages <=N have been seen by the recipient. We check anyway.
expLog("sequence out of order:", client.ackNum, hdr.SeqNum)
}
if client.ackNum < hdr.SeqNum { // If there has been an error, don't back up the count.
client.ackNum = hdr.SeqNum
}
client.mu.Unlock()
case payAckSend:
if nch := client.getChan(hdr, Send); nch != nil {
nch.acked()
}
default:
log.Fatal("netchan export: unknown payload type", hdr.PayloadType)
}
}
client.exp.delClient(client)
}
// Send all the data on a single channel to a client asking for a Recv.
// The header is passed by value to avoid issues of overwriting.
func (client *expClient) serveRecv(nch *netChan, hdr header, count int64) {
for {
val, ok := nch.recv()
if !ok {
if err := client.encode(&hdr, payClosed, nil); err != nil {
expLog("error encoding server closed message:", err)
}
break
}
// We hold the lock during transmission to guarantee messages are
// sent in sequence number order. Also, we increment first so the
// value of client.SeqNum is the value of the highest used sequence
// number, not one beyond.
client.mu.Lock()
client.seqNum++
hdr.SeqNum = client.seqNum
client.seqLock.Lock() // guarantee ordering of messages
client.mu.Unlock()
err := client.encode(&hdr, payData, val.Interface())
client.seqLock.Unlock()
if err != nil {
expLog("error encoding client response:", err)
client.sendError(&hdr, err.Error())
break
}
// Negative count means run forever.
if count >= 0 {
if count--; count <= 0 {
break
}
}
}
}
// Receive and deliver locally one item from a client asking for a Send
// The header is passed by value to avoid issues of overwriting.
func (client *expClient) serveSend(hdr header) {
nch := client.getChan(&hdr, Recv)
if nch == nil {
return
}
// Create a new value for each received item.
val := reflect.New(nch.ch.Type().Elem()).Elem()
if err := client.decode(val); err != nil {
expLog("value decode:", err, "; type ", nch.ch.Type())
return
}
nch.send(val)
}
// Report that client has closed the channel that is sending to us.
// The header is passed by value to avoid issues of overwriting.
func (client *expClient) serveClosed(hdr header) {
nch := client.getChan(&hdr, Recv)
if nch == nil {
return
}
nch.close()
}
func (client *expClient) unackedCount() int64 {
client.mu.Lock()
n := client.seqNum - client.ackNum
client.mu.Unlock()
return n
}
func (client *expClient) seq() int64 {
client.mu.Lock()
n := client.seqNum
client.mu.Unlock()
return n
}
func (client *expClient) ack() int64 {
client.mu.Lock()
n := client.seqNum
client.mu.Unlock()
return n
}
// Serve waits for incoming connections on the listener
// and serves the Exporter's channels on each.
// It blocks until the listener is closed.
func (exp *Exporter) Serve(listener net.Listener) {
for {
conn, err := listener.Accept()
if err != nil {
expLog("listen:", err)
break
}
go exp.ServeConn(conn)
}
}
// ServeConn exports the Exporter's channels on conn.
// It blocks until the connection is terminated.
func (exp *Exporter) ServeConn(conn io.ReadWriter) {
exp.addClient(conn).run()
}
// NewExporter creates a new Exporter that exports a set of channels.
func NewExporter() *Exporter {
e := &Exporter{
clientSet: &clientSet{
names: make(map[string]*chanDir),
clients: make(map[unackedCounter]bool),
},
}
return e
}
// ListenAndServe exports the exporter's channels through the
// given network and local address defined as in net.Listen.
func (exp *Exporter) ListenAndServe(network, localaddr string) error {
listener, err := net.Listen(network, localaddr)
if err != nil {
return err
}
go exp.Serve(listener)
return nil
}
// addClient creates a new expClient and records its existence
func (exp *Exporter) addClient(conn io.ReadWriter) *expClient {
client := newClient(exp, conn)
exp.mu.Lock()
exp.clients[client] = true
exp.mu.Unlock()
return client
}
// delClient forgets the client existed
func (exp *Exporter) delClient(client *expClient) {
exp.mu.Lock()
delete(exp.clients, client)
exp.mu.Unlock()
}
// Drain waits until all messages sent from this exporter/importer, including
// those not yet sent to any client and possibly including those sent while
// Drain was executing, have been received by the importer. In short, it
// waits until all the exporter's messages have been received by a client.
// If the timeout is positive and Drain takes longer than that to complete,
// an error is returned.
func (exp *Exporter) Drain(timeout time.Duration) error {
// This wrapper function is here so the method's comment will appear in godoc.
return exp.clientSet.drain(timeout)
}
// Sync waits until all clients of the exporter have received the messages
// that were sent at the time Sync was invoked. Unlike Drain, it does not
// wait for messages sent while it is running or messages that have not been
// dispatched to any client. If the timeout is positive and Sync takes longer
// than that to complete, an error is returned.
func (exp *Exporter) Sync(timeout time.Duration) error {
// This wrapper function is here so the method's comment will appear in godoc.
return exp.clientSet.sync(timeout)
}
func checkChan(chT interface{}, dir Dir) (reflect.Value, error) {
chanType := reflect.TypeOf(chT)
if chanType.Kind() != reflect.Chan {
return reflect.Value{}, errors.New("not a channel")
}
if dir != Send && dir != Recv {
return reflect.Value{}, errors.New("unknown channel direction")
}
switch chanType.ChanDir() {
case reflect.BothDir:
case reflect.SendDir:
if dir != Recv {
return reflect.Value{}, errors.New("to import/export with Send, must provide <-chan")
}
case reflect.RecvDir:
if dir != Send {
return reflect.Value{}, errors.New("to import/export with Recv, must provide chan<-")
}
}
return reflect.ValueOf(chT), nil
}
// Export exports a channel of a given type and specified direction. The
// channel to be exported is provided in the call and may be of arbitrary
// channel type.
// Despite the literal signature, the effective signature is
// Export(name string, chT chan T, dir Dir)
func (exp *Exporter) Export(name string, chT interface{}, dir Dir) error {
ch, err := checkChan(chT, dir)
if err != nil {
return err
}
exp.mu.Lock()
defer exp.mu.Unlock()
_, present := exp.names[name]
if present {
return errors.New("channel name already being exported:" + name)
}
exp.names[name] = &chanDir{ch, dir}
return nil
}
// Hangup disassociates the named channel from the Exporter and closes
// the channel. Messages in flight for the channel may be dropped.
func (exp *Exporter) Hangup(name string) error {
exp.mu.Lock()
chDir, ok := exp.names[name]
if ok {
delete(exp.names, name)
}
// TODO drop all instances of channel from client sets
exp.mu.Unlock()
if !ok {
return errors.New("netchan export: hangup: no such channel: " + name)
}
chDir.ch.Close()
return nil
}
golang-golang-x-exp_eb7c1fa/old/netchan/import.go 0000664 0000000 0000000 00000017701 12572222044 0022166 0 ustar 00root root 0000000 0000000 // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package netchan
import (
"errors"
"io"
"log"
"net"
"reflect"
"sync"
"time"
)
// Import
// impLog is a logging convenience function. The first argument must be a string.
func impLog(args ...interface{}) {
args[0] = "netchan import: " + args[0].(string)
log.Print(args...)
}
// An Importer allows a set of channels to be imported from a single
// remote machine/network port. A machine may have multiple
// importers, even from the same machine/network port.
type Importer struct {
*encDec
chanLock sync.Mutex // protects access to channel map
names map[string]*netChan
chans map[int]*netChan
errors chan error
maxId int
mu sync.Mutex // protects remaining fields
unacked int64 // number of unacknowledged sends.
seqLock sync.Mutex // guarantees messages are in sequence, only locked under mu
}
// NewImporter creates a new Importer object to import a set of channels
// from the given connection. The Exporter must be available and serving when
// the Importer is created.
func NewImporter(conn io.ReadWriter) *Importer {
imp := new(Importer)
imp.encDec = newEncDec(conn)
imp.chans = make(map[int]*netChan)
imp.names = make(map[string]*netChan)
imp.errors = make(chan error, 10)
imp.unacked = 0
go imp.run()
return imp
}
// Import imports a set of channels from the given network and address.
func Import(network, remoteaddr string) (*Importer, error) {
conn, err := net.Dial(network, remoteaddr)
if err != nil {
return nil, err
}
return NewImporter(conn), nil
}
// shutdown closes all channels for which we are receiving data from the remote side.
func (imp *Importer) shutdown() {
imp.chanLock.Lock()
for _, ich := range imp.chans {
if ich.dir == Recv {
ich.close()
}
}
imp.chanLock.Unlock()
}
// Handle the data from a single imported data stream, which will
// have the form
// (response, data)*
// The response identifies by name which channel is transmitting data.
func (imp *Importer) run() {
// Loop on responses; requests are sent by ImportNValues()
hdr := new(header)
hdrValue := reflect.ValueOf(hdr)
ackHdr := new(header)
err := new(error_)
errValue := reflect.ValueOf(err)
for {
*hdr = header{}
if e := imp.decode(hdrValue); e != nil {
if e != io.EOF {
impLog("header:", e)
imp.shutdown()
}
return
}
switch hdr.PayloadType {
case payData:
// done lower in loop
case payError:
if e := imp.decode(errValue); e != nil {
impLog("error:", e)
return
}
if err.Error != "" {
impLog("response error:", err.Error)
select {
case imp.errors <- errors.New(err.Error):
continue // errors are not acknowledged
default:
imp.shutdown()
return
}
}
case payClosed:
nch := imp.getChan(hdr.Id, false)
if nch != nil {
nch.close()
}
continue // closes are not acknowledged.
case payAckSend:
// we can receive spurious acks if the channel is
// hung up, so we ask getChan to ignore any errors.
nch := imp.getChan(hdr.Id, true)
if nch != nil {
nch.acked()
imp.mu.Lock()
imp.unacked--
imp.mu.Unlock()
}
continue
default:
impLog("unexpected payload type:", hdr.PayloadType)
return
}
nch := imp.getChan(hdr.Id, false)
if nch == nil {
continue
}
if nch.dir != Recv {
impLog("cannot happen: receive from non-Recv channel")
return
}
// Acknowledge receipt
ackHdr.Id = hdr.Id
ackHdr.SeqNum = hdr.SeqNum
imp.encode(ackHdr, payAck, nil)
// Create a new value for each received item.
value := reflect.New(nch.ch.Type().Elem()).Elem()
if e := imp.decode(value); e != nil {
impLog("importer value decode:", e)
return
}
nch.send(value)
}
}
func (imp *Importer) getChan(id int, errOk bool) *netChan {
imp.chanLock.Lock()
ich := imp.chans[id]
imp.chanLock.Unlock()
if ich == nil {
if !errOk {
impLog("unknown id in netchan request: ", id)
}
return nil
}
return ich
}
// Errors returns a channel from which transmission and protocol errors
// can be read. Clients of the importer are not required to read the error
// channel for correct execution. However, if too many errors occur
// without being read from the error channel, the importer will shut down.
func (imp *Importer) Errors() chan error {
return imp.errors
}
// Import imports a channel of the given type, size and specified direction.
// It is equivalent to ImportNValues with a count of -1, meaning unbounded.
func (imp *Importer) Import(name string, chT interface{}, dir Dir, size int) error {
return imp.ImportNValues(name, chT, dir, size, -1)
}
// ImportNValues imports a channel of the given type and specified
// direction and then receives or transmits up to n values on that
// channel. A value of n==-1 implies an unbounded number of values. The
// channel will have buffer space for size values, or 1 value if size < 1.
// The channel to be bound to the remote site's channel is provided
// in the call and may be of arbitrary channel type.
// Despite the literal signature, the effective signature is
// ImportNValues(name string, chT chan T, dir Dir, size, n int) error
// Example usage:
// imp, err := NewImporter("tcp", "netchanserver.mydomain.com:1234")
// if err != nil { log.Fatal(err) }
// ch := make(chan myType)
// err = imp.ImportNValues("name", ch, Recv, 1, 1)
// if err != nil { log.Fatal(err) }
// fmt.Printf("%+v\n", <-ch)
func (imp *Importer) ImportNValues(name string, chT interface{}, dir Dir, size, n int) error {
ch, err := checkChan(chT, dir)
if err != nil {
return err
}
imp.chanLock.Lock()
defer imp.chanLock.Unlock()
_, present := imp.names[name]
if present {
return errors.New("channel name already being imported:" + name)
}
if size < 1 {
size = 1
}
id := imp.maxId
imp.maxId++
nch := newNetChan(name, id, &chanDir{ch, dir}, imp.encDec, size, int64(n))
imp.names[name] = nch
imp.chans[id] = nch
// Tell the other side about this channel.
hdr := &header{Id: id}
req := &request{Name: name, Count: int64(n), Dir: dir, Size: size}
if err = imp.encode(hdr, payRequest, req); err != nil {
impLog("request encode:", err)
return err
}
if dir == Send {
go func() {
for i := 0; n == -1 || i < n; i++ {
val, ok := nch.recv()
if !ok {
if err = imp.encode(hdr, payClosed, nil); err != nil {
impLog("error encoding client closed message:", err)
}
return
}
// We hold the lock during transmission to guarantee messages are
// sent in order.
imp.mu.Lock()
imp.unacked++
imp.seqLock.Lock()
imp.mu.Unlock()
if err = imp.encode(hdr, payData, val.Interface()); err != nil {
impLog("error encoding client send:", err)
return
}
imp.seqLock.Unlock()
}
}()
}
return nil
}
// Hangup disassociates the named channel from the Importer and closes
// the channel. Messages in flight for the channel may be dropped.
func (imp *Importer) Hangup(name string) error {
imp.chanLock.Lock()
defer imp.chanLock.Unlock()
nc := imp.names[name]
if nc == nil {
return errors.New("netchan import: hangup: no such channel: " + name)
}
delete(imp.names, name)
delete(imp.chans, nc.id)
nc.close()
return nil
}
func (imp *Importer) unackedCount() int64 {
imp.mu.Lock()
n := imp.unacked
imp.mu.Unlock()
return n
}
// Drain waits until all messages sent from this exporter/importer, including
// those not yet sent to any server and possibly including those sent while
// Drain was executing, have been received by the exporter. In short, it
// waits until all the importer's messages have been received.
// If the timeout (measured in nanoseconds) is positive and Drain takes
// longer than that to complete, an error is returned.
func (imp *Importer) Drain(timeout int64) error {
deadline := time.Now().Add(time.Duration(timeout))
for imp.unackedCount() > 0 {
if timeout > 0 && time.Now().After(deadline) {
return errors.New("timeout")
}
time.Sleep(100 * time.Millisecond)
}
return nil
}
golang-golang-x-exp_eb7c1fa/old/netchan/netchan_test.go 0000664 0000000 0000000 00000023453 12572222044 0023334 0 ustar 00root root 0000000 0000000 // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package netchan
import (
"net"
"strings"
"testing"
"time"
)
const count = 10 // number of items in most tests
const closeCount = 5 // number of items when sender closes early
const base = 23
func exportSend(exp *Exporter, n int, t *testing.T, done chan bool) {
ch := make(chan int)
err := exp.Export("exportedSend", ch, Send)
if err != nil {
t.Fatal("exportSend:", err)
}
go func() {
for i := 0; i < n; i++ {
ch <- base + i
}
close(ch)
if done != nil {
done <- true
}
}()
}
func exportReceive(exp *Exporter, t *testing.T, expDone chan bool) {
ch := make(chan int)
err := exp.Export("exportedRecv", ch, Recv)
expDone <- true
if err != nil {
t.Fatal("exportReceive:", err)
}
for i := 0; i < count; i++ {
v, ok := <-ch
if !ok {
if i != closeCount {
t.Errorf("exportReceive expected close at %d; got one at %d", closeCount, i)
}
break
}
if v != base+i {
t.Errorf("export Receive: bad value: expected %d+%d=%d; got %d", base, i, base+i, v)
}
}
}
func importSend(imp *Importer, n int, t *testing.T, done chan bool) {
ch := make(chan int)
err := imp.ImportNValues("exportedRecv", ch, Send, 3, -1)
if err != nil {
t.Fatal("importSend:", err)
}
go func() {
for i := 0; i < n; i++ {
ch <- base + i
}
close(ch)
if done != nil {
done <- true
}
}()
}
func importReceive(imp *Importer, t *testing.T, done chan bool) {
ch := make(chan int)
err := imp.ImportNValues("exportedSend", ch, Recv, 3, count)
if err != nil {
t.Fatal("importReceive:", err)
}
for i := 0; i < count; i++ {
v, ok := <-ch
if !ok {
if i != closeCount {
t.Errorf("importReceive expected close at %d; got one at %d", closeCount, i)
}
break
}
if v != base+i {
t.Errorf("importReceive: bad value: expected %d+%d=%d; got %+d", base, i, base+i, v)
}
}
if done != nil {
done <- true
}
}
func TestExportSendImportReceive(t *testing.T) {
exp, imp := pair(t)
exportSend(exp, count, t, nil)
importReceive(imp, t, nil)
}
func TestExportReceiveImportSend(t *testing.T) {
exp, imp := pair(t)
expDone := make(chan bool)
done := make(chan bool)
go func() {
exportReceive(exp, t, expDone)
done <- true
}()
<-expDone
importSend(imp, count, t, nil)
<-done
}
func TestClosingExportSendImportReceive(t *testing.T) {
exp, imp := pair(t)
exportSend(exp, closeCount, t, nil)
importReceive(imp, t, nil)
}
func TestClosingImportSendExportReceive(t *testing.T) {
exp, imp := pair(t)
expDone := make(chan bool)
done := make(chan bool)
go func() {
exportReceive(exp, t, expDone)
done <- true
}()
<-expDone
importSend(imp, closeCount, t, nil)
<-done
}
func TestErrorForIllegalChannel(t *testing.T) {
exp, imp := pair(t)
// Now export a channel.
ch := make(chan int, 1)
err := exp.Export("aChannel", ch, Send)
if err != nil {
t.Fatal("export:", err)
}
ch <- 1234
close(ch)
// Now try to import a different channel.
ch = make(chan int)
err = imp.Import("notAChannel", ch, Recv, 1)
if err != nil {
t.Fatal("import:", err)
}
// Expect an error now. Start a timeout.
timeout := make(chan bool, 1) // buffered so closure will not hang around.
go func() {
time.Sleep(10 * time.Second) // very long, to give even really slow machines a chance.
timeout <- true
}()
select {
case err = <-imp.Errors():
if strings.Index(err.Error(), "no such channel") < 0 {
t.Error("wrong error for nonexistent channel:", err)
}
case <-timeout:
t.Error("import of nonexistent channel did not receive an error")
}
}
// Not a great test but it does at least invoke Drain.
func TestExportDrain(t *testing.T) {
exp, imp := pair(t)
done := make(chan bool)
go func() {
exportSend(exp, closeCount, t, nil)
done <- true
}()
<-done
go importReceive(imp, t, done)
exp.Drain(0)
<-done
}
// Not a great test but it does at least invoke Drain.
func TestImportDrain(t *testing.T) {
exp, imp := pair(t)
expDone := make(chan bool)
go exportReceive(exp, t, expDone)
<-expDone
importSend(imp, closeCount, t, nil)
imp.Drain(0)
}
// Not a great test but it does at least invoke Sync.
func TestExportSync(t *testing.T) {
exp, imp := pair(t)
done := make(chan bool)
exportSend(exp, closeCount, t, nil)
go importReceive(imp, t, done)
exp.Sync(0)
<-done
}
// Test hanging up the send side of an export.
// TODO: test hanging up the receive side of an export.
func TestExportHangup(t *testing.T) {
exp, imp := pair(t)
ech := make(chan int)
err := exp.Export("exportedSend", ech, Send)
if err != nil {
t.Fatal("export:", err)
}
// Prepare to receive two values. We'll actually deliver only one.
ich := make(chan int)
err = imp.ImportNValues("exportedSend", ich, Recv, 1, 2)
if err != nil {
t.Fatal("import exportedSend:", err)
}
// Send one value, receive it.
const Value = 1234
ech <- Value
v := <-ich
if v != Value {
t.Fatal("expected", Value, "got", v)
}
// Now hang up the channel. Importer should see it close.
exp.Hangup("exportedSend")
v, ok := <-ich
if ok {
t.Fatal("expected channel to be closed; got value", v)
}
}
// Test hanging up the send side of an import.
// TODO: test hanging up the receive side of an import.
func TestImportHangup(t *testing.T) {
exp, imp := pair(t)
ech := make(chan int)
err := exp.Export("exportedRecv", ech, Recv)
if err != nil {
t.Fatal("export:", err)
}
// Prepare to Send two values. We'll actually deliver only one.
ich := make(chan int)
err = imp.ImportNValues("exportedRecv", ich, Send, 1, 2)
if err != nil {
t.Fatal("import exportedRecv:", err)
}
// Send one value, receive it.
const Value = 1234
ich <- Value
v := <-ech
if v != Value {
t.Fatal("expected", Value, "got", v)
}
// Now hang up the channel. Exporter should see it close.
imp.Hangup("exportedRecv")
v, ok := <-ech
if ok {
t.Fatal("expected channel to be closed; got value", v)
}
}
// loop back exportedRecv to exportedSend,
// but receive a value from ctlch before starting the loop.
func exportLoopback(exp *Exporter, t *testing.T) {
inch := make(chan int)
if err := exp.Export("exportedRecv", inch, Recv); err != nil {
t.Fatal("exportRecv")
}
outch := make(chan int)
if err := exp.Export("exportedSend", outch, Send); err != nil {
t.Fatal("exportSend")
}
ctlch := make(chan int)
if err := exp.Export("exportedCtl", ctlch, Recv); err != nil {
t.Fatal("exportRecv")
}
go func() {
<-ctlch
for i := 0; i < count; i++ {
x := <-inch
if x != base+i {
t.Errorf("exportLoopback expected %d; got %d", i, x)
}
outch <- x
}
}()
}
// This test checks that channel operations can proceed
// even when other concurrent operations are blocked.
func TestIndependentSends(t *testing.T) {
if testing.Short() {
t.Logf("disabled test during -short")
return
}
exp, imp := pair(t)
exportLoopback(exp, t)
importSend(imp, count, t, nil)
done := make(chan bool)
go importReceive(imp, t, done)
// wait for export side to try to deliver some values.
time.Sleep(250 * time.Millisecond)
ctlch := make(chan int)
if err := imp.ImportNValues("exportedCtl", ctlch, Send, 1, 1); err != nil {
t.Fatal("importSend:", err)
}
ctlch <- 0
<-done
}
// This test cross-connects a pair of exporter/importer pairs.
type value struct {
I int
Source string
}
func TestCrossConnect(t *testing.T) {
e1, i1 := pair(t)
e2, i2 := pair(t)
crossExport(e1, e2, t)
crossImport(i1, i2, t)
}
// Export side of cross-traffic.
func crossExport(e1, e2 *Exporter, t *testing.T) {
s := make(chan value)
err := e1.Export("exportedSend", s, Send)
if err != nil {
t.Fatal("exportSend:", err)
}
r := make(chan value)
err = e2.Export("exportedReceive", r, Recv)
if err != nil {
t.Fatal("exportReceive:", err)
}
go crossLoop("export", s, r, t)
}
// Import side of cross-traffic.
func crossImport(i1, i2 *Importer, t *testing.T) {
s := make(chan value)
err := i2.Import("exportedReceive", s, Send, 2)
if err != nil {
t.Fatal("import of exportedReceive:", err)
}
r := make(chan value)
err = i1.Import("exportedSend", r, Recv, 2)
if err != nil {
t.Fatal("import of exported Send:", err)
}
crossLoop("import", s, r, t)
}
// Cross-traffic: send and receive 'count' numbers.
func crossLoop(name string, s, r chan value, t *testing.T) {
for si, ri := 0, 0; si < count && ri < count; {
select {
case s <- value{si, name}:
si++
case v := <-r:
if v.I != ri {
t.Errorf("loop: bad value: expected %d, hello; got %+v", ri, v)
}
ri++
}
}
}
const flowCount = 100
// test flow control from exporter to importer.
func TestExportFlowControl(t *testing.T) {
if testing.Short() {
t.Logf("disabled test during -short")
return
}
exp, imp := pair(t)
sendDone := make(chan bool, 1)
exportSend(exp, flowCount, t, sendDone)
ch := make(chan int)
err := imp.ImportNValues("exportedSend", ch, Recv, 20, -1)
if err != nil {
t.Fatal("importReceive:", err)
}
testFlow(sendDone, ch, flowCount, t)
}
// test flow control from importer to exporter.
func TestImportFlowControl(t *testing.T) {
if testing.Short() {
t.Logf("disabled test during -short")
return
}
exp, imp := pair(t)
ch := make(chan int)
err := exp.Export("exportedRecv", ch, Recv)
if err != nil {
t.Fatal("importReceive:", err)
}
sendDone := make(chan bool, 1)
importSend(imp, flowCount, t, sendDone)
testFlow(sendDone, ch, flowCount, t)
}
func testFlow(sendDone chan bool, ch <-chan int, N int, t *testing.T) {
go func() {
time.Sleep(500 * time.Millisecond)
sendDone <- false
}()
if <-sendDone {
t.Fatal("send did not block")
}
n := 0
for i := range ch {
t.Log("after blocking, got value ", i)
n++
}
if n != N {
t.Fatalf("expected %d values; got %d", N, n)
}
}
func pair(t *testing.T) (*Exporter, *Importer) {
c0, c1 := net.Pipe()
exp := NewExporter()
go exp.ServeConn(c0)
imp := NewImporter(c1)
return exp, imp
}
golang-golang-x-exp_eb7c1fa/shiny/ 0000775 0000000 0000000 00000000000 12572222044 0017253 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/driver/ 0000775 0000000 0000000 00000000000 12572222044 0020546 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/driver/driver.go 0000664 0000000 0000000 00000001507 12572222044 0022373 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package driver provides the default driver for accessing a screen.
package driver
// TODO: figure out what to say about the responsibility for users of this
// package to check any implicit dependencies' LICENSEs. For example, the
// driver might use third party software outside of golang.org/x, like an X11
// or OpenGL library.
import (
"golang.org/x/exp/shiny/screen"
)
// Main is called by the program's main function to run the graphical
// application.
//
// It calls f on the Screen, possibly in a separate goroutine, as some OS-
// specific libraries require being on 'the main thread'. It returns when f
// returns.
func Main(f func(screen.Screen)) {
main(f)
}
golang-golang-x-exp_eb7c1fa/shiny/driver/driver_darwin.go 0000664 0000000 0000000 00000000520 12572222044 0023731 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin
package driver
import (
"golang.org/x/exp/shiny/driver/gldriver"
"golang.org/x/exp/shiny/screen"
)
func main(f func(screen.Screen)) {
gldriver.Main(f)
}
golang-golang-x-exp_eb7c1fa/shiny/driver/driver_fallback.go 0000664 0000000 0000000 00000000703 12572222044 0024207 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !darwin
// +build !linux android
// +build !windows
package driver
import (
"errors"
"golang.org/x/exp/shiny/driver/internal/errscreen"
"golang.org/x/exp/shiny/screen"
)
func main(f func(screen.Screen)) {
f(errscreen.Stub(errors.New("no driver for accessing a screen")))
}
golang-golang-x-exp_eb7c1fa/shiny/driver/driver_windows.go 0000664 0000000 0000000 00000000500 12572222044 0024135 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package driver
import (
"golang.org/x/exp/shiny/driver/windriver"
"golang.org/x/exp/shiny/screen"
)
func main(f func(screen.Screen)) {
windriver.Main(f)
}
golang-golang-x-exp_eb7c1fa/shiny/driver/driver_x11.go 0000664 0000000 0000000 00000000532 12572222044 0023061 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux,!android
package driver
import (
"golang.org/x/exp/shiny/driver/x11driver"
"golang.org/x/exp/shiny/screen"
)
func main(f func(screen.Screen)) {
x11driver.Main(f)
}
golang-golang-x-exp_eb7c1fa/shiny/driver/gldriver/ 0000775 0000000 0000000 00000000000 12572222044 0022364 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/driver/gldriver/buffer.go 0000664 0000000 0000000 00000001011 12572222044 0024155 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gldriver
import "image"
type bufferImpl struct {
rgba *image.RGBA
size image.Point
}
func (b *bufferImpl) Release() {}
func (b *bufferImpl) Size() image.Point { return b.size }
func (b *bufferImpl) Bounds() image.Rectangle { return image.Rectangle{Max: b.size} }
func (b *bufferImpl) RGBA() *image.RGBA { return b.rgba }
golang-golang-x-exp_eb7c1fa/shiny/driver/gldriver/cocoa.go 0000664 0000000 0000000 00000015402 12572222044 0024001 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin
// +build 386 amd64
// +build !ios
package gldriver
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Cocoa -framework OpenGL -framework QuartzCore
#import // for HIToolbox/Events.h
#import
#include
#include
void startDriver();
void stopDriver();
void makeCurrentContext(uintptr_t ctx);
uintptr_t doNewWindow(int width, int height);
uintptr_t doShowWindow(uintptr_t id);
void doCloseWindow(uintptr_t id);
uint64_t threadID();
*/
import "C"
import (
"log"
"runtime"
"golang.org/x/exp/shiny/screen"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/mouse"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/geom"
"golang.org/x/mobile/gl"
)
var initThreadID C.uint64_t
func init() {
// Lock the goroutine responsible for initialization to an OS thread.
// This means the goroutine running main (and calling startDriver below)
// is locked to the OS thread that started the program. This is
// necessary for the correct delivery of Cocoa events to the process.
//
// A discussion on this topic:
// https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ
runtime.LockOSThread()
initThreadID = C.threadID()
}
func newWindow(width, height int32) uintptr {
return uintptr(C.doNewWindow(C.int(width), C.int(height)))
}
func showWindow(id uintptr) uintptr {
return uintptr(C.doShowWindow(C.uintptr_t(id)))
}
func closeWindow(id uintptr) {
C.doCloseWindow(C.uintptr_t(id))
}
var (
theScreen = &screenImpl{
windows: make(map[uintptr]*windowImpl),
}
mainCallback func(screen.Screen)
)
func main(f func(screen.Screen)) error {
if tid := C.threadID(); tid != initThreadID {
log.Fatalf("gldriver.Main called on thread %d, but gldriver.init ran on %d", tid, initThreadID)
}
mainCallback = f
C.startDriver()
return nil
}
//export driverStarted
func driverStarted() {
go func() {
mainCallback(theScreen)
C.stopDriver()
}()
}
//export drawgl
func drawgl(id uintptr) {
theScreen.mu.Lock()
w := theScreen.windows[id]
theScreen.mu.Unlock()
if w == nil {
return // closing window
}
w.draw <- struct{}{}
<-w.drawDone
}
// drawLoop is the primary drawing loop.
//
// After Cocoa has created an NSWindow on the initial OS thread for
// processing Cocoa events in doNewWindow, it starts drawLoop on another
// goroutine. It is locked to an OS thread for its OpenGL context.
//
// Two Cocoa threads deliver draw signals to drawLoop. The primary
// source of draw events is the CVDisplayLink timer, which is tied to
// the display vsync. Secondary draw events come from [NSView drawRect:]
// when the window is resized.
func drawLoop(w *windowImpl, ctx uintptr) {
runtime.LockOSThread()
// TODO(crawshaw): there are several problematic issues around having
// a draw loop per window, but resolving them requires some thought.
// Firstly, nothing should race on gl.DoWork, so only one person can
// do that at a time. Secondly, which GL ctx we use matters. A ctx
// carries window-specific state (for example, the current glViewport
// value), so we only want to run GL commands on the right context
// between a <-w.draw and a <-w.drawDone. Thirdly, some GL functions
// can be legitimately called outside of a window draw cycle, for
// example, gl.CreateTexture. It doesn't matter which GL ctx we use
// for that, but we have to use a valid one. So if a window gets
// closed, it's important we swap the default ctx. More work needed.
C.makeCurrentContext(C.uintptr_t(ctx))
// TODO(crawshaw): exit this goroutine on Release.
for {
select {
case <-gl.WorkAvailable:
gl.DoWork()
case <-w.draw:
w.Send(paint.Event{})
loop:
for {
select {
case <-gl.WorkAvailable:
gl.DoWork()
case <-w.endPaint:
C.CGLFlushDrawable(C.CGLGetCurrentContext())
break loop
}
}
w.drawDone <- struct{}{}
}
}
}
//export setGeom
func setGeom(id uintptr, ppp float32, widthPx, heightPx int) {
theScreen.mu.Lock()
w := theScreen.windows[id]
theScreen.mu.Unlock()
if w == nil {
return // closing window
}
sz := size.Event{
WidthPx: widthPx,
HeightPx: heightPx,
WidthPt: geom.Pt(float32(widthPx) / ppp),
HeightPt: geom.Pt(float32(heightPx) / ppp),
PixelsPerPt: ppp,
}
w.mu.Lock()
w.sz = sz
w.mu.Unlock()
w.Send(sz)
}
//export windowClosing
func windowClosing(id uintptr) {
theScreen.mu.Lock()
w := theScreen.windows[id]
delete(theScreen.windows, id)
theScreen.mu.Unlock()
w.releaseCleanup()
}
func sendWindowEvent(id uintptr, e interface{}) {
theScreen.mu.Lock()
w := theScreen.windows[id]
theScreen.mu.Unlock()
if w == nil {
return // closing window
}
w.Send(e)
}
var mods = [...]struct {
flags uint32
code uint16
mod key.Modifiers
}{
// Left and right variants of modifier keys have their own masks,
// but they are not documented. These were determined empirically.
{1<<17 | 0x102, C.kVK_Shift, key.ModShift},
{1<<17 | 0x104, C.kVK_RightShift, key.ModShift},
{1<<18 | 0x101, C.kVK_Control, key.ModControl},
// TODO key.ControlRight
{1<<19 | 0x120, C.kVK_Option, key.ModAlt},
{1<<19 | 0x140, C.kVK_RightOption, key.ModAlt},
{1<<20 | 0x108, C.kVK_Command, key.ModMeta},
{1<<20 | 0x110, C.kVK_Command, key.ModMeta}, // TODO: missing kVK_RightCommand
}
func cocoaMods(flags uint32) (m key.Modifiers) {
for _, mod := range mods {
if flags&mod.flags == mod.flags {
m |= mod.mod
}
}
return m
}
func cocoaMouseDir(ty int32) mouse.Direction {
switch ty {
case C.NSLeftMouseDown, C.NSRightMouseDown, C.NSOtherMouseDown:
return mouse.DirPress
case C.NSLeftMouseUp, C.NSRightMouseUp, C.NSOtherMouseUp:
return mouse.DirRelease
default: // dragged
return mouse.DirNone
}
}
func cocoaMouseButton(button int32) mouse.Button {
switch button {
case 0:
return mouse.ButtonLeft
case 1:
return mouse.ButtonRight
case 2:
return mouse.ButtonMiddle
default:
return mouse.ButtonNone
}
}
//export mouseEvent
func mouseEvent(id uintptr, x, y float32, ty, button int32, flags uint32) {
sendWindowEvent(id, mouse.Event{
X: x,
Y: y,
Button: cocoaMouseButton(button),
Direction: cocoaMouseDir(ty),
Modifiers: cocoaMods(flags),
})
}
func sendLifecycle(to lifecycle.Stage) {
log.Printf("sendLifecycle: %v", to) // TODO
}
//export lifecycleDead
func lifecycleDead() { sendLifecycle(lifecycle.StageDead) }
//export lifecycleAlive
func lifecycleAlive() { sendLifecycle(lifecycle.StageAlive) }
//export lifecycleVisible
func lifecycleVisible() { sendLifecycle(lifecycle.StageVisible) }
//export lifecycleFocused
func lifecycleFocused() { sendLifecycle(lifecycle.StageFocused) }
golang-golang-x-exp_eb7c1fa/shiny/driver/gldriver/cocoa.m 0000664 0000000 0000000 00000021301 12572222044 0023623 0 ustar 00root root 0000000 0000000 // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin
// +build 386 amd64
// +build !ios
#include "_cgo_export.h"
#include
#include
#import
#import
#import
#import
#import
static CVReturn displayLinkDraw(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
drawgl((GoUintptr)displayLinkContext);
return kCVReturnSuccess;
}
void makeCurrentContext(uintptr_t context) {
NSOpenGLContext* ctx = (NSOpenGLContext*)context;
[ctx makeCurrentContext];
}
uint64 threadID() {
uint64 id;
if (pthread_threadid_np(pthread_self(), &id)) {
abort();
}
return id;
}
@interface ScreenGLView : NSOpenGLView
{
// Each window maintains its own display link, so that windows on
// different displays honor their current display.
//
// TODO(crawshaw): test that display links transfer properly, if I
// ever find a machine with more than one display.
@public CVDisplayLinkRef displayLink;
}
@end
@implementation ScreenGLView
- (void)prepareOpenGL {
[self setWantsBestResolutionOpenGLSurface:YES];
GLint swapInt = 1;
[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
CVDisplayLinkSetOutputCallback(displayLink, &displayLinkDraw, self);
CGLContextObj cglContext = [[self openGLContext] CGLContextObj];
CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj];
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext, cglPixelFormat);
// Using attribute arrays in OpenGL 3.3 requires the use of a VBA.
// But VBAs don't exist in ES 2. So we bind a default one.
GLuint vba;
glGenVertexArrays(1, &vba);
glBindVertexArray(vba);
}
- (void)callSetGeom {
// Calculate screen PPI.
//
// Note that the backingScaleFactor converts from logical
// pixels to actual pixels, but both of these units vary
// independently from real world size. E.g.
//
// 13" Retina Macbook Pro, 2560x1600, 227ppi, backingScaleFactor=2, scale=3.15
// 15" Retina Macbook Pro, 2880x1800, 220ppi, backingScaleFactor=2, scale=3.06
// 27" iMac, 2560x1440, 109ppi, backingScaleFactor=1, scale=1.51
// 27" Retina iMac, 5120x2880, 218ppi, backingScaleFactor=2, scale=3.03
NSScreen *screen = self.window.screen;
double screenPixW = [screen frame].size.width * [screen backingScaleFactor];
CGDirectDisplayID display = (CGDirectDisplayID)[[[screen deviceDescription] valueForKey:@"NSScreenNumber"] intValue];
CGSize screenSizeMM = CGDisplayScreenSize(display); // in millimeters
float ppi = 25.4 * screenPixW / screenSizeMM.width;
float pixelsPerPt = ppi/72.0;
// The width and height reported to the geom package are the
// bounds of the OpenGL view. Several steps are necessary.
// First, [self bounds] gives us the number of logical pixels
// in the view. Multiplying this by the backingScaleFactor
// gives us the number of actual pixels.
NSRect r = [self bounds];
int w = r.size.width * [screen backingScaleFactor];
int h = r.size.height * [screen backingScaleFactor];
setGeom((GoUintptr)self, pixelsPerPt, w, h);
}
- (void)reshape {
[super reshape];
[self callSetGeom];
}
- (void)drawRect:(NSRect)theRect {
// Called during resize. Do an extra draw if we are visible.
// This gets rid of flicker when resizing.
if (CVDisplayLinkIsRunning(displayLink)) {
drawgl((GoUintptr)self);
}
}
- (void)mouseEventNS:(NSEvent *)theEvent {
double scale = [self.window.screen backingScaleFactor];
NSPoint p = [theEvent locationInWindow];
double x = p.x * scale;
double y = p.y * scale;
mouseEvent((GoUintptr)self, x, y, theEvent.type, theEvent.buttonNumber, theEvent.modifierFlags);
}
- (void)mouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
- (void)mouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
- (void)mouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
- (void)rightMouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
- (void)rightMouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
- (void)rightMouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
- (void)otherMouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
- (void)otherMouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
- (void)otherMouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; }
- (void)windowDidChangeScreenProfile:(NSNotification *)notification {
[self callSetGeom];
}
- (void)windowDidBecomeKey:(NSNotification *)notification {
lifecycleFocused();
}
- (void)windowDidResignKey:(NSNotification *)notification {
if (![NSApp isHidden]) {
lifecycleVisible();
}
}
- (void)windowWillClose:(NSNotification *)notification {
if (self.window.nextResponder == NULL) {
return; // already called close
}
CVDisplayLinkStop(displayLink);
lifecycleAlive();
windowClosing((GoUintptr)self);
[self.window.nextResponder release];
self.window.nextResponder = NULL;
}
@end
@interface WindowResponder : NSResponder
{
}
@end
@implementation WindowResponder
// TODO(crawshaw): key events
@end
@interface AppDelegate : NSObject
{
}
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
driverStarted();
lifecycleAlive();
[[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
lifecycleVisible();
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
lifecycleDead();
}
- (void)applicationDidHide:(NSNotification *)aNotification {
// TODO stop all window display links
lifecycleAlive();
}
- (void)applicationWillUnhide:(NSNotification *)notification {
// TODO start all window display links
lifecycleVisible();
}
@end
uintptr_t doNewWindow(int width, int height) {
NSScreen *screen = [NSScreen mainScreen];
__block double w = (double)width / [screen backingScaleFactor];
__block double h = (double)height / [screen backingScaleFactor];
__block ScreenGLView* view = NULL;
dispatch_barrier_sync(dispatch_get_main_queue(), ^{
id menuBar = [NSMenu new];
id menuItem = [NSMenuItem new];
[menuBar addItem:menuItem];
[NSApp setMainMenu:menuBar];
id menu = [NSMenu new];
NSString* name = [[NSProcessInfo processInfo] processName];
id hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide"
action:@selector(hide:) keyEquivalent:@"h"];
[menu addItem:hideMenuItem];
id quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
action:@selector(terminate:) keyEquivalent:@"q"];
[menu addItem:quitMenuItem];
[menuItem setSubmenu:menu];
NSRect rect = NSMakeRect(0, 0, w, h);
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:NO];
window.styleMask |= NSResizableWindowMask;
window.styleMask |= NSMiniaturizableWindowMask ;
window.styleMask |= NSClosableWindowMask;
window.title = name;
window.displaysWhenScreenProfileChanges = YES;
[window cascadeTopLeftFromPoint:NSMakePoint(20,20)];
NSOpenGLPixelFormatAttribute attr[] = {
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
NSOpenGLPFAColorSize, 24,
NSOpenGLPFAAlphaSize, 8,
NSOpenGLPFADepthSize, 16,
NSOpenGLPFAAccelerated,
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAAllowOfflineRenderers,
0
};
id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
view = [[ScreenGLView alloc] initWithFrame:rect pixelFormat:pixFormat];
[window setContentView:view];
[window setDelegate:view];
window.nextResponder = [[WindowResponder alloc] init];
});
return (uintptr_t)view;
}
uintptr_t doShowWindow(uintptr_t viewID) {
ScreenGLView* view = (ScreenGLView*)viewID;
__block uintptr_t ret = 0;
dispatch_barrier_sync(dispatch_get_main_queue(), ^{
[view.window makeKeyAndOrderFront:view.window];
CVDisplayLinkStart(view->displayLink);
ret = (uintptr_t)[view openGLContext];
});
return ret;
}
void doCloseWindow(uintptr_t viewID) {
ScreenGLView* view = (ScreenGLView*)viewID;
dispatch_sync(dispatch_get_main_queue(), ^{
[view.window performClose:view];
});
}
void startDriver() {
[NSAutoreleasePool new];
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
AppDelegate* delegate = [[AppDelegate alloc] init];
[NSApp setDelegate:delegate];
[NSApp run];
}
void stopDriver() {
dispatch_async(dispatch_get_main_queue(), ^{
[NSApp terminate:nil];
});
}
golang-golang-x-exp_eb7c1fa/shiny/driver/gldriver/gldriver.go 0000664 0000000 0000000 00000006560 12572222044 0024540 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package gldriver provides an OpenGL driver for accessing a screen.
package gldriver
import (
"encoding/binary"
"fmt"
"math"
"golang.org/x/exp/shiny/driver/internal/errscreen"
"golang.org/x/exp/shiny/screen"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/gl"
)
// Main is called by the program's main function to run the graphical
// application.
//
// It calls f on the Screen, possibly in a separate goroutine, as some OS-
// specific libraries require being on 'the main thread'. It returns when f
// returns.
func Main(f func(screen.Screen)) {
if err := main(f); err != nil {
f(errscreen.Stub(err))
}
}
func mul(a, b f64.Aff3) f64.Aff3 {
return f64.Aff3{
a[0]*b[0] + a[1]*b[3],
a[0]*b[1] + a[1]*b[4],
a[0]*b[2] + a[1]*b[5] + a[2],
a[3]*b[0] + a[4]*b[3],
a[3]*b[1] + a[4]*b[4],
a[3]*b[2] + a[4]*b[5] + a[5],
}
}
func writeAff3(u gl.Uniform, a f64.Aff3) {
var m [9]float32
m[0*3+0] = float32(a[0*3+0])
m[0*3+1] = float32(a[1*3+0])
m[0*3+2] = 0
m[1*3+0] = float32(a[0*3+1])
m[1*3+1] = float32(a[1*3+1])
m[1*3+2] = 0
m[2*3+0] = float32(a[0*3+2])
m[2*3+1] = float32(a[1*3+2])
m[2*3+2] = 1
gl.UniformMatrix3fv(u, m[:])
}
// f32Bytes returns the byte representation of float32 values in the given byte
// order. byteOrder must be either binary.BigEndian or binary.LittleEndian.
func f32Bytes(byteOrder binary.ByteOrder, values ...float32) []byte {
le := false
switch byteOrder {
case binary.BigEndian:
case binary.LittleEndian:
le = true
default:
panic(fmt.Sprintf("invalid byte order %v", byteOrder))
}
b := make([]byte, 4*len(values))
for i, v := range values {
u := math.Float32bits(v)
if le {
b[4*i+0] = byte(u >> 0)
b[4*i+1] = byte(u >> 8)
b[4*i+2] = byte(u >> 16)
b[4*i+3] = byte(u >> 24)
} else {
b[4*i+0] = byte(u >> 24)
b[4*i+1] = byte(u >> 16)
b[4*i+2] = byte(u >> 8)
b[4*i+3] = byte(u >> 0)
}
}
return b
}
func compileProgram(vSrc, fSrc string) (gl.Program, error) {
program := gl.CreateProgram()
if program.Value == 0 {
return gl.Program{}, fmt.Errorf("gldriver: no programs available")
}
vertexShader, err := compileShader(gl.VERTEX_SHADER, vSrc)
if err != nil {
return gl.Program{}, err
}
fragmentShader, err := compileShader(gl.FRAGMENT_SHADER, fSrc)
if err != nil {
gl.DeleteShader(vertexShader)
return gl.Program{}, err
}
gl.AttachShader(program, vertexShader)
gl.AttachShader(program, fragmentShader)
gl.LinkProgram(program)
// Flag shaders for deletion when program is unlinked.
gl.DeleteShader(vertexShader)
gl.DeleteShader(fragmentShader)
if gl.GetProgrami(program, gl.LINK_STATUS) == 0 {
defer gl.DeleteProgram(program)
return gl.Program{}, fmt.Errorf("gldriver: program compile: %s", gl.GetProgramInfoLog(program))
}
return program, nil
}
func compileShader(shaderType gl.Enum, src string) (gl.Shader, error) {
shader := gl.CreateShader(shaderType)
if shader.Value == 0 {
return gl.Shader{}, fmt.Errorf("gldriver: could not create shader (type %v)", shaderType)
}
gl.ShaderSource(shader, src)
gl.CompileShader(shader)
if gl.GetShaderi(shader, gl.COMPILE_STATUS) == 0 {
defer gl.DeleteShader(shader)
return gl.Shader{}, fmt.Errorf("gldriver: shader compile: %s", gl.GetShaderInfoLog(shader))
}
return shader, nil
}
golang-golang-x-exp_eb7c1fa/shiny/driver/gldriver/other.go 0000664 0000000 0000000 00000001155 12572222044 0024036 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !darwin !386,!amd64 ios
package gldriver
import (
"fmt"
"runtime"
"golang.org/x/exp/shiny/screen"
)
func newWindow(width, height int32) uintptr { return 0 }
func showWindow(id uintptr) uintptr { return 0 }
func closeWindow(id uintptr) {}
func drawLoop(w *windowImpl, ctx uintptr) {}
func main(f func(screen.Screen)) error {
return fmt.Errorf("gldriver: unsupported GOOS/GOARCH %s/%s", runtime.GOOS, runtime.GOARCH)
}
golang-golang-x-exp_eb7c1fa/shiny/driver/gldriver/screen.go 0000664 0000000 0000000 00000005220 12572222044 0024171 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gldriver
import (
"image"
"sync"
"golang.org/x/exp/shiny/driver/internal/pump"
"golang.org/x/exp/shiny/screen"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/gl"
)
type screenImpl struct {
mu sync.Mutex
windows map[uintptr]*windowImpl
texture struct {
program gl.Program
pos gl.Attrib
mvp gl.Uniform
uvp gl.Uniform
inUV gl.Attrib
sample gl.Uniform
quadXY gl.Buffer
quadUV gl.Buffer
}
fill struct {
program gl.Program
pos gl.Attrib
mvp gl.Uniform
color gl.Uniform
quadXY gl.Buffer
}
}
func (s *screenImpl) NewBuffer(size image.Point) (retBuf screen.Buffer, retErr error) {
return &bufferImpl{
rgba: image.NewRGBA(image.Rectangle{Max: size}),
size: size,
}, nil
}
func (s *screenImpl) NewTexture(size image.Point) (screen.Texture, error) {
s.mu.Lock()
defer s.mu.Unlock()
if !gl.IsProgram(s.texture.program) {
p, err := compileProgram(textureVertexSrc, textureFragmentSrc)
if err != nil {
return nil, err
}
s.texture.program = p
s.texture.pos = gl.GetAttribLocation(p, "pos")
s.texture.mvp = gl.GetUniformLocation(p, "mvp")
s.texture.uvp = gl.GetUniformLocation(p, "uvp")
s.texture.inUV = gl.GetAttribLocation(p, "inUV")
s.texture.sample = gl.GetUniformLocation(p, "sample")
s.texture.quadXY = gl.CreateBuffer()
s.texture.quadUV = gl.CreateBuffer()
gl.BindBuffer(gl.ARRAY_BUFFER, s.texture.quadXY)
gl.BufferData(gl.ARRAY_BUFFER, quadXYCoords, gl.STATIC_DRAW)
gl.BindBuffer(gl.ARRAY_BUFFER, s.texture.quadUV)
gl.BufferData(gl.ARRAY_BUFFER, quadUVCoords, gl.STATIC_DRAW)
}
t := &textureImpl{
id: gl.CreateTexture(),
size: size,
}
gl.BindTexture(gl.TEXTURE_2D, t.id)
gl.TexImage2D(gl.TEXTURE_2D, 0, size.X, size.Y, gl.RGBA, gl.UNSIGNED_BYTE, nil)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
return t, nil
}
func (s *screenImpl) NewWindow(opts *screen.NewWindowOptions) (screen.Window, error) {
// TODO: look at opts.
const width, height = 1024, 768
id := newWindow(width, height)
w := &windowImpl{
s: s,
id: id,
pump: pump.Make(),
endPaint: make(chan paint.Event, 1),
draw: make(chan struct{}),
drawDone: make(chan struct{}),
}
s.mu.Lock()
s.windows[id] = w
s.mu.Unlock()
go drawLoop(w, showWindow(id))
return w, nil
}
golang-golang-x-exp_eb7c1fa/shiny/driver/gldriver/texture.go 0000664 0000000 0000000 00000004405 12572222044 0024416 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gldriver
import (
"encoding/binary"
"image"
"image/color"
"image/draw"
"golang.org/x/exp/shiny/screen"
"golang.org/x/mobile/gl"
)
type textureImpl struct {
id gl.Texture
size image.Point
}
func (t *textureImpl) Size() image.Point { return t.size }
func (t *textureImpl) Bounds() image.Rectangle { return image.Rectangle{Max: t.size} }
func (t *textureImpl) Release() {
gl.DeleteTexture(t.id)
t.id = gl.Texture{}
}
func (t *textureImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle, sender screen.Sender) {
t.upload(dp, src, sr, sender, t)
}
func (t *textureImpl) upload(dp image.Point, src screen.Buffer, sr image.Rectangle, sender screen.Sender, u screen.Uploader) {
// TODO: adjust if dp is outside dst bounds, or r is outside src bounds.
gl.BindTexture(gl.TEXTURE_2D, t.id)
m := src.RGBA().SubImage(sr).(*image.RGBA)
b := m.Bounds()
// TODO check m bounds smaller than t.size
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, b.Dx(), b.Dy(), gl.RGBA, gl.UNSIGNED_BYTE, m.Pix)
sender.Send(screen.UploadedEvent{Buffer: src, Uploader: u})
}
func (t *textureImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
// TODO.
}
var quadXYCoords = f32Bytes(binary.LittleEndian,
-1, +1, // top left
+1, +1, // top right
-1, -1, // bottom left
+1, -1, // bottom right
)
var quadUVCoords = f32Bytes(binary.LittleEndian,
0, 0, // top left
1, 0, // top right
0, 1, // bottom left
1, 1, // bottom right
)
const textureVertexSrc = `#version 100
uniform mat3 mvp;
uniform mat3 uvp;
attribute vec3 pos;
attribute vec2 inUV;
varying vec2 uv;
void main() {
vec3 p = pos;
p.z = 1.0;
gl_Position = vec4(mvp * p, 1);
uv = (uvp * vec3(inUV, 1)).xy;
}
`
const textureFragmentSrc = `#version 100
precision mediump float;
varying vec2 uv;
uniform sampler2D sample;
void main() {
gl_FragColor = texture2D(sample, uv);
}
`
const fillVertexSrc = `#version 100
uniform mat3 mvp;
attribute vec3 pos;
void main() {
vec3 p = pos;
p.z = 1.0;
gl_Position = vec4(mvp * p, 1);
}
`
const fillFragmentSrc = `#version 100
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}
`
golang-golang-x-exp_eb7c1fa/shiny/driver/gldriver/window.go 0000664 0000000 0000000 00000015276 12572222044 0024235 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gldriver
import (
"image"
"image/color"
"image/draw"
"sync"
"golang.org/x/exp/shiny/driver/internal/pump"
"golang.org/x/exp/shiny/screen"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/gl"
)
type windowImpl struct {
s *screenImpl
id uintptr // *C.ScreenGLView
pump pump.Pump
endPaint chan paint.Event
draw chan struct{}
drawDone chan struct{}
mu sync.Mutex
sz size.Event
}
func (w *windowImpl) Release() {
// There are two ways a window can be closed. The first is the user
// clicks the red button, in which case windowWillClose is called,
// which calls Go's windowClosing, which does cleanup in
// releaseCleanup below.
//
// The second way is Release is called programmatically. This calls
// the NSWindow method performClose, which emulates the red button
// being clicked.
//
// If these two approaches race, experiments suggest it is resolved
// by performClose (which is called serially on the main thread).
// If that stops being true, there is a check in windowWillClose
// that avoids the Go cleanup code being invoked more than once.
closeWindow(w.id)
}
func (w *windowImpl) releaseCleanup() {
w.pump.Release()
}
func (w *windowImpl) Events() <-chan interface{} { return w.pump.Events() }
func (w *windowImpl) Send(event interface{}) { w.pump.Send(event) }
func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle, sender screen.Sender) {
// TODO: adjust if dp is outside dst bounds, or sr is outside src bounds.
// TODO: keep a texture around for this purpose?
t, err := w.s.NewTexture(sr.Size())
if err != nil {
panic(err)
}
t.(*textureImpl).upload(dp, src, sr, sender, w)
w.Draw(f64.Aff3{1, 0, 0, 0, 1, 0}, t, sr, draw.Src, nil)
t.Release()
}
func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
if !gl.IsProgram(w.s.fill.program) {
p, err := compileProgram(fillVertexSrc, fillFragmentSrc)
if err != nil {
// TODO: initialize this somewhere else we can better handle the error.
panic(err.Error())
}
w.s.fill.program = p
w.s.fill.pos = gl.GetAttribLocation(p, "pos")
w.s.fill.mvp = gl.GetUniformLocation(p, "mvp")
w.s.fill.color = gl.GetUniformLocation(p, "color")
w.s.fill.quadXY = gl.CreateBuffer()
gl.BindBuffer(gl.ARRAY_BUFFER, w.s.fill.quadXY)
gl.BufferData(gl.ARRAY_BUFFER, quadXYCoords, gl.STATIC_DRAW)
}
gl.UseProgram(w.s.fill.program)
writeAff3(w.s.fill.mvp, w.vertexAff3(dr))
r, g, b, a := src.RGBA()
gl.Uniform4f(
w.s.fill.color,
float32(r)/65535,
float32(g)/65535,
float32(b)/65535,
float32(a)/65535,
)
gl.BindBuffer(gl.ARRAY_BUFFER, w.s.fill.quadXY)
gl.EnableVertexAttribArray(w.s.fill.pos)
gl.VertexAttribPointer(w.s.fill.pos, 2, gl.FLOAT, false, 0, 0)
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
gl.DisableVertexAttribArray(w.s.fill.pos)
}
func (w *windowImpl) vertexAff3(r image.Rectangle) f64.Aff3 {
w.mu.Lock()
sz := w.sz
w.mu.Unlock()
size := r.Size()
tx, ty := float64(size.X), float64(size.Y)
wx, wy := float64(sz.WidthPx), float64(sz.HeightPx)
rx, ry := tx/wx, ty/wy
// We are drawing the texture src onto the window's framebuffer.
// The texture is (0,0)-(tx,ty). The window is (0,0)-(wx,wy), which
// in vertex shader space is
//
// (-1, +1) (+1, +1)
// (-1, -1) (+1, -1)
//
// A src2dst unit affine transform
//
// 1 0 0
// 0 1 0
// 0 0 1
//
// should result in a (tx,ty) texture appearing in the upper-left
// (tx, ty) pixels of the window.
//
// Setting w.s.texture.mvp to a unit affine transform results in
// mapping the 2-unit square (-1,+1)-(+1,-1) given by quadXYCoords
// in texture.go to the same coordinates in vertex shader space.
// Thus, it results in the whole texture ((tx, ty) in texture
// space) occupying the whole window ((wx, wy) in window space).
//
// A scaling affine transform
//
// rx 0 0
// 0 ry 0
// 0 0 1
//
// results in a (tx, ty) texture occupying (tx, ty) pixels in the
// center of the window.
//
// For upper-left alignment, we want to translate by
// (-(1-rx), 1-ry), which is the affine transform
//
// 1 0 -1+rx
// 0 1 +1-ry
// 0 0 1
//
// These multiply to give:
return f64.Aff3{
rx, 0, -1 + rx,
0, ry, +1 - ry,
}
}
func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
t := src.(*textureImpl)
a := w.vertexAff3(sr)
gl.UseProgram(w.s.texture.program)
writeAff3(w.s.texture.mvp, mul(a, src2dst))
// OpenGL's fragment shaders' UV coordinates run from (0,0)-(1,1),
// unlike vertex shaders' XY coordinates running from (-1,+1)-(+1,-1).
//
// We are drawing a rectangle PQRS, defined by two of its
// corners, onto the entire texture. The two quads may actually
// be equal, but in the general case, PQRS can be smaller.
//
// (0,0) +---------------+ (1,0)
// | P +-----+ Q |
// | | | |
// | S +-----+ R |
// (0,1) +---------------+ (1,1)
//
// The PQRS quad is always axis-aligned. First of all, convert
// from pixel space to texture space.
tw := float64(t.size.X)
th := float64(t.size.Y)
px := float64(sr.Min.X-0) / tw
py := float64(sr.Min.Y-0) / th
qx := float64(sr.Max.X-0) / tw
sy := float64(sr.Max.Y-0) / th
// Due to axis alignment, qy = py and sx = px.
//
// The simultaneous equations are:
// 0 + 0 + a02 = px
// 0 + 0 + a12 = py
// a00 + 0 + a02 = qx
// a10 + 0 + a12 = qy = py
// 0 + a01 + a02 = sx = px
// 0 + a11 + a12 = sy
writeAff3(w.s.texture.uvp, f64.Aff3{
qx - px, 0, px,
0, sy - py, py,
})
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, t.id)
gl.Uniform1i(w.s.texture.sample, 0)
gl.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quadXY)
gl.EnableVertexAttribArray(w.s.texture.pos)
gl.VertexAttribPointer(w.s.texture.pos, 2, gl.FLOAT, false, 0, 0)
gl.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quadUV)
gl.EnableVertexAttribArray(w.s.texture.inUV)
gl.VertexAttribPointer(w.s.texture.inUV, 2, gl.FLOAT, false, 0, 0)
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
gl.DisableVertexAttribArray(w.s.texture.pos)
gl.DisableVertexAttribArray(w.s.texture.inUV)
}
func (w *windowImpl) EndPaint(e paint.Event) {
// gl.Flush is a lightweight (on modern GL drivers) blocking call
// that ensures all GL functions pending in the gl package have
// been passed onto the GL driver before the app package attempts
// to swap the screen buffer.
//
// This enforces that the final receive (for this paint cycle) on
// gl.WorkAvailable happens before the send on endPaint.
gl.Flush()
w.endPaint <- e
}
golang-golang-x-exp_eb7c1fa/shiny/driver/internal/ 0000775 0000000 0000000 00000000000 12572222044 0022362 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/driver/internal/errscreen/ 0000775 0000000 0000000 00000000000 12572222044 0024352 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/driver/internal/errscreen/errscreen.go 0000664 0000000 0000000 00000001360 12572222044 0026671 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package errscreen provides a stub Screen implementation.
package errscreen
import (
"image"
"golang.org/x/exp/shiny/screen"
)
// Stub returns a Screen whose methods all return the given error.
func Stub(err error) screen.Screen {
return stub{err}
}
type stub struct {
err error
}
func (s stub) NewBuffer(size image.Point) (screen.Buffer, error) { return nil, s.err }
func (s stub) NewTexture(size image.Point) (screen.Texture, error) { return nil, s.err }
func (s stub) NewWindow(opts *screen.NewWindowOptions) (screen.Window, error) { return nil, s.err }
golang-golang-x-exp_eb7c1fa/shiny/driver/internal/pump/ 0000775 0000000 0000000 00000000000 12572222044 0023343 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/driver/internal/pump/pump.go 0000664 0000000 0000000 00000004100 12572222044 0024646 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package pump provides an infinitely buffered event channel.
package pump
// Make returns a new Pump. Call Release to stop pumping events.
func Make() Pump {
p := Pump{
in: make(chan interface{}),
out: make(chan interface{}),
release: make(chan struct{}),
}
go p.run()
return p
}
// Pump is an event pump, such that calling Send(e) will eventually send e on
// the event channel, in order, but Send will always complete soon, even if
// nothing is receiving on the event channel. It is effectively an infinitely
// buffered channel.
//
// In particular, goroutine A calling p.Send will not deadlock even if
// goroutine B that's responsible for receiving on p.Events() is currently
// blocked trying to send to A on a separate channel.
type Pump struct {
in chan interface{}
out chan interface{}
release chan struct{}
}
// Events returns the event channel.
func (p *Pump) Events() <-chan interface{} {
return p.out
}
// Send sends an event on the event channel.
func (p *Pump) Send(event interface{}) {
select {
case p.in <- event:
case <-p.release:
}
}
// Release stops the event pump. Pending events may or may not be delivered on
// the event channel. Calling Release will not close the event channel.
func (p *Pump) Release() {
close(p.release)
}
func (p *Pump) run() {
// initialSize is the initial size of the circular buffer. It must be a
// power of 2.
const initialSize = 16
i, j, buf, mask := 0, 0, make([]interface{}, initialSize), initialSize-1
for {
maybeOut := p.out
if i == j {
maybeOut = nil
}
select {
case maybeOut <- buf[i&mask]:
buf[i&mask] = nil
i++
case e := <-p.in:
// Allocate a bigger buffer if necessary.
if i+len(buf) == j {
b := make([]interface{}, 2*len(buf))
n := copy(b, buf[j&mask:])
copy(b[n:], buf[:j&mask])
i, j = 0, len(buf)
buf, mask = b, len(b)-1
}
buf[j&mask] = e
j++
case <-p.release:
return
}
}
}
golang-golang-x-exp_eb7c1fa/shiny/driver/internal/swizzle/ 0000775 0000000 0000000 00000000000 12572222044 0024071 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/driver/internal/swizzle/swizzle_amd64.go 0000664 0000000 0000000 00000000345 12572222044 0027124 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package swizzle
const (
haveSIMD16 = true
)
func bgra16(p []byte)
golang-golang-x-exp_eb7c1fa/shiny/driver/internal/swizzle/swizzle_amd64.s 0000664 0000000 0000000 00000001521 12572222044 0026756 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "textflag.h"
TEXT ·bgra16(SB),NOSPLIT,$0-24
MOVQ p+0(FP), SI
MOVQ len+8(FP), DI
// Sanity check that len is a multiple of 16.
MOVQ DI, AX
ANDQ $15, AX
CMPQ AX, $0
JNE done
// Make the shuffle control mask (16-byte register X0) look like this,
// where the low order byte comes first:
//
// 02 01 00 03 06 05 04 07 0a 09 08 0b 0e 0d 0c 0f
//
// Load the bottom 8 bytes into X0, the top into X1, then interleave them
// into X0.
MOVQ $0x0704050603000102, AX
MOVQ AX, X0
MOVQ $0x0f0c0d0e0b08090a, AX
MOVQ AX, X1
PUNPCKLQDQ X1, X0
ADDQ SI, DI
loop:
CMPQ SI, DI
JEQ done
MOVOU (SI), X1
PSHUFB X0, X1
MOVOU X1, (SI)
ADDQ $16, SI
JMP loop
done:
RET
golang-golang-x-exp_eb7c1fa/shiny/driver/internal/swizzle/swizzle_common.go 0000664 0000000 0000000 00000001314 12572222044 0027476 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package swizzle provides functions for converting between RGBA pixel
// formats.
package swizzle
// BGRA converts a pixel buffer between Go's RGBA and other systems' BGRA byte
// orders.
//
// It panics if the input slice length is not a multiple of 4.
func BGRA(p []byte) {
if len(p)%4 != 0 {
panic("input slice length is not a multiple of 4")
}
// Use SIMD code for 16-byte chunks, if supported.
if haveSIMD16 {
n := len(p) &^ (16 - 1)
bgra16(p[:n])
p = p[n:]
}
for i := 0; i < len(p); i += 4 {
p[i+0], p[i+2] = p[i+2], p[i+0]
}
}
golang-golang-x-exp_eb7c1fa/shiny/driver/internal/swizzle/swizzle_other.go 0000664 0000000 0000000 00000000421 12572222044 0027325 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !amd64
package swizzle
const (
haveSIMD16 = false
)
func bgra16(p []byte) { panic("unreachable") }
golang-golang-x-exp_eb7c1fa/shiny/driver/internal/swizzle/swizzle_test.go 0000664 0000000 0000000 00000003705 12572222044 0027173 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package swizzle
import (
"bytes"
"math/rand"
"testing"
)
func TestBGRAShortInput(t *testing.T) {
const s = "012.456.89A.CDE.GHI.KLM.O"
testCases := []string{
0: "012.456.89A.CDE.GHI.KLM.O",
1: "210.456.89A.CDE.GHI.KLM.O",
2: "210.654.89A.CDE.GHI.KLM.O",
3: "210.654.A98.CDE.GHI.KLM.O",
4: "210.654.A98.EDC.GHI.KLM.O",
5: "210.654.A98.EDC.IHG.KLM.O",
6: "210.654.A98.EDC.IHG.MLK.O",
}
for i, want := range testCases {
b := []byte(s)
BGRA(b[:4*i])
got := string(b)
if got != want {
t.Errorf("i=%d: got %q, want %q", i, got, want)
}
changed := got != s
wantChanged := i != 0
if changed != wantChanged {
t.Errorf("i=%d: changed=%t, want %t", i, changed, wantChanged)
}
}
}
func TestBGRARandomInput(t *testing.T) {
r := rand.New(rand.NewSource(1))
fastBuf := make([]byte, 1024)
slowBuf := make([]byte, 1024)
for i := range fastBuf {
fastBuf[i] = uint8(r.Intn(256))
}
copy(slowBuf, fastBuf)
for i := 0; i < 100000; i++ {
o := r.Intn(len(fastBuf))
n := r.Intn(len(fastBuf)-o) &^ 0x03
BGRA(fastBuf[o : o+n])
pureGoBGRA(slowBuf[o : o+n])
if bytes.Equal(fastBuf, slowBuf) {
continue
}
for j := range fastBuf {
x := fastBuf[j]
y := slowBuf[j]
if x != y {
t.Fatalf("iter %d: swizzling [%d:%d+%d]: bytes differ at offset %d (aka %d+%d): %#02x vs %#02x",
i, o, o, n, j, o, j-o, x, y)
}
}
}
}
func pureGoBGRA(p []byte) {
if len(p)%4 != 0 {
return
}
for i := 0; i < len(p); i += 4 {
p[i+0], p[i+2] = p[i+2], p[i+0]
}
}
func benchmarkBGRA(b *testing.B, f func([]byte)) {
const w, h = 1920, 1080 // 1080p RGBA.
buf := make([]byte, 4*w*h)
b.ResetTimer()
for i := 0; i < b.N; i++ {
f(buf)
}
}
func BenchmarkBGRA(b *testing.B) { benchmarkBGRA(b, BGRA) }
func BenchmarkPureGoBGRA(b *testing.B) { benchmarkBGRA(b, pureGoBGRA) }
golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/ 0000775 0000000 0000000 00000000000 12572222044 0022557 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/doc.go 0000664 0000000 0000000 00000010515 12572222044 0023655 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package windriver provides the Windows driver for accessing a screen.
package windriver
/*
Implementation Details
On Windows, UI can run on any thread, but any windows created
on a thread must only be manipulated from that thread. You can send
"window messages" to any window; when you send a window
message to a window owned by another thread, Windows will
temporarily switch to that thread to dispatch the message. As such,
windows serve as the communication endpoints between threads
on Windows. In addition, each thread that hosts UI must handle
incoming window messages from the OS through a "message pump".
These messages include paint events and input events.
windriver designates the thread that calls Main as the UI thread.
It locks this thread, creates a special window to handle screen.Screen
calls, runs the function passed to Main on another goroutine, and
runs a message pump.
The window that handles screen.Screen functions is currently called
the "utility window". A better name can be chosen later. This window
handles creating screen.Windows/Buffers/Textures. As such, all shiny
Windows are owned by a single thread.
Each function in windriver, be it on screen.Screen or screen.Window,
is translated into a window message and sent to a window, namely
the utility window and the screen.Window window, respectively.
This is how windriver remains thread-safe.
(TODO(andlabs): actually move per-window messages to the window itself)
Presently, the actual Windows API work is implemented in C. This is
to encapsulate Windows's data structures, ensure properly handling
signed -> unsigned conversions in constants, handle pointer casts
cleanly, and properly handle the "last error", which I will describe
later.
Here is a demonstration of all of the above. When you call
screen.NewWindow(opts), the Go code calls the C function
createWindow, which is implemented as something similar to
HRESULT createWindow(newWindowOpts *opts, HWND *phwnd) {
return (HRESULT) SendMessageW(utilityWindow,
msgCreateWindow,
(WPARAM) opts,
(LPARAM) phwnd);
}
HRESULT is another type for errors in Windows; I will again describe
this later. This function tells the utility window to make a new window,
using the given options, storing the window's OS handle in phwnd, and
returning any error directly to us through SendMessageW.
This code is running on another goroutine, which will definitely be
run on another OS thread. As such, Windows will switch to the UI
thread to dispatch this new window message. The code for the
implementation of the utility window (called a "window procedure")
contains something like this:
case msgCreateWindow:
return utilCreateWindow((newWindowOpts *) wParam,
(HWND *) lParam);
and the utilCreateWindow function does the actual work:
LRESULT utilCreateWindow(newWindowOpts *opts, HWND *phwnd) {
*phwnd = CreateWindowExW(...);
if (*phwnd == NULL) {
return lastErrorAsLRESULT();
}
return lS_OK;
}
When this returns, Windows switches back to the previous thread,
which can now use the window handle and error value.
Older Windows API functions return a Boolean flag to indicate if they
succeeded or failed, storing the actual reason for failure in what is
called the "last error". This is NOT contractual; functions are free to
fail without setting the last error, or free to clear the last error on
success.
To simplify error reporting, we instead convert all last errors to the
newer HRESULT error code system. The rules are simple: if the
function succeeded, we return the standard success code, S_OK.
If the function failed, we get the last error. If it's zero (no error),
we return the special value E_FAIL. Otherwise, we convert the last
error to an HRESULT (this is a well-defined operation that we can
reverse later when we're ready to report the error to the user).
This is all done by the C lastErrorToHRESULT function. Error
reporting on the Go side is handled by th winerror function.
Because window messages return LRESULTs, not HRESULTs,
the lastErrorToLRESULT and lS_OK macros are provided, which
automatically insert the necessary casts. An LRESULT (which is
pointer-sized) will always be either the same size as or larger than
an HRESULT (which is strictly 32 bits wide).
*/
golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/errors.go 0000664 0000000 0000000 00000000755 12572222044 0024431 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package windriver
// #include "windriver.h"
import "C"
import (
"fmt"
)
func winerror(msg string, hr C.HRESULT) error {
// TODO(andlabs): get long description
if hr == C.E_FAIL {
return fmt.Errorf("windriver: %s: unknown error", msg)
}
return fmt.Errorf("windriver: %s: last error %d", msg, hr&0xFFFF)
}
golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/other.go 0000664 0000000 0000000 00000001310 12572222044 0024222 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !windows
package windriver
import (
"fmt"
"runtime"
"golang.org/x/exp/shiny/driver/internal/errscreen"
"golang.org/x/exp/shiny/screen"
)
// Main is called by the program's main function to run the graphical
// application.
//
// It calls f on the Screen, possibly in a separate goroutine, as some OS-
// specific libraries require being on 'the main thread'. It returns when f
// returns.
func Main(f func(screen.Screen)) {
f(errscreen.Stub(fmt.Errorf(
"windriver: unsupported GOOS/GOARCH %s/%s", runtime.GOOS, runtime.GOARCH)))
}
golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/screen.go 0000664 0000000 0000000 00000004706 12572222044 0024374 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package windriver
import (
"fmt"
"image"
"syscall"
"unsafe"
"golang.org/x/exp/shiny/screen"
)
// screenHWND is the handle to the "Screen window".
// The Screen window encapsulates all screen.Screen operations
// in an actual Windows window so they all run on the main thread.
// Since any messages sent to a window will be executed on the
// main thread, we can safely use the messages below.
var screenHWND syscall.Handle
const (
// wParam - pointer to window options
// lParam - pointer to *screen.Window
// lResult - pointer to error
msgCreateWindow = _WM_USER + iota
)
type screenimpl struct{}
func newScreenImpl() screen.Screen {
return &screenimpl{}
}
func (*screenimpl) NewBuffer(size image.Point) (screen.Buffer, error) {
return nil, fmt.Errorf("TODO")
}
func (*screenimpl) NewTexture(size image.Point) (screen.Texture, error) {
return nil, fmt.Errorf("TODO")
}
type newWindowParams struct {
opts *screen.NewWindowOptions
w screen.Window
err error
}
func (*screenimpl) NewWindow(opts *screen.NewWindowOptions) (screen.Window, error) {
var p newWindowParams
p.opts = opts
_SendMessage(screenHWND, msgCreateWindow,
0,
uintptr(unsafe.Pointer(&p)))
return p.w, p.err
}
func screenWindowWndProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
switch uMsg {
case msgCreateWindow:
p := (*newWindowParams)(unsafe.Pointer(lParam))
p.w, p.err = newWindow(p.opts)
return 0
}
return _DefWindowProc(hwnd, uMsg, wParam, lParam)
}
const screenWindowClass = "shiny_ScreenWindow"
func initScreenWindow() (err error) {
swc, err := syscall.UTF16PtrFromString(screenWindowClass)
if err != nil {
return err
}
emptyString, err := syscall.UTF16PtrFromString("")
if err != nil {
return err
}
wc := _WNDCLASS{
LpszClassName: swc,
LpfnWndProc: syscall.NewCallback(screenWindowWndProc),
HIcon: hDefaultIcon,
HCursor: hDefaultCursor,
HInstance: hThisInstance,
HbrBackground: syscall.Handle(_COLOR_BTNFACE + 1),
}
_, err = _RegisterClass(&wc)
if err != nil {
return err
}
screenHWND, err = _CreateWindowEx(0,
swc, emptyString,
_WS_OVERLAPPEDWINDOW,
_CW_USEDEFAULT, _CW_USEDEFAULT,
_CW_USEDEFAULT, _CW_USEDEFAULT,
_HWND_MESSAGE, 0, hThisInstance, 0)
if err != nil {
return err
}
return nil
}
golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/syscall_windows.go 0000664 0000000 0000000 00000005113 12572222044 0026332 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go
package windriver
import "syscall"
type _POINT struct {
X int32
Y int32
}
type _MSG struct {
Hwnd syscall.Handle
Message uint32
Wparam uintptr
Lparam uintptr
Time uint32
Pt _POINT
}
type _WNDCLASS struct {
Style uint32
LpfnWndProc uintptr
CbClsExtra int32
CbWndExtra int32
HInstance syscall.Handle
HIcon syscall.Handle
HCursor syscall.Handle
HbrBackground syscall.Handle
LpszMenuName *uint16
LpszClassName *uint16
}
const (
_WM_USER = 0x0400
)
const (
_WS_OVERLAPPED = 0x00000000
_WS_CAPTION = 0x00C00000
_WS_SYSMENU = 0x00080000
_WS_THICKFRAME = 0x00040000
_WS_MINIMIZEBOX = 0x00020000
_WS_MAXIMIZEBOX = 0x00010000
_WS_OVERLAPPEDWINDOW = _WS_OVERLAPPED | _WS_CAPTION | _WS_SYSMENU | _WS_THICKFRAME | _WS_MINIMIZEBOX | _WS_MAXIMIZEBOX
)
const (
_COLOR_BTNFACE = 15
)
const (
_IDI_APPLICATION = 32512
_IDC_ARROW = 32512
)
const (
_CW_USEDEFAULT = 0x80000000 - 0x100000000
_HWND_MESSAGE = syscall.Handle(^uintptr(2)) // -3
)
// notes to self
// UINT = uint32
// callbacks = uintptr
// strings = *uint16
//sys _GetMessage(msg *_MSG, hwnd syscall.Handle, msgfiltermin uint32, msgfiltermax uint32) (ret int32, err error) [failretval==-1] = user32.GetMessageW
//sys _TranslateMessage(msg *_MSG) (done bool) = user32.TranslateMessage
//sys _DispatchMessage(msg *_MSG) (ret int32) = user32.DispatchMessageW
//sys _DefWindowProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) = user32.DefWindowProcW
//sys _RegisterClass(wc *_WNDCLASS) (atom uint16, err error) = user32.RegisterClassW
//sys _CreateWindowEx(exstyle uint32, className *uint16, windowText *uint16, style uint32, x int32, y int32, width int32, height int32, parent syscall.Handle, menu syscall.Handle, hInstance syscall.Handle, lpParam uintptr) (hwnd syscall.Handle, err error) = user32.CreateWindowExW
//sys _DestroyWindow(hwnd syscall.Handle) (err error) = user32.DestroyWindow
//sys _SendMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) = user32.SendMessageW
//sys _LoadIcon(hInstance syscall.Handle, iconName uintptr) (icon syscall.Handle, err error) = user32.LoadIconW
//sys _LoadCursor(hInstance syscall.Handle, cursorName uintptr) (cursor syscall.Handle, err error) = user32.LoadCursorW
golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/window.c 0000664 0000000 0000000 00000005171 12572222044 0024236 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
#include "_cgo_export.h"
#include "windriver.h"
#define windowClass L"shiny_Window"
static LRESULT CALLBACK windowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
WINDOWPOS *wp = (WINDOWPOS *) lParam;
RECT r;
HDC dc;
switch (uMsg) {
case WM_PAINT:
handlePaint(hwnd);
break; // explicitly defer to DefWindowProc; it will handle validation for us
case WM_WINDOWPOSCHANGED:
if ((wp->flags & SWP_NOSIZE) != 0) {
break;
}
if (GetClientRect(hwnd, &r) == 0) {
/* TODO(andlabs) */;
}
sendSizeEvent(hwnd, &r);
return 0;
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
// TODO(andlabs): call SetFocus()?
case WM_LBUTTONUP:
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
sendMouseEvent(hwnd, uMsg,
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam));
return 0;
case WM_KEYDOWN:
case WM_KEYUP:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
// TODO
break;
case msgFillSrc:
// TODO error checks
dc = GetDC(hwnd);
fillSrc(dc, (RECT *) lParam, (COLORREF) wParam);
ReleaseDC(hwnd, dc);
break;
case msgFillOver:
// TODO error checks
dc = GetDC(hwnd);
fillOver(dc, (RECT *) lParam, (COLORREF) wParam);
ReleaseDC(hwnd, dc);
break;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
HRESULT initWindowClass(void) {
WNDCLASSW wc;
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = windowClass;
wc.lpfnWndProc = windowWndProc;
wc.hInstance = thishInstance;
wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
if (wc.hIcon == NULL) {
return lastErrorToHRESULT();
}
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
if (wc.hCursor == NULL) {
return lastErrorToHRESULT();
}
// TODO(andlabs): change this to something else? NULL? the hollow brush?
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
if (RegisterClassW(&wc) == 0) {
return lastErrorToHRESULT();
}
return S_OK;
}
HRESULT createWindow(HWND *phwnd) {
*phwnd = CreateWindowExW(0,
windowClass, L"Shiny Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, thishInstance, NULL);
if (*phwnd == NULL) {
return lastErrorToLRESULT();
}
// TODO(andlabs): use proper nCmdShow
ShowWindow(*phwnd, SW_SHOWDEFAULT);
// TODO(andlabs): call UpdateWindow()
return lS_OK;
}
void sendFill(HWND hwnd, UINT uMsg, RECT r, COLORREF color) {
// Note: this SendMessageW won't return until after the fill
// completes, so using &r is safe.
SendMessageW(hwnd, uMsg, (WPARAM) color, (LPARAM) (&r));
}
golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/window.go 0000664 0000000 0000000 00000011461 12572222044 0024420 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package windriver
// #include "windriver.h"
import "C"
import (
"image"
"image/color"
"image/draw"
"sync"
"syscall"
"unsafe"
"golang.org/x/exp/shiny/driver/internal/pump"
"golang.org/x/exp/shiny/screen"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/mouse"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/geom"
)
var (
windows = map[C.HWND]*window{}
windowsLock sync.Mutex
)
type window struct {
hwnd C.HWND
pump pump.Pump
}
func newWindow(opts *screen.NewWindowOptions) (screen.Window, error) {
var hwnd C.HWND
hr := C.createWindow(&hwnd)
if hr != C.S_OK {
return nil, winerror("error creating window", hr)
}
w := &window{
hwnd: hwnd,
pump: pump.Make(),
}
windowsLock.Lock()
windows[hwnd] = w
windowsLock.Unlock()
// Send a fake size event.
// Windows won't generate the WM_WINDOWPOSCHANGED
// we trigger a resize on for the initial size, so we have to do
// it ourselves. The example/basic program assumes it will
// receive a size.Event for the initial window size that isn't 0x0.
var r C.RECT
// TODO(andlabs) error check
C.GetClientRect(w.hwnd, &r)
sendSizeEvent(w.hwnd, &r)
return w, nil
}
func (w *window) Release() {
if w.hwnd == nil { // already released?
return
}
windowsLock.Lock()
delete(windows, w.hwnd)
windowsLock.Unlock()
// TODO(andlabs): check for errors from this?
// TODO(andlabs): remove unsafe
_DestroyWindow(syscall.Handle(uintptr(unsafe.Pointer(w.hwnd))))
w.hwnd = nil
w.pump.Release()
// TODO(andlabs): what happens if we're still painting?
}
func (w *window) Events() <-chan interface{} { return w.pump.Events() }
func (w *window) Send(event interface{}) { w.pump.Send(event) }
func (w *window) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle, sender screen.Sender) {
// TODO
}
func (w *window) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
rect := C.RECT{
left: C.LONG(dr.Min.X),
top: C.LONG(dr.Min.Y),
right: C.LONG(dr.Max.X),
bottom: C.LONG(dr.Max.Y),
}
r, g, b, a := src.RGBA()
r >>= 8
g >>= 8
b >>= 8
a >>= 8
color := (a << 24) | (r << 16) | (g << 8) | b
var msg C.UINT = C.msgFillOver
if op == draw.Src {
msg = C.msgFillSrc
}
C.sendFill(w.hwnd, msg, rect, C.COLORREF(color))
}
func (w *window) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
// TODO
}
func (w *window) EndPaint(p paint.Event) {
// TODO
}
//export handlePaint
func handlePaint(hwnd C.HWND) {
windowsLock.Lock()
w := windows[hwnd]
windowsLock.Unlock()
// TODO(andlabs) - this won't be necessary after the Go rewrite
// Windows sends spurious WM_PAINT messages at window
// creation.
if w == nil {
return
}
w.Send(paint.Event{}) // TODO(andlabs): fill struct field
}
//export sendSizeEvent
func sendSizeEvent(hwnd C.HWND, r *C.RECT) {
windowsLock.Lock()
w := windows[hwnd]
windowsLock.Unlock()
width := int(r.right - r.left)
height := int(r.bottom - r.top)
// TODO(andlabs): don't assume that PixelsPerPt == 1
w.Send(size.Event{
WidthPx: width,
HeightPx: height,
WidthPt: geom.Pt(width),
HeightPt: geom.Pt(height),
PixelsPerPt: 1,
})
}
//export sendMouseEvent
func sendMouseEvent(hwnd C.HWND, uMsg C.UINT, x C.int, y C.int) {
var dir mouse.Direction
var button mouse.Button
windowsLock.Lock()
w := windows[hwnd]
windowsLock.Unlock()
switch uMsg {
case C.WM_MOUSEMOVE:
dir = mouse.DirNone
case C.WM_LBUTTONDOWN, C.WM_MBUTTONDOWN, C.WM_RBUTTONDOWN:
dir = mouse.DirPress
case C.WM_LBUTTONUP, C.WM_MBUTTONUP, C.WM_RBUTTONUP:
dir = mouse.DirRelease
default:
panic("sendMouseEvent() called on non-mouse message")
}
switch uMsg {
case C.WM_MOUSEMOVE:
button = mouse.ButtonNone
case C.WM_LBUTTONDOWN, C.WM_LBUTTONUP:
button = mouse.ButtonLeft
case C.WM_MBUTTONDOWN, C.WM_MBUTTONUP:
button = mouse.ButtonMiddle
case C.WM_RBUTTONDOWN, C.WM_RBUTTONUP:
button = mouse.ButtonRight
}
// TODO(andlabs): mouse wheel
w.Send(mouse.Event{
X: float32(x),
Y: float32(y),
Button: button,
Modifiers: keyModifiers(),
Direction: dir,
})
}
// Precondition: this is called in immediate response to the message that triggered the event (so not after w.Send).
func keyModifiers() (m key.Modifiers) {
down := func(x C.int) bool {
// GetKeyState gets the key state at the time of the message, so this is what we want.
return C.GetKeyState(x)&0x80 != 0
}
if down(C.VK_CONTROL) {
m |= key.ModControl
}
if down(C.VK_MENU) {
m |= key.ModAlt
}
if down(C.VK_SHIFT) {
m |= key.ModShift
}
if down(C.VK_LWIN) || down(C.VK_RWIN) {
m |= key.ModMeta
}
return m
}
golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/windraw.c 0000664 0000000 0000000 00000005155 12572222044 0024404 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "_cgo_export.h"
#include "windriver.h"
static HBITMAP mkbitmap(HDC dc, RECT *r, VOID **ppvBits) {
BITMAPINFO bi;
LONG dx, dy;
HBITMAP bitmap;
dx = r->right - r->left;
dy = r->bottom - r->top;
ZeroMemory(&bi, sizeof (BITMAPINFO));
bi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
bi.bmiHeader.biWidth = (LONG) dx;
bi.bmiHeader.biHeight = -((LONG) dy); // negative height to force top-down drawing
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 32;
bi.bmiHeader.biCompression = BI_RGB;
bi.bmiHeader.biSizeImage = (DWORD) (dx * dy * 4);
bitmap = CreateDIBSection(dc, &bi, DIB_RGB_COLORS, ppvBits, 0, 0);
if (bitmap == NULL) {
// TODO(andlabs)
}
return bitmap;
}
static void blend(HDC dc, HBITMAP bitmap, RECT *dr, LONG sdx, LONG sdy) {
HDC compatibleDC;
HBITMAP prevBitmap;
BLENDFUNCTION blendfunc;
compatibleDC = CreateCompatibleDC(dc);
if (compatibleDC == NULL) {
// TODO(andlabs)
}
prevBitmap = SelectObject(compatibleDC, bitmap);
if (prevBitmap == NULL) {
// TODO(andlabs)
}
ZeroMemory(&blendfunc, sizeof (BLENDFUNCTION));
blendfunc.BlendOp = AC_SRC_OVER;
blendfunc.BlendFlags = 0;
blendfunc.SourceConstantAlpha = 255; // only use per-pixel alphas
blendfunc.AlphaFormat = AC_SRC_ALPHA; // premultiplied
if (AlphaBlend(dc, dr->left, dr->top,
dr->right - dr->left, dr->bottom - dr->top,
compatibleDC, 0, 0, sdx, sdy,
blendfunc) == FALSE) {
// TODO
}
// TODO(andlabs): error check these?
SelectObject(compatibleDC, prevBitmap);
DeleteDC(compatibleDC);
}
// TODO(andlabs): Upload
void fillSrc(HDC dc, RECT *r, COLORREF color) {
HBRUSH brush;
// COLORREF is 0x00BBGGRR; color is 0xAARRGGBB
color = RGB((color >> 16) & 0xFF,
(color >> 8) & 0xFF,
color & 0xFF);
brush = CreateSolidBrush(color);
if (brush == NULL) {
// TODO
}
if (FillRect(dc, r, brush) == 0) {
// TODO
}
// TODO(andlabs) check errors?
DeleteObject(brush);
}
void fillOver(HDC dc, RECT *r, COLORREF color) {
HBITMAP bitmap;
VOID *ppvBits;
RECT oneByOne;
// AlphaBlend will stretch the input image (using StretchBlt's
// COLORONCOLOR mode) to fill the output rectangle. Testing
// this shows that the result appears to be the same as if we had
// used a MxN bitmap instead.
oneByOne.left = 0;
oneByOne.top = 0;
oneByOne.right = 1;
oneByOne.bottom = 1;
bitmap = mkbitmap(dc, &oneByOne, &ppvBits);
*((uint32_t *) ppvBits) = color;
blend(dc, bitmap, r, 1, 1);
// TODO(andlabs): check errors?
DeleteObject(bitmap);
}
// TODO(andlabs): Draw
golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/windriver.c 0000664 0000000 0000000 00000000521 12572222044 0024732 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
#include "windriver.h"
HRESULT lastErrorToHRESULT(void) {
DWORD le;
le = GetLastError();
if (le == 0)
return E_FAIL;
return HRESULT_FROM_WIN32(le);
}
golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/windriver.go 0000664 0000000 0000000 00000004124 12572222044 0025120 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package windriver
// #cgo LDFLAGS: -lgdi32 -lmsimg32
// #include "windriver.h"
import "C"
import (
"runtime"
"syscall"
"golang.org/x/exp/shiny/driver/internal/errscreen"
"golang.org/x/exp/shiny/screen"
)
// TODO(andlabs): Should the Windows API code be split into a
// separate package internal/winbackend so gldriver can use it too?
// Main is called by the program's main function to run the graphical
// application.
//
// It calls f on the Screen, possibly in a separate goroutine, as some OS-
// specific libraries require being on 'the main thread'. It returns when f
// returns.
func Main(f func(screen.Screen)) {
if err := main(f); err != nil {
f(errscreen.Stub(err))
}
}
func main(f func(screen.Screen)) (retErr error) {
// It does not matter which OS thread we are on.
// All that matters is that we confine all UI operations
// to the thread that created the respective window.
runtime.LockOSThread()
if err := initCommon(); err != nil {
return err
}
if err := initScreenWindow(); err != nil {
return err
}
defer func() {
// TODO(andlabs): log an error if this fails?
_DestroyWindow(screenHWND)
// TODO(andlabs): unregister window class
}()
if hr := C.initWindowClass(); hr != C.S_OK {
return winerror("failed to create Window window class", hr)
}
// TODO(andlabs): uninit
s := newScreenImpl()
go f(s)
mainMessagePump()
return nil
}
var (
hDefaultIcon syscall.Handle
hDefaultCursor syscall.Handle
hThisInstance syscall.Handle
)
func initCommon() (err error) {
hDefaultIcon, err = _LoadIcon(0, _IDI_APPLICATION)
if err != nil {
return err
}
hDefaultCursor, err = _LoadCursor(0, _IDC_ARROW)
if err != nil {
return err
}
// TODO(andlabs) hThisInstance
return nil
}
func mainMessagePump() {
var m _MSG
for {
done, err := _GetMessage(&m, 0, 0, 0)
if err != nil {
// TODO
}
if done == 0 { // WM_QUIT
return
}
_TranslateMessage(&m)
_DispatchMessage(&m)
}
}
golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/windriver.h 0000664 0000000 0000000 00000003155 12572222044 0024745 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
#ifndef __SHINY_WINDRIVER_H__
#define __SHINY_WINDRIVER_H__
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
#define CINTERFACE
#define COBJMACROS
// see https://github.com/golang/go/issues/9916#issuecomment-74812211
#define INITGUID
// get Windows version right; right now Windows XP
#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
#define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */
#define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */
#define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */
#include
#include
#include
// see http://blogs.msdn.com/b/oldnewthing/archive/2004/10/25/247180.aspx
// this will work on MinGW too
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define thishInstance ((HINSTANCE) (&__ImageBase))
#define firstClassMessage (WM_USER + 0x40)
// screen.Window private messages.
// TODO elaborate
enum {
// for both of these:
// wParam - COLORREF
// lParam - pointer to RECT
msgFillSrc = WM_USER + 0x20,
msgFillOver,
};
// windriver.c
extern HRESULT lastErrorToHRESULT(void);
#define lS_OK ((LRESULT) S_OK)
#define lastErrorToLRESULT() ((LRESULT) lastErrorToHRESULT())
// window.c
extern HRESULT initWindowClass(void);
extern HRESULT createWindow(HWND *);
extern void sendFill(HWND, UINT, RECT, COLORREF);
// windraw.c
extern void fillSrc(HDC, RECT *, COLORREF);
extern void fillOver(HDC, RECT *, COLORREF);
#endif
golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/zsyscall_windows.go 0000664 0000000 0000000 00000007504 12572222044 0026532 0 ustar 00root root 0000000 0000000 // MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
package windriver
import "unsafe"
import "syscall"
var _ unsafe.Pointer
var (
moduser32 = syscall.NewLazyDLL("user32.dll")
procGetMessageW = moduser32.NewProc("GetMessageW")
procTranslateMessage = moduser32.NewProc("TranslateMessage")
procDispatchMessageW = moduser32.NewProc("DispatchMessageW")
procDefWindowProcW = moduser32.NewProc("DefWindowProcW")
procRegisterClassW = moduser32.NewProc("RegisterClassW")
procCreateWindowExW = moduser32.NewProc("CreateWindowExW")
procDestroyWindow = moduser32.NewProc("DestroyWindow")
procSendMessageW = moduser32.NewProc("SendMessageW")
procLoadIconW = moduser32.NewProc("LoadIconW")
procLoadCursorW = moduser32.NewProc("LoadCursorW")
)
func _GetMessage(msg *_MSG, hwnd syscall.Handle, msgfiltermin uint32, msgfiltermax uint32) (ret int32, err error) {
r0, _, e1 := syscall.Syscall6(procGetMessageW.Addr(), 4, uintptr(unsafe.Pointer(msg)), uintptr(hwnd), uintptr(msgfiltermin), uintptr(msgfiltermax), 0, 0)
ret = int32(r0)
if ret == -1 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func _TranslateMessage(msg *_MSG) (done bool) {
r0, _, _ := syscall.Syscall(procTranslateMessage.Addr(), 1, uintptr(unsafe.Pointer(msg)), 0, 0)
done = r0 != 0
return
}
func _DispatchMessage(msg *_MSG) (ret int32) {
r0, _, _ := syscall.Syscall(procDispatchMessageW.Addr(), 1, uintptr(unsafe.Pointer(msg)), 0, 0)
ret = int32(r0)
return
}
func _DefWindowProc(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
r0, _, _ := syscall.Syscall6(procDefWindowProcW.Addr(), 4, uintptr(hwnd), uintptr(uMsg), uintptr(wParam), uintptr(lParam), 0, 0)
lResult = uintptr(r0)
return
}
func _RegisterClass(wc *_WNDCLASS) (atom uint16, err error) {
r0, _, e1 := syscall.Syscall(procRegisterClassW.Addr(), 1, uintptr(unsafe.Pointer(wc)), 0, 0)
atom = uint16(r0)
if atom == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func _CreateWindowEx(exstyle uint32, className *uint16, windowText *uint16, style uint32, x int32, y int32, width int32, height int32, parent syscall.Handle, menu syscall.Handle, hInstance syscall.Handle, lpParam uintptr) (hwnd syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall12(procCreateWindowExW.Addr(), 12, uintptr(exstyle), uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(windowText)), uintptr(style), uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(parent), uintptr(menu), uintptr(hInstance), uintptr(lpParam))
hwnd = syscall.Handle(r0)
if hwnd == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func _DestroyWindow(hwnd syscall.Handle) (err error) {
r1, _, e1 := syscall.Syscall(procDestroyWindow.Addr(), 1, uintptr(hwnd), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func _SendMessage(hwnd syscall.Handle, uMsg uint32, wParam uintptr, lParam uintptr) (lResult uintptr) {
r0, _, _ := syscall.Syscall6(procSendMessageW.Addr(), 4, uintptr(hwnd), uintptr(uMsg), uintptr(wParam), uintptr(lParam), 0, 0)
lResult = uintptr(r0)
return
}
func _LoadIcon(hInstance syscall.Handle, iconName uintptr) (icon syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall(procLoadIconW.Addr(), 2, uintptr(hInstance), uintptr(iconName), 0)
icon = syscall.Handle(r0)
if icon == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func _LoadCursor(hInstance syscall.Handle, cursorName uintptr) (cursor syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall(procLoadCursorW.Addr(), 2, uintptr(hInstance), uintptr(cursorName), 0)
cursor = syscall.Handle(r0)
if cursor == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
golang-golang-x-exp_eb7c1fa/shiny/driver/x11driver/ 0000775 0000000 0000000 00000000000 12572222044 0022373 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/driver/x11driver/buffer.go 0000664 0000000 0000000 00000006353 12572222044 0024202 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x11driver
import (
"image"
"image/color"
"image/draw"
"log"
"sync"
"unsafe"
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/render"
"github.com/BurntSushi/xgb/shm"
"github.com/BurntSushi/xgb/xproto"
"golang.org/x/exp/shiny/driver/internal/swizzle"
"golang.org/x/exp/shiny/screen"
)
type bufferImpl struct {
s *screenImpl
addr unsafe.Pointer
buf []byte
rgba image.RGBA
size image.Point
xs shm.Seg
mu sync.Mutex
nUpload uint32
released bool
cleanedUp bool
}
func (b *bufferImpl) Size() image.Point { return b.size }
func (b *bufferImpl) Bounds() image.Rectangle { return image.Rectangle{Max: b.size} }
func (b *bufferImpl) RGBA() *image.RGBA { return &b.rgba }
func (b *bufferImpl) preUpload() {
b.mu.Lock()
if b.released {
b.mu.Unlock()
panic("x11driver: Buffer.Upload called after Buffer.Release")
}
needsSwizzle := b.nUpload == 0
b.nUpload++
b.mu.Unlock()
if needsSwizzle {
swizzle.BGRA(b.buf)
}
}
func (b *bufferImpl) postUpload() {
b.mu.Lock()
b.nUpload--
more := b.nUpload != 0
released := b.released
b.mu.Unlock()
if more {
return
}
if released {
b.cleanUp()
} else {
swizzle.BGRA(b.buf)
}
}
func (b *bufferImpl) Release() {
b.mu.Lock()
cleanUp := !b.released && b.nUpload == 0
b.released = true
b.mu.Unlock()
if cleanUp {
b.cleanUp()
}
}
func (b *bufferImpl) cleanUp() {
b.mu.Lock()
alreadyCleanedUp := b.cleanedUp
b.cleanedUp = true
b.mu.Unlock()
if alreadyCleanedUp {
panic("x11driver: Buffer clean-up occurred twice")
}
b.s.mu.Lock()
delete(b.s.buffers, b.xs)
b.s.mu.Unlock()
shm.Detach(b.s.xc, b.xs)
if err := shmClose(b.addr); err != nil {
log.Printf("x11driver: shmClose: %v", err)
}
}
func (b *bufferImpl) upload(u screen.Uploader, xd xproto.Drawable, xg xproto.Gcontext, depth uint8,
dp image.Point, sr image.Rectangle, sender screen.Sender) {
b.preUpload()
// TODO: adjust if dp is outside dst bounds, or sr is outside src bounds.
dr := sr.Sub(sr.Min).Add(dp)
cookie := shm.PutImage(
b.s.xc, xd, xg,
uint16(b.size.X), uint16(b.size.Y), // TotalWidth, TotalHeight,
uint16(sr.Min.X), uint16(sr.Min.Y), // SrcX, SrcY,
uint16(dr.Dx()), uint16(dr.Dy()), // SrcWidth, SrcHeight,
int16(dr.Min.X), int16(dr.Min.Y), // DstX, DstY,
depth, xproto.ImageFormatZPixmap,
1, b.xs, 0, // 1 means send a completion event, 0 means a zero offset.
)
b.s.mu.Lock()
b.s.uploads[cookie.Sequence] = completion{
sender: sender,
event: screen.UploadedEvent{
Buffer: b,
Uploader: u,
},
}
b.s.mu.Unlock()
}
func fill(xc *xgb.Conn, xp render.Picture, dr image.Rectangle, src color.Color, op draw.Op) {
r, g, b, a := src.RGBA()
c := render.Color{
Red: uint16(r),
Green: uint16(g),
Blue: uint16(b),
Alpha: uint16(a),
}
x, y := dr.Min.X, dr.Min.Y
if x < -0x8000 || 0x7fff < x || y < -0x8000 || 0x7fff < y {
return
}
dx, dy := dr.Dx(), dr.Dy()
if dx < 0 || 0xffff < dx || dy < 0 || 0xffff < dy {
return
}
render.FillRectangles(xc, renderOp(op), xp, c, []xproto.Rectangle{{
X: int16(x),
Y: int16(y),
Width: uint16(dx),
Height: uint16(dy),
}})
}
golang-golang-x-exp_eb7c1fa/shiny/driver/x11driver/key.go 0000664 0000000 0000000 00000010526 12572222044 0023516 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x11driver
import (
"golang.org/x/mobile/event/key"
)
// These constants come from /usr/include/X11/X.h
const (
xShiftMask = 1 << 0
xLockMask = 1 << 1
xControlMask = 1 << 2
xMod1Mask = 1 << 3
xMod2Mask = 1 << 4
xMod3Mask = 1 << 5
xMod4Mask = 1 << 6
xMod5Mask = 1 << 7
xButton1Mask = 1 << 8
xButton2Mask = 1 << 9
xButton3Mask = 1 << 10
xButton4Mask = 1 << 11
xButton5Mask = 1 << 12
)
func keyModifiers(state uint16) (m key.Modifiers) {
if state&xShiftMask != 0 {
m |= key.ModShift
}
if state&xControlMask != 0 {
m |= key.ModControl
}
if state&xMod1Mask != 0 {
m |= key.ModAlt
}
if state&xMod4Mask != 0 {
m |= key.ModMeta
}
return m
}
// These constants come from /usr/include/X11/{keysymdef,XF86keysym}.h
const (
xkISOLeftTab = 0xfe20
xkBackSpace = 0xff08
xkTab = 0xff09
xkReturn = 0xff0d
xkEscape = 0xff1b
xkHome = 0xff50
xkLeft = 0xff51
xkUp = 0xff52
xkRight = 0xff53
xkDown = 0xff54
xkPageUp = 0xff55
xkPageDown = 0xff56
xkEnd = 0xff57
xkInsert = 0xff63
xkMenu = 0xff67
xkF1 = 0xffbe
xkF2 = 0xffbf
xkF3 = 0xffc0
xkF4 = 0xffc1
xkF5 = 0xffc2
xkF6 = 0xffc3
xkF7 = 0xffc4
xkF8 = 0xffc5
xkF9 = 0xffc6
xkF10 = 0xffc7
xkF11 = 0xffc8
xkF12 = 0xffc9
xkShiftL = 0xffe1
xkShiftR = 0xffe2
xkControlL = 0xffe3
xkControlR = 0xffe4
xkAltL = 0xffe9
xkAltR = 0xffea
xkSuperL = 0xffeb
xkSuperR = 0xffec
xkDelete = 0xffff
xf86xkAudioLowerVolume = 0x1008ff11
xf86xkAudioMute = 0x1008ff12
xf86xkAudioRaiseVolume = 0x1008ff13
)
// nonUnicodeKeycodes maps from those xproto.Keysym values (converted to runes)
// that do not correspond to a Unicode code point, such as "Page Up", "F1" or
// "Left Shift", to key.Code values.
var nonUnicodeKeycodes = map[rune]key.Code{
xkISOLeftTab: key.CodeTab,
xkBackSpace: key.CodeDeleteBackspace,
xkTab: key.CodeTab,
xkReturn: key.CodeReturnEnter,
xkEscape: key.CodeEscape,
xkHome: key.CodeHome,
xkLeft: key.CodeLeftArrow,
xkUp: key.CodeUpArrow,
xkRight: key.CodeRightArrow,
xkDown: key.CodeDownArrow,
xkPageUp: key.CodePageUp,
xkPageDown: key.CodePageDown,
xkEnd: key.CodeEnd,
xkInsert: key.CodeInsert,
xkMenu: key.CodeRightGUI, // TODO: CodeRightGUI or CodeMenu??
xkF1: key.CodeF1,
xkF2: key.CodeF2,
xkF3: key.CodeF3,
xkF4: key.CodeF4,
xkF5: key.CodeF5,
xkF6: key.CodeF6,
xkF7: key.CodeF7,
xkF8: key.CodeF8,
xkF9: key.CodeF9,
xkF10: key.CodeF10,
xkF11: key.CodeF11,
xkF12: key.CodeF12,
xkShiftL: key.CodeLeftShift,
xkShiftR: key.CodeRightShift,
xkControlL: key.CodeLeftControl,
xkControlR: key.CodeRightControl,
xkAltL: key.CodeLeftAlt,
xkAltR: key.CodeRightAlt,
xkSuperL: key.CodeLeftGUI,
xkSuperR: key.CodeRightGUI,
xkDelete: key.CodeDeleteForward,
xf86xkAudioRaiseVolume: key.CodeVolumeUp,
xf86xkAudioLowerVolume: key.CodeVolumeDown,
xf86xkAudioMute: key.CodeMute,
}
// asciiKeycodes maps lower-case ASCII runes to key.Code values.
var asciiKeycodes = [0x80]key.Code{
'a': key.CodeA,
'b': key.CodeB,
'c': key.CodeC,
'd': key.CodeD,
'e': key.CodeE,
'f': key.CodeF,
'g': key.CodeG,
'h': key.CodeH,
'i': key.CodeI,
'j': key.CodeJ,
'k': key.CodeK,
'l': key.CodeL,
'm': key.CodeM,
'n': key.CodeN,
'o': key.CodeO,
'p': key.CodeP,
'q': key.CodeQ,
'r': key.CodeR,
's': key.CodeS,
't': key.CodeT,
'u': key.CodeU,
'v': key.CodeV,
'w': key.CodeW,
'x': key.CodeX,
'y': key.CodeY,
'z': key.CodeZ,
'1': key.Code1,
'2': key.Code2,
'3': key.Code3,
'4': key.Code4,
'5': key.Code5,
'6': key.Code6,
'7': key.Code7,
'8': key.Code8,
'9': key.Code9,
'0': key.Code0,
' ': key.CodeSpacebar,
'-': key.CodeHyphenMinus,
'=': key.CodeEqualSign,
'[': key.CodeLeftSquareBracket,
']': key.CodeRightSquareBracket,
'\\': key.CodeBackslash,
';': key.CodeSemicolon,
'\'': key.CodeApostrophe,
'`': key.CodeGraveAccent,
',': key.CodeComma,
'.': key.CodeFullStop,
'/': key.CodeSlash,
// TODO: distinguish CodeKeypadSlash vs CodeSlash, and similarly for other
// keypad codes.
}
golang-golang-x-exp_eb7c1fa/shiny/driver/x11driver/screen.go 0000664 0000000 0000000 00000030632 12572222044 0024205 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x11driver
import (
"fmt"
"image"
"log"
"sync"
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/render"
"github.com/BurntSushi/xgb/shm"
"github.com/BurntSushi/xgb/xproto"
"golang.org/x/exp/shiny/driver/internal/pump"
"golang.org/x/exp/shiny/screen"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/mouse"
)
// TODO: check that xgb is safe to use concurrently from multiple goroutines.
// For example, its Conn.WaitForEvent concept is a method, not a channel, so
// it's not obvious how to interrupt it to service a NewWindow request.
type completion struct {
sender screen.Sender
event screen.UploadedEvent
}
type screenImpl struct {
xc *xgb.Conn
xsi *xproto.ScreenInfo
keysyms [256][2]xproto.Keysym
atomWMDeleteWindow xproto.Atom
atomWMProtocols xproto.Atom
atomWMTakeFocus xproto.Atom
pictformat24 render.Pictformat
pictformat32 render.Pictformat
// window32 and its related X11 resources is an unmapped window so that we
// have a depth-32 window to create depth-32 pixmaps from, i.e. pixmaps
// with an alpha channel. The root window isn't guaranteed to be depth-32.
gcontext32 xproto.Gcontext
window32 xproto.Window
mu sync.Mutex
buffers map[shm.Seg]*bufferImpl
uploads map[uint16]completion
windows map[xproto.Window]*windowImpl
}
func newScreenImpl(xc *xgb.Conn) (*screenImpl, error) {
s := &screenImpl{
xc: xc,
xsi: xproto.Setup(xc).DefaultScreen(xc),
buffers: map[shm.Seg]*bufferImpl{},
uploads: map[uint16]completion{},
windows: map[xproto.Window]*windowImpl{},
}
if err := s.initAtoms(); err != nil {
return nil, err
}
if err := s.initKeyboardMapping(); err != nil {
return nil, err
}
if err := s.initPictformats(); err != nil {
return nil, err
}
if err := s.initWindow32(); err != nil {
return nil, err
}
go s.run()
return s, nil
}
func (s *screenImpl) run() {
for {
ev, err := s.xc.WaitForEvent()
if err != nil {
log.Printf("x11driver: xproto.WaitForEvent: %v", err)
continue
}
noWindowFound := false
switch ev := ev.(type) {
case xproto.DestroyNotifyEvent:
s.mu.Lock()
delete(s.windows, ev.Window)
s.mu.Unlock()
case shm.CompletionEvent:
s.handleCompletion(ev)
case xproto.ClientMessageEvent:
if ev.Type != s.atomWMProtocols || ev.Format != 32 {
break
}
switch xproto.Atom(ev.Data.Data32[0]) {
case s.atomWMDeleteWindow:
// TODO.
case s.atomWMTakeFocus:
xproto.SetInputFocus(s.xc, xproto.InputFocusParent, ev.Window, xproto.Timestamp(ev.Data.Data32[1]))
}
case xproto.ConfigureNotifyEvent:
if w := s.findWindow(ev.Window); w != nil {
w.handleConfigureNotify(ev)
} else {
noWindowFound = true
}
case xproto.ExposeEvent:
// TODO: xw = ev.Window
case xproto.FocusInEvent:
// TODO: xw = ev.Event
case xproto.FocusOutEvent:
// TODO: xw = ev.Event
case xproto.KeyPressEvent:
if w := s.findWindow(ev.Event); w != nil {
w.handleKey(ev.Detail, ev.State, key.DirPress)
} else {
noWindowFound = true
}
case xproto.KeyReleaseEvent:
if w := s.findWindow(ev.Event); w != nil {
w.handleKey(ev.Detail, ev.State, key.DirRelease)
} else {
noWindowFound = true
}
case xproto.ButtonPressEvent:
if w := s.findWindow(ev.Event); w != nil {
w.handleMouse(ev.EventX, ev.EventY, ev.Detail, ev.State, mouse.DirPress)
} else {
noWindowFound = true
}
case xproto.ButtonReleaseEvent:
if w := s.findWindow(ev.Event); w != nil {
w.handleMouse(ev.EventX, ev.EventY, ev.Detail, ev.State, mouse.DirRelease)
} else {
noWindowFound = true
}
case xproto.MotionNotifyEvent:
if w := s.findWindow(ev.Event); w != nil {
w.handleMouse(ev.EventX, ev.EventY, 0, ev.State, mouse.DirNone)
} else {
noWindowFound = true
}
}
if noWindowFound {
log.Printf("x11driver: no window found for event %T", ev)
}
}
}
// TODO: is findBuffer and the s.buffers field unused? Delete?
func (s *screenImpl) findBuffer(key shm.Seg) *bufferImpl {
s.mu.Lock()
b := s.buffers[key]
s.mu.Unlock()
return b
}
func (s *screenImpl) findWindow(key xproto.Window) *windowImpl {
s.mu.Lock()
w := s.windows[key]
s.mu.Unlock()
return w
}
func (s *screenImpl) handleCompletion(ev shm.CompletionEvent) {
s.mu.Lock()
completion, ok := s.uploads[ev.Sequence]
s.mu.Unlock()
if !ok {
log.Printf("x11driver: no matching upload for a SHM completion event")
return
}
completion.event.Buffer.(*bufferImpl).postUpload()
if completion.sender != nil {
// Call Send in a separate goroutine, so that this event-handling
// goroutine doesn't block.
go completion.sender.Send(completion.event)
}
}
const (
maxShmSide = 0x00007fff // 32,767 pixels.
maxShmSize = 0x10000000 // 268,435,456 bytes.
)
func (s *screenImpl) NewBuffer(size image.Point) (retBuf screen.Buffer, retErr error) {
// TODO: detect if the X11 server or connection cannot support SHM pixmaps,
// and fall back to regular pixmaps.
w, h := int64(size.X), int64(size.Y)
if w < 0 || maxShmSide < w || h < 0 || maxShmSide < h || maxShmSize < 4*w*h {
return nil, fmt.Errorf("x11driver: invalid buffer size %v", size)
}
xs, err := shm.NewSegId(s.xc)
if err != nil {
return nil, fmt.Errorf("x11driver: shm.NewSegId: %v", err)
}
bufLen := 4 * size.X * size.Y
shmid, addr, err := shmOpen(bufLen)
if err != nil {
return nil, fmt.Errorf("x11driver: shmOpen: %v", err)
}
defer func() {
if retErr != nil {
shmClose(addr)
}
}()
a := (*[maxShmSize]byte)(addr)
buf := (*a)[:bufLen:bufLen]
// readOnly is whether the shared memory is read-only from the X11 server's
// point of view. We need false to use SHM pixmaps.
const readOnly = false
shm.Attach(s.xc, xs, uint32(shmid), readOnly)
b := &bufferImpl{
s: s,
addr: addr,
buf: buf,
rgba: image.RGBA{
Pix: buf,
Stride: 4 * size.X,
Rect: image.Rectangle{Max: size},
},
size: size,
xs: xs,
}
s.mu.Lock()
s.buffers[b.xs] = b
s.mu.Unlock()
return b, nil
}
func (s *screenImpl) NewTexture(size image.Point) (screen.Texture, error) {
w, h := int64(size.X), int64(size.Y)
if w < 0 || maxShmSide < w || h < 0 || maxShmSide < h || maxShmSize < 4*w*h {
return nil, fmt.Errorf("x11driver: invalid texture size %v", size)
}
xm, err := xproto.NewPixmapId(s.xc)
if err != nil {
return nil, fmt.Errorf("x11driver: xproto.NewPixmapId failed: %v", err)
}
xp, err := render.NewPictureId(s.xc)
if err != nil {
return nil, fmt.Errorf("x11driver: xproto.NewPictureId failed: %v", err)
}
t := &textureImpl{
s: s,
size: size,
xm: xm,
xp: xp,
}
xproto.CreatePixmap(s.xc, textureDepth, xm, xproto.Drawable(s.window32), uint16(w), uint16(h))
render.CreatePicture(s.xc, xp, xproto.Drawable(xm), s.pictformat32, 0, nil)
return t, nil
}
func (s *screenImpl) NewWindow(opts *screen.NewWindowOptions) (screen.Window, error) {
// TODO: look at opts.
const width, height = 1024, 768
xw, err := xproto.NewWindowId(s.xc)
if err != nil {
return nil, fmt.Errorf("x11driver: xproto.NewWindowId failed: %v", err)
}
xg, err := xproto.NewGcontextId(s.xc)
if err != nil {
return nil, fmt.Errorf("x11driver: xproto.NewGcontextId failed: %v", err)
}
xp, err := render.NewPictureId(s.xc)
if err != nil {
return nil, fmt.Errorf("x11driver: render.NewPictureId failed: %v", err)
}
pictformat := render.Pictformat(0)
switch s.xsi.RootDepth {
default:
return nil, fmt.Errorf("x11driver: unsupported root depth %d", s.xsi.RootDepth)
case 24:
pictformat = s.pictformat24
case 32:
pictformat = s.pictformat32
}
w := &windowImpl{
s: s,
xw: xw,
xg: xg,
xp: xp,
pump: pump.Make(),
xevents: make(chan xgb.Event),
}
s.mu.Lock()
s.windows[xw] = w
s.mu.Unlock()
xproto.CreateWindow(s.xc, s.xsi.RootDepth, xw, s.xsi.Root,
0, 0, width, height, 0,
xproto.WindowClassInputOutput, s.xsi.RootVisual,
xproto.CwEventMask,
[]uint32{0 |
xproto.EventMaskKeyPress |
xproto.EventMaskKeyRelease |
xproto.EventMaskButtonPress |
xproto.EventMaskButtonRelease |
xproto.EventMaskPointerMotion |
xproto.EventMaskExposure |
xproto.EventMaskStructureNotify |
xproto.EventMaskFocusChange,
},
)
s.setProperty(xw, s.atomWMProtocols, s.atomWMDeleteWindow, s.atomWMTakeFocus)
xproto.CreateGC(s.xc, xg, xproto.Drawable(xw), 0, nil)
render.CreatePicture(s.xc, xp, xproto.Drawable(xw), pictformat, 0, nil)
xproto.MapWindow(s.xc, xw)
return w, nil
}
func (s *screenImpl) initAtoms() (err error) {
s.atomWMDeleteWindow, err = s.internAtom("WM_DELETE_WINDOW")
if err != nil {
return err
}
s.atomWMProtocols, err = s.internAtom("WM_PROTOCOLS")
if err != nil {
return err
}
s.atomWMTakeFocus, err = s.internAtom("WM_TAKE_FOCUS")
if err != nil {
return err
}
return nil
}
func (s *screenImpl) internAtom(name string) (xproto.Atom, error) {
r, err := xproto.InternAtom(s.xc, false, uint16(len(name)), name).Reply()
if err != nil {
return 0, fmt.Errorf("x11driver: xproto.InternAtom failed: %v", err)
}
if r == nil {
return 0, fmt.Errorf("x11driver: xproto.InternAtom failed")
}
return r.Atom, nil
}
func (s *screenImpl) initKeyboardMapping() error {
const keyLo, keyHi = 8, 255
km, err := xproto.GetKeyboardMapping(s.xc, keyLo, keyHi-keyLo+1).Reply()
if err != nil {
return err
}
n := int(km.KeysymsPerKeycode)
if n < 2 {
return fmt.Errorf("x11driver: too few keysyms per keycode: %d", n)
}
for i := keyLo; i <= keyHi; i++ {
s.keysyms[i][0] = km.Keysyms[(i-keyLo)*n+0]
s.keysyms[i][1] = km.Keysyms[(i-keyLo)*n+1]
}
return nil
}
func (s *screenImpl) initPictformats() error {
pformats, err := render.QueryPictFormats(s.xc).Reply()
if err != nil {
return fmt.Errorf("x11driver: render.QueryPictFormats failed: %v", err)
}
s.pictformat24, err = findPictformat(pformats.Formats, 24)
if err != nil {
return err
}
s.pictformat32, err = findPictformat(pformats.Formats, 32)
if err != nil {
return err
}
return nil
}
func findPictformat(fs []render.Pictforminfo, depth byte) (render.Pictformat, error) {
// This presumes little-endian BGRA.
want := render.Directformat{
RedShift: 16,
RedMask: 0xff,
GreenShift: 8,
GreenMask: 0xff,
BlueShift: 0,
BlueMask: 0xff,
AlphaShift: 24,
AlphaMask: 0xff,
}
if depth == 24 {
want.AlphaShift = 0
want.AlphaMask = 0x00
}
for _, f := range fs {
if f.Type == render.PictTypeDirect && f.Depth == depth && f.Direct == want {
return f.Id, nil
}
}
return 0, fmt.Errorf("x11driver: no matching Pictformat for depth %d", depth)
}
func (s *screenImpl) initWindow32() error {
visualid, err := findVisual(s.xsi, 32)
if err != nil {
return err
}
colormap, err := xproto.NewColormapId(s.xc)
if err != nil {
return fmt.Errorf("x11driver: xproto.NewColormapId failed: %v", err)
}
if err := xproto.CreateColormapChecked(
s.xc, xproto.ColormapAllocNone, colormap, s.xsi.Root, visualid).Check(); err != nil {
return fmt.Errorf("x11driver: xproto.CreateColormap failed: %v", err)
}
s.window32, err = xproto.NewWindowId(s.xc)
if err != nil {
return fmt.Errorf("x11driver: xproto.NewWindowId failed: %v", err)
}
s.gcontext32, err = xproto.NewGcontextId(s.xc)
if err != nil {
return fmt.Errorf("x11driver: xproto.NewGcontextId failed: %v", err)
}
const depth = 32
xproto.CreateWindow(s.xc, depth, s.window32, s.xsi.Root,
0, 0, 1, 1, 0,
xproto.WindowClassInputOutput, visualid,
// The CwBorderPixel attribute seems necessary for depth == 32. See
// http://stackoverflow.com/questions/3645632/how-to-create-a-window-with-a-bit-depth-of-32
xproto.CwBorderPixel|xproto.CwColormap,
[]uint32{0, uint32(colormap)},
)
xproto.CreateGC(s.xc, s.gcontext32, xproto.Drawable(s.window32), 0, nil)
return nil
}
func findVisual(xsi *xproto.ScreenInfo, depth byte) (xproto.Visualid, error) {
for _, d := range xsi.AllowedDepths {
if d.Depth != depth {
continue
}
for _, v := range d.Visuals {
if v.RedMask == 0xff0000 && v.GreenMask == 0xff00 && v.BlueMask == 0xff {
return v.VisualId, nil
}
}
}
return 0, fmt.Errorf("x11driver: no matching Visualid")
}
func (s *screenImpl) setProperty(xw xproto.Window, prop xproto.Atom, values ...xproto.Atom) {
b := make([]byte, len(values)*4)
for i, v := range values {
b[4*i+0] = uint8(v >> 0)
b[4*i+1] = uint8(v >> 8)
b[4*i+2] = uint8(v >> 16)
b[4*i+3] = uint8(v >> 24)
}
xproto.ChangeProperty(s.xc, xproto.PropModeReplace, xw, prop, xproto.AtomAtom, 32, uint32(len(values)), b)
}
golang-golang-x-exp_eb7c1fa/shiny/driver/x11driver/shm_linux_amd64.go 0000664 0000000 0000000 00000002035 12572222044 0025723 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x11driver
import (
"syscall"
"unsafe"
)
// These constants are from /usr/include/linux/ipc.h
const (
ipcPrivate = 0
ipcCreat = 0x1000
ipcRmID = 0
)
func shmOpen(size int) (shmid uintptr, addr unsafe.Pointer, err error) {
shmid, _, errno0 := syscall.RawSyscall(syscall.SYS_SHMGET, ipcPrivate, uintptr(size), ipcCreat|0600)
if errno0 != 0 {
return 0, unsafe.Pointer(uintptr(0)), errno0
}
p, _, errno1 := syscall.RawSyscall(syscall.SYS_SHMAT, shmid, 0, 0)
_, _, errno2 := syscall.RawSyscall(syscall.SYS_SHMCTL, shmid, ipcRmID, 0)
if errno1 != 0 {
return 0, unsafe.Pointer(uintptr(0)), errno1
}
if errno2 != 0 {
return 0, unsafe.Pointer(uintptr(0)), errno2
}
return shmid, unsafe.Pointer(p), nil
}
func shmClose(p unsafe.Pointer) error {
_, _, errno := syscall.RawSyscall(syscall.SYS_SHMDT, uintptr(p), 0, 0)
if errno != 0 {
return errno
}
return nil
}
golang-golang-x-exp_eb7c1fa/shiny/driver/x11driver/shm_other.go 0000664 0000000 0000000 00000001057 12572222044 0024715 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !linux !amd64
package x11driver
import (
"fmt"
"runtime"
"unsafe"
)
func shmOpen(size int) (shmid uintptr, addr unsafe.Pointer, err error) {
return 0, unsafe.Pointer(uintptr(0)),
fmt.Errorf("unsupported GOOS/GOARCH %s/%s", runtime.GOOS, runtime.GOARCH)
}
func shmClose(p unsafe.Pointer) error {
return fmt.Errorf("unsupported GOOS/GOARCH %s/%s", runtime.GOOS, runtime.GOARCH)
}
golang-golang-x-exp_eb7c1fa/shiny/driver/x11driver/texture.go 0000664 0000000 0000000 00000003433 12572222044 0024425 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x11driver
import (
"image"
"image/color"
"image/draw"
"sync"
"github.com/BurntSushi/xgb/render"
"github.com/BurntSushi/xgb/xproto"
"golang.org/x/exp/shiny/screen"
"golang.org/x/image/math/f64"
)
const textureDepth = 32
type textureImpl struct {
s *screenImpl
size image.Point
xm xproto.Pixmap
xp render.Picture
mu sync.Mutex
released bool
}
func (t *textureImpl) Size() image.Point { return t.size }
func (t *textureImpl) Bounds() image.Rectangle { return image.Rectangle{Max: t.size} }
func (t *textureImpl) Release() {
t.mu.Lock()
released := t.released
t.released = true
t.mu.Unlock()
if released {
return
}
render.FreePicture(t.s.xc, t.xp)
xproto.FreePixmap(t.s.xc, t.xm)
}
func (t *textureImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle, sender screen.Sender) {
src.(*bufferImpl).upload(t, xproto.Drawable(t.xm), t.s.gcontext32, textureDepth, dp, sr, sender)
}
func (t *textureImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
fill(t.s.xc, t.xp, dr, src, op)
}
func (t *textureImpl) draw(xp render.Picture, src2dst *f64.Aff3, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
// TODO: honor all of src2dst, not just the translation.
dstX := int(src2dst[2]) - sr.Min.X
dstY := int(src2dst[5]) - sr.Min.Y
render.Composite(t.s.xc, renderOp(op), t.xp, 0, xp,
int16(sr.Min.X), int16(sr.Min.Y), // SrcX, SrcY,
0, 0, // MaskX, MaskY,
int16(dstX), int16(dstY), // DstX, DstY,
uint16(sr.Dx()), uint16(sr.Dy()), // Width, Height,
)
}
func renderOp(op draw.Op) byte {
if op == draw.Src {
return render.PictOpSrc
}
return render.PictOpOver
}
golang-golang-x-exp_eb7c1fa/shiny/driver/x11driver/window.go 0000664 0000000 0000000 00000007337 12572222044 0024243 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package x11driver
import (
"image"
"image/color"
"image/draw"
"sync"
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/render"
"github.com/BurntSushi/xgb/xproto"
"golang.org/x/exp/shiny/driver/internal/pump"
"golang.org/x/exp/shiny/screen"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/mouse"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/geom"
)
type windowImpl struct {
s *screenImpl
xw xproto.Window
xg xproto.Gcontext
xp render.Picture
pump pump.Pump
xevents chan xgb.Event
// This next group of variables are mutable, but are only modified in the
// screenImpl.run goroutine.
width, height int
mu sync.Mutex
released bool
}
func (w *windowImpl) Events() <-chan interface{} { return w.pump.Events() }
func (w *windowImpl) Send(event interface{}) { w.pump.Send(event) }
func (w *windowImpl) Release() {
w.mu.Lock()
released := w.released
w.released = true
w.mu.Unlock()
if released {
return
}
render.FreePicture(w.s.xc, w.xp)
xproto.FreeGC(w.s.xc, w.xg)
xproto.DestroyWindow(w.s.xc, w.xw)
w.pump.Release()
}
func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle, sender screen.Sender) {
src.(*bufferImpl).upload(w, xproto.Drawable(w.xw), w.xg, w.s.xsi.RootDepth, dp, sr, sender)
}
func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {
fill(w.s.xc, w.xp, dr, src, op)
}
func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
src.(*textureImpl).draw(w.xp, &src2dst, sr, op, opts)
}
func (w *windowImpl) EndPaint(e paint.Event) {
// TODO.
}
func (w *windowImpl) handleConfigureNotify(ev xproto.ConfigureNotifyEvent) {
// TODO: lifecycle events.
newWidth, newHeight := int(ev.Width), int(ev.Height)
if w.width == newWidth && w.height == newHeight {
return
}
w.width, w.height = newWidth, newHeight
// TODO: don't assume that PixelsPerPt == 1.
w.Send(size.Event{
WidthPx: newWidth,
HeightPx: newHeight,
WidthPt: geom.Pt(newWidth),
HeightPt: geom.Pt(newHeight),
PixelsPerPt: 1,
})
// TODO: translate X11 expose events to shiny paint events, instead of
// sending this synthetic paint event as a hack.
w.Send(paint.Event{})
}
func (w *windowImpl) handleKey(detail xproto.Keycode, state uint16, dir key.Direction) {
// The key event's rune depends on whether the shift key is down.
unshifted := rune(w.s.keysyms[detail][0])
r := unshifted
if state&xShiftMask != 0 {
r = rune(w.s.keysyms[detail][1])
// In X11, a zero xproto.Keysym when shift is down means to use what
// the xproto.Keysym is when shift is up.
if r == 0 {
r = unshifted
}
}
// The key event's code is independent of whether the shift key is down.
var c key.Code
if 0 <= unshifted && unshifted < 0x80 {
// TODO: distinguish the regular '2' key and number-pad '2' key (with
// Num-Lock).
c = asciiKeycodes[unshifted]
} else {
r, c = -1, nonUnicodeKeycodes[unshifted]
}
// TODO: Unicode-but-not-ASCII keysyms like the Swiss keyboard's 'ö'.
w.Send(key.Event{
Rune: r,
Code: c,
Modifiers: keyModifiers(state),
Direction: dir,
})
}
func (w *windowImpl) handleMouse(x, y int16, b xproto.Button, state uint16, dir mouse.Direction) {
// TODO: should a mouse.Event have a separate MouseModifiers field, for
// which buttons are pressed during a mouse move?
w.Send(mouse.Event{
X: float32(x),
Y: float32(y),
Button: mouse.Button(b),
Modifiers: keyModifiers(state),
Direction: dir,
})
}
golang-golang-x-exp_eb7c1fa/shiny/driver/x11driver/x11driver.go 0000664 0000000 0000000 00000003246 12572222044 0024554 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package x11driver provides the X11 driver for accessing a screen.
package x11driver
// TODO: figure out what to say about the responsibility for users of this
// package to check any implicit dependencies' LICENSEs. For example, the
// driver might use third party software outside of golang.org/x, like an X11
// or OpenGL library.
import (
"fmt"
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/render"
"github.com/BurntSushi/xgb/shm"
"golang.org/x/exp/shiny/driver/internal/errscreen"
"golang.org/x/exp/shiny/screen"
)
// Main is called by the program's main function to run the graphical
// application.
//
// It calls f on the Screen, possibly in a separate goroutine, as some OS-
// specific libraries require being on 'the main thread'. It returns when f
// returns.
func Main(f func(screen.Screen)) {
if err := main(f); err != nil {
f(errscreen.Stub(err))
}
}
func main(f func(screen.Screen)) (retErr error) {
xc, err := xgb.NewConn()
if err != nil {
return fmt.Errorf("x11driver: xgb.NewConn failed: %v", err)
}
defer func() {
if retErr != nil {
xc.Close()
}
}()
if err := render.Init(xc); err != nil {
return fmt.Errorf("x11driver: render.Init failed: %v", err)
}
if err := shm.Init(xc); err != nil {
return fmt.Errorf("x11driver: shm.Init failed: %v", err)
}
s, err := newScreenImpl(xc)
if err != nil {
return err
}
f(s)
// TODO: tear down the s.run goroutine? It's probably not worth the
// complexity of doing it cleanly, if the app is about to exit anyway.
return nil
}
golang-golang-x-exp_eb7c1fa/shiny/example/ 0000775 0000000 0000000 00000000000 12572222044 0020706 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/example/basic/ 0000775 0000000 0000000 00000000000 12572222044 0021767 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/example/basic/main.go 0000664 0000000 0000000 00000003710 12572222044 0023243 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
//
// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
// install this example program. Use "go run main.go" to run it.
// Basic is a basic example of a graphical application.
package main
import (
"fmt"
"image"
"image/color"
"image/draw"
"log"
"golang.org/x/exp/shiny/driver"
"golang.org/x/exp/shiny/screen"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
)
func main() {
driver.Main(func(s screen.Screen) {
w, err := s.NewWindow(nil)
if err != nil {
log.Fatal(err)
}
defer w.Release()
winSize := image.Point{256, 256}
b, err := s.NewBuffer(winSize)
if err != nil {
log.Fatal(err)
}
defer b.Release()
drawGradient(b.RGBA())
t, err := s.NewTexture(winSize)
if err != nil {
log.Fatal(err)
}
defer t.Release()
t.Upload(image.Point{}, b, b.Bounds(), w)
var sz size.Event
for e := range w.Events() {
switch e := e.(type) {
default:
// TODO: be more interesting.
fmt.Printf("got %#v\n", e)
case key.Event:
fmt.Printf("got %v\n", e)
if e.Code == key.CodeEscape {
return
}
case paint.Event:
wBounds := image.Rectangle{Max: image.Point{sz.WidthPx, sz.HeightPx}}
w.Fill(wBounds, color.RGBA{0x00, 0x00, 0x3f, 0xff}, draw.Src)
w.Upload(image.Point{}, b, b.Bounds(), w)
w.Draw(f64.Aff3{
1, 0, 100,
0, 1, 200,
}, t, t.Bounds(), screen.Over, nil)
w.EndPaint(e)
case screen.UploadedEvent:
// No-op.
case size.Event:
sz = e
case error:
log.Print(e)
}
}
})
}
func drawGradient(m *image.RGBA) {
b := m.Bounds()
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
m.SetRGBA(x, y, color.RGBA{uint8(x), uint8(y), 0x00, 0xff})
}
}
}
golang-golang-x-exp_eb7c1fa/shiny/example/font/ 0000775 0000000 0000000 00000000000 12572222044 0021654 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/example/font/main.go 0000664 0000000 0000000 00000004547 12572222044 0023141 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
//
// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
// install this example program. Use "go run main.go" to run it.
// Font is a basic example of using fonts.
package main
import (
"flag"
"image"
"image/color"
"image/draw"
"image/png"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"golang.org/x/exp/shiny/font"
"golang.org/x/exp/shiny/font/plan9font"
"golang.org/x/image/math/fixed"
)
var (
fontFlag = flag.String("font", "",
`filename of the Plan 9 font or subfont file, such as "lucsans/unicode.8.font" or "lucsans/lsr.14"`)
firstRuneFlag = flag.Int("firstrune", 0, "the Unicode code point of the first rune in the subfont file")
)
func pt(p fixed.Point26_6) image.Point {
return image.Point{
X: int(p.X+32) >> 6,
Y: int(p.Y+32) >> 6,
}
}
func main() {
flag.Parse()
// TODO: mmap the files.
if *fontFlag == "" {
flag.Usage()
log.Fatal("no font specified")
}
var face font.Face
if strings.HasSuffix(*fontFlag, ".font") {
fontData, err := ioutil.ReadFile(*fontFlag)
if err != nil {
log.Fatal(err)
}
dir := filepath.Dir(*fontFlag)
face, err = plan9font.ParseFont(fontData, func(name string) ([]byte, error) {
return ioutil.ReadFile(filepath.Join(dir, filepath.FromSlash(name)))
})
if err != nil {
log.Fatal(err)
}
} else {
fontData, err := ioutil.ReadFile(*fontFlag)
if err != nil {
log.Fatal(err)
}
face, err = plan9font.ParseSubfont(fontData, rune(*firstRuneFlag))
if err != nil {
log.Fatal(err)
}
}
dst := image.NewRGBA(image.Rect(0, 0, 800, 300))
draw.Draw(dst, dst.Bounds(), image.Black, image.Point{}, draw.Src)
d := &font.Drawer{
Dst: dst,
Src: image.White,
Face: face,
}
ss := []string{
"The quick brown fox jumps over the lazy dog.",
"Hello, 世界.",
"U+FFFD is \ufffd.",
}
for i, s := range ss {
d.Dot = fixed.P(20, 100*i+80)
dot0 := pt(d.Dot)
d.DrawString(s)
dot1 := pt(d.Dot)
dst.SetRGBA(dot0.X, dot0.Y, color.RGBA{0xff, 0x00, 0x00, 0xff})
dst.SetRGBA(dot1.X, dot1.Y, color.RGBA{0x00, 0x00, 0xff, 0xff})
}
out, err := os.Create("out.png")
if err != nil {
log.Fatal(err)
}
defer out.Close()
if err := png.Encode(out, dst); err != nil {
log.Fatal(err)
}
}
golang-golang-x-exp_eb7c1fa/shiny/example/goban/ 0000775 0000000 0000000 00000000000 12572222044 0021774 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/example/goban/asset/ 0000775 0000000 0000000 00000000000 12572222044 0023113 5 ustar 00root root 0000000 0000000 golang-golang-x-exp_eb7c1fa/shiny/example/goban/asset/blackstone01.jpg 0000664 0000000 0000000 00000031233 12572222044 0026105 0 ustar 00root root 0000000 0000000
$.' ",#(7),01444'9=82<.342
2!!22222222222222222222222222222222222222222222222222
} !1AQa"q2#BR$3br
%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ? ((() QqB?SL1h3/cP%V~4AKEQEQEQEQEPHƑON]Aæjƫ@aZucˑY[zUKtx,\)7`CXcf㺛<`,L]MPUNLCpѱ-kSA]đuOj[HUfF#3R'UՐ
-QEQEQEQMw2jƣ2A=+
Ŗj
s\Ʒ2K^}ko_]yhH+i3*Gҫ_x (ϭd$ɼTt@X/nVd
#Am2&d1{W#1l/