Compare commits

...

104 Commits

Author SHA1 Message Date
7d51aaa9a4
hpcu: add a selection unifier
So far not supporting large selections.
2018-09-30 18:45:29 +02:00
f198f9f6ac
xgb-selection: add a demo to track X11 selections 2018-09-30 18:45:29 +02:00
106e9b82b8
nexgb: update README 2018-09-30 17:34:27 +02:00
139c50b748
nexgb: update to xcb-proto 1.13 2018-09-30 17:34:27 +02:00
e6e4e94436
nexgb: regenerate from xcb-proto 1.12
To refresh documentation.
2018-09-30 17:34:27 +02:00
7051829581
xgbgen: make request function signatures shorter 2018-09-30 17:34:26 +02:00
3e9ed4eac6
xgbgen: process <doc> elements
Most of XCB documentation now ends up in Go sources,
although the end result is of mixed quality.
2018-09-30 17:34:26 +02:00
0056720d05
xgbgen: generate proper sentences with full stops 2018-09-30 17:34:26 +02:00
28a5112532
nexgb: export {Major,Minor}Version for extensions 2018-09-30 17:34:25 +02:00
1a53c005e9
nexgb: gofmt 2018-09-30 17:34:25 +02:00
07bf9881cc
nexgb: relicense xgbgen to 0BSD
Doing what the fuck I want to.

Included BurntSushi as a copyright holder because
under the Berne convention he totally is.
2018-09-30 17:34:25 +02:00
7aa2601e66
nexgb: rewrite import paths 2018-09-21 08:37:59 +02:00
48fb710f35
nexgb: post-merge fixups and cleanups
AUTHORS, CONTRIBUTORS: still useful, add people from git log, clean up.

LICENSE: Google doesn't really grant you shit anymore.  The EU doesn't
have software patents either, so it doesn't affect me directly.

README, doc.go: erase mentions of unmaintained xgbutil.
2018-09-08 19:51:53 +02:00
3173202cc1
Merge aarzilli/xgb, branch xcb1.12 as nexgb
History has been linearized and rewritten to stay under the new
subdirectory.  I want to make changes incompatible to BurntSushi/xgb.

The history begs for being thrown away entirely because of its quality
and because it doesn't cover the Google period but it is still useful
for copyright tracking.
2018-09-08 16:54:17 +02:00
aarzilli
3906399e7c
Regenerated from xcb-proto 1.12 2018-09-08 16:49:27 +02:00
aarzilli
1c01d79ba1
Changed xgbgen to support xcb-proto 1.12
* Added minimal support for switch fields.
* Changed the way Size is calculated to accomodate for lists inside structs
  (added to randr)
* Removed heuristic to place alignment gaps, they are now explicitly
  described in xml
2018-09-08 16:49:26 +02:00
aarzilli
a102c4056f
Makefile: do not redefine $XPROTO if it's already defined. 2018-09-08 16:49:26 +02:00
Rabin Vincent
1f83ea75a2
Remove stray format char in randr example 2018-09-08 16:49:26 +02:00
Andrew Gallant
1614b58c42
fix structs with field name of 'Bytes'
(it conflict with a method of the same name that is generated for
all such structs)
2018-09-08 16:49:25 +02:00
aarzilli
8d343cfd3a
Handle wildcard values in Xauthority file
Some field values in the Xauthority file have special meanings:
- a value of 65535 in the 'family' field means that the entry will
match a connection of any family on any address
- an empty string in the 'display number' field means that the entry
will match a connection on any display number

This behaviour is documented at:
https://cgit.freedesktop.org/xorg/lib/libXau/tree/AuGetBest.c#n109
2018-09-08 16:49:25 +02:00
aarzilli
becaf43dcb
Read/Write mutex for Extensions map 2018-09-08 16:49:24 +02:00
aarzilli
baff8c1906
sendRequest reads req.buf after closing req.seq
NewRequest says you can avoid reallocating a new buffer for each request by calling it directly.
This is not true if req.seq is closed before req.buf is read.
2018-09-08 16:49:24 +02:00
Andrew Gallant
5451e59f88
Remove panics/fatal errors.
Fixes #9.

This makes shutdown a little more graceful, but there's more
work to be done here. Namely, all outstanding cookies need to
be given the error, otherwise they will block forever.
2018-09-08 16:49:24 +02:00
Bryan Matsuo
dd00568d44
assign a sequence id to the cookie before returning from Conn.NewRequest 2018-09-08 16:49:23 +02:00
fangyuanziti
a1d1151017
add a new api: NewConnNet 2018-09-08 16:49:23 +02:00
Andrew Gallant
ef8155bf17
Formatting and 80 cols. 2018-09-08 16:49:23 +02:00
aarzilli
a548d9d0f7
Fix Issue #21: automatic calculation of alignment padding after lists 2018-09-08 16:49:22 +02:00
snyh
1f8bd79abe
examples:randr use info.Connection to detect whether there has any Randr.Modes 2018-09-08 16:49:22 +02:00
snyh
64c6e6170d
example/randr: check the GetOutputInfo's mode length (virtual head can have zero mode) 2018-09-08 16:49:22 +02:00
Andrew Gallant
76f9adb599
Use consistent extension names. Close #6. 2018-09-08 16:49:21 +02:00
Andrew Gallant
33509dbeb0
It appears that the "Str" type (which is built into the core X protocol)
doesn't specify any padding. So it has to be treated as a special case.

Close #12.
2018-09-08 16:49:21 +02:00
Andrew Gallant
ad9c35a02f
shortcuts for the lazy 2018-09-08 16:49:20 +02:00
Andrew Gallant
2dc9914b5e
When writing, don't pad the length of bytes produced from inner
structs/unions. Each type should take care of its own padding.

Close #14.
2018-09-08 16:49:20 +02:00
Andrew Gallant
f0385db3a7
Regenerate xgb with latest XML descriptions. 2018-09-08 16:49:20 +02:00
Andrew Gallant
5a07ac7108
Fix fmt'd output for union list writing. 2018-09-08 16:49:19 +02:00
Andrew Gallant
efe87cb908
Ignore the "fd" field for the time being (for the shm extension only). 2018-09-08 16:49:19 +02:00
Andrew Gallant
3b4adabee1
The sync and xinput extensions now use the switch field, which
XGB does not currently support. Therefore, I'm removing the sync and
xinput extensions.

This affects issues #11 and #13.
2018-09-08 16:49:19 +02:00
Axel Wagner
2104b8fcdf
Export the logger (again)
Just enabling or disabling logging falls short of the power of
interfaces of go. A user is forced to either accept the logging to
stderr in the format defined by xgb or disable logging alltogether. By
exporting the logger, we can actually let the user decide where to log
in what format.
2018-09-08 16:49:18 +02:00
Andrew Gallant
38b293e74d
Padding on a list is on the length of the list.
There was a bug where padding was being computed on each element of the
list. Close #5.
2018-09-08 16:49:18 +02:00
Andrew Gallant
b06a8ca976
Don't needlessly change source files every time. 2018-09-08 16:49:18 +02:00
Andrew Gallant
0685fb57e1
Update to latest xproto XML. 2018-09-08 16:49:17 +02:00
Andrew Gallant
4b20ffaf4f
Updated to work with new xproto XML files.
Namely, the "doc" element is ignored. Also, I've sorted everything
before output so that diff isn't completely useless.
2018-09-08 16:49:17 +02:00
Andrew Gallant
5d96993ee1
Fixed a nasty bug where closing could cause ReadFull to crash
the program. Close #4.
2018-09-08 16:49:16 +02:00
Andrew Gallant
3658686aee
gofmt 2018-09-08 16:49:16 +02:00
Andrew Gallant
e635de5e1d
Provide access to the X display number in the XGB Conn. 2018-09-08 16:49:16 +02:00
Andrew Gallant (Ocelot)
269a7b9cc6
The hack continues. I've increased the event channel buffer. I know I'm
goofing here.

What I'd personally like to do is just use an "infinite" channel. That
is, push the limit of how many events can be processed to the machine
and not set an artificial limit in XGB. Some day...
2018-09-08 16:49:15 +02:00
Andrew Gallant (Ocelot)
13eff4bec3
Tests were move to xproto package a long time ago. Update README. 2018-09-08 16:49:15 +02:00
Andrew Gallant (Ocelot)
08275ebda8
Doc fixes and stop exporting ReplyChecked and ReplyUnchecked 2018-09-08 16:49:15 +02:00
Andrew Gallant (Ocelot)
a9eae45cb3
Refresh build. Eh. 2018-09-08 16:49:14 +02:00
Andrew Gallant (Ocelot)
22ceab8074
Add rules for installing all packages. 2018-09-08 16:49:14 +02:00
Andrew Gallant (Ocelot)
e9dc18b4f9
Doc fix. 2018-09-08 16:49:14 +02:00
Andrew Gallant (Ocelot)
744c9688cc
Benchmark with gomaxprocs=3 too 2018-09-08 16:49:13 +02:00
Andrew Gallant (Ocelot)
8ee0ea9899
A pathological example for profiling purposes. Use 'make test'. 2018-09-08 16:49:13 +02:00
Andrew Gallant (Ocelot)
e960f4d34e
Add some style guidelines. 2018-09-08 16:49:07 +02:00
Paul Sbarra
cd22f99b20 auth: use encoding.binary 2012-05-28 17:58:32 -05:00
Andrew Gallant (Ocelot)
4ea94ca0fe Bug fix in the generator that was outputting %(MISSING) crud. 2012-05-26 18:24:52 -04:00
Andrew Gallant (Ocelot)
58bb2572c5 Doc touchups. 2012-05-26 18:22:25 -04:00
Andrew Gallant (Ocelot)
acb84171e5 Add new logger type so that it can be shut off. 2012-05-16 23:57:26 -04:00
Andrew Gallant (Ocelot)
424f293671 export logger so it can be disabled 2012-05-16 23:26:19 -04:00
Andrew Gallant (Ocelot)
45a4ee92eb close channels. 2012-05-12 22:17:10 -04:00
Andrew Gallant (Ocelot)
6bdfd1d1b1 A more idiomatic way of trying a non-blocking send on a buffered channel
and falling back to a blocking send inside a goroutine.

This really needs to be fixed. The situation only arises when events are
sent and aren't pulled off the channel using {Wait,Poll}ForEvent.
Namely, if the event send blocks, the entire program will deadlock.

Using a goroutine is not ideal because we lose a guarantee of order:
that events are processed in the order of their arrival. However, it
seems OK as a temporary band-aide for a situation that probably doesn't
arise too often.

What I need to do is implement a dynamic queue. Here is a reference
implementation: http://play.golang.org/p/AiHBsxTFpj
2012-05-12 21:55:57 -04:00
Andrew Gallant (Ocelot)
7abc9c6455 added some docs and removed some extraneous code 2012-05-12 21:44:53 -04:00
Andrew Gallant (Ocelot)
24fef4062a docs 2012-05-12 21:36:31 -04:00
Andrew Gallant (Ocelot)
f77feff864 some docs in the Makefile and removing a prefix that isn't needed. 2012-05-12 21:27:47 -04:00
Andrew Gallant (Ocelot)
aa95801b2d panic when an extension request is issued before an extension has been initialized. but give a nice error message for the happy people. 2012-05-11 23:59:38 -04:00
Andrew Gallant (Ocelot)
29942bf078 panic when an extension request is issued before an extension has been initialized. but give a nice error message for the happy people. 2012-05-11 23:58:52 -04:00
Andrew Gallant (Ocelot)
fb3128ed2a doc updates and a quick usage 2012-05-11 02:01:29 -04:00
Andrew Gallant (Ocelot)
3e6b354493 add a little more docs for errors 2012-05-11 01:58:52 -04:00
Andrew Gallant (Ocelot)
c00652934e better docs 2012-05-10 23:57:34 -04:00
Andrew Gallant (Ocelot)
a3363755cd adding package header comments 2012-05-10 20:06:22 -04:00
Andrew Gallant (Ocelot)
0c50dc6241 a huge commit. splitting extensions into their own sub-packages. 2012-05-10 17:01:42 -04:00
Andrew Gallant (Ocelot)
e239bb3c68 make resource ids their own individual types. last commit before overhaul to sub-packages 2012-05-10 12:47:19 -04:00
Andrew Gallant (Ocelot)
00c6217ca9 update 2012-05-08 23:03:55 -04:00
Andrew Gallant (Ocelot)
5d64f69030 fixed nasty bug that made XGB not thread safe 2012-05-08 23:03:45 -04:00
Andrew Gallant (Ocelot)
62b293c937 use a custom logger so we don't stomp all over the global log configuration 2012-05-08 00:27:00 -04:00
Andrew Gallant (Ocelot)
e256da00b1 gofmt 2012-05-07 21:58:43 -04:00
Andrew Gallant (Ocelot)
13d598e5e7 more clean up. use log instead of fmt.Print to stderr. bug fix for event blocking (a hack fix for now). 2012-05-07 21:58:33 -04:00
Andrew Gallant (Ocelot)
daad54a5e1 important stuff first please 2012-05-07 04:17:11 -04:00
Andrew Gallant (Ocelot)
eed777ebfd more info in readme. link to docs. 2012-05-07 04:13:41 -04:00
Andrew Gallant (Ocelot)
dc48249e1a lots of docs and examples 2012-05-07 04:09:19 -04:00
Andrew Gallant (Ocelot)
3bf376bd66 80 cols 2012-05-07 01:11:41 -04:00
Andrew Gallant (Ocelot)
fd30f1512a added tests 2012-05-07 01:00:45 -04:00
Andrew Gallant (Ocelot)
6d545e723a add more extension cruft. make extension checking more uniform. 2012-05-06 17:48:40 -04:00
Andrew Gallant (Ocelot)
135cee5761 auto-generated Go code ftw. ~65,000 lines. woooheee 2012-05-06 03:06:48 -04:00
Andrew Gallant (Ocelot)
70ebcf5178 build comman for converting XML to Go 2012-05-06 03:06:27 -04:00
Andrew Gallant (Ocelot)
ea30f1a0a7 more bug fixes for the rest of the extensions 2012-05-06 03:06:02 -04:00
Andrew Gallant (Ocelot)
014a0598bf status update 2012-05-06 02:28:32 -04:00
Andrew Gallant (Ocelot)
18b2d420b0 added documentation and did some slight restructuring. it's party time. 2012-05-06 02:21:31 -04:00
Andrew Gallant (Ocelot)
99bc76de54 examples. some should be tests 2012-05-05 18:22:40 -04:00
Andrew Gallant (Ocelot)
369ad0d33e extensions are working! extensions are working! 2012-05-05 18:22:24 -04:00
Andrew Gallant (Ocelot)
b6715f376f fixing bugs related mostly to extension handling 2012-05-05 18:21:48 -04:00
Andrew Gallant (Ocelot)
4a7b05be36 oh momma. a lot of modifications and it appears to be working. w00t. 2012-05-05 02:56:15 -04:00
Andrew Gallant (Ocelot)
c222d406b0 converting to new reply/cookie scheme 2012-05-05 02:55:38 -04:00
Andrew Gallant (Ocelot)
a5d4ad6c9d reworking xgb. cleaned up connection stuff a little. making new xid generation cleaner and use goroutines for it. 2012-05-03 22:47:50 -04:00
Andrew Gallant (Ocelot)
5cdae5950c holy toldeo... things might actually be working 2012-05-03 01:00:01 -04:00
Andrew Gallant (Ocelot)
39507f86ab finally starting on the crescendo: requests and replies. 2012-05-02 01:46:30 -04:00
Andrew Gallant (Ocelot)
f48b6fafc6 float my boat 2012-05-01 01:09:45 -04:00
Andrew Gallant (Ocelot)
83a71d4648 unions, events and errors... oh my 2012-05-01 01:08:03 -04:00
Andrew Gallant (Ocelot)
73154769b3 splitting up go specific code. too much for one file IMO. more progress. almost done with structs. 2012-04-30 16:18:17 -04:00
Andrew Gallant (Ocelot)
2a2d8653b3 gofmt 2012-04-30 02:44:31 -04:00
Andrew Gallant (Ocelot)
05d8ec6a16 complete and total overhaul like i promised. things are much easier to reason about. still not working yet though. 2012-04-30 02:40:55 -04:00
Andrew Gallant (Ocelot)
3115c13e88 last commit before i tear everything down 2012-04-29 14:09:03 -04:00
Andrew Gallant (Ocelot)
6bf0191fb0 progress. still not working. this is incredibly difficult. 2012-04-29 03:38:29 -04:00
Andrew Gallant (Ocelot)
52a21b415a initial commit. not currently in a working state. 2012-04-28 23:25:57 -04:00
74 changed files with 70738 additions and 4 deletions

21
README
View File

@ -173,6 +173,22 @@ The result of testing hid with telnet, OpenSSL s_client, OpenBSD nc, GNU nc and
Ncat is that neither of them can properly shutdown the connection. We need
a good implementation with TLS support.
hpcu -- PRIMARY-CLIPBOARD unifier
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An improved replacement for autocutsel in selection synchronization "mode":
- using only one OS process;
- not polling selections twice a second unnecessarily;
- calling SetSelectionOwner on change even when it already owns the selection,
so that XFIXES SelectionNotify events are delivered;
- not using cut buffers for anything.
Only UTF8_STRING-convertible selections are synchronized.
ht -- terminal emulator
~~~~~~~~~~~~~~~~~~~~~~~
Similar scope to st(1). Clever display of internal padding for better looks.
hib and hic -- IRC bouncer and client
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An IRC client is a good starting application for building a GUI toolkit, as the
@ -241,6 +257,7 @@ most basic features includes a VFS for archives. The editing widget in read-
-only mode could be used for F3. The shell is going to work very simply,
creating a PTY device and running things under TERM=dumb while decoding SGR,
or one could decide to run a new terminal emulator with a different shortcut.
ht could probably also be integrated.
Eventually the number of panels should be arbitrary with proper shortcuts for
working with them. We might also integrate a special view for picture previews,
@ -255,10 +272,6 @@ Indexing and search may be based on a common database, no need to get all fancy:
http://rachbelaid.com/postgres-full-text-search-is-good-enough/
https://www.sqlite.org/fts3.html#full_text_index_queries (FTS4 seems better)
ht -- terminal emulator
~~~~~~~~~~~~~~~~~~~~~~~
Similar scope to st(1). Clever display of internal padding for better looks.
The rest
~~~~~~~~
Currently there are no significant, specific plans about the other applications.

351
hpcu/main.go Normal file
View File

@ -0,0 +1,351 @@
// hpcu unifies the PRIMARY and CLIPBOARD X11 selections for text contents.
package main
import (
"errors"
"janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xfixes"
"janouch.name/haven/nexgb/xproto"
"log"
)
type selectionState struct {
name string // name of the selection
inProgress xproto.Timestamp // timestamp of retrieved selection
buffer []byte // UTF-8 text buffer
incr bool // INCR running
incrFailed bool // INCR failure indicator
owning xproto.Timestamp // since when we own the selection
}
var (
X *nexgb.Conn
setup *xproto.SetupInfo
screen *xproto.ScreenInfo
atomCLIPBOARD xproto.Atom // X11 atom for CLIPBOARD
atomUTF8String xproto.Atom // X11 atom for UTF8_STRING
atomINCR xproto.Atom // X11 atom for INCR
atomTARGETS xproto.Atom // X11 atom for TARGETS
atomTIMESTAMP xproto.Atom // X11 atom for TIMESTAMP
wid xproto.Window // auxiliary window
selections map[xproto.Atom]*selectionState
contents string // current shared selection contents
)
// resolveAtoms resolves a few required atoms that are not in the core protocol.
func resolveAtoms() error {
for _, i := range []struct {
placement *xproto.Atom
name string
}{
{&atomCLIPBOARD, "CLIPBOARD"},
{&atomUTF8String, "UTF8_STRING"},
{&atomINCR, "INCR"},
{&atomTARGETS, "TARGETS"},
{&atomTIMESTAMP, "TIMESTAMP"},
} {
if reply, err := xproto.InternAtom(X,
false, uint16(len(i.name)), i.name).Reply(); err != nil {
return err
} else {
*i.placement = reply.Atom
}
}
return nil
}
// setupAuxiliaryWindow creates a window that receives notifications about
// changed selection contents, and serves
func setupAuxiliaryWindow() error {
var err error
if wid, err = xproto.NewWindowId(X); err != nil {
return err
}
_ = xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root, 0, 0, 1, 1,
0, xproto.WindowClassInputOutput, screen.RootVisual, xproto.CwEventMask,
[]uint32{xproto.EventMaskPropertyChange})
for _, selection := range []xproto.Atom{xproto.AtomPrimary, atomCLIPBOARD} {
_ = xfixes.SelectSelectionInput(X, wid, selection,
xfixes.SelectionEventMaskSetSelectionOwner|
xfixes.SelectionEventMaskSelectionWindowDestroy|
xfixes.SelectionEventMaskSelectionClientClose)
}
return nil
}
// getProperty reads a window property in a memory-efficient manner.
func getProperty(window xproto.Window, property xproto.Atom) (
*xproto.GetPropertyReply, error) {
// xorg-xserver doesn't seem to limit the length of replies or even
// the length of properties in the first place. It only has a huge
// (0xffffffff - sizeof(xChangePropertyReq))/4 limit for ChangeProperty
// requests, even though I can't XChangeProperty more than 0xffffe0
// bytes at a time.
//
// Since the XGB API doesn't let us provide our own buffer for
// value data, let us avoid multiplying the amount of consumed memory in
// pathological cases where properties are several gigabytes in size by
// chunking the requests. This has a cost of losing atomicity, although
// it shouldn't pose a problem except for timeout-caused INCR races.
var result xproto.GetPropertyReply
for result.Length == 0 || result.BytesAfter > 0 {
reply, err := xproto.GetProperty(X, false, /* delete */
window, property, xproto.GetPropertyTypeAny,
uint32(len(result.Value))/4,
uint32(setup.MaximumRequestLength)).Reply()
if err != nil {
return nil, err
}
if result.Length != 0 &&
(reply.Format != result.Format || reply.Type != result.Type) {
return nil, errors.New("property type changed during read")
}
reply.Value = append(result.Value, reply.Value...)
reply.ValueLen += result.ValueLen
result = *reply
}
return &result, nil
}
// appendText tries to append UTF-8 text to the selection state buffer.
func appendText(state *selectionState, prop *xproto.GetPropertyReply) bool {
if prop.Type == atomUTF8String && prop.Format == 8 {
state.buffer = append(state.buffer, prop.Value...)
return true
}
return false
}
func requestOwnership(origin *selectionState, time xproto.Timestamp) {
contents = string(origin.buffer)
for selection, state := range selections {
// We might want to replace the originator as well but it might have
// undesirable effects, mainly with PRIMARY.
if state != origin {
// No need to GetSelectionOwner, XFIXES is more reliable.
_ = xproto.SetSelectionOwner(X, wid, selection, time)
}
}
}
func handleEvent(ev nexgb.Event) {
switch e := ev.(type) {
case xfixes.SelectionNotifyEvent:
state, ok := selections[e.Selection]
if !ok {
break
}
// Ownership request has been granted, don't ask ourselves for data.
if e.Owner == wid {
state.owning = e.SelectionTimestamp
break
}
// This should always be true.
if state.owning < e.SelectionTimestamp {
state.owning = 0
}
// Not checking whether we should give up when our current retrieval
// attempt is interrupted--the timeout mostly solves this.
if e.Owner == xproto.WindowNone {
break
}
// Don't try to process two things at once. Each request gets a few
// seconds to finish, then we move on, hoping that a property race
// doesn't commence. Ideally we'd set up a separate queue for these
// skipped requests and process them later.
if state.inProgress != 0 && e.Timestamp-state.inProgress < 5000 {
break
}
// ICCCM says we should ensure the named property doesn't exist.
_ = xproto.DeleteProperty(X, e.Window, e.Selection)
_ = xproto.ConvertSelection(X, e.Window, e.Selection,
atomUTF8String, e.Selection, e.Timestamp)
state.inProgress = e.Timestamp
state.incr = false
case xproto.SelectionNotifyEvent:
state, ok := selections[e.Selection]
if e.Requestor != wid || !ok || e.Time != state.inProgress {
break
}
state.inProgress = 0
if e.Property == xproto.AtomNone {
break
}
state.buffer = nil
reply, err := getProperty(e.Requestor, e.Property)
if err != nil {
break
}
// When you select a lot of text in VIM, it starts the ICCCM
// INCR mechanism, from which there is no opt-out.
if reply.Type == atomINCR {
state.inProgress = e.Time
state.incr = true
state.incrFailed = false
} else if appendText(state, reply) {
requestOwnership(state, e.Time)
}
_ = xproto.DeleteProperty(X, e.Requestor, e.Property)
case xproto.PropertyNotifyEvent:
state, ok := selections[e.Atom]
if e.Window != wid || e.State != xproto.PropertyNewValue ||
!ok || !state.incr {
break
}
reply, err := getProperty(e.Window, e.Atom)
if err != nil {
state.incrFailed = true
break
}
if !appendText(state, reply) {
// We need to keep deleting the property.
state.incrFailed = true
}
if reply.ValueLen == 0 {
if !state.incrFailed {
requestOwnership(state, e.Time)
}
state.inProgress = 0
state.incr = false
}
_ = xproto.DeleteProperty(X, e.Window, e.Atom)
case xproto.SelectionRequestEvent:
property := e.Property
if property == xproto.AtomNone {
property = e.Target
}
state, ok := selections[e.Selection]
if e.Owner != wid || !ok {
break
}
var (
typ xproto.Atom
format byte
data []byte
)
// XXX: We should also support the MULTIPLE target but it seems to be
// unimportant and largely abandoned today.
targets := []xproto.Atom{atomTARGETS, atomTIMESTAMP, atomUTF8String}
switch e.Target {
case atomTARGETS:
typ = xproto.AtomAtom
format = 32
data = make([]byte, len(targets)*4)
for i, atom := range targets {
nexgb.Put32(data[i*4:], uint32(atom))
}
case atomTIMESTAMP:
typ = xproto.AtomInteger
format = 32
data = make([]byte, 4)
nexgb.Put32(data, uint32(state.owning))
case atomUTF8String:
typ = atomUTF8String
format = 8
data = []byte(contents)
}
response := xproto.SelectionNotifyEvent{
Time: e.Time,
Requestor: e.Requestor,
Selection: e.Selection,
Target: e.Target,
Property: xproto.AtomNone,
}
if typ == 0 || len(data) > int(setup.MaximumRequestLength)*4-64 ||
state.owning == 0 || e.Time < state.owning {
// TODO: Use the INCR mechanism for large data transfers instead
// of refusing the request, or at least use PropModeAppend.
//
// According to the ICCCM we need to set up a queue for concurrent
// (requestor, selection, target, timestamp) requests that differ
// only in the target property, and process them in order. The ICCCM
// has a nice rationale. It seems to only concern INCR. The queue
// might be a map[(who, what, how, when)][](where, data, offset).
//
// NOTE: Even with BigRequests support, it may technically be
// missing on the particular X server, and XGB copies buffers to yet
// another buffer, making very large transfers a very bad idea.
} else if xproto.ChangePropertyChecked(X, xproto.PropModeReplace,
e.Requestor, property, typ, format,
uint32(len(data)/int(format/8)), data).Check() == nil {
response.Property = property
}
_ = xproto.SendEvent(X, false /* propagate */, e.Requestor,
0 /* event mask */, string(response.Bytes()))
}
}
func main() {
var err error
if X, err = nexgb.NewConn(); err != nil {
log.Fatalln(err)
}
if err = xfixes.Init(X); err != nil {
log.Fatalln(err)
}
// Enable the extension.
_ = xfixes.QueryVersion(X, xfixes.MajorVersion, xfixes.MinorVersion)
setup = xproto.Setup(X)
screen = setup.DefaultScreen(X)
if err = resolveAtoms(); err != nil {
log.Fatalln(err)
}
if err = setupAuxiliaryWindow(); err != nil {
log.Fatalln(err)
}
// Now that we have our atoms, we can initialize state.
selections = map[xproto.Atom]*selectionState{
xproto.AtomPrimary: {name: "PRIMARY"},
atomCLIPBOARD: {name: "CLIPBOARD"},
}
for {
ev, xerr := X.WaitForEvent()
if xerr != nil {
log.Printf("Error: %s\n", xerr)
return
}
if ev != nil {
handleEvent(ev)
}
}
}

2
nexgb/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
xgbgen/xgbgen
.*.swp

26
nexgb/AUTHORS Normal file
View File

@ -0,0 +1,26 @@
# This is the official list of neXGB authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS file.
# See the latter for an explanation.
# Names should be added to this file as
# Name or Organization <email address>
# The email address is not required for organizations.
Anthony Martin <ality@pbrane.org>
Firmansyah Adiputra <frm.adiputra@gmail.com>
Google Inc.
Scott Lawrence <bytbox@gmail.com>
Tor Andersson <tor.andersson@gmail.com>
# The names above come from the original x-go-binding by Google.
# The following list pertains to BurntSushi/xgb and janouch.name/haven/nexgb:
Andrew Gallant <jamslam@gmail.com>
Paul Sbarra <Sbarra.Paul@gmail.com>
Axel Wagner <mail@merovius.de>
snyh <snyh@snyh.org>
Alessandro Arzilli <alessandro.arzilli@gmail.com>
fangyuanziti <tiziyuanfang@gmail.com>
Bryan Matsuo <bryan.matsuo@gmail.com>
Rabin Vincent <rabin@rab.in>
Přemysl Janouch <p@janouch.name>

35
nexgb/CONTRIBUTORS Normal file
View File

@ -0,0 +1,35 @@
# This is the official list of people who may have contributed
# code to the neXGB repository.
#
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# When adding J Random Contributor's name to this file,
# either J's name or J's organization's name should be
# added to the AUTHORS file.
# Names should be added to this file like so:
# Name <email address>
Anthony Martin <ality@pbrane.org>
Firmansyah Adiputra <frm.adiputra@gmail.com>
Ian Lance Taylor <iant@golang.org>
Nigel Tao <nigeltao@golang.org>
Robert Griesemer <gri@golang.org>
Russ Cox <rsc@golang.org>
Scott Lawrence <bytbox@gmail.com>
Tor Andersson <tor.andersson@gmail.com>
# The names above come from the original x-go-binding by Google.
# The following list pertains to BurntSushi/xgb and janouch.name/haven/nexgb:
Andrew Gallant <jamslam@gmail.com>
Paul Sbarra <Sbarra.Paul@gmail.com>
Axel Wagner <mail@merovius.de>
snyh <snyh@snyh.org>
Alessandro Arzilli <alessandro.arzilli@gmail.com>
fangyuanziti <tiziyuanfang@gmail.com>
Bryan Matsuo <bryan.matsuo@gmail.com>
Rabin Vincent <rabin@rab.in>
Přemysl Janouch <p@janouch.name>

27
nexgb/LICENSE Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2009 The XGB Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

76
nexgb/Makefile Normal file
View File

@ -0,0 +1,76 @@
# This Makefile is used by the developer. It is not needed in any way to build
# a checkout of the neXGB repository.
# It will be useful, however, if you are hacking at the code generator.
# i.e., after making a change to the code generator, run 'make' in the
# xgb directory. This will build xgbgen and regenerate each sub-package.
# 'make test' will then run any appropriate tests (just tests xproto right now).
# 'make bench' will test a couple of benchmarks.
# 'make build-all' will then try to build each extension. This isn't strictly
# necessary, but it's a good idea to make sure each sub-package is a valid
# Go package.
# My path to the X protocol XML descriptions.
ifndef XPROTO
XPROTO=/usr/share/xcb
endif
# All of the XML files in my /usr/share/xcb directory except XKB, Xinput, SYNC.
# This is intended to build xgbgen and generate Go code for each supported
# extension.
all: build-xgbgen \
bigreq.xml composite.xml damage.xml dpms.xml dri2.xml \
ge.xml glx.xml randr.xml record.xml render.xml res.xml \
screensaver.xml shape.xml shm.xml xc_misc.xml \
xevie.xml xf86dri.xml xf86vidmode.xml xfixes.xml xinerama.xml \
xprint.xml xproto.xml xselinux.xml xtest.xml \
xvmc.xml xv.xml
build-xgbgen:
(cd xgbgen && go build)
# Builds each individual sub-package to make sure its valid Go code.
build-all: bigreq.b composite.b damage.b dpms.b dri2.b ge.b glx.b randr.b \
record.b render.b res.b screensaver.b shape.b shm.b xcmisc.b \
xevie.b xf86dri.b xf86vidmode.b xfixes.b xinerama.b \
xprint.b xproto.b xselinux.b xtest.b xv.b xvmc.b
%.b:
(cd $* ; go build)
# Installs each individual sub-package.
install: bigreq.i composite.i damage.i dpms.i dri2.i ge.i glx.i randr.i \
record.i render.i res.i screensaver.i shape.i shm.i xcmisc.i \
xevie.i xf86dri.i xf86vidmode.i xfixes.i xinerama.i \
xprint.i xproto.i xselinux.i xtest.i xv.i xvmc.i
go install
%.i:
(cd $* ; go install)
# xc_misc is special because it has an underscore.
# There's probably a way to do this better, but Makefiles aren't my strong suit.
xc_misc.xml: build-xgbgen
mkdir -p xcmisc
xgbgen/xgbgen --proto-path $(XPROTO) $(XPROTO)/xc_misc.xml > xcmisc/xcmisc.go
%.xml: build-xgbgen
mkdir -p $*
xgbgen/xgbgen --proto-path $(XPROTO) $(XPROTO)/$*.xml > $*/$*.go
# Just test the xproto core protocol for now.
test:
(cd xproto ; go test)
# Force all xproto benchmarks to run and no tests.
bench:
(cd xproto ; go test -run 'nomatch' -bench '.*' -cpu 1,2,3,6)
# gofmt all non-auto-generated code.
# (auto-generated code is already gofmt'd.)
# Also do a column check (80 cols) after a gofmt.
# But don't check columns on auto-generated code, since I don't care if they
# break 80 cols.
gofmt:
gofmt -w *.go xgbgen/*.go examples/*.go examples/*/*.go xproto/xproto_test.go
colcheck *.go xgbgen/*.go examples/*.go examples/*/*.go xproto/xproto_test.go

49
nexgb/README Normal file
View File

@ -0,0 +1,49 @@
neXGB is a fork of a fork of the X Go Binding, which is a low-level API to
communicate with the core X protocol and many of the X extensions.
It is closely modelled after XCB and xpyb.
It is thread safe and gets immediate improvement from parallelism when
GOMAXPROCS > 1. (See the benchmarks in xproto/xproto_test.go for evidence.)
Please see doc.go for more info.
Quick usage
-----------
go get janouch.name/haven/nexgb
go run $GOPATH/src/janouch.name/haven/nexgb/examples/create-window/main.go
Přemysl Janouch's fork
----------------------
I've merged BurntSushi/xgb into haven as a subdirectory due to a/ inactivity
upstream, and b/ intentions to make incompatible changes meant to be in sync
with the rest of the project.
It's Not Exactly XGB anymore. Notable changes:
- included aarzilli's changes to support xcb-proto 1.12+, updated to 1.13
- improved documentation, using as much as possible from XCB's <doc> elements
- exporting {Major,Minor}Version of extensions for QueryVersion purposes
BurntSushi's fork
-----------------
I've forked the XGB repository from Google Code due to inactivity upstream.
Much of the code has been rewritten in an effort to support thread safety
and multiple extensions. Namely, go_client.py has been thrown away in favor
of an xgbgen package.
The biggest parts that *haven't* been rewritten by me are the connection and
authentication handshakes. They're inherently messy, and there's really no
reason to re-work them. The rest of XGB has been completely rewritten.
I like to release my code under the WTFPL, but since I'm starting with someone
else's work, I'm leaving the original license/contributor/author information
in tact.
I suppose I can legitimately release xgbgen under the WTFPL. To be fair, it is
at least as complex as XGB itself. *sigh*
License
-------
Unless otherwise noted, the neXGB source files are distributed
under the BSD-style license found in the LICENSE file.

29
nexgb/STYLE Normal file
View File

@ -0,0 +1,29 @@
Keep all code to 80 columns or less. We, the maintainers, have plenty of screen
real estate, but enjoy 80 columns so that we can have multiple code windows
open side to side and not be plagued by the ugly auto-wrapping of a text editor.
If you don't oblige us, we will fix any patch you submit to abide 80 columns.
Note that this style restriction does not preclude gofmt, but introduces a few
peculiarities. The first is that gofmt will occasionally add spacing (typically
to comments) that ends up going over 80 columns. Either shorten the comment or
put it on its own line.
The second and more common hiccup is when a function definition extends beyond
80 columns. If one adds line breaks to keep it below 80 columns, gofmt will
indent all subsequent lines in a function definition to the same indentation
level of the function body. This results in a less-than-ideal separation
between function definition and function body. To remedy this, simply add a
line break like so:
func RestackWindowExtra(xu *xgbutil.XUtil, win xproto.Window, stackMode int,
sibling xproto.Window, source int) error {
return ClientEvent(xu, win, "_NET_RESTACK_WINDOW", source, int(sibling),
stackMode)
}
Something similar should also be applied to long 'if' or 'for' conditionals,
although it would probably be preferrable to break up the conditional to
smaller chunks with a few helper variables.

110
nexgb/auth.go Normal file
View File

@ -0,0 +1,110 @@
package nexgb
/*
auth.go contains functions to facilitate the parsing of .Xauthority files.
It is largely unmodified from the original XGB package that I forked.
*/
import (
"encoding/binary"
"errors"
"io"
"os"
)
// readAuthority reads the X authority file for the DISPLAY.
// If hostname == "" or hostname == "localhost",
// then use the system's hostname (as returned by os.Hostname) instead.
func readAuthority(hostname, display string) (
name string, data []byte, err error) {
// b is a scratch buffer to use and should be at least 256 bytes long
// (i.e. it should be able to hold a hostname).
b := make([]byte, 256)
// As per /usr/include/X11/Xauth.h.
const familyLocal = 256
const familyWild = 65535
if len(hostname) == 0 || hostname == "localhost" {
hostname, err = os.Hostname()
if err != nil {
return "", nil, err
}
}
fname := os.Getenv("XAUTHORITY")
if len(fname) == 0 {
home := os.Getenv("HOME")
if len(home) == 0 {
err = errors.New("Xauthority not found: $XAUTHORITY, $HOME not set")
return "", nil, err
}
fname = home + "/.Xauthority"
}
r, err := os.Open(fname)
if err != nil {
return "", nil, err
}
defer r.Close()
for {
var family uint16
if err := binary.Read(r, binary.BigEndian, &family); err != nil {
return "", nil, err
}
addr, err := getString(r, b)
if err != nil {
return "", nil, err
}
disp, err := getString(r, b)
if err != nil {
return "", nil, err
}
name0, err := getString(r, b)
if err != nil {
return "", nil, err
}
data0, err := getBytes(r, b)
if err != nil {
return "", nil, err
}
addrmatch := (family == familyWild) ||
(family == familyLocal && addr == hostname)
dispmatch := (disp == "") || (disp == display)
if addrmatch && dispmatch {
return name0, data0, nil
}
}
panic("unreachable")
}
func getBytes(r io.Reader, b []byte) ([]byte, error) {
var n uint16
if err := binary.Read(r, binary.BigEndian, &n); err != nil {
return nil, err
} else if n > uint16(len(b)) {
return nil, errors.New("bytes too long for buffer")
}
if _, err := io.ReadFull(r, b[0:n]); err != nil {
return nil, err
}
return b[0:n], nil
}
func getString(r io.Reader, b []byte) (string, error) {
b, err := getBytes(r, b)
if err != nil {
return "", err
}
return string(b), nil
}

156
nexgb/bigreq/bigreq.go Normal file
View File

@ -0,0 +1,156 @@
// Package bigreq is the X client API for the BIG-REQUESTS extension.
package bigreq
// This file is automatically generated from bigreq.xml. Edit at your peril!
import (
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xproto"
)
const (
MajorVersion = 0
MinorVersion = 0
)
// Init must be called before using the BIG-REQUESTS extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 12, "BIG-REQUESTS").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named BIG-REQUESTS could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["BIG-REQUESTS"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["BIG-REQUESTS"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["BIG-REQUESTS"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["BIG-REQUESTS"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["BIG-REQUESTS"] = make(map[int]xgb.NewErrorFun)
}
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// EnableCookie is a cookie used only for Enable requests.
type EnableCookie struct {
*xgb.Cookie
}
// Enable sends a checked request.
// If an error occurs, it will be returned with the reply by calling EnableCookie.Reply.
func Enable(c *xgb.Conn) EnableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["BIG-REQUESTS"]; !ok {
panic("Cannot issue request 'Enable' using the uninitialized extension 'BIG-REQUESTS'. bigreq.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(enableRequest(c), cookie)
return EnableCookie{cookie}
}
// EnableUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func EnableUnchecked(c *xgb.Conn) EnableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["BIG-REQUESTS"]; !ok {
panic("Cannot issue request 'Enable' using the uninitialized extension 'BIG-REQUESTS'. bigreq.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(enableRequest(c), cookie)
return EnableCookie{cookie}
}
// EnableReply represents the data returned from a Enable request.
type EnableReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
MaximumRequestLength uint32
}
// Reply blocks and returns the reply data for a Enable request.
func (cook EnableCookie) Reply() (*EnableReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return enableReply(buf), nil
}
// enableReply reads a byte slice into a EnableReply value.
func enableReply(buf []byte) *EnableReply {
v := new(EnableReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.MaximumRequestLength = xgb.Get32(buf[b:])
b += 4
return v
}
// enableRequest writes a Enable request to a byte slice for transfer.
func enableRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["BIG-REQUESTS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}

View File

@ -0,0 +1,717 @@
// Package composite is the X client API for the Composite extension.
package composite
// This file is automatically generated from composite.xml. Edit at your peril!
import (
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xfixes"
"janouch.name/haven/nexgb/xproto"
)
const (
MajorVersion = 0
MinorVersion = 4
)
// Init must be called before using the Composite extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 9, "Composite").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named Composite could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["Composite"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["Composite"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["Composite"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["Composite"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["Composite"] = make(map[int]xgb.NewErrorFun)
}
const (
RedirectAutomatic = 0
RedirectManual = 1
)
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// CreateRegionFromBorderClipCookie is a cookie used only for CreateRegionFromBorderClip requests.
type CreateRegionFromBorderClipCookie struct {
*xgb.Cookie
}
// CreateRegionFromBorderClip sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func CreateRegionFromBorderClip(c *xgb.Conn, Region xfixes.Region, Window xproto.Window) CreateRegionFromBorderClipCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'CreateRegionFromBorderClip' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(createRegionFromBorderClipRequest(c, Region, Window), cookie)
return CreateRegionFromBorderClipCookie{cookie}
}
// CreateRegionFromBorderClipChecked sends a checked request.
// If an error occurs, it can be retrieved using CreateRegionFromBorderClipCookie.Check.
func CreateRegionFromBorderClipChecked(c *xgb.Conn, Region xfixes.Region, Window xproto.Window) CreateRegionFromBorderClipCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'CreateRegionFromBorderClip' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(createRegionFromBorderClipRequest(c, Region, Window), cookie)
return CreateRegionFromBorderClipCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook CreateRegionFromBorderClipCookie) Check() error {
return cook.Cookie.Check()
}
// createRegionFromBorderClipRequest writes a CreateRegionFromBorderClip request to a byte slice for transfer.
func createRegionFromBorderClipRequest(c *xgb.Conn, Region xfixes.Region, Window xproto.Window) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 5 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Region))
b += 4
xgb.Put32(buf[b:], uint32(Window))
b += 4
return buf
}
// GetOverlayWindowCookie is a cookie used only for GetOverlayWindow requests.
type GetOverlayWindowCookie struct {
*xgb.Cookie
}
// GetOverlayWindow sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetOverlayWindowCookie.Reply.
func GetOverlayWindow(c *xgb.Conn, Window xproto.Window) GetOverlayWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'GetOverlayWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getOverlayWindowRequest(c, Window), cookie)
return GetOverlayWindowCookie{cookie}
}
// GetOverlayWindowUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetOverlayWindowUnchecked(c *xgb.Conn, Window xproto.Window) GetOverlayWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'GetOverlayWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getOverlayWindowRequest(c, Window), cookie)
return GetOverlayWindowCookie{cookie}
}
// GetOverlayWindowReply represents the data returned from a GetOverlayWindow request.
type GetOverlayWindowReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
OverlayWin xproto.Window
// padding: 20 bytes
}
// Reply blocks and returns the reply data for a GetOverlayWindow request.
func (cook GetOverlayWindowCookie) Reply() (*GetOverlayWindowReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getOverlayWindowReply(buf), nil
}
// getOverlayWindowReply reads a byte slice into a GetOverlayWindowReply value.
func getOverlayWindowReply(buf []byte) *GetOverlayWindowReply {
v := new(GetOverlayWindowReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.OverlayWin = xproto.Window(xgb.Get32(buf[b:]))
b += 4
b += 20 // padding
return v
}
// getOverlayWindowRequest writes a GetOverlayWindow request to a byte slice for transfer.
func getOverlayWindowRequest(c *xgb.Conn, Window xproto.Window) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 7 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
return buf
}
// NameWindowPixmapCookie is a cookie used only for NameWindowPixmap requests.
type NameWindowPixmapCookie struct {
*xgb.Cookie
}
// NameWindowPixmap sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func NameWindowPixmap(c *xgb.Conn, Window xproto.Window, Pixmap xproto.Pixmap) NameWindowPixmapCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'NameWindowPixmap' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(nameWindowPixmapRequest(c, Window, Pixmap), cookie)
return NameWindowPixmapCookie{cookie}
}
// NameWindowPixmapChecked sends a checked request.
// If an error occurs, it can be retrieved using NameWindowPixmapCookie.Check.
func NameWindowPixmapChecked(c *xgb.Conn, Window xproto.Window, Pixmap xproto.Pixmap) NameWindowPixmapCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'NameWindowPixmap' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(nameWindowPixmapRequest(c, Window, Pixmap), cookie)
return NameWindowPixmapCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook NameWindowPixmapCookie) Check() error {
return cook.Cookie.Check()
}
// nameWindowPixmapRequest writes a NameWindowPixmap request to a byte slice for transfer.
func nameWindowPixmapRequest(c *xgb.Conn, Window xproto.Window, Pixmap xproto.Pixmap) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 6 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
xgb.Put32(buf[b:], uint32(Pixmap))
b += 4
return buf
}
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply.
func QueryVersion(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint32) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint32) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
MajorVersion uint32
MinorVersion uint32
// padding: 16 bytes
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.MajorVersion = xgb.Get32(buf[b:])
b += 4
v.MinorVersion = xgb.Get32(buf[b:])
b += 4
b += 16 // padding
return v
}
// queryVersionRequest writes a QueryVersion request to a byte slice for transfer.
func queryVersionRequest(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint32) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], ClientMajorVersion)
b += 4
xgb.Put32(buf[b:], ClientMinorVersion)
b += 4
return buf
}
// RedirectSubwindowsCookie is a cookie used only for RedirectSubwindows requests.
type RedirectSubwindowsCookie struct {
*xgb.Cookie
}
// RedirectSubwindows sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func RedirectSubwindows(c *xgb.Conn, Window xproto.Window, Update byte) RedirectSubwindowsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'RedirectSubwindows' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(redirectSubwindowsRequest(c, Window, Update), cookie)
return RedirectSubwindowsCookie{cookie}
}
// RedirectSubwindowsChecked sends a checked request.
// If an error occurs, it can be retrieved using RedirectSubwindowsCookie.Check.
func RedirectSubwindowsChecked(c *xgb.Conn, Window xproto.Window, Update byte) RedirectSubwindowsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'RedirectSubwindows' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(redirectSubwindowsRequest(c, Window, Update), cookie)
return RedirectSubwindowsCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook RedirectSubwindowsCookie) Check() error {
return cook.Cookie.Check()
}
// redirectSubwindowsRequest writes a RedirectSubwindows request to a byte slice for transfer.
func redirectSubwindowsRequest(c *xgb.Conn, Window xproto.Window, Update byte) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
buf[b] = Update
b += 1
b += 3 // padding
return buf
}
// RedirectWindowCookie is a cookie used only for RedirectWindow requests.
type RedirectWindowCookie struct {
*xgb.Cookie
}
// RedirectWindow sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func RedirectWindow(c *xgb.Conn, Window xproto.Window, Update byte) RedirectWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'RedirectWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(redirectWindowRequest(c, Window, Update), cookie)
return RedirectWindowCookie{cookie}
}
// RedirectWindowChecked sends a checked request.
// If an error occurs, it can be retrieved using RedirectWindowCookie.Check.
func RedirectWindowChecked(c *xgb.Conn, Window xproto.Window, Update byte) RedirectWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'RedirectWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(redirectWindowRequest(c, Window, Update), cookie)
return RedirectWindowCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook RedirectWindowCookie) Check() error {
return cook.Cookie.Check()
}
// redirectWindowRequest writes a RedirectWindow request to a byte slice for transfer.
func redirectWindowRequest(c *xgb.Conn, Window xproto.Window, Update byte) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
buf[b] = Update
b += 1
b += 3 // padding
return buf
}
// ReleaseOverlayWindowCookie is a cookie used only for ReleaseOverlayWindow requests.
type ReleaseOverlayWindowCookie struct {
*xgb.Cookie
}
// ReleaseOverlayWindow sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func ReleaseOverlayWindow(c *xgb.Conn, Window xproto.Window) ReleaseOverlayWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'ReleaseOverlayWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(releaseOverlayWindowRequest(c, Window), cookie)
return ReleaseOverlayWindowCookie{cookie}
}
// ReleaseOverlayWindowChecked sends a checked request.
// If an error occurs, it can be retrieved using ReleaseOverlayWindowCookie.Check.
func ReleaseOverlayWindowChecked(c *xgb.Conn, Window xproto.Window) ReleaseOverlayWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'ReleaseOverlayWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(releaseOverlayWindowRequest(c, Window), cookie)
return ReleaseOverlayWindowCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook ReleaseOverlayWindowCookie) Check() error {
return cook.Cookie.Check()
}
// releaseOverlayWindowRequest writes a ReleaseOverlayWindow request to a byte slice for transfer.
func releaseOverlayWindowRequest(c *xgb.Conn, Window xproto.Window) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 8 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
return buf
}
// UnredirectSubwindowsCookie is a cookie used only for UnredirectSubwindows requests.
type UnredirectSubwindowsCookie struct {
*xgb.Cookie
}
// UnredirectSubwindows sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func UnredirectSubwindows(c *xgb.Conn, Window xproto.Window, Update byte) UnredirectSubwindowsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'UnredirectSubwindows' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(unredirectSubwindowsRequest(c, Window, Update), cookie)
return UnredirectSubwindowsCookie{cookie}
}
// UnredirectSubwindowsChecked sends a checked request.
// If an error occurs, it can be retrieved using UnredirectSubwindowsCookie.Check.
func UnredirectSubwindowsChecked(c *xgb.Conn, Window xproto.Window, Update byte) UnredirectSubwindowsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'UnredirectSubwindows' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(unredirectSubwindowsRequest(c, Window, Update), cookie)
return UnredirectSubwindowsCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook UnredirectSubwindowsCookie) Check() error {
return cook.Cookie.Check()
}
// unredirectSubwindowsRequest writes a UnredirectSubwindows request to a byte slice for transfer.
func unredirectSubwindowsRequest(c *xgb.Conn, Window xproto.Window, Update byte) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
buf[b] = Update
b += 1
b += 3 // padding
return buf
}
// UnredirectWindowCookie is a cookie used only for UnredirectWindow requests.
type UnredirectWindowCookie struct {
*xgb.Cookie
}
// UnredirectWindow sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func UnredirectWindow(c *xgb.Conn, Window xproto.Window, Update byte) UnredirectWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'UnredirectWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(unredirectWindowRequest(c, Window, Update), cookie)
return UnredirectWindowCookie{cookie}
}
// UnredirectWindowChecked sends a checked request.
// If an error occurs, it can be retrieved using UnredirectWindowCookie.Check.
func UnredirectWindowChecked(c *xgb.Conn, Window xproto.Window, Update byte) UnredirectWindowCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Composite"]; !ok {
panic("Cannot issue request 'UnredirectWindow' using the uninitialized extension 'Composite'. composite.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(unredirectWindowRequest(c, Window, Update), cookie)
return UnredirectWindowCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook UnredirectWindowCookie) Check() error {
return cook.Cookie.Check()
}
// unredirectWindowRequest writes a UnredirectWindow request to a byte slice for transfer.
func unredirectWindowRequest(c *xgb.Conn, Window xproto.Window, Update byte) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Composite"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
buf[b] = Update
b += 1
b += 3 // padding
return buf
}

185
nexgb/conn.go Normal file
View File

@ -0,0 +1,185 @@
package nexgb
/*
conn.go contains a couple of functions that do some real dirty work related
to the initial connection handshake with X.
This code is largely unmodified from the original XGB package that I forked.
*/
import (
"errors"
"fmt"
"io"
"net"
"os"
"strconv"
"strings"
)
// connect connects to the X server given in the 'display' string,
// and does all the necessary setup handshaking.
// If 'display' is empty it will be taken from os.Getenv("DISPLAY").
// Note that you should read and understand the "Connection Setup" of the
// X Protocol Reference Manual before changing this function:
// http://goo.gl/4zGQg
func (c *Conn) connect(display string) error {
err := c.dial(display)
if err != nil {
return err
}
return c.postConnect()
}
// connect init from to the net.Conn,
func (c *Conn) connectNet(netConn net.Conn) error {
c.conn = netConn
return c.postConnect()
}
// do the postConnect action after Conn get it's underly net.Conn
func (c *Conn) postConnect() error {
// Get authentication data
authName, authData, err := readAuthority(c.host, c.display)
noauth := false
if err != nil {
Logger.Printf("Could not get authority info: %v", err)
Logger.Println("Trying connection without authority info...")
authName = ""
authData = []byte{}
noauth = true
}
// Assume that the authentication protocol is "MIT-MAGIC-COOKIE-1".
if !noauth && (authName != "MIT-MAGIC-COOKIE-1" || len(authData) != 16) {
return errors.New("unsupported auth protocol " + authName)
}
buf := make([]byte, 12+Pad(len(authName))+Pad(len(authData)))
buf[0] = 0x6c
buf[1] = 0
Put16(buf[2:], 11)
Put16(buf[4:], 0)
Put16(buf[6:], uint16(len(authName)))
Put16(buf[8:], uint16(len(authData)))
Put16(buf[10:], 0)
copy(buf[12:], []byte(authName))
copy(buf[12+Pad(len(authName)):], authData)
if _, err = c.conn.Write(buf); err != nil {
return err
}
head := make([]byte, 8)
if _, err = io.ReadFull(c.conn, head[0:8]); err != nil {
return err
}
code := head[0]
reasonLen := head[1]
major := Get16(head[2:])
minor := Get16(head[4:])
dataLen := Get16(head[6:])
if major != 11 || minor != 0 {
return fmt.Errorf("x protocol version mismatch: %d.%d", major, minor)
}
buf = make([]byte, int(dataLen)*4+8, int(dataLen)*4+8)
copy(buf, head)
if _, err = io.ReadFull(c.conn, buf[8:]); err != nil {
return err
}
if code == 0 {
reason := buf[8 : 8+reasonLen]
return fmt.Errorf("x protocol authentication refused: %s",
string(reason))
}
// Unfortunately, it isn't really feasible to read the setup bytes here,
// since the code to do so is in a different package.
// Users must call 'xproto.Setup(X)' to get the setup info.
c.SetupBytes = buf
// But also read stuff that we *need* to get started.
c.setupResourceIdBase = Get32(buf[12:])
c.setupResourceIdMask = Get32(buf[16:])
return nil
}
// dial initializes the actual net connection with X.
func (c *Conn) dial(display string) error {
if len(display) == 0 {
display = os.Getenv("DISPLAY")
}
display0 := display
if len(display) == 0 {
return errors.New("empty display string")
}
colonIdx := strings.LastIndex(display, ":")
if colonIdx < 0 {
return errors.New("bad display string: " + display0)
}
var protocol, socket string
if display[0] == '/' {
socket = display[0:colonIdx]
} else {
slashIdx := strings.LastIndex(display, "/")
if slashIdx >= 0 {
protocol = display[0:slashIdx]
c.host = display[slashIdx+1 : colonIdx]
} else {
c.host = display[0:colonIdx]
}
}
display = display[colonIdx+1:]
if len(display) == 0 {
return errors.New("bad display string: " + display0)
}
var scr string
dotIdx := strings.LastIndex(display, ".")
if dotIdx < 0 {
c.display = display[0:]
} else {
c.display = display[0:dotIdx]
scr = display[dotIdx+1:]
}
var err error
c.DisplayNumber, err = strconv.Atoi(c.display)
if err != nil || c.DisplayNumber < 0 {
return errors.New("bad display string: " + display0)
}
if len(scr) != 0 {
c.DefaultScreen, err = strconv.Atoi(scr)
if err != nil {
return errors.New("bad display string: " + display0)
}
}
// Connect to server
if len(socket) != 0 {
c.conn, err = net.Dial("unix", socket+":"+c.display)
} else if len(c.host) != 0 {
if protocol == "" {
protocol = "tcp"
}
c.conn, err = net.Dial(protocol,
c.host+":"+strconv.Itoa(6000+c.DisplayNumber))
} else {
c.conn, err = net.Dial("unix", "/tmp/.X11-unix/X"+c.display)
}
if err != nil {
return errors.New("cannot connect to " + display0 + ": " + err.Error())
}
return nil
}

165
nexgb/cookie.go Normal file
View File

@ -0,0 +1,165 @@
package nexgb
import (
"errors"
)
// Cookie is the internal representation of a cookie, where one is generated
// for *every* request sent by XGB.
// 'cookie' is most frequently used by embedding it into a more specific
// kind of cookie, i.e., 'GetInputFocusCookie'.
type Cookie struct {
conn *Conn
Sequence uint16
replyChan chan []byte
errorChan chan error
pingChan chan bool
}
// NewCookie creates a new cookie with the correct channels initialized
// depending upon the values of 'checked' and 'reply'. Together, there are
// four different kinds of cookies. (See more detailed comments in the
// function for more info on those.)
// Note that a sequence number is not set until just before the request
// corresponding to this cookie is sent over the wire.
//
// Unless you're building requests from bytes by hand, this method should
// not be used.
func (c *Conn) NewCookie(checked, reply bool) *Cookie {
cookie := &Cookie{
conn: c,
Sequence: 0, // we add the sequence id just before sending a request
replyChan: nil,
errorChan: nil,
pingChan: nil,
}
// There are four different kinds of cookies:
// Checked requests with replies get a reply channel and an error channel.
// Unchecked requests with replies get a reply channel and a ping channel.
// Checked requests w/o replies get a ping channel and an error channel.
// Unchecked requests w/o replies get no channels.
// The reply channel is used to send reply data.
// The error channel is used to send error data.
// The ping channel is used when one of the 'reply' or 'error' channels
// is missing but the other is present. The ping channel is way to force
// the blocking to stop and basically say "the error has been received
// in the main event loop" (when the ping channel is coupled with a reply
// channel) or "the request you made that has no reply was successful"
// (when the ping channel is coupled with an error channel).
if checked {
cookie.errorChan = make(chan error, 1)
if !reply {
cookie.pingChan = make(chan bool, 1)
}
}
if reply {
cookie.replyChan = make(chan []byte, 1)
if !checked {
cookie.pingChan = make(chan bool, 1)
}
}
return cookie
}
// Reply detects whether this is a checked or unchecked cookie, and calls
// 'replyChecked' or 'replyUnchecked' appropriately.
//
// Unless you're building requests from bytes by hand, this method should
// not be used.
func (c Cookie) Reply() ([]byte, error) {
// checked
if c.errorChan != nil {
return c.replyChecked()
}
return c.replyUnchecked()
}
// replyChecked waits for a response on either the replyChan or errorChan
// channels. If the former arrives, the bytes are returned with a nil error.
// If the latter arrives, no bytes are returned (nil) and the error received
// is returned.
//
// Unless you're building requests from bytes by hand, this method should
// not be used.
func (c Cookie) replyChecked() ([]byte, error) {
if c.replyChan == nil {
return nil, errors.New("Cannot call 'replyChecked' on a cookie that " +
"is not expecting a *reply* or an error.")
}
if c.errorChan == nil {
return nil, errors.New("Cannot call 'replyChecked' on a cookie that " +
"is not expecting a reply or an *error*.")
}
select {
case reply := <-c.replyChan:
return reply, nil
case err := <-c.errorChan:
return nil, err
}
}
// replyUnchecked waits for a response on either the replyChan or pingChan
// channels. If the former arrives, the bytes are returned with a nil error.
// If the latter arrives, no bytes are returned (nil) and a nil error
// is returned. (In the latter case, the corresponding error can be retrieved
// from (Wait|Poll)ForEvent asynchronously.)
// In all honesty, you *probably* don't want to use this method.
//
// Unless you're building requests from bytes by hand, this method should
// not be used.
func (c Cookie) replyUnchecked() ([]byte, error) {
if c.replyChan == nil {
return nil, errors.New("Cannot call 'replyUnchecked' on a cookie " +
"that is not expecting a *reply*.")
}
select {
case reply := <-c.replyChan:
return reply, nil
case <-c.pingChan:
return nil, nil
}
}
// Check is used for checked requests that have no replies. It is a mechanism
// by which to report "success" or "error" in a synchronous fashion. (Therefore,
// unchecked requests without replies cannot use this method.)
// If the request causes an error, it is sent to this cookie's errorChan.
// If the request was successful, there is no response from the server.
// Thus, pingChan is sent a value when the *next* reply is read.
// If no more replies are being processed, we force a round trip request with
// GetInputFocus.
//
// Unless you're building requests from bytes by hand, this method should
// not be used.
func (c Cookie) Check() error {
if c.replyChan != nil {
return errors.New("Cannot call 'Check' on a cookie that is " +
"expecting a *reply*. Use 'Reply' instead.")
}
if c.errorChan == nil {
return errors.New("Cannot call 'Check' on a cookie that is " +
"not expecting a possible *error*.")
}
// First do a quick non-blocking check to see if we've been pinged.
select {
case err := <-c.errorChan:
return err
case <-c.pingChan:
return nil
default:
}
// Now force a round trip and try again, but block this time.
c.conn.Sync()
select {
case err := <-c.errorChan:
return err
case <-c.pingChan:
return nil
}
}

592
nexgb/damage/damage.go Normal file
View File

@ -0,0 +1,592 @@
// Package damage is the X client API for the DAMAGE extension.
package damage
// This file is automatically generated from damage.xml. Edit at your peril!
import (
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xfixes"
"janouch.name/haven/nexgb/xproto"
)
const (
MajorVersion = 1
MinorVersion = 1
)
// Init must be called before using the DAMAGE extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 6, "DAMAGE").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named DAMAGE could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["DAMAGE"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["DAMAGE"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["DAMAGE"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["DAMAGE"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["DAMAGE"] = make(map[int]xgb.NewErrorFun)
}
// BadBadDamage is the error number for a BadBadDamage.
const BadBadDamage = 0
type BadDamageError struct {
Sequence uint16
NiceName string
}
// BadDamageErrorNew constructs a BadDamageError value that implements xgb.Error from a byte slice.
func BadDamageErrorNew(buf []byte) xgb.Error {
v := BadDamageError{}
v.NiceName = "BadDamage"
b := 1 // skip error determinant
b += 1 // don't read error number
v.Sequence = xgb.Get16(buf[b:])
b += 2
return v
}
// SequenceId returns the sequence id attached to the BadBadDamage error.
// This is mostly used internally.
func (err BadDamageError) SequenceId() uint16 {
return err.Sequence
}
// BadId returns the 'BadValue' number if one exists for the BadBadDamage error. If no bad value exists, 0 is returned.
func (err BadDamageError) BadId() uint32 {
return 0
}
// Error returns a rudimentary string representation of the BadBadDamage error.
func (err BadDamageError) Error() string {
fieldVals := make([]string, 0, 0)
fieldVals = append(fieldVals, "NiceName: "+err.NiceName)
fieldVals = append(fieldVals, xgb.Sprintf("Sequence: %d", err.Sequence))
return "BadBadDamage {" + xgb.StringsJoin(fieldVals, ", ") + "}"
}
func init() {
xgb.NewExtErrorFuncs["DAMAGE"][0] = BadDamageErrorNew
}
type Damage uint32
func NewDamageId(c *xgb.Conn) (Damage, error) {
id, err := c.NewId()
if err != nil {
return 0, err
}
return Damage(id), nil
}
// Notify is the event number for a NotifyEvent.
const Notify = 0
type NotifyEvent struct {
Sequence uint16
Level byte
Drawable xproto.Drawable
Damage Damage
Timestamp xproto.Timestamp
Area xproto.Rectangle
Geometry xproto.Rectangle
}
// NotifyEventNew constructs a NotifyEvent value that implements xgb.Event from a byte slice.
func NotifyEventNew(buf []byte) xgb.Event {
v := NotifyEvent{}
b := 1 // don't read event number
v.Level = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Drawable = xproto.Drawable(xgb.Get32(buf[b:]))
b += 4
v.Damage = Damage(xgb.Get32(buf[b:]))
b += 4
v.Timestamp = xproto.Timestamp(xgb.Get32(buf[b:]))
b += 4
v.Area = xproto.Rectangle{}
b += xproto.RectangleRead(buf[b:], &v.Area)
v.Geometry = xproto.Rectangle{}
b += xproto.RectangleRead(buf[b:], &v.Geometry)
return v
}
// Bytes writes a NotifyEvent value to a byte slice.
func (v NotifyEvent) Bytes() []byte {
buf := make([]byte, 32)
b := 0
// write event number
buf[b] = 0
b += 1
buf[b] = v.Level
b += 1
b += 2 // skip sequence number
xgb.Put32(buf[b:], uint32(v.Drawable))
b += 4
xgb.Put32(buf[b:], uint32(v.Damage))
b += 4
xgb.Put32(buf[b:], uint32(v.Timestamp))
b += 4
{
structBytes := v.Area.Bytes()
copy(buf[b:], structBytes)
b += len(structBytes)
}
{
structBytes := v.Geometry.Bytes()
copy(buf[b:], structBytes)
b += len(structBytes)
}
return buf
}
// SequenceId returns the sequence id attached to the Notify event.
// Events without a sequence number (KeymapNotify) return 0.
// This is mostly used internally.
func (v NotifyEvent) SequenceId() uint16 {
return v.Sequence
}
// String is a rudimentary string representation of NotifyEvent.
func (v NotifyEvent) String() string {
fieldVals := make([]string, 0, 6)
fieldVals = append(fieldVals, xgb.Sprintf("Sequence: %d", v.Sequence))
fieldVals = append(fieldVals, xgb.Sprintf("Level: %d", v.Level))
fieldVals = append(fieldVals, xgb.Sprintf("Drawable: %d", v.Drawable))
fieldVals = append(fieldVals, xgb.Sprintf("Damage: %d", v.Damage))
fieldVals = append(fieldVals, xgb.Sprintf("Timestamp: %d", v.Timestamp))
return "Notify {" + xgb.StringsJoin(fieldVals, ", ") + "}"
}
func init() {
xgb.NewExtEventFuncs["DAMAGE"][0] = NotifyEventNew
}
const (
ReportLevelRawRectangles = 0
ReportLevelDeltaRectangles = 1
ReportLevelBoundingBox = 2
ReportLevelNonEmpty = 3
)
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// AddCookie is a cookie used only for Add requests.
type AddCookie struct {
*xgb.Cookie
}
// Add sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Add(c *xgb.Conn, Drawable xproto.Drawable, Region xfixes.Region) AddCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Add' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(addRequest(c, Drawable, Region), cookie)
return AddCookie{cookie}
}
// AddChecked sends a checked request.
// If an error occurs, it can be retrieved using AddCookie.Check.
func AddChecked(c *xgb.Conn, Drawable xproto.Drawable, Region xfixes.Region) AddCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Add' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(addRequest(c, Drawable, Region), cookie)
return AddCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook AddCookie) Check() error {
return cook.Cookie.Check()
}
// addRequest writes a Add request to a byte slice for transfer.
func addRequest(c *xgb.Conn, Drawable xproto.Drawable, Region xfixes.Region) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DAMAGE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
xgb.Put32(buf[b:], uint32(Region))
b += 4
return buf
}
// CreateCookie is a cookie used only for Create requests.
type CreateCookie struct {
*xgb.Cookie
}
// Create sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Create(c *xgb.Conn, Damage Damage, Drawable xproto.Drawable, Level byte) CreateCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Create' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(createRequest(c, Damage, Drawable, Level), cookie)
return CreateCookie{cookie}
}
// CreateChecked sends a checked request.
// If an error occurs, it can be retrieved using CreateCookie.Check.
func CreateChecked(c *xgb.Conn, Damage Damage, Drawable xproto.Drawable, Level byte) CreateCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Create' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(createRequest(c, Damage, Drawable, Level), cookie)
return CreateCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook CreateCookie) Check() error {
return cook.Cookie.Check()
}
// createRequest writes a Create request to a byte slice for transfer.
func createRequest(c *xgb.Conn, Damage Damage, Drawable xproto.Drawable, Level byte) []byte {
size := 16
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DAMAGE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Damage))
b += 4
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
buf[b] = Level
b += 1
b += 3 // padding
return buf
}
// DestroyCookie is a cookie used only for Destroy requests.
type DestroyCookie struct {
*xgb.Cookie
}
// Destroy sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Destroy(c *xgb.Conn, Damage Damage) DestroyCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Destroy' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(destroyRequest(c, Damage), cookie)
return DestroyCookie{cookie}
}
// DestroyChecked sends a checked request.
// If an error occurs, it can be retrieved using DestroyCookie.Check.
func DestroyChecked(c *xgb.Conn, Damage Damage) DestroyCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Destroy' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(destroyRequest(c, Damage), cookie)
return DestroyCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook DestroyCookie) Check() error {
return cook.Cookie.Check()
}
// destroyRequest writes a Destroy request to a byte slice for transfer.
func destroyRequest(c *xgb.Conn, Damage Damage) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DAMAGE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Damage))
b += 4
return buf
}
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply.
func QueryVersion(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint32) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint32) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
MajorVersion uint32
MinorVersion uint32
// padding: 16 bytes
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.MajorVersion = xgb.Get32(buf[b:])
b += 4
v.MinorVersion = xgb.Get32(buf[b:])
b += 4
b += 16 // padding
return v
}
// queryVersionRequest writes a QueryVersion request to a byte slice for transfer.
func queryVersionRequest(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint32) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DAMAGE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], ClientMajorVersion)
b += 4
xgb.Put32(buf[b:], ClientMinorVersion)
b += 4
return buf
}
// SubtractCookie is a cookie used only for Subtract requests.
type SubtractCookie struct {
*xgb.Cookie
}
// Subtract sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Subtract(c *xgb.Conn, Damage Damage, Repair, Parts xfixes.Region) SubtractCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Subtract' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(subtractRequest(c, Damage, Repair, Parts), cookie)
return SubtractCookie{cookie}
}
// SubtractChecked sends a checked request.
// If an error occurs, it can be retrieved using SubtractCookie.Check.
func SubtractChecked(c *xgb.Conn, Damage Damage, Repair, Parts xfixes.Region) SubtractCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DAMAGE"]; !ok {
panic("Cannot issue request 'Subtract' using the uninitialized extension 'DAMAGE'. damage.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(subtractRequest(c, Damage, Repair, Parts), cookie)
return SubtractCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook SubtractCookie) Check() error {
return cook.Cookie.Check()
}
// subtractRequest writes a Subtract request to a byte slice for transfer.
func subtractRequest(c *xgb.Conn, Damage Damage, Repair, Parts xfixes.Region) []byte {
size := 16
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DAMAGE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Damage))
b += 4
xgb.Put32(buf[b:], uint32(Repair))
b += 4
xgb.Put32(buf[b:], uint32(Parts))
b += 4
return buf
}

144
nexgb/doc.go Normal file
View File

@ -0,0 +1,144 @@
/*
Package nexgb 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 neXGB. 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 neXGB typically fall under the realm of window manager and GUI kit
development, but other applications (like pagers, panels, tilers, etc.) may
also require neXGB.
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"
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xproto"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
fmt.Println(err)
return
}
wid, _ := xproto.NewWindowId(X)
screen := xproto.Setup(X).DefaultScreen(X)
xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root,
0, 0, 500, 500, 0,
xproto.WindowClassInputOutput, screen.RootVisual,
xproto.CwBackPixel | xproto.CwEventMask,
[]uint32{ // values must be in the order defined by the protocol
0xffffffff,
xproto.EventMaskStructureNotify |
xproto.EventMaskKeyPress |
xproto.EventMaskKeyRelease})
xproto.MapWindow(X, wid)
for {
ev, xerr := X.WaitForEvent()
if ev == nil && xerr == nil {
fmt.Println("Both event and error are nil. Exiting...")
return
}
if ev != nil {
fmt.Printf("Event: %s\n", ev)
}
if xerr != nil {
fmt.Printf("Error: %s\n", xerr)
}
}
}
Xinerama Example
This is another small example that shows how to query Xinerama for geometry
information of each active head. Accompanying documentation for this example
can be found in examples/xinerama.
package main
import (
"fmt"
"log"
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xinerama"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
// Initialize the Xinerama extension.
// The appropriate 'Init' function must be run for *every*
// extension before any of its requests can be used.
err = xinerama.Init(X)
if err != nil {
log.Fatal(err)
}
reply, err := xinerama.QueryScreens(X).Reply()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Number of heads: %d\n", reply.Number)
for i, screen := range reply.ScreenInfo {
fmt.Printf("%d :: X: %d, Y: %d, Width: %d, Height: %d\n",
i, screen.XOrg, screen.YOrg, screen.Width, screen.Height)
}
}
Parallelism
neXGB can benefit greatly from parallelism due to its concurrent design. For
evidence of this claim, please see the benchmarks in xproto/xproto_test.go.
Tests
xproto/xproto_test.go contains a number of contrived tests that stress
particular corners of neXGB 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.
neXGB (before BurntSushi's fork) used the same code generator as well, but in my
attempt to add support for more extensions, I found the code generator extremely
difficult to work with. Therefore, I re-wrote the code generator in Go. It can
be found in its own sub-package, xgbgen, of xgb. My design of xgbgen includes a
rough consideration that it could be used for other languages.
What works
I am reasonably confident that the core X protocol is in full working form. I've
also tested the Xinerama and RandR extensions sparingly. Many of the other
existing extensions have Go source generated (and are compilable) and are
included in this package, but I am currently unsure of their status. They
*should* work.
What does not work
XKB is the only extension that intentionally does not work, although I suspect
that GLX also does not work (however, there is Go source code for GLX that
compiles, unlike XKB). I don't currently have any intention of getting XKB
working, due to its complexity and my current mental incapacity to test it.
*/
package nexgb

712
nexgb/dpms/dpms.go Normal file
View File

@ -0,0 +1,712 @@
// Package dpms is the X client API for the DPMS extension.
package dpms
// This file is automatically generated from dpms.xml. Edit at your peril!
import (
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xproto"
)
const (
MajorVersion = 0
MinorVersion = 0
)
// Init must be called before using the DPMS extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 4, "DPMS").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named DPMS could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["DPMS"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["DPMS"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["DPMS"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["DPMS"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["DPMS"] = make(map[int]xgb.NewErrorFun)
}
const (
DPMSModeOn = 0
DPMSModeStandby = 1
DPMSModeSuspend = 2
DPMSModeOff = 3
)
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// CapableCookie is a cookie used only for Capable requests.
type CapableCookie struct {
*xgb.Cookie
}
// Capable sends a checked request.
// If an error occurs, it will be returned with the reply by calling CapableCookie.Reply.
func Capable(c *xgb.Conn) CapableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Capable' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(capableRequest(c), cookie)
return CapableCookie{cookie}
}
// CapableUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func CapableUnchecked(c *xgb.Conn) CapableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Capable' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(capableRequest(c), cookie)
return CapableCookie{cookie}
}
// CapableReply represents the data returned from a Capable request.
type CapableReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
Capable bool
// padding: 23 bytes
}
// Reply blocks and returns the reply data for a Capable request.
func (cook CapableCookie) Reply() (*CapableReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return capableReply(buf), nil
}
// capableReply reads a byte slice into a CapableReply value.
func capableReply(buf []byte) *CapableReply {
v := new(CapableReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
if buf[b] == 1 {
v.Capable = true
} else {
v.Capable = false
}
b += 1
b += 23 // padding
return v
}
// capableRequest writes a Capable request to a byte slice for transfer.
func capableRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// DisableCookie is a cookie used only for Disable requests.
type DisableCookie struct {
*xgb.Cookie
}
// Disable sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Disable(c *xgb.Conn) DisableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Disable' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(disableRequest(c), cookie)
return DisableCookie{cookie}
}
// DisableChecked sends a checked request.
// If an error occurs, it can be retrieved using DisableCookie.Check.
func DisableChecked(c *xgb.Conn) DisableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Disable' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(disableRequest(c), cookie)
return DisableCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook DisableCookie) Check() error {
return cook.Cookie.Check()
}
// disableRequest writes a Disable request to a byte slice for transfer.
func disableRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 5 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// EnableCookie is a cookie used only for Enable requests.
type EnableCookie struct {
*xgb.Cookie
}
// Enable sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Enable(c *xgb.Conn) EnableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Enable' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(enableRequest(c), cookie)
return EnableCookie{cookie}
}
// EnableChecked sends a checked request.
// If an error occurs, it can be retrieved using EnableCookie.Check.
func EnableChecked(c *xgb.Conn) EnableCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Enable' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(enableRequest(c), cookie)
return EnableCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook EnableCookie) Check() error {
return cook.Cookie.Check()
}
// enableRequest writes a Enable request to a byte slice for transfer.
func enableRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// ForceLevelCookie is a cookie used only for ForceLevel requests.
type ForceLevelCookie struct {
*xgb.Cookie
}
// ForceLevel sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func ForceLevel(c *xgb.Conn, PowerLevel uint16) ForceLevelCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'ForceLevel' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(forceLevelRequest(c, PowerLevel), cookie)
return ForceLevelCookie{cookie}
}
// ForceLevelChecked sends a checked request.
// If an error occurs, it can be retrieved using ForceLevelCookie.Check.
func ForceLevelChecked(c *xgb.Conn, PowerLevel uint16) ForceLevelCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'ForceLevel' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(forceLevelRequest(c, PowerLevel), cookie)
return ForceLevelCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook ForceLevelCookie) Check() error {
return cook.Cookie.Check()
}
// forceLevelRequest writes a ForceLevel request to a byte slice for transfer.
func forceLevelRequest(c *xgb.Conn, PowerLevel uint16) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 6 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put16(buf[b:], PowerLevel)
b += 2
return buf
}
// GetTimeoutsCookie is a cookie used only for GetTimeouts requests.
type GetTimeoutsCookie struct {
*xgb.Cookie
}
// GetTimeouts sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetTimeoutsCookie.Reply.
func GetTimeouts(c *xgb.Conn) GetTimeoutsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'GetTimeouts' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getTimeoutsRequest(c), cookie)
return GetTimeoutsCookie{cookie}
}
// GetTimeoutsUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetTimeoutsUnchecked(c *xgb.Conn) GetTimeoutsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'GetTimeouts' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getTimeoutsRequest(c), cookie)
return GetTimeoutsCookie{cookie}
}
// GetTimeoutsReply represents the data returned from a GetTimeouts request.
type GetTimeoutsReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
StandbyTimeout uint16
SuspendTimeout uint16
OffTimeout uint16
// padding: 18 bytes
}
// Reply blocks and returns the reply data for a GetTimeouts request.
func (cook GetTimeoutsCookie) Reply() (*GetTimeoutsReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getTimeoutsReply(buf), nil
}
// getTimeoutsReply reads a byte slice into a GetTimeoutsReply value.
func getTimeoutsReply(buf []byte) *GetTimeoutsReply {
v := new(GetTimeoutsReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.StandbyTimeout = xgb.Get16(buf[b:])
b += 2
v.SuspendTimeout = xgb.Get16(buf[b:])
b += 2
v.OffTimeout = xgb.Get16(buf[b:])
b += 2
b += 18 // padding
return v
}
// getTimeoutsRequest writes a GetTimeouts request to a byte slice for transfer.
func getTimeoutsRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// GetVersionCookie is a cookie used only for GetVersion requests.
type GetVersionCookie struct {
*xgb.Cookie
}
// GetVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetVersionCookie.Reply.
func GetVersion(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint16) GetVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'GetVersion' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return GetVersionCookie{cookie}
}
// GetVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetVersionUnchecked(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint16) GetVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'GetVersion' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return GetVersionCookie{cookie}
}
// GetVersionReply represents the data returned from a GetVersion request.
type GetVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
ServerMajorVersion uint16
ServerMinorVersion uint16
}
// Reply blocks and returns the reply data for a GetVersion request.
func (cook GetVersionCookie) Reply() (*GetVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getVersionReply(buf), nil
}
// getVersionReply reads a byte slice into a GetVersionReply value.
func getVersionReply(buf []byte) *GetVersionReply {
v := new(GetVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.ServerMajorVersion = xgb.Get16(buf[b:])
b += 2
v.ServerMinorVersion = xgb.Get16(buf[b:])
b += 2
return v
}
// getVersionRequest writes a GetVersion request to a byte slice for transfer.
func getVersionRequest(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint16) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put16(buf[b:], ClientMajorVersion)
b += 2
xgb.Put16(buf[b:], ClientMinorVersion)
b += 2
return buf
}
// InfoCookie is a cookie used only for Info requests.
type InfoCookie struct {
*xgb.Cookie
}
// Info sends a checked request.
// If an error occurs, it will be returned with the reply by calling InfoCookie.Reply.
func Info(c *xgb.Conn) InfoCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Info' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(infoRequest(c), cookie)
return InfoCookie{cookie}
}
// InfoUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func InfoUnchecked(c *xgb.Conn) InfoCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'Info' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(infoRequest(c), cookie)
return InfoCookie{cookie}
}
// InfoReply represents the data returned from a Info request.
type InfoReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
PowerLevel uint16
State bool
// padding: 21 bytes
}
// Reply blocks and returns the reply data for a Info request.
func (cook InfoCookie) Reply() (*InfoReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return infoReply(buf), nil
}
// infoReply reads a byte slice into a InfoReply value.
func infoReply(buf []byte) *InfoReply {
v := new(InfoReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.PowerLevel = xgb.Get16(buf[b:])
b += 2
if buf[b] == 1 {
v.State = true
} else {
v.State = false
}
b += 1
b += 21 // padding
return v
}
// infoRequest writes a Info request to a byte slice for transfer.
func infoRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 7 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// SetTimeoutsCookie is a cookie used only for SetTimeouts requests.
type SetTimeoutsCookie struct {
*xgb.Cookie
}
// SetTimeouts sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func SetTimeouts(c *xgb.Conn, StandbyTimeout, SuspendTimeout, OffTimeout uint16) SetTimeoutsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'SetTimeouts' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(setTimeoutsRequest(c, StandbyTimeout, SuspendTimeout, OffTimeout), cookie)
return SetTimeoutsCookie{cookie}
}
// SetTimeoutsChecked sends a checked request.
// If an error occurs, it can be retrieved using SetTimeoutsCookie.Check.
func SetTimeoutsChecked(c *xgb.Conn, StandbyTimeout, SuspendTimeout, OffTimeout uint16) SetTimeoutsCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["DPMS"]; !ok {
panic("Cannot issue request 'SetTimeouts' using the uninitialized extension 'DPMS'. dpms.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(setTimeoutsRequest(c, StandbyTimeout, SuspendTimeout, OffTimeout), cookie)
return SetTimeoutsCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook SetTimeoutsCookie) Check() error {
return cook.Cookie.Check()
}
// setTimeoutsRequest writes a SetTimeouts request to a byte slice for transfer.
func setTimeoutsRequest(c *xgb.Conn, StandbyTimeout, SuspendTimeout, OffTimeout uint16) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["DPMS"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put16(buf[b:], StandbyTimeout)
b += 2
xgb.Put16(buf[b:], SuspendTimeout)
b += 2
xgb.Put16(buf[b:], OffTimeout)
b += 2
return buf
}

1812
nexgb/dri2/dri2.go Normal file

File diff suppressed because it is too large Load Diff

3
nexgb/examples/atoms/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
atoms
*.info
*.prof

View File

@ -0,0 +1,12 @@
atoms:
go build
test: atoms
./atoms --requests 500000 --cpu 1 \
--cpuprof cpu1.prof --memprof mem1.prof > atoms1.info 2>&1
./atoms --requests 500000 --cpu 2 \
--cpuprof cpu2.prof --memprof mem2.prof > atoms2.info 2>&1
./atoms --requests 500000 --cpu 3 \
--cpuprof cpu3.prof --memprof mem3.prof > atoms3.info 2>&1
./atoms --requests 500000 --cpu 6 \
--cpuprof cpu6.prof --memprof mem6.prof > atoms6.info 2>&1

View File

@ -0,0 +1,91 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"runtime"
"runtime/pprof"
"time"
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xproto"
)
var (
flagRequests int
flagGOMAXPROCS int
flagCpuProfName string
flagMemProfName string
)
func init() {
flag.IntVar(&flagRequests, "requests", 100000, "Number of atoms to intern.")
flag.IntVar(&flagGOMAXPROCS, "cpu", 1, "Value of GOMAXPROCS.")
flag.StringVar(&flagCpuProfName, "cpuprof", "cpu.prof",
"Name of CPU profile file.")
flag.StringVar(&flagMemProfName, "memprof", "mem.prof",
"Name of memory profile file.")
flag.Parse()
runtime.GOMAXPROCS(flagGOMAXPROCS)
}
func seqNames(n int) []string {
names := make([]string, n)
for i := range names {
names[i] = fmt.Sprintf("NAME%d", i)
}
return names
}
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
names := seqNames(flagRequests)
fcpu, err := os.Create(flagCpuProfName)
if err != nil {
log.Fatal(err)
}
defer fcpu.Close()
pprof.StartCPUProfile(fcpu)
defer pprof.StopCPUProfile()
start := time.Now()
cookies := make([]xproto.InternAtomCookie, flagRequests)
for i := 0; i < flagRequests; i++ {
cookies[i] = xproto.InternAtom(X,
false, uint16(len(names[i])), names[i])
}
for _, cookie := range cookies {
cookie.Reply()
}
fmt.Printf("Exec time: %s\n\n", time.Since(start))
fmem, err := os.Create(flagMemProfName)
if err != nil {
log.Fatal(err)
}
defer fmem.Close()
pprof.WriteHeapProfile(fmem)
memStats := &runtime.MemStats{}
runtime.ReadMemStats(memStats)
// This isn't right. I'm not sure what's wrong.
lastGcTime := time.Unix(int64(memStats.LastGC/1000000000),
int64(memStats.LastGC-memStats.LastGC/1000000000))
fmt.Printf("Alloc: %d\n", memStats.Alloc)
fmt.Printf("TotalAlloc: %d\n", memStats.TotalAlloc)
fmt.Printf("LastGC: %s\n", lastGcTime)
fmt.Printf("PauseTotalNs: %d\n", memStats.PauseTotalNs)
fmt.Printf("PauseNs: %d\n", memStats.PauseNs)
fmt.Printf("NumGC: %d\n", memStats.NumGC)
}

View File

@ -0,0 +1,107 @@
// 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"
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xproto"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
fmt.Println(err)
return
}
// xproto.Setup retrieves the Setup information from the setup bytes
// gathered during connection.
setup := xproto.Setup(X)
// This is the default screen with all its associated info.
screen := setup.DefaultScreen(X)
// Any time a new resource (i.e., a window, pixmap, graphics context, etc.)
// is created, we need to generate a resource identifier.
// If the resource is a window, then use xproto.NewWindowId. If it's for
// a pixmap, then use xproto.NewPixmapId. And so on...
wid, _ := xproto.NewWindowId(X)
// CreateWindow takes a boatload of parameters.
xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root,
0, 0, 500, 500, 0,
xproto.WindowClassInputOutput, screen.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.
xproto.ChangeWindowAttributes(X, wid,
xproto.CwBackPixel|xproto.CwEventMask,
[]uint32{ // values must be in the order defined by the protocol
0xffffffff,
xproto.EventMaskStructureNotify |
xproto.EventMaskKeyPress |
xproto.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 = xproto.MapWindowChecked(X, 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 = xproto.MapWindowChecked(X, 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://janouch.name/haven/nexgbutil
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,61 @@
// 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"
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xproto"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
// Get the window id of the root window.
setup := xproto.Setup(X)
root := setup.DefaultScreen(X).Root
// Get the atom id (i.e., intern an atom) of "_NET_ACTIVE_WINDOW".
aname := "_NET_ACTIVE_WINDOW"
activeAtom, err := xproto.InternAtom(X, 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 := xproto.InternAtom(X, 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 := xproto.GetProperty(X, false, root, activeAtom.Atom,
xproto.GetPropertyTypeAny, 0, (1<<32)-1).Reply()
if err != nil {
log.Fatal(err)
}
windowId := xproto.Window(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 = xproto.GetProperty(X, false, windowId, nameAtom.Atom,
xproto.GetPropertyTypeAny, 0, (1<<32)-1).Reply()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Active window name: %s\n", string(reply.Value))
}

View File

@ -0,0 +1,92 @@
// 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"
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/randr"
"janouch.name/haven/nexgb/xproto"
)
func main() {
X, _ := xgb.NewConn()
// Every extension must be initialized before it can be used.
err := randr.Init(X)
if err != nil {
log.Fatal(err)
}
// Get the root window on the default screen.
root := xproto.Setup(X).DefaultScreen(X).Root
// Gets the current screen resources. Screen resources contains a list
// of names, crtcs, outputs and modes, among other things.
resources, err := randr.GetScreenResources(X, 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 := randr.GetOutputInfo(X, output, 0).Reply()
if err != nil {
log.Fatal(err)
}
if info.Connection == randr.ConnectionConnected {
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 := randr.GetCrtcInfo(X, 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 = randr.SelectInputChecked(X, root,
randr.NotifyMaskScreenChange|
randr.NotifyMaskCrtcChange|
randr.NotifyMaskOutputChange|
randr.NotifyMaskOutputProperty).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

@ -0,0 +1,40 @@
// Example xinerama shows how to query the geometry of all active heads.
package main
import (
"fmt"
"log"
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xinerama"
)
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
// Initialize the Xinerama extension.
// The appropriate 'Init' function must be run for *every*
// extension before any of its requests can be used.
err = xinerama.Init(X)
if err != nil {
log.Fatal(err)
}
// Issue a request to get the screen information.
reply, err := xinerama.QueryScreens(X).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)
}
}

169
nexgb/ge/ge.go Normal file
View File

@ -0,0 +1,169 @@
// Package ge is the X client API for the Generic Event Extension extension.
package ge
// This file is automatically generated from ge.xml. Edit at your peril!
import (
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xproto"
)
const (
MajorVersion = 1
MinorVersion = 0
)
// Init must be called before using the Generic Event Extension extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 23, "Generic Event Extension").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named Generic Event Extension could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["Generic Event Extension"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["Generic Event Extension"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["Generic Event Extension"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["Generic Event Extension"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["Generic Event Extension"] = make(map[int]xgb.NewErrorFun)
}
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply.
func QueryVersion(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint16) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Generic Event Extension"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'Generic Event Extension'. ge.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint16) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["Generic Event Extension"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'Generic Event Extension'. ge.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
MajorVersion uint16
MinorVersion uint16
// padding: 20 bytes
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.MajorVersion = xgb.Get16(buf[b:])
b += 2
v.MinorVersion = xgb.Get16(buf[b:])
b += 2
b += 20 // padding
return v
}
// queryVersionRequest writes a QueryVersion request to a byte slice for transfer.
func queryVersionRequest(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint16) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["Generic Event Extension"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put16(buf[b:], ClientMajorVersion)
b += 2
xgb.Put16(buf[b:], ClientMinorVersion)
b += 2
return buf
}

10826
nexgb/glx/glx.go Normal file

File diff suppressed because it is too large Load Diff

105
nexgb/help.go Normal file
View File

@ -0,0 +1,105 @@
package nexgb
/*
help.go is meant to contain a rough hodge podge of functions that are mainly
used in the auto generated code. Indeed, several functions here are simple
wrappers so that the sub-packages don't need to be smart about which stdlib
packages to import.
Also, the 'Get..' and 'Put..' functions are used through the core xgb package
too. (xgbutil uses them too.)
*/
import (
"fmt"
"strings"
)
// StringsJoin is an alias to strings.Join. It allows us to avoid having to
// import 'strings' in each of the generated Go files.
func StringsJoin(ss []string, sep string) string {
return strings.Join(ss, sep)
}
// Sprintf is so we don't need to import 'fmt' in the generated Go files.
func Sprintf(format string, v ...interface{}) string {
return fmt.Sprintf(format, v...)
}
// Errorf is just a wrapper for fmt.Errorf. Exists for the same reason
// that 'stringsJoin' and 'sprintf' exists.
func Errorf(format string, v ...interface{}) error {
return fmt.Errorf(format, v...)
}
// Pad a length to align on 4 bytes.
func Pad(n int) int {
return (n + 3) & ^3
}
// PopCount counts the number of bits set in a value list mask.
func PopCount(mask0 int) int {
mask := uint32(mask0)
n := 0
for i := uint32(0); i < 32; i++ {
if mask&(1<<i) != 0 {
n++
}
}
return n
}
// Put16 takes a 16 bit integer and copies it into a byte slice.
func Put16(buf []byte, v uint16) {
buf[0] = byte(v)
buf[1] = byte(v >> 8)
}
// Put32 takes a 32 bit integer and copies it into a byte slice.
func Put32(buf []byte, v uint32) {
buf[0] = byte(v)
buf[1] = byte(v >> 8)
buf[2] = byte(v >> 16)
buf[3] = byte(v >> 24)
}
// Put64 takes a 64 bit integer and copies it into a byte slice.
func Put64(buf []byte, v uint64) {
buf[0] = byte(v)
buf[1] = byte(v >> 8)
buf[2] = byte(v >> 16)
buf[3] = byte(v >> 24)
buf[4] = byte(v >> 32)
buf[5] = byte(v >> 40)
buf[6] = byte(v >> 48)
buf[7] = byte(v >> 56)
}
// Get16 constructs a 16 bit integer from the beginning of a byte slice.
func Get16(buf []byte) uint16 {
v := uint16(buf[0])
v |= uint16(buf[1]) << 8
return v
}
// Get32 constructs a 32 bit integer from the beginning of a byte slice.
func Get32(buf []byte) uint32 {
v := uint32(buf[0])
v |= uint32(buf[1]) << 8
v |= uint32(buf[2]) << 16
v |= uint32(buf[3]) << 24
return v
}
// Get64 constructs a 64 bit integer from the beginning of a byte slice.
func Get64(buf []byte) uint64 {
v := uint64(buf[0])
v |= uint64(buf[1]) << 8
v |= uint64(buf[2]) << 16
v |= uint64(buf[3]) << 24
v |= uint64(buf[4]) << 32
v |= uint64(buf[5]) << 40
v |= uint64(buf[6]) << 48
v |= uint64(buf[7]) << 56
return v
}

6566
nexgb/randr/randr.go Normal file

File diff suppressed because it is too large Load Diff

1201
nexgb/record/record.go Normal file

File diff suppressed because it is too large Load Diff

4003
nexgb/render/render.go Normal file

File diff suppressed because it is too large Load Diff

1109
nexgb/res/res.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,695 @@
// Package screensaver is the X client API for the MIT-SCREEN-SAVER extension.
package screensaver
// This file is automatically generated from screensaver.xml. Edit at your peril!
import (
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xproto"
)
const (
MajorVersion = 1
MinorVersion = 1
)
// Init must be called before using the MIT-SCREEN-SAVER extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 16, "MIT-SCREEN-SAVER").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named MIT-SCREEN-SAVER could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["MIT-SCREEN-SAVER"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["MIT-SCREEN-SAVER"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["MIT-SCREEN-SAVER"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["MIT-SCREEN-SAVER"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["MIT-SCREEN-SAVER"] = make(map[int]xgb.NewErrorFun)
}
const (
EventNotifyMask = 1
EventCycleMask = 2
)
const (
KindBlanked = 0
KindInternal = 1
KindExternal = 2
)
// Notify is the event number for a NotifyEvent.
const Notify = 0
type NotifyEvent struct {
Sequence uint16
State byte
Time xproto.Timestamp
Root xproto.Window
Window xproto.Window
Kind byte
Forced bool
// padding: 14 bytes
}
// NotifyEventNew constructs a NotifyEvent value that implements xgb.Event from a byte slice.
func NotifyEventNew(buf []byte) xgb.Event {
v := NotifyEvent{}
b := 1 // don't read event number
v.State = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Time = xproto.Timestamp(xgb.Get32(buf[b:]))
b += 4
v.Root = xproto.Window(xgb.Get32(buf[b:]))
b += 4
v.Window = xproto.Window(xgb.Get32(buf[b:]))
b += 4
v.Kind = buf[b]
b += 1
if buf[b] == 1 {
v.Forced = true
} else {
v.Forced = false
}
b += 1
b += 14 // padding
return v
}
// Bytes writes a NotifyEvent value to a byte slice.
func (v NotifyEvent) Bytes() []byte {
buf := make([]byte, 32)
b := 0
// write event number
buf[b] = 0
b += 1
buf[b] = v.State
b += 1
b += 2 // skip sequence number
xgb.Put32(buf[b:], uint32(v.Time))
b += 4
xgb.Put32(buf[b:], uint32(v.Root))
b += 4
xgb.Put32(buf[b:], uint32(v.Window))
b += 4
buf[b] = v.Kind
b += 1
if v.Forced {
buf[b] = 1
} else {
buf[b] = 0
}
b += 1
b += 14 // padding
return buf
}
// SequenceId returns the sequence id attached to the Notify event.
// Events without a sequence number (KeymapNotify) return 0.
// This is mostly used internally.
func (v NotifyEvent) SequenceId() uint16 {
return v.Sequence
}
// String is a rudimentary string representation of NotifyEvent.
func (v NotifyEvent) String() string {
fieldVals := make([]string, 0, 7)
fieldVals = append(fieldVals, xgb.Sprintf("Sequence: %d", v.Sequence))
fieldVals = append(fieldVals, xgb.Sprintf("State: %d", v.State))
fieldVals = append(fieldVals, xgb.Sprintf("Time: %d", v.Time))
fieldVals = append(fieldVals, xgb.Sprintf("Root: %d", v.Root))
fieldVals = append(fieldVals, xgb.Sprintf("Window: %d", v.Window))
fieldVals = append(fieldVals, xgb.Sprintf("Kind: %d", v.Kind))
fieldVals = append(fieldVals, xgb.Sprintf("Forced: %t", v.Forced))
return "Notify {" + xgb.StringsJoin(fieldVals, ", ") + "}"
}
func init() {
xgb.NewExtEventFuncs["MIT-SCREEN-SAVER"][0] = NotifyEventNew
}
const (
StateOff = 0
StateOn = 1
StateCycle = 2
StateDisabled = 3
)
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// QueryInfoCookie is a cookie used only for QueryInfo requests.
type QueryInfoCookie struct {
*xgb.Cookie
}
// QueryInfo sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryInfoCookie.Reply.
func QueryInfo(c *xgb.Conn, Drawable xproto.Drawable) QueryInfoCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'QueryInfo' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryInfoRequest(c, Drawable), cookie)
return QueryInfoCookie{cookie}
}
// QueryInfoUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryInfoUnchecked(c *xgb.Conn, Drawable xproto.Drawable) QueryInfoCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'QueryInfo' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryInfoRequest(c, Drawable), cookie)
return QueryInfoCookie{cookie}
}
// QueryInfoReply represents the data returned from a QueryInfo request.
type QueryInfoReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
State byte
SaverWindow xproto.Window
MsUntilServer uint32
MsSinceUserInput uint32
EventMask uint32
Kind byte
// padding: 7 bytes
}
// Reply blocks and returns the reply data for a QueryInfo request.
func (cook QueryInfoCookie) Reply() (*QueryInfoReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryInfoReply(buf), nil
}
// queryInfoReply reads a byte slice into a QueryInfoReply value.
func queryInfoReply(buf []byte) *QueryInfoReply {
v := new(QueryInfoReply)
b := 1 // skip reply determinant
v.State = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.SaverWindow = xproto.Window(xgb.Get32(buf[b:]))
b += 4
v.MsUntilServer = xgb.Get32(buf[b:])
b += 4
v.MsSinceUserInput = xgb.Get32(buf[b:])
b += 4
v.EventMask = xgb.Get32(buf[b:])
b += 4
v.Kind = buf[b]
b += 1
b += 7 // padding
return v
}
// queryInfoRequest writes a QueryInfo request to a byte slice for transfer.
func queryInfoRequest(c *xgb.Conn, Drawable xproto.Drawable) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SCREEN-SAVER"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
return buf
}
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply.
func QueryVersion(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion byte) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion byte) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
ServerMajorVersion uint16
ServerMinorVersion uint16
// padding: 20 bytes
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.ServerMajorVersion = xgb.Get16(buf[b:])
b += 2
v.ServerMinorVersion = xgb.Get16(buf[b:])
b += 2
b += 20 // padding
return v
}
// queryVersionRequest writes a QueryVersion request to a byte slice for transfer.
func queryVersionRequest(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion byte) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SCREEN-SAVER"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
buf[b] = ClientMajorVersion
b += 1
buf[b] = ClientMinorVersion
b += 1
b += 2 // padding
return buf
}
// SelectInputCookie is a cookie used only for SelectInput requests.
type SelectInputCookie struct {
*xgb.Cookie
}
// SelectInput sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func SelectInput(c *xgb.Conn, Drawable xproto.Drawable, EventMask uint32) SelectInputCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'SelectInput' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(selectInputRequest(c, Drawable, EventMask), cookie)
return SelectInputCookie{cookie}
}
// SelectInputChecked sends a checked request.
// If an error occurs, it can be retrieved using SelectInputCookie.Check.
func SelectInputChecked(c *xgb.Conn, Drawable xproto.Drawable, EventMask uint32) SelectInputCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'SelectInput' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(selectInputRequest(c, Drawable, EventMask), cookie)
return SelectInputCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook SelectInputCookie) Check() error {
return cook.Cookie.Check()
}
// selectInputRequest writes a SelectInput request to a byte slice for transfer.
func selectInputRequest(c *xgb.Conn, Drawable xproto.Drawable, EventMask uint32) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SCREEN-SAVER"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
xgb.Put32(buf[b:], EventMask)
b += 4
return buf
}
// SetAttributesCookie is a cookie used only for SetAttributes requests.
type SetAttributesCookie struct {
*xgb.Cookie
}
// SetAttributes sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func SetAttributes(c *xgb.Conn, Drawable xproto.Drawable, X, Y int16, Width, Height, BorderWidth uint16, Class, Depth byte, Visual xproto.Visualid, ValueMask uint32, ValueList []uint32) SetAttributesCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'SetAttributes' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(setAttributesRequest(c, Drawable, X, Y, Width, Height, BorderWidth, Class, Depth, Visual, ValueMask, ValueList), cookie)
return SetAttributesCookie{cookie}
}
// SetAttributesChecked sends a checked request.
// If an error occurs, it can be retrieved using SetAttributesCookie.Check.
func SetAttributesChecked(c *xgb.Conn, Drawable xproto.Drawable, X, Y int16, Width, Height, BorderWidth uint16, Class, Depth byte, Visual xproto.Visualid, ValueMask uint32, ValueList []uint32) SetAttributesCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'SetAttributes' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(setAttributesRequest(c, Drawable, X, Y, Width, Height, BorderWidth, Class, Depth, Visual, ValueMask, ValueList), cookie)
return SetAttributesCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook SetAttributesCookie) Check() error {
return cook.Cookie.Check()
}
// setAttributesRequest writes a SetAttributes request to a byte slice for transfer.
func setAttributesRequest(c *xgb.Conn, Drawable xproto.Drawable, X, Y int16, Width, Height, BorderWidth uint16, Class, Depth byte, Visual xproto.Visualid, ValueMask uint32, ValueList []uint32) []byte {
size := xgb.Pad((28 + xgb.Pad((4 * xgb.PopCount(int(ValueMask))))))
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SCREEN-SAVER"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
xgb.Put16(buf[b:], uint16(X))
b += 2
xgb.Put16(buf[b:], uint16(Y))
b += 2
xgb.Put16(buf[b:], Width)
b += 2
xgb.Put16(buf[b:], Height)
b += 2
xgb.Put16(buf[b:], BorderWidth)
b += 2
buf[b] = Class
b += 1
buf[b] = Depth
b += 1
xgb.Put32(buf[b:], uint32(Visual))
b += 4
xgb.Put32(buf[b:], ValueMask)
b += 4
for i := 0; i < xgb.PopCount(int(ValueMask)); i++ {
xgb.Put32(buf[b:], ValueList[i])
b += 4
}
b = xgb.Pad(b)
return buf
}
// SuspendCookie is a cookie used only for Suspend requests.
type SuspendCookie struct {
*xgb.Cookie
}
// Suspend sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Suspend(c *xgb.Conn, Suspend bool) SuspendCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'Suspend' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(suspendRequest(c, Suspend), cookie)
return SuspendCookie{cookie}
}
// SuspendChecked sends a checked request.
// If an error occurs, it can be retrieved using SuspendCookie.Check.
func SuspendChecked(c *xgb.Conn, Suspend bool) SuspendCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'Suspend' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(suspendRequest(c, Suspend), cookie)
return SuspendCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook SuspendCookie) Check() error {
return cook.Cookie.Check()
}
// suspendRequest writes a Suspend request to a byte slice for transfer.
func suspendRequest(c *xgb.Conn, Suspend bool) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SCREEN-SAVER"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 5 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
if Suspend {
buf[b] = 1
} else {
buf[b] = 0
}
b += 1
b += 3 // padding
return buf
}
// UnsetAttributesCookie is a cookie used only for UnsetAttributes requests.
type UnsetAttributesCookie struct {
*xgb.Cookie
}
// UnsetAttributes sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func UnsetAttributes(c *xgb.Conn, Drawable xproto.Drawable) UnsetAttributesCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'UnsetAttributes' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(unsetAttributesRequest(c, Drawable), cookie)
return UnsetAttributesCookie{cookie}
}
// UnsetAttributesChecked sends a checked request.
// If an error occurs, it can be retrieved using UnsetAttributesCookie.Check.
func UnsetAttributesChecked(c *xgb.Conn, Drawable xproto.Drawable) UnsetAttributesCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SCREEN-SAVER"]; !ok {
panic("Cannot issue request 'UnsetAttributes' using the uninitialized extension 'MIT-SCREEN-SAVER'. screensaver.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(unsetAttributesRequest(c, Drawable), cookie)
return UnsetAttributesCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook UnsetAttributesCookie) Check() error {
return cook.Cookie.Check()
}
// unsetAttributesRequest writes a UnsetAttributes request to a byte slice for transfer.
func unsetAttributesRequest(c *xgb.Conn, Drawable xproto.Drawable) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SCREEN-SAVER"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
return buf
}

1021
nexgb/shape/shape.go Normal file

File diff suppressed because it is too large Load Diff

942
nexgb/shm/shm.go Normal file
View File

@ -0,0 +1,942 @@
// Package shm is the X client API for the MIT-SHM extension.
package shm
// This file is automatically generated from shm.xml. Edit at your peril!
import (
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xproto"
)
const (
MajorVersion = 1
MinorVersion = 2
)
// Init must be called before using the MIT-SHM extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 7, "MIT-SHM").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named MIT-SHM could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["MIT-SHM"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["MIT-SHM"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["MIT-SHM"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["MIT-SHM"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["MIT-SHM"] = make(map[int]xgb.NewErrorFun)
}
// BadBadSeg is the error number for a BadBadSeg.
const BadBadSeg = 0
type BadSegError xproto.ValueError
// BadSegErrorNew constructs a BadSegError value that implements xgb.Error from a byte slice.
func BadSegErrorNew(buf []byte) xgb.Error {
v := BadSegError(xproto.ValueErrorNew(buf).(xproto.ValueError))
v.NiceName = "BadSeg"
return v
}
// SequenceId returns the sequence id attached to the BadBadSeg error.
// This is mostly used internally.
func (err BadSegError) SequenceId() uint16 {
return err.Sequence
}
// BadId returns the 'BadValue' number if one exists for the BadBadSeg error. If no bad value exists, 0 is returned.
func (err BadSegError) BadId() uint32 {
return 0
}
// Error returns a rudimentary string representation of the BadBadSeg error.
func (err BadSegError) Error() string {
fieldVals := make([]string, 0, 4)
fieldVals = append(fieldVals, "NiceName: "+err.NiceName)
fieldVals = append(fieldVals, xgb.Sprintf("Sequence: %d", err.Sequence))
fieldVals = append(fieldVals, xgb.Sprintf("BadValue: %d", err.BadValue))
fieldVals = append(fieldVals, xgb.Sprintf("MinorOpcode: %d", err.MinorOpcode))
fieldVals = append(fieldVals, xgb.Sprintf("MajorOpcode: %d", err.MajorOpcode))
return "BadBadSeg {" + xgb.StringsJoin(fieldVals, ", ") + "}"
}
func init() {
xgb.NewExtErrorFuncs["MIT-SHM"][0] = BadSegErrorNew
}
// Completion is the event number for a CompletionEvent.
const Completion = 0
type CompletionEvent struct {
Sequence uint16
// padding: 1 bytes
Drawable xproto.Drawable
MinorEvent uint16
MajorEvent byte
// padding: 1 bytes
Shmseg Seg
Offset uint32
}
// CompletionEventNew constructs a CompletionEvent value that implements xgb.Event from a byte slice.
func CompletionEventNew(buf []byte) xgb.Event {
v := CompletionEvent{}
b := 1 // don't read event number
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Drawable = xproto.Drawable(xgb.Get32(buf[b:]))
b += 4
v.MinorEvent = xgb.Get16(buf[b:])
b += 2
v.MajorEvent = buf[b]
b += 1
b += 1 // padding
v.Shmseg = Seg(xgb.Get32(buf[b:]))
b += 4
v.Offset = xgb.Get32(buf[b:])
b += 4
return v
}
// Bytes writes a CompletionEvent value to a byte slice.
func (v CompletionEvent) Bytes() []byte {
buf := make([]byte, 32)
b := 0
// write event number
buf[b] = 0
b += 1
b += 1 // padding
b += 2 // skip sequence number
xgb.Put32(buf[b:], uint32(v.Drawable))
b += 4
xgb.Put16(buf[b:], v.MinorEvent)
b += 2
buf[b] = v.MajorEvent
b += 1
b += 1 // padding
xgb.Put32(buf[b:], uint32(v.Shmseg))
b += 4
xgb.Put32(buf[b:], v.Offset)
b += 4
return buf
}
// SequenceId returns the sequence id attached to the Completion event.
// Events without a sequence number (KeymapNotify) return 0.
// This is mostly used internally.
func (v CompletionEvent) SequenceId() uint16 {
return v.Sequence
}
// String is a rudimentary string representation of CompletionEvent.
func (v CompletionEvent) String() string {
fieldVals := make([]string, 0, 7)
fieldVals = append(fieldVals, xgb.Sprintf("Sequence: %d", v.Sequence))
fieldVals = append(fieldVals, xgb.Sprintf("Drawable: %d", v.Drawable))
fieldVals = append(fieldVals, xgb.Sprintf("MinorEvent: %d", v.MinorEvent))
fieldVals = append(fieldVals, xgb.Sprintf("MajorEvent: %d", v.MajorEvent))
fieldVals = append(fieldVals, xgb.Sprintf("Shmseg: %d", v.Shmseg))
fieldVals = append(fieldVals, xgb.Sprintf("Offset: %d", v.Offset))
return "Completion {" + xgb.StringsJoin(fieldVals, ", ") + "}"
}
func init() {
xgb.NewExtEventFuncs["MIT-SHM"][0] = CompletionEventNew
}
type Seg uint32
func NewSegId(c *xgb.Conn) (Seg, error) {
id, err := c.NewId()
if err != nil {
return 0, err
}
return Seg(id), nil
}
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// AttachCookie is a cookie used only for Attach requests.
type AttachCookie struct {
*xgb.Cookie
}
// Attach sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Attach(c *xgb.Conn, Shmseg Seg, Shmid uint32, ReadOnly bool) AttachCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'Attach' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(attachRequest(c, Shmseg, Shmid, ReadOnly), cookie)
return AttachCookie{cookie}
}
// AttachChecked sends a checked request.
// If an error occurs, it can be retrieved using AttachCookie.Check.
func AttachChecked(c *xgb.Conn, Shmseg Seg, Shmid uint32, ReadOnly bool) AttachCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'Attach' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(attachRequest(c, Shmseg, Shmid, ReadOnly), cookie)
return AttachCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook AttachCookie) Check() error {
return cook.Cookie.Check()
}
// attachRequest writes a Attach request to a byte slice for transfer.
func attachRequest(c *xgb.Conn, Shmseg Seg, Shmid uint32, ReadOnly bool) []byte {
size := 16
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
xgb.Put32(buf[b:], Shmid)
b += 4
if ReadOnly {
buf[b] = 1
} else {
buf[b] = 0
}
b += 1
b += 3 // padding
return buf
}
// AttachFdCookie is a cookie used only for AttachFd requests.
type AttachFdCookie struct {
*xgb.Cookie
}
// AttachFd sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func AttachFd(c *xgb.Conn, Shmseg Seg, ReadOnly bool) AttachFdCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'AttachFd' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(attachFdRequest(c, Shmseg, ReadOnly), cookie)
return AttachFdCookie{cookie}
}
// AttachFdChecked sends a checked request.
// If an error occurs, it can be retrieved using AttachFdCookie.Check.
func AttachFdChecked(c *xgb.Conn, Shmseg Seg, ReadOnly bool) AttachFdCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'AttachFd' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(attachFdRequest(c, Shmseg, ReadOnly), cookie)
return AttachFdCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook AttachFdCookie) Check() error {
return cook.Cookie.Check()
}
// attachFdRequest writes a AttachFd request to a byte slice for transfer.
func attachFdRequest(c *xgb.Conn, Shmseg Seg, ReadOnly bool) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 6 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
if ReadOnly {
buf[b] = 1
} else {
buf[b] = 0
}
b += 1
b += 3 // padding
return buf
}
// CreatePixmapCookie is a cookie used only for CreatePixmap requests.
type CreatePixmapCookie struct {
*xgb.Cookie
}
// CreatePixmap sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func CreatePixmap(c *xgb.Conn, Pid xproto.Pixmap, Drawable xproto.Drawable, Width, Height uint16, Depth byte, Shmseg Seg, Offset uint32) CreatePixmapCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'CreatePixmap' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(createPixmapRequest(c, Pid, Drawable, Width, Height, Depth, Shmseg, Offset), cookie)
return CreatePixmapCookie{cookie}
}
// CreatePixmapChecked sends a checked request.
// If an error occurs, it can be retrieved using CreatePixmapCookie.Check.
func CreatePixmapChecked(c *xgb.Conn, Pid xproto.Pixmap, Drawable xproto.Drawable, Width, Height uint16, Depth byte, Shmseg Seg, Offset uint32) CreatePixmapCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'CreatePixmap' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(createPixmapRequest(c, Pid, Drawable, Width, Height, Depth, Shmseg, Offset), cookie)
return CreatePixmapCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook CreatePixmapCookie) Check() error {
return cook.Cookie.Check()
}
// createPixmapRequest writes a CreatePixmap request to a byte slice for transfer.
func createPixmapRequest(c *xgb.Conn, Pid xproto.Pixmap, Drawable xproto.Drawable, Width, Height uint16, Depth byte, Shmseg Seg, Offset uint32) []byte {
size := 28
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 5 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Pid))
b += 4
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
xgb.Put16(buf[b:], Width)
b += 2
xgb.Put16(buf[b:], Height)
b += 2
buf[b] = Depth
b += 1
b += 3 // padding
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
xgb.Put32(buf[b:], Offset)
b += 4
return buf
}
// CreateSegmentCookie is a cookie used only for CreateSegment requests.
type CreateSegmentCookie struct {
*xgb.Cookie
}
// CreateSegment sends a checked request.
// If an error occurs, it will be returned with the reply by calling CreateSegmentCookie.Reply.
func CreateSegment(c *xgb.Conn, Shmseg Seg, Size uint32, ReadOnly bool) CreateSegmentCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'CreateSegment' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(createSegmentRequest(c, Shmseg, Size, ReadOnly), cookie)
return CreateSegmentCookie{cookie}
}
// CreateSegmentUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func CreateSegmentUnchecked(c *xgb.Conn, Shmseg Seg, Size uint32, ReadOnly bool) CreateSegmentCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'CreateSegment' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(createSegmentRequest(c, Shmseg, Size, ReadOnly), cookie)
return CreateSegmentCookie{cookie}
}
// CreateSegmentReply represents the data returned from a CreateSegment request.
type CreateSegmentReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
Nfd byte
// padding: 24 bytes
}
// Reply blocks and returns the reply data for a CreateSegment request.
func (cook CreateSegmentCookie) Reply() (*CreateSegmentReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return createSegmentReply(buf), nil
}
// createSegmentReply reads a byte slice into a CreateSegmentReply value.
func createSegmentReply(buf []byte) *CreateSegmentReply {
v := new(CreateSegmentReply)
b := 1 // skip reply determinant
v.Nfd = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
b += 24 // padding
return v
}
// createSegmentRequest writes a CreateSegment request to a byte slice for transfer.
func createSegmentRequest(c *xgb.Conn, Shmseg Seg, Size uint32, ReadOnly bool) []byte {
size := 16
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 7 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
xgb.Put32(buf[b:], Size)
b += 4
if ReadOnly {
buf[b] = 1
} else {
buf[b] = 0
}
b += 1
b += 3 // padding
return buf
}
// DetachCookie is a cookie used only for Detach requests.
type DetachCookie struct {
*xgb.Cookie
}
// Detach sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func Detach(c *xgb.Conn, Shmseg Seg) DetachCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'Detach' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(detachRequest(c, Shmseg), cookie)
return DetachCookie{cookie}
}
// DetachChecked sends a checked request.
// If an error occurs, it can be retrieved using DetachCookie.Check.
func DetachChecked(c *xgb.Conn, Shmseg Seg) DetachCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'Detach' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(detachRequest(c, Shmseg), cookie)
return DetachCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook DetachCookie) Check() error {
return cook.Cookie.Check()
}
// detachRequest writes a Detach request to a byte slice for transfer.
func detachRequest(c *xgb.Conn, Shmseg Seg) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
return buf
}
// GetImageCookie is a cookie used only for GetImage requests.
type GetImageCookie struct {
*xgb.Cookie
}
// GetImage sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetImageCookie.Reply.
func GetImage(c *xgb.Conn, Drawable xproto.Drawable, X, Y int16, Width, Height uint16, PlaneMask uint32, Format byte, Shmseg Seg, Offset uint32) GetImageCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'GetImage' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getImageRequest(c, Drawable, X, Y, Width, Height, PlaneMask, Format, Shmseg, Offset), cookie)
return GetImageCookie{cookie}
}
// GetImageUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetImageUnchecked(c *xgb.Conn, Drawable xproto.Drawable, X, Y int16, Width, Height uint16, PlaneMask uint32, Format byte, Shmseg Seg, Offset uint32) GetImageCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'GetImage' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getImageRequest(c, Drawable, X, Y, Width, Height, PlaneMask, Format, Shmseg, Offset), cookie)
return GetImageCookie{cookie}
}
// GetImageReply represents the data returned from a GetImage request.
type GetImageReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
Depth byte
Visual xproto.Visualid
Size uint32
}
// Reply blocks and returns the reply data for a GetImage request.
func (cook GetImageCookie) Reply() (*GetImageReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getImageReply(buf), nil
}
// getImageReply reads a byte slice into a GetImageReply value.
func getImageReply(buf []byte) *GetImageReply {
v := new(GetImageReply)
b := 1 // skip reply determinant
v.Depth = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.Visual = xproto.Visualid(xgb.Get32(buf[b:]))
b += 4
v.Size = xgb.Get32(buf[b:])
b += 4
return v
}
// getImageRequest writes a GetImage request to a byte slice for transfer.
func getImageRequest(c *xgb.Conn, Drawable xproto.Drawable, X, Y int16, Width, Height uint16, PlaneMask uint32, Format byte, Shmseg Seg, Offset uint32) []byte {
size := 32
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
xgb.Put16(buf[b:], uint16(X))
b += 2
xgb.Put16(buf[b:], uint16(Y))
b += 2
xgb.Put16(buf[b:], Width)
b += 2
xgb.Put16(buf[b:], Height)
b += 2
xgb.Put32(buf[b:], PlaneMask)
b += 4
buf[b] = Format
b += 1
b += 3 // padding
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
xgb.Put32(buf[b:], Offset)
b += 4
return buf
}
// PutImageCookie is a cookie used only for PutImage requests.
type PutImageCookie struct {
*xgb.Cookie
}
// PutImage sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func PutImage(c *xgb.Conn, Drawable xproto.Drawable, Gc xproto.Gcontext, TotalWidth, TotalHeight, SrcX, SrcY, SrcWidth, SrcHeight uint16, DstX, DstY int16, Depth, Format, SendEvent byte, Shmseg Seg, Offset uint32) PutImageCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'PutImage' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(putImageRequest(c, Drawable, Gc, TotalWidth, TotalHeight, SrcX, SrcY, SrcWidth, SrcHeight, DstX, DstY, Depth, Format, SendEvent, Shmseg, Offset), cookie)
return PutImageCookie{cookie}
}
// PutImageChecked sends a checked request.
// If an error occurs, it can be retrieved using PutImageCookie.Check.
func PutImageChecked(c *xgb.Conn, Drawable xproto.Drawable, Gc xproto.Gcontext, TotalWidth, TotalHeight, SrcX, SrcY, SrcWidth, SrcHeight uint16, DstX, DstY int16, Depth, Format, SendEvent byte, Shmseg Seg, Offset uint32) PutImageCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'PutImage' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(putImageRequest(c, Drawable, Gc, TotalWidth, TotalHeight, SrcX, SrcY, SrcWidth, SrcHeight, DstX, DstY, Depth, Format, SendEvent, Shmseg, Offset), cookie)
return PutImageCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook PutImageCookie) Check() error {
return cook.Cookie.Check()
}
// putImageRequest writes a PutImage request to a byte slice for transfer.
func putImageRequest(c *xgb.Conn, Drawable xproto.Drawable, Gc xproto.Gcontext, TotalWidth, TotalHeight, SrcX, SrcY, SrcWidth, SrcHeight uint16, DstX, DstY int16, Depth, Format, SendEvent byte, Shmseg Seg, Offset uint32) []byte {
size := 40
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Drawable))
b += 4
xgb.Put32(buf[b:], uint32(Gc))
b += 4
xgb.Put16(buf[b:], TotalWidth)
b += 2
xgb.Put16(buf[b:], TotalHeight)
b += 2
xgb.Put16(buf[b:], SrcX)
b += 2
xgb.Put16(buf[b:], SrcY)
b += 2
xgb.Put16(buf[b:], SrcWidth)
b += 2
xgb.Put16(buf[b:], SrcHeight)
b += 2
xgb.Put16(buf[b:], uint16(DstX))
b += 2
xgb.Put16(buf[b:], uint16(DstY))
b += 2
buf[b] = Depth
b += 1
buf[b] = Format
b += 1
buf[b] = SendEvent
b += 1
b += 1 // padding
xgb.Put32(buf[b:], uint32(Shmseg))
b += 4
xgb.Put32(buf[b:], Offset)
b += 4
return buf
}
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply.
func QueryVersion(c *xgb.Conn) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["MIT-SHM"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'MIT-SHM'. shm.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
SharedPixmaps bool
MajorVersion uint16
MinorVersion uint16
Uid uint16
Gid uint16
PixmapFormat byte
// padding: 15 bytes
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
if buf[b] == 1 {
v.SharedPixmaps = true
} else {
v.SharedPixmaps = false
}
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.MajorVersion = xgb.Get16(buf[b:])
b += 2
v.MinorVersion = xgb.Get16(buf[b:])
b += 2
v.Uid = xgb.Get16(buf[b:])
b += 2
v.Gid = xgb.Get16(buf[b:])
b += 2
v.PixmapFormat = buf[b]
b += 1
b += 15 // padding
return v
}
// queryVersionRequest writes a QueryVersion request to a byte slice for transfer.
func queryVersionRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["MIT-SHM"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}

29
nexgb/sync.go Normal file
View File

@ -0,0 +1,29 @@
package nexgb
// Sync sends a round trip request and waits for the response.
// This forces all pending cookies to be dealt with.
// You actually shouldn't need to use this like you might with Xlib. Namely,
// buffers are automatically flushed using Go's channels and round trip requests
// are forced where appropriate automatically.
func (c *Conn) Sync() {
cookie := c.NewCookie(true, true)
c.NewRequest(c.getInputFocusRequest(), cookie)
cookie.Reply() // wait for the buffer to clear
}
// getInputFocusRequest writes the raw bytes to a buffer.
// It is duplicated from xproto/xproto.go.
func (c *Conn) getInputFocusRequest() []byte {
size := 4
b := 0
buf := make([]byte, size)
buf[b] = 43 // request opcode
b += 1
b += 1 // padding
Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}

363
nexgb/xcmisc/xcmisc.go Normal file
View File

@ -0,0 +1,363 @@
// Package xcmisc is the X client API for the XC-MISC extension.
package xcmisc
// This file is automatically generated from xc_misc.xml. Edit at your peril!
import (
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xproto"
)
const (
MajorVersion = 1
MinorVersion = 1
)
// Init must be called before using the XC-MISC extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 7, "XC-MISC").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named XC-MISC could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["XC-MISC"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["XC-MISC"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["XC-MISC"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["XC-MISC"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["XC-MISC"] = make(map[int]xgb.NewErrorFun)
}
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// GetVersionCookie is a cookie used only for GetVersion requests.
type GetVersionCookie struct {
*xgb.Cookie
}
// GetVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetVersionCookie.Reply.
func GetVersion(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint16) GetVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XC-MISC"]; !ok {
panic("Cannot issue request 'GetVersion' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return GetVersionCookie{cookie}
}
// GetVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetVersionUnchecked(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint16) GetVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XC-MISC"]; !ok {
panic("Cannot issue request 'GetVersion' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return GetVersionCookie{cookie}
}
// GetVersionReply represents the data returned from a GetVersion request.
type GetVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
ServerMajorVersion uint16
ServerMinorVersion uint16
}
// Reply blocks and returns the reply data for a GetVersion request.
func (cook GetVersionCookie) Reply() (*GetVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getVersionReply(buf), nil
}
// getVersionReply reads a byte slice into a GetVersionReply value.
func getVersionReply(buf []byte) *GetVersionReply {
v := new(GetVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.ServerMajorVersion = xgb.Get16(buf[b:])
b += 2
v.ServerMinorVersion = xgb.Get16(buf[b:])
b += 2
return v
}
// getVersionRequest writes a GetVersion request to a byte slice for transfer.
func getVersionRequest(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint16) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XC-MISC"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put16(buf[b:], ClientMajorVersion)
b += 2
xgb.Put16(buf[b:], ClientMinorVersion)
b += 2
return buf
}
// GetXIDListCookie is a cookie used only for GetXIDList requests.
type GetXIDListCookie struct {
*xgb.Cookie
}
// GetXIDList sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetXIDListCookie.Reply.
func GetXIDList(c *xgb.Conn, Count uint32) GetXIDListCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XC-MISC"]; !ok {
panic("Cannot issue request 'GetXIDList' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getXIDListRequest(c, Count), cookie)
return GetXIDListCookie{cookie}
}
// GetXIDListUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetXIDListUnchecked(c *xgb.Conn, Count uint32) GetXIDListCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XC-MISC"]; !ok {
panic("Cannot issue request 'GetXIDList' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getXIDListRequest(c, Count), cookie)
return GetXIDListCookie{cookie}
}
// GetXIDListReply represents the data returned from a GetXIDList request.
type GetXIDListReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
IdsLen uint32
// padding: 20 bytes
Ids []uint32 // size: xgb.Pad((int(IdsLen) * 4))
}
// Reply blocks and returns the reply data for a GetXIDList request.
func (cook GetXIDListCookie) Reply() (*GetXIDListReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getXIDListReply(buf), nil
}
// getXIDListReply reads a byte slice into a GetXIDListReply value.
func getXIDListReply(buf []byte) *GetXIDListReply {
v := new(GetXIDListReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.IdsLen = xgb.Get32(buf[b:])
b += 4
b += 20 // padding
v.Ids = make([]uint32, v.IdsLen)
for i := 0; i < int(v.IdsLen); i++ {
v.Ids[i] = xgb.Get32(buf[b:])
b += 4
}
return v
}
// getXIDListRequest writes a GetXIDList request to a byte slice for transfer.
func getXIDListRequest(c *xgb.Conn, Count uint32) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XC-MISC"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], Count)
b += 4
return buf
}
// GetXIDRangeCookie is a cookie used only for GetXIDRange requests.
type GetXIDRangeCookie struct {
*xgb.Cookie
}
// GetXIDRange sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetXIDRangeCookie.Reply.
func GetXIDRange(c *xgb.Conn) GetXIDRangeCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XC-MISC"]; !ok {
panic("Cannot issue request 'GetXIDRange' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getXIDRangeRequest(c), cookie)
return GetXIDRangeCookie{cookie}
}
// GetXIDRangeUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetXIDRangeUnchecked(c *xgb.Conn) GetXIDRangeCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XC-MISC"]; !ok {
panic("Cannot issue request 'GetXIDRange' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getXIDRangeRequest(c), cookie)
return GetXIDRangeCookie{cookie}
}
// GetXIDRangeReply represents the data returned from a GetXIDRange request.
type GetXIDRangeReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
StartId uint32
Count uint32
}
// Reply blocks and returns the reply data for a GetXIDRange request.
func (cook GetXIDRangeCookie) Reply() (*GetXIDRangeReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getXIDRangeReply(buf), nil
}
// getXIDRangeReply reads a byte slice into a GetXIDRangeReply value.
func getXIDRangeReply(buf []byte) *GetXIDRangeReply {
v := new(GetXIDRangeReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.StartId = xgb.Get32(buf[b:])
b += 4
v.Count = xgb.Get32(buf[b:])
b += 4
return v
}
// getXIDRangeRequest writes a GetXIDRange request to a byte slice for transfer.
func getXIDRangeRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XC-MISC"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}

595
nexgb/xevie/xevie.go Normal file
View File

@ -0,0 +1,595 @@
// Package xevie is the X client API for the XEVIE extension.
package xevie
// This file is automatically generated from xevie.xml. Edit at your peril!
import (
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xproto"
)
const (
MajorVersion = 1
MinorVersion = 0
)
// Init must be called before using the XEVIE extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 5, "XEVIE").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named XEVIE could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["XEVIE"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["XEVIE"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["XEVIE"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["XEVIE"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["XEVIE"] = make(map[int]xgb.NewErrorFun)
}
const (
DatatypeUnmodified = 0
DatatypeModified = 1
)
type Event struct {
// padding: 32 bytes
}
// EventRead reads a byte slice into a Event value.
func EventRead(buf []byte, v *Event) int {
b := 0
b += 32 // padding
return b
}
// EventReadList reads a byte slice into a list of Event values.
func EventReadList(buf []byte, dest []Event) int {
b := 0
for i := 0; i < len(dest); i++ {
dest[i] = Event{}
b += EventRead(buf[b:], &dest[i])
}
return xgb.Pad(b)
}
// Bytes writes a Event value to a byte slice.
func (v Event) Bytes() []byte {
buf := make([]byte, 32)
b := 0
b += 32 // padding
return buf[:b]
}
// EventListBytes writes a list of Event values to a byte slice.
func EventListBytes(buf []byte, list []Event) int {
b := 0
var structBytes []byte
for _, item := range list {
structBytes = item.Bytes()
copy(buf[b:], structBytes)
b += len(structBytes)
}
return xgb.Pad(b)
}
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// EndCookie is a cookie used only for End requests.
type EndCookie struct {
*xgb.Cookie
}
// End sends a checked request.
// If an error occurs, it will be returned with the reply by calling EndCookie.Reply.
func End(c *xgb.Conn, Cmap uint32) EndCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'End' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(endRequest(c, Cmap), cookie)
return EndCookie{cookie}
}
// EndUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func EndUnchecked(c *xgb.Conn, Cmap uint32) EndCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'End' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(endRequest(c, Cmap), cookie)
return EndCookie{cookie}
}
// EndReply represents the data returned from a End request.
type EndReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
// padding: 24 bytes
}
// Reply blocks and returns the reply data for a End request.
func (cook EndCookie) Reply() (*EndReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return endReply(buf), nil
}
// endReply reads a byte slice into a EndReply value.
func endReply(buf []byte) *EndReply {
v := new(EndReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
b += 24 // padding
return v
}
// endRequest writes a End request to a byte slice for transfer.
func endRequest(c *xgb.Conn, Cmap uint32) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XEVIE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], Cmap)
b += 4
return buf
}
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply.
func QueryVersion(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint16) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint16) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
ServerMajorVersion uint16
ServerMinorVersion uint16
// padding: 20 bytes
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.ServerMajorVersion = xgb.Get16(buf[b:])
b += 2
v.ServerMinorVersion = xgb.Get16(buf[b:])
b += 2
b += 20 // padding
return v
}
// queryVersionRequest writes a QueryVersion request to a byte slice for transfer.
func queryVersionRequest(c *xgb.Conn, ClientMajorVersion, ClientMinorVersion uint16) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XEVIE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put16(buf[b:], ClientMajorVersion)
b += 2
xgb.Put16(buf[b:], ClientMinorVersion)
b += 2
return buf
}
// SelectInputCookie is a cookie used only for SelectInput requests.
type SelectInputCookie struct {
*xgb.Cookie
}
// SelectInput sends a checked request.
// If an error occurs, it will be returned with the reply by calling SelectInputCookie.Reply.
func SelectInput(c *xgb.Conn, EventMask uint32) SelectInputCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'SelectInput' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(selectInputRequest(c, EventMask), cookie)
return SelectInputCookie{cookie}
}
// SelectInputUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func SelectInputUnchecked(c *xgb.Conn, EventMask uint32) SelectInputCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'SelectInput' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(selectInputRequest(c, EventMask), cookie)
return SelectInputCookie{cookie}
}
// SelectInputReply represents the data returned from a SelectInput request.
type SelectInputReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
// padding: 24 bytes
}
// Reply blocks and returns the reply data for a SelectInput request.
func (cook SelectInputCookie) Reply() (*SelectInputReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return selectInputReply(buf), nil
}
// selectInputReply reads a byte slice into a SelectInputReply value.
func selectInputReply(buf []byte) *SelectInputReply {
v := new(SelectInputReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
b += 24 // padding
return v
}
// selectInputRequest writes a SelectInput request to a byte slice for transfer.
func selectInputRequest(c *xgb.Conn, EventMask uint32) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XEVIE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], EventMask)
b += 4
return buf
}
// SendCookie is a cookie used only for Send requests.
type SendCookie struct {
*xgb.Cookie
}
// Send sends a checked request.
// If an error occurs, it will be returned with the reply by calling SendCookie.Reply.
func Send(c *xgb.Conn, Event Event, DataType uint32) SendCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'Send' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(sendRequest(c, Event, DataType), cookie)
return SendCookie{cookie}
}
// SendUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func SendUnchecked(c *xgb.Conn, Event Event, DataType uint32) SendCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'Send' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(sendRequest(c, Event, DataType), cookie)
return SendCookie{cookie}
}
// SendReply represents the data returned from a Send request.
type SendReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
// padding: 24 bytes
}
// Reply blocks and returns the reply data for a Send request.
func (cook SendCookie) Reply() (*SendReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return sendReply(buf), nil
}
// sendReply reads a byte slice into a SendReply value.
func sendReply(buf []byte) *SendReply {
v := new(SendReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
b += 24 // padding
return v
}
// sendRequest writes a Send request to a byte slice for transfer.
func sendRequest(c *xgb.Conn, Event Event, DataType uint32) []byte {
size := 104
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XEVIE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
{
structBytes := Event.Bytes()
copy(buf[b:], structBytes)
b += len(structBytes)
}
xgb.Put32(buf[b:], DataType)
b += 4
b += 64 // padding
return buf
}
// StartCookie is a cookie used only for Start requests.
type StartCookie struct {
*xgb.Cookie
}
// Start sends a checked request.
// If an error occurs, it will be returned with the reply by calling StartCookie.Reply.
func Start(c *xgb.Conn, Screen uint32) StartCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'Start' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(startRequest(c, Screen), cookie)
return StartCookie{cookie}
}
// StartUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func StartUnchecked(c *xgb.Conn, Screen uint32) StartCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XEVIE"]; !ok {
panic("Cannot issue request 'Start' using the uninitialized extension 'XEVIE'. xevie.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(startRequest(c, Screen), cookie)
return StartCookie{cookie}
}
// StartReply represents the data returned from a Start request.
type StartReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
// padding: 24 bytes
}
// Reply blocks and returns the reply data for a Start request.
func (cook StartCookie) Reply() (*StartReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return startReply(buf), nil
}
// startReply reads a byte slice into a StartReply value.
func startReply(buf []byte) *StartReply {
v := new(StartReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
b += 24 // padding
return v
}
// startRequest writes a Start request to a byte slice for transfer.
func startRequest(c *xgb.Conn, Screen uint32) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XEVIE"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], Screen)
b += 4
return buf
}

1294
nexgb/xf86dri/xf86dri.go Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2807
nexgb/xfixes/xfixes.go Normal file

File diff suppressed because it is too large Load Diff

554
nexgb/xgb.go Normal file
View File

@ -0,0 +1,554 @@
package nexgb
import (
"errors"
"io"
"log"
"net"
"os"
"sync"
)
var (
// Where to log error-messages. Defaults to stderr.
// To disable logging, just set this to log.New(ioutil.Discard, "", 0)
Logger = log.New(os.Stderr, "XGB: ", log.Lshortfile)
)
const (
// cookieBuffer represents the queue size of cookies existing at any
// point in time. The size of the buffer is really only important when
// there are many requests without replies made in sequence. Once the
// buffer fills, a round trip request is made to clear the buffer.
cookieBuffer = 1000
// xidBuffer represents the queue size of the xid channel.
// I don't think this value matters much, since xid generation is not
// that expensive.
xidBuffer = 5
// seqBuffer represents the queue size of the sequence number channel.
// I don't think this value matters much, since sequence number generation
// is not that expensive.
seqBuffer = 5
// reqBuffer represents the queue size of the number of requests that
// can be made until new ones block. This value seems OK.
reqBuffer = 100
// eventBuffer represents the queue size of the number of events or errors
// that can be loaded off the wire and not grabbed with WaitForEvent
// until reading an event blocks. This value should be big enough to handle
// bursts of events.
eventBuffer = 5000
)
// A Conn represents a connection to an X server.
type Conn struct {
host string
conn net.Conn
display string
DisplayNumber int
DefaultScreen int
SetupBytes []byte
setupResourceIdBase uint32
setupResourceIdMask uint32
eventChan chan eventOrError
cookieChan chan *Cookie
xidChan chan xid
seqChan chan uint16
reqChan chan *request
closing chan chan struct{}
// ExtLock is a lock used whenever new extensions are initialized.
// It should not be used. It is exported for use in the extension
// sub-packages.
ExtLock sync.RWMutex
// Extensions is a map from extension name to major opcode. It should
// not be used. It is exported for use in the extension sub-packages.
Extensions map[string]byte
}
// NewConn creates a new connection instance. It initializes locks, data
// structures, and performs the initial handshake. (The code for the handshake
// has been relegated to conn.go.)
func NewConn() (*Conn, error) {
return NewConnDisplay("")
}
// NewConnDisplay is just like NewConn, but allows a specific DISPLAY
// string to be used.
// If 'display' is empty it will be taken from os.Getenv("DISPLAY").
//
// Examples:
// NewConn(":1") -> net.Dial("unix", "", "/tmp/.X11-unix/X1")
// NewConn("/tmp/launch-12/:0") -> net.Dial("unix", "", "/tmp/launch-12/:0")
// NewConn("hostname:2.1") -> net.Dial("tcp", "", "hostname:6002")
// NewConn("tcp/hostname:1.0") -> net.Dial("tcp", "", "hostname:6001")
func NewConnDisplay(display string) (*Conn, error) {
conn := &Conn{}
// First connect. This reads authority, checks DISPLAY environment
// variable, and loads the initial Setup info.
err := conn.connect(display)
if err != nil {
return nil, err
}
return postNewConn(conn)
}
// NewConnDisplay is just like NewConn, but allows a specific net.Conn
// to be used.
func NewConnNet(netConn net.Conn) (*Conn, error) {
conn := &Conn{}
// First connect. This reads authority, checks DISPLAY environment
// variable, and loads the initial Setup info.
err := conn.connectNet(netConn)
if err != nil {
return nil, err
}
return postNewConn(conn)
}
func postNewConn(conn *Conn) (*Conn, error) {
conn.Extensions = make(map[string]byte)
conn.cookieChan = make(chan *Cookie, cookieBuffer)
conn.xidChan = make(chan xid, xidBuffer)
conn.seqChan = make(chan uint16, seqBuffer)
conn.reqChan = make(chan *request, reqBuffer)
conn.eventChan = make(chan eventOrError, eventBuffer)
conn.closing = make(chan chan struct{}, 1)
go conn.generateXIds()
go conn.generateSeqIds()
go conn.sendRequests()
go conn.readResponses()
return conn, nil
}
// Close gracefully closes the connection to the X server.
func (c *Conn) Close() {
close(c.reqChan)
}
// Event is an interface that can contain any of the events returned by the
// server. Use a type assertion switch to extract the Event structs.
type Event interface {
Bytes() []byte
String() string
}
// NewEventFun is the type of function use to construct events from raw bytes.
// It should not be used. It is exported for use in the extension sub-packages.
type NewEventFun func(buf []byte) Event
// NewEventFuncs is a map from event numbers to functions that create
// the corresponding event. It should not be used. It is exported for use
// in the extension sub-packages.
var NewEventFuncs = make(map[int]NewEventFun)
// NewExtEventFuncs is a temporary map that stores event constructor functions
// for each extension. When an extension is initialized, each event for that
// extension is added to the 'NewEventFuncs' map. It should not be used. It is
// exported for use in the extension sub-packages.
var NewExtEventFuncs = make(map[string]map[int]NewEventFun)
// Error is an interface that can contain any of the errors returned by
// the server. Use a type assertion switch to extract the Error structs.
type Error interface {
SequenceId() uint16
BadId() uint32
Error() string
}
// NewErrorFun is the type of function use to construct errors from raw bytes.
// It should not be used. It is exported for use in the extension sub-packages.
type NewErrorFun func(buf []byte) Error
// NewErrorFuncs is a map from error numbers to functions that create
// the corresponding error. It should not be used. It is exported for use in
// the extension sub-packages.
var NewErrorFuncs = make(map[int]NewErrorFun)
// NewExtErrorFuncs is a temporary map that stores error constructor functions
// for each extension. When an extension is initialized, each error for that
// extension is added to the 'NewErrorFuncs' map. It should not be used. It is
// exported for use in the extension sub-packages.
var NewExtErrorFuncs = make(map[string]map[int]NewErrorFun)
// eventOrError corresponds to values that can be either an event or an
// error.
type eventOrError interface{}
// NewId generates a new unused ID for use with requests like CreateWindow.
// If no new ids can be generated, the id returned is 0 and error is non-nil.
// This shouldn't be used directly, and is exported for use in the extension
// sub-packages.
// If you need identifiers, use the appropriate constructor.
// e.g., For a window id, use xproto.NewWindowId. For
// a new pixmap id, use xproto.NewPixmapId. And so on.
func (c *Conn) NewId() (uint32, error) {
xid := <-c.xidChan
if xid.err != nil {
return 0, xid.err
}
return xid.id, nil
}
// xid encapsulates a resource identifier being sent over the Conn.xidChan
// channel. If no new resource id can be generated, id is set to 0 and a
// non-nil error is set in xid.err.
type xid struct {
id uint32
err error
}
// generateXids sends new Ids down the channel for NewId to use.
// generateXids should be run in its own goroutine.
// This needs to be updated to use the XC Misc extension once we run out of
// new ids.
// Thanks to libxcb/src/xcb_xid.c. This code is greatly inspired by it.
func (conn *Conn) generateXIds() {
defer close(conn.xidChan)
// This requires some explanation. From the horse's mouth:
// "The resource-id-mask contains a single contiguous set of bits (at least
// 18). The client allocates resource IDs for types WINDOW, PIXMAP,
// CURSOR, FONT, GCONTEXT, and COLORMAP by choosing a value with only some
// subset of these bits set and ORing it with resource-id-base. Only values
// constructed in this way can be used to name newly created resources over
// this connection."
// So for example (using 8 bit integers), the mask might look like:
// 00111000
// So that valid values would be 00101000, 00110000, 00001000, and so on.
// Thus, the idea is to increment it by the place of the last least
// significant '1'. In this case, that value would be 00001000. To get
// that value, we can AND the original mask with its two's complement:
// 00111000 & 11001000 = 00001000.
// And we use that value to increment the last resource id to get a new one.
// (And then, of course, we OR it with resource-id-base.)
inc := conn.setupResourceIdMask & -conn.setupResourceIdMask
max := conn.setupResourceIdMask
last := uint32(0)
for {
// TODO: Use the XC Misc extension to look for released ids.
if last > 0 && last >= max-inc+1 {
conn.xidChan <- xid{
id: 0,
err: errors.New("There are no more available resource" +
"identifiers."),
}
}
last += inc
conn.xidChan <- xid{
id: last | conn.setupResourceIdBase,
err: nil,
}
}
}
// newSeqId fetches the next sequence id from the Conn.seqChan channel.
func (c *Conn) newSequenceId() uint16 {
return <-c.seqChan
}
// generateSeqIds returns new sequence ids. It is meant to be run in its
// own goroutine.
// A sequence id is generated for *every* request. It's the identifier used
// to match up replies with requests.
// Since sequence ids can only be 16 bit integers we start over at zero when it
// comes time to wrap.
// N.B. As long as the cookie buffer is less than 2^16, there are no limitations
// on the number (or kind) of requests made in sequence.
func (c *Conn) generateSeqIds() {
defer close(c.seqChan)
seqid := uint16(1)
for {
c.seqChan <- seqid
if seqid == uint16((1<<16)-1) {
seqid = 0
} else {
seqid++
}
}
}
// request encapsulates a buffer of raw bytes (containing the request data)
// and a cookie, which when combined represents a single request.
// The cookie is used to match up the reply/error.
type request struct {
buf []byte
cookie *Cookie
// seq is closed when the request (cookie) has been sequenced by the Conn.
seq chan struct{}
}
// NewRequest takes the bytes and a cookie of a particular request, constructs
// a request type, and sends it over the Conn.reqChan channel.
// Note that the sequence number is added to the cookie after it is sent
// over the request channel, but before it is sent to X.
//
// Note that you may safely use NewRequest to send arbitrary byte requests
// to X. The resulting cookie can be used just like any normal cookie and
// abides by the same rules, except that for replies, you'll get back the
// raw byte data. This may be useful for performance critical sections where
// every allocation counts, since all X requests in XGB allocate a new byte
// slice. In contrast, NewRequest allocates one small request struct and
// nothing else. (Except when the cookie buffer is full and has to be flushed.)
//
// If you're using NewRequest manually, you'll need to use NewCookie to create
// a new cookie.
//
// In all likelihood, you should be able to copy and paste with some minor
// edits the generated code for the request you want to issue.
func (c *Conn) NewRequest(buf []byte, cookie *Cookie) {
seq := make(chan struct{})
c.reqChan <- &request{buf: buf, cookie: cookie, seq: seq}
<-seq
}
// sendRequests is run as a single goroutine that takes requests and writes
// the bytes to the wire and adds the cookie to the cookie queue.
// It is meant to be run as its own goroutine.
func (c *Conn) sendRequests() {
defer close(c.cookieChan)
for req := range c.reqChan {
// ho there! if the cookie channel is nearly full, force a round
// trip to clear out the cookie buffer.
// Note that we circumvent the request channel, because we're *in*
// the request channel.
if len(c.cookieChan) == cookieBuffer-1 {
if err := c.noop(); err != nil {
// Shut everything down.
break
}
}
req.cookie.Sequence = c.newSequenceId()
c.cookieChan <- req.cookie
c.writeBuffer(req.buf)
close(req.seq)
}
response := make(chan struct{})
c.closing <- response
c.noop() // Flush the response reading goroutine, ignore error.
<-response
c.conn.Close()
}
// noop circumvents the usual request sending goroutines and forces a round
// trip request manually.
func (c *Conn) noop() error {
cookie := c.NewCookie(true, true)
cookie.Sequence = c.newSequenceId()
c.cookieChan <- cookie
if err := c.writeBuffer(c.getInputFocusRequest()); err != nil {
return err
}
cookie.Reply() // wait for the buffer to clear
return nil
}
// writeBuffer is a convenience function for writing a byte slice to the wire.
func (c *Conn) writeBuffer(buf []byte) error {
if _, err := c.conn.Write(buf); err != nil {
Logger.Printf("A write error is unrecoverable: %s", err)
return err
} else {
return nil
}
}
// readResponses is a goroutine that reads events, errors and
// replies off the wire.
// When an event is read, it is always added to the event channel.
// When an error is read, if it corresponds to an existing checked cookie,
// it is sent to that cookie's error channel. Otherwise it is added to the
// event channel.
// When a reply is read, it is added to the corresponding cookie's reply
// channel. (It is an error if no such cookie exists in this case.)
// Finally, cookies that came "before" this reply are always cleaned up.
func (c *Conn) readResponses() {
defer close(c.eventChan)
var (
err Error
seq uint16
replyBytes []byte
)
for {
select {
case respond := <-c.closing:
respond <- struct{}{}
return
default:
}
buf := make([]byte, 32)
err, seq = nil, 0
if _, err := io.ReadFull(c.conn, buf); err != nil {
Logger.Printf("A read error is unrecoverable: %s", err)
c.eventChan <- err
c.Close()
continue
}
switch buf[0] {
case 0: // This is an error
// Use the constructor function for this error (that is auto
// generated) by looking it up by the error number.
newErrFun, ok := NewErrorFuncs[int(buf[1])]
if !ok {
Logger.Printf("BUG: Could not find error constructor function "+
"for error with number %d.", buf[1])
continue
}
err = newErrFun(buf)
seq = err.SequenceId()
// This error is either sent to the event channel or a specific
// cookie's error channel below.
case 1: // This is a reply
seq = Get16(buf[2:])
// check to see if this reply has more bytes to be read
size := Get32(buf[4:])
if size > 0 {
byteCount := 32 + size*4
biggerBuf := make([]byte, byteCount)
copy(biggerBuf[:32], buf)
if _, err := io.ReadFull(c.conn, biggerBuf[32:]); err != nil {
Logger.Printf("A read error is unrecoverable: %s", err)
c.eventChan <- err
c.Close()
continue
}
replyBytes = biggerBuf
} else {
replyBytes = buf
}
// This reply is sent to its corresponding cookie below.
default: // This is an event
// Use the constructor function for this event (like for errors,
// and is also auto generated) by looking it up by the event number.
// Note that we AND the event number with 127 so that we ignore
// the most significant bit (which is set when it was sent from
// a SendEvent request).
evNum := int(buf[0] & 127)
newEventFun, ok := NewEventFuncs[evNum]
if !ok {
Logger.Printf("BUG: Could not find event construct function "+
"for event with number %d.", evNum)
continue
}
c.eventChan <- newEventFun(buf)
continue
}
// At this point, we have a sequence number and we're either
// processing an error or a reply, which are both responses to
// requests. So all we have to do is find the cookie corresponding
// to this error/reply, and send the appropriate data to it.
// In doing so, we make sure that any cookies that came before it
// are marked as successful if they are void and checked.
// If there's a cookie that requires a reply that is before this
// reply, then something is wrong.
for cookie := range c.cookieChan {
// This is the cookie we're looking for. Process and break.
if cookie.Sequence == seq {
if err != nil { // this is an error to a request
// synchronous processing
if cookie.errorChan != nil {
cookie.errorChan <- err
} else { // asynchronous processing
c.eventChan <- err
// if this is an unchecked reply, ping the cookie too
if cookie.pingChan != nil {
cookie.pingChan <- true
}
}
} else { // this is a reply
if cookie.replyChan == nil {
Logger.Printf("Reply with sequence id %d does not "+
"have a cookie with a valid reply channel.", seq)
continue
} else {
cookie.replyChan <- replyBytes
}
}
break
}
switch {
// Checked requests with replies
case cookie.replyChan != nil && cookie.errorChan != nil:
Logger.Printf("Found cookie with sequence id %d that is "+
"expecting a reply but will never get it. Currently "+
"on sequence number %d", cookie.Sequence, seq)
// Unchecked requests with replies
case cookie.replyChan != nil && cookie.pingChan != nil:
Logger.Printf("Found cookie with sequence id %d that is "+
"expecting a reply (and not an error) but will never "+
"get it. Currently on sequence number %d",
cookie.Sequence, seq)
// Checked requests without replies
case cookie.pingChan != nil && cookie.errorChan != nil:
cookie.pingChan <- true
// Unchecked requests without replies don't have any channels,
// so we can't do anything with them except let them pass by.
}
}
}
}
// processEventOrError takes an eventOrError, type switches on it,
// and returns it in Go idiomatic style.
func processEventOrError(everr eventOrError) (Event, Error) {
switch ee := everr.(type) {
case Event:
return ee, nil
case Error:
return nil, ee
default:
Logger.Printf("Invalid event/error type: %T", everr)
return nil, nil
}
}
// WaitForEvent returns the next event from the server.
// It will block until an event is available.
// WaitForEvent returns either an Event or an Error. (Returning both
// is a bug.) Note than an Error here is an X error and not an XGB error. That
// is, X errors are sometimes completely expected (and you may want to ignore
// them in some cases).
//
// If both the event and error are nil, then the connection has been closed.
func (c *Conn) WaitForEvent() (Event, Error) {
return processEventOrError(<-c.eventChan)
}
// PollForEvent returns the next event from the server if one is available in
// the internal queue without blocking. Note that unlike WaitForEvent, both
// Event and Error could be nil. Indeed, they are both nil when the event queue
// is empty.
func (c *Conn) PollForEvent() (Event, Error) {
select {
case everr := <-c.eventChan:
return processEventOrError(everr)
default:
return nil, nil
}
}

13
nexgb/xgbgen/LICENSE Normal file
View File

@ -0,0 +1,13 @@
Copyright (c) 2012 - 2016, Andrew Gallant <jamslam@gmail.com>
Copyright (c) 2018, Přemysl Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

182
nexgb/xgbgen/context.go Normal file
View File

@ -0,0 +1,182 @@
package main
import (
"bytes"
"encoding/xml"
"fmt"
"log"
"sort"
"strings"
)
// Context represents the protocol we're converting to Go, and a writer
// buffer to write the Go source to.
type Context struct {
protocol *Protocol
out *bytes.Buffer
}
func newContext() *Context {
return &Context{
out: bytes.NewBuffer([]byte{}),
}
}
// Putln calls put and adds a new line to the end of 'format'.
func (c *Context) Putln(format string, v ...interface{}) {
c.Put(format+"\n", v...)
}
// Put is a short alias to write to 'out'.
func (c *Context) Put(format string, v ...interface{}) {
_, err := c.out.WriteString(fmt.Sprintf(format, v...))
if err != nil {
log.Fatalf("There was an error writing to context buffer: %s", err)
}
}
// PutComment writes each line of comment commented out to 'out'.
func (c *Context) PutComment(comment string) {
for _, line := range strings.Split(comment, "\n") {
c.Putln("// %s", line)
}
}
// Morph is the big daddy of them all. It takes in an XML byte slice,
// parse it, transforms the XML types into more usable types,
// and writes Go code to the 'out' buffer.
func (c *Context) Morph(xmlBytes []byte) {
parsedXml := &XML{}
err := xml.Unmarshal(xmlBytes, parsedXml)
if err != nil {
log.Fatal(err)
}
// Parse all imports
parsedXml.Imports.Eval()
// Translate XML types to nice types
c.protocol = parsedXml.Translate(nil)
// For backwards compatibility we patch the type of the send_event field of
// PutImage to be byte
if c.protocol.Name == "shm" {
for _, req := range c.protocol.Requests {
if req.xmlName != "PutImage" {
continue
}
for _, ifield := range req.Fields {
field, ok := ifield.(*SingleField)
if !ok || field.xmlName != "send_event" {
continue
}
field.Type = &Base{srcName: "byte", xmlName: "CARD8", size: newFixedSize(1, true)}
}
}
}
// Start with Go header.
c.Putln("// Package %s is the X client API for the %s extension.",
c.protocol.PkgName(), c.protocol.ExtXName)
c.Putln("package %s", c.protocol.PkgName())
c.Putln("")
c.Putln("// This file is automatically generated from %s.xml. "+
"Edit at your peril!", c.protocol.Name)
c.Putln("")
// Write imports. We always need to import at least xgb.
// We also need to import xproto if it's an extension.
c.Putln("import (")
c.Putln("xgb \"janouch.name/haven/nexgb\"")
c.Putln("")
if c.protocol.isExt() {
c.Putln("\"janouch.name/haven/nexgb/xproto\"")
}
sort.Sort(Protocols(c.protocol.Imports))
for _, imp := range c.protocol.Imports {
// We always import xproto, so skip it if it's explicitly imported
if imp.Name == "xproto" {
continue
}
c.Putln("\"janouch.name/haven/nexgb/%s\"", imp.Name)
}
c.Putln(")")
c.Putln("")
// If this is an extension, create a function to initialize the extension
// before it can be used.
if c.protocol.isExt() {
xname := c.protocol.ExtXName
c.Putln("const (")
c.Putln("MajorVersion = %s", c.protocol.MajorVersion)
c.Putln("MinorVersion = %s", c.protocol.MinorVersion)
c.Putln(")")
c.Putln("")
c.Putln("// Init must be called before using the %s extension.",
xname)
c.Putln("func Init(c *xgb.Conn) error {")
c.Putln("reply, err := xproto.QueryExtension(c, %d, \"%s\").Reply()",
len(xname), xname)
c.Putln("switch {")
c.Putln("case err != nil:")
c.Putln("return err")
c.Putln("case !reply.Present:")
c.Putln("return xgb.Errorf(\"No extension named %s could be found on "+
"on the server.\")", xname)
c.Putln("}")
c.Putln("")
c.Putln("c.ExtLock.Lock()")
c.Putln("c.Extensions[\"%s\"] = reply.MajorOpcode", xname)
c.Putln("c.ExtLock.Unlock()")
c.Putln("for evNum, fun := range xgb.NewExtEventFuncs[\"%s\"] {",
xname)
c.Putln("xgb.NewEventFuncs[int(reply.FirstEvent) + evNum] = fun")
c.Putln("}")
c.Putln("for errNum, fun := range xgb.NewExtErrorFuncs[\"%s\"] {",
xname)
c.Putln("xgb.NewErrorFuncs[int(reply.FirstError) + errNum] = fun")
c.Putln("}")
c.Putln("return nil")
c.Putln("}")
c.Putln("")
// Make sure newExtEventFuncs["EXT_NAME"] map is initialized.
// Same deal for newExtErrorFuncs["EXT_NAME"]
c.Putln("func init() {")
c.Putln("xgb.NewExtEventFuncs[\"%s\"] = make(map[int]xgb.NewEventFun)",
xname)
c.Putln("xgb.NewExtErrorFuncs[\"%s\"] = make(map[int]xgb.NewErrorFun)",
xname)
c.Putln("}")
c.Putln("")
} else {
// In the xproto package, we must provide a Setup function that uses
// SetupBytes in xgb.Conn to return a SetupInfo structure.
c.Putln("// Setup parses the setup bytes retrieved when")
c.Putln("// connecting into a SetupInfo struct.")
c.Putln("func Setup(c *xgb.Conn) *SetupInfo {")
c.Putln("setup := new(SetupInfo)")
c.Putln("SetupInfoRead(c.SetupBytes, setup)")
c.Putln("return setup")
c.Putln("}")
c.Putln("")
c.Putln("// DefaultScreen gets the default screen info from SetupInfo.")
c.Putln("func (s *SetupInfo) DefaultScreen(c *xgb.Conn) *ScreenInfo {")
c.Putln("return &s.Roots[c.DefaultScreen]")
c.Putln("}")
c.Putln("")
}
// Now write Go source code
sort.Sort(Types(c.protocol.Types))
sort.Sort(Requests(c.protocol.Requests))
for _, typ := range c.protocol.Types {
typ.Define(c)
}
for _, req := range c.protocol.Requests {
req.Define(c)
}
}

74
nexgb/xgbgen/doc.go Normal file
View File

@ -0,0 +1,74 @@
/*
xgbgen constructs Go source files from xproto XML description files. xgbgen
accomplishes the same task as the Python code generator for XCB and xpyb.
Usage:
xgbgen [flags] some-protocol.xml
The flags are:
--proto-path path
The path to a directory containing xproto XML description files.
This is only necessary when 'some-protocol.xml' imports other
protocol files.
--gofmt=true
When false, the outputted Go code will not be gofmt'd. And it won't
be very pretty at all. This is typically useful if there are syntax
errors that need to be debugged in code generation. gofmt will hiccup;
this will allow you to see the raw code.
How it works
xgbgen works by parsing the input XML file using Go's encoding/xml package.
The majority of this work is done in xml.go and xml_fields.go, where the
appropriate types are declared.
Due to the nature of the XML in the protocol description files, the types
required to parse the XML are not well suited to reasoning about code
generation. Because of this, all data parsed in the XML types is translated
into more reasonable types. This translation is done in translation.go,
and is mainly grunt work. (The only interesting tidbits are the translation
of XML names to Go source names, and connecting fields with their
appropriate types.)
The organization of these types is greatly
inspired by the description of the XML found here:
http://cgit.freedesktop.org/xcb/proto/tree/doc/xml-xcb.txt
These types come with a lot of supporting methods to make their use in
code generation easier. They can be found in expression.go, field.go,
protocol.go, request_reply.go and type.go. Of particular interest are
expression evaluation and size calculation (in bytes).
These types also come with supporting methods that convert their
representation into Go source code. I've quartered such methods in
go.go, go_error.go, go_event.go, go_list.go, go_request_reply.go,
go_single_field.go, go_struct.go and go_union.go. The idea is to keep
as much of the Go specific code generation in one area as possible. Namely,
while not *all* Go related code is found in the 'go*.go' files, *most*
of it is. (If there's any interest in using xgbgen for other languages,
I'd be happy to try and make xgbgen a little more friendly in this regard.
I did, however, design xgbgen with this in mind, so it shouldn't involve
anything as serious as a re-design.)
Why
I wrote xgbgen because I found the existing code generator that was written in
Python to be unwieldy. In particular, static and strong typing greatly helped
me reason better about the code generation task.
What does not work
The core X protocol should be completely working. As far as I know, most
extensions should work too, although I've only tested (and not much) the
Xinerama and RandR extensions.
XKB does not work. I don't have any real plans of working on this unless there
is demand and I have some test cases to work with. (i.e., even if I could get
something generated for XKB, I don't have the inclination to understand it
enough to verify that it works.) XKB poses several extremely difficult
problems that XCB also has trouble with. More info on that can be found at
http://cgit.freedesktop.org/xcb/libxcb/tree/doc/xkb_issues and
http://cgit.freedesktop.org/xcb/libxcb/tree/doc/xkb_internals.
*/
package main

436
nexgb/xgbgen/expression.go Normal file
View File

@ -0,0 +1,436 @@
package main
import (
"fmt"
"log"
)
// Expression represents all the different forms of expressions possible in
// side an XML protocol description file. It's also received a few custom
// addendums to make applying special functions (like padding) easier.
type Expression interface {
// Concrete determines whether this particular expression can be computed
// to some constant value inside xgbgen. (The alternative is that the
// expression can only be computed with values at run time of the
// generated code.)
Concrete() bool
// Eval evaluates a concrete expression. It is an error to call Eval
// on any expression that is not concrete (or contains any sub-expression
// that is not concrete).
Eval() int
// Reduce attempts to evaluate any concrete sub-expressions.
// i.e., (1 + 2 * (5 + 1 + someSizeOfStruct) reduces to
// (3 * (6 + someSizeOfStruct)).
// 'prefix' is used preprended to any field reference name.
Reduce(prefix string) string
// String is an alias for Reduce("")
String() string
// Initialize makes sure all names in this expression and any subexpressions
// have been translated to Go source names.
Initialize(p *Protocol)
// Makes all field references relative to path
Specialize(path string) Expression
}
// Function is a custom expression not found in the XML. It's simply used
// to apply a function named in 'Name' to the Expr expression.
type Function struct {
Name string
Expr Expression
}
func (e *Function) Concrete() bool {
return false
}
func (e *Function) Eval() int {
log.Fatalf("Cannot evaluate a 'Function'. It is not concrete.")
panic("unreachable")
}
func (e *Function) Reduce(prefix string) string {
return fmt.Sprintf("%s(%s)", e.Name, e.Expr.Reduce(prefix))
}
func (e *Function) String() string {
return e.Reduce("")
}
func (e *Function) Initialize(p *Protocol) {
e.Expr.Initialize(p)
}
func (e *Function) Specialize(path string) Expression {
r := *e
r.Expr = r.Expr.Specialize(path)
return &r
}
// BinaryOp is an expression that performs some operation (defined in the XML
// file) with Expr1 and Expr2 as operands.
type BinaryOp struct {
Op string
Expr1 Expression
Expr2 Expression
}
// newBinaryOp constructs a new binary expression when both expr1 and expr2
// are not nil. If one or both are nil, then the non-nil expression is
// returned unchanged or nil is returned.
func newBinaryOp(op string, expr1, expr2 Expression) Expression {
switch {
case expr1 != nil && expr2 != nil:
return &BinaryOp{
Op: op,
Expr1: expr1,
Expr2: expr2,
}
case expr1 != nil && expr2 == nil:
return expr1
case expr1 == nil && expr2 != nil:
return expr2
case expr1 == nil && expr2 == nil:
return nil
}
panic("unreachable")
}
func (e *BinaryOp) Concrete() bool {
return e.Expr1.Concrete() && e.Expr2.Concrete()
}
func (e *BinaryOp) Eval() int {
switch e.Op {
case "+":
return e.Expr1.Eval() + e.Expr2.Eval()
case "-":
return e.Expr1.Eval() - e.Expr2.Eval()
case "*":
return e.Expr1.Eval() * e.Expr2.Eval()
case "/":
return e.Expr1.Eval() / e.Expr2.Eval()
case "&amp;":
return e.Expr1.Eval() & e.Expr2.Eval()
case "&lt;&lt;":
return int(uint(e.Expr1.Eval()) << uint(e.Expr2.Eval()))
}
log.Fatalf("Invalid binary operator '%s' for expression.", e.Op)
panic("unreachable")
}
func (e *BinaryOp) Reduce(prefix string) string {
if e.Concrete() {
return fmt.Sprintf("%d", e.Eval())
}
// An incredibly dirty hack to make sure any time we perform an operation
// on a field, we're dealing with ints...
expr1, expr2 := e.Expr1, e.Expr2
switch expr1.(type) {
case *FieldRef:
expr1 = &Function{
Name: "int",
Expr: expr1,
}
}
switch expr2.(type) {
case *FieldRef:
expr2 = &Function{
Name: "int",
Expr: expr2,
}
}
return fmt.Sprintf("(%s %s %s)",
expr1.Reduce(prefix), e.Op, expr2.Reduce(prefix))
}
func (e *BinaryOp) String() string {
return e.Reduce("")
}
func (e *BinaryOp) Initialize(p *Protocol) {
e.Expr1.Initialize(p)
e.Expr2.Initialize(p)
}
func (e *BinaryOp) Specialize(path string) Expression {
r := *e
r.Expr1 = r.Expr1.Specialize(path)
r.Expr2 = r.Expr2.Specialize(path)
return &r
}
// UnaryOp is the same as BinaryOp, except it's a unary operator with only
// one sub-expression.
type UnaryOp struct {
Op string
Expr Expression
}
func (e *UnaryOp) Concrete() bool {
return e.Expr.Concrete()
}
func (e *UnaryOp) Eval() int {
switch e.Op {
case "~":
return ^e.Expr.Eval()
}
log.Fatalf("Invalid unary operator '%s' for expression.", e.Op)
panic("unreachable")
}
func (e *UnaryOp) Reduce(prefix string) string {
if e.Concrete() {
return fmt.Sprintf("%d", e.Eval())
}
return fmt.Sprintf("(%s (%s))", e.Op, e.Expr.Reduce(prefix))
}
func (e *UnaryOp) String() string {
return e.Reduce("")
}
func (e *UnaryOp) Initialize(p *Protocol) {
e.Expr.Initialize(p)
}
func (e *UnaryOp) Specialize(path string) Expression {
r := *e
r.Expr = r.Expr.Specialize(path)
return &r
}
// Padding represents the application of the 'pad' function to some
// sub-expression.
type Padding struct {
Expr Expression
}
func (e *Padding) Concrete() bool {
return e.Expr.Concrete()
}
func (e *Padding) Eval() int {
return pad(e.Expr.Eval())
}
func (e *Padding) Reduce(prefix string) string {
if e.Concrete() {
return fmt.Sprintf("%d", e.Eval())
}
return fmt.Sprintf("xgb.Pad(%s)", e.Expr.Reduce(prefix))
}
func (e *Padding) String() string {
return e.Reduce("")
}
func (e *Padding) Initialize(p *Protocol) {
e.Expr.Initialize(p)
}
func (e *Padding) Specialize(path string) Expression {
r := *e
r.Expr = r.Expr.Specialize(path)
return &r
}
// PopCount represents the application of the 'PopCount' function to
// some sub-expression.
type PopCount struct {
Expr Expression
}
func (e *PopCount) Concrete() bool {
return e.Expr.Concrete()
}
func (e *PopCount) Eval() int {
return int(popCount(uint(e.Expr.Eval())))
}
func (e *PopCount) Reduce(prefix string) string {
if e.Concrete() {
return fmt.Sprintf("%d", e.Eval())
}
return fmt.Sprintf("xgb.PopCount(%s)", e.Expr.Reduce(prefix))
}
func (e *PopCount) String() string {
return e.Reduce("")
}
func (e *PopCount) Initialize(p *Protocol) {
e.Expr.Initialize(p)
}
func (e *PopCount) Specialize(path string) Expression {
r := *e
r.Expr = r.Expr.Specialize(path)
return &r
}
// Value represents some constant integer.
type Value struct {
v int
}
func (e *Value) Concrete() bool {
return true
}
func (e *Value) Eval() int {
return e.v
}
func (e *Value) Reduce(prefix string) string {
return fmt.Sprintf("%d", e.v)
}
func (e *Value) String() string {
return e.Reduce("")
}
func (e *Value) Initialize(p *Protocol) {}
func (e *Value) Specialize(path string) Expression {
return e
}
// Bit represents some bit whose value is computed by '1 << bit'.
type Bit struct {
b int
}
func (e *Bit) Concrete() bool {
return true
}
func (e *Bit) Eval() int {
return int(1 << uint(e.b))
}
func (e *Bit) Reduce(prefix string) string {
return fmt.Sprintf("%d", e.Eval())
}
func (e *Bit) String() string {
return e.Reduce("")
}
func (e *Bit) Initialize(p *Protocol) {}
func (e *Bit) Specialize(path string) Expression {
return e
}
// FieldRef represents a reference to some variable in the generated code
// with name Name.
type FieldRef struct {
Name string
}
func (e *FieldRef) Concrete() bool {
return false
}
func (e *FieldRef) Eval() int {
log.Fatalf("Cannot evaluate a 'FieldRef'. It is not concrete.")
panic("unreachable")
}
func (e *FieldRef) Reduce(prefix string) string {
val := e.Name
if len(prefix) > 0 {
val = fmt.Sprintf("%s%s", prefix, val)
}
return val
}
func (e *FieldRef) String() string {
return e.Reduce("")
}
func (e *FieldRef) Initialize(p *Protocol) {
e.Name = SrcName(p, e.Name)
}
func (e *FieldRef) Specialize(path string) Expression {
return &FieldRef{Name: path + "." + e.Name}
}
// EnumRef represents a reference to some enumeration field.
// EnumKind is the "group" an EnumItem is the name of the specific enumeration
// value inside that group.
type EnumRef struct {
EnumKind Type
EnumItem string
}
func (e *EnumRef) Concrete() bool {
return false
}
func (e *EnumRef) Eval() int {
log.Fatalf("Cannot evaluate an 'EnumRef'. It is not concrete.")
panic("unreachable")
}
func (e *EnumRef) Reduce(prefix string) string {
return fmt.Sprintf("%s%s", e.EnumKind, e.EnumItem)
}
func (e *EnumRef) String() string {
return e.Reduce("")
}
func (e *EnumRef) Initialize(p *Protocol) {
e.EnumKind = e.EnumKind.(*Translation).RealType(p)
e.EnumItem = SrcName(p, e.EnumItem)
}
func (e *EnumRef) Specialize(path string) Expression {
return e
}
// SumOf represents a summation of the variable in the generated code named by
// Name. It is not currently used. (It's XKB voodoo.)
type SumOf struct {
Name string
}
func (e *SumOf) Concrete() bool {
return false
}
func (e *SumOf) Eval() int {
log.Fatalf("Cannot evaluate a 'SumOf'. It is not concrete.")
panic("unreachable")
}
func (e *SumOf) Reduce(prefix string) string {
if len(prefix) > 0 {
return fmt.Sprintf("sum(%s%s)", prefix, e.Name)
}
return fmt.Sprintf("sum(%s)", e.Name)
}
func (e *SumOf) String() string {
return e.Reduce("")
}
func (e *SumOf) Initialize(p *Protocol) {
e.Name = SrcName(p, e.Name)
}
func (e *SumOf) Specialize(path string) Expression {
return e
}

402
nexgb/xgbgen/field.go Normal file
View File

@ -0,0 +1,402 @@
package main
import (
"fmt"
"log"
"strings"
)
// Field corresponds to any field described in an XML protocol description
// file. This includes struct fields, union fields, request fields,
// reply fields and so on.
// To make code generation easier, fields that have types are also stored.
// Note that not all fields support all methods defined in this interface.
// For instance, a padding field does not have a source name.
type Field interface {
// Initialize sets up the source name of this field.
Initialize(p *Protocol)
// SrcName is the Go source name of this field.
SrcName() string
// XmlName is the name of this field from the XML file.
XmlName() string
// SrcType is the Go source type name of this field.
SrcType() string
// Size returns an expression that computes the size (in bytes)
// of this field.
Size() Size
// Define writes the Go code to declare this field (in a struct definition).
Define(c *Context)
// Read writes the Go code to convert a byte slice to a Go value
// of this field.
// 'prefix' is the prefix of the name of the Go value.
Read(c *Context, prefix string)
// Write writes the Go code to convert a Go value to a byte slice of
// this field.
// 'prefix' is the prefix of the name of the Go value.
Write(c *Context, prefix string)
}
func (pad *PadField) Initialize(p *Protocol) {}
// PadField represents any type of padding. It is omitted from
// definitions, but is used in Read/Write to increment the buffer index.
// It is also used in size calculation.
type PadField struct {
Bytes uint
Align uint16
}
func (p *PadField) SrcName() string {
panic("illegal to take source name of a pad field")
}
func (p *PadField) XmlName() string {
panic("illegal to take XML name of a pad field")
}
func (f *PadField) SrcType() string {
panic("it is illegal to call SrcType on a PadField field")
}
func (p *PadField) Size() Size {
if p.Align > 0 {
return newFixedSize(uint(p.Align), false)
} else {
return newFixedSize(p.Bytes, true)
}
}
type RequiredStartAlign struct {
}
func (f *RequiredStartAlign) Initialize(p *Protocol) {}
func (f *RequiredStartAlign) SrcName() string {
panic("illegal to take source name of a required_start_align field")
}
func (f *RequiredStartAlign) XmlName() string {
panic("illegal to take XML name of a required_start_align field")
}
func (f *RequiredStartAlign) SrcType() string {
panic("it is illegal to call SrcType on a required_start_align field")
}
func (f *RequiredStartAlign) Size() Size {
return newFixedSize(0, true)
}
func (f *RequiredStartAlign) Define(c *Context) {}
func (f *RequiredStartAlign) Read(c *Context, prefix string) {}
func (f *RequiredStartAlign) Write(c *Context, prefix string) {}
// SingleField represents most of the fields in an XML protocol description.
// It corresponds to any single value.
type SingleField struct {
srcName string
xmlName string
Type Type
Comment string
}
func (f *SingleField) Initialize(p *Protocol) {
f.srcName = SrcName(p, f.XmlName())
f.Type = f.Type.(*Translation).RealType(p)
}
func (f *SingleField) SrcName() string {
if f.srcName == "Bytes" {
return "Bytes_"
}
return f.srcName
}
func (f *SingleField) XmlName() string {
return f.xmlName
}
func (f *SingleField) SrcType() string {
return f.Type.SrcName()
}
func (f *SingleField) Size() Size {
return f.Type.Size()
}
// ListField represents a list of values.
type ListField struct {
srcName string
xmlName string
Type Type
LengthExpr Expression
}
func (f *ListField) SrcName() string {
return f.srcName
}
func (f *ListField) XmlName() string {
return f.xmlName
}
func (f *ListField) SrcType() string {
if strings.ToLower(f.Type.XmlName()) == "char" {
return fmt.Sprintf("string")
}
return fmt.Sprintf("[]%s", f.Type.SrcName())
}
// Length computes the *number* of values in a list.
// If this ListField does not have any length expression, we throw our hands
// up and simply compute the 'len' of the field name of this list.
func (f *ListField) Length() Size {
if f.LengthExpr == nil {
return newExpressionSize(&Function{
Name: "len",
Expr: &FieldRef{
Name: f.SrcName(),
},
}, true)
}
return newExpressionSize(f.LengthExpr, true)
}
// Size computes the *size* of a list (in bytes).
// It it typically a simple matter of multiplying the length of the list by
// the size of the type of the list.
// But if it's a list of struct where the struct has a list field, we use a
// special function written in go_struct.go to compute the size (since the
// size in this case can only be computed recursively).
func (f *ListField) Size() Size {
elsz := f.Type.Size()
simpleLen := &Padding{
Expr: newBinaryOp("*", f.Length().Expression, elsz.Expression),
}
switch field := f.Type.(type) {
case *Struct:
if field.HasList() {
sizeFun := &Function{
Name: fmt.Sprintf("%sListSize", f.Type.SrcName()),
Expr: &FieldRef{Name: f.SrcName()},
}
return newExpressionSize(sizeFun, elsz.exact)
} else {
return newExpressionSize(simpleLen, elsz.exact)
}
case *Union:
return newExpressionSize(simpleLen, elsz.exact)
case *Base:
return newExpressionSize(simpleLen, elsz.exact)
case *Resource:
return newExpressionSize(simpleLen, elsz.exact)
case *TypeDef:
return newExpressionSize(simpleLen, elsz.exact)
default:
log.Panicf("Cannot compute list size with type '%T'.", f.Type)
}
panic("unreachable")
}
func (f *ListField) Initialize(p *Protocol) {
f.srcName = SrcName(p, f.XmlName())
f.Type = f.Type.(*Translation).RealType(p)
if f.LengthExpr != nil {
f.LengthExpr.Initialize(p)
}
}
// LocalField is exactly the same as a regular SingleField, except it isn't
// sent over the wire. (i.e., it's probably used to compute an ExprField).
type LocalField struct {
*SingleField
}
// ExprField is a field that is not parameterized, but is computed from values
// of other fields.
type ExprField struct {
srcName string
xmlName string
Type Type
Expr Expression
}
func (f *ExprField) SrcName() string {
return f.srcName
}
func (f *ExprField) XmlName() string {
return f.xmlName
}
func (f *ExprField) SrcType() string {
return f.Type.SrcName()
}
func (f *ExprField) Size() Size {
return f.Type.Size()
}
func (f *ExprField) Initialize(p *Protocol) {
f.srcName = SrcName(p, f.XmlName())
f.Type = f.Type.(*Translation).RealType(p)
f.Expr.Initialize(p)
}
// ValueField represents two fields in one: a mask and a list of 4-byte
// integers. The mask specifies which kinds of values are in the list.
// (i.e., See ConfigureWindow, CreateWindow, ChangeWindowAttributes, etc.)
type ValueField struct {
Parent interface{}
MaskType Type
MaskName string
ListName string
MaskComment string
ListComment string
}
func (f *ValueField) SrcName() string {
panic("it is illegal to call SrcName on a ValueField field")
}
func (f *ValueField) XmlName() string {
panic("it is illegal to call XmlName on a ValueField field")
}
func (f *ValueField) SrcType() string {
return f.MaskType.SrcName()
}
// Size computes the size in bytes of the combination of the mask and list
// in this value field.
// The expression to compute this looks complicated, but it's really just
// the number of bits set in the mask multiplied 4 (and padded of course).
func (f *ValueField) Size() Size {
maskSize := f.MaskType.Size()
listSize := newExpressionSize(&Function{
Name: "xgb.Pad",
Expr: &BinaryOp{
Op: "*",
Expr1: &Value{v: 4},
Expr2: &PopCount{
Expr: &Function{
Name: "int",
Expr: &FieldRef{
Name: f.MaskName,
},
},
},
},
}, true)
return maskSize.Add(listSize)
}
func (f *ValueField) ListLength() Size {
return newExpressionSize(&PopCount{
Expr: &Function{
Name: "int",
Expr: &FieldRef{
Name: f.MaskName,
},
},
}, true)
}
func (f *ValueField) Initialize(p *Protocol) {
f.MaskType = f.MaskType.(*Translation).RealType(p)
f.MaskName = SrcName(p, f.MaskName)
f.ListName = SrcName(p, f.ListName)
}
// SwitchField represents a 'switch' element in the XML protocol description
// file.
// Currently we translate this to a slice of uint32 and let the user sort
// through it.
type SwitchField struct {
xmlName string
Name string
MaskName string
Expr Expression
Bitcases []*Bitcase
Comment string
}
func (f *SwitchField) SrcName() string {
return f.Name
}
func (f *SwitchField) XmlName() string {
return f.xmlName
}
func (f *SwitchField) SrcType() string {
return "[]uint32"
}
func (f *SwitchField) Size() Size {
// TODO: size expression used here is not correct unless every element of
// the switch is 32 bit long. This assumption holds for xproto but may not
// hold for other protocols (xkb?)
listSize := newExpressionSize(&Function{
Name: "xgb.Pad",
Expr: &BinaryOp{
Op: "*",
Expr1: &Value{v: 4},
Expr2: &PopCount{
Expr: &Function{
Name: "int",
Expr: &FieldRef{
Name: f.MaskName,
},
},
},
},
}, true)
return listSize
}
func (f *SwitchField) ListLength() Size {
return newExpressionSize(&PopCount{
Expr: &Function{
Name: "int",
Expr: &FieldRef{
Name: f.MaskName,
},
},
}, true)
}
func (f *SwitchField) Initialize(p *Protocol) {
f.xmlName = f.Name
f.Name = SrcName(p, f.Name)
f.Expr.Initialize(p)
fieldref, ok := f.Expr.(*FieldRef)
if !ok {
panic("switch field's expression not a fieldref")
}
f.MaskName = SrcName(p, fieldref.Name)
for _, bitcase := range f.Bitcases {
bitcase.Expr.Initialize(p)
for _, field := range bitcase.Fields {
field.Initialize(p)
}
}
}
// Bitcase represents a single bitcase inside a switch expression.
// It is not currently used. (i.e., it's XKB voodoo.)
type Bitcase struct {
Fields []Field
Expr Expression
}

229
nexgb/xgbgen/go.go Normal file
View File

@ -0,0 +1,229 @@
package main
import (
"fmt"
)
// BaseTypeMap is a map from X base types to Go types.
// X base types should correspond to the smallest set of X types
// that can be used to rewrite ALL X types in terms of Go types.
// That is, if you remove any of the following types, at least one
// XML protocol description will produce an invalid Go program.
// The types on the left *never* show themselves in the source.
var BaseTypeMap = map[string]string{
"CARD8": "byte",
"CARD16": "uint16",
"CARD32": "uint32",
"INT8": "int8",
"INT16": "int16",
"INT32": "int32",
"BYTE": "byte",
"BOOL": "bool",
"float": "float64",
"double": "float64",
"char": "byte",
"void": "byte",
}
// BaseTypeSizes should have precisely the same keys as in BaseTypeMap,
// and the values should correspond to the size of the type in bytes.
var BaseTypeSizes = map[string]uint{
"CARD8": 1,
"CARD16": 2,
"CARD32": 4,
"INT8": 1,
"INT16": 2,
"INT32": 4,
"BYTE": 1,
"BOOL": 1,
"float": 4,
"double": 8,
"char": 1,
"void": 1,
// Id is a special type used to determine the size of all Xid types.
// "Id" is not actually written in the source.
"Id": 4,
}
// TypeMap is a map from types in the XML to type names that is used
// in the functions that follow. Basically, every occurrence of the key
// type is replaced with the value type.
var TypeMap = map[string]string{
"VISUALTYPE": "VisualInfo",
"DEPTH": "DepthInfo",
"SCREEN": "ScreenInfo",
"Setup": "SetupInfo",
}
// NameMap is the same as TypeMap, but for names.
var NameMap = map[string]string{}
// Reading, writing and defining...
// Base types
func (b *Base) Define(c *Context) {
c.Putln("// Skipping definition for base type '%s'",
SrcName(c.protocol, b.XmlName()))
c.Putln("")
}
// Enum types
func (enum *Enum) Define(c *Context) {
c.Putln("const (")
for _, item := range enum.Items {
c.Putln("%s%s = %d", enum.SrcName(), item.srcName, item.Expr.Eval())
}
c.Putln(")")
c.Putln("")
}
// Resource types
func (res *Resource) Define(c *Context) {
c.Putln("type %s uint32", res.SrcName())
c.Putln("")
c.Putln("func New%sId(c *xgb.Conn) (%s, error) {",
res.SrcName(), res.SrcName())
c.Putln("id, err := c.NewId()")
c.Putln("if err != nil {")
c.Putln("return 0, err")
c.Putln("}")
c.Putln("return %s(id), nil", res.SrcName())
c.Putln("}")
c.Putln("")
}
// TypeDef types
func (td *TypeDef) Define(c *Context) {
c.Putln("type %s %s", td.srcName, td.Old.SrcName())
c.Putln("")
}
// Field definitions, reads and writes.
// Pad fields
func (f *PadField) Define(c *Context) {
if f.Align > 0 {
c.Putln("// alignment gap to multiple of %d", f.Align)
} else {
c.Putln("// padding: %d bytes", f.Bytes)
}
}
func (f *PadField) Read(c *Context, prefix string) {
if f.Align > 0 {
c.Putln("b = (b + %d) & ^%d // alignment gap", f.Align-1, f.Align-1)
} else {
c.Putln("b += %s // padding", f.Size())
}
}
func (f *PadField) Write(c *Context, prefix string) {
if f.Align > 0 {
c.Putln("b = (b + %d) & ^%d // alignment gap", f.Align-1, f.Align-1)
} else {
c.Putln("b += %s // padding", f.Size())
}
}
// Local fields
func (f *LocalField) Define(c *Context) {
c.Putln("// local field: %s %s", f.SrcName(), f.Type.SrcName())
panic("unreachable")
}
func (f *LocalField) Read(c *Context, prefix string) {
c.Putln("// reading local field: %s (%s) :: %s",
f.SrcName(), f.Size(), f.Type.SrcName())
panic("unreachable")
}
func (f *LocalField) Write(c *Context, prefix string) {
c.Putln("// skip writing local field: %s (%s) :: %s",
f.SrcName(), f.Size(), f.Type.SrcName())
}
// Expr fields
func (f *ExprField) Define(c *Context) {
c.Putln("// expression field: %s %s (%s)",
f.SrcName(), f.Type.SrcName(), f.Expr)
panic("unreachable")
}
func (f *ExprField) Read(c *Context, prefix string) {
c.Putln("// reading expression field: %s (%s) (%s) :: %s",
f.SrcName(), f.Size(), f.Expr, f.Type.SrcName())
panic("unreachable")
}
func (f *ExprField) Write(c *Context, prefix string) {
// Special case for bools, grrr.
if f.Type.SrcName() == "bool" {
c.Putln("buf[b] = byte(%s)", f.Expr.Reduce(prefix))
c.Putln("b += 1")
} else {
WriteSimpleSingleField(c, f.Expr.Reduce(prefix), f.Type)
}
}
// Value field
func (f *ValueField) Define(c *Context) {
if f.MaskComment != "" {
c.PutComment(f.MaskComment)
}
c.Putln("%s %s", f.MaskName, f.SrcType())
if f.ListComment != "" {
c.PutComment(f.ListComment)
}
c.Putln("%s []uint32", f.ListName)
}
func (f *ValueField) Read(c *Context, prefix string) {
ReadSimpleSingleField(c,
fmt.Sprintf("%s%s", prefix, f.MaskName), f.MaskType)
c.Putln("")
c.Putln("%s%s = make([]uint32, %s)",
prefix, f.ListName, f.ListLength().Reduce(prefix))
c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix))
c.Putln("%s%s[i] = xgb.Get32(buf[b:])", prefix, f.ListName)
c.Putln("b += 4")
c.Putln("}")
c.Putln("b = xgb.Pad(b)")
}
func (f *ValueField) Write(c *Context, prefix string) {
WriteSimpleSingleField(c,
fmt.Sprintf("%s%s", prefix, f.MaskName), f.MaskType)
c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix))
c.Putln("xgb.Put32(buf[b:], %s%s[i])", prefix, f.ListName)
c.Putln("b += 4")
c.Putln("}")
c.Putln("b = xgb.Pad(b)")
}
// Switch field
func (f *SwitchField) Define(c *Context) {
if f.Comment != "" {
c.PutComment(f.Comment)
}
c.Putln("%s []uint32", f.Name)
}
func (f *SwitchField) Read(c *Context, prefix string) {
c.Putln("")
c.Putln("%s%s = make([]uint32, %s)",
prefix, f.Name, f.ListLength().Reduce(prefix))
c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix))
c.Putln("%s%s[i] = xgb.Get32(buf[b:])", prefix, f.Name)
c.Putln("b += 4")
c.Putln("}")
c.Putln("b = xgb.Pad(b)")
}
func (f *SwitchField) Write(c *Context, prefix string) {
c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix))
c.Putln("xgb.Put32(buf[b:], %s%s[i])", prefix, f.Name)
c.Putln("b += 4")
c.Putln("}")
c.Putln("b = xgb.Pad(b)")
}

179
nexgb/xgbgen/go_error.go Normal file
View File

@ -0,0 +1,179 @@
package main
import (
"fmt"
)
// Error types
func (e *Error) Define(c *Context) {
c.Putln("// %s is the error number for a %s.", e.ErrConst(), e.ErrConst())
c.Putln("const %s = %d", e.ErrConst(), e.Number)
c.Putln("")
c.Putln("type %s struct {", e.ErrType())
c.Putln("Sequence uint16")
c.Putln("NiceName string")
for _, field := range e.Fields {
field.Define(c)
}
c.Putln("}")
c.Putln("")
// Read defines a function that transforms a byte slice into this
// error struct.
e.Read(c)
// Makes sure this error type implements the xgb.Error interface.
e.ImplementsError(c)
// Let's the XGB event loop read this error.
c.Putln("func init() {")
if c.protocol.isExt() {
c.Putln("xgb.NewExtErrorFuncs[\"%s\"][%d] = %sNew",
c.protocol.ExtXName, e.Number, e.ErrType())
} else {
c.Putln("xgb.NewErrorFuncs[%d] = %sNew", e.Number, e.ErrType())
}
c.Putln("}")
c.Putln("")
}
func (e *Error) Read(c *Context) {
c.Putln("// %sNew constructs a %s value that implements xgb.Error from "+
"a byte slice.", e.ErrType(), e.ErrType())
c.Putln("func %sNew(buf []byte) xgb.Error {", e.ErrType())
c.Putln("v := %s{}", e.ErrType())
c.Putln("v.NiceName = \"%s\"", e.SrcName())
c.Putln("")
c.Putln("b := 1 // skip error determinant")
c.Putln("b += 1 // don't read error number")
c.Putln("")
c.Putln("v.Sequence = xgb.Get16(buf[b:])")
c.Putln("b += 2")
c.Putln("")
for _, field := range e.Fields {
field.Read(c, "v.")
c.Putln("")
}
c.Putln("return v")
c.Putln("}")
c.Putln("")
}
// ImplementsError writes functions to implement the XGB Error interface.
func (e *Error) ImplementsError(c *Context) {
c.Putln("// SequenceId returns the sequence id attached to the %s error.",
e.ErrConst())
c.Putln("// This is mostly used internally.")
c.Putln("func (err %s) SequenceId() uint16 {", e.ErrType())
c.Putln("return err.Sequence")
c.Putln("}")
c.Putln("")
c.Putln("// BadId returns the 'BadValue' number if one exists for the "+
"%s error. If no bad value exists, 0 is returned.", e.ErrConst())
c.Putln("func (err %s) BadId() uint32 {", e.ErrType())
if !c.protocol.isExt() {
c.Putln("return err.BadValue")
} else {
c.Putln("return 0")
}
c.Putln("}")
c.Putln("// Error returns a rudimentary string representation of the %s "+
"error.", e.ErrConst())
c.Putln("")
c.Putln("func (err %s) Error() string {", e.ErrType())
ErrorFieldString(c, e.Fields, e.ErrConst())
c.Putln("}")
c.Putln("")
}
// ErrorCopy types
func (e *ErrorCopy) Define(c *Context) {
c.Putln("// %s is the error number for a %s.", e.ErrConst(), e.ErrConst())
c.Putln("const %s = %d", e.ErrConst(), e.Number)
c.Putln("")
c.Putln("type %s %s", e.ErrType(), e.Old.(*Error).ErrType())
c.Putln("")
// Read defines a function that transforms a byte slice into this
// error struct.
e.Read(c)
// Makes sure this error type implements the xgb.Error interface.
e.ImplementsError(c)
// Let's the XGB know how to read this error.
c.Putln("func init() {")
if c.protocol.isExt() {
c.Putln("xgb.NewExtErrorFuncs[\"%s\"][%d] = %sNew",
c.protocol.ExtXName, e.Number, e.ErrType())
} else {
c.Putln("xgb.NewErrorFuncs[%d] = %sNew", e.Number, e.ErrType())
}
c.Putln("}")
c.Putln("")
}
func (e *ErrorCopy) Read(c *Context) {
c.Putln("// %sNew constructs a %s value that implements xgb.Error from "+
"a byte slice.", e.ErrType(), e.ErrType())
c.Putln("func %sNew(buf []byte) xgb.Error {", e.ErrType())
c.Putln("v := %s(%sNew(buf).(%s))",
e.ErrType(), e.Old.(*Error).ErrType(), e.Old.(*Error).ErrType())
c.Putln("v.NiceName = \"%s\"", e.SrcName())
c.Putln("return v")
c.Putln("}")
c.Putln("")
}
// ImplementsError writes functions to implement the XGB Error interface.
func (e *ErrorCopy) ImplementsError(c *Context) {
c.Putln("// SequenceId returns the sequence id attached to the %s error.",
e.ErrConst())
c.Putln("// This is mostly used internally.")
c.Putln("func (err %s) SequenceId() uint16 {", e.ErrType())
c.Putln("return err.Sequence")
c.Putln("}")
c.Putln("")
c.Putln("// BadId returns the 'BadValue' number if one exists for the "+
"%s error. If no bad value exists, 0 is returned.", e.ErrConst())
c.Putln("func (err %s) BadId() uint32 {", e.ErrType())
if !c.protocol.isExt() {
c.Putln("return err.BadValue")
} else {
c.Putln("return 0")
}
c.Putln("}")
c.Putln("")
c.Putln("// Error returns a rudimentary string representation of the %s "+
"error.", e.ErrConst())
c.Putln("func (err %s) Error() string {", e.ErrType())
ErrorFieldString(c, e.Old.(*Error).Fields, e.ErrConst())
c.Putln("}")
c.Putln("")
}
// ErrorFieldString works for both Error and ErrorCopy. It assembles all of the
// fields in an error and formats them into a single string.
func ErrorFieldString(c *Context, fields []Field, errName string) {
c.Putln("fieldVals := make([]string, 0, %d)", len(fields))
c.Putln("fieldVals = append(fieldVals, \"NiceName: \" + err.NiceName)")
c.Putln("fieldVals = append(fieldVals, "+
"xgb.Sprintf(\"Sequence: %s\", err.Sequence))", "%d")
for _, field := range fields {
switch field.(type) {
case *PadField:
continue
default:
if field.SrcType() == "string" {
c.Putln("fieldVals = append(fieldVals, \"%s: \" + err.%s)",
field.SrcName(), field.SrcName())
} else {
format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", err.%s)",
field.SrcName(), "%d", field.SrcName())
c.Putln("fieldVals = append(fieldVals, %s)", format)
}
}
}
c.Putln("return \"%s {\" + xgb.StringsJoin(fieldVals, \", \") + \"}\"",
errName)
}

209
nexgb/xgbgen/go_event.go Normal file
View File

@ -0,0 +1,209 @@
package main
import (
"fmt"
)
// Event types
func (e *Event) Define(c *Context) {
c.Putln("// %s is the event number for a %s.", e.SrcName(), e.EvType())
c.Putln("const %s = %d", e.SrcName(), e.Number)
c.Putln("")
c.Putln("type %s struct {", e.EvType())
if !e.NoSequence {
c.Putln("Sequence uint16")
}
for _, field := range e.Fields {
field.Define(c)
}
c.Putln("}")
c.Putln("")
// Read defines a function that transforms a byte slice into this
// event struct.
e.Read(c)
// Write defines a function that transforms this event struct into
// a byte slice.
e.Write(c)
// Makes sure that this event type is an Event interface.
c.Putln("// SequenceId returns the sequence id attached to the %s event.",
e.SrcName())
c.Putln("// Events without a sequence number (KeymapNotify) return 0.")
c.Putln("// This is mostly used internally.")
c.Putln("func (v %s) SequenceId() uint16 {", e.EvType())
if e.NoSequence {
c.Putln("return uint16(0)")
} else {
c.Putln("return v.Sequence")
}
c.Putln("}")
c.Putln("")
c.Putln("// String is a rudimentary string representation of %s.",
e.EvType())
c.Putln("func (v %s) String() string {", e.EvType())
EventFieldString(c, e.Fields, e.SrcName())
c.Putln("}")
c.Putln("")
// Let's the XGB event loop read this event.
c.Putln("func init() {")
if c.protocol.isExt() {
c.Putln("xgb.NewExtEventFuncs[\"%s\"][%d] = %sNew",
c.protocol.ExtXName, e.Number, e.EvType())
} else {
c.Putln("xgb.NewEventFuncs[%d] = %sNew", e.Number, e.EvType())
}
c.Putln("}")
c.Putln("")
}
func (e *Event) Read(c *Context) {
c.Putln("// %sNew constructs a %s value that implements xgb.Event from "+
"a byte slice.", e.EvType(), e.EvType())
c.Putln("func %sNew(buf []byte) xgb.Event {", e.EvType())
c.Putln("v := %s{}", e.EvType())
c.Putln("b := 1 // don't read event number")
c.Putln("")
for i, field := range e.Fields {
if i == 1 && !e.NoSequence {
c.Putln("v.Sequence = xgb.Get16(buf[b:])")
c.Putln("b += 2")
c.Putln("")
}
field.Read(c, "v.")
c.Putln("")
}
c.Putln("return v")
c.Putln("}")
c.Putln("")
}
func (e *Event) Write(c *Context) {
c.Putln("// Bytes writes a %s value to a byte slice.", e.EvType())
c.Putln("func (v %s) Bytes() []byte {", e.EvType())
c.Putln("buf := make([]byte, %s)", e.Size())
c.Putln("b := 0")
c.Putln("")
c.Putln("// write event number")
c.Putln("buf[b] = %d", e.Number)
c.Putln("b += 1")
c.Putln("")
for i, field := range e.Fields {
if i == 1 && !e.NoSequence {
c.Putln("b += 2 // skip sequence number")
c.Putln("")
}
field.Write(c, "v.")
c.Putln("")
}
c.Putln("return buf")
c.Putln("}")
c.Putln("")
}
// EventCopy types
func (e *EventCopy) Define(c *Context) {
c.Putln("// %s is the event number for a %s.", e.SrcName(), e.EvType())
c.Putln("const %s = %d", e.SrcName(), e.Number)
c.Putln("")
c.Putln("type %s %s", e.EvType(), e.Old.(*Event).EvType())
c.Putln("")
// Read defines a function that transforms a byte slice into this
// event struct.
e.Read(c)
// Write defines a function that transoforms this event struct into
// a byte slice.
e.Write(c)
// Makes sure that this event type is an Event interface.
c.Putln("// SequenceId returns the sequence id attached to the %s event.",
e.SrcName())
c.Putln("// Events without a sequence number (KeymapNotify) return 0.")
c.Putln("// This is mostly used internally.")
c.Putln("func (v %s) SequenceId() uint16 {", e.EvType())
if e.Old.(*Event).NoSequence {
c.Putln("return uint16(0)")
} else {
c.Putln("return v.Sequence")
}
c.Putln("}")
c.Putln("")
c.Putln("func (v %s) String() string {", e.EvType())
EventFieldString(c, e.Old.(*Event).Fields, e.SrcName())
c.Putln("}")
c.Putln("")
// Let's the XGB event loop read this event.
c.Putln("func init() {")
if c.protocol.isExt() {
c.Putln("xgb.NewExtEventFuncs[\"%s\"][%d] = %sNew",
c.protocol.ExtXName, e.Number, e.EvType())
} else {
c.Putln("xgb.NewEventFuncs[%d] = %sNew", e.Number, e.EvType())
}
c.Putln("}")
c.Putln("")
}
func (e *EventCopy) Read(c *Context) {
c.Putln("// %sNew constructs a %s value that implements xgb.Event from "+
"a byte slice.", e.EvType(), e.EvType())
c.Putln("func %sNew(buf []byte) xgb.Event {", e.EvType())
c.Putln("return %s(%sNew(buf).(%s))",
e.EvType(), e.Old.(*Event).EvType(), e.Old.(*Event).EvType())
c.Putln("}")
c.Putln("")
}
func (e *EventCopy) Write(c *Context) {
c.Putln("// Bytes writes a %s value to a byte slice.", e.EvType())
c.Putln("func (v %s) Bytes() []byte {", e.EvType())
c.Putln("return %s(v).Bytes()", e.Old.(*Event).EvType())
c.Putln("}")
c.Putln("")
}
// EventFieldString works for both Event and EventCopy. It assembles all of the
// fields in an event and formats them into a single string.
func EventFieldString(c *Context, fields []Field, evName string) {
c.Putln("fieldVals := make([]string, 0, %d)", len(fields))
if evName != "KeymapNotify" {
c.Putln("fieldVals = append(fieldVals, "+
"xgb.Sprintf(\"Sequence: %s\", v.Sequence))", "%d")
}
for _, field := range fields {
switch f := field.(type) {
case *PadField:
continue
case *SingleField:
switch f.Type.(type) {
case *Base:
case *Resource:
case *TypeDef:
default:
continue
}
switch field.SrcType() {
case "string":
format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", v.%s)",
field.SrcName(), "%s", field.SrcName())
c.Putln("fieldVals = append(fieldVals, %s)", format)
case "bool":
format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", v.%s)",
field.SrcName(), "%t", field.SrcName())
c.Putln("fieldVals = append(fieldVals, %s)", format)
default:
format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", v.%s)",
field.SrcName(), "%d", field.SrcName())
c.Putln("fieldVals = append(fieldVals, %s)", format)
}
}
}
c.Putln("return \"%s {\" + xgb.StringsJoin(fieldVals, \", \") + \"}\"",
evName)
}

107
nexgb/xgbgen/go_list.go Normal file
View File

@ -0,0 +1,107 @@
package main
import (
"fmt"
"log"
"strings"
)
// List fields
func (f *ListField) Define(c *Context) {
c.Putln("%s %s // size: %s",
f.SrcName(), f.SrcType(), f.Size())
}
func (f *ListField) Read(c *Context, prefix string) {
switch t := f.Type.(type) {
case *Resource:
length := f.LengthExpr.Reduce(prefix)
c.Putln("%s%s = make([]%s, %s)",
prefix, f.SrcName(), t.SrcName(), length)
c.Putln("for i := 0; i < int(%s); i++ {", length)
ReadSimpleSingleField(c, fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t)
c.Putln("}")
case *Base:
length := f.LengthExpr.Reduce(prefix)
if strings.ToLower(t.XmlName()) == "char" {
c.Putln("{")
c.Putln("byteString := make([]%s, %s)", t.SrcName(), length)
c.Putln("copy(byteString[:%s], buf[b:])", length)
c.Putln("%s%s = string(byteString)", prefix, f.SrcName())
// This is apparently a special case. The "Str" type itself
// doesn't specify any padding. I suppose it's up to the
// request/reply spec that uses it to get the padding right?
c.Putln("b += int(%s)", length)
c.Putln("}")
} else if t.SrcName() == "byte" {
c.Putln("%s%s = make([]%s, %s)",
prefix, f.SrcName(), t.SrcName(), length)
c.Putln("copy(%s%s[:%s], buf[b:])", prefix, f.SrcName(), length)
c.Putln("b += int(%s)", length)
} else {
c.Putln("%s%s = make([]%s, %s)",
prefix, f.SrcName(), t.SrcName(), length)
c.Putln("for i := 0; i < int(%s); i++ {", length)
ReadSimpleSingleField(c,
fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t)
c.Putln("}")
}
case *TypeDef:
length := f.LengthExpr.Reduce(prefix)
c.Putln("%s%s = make([]%s, %s)",
prefix, f.SrcName(), t.SrcName(), length)
c.Putln("for i := 0; i < int(%s); i++ {", length)
ReadSimpleSingleField(c, fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t)
c.Putln("}")
case *Union:
c.Putln("%s%s = make([]%s, %s)",
prefix, f.SrcName(), t.SrcName(), f.LengthExpr.Reduce(prefix))
c.Putln("b += %sReadList(buf[b:], %s%s)",
t.SrcName(), prefix, f.SrcName())
case *Struct:
c.Putln("%s%s = make([]%s, %s)",
prefix, f.SrcName(), t.SrcName(), f.LengthExpr.Reduce(prefix))
c.Putln("b += %sReadList(buf[b:], %s%s)",
t.SrcName(), prefix, f.SrcName())
default:
log.Panicf("Cannot read list field '%s' with %T type.",
f.XmlName(), f.Type)
}
}
func (f *ListField) Write(c *Context, prefix string) {
switch t := f.Type.(type) {
case *Resource:
length := f.Length().Reduce(prefix)
c.Putln("for i := 0; i < int(%s); i++ {", length)
WriteSimpleSingleField(c,
fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t)
c.Putln("}")
case *Base:
length := f.Length().Reduce(prefix)
if t.SrcName() == "byte" {
c.Putln("copy(buf[b:], %s%s[:%s])", prefix, f.SrcName(), length)
c.Putln("b += int(%s)", length)
} else {
c.Putln("for i := 0; i < int(%s); i++ {", length)
WriteSimpleSingleField(c,
fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t)
c.Putln("}")
}
case *TypeDef:
length := f.Length().Reduce(prefix)
c.Putln("for i := 0; i < int(%s); i++ {", length)
WriteSimpleSingleField(c,
fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t)
c.Putln("}")
case *Union:
c.Putln("b += %sListBytes(buf[b:], %s%s)",
t.SrcName(), prefix, f.SrcName())
case *Struct:
c.Putln("b += %sListBytes(buf[b:], %s%s)",
t.SrcName(), prefix, f.SrcName())
default:
log.Panicf("Cannot write list field '%s' with %T type.",
f.XmlName(), f.Type)
}
}

View File

@ -0,0 +1,288 @@
package main
import (
"fmt"
"sort"
"strings"
)
func (r *Request) Define(c *Context) {
c.Putln("// %s is a cookie used only for %s requests.",
r.CookieName(), r.SrcName())
c.Putln("type %s struct {", r.CookieName())
c.Putln("*xgb.Cookie")
c.Putln("}")
c.Putln("")
if r.Doc.Description != "" {
c.PutComment(r.Doc.Description)
c.Putln("//")
}
allErrors := make([]string, 0, len(r.Doc.Errors))
for kind := range r.Doc.Errors {
allErrors = append(allErrors, kind)
}
sort.Strings(allErrors)
undocErrors := make([]string, 0)
for _, kind := range allErrors {
if desc := r.Doc.Errors[kind]; desc == "" {
undocErrors = append(undocErrors, kind)
} else {
c.PutComment(fmt.Sprintf("May return a %s error if %s%s", kind,
strings.ToLower(desc[:1]), desc[1:]))
c.Putln("//")
}
}
if len(undocErrors) > 0 {
c.Putln("// May return %s errors.", strings.Join(undocErrors, ", "))
c.Putln("//")
}
if r.Reply != nil {
c.Putln("// %s sends a checked request.", r.SrcName())
c.Putln("// If an error occurs, it will be returned with the reply "+
"by calling %s.Reply.", r.CookieName())
c.Putln("func %s(c *xgb.Conn, %s) %s {",
r.SrcName(), r.ParamNameTypes(), r.CookieName())
r.CheckExt(c)
c.Putln("cookie := c.NewCookie(true, true)")
c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames())
c.Putln("return %s{cookie}", r.CookieName())
c.Putln("}")
c.Putln("")
c.Putln("// %sUnchecked sends an unchecked request.", r.SrcName())
c.Putln("// If an error occurs, it can only be retrieved using " +
"xgb.WaitForEvent or xgb.PollForEvent.")
c.Putln("func %sUnchecked(c *xgb.Conn, %s) %s {",
r.SrcName(), r.ParamNameTypes(), r.CookieName())
r.CheckExt(c)
c.Putln("cookie := c.NewCookie(false, true)")
c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames())
c.Putln("return %s{cookie}", r.CookieName())
c.Putln("}")
c.Putln("")
r.ReadReply(c)
} else {
c.Putln("// %s sends an unchecked request.", r.SrcName())
c.Putln("// If an error occurs, it can only be retrieved using " +
"xgb.WaitForEvent or xgb.PollForEvent.")
c.Putln("func %s(c *xgb.Conn, %s) %s {",
r.SrcName(), r.ParamNameTypes(), r.CookieName())
r.CheckExt(c)
c.Putln("cookie := c.NewCookie(false, false)")
c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames())
c.Putln("return %s{cookie}", r.CookieName())
c.Putln("}")
c.Putln("")
c.Putln("// %sChecked sends a checked request.", r.SrcName())
c.Putln("// If an error occurs, it can be retrieved using "+
"%s.Check.", r.CookieName())
c.Putln("func %sChecked(c *xgb.Conn, %s) %s {",
r.SrcName(), r.ParamNameTypes(), r.CookieName())
r.CheckExt(c)
c.Putln("cookie := c.NewCookie(true, false)")
c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames())
c.Putln("return %s{cookie}", r.CookieName())
c.Putln("}")
c.Putln("")
c.Putln("// Check returns an error if one occurred for checked " +
"requests that are not expecting a reply.")
c.Putln("// This cannot be called for requests expecting a reply, " +
"nor for unchecked requests.")
c.Putln("func (cook %s) Check() error {", r.CookieName())
c.Putln("return cook.Cookie.Check()")
c.Putln("}")
c.Putln("")
}
r.WriteRequest(c)
}
func (r *Request) CheckExt(c *Context) {
if !c.protocol.isExt() {
return
}
c.Putln("c.ExtLock.RLock()")
c.Putln("defer c.ExtLock.RUnlock()")
c.Putln("if _, ok := c.Extensions[\"%s\"]; !ok {", c.protocol.ExtXName)
c.Putln("panic(\"Cannot issue request '%s' using the uninitialized "+
"extension '%s'. %s.Init(connObj) must be called first.\")",
r.SrcName(), c.protocol.ExtXName, c.protocol.PkgName())
c.Putln("}")
}
func (r *Request) ReadReply(c *Context) {
c.Putln("// %s represents the data returned from a %s request.",
r.ReplyTypeName(), r.SrcName())
c.Putln("type %s struct {", r.ReplyTypeName())
c.Putln("Sequence uint16 // sequence number of the request for this reply")
c.Putln("Length uint32 // number of bytes in this reply")
for _, field := range r.Reply.Fields {
field.Define(c)
}
c.Putln("}")
c.Putln("")
c.Putln("// Reply blocks and returns the reply data for a %s request.",
r.SrcName())
c.Putln("func (cook %s) Reply() (*%s, error) {",
r.CookieName(), r.ReplyTypeName())
c.Putln("buf, err := cook.Cookie.Reply()")
c.Putln("if err != nil {")
c.Putln("return nil, err")
c.Putln("}")
c.Putln("if buf == nil {")
c.Putln("return nil, nil")
c.Putln("}")
c.Putln("return %s(buf), nil", r.ReplyName())
c.Putln("}")
c.Putln("")
c.Putln("// %s reads a byte slice into a %s value.",
r.ReplyName(), r.ReplyTypeName())
c.Putln("func %s(buf []byte) *%s {",
r.ReplyName(), r.ReplyTypeName())
c.Putln("v := new(%s)", r.ReplyTypeName())
c.Putln("b := 1 // skip reply determinant")
c.Putln("")
for i, field := range r.Reply.Fields {
field.Read(c, "v.")
c.Putln("")
if i == 0 {
c.Putln("v.Sequence = xgb.Get16(buf[b:])")
c.Putln("b += 2")
c.Putln("")
c.Putln("v.Length = xgb.Get32(buf[b:]) // 4-byte units")
c.Putln("b += 4")
c.Putln("")
}
}
c.Putln("return v")
c.Putln("}")
c.Putln("")
}
func (r *Request) WriteRequest(c *Context) {
sz := r.Size(c)
writeSize1 := func() {
if sz.exact {
c.Putln("xgb.Put16(buf[b:], uint16(size / 4)) " +
"// write request size in 4-byte units")
} else {
c.Putln("blen := b")
}
c.Putln("b += 2")
c.Putln("")
}
writeSize2 := func() {
if sz.exact {
c.Putln("return buf")
return
}
c.Putln("b = xgb.Pad(b)")
c.Putln("xgb.Put16(buf[blen:], uint16(b / 4)) " +
"// write request size in 4-byte units")
c.Putln("return buf[:b]")
}
c.Putln("// %s writes a %s request to a byte slice for transfer.",
r.ReqName(), r.SrcName())
c.Putln("func %s(c *xgb.Conn, %s) []byte {",
r.ReqName(), r.ParamNameTypes())
c.Putln("size := %s", sz)
c.Putln("b := 0")
c.Putln("buf := make([]byte, size)")
c.Putln("")
if c.protocol.isExt() {
c.Putln("c.ExtLock.RLock()")
c.Putln("buf[b] = c.Extensions[\"%s\"]", c.protocol.ExtXName)
c.Putln("c.ExtLock.RUnlock()")
c.Putln("b += 1")
c.Putln("")
}
c.Putln("buf[b] = %d // request opcode", r.Opcode)
c.Putln("b += 1")
c.Putln("")
if len(r.Fields) == 0 {
if !c.protocol.isExt() {
c.Putln("b += 1 // padding")
}
writeSize1()
} else if c.protocol.isExt() {
writeSize1()
}
for i, field := range r.Fields {
field.Write(c, "")
c.Putln("")
if i == 0 && !c.protocol.isExt() {
writeSize1()
}
}
writeSize2()
c.Putln("}")
c.Putln("")
}
func (r *Request) ParamNames() string {
names := make([]string, 0, len(r.Fields))
for _, field := range r.Fields {
switch f := field.(type) {
case *ValueField:
names = append(names, f.MaskName)
names = append(names, f.ListName)
case *PadField:
continue
case *ExprField:
continue
default:
names = append(names, fmt.Sprintf("%s", field.SrcName()))
}
}
return strings.Join(names, ", ")
}
func (r *Request) ParamNameTypes() string {
nameTypes := make([]string, 0, len(r.Fields))
chunk := make([]string, 0)
chunkType := ""
flushChunk := func() {
if len(chunk) > 0 {
nameTypes = append(nameTypes, chunk[:len(chunk)-1]...)
nameTypes = append(nameTypes,
fmt.Sprintf("%s %s", chunk[len(chunk)-1], chunkType))
}
chunk = nil
chunkType = ""
}
for _, field := range r.Fields {
switch f := field.(type) {
case *ValueField:
flushChunk()
nameTypes = append(nameTypes,
fmt.Sprintf("%s %s", f.MaskName, f.MaskType.SrcName()))
nameTypes = append(nameTypes,
fmt.Sprintf("%s []uint32", f.ListName))
case *PadField:
continue
case *ExprField:
continue
case *RequiredStartAlign:
continue
default:
curType := field.SrcType()
if curType != chunkType {
flushChunk()
}
chunk = append(chunk, field.SrcName())
chunkType = curType
}
}
flushChunk()
return strings.Join(nameTypes, ", ")
}

View File

@ -0,0 +1,169 @@
package main
import (
"fmt"
"log"
)
func (f *SingleField) Define(c *Context) {
if f.Comment != "" {
c.PutComment(f.Comment)
}
c.Putln("%s %s", f.SrcName(), f.Type.SrcName())
}
func ReadSimpleSingleField(c *Context, name string, typ Type) {
switch t := typ.(type) {
case *Resource:
c.Putln("%s = %s(xgb.Get32(buf[b:]))", name, t.SrcName())
case *TypeDef:
switch t.Size().Eval() {
case 1:
c.Putln("%s = %s(buf[b])", name, t.SrcName())
case 2:
c.Putln("%s = %s(xgb.Get16(buf[b:]))", name, t.SrcName())
case 4:
c.Putln("%s = %s(xgb.Get32(buf[b:]))", name, t.SrcName())
case 8:
c.Putln("%s = %s(xgb.Get64(buf[b:]))", name, t.SrcName())
}
case *Base:
// If this is a bool, stop short and do something special.
if t.SrcName() == "bool" {
c.Putln("if buf[b] == 1 {")
c.Putln("%s = true", name)
c.Putln("} else {")
c.Putln("%s = false", name)
c.Putln("}")
break
}
var val string
switch t.Size().Eval() {
case 1:
val = fmt.Sprintf("buf[b]")
case 2:
val = fmt.Sprintf("xgb.Get16(buf[b:])")
case 4:
val = fmt.Sprintf("xgb.Get32(buf[b:])")
case 8:
val = fmt.Sprintf("xgb.Get64(buf[b:])")
}
// We need to convert base types if they aren't uintXX or byte
ty := t.SrcName()
if ty != "byte" && ty != "uint16" && ty != "uint32" && ty != "uint64" {
val = fmt.Sprintf("%s(%s)", ty, val)
}
c.Putln("%s = %s", name, val)
default:
log.Panicf("Cannot read field '%s' as a simple field with %T type.",
name, typ)
}
c.Putln("b += %s", typ.Size())
}
func (f *SingleField) Read(c *Context, prefix string) {
switch t := f.Type.(type) {
case *Resource:
ReadSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t)
case *TypeDef:
ReadSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t)
case *Base:
ReadSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t)
case *Struct:
c.Putln("%s%s = %s{}", prefix, f.SrcName(), t.SrcName())
c.Putln("b += %sRead(buf[b:], &%s%s)", t.SrcName(), prefix, f.SrcName())
case *Union:
c.Putln("%s%s = %s{}", prefix, f.SrcName(), t.SrcName())
c.Putln("b += %sRead(buf[b:], &%s%s)", t.SrcName(), prefix, f.SrcName())
default:
log.Panicf("Cannot read field '%s' with %T type.", f.XmlName(), f.Type)
}
}
func WriteSimpleSingleField(c *Context, name string, typ Type) {
switch t := typ.(type) {
case *Resource:
c.Putln("xgb.Put32(buf[b:], uint32(%s))", name)
case *TypeDef:
switch t.Size().Eval() {
case 1:
c.Putln("buf[b] = byte(%s)", name)
case 2:
c.Putln("xgb.Put16(buf[b:], uint16(%s))", name)
case 4:
c.Putln("xgb.Put32(buf[b:], uint32(%s))", name)
case 8:
c.Putln("xgb.Put64(buf[b:], uint64(%s))", name)
}
case *Base:
// If this is a bool, stop short and do something special.
if t.SrcName() == "bool" {
c.Putln("if %s {", name)
c.Putln("buf[b] = 1")
c.Putln("} else {")
c.Putln("buf[b] = 0")
c.Putln("}")
break
}
switch t.Size().Eval() {
case 1:
if t.SrcName() != "byte" {
c.Putln("buf[b] = byte(%s)", name)
} else {
c.Putln("buf[b] = %s", name)
}
case 2:
if t.SrcName() != "uint16" {
c.Putln("xgb.Put16(buf[b:], uint16(%s))", name)
} else {
c.Putln("xgb.Put16(buf[b:], %s)", name)
}
case 4:
if t.SrcName() != "uint32" {
c.Putln("xgb.Put32(buf[b:], uint32(%s))", name)
} else {
c.Putln("xgb.Put32(buf[b:], %s)", name)
}
case 8:
if t.SrcName() != "uint64" {
c.Putln("xgb.Put64(buf[b:], uint64(%s))", name)
} else {
c.Putln("xgb.Put64(buf[b:], %s)", name)
}
}
default:
log.Fatalf("Cannot read field '%s' as a simple field with %T type.",
name, typ)
}
c.Putln("b += %s", typ.Size())
}
func (f *SingleField) Write(c *Context, prefix string) {
switch t := f.Type.(type) {
case *Resource:
WriteSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t)
case *TypeDef:
WriteSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t)
case *Base:
WriteSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t)
case *Union:
c.Putln("{")
c.Putln("unionBytes := %s%s.Bytes()", prefix, f.SrcName())
c.Putln("copy(buf[b:], unionBytes)")
c.Putln("b += len(unionBytes)")
c.Putln("}")
case *Struct:
c.Putln("{")
c.Putln("structBytes := %s%s.Bytes()", prefix, f.SrcName())
c.Putln("copy(buf[b:], structBytes)")
c.Putln("b += len(structBytes)")
c.Putln("}")
default:
log.Fatalf("Cannot read field '%s' with %T type.", f.XmlName(), f.Type)
}
}

118
nexgb/xgbgen/go_struct.go Normal file
View File

@ -0,0 +1,118 @@
package main
func (s *Struct) Define(c *Context) {
c.Putln("type %s struct {", s.SrcName())
for _, field := range s.Fields {
field.Define(c)
}
c.Putln("}")
c.Putln("")
// Write function that reads bytes and produces this struct.
s.Read(c)
// Write function that reads bytes and produces a list of this struct.
s.ReadList(c)
// Write function that writes bytes given this struct.
s.Write(c)
// Write function that writes a list of this struct.
s.WriteList(c)
// Write function that computes the size of a list of these structs,
// IF there is a list field in this struct.
if s.HasList() {
s.WriteListSize(c)
}
}
// Read for a struct creates a function 'ReadStructName' that takes a source
// byte slice (i.e., the buffer) and a destination struct, and returns
// the number of bytes read off the buffer.
// 'ReadStructName' should only be used to read raw reply data from the wire.
func (s *Struct) Read(c *Context) {
c.Putln("// %sRead reads a byte slice into a %s value.",
s.SrcName(), s.SrcName())
c.Putln("func %sRead(buf []byte, v *%s) int {", s.SrcName(), s.SrcName())
c.Putln("b := 0")
c.Putln("")
for _, field := range s.Fields {
field.Read(c, "v.")
c.Putln("")
}
c.Putln("return b")
c.Putln("}")
c.Putln("")
}
// ReadList for a struct creates a function 'ReadStructNameList' that takes
// a source (i.e., the buffer) byte slice, and a destination slice and returns
// the number of bytes read from the byte slice.
func (s *Struct) ReadList(c *Context) {
c.Putln("// %sReadList reads a byte slice into a list of %s values.",
s.SrcName(), s.SrcName())
c.Putln("func %sReadList(buf []byte, dest []%s) int {",
s.SrcName(), s.SrcName())
c.Putln("b := 0")
c.Putln("for i := 0; i < len(dest); i++ {")
c.Putln("dest[i] = %s{}", s.SrcName())
c.Putln("b += %sRead(buf[b:], &dest[i])", s.SrcName())
c.Putln("}")
c.Putln("return xgb.Pad(b)")
c.Putln("}")
c.Putln("")
}
func (s *Struct) Write(c *Context) {
c.Putln("// Bytes writes a %s value to a byte slice.", s.SrcName())
c.Putln("func (v %s) Bytes() []byte {", s.SrcName())
c.Putln("buf := make([]byte, %s)", s.Size().Reduce("v."))
c.Putln("b := 0")
c.Putln("")
for _, field := range s.Fields {
field.Write(c, "v.")
c.Putln("")
}
c.Putln("return buf[:b]")
c.Putln("}")
c.Putln("")
}
func (s *Struct) WriteList(c *Context) {
c.Putln("// %sListBytes writes a list of %s values to a byte slice.",
s.SrcName(), s.SrcName())
c.Putln("func %sListBytes(buf []byte, list []%s) int {",
s.SrcName(), s.SrcName())
c.Putln("b := 0")
c.Putln("var structBytes []byte")
c.Putln("for _, item := range list {")
c.Putln("structBytes = item.Bytes()")
c.Putln("copy(buf[b:], structBytes)")
c.Putln("b += len(structBytes)")
c.Putln("}")
c.Putln("return xgb.Pad(b)")
c.Putln("}")
c.Putln("")
}
func (s *Struct) WriteListSize(c *Context) {
c.Putln("// %sListSize computes the size (bytes) of a list of %s values.",
s.SrcName(), s.SrcName())
c.Putln("func %sListSize(list []%s) int {", s.SrcName(), s.SrcName())
c.Putln("size := 0")
if s.Size().Expression.Concrete() {
c.Putln("for _ = range list {")
} else {
c.Putln("for _, item := range list {")
}
c.Putln("size += %s", s.Size().Reduce("item."))
c.Putln("}")
c.Putln("return size")
c.Putln("}")
c.Putln("")
}

147
nexgb/xgbgen/go_union.go Normal file
View File

@ -0,0 +1,147 @@
package main
// Union types
func (u *Union) Define(c *Context) {
c.Putln("// %s is a represention of the %s union type.",
u.SrcName(), u.SrcName())
c.Putln("// Note that to *create* a Union, you should *never* create")
c.Putln("// this struct directly (unless you know what you're doing).")
c.Putln("// Instead use one of the following constructors for '%s':",
u.SrcName())
for _, field := range u.Fields {
c.Putln("// %s%sNew(%s %s) %s", u.SrcName(), field.SrcName(),
field.SrcName(), field.SrcType(), u.SrcName())
}
c.Putln("type %s struct {", u.SrcName())
for _, field := range u.Fields {
field.Define(c)
}
c.Putln("}")
c.Putln("")
// Write functions for each field that create instances of this
// union using the corresponding field.
u.New(c)
// Write function that reads bytes and produces this union.
u.Read(c)
// Write function that reads bytes and produces a list of this union.
u.ReadList(c)
// Write function that writes bytes given this union.
u.Write(c)
// Write function that writes a list of this union.
u.WriteList(c)
}
func (u *Union) New(c *Context) {
for _, field := range u.Fields {
c.Putln("// %s%sNew constructs a new %s union type with the %s field.",
u.SrcName(), field.SrcName(), u.SrcName(), field.SrcName())
c.Putln("func %s%sNew(%s %s) %s {",
u.SrcName(), field.SrcName(), field.SrcName(),
field.SrcType(), u.SrcName())
c.Putln("var b int")
c.Putln("buf := make([]byte, %s)", u.Size())
c.Putln("")
field.Write(c, "")
c.Putln("")
c.Putln("// Create the Union type")
c.Putln("v := %s{}", u.SrcName())
c.Putln("")
c.Putln("// Now copy buf into all fields")
c.Putln("")
for _, field2 := range u.Fields {
c.Putln("b = 0 // always read the same bytes")
field2.Read(c, "v.")
c.Putln("")
}
c.Putln("return v")
c.Putln("}")
c.Putln("")
}
}
func (u *Union) Read(c *Context) {
c.Putln("// %sRead reads a byte slice into a %s value.",
u.SrcName(), u.SrcName())
c.Putln("func %sRead(buf []byte, v *%s) int {", u.SrcName(), u.SrcName())
c.Putln("var b int")
c.Putln("")
for _, field := range u.Fields {
c.Putln("b = 0 // re-read the same bytes")
field.Read(c, "v.")
c.Putln("")
}
c.Putln("return %s", u.Size())
c.Putln("}")
c.Putln("")
}
func (u *Union) ReadList(c *Context) {
c.Putln("// %sReadList reads a byte slice into a list of %s values.",
u.SrcName(), u.SrcName())
c.Putln("func %sReadList(buf []byte, dest []%s) int {",
u.SrcName(), u.SrcName())
c.Putln("b := 0")
c.Putln("for i := 0; i < len(dest); i++ {")
c.Putln("dest[i] = %s{}", u.SrcName())
c.Putln("b += %sRead(buf[b:], &dest[i])", u.SrcName())
c.Putln("}")
c.Putln("return xgb.Pad(b)")
c.Putln("}")
c.Putln("")
}
// This is a bit tricky since writing from a Union implies that only
// the data inside ONE of the elements is actually written.
// However, we only currently support unions where every field has the
// *same* *fixed* size. Thus, we make sure to always read bytes into
// every field which allows us to simply pick the first field and write it.
func (u *Union) Write(c *Context) {
c.Putln("// Bytes writes a %s value to a byte slice.", u.SrcName())
c.Putln("// Each field in a union must contain the same data.")
c.Putln("// So simply pick the first field and write that to the wire.")
c.Putln("func (v %s) Bytes() []byte {", u.SrcName())
c.Putln("buf := make([]byte, %s)", u.Size().Reduce("v."))
c.Putln("b := 0")
c.Putln("")
u.Fields[0].Write(c, "v.")
c.Putln("return buf")
c.Putln("}")
c.Putln("")
}
func (u *Union) WriteList(c *Context) {
c.Putln("// %sListBytes writes a list of %s values to a byte slice.",
u.SrcName(), u.SrcName())
c.Putln("func %sListBytes(buf []byte, list []%s) int {",
u.SrcName(), u.SrcName())
c.Putln("b := 0")
c.Putln("var unionBytes []byte")
c.Putln("for _, item := range list {")
c.Putln("unionBytes = item.Bytes()")
c.Putln("copy(buf[b:], unionBytes)")
c.Putln("b += xgb.Pad(len(unionBytes))")
c.Putln("}")
c.Putln("return b")
c.Putln("}")
c.Putln("")
}
func (u *Union) WriteListSize(c *Context) {
c.Putln("// Union list size %s", u.SrcName())
c.Putln("// %sListSize computes the size (bytes) of a list of %s values.",
u.SrcName())
c.Putln("func %sListSize(list []%s) int {", u.SrcName(), u.SrcName())
c.Putln("size := 0")
c.Putln("for _, item := range list {")
c.Putln("size += %s", u.Size().Reduce("item."))
c.Putln("}")
c.Putln("return size")
c.Putln("}")
c.Putln("")
}

64
nexgb/xgbgen/main.go Normal file
View File

@ -0,0 +1,64 @@
package main
import (
"flag"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
)
var (
protoPath = flag.String("proto-path",
"/usr/share/xcb", "path to directory of X protocol XML files")
gofmt = flag.Bool("gofmt", true,
"When disabled, gofmt will not be run before outputting Go code")
)
func usage() {
basename := os.Args[0]
if lastSlash := strings.LastIndex(basename, "/"); lastSlash > -1 {
basename = basename[lastSlash+1:]
}
log.Printf("Usage: %s [flags] xml-file", basename)
flag.PrintDefaults()
os.Exit(1)
}
func init() {
log.SetFlags(0)
}
func main() {
flag.Usage = usage
flag.Parse()
if flag.NArg() != 1 {
log.Printf("A single XML protocol file can be processed at once.")
flag.Usage()
}
// Read the single XML file into []byte
xmlBytes, err := ioutil.ReadFile(flag.Arg(0))
if err != nil {
log.Fatal(err)
}
// Initialize the buffer, parse it, and filter it through gofmt.
c := newContext()
c.Morph(xmlBytes)
if !*gofmt {
c.out.WriteTo(os.Stdout)
} else {
cmdGofmt := exec.Command("gofmt")
cmdGofmt.Stdin = c.out
cmdGofmt.Stdout = os.Stdout
cmdGofmt.Stderr = os.Stderr
err = cmdGofmt.Run()
if err != nil {
log.Fatal(err)
}
}
}

49
nexgb/xgbgen/misc.go Normal file
View File

@ -0,0 +1,49 @@
package main
import (
"regexp"
"strings"
)
// AllCaps is a regex to test if a string identifier is made of
// all upper case letters.
var allCaps = regexp.MustCompile("^[A-Z0-9]+$")
// popCount counts number of bits 'set' in mask.
func popCount(mask uint) uint {
m := uint32(mask)
n := uint(0)
for i := uint32(0); i < 32; i++ {
if m&(1<<i) != 0 {
n++
}
}
return n
}
// pad makes sure 'n' aligns on 4 bytes.
func pad(n int) int {
return (n + 3) & ^3
}
// splitAndTitle takes a string, splits it by underscores, capitalizes the
// first letter of each chunk, and smushes'em back together.
func splitAndTitle(s string) string {
// If the string is all caps, lower it and capitalize first letter.
if allCaps.MatchString(s) {
return strings.Title(strings.ToLower(s))
}
// If the string has no underscores, capitalize it and leave it be.
if i := strings.Index(s, "_"); i == -1 {
return strings.Title(s)
}
// Now split the name at underscores, capitalize the first
// letter of each chunk, and smush'em back together.
chunks := strings.Split(s, "_")
for i, chunk := range chunks {
chunks[i] = strings.Title(strings.ToLower(chunk))
}
return strings.Join(chunks, "")
}

70
nexgb/xgbgen/protocol.go Normal file
View File

@ -0,0 +1,70 @@
package main
import (
"log"
"strings"
)
// Protocol is a type that encapsulates all information about one
// particular XML file. It also contains links to other protocol types
// if this protocol imports other other extensions. The import relationship
// is recursive.
type Protocol struct {
Parent *Protocol
Name string
ExtXName string
ExtName string
MajorVersion string
MinorVersion string
Imports []*Protocol
Types []Type
Requests []*Request
}
type Protocols []*Protocol
func (ps Protocols) Len() int { return len(ps) }
func (ps Protocols) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] }
func (ps Protocols) Less(i, j int) bool { return ps[i].ExtName < ps[j].ExtName }
// Initialize traverses all structures, looks for 'Translation' type,
// and looks up the real type in the namespace. It also sets the source
// name for all relevant fields/structures.
// This is necessary because we don't traverse the XML in order initially.
func (p *Protocol) Initialize() {
for _, typ := range p.Types {
typ.Initialize(p)
}
for _, req := range p.Requests {
req.Initialize(p)
}
}
// PkgName returns the name of this package.
// i.e., 'xproto' for the core X protocol, 'randr' for the RandR extension, etc.
func (p *Protocol) PkgName() string {
return strings.Replace(p.Name, "_", "", -1)
}
// ProtocolGet searches the current context for the protocol with the given
// name. (i.e., the current protocol and its imports.)
// It is an error if one is not found.
func (p *Protocol) ProtocolFind(name string) *Protocol {
if p.Name == name {
return p // that was easy
}
for _, imp := range p.Imports {
if imp.Name == name {
return imp
}
}
log.Panicf("Could not find protocol with name '%s'.", name)
panic("unreachable")
}
// isExt returns true if this protocol is an extension.
// i.e., it's name isn't "xproto".
func (p *Protocol) isExt() bool {
return strings.ToLower(p.Name) != "xproto"
}

View File

@ -0,0 +1,170 @@
package main
import (
"fmt"
"log"
"unicode"
)
// Doc contains any documentation, if present. Example C code is excluded.
type Doc struct {
Brief string // short description
Description string // long description
Fields map[string]string // from field name to description
Errors map[string]string // from error type to description
}
// DescribeField is an accessor that supports nil receivers.
func (d *Doc) DescribeField(name string) string {
if d == nil {
return ""
}
return d.Fields[name]
}
// Request represents all XML 'request' nodes.
// If the request doesn't have a reply, Reply is nil.
type Request struct {
srcName string // The Go name of this request.
xmlName string // The XML name of this request.
Opcode int
Combine bool // Not currently used.
Fields []Field // All fields in the request.
Reply *Reply // A reply, if one exists for this request.
Doc Doc // Documentation.
}
type Requests []*Request
func (rs Requests) Len() int { return len(rs) }
func (rs Requests) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] }
func (rs Requests) Less(i, j int) bool { return rs[i].xmlName < rs[j].xmlName }
// Initialize creates the proper Go source name for this request.
// It also initializes the reply if one exists, and all fields in this request.
func (r *Request) Initialize(p *Protocol) {
r.srcName = SrcName(p, r.xmlName)
if p.isExt() {
r.srcName = r.srcName
}
if r.Reply != nil {
r.Reply.Initialize(p)
}
for _, field := range r.Fields {
field.Initialize(p)
}
}
func (r *Request) SrcName() string {
return r.srcName
}
func (r *Request) XmlName() string {
return r.xmlName
}
// ReplyName gets the Go source name of the function that generates a
// reply type from a slice of bytes.
// The generated function is not currently exported.
func (r *Request) ReplyName() string {
if r.Reply == nil {
log.Panicf("Cannot call 'ReplyName' on request %s, which has no reply.",
r.SrcName())
}
name := r.SrcName()
lower := string(unicode.ToLower(rune(name[0]))) + name[1:]
return fmt.Sprintf("%sReply", lower)
}
// ReplyTypeName gets the Go source name of the type holding all reply data
// for this request.
func (r *Request) ReplyTypeName() string {
if r.Reply == nil {
log.Panicf("Cannot call 'ReplyName' on request %s, which has no reply.",
r.SrcName())
}
return fmt.Sprintf("%sReply", r.SrcName())
}
// ReqName gets the Go source name of the function that generates a byte
// slice from a list of parameters.
// The generated function is not currently exported.
func (r *Request) ReqName() string {
name := r.SrcName()
lower := string(unicode.ToLower(rune(name[0]))) + name[1:]
return fmt.Sprintf("%sRequest", lower)
}
// CookieName gets the Go source name of the type that holds cookies for
// this request.
func (r *Request) CookieName() string {
return fmt.Sprintf("%sCookie", r.SrcName())
}
// Size for Request needs a context.
// Namely, if this is an extension, we need to account for *four* bytes
// of a header (extension opcode, request opcode, and the sequence number).
// If it's a core protocol request, then we only account for *three*
// bytes of the header (remove the extension opcode).
func (r *Request) Size(c *Context) Size {
size := newFixedSize(0, true)
// If this is a core protocol request, we squeeze in an extra byte of
// data (from the fields below) between the opcode and the size of the
// request. In an extension request, this byte is always occupied
// by the opcode of the request (while the first byte is always occupied
// by the opcode of the extension).
if !c.protocol.isExt() {
size = size.Add(newFixedSize(3, true))
} else {
size = size.Add(newFixedSize(4, true))
}
for _, field := range r.Fields {
switch field := field.(type) {
case *LocalField: // local fields don't go over the wire
continue
case *SingleField:
fsz := field.Size()
if _, isstruct := field.Type.(*Struct); isstruct {
fsz.Expression = fsz.Expression.Specialize(field.SrcName())
}
size = size.Add(fsz)
default:
size = size.Add(field.Size())
}
}
return newExpressionSize(&Padding{
Expr: size.Expression,
}, size.exact)
}
// Reply encapsulates the fields associated with a 'reply' element.
type Reply struct {
Fields []Field
Doc Doc
}
// Size gets the number of bytes in this request's reply.
// A reply always has at least 7 bytes:
// 1 byte: A reply discriminant (first byte set to 1)
// 2 bytes: A sequence number
// 4 bytes: Number of additional bytes in 4-byte units past initial 32 bytes.
func (r *Reply) Size() Size {
size := newFixedSize(0, true)
// Account for reply discriminant, sequence number and reply length
size = size.Add(newFixedSize(7, true))
for _, field := range r.Fields {
size = size.Add(field.Size())
}
return size
}
func (r *Reply) Initialize(p *Protocol) {
for _, field := range r.Fields {
field.Initialize(p)
}
}

31
nexgb/xgbgen/size.go Normal file
View File

@ -0,0 +1,31 @@
package main
// Size corresponds to an expression that represents the number of bytes
// in some *thing*. Generally, sizes are used to allocate buffers and to
// inform X how big requests are.
// Size is basically a thin layer over an Expression that yields easy methods
// for adding and multiplying sizes.
type Size struct {
Expression
exact bool
}
// newFixedSize creates a new Size with some fixed and known value.
func newFixedSize(fixed uint, exact bool) Size {
return Size{&Value{v: int(fixed)}, exact}
}
// newExpressionSize creates a new Size with some expression.
func newExpressionSize(variable Expression, exact bool) Size {
return Size{variable, exact}
}
// Add adds s1 and s2 and returns a new Size.
func (s1 Size) Add(s2 Size) Size {
return Size{newBinaryOp("+", s1, s2), s1.exact && s2.exact}
}
// Multiply mupltiplies s1 and s2 and returns a new Size.
func (s1 Size) Multiply(s2 Size) Size {
return Size{newBinaryOp("*", s1, s2), s1.exact && s2.exact}
}

498
nexgb/xgbgen/translation.go Normal file
View File

@ -0,0 +1,498 @@
package main
/*
translation.go provides a 'Translate' method on every XML type that converts
the XML type into our "better" representation.
i.e., the representation of Fields and Expressions is just too general.
We end up losing a lot of the advantages of static typing if we keep
the types that encoding/xml forces us into.
*/
import (
"log"
"regexp"
"strconv"
"strings"
)
func (xml *XML) Translate(parent *Protocol) *Protocol {
protocol := &Protocol{
Parent: parent,
Name: xml.Header,
ExtXName: xml.ExtensionXName,
ExtName: xml.ExtensionName,
MajorVersion: xml.MajorVersion,
MinorVersion: xml.MinorVersion,
Imports: make([]*Protocol, 0),
Types: make([]Type, 0),
Requests: make([]*Request, len(xml.Requests)),
}
for _, imp := range xml.Imports {
if imp.xml != nil {
protocol.Imports = append(protocol.Imports,
imp.xml.Translate(protocol))
}
}
for xmlName, srcName := range BaseTypeMap {
newBaseType := &Base{
srcName: srcName,
xmlName: xmlName,
size: newFixedSize(BaseTypeSizes[xmlName], true),
}
protocol.Types = append(protocol.Types, newBaseType)
}
for _, enum := range xml.Enums {
protocol.Types = append(protocol.Types, enum.Translate())
}
for _, xid := range xml.Xids {
protocol.Types = append(protocol.Types, xid.Translate())
}
for _, xidunion := range xml.XidUnions {
protocol.Types = append(protocol.Types, xidunion.Translate())
}
for _, typedef := range xml.TypeDefs {
protocol.Types = append(protocol.Types, typedef.Translate())
}
for _, s := range xml.Structs {
protocol.Types = append(protocol.Types, s.Translate())
}
for _, union := range xml.Unions {
protocol.Types = append(protocol.Types, union.Translate())
}
for _, ev := range xml.Events {
protocol.Types = append(protocol.Types, ev.Translate())
}
for _, evcopy := range xml.EventCopies {
protocol.Types = append(protocol.Types, evcopy.Translate())
}
for _, err := range xml.Errors {
protocol.Types = append(protocol.Types, err.Translate())
}
for _, errcopy := range xml.ErrorCopies {
protocol.Types = append(protocol.Types, errcopy.Translate())
}
for i, request := range xml.Requests {
protocol.Requests[i] = request.Translate()
}
// Now load all of the type and source name information.
protocol.Initialize()
// Make sure all enums have concrete values.
for _, typ := range protocol.Types {
enum, ok := typ.(*Enum)
if !ok {
continue
}
nextValue := 0
for _, item := range enum.Items {
if item.Expr == nil {
item.Expr = &Value{v: nextValue}
nextValue++
} else {
nextValue = item.Expr.Eval() + 1
}
}
}
return protocol
}
func (x *XMLEnum) Translate() *Enum {
enum := &Enum{
xmlName: x.Name,
Items: make([]*EnumItem, len(x.Items)),
}
for i, item := range x.Items {
enum.Items[i] = &EnumItem{
xmlName: item.Name,
Expr: item.Expr.Translate(),
}
}
return enum
}
func (x *XMLXid) Translate() *Resource {
return &Resource{
xmlName: x.Name,
}
}
func (x *XMLTypeDef) Translate() *TypeDef {
return &TypeDef{
xmlName: x.New,
Old: newTranslation(x.Old),
}
}
func (x *XMLEvent) Translate() *Event {
ev := &Event{
xmlName: x.Name,
Number: x.Number,
NoSequence: x.NoSequence,
Fields: make([]Field, 0, len(x.Fields)),
Doc: x.Doc.Translate(),
}
for _, field := range x.Fields {
ev.Fields = append(ev.Fields, field.Translate(ev, &ev.Doc))
}
return ev
}
func (x *XMLEventCopy) Translate() *EventCopy {
return &EventCopy{
xmlName: x.Name,
Number: x.Number,
Old: newTranslation(x.Ref),
}
}
func (x *XMLError) Translate() *Error {
err := &Error{
xmlName: x.Name,
Number: x.Number,
Fields: make([]Field, len(x.Fields)),
}
for i, field := range x.Fields {
err.Fields[i] = field.Translate(err, nil)
}
return err
}
func (x *XMLErrorCopy) Translate() *ErrorCopy {
return &ErrorCopy{
xmlName: x.Name,
Number: x.Number,
Old: newTranslation(x.Ref),
}
}
// XCB documentation is positively stuffed with TODOs. We'd like to make it
// look a bit less shitty, so remove those as they don't convey information.
//
// ^TODO
// ^TODO:
// ^TODO: question?
// ^TODO: Some words
// ^TODO: some words
// ^TODO: some words with full stop.
// ^TODO: some words with full stop. and a question?
// ... (TODO),
// ... (TODO).
// ... (TODO: a question?).
// ... TODO: a question?
// ... (word TODO) ...
var todoRE = regexp.MustCompile(`(?m:^TODO.*| \([^)]*TODO[^)]*\)| TODO:.*)`)
var paraRE = regexp.MustCompile(`\n{3,}`)
var backticksRE = regexp.MustCompile("`(?:xcb_|XCB_)?(.*?)(?:_t)?`")
// fixDocumentation tries to translate XCB documentation to match XGB.
// Note that <doc> blocks only appear in xproto, so this doesn't have that much
// of a value--users still need to read Xlib or X11 protocol docs.
// Despite that, it's better to have something than nothing.
//
// We don't attempt to add proper prefixes to enum values or guess at
// specific XCB_NONE types (the latter is undecidable).
//
// We can't decide whether `fields_len` should be converted to len(Fields)
// or FieldsLen because e.g. StringLen is retained in ImageText8/16 but
// PointsLen is implied by the length of the Points slice in PolyLine.
func fixDocumentation(xcb string) string {
last, result := 0, make([]byte, 0, len(xcb))
for _, m := range backticksRE.FindAllStringSubmatchIndex(xcb, -1) {
result = append(result, xcb[last:m[0]]...)
inner := xcb[m[2]:m[3]]
last = m[1]
// Do not convert atom names to identifiers, mainly _NET_WM_*.
if strings.Contains(inner, "WM_") {
result = append(result, inner...)
} else {
result = append(result, splitAndTitle(inner)...)
}
}
result = todoRE.ReplaceAllLiteral(append(result, xcb[last:]...), nil)
result = paraRE.ReplaceAllLiteral(result, []byte("\n\n"))
return strings.TrimSpace(string(result))
}
func (x *XMLDoc) Translate() Doc {
if x == nil {
return Doc{}
}
d := Doc{
Brief: fixDocumentation(x.Brief),
Description: fixDocumentation(x.Description),
Fields: make(map[string]string),
Errors: make(map[string]string),
}
for _, x := range x.Fields {
d.Fields[x.Name] = fixDocumentation(x.Description)
}
for _, x := range x.Errors {
d.Errors[x.Type] = fixDocumentation(x.Description)
}
return d
}
func (x *XMLStruct) Translate() *Struct {
s := &Struct{
xmlName: x.Name,
Fields: make([]Field, len(x.Fields)),
}
for i, field := range x.Fields {
s.Fields[i] = field.Translate(s, nil)
}
return s
}
func (x *XMLUnion) Translate() *Union {
u := &Union{
xmlName: x.Name,
Fields: make([]Field, len(x.Fields)),
}
for i, field := range x.Fields {
u.Fields[i] = field.Translate(u, nil)
}
return u
}
func (x *XMLRequest) Translate() *Request {
r := &Request{
xmlName: x.Name,
Opcode: x.Opcode,
Combine: x.Combine,
Fields: make([]Field, 0, len(x.Fields)),
Reply: x.Reply.Translate(),
Doc: x.Doc.Translate(),
}
for _, field := range x.Fields {
if field.XMLName.Local == "fd" {
continue
}
r.Fields = append(r.Fields, field.Translate(r, &r.Doc))
}
// Address bug (or legacy code) in QueryTextExtents.
// The XML protocol description references 'string_len' in the
// computation of the 'odd_length' field. However, 'string_len' is not
// defined. Therefore, let's forcefully add it as a 'local field'.
// (i.e., a parameter in the caller but does not get sent over the wire.)
if x.Name == "QueryTextExtents" {
stringLenLocal := &LocalField{&SingleField{
xmlName: "string_len",
Type: newTranslation("CARD16"),
}}
r.Fields = append(r.Fields, stringLenLocal)
}
return r
}
func (x *XMLReply) Translate() *Reply {
if x == nil {
return nil
}
r := &Reply{
Fields: make([]Field, 0, len(x.Fields)),
Doc: x.Doc.Translate(),
}
for _, field := range x.Fields {
if field.XMLName.Local == "fd" {
continue
}
r.Fields = append(r.Fields, field.Translate(r, &r.Doc))
}
return r
}
func (x *XMLExpression) Translate() Expression {
if x == nil {
return nil
}
switch x.XMLName.Local {
case "op":
if len(x.Exprs) != 2 {
log.Panicf("'op' found %d expressions; expected 2.", len(x.Exprs))
}
return &BinaryOp{
Op: x.Op,
Expr1: x.Exprs[0].Translate(),
Expr2: x.Exprs[1].Translate(),
}
case "unop":
if len(x.Exprs) != 1 {
log.Panicf("'unop' found %d expressions; expected 1.", len(x.Exprs))
}
return &UnaryOp{
Op: x.Op,
Expr: x.Exprs[0].Translate(),
}
case "popcount":
if len(x.Exprs) != 1 {
log.Panicf("'popcount' found %d expressions; expected 1.",
len(x.Exprs))
}
return &PopCount{
Expr: x.Exprs[0].Translate(),
}
case "value":
val, err := strconv.Atoi(strings.TrimSpace(x.Data))
if err != nil {
log.Panicf("Could not convert '%s' in 'value' expression to int.",
x.Data)
}
return &Value{
v: val,
}
case "bit":
bit, err := strconv.Atoi(strings.TrimSpace(x.Data))
if err != nil {
log.Panicf("Could not convert '%s' in 'bit' expression to int.",
x.Data)
}
if bit < 0 || bit > 31 {
log.Panicf("A 'bit' literal must be in the range [0, 31], but "+
" is %d", bit)
}
return &Bit{
b: bit,
}
case "fieldref":
return &FieldRef{
Name: x.Data,
}
case "enumref":
return &EnumRef{
EnumKind: newTranslation(x.Ref),
EnumItem: x.Data,
}
case "sumof":
return &SumOf{
Name: x.Ref,
}
}
log.Panicf("Unrecognized tag '%s' in expression context. Expected one of "+
"op, fieldref, value, bit, enumref, unop, sumof or popcount.",
x.XMLName.Local)
panic("unreachable")
}
func (x *XMLField) Translate(parent interface{}, doc *Doc) Field {
switch x.XMLName.Local {
case "pad":
return &PadField{
Bytes: x.Bytes,
Align: x.Align,
}
case "field":
s := &SingleField{
xmlName: x.Name,
Type: newTranslation(x.Type),
Comment: doc.DescribeField(x.Name),
}
return s
case "list":
return &ListField{
xmlName: x.Name,
Type: newTranslation(x.Type),
LengthExpr: x.Expr.Translate(),
}
case "localfield":
return &LocalField{&SingleField{
xmlName: x.Name,
Type: newTranslation(x.Type),
}}
case "exprfield":
return &ExprField{
xmlName: x.Name,
Type: newTranslation(x.Type),
Expr: x.Expr.Translate(),
}
case "valueparam":
return &ValueField{
Parent: parent,
MaskType: newTranslation(x.ValueMaskType),
MaskName: x.ValueMaskName,
ListName: x.ValueListName,
MaskComment: doc.DescribeField(x.ValueMaskName),
ListComment: doc.DescribeField(x.ValueListName),
}
case "switch":
swtch := &SwitchField{
Name: x.Name,
Expr: x.Expr.Translate(),
Bitcases: make([]*Bitcase, len(x.Bitcases)),
Comment: doc.DescribeField(x.Name),
}
for i, bitcase := range x.Bitcases {
swtch.Bitcases[i] = bitcase.Translate()
}
return swtch
case "required_start_align":
return &RequiredStartAlign{}
}
log.Panicf("Unrecognized field element: %s", x.XMLName.Local)
panic("unreachable")
}
func (x *XMLBitcase) Translate() *Bitcase {
b := &Bitcase{
Expr: x.Expr().Translate(),
Fields: make([]Field, len(x.Fields)),
}
for i, field := range x.Fields {
b.Fields[i] = field.Translate(b, nil)
}
return b
}
// SrcName is used to translate any identifier into a Go name.
// Mostly used for fields, but used in a couple other places too (enum items).
func SrcName(p *Protocol, name string) string {
// If it's in the name map, use that translation.
if newn, ok := NameMap[name]; ok {
return newn
}
return splitAndTitle(name)
}
func TypeSrcName(p *Protocol, typ Type) string {
t := typ.XmlName()
// If this is a base type, then write the raw Go type.
if baseType, ok := typ.(*Base); ok {
return baseType.SrcName()
}
// If it's in the type map, use that translation.
if newt, ok := TypeMap[t]; ok {
return newt
}
// If there's a namespace to this type, just use it and be done.
if colon := strings.Index(t, ":"); colon > -1 {
namespace := t[:colon]
rest := t[colon+1:]
return p.ProtocolFind(namespace).PkgName() + "." + splitAndTitle(rest)
}
// Since there's no namespace, we're left with the raw type name.
// If the type is part of the source we're generating (i.e., there is
// no parent protocol), then just return that type name.
// Otherwise, we must qualify it with a package name.
if p.Parent == nil {
return splitAndTitle(t)
}
return p.PkgName() + "." + splitAndTitle(t)
}

391
nexgb/xgbgen/type.go Normal file
View File

@ -0,0 +1,391 @@
package main
import (
"fmt"
"strings"
)
type Type interface {
Initialize(p *Protocol)
SrcName() string
XmlName() string
Size() Size
Define(c *Context)
}
type Types []Type
func (ts Types) Len() int { return len(ts) }
func (ts Types) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] }
func (ts Types) Less(i, j int) bool {
x1, x2 := ts[i].XmlName(), ts[j].XmlName()
s1, s2 := ts[i].SrcName(), ts[j].SrcName()
return (s1 == s2 && x1 < x2) || s1 < s2
}
// Translation is used *only* when transitioning from XML types to
// our better representation. They are placeholders for the real types (below)
// that will replace them.
type Translation struct {
xmlName string
}
func newTranslation(name string) *Translation {
return &Translation{xmlName: name}
}
// RealType takes 'XmlName' and finds its real concrete type in our Protocol.
// It is an error if we can't find such a type.
func (t *Translation) RealType(p *Protocol) Type {
// Check to see if there is a namespace. If so, strip it and use it to
// make sure we only look for a type in that protocol.
namespace, typeName := "", t.XmlName()
if ni := strings.Index(t.XmlName(), ":"); ni > -1 {
namespace, typeName = strings.ToLower(typeName[:ni]), typeName[ni+1:]
}
if len(namespace) == 0 || namespace == strings.ToLower(p.Name) {
for _, typ := range p.Types {
if typeName == typ.XmlName() {
return typ
}
}
}
for _, imp := range p.Imports {
if len(namespace) == 0 || namespace == strings.ToLower(imp.Name) {
for _, typ := range imp.Types {
if typeName == typ.XmlName() {
return typ
}
}
}
}
panic("Could not find real type for translation type: " + t.XmlName())
}
func (t *Translation) SrcName() string {
panic("it is illegal to call SrcName on a translation type")
}
func (t *Translation) XmlName() string {
return t.xmlName
}
func (t *Translation) Size() Size {
panic("it is illegal to call Size on a translation type")
}
func (t *Translation) Define(c *Context) {
panic("it is illegal to call Define on a translation type")
}
func (t *Translation) Initialize(p *Protocol) {
panic("it is illegal to call Initialize on a translation type")
}
type Base struct {
srcName string
xmlName string
size Size
}
func (b *Base) SrcName() string {
return b.srcName
}
func (b *Base) XmlName() string {
return b.xmlName
}
func (b *Base) Size() Size {
return b.size
}
func (b *Base) Initialize(p *Protocol) {
b.srcName = TypeSrcName(p, b)
}
type Enum struct {
srcName string
xmlName string
Items []*EnumItem
}
type EnumItem struct {
srcName string
xmlName string
Expr Expression
}
func (enum *Enum) SrcName() string {
return enum.srcName
}
func (enum *Enum) XmlName() string {
return enum.xmlName
}
func (enum *Enum) Size() Size {
panic("Cannot take size of enum")
}
func (enum *Enum) Initialize(p *Protocol) {
enum.srcName = TypeSrcName(p, enum)
for _, item := range enum.Items {
item.srcName = SrcName(p, item.xmlName)
if item.Expr != nil {
item.Expr.Initialize(p)
}
}
}
type Resource struct {
srcName string
xmlName string
}
func (r *Resource) SrcName() string {
return r.srcName
}
func (r *Resource) XmlName() string {
return r.xmlName
}
func (r *Resource) Size() Size {
return newFixedSize(BaseTypeSizes["Id"], true)
}
func (r *Resource) Initialize(p *Protocol) {
r.srcName = TypeSrcName(p, r)
}
type TypeDef struct {
srcName string
xmlName string
Old Type
}
func (t *TypeDef) SrcName() string {
return t.srcName
}
func (t *TypeDef) XmlName() string {
return t.xmlName
}
func (t *TypeDef) Size() Size {
return t.Old.Size()
}
func (t *TypeDef) Initialize(p *Protocol) {
t.Old = t.Old.(*Translation).RealType(p)
t.srcName = TypeSrcName(p, t)
}
type Event struct {
srcName string
xmlName string
Number int
NoSequence bool
Fields []Field
Doc Doc
}
func (e *Event) SrcName() string {
return e.srcName
}
func (e *Event) XmlName() string {
return e.xmlName
}
func (e *Event) Size() Size {
return newExpressionSize(&Value{v: 32}, true)
}
func (e *Event) Initialize(p *Protocol) {
e.srcName = TypeSrcName(p, e)
for _, field := range e.Fields {
field.Initialize(p)
}
}
func (e *Event) EvType() string {
return fmt.Sprintf("%sEvent", e.srcName)
}
type EventCopy struct {
srcName string
xmlName string
Old Type
Number int
}
func (e *EventCopy) SrcName() string {
return e.srcName
}
func (e *EventCopy) XmlName() string {
return e.xmlName
}
func (e *EventCopy) Size() Size {
return newExpressionSize(&Value{v: 32}, true)
}
func (e *EventCopy) Initialize(p *Protocol) {
e.srcName = TypeSrcName(p, e)
e.Old = e.Old.(*Translation).RealType(p)
if _, ok := e.Old.(*Event); !ok {
panic("an EventCopy's old type *must* be *Event")
}
}
func (e *EventCopy) EvType() string {
return fmt.Sprintf("%sEvent", e.srcName)
}
type Error struct {
srcName string
xmlName string
Number int
Fields []Field
}
func (e *Error) SrcName() string {
return e.srcName
}
func (e *Error) XmlName() string {
return e.xmlName
}
func (e *Error) Size() Size {
return newExpressionSize(&Value{v: 32}, true)
}
func (e *Error) Initialize(p *Protocol) {
e.srcName = TypeSrcName(p, e)
for _, field := range e.Fields {
field.Initialize(p)
}
}
func (e *Error) ErrConst() string {
return fmt.Sprintf("Bad%s", e.srcName)
}
func (e *Error) ErrType() string {
return fmt.Sprintf("%sError", e.srcName)
}
type ErrorCopy struct {
srcName string
xmlName string
Old Type
Number int
}
func (e *ErrorCopy) SrcName() string {
return e.srcName
}
func (e *ErrorCopy) XmlName() string {
return e.xmlName
}
func (e *ErrorCopy) Size() Size {
return newExpressionSize(&Value{v: 32}, true)
}
func (e *ErrorCopy) Initialize(p *Protocol) {
e.srcName = TypeSrcName(p, e)
e.Old = e.Old.(*Translation).RealType(p)
if _, ok := e.Old.(*Error); !ok {
panic("an ErrorCopy's old type *must* be *Event")
}
}
func (e *ErrorCopy) ErrConst() string {
return fmt.Sprintf("Bad%s", e.srcName)
}
func (e *ErrorCopy) ErrType() string {
return fmt.Sprintf("%sError", e.srcName)
}
type Struct struct {
srcName string
xmlName string
Fields []Field
}
func (s *Struct) SrcName() string {
return s.srcName
}
func (s *Struct) XmlName() string {
return s.xmlName
}
func (s *Struct) Size() Size {
size := newFixedSize(0, true)
for _, field := range s.Fields {
size = size.Add(field.Size())
}
return size
}
func (s *Struct) Initialize(p *Protocol) {
s.srcName = TypeSrcName(p, s)
for _, field := range s.Fields {
field.Initialize(p)
}
}
// HasList returns whether there is a field in this struct that is a list.
// When true, a more involved calculation is necessary to compute this struct's
// size.
func (s *Struct) HasList() bool {
for _, field := range s.Fields {
if _, ok := field.(*ListField); ok {
return true
}
}
return false
}
type Union struct {
srcName string
xmlName string
Fields []Field
}
func (u *Union) SrcName() string {
return u.srcName
}
func (u *Union) XmlName() string {
return u.xmlName
}
// Size for Union is broken. At least, it's broken for XKB.
// It *looks* like the protocol inherently relies on some amount of
// memory unsafety, since some members of unions in XKB are *variable* in
// length! The only thing I can come up with, maybe, is when a union has
// variable size, simply return the raw bytes. Then it's up to the user to
// pass those raw bytes into the appropriate New* constructor. GROSS!
// As of now, just pluck out the first field and return that size. This
// should work for union elements in randr.xml and xproto.xml.
func (u *Union) Size() Size {
return u.Fields[0].Size()
}
func (u *Union) Initialize(p *Protocol) {
u.srcName = fmt.Sprintf("%sUnion", TypeSrcName(p, u))
for _, field := range u.Fields {
field.Initialize(p)
}
}

159
nexgb/xgbgen/xml.go Normal file
View File

@ -0,0 +1,159 @@
package main
import (
"encoding/xml"
"io/ioutil"
"log"
)
type XML struct {
// Root 'xcb' element properties.
XMLName xml.Name `xml:"xcb"`
Header string `xml:"header,attr"`
ExtensionXName string `xml:"extension-xname,attr"`
ExtensionName string `xml:"extension-name,attr"`
MajorVersion string `xml:"major-version,attr"`
MinorVersion string `xml:"minor-version,attr"`
// Types for all top-level elements.
// First are the simple ones.
Imports XMLImports `xml:"import"`
Enums []*XMLEnum `xml:"enum"`
Xids []*XMLXid `xml:"xidtype"`
XidUnions []*XMLXid `xml:"xidunion"`
TypeDefs []*XMLTypeDef `xml:"typedef"`
EventCopies []*XMLEventCopy `xml:"eventcopy"`
ErrorCopies []*XMLErrorCopy `xml:"errorcopy"`
// Here are the complex ones, i.e., anything with "structure contents"
Structs []*XMLStruct `xml:"struct"`
Unions []*XMLUnion `xml:"union"`
Requests []*XMLRequest `xml:"request"`
Events []*XMLEvent `xml:"event"`
Errors []*XMLError `xml:"error"`
}
type XMLImports []*XMLImport
func (imports XMLImports) Eval() {
for _, imp := range imports {
xmlBytes, err := ioutil.ReadFile(*protoPath + "/" + imp.Name + ".xml")
if err != nil {
log.Fatalf("Could not read X protocol description for import "+
"'%s' because: %s", imp.Name, err)
}
imp.xml = &XML{}
err = xml.Unmarshal(xmlBytes, imp.xml)
if err != nil {
log.Fatal("Could not parse X protocol description for import "+
"'%s' because: %s", imp.Name, err)
}
// recursive imports...
imp.xml.Imports.Eval()
}
}
type XMLImport struct {
Name string `xml:",chardata"`
xml *XML `xml:"-"`
}
type XMLEnum struct {
Name string `xml:"name,attr"`
Items []*XMLEnumItem `xml:"item"`
}
type XMLEnumItem struct {
Name string `xml:"name,attr"`
Expr *XMLExpression `xml:",any"`
}
type XMLXid struct {
XMLName xml.Name
Name string `xml:"name,attr"`
}
type XMLTypeDef struct {
Old string `xml:"oldname,attr"`
New string `xml:"newname,attr"`
}
type XMLEventCopy struct {
Name string `xml:"name,attr"`
Number int `xml:"number,attr"`
Ref string `xml:"ref,attr"`
}
type XMLErrorCopy struct {
Name string `xml:"name,attr"`
Number int `xml:"number,attr"`
Ref string `xml:"ref,attr"`
}
type XMLDocField struct {
Name string `xml:"name,attr"`
Description string `xml:",chardata"`
}
type XMLDocError struct {
Type string `xml:"type,attr"`
Description string `xml:",chardata"`
}
type XMLDoc struct {
Brief string `xml:"brief"`
Description string `xml:"description"`
Example string `xml:"example"`
Fields []*XMLDocField `xml:"field"`
Errors []*XMLDocError `xml:"error"`
}
type XMLStruct struct {
Name string `xml:"name,attr"`
Fields []*XMLField `xml:",any"`
}
type XMLUnion struct {
Name string `xml:"name,attr"`
Fields []*XMLField `xml:",any"`
}
type XMLRequest struct {
Name string `xml:"name,attr"`
Opcode int `xml:"opcode,attr"`
Combine bool `xml:"combine-adjacent,attr"`
Fields []*XMLField `xml:",any"`
Reply *XMLReply `xml:"reply"`
Doc *XMLDoc `xml:"doc"`
}
type XMLReply struct {
Fields []*XMLField `xml:",any"`
Doc *XMLDoc `xml:"doc"`
}
type XMLEvent struct {
Name string `xml:"name,attr"`
Number int `xml:"number,attr"`
NoSequence bool `xml:"no-sequence-number,attr"`
Fields []*XMLField `xml:",any"`
Doc *XMLDoc `xml:"doc"`
}
type XMLError struct {
Name string `xml:"name,attr"`
Number int `xml:"number,attr"`
Fields []*XMLField `xml:",any"`
}
type XMLExpression struct {
XMLName xml.Name
Exprs []*XMLExpression `xml:",any"`
Data string `xml:",chardata"`
Op string `xml:"op,attr"`
Ref string `xml:"ref,attr"`
}

View File

@ -0,0 +1,86 @@
package main
import (
"encoding/xml"
"log"
)
type XMLField struct {
XMLName xml.Name
// For 'pad' element
Bytes uint `xml:"bytes,attr"`
Align uint16 `xml:"align,attr"`
// For 'field', 'list', 'localfield', 'exprfield' and 'switch' elements.
Name string `xml:"name,attr"`
// For 'field', 'list', 'localfield', and 'exprfield' elements.
Type string `xml:"type,attr"`
// For 'list', 'exprfield' and 'switch' elements.
Expr *XMLExpression `xml:",any"`
// For 'valueparm' element.
ValueMaskType string `xml:"value-mask-type,attr"`
ValueMaskName string `xml:"value-mask-name,attr"`
ValueListName string `xml:"value-list-name,attr"`
// For 'switch' element.
Bitcases []*XMLBitcase `xml:"bitcase"`
// I don't know which elements these are for. The documentation is vague.
// They also seem to be completely optional.
OptEnum string `xml:"enum,attr"`
OptMask string `xml:"mask,attr"`
OptAltEnum string `xml:"altenum,attr"`
}
// Bitcase represents a single expression followed by any number of fields.
// Namely, if the switch's expression (all bitcases are inside a switch),
// and'd with the bitcase's expression is equal to the bitcase expression,
// then the fields should be included in its parent structure.
// Note that since a bitcase is unique in that expressions and fields are
// siblings, we must exhaustively search for one of them. Essentially,
// it's the closest thing to a Union I can get to in Go without interfaces.
// Would an '<expression>' tag have been too much to ask? :-(
type XMLBitcase struct {
Fields []*XMLField `xml:",any"`
// All the different expressions.
// When it comes time to choose one, use the 'Expr' method.
ExprOp *XMLExpression `xml:"op"`
ExprUnOp *XMLExpression `xml:"unop"`
ExprField *XMLExpression `xml:"fieldref"`
ExprValue *XMLExpression `xml:"value"`
ExprBit *XMLExpression `xml:"bit"`
ExprEnum *XMLExpression `xml:"enumref"`
ExprSum *XMLExpression `xml:"sumof"`
ExprPop *XMLExpression `xml:"popcount"`
}
// Expr chooses the only non-nil Expr* field from Bitcase.
// Panic if there is more than one non-nil expression.
func (b *XMLBitcase) Expr() *XMLExpression {
choices := []*XMLExpression{
b.ExprOp, b.ExprUnOp, b.ExprField, b.ExprValue,
b.ExprBit, b.ExprEnum, b.ExprSum, b.ExprPop,
}
var choice *XMLExpression = nil
numNonNil := 0
for _, c := range choices {
if c != nil {
numNonNil++
choice = c
}
}
if choice == nil {
log.Panicf("No top level expression found in a bitcase.")
}
if numNonNil > 1 {
log.Panicf("More than one top-level expression was found in a bitcase.")
}
return choice
}

717
nexgb/xinerama/xinerama.go Normal file
View File

@ -0,0 +1,717 @@
// Package xinerama is the X client API for the XINERAMA extension.
package xinerama
// This file is automatically generated from xinerama.xml. Edit at your peril!
import (
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xproto"
)
const (
MajorVersion = 1
MinorVersion = 1
)
// Init must be called before using the XINERAMA extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 8, "XINERAMA").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named XINERAMA could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["XINERAMA"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["XINERAMA"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["XINERAMA"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["XINERAMA"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["XINERAMA"] = make(map[int]xgb.NewErrorFun)
}
type ScreenInfo struct {
XOrg int16
YOrg int16
Width uint16
Height uint16
}
// ScreenInfoRead reads a byte slice into a ScreenInfo value.
func ScreenInfoRead(buf []byte, v *ScreenInfo) int {
b := 0
v.XOrg = int16(xgb.Get16(buf[b:]))
b += 2
v.YOrg = int16(xgb.Get16(buf[b:]))
b += 2
v.Width = xgb.Get16(buf[b:])
b += 2
v.Height = xgb.Get16(buf[b:])
b += 2
return b
}
// ScreenInfoReadList reads a byte slice into a list of ScreenInfo values.
func ScreenInfoReadList(buf []byte, dest []ScreenInfo) int {
b := 0
for i := 0; i < len(dest); i++ {
dest[i] = ScreenInfo{}
b += ScreenInfoRead(buf[b:], &dest[i])
}
return xgb.Pad(b)
}
// Bytes writes a ScreenInfo value to a byte slice.
func (v ScreenInfo) Bytes() []byte {
buf := make([]byte, 8)
b := 0
xgb.Put16(buf[b:], uint16(v.XOrg))
b += 2
xgb.Put16(buf[b:], uint16(v.YOrg))
b += 2
xgb.Put16(buf[b:], v.Width)
b += 2
xgb.Put16(buf[b:], v.Height)
b += 2
return buf[:b]
}
// ScreenInfoListBytes writes a list of ScreenInfo values to a byte slice.
func ScreenInfoListBytes(buf []byte, list []ScreenInfo) int {
b := 0
var structBytes []byte
for _, item := range list {
structBytes = item.Bytes()
copy(buf[b:], structBytes)
b += len(structBytes)
}
return xgb.Pad(b)
}
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// GetScreenCountCookie is a cookie used only for GetScreenCount requests.
type GetScreenCountCookie struct {
*xgb.Cookie
}
// GetScreenCount sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetScreenCountCookie.Reply.
func GetScreenCount(c *xgb.Conn, Window xproto.Window) GetScreenCountCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'GetScreenCount' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getScreenCountRequest(c, Window), cookie)
return GetScreenCountCookie{cookie}
}
// GetScreenCountUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetScreenCountUnchecked(c *xgb.Conn, Window xproto.Window) GetScreenCountCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'GetScreenCount' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getScreenCountRequest(c, Window), cookie)
return GetScreenCountCookie{cookie}
}
// GetScreenCountReply represents the data returned from a GetScreenCount request.
type GetScreenCountReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
ScreenCount byte
Window xproto.Window
}
// Reply blocks and returns the reply data for a GetScreenCount request.
func (cook GetScreenCountCookie) Reply() (*GetScreenCountReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getScreenCountReply(buf), nil
}
// getScreenCountReply reads a byte slice into a GetScreenCountReply value.
func getScreenCountReply(buf []byte) *GetScreenCountReply {
v := new(GetScreenCountReply)
b := 1 // skip reply determinant
v.ScreenCount = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.Window = xproto.Window(xgb.Get32(buf[b:]))
b += 4
return v
}
// getScreenCountRequest writes a GetScreenCount request to a byte slice for transfer.
func getScreenCountRequest(c *xgb.Conn, Window xproto.Window) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XINERAMA"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
return buf
}
// GetScreenSizeCookie is a cookie used only for GetScreenSize requests.
type GetScreenSizeCookie struct {
*xgb.Cookie
}
// GetScreenSize sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetScreenSizeCookie.Reply.
func GetScreenSize(c *xgb.Conn, Window xproto.Window, Screen uint32) GetScreenSizeCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'GetScreenSize' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getScreenSizeRequest(c, Window, Screen), cookie)
return GetScreenSizeCookie{cookie}
}
// GetScreenSizeUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetScreenSizeUnchecked(c *xgb.Conn, Window xproto.Window, Screen uint32) GetScreenSizeCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'GetScreenSize' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getScreenSizeRequest(c, Window, Screen), cookie)
return GetScreenSizeCookie{cookie}
}
// GetScreenSizeReply represents the data returned from a GetScreenSize request.
type GetScreenSizeReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
Width uint32
Height uint32
Window xproto.Window
Screen uint32
}
// Reply blocks and returns the reply data for a GetScreenSize request.
func (cook GetScreenSizeCookie) Reply() (*GetScreenSizeReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getScreenSizeReply(buf), nil
}
// getScreenSizeReply reads a byte slice into a GetScreenSizeReply value.
func getScreenSizeReply(buf []byte) *GetScreenSizeReply {
v := new(GetScreenSizeReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.Width = xgb.Get32(buf[b:])
b += 4
v.Height = xgb.Get32(buf[b:])
b += 4
v.Window = xproto.Window(xgb.Get32(buf[b:]))
b += 4
v.Screen = xgb.Get32(buf[b:])
b += 4
return v
}
// getScreenSizeRequest writes a GetScreenSize request to a byte slice for transfer.
func getScreenSizeRequest(c *xgb.Conn, Window xproto.Window, Screen uint32) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XINERAMA"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
xgb.Put32(buf[b:], Screen)
b += 4
return buf
}
// GetStateCookie is a cookie used only for GetState requests.
type GetStateCookie struct {
*xgb.Cookie
}
// GetState sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetStateCookie.Reply.
func GetState(c *xgb.Conn, Window xproto.Window) GetStateCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'GetState' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getStateRequest(c, Window), cookie)
return GetStateCookie{cookie}
}
// GetStateUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetStateUnchecked(c *xgb.Conn, Window xproto.Window) GetStateCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'GetState' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getStateRequest(c, Window), cookie)
return GetStateCookie{cookie}
}
// GetStateReply represents the data returned from a GetState request.
type GetStateReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
State byte
Window xproto.Window
}
// Reply blocks and returns the reply data for a GetState request.
func (cook GetStateCookie) Reply() (*GetStateReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getStateReply(buf), nil
}
// getStateReply reads a byte slice into a GetStateReply value.
func getStateReply(buf []byte) *GetStateReply {
v := new(GetStateReply)
b := 1 // skip reply determinant
v.State = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.Window = xproto.Window(xgb.Get32(buf[b:]))
b += 4
return v
}
// getStateRequest writes a GetState request to a byte slice for transfer.
func getStateRequest(c *xgb.Conn, Window xproto.Window) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XINERAMA"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
return buf
}
// IsActiveCookie is a cookie used only for IsActive requests.
type IsActiveCookie struct {
*xgb.Cookie
}
// IsActive sends a checked request.
// If an error occurs, it will be returned with the reply by calling IsActiveCookie.Reply.
func IsActive(c *xgb.Conn) IsActiveCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'IsActive' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(isActiveRequest(c), cookie)
return IsActiveCookie{cookie}
}
// IsActiveUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func IsActiveUnchecked(c *xgb.Conn) IsActiveCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'IsActive' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(isActiveRequest(c), cookie)
return IsActiveCookie{cookie}
}
// IsActiveReply represents the data returned from a IsActive request.
type IsActiveReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
State uint32
}
// Reply blocks and returns the reply data for a IsActive request.
func (cook IsActiveCookie) Reply() (*IsActiveReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return isActiveReply(buf), nil
}
// isActiveReply reads a byte slice into a IsActiveReply value.
func isActiveReply(buf []byte) *IsActiveReply {
v := new(IsActiveReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.State = xgb.Get32(buf[b:])
b += 4
return v
}
// isActiveRequest writes a IsActive request to a byte slice for transfer.
func isActiveRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XINERAMA"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 4 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// QueryScreensCookie is a cookie used only for QueryScreens requests.
type QueryScreensCookie struct {
*xgb.Cookie
}
// QueryScreens sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryScreensCookie.Reply.
func QueryScreens(c *xgb.Conn) QueryScreensCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'QueryScreens' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryScreensRequest(c), cookie)
return QueryScreensCookie{cookie}
}
// QueryScreensUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryScreensUnchecked(c *xgb.Conn) QueryScreensCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'QueryScreens' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryScreensRequest(c), cookie)
return QueryScreensCookie{cookie}
}
// QueryScreensReply represents the data returned from a QueryScreens request.
type QueryScreensReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
Number uint32
// padding: 20 bytes
ScreenInfo []ScreenInfo // size: xgb.Pad((int(Number) * 8))
}
// Reply blocks and returns the reply data for a QueryScreens request.
func (cook QueryScreensCookie) Reply() (*QueryScreensReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryScreensReply(buf), nil
}
// queryScreensReply reads a byte slice into a QueryScreensReply value.
func queryScreensReply(buf []byte) *QueryScreensReply {
v := new(QueryScreensReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.Number = xgb.Get32(buf[b:])
b += 4
b += 20 // padding
v.ScreenInfo = make([]ScreenInfo, v.Number)
b += ScreenInfoReadList(buf[b:], v.ScreenInfo)
return v
}
// queryScreensRequest writes a QueryScreens request to a byte slice for transfer.
func queryScreensRequest(c *xgb.Conn) []byte {
size := 4
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XINERAMA"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 5 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
return buf
}
// QueryVersionCookie is a cookie used only for QueryVersion requests.
type QueryVersionCookie struct {
*xgb.Cookie
}
// QueryVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply.
func QueryVersion(c *xgb.Conn, Major, Minor byte) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(queryVersionRequest(c, Major, Minor), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func QueryVersionUnchecked(c *xgb.Conn, Major, Minor byte) QueryVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XINERAMA"]; !ok {
panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'XINERAMA'. xinerama.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(queryVersionRequest(c, Major, Minor), cookie)
return QueryVersionCookie{cookie}
}
// QueryVersionReply represents the data returned from a QueryVersion request.
type QueryVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
// padding: 1 bytes
Major uint16
Minor uint16
}
// Reply blocks and returns the reply data for a QueryVersion request.
func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return queryVersionReply(buf), nil
}
// queryVersionReply reads a byte slice into a QueryVersionReply value.
func queryVersionReply(buf []byte) *QueryVersionReply {
v := new(QueryVersionReply)
b := 1 // skip reply determinant
b += 1 // padding
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.Major = xgb.Get16(buf[b:])
b += 2
v.Minor = xgb.Get16(buf[b:])
b += 2
return v
}
// queryVersionRequest writes a QueryVersion request to a byte slice for transfer.
func queryVersionRequest(c *xgb.Conn, Major, Minor byte) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XINERAMA"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
buf[b] = Major
b += 1
buf[b] = Minor
b += 1
return buf
}

2520
nexgb/xprint/xprint.go Normal file

File diff suppressed because it is too large Load Diff

15513
nexgb/xproto/xproto.go Normal file

File diff suppressed because it is too large Load Diff

384
nexgb/xproto/xproto_test.go Normal file
View File

@ -0,0 +1,384 @@
package xproto
/*
Tests for XGB.
These tests only test the core X protocol at the moment. It isn't even
close to complete coverage (and probably never will be), but it does test
a number of different corners: requests with no replies, requests without
replies, checked (i.e., synchronous) errors, unchecked (i.e., asynchronous)
errors, and sequence number wrapping.
There are also a couple of benchmarks that show the difference between
correctly issuing lots of requests and gathering replies and
incorrectly doing the same. (This particular difference is one of the
claimed advantages of the XCB, and therefore XGB, family.)
In sum, these tests are more focused on testing the core xgb package itself,
rather than whether xproto has properly implemented the core X client
protocol.
*/
import (
"fmt"
"log"
"math/rand"
"testing"
"time"
xgb "janouch.name/haven/nexgb"
)
// The X connection used throughout testing.
var X *xgb.Conn
// init initializes the X connection, seeds the RNG and starts waiting
// for events.
func init() {
var err error
X, err = xgb.NewConn()
if err != nil {
log.Fatal(err)
}
rand.Seed(time.Now().UnixNano())
go grabEvents()
}
/******************************************************************************/
// Tests
/******************************************************************************/
// TestSynchronousError purposefully causes a BadWindow error in a
// MapWindow request, and checks it synchronously.
func TestSynchronousError(t *testing.T) {
err := MapWindowChecked(X, 0).Check() // resource 0 is always invalid
if err == nil {
t.Fatalf("MapWindow: A MapWindow request that should return an " +
"error has returned a nil error.")
}
verifyMapWindowError(t, err)
}
// TestAsynchronousError does the same thing as TestSynchronousError, but
// grabs the error asynchronously instead.
func TestAsynchronousError(t *testing.T) {
MapWindow(X, 0) // resource id 0 is always invalid
evOrErr := waitForEvent(t, 5)
if evOrErr.ev != nil {
t.Fatalf("After issuing an erroneous MapWindow request, we have "+
"received an event rather than an error: %s", evOrErr.ev)
}
verifyMapWindowError(t, evOrErr.err)
}
// TestCookieBuffer issues (2^16) + n requets *without* replies to guarantee
// that the sequence number wraps and that the cookie buffer will have to
// flush itself (since there are no replies coming in to flush it).
// And just like TestSequenceWrap, we issue another request with a reply
// at the end to make sure XGB is still working properly.
func TestCookieBuffer(t *testing.T) {
n := (1 << 16) + 10
for i := 0; i < n; i++ {
NoOperation(X)
}
TestProperty(t)
}
// TestSequenceWrap issues (2^16) + n requests w/ replies to guarantee that the
// sequence number (which is a 16 bit integer) will wrap. It then issues one
// final request to ensure things still work properly.
func TestSequenceWrap(t *testing.T) {
n := (1 << 16) + 10
for i := 0; i < n; i++ {
_, err := InternAtom(X, false, 5, "RANDO").Reply()
if err != nil {
t.Fatalf("InternAtom: %s", err)
}
}
TestProperty(t)
}
// TestProperty tests whether a random value can be set and read.
func TestProperty(t *testing.T) {
propName := randString(20) // whatevs
writeVal := randString(20)
readVal, err := changeAndGetProp(propName, writeVal)
if err != nil {
t.Error(err)
}
if readVal != writeVal {
t.Errorf("The value written, '%s', is not the same as the "+
"value read '%s'.", writeVal, readVal)
}
}
// TestWindowEvents creates a window, maps it, listens for configure notify
// events, issues a configure request, and checks for the appropriate
// configure notify event.
// This probably violates the notion of "test one thing and test it well,"
// but testing X stuff is unique since it involves so much state.
// Each request is checked to make sure there are no errors returned. If there
// is an error, the test is failed.
// You may see a window appear quickly and then disappear. Do not be alarmed :P
// It's possible that this test will yield a false negative because we cannot
// control our environment. That is, the window manager could override the
// placement set. However, we set override redirect on the window, so the
// window manager *shouldn't* touch our window if it is well-behaved.
func TestWindowEvents(t *testing.T) {
// The geometry to set the window.
gx, gy, gw, gh := 200, 400, 1000, 300
wid, err := NewWindowId(X)
if err != nil {
t.Fatalf("NewId: %s", err)
}
screen := Setup(X).DefaultScreen(X) // alias
err = CreateWindowChecked(X, screen.RootDepth, wid, screen.Root,
0, 0, 500, 500, 0,
WindowClassInputOutput, screen.RootVisual,
CwBackPixel|CwOverrideRedirect, []uint32{0xffffffff, 1}).Check()
if err != nil {
t.Fatalf("CreateWindow: %s", err)
}
err = MapWindowChecked(X, wid).Check()
if err != nil {
t.Fatalf("MapWindow: %s", err)
}
// We don't listen in the CreateWindow request so that we don't get
// a MapNotify event.
err = ChangeWindowAttributesChecked(X, wid,
CwEventMask, []uint32{EventMaskStructureNotify}).Check()
if err != nil {
t.Fatalf("ChangeWindowAttributes: %s", err)
}
err = ConfigureWindowChecked(X, wid,
ConfigWindowX|ConfigWindowY|
ConfigWindowWidth|ConfigWindowHeight,
[]uint32{uint32(gx), uint32(gy), uint32(gw), uint32(gh)}).Check()
if err != nil {
t.Fatalf("ConfigureWindow: %s", err)
}
evOrErr := waitForEvent(t, 5)
switch event := evOrErr.ev.(type) {
case ConfigureNotifyEvent:
if event.X != int16(gx) {
t.Fatalf("x was set to %d but ConfigureNotify reports %d",
gx, event.X)
}
if event.Y != int16(gy) {
t.Fatalf("y was set to %d but ConfigureNotify reports %d",
gy, event.Y)
}
if event.Width != uint16(gw) {
t.Fatalf("width was set to %d but ConfigureNotify reports %d",
gw, event.Width)
}
if event.Height != uint16(gh) {
t.Fatalf("height was set to %d but ConfigureNotify reports %d",
gh, event.Height)
}
default:
t.Fatalf("Expected a ConfigureNotifyEvent but got %T instead.", event)
}
// Okay, clean up!
err = ChangeWindowAttributesChecked(X, wid,
CwEventMask, []uint32{0}).Check()
if err != nil {
t.Fatalf("ChangeWindowAttributes: %s", err)
}
err = DestroyWindowChecked(X, wid).Check()
if err != nil {
t.Fatalf("DestroyWindow: %s", err)
}
}
// Calls GetFontPath function, Issue #12
func TestGetFontPath(t *testing.T) {
fontPathReply, err := GetFontPath(X).Reply()
if err != nil {
t.Fatalf("GetFontPath: %v", err)
}
_ = fontPathReply
}
func TestListFonts(t *testing.T) {
listFontsReply, err := ListFonts(X, 10, 1, "*").Reply()
if err != nil {
t.Fatalf("ListFonts: %v", err)
}
_ = listFontsReply
}
/******************************************************************************/
// Benchmarks
/******************************************************************************/
// 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 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.
func BenchmarkInternAtomsGood(b *testing.B) {
b.StopTimer()
names := seqNames(b.N)
b.StartTimer()
cookies := make([]InternAtomCookie, b.N)
for i := 0; i < b.N; i++ {
cookies[i] = InternAtom(X, false, uint16(len(names[i])), names[i])
}
for _, cookie := range cookies {
cookie.Reply()
}
}
// BenchmarkInternAtomsBad shows how *not* to issue a lot of requests with
// replies. Namely, each subsequent request isn't issued *until* the last
// reply is made. This implies a round trip to the X server for every
// iteration.
func BenchmarkInternAtomsPoor(b *testing.B) {
b.StopTimer()
names := seqNames(b.N)
b.StartTimer()
for i := 0; i < b.N; i++ {
InternAtom(X, false, uint16(len(names[i])), names[i]).Reply()
}
}
/******************************************************************************/
// Helper functions
/******************************************************************************/
// changeAndGetProp sets property 'prop' with value 'val'.
// It then gets the value of that property and returns it.
// (It's used to check that the 'val' going in is the same 'val' going out.)
// It tests both requests with and without replies (GetProperty and
// ChangeProperty respectively.)
func changeAndGetProp(prop, val string) (string, error) {
setup := Setup(X)
root := setup.DefaultScreen(X).Root
propAtom, err := InternAtom(X, false, uint16(len(prop)), prop).Reply()
if err != nil {
return "", fmt.Errorf("InternAtom: %s", err)
}
typName := "UTF8_STRING"
typAtom, err := InternAtom(X, false, uint16(len(typName)), typName).Reply()
if err != nil {
return "", fmt.Errorf("InternAtom: %s", err)
}
err = ChangePropertyChecked(X, PropModeReplace, root, propAtom.Atom,
typAtom.Atom, 8, uint32(len(val)), []byte(val)).Check()
if err != nil {
return "", fmt.Errorf("ChangeProperty: %s", err)
}
reply, err := GetProperty(X, false, root, propAtom.Atom,
GetPropertyTypeAny, 0, (1<<32)-1).Reply()
if err != nil {
return "", fmt.Errorf("GetProperty: %s", err)
}
if reply.Format != 8 {
return "", fmt.Errorf("Property reply format is %d but it should be 8.",
reply.Format)
}
return string(reply.Value), nil
}
// verifyMapWindowError takes an error that is returned with an invalid
// MapWindow request with a window Id of 0 and makes sure the error is the
// right type and contains the correct values.
func verifyMapWindowError(t *testing.T, err error) {
switch e := err.(type) {
case WindowError:
if e.BadValue != 0 {
t.Fatalf("WindowError should report a bad value of 0 but "+
"it reports %d instead.", e.BadValue)
}
if e.MajorOpcode != 8 {
t.Fatalf("WindowError should report a major opcode of 8 "+
"(which is a MapWindow request), but it reports %d instead.",
e.MajorOpcode)
}
default:
t.Fatalf("Expected a WindowError but got %T instead.", e)
}
}
// randString generates a random string of length n.
func randString(n int) string {
byts := make([]byte, n)
for i := 0; i < n; i++ {
rando := rand.Intn(53)
switch {
case rando <= 25:
byts[i] = byte(65 + rando)
case rando <= 51:
byts[i] = byte(97 + rando - 26)
default:
byts[i] = ' '
}
}
return string(byts)
}
// seqNames creates a slice of NAME0, NAME1, ..., NAMEN.
func seqNames(n int) []string {
names := make([]string, n)
for i := range names {
names[i] = fmt.Sprintf("NAME%d", i)
}
return names
}
// evErr represents a value that is either an event or an error.
type evErr struct {
ev xgb.Event
err xgb.Error
}
// channel used to pass evErrs.
var evOrErrChan = make(chan evErr, 0)
// grabEvents is a goroutine that reads events off the wire.
// We used this instead of WaitForEvent directly in our tests so that
// we can timeout and fail a test.
func grabEvents() {
for {
ev, err := X.WaitForEvent()
evOrErrChan <- evErr{ev, err}
}
}
// waitForEvent asks the evOrErrChan channel for an event.
// If it doesn't get an event in 'n' seconds, the current test is failed.
func waitForEvent(t *testing.T, n int) evErr {
var evOrErr evErr
select {
case evOrErr = <-evOrErrChan:
case <-time.After(time.Second * 5):
t.Fatalf("After waiting 5 seconds for an event or an error, " +
"we have timed out.")
}
return evOrErr
}

2249
nexgb/xselinux/xselinux.go Normal file

File diff suppressed because it is too large Load Diff

417
nexgb/xtest/xtest.go Normal file
View File

@ -0,0 +1,417 @@
// Package xtest is the X client API for the XTEST extension.
package xtest
// This file is automatically generated from xtest.xml. Edit at your peril!
import (
xgb "janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xproto"
)
const (
MajorVersion = 2
MinorVersion = 2
)
// Init must be called before using the XTEST extension.
func Init(c *xgb.Conn) error {
reply, err := xproto.QueryExtension(c, 5, "XTEST").Reply()
switch {
case err != nil:
return err
case !reply.Present:
return xgb.Errorf("No extension named XTEST could be found on on the server.")
}
c.ExtLock.Lock()
c.Extensions["XTEST"] = reply.MajorOpcode
c.ExtLock.Unlock()
for evNum, fun := range xgb.NewExtEventFuncs["XTEST"] {
xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun
}
for errNum, fun := range xgb.NewExtErrorFuncs["XTEST"] {
xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun
}
return nil
}
func init() {
xgb.NewExtEventFuncs["XTEST"] = make(map[int]xgb.NewEventFun)
xgb.NewExtErrorFuncs["XTEST"] = make(map[int]xgb.NewErrorFun)
}
const (
CursorNone = 0
CursorCurrent = 1
)
// Skipping definition for base type 'Bool'
// Skipping definition for base type 'Byte'
// Skipping definition for base type 'Card8'
// Skipping definition for base type 'Char'
// Skipping definition for base type 'Void'
// Skipping definition for base type 'Double'
// Skipping definition for base type 'Float'
// Skipping definition for base type 'Int16'
// Skipping definition for base type 'Int32'
// Skipping definition for base type 'Int8'
// Skipping definition for base type 'Card16'
// Skipping definition for base type 'Card32'
// CompareCursorCookie is a cookie used only for CompareCursor requests.
type CompareCursorCookie struct {
*xgb.Cookie
}
// CompareCursor sends a checked request.
// If an error occurs, it will be returned with the reply by calling CompareCursorCookie.Reply.
func CompareCursor(c *xgb.Conn, Window xproto.Window, Cursor xproto.Cursor) CompareCursorCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XTEST"]; !ok {
panic("Cannot issue request 'CompareCursor' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(compareCursorRequest(c, Window, Cursor), cookie)
return CompareCursorCookie{cookie}
}
// CompareCursorUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func CompareCursorUnchecked(c *xgb.Conn, Window xproto.Window, Cursor xproto.Cursor) CompareCursorCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XTEST"]; !ok {
panic("Cannot issue request 'CompareCursor' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(compareCursorRequest(c, Window, Cursor), cookie)
return CompareCursorCookie{cookie}
}
// CompareCursorReply represents the data returned from a CompareCursor request.
type CompareCursorReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
Same bool
}
// Reply blocks and returns the reply data for a CompareCursor request.
func (cook CompareCursorCookie) Reply() (*CompareCursorReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return compareCursorReply(buf), nil
}
// compareCursorReply reads a byte slice into a CompareCursorReply value.
func compareCursorReply(buf []byte) *CompareCursorReply {
v := new(CompareCursorReply)
b := 1 // skip reply determinant
if buf[b] == 1 {
v.Same = true
} else {
v.Same = false
}
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
return v
}
// compareCursorRequest writes a CompareCursor request to a byte slice for transfer.
func compareCursorRequest(c *xgb.Conn, Window xproto.Window, Cursor xproto.Cursor) []byte {
size := 12
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XTEST"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 1 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
xgb.Put32(buf[b:], uint32(Window))
b += 4
xgb.Put32(buf[b:], uint32(Cursor))
b += 4
return buf
}
// FakeInputCookie is a cookie used only for FakeInput requests.
type FakeInputCookie struct {
*xgb.Cookie
}
// FakeInput sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func FakeInput(c *xgb.Conn, Type, Detail byte, Time uint32, Root xproto.Window, RootX, RootY int16, Deviceid byte) FakeInputCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XTEST"]; !ok {
panic("Cannot issue request 'FakeInput' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(fakeInputRequest(c, Type, Detail, Time, Root, RootX, RootY, Deviceid), cookie)
return FakeInputCookie{cookie}
}
// FakeInputChecked sends a checked request.
// If an error occurs, it can be retrieved using FakeInputCookie.Check.
func FakeInputChecked(c *xgb.Conn, Type, Detail byte, Time uint32, Root xproto.Window, RootX, RootY int16, Deviceid byte) FakeInputCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XTEST"]; !ok {
panic("Cannot issue request 'FakeInput' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(fakeInputRequest(c, Type, Detail, Time, Root, RootX, RootY, Deviceid), cookie)
return FakeInputCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook FakeInputCookie) Check() error {
return cook.Cookie.Check()
}
// fakeInputRequest writes a FakeInput request to a byte slice for transfer.
func fakeInputRequest(c *xgb.Conn, Type, Detail byte, Time uint32, Root xproto.Window, RootX, RootY int16, Deviceid byte) []byte {
size := 36
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XTEST"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 2 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
buf[b] = Type
b += 1
buf[b] = Detail
b += 1
b += 2 // padding
xgb.Put32(buf[b:], Time)
b += 4
xgb.Put32(buf[b:], uint32(Root))
b += 4
b += 8 // padding
xgb.Put16(buf[b:], uint16(RootX))
b += 2
xgb.Put16(buf[b:], uint16(RootY))
b += 2
b += 7 // padding
buf[b] = Deviceid
b += 1
return buf
}
// GetVersionCookie is a cookie used only for GetVersion requests.
type GetVersionCookie struct {
*xgb.Cookie
}
// GetVersion sends a checked request.
// If an error occurs, it will be returned with the reply by calling GetVersionCookie.Reply.
func GetVersion(c *xgb.Conn, MajorVersion byte, MinorVersion uint16) GetVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XTEST"]; !ok {
panic("Cannot issue request 'GetVersion' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, true)
c.NewRequest(getVersionRequest(c, MajorVersion, MinorVersion), cookie)
return GetVersionCookie{cookie}
}
// GetVersionUnchecked sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GetVersionUnchecked(c *xgb.Conn, MajorVersion byte, MinorVersion uint16) GetVersionCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XTEST"]; !ok {
panic("Cannot issue request 'GetVersion' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, true)
c.NewRequest(getVersionRequest(c, MajorVersion, MinorVersion), cookie)
return GetVersionCookie{cookie}
}
// GetVersionReply represents the data returned from a GetVersion request.
type GetVersionReply struct {
Sequence uint16 // sequence number of the request for this reply
Length uint32 // number of bytes in this reply
MajorVersion byte
MinorVersion uint16
}
// Reply blocks and returns the reply data for a GetVersion request.
func (cook GetVersionCookie) Reply() (*GetVersionReply, error) {
buf, err := cook.Cookie.Reply()
if err != nil {
return nil, err
}
if buf == nil {
return nil, nil
}
return getVersionReply(buf), nil
}
// getVersionReply reads a byte slice into a GetVersionReply value.
func getVersionReply(buf []byte) *GetVersionReply {
v := new(GetVersionReply)
b := 1 // skip reply determinant
v.MajorVersion = buf[b]
b += 1
v.Sequence = xgb.Get16(buf[b:])
b += 2
v.Length = xgb.Get32(buf[b:]) // 4-byte units
b += 4
v.MinorVersion = xgb.Get16(buf[b:])
b += 2
return v
}
// getVersionRequest writes a GetVersion request to a byte slice for transfer.
func getVersionRequest(c *xgb.Conn, MajorVersion byte, MinorVersion uint16) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XTEST"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 0 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
buf[b] = MajorVersion
b += 1
b += 1 // padding
xgb.Put16(buf[b:], MinorVersion)
b += 2
return buf
}
// GrabControlCookie is a cookie used only for GrabControl requests.
type GrabControlCookie struct {
*xgb.Cookie
}
// GrabControl sends an unchecked request.
// If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent.
func GrabControl(c *xgb.Conn, Impervious bool) GrabControlCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XTEST"]; !ok {
panic("Cannot issue request 'GrabControl' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.")
}
cookie := c.NewCookie(false, false)
c.NewRequest(grabControlRequest(c, Impervious), cookie)
return GrabControlCookie{cookie}
}
// GrabControlChecked sends a checked request.
// If an error occurs, it can be retrieved using GrabControlCookie.Check.
func GrabControlChecked(c *xgb.Conn, Impervious bool) GrabControlCookie {
c.ExtLock.RLock()
defer c.ExtLock.RUnlock()
if _, ok := c.Extensions["XTEST"]; !ok {
panic("Cannot issue request 'GrabControl' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.")
}
cookie := c.NewCookie(true, false)
c.NewRequest(grabControlRequest(c, Impervious), cookie)
return GrabControlCookie{cookie}
}
// Check returns an error if one occurred for checked requests that are not expecting a reply.
// This cannot be called for requests expecting a reply, nor for unchecked requests.
func (cook GrabControlCookie) Check() error {
return cook.Cookie.Check()
}
// grabControlRequest writes a GrabControl request to a byte slice for transfer.
func grabControlRequest(c *xgb.Conn, Impervious bool) []byte {
size := 8
b := 0
buf := make([]byte, size)
c.ExtLock.RLock()
buf[b] = c.Extensions["XTEST"]
c.ExtLock.RUnlock()
b += 1
buf[b] = 3 // request opcode
b += 1
xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units
b += 2
if Impervious {
buf[b] = 1
} else {
buf[b] = 0
}
b += 1
b += 3 // padding
return buf
}

3048
nexgb/xv/xv.go Normal file

File diff suppressed because it is too large Load Diff

1037
nexgb/xvmc/xvmc.go Normal file

File diff suppressed because it is too large Load Diff

199
prototypes/xgb-selection.go Normal file
View File

@ -0,0 +1,199 @@
// Follow X11 selection contents as they are being changed.
package main
import (
"janouch.name/haven/nexgb"
"janouch.name/haven/nexgb/xfixes"
"janouch.name/haven/nexgb/xproto"
"log"
)
func main() {
X, err := nexgb.NewConn()
if err != nil {
log.Fatalln(err)
}
if err := xfixes.Init(X); err != nil {
log.Fatalln(err)
}
setup := xproto.Setup(X)
screen := setup.DefaultScreen(X)
// Resolve a few required atoms that are not static in the core protocol.
const (
clipboard = "CLIPBOARD"
utf8String = "UTF8_STRING"
incr = "INCR"
)
atomCLIPBOARD, err := xproto.InternAtom(X, false,
uint16(len(clipboard)), clipboard).Reply()
if err != nil {
log.Fatalln(err)
}
atomUTF8String, err := xproto.InternAtom(X, false,
uint16(len(utf8String)), utf8String).Reply()
if err != nil {
log.Fatalln(err)
}
atomINCR, err := xproto.InternAtom(X, false,
uint16(len(incr)), incr).Reply()
if err != nil {
log.Fatalln(err)
}
// Create a window.
wid, err := xproto.NewWindowId(X)
if err != nil {
log.Fatalln(err)
}
_ = xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root, 0, 0, 1, 1,
0, xproto.WindowClassInputOutput, screen.RootVisual, xproto.CwEventMask,
[]uint32{xproto.EventMaskPropertyChange})
// Select for update events of each selection.
_ = xfixes.QueryVersion(X, xfixes.MajorVersion, xfixes.MinorVersion)
_ = xfixes.SelectSelectionInput(X, wid,
xproto.AtomPrimary, xfixes.SelectionEventMaskSetSelectionOwner|
xfixes.SelectionEventMaskSelectionWindowDestroy|
xfixes.SelectionEventMaskSelectionClientClose)
_ = xfixes.SelectSelectionInput(X, wid,
atomCLIPBOARD.Atom, xfixes.SelectionEventMaskSetSelectionOwner|
xfixes.SelectionEventMaskSelectionWindowDestroy|
xfixes.SelectionEventMaskSelectionClientClose)
type selectionState struct {
name string // name of the selection
inProgress xproto.Timestamp // timestamp of retrieved selection
buffer []byte // UTF-8 text buffer
incr bool // INCR running
incrFailed bool // INCR failure indicator
}
states := map[xproto.Atom]*selectionState{
xproto.AtomPrimary: {name: "PRIMARY"},
atomCLIPBOARD.Atom: {name: "CLIPBOARD"},
}
for {
ev, xerr := X.WaitForEvent()
if xerr != nil {
log.Printf("Error: %s\n", xerr)
return
}
if ev == nil {
return
}
switch e := ev.(type) {
case xfixes.SelectionNotifyEvent:
state, ok := states[e.Selection]
if !ok {
break
}
// Not checking whether we should give up when our current retrieval
// attempt is interrupted--the timeout mostly solves this.
if e.Owner == xproto.WindowNone {
// This may potentially log before a request for past data
// finishes, if only because of INCR. Just saying.
log.Printf("%s: -\n", state.name)
break
}
// Don't try to process two things at once. Each request gets a few
// seconds to finish, then we move on, hoping that a property race
// doesn't commence. Ideally we'd set up a separate queue for these
// skipped requests and process them later.
if state.inProgress != 0 && e.Timestamp-state.inProgress < 5000 {
break
}
// ICCCM says we should ensure the named property doesn't exist.
_ = xproto.DeleteProperty(X, e.Window, e.Selection)
_ = xproto.ConvertSelection(X, e.Window, e.Selection,
atomUTF8String.Atom, e.Selection, e.Timestamp)
state.inProgress = e.Timestamp
state.incr = false
case xproto.SelectionNotifyEvent:
state, ok := states[e.Selection]
if e.Requestor != wid || !ok || e.Time != state.inProgress {
break
}
state.inProgress = 0
if e.Property == xproto.AtomNone {
break
}
// XXX: This is simplified and doesn't necessarily read it all.
// Maybe we could use setup.MaximumRequestLength.
// Though xorg-xserver doesn't seem to limit the length of replies
// or even the length of properties in the first place.
// It only has a huge (0xffffffff - sizeof(xChangePropertyReq))/4
// limit for ChangeProperty requests, which is way more than
// max-request-len (normally 0xffff), even though I can't
// XChangeProperty more than 0xffffe0 bytes at a time.
reply, err := xproto.GetProperty(X, false, /* delete */
e.Requestor, e.Property, xproto.GetPropertyTypeAny,
0, 0x8000).Reply()
if err != nil {
break
}
state.buffer = nil
// When you select a lot of text in VIM, it starts the ICCCM
// INCR mechanism, from which there is no opt-out.
if reply.Type == atomINCR.Atom {
state.inProgress = e.Time
state.incr = true
state.incrFailed = false
} else if reply.Type == atomUTF8String.Atom && reply.Format == 8 {
log.Printf("%s: '%s'\n", state.name, string(reply.Value))
}
_ = xproto.DeleteProperty(X, e.Requestor, e.Property)
case xproto.PropertyNotifyEvent:
state, ok := states[e.Atom]
if e.Window != wid || e.State != xproto.PropertyNewValue ||
!ok || !state.incr {
break
}
reply, err := xproto.GetProperty(X, false, /* delete */
e.Window, e.Atom, xproto.GetPropertyTypeAny, 0, 0x8000).Reply()
if err != nil {
state.incrFailed = true
break
}
if reply.Type == atomUTF8String.Atom && reply.Format == 8 {
state.buffer = append(state.buffer, reply.Value...)
} else {
// We need to keep deleting the property.
state.incrFailed = true
}
if reply.ValueLen == 0 {
if !state.incrFailed {
log.Printf("%s: '%s'\n",
state.name, string(state.buffer))
}
state.inProgress = 0
state.incr = false
}
_ = xproto.DeleteProperty(X, e.Window, e.Atom)
}
}
}