Received: from git.sr.ht (unknown [173.195.146.142]) by mail-b.sr.ht (Postfix) with ESMTPSA id 7C45DFF139 for <~eliasnaur/gio-patches@lists.sr.ht>; Tue, 1 Dec 2020 04:37:56 +0000 (UTC) From: ~inkeliz Date: Tue, 01 Dec 2020 04:37:22 +0000 Subject: [PATCH gio] io,app: add ReadClipboardOp and WriteClipboardOp v2 Message-ID: <160679747630.5621.6662469352245087419-0@git.sr.ht> X-Mailer: git.sr.ht Reply-to: ~inkeliz To: ~eliasnaur/gio-patches@lists.sr.ht Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 From: Inkeliz Previously, the only way to manipulate the clipboard (read or write) is using the `app.Window`, that is inaccessible inside widgets, by default. The new `clipboard.ReadClipboardOp` and `clipboard.WriteClipboardOp` makes possible to read/write inside the widget. The old `system.ClipboardEvent` was removed and replaced by `clipboard.Event`. Fixes gio#183. Signed-off-by: Inkeliz --- app/internal/window/os_android.go | 3 +- app/internal/window/os_ios.go | 3 +- app/internal/window/os_js.go | 3 +- app/internal/window/os_macos.go | 3 +- app/internal/window/os_wayland.go | 5 +- app/internal/window/os_windows.go | 3 +- app/internal/window/os_x11.go | 3 +- app/window.go | 12 +++- internal/opconst/ops.go | 8 ++- io/clipboard/clipboard.go | 34 +++++++++++ io/router/clipboard.go | 93 +++++++++++++++++++++++++++++++ io/router/router.go | 17 ++++++ io/system/system.go | 7 --- 13 files changed, 177 insertions(+), 17 deletions(-) create mode 100644 io/clipboard/clipboard.go create mode 100644 io/router/clipboard.go diff --git a/app/internal/window/os_android.go b/app/internal/window/os_andro= id.go index fc494e5..b73ca38 100644 --- a/app/internal/window/os_android.go +++ b/app/internal/window/os_android.go @@ -52,6 +52,7 @@ import ( "unsafe" =20 "gioui.org/f32" + "gioui.org/io/clipboard" "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/io/system" @@ -645,7 +646,7 @@ func (w *window) ReadClipboard() { return } content :=3D goString(env, C.jstring(c)) - w.callbacks.Event(system.ClipboardEvent{Text: content}) + w.callbacks.Event(clipboard.Event{Text: content}) }) } =20 diff --git a/app/internal/window/os_ios.go b/app/internal/window/os_ios.go index 1865454..d92999a 100644 --- a/app/internal/window/os_ios.go +++ b/app/internal/window/os_ios.go @@ -38,6 +38,7 @@ import ( "unsafe" =20 "gioui.org/f32" + "gioui.org/io/clipboard" "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/io/system" @@ -222,7 +223,7 @@ func onTouch(last C.int, view, touchRef C.CFTypeRef, phas= e C.NSInteger, x, y C.C func (w *window) ReadClipboard() { runOnMain(func() { content :=3D nsstringToString(C.gio_readClipboard()) - w.w.Event(system.ClipboardEvent{Text: content}) + w.w.Event(clipboard.Event{Text: content}) }) } =20 diff --git a/app/internal/window/os_js.go b/app/internal/window/os_js.go index e4fb17c..3c26922 100644 --- a/app/internal/window/os_js.go +++ b/app/internal/window/os_js.go @@ -12,6 +12,7 @@ import ( "unicode/utf8" =20 "gioui.org/f32" + "gioui.org/io/clipboard" "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/io/system" @@ -59,7 +60,7 @@ func NewWindow(win Callbacks, opts *Options) error { }) w.clipboardCallback =3D w.funcOf(func(this js.Value, args []js.Value) inter= face{} { content :=3D args[0].String() - win.Event(system.ClipboardEvent{Text: content}) + win.Event(clipboard.Event{Text: content}) return nil }) w.addEventListeners() diff --git a/app/internal/window/os_macos.go b/app/internal/window/os_macos.go index 1afec6f..339e659 100644 --- a/app/internal/window/os_macos.go +++ b/app/internal/window/os_macos.go @@ -14,6 +14,7 @@ import ( "unsafe" =20 "gioui.org/f32" + "gioui.org/io/clipboard" "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/io/system" @@ -107,7 +108,7 @@ func (w *window) contextView() C.CFTypeRef { func (w *window) ReadClipboard() { runOnMain(func() { content :=3D nsstringToString(C.gio_readClipboard()) - w.w.Event(system.ClipboardEvent{Text: content}) + w.w.Event(clipboard.Event{Text: content}) }) } =20 diff --git a/app/internal/window/os_wayland.go b/app/internal/window/os_wayla= nd.go index 60f59b1..36837b1 100644 --- a/app/internal/window/os_wayland.go +++ b/app/internal/window/os_wayland.go @@ -22,6 +22,7 @@ import ( "gioui.org/app/internal/xkb" "gioui.org/f32" "gioui.org/internal/fling" + "gioui.org/io/clipboard" "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/io/system" @@ -1099,14 +1100,14 @@ func (w *window) process() { r, err :=3D w.disp.readClipboard() // Send empty responses on unavailable clipboards or errors. if r =3D=3D nil || err !=3D nil { - w.w.Event(system.ClipboardEvent{}) + w.w.Event(clipboard.Event{}) return } // Don't let slow clipboard transfers block event loop. go func() { defer r.Close() data, _ :=3D ioutil.ReadAll(r) - w.w.Event(system.ClipboardEvent{Text: string(data)}) + w.w.Event(clipboard.Event{Text: string(data)}) }() } if writeClipboard !=3D nil { diff --git a/app/internal/window/os_windows.go b/app/internal/window/os_windo= ws.go index 2a38d53..041c276 100644 --- a/app/internal/window/os_windows.go +++ b/app/internal/window/os_windows.go @@ -22,6 +22,7 @@ import ( "gioui.org/unit" =20 "gioui.org/f32" + "gioui.org/io/clipboard" "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/io/system" @@ -508,7 +509,7 @@ func (w *window) readClipboard() error { hdr.Len =3D n content :=3D string(utf16.Decode(u16)) go func() { - w.w.Event(system.ClipboardEvent{Text: content}) + w.w.Event(clipboard.Event{Text: content}) }() return nil } diff --git a/app/internal/window/os_x11.go b/app/internal/window/os_x11.go index 413a959..4317386 100644 --- a/app/internal/window/os_x11.go +++ b/app/internal/window/os_x11.go @@ -34,6 +34,7 @@ import ( "unsafe" =20 "gioui.org/f32" + "gioui.org/io/clipboard" "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/io/system" @@ -410,7 +411,7 @@ func (h *x11EventHandler) handleEvents() bool { break } str :=3D C.GoStringN((*C.char)(unsafe.Pointer(text.value)), C.int(text.ni= tems)) - w.w.Event(system.ClipboardEvent{Text: str}) + w.w.Event(clipboard.Event{Text: str}) case C.SelectionRequest: cevt :=3D (*C.XSelectionRequestEvent)(unsafe.Pointer(xev)) if cevt.selection !=3D w.atoms.clipboard || cevt.property =3D=3D C.None { diff --git a/app/window.go b/app/window.go index ed46958..99fa89e 100644 --- a/app/window.go +++ b/app/window.go @@ -167,6 +167,16 @@ func (w *Window) processFrame(frameStart time.Time, size= image.Point, frame *op. case router.TextInputClose: w.driver.ShowTextInput(false) } + if w.queue.q.ReadClipboard() { + go func() { + w.driver.ReadClipboard() + }() + } + if text :=3D w.queue.q.WriteClipboard(); text !=3D nil { + go func() { + w.driver.WriteClipboard(*text) + }() + } if w.queue.q.Profiling() { frameDur :=3D time.Since(frameStart) frameDur =3D frameDur.Truncate(100 * time.Microsecond) @@ -194,7 +204,7 @@ func (w *Window) Invalidate() { } =20 // ReadClipboard initiates a read of the clipboard in the form -// of a system.ClipboardEvent. Multiple reads may be coalesced +// of a clipboard.Event. Multiple reads may be coalesced // to a single event. func (w *Window) ReadClipboard() { go w.driverDo(func() { diff --git a/internal/opconst/ops.go b/internal/opconst/ops.go index dba0f54..3634ce9 100644 --- a/internal/opconst/ops.go +++ b/internal/opconst/ops.go @@ -20,6 +20,8 @@ const ( TypeArea TypePointerInput TypePass + TypeWriteClipboard + TypeReadClipboard TypeKeyInput TypeKeyFocus TypeKeySoftKeyboard @@ -43,6 +45,8 @@ const ( TypeAreaLen =3D 1 + 1 + 4*4 TypePointerInputLen =3D 1 + 1 + 1 TypePassLen =3D 1 + 1 + TypeWriteClipboardLen =3D 1 + TypeReadClipboardLen =3D 1 TypeKeyInputLen =3D 1 TypeKeyFocusLen =3D 1 + 1 TypeKeySoftKeyboardLen =3D 1 + 1 @@ -67,6 +71,8 @@ func (t OpType) Size() int { TypeAreaLen, TypePointerInputLen, TypePassLen, + TypeWriteClipboardLen, + TypeReadClipboardLen, TypeKeyInputLen, TypeKeyFocusLen, TypeKeySoftKeyboardLen, @@ -80,7 +86,7 @@ func (t OpType) Size() int { =20 func (t OpType) NumRefs() int { switch t { - case TypeKeyInput, TypePointerInput, TypeProfile, TypeCall: + case TypeKeyInput, TypePointerInput, TypeProfile, TypeCall, TypeReadClipboa= rd, TypeWriteClipboard: return 1 case TypeImage: return 2 diff --git a/io/clipboard/clipboard.go b/io/clipboard/clipboard.go new file mode 100644 index 0000000..46d6b00 --- /dev/null +++ b/io/clipboard/clipboard.go @@ -0,0 +1,34 @@ +package clipboard + +import ( + "gioui.org/internal/opconst" + "gioui.org/io/event" + "gioui.org/op" +) + +type ReadClipboardOp struct { + Tag event.Tag +} + +type WriteClipboardOp struct { + Text string +} + +// Event is generated when a handler request +// the ReadClipboardOp +type Event struct { + Text string +} + +func (h ReadClipboardOp) Add(o *op.Ops) { + data :=3D o.Write1(opconst.TypeReadClipboardLen, h.Tag) + data[0] =3D byte(opconst.TypeReadClipboard) +} + +func (h WriteClipboardOp) Add(o *op.Ops) { + data :=3D o.Write1(opconst.TypeWriteClipboardLen, &h.Text) + data[0] =3D byte(opconst.TypeWriteClipboard) +} + + +func (Event) ImplementsEvent() {} \ No newline at end of file diff --git a/io/router/clipboard.go b/io/router/clipboard.go new file mode 100644 index 0000000..06aa768 --- /dev/null +++ b/io/router/clipboard.go @@ -0,0 +1,93 @@ +package router + +import ( + "gioui.org/internal/opconst" + "gioui.org/internal/ops" + "gioui.org/io/event" + "gioui.org/op" +) + +type clipboardQueue struct { + receiver event.Tag + requested bool + text *string + reader ops.Reader +} + +// WriteClipboard returns the last text supossed to be +// copied to clipboard as determined in Frame. +func (q *clipboardQueue) WriteClipboard() *string { + if q.text !=3D nil { + t :=3D q.text + q.text =3D nil + return t + } + return nil +} + +// ReadClipboard returns true if there's any request +// to read the clipboard. +func (q *clipboardQueue) ReadClipboard() bool { + if q.receiver !=3D nil && !q.requested { + q.requested =3D true + return true + } + return false +} + +func (q *clipboardQueue) Frame(root *op.Ops, events *handlerEvents) { + q.reader.Reset(root) + + receiver, text :=3D q.resolveClipboard(events) + if text !=3D nil { + q.text =3D text + } + if receiver !=3D nil { + q.receiver =3D receiver + q.requested =3D false + } +} + +func (q *clipboardQueue) Push(e event.Event, events *handlerEvents) { + if q.receiver !=3D nil { + events.Add(q.receiver, e) + q.receiver =3D nil + } +} + +func (q *clipboardQueue) resolveClipboard(events *handlerEvents) (receiver e= vent.Tag, text *string) { +loop: + for encOp, ok :=3D q.reader.Decode(); ok; encOp, ok =3D q.reader.Decode() { + switch opconst.OpType(encOp.Data[0]) { + case opconst.TypeWriteClipboard: + text =3D decodeWriteClipboard(encOp.Data, encOp.Refs) + case opconst.TypeReadClipboard: + receiver =3D decodeReadClipboard(encOp.Data, encOp.Refs) + case opconst.TypePush: + newReceiver, newWrite :=3D q.resolveClipboard(events) + if newWrite !=3D nil { + text =3D newWrite + } + if newReceiver !=3D nil { + receiver =3D newReceiver + } + case opconst.TypePop: + break loop + } + } + return receiver, text +} + +func decodeWriteClipboard(d []byte, refs []interface{}) *string { + if opconst.OpType(d[0]) !=3D opconst.TypeWriteClipboard { + panic("invalid op") + } + return refs[0].(*string) +} + +func decodeReadClipboard(d []byte, refs []interface{}) event.Tag { + if opconst.OpType(d[0]) !=3D opconst.TypeReadClipboard { + panic("invalid op") + } + return refs[0].(event.Tag) +} diff --git a/io/router/router.go b/io/router/router.go index d24d214..7ab212d 100644 --- a/io/router/router.go +++ b/io/router/router.go @@ -16,6 +16,7 @@ import ( =20 "gioui.org/internal/opconst" "gioui.org/internal/ops" + "gioui.org/io/clipboard" "gioui.org/io/event" "gioui.org/io/key" "gioui.org/io/pointer" @@ -28,6 +29,7 @@ import ( type Router struct { pqueue pointerQueue kqueue keyQueue + cqueue clipboardQueue =20 handlers handlerEvents =20 @@ -73,6 +75,7 @@ func (q *Router) Frame(ops *op.Ops) { =20 q.pqueue.Frame(ops, &q.handlers) q.kqueue.Frame(ops, &q.handlers) + q.cqueue.Frame(ops, &q.handlers) if q.handlers.HadEvents() { q.wakeup =3D true q.wakeupTime =3D time.Time{} @@ -88,6 +91,8 @@ func (q *Router) Add(events ...event.Event) bool { q.pqueue.Push(e, &q.handlers) case key.EditEvent, key.Event, key.FocusEvent: q.kqueue.Push(e, &q.handlers) + case clipboard.Event: + q.cqueue.Push(e, &q.handlers) } } return q.handlers.HadEvents() @@ -99,6 +104,18 @@ func (q *Router) TextInputState() TextInputState { return q.kqueue.InputState() } =20 +// WriteClipboard returns the most recent text to be copied +// to the clipboard, if any. +func (q *Router) WriteClipboard() *string { + return q.cqueue.WriteClipboard() +} + +// ReadClipboard returns true if some handler is waiting to +// retrieve the clipboard. +func (q *Router) ReadClipboard() bool { + return q.cqueue.ReadClipboard() +} + func (q *Router) collect() { for encOp, ok :=3D q.reader.Decode(); ok; encOp, ok =3D q.reader.Decode() { switch opconst.OpType(encOp.Data[0]) { diff --git a/io/system/system.go b/io/system/system.go index 4aa88d3..8a84051 100644 --- a/io/system/system.go +++ b/io/system/system.go @@ -60,12 +60,6 @@ type DestroyEvent struct { Err error } =20 -// ClipboardEvent is sent once for each request for the -// clipboard content. -type ClipboardEvent struct { - Text string -} - // Insets is the space taken up by // system decoration such as translucent // system bars and software keyboards. @@ -122,4 +116,3 @@ func (FrameEvent) ImplementsEvent() {} func (StageEvent) ImplementsEvent() {} func (*CommandEvent) ImplementsEvent() {} func (DestroyEvent) ImplementsEvent() {} -func (ClipboardEvent) ImplementsEvent() {} --=20 2.26.2