[PATCH] Request for Comments BIG-REQUESTS
Richard M Kreuter <[email protected]> Sun, 08 Jan 2006 12:21:46 -0500
| Newsgroups | gmane.lisp.clx.devel |
|---|---|
| Message-ID | <[email protected]> |
Howdy,
I've been working on adding support for BIG-REQUESTS to portable-clx. A
lightly tested patch follows. It's not ready for prime-time; I'm
looking for feedback from folks in the know. Here are some notes; more
asterisks means more important:
** The main change is to the macroexpansion of with-buffer-request. A
macroexpand-all on the body of DRAW-LINES should explain it all.
It's possible that a more thorough approach is necessary to
accomodate with-buffer-request-and-reply.
** It's not clear to me how to add an extension that needs to get
defined before input.lisp gets compiled (input.lisp defines
define-extension and extension-opcode). At present I have a handful
of BIG-REQUESTS operators in a separate file, and compile clx twice
to get things going. What's the right way to do this?
*** A client may only use extended-length encoding if the server
supports it (i.e., at run-time), and CLX distributes responsibility
for calculating request buffer layouts and request length
calculations all over the place (in the accessor definitions, in the
with-buffer-request* macros, in the definitions of some functions).
So I've added a field to the buffer structure that's meant to
dynamically indicate whether the request currently being encoded is
using the extended-length encoding. IIUC, this is unlikely to be
thread-safe. Is there a better way to do this?
** The BIG-REQUESTS specification doesn't say much about what clients
ought to do in case the server doesn't support the extension. At
present I'm having CLX do exactly as it already does in this case,
which unfortunately leads to inconsistent results (sometimes the X
server errors, sometimes Lisp receives a sigpipe). What's the
desired behavior?
* This patch probably needs some type declarations in various places.
* There's some format-debugging in there that won't be present in a
final version of the patch.
* I've only modified the 11 core protocol requests that can usefully
employ the extended-length encoding.
* Probably all Xrender requests will need to be able to use the
extended-length encoding, but I haven't touched them yet.
** I've crashed Xorg a few times by sending some huge FillPoly requests,
and found that the server fails to draw some large requests
correctly. This suggests that a disquieting amount of testing
against different X servers may be necessary. If people here think
this way of implementing BIG-REQUESTS is worth working on, I may ask
the teeming masses of cl-gardeners for help.
Thanks,
kreuter
diff -x'*.fasl' -x'*~' -purN /home/kreuter/lsp/pkg/clx/clx_0.7.1/bigreq.lisp /home/kreuter/.sbcl/site/clx_0.7.1/bigreq.lisp
--- /home/kreuter/lsp/pkg/clx/clx_0.7.1/bigreq.lisp 1969-12-31 19:00:00.000000000 -0500
+++ /home/kreuter/.sbcl/site/clx_0.7.1/bigreq.lisp 2006-01-01 19:53:40.000000000 -0500
@@ -0,0 +1,93 @@
+;;; -*- Mode: Lisp; Syntax: Common-Lisp; Package: XLIB; -*-
+
+;;; bigreq.lisp: Part of the implementation of the BIG-REQUESTS
+;;; extension for CLX.
+;;; (c) copyright 2005 Richard M Kreuter <[email protected]>
+;;; Time-stamp: <2006-01-01 19:53:40 kreuter>
+;;;
+;;; Permission is granted to any individual or institution to use,
+;;; copy, modify, and distribute this software, provided that this
+;;; complete copyright and permission notice is maintained, intact, in
+;;; all copies and supporting documentation.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+;;;
+
+;;; The BIG-REQUESTS extension is defined by a 3-page document which
+;;; was googleable circa December 2005. You can try contacting
+;;; [email protected] for a copy if you can't find it in your
+;;; space-time coordinates.
+
+;;; Notes about compilation order: enable-big-requests is needed in
+;;; open-display, as per spec:
+
+;; | ... XOpenDisplay will determine if the extension is supported by
+;; | the server and, if it is, enable extended-length encodings.
+
+;; Unfortunately, in order to use extension-opcode (which in turn
+;; requires define-extension), both define-extension and
+;; extension-opcode need to be defined earlier than they are.
+
+(in-package :xlib)
+
+;; BIG-REQUESTS doesn't define any new events or errors. Note that
+;; define-extension must be called after initialize-extensions.
+(define-extension "BIG-REQUESTS")
+
+(defun enable-big-requests (display)
+ "Determine whether DISPLAY supports the BIG-REQUESTS extension,
+and if so:
+
+* modify DISPLAY such that (EXTENDED-MAX-REQUEST-SIZE DISPLAY)
+ returns the maximum size of a big request, and
+
+* cause that the various protocol requests (and perhaps extension
+ requests) may be encoded using the BIG-REQUEST encoding.
+"
+ (declare (type display display))
+ (let ((opcode (extension-opcode display "BIG-REQUESTS")))
+ (with-buffer-request-and-reply (display opcode nil)
+ ((data 0)) ;; bigreq opcode
+ (let ((maximum-request-length (card32-get 8)))
+ (setf (display-max-big-request-length display) maximum-request-length)
+ maximum-request-length))))
+
+
+;; The specification only calls for this one function.
+(defun extended-max-request-size (display)
+ "Returns the maximum request length (in bytes) supported by
+DISPLAY, or zero if the display doesn't support that extension."
+ (slot-value display 'max-big-request-length))
+
+
+;; It violates the buffer abstraction to ask the buffer whether /it/
+;; supports BIG-REQUESTS, but (1) at present, the buffer object
+;; happens to be the display object, (2) the places in the code where
+;; buffer layout needs to vary depending on whether BIG-REQUESTS is
+;; available don't technically involve the display object. If buffers
+;; and displays are decoupled, this will have to change.
+(defmacro buffer-supports-big-requests-p (buffer)
+ `(if (zerop (the card32 (extended-max-request-size ,buffer)))
+ nil
+ t))
+
+
+;; Accessors of type progn have to handle request length and component
+;; offset themselves. These macros are only likely to work within a
+;; with-buffer-request body.
+(defmacro put-request-length (buffer body)
+ `(if (buffer-encode-with-extended-length ,buffer)
+ (progn
+ (format *debug-io* "Encoding with extended-length!~%")
+ (card16-put 2 0)
+ (card32-put 4 (index+ ,body 4))) ; Extended-length encoding
+ ; adds 1 word to the total
+ ; request size.
+ (card16-put 2 (index+ ,body 3)))) ; Ordinary encoding.
+
+(defmacro dynamic-offset (buffer offset)
+ `(if (buffer-encode-with-extended-length ,buffer)
+ (index+ ,offset 4)
+ ,offset))
diff -x'*.fasl' -x'*~' -purN /home/kreuter/lsp/pkg/clx/clx_0.7.1/clx.asd /home/kreuter/.sbcl/site/clx_0.7.1/clx.asd
--- /home/kreuter/lsp/pkg/clx/clx_0.7.1/clx.asd 2005-08-24 16:31:06.000000000 -0400
+++ /home/kreuter/.sbcl/site/clx_0.7.1/clx.asd 2005-12-31 12:04:26.000000000 -0500
@@ -50,6 +50,7 @@
(:file "display")
(:file "gcontext")
(:file "input")
+ (:file "bigreq")
(:file "requests")
(:file "fonts")
(:file "graphics")
diff -x'*.fasl' -x'*~' -purN /home/kreuter/lsp/pkg/clx/clx_0.7.1/clx.lisp /home/kreuter/.sbcl/site/clx_0.7.1/clx.lisp
--- /home/kreuter/lsp/pkg/clx/clx_0.7.1/clx.lisp 2005-07-14 09:24:45.000000000 -0400
+++ /home/kreuter/.sbcl/site/clx_0.7.1/clx.lisp 2005-12-27 20:50:26.000000000 -0500
@@ -293,6 +293,7 @@
#-clx-little-endian :msbfirst)
(release-number 0 :type card32) ; release of the server
(max-request-length 0 :type card16) ; maximum number 32 bit words in request
+ (max-big-request-length 0 :type card32) ; maximum request length if BigRequest
(default-screen) ; default screen for operations
(roots nil :type list) ; List of screens
(motion-buffer-size 0 :type card32) ; size of motion buffer
diff -x'*.fasl' -x'*~' -purN /home/kreuter/lsp/pkg/clx/clx_0.7.1/depdefs.lisp /home/kreuter/.sbcl/site/clx_0.7.1/depdefs.lisp
--- /home/kreuter/lsp/pkg/clx/clx_0.7.1/depdefs.lisp 2005-07-14 09:24:44.000000000 -0400
+++ /home/kreuter/.sbcl/site/clx_0.7.1/depdefs.lisp 2005-12-31 11:56:13.000000000 -0500
@@ -588,6 +588,10 @@ used, since NIL is the empty list.")
(listen-function 'buffer-listen-default)
#+Genera (debug-io nil :type (or null stream))
+
+ ;; Dynamically set to t if the request currently using the
+ ;; buffer wants extended length encoding, for BIG-REQUEST.
+ (encode-with-extended-length nil :type (or null t))
)
;;-----------------------------------------------------------------------------
diff -x'*.fasl' -x'*~' -purN /home/kreuter/lsp/pkg/clx/clx_0.7.1/display.lisp /home/kreuter/.sbcl/site/clx_0.7.1/display.lisp
--- /home/kreuter/lsp/pkg/clx/clx_0.7.1/display.lisp 2005-07-14 09:24:45.000000000 -0400
+++ /home/kreuter/.sbcl/site/clx_0.7.1/display.lisp 2006-01-02 22:02:39.000000000 -0500
@@ -378,8 +378,11 @@ gethostname(3) - is used instead."
(initialize-resource-allocator disp)
(initialize-predefined-atoms disp)
(initialize-extensions disp)
+ ;; Try setting up BIG-REQUESTS.
+ (enable-big-requests disp)
(setq ok-p t))
(unless ok-p (close-display disp :abort t)))
+ ;;
disp))
(defun display-force-output (display)
diff -x'*.fasl' -x'*~' -purN /home/kreuter/lsp/pkg/clx/clx_0.7.1/gcontext.lisp /home/kreuter/.sbcl/site/clx_0.7.1/gcontext.lisp
--- /home/kreuter/lsp/pkg/clx/clx_0.7.1/gcontext.lisp 2005-07-14 09:24:45.000000000 -0400
+++ /home/kreuter/.sbcl/site/clx_0.7.1/gcontext.lisp 2006-01-01 19:02:18.000000000 -0500
@@ -453,7 +453,8 @@
(unless (equalp local-clip server-clip)
(setf (gcontext-internal-clip server-state) nil)
(unless (null local-clip)
- (with-buffer-request (display +x-setcliprectangles+)
+ (with-buffer-request (display +x-setcliprectangles+
+ :try-big-request t)
(data (first local-clip))
(gcontext gcontext)
;; XXX treat nil correctly
diff -x'*.fasl' -x'*~' -purN /home/kreuter/lsp/pkg/clx/clx_0.7.1/graphics.lisp /home/kreuter/.sbcl/site/clx_0.7.1/graphics.lisp
--- /home/kreuter/lsp/pkg/clx/clx_0.7.1/graphics.lisp 2005-07-14 09:24:44.000000000 -0400
+++ /home/kreuter/.sbcl/site/clx_0.7.1/graphics.lisp 2006-01-02 22:03:03.000000000 -0500
@@ -78,7 +78,9 @@
(type gcontext gcontext)
(type sequence points) ;(repeat-seq (integer x) (integer y))
(type generalized-boolean relative-p))
- (with-buffer-request ((drawable-display drawable) +x-polypoint+ :gc-force gcontext)
+ (with-buffer-request ((drawable-display drawable) +x-polypoint+
+ :gc-force gcontext
+ :try-big-request t)
((data boolean) relative-p)
(drawable drawable)
(gcontext gcontext)
@@ -144,11 +146,13 @@
(type (member :complex :non-convex :convex) shape))
(if fill-p
(fill-polygon drawable gcontext points relative-p shape)
- (with-buffer-request ((drawable-display drawable) +x-polyline+ :gc-force gcontext)
- ((data boolean) relative-p)
- (drawable drawable)
- (gcontext gcontext)
- ((sequence :format int16) points))))
+ (with-buffer-request ((drawable-display drawable) +x-polyline+
+ :gc-force gcontext
+ :try-big-request (> (length points) #.(ash 1 15)))
+ ((data boolean) relative-p)
+ (drawable drawable)
+ (gcontext gcontext)
+ ((sequence :format int16) points))))
;; Internal function called from DRAW-LINES
(defun fill-polygon (drawable gcontext points relative-p shape)
@@ -158,7 +162,9 @@
(type sequence points) ;(repeat-seq (integer x) (integer y))
(type generalized-boolean relative-p)
(type (member :complex :non-convex :convex) shape))
- (with-buffer-request ((drawable-display drawable) +x-fillpoly+ :gc-force gcontext)
+ (with-buffer-request ((drawable-display drawable) +x-fillpoly+
+ :gc-force gcontext
+ :try-big-request t)
(drawable drawable)
(gcontext gcontext)
((member8 :complex :non-convex :convex) shape)
@@ -170,7 +176,9 @@
(type gcontext gcontext)
;; (repeat-seq (integer x1) (integer y1) (integer x2) (integer y2)))
(type sequence segments))
- (with-buffer-request ((drawable-display drawable) +x-polysegment+ :gc-force gcontext)
+ (with-buffer-request ((drawable-display drawable) +x-polysegment+
+ :gc-force gcontext
+ :try-big-request t)
(drawable drawable)
(gcontext gcontext)
((sequence :format int16) segments)))
@@ -237,7 +245,8 @@
(type generalized-boolean fill-p))
(with-buffer-request ((drawable-display drawable)
(if fill-p +x-polyfillrectangle+ +x-polyrectangle+)
- :gc-force gcontext)
+ :gc-force gcontext
+ :try-big-request t)
(drawable drawable)
(gcontext gcontext)
((sequence :format int16) rectangles)))
@@ -305,15 +314,18 @@
(type list arcs)
(type generalized-boolean fill-p))
(let* ((display (drawable-display drawable))
- (limit (index- (buffer-size display) 12))
+ (limit (index- (buffer-size display) (+ 12 4))) ; XXX FIXME: move to runtime, bigreq
(length (length arcs))
(request (if fill-p +x-polyfillarc+ +x-polyarc+)))
- (with-buffer-request ((drawable-display drawable) request :gc-force gcontext)
+ (with-buffer-request ((drawable-display drawable) request
+ :gc-force gcontext
+ :try-big-request t)
(drawable drawable)
(gcontext gcontext)
(progn
- (card16-put 2 (index+ (index-ash length -1) 3)) ; Set request length (in words)
- (set-buffer-offset (index+ buffer-boffset 12)) ; Position to start of data
+ (put-request-length display (index-ash length -1))
+ (set-buffer-offset (index+ buffer-boffset
+ (dynamic-offset display 12))) ; Position to start of data
(do ((arc arcs))
((endp arc)
(setf (buffer-boffset display) buffer-boffset))
@@ -328,23 +340,29 @@
(card16-put 6 (pop arc))
(angle-put 8 (pop arc))
(angle-put 10 (pop arc))
- (set-buffer-offset (index+ buffer-boffset 12)))))))
+ (set-buffer-offset (index+ buffer-boffset
+ (dynamic-offset display 12))))))))
(defun draw-arcs-vector (drawable gcontext arcs &optional fill-p)
(declare (type drawable drawable)
(type gcontext gcontext)
- (type vector arcs)
+ (type vector arcs) ; this is a vector of int16/card16
(type generalized-boolean fill-p))
(let* ((display (drawable-display drawable))
- (limit (index- (buffer-size display) 12))
+ (limit (index- (buffer-size display) (+ 12 4))) ; XXX FIXME: move to runtime, bigreq
(length (length arcs))
(request (if fill-p +x-polyfillarc+ +x-polyarc+)))
- (with-buffer-request ((drawable-display drawable) request :gc-force gcontext)
+ (with-buffer-request ((drawable-display drawable) request
+ :gc-force gcontext
+ :try-big-request t)
(drawable drawable)
(gcontext gcontext)
(progn
- (card16-put 2 (index+ (index-ash length -1) 3)) ; Set request length (in words)
- (set-buffer-offset (index+ buffer-boffset 12)) ; Position to start of data
+ ;; Set the request length.
+ (put-request-length display (index-ash length -1))
+ ;; Position to start of data
+ (set-buffer-offset (index+ buffer-boffset
+ (dynamic-offset display 12)))
(do ((n 0 (index+ n 6))
(length (length arcs)))
((index>= n length)
@@ -360,7 +378,8 @@
(card16-put 6 (aref arcs (index+ n 3)))
(angle-put 8 (aref arcs (index+ n 4)))
(angle-put 10 (aref arcs (index+ n 5)))
- (set-buffer-offset (index+ buffer-boffset 12)))))))
+ (set-buffer-offset (index+ buffer-boffset
+ (dynamic-offset display 12))))))))
(defun draw-arcs (drawable gcontext arcs &optional fill-p)
(declare (type drawable drawable)
@@ -397,7 +416,9 @@
(type int16 x y) ;; required
(type card16 width height) ;; required
(type (member :bitmap :xy-pixmap :z-pixmap) format))
- (with-buffer-request ((drawable-display drawable) +x-putimage+ :gc-force gcontext)
+ (with-buffer-request ((drawable-display drawable) +x-putimage+
+ :gc-force gcontext
+ :try-big-request t)
((data (member :bitmap :xy-pixmap :z-pixmap)) format)
(drawable drawable)
(gcontext gcontext)
diff -x'*.fasl' -x'*~' -purN /home/kreuter/lsp/pkg/clx/clx_0.7.1/macros.lisp /home/kreuter/.sbcl/site/clx_0.7.1/macros.lisp
--- /home/kreuter/lsp/pkg/clx/clx_0.7.1/macros.lisp 2005-07-14 09:24:44.000000000 -0400
+++ /home/kreuter/.sbcl/site/clx_0.7.1/macros.lisp 2006-01-02 22:04:20.000000000 -0500
@@ -343,7 +343,15 @@
`(read-bitvector256 buffer-bbuf ,real-index ,data))
((index map &optional (real-index index) (buffer '%buffer))
`(write-bitvector256 ,buffer (index+ buffer-boffset ,real-index) ,map)))
-
+
+(define-accessor extended-length (32)
+ ((index)
+ (declare (ignore index))
+ (error "It is an error to try to get an extended-length component."))
+ ((index value)
+ (declare (ignore index value))
+ nil))
+
(define-accessor string (nil)
((length index &key reply-buffer)
`(read-sequence-char
@@ -378,6 +386,8 @@
(let* ((real-end (if appending (or end `(length ,data)) (gensym)))
(writer (xintern 'write-sequence- format))
(form `(,writer ,buffer (index+ buffer-boffset ,(lround index))
+ ,data ,start ,real-end ,transform))
+ (ext-form `(,writer ,buffer (index+ buffer-boffset ,(lround (1+ index)))
,data ,start ,real-end ,transform)))
(flet ((maker (size)
(if appending
@@ -386,8 +396,17 @@
(unless (= size 1)
(setq idx `(index-ceiling ,idx ,size)))
`(let ((,real-end ,(or end `(length ,data))))
- (write-card16 2 (index+ ,idx ,(index-ceiling index 4)))
- ,form)))))
+ (if (buffer-encode-with-extended-length ,buffer)
+ ;; We're doing an extended-length encoding.
+ (progn
+ (write-card16 2 0)
+ (write-card32 4 (index+ ,idx ,(index-ceiling (1+ index) 4)))
+; (format *debug-io* "opcode: ~A~%reglen: ~A~%biglen: ~A~%"
+; (read-card8 0) (read-card16 2) (read-card32 4))
+ ,ext-form)
+ (progn
+ (write-card16 2 (index+ ,idx ,(index-ceiling index 4)))
+ ,form)))))))
(ecase format
((card8 int8)
(maker 4))
@@ -684,21 +703,66 @@
(pushnew (* increment 8) sizes)))))))))
(defmacro with-buffer-request-internal
- ((buffer opcode &key length sizes &allow-other-keys)
- &body type-args)
- (multiple-value-bind (code index item-sizes)
- (get-put-items 4 type-args t)
- (let ((length (if length `(index+ ,length +requestsize+) '+requestsize+))
- (sizes (remove-duplicates (append '(8 16) item-sizes sizes))))
- `(with-buffer-output (,buffer :length ,length :sizes ,sizes)
- (setf (buffer-last-request ,buffer) buffer-boffset)
- (write-card8 0 ,opcode) ;; Stick in the opcode
- ,@code
- ,@(when index
- (setq index (lround index))
- `((write-card16 2 ,(ceiling index 4))
- (setf (buffer-boffset ,buffer) (index+ buffer-boffset ,index))))
- (buffer-new-request-number ,buffer)))))
+ ((buffer opcode &key length sizes try-big-request &allow-other-keys)
+ &body type-args)
+ (let ((standard-request
+ (multiple-value-bind (code index item-sizes)
+ (get-put-items 4 type-args t)
+ (let ((length (if length `(index+ ,length +requestsize+) '+requestsize+))
+ (sizes (remove-duplicates (append '(8 16) item-sizes sizes))))
+ `(with-buffer-output (,buffer :length ,length :sizes ,sizes)
+ (setf (buffer-last-request ,buffer) buffer-boffset)
+ (write-card8 0 ,opcode) ;; Stick in the opcode
+ ,@code
+ ,@(when index
+ (setq index (lround index))
+ `((write-card16 2 ,(ceiling index 4))
+ (setf (buffer-boffset ,buffer)
+ (index+ buffer-boffset ,index))))
+ (buffer-new-request-number ,buffer)))))
+ (extended-request
+ (let ((extended-type-args (if (eq (if (consp (caar type-args))
+ (caaar type-args)
+ (caar type-args))
+ 'data)
+ (append (list (first type-args)
+ '(extended-length nil))
+ (rest type-args))
+ (append (list '(extended-length nil))
+ type-args))))
+ (multiple-value-bind (code index item-sizes)
+ (get-put-items 4 extended-type-args t)
+ (let ((length (if length `(index+ ,length +requestsize+) '+requestsize+))
+ (sizes (remove-duplicates (append '(8 16) item-sizes sizes))))
+ `(with-buffer-output (,buffer :length ,length :sizes ,sizes)
+ (setf (buffer-last-request ,buffer) buffer-boffset)
+ (write-card8 0 ,opcode) ;; Stick in the opcode
+ ,@code
+ ,@(when index
+ (setq index (lround index))
+ `((write-card16 2 0)
+ (write-card32 4 ,(ceiling index 4))
+ (setf (buffer-boffset ,buffer)
+ (index+ buffer-boffset ,index))))
+ (buffer-new-request-number ,buffer)))))))
+ (if try-big-request
+ ;; We're compiling a request that will attempt extended-length
+ ;; encoding.
+ `(progn
+ (if ,(if (consp try-big-request)
+ `(and (buffer-supports-big-requests-p ,buffer)
+ ,try-big-request)
+ `(buffer-supports-big-requests-p ,buffer))
+ (unwind-protect
+ (progn
+ (setf (buffer-encode-with-extended-length ,buffer) t)
+ ,extended-request)
+ (setf (buffer-encode-with-extended-length ,buffer) nil))
+ ,standard-request))
+ ;; We're compiling something that doesn't want to try
+ ;; extended-length encoding.
+ standard-request)))
+
(defmacro with-buffer-request
((buffer opcode &rest options &key inline gc-force &allow-other-keys)
diff -x'*.fasl' -x'*~' -purN /home/kreuter/lsp/pkg/clx/clx_0.7.1/requests.lisp /home/kreuter/.sbcl/site/clx_0.7.1/requests.lisp
--- /home/kreuter/lsp/pkg/clx/clx_0.7.1/requests.lisp 2005-07-14 09:24:44.000000000 -0400
+++ /home/kreuter/.sbcl/site/clx_0.7.1/requests.lisp 2006-01-02 22:03:48.000000000 -0500
@@ -290,13 +290,14 @@
(declare (type display display)
(type array-index length)
(type resource-id property-id type-id))
- (with-buffer-request (display +x-changeproperty+)
+ (with-buffer-request (display +x-changeproperty+ :try-big-request t)
((data (member :replace :prepend :append)) mode)
(window window)
(resource-id property-id type-id)
(card8 format)
(card32 length)
(progn
+; (format *debug-io* "format: ~A~%length: ~A~%" format length)
(ecase format
(8 (sequence-put 24 data :format card8
:start start :end end :transform transform))
_______________________________________________
Portable-clx mailing list
[email protected]
http://lists.metacircles.com/cgi-bin/mailman/listinfo/portable-clx
See http://www.cliki.net/clx for darcs URL(s)