io/input,io/clipboard: [API] replace WriteOp with command

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2023-10-09 18:27:01 -05:00
parent a3c539b3c2
commit d51aea553f
16 changed files with 67 additions and 62 deletions
+1 -1
View File
@@ -160,7 +160,7 @@ type driver interface {
// ReadClipboard requests the clipboard content. // ReadClipboard requests the clipboard content.
ReadClipboard() ReadClipboard()
// WriteClipboard requests a clipboard write. // WriteClipboard requests a clipboard write.
WriteClipboard(s string) WriteClipboard(mime string, s []byte)
// Configure the window. // Configure the window.
Configure([]Option) Configure([]Option)
// SetCursor updates the current cursor to name. // SetCursor updates the current cursor to name.
+2 -2
View File
@@ -1295,9 +1295,9 @@ func newWindow(window *callbacks, options []Option) error {
return <-mainWindow.errs return <-mainWindow.errs
} }
func (w *window) WriteClipboard(s string) { func (w *window) WriteClipboard(mime string, s []byte) {
runInJVM(javaVM(), func(env *C.JNIEnv) { runInJVM(javaVM(), func(env *C.JNIEnv) {
jstr := javaString(env, s) jstr := javaString(env, string(s))
callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard, callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
jvalue(android.appCtx), jvalue(jstr)) jvalue(android.appCtx), jvalue(jstr))
}) })
+2 -2
View File
@@ -268,8 +268,8 @@ func (w *window) ReadClipboard() {
w.w.Event(clipboard.Event{Text: content}) w.w.Event(clipboard.Event{Text: content})
} }
func (w *window) WriteClipboard(s string) { func (w *window) WriteClipboard(mime string, s []byte) {
u16 := utf16.Encode([]rune(s)) u16 := utf16.Encode([]rune(string(s)))
var chars *C.unichar var chars *C.unichar
if len(u16) > 0 { if len(u16) > 0 {
chars = (*C.unichar)(unsafe.Pointer(&u16[0])) chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
+2 -2
View File
@@ -533,14 +533,14 @@ func (w *window) ReadClipboard() {
w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback) w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
} }
func (w *window) WriteClipboard(s string) { func (w *window) WriteClipboard(mime string, s []byte) {
if w.clipboard.IsUndefined() { if w.clipboard.IsUndefined() {
return return
} }
if w.clipboard.Get("writeText").IsUndefined() { if w.clipboard.Get("writeText").IsUndefined() {
return return
} }
w.clipboard.Call("writeText", s) w.clipboard.Call("writeText", string(s))
} }
func (w *window) Configure(options []Option) { func (w *window) Configure(options []Option) {
+2 -2
View File
@@ -308,8 +308,8 @@ func (w *window) ReadClipboard() {
w.w.Event(clipboard.Event{Text: content}) w.w.Event(clipboard.Event{Text: content})
} }
func (w *window) WriteClipboard(s string) { func (w *window) WriteClipboard(mime string, s []byte) {
cstr := stringToNSString(s) cstr := stringToNSString(string(s))
defer C.CFRelease(cstr) defer C.CFRelease(cstr)
C.writeClipboard(cstr) C.writeClipboard(cstr)
} }
+2 -2
View File
@@ -1033,8 +1033,8 @@ func (w *window) ReadClipboard() {
}() }()
} }
func (w *window) WriteClipboard(s string) { func (w *window) WriteClipboard(mime string, s []byte) {
w.disp.writeClipboard([]byte(s)) w.disp.writeClipboard(s)
} }
func (w *window) Configure(options []Option) { func (w *window) Configure(options []Option) {
+2 -2
View File
@@ -735,8 +735,8 @@ func (w *window) Configure(options []Option) {
w.update() w.update()
} }
func (w *window) WriteClipboard(s string) { func (w *window) WriteClipboard(mime string, s []byte) {
w.writeClipboard(s) w.writeClipboard(string(s))
} }
func (w *window) writeClipboard(s string) error { func (w *window) writeClipboard(s string) error {
+2 -2
View File
@@ -151,8 +151,8 @@ func (w *x11Window) ReadClipboard() {
C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime) C.XConvertSelection(w.x, w.atoms.clipboard, w.atoms.utf8string, w.atoms.clipboardContent, w.xw, C.CurrentTime)
} }
func (w *x11Window) WriteClipboard(s string) { func (w *x11Window) WriteClipboard(mime string, s []byte) {
w.clipboard.content = []byte(s) w.clipboard.content = s
C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime) C.XSetSelectionOwner(w.x, w.atoms.clipboard, w.xw, C.CurrentTime)
C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime) C.XSetSelectionOwner(w.x, w.atoms.primary, w.xw, C.CurrentTime)
} }
+2 -9
View File
@@ -316,8 +316,8 @@ func (w *Window) processFrame(d driver) {
if hint, ok := q.TextInputHint(); ok { if hint, ok := q.TextInputHint(); ok {
d.SetInputHint(hint) d.SetInputHint(hint)
} }
if txt, ok := q.WriteClipboard(); ok { if mime, txt, ok := q.WriteClipboard(); ok {
d.WriteClipboard(txt) d.WriteClipboard(mime, txt)
} }
if q.ReadClipboard() { if q.ReadClipboard() {
d.ReadClipboard() d.ReadClipboard()
@@ -372,13 +372,6 @@ func (w *Window) Option(opts ...Option) {
} }
} }
// WriteClipboard writes a string to the clipboard.
func (w *Window) WriteClipboard(s string) {
w.driverDefer(func(d driver) {
d.WriteClipboard(s)
})
}
// Run f in the same thread as the native window event loop, and wait for f to // Run f in the same thread as the native window event loop, and wait for f to
// return or the window to close. Run is guaranteed not to deadlock if it is // return or the window to close. Run is guaranteed not to deadlock if it is
// invoked during the handling of a ViewEvent, system.FrameEvent, // invoked during the handling of a ViewEvent, system.FrameEvent,
-5
View File
@@ -64,7 +64,6 @@ const (
TypePopPass TypePopPass
TypePointerInput TypePointerInput
TypeClipboardRead TypeClipboardRead
TypeClipboardWrite
TypeSource TypeSource
TypeTarget TypeTarget
TypeKeyInput TypeKeyInput
@@ -144,7 +143,6 @@ const (
TypePopPassLen = 1 TypePopPassLen = 1
TypePointerInputLen = 1 + 1 + 1*2 + 2*4 + 2*4 TypePointerInputLen = 1 + 1 + 1*2 + 2*4 + 2*4
TypeClipboardReadLen = 1 TypeClipboardReadLen = 1
TypeClipboardWriteLen = 1
TypeSourceLen = 1 TypeSourceLen = 1
TypeTargetLen = 1 TypeTargetLen = 1
TypeKeyInputLen = 1 + 1 TypeKeyInputLen = 1 + 1
@@ -422,7 +420,6 @@ var opProps = [0x100]opProp{
TypePopPass: {Size: TypePopPassLen, NumRefs: 0}, TypePopPass: {Size: TypePopPassLen, NumRefs: 0},
TypePointerInput: {Size: TypePointerInputLen, NumRefs: 1}, TypePointerInput: {Size: TypePointerInputLen, NumRefs: 1},
TypeClipboardRead: {Size: TypeClipboardReadLen, NumRefs: 1}, TypeClipboardRead: {Size: TypeClipboardReadLen, NumRefs: 1},
TypeClipboardWrite: {Size: TypeClipboardWriteLen, NumRefs: 1},
TypeSource: {Size: TypeSourceLen, NumRefs: 2}, TypeSource: {Size: TypeSourceLen, NumRefs: 2},
TypeTarget: {Size: TypeTargetLen, NumRefs: 2}, TypeTarget: {Size: TypeTargetLen, NumRefs: 2},
TypeKeyInput: {Size: TypeKeyInputLen, NumRefs: 2}, TypeKeyInput: {Size: TypeKeyInputLen, NumRefs: 2},
@@ -489,8 +486,6 @@ func (t OpType) String() string {
return "PointerInput" return "PointerInput"
case TypeClipboardRead: case TypeClipboardRead:
return "ClipboardRead" return "ClipboardRead"
case TypeClipboardWrite:
return "ClipboardWrite"
case TypeSource: case TypeSource:
return "Source" return "Source"
case TypeTarget: case TypeTarget:
+10 -10
View File
@@ -3,6 +3,8 @@
package clipboard package clipboard
import ( import (
"io"
"gioui.org/internal/ops" "gioui.org/internal/ops"
"gioui.org/io/event" "gioui.org/io/event"
"gioui.org/op" "gioui.org/op"
@@ -13,25 +15,23 @@ type Event struct {
Text string Text string
} }
// WriteCmd copies Text to the clipboard.
type WriteCmd struct {
Type string
Data io.ReadCloser
}
// ReadOp requests the text of the clipboard, delivered to // ReadOp requests the text of the clipboard, delivered to
// the current handler through an Event. // the current handler through an Event.
type ReadOp struct { type ReadOp struct {
Tag event.Tag Tag event.Tag
} }
// WriteOp copies Text to the clipboard.
type WriteOp struct {
Text string
}
func (h ReadOp) Add(o *op.Ops) { func (h ReadOp) Add(o *op.Ops) {
data := ops.Write1(&o.Internal, ops.TypeClipboardReadLen, h.Tag) data := ops.Write1(&o.Internal, ops.TypeClipboardReadLen, h.Tag)
data[0] = byte(ops.TypeClipboardRead) data[0] = byte(ops.TypeClipboardRead)
} }
func (h WriteOp) Add(o *op.Ops) {
data := ops.Write1String(&o.Internal, ops.TypeClipboardWriteLen, h.Text)
data[0] = byte(ops.TypeClipboardWrite)
}
func (Event) ImplementsEvent() {} func (Event) ImplementsEvent() {}
func (WriteCmd) ImplementsCommand() {}
+18 -8
View File
@@ -3,6 +3,9 @@
package input package input
import ( import (
"io"
"gioui.org/io/clipboard"
"gioui.org/io/event" "gioui.org/io/event"
) )
@@ -10,18 +13,19 @@ type clipboardQueue struct {
receivers map[event.Tag]struct{} receivers map[event.Tag]struct{}
// request avoid read clipboard every frame while waiting. // request avoid read clipboard every frame while waiting.
requested bool requested bool
text *string mime string
text []byte
} }
// WriteClipboard returns the most recent text to be copied // WriteClipboard returns the most recent data to be copied
// to the clipboard, if any. // to the clipboard, if any.
func (q *clipboardQueue) WriteClipboard() (string, bool) { func (q *clipboardQueue) WriteClipboard() (mime string, content []byte, ok bool) {
if q.text == nil { if q.text == nil {
return "", false return "", nil, false
} }
text := *q.text content = q.text
q.text = nil q.text = nil
return text, true return q.mime, content, true
} }
// ReadClipboard reports if any new handler is waiting // ReadClipboard reports if any new handler is waiting
@@ -41,8 +45,14 @@ func (q *clipboardQueue) Push(e event.Event, events *handlerEvents) {
} }
} }
func (q *clipboardQueue) ProcessWriteClipboard(refs []interface{}) { func (q *clipboardQueue) ProcessWriteClipboard(req clipboard.WriteCmd) {
q.text = refs[0].(*string) defer req.Data.Close()
content, err := io.ReadAll(req.Data)
if err != nil {
return
}
q.mime = req.Type
q.text = content
} }
func (q *clipboardQueue) ProcessReadClipboard(refs []interface{}) { func (q *clipboardQueue) ProcessReadClipboard(refs []interface{}) {
+15 -9
View File
@@ -3,6 +3,8 @@
package input package input
import ( import (
"io"
"strings"
"testing" "testing"
"gioui.org/io/clipboard" "gioui.org/io/clipboard"
@@ -84,23 +86,24 @@ func TestQueueProcessWriteClipboard(t *testing.T) {
ops, router := new(op.Ops), new(Router) ops, router := new(op.Ops), new(Router)
ops.Reset() ops.Reset()
clipboard.WriteOp{Text: "Write 1"}.Add(ops) const mime = "application/text"
router.Source().Queue(clipboard.WriteCmd{Type: mime, Data: io.NopCloser(strings.NewReader("Write 1"))})
router.Frame(ops) router.Frame(ops)
assertClipboardWriteOp(t, router, "Write 1") assertClipboardWriteCmd(t, router, mime, "Write 1")
ops.Reset() ops.Reset()
// No WriteOp // No WriteCmd
router.Frame(ops) router.Frame(ops)
assertClipboardWriteOp(t, router, "") assertClipboardWriteCmd(t, router, "", "")
ops.Reset() ops.Reset()
clipboard.WriteOp{Text: "Write 2"}.Add(ops) router.Source().Queue(clipboard.WriteCmd{Type: mime, Data: io.NopCloser(strings.NewReader("Write 2"))})
router.Frame(ops) router.Frame(ops)
assertClipboardReadOp(t, router, 0) assertClipboardReadOp(t, router, 0)
assertClipboardWriteOp(t, router, "Write 2") assertClipboardWriteCmd(t, router, mime, "Write 2")
ops.Reset() ops.Reset()
} }
@@ -141,16 +144,19 @@ func assertClipboardReadOpDuplicated(t *testing.T, router *Router, expected int)
} }
} }
func assertClipboardWriteOp(t *testing.T, router *Router, expected string) { func assertClipboardWriteCmd(t *testing.T, router *Router, mimeExp, expected string) {
t.Helper() t.Helper()
if (router.cqueue.text != nil) != (expected != "") { if (router.cqueue.text != nil) != (expected != "") {
t.Error("text not defined") t.Error("text not defined")
} }
text, ok := router.cqueue.WriteClipboard() mime, text, ok := router.cqueue.WriteClipboard()
if ok != (expected != "") { if ok != (expected != "") {
t.Error("duplicated requests") t.Error("duplicated requests")
} }
if text != expected { if string(mime) != mimeExp {
t.Errorf("got MIME type %s, expected %s", mime, mimeExp)
}
if string(text) != expected {
t.Errorf("got text %s, expected %s", text, expected) t.Errorf("got text %s, expected %s", text, expected)
} }
} }
+4 -4
View File
@@ -222,6 +222,8 @@ func (q *Router) executeCommands() {
q.key.queue.setSnippet(req) q.key.queue.setSnippet(req)
case transfer.OfferCmd: case transfer.OfferCmd:
q.pointer.queue.offerData(req, &q.handlers) q.pointer.queue.offerData(req, &q.handlers)
case clipboard.WriteCmd:
q.cqueue.ProcessWriteClipboard(req)
} }
} }
q.commands = nil q.commands = nil
@@ -374,9 +376,9 @@ func (q *Router) TextInputHint() (key.InputHint, bool) {
return q.key.queue.InputHint() return q.key.queue.InputHint()
} }
// WriteClipboard returns the most recent text to be copied // WriteClipboard returns the most recent content to be copied
// to the clipboard, if any. // to the clipboard, if any.
func (q *Router) WriteClipboard() (string, bool) { func (q *Router) WriteClipboard() (mime string, content []byte, ok bool) {
return q.cqueue.WriteClipboard() return q.cqueue.WriteClipboard()
} }
@@ -429,8 +431,6 @@ func (q *Router) collect() {
} }
case ops.TypeClipboardRead: case ops.TypeClipboardRead:
q.cqueue.ProcessReadClipboard(encOp.Refs) q.cqueue.ProcessReadClipboard(encOp.Refs)
case ops.TypeClipboardWrite:
q.cqueue.ProcessWriteClipboard(encOp.Refs)
case ops.TypeSave: case ops.TypeSave:
id := ops.DecodeSave(encOp.Data) id := ops.DecodeSave(encOp.Data)
if extra := id - len(q.savedTrans) + 1; extra > 0 { if extra := id - len(q.savedTrans) + 1; extra > 0 {
+1 -1
View File
@@ -417,7 +417,7 @@ func (e *Editor) command(gtx layout.Context, k key.Event) {
case "C", "X": case "C", "X":
e.scratch = e.text.SelectedText(e.scratch) e.scratch = e.text.SelectedText(e.scratch)
if text := string(e.scratch); text != "" { if text := string(e.scratch); text != "" {
clipboard.WriteOp{Text: text}.Add(gtx.Ops) gtx.Queue(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(strings.NewReader(text))})
if k.Name == "X" && !e.ReadOnly { if k.Name == "X" && !e.ReadOnly {
e.Delete(1) e.Delete(1)
} }
+2 -1
View File
@@ -2,6 +2,7 @@ package widget
import ( import (
"image" "image"
"io"
"math" "math"
"strings" "strings"
@@ -332,7 +333,7 @@ func (e *Selectable) command(gtx layout.Context, k key.Event) {
case "C", "X": case "C", "X":
e.scratch = e.text.SelectedText(e.scratch) e.scratch = e.text.SelectedText(e.scratch)
if text := string(e.scratch); text != "" { if text := string(e.scratch); text != "" {
clipboard.WriteOp{Text: text}.Add(gtx.Ops) gtx.Queue(clipboard.WriteCmd{Type: "application/text", Data: io.NopCloser(strings.NewReader(text))})
} }
// Select all // Select all
case "A": case "A":