lots of docs and examples

This commit is contained in:
Andrew Gallant (Ocelot) 2012-05-07 04:09:19 -04:00
parent 3bf376bd66
commit dc48249e1a
27 changed files with 495 additions and 305 deletions

View File

@ -1,5 +1,6 @@
# This Makefile is used by the developer. It is not needed in any way to build
# a checkout of the XGB repository.
# It will be useful, however, if you are hacking at the code generator.
XPROTO=/usr/share/xcb
@ -20,3 +21,8 @@ test:
bench:
go test -run 'nomatch' -bench '.*' -cpu 1,2,6
gofmt:
gofmt -w *.go xgbgen/*.go examples/*.go examples/*/*.go
colcheck xgbgen/*.go examples/*.go examples/*/*.go \
auth.go conn.go cookie.go doc.go xgb.go xgb_help.go xgb_test.go

152
nexgb/doc.go Normal file
View File

@ -0,0 +1,152 @@
/*
Package XGB provides the X Go Binding, which is a low-level API to communicate
with the core X protocol and many of the X extensions.
It is *very* closely modeled on XCB, so that experience with XCB (or xpyb) is
easily translatable to XGB. That is, it uses the same cookie/reply model
and is thread safe. There are otherwise no major differences (in the API).
Most uses of XGB typically fall under the realm of window manager and GUI kit
development, but other applications (like pagers, panels, tilers, etc.) may
also require XGB. Moreover, it is a near certainty that if you need to work
with X, xgbutil will be of great use to you as well:
https://github.com/BurntSushi/xgbutil
Example
This is an extremely terse example that demonstrates how to connect to X,
create a window, listen to StructureNotify events and Key{Press,Release}
events, map the window, and print out all events received. An example with
accompanying documentation can be found in examples/create-window.
package main
import (
"fmt"
"github.com/BurntSushi/xgb"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
fmt.Println(err)
return
}
wid, _ := X.NewId()
X.CreateWindow(X.DefaultScreen().RootDepth, wid, X.DefaultScreen().Root,
0, 0, 500, 500, 0,
xgb.WindowClassInputOutput, X.DefaultScreen().RootVisual,
xgb.CwBackPixel | xgb.CwEventMask,
[]uint32{ // values must be in the order defined by the protocol
0xffffffff,
xgb.EventMaskStructureNotify |
xgb.EventMaskKeyPress |
xgb.EventMaskKeyRelease})
X.MapWindow(wid)
for {
ev, xerr := X.WaitForEvent()
if ev == nil && xerr == nil {
fmt.Println("Both event and error are nil. Exiting...")
return
}
if ev != nil {
fmt.Printf("Event: %s\n", ev)
}
if xerr != nil {
fmt.Printf("Error: %s\n", xerr)
}
}
}
Xinerama Example
This is another small example that shows how to query Xinerama for geometry
information of each active head. Accompanying documentation for this example
can be found in examples/xinerama.
package main
import (
"fmt"
"log"
"github.com/BurntSushi/xgb"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
// Initialize the Xinerama extension.
// The appropriate 'Init' function must be run for *every*
// extension before any of its requests can be used.
err = X.XineramaInit()
if err != nil {
log.Fatal(err)
}
reply, err := X.XineramaQueryScreens().Reply()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Number of heads: %d\n", reply.Number)
for i, screen := range reply.ScreenInfo {
fmt.Printf("%d :: X: %d, Y: %d, Width: %d, Height: %d\n",
i, screen.XOrg, screen.YOrg, screen.Width, screen.Height)
}
}
Parallelism
XGB can benefit greatly from parallelism due to its concurrent design. For
evidence of this claim, please see the benchmarks in xgb_test.go.
Tests
xgb_test.go contains a number of contrived tests that stress particular corners
of XGB that I presume could be problem areas. Namely: requests with no replies,
requests with replies, checked errors, unchecked errors, sequence number
wrapping, cookie buffer flushing (i.e., forcing a round trip every N requests
made that don't have a reply), getting/setting properties and creating a window
and listening to StructureNotify events.
Code Generator
Both XCB and xpyb use the same Python module (xcbgen) for a code generator. XGB
(before this fork) used the same code generator as well, but in my attempt to
add support for more extensions, I found the code generator extremely difficult
to work with. Therefore, I re-wrote the code generator in Go. It can be found
in its own sub-package, xgbgen, of xgb. My design of xgbgen includes a rough
consideration that it could be used for other languages.
What works
I am reasonably confident that the core X protocol is in full working form. I've
also tested the Xinerama and RandR extensions sparingly. Many of the other
existing extensions have Go source generated (and are compilable) and are
included in this package, but I am currently unsure of their status. They
*should* work.
What does not work
XKB is the only extension that intentionally does not work, although I suspect
that GLX also does not work (however, there is Go source code for GLX that
compiles, unlike XKB). I don't currently have any intention of getting XKB
working, due to its complexity and my current mental incapacity to test it.
There are so many functions
Indeed. Everything below this initial overview is useful insomuch as your
browser's "Find" feature is useful. The following list of types and functions
should act as a reference to the Go representation of a request, type or reply
of something you *already know about*. To search the following list in hopes
of attaining understanding is a quest in folly. For understanding, please see
the X Protocol Reference Manual: http://goo.gl/aMd2e
*/
package xgb

View File

@ -1,27 +0,0 @@
package main
import (
// "fmt"
"log"
"github.com/BurntSushi/xgb"
)
func init() {
log.SetFlags(0)
}
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
aname := "_NET_ACTIVE_WINDOW"
atom, err := X.InternAtom(true, uint16(len(aname)), aname).Reply()
if err != nil {
log.Fatal(err)
}
log.Printf("%d", atom.Atom)
}

View File

@ -0,0 +1,98 @@
// Example create-window shows how to create a window, map it, resize it,
// and listen to structure and key events (i.e., when the window is resized
// by the window manager, or when key presses/releases are made when the
// window has focus). The events are printed to stdout.
package main
import (
"fmt"
"github.com/BurntSushi/xgb"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
fmt.Println(err)
return
}
// Any time a new resource (i.e., a window, pixmap, graphics context, etc.)
// is created, we need to generate a resource identifier with NewId.
wid, _ := X.NewId()
// CreateWindow takes a boatload of parameters.
X.CreateWindow(X.DefaultScreen().RootDepth, wid, X.DefaultScreen().Root,
0, 0, 500, 500, 0,
xgb.WindowClassInputOutput, X.DefaultScreen().RootVisual,
0, []uint32{})
// This call to ChangeWindowAttributes could be factored out and
// included with the above CreateWindow call, but it is left here for
// instructive purposes. It tells X to send us events when the 'structure'
// of the window is changed (i.e., when it is resized, mapped, unmapped,
// etc.) and when a key press or a key release has been made when the
// window has focus.
// We also set the 'BackPixel' to white so that the window isn't butt ugly.
X.ChangeWindowAttributes(wid,
xgb.CwBackPixel|xgb.CwEventMask,
[]uint32{ // values must be in the order defined by the protocol
0xffffffff,
xgb.EventMaskStructureNotify |
xgb.EventMaskKeyPress |
xgb.EventMaskKeyRelease})
// MapWindow makes the window we've created appear on the screen.
// We demonstrated the use of a 'checked' request here.
// A checked request is a fancy way of saying, "do error handling
// synchronously." Namely, if there is a problem with the MapWindow request,
// we'll get the error *here*. If we were to do a normal unchecked
// request (like the above CreateWindow and ChangeWindowAttributes
// requests), then we would only see the error arrive in the main event
// loop.
//
// Typically, checked requests are useful when you need to make sure they
// succeed. Since they are synchronous, they incur a round trip cost before
// the program can continue, but this is only going to be noticeable if
// you're issuing tons of requests in succession.
//
// Note that requests without replies are by default unchecked while
// requests *with* replies are checked by default.
err = X.MapWindowChecked(wid).Check()
if err != nil {
fmt.Printf("Checked Error for mapping window %d: %s\n", wid, err)
} else {
fmt.Printf("Map window %d successful!\n", wid)
}
// This is an example of an invalid MapWindow request and what an error
// looks like.
err = X.MapWindowChecked(0).Check()
if err != nil {
fmt.Printf("Checked Error for mapping window 0x1: %s\n", err)
} else { // neva
fmt.Printf("Map window 0x1 successful!\n")
}
// Start the main event loop.
for {
// WaitForEvent either returns an event or an error and never both.
// If both are nil, then something went wrong and the loop should be
// halted.
//
// An error can only be seen here as a response to an unchecked
// request.
ev, xerr := X.WaitForEvent()
if ev == nil && xerr == nil {
fmt.Println("Both event and error are nil. Exiting...")
return
}
if ev != nil {
fmt.Printf("Event: %s\n", ev)
}
if xerr != nil {
fmt.Printf("Error: %s\n", xerr)
}
}
}

21
nexgb/examples/doc.go Normal file
View File

@ -0,0 +1,21 @@
/*
Package examples contains a few different use cases of XGB, like creating
a window, reading properties, and querying for information about multiple
heads using the Xinerama or RandR extensions.
If you're looking to get started quickly, I recommend checking out the
create-window example first. It is the most documented and probably covers
some of the more common bare bones cases of creating windows and responding
to events.
If you're looking to query information about your window manager,
get-active-window is a start. However, to do anything extensive requires
a lot of boiler plate. To that end, I'd recommend use of my higher level
library, xgbutil: https://github.com/BurntSushi/xgbutil
There are also examples of using the Xinerama and RandR extensions, if you're
interested in querying information about your active heads. In RandR's case,
you can also reconfigure your heads, but the example doesn't cover that.
*/
package documentation

View File

@ -0,0 +1,57 @@
// Example get-active-window reads the _NET_ACTIVE_WINDOW property of the root
// window and uses the result (a window id) to get the name of the window.
package main
import (
"fmt"
"log"
"github.com/BurntSushi/xgb"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
// Get the window id of the root window.
root := X.DefaultScreen().Root
// Get the atom id (i.e., intern an atom) of "_NET_ACTIVE_WINDOW".
aname := "_NET_ACTIVE_WINDOW"
activeAtom, err := X.InternAtom(true, uint16(len(aname)), aname).Reply()
if err != nil {
log.Fatal(err)
}
// Get the atom id (i.e., intern an atom) of "_NET_WM_NAME".
aname = "_NET_WM_NAME"
nameAtom, err := X.InternAtom(true, uint16(len(aname)), aname).Reply()
if err != nil {
log.Fatal(err)
}
// Get the actual value of _NET_ACTIVE_WINDOW.
// Note that 'reply.Value' is just a slice of bytes, so we use an
// XGB helper function, 'Get32', to pull an unsigned 32-bit integer out
// of the byte slice. We then convert it to an X resource id so it can
// be used to get the name of the window in the next GetProperty request.
reply, err := X.GetProperty(false, root, activeAtom.Atom,
xgb.GetPropertyTypeAny, 0, (1<<32)-1).Reply()
if err != nil {
log.Fatal(err)
}
windowId := xgb.Id(xgb.Get32(reply.Value))
fmt.Printf("Active window id: %X\n", windowId)
// Now get the value of _NET_WM_NAME for the active window.
// Note that this time, we simply convert the resulting byte slice,
// reply.Value, to a string.
reply, err = X.GetProperty(false, windowId, nameAtom.Atom,
xgb.GetPropertyTypeAny, 0, (1<<32)-1).Reply()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Active window name: %s\n", string(reply.Value))
}

View File

@ -1,41 +0,0 @@
package main
import (
"log"
"github.com/BurntSushi/xgb"
)
func init() {
log.SetFlags(0)
}
func get32(buf []byte) uint32 {
v := uint32(buf[0])
v |= uint32(buf[1]) << 8
v |= uint32(buf[2]) << 16
v |= uint32(buf[3]) << 24
return v
}
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
root := X.DefaultScreen().Root
aname := "_NET_ACTIVE_WINDOW"
atom, err := X.InternAtom(true, uint16(len(aname)), aname).Reply()
if err != nil {
log.Fatal(err)
}
reply, err := X.GetProperty(false, root, atom.Atom,
xgb.GetPropertyTypeAny, 0, (1<<32)-1).Reply()
if err != nil {
log.Fatal(err)
}
log.Printf("%X", get32(reply.Value))
}

View File

@ -1,48 +0,0 @@
package main
import (
"fmt"
"log"
"github.com/BurntSushi/xgb"
)
func main() {
X, _ := xgb.NewConn()
err := X.RegisterExtension("randr")
if err != nil {
log.Fatal(err)
}
resources, err := X.RandrGetScreenResources(X.DefaultScreen().Root).Reply()
if err != nil {
log.Fatal(err)
}
for _, output := range resources.Outputs {
info, err := X.RandrGetOutputInfo(output, 0).Reply()
if err != nil {
log.Fatal(err)
}
bestMode := info.Modes[0]
for _, mode := range resources.Modes {
if mode.Id == uint32(bestMode) {
fmt.Printf("Width: %d, Height: %d\n", mode.Width, mode.Height)
}
}
}
fmt.Println("\n")
for _, crtc := range resources.Crtcs {
info, err := X.RandrGetCrtcInfo(crtc, 0).Reply()
if err != nil {
log.Fatal(err)
}
fmt.Printf("X: %d, Y: %d, Width: %d, Height: %d\n",
info.X, info.Y, info.Width, info.Height)
}
}

View File

@ -0,0 +1,84 @@
// Example randr uses the randr protocol to get information about the active
// heads. It also listens for events that are sent when the head configuration
// changes. Since it listens to events, you'll have to manually kill this
// process when you're done (i.e., ctrl+c.)
//
// While this program is running, if you use 'xrandr' to reconfigure your
// heads, you should see event information dumped to standard out.
//
// For more information, please see the RandR protocol spec:
// http://www.x.org/releases/X11R7.6/doc/randrproto/randrproto.txt
package main
import (
"fmt"
"log"
"github.com/BurntSushi/xgb"
)
func main() {
X, _ := xgb.NewConn()
// Every extension must be initialized before it can be used.
err := X.RandrInit()
if err != nil {
log.Fatal(err)
}
// Gets the current screen resources. Screen resources contains a list
// of names, crtcs, outputs and modes, among other things.
resources, err := X.RandrGetScreenResources(X.DefaultScreen().Root).Reply()
if err != nil {
log.Fatal(err)
}
// Iterate through all of the outputs and show some of their info.
for _, output := range resources.Outputs {
info, err := X.RandrGetOutputInfo(output, 0).Reply()
if err != nil {
log.Fatal(err)
}
bestMode := info.Modes[0]
for _, mode := range resources.Modes {
if mode.Id == uint32(bestMode) {
fmt.Printf("Width: %d, Height: %d\n", mode.Width, mode.Height)
}
}
}
fmt.Println("\n")
// Iterate through all of the crtcs and show some of their info.
for _, crtc := range resources.Crtcs {
info, err := X.RandrGetCrtcInfo(crtc, 0).Reply()
if err != nil {
log.Fatal(err)
}
fmt.Printf("X: %d, Y: %d, Width: %d, Height: %d\n",
info.X, info.Y, info.Width, info.Height)
}
// Tell RandR to send us events. (I think these are all of them, as of 1.3.)
err = X.RandrSelectInputChecked(X.DefaultScreen().Root,
xgb.RandrNotifyMaskScreenChange|
xgb.RandrNotifyMaskCrtcChange|
xgb.RandrNotifyMaskOutputChange|
xgb.RandrNotifyMaskOutputProperty).Check()
if err != nil {
log.Fatal(err)
}
// Listen to events and just dump them to standard out.
// A more involved approach will have to read the 'U' field of
// RandrNotifyEvent, which is a union (really a struct) of type
// RanrNotifyDataUnion.
for {
ev, err := X.WaitForEvent()
if err != nil {
log.Fatal(err)
}
fmt.Println(ev)
}
}

View File

@ -1,29 +0,0 @@
package main
import (
"fmt"
"github.com/BurntSushi/xgb"
)
func main() {
X, _ := xgb.NewConn()
for i := 1; i <= 1<<16 + 10; i++ {
X.NoOperation()
// fmt.Printf("%d. No-Op\n", i)
}
aname := "_NET_ACTIVE_WINDOW"
for i := 1; i <= 10; i++ {
atom, err := X.InternAtom(true, uint16(len(aname)), aname).Reply()
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("%d. Sequence: %d, Atom: %d\n",
i, atom.Sequence, atom.Atom)
}
}
}

View File

@ -1,24 +0,0 @@
package main
import (
"fmt"
"github.com/BurntSushi/xgb"
)
func main() {
X, _ := xgb.NewConn()
aname := "_NET_ACTIVE_WINDOW"
for i := 1; i <= 1<<16 + 10; i++ {
atom, err := X.InternAtom(true, uint16(len(aname)), aname).Reply()
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("%d. Sequence: %d, Atom: %d\n",
i, atom.Sequence, atom.Atom)
}
}
}

View File

@ -1,61 +0,0 @@
package main
import (
"fmt"
"log"
"github.com/BurntSushi/xgb"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
wid, _ := X.NewId()
X.CreateWindow(X.DefaultScreen().RootDepth, wid, X.DefaultScreen().Root,
0, 0, 500, 500, 0,
xgb.WindowClassInputOutput, X.DefaultScreen().RootVisual,
0, []uint32{})
X.ChangeWindowAttributes(wid, xgb.CwEventMask | xgb.CwBackPixel,
[]uint32{0xffffffff, xgb.EventMaskKeyPress | xgb.EventMaskKeyRelease})
err = X.MapWindowChecked(wid).Check()
if err != nil {
fmt.Printf("Checked Error for mapping window %d: %s\n", wid, err)
} else {
fmt.Printf("Map window %d successful!\n", wid)
}
err = X.MapWindowChecked(0x1).Check()
if err != nil {
fmt.Printf("Checked Error for mapping window 0x1: %s\n", err)
} else {
fmt.Printf("Map window 0x1 successful!\n")
}
for {
ev, xerr := X.WaitForEvent()
if ev == nil && xerr == nil {
log.Fatal("Both event and error are nil. Exiting...")
}
if ev != nil {
fmt.Printf("Event: %s\n", ev)
}
if xerr != nil {
fmt.Printf("Error: %s\n", xerr)
}
if xerr == nil {
geom, err := X.GetGeometry(0x1).Reply()
if err != nil {
fmt.Printf("Geom Error: %#v\n", err)
} else {
fmt.Printf("Geometry: %#v\n", geom)
}
}
}
}

View File

@ -1,29 +0,0 @@
package main
import (
"fmt"
"log"
"github.com/BurntSushi/xgb"
)
func main() {
X, _ := xgb.NewConn()
err := X.RegisterExtension("xinerama")
if err != nil {
log.Fatal(err)
}
reply, err := X.XineramaQueryScreens().Reply()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Xinerama number: %d\n", reply.Number)
for i, screen := range reply.ScreenInfo {
fmt.Printf("%d :: X: %d, Y: %d, Width: %d, Height: %d\n",
i, screen.XOrg, screen.YOrg, screen.Width, screen.Height)
}
}

View File

@ -0,0 +1,39 @@
// Example xinerama shows how to query the geometry of all active heads.
package main
import (
"fmt"
"log"
"github.com/BurntSushi/xgb"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
// Initialize the Xinerama extension.
// The appropriate 'Init' function must be run for *every*
// extension before any of its requests can be used.
err = X.XineramaInit()
if err != nil {
log.Fatal(err)
}
// Issue a request to get the screen information.
reply, err := X.XineramaQueryScreens().Reply()
if err != nil {
log.Fatal(err)
}
// reply.Number is the number of active heads, while reply.ScreenInfo
// is a slice of XineramaScreenInfo containing the rectangle geometry
// of each head.
fmt.Printf("Number of heads: %d\n", reply.Number)
for i, screen := range reply.ScreenInfo {
fmt.Printf("%d :: X: %d, Y: %d, Width: %d, Height: %d\n",
i, screen.XOrg, screen.YOrg, screen.Width, screen.Height)
}
}

View File

@ -1,9 +1,3 @@
// Copyright 2009 The XGB Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The XGB package implements the X11 core protocol.
// It is based on XCB: http://xcb.freedesktop.org/
package xgb
import (

View File

@ -45,8 +45,8 @@ func init() {
// Tests
/******************************************************************************/
// TestSynchronousError purposefully causes a BadLength error in an
// InternAtom request, and checks it synchronously.
// TestSynchronousError purposefully causes a BadWindow error in a
// MapWindow request, and checks it synchronously.
func TestSynchronousError(t *testing.T) {
err := X.MapWindowChecked(0).Check() // resource id 0 is always invalid
if err == nil {
@ -205,7 +205,7 @@ func TestWindowEvents(t *testing.T) {
// BenchmarkInternAtomsGood shows how many requests with replies
// *should* be sent and gathered from the server. Namely, send as many
// requests as you can at once, then go back and gather up all the replies.
// More importantly, this approach can exploit parallelism better when
// More importantly, this approach can exploit parallelism when
// GOMAXPROCS > 1.
// Run with `go test -run 'nomatch' -bench '.*' -cpu 1,2,6` if you have
// multiple cores to see the improvement that parallelism brings.

View File

@ -177,7 +177,8 @@ func EventFieldString(c *Context, fields []Field, evName string) {
case *Base:
case *Resource:
case *TypeDef:
default: continue
default:
continue
}
switch field.SrcType() {

View File

@ -107,4 +107,3 @@ func (f *ListField) Write(c *Context, prefix string) {
f.XmlName(), f.Type)
}
}

View File

@ -139,4 +139,3 @@ func (u *Union) WriteListSize(c *Context) {
c.Putln("}")
c.Putln("")
}

View File

@ -38,4 +38,3 @@ func (p *Protocol) Initialize() {
func (p *Protocol) isExt() bool {
return strings.ToLower(p.Name) != "xproto"
}