4 Commits

Author SHA1 Message Date
c48264a220 Add copy example 2024-07-11 01:50:25 -04:00
1db7d2d582 Improve paste example 2024-07-11 01:50:16 -04:00
dab360de75 Add NewClaimWithTimestamp
Remedy #4
2024-07-06 22:31:49 -04:00
d1ba6eac9a Improve doc comments on the Requestor interface 2024-07-05 23:38:57 -04:00
4 changed files with 125 additions and 11 deletions

View File

@@ -16,8 +16,18 @@ type Claim struct {
}
// NewClaim claims ownership of a specified selection, and allows using data
// passed to it to fulfill requests for the selection's contents.
// 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.
func NewClaim (window *xwindow.Window, selection xproto.Atom, data Data) *Claim {
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 {
// Follow:
// https://tronche.com/gui/x/icccm/sec-2.html#s-2.1
@@ -35,7 +45,7 @@ func NewClaim (window *xwindow.Window, selection xproto.Atom, data Data) *Claim
// owner of the selection.
err := xproto.SetSelectionOwnerChecked (
window.X.Conn(),
window.Id, selection, 0).Check() // FIXME: should not be zero
window.Id, selection, timestamp).Check()
if err != nil { return nil }
ownerReply, err := xproto.GetSelectionOwner (

73
examples/copy/main.go Normal file
View File

@@ -0,0 +1,73 @@
// Example copy shows how to place text data in the CLIPBOARD selection.
package main
import "os"
import "io"
import "log"
import "bytes"
import "github.com/jezek/xgbutil"
import "github.com/jezek/xgb/xproto"
import "git.tebibyte.media/tomo/xgbsel"
import "github.com/jezek/xgbutil/xprop"
import "github.com/jezek/xgbutil/xevent"
import "github.com/jezek/xgbutil/xwindow"
// data is a very basic implementation of xgbsel.Data that only serves data of
// one type.
type data struct {
buffer io.ReadSeekCloser
mime string
}
func (this *data) Convert (target xgbsel.Target) (io.ReadSeekCloser, bool) {
if mime, _ := target.ToMime(); mime == this.mime {
return this.buffer, true
} else {
return nil, false
}
}
func (this *data) Supported () []xgbsel.Target {
return xgbsel.MimeToTargets(this.mime)
}
// nopSeekCloser is like io.NopCloser but for an io.ReadSeeker.
type nopSeekCloser struct { io.ReadSeeker }
func (nopSeekCloser) Close () error { return nil }
func main () {
// get data from user
log.Println("enter data, ^D when done: ")
buffer, _ := io.ReadAll(os.Stdin)
data := &data {
buffer: nopSeekCloser {
ReadSeeker: bytes.NewReader(buffer),
},
mime: "text/plain",
}
// establish connection
X, err := xgbutil.NewConn()
if err != nil {
log.Fatal(err)
}
// create window
window, err := xwindow.Generate(X)
if err != nil {
log.Fatalln("could not generate a new window X id:", err)
}
window.Create(X.RootWin(), 0, 0, 500, 500, xproto.CwBackPixel, 0xffffffff)
// obtain claim on CLIPBOARD
log.Println("obtaining claim")
clipboard, _ := xprop.Atm(X, "CLIPBOARD")
claim := xgbsel.NewClaim(window, clipboard, data)
// listen for events
window.Listen(xproto.EventMaskPropertyChange)
xevent.SelectionRequestFun(claim.HandleSelectionRequest).Connect(X, window.Id)
log.Println("running main event loop")
xevent.Main(X)
}

View File

@@ -11,6 +11,8 @@ import "github.com/jezek/xgbutil/xprop"
import "github.com/jezek/xgbutil/xevent"
import "github.com/jezek/xgbutil/xwindow"
// requestor implements xgbsel.Requestor. It asks for text and outputs it to
// os.Stdout, and any logs to the default logging output (os.Stderr).
type requestor struct {
window *xwindow.Window
}
@@ -22,23 +24,41 @@ func (requestor requestor) Window () *xwindow.Window {
func (requestor requestor) Success (target xgbsel.Target, data io.ReadCloser) {
defer data.Close()
text, _ := io.ReadAll(data)
log.Println("Clipboard text:", string(text))
log.Println("got clipboard text:")
os.Stdout.Write(text)
os.Exit(0)
}
func (requestor requestor) Failure (err error) {
if err == nil {
log.Fatalln("no available clipboard data")
} else {
log.Fatalln("could not get clipboard:", err)
}
os.Exit(1)
}
func (requestor requestor) Choose (from []xgbsel.Target) (xgbsel.Target, bool) {
for _, target := range from {
if target == "TEXT" {
return target, true
func (requestor requestor) Choose (available []xgbsel.Target) (xgbsel.Target, bool) {
log.Println("owner supports these targets:", available)
// try to find the closest thing to text/plain by converting each target
// to a MIME type and comparing confidence values
var bestTarget xgbsel.Target
var bestConfidence xgbsel.Confidence
for _, target := range available {
mime, confidence := target.ToMime()
if mime == "text/plain" && confidence > bestConfidence {
bestConfidence = confidence
bestTarget = target
}
}
// if we have any confidence at all, return the result we got
if bestConfidence > xgbsel.ConfidenceNone {
return bestTarget, true
} else {
return "", false
}
}
func main () {

View File

@@ -20,9 +20,20 @@ type selReqState int; const (
// Requestor provices details about the request such as the window that is
// requesting the selection data, and what targets it accepts.
type Requestor interface {
// Window returns the window that is requesting the selection data.
Window () *xwindow.Window
Choose (from []Target) (chosen Target, ok bool)
// Choose picks target from a slice of available targets and returns
// (target, true). If no target was picket, it returns ("", false).
Choose (available []Target) (target Target, ok bool)
// Success is called once the owner responds with data. The data must be
// closed once it has been read.
Success (Target, io.ReadCloser)
// Failure is called if the transfer fails at any point, or if there
// isn't any data to begin with. In the first case, an error is given.
// In the second case, the error will be nil.
Failure (error)
}