[Small-cl-src] "simple" X11 clipboard client

Christophe Rhodes csr21 at cam.ac.uk
Tue Oct 19 14:16:31 EDT 2004


#| 

Howdy,

I found myself wondering how hard it would be to make an X client
which plays nice with select-and-paste (as seen by all the other X
clients out there).  The answer seems to be "quite hard", but here's a
step along the road: a translation of some C test code, yielding a
proof-of-concept clipboard client.  Pastes both to and from the client
work: a paste of text to the client yields printed messages to the
lisp's standard output, and pastes while the client owns PRIMARY
(left-click in the window) cause a silly piece of text to be pasted.

Christophe

|#

-------------- next part --------------
;;; This is a pretty direct translation of the Xlib selection test
;;; program by Tor Andersson found at
;;; <http://ghostscript.com/~tor/repos/Klipp/x11clipboard.c>, with
;;; minor enhancements:
;;;
;;; * gdk requestors apparently unconditionally request UTF8_STRING
;;;   selections without checking the TARGETS list of the selection
;;;   owner -- and apparently even never request anything else.  This
;;;   seems to be in contradiction with the freedesktop.org draft
;;;   specification at
;;;   <http://www.pps.jussieu.fr/~jch/software/UTF8_STRING/UTF8_STRING.text>
;;;   (linked from <http://freedesktop.org/Standards>), but this is
;;;   the real world and we have to live in it.  It would be nice if
;;;   someone in the freedesktop community could resolve this.
;;;
;;; * the original C code, in the XSendEvent call, has an event mask
;;;   of SelectionNotify.  SelectionNotify is not an event mask at
;;;   all, however: but the code works "by accident" because
;;;   SelectionNotify happens to have value 31, which has enough bits
;;;   flipped on that most clients select on at least one of those
;;;   events.  This bug is fixed below.
;;;
;;; As ever with these things, the divisions in intellectual property
;;; between the writer of the original C program, Tor Andersson
;;; (contactable at tor [dot] andersson [at] gmail [dot] com) and the
;;; translator (Christophe Rhodes, csr21 [at] cam [dot] ac [dot] uk)
;;; are murky, probably depend on jurisdiction, and in addition for
;;; such a small work are essentially trivial.  To set peoples' minds
;;; at ease, Tor wishes this information to be disseminated as widely
;;; as possible.

;;; Copyright (c) 2004, Christophe Rhodes
;;;
;;; Permission is hereby granted, free of charge, to any person
;;; obtaining a copy of this software and associated documentation
;;; files (the "Software"), to deal in the Software without
;;; restriction, including without limitation the rights to use, copy,
;;; modify, merge, publish, distribute, sublicense, and/or sell copies
;;; of the Software, and to permit persons to whom the Software is
;;; furnished to do so, subject to the following conditions:
;;;
;;; The above copyright notice and this permission notice shall be
;;; included in all copies or substantial portions of the Software.
;;;
;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
;;; DEALINGS IN THE SOFTWARE.

(defpackage "CLIPBOARD"
  (:use "CL" "XLIB")
  (:export "MAIN"))

(in-package "CLIPBOARD")

;;; This is "traditional" XLIB style; I don't really know if it's the
;;; best way -- in developing this program, style of XLIB programming
;;; was secondary to achieving First Paste.
(defvar *window*)
(defvar *time*)
(defvar *display*)

(defun ownselect ()
  (format t "~&> set-selection-owner~%") (finish-output)
  (set-selection-owner *display* :primary *window* *time*)
  (unless (eq *window* (selection-owner *display* :primary))
    (write-string "failed to own primary")))

(defun deselect ()
  (format t "~&> unset-selection-owner~%") (finish-output)
  (set-selection-owner *display* :primary nil *time*)
  (unless (eq nil (selection-owner *display* :primary))
    (write-string "failed to disown primary")))

(defun ask-paste ()
  (format t "~&! deleting properties on window~%") (finish-output)
  (delete-property *window* :aeclip-target)
  (delete-property *window* :aeclip-string)
  (delete-property *window* :aeclip-utf8_string)
  (delete-property *window* :aeclip-text)
  (format t "~&> convert-selection TARGETS~%") (finish-output)
  (convert-selection :primary :targets *window* :aeclip-target)
  (format t "~&> convert-selection STRING~%")  (finish-output)
  (convert-selection :primary :string *window* :aeclip-string)
  (format t "~&> convert-selection UTF8_STRING~%") (finish-output)
  (convert-selection :primary :utf8_string *window* :aeclip-utf8_string)
  (format t "~&> convert-selection TEXT~%") (finish-output)
  (convert-selection :primary :text *window* :aeclip-text)
  nil)

(defun recv-paste (property)
  (multiple-value-bind (data name format)
      (get-property *window* property)
    (format t "~&< get-prop ~S " name)
    (case format
      (32 (format t "[~{~S~^,~}]"
                  (mapcar (lambda (x) (atom-name *display* x)) data)))
      (8 (format t "~S" (map 'string 'code-char data)))
      (t (format t "format=~S data=~S" format data)))
    (format t "~%") (finish-output)
    (delete-property *window* property)))

(defun send-copy (selection target property requestor time)
  (case target
    ;; we are being a little liberal in what we accept here: no
    ;; requestor should ask us for a UTF8_STRING selection because we
    ;; don't advertise the capability (see the TARGETS clause below).
    ;; However, Xt-based requestors appear not to query TARGETS,
    ;; instead trying UTF8_STRING, COMPOUND_TEXT and STRING in order;
    ;; Gdk-based requestors don't query TARGETS either, trying just
    ;; UTF8_STRING (and giving up if the selection owner returns a
    ;; null property).  So pretend that we can send UTF8_STRING
    ;; selections.
    ((:string :utf8_string)
     (format t "~&> sending text data~%") (finish-output)
     (change-property requestor property
                      "Hello, World (from the CLX clipboard)!" target 8
                      :transform #'char-code))
    (:targets
     (format t "~&> sending targets list~%") (finish-output)
     (change-property requestor property '(:targets :string) target 32
                      :transform (lambda (x) (intern-atom *display* x))))
    (t
     (format t "~&> sending none~%") (finish-output)
     (setf property nil)))
  (send-event requestor :selection-notify
              (make-event-mask :button-press :property-change)
              :selection selection :target target :property property :time time
              :event-window requestor :window requestor))

(defun main ()
  (let* ((*display* (open-default-display))
         (screen (display-default-screen *display*))
         (*window*
          (create-window
           :parent (screen-root screen)
           :x 10 :y 10 :width 200 :height 200
           :event-mask (make-event-mask :button-press :property-change))))
    (map-window *window*)
    (display-finish-output *display*)
    (event-case (*display*)
      (:button-press (code time)
        (format t "~&ButtonPress~%") (finish-output)
        (case code
          (1 (setf *time* time) (ownselect))
          (2 (ask-paste))
          (3 (deselect))))
      (:client-message ()
        (format t "~&ClientMessage~%") (finish-output))
      (:selection-clear (selection)
        (format t "~&SelectionClear ~S~%" selection) (finish-output))
      (:selection-notify (selection target property)
        (format t "~&SelectionNotify ~S ~S ~S~%" selection target property)
        (finish-output)
        (unless (eq property nil)
          (recv-paste property))
        (display-finish-output *display*))
      (:selection-request (selection target property requestor time)
        (format t "~&SelectionRequest ~S ~S ~S~%" selection target property)
        (finish-output)
        (send-copy selection target property requestor time)
        (display-finish-output *display*))
      (:property-notify (atom state)
        (format t "~&PropertyNotify ~S ~S~%" atom state) (finish-output)))))


More information about the small-cl-src mailing list