diff options
author | Přemysl Janouch <p@janouch.name> | 2018-09-08 16:54:17 +0200 |
---|---|---|
committer | Přemysl Janouch <p@janouch.name> | 2018-09-08 16:54:17 +0200 |
commit | 3173202cc1e08762c6e156a8fffd23269a5ddb2b (patch) | |
tree | 95c4a06f8384d41b15e9c22afac0a387de79dc51 /nexgb/cookie.go | |
parent | 632b3ae494d45755525644fe5d04475c95aae364 (diff) | |
parent | 3906399e7c2a40fbaf355de572cf50a314083f64 (diff) | |
download | haven-3173202cc1e08762c6e156a8fffd23269a5ddb2b.tar.gz haven-3173202cc1e08762c6e156a8fffd23269a5ddb2b.tar.xz haven-3173202cc1e08762c6e156a8fffd23269a5ddb2b.zip |
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.
Diffstat (limited to 'nexgb/cookie.go')
-rw-r--r-- | nexgb/cookie.go | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/nexgb/cookie.go b/nexgb/cookie.go new file mode 100644 index 0000000..d5cdb29 --- /dev/null +++ b/nexgb/cookie.go @@ -0,0 +1,165 @@ +package xgb + +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 + } +} |