github.com/robotn/xgb@v0.0.0-20190912153532-2cb92d044934/conn.go (about)

     1  package xgb
     2  
     3  /*
     4  conn.go contains a couple of functions that do some real dirty work related
     5  to the initial connection handshake with X.
     6  
     7  This code is largely unmodified from the original XGB package that I forked.
     8  */
     9  
    10  import (
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"net"
    15  	"os"
    16  	"strconv"
    17  	"strings"
    18  )
    19  
    20  // connect connects to the X server given in the 'display' string,
    21  // and does all the necessary setup handshaking.
    22  // If 'display' is empty it will be taken from os.Getenv("DISPLAY").
    23  // Note that you should read and understand the "Connection Setup" of the
    24  // X Protocol Reference Manual before changing this function:
    25  // http://goo.gl/4zGQg
    26  func (c *Conn) connect(display string) error {
    27  	err := c.dial(display)
    28  	if err != nil {
    29  		return err
    30  	}
    31  
    32  	return c.postConnect()
    33  }
    34  
    35  // connect init from to the net.Conn,
    36  func (c *Conn) connectNet(netConn net.Conn) error {
    37  	c.conn = netConn
    38  	return c.postConnect()
    39  }
    40  
    41  // do the postConnect action after Conn get it's underly net.Conn
    42  func (c *Conn) postConnect() error {
    43  	// Get authentication data
    44  	authName, authData, err := readAuthority(c.host, c.display)
    45  	noauth := false
    46  	if err != nil {
    47  		Logger.Printf("Could not get authority info: %v", err)
    48  		Logger.Println("Trying connection without authority info...")
    49  		authName = ""
    50  		authData = []byte{}
    51  		noauth = true
    52  	}
    53  
    54  	// Assume that the authentication protocol is "MIT-MAGIC-COOKIE-1".
    55  	if !noauth && (authName != "MIT-MAGIC-COOKIE-1" || len(authData) != 16) {
    56  		return errors.New("unsupported auth protocol " + authName)
    57  	}
    58  
    59  	buf := make([]byte, 12+Pad(len(authName))+Pad(len(authData)))
    60  	buf[0] = 0x6c
    61  	buf[1] = 0
    62  	Put16(buf[2:], 11)
    63  	Put16(buf[4:], 0)
    64  	Put16(buf[6:], uint16(len(authName)))
    65  	Put16(buf[8:], uint16(len(authData)))
    66  	Put16(buf[10:], 0)
    67  	copy(buf[12:], []byte(authName))
    68  	copy(buf[12+Pad(len(authName)):], authData)
    69  	if _, err = c.conn.Write(buf); err != nil {
    70  		return err
    71  	}
    72  
    73  	head := make([]byte, 8)
    74  	if _, err = io.ReadFull(c.conn, head[0:8]); err != nil {
    75  		return err
    76  	}
    77  	code := head[0]
    78  	reasonLen := head[1]
    79  	major := Get16(head[2:])
    80  	minor := Get16(head[4:])
    81  	dataLen := Get16(head[6:])
    82  
    83  	if major != 11 || minor != 0 {
    84  		return fmt.Errorf("x protocol version mismatch: %d.%d", major, minor)
    85  	}
    86  
    87  	buf = make([]byte, int(dataLen)*4+8, int(dataLen)*4+8)
    88  	copy(buf, head)
    89  	if _, err = io.ReadFull(c.conn, buf[8:]); err != nil {
    90  		return err
    91  	}
    92  
    93  	if code == 0 {
    94  		reason := buf[8 : 8+reasonLen]
    95  		return fmt.Errorf("x protocol authentication refused: %s",
    96  			string(reason))
    97  	}
    98  
    99  	// Unfortunately, it isn't really feasible to read the setup bytes here,
   100  	// since the code to do so is in a different package.
   101  	// Users must call 'xproto.Setup(X)' to get the setup info.
   102  	c.SetupBytes = buf
   103  
   104  	// But also read stuff that we *need* to get started.
   105  	c.setupResourceIdBase = Get32(buf[12:])
   106  	c.setupResourceIdMask = Get32(buf[16:])
   107  
   108  	return nil
   109  }
   110  
   111  // dial initializes the actual net connection with X.
   112  func (c *Conn) dial(display string) error {
   113  	if len(display) == 0 {
   114  		display = os.Getenv("DISPLAY")
   115  	}
   116  
   117  	display0 := display
   118  	if len(display) == 0 {
   119  		return errors.New("empty display string")
   120  	}
   121  
   122  	colonIdx := strings.LastIndex(display, ":")
   123  	if colonIdx < 0 {
   124  		return errors.New("bad display string: " + display0)
   125  	}
   126  
   127  	var protocol, socket string
   128  
   129  	if display[0] == '/' {
   130  		socket = display[0:colonIdx]
   131  	} else {
   132  		slashIdx := strings.LastIndex(display, "/")
   133  		if slashIdx >= 0 {
   134  			protocol = display[0:slashIdx]
   135  			c.host = display[slashIdx+1 : colonIdx]
   136  		} else {
   137  			c.host = display[0:colonIdx]
   138  		}
   139  	}
   140  
   141  	display = display[colonIdx+1:]
   142  	if len(display) == 0 {
   143  		return errors.New("bad display string: " + display0)
   144  	}
   145  
   146  	var scr string
   147  	dotIdx := strings.LastIndex(display, ".")
   148  	if dotIdx < 0 {
   149  		c.display = display[0:]
   150  	} else {
   151  		c.display = display[0:dotIdx]
   152  		scr = display[dotIdx+1:]
   153  	}
   154  
   155  	var err error
   156  	c.DisplayNumber, err = strconv.Atoi(c.display)
   157  	if err != nil || c.DisplayNumber < 0 {
   158  		return errors.New("bad display string: " + display0)
   159  	}
   160  
   161  	if len(scr) != 0 {
   162  		c.DefaultScreen, err = strconv.Atoi(scr)
   163  		if err != nil {
   164  			return errors.New("bad display string: " + display0)
   165  		}
   166  	}
   167  
   168  	// Connect to server
   169  	if len(socket) != 0 {
   170  		c.conn, err = net.Dial("unix", socket+":"+c.display)
   171  	} else if len(c.host) != 0 {
   172  		if protocol == "" {
   173  			protocol = "tcp"
   174  		}
   175  		c.conn, err = net.Dial(protocol,
   176  			c.host+":"+strconv.Itoa(6000+c.DisplayNumber))
   177  	} else {
   178  		c.conn, err = net.Dial("unix", "/tmp/.X11-unix/X"+c.display)
   179  	}
   180  
   181  	if err != nil {
   182  		return errors.New("cannot connect to " + display0 + ": " + err.Error())
   183  	}
   184  	return nil
   185  }