Pasting implemented (nonworking)

This commit is contained in:
Sasha Koshka 2023-03-30 13:10:58 -04:00
parent 8abc4defa7
commit f9e5503320
4 changed files with 171 additions and 15 deletions

View File

@ -257,12 +257,15 @@ func (window *window) handleSelectionClear (
connection *xgbutil.XUtil,
event xevent.SelectionClearEvent,
) {
// TODO: schedule the claim to be deleted. when the event loop fires we
// will check to see if the claim is scheduled to be deleted and if it
// is, delete it.
if window.selectionClaim != nil {
window.selectionClaim.scheduledDelete = true
}
window.selectionClaim = nil
}
func (window *window) handleSelectionRequest (
connection *xgbutil.XUtil,
event xevent.SelectionRequestEvent,
) {
if window.selectionClaim == nil { return }
window.selectionClaim.handleSelectionRequest(connection, event)
}
func (window *window) compressExpose (

View File

@ -146,6 +146,23 @@ func targetToMime (name string) (data.Mime, confidence) {
}
}
func mimeToTargets (mime data.Mime) (names []string) {
names = append(names, mime.String())
switch mime {
case data.M("application", "pdf"):
names = append(names, "ADOBE_PORTABLE_DOCUMENT_FORMAT")
case data.M("image", "pict"):
names = append(names, "APPLE_PICT")
case data.M("application", "postscript"):
names = append(names, "POSTSCRIPT")
case data.MimeFile:
names = append(names, "FILE_NAME")
case data.MimePlain:
names = append(names, "UTF8_STRING", "TEXT", "STRING")
}
return
}
func (request *selectionRequest) handleSelectionNotify (
connection *xgbutil.XUtil,
event xevent.SelectionNotifyEvent,
@ -195,7 +212,7 @@ func (request *selectionRequest) handleSelectionNotify (
// 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.
// selection.
incr, err := xprop.Atm(request.window.backend.connection, "INCR")
if err != nil { request.die(err); return }
if reply.Type == incr {

View File

@ -1,24 +1,155 @@
package x
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 "git.tebibyte.media/sashakoshka/tomo/data"
type selectionClaim struct {
window *window
data data.Data
scheduledDelete bool
name xproto.Atom
}
func (window *window) newSelectionClaim (data data.Data) *selectionClaim {
return &selectionClaim{
func (window *window) claimSelection (name xproto.Atom, data data.Data) *selectionClaim {
// 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.backend.connection.Conn(),
window.xWindow.Id, name, 0).Check() // FIXME: should not be zero
if err != nil { return nil }
return &selectionClaim {
window: window,
data: data,
name: name,
}
}
func (claim *selectionClaim) idle () bool {
// TODO
func (window *window) 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 (
window.backend.connection.Conn(),
false, request.Requestor, 0, string(event))
}
func (window *window) fulfillSelectionRequest (
data []byte,
format byte,
request xevent.SelectionRequestEvent,
) {
die := func () { window.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 (
window.backend.connection.Conn(),
xproto.PropModeReplace, window.xWindow.Id,
request.Property,
request.Target, 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 (
window.backend.connection.Conn(),
false, request.Requestor, 0, string(event))
}
func (claim *selectionClaim) handleSelectionRequest (
// TODO
connection *xgbutil.XUtil,
event xevent.SelectionRequestEvent,
) {
// TODO
// Follow:
// https://tronche.com/gui/x/icccm/sec-2.html#s-2.2
die := func () { claim.window.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.name { 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.backend.connection, event.Target)
if err != nil { die(); return }
switch targetName {
case "TARGETS":
targetNames := []string { }
for mime := range claim.data {
targetNames = append(targetNames, mimeToTargets(mime)...)
}
data := make([]byte, len(targetNames) * 4)
for index, name := range targetNames {
atom, err := xprop.Atm(claim.window.backend.connection, name)
if err != nil { die(); return }
xgb.Put32(data[:index * 4], uint32(atom))
}
claim.window.fulfillSelectionRequest(data, 8, event)
default:
mime, confidence := targetToMime(targetName)
if confidence == confidenceNone { die(); return }
reader, ok := claim.data[mime]
if !ok { die(); return }
data, err := io.ReadAll(reader)
if err != nil { die() }
claim.window.fulfillSelectionRequest(data, 32, event)
}
}

View File

@ -102,6 +102,8 @@ func (backend *Backend) newWindow (
Connect(backend.connection, window.xWindow.Id)
xevent.SelectionClearFun(window.handleSelectionClear).
Connect(backend.connection, window.xWindow.Id)
xevent.SelectionRequestFun(window.handleSelectionRequest).
Connect(backend.connection, window.xWindow.Id)
window.SetTheme(backend.theme)
window.SetConfig(backend.config)
@ -288,7 +290,10 @@ func (window *window) Hide () {
}
func (window *window) Copy (data data.Data) {
// TODO
selectionName := "CLIPBOARD"
selectionAtom, err := xprop.Atm(window.backend.connection, selectionName)
if err != nil { return }
window.selectionClaim = window.claimSelection(selectionAtom, data)
}
func (window *window) Paste (callback func (data.Data, error), accept ...data.Mime) {