pax_global_header00006660000000000000000000000064125722220440014512gustar00rootroot0000000000000052 comment=eb7c1fa4d21cad64754b3d847ab08550c4c68529 golang-golang-x-exp_eb7c1fa/000077500000000000000000000000001257222204400161215ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/.gitattributes000066400000000000000000000005311257222204400210130ustar00rootroot00000000000000# 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/.gitignore000066400000000000000000000001241257222204400201060ustar00rootroot00000000000000# Add no patterns to .hgignore except for files generated by the build. last-change golang-golang-x-exp_eb7c1fa/AUTHORS000066400000000000000000000002551257222204400171730ustar00rootroot00000000000000# 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.md000066400000000000000000000020071257222204400203510ustar00rootroot00000000000000# 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/CONTRIBUTORS000066400000000000000000000002521257222204400200000ustar00rootroot00000000000000# 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/LICENSE000066400000000000000000000027071257222204400171340ustar00rootroot00000000000000Copyright (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/PATENTS000066400000000000000000000024271257222204400171670ustar00rootroot00000000000000Additional 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/README000066400000000000000000000014061257222204400170020ustar00rootroot00000000000000This 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.cfg000066400000000000000000000000251257222204400207330ustar00rootroot00000000000000issuerepo: golang/go golang-golang-x-exp_eb7c1fa/ebnf/000077500000000000000000000000001257222204400170335ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/ebnf/ebnf.go000066400000000000000000000161171257222204400203020ustar00rootroot00000000000000// 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.go000066400000000000000000000024651257222204400213420ustar00rootroot00000000000000// 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.go000066400000000000000000000076101257222204400206620ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400177225ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/ebnflint/doc.go000066400000000000000000000011621257222204400210160ustar00rootroot00000000000000// 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.go000066400000000000000000000040341257222204400220530ustar00rootroot00000000000000// 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.go000066400000000000000000000005341257222204400231130ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400177625ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/fsnotify/README.txt000066400000000000000000000001221257222204400214530ustar00rootroot00000000000000Please use gopkg.in/fsnotify.v0 instead. For updates, see: https://fsnotify.org/ golang-golang-x-exp_eb7c1fa/inotify/000077500000000000000000000000001257222204400176025ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/inotify/inotify_linux.go000066400000000000000000000203211257222204400230270ustar00rootroot00000000000000// 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.go000066400000000000000000000046771257222204400241060ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400170535ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/mmap/manual_test_program.go000066400000000000000000000022641257222204400234510ustar00rootroot00000000000000// 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.go000066400000000000000000000050601257222204400215540ustar00rootroot00000000000000// 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.go000066400000000000000000000030211257222204400215310ustar00rootroot00000000000000// 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.go000066400000000000000000000014011257222204400213670ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400166775ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/old/netchan/000077500000000000000000000000001257222204400203175ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/old/netchan/common.go000066400000000000000000000177651257222204400221560ustar00rootroot00000000000000// 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.go000066400000000000000000000272731257222204400222020ustar00rootroot00000000000000// 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.go000066400000000000000000000177011257222204400221660ustar00rootroot00000000000000// 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.go000066400000000000000000000234531257222204400233340ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400172535ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/driver/000077500000000000000000000000001257222204400205465ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/driver/driver.go000066400000000000000000000015071257222204400223730ustar00rootroot00000000000000// 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.go000066400000000000000000000005201257222204400237310ustar00rootroot00000000000000// 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.go000066400000000000000000000007031257222204400242070ustar00rootroot00000000000000// 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.go000066400000000000000000000005001257222204400241350ustar00rootroot00000000000000// 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.go000066400000000000000000000005321257222204400230610ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400223645ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/driver/gldriver/buffer.go000066400000000000000000000010111257222204400241550ustar00rootroot00000000000000// 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.go000066400000000000000000000154021257222204400240010ustar00rootroot00000000000000// 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.m000066400000000000000000000213011257222204400236230ustar00rootroot00000000000000// 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.go000066400000000000000000000065601257222204400245400ustar00rootroot00000000000000// 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.go000066400000000000000000000011551257222204400240360ustar00rootroot00000000000000// 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.go000066400000000000000000000052201257222204400241710ustar00rootroot00000000000000// 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.go000066400000000000000000000044051257222204400244160ustar00rootroot00000000000000// 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.go000066400000000000000000000152761257222204400242350ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400223625ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/driver/internal/errscreen/000077500000000000000000000000001257222204400243525ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/driver/internal/errscreen/errscreen.go000066400000000000000000000013601257222204400266710ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400233435ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/driver/internal/pump/pump.go000066400000000000000000000041001257222204400246460ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400240715ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/driver/internal/swizzle/swizzle_amd64.go000066400000000000000000000003451257222204400271240ustar00rootroot00000000000000// 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.s000066400000000000000000000015211257222204400267560ustar00rootroot00000000000000// 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.go000066400000000000000000000013141257222204400274760ustar00rootroot00000000000000// 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.go000066400000000000000000000004211257222204400273250ustar00rootroot00000000000000// 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.go000066400000000000000000000037051257222204400271730ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400225575ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/driver/windriver/doc.go000066400000000000000000000105151257222204400236550ustar00rootroot00000000000000// 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.go000066400000000000000000000007551257222204400244310ustar00rootroot00000000000000// 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.go000066400000000000000000000013101257222204400242220ustar00rootroot00000000000000// 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.go000066400000000000000000000047061257222204400243740ustar00rootroot00000000000000// 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.go000066400000000000000000000051131257222204400263320ustar00rootroot00000000000000// 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.c000066400000000000000000000051711257222204400242360ustar00rootroot00000000000000// 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.go000066400000000000000000000114611257222204400244200ustar00rootroot00000000000000// 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.c000066400000000000000000000051551257222204400244040ustar00rootroot00000000000000// 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.c000066400000000000000000000005211257222204400247320ustar00rootroot00000000000000// 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.go000066400000000000000000000041241257222204400251200ustar00rootroot00000000000000// 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.h000066400000000000000000000031551257222204400247450ustar00rootroot00000000000000// 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.go000066400000000000000000000075041257222204400265320ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400223735ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/driver/x11driver/buffer.go000066400000000000000000000063531257222204400242020ustar00rootroot00000000000000// 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.go000066400000000000000000000105261257222204400235160ustar00rootroot00000000000000// 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.go000066400000000000000000000306321257222204400242050ustar00rootroot00000000000000// 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.go000066400000000000000000000020351257222204400257230ustar00rootroot00000000000000// 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.go000066400000000000000000000010571257222204400247150ustar00rootroot00000000000000// 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.go000066400000000000000000000034331257222204400244250ustar00rootroot00000000000000// 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.go000066400000000000000000000073371257222204400242430ustar00rootroot00000000000000// 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.go000066400000000000000000000032461257222204400245540ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400207065ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/example/basic/000077500000000000000000000000001257222204400217675ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/example/basic/main.go000066400000000000000000000037101257222204400232430ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400216545ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/example/font/main.go000066400000000000000000000045471257222204400231410ustar00rootroot00000000000000// 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/000077500000000000000000000000001257222204400217745ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/example/goban/asset/000077500000000000000000000000001257222204400231135ustar00rootroot00000000000000golang-golang-x-exp_eb7c1fa/shiny/example/goban/asset/blackstone01.jpg000066400000000000000000000312331257222204400261050ustar00rootroot00000000000000    $.' ",#(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#3޶R'UՐ -QEQEQEQMw2jƣ 2A=+ Ŗj s\Ʒ2K^}ko_]yhH+i3*Gҫ_x  (ϭd$ɼTt@X/nVd #Am2&d1{W׾#1l/((*؃ ^uH#I };N㺗 93¹W}b#J.]]yk-%! 2O=u$Vb7Y?6Y_iQʸZC\޹XTTݹ^֢cyqO<zKX}*8fؗRW.H>CoخmQPCw~y"m7C$ = U{CMҢ`I*T kBNl\ݴ,6J zk9[k#,3kwAXw@w=(v]DRчB E>((]%,zWxշiHV2n*WZUȍ{lKLB|ګ_^Zـ;PBsf3=k:-Gi$inUn׶Ei=ɺ*"pvj>y¨hf]}k{CՍǕpPڵV di&EnJ Z[sNߕg$ xGm6Gr <$~[*sLqsm#)3ێl!CW麻i%~\q^_3q pc]b֖(*9eX,H ŚF<^ k\` vjX*f ۾N}MhY+; C:Bŵiے+n->HC֐r8 Ayo>ht,#{iL[S9=R5Pf HŸjm߅""1ǴфƒZ|$>&B9 |Hh ɞɭ%mBHcX&S}:\M0pMf#KySz&TDKcQRƘ$3#[}4YgL2/c\zФ[W+Pw>xpB_Z b^@"F3ڲխR6hZq$1wG!TxCsϭz聶(RMr'ť炧 Q޼JZuO qE!MA`q]yݻNXʥ1V\I=*V<``޼ՙ4 FYk_Nշ \b*O֣Q7Tc+JI.PwrOҹmSIDEN:18#[Mwt ʤ!G-58uV;J-yÙя(խD<,(8 ў ŝ5?>bh݀)y~qޯ^MV{spV?Qip[E=ЩsNR֕`Cqg_]Mf &T<`{ $Yf$+7T I%3_)7M(\m6_6uw\ozRrd9翭z55Ff RF+8ȥd 'q"Yp'{BC$4cnXv\ژds)'u6oym{ F9$e\_LZ.͝WF4kJѴ4̋<& 't#haFA,?0Z7Cހb=TK H,GOan,>Qkuݲ˕`2ռXחRI +b\{09g#bv`gj\HUobxqUf#Ӄڹ&7mxV#4"3lnz}kn0X`)a:vWRZ\fFG*sYOm+J.7y= C s>zJ,u[f3[S.1w9W8q"71& #`OL+R7Cº*Lȸdw'Ҷ`EM  qW{3I׫N%q՚(BmG#5.tr`}-"I_"B>מͦ*!Vt* `ḕ٣wgmnw}y庰_7!O56ċ<{*ъ>E;'j3)a4E ^fGZɆxuF#27OJ4My2D{Fto#WEڊk>ծ hFT_\fWϩLpo8kMzKyxYId~Bl6*I&۳$F`uܐ5L W,](=]([ja.k< qeyV=b.!Bd^88ULN$Z2(؁r{UQVD82 >\yw?,e\'oy;@(C\sFwc,3޸gI'6^0;5$0@ږkD`1횭i#7c؛zR lhkqZ`3nOe$ݨgxM,j͸Q۩=hh#r"y%i[8_U6%Ejđq=K#qU죉/ߴ.Puu:vEc[1h 0 i-'25veN}Gzޛ'j>v^ɱH86֯!ы֯4U{$@W? +,qoKiuื`'iXVܔY՝Nydi#XG&.1U|kfhmnʏn#0HZ0lj7H"g`{YB0!88>J"UEEG+!k2O:v*S?(=E39sڜaJXHJ_xI-x_<8\.+-1pO5[cvRx _J4dQsqH"\$8kXIrȑ-ɎW"vg-$Àp}cF+,[z^)%DqkݹMw<ڬE,Tٺ|B{HF>B;5Qnӯ{);%]+/fJ&$*[I4SxTqgBC`jʱ4I!pףin# 9j Um@@rlW#A$9ڴpyz*:(5g59ִoW*PNN?/P%_q!0HAirDQ^3GB%Vdĭ}0 ^iڝgB8 ITH+x]%qy`U´ d3SHUkCDVyWppPX-$]4ܧ^A>o8MŒTc!reJMM|,N㏰4]>]Rm -lgi7\ŎK1$z|rՈ6O֮ͦ.[r%&.q+N|M:Y2dPx㡤mha1;6tnHZ:6221%&ZwsZZX73H.ᎵGI`'$mGBs4gpUD[-2FN+JO_-?)o4Hg=s;\$c#Ҥ[14Ӓ/`)"P3,rg\Ҷ-0@@wWKhsr[ ]NâG=ZYMʷr*.B+{gE+@;´uhbwI@BpS9Dr=FIcE$ }1W,yn#+ڣUʃqq;5SV>\6bR7+LJF'Uy!Tw#@D7).;Wi ULU|Cc5mc+yڼ_WӮKRʏjdTiQO?Zk9GKg'ۚ+.z6ɀy-Yz|;Jr@[{{ك9B*ԶWAb7 {PڋZ[br7 aĖO5OG*ӭ5ʮF,үAn'+f;pIR6*DZ6ӟB+oq\KA Up6x=[IgrvB9QwoQ1<#k/dc1l%co7̱ˏz,-Ucu{qAJ3+2,3O/̌Q2PB uP%%1 ,Ԋ=ԗDu3OFy:ջg{hiͪ}slV-ص1`͈@sZEC);y^21ۊ!dChsXQk -m +#WIu16f,>PJJ*RIlcar#|4{gW;}yb9Y;9r I'Y>]Sn74|<{sIglmFT@<G\F-we"PU`YI8m%͙X\$g sAN[h_I{ =N Zd7Mix<2H'[oi(S +qڬzIt1L߼_{#{fu Pلl oYsޣ3)0u܎I1NeK #0JMieHg^C$'$WuwmBc*Mg?*3TnqI,Fa a1{ mB9 T)OZ{_Ge_;mU(sXweGgm+ OTԢM=HZ ?(J}23-nkJ6"{ --䵉Ysߊ7y KpybΐBdpOVu1SQ̈́`*xSY|̠+ 2F?+(1D} rO"M LY++EC~3r ^XA<3ڹ5$k?'++(eڣ4.J۩h֍ 3Zj yePzU{fkwb#aNj֨eigl{kGs!@ CIN e!I$7SiBHI]cU/4aʍw7l۷7#5.XK(tO#%+&9aYQ,9bQ]<Nj``ADQm2ǵoG{,O.;u-Znio I,\R跶)2G)y'_sLmg0Et7LIvo&MGɟǥWӢ_cgP7\+ƫiqw%|s~Ժ [n@Ɍg[\\\6[1 vͪ]A<~VHۓ:cen.e:s7WUo[&HX݅FHX@D % VREc4:cz~=—V2(=Eyxy  ֋oQi#(z^S Wްؔ(Y@*K-~q! g¾YBwlcڹhdd`9ɭ}&XL>DS_h[kV=T{Vekh1,p{}jņH2{B!&G"t{{`(؟ cԎj/n0# v>KbEܕ#JA$ ,6Zh0ލq򁚚[Еo-'3[2r0Gjltoe0[sn}Ko[P3ƦK@R2&"1/*c2لg;@7Om:pce>^A, lJXa$k˄ sVFKki(a8}|CIȖs9bP*9$v5 GQhaٜp~ G-;6yf_kREFxF A.Ջuʊȅ}xik|M^:VnK߽Tz<,L c]#FVKo!bB!9ޤ|"As9 'TJK [HJEd>S/|U/!0 1Ď-qT.ЅpsgҚ ʱt\skcOP"Alk$"(+xGgb9 98]5\]$+zԔQE c+\o4KK]?*q^_4Idei Z)ޟs.&tOE#JUkq+[y>p* 'ڝ̨Tu+qTf&g&[k7:UkUv[&e!6󏗿CLq>q՚WQC$6䳒L{ZM7rCÐ#eQ֥["$gĞty~?>E!KA=̘H*/'Lm횾b o*LYqTwlf?uĹV!؏U=7@k+_z7- ˉw-y$H ڹpQʈon`;ga:*Iy''Zl4VƦUflWAilă# U+Ӏs:?Jml(dqtmQEQU紊duCYRntUXFזKr.FѶ(_sT֓pq CĪlpA%E혚4 9= Ot)cd>U\ؚy6Y:LwbF$;zHBNb%1ăak-R5hUQgޤ[6 b家}VcxR[VuW1.K)M ksa_UKbW8xH0_= S[V4lI{SW42Ѭ*7 ^;[V:;#4˹74_',zi"MJVP`/^(((aPKl!R85RM1PZM) ?QT% C; C1Yr0p I:T'H4y8䐲o!ho~e4#^@VŪbXU@((((( MҚcCivJO)28(8jQzQKEQEQEgolang-golang-x-exp_eb7c1fa/shiny/example/goban/asset/blackstone02.jpg000066400000000000000000000301141257222204400261030ustar00rootroot00000000000000    $.' ",#(7),01444'9=82<.342  2!!22222222222222222222222222222222222222222222222222  }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz?(((deA%hq-D2ZԀHWZb]bܜYKeFŸEQEQEQEQEQHd.k>;V|gZȟ\7F}IoֹC (Vs pV)MH t_l&.P1rSګI⩣Ueۜr߇ZB#I[V4c򲓷v{V)FQZ EdVŦivN{gjP4QEQEQEQEG$SXڎ#n}8^U1m$kS/w(kz[9r$|:S}GYUBu_ X`xsJևR\ǽ:-M/" SR5OmDhG"0=&O.ĨnʈcBҗ]cE#<4`'ܰ66ӂXx g#[r9'֪4(((qTooLj,8=GUD-#>09jZ>b%#\yf+Jr.GI&uKc4 #֒=0@&ykE,mev$tqjzB$eܮ?x[ڳeGR0n B(>ZM#oȬe)if݈cҦ[2pG硨EDVOQVH:AC?Ux_Z0ۜc޶LҬr͍cYx! \uZjώG<֊a-QEQE! u5RIMZQ瘰 lgx.u&Cw Wqzu^kⲴްy֥c!}~l#, An}*o#AAc8Q,Mkr@{SNٞ1+ 1l`yǵf/|%ߺ9 z"Kri$+,d.@=F'p sVu\!;g8Ȃ8F 88!t~.9om%)IUp1_jԚG $jZ@wsZ(kA A>Jq6 Z_0f-ʎ[kWv2$[ b ƈYrR3}ue dֺ{r5=QER3YwW)xcL0pf\%E$cs_x[0ȶqH >s.|褘G#Ҷ4&W2@rXYgj`# ` ZRa8?J(mj~bRCnZ8GZ[>i)SOz7sjT+1}]'\xbȣlnͬ2 {e^X >"2@o,LHvm{,rIߴ*O>sv!n М(>=/Ei,]ٓ"Eu%q1V(F6-ǯֳ%B5bKm-#>h$<.0qz㍐.# {(:uJZ9dEa[䟭p* \G-͗rkdcl΋-*77rAkbG1HUU:zm[ cZŧ%er dcڶ,8ŮWϑXp]ҲpN{sO F21照fwaUG Sx`PDpd(/S/17d>c޹}=x.̓o'y\bLUw}=* {2qr>8u%؜Ϯ}+oKdT4#h#[VE͆ ۓֳCm-DI}r$jb J_[UEuokcfH$yʏ]!{}jNz j,E* zUr<oZ(7Imk$K0^ko;>MU#R2旦yZ uW VƟdl Ċkl_(s1v.Pd"Ծù5i67 pC!Zض1D;ʬU#"FP2zK[ḴƤ &&1vXj2Ȩrg\5!kHUD {zsS)MqJY[h\sީI@*ޟ7:L:1rOB*T RȆEs%B,BrzG[{w̢S!p# ];wxm*#ƤәQ&?'z{ F;R9[}~xKeI&0}\ ۭ^dIc[>pD$1KhF n>z=M*,BM@G@i~uD҅<'Ozo.#HXor8l5d]ܲefQU>>$? v[LN,A%cH[+8'/g6kƥa;5>!-.pؚж">Yf8X+g6smĆX>m$|Pdƪ/ .}qVDks2YF@ Dno9l|V~ײ%HYRIS5K[{bX8PIzTohgx6޹z]FH#Hw" Iݤ<7ne#`{2ܹXmEe?Aٳ7E4Ýͽ[KVF)ԜgsVVOˎv^+$ eHkGzulVbV=i%6>ksf0Ȧ8@ ]\^\4dm-`=znnZ*#ަPPYw;"Js8K[#i&PF??n+53\fΎ'/&<[ci(' ['ҥd[&wpCk2&T{ pdvS9O&MC!mR<Ce̮L`ޟmnv{ Ģ9☮p<½:b3RX"|rW3E Es{l jDeRVp6OF>G5|'VKȳ 4|̈y.t˨H X|}su; w0GB?1<ճ ̮?vOG~.HӮDWFD}+uK{ YĤݴw Xs,m"ʅv}; dbdD,cio)dScU.5"G HoJU,Tq ڰAXWz^Ұf_NO*U pepL[C>v@L% nWCl-IW Q֨`@q#} ?_=jW;JLuG2DA ñ5$TwsޠC ’G,uJS^ۦ̳[)FzA\/ol$p9ڶ}}:Df\2Jb2K_7[x&ϗ;޳ KDmwLqZB y$w1ZmK-#nsi {䕉 ~2ǖя &#J(>~0,:q\dI"2OU珥r|IbaH7qЊ3bD>o|GLٻVͣ]FoWszvk1YcpךU viddpyZZ}"$'>²YTʎc nCƴtэ.@ u5-K*FTjس\ }q^6 ں*l^O߲j:"r| A'An$;ۢ+î#JwW+l*zVVXͰ+`zvw$~#[Ub*>Z庺Y-!P\XUX8K?Ow1H*ak/_f$xNG=aM!Pa:]{ݢBCw2iϹ ֠_ˋChpJzW3~ؤ&InM#"@O:޴dFg>۳HSZyOo ,vtV۳4F_܏ޯެ\nQ-Իc$n'Zeʹ+&j8sҼzI[| w‘WuKi㉭psxaH5yS ҢKp.S;08SZ34YU( `Wlb RpY¿ds}} fKf7un:UydL`J } F < NJ_,RTiv9!{Sgr9;f#U&@2W H$+IxB|^k 戀7qn!$u@=O '_܈d޽Jу@c*1Wx单RH/~^W?k e:-kgE3g oiʸ3Mr6Z)i]4Q˱Sz_GH$̒U¯ֹ5Oiǟf9 HEbpN6_MT J}5υF.ak60 ~o2@pG9Nփ\j,i`zyo<%A!,1;ao=1jVi-=_ }j>{"E 5޾PPJ8i>m?FmJq)`0Z( WGAS|JSVt٤RlAGA[QӣVD*v=Y%/} s׮F|e9(^åO'=,otHhO=;m YHs!!^׽ 1iRzm=9ϴK+a=*דgKk/ڗ*۰No,xSζrB`ɏP?Z{(+'~$ul AޡM;VkhMڌه?ޱI\f>e'wX[PN#kj8#љg~ڙ\quElA2c$sQ,zn[x0?T ([t-YS-L{{Ɓe'yUC6Ѐ#[1ľFN ?*Cs1gp+>¤UhyXiu;x`8d[vkB!qp-]ifK1i^%¡JP?B YGz+HʐHom]\<6/eٺX`Ikּ&GUK {Fp9y:pH[.(8홐)@Y m