| Left: | ||
| Right: |
| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2009 The Go Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style | |
| 3 // license that can be found in the LICENSE file. | |
| 4 | |
| 5 // The XGB package implements the X11 core protocol. | |
| 6 // It is based on XCB: http://xcb.freedesktop.org/ | |
| 7 package xgb | |
| 8 | |
| 9 import "fmt" | |
| nigeltao 2009/11/30 13:02:33 Russ -- is it conventional to have only one "impor rsc 2009/11/30 17:16:21 Good catch - this import list should be factored. | |
| 10 import "os" | |
| 11 import "io" | |
| 12 import "net" | |
| 13 import "strings" | |
| 14 import "strconv" | |
| 15 | |
| 16 // The connection to the server. This struct is not reentrant, so please | |
| rsc 2009/11/30 02:01:11 Documentation comments should begin with the name tor 2009/11/30 13:48:46 Done. | |
| 17 // only call its methods from within one goroutine. | |
| 18 type Conn struct { | |
| 19 sock net.Conn; | |
| rsc 2009/11/30 02:01:11 please call this conn (on some systems it might no tor 2009/11/30 13:48:46 Done. | |
| 20 nextId uint32; | |
| 21 nextCookie uint16; | |
| 22 replies map[uint16][]byte; | |
| nigeltao 2009/11/30 13:02:33 Russ -- would it be worth having type seqNum uint1 tor 2009/11/30 13:48:46 I decided against using too many type aliases, esp rsc 2009/11/30 17:16:21 This seems like a good compromise: it's always con | |
| 23 events queue; | |
| 24 error os.Error; | |
| rsc 2009/11/30 02:01:11 typically err no error. tor 2009/11/30 13:48:46 I've been writing error and okay for five years in rsc 2009/11/30 17:16:21 I certainly understand the consistency in your oth | |
| 25 defaultScreen int; | |
| 26 Setup SetupInfo; | |
| 27 } | |
| 28 | |
| 29 // An interface that can contain any of the events returned by the server. | |
| 30 // Use a type assertion switch to extract the Event structs. | |
| 31 type Event interface{} | |
| 32 | |
| 33 // Generate a new unused ID for use with requests like CreateWindow. | |
| nigeltao 2009/11/30 13:02:33 Can you add a TODO comment to check for ID overflo tor 2009/11/30 13:48:46 What should the behavior be when we run out? rsc 2009/11/30 17:16:21 That's why it's a TODO, I guess. ;-) // TODO(tor, | |
| 34 func (c *Conn) NewId() uint32 { | |
| 35 id := c.nextId; | |
| 36 c.nextId++; | |
| 37 return id; | |
| 38 } | |
| 39 | |
| 40 // Pad a length to align on 4 bytes. | |
| 41 func pad(n int) int { return (n + 3) & ^3 } | |
| 42 | |
| 43 func put8(buf []byte, offset int, v uint8) { buf[offset] = v } | |
| rsc 2009/11/30 02:01:11 Instead of passing offset explicitly, you could ju tor 2009/11/30 13:48:46 Good idea, but I could also make the auto generate | |
| 44 func get8(buf []byte, offset int) uint8 { return buf[offset] } | |
| 45 | |
| 46 func put16(buf []byte, offset int, v uint16) { | |
| 47 buf[offset] = byte(v); | |
| 48 buf[offset+1] = byte(v >> 8); | |
| 49 } | |
| 50 | |
| 51 func put32(buf []byte, offset int, v uint32) { | |
| 52 buf[offset] = byte(v); | |
| 53 buf[offset+1] = byte(v >> 8); | |
| 54 buf[offset+2] = byte(v >> 16); | |
| 55 buf[offset+3] = byte(v >> 24); | |
| 56 } | |
| 57 | |
| 58 func get16(buf []byte, offset int) uint16 { | |
| 59 v := uint16(buf[offset]); | |
| 60 v |= uint16(buf[offset+1]) << 8; | |
| 61 return v; | |
| 62 } | |
| 63 | |
| 64 func get32(buf []byte, offset int) uint32 { | |
| 65 v := uint32(buf[offset]); | |
| 66 v |= uint32(buf[offset+1]) << 8; | |
| 67 v |= uint32(buf[offset+2]) << 16; | |
| 68 v |= uint32(buf[offset+3]) << 32; | |
| 69 return v; | |
| 70 } | |
| 71 | |
| 72 // Voodoo to count the number of bits set in a value list mask. | |
| 73 func popCount(mask0 int) int { | |
| 74 mask := uint32(mask0); | |
| 75 y := (mask >> 1) & 033333333333; | |
| 76 y = mask - y - ((y >> 1) & 033333333333); | |
| 77 y = ((y + (y >> 3)) & 030707070707) % 077; | |
| rsc 2009/11/30 02:01:11 The % 077 trick might have made sense on a PDP-10, tor 2009/11/30 13:48:46 Done. | |
| 78 return int(y); | |
| 79 } | |
| 80 | |
| 81 // A simple queue used to stow away events. | |
| 82 type queue struct { | |
| 83 data [][]byte; | |
| 84 a, b int; | |
| 85 } | |
| 86 | |
| 87 func (q *queue) queue(item []byte) { | |
| 88 if q.b == len(q.data) { | |
| 89 if q.a > 0 { | |
| 90 for i := q.a; i < q.b; i++ { | |
| rsc 2009/11/30 02:01:11 If you update to the latest tree (cd $GOROOT/src; tor 2009/11/30 13:48:46 Or I could just have a and b wrap around len(data) | |
| 91 q.data[i-q.a] = q.data[i] | |
| 92 } | |
| 93 q.a, q.b = 0, q.b-q.a; | |
| 94 } else { | |
| 95 newData := make([][]byte, (len(q.data)*3)/2); | |
| 96 for i := q.a; i < q.b; i++ { | |
| rsc 2009/11/30 02:01:11 copy(newData, q.data); tor 2009/11/30 13:48:46 Done. | |
| 97 newData[i] = q.data[i] | |
| 98 } | |
| 99 q.data = newData; | |
| 100 } | |
| 101 } | |
| 102 q.data[q.b] = item; | |
| 103 q.b++; | |
| 104 } | |
| 105 | |
| 106 func (q *queue) unqueue() []byte { | |
| rsc 2009/11/30 02:01:11 the verb is usually dequeue. tor 2009/11/30 13:48:46 Done. | |
| 107 if q.a < q.b { | |
| 108 item := q.data[q.a]; | |
| 109 q.a++; | |
| 110 return item; | |
| 111 } | |
| 112 return nil; | |
| 113 } | |
| 114 | |
| 115 // Send a request to the server and return its associated sequence number, or co okie. | |
| rsc 2009/11/30 02:01:11 // sendRequest sends ... and returns ... tor 2009/11/30 13:48:46 Done. | |
| 116 // This will only send the fixed length portion of the request. | |
| 117 func (c *Conn) sendRequest(buf []byte) uint16 { | |
| 118 _, error := c.sock.Write(buf); | |
| 119 if error != nil { | |
| 120 fmt.Fprintf(os.Stderr, "x protocol write error: %s\n", error); | |
| nigeltao 2009/11/30 13:02:33 Nit: I think the "X" in "X protocol" should be cap | |
| 121 c.error = error; | |
| 122 } | |
| 123 cookie := c.nextCookie; | |
| 124 c.nextCookie++; | |
| 125 return cookie; | |
| 126 } | |
| 127 | |
| 128 // Send enough bytes to align to a 4-byte border. | |
| rsc 2009/11/30 02:01:11 // sendPadding sends .... // It is used to pad ... tor 2009/11/30 13:48:46 Done. | |
| 129 // Used to pad the variable length data that is used with some requests. | |
| 130 func (c *Conn) sendPadding(n int) { | |
| 131 var three [3]byte; | |
| rsc 2009/11/30 02:01:11 This allocates each time sendPadding is called. It tor 2009/11/30 13:48:46 Ow. I just assumed that since it was a constant si rsc 2009/11/30 17:16:21 If you take its address and the compiler isn't sur | |
| 132 x := pad(n) - n; | |
| 133 if x > 0 { | |
| 134 _, error := c.sock.Write(three[0:x]); | |
| rsc 2009/11/30 02:01:11 Typically called err not error. (etc.) tor 2009/11/30 13:48:46 Done. | |
| 135 if error != nil { | |
| 136 fmt.Fprintf(os.Stderr, "x protocol write error: %s\n", e rror); | |
| 137 c.error = error; | |
| 138 } | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 // Send a byte slice as variable length data after the fixed portion of a reques t. | |
| 143 // Automatically pads. | |
| 144 func (c *Conn) sendBytes(buf []byte) { | |
| 145 _, error := c.sock.Write(buf); | |
| 146 if error != nil { | |
| 147 fmt.Fprintf(os.Stderr, "x protocol write error: %s\n", error); | |
| 148 c.error = error; | |
| 149 } | |
| 150 c.sendPadding(len(buf)); | |
| 151 } | |
| 152 | |
| 153 func (c *Conn) sendString(str string) { c.sendBytes(strings.Bytes(str)) } | |
| 154 | |
| 155 // Send a list of 32-bit integers as variable length data. | |
| 156 func (c *Conn) sendUInt32s(list []uint32) { | |
| 157 buf := make([]byte, len(list)*4); | |
| 158 for i := 0; i < len(list); i++ { | |
| 159 put32(buf, i*4, list[i]) | |
| 160 } | |
| 161 c.sendBytes(buf); | |
| 162 } | |
| 163 | |
| 164 // Read the next server reply. If it is a protocol error then return it as an er ror. | |
| 165 // Push events onto the event queue and stash away replies to requests in a map | |
| 166 // indexed by the sequence number. | |
| 167 func (c *Conn) readNextReply() os.Error { | |
| nigeltao 2009/11/30 13:02:33 Since this method reads both replies and events, p | |
| 168 buf := make([]byte, 32); | |
| 169 _, error := io.ReadFull(c.sock, buf); | |
| 170 if error != nil { | |
| 171 fmt.Fprintf(os.Stderr, "x protocol read error: %s\n", error); | |
| 172 return error; | |
| 173 } | |
| 174 | |
| 175 if buf[0] == 0 { | |
| rsc 2009/11/30 02:01:11 switch buf[0] { tor 2009/11/30 13:48:46 Done. | |
| 176 detail := buf[1]; | |
| 177 cookie := get16(buf, 2); | |
| 178 id := get32(buf, 4); | |
| 179 minor := get16(buf, 8); | |
| 180 major := buf[10]; | |
| 181 str := fmt.Sprintf("Bad%s (major=%d minor=%d cookie=%d id=0x%x)" , | |
| 182 errorNames[detail], major, minor, cookie, id); | |
| 183 fmt.Fprintf(os.Stderr, "x protocol error: %s\n", str); | |
| 184 return os.NewError(str); | |
| rsc 2009/11/30 02:01:11 More idiomatic Go would be to put this information tor 2009/11/30 13:48:46 Done. | |
| 185 } | |
| 186 | |
| 187 if buf[0] == 1 { | |
| 188 seq := get16(buf, 2); | |
| 189 size := get32(buf, 4); | |
| 190 if size > 0 { | |
| 191 bigbuf := make([]byte, 32+size*4, 32+size*4); | |
| 192 copy(bigbuf[0:32], buf); | |
| rsc 2009/11/30 02:01:11 I see you found copy. On the next line you can dr tor 2009/11/30 13:48:46 Done. | |
| 193 _, error = io.ReadFull(c.sock, bigbuf[32:len(bigbuf)]); | |
| 194 if error != nil { | |
| 195 fmt.Fprintf(os.Stderr, "x protocol read error: % s\n", error); | |
| 196 return error; | |
| 197 } | |
| 198 c.replies[seq] = bigbuf; | |
| 199 } else { | |
| 200 c.replies[seq] = buf | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 if buf[0] > 1 { | |
| 205 c.events.queue(buf) | |
| 206 } | |
| 207 | |
| 208 return nil; | |
| 209 } | |
| 210 | |
| 211 // Looks for a reply in the map indexed by sequence number. | |
| 212 // Will block while reading replies from the server until it is found. | |
| 213 func (c *Conn) waitForReply(cookie uint16) ([]byte, os.Error) { | |
| 214 for { | |
| 215 reply, exists := c.replies[cookie]; | |
| rsc 2009/11/30 02:01:11 exists is fine, but this is typically ok, and merg tor 2009/11/30 13:48:46 I generally dislike putting more than one concept/ rsc 2009/11/30 17:16:21 I'm sympathetic to that but it's still a common Go | |
| 216 if exists { | |
| 217 c.replies[cookie] = reply, false; | |
| 218 return reply, nil; | |
| 219 } | |
| 220 error := c.readNextReply(); | |
| 221 if error != nil { | |
| 222 return nil, error | |
| 223 } | |
| 224 } | |
| 225 panic("unreachable"); | |
| 226 } | |
| 227 | |
| 228 // Return the next event from the server. | |
| 229 // Block until an event is available. | |
| 230 func (c *Conn) WaitForEvent() (Event, os.Error) { | |
| 231 for { | |
| 232 reply := c.events.unqueue(); | |
| 233 if reply != nil { | |
| 234 return parseEvent(reply), nil | |
| rsc 2009/11/30 02:01:11 It seems like if parseEvent fails, it should retur tor 2009/11/30 13:48:46 Done. | |
| 235 } | |
| 236 error := c.readNextReply(); | |
| 237 if error != nil { | |
| 238 return nil, error | |
| 239 } | |
| 240 } | |
| 241 panic("unreachable"); | |
| 242 } | |
| 243 | |
| 244 // Return the next event from the server if one is available in the internal que ue. | |
| 245 // This function will not read from the socket, so you must call WaitForEvent to receive new events. | |
| 246 // Only use this function to empty the queue without blocking, | |
| 247 // for instance when processing Expose events. | |
| 248 func (c *Conn) PollForEvent() Event { | |
| 249 reply := c.events.unqueue(); | |
| 250 if reply != nil { | |
| 251 return parseEvent(reply) | |
| 252 } | |
| 253 return nil; | |
| 254 } | |
| 255 | |
| 256 // Return the Screen info for the default screen (0 or the one given in the DISP LAY environment variable). | |
| rsc 2009/11/30 02:01:11 This mentions the DISPLAY environment variable but tor 2009/11/30 13:48:46 Done. | |
| 257 func (c *Conn) DefaultScreen() *ScreenInfo { return &c.Setup.Roots[c.defaul tScreen] } | |
| 258 | |
| 259 // Connect to the X server given in the 'display' string. | |
| nigeltao 2009/11/30 13:02:33 Is there an example main.go program to show me how tor 2009/11/30 13:48:46 See my darcs repo for an example program: http:// | |
| 260 func Dial(display string) (*Conn, os.Error) { | |
| 261 var error os.Error; | |
| 262 | |
| 263 c := new(Conn); | |
| 264 | |
| 265 if display[0] == '/' { | |
| 266 c.sock, error = net.Dial("unix", "", display); | |
| 267 if error != nil { | |
| 268 fmt.Printf("cannot connect: %v\n", error); | |
| 269 return nil, error; | |
| 270 } | |
| 271 } else { | |
| 272 parts := strings.Split(display, ":", 2); | |
| 273 host := parts[0]; | |
| 274 port := 0; | |
| 275 if len(parts) > 1 { | |
| 276 parts = strings.Split(parts[1], ".", 2); | |
| 277 port, _ = strconv.Atoi(parts[0]); | |
| 278 if len(parts) > 1 { | |
| 279 c.defaultScreen, _ = strconv.Atoi(parts[1]) | |
| 280 } | |
| 281 } | |
| 282 display = fmt.Sprintf("%s:%d", host, port+6000); | |
| 283 c.sock, error = net.Dial("tcp", "", display); | |
| 284 if error != nil { | |
| 285 fmt.Printf("cannot connect: %v\n", error); | |
| 286 return nil, error; | |
| 287 } | |
| 288 } | |
| 289 | |
| 290 var authName, authData []byte; | |
| rsc 2009/11/30 02:01:11 // TODO: Fill these in? tor 2009/11/30 13:48:46 Indeed. It's one of the things I haven't done yet, | |
| 291 var buf []byte; | |
| nigeltao 2009/11/30 13:02:33 I would fold this line with the next one using the tor 2009/11/30 13:48:46 I put it separately because I reuse the variable f rsc 2009/11/30 17:16:21 You can still use := though. It's pretty unusual t | |
| 292 | |
| 293 buf = make([]byte, 12+pad(len(authName))+pad(len(authData))); | |
| 294 buf[0] = 'l'; | |
| 295 buf[1] = 0; | |
| 296 put16(buf, 2, 11); | |
| 297 put16(buf, 4, 0); | |
| 298 put16(buf, 6, uint16(len(authName))); | |
| 299 put16(buf, 8, uint16(len(authData))); | |
| 300 put16(buf, 10, 0); | |
| 301 copy(buf[12:], authName); | |
| 302 copy(buf[12+pad(len(authName)):], authData); | |
| 303 _, error = c.sock.Write(buf); | |
| 304 if error != nil { | |
| 305 return nil, error | |
| 306 } | |
| 307 | |
| 308 head := make([]byte, 8); | |
| 309 _, error = io.ReadFull(c.sock, head[0:8]); | |
| 310 if error != nil { | |
| 311 return nil, error | |
| 312 } | |
| 313 code := head[0]; | |
| 314 reasonLen := head[1]; | |
| 315 major := get16(head, 2); | |
| 316 minor := get16(head, 4); | |
| 317 dataLen := get16(head, 6); | |
| 318 | |
| 319 if major != 11 || minor != 0 { | |
| 320 return nil, os.NewError(fmt.Sprintf("x protocol version mismatch : %d.%d", major, minor)) | |
| 321 } | |
| 322 | |
| 323 buf = make([]byte, int(dataLen)*4+8, int(dataLen)*4+8); | |
| 324 copy(buf, head); | |
| 325 _, error = io.ReadFull(c.sock, buf[8:]); | |
| 326 if error != nil { | |
| 327 return nil, error | |
| 328 } | |
| 329 | |
| 330 if code == 0 { | |
| 331 reason := buf[8 : 8+reasonLen]; | |
| 332 return nil, os.NewError(fmt.Sprintf("x protocol authentication r efused: %s", string(reason))); | |
| 333 } | |
| 334 | |
| 335 parseSetupInfo(buf, &c.Setup); | |
| 336 | |
| 337 if c.defaultScreen >= len(c.Setup.Roots) { | |
| 338 c.defaultScreen = 0 | |
| 339 } | |
| 340 | |
| 341 c.nextId = c.Setup.ResourceIdBase; | |
| 342 c.nextCookie = 1; | |
| 343 c.replies = make(map[uint16][]byte); | |
| 344 c.events = queue{make([][]byte, 100), 0, 0}; | |
| 345 return c, nil; | |
| 346 } | |
| 347 | |
| 348 // Close the connection. | |
| 349 func (c *Conn) Close() { c.sock.Close() } | |
| 350 | |
| 351 // Go doesn't have unions so we duplicate the data in all | |
| 352 // three formats in ClientMessageData instead. | |
| 353 type ClientMessageData struct /*union */ { | |
| nigeltao 2009/11/30 13:02:33 I'd drop the /*union*/, since you've already made tor 2009/11/30 13:48:46 Done. | |
| 354 Data8 [20]byte; | |
| 355 Data16 [10]uint16; | |
| 356 Data32 [5]uint32; | |
| 357 } | |
| 358 | |
| 359 func parseClientMessageData(b []byte, v *ClientMessageData) int { | |
| 360 copy(&v.Data8, b); | |
| 361 for i := 0; i < 10; i++ { | |
| 362 v.Data16[i] = get16(b, i*2) | |
| 363 } | |
| 364 for i := 0; i < 5; i++ { | |
| 365 v.Data32[i] = get32(b, i*4) | |
| 366 } | |
| 367 return 20; | |
| 368 } | |
| OLD | NEW |