more clipboard hacking

Christophe Rhodes <[email protected]> Fri, 11 Mar 2005 17:22:19 +0000
Newsgroups gmane.lisp.clx.devel
Message-ID <[email protected]>
--=-=-=

Hi,

I had occasion to flick through ICCCM, and discovered that the
clipboard handling demo program (in demo/clipboard.lisp) wasn't
compliant.  In particular, the code didn't handle the required
targets MULTIPLE and STRING.

The attached patch implements handling of those targets -- in
particular I'd like you to admire what's necessary to support MULTIPLE
-- but in the process of doing that I discovered to my horror that CLX
request functions are mutually non-re-entrant.

This means that code of the form
  (change-property requestor property target '(:targets :string)
                   :transform (lambda (x) (intern-atom *display* x)))
is broken, because if the intern-atom call needs to make a server
request, it will scribble over the buffer that change-property is
building to send to the server.

This was a nasty surprise to me; I thought others might wish to be
advised of this before it bit them too.  Is this documented anywhere?


--=-=-=
Content-Disposition: attachment; filename=clipboard.diff
Content-Description: fix clipboard demo

Index: demo/clipboard.lisp
===================================================================
RCS file: /usr/local/src/cvs/clx/demo/clipboard.lisp,v
retrieving revision 1.2
diff -u -r1.2 clipboard.lisp
--- demo/clipboard.lisp	29 Nov 2004 16:14:05 -0000	1.2
+++ demo/clipboard.lisp	11 Mar 2005 17:22:04 -0000
@@ -26,6 +26,8 @@
 ;;;   with an empty event mask ("2.2 Responsibilities of the Selection
 ;;;   Owner").
 ;;;
+;;; * implemented the ICCCM-required TIMESTAMP and MULTIPLE targets
+;;;
 ;;; 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
@@ -111,22 +113,56 @@
     (delete-property *window* property)))
 
 (defun send-copy (selection target property requestor time)
-  (case target
-    ((: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)
-              :selection selection :target target :property property :time time
-              :event-window requestor :window requestor))
+  (flet ((send (target property)
+	   (case target
+	     ((:string)
+	      (format t "~&> sending text data~%") (finish-output)
+	      (change-property requestor property
+			       "Hello, World (from the CLX clipboard)!"
+			       target 8
+			       :transform #'char-code)
+	      property)
+	     (:targets
+	      (format t "~&> sending targets list~%") (finish-output)
+	      ;; ARGH.  Can't use :TRANSFORM as we scribble over CLX's buffer.
+	      (let ((targets
+		     (mapcar (lambda (x) (intern-atom *display* x))
+			     '(:targets :timestamp :multiple :string))))
+		(change-property requestor property targets target 32))
+	      property)
+	     (:timestamp
+	      (format t "~&> sending timestamp~%") (finish-output)
+	      (change-property requestor property (list *time*) target 32)
+	      property)
+	     (t
+	      (format t "~&> sending none~%") (finish-output)
+	      nil))))
+    (case target
+      ;; WARNING: this is untested.  I don't know of any clients which
+      ;; use the :MULTIPLE target.
+      (:multiple
+       (let* ((list (get-property requestor property))
+	      (plist (mapcar (lambda (x) (atom-name *display* x)) list)))
+	 (loop for (ptarget pproperty) on plist by #'cddr
+	    with all-succeeded = t
+	    if (send ptarget pproperty)
+	    collect ptarget into result
+	    and collect pproperty into result
+	    else
+	    collect nil into result
+	    and collect pproperty into result
+	    and do (setf all-succeeded nil)
+	    finally (unless all-succeeded
+		      (let ((new-list
+			     (mapcar (lambda (x) (intern-atom *display* x))
+				     result)))
+			(change-property requestor property new-list
+					 target 32))))))
+      (t (setf property (send target property))))
+    (send-event requestor :selection-notify (make-event-mask)
+		:selection selection :target target
+		:property property :time time
+		:event-window requestor :window requestor)))
 
 (defun main ()
   (let* ((*display* (open-default-display))

--=-=-=


Cheers,

Christophe

--=-=-=--

_______________________________________________
Portable-clx mailing list
[email protected]
http://lists.metacircles.com/cgi-bin/mailman/listinfo/portable-clx
cvs -d :ext:cvs.telent.net:/usr/local/src/cvs co clx  # over ssh
cvs -d :pserver:[email protected]:/cvs co clx  # anonymous