From 1ebf5e1103fd582c93312c84c7525a0f39db84eb Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 29 Mar 2023 12:27:23 -0400 Subject: [PATCH] Implemented INCR selection properties --- backends/x/selection.go | 82 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/backends/x/selection.go b/backends/x/selection.go index 949e193..bd9bb30 100644 --- a/backends/x/selection.go +++ b/backends/x/selection.go @@ -13,6 +13,7 @@ type selReqState int; const ( selReqStateClosed selReqState = iota selReqStateAwaitTargets selReqStateAwaitValue + selReqStateAwaitFirstChunk selReqStateAwaitChunk ) @@ -22,6 +23,8 @@ type selectionRequest struct { source xproto.Atom destination xproto.Atom accept []data.Mime + incrBuffer []byte + incrMime data.Mime callback func (data.Data, error) } @@ -138,9 +141,11 @@ func (request *selectionRequest) handleSelectionNotify ( event xevent.SelectionNotifyEvent, ) { // the only valid states that we can process a SelectionNotify event in - if request.state != selReqStateAwaitValue && request.state != selReqStateAwaitTargets { - return - } + invalidState := + request.state != selReqStateAwaitFirstChunk && + request.state != selReqStateAwaitValue && + request.state != selReqStateAwaitTargets + if invalidState { return } // Follow: // https://tronche.com/gui/x/icccm/sec-2.html#s-2.4 @@ -152,6 +157,13 @@ func (request *selectionRequest) handleSelectionNotify ( // data. if event.Property == 0 { request.die(nil); return } + // if we are waiting for the first INCR chunk, do the special stuff for + // that and not the other stuff. + if request.state == selReqStateAwaitFirstChunk { + request.handleINCRProperty(event.Property) + return + } + // When using GetProperty to retrieve the value of a selection, the // property argument should be set to the corresponding value in the // SelectionNotify event. Because the requestor has no way of knowing @@ -172,7 +184,30 @@ func (request *selectionRequest) handleSelectionNotify ( return } - // TODO: handle INCR. do it here. + // https://tronche.com/gui/x/icccm/sec-2.html#s-2.7.2 + // Requestors may receive a property of type INCR9 in response to any + // target that results in selection data. This indicates that the owner + // will send the actual data incrementally. The contents of the INCR + // property will be an integer, which represents a lower bound on the + // number of bytes of data in the selection. The requestor and the + // selection owner transfer the data in the selection in the following + // manner. The selection requestor starts the transfer process by + // deleting the (type==INCR) property forming the reply to the + // selection. + incr, err := xprop.Atm(request.window.backend.connection, "INCR") + if err != nil { request.die(err); return } + if reply.Type == incr { + // reply to the INCR selection + err = xproto.DeletePropertyChecked ( + request.window.backend.connection.Conn(), + request.window.xWindow.Id, + request.destination).Check() + if err != nil { request.die(err); return } + + // await the first chunk + request.state = selReqStateAwaitFirstChunk + return + } // Once all the data in the selection has been retrieved (which may // require getting the values of several properties &emdash; see section @@ -188,6 +223,8 @@ func (request *selectionRequest) handleSelectionNotify ( request.destination).Check() if err != nil { request.die(err); return } + // depending on which state the selection request is in, do something + // different with the property's value switch request.state { case selReqStateAwaitValue: // get the type from the property and convert that to the mime @@ -264,3 +301,40 @@ func (request *selectionRequest) handleSelectionNotify ( request.convertSelection(chosenTarget, selReqStateAwaitValue) } } + +func (request *selectionRequest) handlePropertyNotify ( + connection *xgbutil.XUtil, + event xevent.PropertyNotifyEvent, +) { + // the only valid state that we can process a PropertyNotify event in + if request.state != selReqStateAwaitChunk { return } + if event.State != xproto.PropertyNewValue { return } + + request.handleINCRProperty(event.Atom) +} + +func (request *selectionRequest) handleINCRProperty (property xproto.Atom) { + // Retrieving data using GetProperty with the delete argument True. + reply, err := xproto.GetProperty ( + request.window.backend.connection.Conn(), true, + request.window.xWindow.Id, property, xproto.GetPropertyTypeAny, + 0, (1 << 32) - 1).Reply() + if err != nil { request.die(err); return } + + if len(reply.Value) == 0 { + // a zero length property means the transfer has finished. we + // finalize the request with the data we have, and don't wait + // for more. + request.finalize(data.Bytes(request.incrMime, request.incrBuffer)) + } else { + // a property with content means the transfer is still ongoing. + // we append the data we got and wait for more. + request.state = selReqStateAwaitChunk + request.incrBuffer = append(request.incrBuffer, reply.Value...) + + targetName, err := xprop.AtomName ( + request.window.backend.connection, reply.Type) + if err != nil { request.die(err); return } + request.incrMime, _ = targetToMime(targetName) + } +}