app: implement Wayland clipboard support

Updates gio#31

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2020-05-17 15:16:02 +02:00
parent 9534337a43
commit 2da1d37ce7
2 changed files with 319 additions and 23 deletions
+33
View File
@@ -139,3 +139,36 @@ void gio_zwp_text_input_v3_add_listener(struct zwp_text_input_v3 *im, void *data
zwp_text_input_v3_add_listener(im, &listener, data);
}
void gio_wl_data_device_add_listener(struct wl_data_device *dd, void *data) {
static const struct wl_data_device_listener listener = {
.data_offer = gio_onDataDeviceOffer,
.enter = gio_onDataDeviceEnter,
.leave = gio_onDataDeviceLeave,
.motion = gio_onDataDeviceMotion,
.drop = gio_onDataDeviceDrop,
.selection = gio_onDataDeviceSelection,
};
wl_data_device_add_listener(dd, &listener, data);
}
void gio_wl_data_offer_add_listener(struct wl_data_offer *offer, void *data) {
static const struct wl_data_offer_listener listener = {
.offer = (void (*)(void *, struct wl_data_offer *, const char *))gio_onDataOfferOffer,
.source_actions = gio_onDataOfferSourceActions,
.action = gio_onDataOfferAction,
};
wl_data_offer_add_listener(offer, &listener, data);
}
void gio_wl_data_source_add_listener(struct wl_data_source *source, void *data) {
static const struct wl_data_source_listener listener = {
.target = (void (*)(void *, struct wl_data_source *, const char *))gio_onDataSourceTarget,
.send = (void (*)(void *, struct wl_data_source *, const char *, int32_t))gio_onDataSourceSend,
.cancelled = gio_onDataSourceCancelled,
.dnd_drop_performed = gio_onDataSourceDNDDropPerformed,
.dnd_finished = gio_onDataSourceDNDFinished,
.action = gio_onDataSourceAction,
};
wl_data_source_add_listener(source, &listener, data);
}
+286 -23
View File
@@ -9,7 +9,10 @@ import (
"errors"
"fmt"
"image"
"io"
"io/ioutil"
"math"
"os"
"os/exec"
"strconv"
"sync"
@@ -64,21 +67,25 @@ __attribute__ ((visibility ("hidden"))) void gio_wl_pointer_add_listener(struct
__attribute__ ((visibility ("hidden"))) void gio_wl_touch_add_listener(struct wl_touch *touch, void *data);
__attribute__ ((visibility ("hidden"))) void gio_wl_keyboard_add_listener(struct wl_keyboard *keyboard, void *data);
__attribute__ ((visibility ("hidden"))) void gio_zwp_text_input_v3_add_listener(struct zwp_text_input_v3 *im, void *data);
__attribute__ ((visibility ("hidden"))) void gio_wl_data_device_add_listener(struct wl_data_device *dd, void *data);
__attribute__ ((visibility ("hidden"))) void gio_wl_data_offer_add_listener(struct wl_data_offer *offer, void *data);
__attribute__ ((visibility ("hidden"))) void gio_wl_data_source_add_listener(struct wl_data_source *source, void *data);
*/
import "C"
type wlDisplay struct {
disp *C.struct_wl_display
reg *C.struct_wl_registry
compositor *C.struct_wl_compositor
wm *C.struct_xdg_wm_base
imm *C.struct_zwp_text_input_manager_v3
shm *C.struct_wl_shm
decor *C.struct_zxdg_decoration_manager_v1
seat *wlSeat
xkb *xkb.Context
outputMap map[C.uint32_t]*C.struct_wl_output
outputConfig map[*C.struct_wl_output]*wlOutput
disp *C.struct_wl_display
reg *C.struct_wl_registry
compositor *C.struct_wl_compositor
wm *C.struct_xdg_wm_base
imm *C.struct_zwp_text_input_manager_v3
shm *C.struct_wl_shm
dataDeviceManager *C.struct_wl_data_device_manager
decor *C.struct_zxdg_decoration_manager_v1
seat *wlSeat
xkb *xkb.Context
outputMap map[C.uint32_t]*C.struct_wl_output
outputConfig map[*C.struct_wl_output]*wlOutput
// Notification pipe fds.
notify struct {
@@ -97,9 +104,27 @@ type wlSeat struct {
keyboard *C.struct_wl_keyboard
im *C.struct_zwp_text_input_v3
// The most recent input serial.
serial C.uint32_t
pointerFocus *window
keyboardFocus *window
touchFoci map[C.int32_t]*window
// Clipboard support.
dataDev *C.struct_wl_data_device
// offers is a map from active wl_data_offers to
// the list of mime types they support.
offers map[*C.struct_wl_data_offer][]string
// clipboard is the wl_data_offer for the clipboard.
clipboard *C.struct_wl_data_offer
// mimeType is the chosen mime type of clipboard.
mimeType string
// source represents the clipboard content of the most recent
// clipboard write, if any.
source *C.struct_wl_data_source
// content is the data belonging to source.
content []byte
}
type repeatState struct {
@@ -154,12 +179,16 @@ type window struct {
mu sync.Mutex
animating bool
needAck bool
// The last configure serial waiting to be ack'ed.
// The most recent configure serial waiting to be ack'ed.
serial C.uint32_t
width int
height int
newScale bool
scale int
// readClipboard tracks whether a ClipboardEvent is requested.
readClipboard bool
// writeClipboard is set whenever a clipboard write is requested.
writeClipboard *string
}
type poller struct {
@@ -184,6 +213,10 @@ type wlOutput struct {
// in C is forbidden.
var callbackMap sync.Map
// clipboardMimeTypes is a list of supported clipboard mime types, in
// order of preference.
var clipboardMimeTypes = []string{"text/plain;charset=utf8", "UTF8_STRING", "text/plain", "TEXT", "STRING"}
func init() {
wlDriver = newWLWindow
}
@@ -216,6 +249,48 @@ func newWLWindow(window Callbacks, opts *Options) error {
return nil
}
func (d *wlDisplay) writeClipboard(content []byte) error {
s := d.seat
if s == nil {
return nil
}
// Clear old offer.
if s.source != nil {
C.wl_data_source_destroy(s.source)
s.source = nil
s.content = nil
}
if d.dataDeviceManager == nil || s.dataDev == nil {
return nil
}
s.content = content
s.source = C.wl_data_device_manager_create_data_source(d.dataDeviceManager)
C.gio_wl_data_source_add_listener(s.source, unsafe.Pointer(s.seat))
for _, mime := range clipboardMimeTypes {
C.wl_data_source_offer(s.source, C.CString(mime))
}
C.wl_data_device_set_selection(s.dataDev, s.source, s.serial)
return nil
}
func (d *wlDisplay) readClipboard() (io.ReadCloser, error) {
s := d.seat
if s == nil {
return nil, nil
}
if s.clipboard == nil {
return nil, nil
}
r, w, err := os.Pipe()
if err != nil {
return nil, err
}
cmimeType := C.CString(s.mimeType)
defer C.free(unsafe.Pointer(cmimeType))
C.wl_data_offer_receive(s.clipboard, cmimeType, C.int(w.Fd()))
return r, nil
}
func (d *wlDisplay) createNativeWindow(opts *Options) (*window, error) {
if d.compositor == nil {
return nil, errors.New("wayland: no compositor available")
@@ -320,7 +395,25 @@ func gio_onSeatCapabilities(data unsafe.Pointer, seat *C.struct_wl_seat, caps C.
s.updateCaps(caps)
}
// flushOffers remove all wl_data_offers that isn't the clipboard
// content.
func (s *wlSeat) flushOffers() {
for o := range s.offers {
if o == s.clipboard {
continue
}
// We're only interested in clipboard offers.
delete(s.offers, o)
callbackDelete(unsafe.Pointer(o))
C.wl_data_offer_destroy(o)
}
}
func (s *wlSeat) destroy() {
if s.source != nil {
C.wl_data_source_destroy(s.source)
s.source = nil
}
if s.im != nil {
C.zwp_text_input_v3_destroy(s.im)
s.im = nil
@@ -334,6 +427,11 @@ func (s *wlSeat) destroy() {
if s.keyboard != nil {
C.wl_keyboard_release(s.keyboard)
}
s.clipboard = nil
s.flushOffers()
if s.dataDev != nil {
C.wl_data_device_release(s.dataDev)
}
if s.seat != nil {
callbackDelete(unsafe.Pointer(s.seat))
C.wl_seat_release(s.seat)
@@ -482,16 +580,31 @@ func gio_onRegistryGlobal(data unsafe.Pointer, reg *C.struct_wl_registry, name C
d.outputMap[name] = output
d.outputConfig[output] = new(wlOutput)
case "wl_seat":
if d.seat == nil {
s := (*C.struct_wl_seat)(C.wl_registry_bind(reg, name, &C.wl_seat_interface, 5))
d.seat = &wlSeat{
disp: d,
name: name,
seat: s,
}
callbackStore(unsafe.Pointer(s), d.seat)
C.gio_wl_seat_add_listener(d.seat.seat, unsafe.Pointer(d.seat.seat))
if d.seat != nil {
break
}
s := (*C.struct_wl_seat)(C.wl_registry_bind(reg, name, &C.wl_seat_interface, 5))
if s == nil {
// No support for v5 protocol.
break
}
d.seat = &wlSeat{
disp: d,
name: name,
seat: s,
offers: make(map[*C.struct_wl_data_offer][]string),
}
callbackStore(unsafe.Pointer(s), d.seat)
C.gio_wl_seat_add_listener(s, unsafe.Pointer(s))
if d.dataDeviceManager == nil {
break
}
d.seat.dataDev = C.wl_data_device_manager_get_data_device(d.dataDeviceManager, s)
if d.seat.dataDev == nil {
break
}
callbackStore(unsafe.Pointer(d.seat.dataDev), d.seat)
C.gio_wl_data_device_add_listener(d.seat.dataDev, unsafe.Pointer(d.seat.dataDev))
case "wl_shm":
d.shm = (*C.struct_wl_shm)(C.wl_registry_bind(reg, name, &C.wl_shm_interface, 1))
case "xdg_wm_base":
@@ -501,6 +614,67 @@ func gio_onRegistryGlobal(data unsafe.Pointer, reg *C.struct_wl_registry, name C
// TODO: Implement and test text-input support.
/*case "zwp_text_input_manager_v3":
d.imm = (*C.struct_zwp_text_input_manager_v3)(C.wl_registry_bind(reg, name, &C.zwp_text_input_manager_v3_interface, 1))*/
case "wl_data_device_manager":
d.dataDeviceManager = (*C.struct_wl_data_device_manager)(C.wl_registry_bind(reg, name, &C.wl_data_device_manager_interface, 3))
}
}
//export gio_onDataOfferOffer
func gio_onDataOfferOffer(data unsafe.Pointer, offer *C.struct_wl_data_offer, mime *C.char) {
s := callbackLoad(data).(*wlSeat)
s.offers[offer] = append(s.offers[offer], C.GoString(mime))
}
//export gio_onDataOfferSourceActions
func gio_onDataOfferSourceActions(data unsafe.Pointer, offer *C.struct_wl_data_offer, acts C.uint32_t) {
}
//export gio_onDataOfferAction
func gio_onDataOfferAction(data unsafe.Pointer, offer *C.struct_wl_data_offer, act C.uint32_t) {
}
//export gio_onDataDeviceOffer
func gio_onDataDeviceOffer(data unsafe.Pointer, dataDev *C.struct_wl_data_device, id *C.struct_wl_data_offer) {
s := callbackLoad(data).(*wlSeat)
callbackStore(unsafe.Pointer(id), s)
C.gio_wl_data_offer_add_listener(id, unsafe.Pointer(id))
s.offers[id] = nil
}
//export gio_onDataDeviceEnter
func gio_onDataDeviceEnter(data unsafe.Pointer, dataDev *C.struct_wl_data_device, serial C.uint32_t, surf *C.struct_wl_surface, x, y C.wl_fixed_t, id *C.struct_wl_data_offer) {
s := callbackLoad(data).(*wlSeat)
s.serial = serial
s.flushOffers()
}
//export gio_onDataDeviceLeave
func gio_onDataDeviceLeave(data unsafe.Pointer, dataDev *C.struct_wl_data_device) {
}
//export gio_onDataDeviceMotion
func gio_onDataDeviceMotion(data unsafe.Pointer, dataDev *C.struct_wl_data_device, t C.uint32_t, x, y C.wl_fixed_t) {
}
//export gio_onDataDeviceDrop
func gio_onDataDeviceDrop(data unsafe.Pointer, dataDev *C.struct_wl_data_device) {
}
//export gio_onDataDeviceSelection
func gio_onDataDeviceSelection(data unsafe.Pointer, dataDev *C.struct_wl_data_device, id *C.struct_wl_data_offer) {
s := callbackLoad(data).(*wlSeat)
defer s.flushOffers()
s.clipboard = nil
loop:
for _, want := range clipboardMimeTypes {
for _, got := range s.offers[id] {
if want != got {
continue
}
s.clipboard = id
s.mimeType = got
break loop
}
}
}
@@ -521,6 +695,7 @@ func gio_onRegistryGlobalRemove(data unsafe.Pointer, reg *C.struct_wl_registry,
//export gio_onTouchDown
func gio_onTouchDown(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.uint32_t, surf *C.struct_wl_surface, id C.int32_t, x, y C.wl_fixed_t) {
s := callbackLoad(data).(*wlSeat)
s.serial = serial
w := callbackLoad(unsafe.Pointer(surf)).(*window)
s.touchFoci[id] = w
w.lastTouch = f32.Point{
@@ -539,6 +714,7 @@ func gio_onTouchDown(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.
//export gio_onTouchUp
func gio_onTouchUp(data unsafe.Pointer, touch *C.struct_wl_touch, serial, t C.uint32_t, id C.int32_t) {
s := callbackLoad(data).(*wlSeat)
s.serial = serial
w := s.touchFoci[id]
delete(s.touchFoci, id)
w.w.Event(pointer.Event{
@@ -586,6 +762,7 @@ func gio_onTouchCancel(data unsafe.Pointer, touch *C.struct_wl_touch) {
//export gio_onPointerEnter
func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, serial C.uint32_t, surf *C.struct_wl_surface, x, y C.wl_fixed_t) {
s := callbackLoad(data).(*wlSeat)
s.serial = serial
w := callbackLoad(unsafe.Pointer(surf)).(*window)
s.pointerFocus = w
// Get images[0].
@@ -603,6 +780,8 @@ func gio_onPointerEnter(data unsafe.Pointer, pointer *C.struct_wl_pointer, seria
//export gio_onPointerLeave
func gio_onPointerLeave(data unsafe.Pointer, p *C.struct_wl_pointer, serial C.uint32_t, surface *C.struct_wl_surface) {
s := callbackLoad(data).(*wlSeat)
s.serial = serial
}
//export gio_onPointerMotion
@@ -616,6 +795,7 @@ func gio_onPointerMotion(data unsafe.Pointer, p *C.struct_wl_pointer, t C.uint32
//export gio_onPointerButton
func gio_onPointerButton(data unsafe.Pointer, p *C.struct_wl_pointer, serial, t, wbtn, state C.uint32_t) {
s := callbackLoad(data).(*wlSeat)
s.serial = serial
w := s.pointerFocus
// From linux-event-codes.h.
const (
@@ -723,6 +903,20 @@ func gio_onPointerAxisDiscrete(data unsafe.Pointer, p *C.struct_wl_pointer, axis
}
}
func (w *window) ReadClipboard() {
w.mu.Lock()
w.readClipboard = true
w.mu.Unlock()
w.disp.wakeup()
}
func (w *window) WriteClipboard(s string) {
w.mu.Lock()
w.writeClipboard = &s
w.mu.Unlock()
w.disp.wakeup()
}
func (w *window) resetFling() {
w.fling.start = false
w.fling.anim = fling.Animation{}
@@ -746,6 +940,7 @@ func gio_onKeyboardKeymap(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, f
//export gio_onKeyboardEnter
func gio_onKeyboardEnter(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial C.uint32_t, surf *C.struct_wl_surface, keys *C.struct_wl_array) {
s := callbackLoad(data).(*wlSeat)
s.serial = serial
w := callbackLoad(unsafe.Pointer(surf)).(*window)
s.keyboardFocus = w
s.disp.repeat.Stop(0)
@@ -755,6 +950,7 @@ func gio_onKeyboardEnter(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
//export gio_onKeyboardLeave
func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial C.uint32_t, surf *C.struct_wl_surface) {
s := callbackLoad(data).(*wlSeat)
s.serial = serial
s.disp.repeat.Stop(0)
w := s.keyboardFocus
w.w.Event(key.FocusEvent{Focus: false})
@@ -763,6 +959,7 @@ func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se
//export gio_onKeyboardKey
func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial, timestamp, keyCode, state C.uint32_t) {
s := callbackLoad(data).(*wlSeat)
s.serial = serial
w := s.keyboardFocus
t := time.Duration(timestamp) * time.Millisecond
s.disp.repeat.Stop(t)
@@ -876,12 +1073,39 @@ func (w *window) loop() error {
w.w.Event(system.DestroyEvent{})
break
}
// pass false to skip unnecessary drawing.
w.draw(false)
w.process()
}
return nil
}
func (w *window) process() {
w.mu.Lock()
readClipboard := w.readClipboard
writeClipboard := w.writeClipboard
w.readClipboard = false
w.writeClipboard = nil
w.mu.Unlock()
if readClipboard {
r, err := w.disp.readClipboard()
// Send empty responses on unavailable clipboards or errors.
if r == nil || err != nil {
w.w.Event(system.ClipboardEvent{})
return
}
// Don't let slow clipboard transfers block event loop.
go func() {
defer r.Close()
data, _ := ioutil.ReadAll(r)
w.w.Event(system.ClipboardEvent{Text: string(data)})
}()
}
if writeClipboard != nil {
w.disp.writeClipboard([]byte(*writeClipboard))
}
// pass false to skip unnecessary drawing.
w.draw(false)
}
func (d *wlDisplay) dispatch(p *poller) error {
dispfd := C.wl_display_get_fd(d.disp)
// Poll for events and notifications.
@@ -964,6 +1188,7 @@ func (w *window) destroy() {
//export gio_onKeyboardModifiers
func gio_onKeyboardModifiers(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial, depressed, latched, locked, group C.uint32_t) {
s := callbackLoad(data).(*wlSeat)
s.serial = serial
d := s.disp
d.repeat.Stop(0)
if d.xkb == nil {
@@ -1003,6 +1228,44 @@ func gio_onTextInputDeleteSurroundingText(data unsafe.Pointer, im *C.struct_zwp_
//export gio_onTextInputDone
func gio_onTextInputDone(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, serial C.uint32_t) {
s := callbackLoad(data).(*wlSeat)
s.serial = serial
}
//export gio_onDataSourceTarget
func gio_onDataSourceTarget(data unsafe.Pointer, source *C.struct_wl_data_source, mime *C.char) {
}
//export gio_onDataSourceSend
func gio_onDataSourceSend(data unsafe.Pointer, source *C.struct_wl_data_source, mime *C.char, fd C.int32_t) {
s := callbackLoad(data).(*wlSeat)
content := s.content
go func() {
defer syscall.Close(int(fd))
syscall.Write(int(fd), content)
}()
}
//export gio_onDataSourceCancelled
func gio_onDataSourceCancelled(data unsafe.Pointer, source *C.struct_wl_data_source) {
s := callbackLoad(data).(*wlSeat)
if s.source == source {
s.content = nil
s.source = nil
}
C.wl_data_source_destroy(source)
}
//export gio_onDataSourceDNDDropPerformed
func gio_onDataSourceDNDDropPerformed(data unsafe.Pointer, source *C.struct_wl_data_source) {
}
//export gio_onDataSourceDNDFinished
func gio_onDataSourceDNDFinished(data unsafe.Pointer, source *C.struct_wl_data_source) {
}
//export gio_onDataSourceAction
func gio_onDataSourceAction(data unsafe.Pointer, source *C.struct_wl_data_source, act C.uint32_t) {
}
func (w *window) flushScroll() {