xgbsel/claim.go

181 lines
6.9 KiB
Go
Raw Normal View History

2023-06-06 11:41:30 -06:00
package xgbsel
import "io"
import "github.com/jezek/xgb"
import "github.com/jezek/xgbutil"
import "github.com/jezek/xgb/xproto"
import "github.com/jezek/xgbutil/xprop"
import "github.com/jezek/xgbutil/xevent"
import "github.com/jezek/xgbutil/xwindow"
// Claim represents a claim that a window has on a particular selection.
type Claim struct {
window *xwindow.Window
data Data
selection xproto.Atom
}
// NewClaim claims ownership of a specified selection, and allows using data
2024-07-06 20:31:49 -06:00
// passed to it to fulfill requests for the selection's contents. If the claim
// happens because of a user input event, use NewClaimWithTimestamp instead of
// this function. See the documentation of NewClaimWithTimestamp for details.
2023-06-06 11:41:30 -06:00
func NewClaim (window *xwindow.Window, selection xproto.Atom, data Data) *Claim {
2024-07-06 20:31:49 -06:00
return NewClaimWithTimestamp(window, selection, data, 0)
}
// NewClaimWithTimestamp claims ownership of a specified selection, and allows
// using data passed to it to fulfill requests for the selection's contents. The
// timestamp should be set to that of the event that triggered the claim, such
// as a Ctrl+C event, or a mouse motion that led to text being selected.
func NewClaimWithTimestamp (window *xwindow.Window, selection xproto.Atom, data Data, timestamp xproto.Timestamp) *Claim {
2023-06-06 11:41:30 -06:00
// Follow:
// https://tronche.com/gui/x/icccm/sec-2.html#s-2.1
// A client wishing to acquire ownership of a particular selection
// should call SetSelectionOwner. The client should set the specified
// selection to the atom that represents the selection, set the
// specified owner to some window that the client created, and set the
// specified time to some time between the current last-change time of
// the selection concerned and the current server time. This time value
// usually will be obtained from the timestamp of the event that
// triggers the acquisition of the selection. Clients should not set the
// time value to CurrentTime, because if they do so, they have no way of
// finding when they gained ownership of the selection. Clients must use
// a window they created so that requestors can route events to the
// owner of the selection.
err := xproto.SetSelectionOwnerChecked (
window.X.Conn(),
2024-07-06 20:31:49 -06:00
window.Id, selection, timestamp).Check()
2023-06-06 11:41:30 -06:00
if err != nil { return nil }
ownerReply, err := xproto.GetSelectionOwner (
window.X.Conn(), selection).Reply()
if err != nil { return nil }
if ownerReply.Owner != window.Id { return nil }
return &Claim {
window: window,
data: data,
selection: selection,
}
}
func (claim *Claim) refuseSelectionRequest (request xevent.SelectionRequestEvent) {
// ... refuse the SelectionRequest by sending the requestor window a
// SelectionNotify event with the property set to None (by means of a
// SendEvent request with an empty event mask).
event := xproto.SelectionNotifyEvent {
Requestor: request.Requestor,
Selection: request.Selection,
Target: request.Target,
Property: 0,
}.Bytes()
xproto.SendEvent (
claim.window.X.Conn(),
false, request.Requestor, 0, string(event))
}
func (claim *Claim) fulfillSelectionRequest (
data []byte,
format byte,
ty xproto.Atom,
request xevent.SelectionRequestEvent,
) {
die := func () { claim.refuseSelectionRequest(request) }
// If the specified property is not None, the owner should place the
// data resulting from converting the selection into the specified
// property on the requestor window and should set the property's type
// to some appropriate value, which need not be the same as the
// specified target.
err := xproto.ChangePropertyChecked (
claim.window.X.Conn(),
xproto.PropModeReplace, request.Requestor,
request.Property,
ty, format,
uint32(len(data) / (int(format) / 8)), data).Check()
if err != nil { die() }
// If the property is successfully stored, the owner should acknowledge
// the successful conversion by sending the requestor window a
// SelectionNotify event (by means of a SendEvent request with an empty
// mask).
event := xproto.SelectionNotifyEvent {
Requestor: request.Requestor,
Selection: request.Selection,
Target: request.Target,
Property: request.Property,
}.Bytes()
xproto.SendEvent (
claim.window.X.Conn(),
false, request.Requestor, 0, string(event))
}
// While the selection claim is active, HandleSelectionRequest should be called
// when the owner window recieves a SelectionRequest event. This must be
// registered as an event handler manually.
2023-06-06 11:41:30 -06:00
func (claim *Claim) HandleSelectionRequest (
connection *xgbutil.XUtil,
event xevent.SelectionRequestEvent,
) {
// Follow:
// https://tronche.com/gui/x/icccm/sec-2.html#s-2.2
die := func () { claim.refuseSelectionRequest(event) }
// When a requestor wants the value of a selection, the owner receives a
// SelectionRequest event. The specified owner and selection will be the
// values that were specified in the SetSelectionOwner request. The
// owner should compare the timestamp with the period it has owned the
// selection and, if the time is outside, refuse the SelectionRequest.
if event.Selection != claim.selection { die(); return }
// If the specified property is None, the requestor is an obsolete
// client. Owners are encouraged to support these clients by using the
// specified target atom as the property name to be used for the reply.
if event.Property == 0 {
event.Property = event.Target
}
// Otherwise, the owner should use the target to decide the form into
// which the selection should be converted. Some targets may be defined
// such that requestors can pass parameters along with the request. The
// owner will find these parameters in the property named in the
// selection request. The type, format, and contents of this property
// are dependent upon the definition of the target. If the target is not
// defined to have parameters, the owner should ignore the property if
// it is present. If the selection cannot be converted into a form based
// on the target (and parameters, if any), the owner should refuse the
// SelectionRequest as previously described.
targetName, err := xprop.AtomName(claim.window.X, event.Target)
if err != nil { die(); return }
switch targetName {
case "TARGETS":
// generate a list of supported targets
targetNames := []Target { "TARGETS" }
targetNames = append(targetNames, claim.data.Supported()...)
data := make([]byte, len(targetNames) * 4)
for index, name := range targetNames {
atom, err := xprop.Atm(claim.window.X, string(name))
if err != nil { die(); return }
xgb.Put32(data[(index) * 4:], uint32(atom))
}
// send that list to the requestor
atomAtom, err := xprop.Atm(claim.window.X, "ATOM")
if err != nil { die(); return }
claim.fulfillSelectionRequest(data, 32, atomAtom, event)
default:
// respond with data
reader, ok := claim.data.Convert(Target(targetName))
if !ok { die(); return }
reader.Seek(0, io.SeekStart)
data, err := io.ReadAll(reader)
reader.Close()
if err != nil { die() }
claim.fulfillSelectionRequest(data, 8, event.Target, event)
}
}