mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 10:25:37 +00:00
io/input,io/clipboard: [API] replace WriteOp with command
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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{}) {
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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,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":
|
||||||
|
|||||||
Reference in New Issue
Block a user