package xgob import ( "bufio" "bytes" "io" "net" "os" "strconv" "strings" "sync" "time" "xgob/xau" ) const X_TCP_PORT = 6000 const ( LSBFirst = iota MSBFirst = iota ) const ( FamilyInternet uint16 = 0 FamilyDECNet = 1 FamilyCHAOS = 2 FamilyServerInterpreted = 5 FamilyInternet6 = 6 ) type Format struct { Depth, BitsPerPixel, ScanlinePad uint8 } type VisualType struct { VisualID uint32 Class uint8 BitsPerRGBValue uint8 ColormapEntries uint16 RedMask uint32 GreenMask uint32 BlueMask uint32 } type Depth struct { Depth uint8 Visuals []VisualType } type Screen struct { Root uint32 DefaultColormap uint32 WhitePixel uint32 BlackPixel uint32 CurrentInputMasks uint32 WidthInPixels uint16 HeightInPixels uint16 WidthInMillimeters uint16 HeightInMillimeters uint16 MinInstalledMaps uint16 MaxInstalledMaps uint16 RootVisual uint32 BackingStores uint8 SaveUnders bool RootDepth uint8 AllowedDepths []Depth } type ConnectionBlock struct { Major, Minor uint16 Release uint32 ResourceIDBase uint32 ResourceIDMask uint32 MotionBufferSize uint32 MaximumRequestLength uint16 ImageByteOrder uint8 BitmapBitOrder uint8 BitmapScanlineUnit uint8 BitmapScanlinePad uint8 MinKeycode, MaxKeycode uint8 Vendor string PixmapFormats []Format Roots []Screen } type Event struct { EventType uint8 Sent bool Remainder [31]byte } type Error struct { Sequence uint64 BadValue uint32 Minor uint16 Major uint8 Err uint8 } type Reply struct { Sequence uint64 Length uint32 Remainder []byte Aux byte } type Connection struct { Setup ConnectionBlock Event chan Event Error chan Error conn io.ReadWriteCloser out *bufio.Writer reply map[uint64]chan interface{} callback map[uint64]func(interface{}) replyMutex sync.Mutex writeMutex sync.Mutex lastRequestWritten uint64 lastSequenceRead uint64 lastReplierSent uint64 } const events_before_readblock = 10000 const errors_before_readblock = 10000 func parse_display(name string) (host string, protocol string, display int, screen int) { if len(name) == 0 { name = os.Getenv("DISPLAY") } if len(name) == 0 { return } if !strings.HasPrefix(name, "/tmp/launch") { slash := strings.LastIndex(name, "/") if slash >= 0 { protocol = name[:slash] name = name[slash+1:] } } var err error dot := strings.LastIndex(name, ".") if dot >= 0 { screen, err = strconv.Atoi(name[dot+1:]) if err == nil { name = name[:dot] } } colon := strings.LastIndex(name, ":") if colon < 0 { return } display, err = strconv.Atoi(name[colon+1:]) if err != nil { return } host = name[:colon] return } func open(host string, protocol string, display int) io.ReadWriteCloser { unix_base := "/tmp/.X11-unix/X" base := unix_base if strings.HasPrefix(host, "/tmp/launch") { base = host host = "" protocol = "" } if len(host) > 0 || len(protocol) > 0 { if len(protocol) > 0 || "unix" != host { /* follow the old unix: rule */ /* display specifies TCP */ port := X_TCP_PORT + display return open_tcp(host, protocol, port) } } var file string /* display specifies Unix socket */ if strings.HasPrefix(base, "/tmp/launch") { file = base + ":" + strconv.Itoa(display) } else { file = base + strconv.Itoa(display) } return open_unix(protocol, file) } func open_tcp(host string, protocol string, port int) io.ReadWriteCloser { if len(protocol) > 0 && "tcp" != protocol && "inet" != protocol && "inet6" != protocol { return nil } proto := "tcp" if protocol == "inet6" { proto = "tcp6" } if len(host) == 0 { host = "localhost" } conn, err := net.Dial(proto, host+":"+strconv.Itoa(port)) // TODO - remove if err != nil { println("TCP error ", err.Error()) } return conn } func open_unix(protocol string, file string) io.ReadWriteCloser { if len(protocol) > 0 && "unix" != protocol { return nil } conn, err := net.Dial("unix", file) // TODO - remove if err != nil { println("Unix socket error ", err.Error()) } return conn } func Pad(i int) int { return (-i) & 3 } func (c *Connection) write_setup(name string, data []byte) { buf := make([]byte, 0, 100) buf = append(buf, 'B') // Big endian. buf = append(buf, 'l') buf = append(buf, 0, 11) // X11 buf = append(buf, 0, 0) // .0 buf = append(buf, byte((len(name)&0xFF00)>>8), byte(len(name)&0xFF)) buf = append(buf, byte((len(data)&0xFF00)>>8), byte(len(data)&0xFF)) buf = append(buf, 0, 0) // pad if len(name) > 0 && len(data) > 0 { n := bytes.NewBufferString(name) buf = append(buf, n.Bytes()...) for i := 0; i < Pad(len(name)); i++ { buf = append(buf, 0) } buf = append(buf, data...) for i := 0; i < Pad(len(data)); i++ { buf = append(buf, 0) } } c.conn.Write(buf) } func readUint16(in *bufio.Reader) uint16 { var rv uint16 i, _ := in.ReadByte() rv = uint16(i) << 8 i, _ = in.ReadByte() rv |= uint16(i) return rv } func readUint32(in *bufio.Reader) uint32 { var rv uint32 i, _ := in.ReadByte() rv = uint32(i) << 24 i, _ = in.ReadByte() rv |= uint32(i) << 16 i, _ = in.ReadByte() rv |= uint32(i) << 8 i, _ = in.ReadByte() rv |= uint32(i) return rv } func skipBytes(in *bufio.Reader, skip uint) { for i := uint(0); i < skip; i++ { _, _ = in.ReadByte() } } func readAll(in *bufio.Reader, buf []byte) { var read int len := len(buf) for read < len { input, err := in.Read(buf[read:]) if err != nil { return } read += input } } func getUint16(buf []byte) (uint16, []byte) { var rv uint16 rv = uint16(buf[0]) << 8 rv |= uint16(buf[1]) return rv, buf[2:] } func getUint32(buf []byte) (uint32, []byte) { var rv uint32 rv = uint32(buf[0]) << 24 rv |= uint32(buf[1]) << 16 rv |= uint32(buf[2]) << 8 rv |= uint32(buf[3]) return rv, buf[4:] } func (c *Connection) read_setup(in *bufio.Reader) bool { status, _ := in.ReadByte() reason_len, _ := in.ReadByte() c.Setup.Major = readUint16(in) c.Setup.Minor = readUint16(in) length := readUint16(in) todo := make([]byte, length*4) readAll(in, todo) switch status { case 0: println("Server refused connection because") println(bytes.NewBuffer(todo[:reason_len]).String()) case 1: /* Success */ c.Setup.Release, todo = getUint32(todo) c.Setup.ResourceIDBase, todo = getUint32(todo) c.Setup.ResourceIDMask, todo = getUint32(todo) c.Setup.MotionBufferSize, todo = getUint32(todo) vendorLen, todo := getUint16(todo) c.Setup.MaximumRequestLength, todo = getUint16(todo) rootsLen := todo[0] pixmapFormatsLen := todo[1] c.Setup.ImageByteOrder = todo[2] c.Setup.BitmapBitOrder = todo[3] c.Setup.BitmapScanlineUnit = todo[4] c.Setup.BitmapScanlinePad = todo[5] c.Setup.MinKeycode = todo[6] c.Setup.MaxKeycode = todo[7] todo = todo[12:] // 8 above, plus 4 pad c.Setup.Vendor = string(todo[:vendorLen]) vendorLen += -vendorLen & 3 todo = todo[vendorLen:] c.Setup.PixmapFormats = make([]Format, 0, pixmapFormatsLen) for i := uint8(0); i < pixmapFormatsLen; i++ { var f Format f.Depth = todo[0] f.BitsPerPixel = todo[1] f.ScanlinePad = todo[2] todo = todo[8:] c.Setup.PixmapFormats = append(c.Setup.PixmapFormats, f) } c.Setup.Roots = make([]Screen, rootsLen) for i := uint8(0); i < rootsLen; i++ { s := &c.Setup.Roots[i] s.Root, todo = getUint32(todo) s.DefaultColormap, todo = getUint32(todo) s.WhitePixel, todo = getUint32(todo) s.BlackPixel, todo = getUint32(todo) s.CurrentInputMasks, todo = getUint32(todo) s.WidthInPixels, todo = getUint16(todo) s.HeightInPixels, todo = getUint16(todo) s.WidthInMillimeters, todo = getUint16(todo) s.HeightInMillimeters, todo = getUint16(todo) s.MinInstalledMaps, todo = getUint16(todo) s.MaxInstalledMaps, todo = getUint16(todo) s.RootVisual, todo = getUint32(todo) s.BackingStores = todo[0] s.SaveUnders = todo[1] != 0 s.RootDepth = todo[2] depthsLen := todo[3] todo = todo[4:] s.AllowedDepths = make([]Depth, depthsLen) for j := uint8(0); j < depthsLen; j++ { d := &s.AllowedDepths[j] d.Depth = todo[0] todo = todo[2:] var visualsLen uint16 visualsLen, todo = getUint16(todo) todo = todo[4:] // Pad d.Visuals = make([]VisualType, visualsLen) for k := uint16(0); k < visualsLen; k++ { v := &d.Visuals[k] v.VisualID, todo = getUint32(todo) v.Class = todo[0] v.BitsPerRGBValue = todo[1] todo = todo[2:] v.ColormapEntries, todo = getUint16(todo) v.RedMask, todo = getUint32(todo) v.GreenMask, todo = getUint32(todo) v.BlueMask, todo = getUint32(todo) todo = todo[4:] // Pad } } } return true case 2: println("Server requires authentication") println(bytes.NewBuffer(todo).String()) default: panic("Unexpected return value from server " + strconv.FormatUint(uint64(status), 10)) } return false } func (c *Connection) fullSeq(seq uint16) uint64 { var fs uint64 fs = (c.lastSequenceRead &^ 0xFFFF) | uint64(seq) if fs < c.lastSequenceRead { fs += 0x10000 } return fs } func (c *Connection) registerReply(reply chan interface{}) { c.replyMutex.Lock() c.lastRequestWritten++ if reply != nil { c.reply[c.lastRequestWritten] = reply } c.replyMutex.Unlock() } func (c *Connection) registerCallback(reply func(interface{})) { c.replyMutex.Lock() c.lastRequestWritten++ if reply != nil { c.callback[c.lastRequestWritten] = reply } c.replyMutex.Unlock() } func (c *Connection) postReply(seq uint64, reply interface{}) { c.replyMutex.Lock() if seq != c.lastSequenceRead { delete(c.reply, c.lastSequenceRead) } for i := c.lastSequenceRead + 1; i < seq; i++ { if target := c.reply[i]; target != nil { target <- nil delete(c.reply, i) } if target := c.callback[i]; target != nil { target(nil) delete(c.callback, i) } } if target := c.reply[seq]; target != nil { target <- reply } else if target := c.callback[seq]; target != nil { target(reply) } else { error, ok := reply.(Error) if ok { c.Error <- error } else { println("Unexpected reply to sequence", seq) } } c.lastSequenceRead = seq c.replyMutex.Unlock() } func (c *Connection) read(in *bufio.Reader) { for { t, err := in.ReadByte() if err != nil { c.conn = nil return } switch t { case 0: var e Error e.Err, _ = in.ReadByte() e.Sequence = c.fullSeq(readUint16(in)) e.BadValue = readUint32(in) e.Minor = readUint16(in) e.Major, _ = in.ReadByte() skipBytes(in, 21) c.postReply(e.Sequence, e) case 1: var r Reply r.Aux, _ = in.ReadByte() r.Sequence = c.fullSeq(readUint16(in)) r.Length = readUint32(in) r.Remainder = make([]byte, r.Length*4+24) readAll(in, r.Remainder) c.postReply(r.Sequence, r) default: var e Event e.EventType = t if t&0x80 != 0 { e.EventType = t & 0x7F e.Sent = true } readAll(in, e.Remainder[:]) c.Event <- e // TODO GenericEvent } } } func (c *Connection) connect_auth(name string, data []byte) { c.write_setup(name, data) in := bufio.NewReader(c.conn) if !c.read_setup(in) { c.Disconnect() return } c.out = bufio.NewWriter(c.conn) c.Event = make(chan Event, events_before_readblock) c.Error = make(chan Error, errors_before_readblock) c.reply = make(map[uint64]chan interface{}) c.callback = make(map[uint64]func(interface{})) go c.read(in) } type AuthInfo struct { Name string Data []byte } // AUTH starts const authXDM = "XDM-AUTHORIZATION-1" const authCookie = "MIT-MAGIC-COOKIE-1" type addresser interface { LocalAddr() net.Addr RemoteAddr() net.Addr } func getAuthPtr(a net.Addr, display int) *xau.Xauth { family := xau.FamilyLocal addr := []byte{0, 0, 0, 0} switch a.Network() { case "unix": if name, err := os.Hostname(); err == nil { addr = []byte(name) } case "tcp", "tcp4", "tcp6": if tcp, ok := a.(*net.TCPAddr); ok { ip4 := tcp.IP.To4() if ip4 != nil { addr = []byte(ip4) if addr[0] == 127 { // Loopback - use FamilyLocal if name, err := os.Hostname(); err == nil { addr = []byte(name) } } else { family = FamilyInternet } } else { ip6 := tcp.IP.To16() if ip6 != nil { addr = []byte(tcp.IP) if bytes.Equal(addr, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) { // Loopback - use FamilyLocal if name, err := os.Hostname(); err == nil { addr = []byte(name) } } else { family = FamilyInternet6 } } else { return nil } } } else { return nil } default: return nil } disp := []byte(strconv.Itoa(display)) return xau.GetBestAuthByAddr(family, addr, disp, []string{ /*authXDM,*/ authCookie}) } func appendUint32(buf []byte, val uint32) []byte { buf = append(buf, byte(val>>32)) buf = append(buf, byte(val>>24)) buf = append(buf, byte(val>>8)) buf = append(buf, byte(val&0xFF)) return buf } func computeAuth(auth *xau.Xauth, sockaddr net.Addr) *AuthInfo { switch auth.Name { case authCookie: return &AuthInfo{auth.Name, auth.Data} case authXDM: var info AuthInfo info.Data = make([]byte, 8, 192/8) copy(info.Data, auth.Data) switch sockaddr.Network() { case "tcp", "tcp4", "tcp6": if tcp, ok := sockaddr.(*net.TCPAddr); ok { if ip4 := tcp.IP.To4(); ip4 != nil { info.Data = append(info.Data, []byte(ip4)...) info.Data = append(info.Data, byte(tcp.Port>>8)) info.Data = append(info.Data, byte(tcp.Port&0xFF)) } else if ip6 := tcp.IP.To16(); ip6 != nil { /* XDM-AUTHORIZATION-1 does not handle IPv6 correctly. Do the same thing Xlib does: use all zeroes for the 4-byte address and 2-byte port number. */ info.Data = append(info.Data, 0, 0, 0, 0, 0, 0) } } case "unix": info.Data = appendUint32(info.Data, 0xFFFFFFFF) // TODO: -next_nonce() pid := os.Getpid() info.Data = append(info.Data, byte(pid>>8)) info.Data = append(info.Data, byte(pid&0xFF)) default: return nil } info.Data = appendUint32(info.Data, uint32(time.Now().Unix())) for len(info.Data) < 192/8 { info.Data = append(info.Data, 0) } panic("TODO: XdmcpWrap") } return nil } func (c *Connection) getAuthInfo(display int) *AuthInfo { // code adapted from libXCB/xcb_auth.c addr, ok := c.conn.(addresser) if !ok { return nil } var gotsockname bool /* Some systems like hpux or Hurd do not expose peer names * for UNIX Domain Sockets, but this is irrelevant, * since compute_auth() ignores the peer name in this * case anyway.*/ a := addr.RemoteAddr() if a.String() == "" { a = addr.LocalAddr() if a.String() == "" { return nil } if a.Network() != "unix" { return nil } gotsockname = true } auth := getAuthPtr(a, display) if auth == nil { return nil } if !gotsockname { a = addr.LocalAddr() if a.String() == "" { return nil } } return computeAuth(auth, a) } // AUTH ends func ConnectWithAuth(dpy string, info *AuthInfo) (conn *Connection, screen int) { host, proto, display, screen := parse_display(dpy) var c Connection c.conn = open(host, proto, display) if c.conn == nil { return } var name string data := make([]byte, 0) if info == nil { info = c.getAuthInfo(display) } if info != nil { name = info.Name data = info.Data } c.connect_auth(name, data) conn = &c return } func Connect(dpy string) (conn *Connection, screen int) { return ConnectWithAuth(dpy, nil) } func (c *Connection) Disconnect() { c.conn.Close() c.conn = nil } func (c *Connection) writeRequest(req []byte, replier bool, rv chan interface{}) { c.writeMutex.Lock() c.registerReply(rv) c.out.Write(req) if replier { c.lastReplierSent = c.lastRequestWritten } else { if c.lastRequestWritten-c.lastReplierSent >= 65534 { c.registerReply(make(chan interface{}, 1)) c.out.Write([]byte{43, 0, 0, 1}) c.lastReplierSent = c.lastRequestWritten } } c.writeMutex.Unlock() } func (c *Connection) WriteReplyRequestCallback(req []byte, closure func(interface{})) { c.writeMutex.Lock() c.registerCallback(closure) c.out.Write(req) c.lastReplierSent = c.lastRequestWritten c.writeMutex.Unlock() } func (c *Connection) WriteMultiRequest(req []byte, expected int) chan interface{} { if c == nil || c.conn == nil { return nil } replier := true if expected < 1 { expected = 1 replier = false } rv := make(chan interface{}, expected) c.writeRequest(req, replier, rv) return rv } func (c *Connection) WriteNoreplyRequest(req []byte, rv chan interface{}) { if c == nil || c.conn == nil { return } c.writeRequest(req, false, rv) } func (c *Connection) WriteReplyRequest(req []byte) chan interface{} { return c.WriteMultiRequest(req, 1) } func (c *Connection) Flush() { if c == nil || c.conn == nil { return } c.writeMutex.Lock() c.out.Flush() c.writeMutex.Unlock() } func (c *Connection) HasError() bool { if c == nil || c.conn == nil { return true } return false }