gui/gui-lib/mred/private/wx/gtk/clipboard.rkt
Matthew Flatt f42356da3f Support and prefer GTK+ 3 on Unix/X
The main advantage of GTK+ 3 is better support for HiDPI
displays. If GTK+ 3 libraries are not available or if the
`PLT_GTK2` environment variable is defined, GTK+ 2 is used
as before.
2015-08-16 20:55:35 -06:00

315 lines
12 KiB
Racket

#lang racket/base
(require racket/class
racket/promise
ffi/unsafe
ffi/unsafe/alloc
racket/draw/unsafe/bstr
"../../syntax.rkt"
"../../lock.rkt"
"../common/queue.rkt"
"../common/local.rkt"
"../common/freeze.rkt"
"utils.rkt"
"types.rkt"
"pixbuf.rkt")
(provide
(protect-out clipboard-driver%
has-x-selection?
_GtkSelectionData
gtk_selection_data_get_length
gtk_selection_data_get_data
get-primary-atom
get-selection-eventspace))
(define (has-x-selection?) #t)
(define _GtkClipboard (_cpointer 'GtkClipboard))
(define _GtkDisplay _pointer)
;; Recent versions of Gtk provide function calls to
;; access data, but use structure when the functions are
;; not available
(define-cstruct _GtkSelectionDataT ([selection _GdkAtom]
[target _GdkAtom]
[type _GdkAtom]
[format _int]
[data _pointer]
[length _int]
[display _GtkDisplay]))
(define _GtkSelectionData _GtkSelectionDataT-pointer)
(define-gdk gdk_atom_intern (_fun _string _gboolean -> _GdkAtom))
(define-gtk gtk_clipboard_get (_fun _GdkAtom -> _GtkClipboard))
(define-gtk gtk_clipboard_set_with_data (_fun _GtkClipboard _pointer _uint
_fpointer _fpointer
_pointer
-> _void))
(define-gtk gtk_selection_data_set (_fun _GtkSelectionData
_GdkAtom
_int
_bytes
_int
-> _void))
(define-gtk gtk_selection_data_free (_fun _GtkSelectionData -> _void))
(define-gtk gtk_selection_data_get_length (_fun _GtkSelectionData -> _int)
#:fail (lambda () GtkSelectionDataT-length))
(define-gtk gtk_selection_data_get_data (_fun _GtkSelectionData -> _pointer)
#:fail (lambda () GtkSelectionDataT-data))
(define-cstruct _GtkTargetEntry ([target _pointer]
[flags _uint]
[info _uint]))
(define (get-data cb sel-data info self-box)
(send (ptr-ref self-box _scheme) provide-data info sel-data))
(define get_data
(function-ptr get-data (_fun #:atomic? #t _GtkClipboard _GtkSelectionData _int _pointer -> _void)))
(define (clear-owner cb self-box)
(send (ptr-ref self-box _scheme) replaced self-box))
(define clear_owner
(function-ptr clear-owner (_fun #:atomic? #t _GtkClipboard _pointer -> _void)))
(define primary-atom (delay (gdk_atom_intern "PRIMARY" #t)))
(define clipboard-atom (delay (gdk_atom_intern "CLIPBOARD" #t)))
(define (get-primary-atom) (force primary-atom))
(define the-x-selection-driver #f)
;; ----------------------------------------
(define _request-fun (_fun #:atomic? #t _GtkClipboard (_or-null _GtkSelectionData) _pointer -> _void))
(define _request-string-fun (_fun #:atomic? #t _GtkClipboard _string _pointer -> _void))
(define _request-image-fun (_fun #:atomic? #t _GtkClipboard _GdkPixbuf _pointer -> _void))
(define (handle-receipt backref data convert)
(let ([l (ptr-ref backref _racket)])
(free-immobile-cell backref)
(set-box! (car l) (and data (convert data)))
(semaphore-post (cdr l))))
(define (make-request-backref)
(let ([l (cons (box #f) (make-semaphore))])
(values l (malloc-immobile-cell l))))
(define (wait-request-backref l)
(semaphore-wait (cdr l))
(unbox (car l)))
(define (request-received cb data backref)
(handle-receipt backref
data
(lambda (v)
(let ([bstr (scheme_make_sized_byte_string
(gtk_selection_data_get_data v)
(gtk_selection_data_get_length v)
1)])
bstr))))
(define (string-request-received cb str backref)
(handle-receipt backref
str
(lambda (str) str)))
(define (image-request-received cb pix backref)
(handle-receipt backref
pix
pixbuf->bitmap))
(define request_received (function-ptr request-received _request-fun))
(define string_request_received (function-ptr string-request-received _request-string-fun))
(define image_request_received (function-ptr image-request-received _request-image-fun))
(define-gtk gtk_clipboard_request_contents
(_fun _GtkClipboard _GdkAtom (_fpointer = request_received) _pointer -> _void))
(define-gtk gtk_clipboard_request_text
(_fun _GtkClipboard (_fpointer = string_request_received) _pointer -> _void))
(define-gtk gtk_clipboard_request_image
(_fun _GtkClipboard (_fpointer = image_request_received) _pointer -> _void))
(define-gtk gtk_clipboard_set_image
(_fun _GtkClipboard _GdkPixbuf -> _void))
;; ----------------------------------------
(defclass clipboard-driver% object%
(init-field [x-selection? #f])
(when x-selection?
(set! the-x-selection-driver this))
(define client #f)
(define client-data #f)
(define client-types #f)
(define client-orig-types #f)
(define cb (gtk_clipboard_get
(if x-selection?
(force primary-atom)
(force clipboard-atom))))
(define self-box #f)
(define/public (get-client) client)
(define/public (set-client c orig-types)
(let ([all-data (if x-selection?
;; In X selection mode, get the data on demand:
#f
;; In clipboard mode, we can get the data
;; now, so it's ready if anyone asks:
(for/list ([t (in-list orig-types)])
(send c get-data t)))]
[types (for/list ([t (in-list orig-types)])
(if (equal? t "TEXT")
"UTF8_STRING"
t))])
(let-values ([(orig-types types all-data)
;; For "TEXT", provide "UTF8_STRING", "STRING", and "TEXT":
(if (member "TEXT" orig-types)
(values (append orig-types (list "TEXT" "TEXT"))
(append types (list "STRING" "TEXT"))
(and all-data (append all-data
(let loop ([all-data all-data]
[orig-types orig-types])
(if (equal? "TEXT" (car orig-types))
(list (car all-data) (car all-data))
(loop (cdr all-data) (cdr orig-types)))))))
(values orig-types types all-data))])
(let ([target-strings (malloc 'raw _byte (+ (length types)
(apply + (map string-utf-8-length types))))]
[targets (malloc _GtkTargetEntry (length types))])
(for/fold ([offset 0]) ([str (in-list types)]
[i (in-naturals)])
(let ([t (ptr-add targets i _GtkTargetEntry)])
(cpointer-push-tag! t 'GtkTargetEntry)
(set-GtkTargetEntry-target! t (ptr-add target-strings offset))
(set-GtkTargetEntry-flags! t 0)
(set-GtkTargetEntry-info! t i))
(let ([bstr (string->bytes/utf-8 str)])
(memcpy target-strings offset bstr 0 (bytes-length bstr))
(let ([offset (+ offset (bytes-length bstr))])
(ptr-set! (ptr-add target-strings offset) _byte 0)
(+ offset 1))))
(set! client c)
(set! client-data all-data)
(set! client-types types)
(set! client-orig-types orig-types)
(atomically
(let ([this-box (malloc-immobile-cell this)])
(set! self-box this-box)
(gtk_clipboard_set_with_data cb
targets
(length types)
get_data
clear_owner
this-box)))
(free target-strings)))))
(define/public (replaced s-box)
;; In atomic mode
(when (ptr-equal? s-box self-box)
(set! self-box #f)
(let ([c client])
(when c
(set! client #f)
(set! client-data #f)
(set! client-types #f)
(set! client-orig-types #f)
(queue-event (send c get-client-eventspace)
(lambda ()
(send c on-replaced))))))
(free-immobile-cell s-box))
(define/public (provide-data i sel-data)
;; In atomic mode; if it's the selection (not clipboard),
;; then hopefully we're in the right eventspace
(let ([bstr (if client
(if client-data
(list-ref client-data i)
(constrained-reply (send client get-client-eventspace)
(lambda ()
(send client get-data
(list-ref client-orig-types i)))
#f))
#f)])
(when bstr
(let ([bstr (if (string? bstr)
(string->bytes/utf-8 bstr)
bstr)])
(gtk_selection_data_set sel-data
(gdk_atom_intern (list-ref client-types i) #t)
8
bstr
(bytes-length bstr))))))
(define/private (self-data data-format)
;; Due to the way we block for X-selection data and
;; provide only when the request arrives in the right
;; eventspace, we handle self-X-selection specially:
(and x-selection?
self-box
(let ([c client]
[types client-types]
[orig-types client-orig-types])
(for/or ([t (in-list types)]
[o (in-list orig-types)])
(and (equal? t data-format)
(let ([e (send c get-client-eventspace)])
(if (eq? (current-eventspace) e)
(send client get-data t)
(let ([s #f]
[done (make-semaphore)])
(parameterize ([current-eventspace e])
(queue-callback
(lambda ()
(set! s (send client get-data t))
(semaphore-post done))))
(sync/timeout 0.1 done)
s))))))))
(define/public (get-data data-format)
(let* ([data-format (if (equal? data-format "TEXT")
"UTF8_STRING"
data-format)]
[atom (gdk_atom_intern data-format #t)])
(or (self-data data-format)
(wait-request-backref
(atomically
(let-values ([(l backref) (make-request-backref)])
(gtk_clipboard_request_contents cb atom backref)
l))))))
(define/public (get-text-data)
(or (let ([s (self-data "UTF8_STRING")])
(and s (bytes->string/utf-8 s #\?)))
(wait-request-backref
(atomically
(let-values ([(l backref) (make-request-backref)])
(gtk_clipboard_request_text cb backref)
l)))
""))
(define/public (get-bitmap-data)
(wait-request-backref
(atomically
(let-values ([(l backref) (make-request-backref)])
(gtk_clipboard_request_image cb backref)
l))))
(define/public (set-bitmap-data bm timestamp)
(define pb (bitmap->pixbuf bm))
(gtk_clipboard_set_image cb pb))
(super-new))
(define (get-selection-eventspace)
(and the-x-selection-driver
(let ([c (send the-x-selection-driver get-client)])
(and c
(send c get-client-eventspace)))))